我正在为高度不平衡的分类问题实现 CNN,我想在张量流中实现自定义指标以使用“选择最佳模型”回调。 具体来说,我想实现平衡的准确度分数,这是每个类别的召回率的平均值(请参阅sklearn实现here),有人知道该怎么做吗?
我面临着同样的问题,所以我实现了一个基于
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)随机欠采样 - 在这种方法中,您可以从大多数类中随机删除样本。
2)随机过采样 - 在这种方法中,您可以通过复制样本来增加样本。
3)加权交叉熵 - 还可以使用加权交叉熵,这样就可以补偿少数类的损失值。 看这里
我个人尝试过方法2,它确实显着提高了我的准确性,但它可能因数据集而异
注意
看来我用作答案模板的
Recall
类的实现/API,已在较新的 TF 版本中进行了修改(正如 @guilaumme-gaudin 所指出的),所以我推荐您查看当前 TF 版本中使用的 Recall
实现,并从那里使用我在原始帖子中描述的相同方法来实现指标,这样我就不必在 TF 团队每次修改时更新我的答案其指标的实现/API。
原帖
我不是 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
的平衡准确度分数和匹配的值进行了比较,因此我认为它是正确的,但请仔细检查以防万一。
我还没有测试过这段代码,但是查看tensorflow==2.1.0
的
源代码,这可能适用于二元分类情况:
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
作为编写自定义指标的替代方法,您可以使用通过训练日志提供的已实现广告的指标来编写自定义回调。例如,您可以像这样定义训练平衡精度回调:
class TrainBalancedAccuracyCallback(tf.keras.callbacks.Callback):
def __init__(self, **kargs):
super(TrainBalancedAccuracyCallback, self).__init__(**kargs)
def on_epoch_end(self, epoch, logs={}):
train_sensitivity = logs['tp'] / (logs['tp'] + logs['fn'])
train_specificity = logs['tn'] / (logs['tn'] + logs['fp'])
logs['train_sensitivity'] = train_sensitivity
logs['train_specificity'] = train_specificity
logs['train_balacc'] = (train_sensitivity + train_specificity) / 2
print('train_balacc', logs['train_balacc'])
验证也是如此:
class ValBalancedAccuracyCallback(tf.keras.callbacks.Callback):
def __init__(self, **kargs):
super(ValBalancedAccuracyCallback, self).__init__(**kargs)
def on_epoch_end(self, epoch, logs={}):
val_sensitivity = logs['val_tp'] / (logs['val_tp'] + logs['val_fn'])
val_specificity = logs['val_tn'] / (logs['val_tn'] + logs['val_fp'])
logs['val_sensitivity'] = val_sensitivity
logs['val_specificity'] = val_specificity
logs['val_balacc'] = (val_sensitivity + val_specificity) / 2
print('val_balacc', logs['val_balacc'])
然后您可以将它们用作模型拟合方法的
callback
参数的值。
与
sklearn
实现类似,如果y_true
和y_pred
是根据类别的张量流张量:
import tensorflow as tf
def balanced_accuracy(y_true, y_pred):
C = tf.math.confusion_matrix(y_true, y_pred)
diag = tf.linalg.diag_part(C)
true_num = tf.reduce_sum(C, axis=1)
per_class = tf.math.divide_no_nan(tf.cast(diag, tf.float32), tf.cast(true_num, tf.float32))
return tf.math.reduce_mean(per_class)
当然,如果您有 one-hot 编码向量作为输入,请首先计算
tf.math.argmax(y, axis=1)
。
然后您可以将此函数作为指标传递给模型编译。