Keras训练日志val_loss与手动计算BCE损失数值不一致问题
问题描述
我有一个采用binary cross entropy(二元交叉熵)作为损失函数的CNN分类模型,模型编译相关代码如下:
optimizer_instance = Adam(learning_rate=learning_rate, decay=learning_rate / 200) model.compile(optimizer=optimizer_instance, loss='binary_crossentropy')
训练过程中我配置了最优模型保存逻辑,训练阶段保存的模型为取得最优val_loss的权重版本,相关回调配置与训练启动代码如下:
es = EarlyStopping(monitor='val_loss', mode='min', verbose=0, patience=Config.LearningParameters.Patience) modelPath = modelFileFolder + Config.LearningParameters.ModelFileName checkpoint = keras.callbacks.ModelCheckpoint(modelPath , monitor='val_loss', save_best_only=True, save_weights_only=False, verbose=1) callbacks = [checkpoint,es] history = model.fit(x=training_generator, batch_size=Config.LearningParameters.Batch_size, epochs=Config.LearningParameters.Epochs, validation_data=validation_generator, callbacks=callbacks, verbose=1)
训练日志显示val_loss最低可下降至0.41。训练结束后我加载训练过程中保存的最优模型,对验证集进行预测,随后手动计算BCE损失,得到的结果为2.335,与日志记录的val_loss存在极大差异。
手动计算损失的代码如下:
bce = tf.keras.losses.BinaryCrossentropy() binaryCSELoss = bce(y_valid, preds) print("Calculated Val Loss is: " + str(binaryCSELoss ))
训练阶段末尾日志片段如下:
10/10 [==============================] - ETA: 0s - loss: 0.0778 Epoch 40: val_loss did not improve from 0.41081 10/10 [==============================] - 4s 399ms/step - loss: 0.0778 - val_loss: 0.5413 % of marked 1 in validation: [0.51580906 0.48419094] % of marked 1 in Test: [0.51991504 0.480085 ] --------------------------------- Calculated Val Loss is: 2.3350689765791395
我最初怀疑该差异与使用数据生成器、损失逐batch单独计算的机制有关,因此补充了不使用数据生成器的对照实验,直接传入全量训练与验证数据进行训练,测试代码如下:
history = model.fit(x=trainX,y = y_train, epochs=Config.LearningParameters.Epochs, validation_data=(validateion_x,y_valid), callbacks=callbacks, verbose=1) predictions_cnn = model.predict(validateion_x) bce = tf.keras.losses.BinaryCrossentropy(from_logits=False) binaryCSELoss = bce(y_valid, predictions_cnn) valloss = binaryCSELoss.numpy() print("binaryCSELoss logits=false on all Val Loss is: " + str(valloss)) bce = tf.keras.losses.BinaryCrossentropy(from_logits=True) binaryCSELoss = bce(y_valid, predictions_cnn) valloss = binaryCSELoss.numpy() print("binaryCSELoss logits=true on all Val Loss is: " + str(valloss))
对照实验的训练日志末尾如下,结果显示手动计算的损失依然与日志输出的val_loss不匹配:
54/54 [==============================] - ETA: 0s - loss: 0.5015 Epoch 6: val_loss did not improve from 0.66096 54/54 [==============================] - 8s 144ms/step - loss: 0.5015 - val_loss: 1.9742 % of marked 1 in validation: [0.28723404 0.71276593] % of marked 1 in Test: [0.52077866 0.47922137] loading Model: E:\CnnModels\2022-06-03_11-53-53\model.h5 Backend TkAgg is interactive backend. Turning interactive mode on. binaryCSELoss logits=false on all Val Loss is: 0.6353029 binaryCSELoss logits=true on all Val Loss is: 0.7070135
问题原因
该数值差异是Keras/TensorFlow训练CNN时的常见问题,核心诱因按出现概率从高到低排列:
- BatchNormalization(BN)层的统计量保存错位
这是最高发的原因。BN层在训练过程中会持续更新全局滑动平均的均值、方差,这组统计量是推理阶段做归一化的核心参数。ModelCheckpoint保存模型的触发时机是验证集跑完、确认val_loss达到最优之后,此时BN的滑动统计量已经被当前epoch最后几个训练batch更新过,根本不是跑出最优val_loss时,模型做验证前向传播所用的那组统计量。如果BN的momentum参数设置偏小,滑动统计量更新速度快,保存下来的模型BN参数和最优状态下的参数差异会非常大,直接加载计算出来的损失自然偏差明显。第一个实验中手动计算的损失高达2.33,基本就是BN统计量完全跑偏导致的。 - 训练/推理前向传播逻辑不一致
Keras计算训练过程中val_loss时的前向逻辑,和手动调用model.predict()的逻辑不完全一致:训练态下Dropout、随机深度等随机正则层会保持激活,BN默认优先使用当前batch的统计量;而model.predict()是严格的推理模式,会关闭所有随机正则,BN强制使用保存的全局滑动统计量。两种模式下模型输出本身就存在差异,损失计算结果自然不会完全对齐。 - 验证集数据预处理/增强逻辑不一致
如果使用数据生成器时,给验证集误开了随机增强(随机裁剪、翻转、亮度扰动等),训练时每次跑验证集的输入都是随机变化的,当时记录的0.41的val_loss只是随机增强后某一批验证数据的损失,用固定的原始验证集算损失,结果肯定无法对齐。如果手动读取验证集时漏了和训练时一致的归一化、尺寸resize等预处理,也会出现极大的损失偏差。 - 损失计算参数不匹配
用字符串'binary_crossentropy'指定损失时,Keras不同版本的默认参数和手动实例化BinaryCrossentropy()的参数可能存在差异:比如是否对预测概率做epsilon裁剪防止log(0)、损失聚合方式是求全局平均还是求和、是否应用了隐式的样本权重,这些细节差异也会导致损失数值不匹配。
快速排查方法:手动计算损失时不要用
predict(),改用model(x_val, training=False)直接获取推理模式下的输出,同时确认验证集预处理逻辑和训练时完全一致;如果还是存在巨大差异,可以在每个epoch验证开始前手动保存BN层的滑动均值和方差,和checkpoint保存的BN参数做对比,就能确认是不是BN统计量错位的问题。
内容的提问来源于stack exchange,提问作者Amit Raz
相关产品推荐
相关产品推荐

