Tensorflow中的平衡精度得分

时间:2019-12-14 21:59:49

标签: tensorflow machine-learning neural-network conv-neural-network metrics

我正在为高度不平衡的分类问题实现CNN,并且我想在张量流中实现定制指标以使用Select Best Model回调。 具体来说,我想实现平衡的准确性评分,即每个类别的召回总和(请参阅sklearn实现here),有人知道该怎么做吗?

我在网上找不到任何实现,而且我不习惯使用张量...

非常感谢

Matteo

4 个答案:

答案 0 :(得分:4)

我遇到了同样的问题,因此我基于SparseCategoricalAccuracy实现了一个自定义类:

class BalancedSparseCategoricalAccuracy(keras.metrics.SparseCategoricalAccuracy):
    def __init__(self, name='balanced_sparse_categorical_accuracy', dtype=None):
        super().__init__(name, dtype=dtype)

    def update_state(self, y_true, y_pred, sample_weight=None):
        y_flat = y_true
        if y_true.shape.ndims == y_pred.shape.ndims:
            y_flat = tf.squeeze(y_flat, axis=[-1])
        y_true_int = tf.cast(y_flat, tf.int32)

        cls_counts = tf.math.bincount(y_true_int)
        cls_counts = tf.math.reciprocal_no_nan(tf.cast(cls_counts, self.dtype))
        weight = tf.gather(cls_counts, y_true_int)
        return super().update_state(y_true, y_pred, sample_weight=weight)

想法是将每个班级的权重设置成与其体重成反比。

此代码会从Autograph产生一些警告,但我相信这些是Autograph的错误,并且该指标似乎可以正常工作。

答案 1 :(得分:1)

我尚未测试此代码,但查看tensorflow==2.1.0中的source code,这可能适用于二进制分类情况:

from tensorflow.keras.metrics import Recall
from tensorflow.python.ops import math_ops


class BalancedBinaryAccuracy(Recall):
    def result(self):
        result = (math_ops.div_no_nan(self.true_positives, self.true_positives + self.false_negatives) +
                  math_ops.div_no_nan(self.true_negatives, self.true_negatives + self.false_positives)) / 2
        return result[0] if len(self.thresholds) == 1 else result

答案 2 :(得分:1)

我不是Tensorflow方面的专家,但是我在tf源代码中的指标实现之间使用了一些模式匹配

from tensorflow.python.keras import backend as K
from tensorflow.python.keras.metrics import Metric
from tensorflow.python.keras.utils import metrics_utils
from tensorflow.python.ops import init_ops
from tensorflow.python.ops import math_ops
from tensorflow.python.keras.utils.generic_utils import to_list

class BACC(Metric):

    def __init__(self, thresholds=None, top_k=None, class_id=None, name=None, dtype=None):
        super(BACC, self).__init__(name=name, dtype=dtype)
        self.init_thresholds = thresholds
        self.top_k = top_k
        self.class_id = class_id

        default_threshold = 0.5 if top_k is None else metrics_utils.NEG_INF
        self.thresholds = metrics_utils.parse_init_thresholds(
            thresholds, default_threshold=default_threshold)
        self.true_positives = self.add_weight(
            'true_positives',
            shape=(len(self.thresholds),),
            initializer=init_ops.zeros_initializer)
        self.true_negatives = self.add_weight(
            'true_negatives',
            shape=(len(self.thresholds),),
            initializer=init_ops.zeros_initializer)
        self.false_positives = self.add_weight(
            'false_positives',
            shape=(len(self.thresholds),),
            initializer=init_ops.zeros_initializer)
        self.false_negatives = self.add_weight(
            'false_negatives',
            shape=(len(self.thresholds),),
            initializer=init_ops.zeros_initializer)

    def update_state(self, y_true, y_pred, sample_weight=None):

        return metrics_utils.update_confusion_matrix_variables(
            {
                metrics_utils.ConfusionMatrix.TRUE_POSITIVES: self.true_positives,
                metrics_utils.ConfusionMatrix.TRUE_NEGATIVES: self.true_negatives,
                metrics_utils.ConfusionMatrix.FALSE_POSITIVES: self.false_positives,
                metrics_utils.ConfusionMatrix.FALSE_NEGATIVES: self.false_negatives,
            },
            y_true,
            y_pred,
            thresholds=self.thresholds,
            top_k=self.top_k,
            class_id=self.class_id,
            sample_weight=sample_weight)

    def result(self):
        """
        Returns the Balanced Accuracy (average between recall and specificity)
        """
        result = (math_ops.div_no_nan(self.true_positives, self.true_positives + self.false_negatives) +
                  math_ops.div_no_nan(self.true_negatives, self.true_negatives + self.false_positives)) / 2
        
        return result

    def reset_states(self):
        num_thresholds = len(to_list(self.thresholds))
        K.batch_set_value(
            [(v, np.zeros((num_thresholds,))) for v in self.variables])

    def get_config(self):
        config = {
            'thresholds': self.init_thresholds,
            'top_k': self.top_k,
            'class_id': self.class_id
        }
        base_config = super(BACC, self).get_config()
        return dict(list(base_config.items()) + list(config.items()))

我只是将源代码中的Recall类实现作为模板,并对其进行了扩展以确保定义了TP,TN,FP和FN。

此后,我修改了result方法,以便计算出平衡的准确度,瞧瞧:)

我将由此得到的结果与sklearn的平衡准确度得分和匹配的值进行了比较,因此我认为这是正确的,但请仔细检查以防万一。

答案 3 :(得分:0)

我可以通过3种方式来解决这种情况:-

1)随机欠采样-在这种方法中,您可以从多数类中随机删除采样。

2)随机过采样-通过这种方法,您可以通过复制样本来增加样本。

3)加权交叉熵-您也可以使用加权交叉熵,以便可以补偿少数类的损耗值。 See here

我亲自尝试过method2,它确实使我的准确度提高了可观的价值,但是它可能因数据集而异