You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 04:54:24