Keras自定义多分类指标编写疑问:张量处理异常解析
Keras自定义多分类F1指标问题解析
问题背景
我想在Keras中编写自定义多分类指标,尝试时遇到了问题。核心原因是不熟悉训练时的张量运作(Graph模式),但我能用NumPy或Python列表生成混淆矩阵并计算F1分数。
我使用的函数
def f1_scores(y_true,y_pred): y_true = K.print_tensor(y_true, message='y_true = ') y_pred = K.print_tensor(y_pred, message='y_pred = ') print(f"y_true_shape:{K.int_shape(y_true)}") print(f"y_pred_shape:{K.int_shape(y_pred)}") y_true_f = K.flatten(y_true) y_pred_f = K.flatten(y_pred) gt = K.argmax(y_true_f) pred = K.argmax(y_pred_f) print(f"pred_print:{pred}") print(f"gt_print:{gt}") pred = K.print_tensor(pred, message='pred= ') gt = K.print_tensor(gt, message='gt =') print(f"pred_shape:{K.int_shape(pred)}") print(f"gt_shape:{K.int_shape(gt)}") pred_f = K.flatten(pred) gt_f = K.flatten(gt) pred_f = K.print_tensor(pred_f, message='pred_f= ') gt_f = K.print_tensor(gt_f, message='gt_f =') print(f"pred_f_shape:{K.int_shape(pred_f)}") print(f"gt_f_shape:{K.int_shape(gt_f)}") conf_mat = tf.math.confusion_matrix(y_true_f,y_pred_f, num_classes = 14) """ add codes to find F1 score for each class """ # return an arbitrary number, as F1 scores not found yet. return 1
输出情况
第1轮epoch初始输出
y_true_shape:(None, 256, 256, 14) y_pred_shape:(None, 256, 256, 14) pred_print:Tensor("ArgMax_1:0", shape=(), dtype=int64) gt_print:Tensor("ArgMax:0", shape=(), dtype=int64) pred_shape:() gt_shape:() pred_f_shape:(1,) gt_f_shape:(1,)
后续epoch输出示例
y_true = [[[[1 0 0 ... 0 0 0] [1 0 0 ... 0 0 0] [1 0 0 ... 0 0 0] ... y_pred = [[[[0.0889623 0.0624801107 0.0729747042 ... 0.0816219151 0.0735477135 0.0698677748] [0.0857798532 0.0721047595 0.0754121244 ... 0.0723947287 0.0728530064 0.0676521733] [0.0825942457 0.0670698211 0.0879610255 ... 0.0721599609 0.0845924541 0.0638583601] ... pred= 1283828 gt = 0 pred_f= [1283828] gt_f = [0]
我的疑问
- 为什么pred是单个数字而非代表类别索引的列表?pred_f为什么也是仅含一个数字的列表?
- gt及gt_f的值为什么是0?我预期它们应该是类别索引的列表。
问题根源与解决方法
核心错误分析
错误的扁平化操作:
你的输入形状是(None, 256, 256, 14),对应图像分割任务的输出格式(批次大小、图像高、图像宽、类别数)。直接用K.flatten()把整个张量压成一维,会把每个像素的14个类别概率摊平成一个长向量,完全破坏了“每个像素对应一个类别分布”的结构。K.argmax()轴参数缺失:
默认情况下K.argmax()会在整个张量的所有轴上取最大值的索引,而非在类别轴(axis=3)上。这导致你得到的不是每个像素的类别索引,而是整个扁平化张量中最大值的位置(1283828就是这个位置);而gt的值为0,是因为y_true的第一个元素是1,是扁平化张量里最大值的位置。
修正后的代码
import tensorflow as tf from tensorflow.keras import backend as K def f1_scores(y_true, y_pred): # 在类别轴(axis=3)上获取每个像素的真实类别和预测类别索引 y_true_idx = K.argmax(y_true, axis=3) y_pred_idx = K.argmax(y_pred, axis=3) # 扁平化索引张量,得到一维的类别索引列表 y_true_flat = K.flatten(y_true_idx) y_pred_flat = K.flatten(y_pred_idx) # 计算混淆矩阵 conf_mat = tf.math.confusion_matrix(y_true_flat, y_pred_flat, num_classes=14) # 计算每个类别的Precision、Recall与F1,加入epsilon避免除以0 epsilon = K.epsilon() true_positives = K.diag(conf_mat) predicted_positives = K.sum(conf_mat, axis=0) actual_positives = K.sum(conf_mat, axis=1) precision = true_positives / (predicted_positives + epsilon) recall = true_positives / (actual_positives + epsilon) f1 = 2 * (precision * recall) / (precision + recall + epsilon) # 返回平均F1,若需每个类别的F1可直接return f1(需适配Keras多输出指标逻辑) return K.mean(f1)
补充说明
- Graph模式下,所有操作都是张量运算,
print()只能打印张量的形状信息,K.print_tensor()可在运行时打印张量内容,但正式训练时建议移除,避免拖慢速度。 - 若要返回每个类别的F1分数,可将
return K.mean(f1)改为return f1,但Keras默认仅支持标量指标,这种情况需要自定义tf.keras.metrics.Metric类来实现。
内容的提问来源于stack exchange,提问作者vuict
相关产品推荐
相关产品推荐

