Files
ModelTrainEval/configs/cluster/film.yaml
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

125 lines
6.0 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 未分类样本的无监督聚类。
#
# 预处理决策(针对 data/raw/film 的实际情况:14.5k 张已裁好的缺陷小图,
# 中位尺寸 152x113,宽高比 0.24~39.9,尺寸几乎每张都不同):
#
# 前提:图片的尺寸和宽高比是上游 ROI 裁剪环节的产物,不是缺陷属性。
# 所以预处理的目标是**把这个采集噪声抹掉**,只留下缺陷的纹理/形态。
#
# 1. 直接 Resize 拉伸到 128x128,不用 letterbox —— letterbox 的填充比例
# 恰好等价于编码了宽高比,灰边占多少就等于把采集噪声明着喂给网络;
# 拉伸则让所有图变成同一个画幅,宽高比不再是可用信号。
# 2. use_shape_features 关掉 —— 同理,尺寸不进 embedding。
# 3. 逐图标准化(PerImageStandardize)而不是固定的 ImageNet mean/std ——
# 整体亮度来自光照和相机增益,同样不是缺陷属性。用固定归一化时,
# 4 个簇里有 3 个花在"背景偏亮/偏暗"上;改成逐图 z-score 后,
# 簇变成"条纹状 / 团块状 / 弱纹理",轮廓 0.256->0.322,稳定性 0.966->0.995。
# 4. 不下采样到 224 —— 这批图本来就小,放大只会糊,128 已经够。
#
# 代价:拉伸会让纹理各向异性形变(40:1 的长条尤其明显)。如果后续发现
# 聚类被形变干扰,可选的替代是在原始分辨率上取固定大小的中心 patch
# (不缩放、不填充),配置里把 Resize 换成 CenterCrop 即可。
work_dir: runs/film
seed: 42
device: auto # auto 按 cuda > mps > cpu 挑最快的可用后端;也可写 cuda / mps / cpu
num_clusters: 4 # 想分几类改这里(或 --num-clusters 6)
# pretrained: ImageNet 预训练卷积骨架直接抽特征,不训练,CPU 也能跑,几分钟出结果。
# simclr: 在本目录数据上自监督训练项目自己的卷积骨架,域适配更好;Mac 上走 MPS 即可。
method: pretrained
# method=pretrained 用的骨架(method=simclr 的骨架配在 simclr.backbone,两者字段完全不同,
# 各自独立以免 _base_ 合并时串味)
backbone:
name: torchvision
arch: resnet18
pretrained: true
in_channels: 3
image_size: [128, 128]
use_shape_features: false # 尺寸/宽高比是采集产物,不作为聚类特征;true 仅供对照实验
# 取哪几个 stage 的特征拼起来(torchvision resnet 的 features 下标:4~7 = layer1~4)。
# 实测(2000 张随机样本,k=4):
# 只用最后一层 layer4 轮廓 0.191 换种子稳定性 0.410
# layer2+3 轮廓 0.256 稳定性 0.989 <- 默认
# layer2+3+4 轮廓 0.220 稳定性 0.627
# 缺陷之间的差别主要在纹理(颗粒感、条纹方向、边缘锐度),中间层保留得比最后一层好;
# 最后一层的 ImageNet 语义("这是猫还是车")对这批图基本是噪声。
feature_stages: [5, 6]
# 特征降维。whiten 默认关闭:CNN 特征只有前若干主成分携带结构,
# 白化会把剩下几十个噪声方向抬到同权,簇立刻塌成"一个大簇 + 几个碎簇"
# (实测开白化时最大簇占 96%,关掉后降到 38%)。
pca_dim: 32
whiten: false
n_init: 10
# 只用一部分样本拟合质心以节省时间(0 = 用全部);纯随机抽取,不按目录分层——
# 目录名是人工整理的历史产物,不参与聚类。拟合完仍会把全部样本分配到最近的簇。
fit_max_samples: 0
dataset:
name: unlabeled
root: data/raw/film
gray: false
exclude: [images, labels] # 跳过已经整理成 YOLO 结构的部分
transforms:
- {name: Resize, size: [128, 128]} # 拉伸,不保长宽比:见文件开头的说明
- {name: ToTensor}
- {name: PerImageStandardize} # 逐图 z-score,抹掉整体亮度/对比度
loader:
batch_size: 128
num_workers: 8
# method=simclr 时每个视图各自过一遍的随机增强(正样本对就是同一张图的两个视图)
view_transforms:
- {name: RandomResizedCrop, size: [128, 128], scale: [0.5, 1.0]}
- {name: RandomHorizontalFlip, p: 0.5}
- {name: RandomVerticalFlip, p: 0.5}
- {name: ColorJitter, brightness: 0.4, contrast: 0.4}
- {name: GaussianBlur, p: 0.3}
# 注意:主 transforms 里的 PerImageStandardize 会在这些增强之后执行,
# 所以 ColorJitter 的亮度扰动最终会被抵消掉,起作用的主要是裁剪/翻转/模糊。
simclr:
# 自监督用项目自己的纯卷积骨架:练出来的权重可以直接拿去初始化 DefectNet 的 backbone
backbone:
name: tiny_conv
in_channels: 3
widths: [32, 64, 128, 256]
depths: [1, 2, 2, 2]
block: dw
feature_stages: null # tiny_conv 不是 nn.Sequential,取最终输出
epochs: 100
batch_size: 128
temperature: 0.2 # 越小越关注难负样本
amp: auto # CUDA 上开、MPS 上关
save_interval: 5 # 每 5 个 epoch 落一次盘,中途中断也能拿到权重
# 训练可视化,字段与 train.swanlab 相同。默认 local:不需要登录,
# 日志写到 <work_dir>/swanlog,另开终端 `uv run swanlab watch runs/simclr/swanlog` 看板。
swanlab:
enabled: true
project: film-simclr
name: null # 缺省用 work_dir 目录名
mode: local # online(云端) | local(本地看板) | offline(仅存盘) | disabled
tags: [simclr, tiny_conv]
log_interval: 20
# checkpoint 与 resume 二选一,语义不同:
# checkpoint: 有权重就**跳过训练**,直接拿它抽特征聚类(换簇数时用这个)
# resume: 载入骨架+投影头+优化器+调度器,从上次的 epoch **接着训练**
checkpoint: null # 例:runs/simclr/simclr_backbone.pth
resume: null # 例:runs/simclr/simclr_last.pth
projection: {hidden_channels: 256, out_channels: 128}
optimizer: {name: adamw, lr: 0.001, weight_decay: 0.0001}
scheduler: {name: cosine}
# 输出:每簇软链多少张图 / 预览拼图放多少张
link_limit: 300
preview_count: 100