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
+36 -1
View File
@@ -37,9 +37,13 @@ def test_parse_override_accepts_yaml_style_scalars():
def test_apply_overrides_creates_missing_nodes():
cfg = apply_overrides({}, ["a.b.c=1"])
"""非严格模式下仍可新建嵌套节点;严格模式(load_config 默认)会拦下未知键。"""
cfg = apply_overrides({}, ["a.b.c=1"], strict=False)
assert cfg == {"a": {"b": {"c": 1}}}
with pytest.raises(KeyError, match="不存在"):
apply_overrides({}, ["a.b.c=1"])
def test_load_config_with_base(tmp_path):
(tmp_path / "base.yaml").write_text("train:\n lr: 0.1\n epochs: 10\n", encoding="utf-8")
@@ -152,3 +156,34 @@ def test_eval_config_matches_its_training_backbone():
("configs/train/resnet18.yaml", "configs/eval/resnet18.yaml"),
]:
assert load_config(train_cfg)["model"] == load_config(eval_cfg)["model"], eval_cfg
@pytest.mark.parametrize(
"override,expected_hint",
[
("simclr.optmizer.weight_decay=0.0", "optimizer"), # 少了个 i
("simclr.optimizer.lrr=1e-3", "lr"), # 多了个 r
("num_clusers=4", "num_clusters"), # 少了个 t
],
)
def test_set_rejects_typos_with_suggestion(override, expected_hint):
"""--set 打错字母会凭空造出没人读的键,训练照跑但改动无效——必须拦下并给出建议。"""
with pytest.raises(KeyError) as excinfo:
load_config("configs/cluster/film_simclr_resnet50.yaml", [override])
assert expected_hint in str(excinfo.value)
def test_set_accepts_existing_keys():
cfg = load_config(
"configs/cluster/film_simclr_resnet50.yaml",
["simclr.optimizer.lr=0.001", "simclr.optimizer.weight_decay=0.0", "num_clusters=7"],
)
assert cfg["simclr"]["optimizer"]["lr"] == 0.001
assert cfg["simclr"]["optimizer"]["weight_decay"] == 0.0
assert cfg["num_clusters"] == 7
def test_apply_overrides_can_add_keys_when_not_strict():
from utils.config import apply_overrides
assert apply_overrides({"a": {"b": 1}}, ["a.c=2"], strict=False) == {"a": {"b": 1, "c": 2}}