feat(cluster): 新增 resnet18 自监督聚类配置,并修复 --set 的 YAML 标量解析

- 新增 configs/cluster/film_simclr_resnet18.yaml:用 resnet18 做域内自监督后聚类。
  backbone 块带 _replace_,避免 base 里 tiny_conv 的 widths/depths 残留
- parse_override 此前只用 ast.literal_eval,不认 YAML 风格的 null/true/false:
  `--set xxx.enabled=false` 会得到字符串 "false",而非空字符串是真值,开关反被打开。
  改为先试 Python 字面量(保住 1e-3 这类),再退回 YAML 标量解析
- feature_stages 默认由 null 改为 [5, 6]:实测 1 epoch 自监督后 layer2+3 的轮廓
  0.243/稳定性 0.997,明显优于 layer4 的 0.156/0.528。原注释里"自监督后 layer4
  已是域内特征"的推断被数据推翻
- README 补充三种方案的实测对比与成本

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Shaun Zhou
2026-09-04 15:51:28 +08:00
co-authored by Claude Opus 5
parent 47fb0f1125
commit 6f00074199
4 changed files with 92 additions and 4 deletions
+20 -4
View File
@@ -107,15 +107,31 @@ def _load_raw(path: Path, seen: set[Path]) -> dict:
def parse_override(item: str) -> tuple[str, Any]:
"""解析 `train.lr=1e-3`,值按 Python 字面量尝试转换,失败则保留字符串。"""
"""解析 `train.lr=1e-3`,把值转成合适的类型。
先按 Python 字面量解析(`1e-3`、`[5, 6]`、`None`、`False` 都能认),
再退回 YAML 标量解析,好让配置文件里的写法在命令行上同样可用
(`null`、`true`、`false`)。两者都失败才保留字符串。
这一步不能只用 `ast.literal_eval`:YAML 风格的 `false` 会原样变成字符串,
而非空字符串是**真值**——`--set xxx.enabled=false` 反而把开关打开了。
也不能只用 YAML:PyYAML 把 `1e-3` 当字符串(YAML 1.1 要写成 `1.0e-3`)。
"""
if "=" not in item:
raise ValueError(f"覆盖项格式应为 key=value,收到: {item}")
key, raw = item.split("=", 1)
raw = raw.strip()
try:
value = ast.literal_eval(raw)
return key.strip(), ast.literal_eval(raw)
except (ValueError, SyntaxError):
value = raw
return key.strip(), value
pass
try:
parsed = yaml.safe_load(raw)
except yaml.YAMLError:
return key.strip(), raw
# YAML 会把 "abc: def" 之类解析成 dict,命令行上应当按字符串处理
return key.strip(), parsed if isinstance(parsed, (bool, int, float, list, type(None))) else raw
def apply_overrides(cfg: dict, overrides: Iterable[str]) -> dict: