Files
ModelTrainEval/figures/clustering-pipeline.md
T
Shaun ZhouandClaude Opus 5 5d395b7c25 chore: 数据迁到 data/raw/ 后同步所有路径引用
原始数据从 data/<数据集> 移到了 data/raw/<数据集>。

- 聚类配置的 dataset.root:film / pearlescent 均改为 data/raw/ 下
- 检测配置的 img_dir 与 label_dir 注释同步
- README、data/README、算法文档、流程图(含重新渲染的 PNG)与各脚本
  docstring 里的路径示例一并更新
- data/README 补充目录约定:原始数据只读放 data/raw/,
  export_clusters.py 的派生数据集放在 data/ 下与 raw/ 平级
- 顺手把 unlabeled 数据集加进 datasets/__init__.py:此前它没在包初始化时注册,
  build_dataset({"name": "unlabeled"}) 只有在别处先 import 过该模块时才可用

迁移后重跑全量聚类,结果与迁移前逐项一致(轮廓 0.3255、稳定性 0.9641、
簇大小 [4507, 3795, 4849, 1428])。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 11:19:42 +08:00

3.5 KiB
Raw Blame History

无监督聚类流水线

从 data/raw/film 的原始图片到可人工核对的分组结果:六个阶段的数据流、张量形状与每步的数学操作。 虚线是可选的 SimCLR 自监督分支(method: simclr,CUDA 与 Apple MPS 均可)。

flowchart TB
    classDef input fill:#D1FAE5,stroke:#10B981,stroke-width:2px,color:#064E3B
    classDef prep fill:#DBEAFE,stroke:#2563EB,stroke-width:2px,color:#1E3A8A
    classDef feat fill:#DBEAFE,stroke:#2563EB,stroke-width:2px,color:#1E3A8A
    classDef reduce fill:#EDE9FE,stroke:#7C3AED,stroke-width:2px,color:#4C1D95
    classDef cluster fill:#EDE9FE,stroke:#7C3AED,stroke-width:2px,color:#4C1D95
    classDef out fill:#FFEDD5,stroke:#EA580C,stroke-width:2px,color:#7C2D12
    classDef metric fill:#FEF3C7,stroke:#D97706,stroke-width:2px,color:#78350F

    scan["data/raw/film 递归扫描<br/>N = 14,579 张 PNG<br/>尺寸各异 84×83 ~ 4464×1139"]:::input

    subgraph PRE["① 预处理 — 抹掉采集因子"]
        direction LR
        resize["双线性拉伸<br/>H×W → 128×128<br/>不保长宽比"]:::prep
        tensor["转张量 + RGBA/L → RGB<br/>x ∈ ℝ³ˣ¹²⁸ˣ¹²⁸"]:::prep
        zscore["逐图标准化<br/>x̃ = (x − μₓ) / σₓ<br/>抹掉亮度 μ 与对比度 σ"]:::prep
        resize --> tensor --> zscore
    end

    subgraph FEAT["② 特征提取 — 纹理优先"]
        direction LR
        stem["resnet18 stem<br/>conv1 → bn → relu → maxpool"]:::feat
        l1["layer1<br/>64ch, stride 4"]:::feat
        l2["layer2<br/>128ch, stride 8"]:::feat
        l3["layer3<br/>256ch, stride 16"]:::feat
        gap2["GAP → ℓ₂ 归一化<br/>u⁽²⁾ ∈ S¹²⁷"]:::feat
        gap3["GAP → ℓ₂ 归一化<br/>u⁽³⁾ ∈ S²⁵⁵"]:::feat
        concat["拼接<br/>z = [u⁽²⁾; u⁽³⁾] ∈ ℝ³⁸⁴"]:::feat
        stem --> l1 --> l2 --> l3
        l2 --> gap2 --> concat
        l3 --> gap3 --> concat
    end

    subgraph RED["③ 降维"]
        direction LR
        norm["ℓ₂ 归一化<br/>ẑ = z / ‖z‖₂"]:::reduce
        pca["PCA 投影 384 → 32<br/>y = Uᵀ(ẑ − z̄)<br/>不白化:保留 λ 谱"]:::reduce
        norm --> pca
    end

    subgraph CLU["④ 聚类 K-Means"]
        direction LR
        init["k-means++ 初始化<br/>p(xᵢ) ∝ D(xᵢ)²"]:::cluster
        lloyd["Lloyd 迭代<br/>指派 ↔ 更新质心"]:::cluster
        best["n_init = 10<br/>取 inertia 最小的一次"]:::cluster
        assign["全量指派<br/>cᵢ = argminⱼ ‖yᵢ − μⱼ‖₂"]:::cluster
        init --> lloyd --> best --> assign
    end

    subgraph EVAL["⑤ 无标签评估"]
        direction LR
        sil["轮廓系数 s̄<br/>簇是否紧致"]:::metric
        stab["换种子稳定性<br/>NMI(C_seed0, C_seed7)"]:::metric
    end

    subgraph OUT["⑥ 产出"]
        direction LR
        csv["assignments.csv<br/>路径 / 簇号 / 距质心"]:::out
        links["clusters/cluster_XX/<br/>软链目录,按距质心排序"]:::out
        prev["cluster_XX_preview.png<br/>代表样本拼图"]:::out
        summ["summary.json<br/>簇大小 / 轮廓 / 稳定性"]:::out
    end

    simclr["可选:SimCLR 自监督<br/>method = simclr<br/>cuda / mps 均可"]:::feat

    scan --> PRE
    PRE -->|"x̃ᵢ"| FEAT
    scan -.->|"双视图增强"| simclr
    simclr -.->|"域内骨架权重"| FEAT
    FEAT -->|"z ∈ ℝᴺˣ³⁸⁴"| RED
    RED -->|"y ∈ ℝᴺˣ³²"| CLU
    CLU -->|"标签 c"| EVAL
    CLU -->|"标签 c + 距离 d"| OUT