TensorFlow 2.0 Keras自定义Hamming Loss指标打印值与返回值不匹配
我正在开发一个输出多种疾病标签的深度学习模型,输入为文本(医生病历)和标量临床数据,属于稀疏多标签分类任务。由于朴素准确率测试(生成与真实标签同形状的零矩阵)准确率约92%,因此选用Hamming Loss(错误标签数/总标签数)作为评估指标。
为验证自定义Hamming Loss函数正确性,我在训练中使用tf.print输出计算值,但设置batch_size=1时,手动打印的Hamming Loss值与函数返回的训练显示值不匹配。以下是我的代码及训练输出:
自定义Hamming Loss函数
def hamming_loss(y, y_hat, thresh=0.8, use_thresh=True, mode='multilabel'): if use_thresh is False: threshold = tf.reduce_max(y_hat, axis=-1, keepdims=True) # make sure [0, 0, 0] doesn't become [1, 1, 1] # Use abs(x) > eps, instead of x != 0 to check for zero y_pred = tf.logical_and(y_hat >= threshold, tf.abs(y_hat) > 1e-12) else: y_pred = y_hat > thresh y_true = tf.cast(y, tf.int32) y_pred = tf.cast(y_pred, tf.int32) shape = tf.cast(tf.shape(y)[-1], tf.int32) #tf.print(y, summarize = -1) #tf.print('\n') #tf.print(y_pred, summarize = -1) #tf.print(shape) #tf.print(y_hat, summarize = -1) if mode == 'multiclass': nonzero = tf.cast( tf.math.count_nonzero(y_true * y_pred, axis=-1), tf.float32) return 1.0 - nonzero else: nonzero = tf.cast( tf.math.count_nonzero(y_true - y_pred, axis=-1), tf.int32) #tf.print(nonzero, summarize=-1) tf.print(nonzero/shape, summarize=-1) return nonzero/shape
网络结构
nlp_input = Input(shape=(1024,), name='nlp_input') lr = LeakyReLU(alpha=0.1) meta_input = Input(shape=(24,), name='meta_input') hidden_m1 = Dense(2000, activation=lr,kernel_regularizer = L1L2(l1=0.0001) )(meta_input) hidden_m2 = Dense(200, activation=lr, )(hidden_m1) hidden_m3 = Dense(100, activation=lr, )(hidden_m2) hidden_m4 = Dense(10, activation='relu', )(hidden_m3) hidden1 = Dense(5000, activation=lr,kernel_regularizer = L1L2(l1=0.0001) )(nlp_input) hidden2 = Dense(3000, activation=lr )(hidden1) hidden3 = Dense(3000, activation=lr, )(hidden2) hidden4 = Dense(768, activation=lr, )(hidden3) hidden5 = Dense(500, activation=lr, )(hidden4) hidden6 = Dense(100, activation='relu')(hidden5) conc = concatenate([hidden6, hidden_m4]) conch1 = Dense(1000, activation =lr, kernel_regularizer = L1L2(l1=0.0001))(conc) conch2 = Dense(500, activation =lr)(conch1) conch3 = Dense(500, activation =lr)(conch2) conch4 = Dense(100, activation ='relu')(conch3) acti = Dense(15, 'sigmoid')(conch4) model = Model(inputs=[nlp_input, meta_input], outputs=[acti]) opt = tf.keras.optimizers.Adam(learning_rate=1e-4) model.compile(loss=focal_loss(), optimizer=opt,metrics=[tf_prec,tf_rec, hamming_loss]) print(model.summary())
训练输出示例
Train on 1315 samples, validate on 328 samples 0.066666666666666666 1/1315 [..............................] - ETA: 12:54 - loss: 6.6111 - tf_prec: 0.2000 - tf_rec: 1.0000 - hamming_loss: 0.0667 0.066666666666666666 2/1315 [..............................] - ETA: 8:16 - loss: 6.5891 - tf_prec: 0.2000 - tf_rec: 1.0000 - hamming_loss: 0.0667 0.13333333333333333 0.13333333333333333 4/1315 [..............................] - ETA: 4:27 - loss: 6.5504 - tf_prec: 0.2500 - tf_rec: 1.0000 - hamming_loss: 0.1000 0.26666666666666666 0.2 6/1315 [..............................] - ETA: 3:10 - loss: 6.5065 - tf_prec: 0.3000 - tf_rec: 1.0000 - hamming_loss: 0.1444 0.066666666666666666 0.2 8/1315 [..............................] - ETA: 2:31 - loss: 6.4611 - tf_prec: 0.3187 - tf_rec: 1.0000 - hamming_loss: 0.1417 0.2 0.066666666666666666 10/1315 [..............................] - ETA: 2:08 - loss: 6.4265 - tf_prec: 0.2800 - tf_rec: 0.9000 - hamming_loss: 0.1400 0.066666666666666666 0.066666666666666666 12/1315 [..............................] - ETA: 1:52 - loss: 6.3848 - tf_prec: 0.2542 - tf_rec: 0.8333 - hamming_loss: 0.1278 0.066666666666666666 0.066666666666666666
为什么手动打印的Hamming Loss与函数返回的训练指标值不一致?
这是一个很典型的Keras metrics工作机制误解,我来帮你拆解原因:
核心原因:打印的是单样本Loss,训练日志显示的是累积样本平均值
你的自定义hamming_loss函数里,tf.print(nonzero/shape)打印的是每个单独样本的Hamming Loss值;而Keras在训练过程中显示的hamming_loss指标,是从训练开始到当前步骤所有已处理样本的平均值。
我们拿你的训练输出验证一下:
- 第4步(已处理4个样本),你打印了4个值:
0.0666、0.0666、0.1333、0.1333 - 计算这4个值的平均值:
(0.0666+0.0666+0.1333+0.1333)/4 = 0.4/4 = 0.1,正好对应训练日志里的hamming_loss: 0.1000
再看第6步:
- 6个样本的Loss值分别是:
0.0666、0.0666、0.1333、0.1333、0.2666、0.2 - 平均值:
(0.0666*2 + 0.1333*2 + 0.2666 + 0.2)/6 = 0.8664/6 = 0.1444,和日志里的hamming_loss: 0.1444完全匹配
额外注意点:整数除法的潜在问题
你的代码里shape被定义为tf.int32,nonzero也是tf.int32,虽然在TensorFlow中整数除以整数会自动转成浮点数,但为了避免意外(比如某些版本或场景下的整数截断),建议把shape转成tf.float32:
shape = tf.cast(tf.shape(y)[-1], tf.float32)
如何让打印值和日志显示一致?
如果你想让tf.print输出的内容和训练日志里的指标一致,可以在函数里计算当前批次的平均值,或者直接打印累积平均值(不过累积平均值需要借助Keras的metrics状态,更简单的是打印批次平均):
修改后的hamming_loss函数:
def hamming_loss(y, y_hat, thresh=0.8, use_thresh=True, mode='multilabel'): if use_thresh is False: threshold = tf.reduce_max(y_hat, axis=-1, keepdims=True) y_pred = tf.logical_and(y_hat >= threshold, tf.abs(y_hat) > 1e-12) else: y_pred = y_hat > thresh y_true = tf.cast(y, tf.int32) y_pred = tf.cast(y_pred, tf.int32) shape = tf.cast(tf.shape(y)[-1], tf.float32) # 改为float32 if mode == 'multiclass': nonzero = tf.cast( tf.math.count_nonzero(y_true * y_pred, axis=-1), tf.float32) return 1.0 - nonzero else: nonzero = tf.cast( tf.math.count_nonzero(y_true - y_pred, axis=-1), tf.float32) sample_loss = nonzero / shape # 打印当前批次的平均Loss batch_avg = tf.reduce_mean(sample_loss) tf.print("Batch avg Hamming Loss:", batch_avg, summarize=-1) return sample_loss # Keras会自动处理累积平均
如果你的batch_size=1,那么批次平均就是单样本值,但日志显示的还是累积平均;如果想让日志显示单样本值,你需要自定义一个Metric类,每次只返回当前样本的值而不累积,但这通常不是我们想要的训练指标展示方式。
内容的提问来源于stack exchange,提问作者kjans_tbme

