feat: 未分类样本的无监督聚类(多 stage 特征 + KMeans,可选 SimCLR)

把 data/film 下所有图片拉平做聚类,不使用任何目录名,簇数由 num_clusters 决定。

预处理针对该批数据的实际情况(14.5k 张已裁好的缺陷小图,中位 152x113,
宽高比 0.24~39.9):尺寸与亮度都是 ROI 裁剪和光照的产物、不是缺陷属性,
因此拉伸到 128x128(不用 letterbox,其填充比例等价于编码了宽高比)并逐图 z-score。

- cluster: torch 实现的 kmeans++/Lloyd(含空簇处理与 n_init)、PCA、轮廓系数、
  NMI 稳定性、多 stage 特征提取;不引 scikit-learn
- 白化默认关闭:CNN 特征谱极陡(λ1/λ32≈165x),白化会把噪声方向抬到与信号同权,
  实测最大簇一度吃掉 96% 的样本
- simclr: NT-Xent 自监督预训练,用项目自己的卷积骨架,权重可直接初始化 DefectNet;
  属于训练,走 require_cuda() 守卫
- 产出 assignments.csv、summary.json、按簇的软链目录与代表样本拼图,供人工核对

全量 14,579 张:轮廓系数 0.326、换种子稳定性 0.964,CPU 约 100 秒。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Shaun Zhou
2026-09-02 17:55:47 +08:00
co-authored by Claude Opus 5
parent 4b1147ed8f
commit f433e5cf59
6 changed files with 976 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
"""SimCLR 的投影头。
对比损失只作用在投影后的空间;聚类时**丢掉投影头、用骨架特征**——
这是 SimCLR 的既有结论:投影头会丢掉对下游任务有用但对比任务用不上的信息。
"""
from __future__ import annotations
import torch
from torch import nn
from models.registry import HEADS
@HEADS.register("projection")
class ProjectionHead(nn.Module):
def __init__(self, in_channels: int, hidden_channels: int = 256, out_channels: int = 128) -> None:
super().__init__()
self.pool = nn.AdaptiveAvgPool2d(1)
self.mlp = nn.Sequential(
nn.Linear(in_channels, hidden_channels),
nn.BatchNorm1d(hidden_channels),
nn.ReLU(inplace=True),
nn.Linear(hidden_channels, out_channels),
)
def forward(self, feat: torch.Tensor) -> torch.Tensor:
if feat.dim() == 4:
feat = self.pool(feat).flatten(1)
return self.mlp(feat)