fix(config): --set 拦下不存在的键,并支持续训时重开学习率曲线

用户反馈"聚类配置里改不了 lr/weight_decay",实测是两件事:

1. --set 的键打错字母会凭空造出一个谁也不读的键,训练照跑但改动无效。
   实例:simclr.optmizer.weight_decay=0.0(optimizer 少个 i)被静默接受。
   现在 apply_overrides 默认只允许覆盖已存在的键,报错时给出同级可用键
   与最接近的拼写("是不是想写 'optimizer'?")。需要新增键时可 strict=False。

2. lr 其实生效了,但从 2229/2500 续训时 cosine 只剩满值的 3%,
   新设的 1e-3 落到 2.87e-5。新增 restart_schedule 开关:按剩余轮数重开曲线,
   新 lr 从满值开始;SimCLR 与检测训练都支持。续训日志补上调度进度,
   并在 lr 不足 initial_lr 的 10% 时警告并给出两种解法。

顺带:严格校验立刻暴露出 configs/cluster/film.yaml 里根本没有 scatter 段
(代码 cfg.get("scatter", {}) 兜底走默认值,所以功能正常但配置项形同虚设),已补上。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Shaun Zhou
2026-09-07 19:30:17 +08:00
co-authored by Claude Opus 5
parent 506fde0290
commit 92c88b785c
8 changed files with 165 additions and 7 deletions
+33
View File
@@ -292,6 +292,39 @@ def test_resume_with_a_longer_schedule_rebuilds_the_lr_curve(tiny_image_dir, tmp
assert lr == pytest.approx(1e-3 * (1 + math.cos(math.pi * 10 / 30)) / 2, rel=1e-3)
def test_restart_schedule_gives_the_new_lr_full_value(tiny_image_dir, tmp_path):
"""续训到调度末段时,cosine 会把新 lr 乘上接近 0 的系数;
restart_schedule 按剩余轮数重开曲线,让新 lr 从满值开始。"""
import math
work_dir = tmp_path / "run"
_make_trainer(
tiny_image_dir, work_dir, epochs=9,
optimizer={"name": "adamw", "lr": 1e-3},
).fit()
def resumed(restart):
return SimCLRTrainer(
BACKBONES.build({"name": "tiny_conv", "widths": [8, 16], "depths": [1, 1]}),
_make_trainer(tiny_image_dir, work_dir, epochs=1).dataloader,
{"epochs": 10, "amp": False, "log_interval": 999, "save_interval": 1,
"scheduler": {"name": "cosine"}, "optimizer": {"name": "adamw", "lr": 1e-3},
"projection": {"hidden_channels": 16, "out_channels": 8},
"swanlab": {"enabled": False}, "restart_schedule": restart},
torch.device("cpu"),
work_dir,
resume=work_dir / "simclr_last.pth",
)
# 默认:沿用整条曲线,9/10 处只剩很小的系数
default_lr = resumed(False).optimizer.param_groups[0]["lr"]
assert default_lr == pytest.approx(1e-3 * (1 + math.cos(math.pi * 9 / 10)) / 2)
assert default_lr < 1e-3 * 0.1
# 打开开关:新 lr 从满值开始
assert resumed(True).optimizer.param_groups[0]["lr"] == pytest.approx(1e-3)
def test_resume_takes_hyperparams_from_config_not_checkpoint(tiny_image_dir, tmp_path):
"""续训时 lr / weight_decay / 总轮数一律以 yaml 为准,存档只提供进度。