ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

ReCLIP原理与复现:组合式零样本学习如何让CLIP识别‘红色苹果’

2026/9/4 16:46:47 拓冰建站 浏览量
ReCLIP原理与复现:组合式零样本学习如何让CLIP识别‘红色苹果’ 当业务场景从“给一张图打上类别标签”升级为“判断一个物体的颜色、材质、形状等组合属性”时常规分类模型往往力不从心。ReCLIP 这类基于 CLIP 的项目之所以受到关注是因为它把研究重心放到了“组合式零样本学习”上也就是在训练阶段没有见过的属性-物体组合上做分类。这篇文章会围绕 ReCLIP 的原理分析与上手复现展开先从组合式零样本学习的定义说起再拆解负传播和区域注释这两项关键技术最后给出可参考的代码框架和调参建议。如果读者已经接触过 OpenAI CLIP但一直不太清楚什么叫“组合稀疏性”或者希望了解怎么让 CLIP 从“能认出苹果”进阶到“能认出红色的苹果与绿色的苹果”这篇文章会很合适。整篇不会只停留在名词解释层面而是尽量把数据准备、候选构造、推理打分、注意力图获取和常见异常都串起来。1. 组合式零样本学习ReCLIP 要解决的问题1.1 从“红色番茄”这个组合说起传统图像分类面临一个常见的困境训练集里有“红色番茄”图片里也出现“红色番茄”时模型可以做得很好但是当测试集出现“黄色番茄”时模型如果没有见过这种颜色组合就很难做出正确判断。这类问题被称为组合式零样本学习英文全称是 Compositional Zero-Shot Learning简称 CZSL。它把视觉概念拆成两个维度来理解一个是属性例如“红色”“金属”“木质”另一个是物体例如“苹果”“杯子”“汽车”。一个组合标签可以写成“属性 物体”的形式。在训练阶段模型只能看到部分属性-物体组合。在测试阶段模型需要泛化到未出现过的属性-物体对。比如训练集可能有“红色苹果”和“绿色叶子”但没有“绿色苹果”。如果模型真的学会了“绿色”这个属性那么面对一张绿色苹果的图就应该能够推测出它属于“绿色苹果”而不是简单粗暴地把它分类成“叶子”。1.2 为什么普通分类模型很难做好 CZSL普通分类模型通常会给“红色苹果”一个独立类别。如果训练集只有一百个属性、一千个物体理论上组合空间就有十万种。绝大部分组合在训练阶段根本不会出现所以“给每个组合分配一个类别编号”的做法几乎不可行。更关键的一点是属性本身是跨物体共享的。“红色”可以出现在红色汽车、红色番茄、红色杯子上物体本身又承载了多种属性。“红色”和“番茄”之间不是独立互斥的关系而是存在一种可组合结构。因此单纯用视觉特征映射到一个固定分类向量很难把属性从物体特征中分离出来。ReCLIP 项目所关心的问题就是这个组合空间训练样本稀疏、难以穷举的问题。它的核心思路不是重新设计一个复杂的分类头而是利用 CLIP 文本分支里蕴含的丰富语义把“属性”和“物体”之间的组合规则显式引入推理过程。1.3 CZSL 的两个评估设置要理解 ReCLIP 的产出还需要区分两个高频出现的评估场景。第一个叫 closed-world 设置测试时只考虑那些属于未见组合的标签换句话说已知候选集合里不会混入大量无关组合。第二个叫 open-world 设置测试时要考虑全部属性-物体组合包括很多在训练阶段没出现过的组合甚至可能包含不符合物理常识的组合例如“方形橘子”难度明显更高。在实际论文和开源仓库中MIT-States、UT-Zappos、C-GQA 是三个最常用的数据集。MIT-States 数据量适中属性与物体类别丰富UT-Zappos 以鞋子图像为主属性更多是颜色、材质、样式C-GQA 是一个规模较大的组合数据集组合空间更大也更接近真实开放场景。2. 从 CLIP 到 ReCLIP为什么直接使用 CLIP 还不够2.1 CLIP 零样本分类的核心逻辑CLIP 由图像编码器和文本编码器组成。训练时模型在海量图文对上学习对比学习目标让匹配的图片和文本在共享嵌入空间中距离更近不匹配的图片和文本距离更远。做零样本分类时CLIP 会把所有候选类别名称转成句子再用文本编码器编码。例如候选类别是“dog”和“cat”可以构造“a photo of a dog”和“a photo of a cat”。同时图片经过图像编码器得到特征。最后计算图片特征和所有文本特征的余弦相似度相似度最高的类别就是预测结果。用一段常见的代码来表示就是下面的样子import torch import clip from PIL import Image device cuda if torch.cuda.is_available() else cpu model, preprocess clip.load(ViT-B/32, devicedevice) image preprocess(Image.open(test.jpg)).unsqueeze(0).to(device) texts clip.tokenize([ a photo of a dog, a photo of a cat, a photo of a bird ]).to(device) with torch.no_grad(): image_features model.encode_image(image) text_features model.encode_text(texts) image_features image_features / image_features.norm(dim-1, keepdimTrue) text_features text_features / text_features.norm(dim-1, keepdimTrue) logit_scale model.logit_scale.exp() logits logit_scale * image_features text_features.t() probs logits.softmax(dim-1) print(probs)CLIP 的优势是文本分支可以直接描述一个由多个单词组合成的概念。比如“a photo of a red apple”文本编码器并不是简单地把“red”和“apple”的特征向量相加而是能够将整句话编码成一个具有组合语义的向量。2.2 直接迁移到 CZSL 的问题既然 CLIP 能理解“red apple”那把“red”“apple”两两组合成文本再与图像特征比对是不是就足够解决组合式零样本分类了实验结果表明并非如此。原因是 CLIP 的图像编码器和文本编码器之间存在一定偏差。文本分支对组合语义的理解更充分但图像分支往往把主要注意力放在“这是什么物体”上对颜色、材质、形状等细粒度属性不够敏感。举个例子图里有一个红色番茄。CLIP 的图像编码器很擅长抓住“番茄”这个主体但红色所占的像素区域可能被全局池化过程稀释。直接计算图像特征与“a photo of a red tomato”的文本特征相似度很可能得到中等分数甚至会低于模型预测出的“a photo of a tomato”。也就是说组合信息虽然存在于文本空间中但在图像特征中没有得到充分激活。这就为 ReCLIP 留下了优化空间。ReCLIP 的核心假设是CLIP 的文本空间已经具备组合知识不需要重新训练大规模分类器只需要在推理阶段设计更好的组合打分方式并且对图像特征做轻量修正。2.3 ReCLIP 的两大改进方向ReCLIP 的改进可以概括为两条线索。第一条是负传播主要解决图像特征中“属性信息被物体信息遮盖”的问题。第二条是区域注释主要解决全局图像特征无法体现局部属性细节的问题。这两个方向并不是脱离 CLIP 结构重新发明轮子而是尽量以最小成本把 CLIP 变成更强的组合式零样本分类器。正因如此ReCLIP 即使在零样本、无训练微调的情况下也能得到不错的基线效果这也是它在社区里受到关注的重要原因。3. ReCLIP 方法拆解负传播与区域注释3.1 基本打分思路把候选组合变成提示句在实现 ReCLIP 时最基础的工作是把属性集合和物体集合做笛卡尔积生成候选组合。比如有 100 个属性、200 个物体就会生成 2 万个候选组合。如果直接构造 2 万条文本再分别和一张图片求相似度在 CLIP 文本编码器上仍然可行但需要注意内存管理。ReCLIP 在设计提示语时不会只使用单一模板。因为 CLIP 的性能高度依赖提示语格式单一模板在不同数据集上可能有偏。常见的做法是同时使用多个模板并对多个模板的输出做平均或加权。比如a photo of a {attr} {obj}a {attr} {obj} in a photoan {attr} {obj}{attr} {obj} with a clean background每一条模板都对应一个文本嵌入。得到多个候选分数后再通过聚合函数得到最终分数。这里有一个容易理解偏差的地方CLIP 的文本编码器不是把所有单词的 embedding 简单求平均而是通过 Transformer 对整句话建模。因此“a red tomato”和单独编码“red”与“tomato”后相加结果并不一样。ReCLIP 实际使用中通常以整句文本为主属性单词和物体单词只作为辅助特征。3.2 负传播让组合评分学习“拒绝错误组合”负传播是 ReCLIP 方法名字里最吸引人的部分也是理解门槛最高的部分。直接看图分类时模型只需要回答“这是什么物体”但在 CZSL 中模型需要同时回答“属性是什么”和“物体是什么”。如果模型只能识别物体但完全忽略属性它就会把红色番茄和黄色番茄看作同一个类别。负传播的核心思路是在推理阶段除了给正样本组合高分数之外还要主动让图像特征在某些负样本组合上降低分数。比如模型识别出图片中的物体很可能是“番茄”那么对“红色番茄”的分数应该高于“绿色番茄”。如果 CLIP 默认把绿色和番茄关联得更强那么负传播就会利用属性与物体之间的不匹配关系给出一个梯度更新信号。这个操作不是对模型权重做梯度更新而是像一个“测试时优化模块”。在推理时通过候选组合的文本特征和当前图片特征计算损失然后让图片的输入特征或中间特征沿损失下降的方向做少量修正让后续打分对属性更加敏感。如果只用一句话总结负传播就是不是让模型死记“红色番茄”这个组合而是让图像特征在“番茄”这一物体条件下关注属性和物体之间的一致性。这比强制模型记住更多组合标签要灵活得多。3.3 区域注释局部特征解决全局信息的属性丢失负传播解决的是属性权重偏低的问题而区域注释解决的是属性可能只出现在局部区域的问题。如果模型只看整张图片的全局特征那么一个小区域的“银色”或“破损”很可能被大面积背景淹没。ReCLIP 的区域注释会借助视觉 Transformer 内部的注意力信息找到图片中真正承载属性判别的若干局部区域。常见做法是从 CLIP 视觉编码器的某一层或多层提取注意力图然后根据注意力图对 patch token 做加权得到一组区域特征。属性分类打分时候选属性文本可以与这些区域特征计算相似度物体分类打分时仍然可以使用全局特征。这样一来图片特征不再只有一个全局向量而是由“全局特征 区域特征”共同组成。属性文本匹配区域特征物体文本匹配全局特征分工明确。需要说明的是不同开源仓库在实现区域注释时细节并不完全相同。有的直接使用最后一层多头注意力的均值有的会综合多层注意力有的还会加入一定空间平滑。实际使用时需要根据项目代码和 CLIP 的骨干网络进行调整。3.4 推理流程整体串联把前面几个模块串起来ReCLIP 的一次完整推理大致是下面这样的流程读取一张图像使用 CLIP 预处理器转为模型输入。提取图像全局特征并额外提取注意力图或区域特征。使用多个文本模板构造属性-物体候选文本集。对候选文本集编码得到文本特征。结合全局特征和区域特征计算每个候选组合的初始分数。进入负传播优化阶段用正负组合差异构造损失对图片相关特征做少量更新。重新计算所有候选组合分数输出 Top-K 结果。这样做的好处是整个过程不需要重新训练 CLIP不会引入大量额外参数。即使没有大规模 GPU 集群也能在单卡环境下完成测试。这也是 ReCLIP 在生产或研究项目中受欢迎的原因。4. 环境准备与数据说明4.1 基础环境依赖ReCLIP 是基于 PyTorch 和 OpenAI CLIP 的视觉语言项目。环境准备可以按下面的依赖关系来理解。运行环境推荐使用 Linux 或 Windows 的 Python 环境Python 3.8 以上通常问题不大。深度学习框架方面PyTorch 是核心版本需要与 CUDA 驱动匹配。由于 CLIP 是一个比较轻量的模型常见显卡例如 1080Ti、2080Ti、3090、A100 都可以运行只是测试速度差异较大。模型依赖方面至少需要安装 OpenAI 开源的 CLIP 库并配合 torchvision、Pillow、numpy 等常用库。安装命令可以参考如下pip install ftfy regex tqdm torch torchvision pip install githttps://github.com/openai/CLIP.git如果用户只需要快速体验 CLIP 本身也可以直接使用 Hugging Face Transformers 版本。但 ReCLIP 项目的很多代码细节与 OpenAI CLIP 的原始实现有关特别是注意力图获取方式因此建议先按照 OpenAI CLIP 库来复现。4.2 数据集如何选不同数据集对属性、物体的定义差异很大准备方式也不同。这里不打算给出一个统一的下载命令因为项目版本变化很快更推荐先阅读仓库 README 中关于数据集的说明。MIT-States 是一个比较经典的 CZSL 数据集包含大量日常属性和物体图像多为现实照片。数据集中需要重点关注训练、验证、测试划分方式尤其是哪些组合属于 seen哪些属于 unseen。如果数据集划分不统一复现结果会产生明显偏差。UT-Zappos 主要由鞋子图像构成类别比 MIT-States 少但属性更细腻适合调试属性识别逻辑。C-GQA 规模较大组合标签覆盖很广对 baseline 有更高要求也更适合评估模型的真实开放世界表现。下载数据集时需要注意版权和学术引用规范。不少视觉数据集只允许用于研究不能直接用于商业项目。在复现仓库时最好保留原始数据文件的目录结构避免因为路径不一致导致数据加载失败。4.3 CLIP 权重离线加载有些实验环境无法直接访问外网下载 CLIP 预训练权重。OpenAI 提供的 CLIP 权重默认会缓存在本地目录。常见的缓存路径是用户的~/.cache/clip文件夹。如果离线环境已经拿到了权重文件可以手动把权重放到该目录再执行clip.load(ViT-B/32)。代码里通常不需要修改模型名只需要保证缓存文件存在。如果离线环境没有缓存目录CLIP 库会自动创建目录并尝试下载。若下载失败可以检查路径权限、磁盘空间和网络连通性。# 查看权重缓存目录的一般位置 ls -la ~/.cache/clip不同版本 CLIP 可能使用不同缓存策略。最稳妥的做法是让项目依赖固定版本的 OpenAI CLIP以保证推理结果可复现。5. 一个可运行的 ReCLIP 推理框架5.1 构造属性与物体候选集为了让文章不只停留在原理层面下面给出一个可以运行的代码示例。示例并不会完全复刻 ReCLIP 论文里的全部数学细节而是把推理链路中最关键的部分提取出来方便读者理解每一步的输入输出。首先是定义候选标签和文本模板。假定属性集合是[red, green, wooden]物体集合是[apple, tomato, bowl]。我们需要把它们两两组合。attributes [red, green, wooden] objects [apple, tomato, bowl] templates [ a photo of a {attr} {obj}, a {attr} {obj} in a photo, an {attr} {obj} ] labels [] texts [] for a in attributes: for o in objects: labels.append((a, o)) pair_texts [t.format(attra, objo) for t in templates] texts.extend(pair_texts) print(labels) print(texts)这段代码会产生 9 个候选组合每个组合对应 3 条文本提示。后面计算相似度时一种简单的做法是先求 3 条文本嵌入的平均再用平均嵌入与图片特征比较。5.2 加载模型和图片接下来加载 CLIP 模型和图片。为了让示例可以执行需要准备一张测试图片比如一张番茄照片。import torch import clip from PIL import Image device cuda if torch.cuda.is_available() else cpu model, preprocess clip.load(ViT-B/32, devicedevice) image_path example_tomato.jpg image preprocess(Image.open(image_path)).unsqueeze(0).to(device) with torch.no_grad(): image_features model.encode_image(image) image_features image_features / image_features.norm(dim-1, keepdimTrue)这里使用ViT-B/32是因为它在显存占用和效果之间比较均衡。如果显存有限可以换RN50如果追求更强的效果可以使用ViT-L/14。5.3 组合文本编码与打分对候选文本编码时需要注意 batch size。当属性和物体数量很大时候选文本可能达到几十万条一次性编码容易导致显存溢出。这里先演示批量编码并打分的基本思路。def encode_prompts(prompts, batch_size256): features [] for i in range(0, len(prompts), batch_size): batch prompts[i: i batch_size] tokens clip.tokenize(batch).to(device) with torch.no_grad(): batch_feat model.encode_text(tokens) batch_feat batch_feat / batch_feat.norm(dim-1, keepdimTrue) features.append(batch_feat) return torch.cat(features, dim0) text_features encode_prompts(texts) text_features text_features.view(len(labels), len(templates), -1) # 简单起见先对同一组合的多模板特征取平均 text_features text_features.mean(dim1) text_features text_features / text_features.norm(dim-1, keepdimTrue) logit_scale model.logit_scale.exp() logits logit_scale * image_features text_features.t() probs logits.softmax(dim-1).cpu().squeeze(0) rank sorted(zip(labels, probs.tolist()), keylambda x: x[1], reverseTrue) for label, prob in rank[:5]: print(label, round(prob, 4))注意这里先对多模板取平均是一种简化策略。实际 ReCLIP 项目中可能使用更复杂的文本特征融合方式例如把属性文本、物体文本和组合文本分别编码后再做插值。但整体思想是一致的从不同提示角度打分再综合排序。5.4 如何引入区域注释在官方 CLIP 实现中如果想获取注意力图通常需要对模型代码做少量修改。最直接的方式是给视觉 Transformer 的注意力层挂上一个 hook 或修改 forward 函数把计算得到的注意力权重保存下来。下面是思路层面的示例并不保证在最新版 CLIP 中直接通过默认 API 运行因为不同版本对注意力层的封装有所差异。attention_maps [] def save_attention(module, input, output): # 通常需要从 Attention 模块内部拿到注意力矩阵 # 在不同 CLIP 版本中实现位置不同 if hasattr(module, attn_weights): attention_maps.append(module.attn_weights.detach())实际运行时更稳妥的方法是修改 OpenAI CLIP 源码中Attention.forward在_attn计算完成后把注意力权重保存下来。得到注意力图后就可以对 patch token 做加权池化。举例来说如果最后一层注意力图显示图的右下角与物体区域重合那么模型在识别“红色”属性时可以重点关注右下角对应的 patch 特征而不是平均所有 patch 特征。区域注释的具体权重计算在不同实现中存在差异。有的方法会把注意力图 resize 到原图大小再做空间加权有的方法直接对 patch token 做 top-k 选择。读者在复现时应该优先阅读仓库源码中的model_utils.py或clip_utils.py这类文件。5.5 负传播的工程表达负传播在工程实现上更像一个小型的可优化模块。它不是端到端训练而是在测试阶段对某张图像的特征进行迭代更新。假设当前图片的全局特征为image_features候选组合文本特征为text_features。模型先计算出所有组合分数。负传播阶段要构造一个优化目标让正样本组合的分数尽量高让负样本组合的分数尽量低。这里的“正负”取决于某张图片的候选物体。一个简化后的伪代码如下target_text text_features[target_index].unsqueeze(0) negative_text text_features[negative_index].unsqueeze(0) # 用正负样本差异约束当前图像特征 loss -torch.cosine_similarity(image_feature, target_text, dim-1).mean() loss loss torch.cosine_similarity(image_feature, negative_text, dim-1).mean() # 仅更新图像特征不更新模型 optimizer torch.optim.SGD([image_feature], lr0.01) for _ in range(10): optimizer.zero_grad() loss.backward() optimizer.step() # 每次更新后重新归一化可以保持数值稳定 image_feature.data image_feature.data / image_feature.data.norm(dim-1, keepdimTrue)这段代码只用于表达负传播的大致思想不能直接作为最终可运行方案。因为真实场景中负样本的选择、损失函数的具体形式、梯度更新层的位置都需要根据论文和仓库实现调整。但它可以解释为什么 ReCLIP 虽被称为零样本方法却能做到比普通 CLIP 更强的组合区分能力它使用一小步测试时优化把模型对物体显著性的偏好推回到更均衡的状态。5.6 输出结果与可视化ReCLIP 项目最终通常会输出一个 JSON 或 CSV 文件记录每张图片的预测标签和置信度。除了标准 Top-1 准确率外还可以做错误案例可视化。比如把预测错误但置信度很高的图片单独保存可以帮助判断是属性识别错误还是物体识别错误。一个常见现象是模型识别物体的准确率远高于属性。遇到这种情况不必急着修改整个模型可以先检查文本模板中属性词的语义是否容易被 CLIP 理解。例如有些属性本身是“相对属性”像“大”和“小”它们必须依赖物体尺寸才能判断这类属性在零样本设置下会比颜色属性更难识别。6. 评测指标与实验配置建议6.1 评估指标ReCLIP 这类 CZSL 项目中最常见的评估指标是 Top-1 准确率。在 closed-world 设置下候选集合已经提前限制因此 Top-1 可以直接反映模型在受限组合空间的分类能力。在 open-world 设置下候选集合包含全部组合除 Top-1 外还会关注 Top-5、Top-10 等排序指标。更严格地说CZSL 论文中通常还要求模型同时输出准确的属性标签和物体标签。即使属性识别错误只要物体识别正确也会被算作整体失败。因此看指标时要把属性准确率和物体准确率分开分析这对改进模型很有帮助。6.2 对比实验怎么设计如果想做对比实验建议至少设置下面几条基线原始 CLIP 提示分类直接把每个属性-物体组合构造成文本不做负传播不做区域注释。CLIP zero-shot 模板集成使用多条文本模板并集成观察提示工程带来的提升。ReCLIP 全局特征版本只使用负传播不加入区域注释。ReCLIP 完整版本同时使用负传播和区域注释。这样拆分以后能够更清楚地看到每个模块带来的增益。如果只看完整模型和原始 CLIP 的差异很难判断到底是负传播起了作用还是区域注释起了作用。有一个容易被忽略