feat(cluster): 加 --train-only,自监督训练与聚类可分两步跑
engine/simclr.py 与 engine/cluster.py 本来就互不引用,唯一接口是 simclr_backbone.pth;耦合只在 CLI——method=simclr 会训练完直接聚类。 此前"只聚类"可以用 simclr.checkpoint 做到,"只训练"没有办法。 - --train-only:训练存完权重即退出,不抽特征也不聚类,并打印下一步的聚类命令 - 与 method=pretrained / simclr.checkpoint / --sweep-k 组合时立刻报错说明原因, 而不是跑完才发现没有产出 - 典型用法:GPU 机器上 --train-only 跑几十小时,之后拿权重反复调 k,每次约 1 分钟 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
5d395b7c25
commit
0f5bb4a5c0
@@ -480,6 +480,99 @@ def test_cluster_script_simclr_branch_end_to_end(tiny_image_dir, tmp_path, monke
|
||||
assert (work_dir / "clusters" / "cluster_00").is_dir()
|
||||
|
||||
|
||||
def _cluster_argv(work_dir, image_dir, *extra):
|
||||
return [
|
||||
"cluster_unlabeled.py", "--config", "configs/cluster/film.yaml",
|
||||
"--work-dir", str(work_dir), "--root", str(image_dir), "--set",
|
||||
"device=cpu", "loader.num_workers=0", "loader.batch_size=4",
|
||||
"num_clusters=2", "n_init=2", *extra,
|
||||
]
|
||||
|
||||
|
||||
def test_train_only_stops_before_clustering(tiny_image_dir, tmp_path, monkeypatch):
|
||||
"""--train-only 只训练存权重,不抽特征也不聚类。"""
|
||||
sys.path.insert(0, "scripts")
|
||||
import cluster_unlabeled as cu
|
||||
|
||||
work_dir = tmp_path / "train_only"
|
||||
monkeypatch.setattr(
|
||||
sys, "argv",
|
||||
[*_cluster_argv(work_dir, tiny_image_dir,
|
||||
"method=simclr", "simclr.epochs=1", "simclr.batch_size=4",
|
||||
"simclr.backbone.widths=[8,16]", "simclr.backbone.depths=[1,1]",
|
||||
"simclr.projection.hidden_channels=16", "simclr.projection.out_channels=8"),
|
||||
"--train-only"],
|
||||
)
|
||||
assert cu.main() == 0
|
||||
|
||||
assert (work_dir / "simclr_backbone.pth").exists()
|
||||
assert (work_dir / "simclr_last.pth").exists() # 续训状态照常落盘
|
||||
for produced_by_clustering in ("assignments.csv", "summary.json", "cluster_scatter.png", "clusters"):
|
||||
assert not (work_dir / produced_by_clustering).exists(), produced_by_clustering
|
||||
|
||||
|
||||
def test_train_only_then_cluster_only(tiny_image_dir, tmp_path, monkeypatch):
|
||||
"""训练与聚类可以分两次跑:第一次只训练,第二次用 checkpoint 只聚类。"""
|
||||
sys.path.insert(0, "scripts")
|
||||
import cluster_unlabeled as cu
|
||||
|
||||
train_dir = tmp_path / "phase1"
|
||||
monkeypatch.setattr(
|
||||
sys, "argv",
|
||||
[*_cluster_argv(train_dir, tiny_image_dir,
|
||||
"method=simclr", "simclr.epochs=1", "simclr.batch_size=4",
|
||||
"simclr.backbone.widths=[8,16]", "simclr.backbone.depths=[1,1]",
|
||||
"simclr.projection.hidden_channels=16", "simclr.projection.out_channels=8"),
|
||||
"--train-only"],
|
||||
)
|
||||
assert cu.main() == 0
|
||||
|
||||
cluster_dir = tmp_path / "phase2"
|
||||
monkeypatch.setattr(
|
||||
sys, "argv",
|
||||
_cluster_argv(cluster_dir, tiny_image_dir,
|
||||
"method=simclr",
|
||||
f"simclr.checkpoint={train_dir / 'simclr_backbone.pth'}",
|
||||
"simclr.backbone.widths=[8,16]", "simclr.backbone.depths=[1,1]"),
|
||||
)
|
||||
assert cu.main() == 0
|
||||
|
||||
assert (cluster_dir / "assignments.csv").exists()
|
||||
assert not (cluster_dir / "simclr_backbone.pth").exists(), "第二步不该重新训练"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"extra,reason",
|
||||
[
|
||||
([], "method=pretrained 没有要训练的东西"),
|
||||
(["method=simclr", "simclr.checkpoint=whatever.pth"], "checkpoint 会跳过训练"),
|
||||
],
|
||||
)
|
||||
def test_train_only_rejects_meaningless_combinations(
|
||||
tiny_image_dir, tmp_path, monkeypatch, extra, reason
|
||||
):
|
||||
"""这些组合下 --train-only 什么都不会做,应当立刻报错而不是跑完才发现没产出。"""
|
||||
sys.path.insert(0, "scripts")
|
||||
import cluster_unlabeled as cu
|
||||
|
||||
monkeypatch.setattr(
|
||||
sys, "argv", [*_cluster_argv(tmp_path / "bad", tiny_image_dir, *extra), "--train-only"]
|
||||
)
|
||||
assert cu.main() == 1, reason
|
||||
|
||||
|
||||
def test_train_only_conflicts_with_sweep_k(tiny_image_dir, tmp_path, monkeypatch):
|
||||
sys.path.insert(0, "scripts")
|
||||
import cluster_unlabeled as cu
|
||||
|
||||
monkeypatch.setattr(
|
||||
sys, "argv",
|
||||
[*_cluster_argv(tmp_path / "bad", tiny_image_dir, "method=simclr"),
|
||||
"--train-only", "--sweep-k", "2-4"],
|
||||
)
|
||||
assert cu.main() == 1
|
||||
|
||||
|
||||
def test_scatter_plot_is_written_next_to_previews(tiny_image_dir, tmp_path, monkeypatch):
|
||||
"""cluster_scatter.png 要和 preview 拼图落在同一级目录。"""
|
||||
sys.path.insert(0, "scripts")
|
||||
|
||||
Reference in New Issue
Block a user