fix(trainer): 检测训练续训以配置为准,并恢复学习率调度进度

train.py --resume 走的是与 SimCLR 不同的一套代码,同样的问题在这里更严重:

- load_checkpoint 直接 optimizer.load_state_dict,把 lr/weight_decay 连同动量缓冲
  一起还原成存档值:yaml 写 lr=5e-4,实际仍按检查点的 1e-3 训
- save_checkpoint 根本没保存调度器状态,续训时 scheduler.last_epoch 从 0 开始,
  而 optimizer 的 lr 又来自检查点,两者互相矛盾,cosine 曲线完全错乱

改为与 SimCLR 一致的「配置文件是唯一真相源」:

- save_checkpoint 补上 scheduler 状态、global_step 与当时的 train 配置快照
- 新增 Trainer.resume():加载优化器状态后按组写回配置里的超参(只留动量缓冲,
  并保住 BN/bias 那组 weight_decay=0 的结构),调度器按当前配置重建再快进到已训 epoch
- 配置与检查点不一致时逐项打日志说明按当前配置执行
- train.py 改用 trainer.resume(),不再自己拼恢复逻辑

实测续训到 10 轮时 lr=3.273e-04,与 cosine 在 4/10 处的理论值精确一致;
weight_decay、T_max、调度进度、动量缓冲均正确。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Shaun Zhou
2026-09-07 19:06:37 +08:00
co-authored by Claude Opus 5
parent 01cfff9b02
commit 105cb38831
5 changed files with 179 additions and 7 deletions
+1 -6
View File
@@ -87,12 +87,7 @@ def main() -> int:
)
if args.resume:
from engine import load_checkpoint
state = load_checkpoint(args.resume, model, trainer.optimizer, map_location=device)
trainer.start_epoch = int(state.get("epoch", 0)) + 1
trainer.best_metric = float(state.get("best_metric") or float("-inf"))
logger.info("已从 %s 恢复,起始 epoch=%d", args.resume, trainer.start_epoch)
trainer.resume(args.resume)
cfg.dump(work_dir / "config.yaml")
best = trainer.fit()