尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

MaskCLIP无监督分割实战

MaskCLIP无监督分割实战 MaskCLIP 复现完整记录第一阶段尝试复现与基础问题修复1. 安装与环境配置在 GitHub 上克隆源文件chongzhou96/MaskCLIP: Official PyTorch implementation of Extract Free Dense Labels from CLIP (ECCV 22 Oral)安装必要的依赖。环境适配说明本人配置为 RTX 5060必须搭配CUDA 12.x 及 PyTorch 2.x而MaskCLIP官方代码库基于早期 OpenMMLab 体系mmsegmentationv0.x /mmcv1.x默认要求的 Python 3.7/3.8 和 PyTorch 1.x 无法在新显卡上调用 CUDA于是禁用所有 CUDA改用 CPU 计算。2. 下载并转换 CLIP 模型运行convert_clip_weights.py生成一个ViT16_clip_backbone.pth权重文件。带--backbone参数时提取出ViT16_clip_backbone.pth主干网络权重。不带--backbone参数时提取出ViT16_clip_weights.pth解码头需要的投影层权重3. 准备感兴趣对象的文本嵌入运行prompt_engineering.py提取 PASCAL VOC 数据集的类别文本特征用来做无监督分割voc_ViT16_clip_text.pth文本特征文件生成并保存到pretrain/目录下。运行python tools/test.py configs/maskclip/maskclip_vit16_520x520_pascal_context_59.py pretrain/ViT16_clip_backbone.pth --show-dir output/生成了context_ViT16_clip_text.pth文件。3.1 解决 mmcv._ext 模块缺失问题mmseg在启动时会自动加载所有的网络组件和损失函数其中包含了一些用 C 写的算子如focal_loss。它调用的mmcv.ops试图加载编译好的 C 动态链接库mmcv._ext。因为在 Windows 较新版本 PyTorch 下通过标准pip安装的mmcv通常只包含了纯 Python 代码缺少编译好的_ext.pyd文件所以抛出了ModuleNotFoundError: No module named mmcv._ext。4. 数据集准备去 PASCAL Context 官网下载数据集。E:\MaskCLIP\data\ └── VOCdevkit/ └── VOC2010/ ├── JPEGImages/ -- 从 VOCtrainval_03-May-2010 得到的所有 .jpg 图片 │ ├── 2008_000002.jpg │ └── ... ├── ImageSets/ │ └── SegmentationContext/ │ └── val.txt └── SegmentationClassContext/ -- 从 Stanford 的 59_context_labels.tar.gz 中解压 ├── 2008_000002.png └── ...5. 获取定量结果mIoU—— 首次尝试失败5.1 为什么 mIoU 只有 1.42 且一直报 Missing Keys5.1.1 权重文件被拆分权重文件被拆分了tools/test.py加载的pretrain/ViT16_clip_backbone.pth文件里只有主干网络 (Backbone) 的权重完全不包含decode_head的文本向量和投影矩阵。在tools/test.py里重写了校验逻辑确保在模型推理前强制把文本向量和 1x1 卷积权重补充挂载进去。5.1.2 源码拼写错误源码自带致命拼写 Bug在mmseg/models/decode_heads/maskclip_head.py的init_weights函数中原作者把类名拼错了写成了super(MaskCLIPHead, self)多了个大写的L导致 Python 报NameError并跳过了权重的初始化。5.1.3 DataContainer 格式解包失败DataContainer格式解包失败在mmsegmentation/mmcv中单卡/CPU 测试single_gpu_test期望从 DataLoader 传进来的img_metas是一个标准的list比如[{ori_shape: ...}]。但启动了强制 CPU 模式没有用标准的MMDataParallel包装DataLoader 吐出来的img_metas被封装在了一个DataContainer对象里没有自动被.data[0]解包出来。导致模型在读取图像尺寸ori_shape时拿着DataContainer当list迭代直接抛出了TypeError。5.1.4 CPU 单卡运行问题CPU 单卡运行时没有 MMCV 的MMDataParallel自动拆包数据流里的img_meta在每个流程节点都需要手动把.data提出来。5.1.5 img_meta 数据结构问题问题原因测试时img_meta经过forward_test整理后是List[List[dict]]结构外层测试增强内层batch 图片inference中img_meta[0]拿到的是内层 list 而非 dict导致img_meta[0][ori_shape]抛出TypeError。修复内容在inference方法中增加一层扁平化处理将[[{...}]]规范化为[{...}]。5.2 修复后 mIoU 仍然很低mIoU仍然很低~1.4%所有类别的余弦相似度得分都在0.25 ~ 0.32之间非常接近。尝试改在wsl里面运行第二阶段深入诊断与根因分析MaskCLIP Zero-Shot 评估全流程诊断报告一、问题现象在 PASCAL Context 59 类上运行 MaskCLIP (ViT-B/16) 零样本评估命令python tools/test.py configs/maskclip/maskclip_vit16_520x520_pascal_context_59.py pretrain/ViT16_clip_backbone.pth --eval mIoU结果mIoU 1.63%随机基线 1/59 ≈ 1.69%几乎等于瞎猜。二、排查过程阶段 1标签映射 (已解决非根因)问题最初怀疑_LABEL_MAP映射错误。关键发现PASCAL Context 官方 Stanford 59_context_labels 是调色板/索引 PNG (modeP)像素值直接为 1~59。重要教训cv2.imread(GRAYSCALE)在读取调色板 PNG 时会读取 RGB 调色板色值而非索引值导致错误分析。必须用PIL/Pillow读取# 错误方式 cv2.imread(file, cv2.IMREAD_GRAYSCALE) # 读取调色板颜色值非索引 # 正确方式 np.array(Image.open(file)) # 正确返回 1~59 的调色板索引最终状态mmseg/datasets/pascal_context.py中self.label_map Nonereduce_zero_labelTrue独自处理 1→0, 2→1, ..., 59→58 的映射。所有 5,105 张验证图片的 59 个类别标注均完整。阶段 2后处理阈值 (不相关)Config 中ks_thresh0.和pd_thresh0.已为 0refine_output()方法实际上跳过所有后处理直接返回原始 logits。阶段 3权重加载验证 (全部正确)Backbone (151/151 keys 全部匹配)checkpoint load_checkpoint(model, pretrain/ViT16_clip_backbone.pth, ...)验证了pos_embed、cls_token、patch_embed.projection.weight、所有 12 层的attn/ln/ffn权重 ——全部与 checkpoint 完全一致。Decode HeadMaskClipHead.__init__()分别从两个文件加载文件内容状态pretrain/context_ViT16_clip_text.pthtext_embeddings [59, 512], L2 norm ≈ 1.0正确加载pretrain/ViT16_clip_weights.pthproj.weight [512, 768] CLIP 原始权重备份正确加载阶段 4前向传播逐阶段追踪 (核心发现)对单张图片2008_000002.jpg390×520进行逐阶段追踪Step 1: Backbone → v (value-path features)shape: [1, 768, 25, 33]per-pixel norm (channel L2): mean 27.71 ≈ sqrt(768)Step 2: proj(v) [Conv2d 768→512]shape: [1, 512, 25, 33]per-pixel norm: mean 7.87Step 3: L2 normalize per pixel每个 512-dim 向量 → 单位向量Step 4: Cosine similarity with text_embeddings [59, 512]mean 0.000685 ←正交std 0.031694Step 5: ×100 (logit_scale)range: [-8.52, 9.52]Step 6: Softmax最大置信度 per-pixel: mean 0.33, median 0.36仅 19/59 类别被预测到阶段 5GitHub Issue 线索找到了一个相同的 GitHub issue (Xuefei98, 2023年7月26日)同样是在 PASCAL Context 59 上测试结果不对。评论者 WJsebastian 建议 adding the pre-trained weights path could be of help。阶段 6convert_clip_weights.py 源码分析# convert_clip_weights.py, line 28-33 if ViT in args.model and prefix in key: new_key key[len(f{prefix}.):] if new_key proj: all_model[proj] {} all_model[proj][weight] state_dict[key].float().t() # ← 转置 continueCLIP ViT 的visual.proj形状为[768, 512]width768, output_dim512。脚本将其转置为[512, 768]保存匹配 MaskCLIP head 中Conv2d(768, 512, 1)的权重格式。这个转置是正确的。阶段 7prompt_engineering.py 分析使用标准 CLIP 提示工程85 个模板对每个类别进行对每个模板填充类名 → tokenize用 CLIP text_encoder 编码 → 得到 embeddingL2 normalize对同一类别的 85 个模板 embedding 取平均 → 最终类 embeddingL2 normalize 最终结果三、根因判断完整版3.1 排除法四个怀疑方向全部验证通过我们对四个最可能出问题的地方做了逐项验证全部排除测试项验证方法结论Root Cause Ainit_weights()覆盖已加载权重对比load_visual_projs()前后 decode_head 参数✅ 通过 —load_visual_projs()在init_weights()之后调用CLIP 权重不会被覆盖Root Cause B文本嵌入 L2 归一化维度错误检查所有 59 个类别的 L2 norm✅ 通过 — 全部 ≈ 1.0沿dim-1归一化正确Root Cause Cproj权重转换错误对比 CLIPvisual.proj和转换后proj.weight✅ 通过 — 转置.t()后形状正确数值完全一致Root Cause DMaskCLIP ViT 前向传播与原始 CLIP ViT 存在差异逐层对比 mmcv ViT 与 PyTorch 原生 MultiheadAttention✅ 通过 — 实现完全等价3.2 Test D 详细验证mmcv ViT PyTorch ViT这是最关键的一次验证。我们写了一个修正了残差连接处理的诊断脚本将 mmcv 的 MultiheadAttention 和 PyTorch 原生的 nn.MultiheadAttention 在完全相同的输入和权重下做对比Attention output comparison (residual removed): max_diff 2.542511e-07 cos 1.00000131 IDENTICAL: mmcv MultiheadAttention torch.nn.MultiheadAttention Full pre-norm block comparison: max_diff 4.768372e-07 cos 1.00000048 IDENTICAL: mmcv block naive torch block结论mmcv 的 ViT 实现和 PyTorch 原生实现在数值上完全一致不存在任何计算 Bug。唯一的微小差异来自 LayerNorm 的 eps 参数MaskCLIP 配置使用 eps1e-6CLIP 原版使用 eps1e-5这个差异在 12 层 Transformer 中累积后会导致 CLS token 余弦相似度约 0.94~6% 的方向偏移但这属于正常的数值行为不是 Bug也不足以解释 mIoU 1.63% 的惨烈结果。3.3 真正的根因所有代码层面的可能性都被排除了。真正的问题在于PASCAL Context 59 类的 CLIP 文本嵌入高度相关。数据分析显示59 个文本嵌入的平均向量范数0.9003类间余弦相似度最小 0.159均值 0.322全部为正什么意思呢在 CLIP 的 512 维特征空间里这 59 个类别的文本描述全部挤在同一个狭窄的锥面内彼此之间没有负相关即不存在对立的语义方向。所以无论你输入什么图片模型在每个像素上都只能看到一堆相似度差不多的分数softmax 后接近于均匀分布。换数据集无法解决这个问题。我们用随机噪声torch.randn(1, 3, 520, 520)测试时raw cosine mean 已经是 -0.002——即在完全没有语义信息的随机输入上视觉特征就已经和文本嵌入正交了。这说明问题出在 proj 投影层的翻译能力而非数据集本身。CLIP 的visual.proj权重是为全局 [CLS] token 训练的不是为 patch-level value features 训练的。把它直接用在每个像素的 v 特征上投影后的方向就不对——这就是 投影层翻译坏了 的本质。四、其他已修复的次要问题问题修复位置说明persistent_workers 错误mmseg/datasets/builder.py:145-147num_workers0 时自动禁用 persistent_workers_LABEL_MAP 误用mmseg/datasets/pascal_context.py已移除依赖 reduce_zero_labelTruesingle_gpu_test 兼容性tools/test.py:239使用 scatter_kwargs 代替 MMDataParallelcv2 读取调色板 PNG无代码改动教训必须用 PIL Image.open() 读取 indexed/palette PNGconvert_clip_weights.py LN 命名脚本中 norm 应改为 lnmmcv build_norm_layer 将 LN 命名为 ln0/ln1非 norm0/norm1第三阶段PASCAL VOC 2012 完整评估结果maskclip_a 在 Pascal VOC 2012 上的完整评估结果1449张验证集图片单尺度推理mIoU 24.62%每类 IoU按降序排列类别IoU类别IoUdog (狗)53.80%sheep (羊)18.12%bus (公交车)53.32%train (火车)16.36%car (汽车)44.01%boat (船)14.72%bird (鸟)41.43%bicycle (自行车)11.77%horse (马)40.12%sofa (沙发)9.20%dining table (餐桌)38.09%motorbike (摩托车)8.31%cow (牛)38.07%potted plant (盆栽)7.18%cat (猫)37.19%airplane (飞机)5.89%bottle (瓶子)31.05%tv monitor (电视)3.76%chair (椅子)18.67%person (人)1.26%切换到官方 mmseg 实现后的 VOC 20 类结果后来我用官方 mmseg 版本的 MaskCLIP而非简化版重新在 VOC 2012 上跑了一遍。这次的结果是官方 mmseg 版 VOC 2012 每类 IoU类别IoU类别IoUcat69.2%cow54.6%airplane64.9%car53.2%train60.1%motorbike49.9%dog59.6%person49.7%bus58.2%bottle42.1%horse57.8%sofa40.5%bird57.5%tv monitor39.2%potted plant57.0%bicycle38.4%sheep56.2%boat32.1%dining table28.8%chair20.6%mIoU: 49.49%这个数字远远高于之前非官方版本的 24.62%。对比总结对比项e:/MaskCLIP (mmseg)maskclippytorchVOC 2012 mIoU49.49%24.62%Pascal Context 59 mIoU0.62%?架构风格mmseg 0.x, MaskClipHead独立封装, MaskCLIP.forward输入分辨率512×512 (bicubic pos embed)512×512归一化ImageNet (mean/std RGB)CLIP (mean/std)v features 处理out_proj vx residual直接取 transformer layer v原论文里两个数据集的零样本结果是PASCAL Context 59 类21.7-22.9%PASCAL VOC 20 类没有直接报告原始 MaskCLIP 的零样本数字但从 MaskCLIP 的提升幅度从 ~35% 到 86%和社区复现的 58.5% 来看VOC 20 类的合理零样本基线应该在 50%-60% 区间。所以这次跑出的 49.49% 在 VOC 20 类上完全是合理的。真正的问题是 PASCAL Context 59 类只有 1.6%。那为什么 PASCAL Context 59 类只有 1.6%而 VOC 20 类有 49.49%关键差异已经在之前的诊断中确定了——59 个类别的文本嵌入在 CLIP 空间中高度相关类间余弦相似度全部为正值模型无法有效区分。简单说就是VOC 20 类的语义空间区分度足够而 PASCAL Context 59 类的语义空间太挤。49.49% 对零样本 MaskCLIP 在 VOC 2012 上是合理的。它是以下因素的组合正确的骨干架构ViT CLIP 权重较高的输入分辨率512 vs 224正确的 v 特征提取out_proj 残差连接最终结论不是数据集的问题。官方的 mmseg 版 MaskCLIP 在 VOC 201220类上跑得相当好mIoU 49.49%没有任何一个类别零分所有20类都有显著的 IoU。这说明 mmseg 实现本身是正常工作的。
返回列表