EarlyStopping 必须监控 val_loss 而不是 loss,因为 loss 持续下降无法反映泛化能力,只有 val_loss 上升才表明过拟合;且 Keras 默认不记录 loss 到 logs 中,传 monitor='loss' 会报 KeyError。

EarlyStopping 为什么必须监控 val_loss 而不是 loss
因为 EarlyStopping 的核心逻辑是防止过拟合,而训练损失 loss 在整个训练过程中天然持续下降,无法反映模型在未见数据上的泛化能力。只有验证集损失 val_loss 出现持续不降甚至上升时,才说明模型开始记住了训练噪声。Keras 默认只监听 val_loss,如果你传入 monitor='loss',它会报 KeyError: 'loss' —— 因为训练损失在每个 epoch 结束时不一定被记录进 logs 字典(尤其使用 tf.data 或自定义训练循环时)。
- 确保你在
model.fit()中传了validation_data或validation_split,否则val_loss根本不会被计算,EarlyStopping会直接失效 - 如果用
validation_split=0.2,注意这会从训练数据末尾切出 20%,对时序数据不适用;此时应手动构造validation_data=(X_val, y_val) -
val_loss在第一个 epoch 就存在,但前几个 epoch 波动大,建议设patience=5以上,避免过早终止
如何设置 min_delta 和 patience 才不误杀
min_delta 是“最小改善阈值”,默认为 0,意味着只要 val_loss 没变小就计数;但实际中浮点误差或小幅度震荡很常见。设 min_delta=1e-4 可过滤掉无意义的微小下降(比如从 0.123456 降到 0.123455),避免把震荡当成改善。
-
patience=0表示只要下一个 epochval_loss不严格下降就停止——几乎总导致训练只跑 2 个 epoch,慎用 -
patience=3是常见起点,但若你的验证集很小(val_loss 方差大,建议提高到 7–10 - 如果你观察到
val_loss在后期稳定在 ±0.002 范围内波动,min_delta应设为至少 0.003,否则每次小幅上浮都会重置计数器
恢复最佳权重:必须设 restore_best_weights=True
EarlyStopping 默认只中断训练,不恢复模型参数。如果你没加 restore_best_weights=True,最终模型就是最后一次 epoch 的权重,大概率比最佳 epoch 的模型更差。这个参数在 Keras 2.2+ 才可用,旧版本需手动保存/加载。
- 启用后,回调会在内部记录
best_weights,并在on_train_end时调用model.set_weights() - 它依赖
monitor的方向:val_loss是越小越好,所以判断逻辑是current ;如果是 <code>val_accuracy,就得配mode='max' - 注意:该操作不保存 optimizer 状态(如 Adam 的
m、v),所以恢复后不能直接继续训练,只能用于推理或评估
自定义 EarlyStopping:监听多个指标或添加日志
标准 EarlyStopping 只支持单指标,但你可以继承它并重写 on_epoch_end。例如同时监控 val_loss 下降和 val_accuracy 停滞:
立即学习“Python免费学习笔记(深入)”;
class DualEarlyStopping(tf.keras.callbacks.Callback):
def __init__(self, monitor_loss='val_loss', monitor_acc='val_accuracy',
patience=7, min_delta=1e-4, mode='min'):
self.monitor_loss = monitor_loss
self.monitor_acc = monitor_acc
self.patience = patience
self.min_delta = min_delta
self.wait = 0
self.stopped_epoch = 0
self.best_loss = np.inf
self.best_acc = -np.inf
<pre class='brush:python;toolbar:false;'>def on_train_begin(self, logs=None):
self.wait = 0
def on_epoch_end(self, epoch, logs=None):
current_loss = logs.get(self.monitor_loss)
current_acc = logs.get(self.monitor_acc)
if current_loss is None or current_acc is None:
return
# loss 必须下降,acc 不能连续下降
improved_loss = current_loss < self.best_loss - self.min_delta
if improved_loss:
self.best_loss = current_loss
self.wait = 0
else:
self.wait += 1
if self.wait >= self.patience:
self.stopped_epoch = epoch
self.model.stop_training = True这种写法绕过了内置逻辑,但要注意 logs 字典里是否真有你要的 key —— 比如 val_accuracy 在二分类中可能是 val_acc,取决于你编译模型时用的指标名。
真正容易被忽略的是:EarlyStopping 不会自动处理 NaN。如果 val_loss 某轮变成 nan,默认行为是继续计数,直到 patience 耗尽才停。你应该在自定义回调里加 np.isnan(current_loss) 判断,或前置用 tf.debugging.check_numerics。


















