ARTICLE DETAIL

资讯详情

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

CLIP原理详解:对比学习、双塔结构、零样本分类与多模态应用实践

CLIP原理详解:对比学习、双塔结构、零样本分类与多模态应用实践 这几年多模态模型火得不行从GPT-4V到各种图文大模型背后绕不开的一个名字就是CLIP。这篇论文全称是《Learning Transferable Visual Models From Natural Language Supervision》出自OpenAI发表在ICML 2021可以说是多模态领域的里程碑。现在做多模态相关的同学十个里有九个都得把CLIP的论文和代码吃透。我最初接触CLIP是想解决一个商品图文检索的需求后来一步步把对比学习、双塔结构、零样本分类这些概念都啃了一遍又把CLIP接到过Stable Diffusion的生态里做调试期间踩了不少坑。这篇文章就把我理解的CLIP原理、复现细节和落地经验一次性讲清楚适合刚入门多模态的新手也适合已经做相关项目但想补原理底子的朋友。1. CLIP是什么一张图加一句话打通视觉和文本的“翻译官”1.1 论文的核心idea用对比学习把图片和文字拉到同一个空间CLIP最核心的思路说起来其实很朴素不再用人工标注的类别标签去训练图像分类模型而是直接用自然语言描述作为监督信号让模型学会判断“哪张图片跟哪句话是匹配的”。这个改变看起来很轻实际上把视觉任务的边界彻底打开了。以前的分类模型比如ResNet在ImageNet上训练类别是1000个固定的标签训练完就只能在这1000类里打转换个场景就得重新标注数据重新训练。CLIP换了个玩法它学习的是图片和文本之间的对应关系而文本本身是开放的所以下游任务里只要把类别名称转成文字描述模型就能直接做零样本预测。这里有个生活化的类比CLIP就像是一个语言翻译官——但不是翻中文英文而是翻译“图像的语言”和“文本的语言”。它通过看海量的图文对学会了“一只橘色的猫坐在沙发上”这句话对应的图像大概长什么样于是下次给它一张新图它就能在候选的几句文本里挑出最匹配的那句。训练方式上CLIP用的是对比学习。一个batch里同时进来一批图片和一批文本模型要把“配对的图文”拉到向量空间里相近的位置同时把“不配对的图文”推开。这个思路后来被几乎所有多模态模型继承包括现在各种多模态大模型在预训练阶段基本都跑不掉对比学习这一步。1.2 为什么这个思路能成立从“固定标签”到“自然语言描述”要理解CLIP为什么强得先看它解决了什么痛点。传统监督学习的问题在于标签空间是封闭的模型只能说“是或不是”某个预设类别而自然语言的表达空间几乎是无限的。CLIP的训练目标是“图文匹配”实际上是在学习一个跨模态的语义对齐空间。在这个空间里猫的图片、猫这个词的文本、甚至“一只正在打哈欠的猫”这样的长句都会被映射到相近的位置。论文里的关键实验也证明了这一点CLIP在ImageNet上做零样本分类不做任何微调直接测试Top-1准确率能达到76.2%。这个数字已经超过了当年很多有监督训练的小模型。虽然跟最好的有监督模型比如EfficientNet系列能到85%以上比还有差距但关键在于它完全没有用过ImageNet的训练数据这种泛化能力才是跨模态预训练最大的价值。也正是因为这一点后来很多多模态大模型都沿用了CLIP的思路要么拿CLIP当视觉编码器要么借鉴它的对比学习范式。日常生活里做一个类比CLIP像一个见多识广的图书管理员他虽然没专门背过某本书但你给他一段文字描述他能凭自己读过的海量书籍从书架上把最匹配的那本挑出来。这就是“零样本”能力的来源——见过足够多、足够多样的图文对自然就能泛化到没见过的类别上。2. 核心技术拆解双塔架构、对比损失和数据配方2.1 双塔结构Image Encoder Text EncoderCLIP的整体结构用一句话概括就是“双塔”一个图像编码器一个文本编码器两者各自把输入映射到一个共同的多模态嵌入空间。图像编码器可以选择ResNet或者ViT文本编码器用的是Transformer。论文里做了大量消融实验结论是ViT作为图像编码器时效果更好尤其是ViT-L/14这个配置在ImageNet零样本准确率上比ResNet版本高出不少。工程上大家默认用ViT-B/32或者ViT-B/16作为性价比之选ViT-L/14用来冲效果。这里顺便说一句很多人在网上搜“resnet预训练模型”的时候会发现ResNet系在CLIP里也有对应版本RN50、RN101只是相比之下ViT家族在复杂语义对齐上优势明显所以后来社区基本都转向ViT了。训练的时候一个batch里包含N对图片文本图像编码器和文本编码器分别对图片和文本做编码得到两个N×d的特征矩阵然后计算两两之间的相似度通常用cosine similarity形成一个N×N的相似度矩阵。对角线上是正样本对非对角线上是负样本对。CLIP用的对比损失InfoNCE本质上就是让对角线上的相似度尽量大让非对角线上的相似度尽量小。论文里还加了一个可学习的temperature参数来缩放logits这个参数的初值和上下界设置会直接影响训练稳定性后面讲实操的时候我再细说。双塔结构的优势在于模态独立、索引容易。实际落地时你可以提前把几十万张图片全部编码成向量存进向量数据库线上查询时只需要对输入的文本做一次编码然后去数据库里做最近邻搜索即可。这比传统的“每来一个查询就要跑一遍模型”的方案在工程上高效得多。2.2 对比损失函数InfoNCE是怎么计算的InfoNCE在CLIP里的具体形式可以直接写成下面这样。对于图像侧的损失第i个样本的损失是L_i -log( exp(sim(i,i)/tau) / sum_j exp(sim(i,j)/tau) )其中sim(i,j)是第i张图片和第j句文本的相似度tau是temperature。整个batch的损失是图像侧和文本侧两个方向loss的平均。为什么要算两个方向因为对称的对比损失能让模型同时学到“根据图片找文本”和“根据文本找图片”的能力这也是CLIP在图文检索任务上两个方向都能用的原因。如果用伪代码表示大概是这样# features: (N, d) 已经做了L2归一化 # logits: (N, N)对角线为正样本 logits image_features text_features.T / temperature labels torch.arange(N) loss_img cross_entropy(logits, labels) # 图片 - 文本 loss_text cross_entropy(logits.T, labels) # 文本 - 图片 loss (loss_img loss_text) / 2这里有个关键观察一个batch里有多少个负样本直接决定对比学习的难度。CLIP原论文把batch size开到了32768就是因为负样本数量越大模型越能学到精细的语义边界。如果batch size只有64或者128你会发现训练出来的模型区分力很差很容易把所有东西都混在一起。2.3 数据配方WIT-400M和训练细节CLIP论文里最被低估的部分其实是数据。WITWebImageText数据集包含大约4亿个图文对是从互联网上抓取的质量参差不齐但胜在规模大、覆盖面广。论文作者专门提到数据规模和数据多样性对CLIP的效果提升贡献很大甚至比模型结构选择的影响更大。做多模态的朋友应该把这句话记下来多模态模型的上限很大程度上由图文对数据的质量和规模决定。训练细节上CLIP用了32K的大batch size训练了32个epoch这跟传统对比学习模型的通用做法一致——batch size越大负样本越多对比学习的效果越好。同时CLIP还用了混合精度训练、AdamW优化器、余弦学习率调度。论文里也提到了一个非常工程化的细节训练初期如果temperature初始值设置不合理模型很容易崩溃或退化。原实现里把temperature初始化为0.07并且做了上限裁剪比如限制到100这个细节看似不起眼实际上对训练稳定性影响很大。我自己复现时在这个参数上栽过跟头后面踩坑部分再展开。WIT数据集的另一个特点是它包含了很多“噪声”图文对比如网页上图片旁边的文字不一定描述的就是图片内容。论文研究发现适当增加数据规模可以抵消一部分噪声的影响这给我们的启示是与其花大量精力清洗数据到完美不如先把规模做上去再辅助一些简单的过滤策略比如去重、短文本过滤性价比往往更高。2.4 一个关键trickPrompt Engineering与EnsembleCLIP论文里非常有意思的一个细节是Prompt Engineering。因为CLIP是拿自然语言做监督的文本提示的写法会直接影响零样本分类的准确率。比如在ImageNet上如果只用“a photo of a {label}”作为模板输入准确率会低于用精心设计的多个模板做集成后的结果。论文发现换成“a photo of a {label}, a type of pet”这种更具描述性的模板对细粒度类别有明显提升。如果再构造一组模板比如“a centered satellite photo of {label}”“a photo of a {label}”等等把多个提示的预测结果做平均Ensemble效果还能再稳一截。这里的关键在于文本端的小改动会改变文本特征在整个语义空间中的分布所以想用好CLIP提示词工程不能省。这个道理放到后面的Stable Diffusion生态里也成立。ComfyUI里那个“CLIP Text Encode”节点本质上就是调用CLIP的文本编码器把用户写的提示词转成条件向量。很多人在SD里觉得“写长句比堆关键词效果好”根源就在CLIP的语义对齐是面向完整自然语言的拼凑式的标签反而不容易落到理想的语义位置。3. 实操细节环境搭建、推理和微调3.1 模型选型和环境配置实操的第一步是选模型。OpenAI开源了多个规格的CLIP权重RN50、RN101、ViT-B/32、ViT-B/16、ViT-L/14、ViT-L/14336px等。选型主要看你的任务和资源如果是做简单的零样本分类实验ViT-B/32就够用显存占用小、推理快如果是做检索或者作为下游模型的视觉编码器我推荐ViT-B/16精度和速度比较平衡如果资源充足且需要最好的效果再考虑ViT-L/14。在环境方面直接用HuggingFace的transformers库和open_clip_pytorch都可以。我个人更习惯用open_clip_pytorch因为它的训练和评测工具链更完整也保留了OpenAI原始实现的很多细节。pip install open_clip_torch另外提醒一句如果你用的是ComfyUI、InvokeAI这类图形化工具它们内部已经打包好了CLIP权重不需要自己额外管理模型文件。但如果你想在自定义脚本里调用还是建议自己下载权重并按需缓存。3.2 推理示例零样本图像分类下面给一个可以直接跑的推理示例用的是open_clip库。这段代码做的事情很简单加载ViT-B/32权重读一张图准备三个文本候选然后计算图片和文本的相似度。import torch import open_clip from PIL import Image model, _, preprocess open_clip.create_model_and_transforms( ViT-B/32, pretrainedopenai ) tokenizer open_clip.get_tokenizer(ViT-B/32) image preprocess(Image.open(cat.jpg)).unsqueeze(0) text tokenizer([a photo of a cat, a photo of a dog, a photo of a car]) with torch.no_grad(): image_features model.encode_image(image) text_features model.encode_text(text) image_features / image_features.norm(dim-1, keepdimTrue) text_features / text_features.norm(dim-1, keepdimTrue) similarity (image_features text_features.T).softmax(dim-1) print(similarity)我建议在推理前一定要做特征归一化否则cosine相似度算出来意义不明确Softmax后的概率分布也没有可比性。这个细节很多人会漏掉但它是CLIP使用中的基本规范。实际项目里你往往不是只处理一张图而是要对整个商品库离线编码这个时候就要考虑用batch推理加向量数据库存储而不是每来一次请求都重新跑一遍图像编码器。3.3 CLIP微调的几种常见路子CLIP微调大概是提问率最高的话题。常见的几种方式有全量微调效果最好但成本最高容易在中小数据集上过拟合一般需要较大数据和较长训练时间。Linear Probe冻结CLIP的特征提取部分只训练一个线性分类头适合样本量较少的下游任务收敛快、不容易过拟合。LoRA微调在CLIP各层注入低秩矩阵用很少的参数就能达到接近全量微调的效果是目前社区最流行的方案。Prompt TuningCoOp/CoCoOp在输入文本端学习连续向量形式的prompt而不是修改模型权重训练成本极低但效果上限受限于文本编码器容量。这里单独说一下全量微调的一个经验教训CLIP的底层图像特征本身已经很通用如果你在下游数据集上做全量微调很容易出现灾难性遗忘——也就是说模型在目标任务上表现变好的同时zero-shot能力退化得很厉害。所以除非你的数据和计算资源都非常充足否则我不建议一上来就全量微调。LoRA在这个问题上就友好很多因为它只更新低秩分支CLIP主干的特征空间不会发生剧烈变化。3.4 计算资源和显存占用下面整理了一个实用的显存和场景参考表数值是我在单卡A100和RTX 3090上实测的大致水平不同环境会有些浮动但可以作为预算估算依据。模型参数量推理显存(FP16)适合场景RN50约1.02亿约1GB轻量检索、移动端ViT-B/32约1.51亿约1-2GB快速实验、零样本分类ViT-B/16约1.51亿约2-3GB通用检索、特征提取ViT-L/14约4.28亿约6-8GB高精度任务、微调基线ViT-L/14336px约4.28亿约8-10GB最优效果、资源充足如果你的显存吃紧一个常用技巧是切分推理把图像分成若干batch逐批编码然后拼接特征。另一个技巧是全程使用FP16视觉模型对低精度不太敏感CLIP的FP16推理质量下降很小基本可以忽略。微调时再叠加梯度累积和混合精度就能在24GB显存上跑ViT-L/14级别的LoRA训练。4. 应用场景从检索到生成CLIP的生态外延4.1 图文检索和商品多模态支持CLIP最直接的应用是图文检索。电商场景下用户输入一句描述就能找到对应商品图片或者用一张参考图去找相似商品CLIP都能胜任。我做过一个商品检索项目用CLIP把商品图和商家写的描述文本映射到同一个空间线上检索时先对全部商品图做embedding存到向量数据库查询时直接算相似度效果比纯视觉特征检索好很多。关键在于CLIP的多模态对齐能力能理解“红色圆领短袖棉质T恤”和图片之间的语义关系。这种方案的工程实现模式已经很成熟了离线阶段把商品库的图片通过CLIP图像编码器转成向量存入支持向量检索的数据库比如FAISS、Milvus、Qdrant都可以在线阶段把用户的文本查询通过CLIP文本编码器转成查询向量然后在向量库中做近邻搜索。整个流程里CLIP只需要参与编码线上推理压力很小支撑百万级商品库也没问题。商品场景还有一个附加好处CLIP天然支持“以图搜文”。用户上传一张参考图你可以反向找到最匹配的商品文案这个能力在电商平台做相似推荐时非常实用。多模态统一处理的优势在这里体现得很明显——不需要为不同模态分别建索引所有东西都在同一个向量空间里比较维护成本低了很多。4.2 与Stable Diffusion / ComfyUI的生态结合CLIP在生成模型里的角色也很关键。Stable Diffusion里那个把文本变成条件向量的Text Encoder用的就是CLIP的文本编码器。很多人在ComfyUI里会遇到CLIP Text Encode节点它本质就是在调用CLIP的文本编码器来生成prompt对应的条件向量。理解CLIP以后你会更容易明白为什么SD里的提示词讲究“自然语言描述”而不是标签堆砌因为CLIP本身就倾向于理解完整的句子结构。我实际用ComfyUI调图时发现一个现象当你写一段细节丰富的长句时生成结果往往比堆几十个关键词更稳定。这背后就是CLIP文本编码器在起作用——它把整句话作为一个整体映射到语义空间而不是把每个词拆开单独理解。反过来如果你在SD里写了一句逻辑混乱的描述比如“一只猫在天空飞翔的背景是海滩”CLIP的文本端特征可能会出现语义冲突最终生成的图就会“四不像”。所以用好CLIP的文本编码器直接决定了你在生成类应用里的出图质量。4.3 目标检测、视频理解等方向的扩展CLIP本身不做目标检测但可以和外接检测头结合做开放词汇检测。常见的做法是使用类似Grounding DINO的架构把CLIP的文本编码器输出的类别描述特征和检测框内的视觉特征做匹配从而检测训练时没见过的类别。视频理解方向也有团队用它做零样本动作识别把帧序列的视觉特征和动作描述文本配对。这类扩展本质上没有改变CLIP的“图文匹配”能力而是把CLIP当作一个通用的视觉-语义特征提取器来用。比较典型的应用是“YOLO目标检测多模态AI分析”的智慧交通方案先用YOLO定位目标区域再用CLIP或类CLIP模型对检测目标做属性描述和场景语义匹配。CLIP在这里负责的是“理解”部分它能把“白色轿车”“正在左转”这类自然语言描述和剪裁出的目标图像做匹配从而输出比固定类别标签更丰富的结构化信息。不过要注意CLIP的开放词汇能力只体现在“匹配已知名称”上它不会被训练来“生成”文本。真正要生成自然语言描述还需要再接一个语言模型实现“以图生成文”CLIP更适合做中间的特征桥接。4.4 多模态统一处理与AGI的探索从更宏观的视角看CLIP奠定了“多模态统一处理”的范式不同模态的数据通过对比学习投射到同一个语义空间这是通向多模态AGI的重要一步。现阶段的很多多模态大模型在做视觉编码时用的依然是CLIP或CLIP的变体。理解CLIP其实就是理解多模态大模型的基石之一。有一个很直观的例子GPT-4V这类模型在做图文对话时视觉部分需要把图像转成token序列给语言模型用。这个“转”的过程通常离不开CLIP或其衍生模型的支持。所以当你看到“多模态大模型”、“多模态统一处理”、“多模态AGI”这些词频繁出现时底层技术的根子都扎在CLIP开辟的这条路上。于此同时多模态融合算法也在不断发展比如用注意力机制把多个模态的特征更细粒度地融合或者在CLIP基础上引入区域级别的对齐如GLIP、Florence等。但不管怎么演进CLIP提出的“图文对比对齐”依然是这些工作的起点。5. 常见问题与踩坑实录5.1 训练不收敛或效果差怎么排查如果你自己训练或微调CLIP最常见的问题是“loss降不下去”或“zero-shot效果很拉”。我排查过几次基本都出在下面几个环节。第一batch size太小。对比学习的负样本数量依赖于batch size如果你只有64或者128模型几乎学不到有区分度的特征。一个临时解决办法是使用梯度累积让有效batch size达到512以上虽然不能完全替代大batch但效果会比直接跑小batch好很多。第二temperature初始值不合理。CLIP里的temperature初始化为0.07然后作为可学习参数训练。如果你把初始值设置太大logits会被压缩得很小Softmax分布接近均匀梯度信号会很弱如果太小模型又容易在训练初期出现振荡。直接照抄论文的0.07一般没错。第三数据预处理不一致。CLIP训练时图像会被缩放和裁剪到224x224文本会被截断到77个token。如果你在下游任务里忘了截断或者用了一个超长文本文本编码器会静默地给你一个截断后的特征结果自然不会好。这个坑非常隐蔽。5.2 显存不足和数据加载瓶颈训练CLIP时显存不足是家常便饭我自己在24GB显存下训练ViT-L/14时主要靠三招解决混合精度AMP能把显存占用直接砍半梯度累积能把有效batch size撑大激活重计算则用时间换空间。三者配合24GB基本能跑起ViT-L/14级别的训练。数据加载是另一个容易忽略的瓶颈。CLIP的数据集动辄上千万图文对如果还用普通的Dataset类和PIL一张张读图训练速度会被IO拖死。社区通用的做法是用WebDataset或FFCV这类流式数据加载工具直接把数据打包成tar或者二进制格式训练时边读边解码吞吐量能提升好几倍。如果你只是做中小规模微调也要注意把图片预处理和tensor转换放到GPU之前完成不要让CPU解码成为训练循环的短板。5.3 Prompt写不好影响精度的案例很多人用CLIP做零样本分类发现效果不如预期第一个想到的就是“模型不行”但往往是Prompt没写好。举个例子我做过一个细粒度鸟类分类实验直接用“a photo of a {species}”做模板准确率大约在43%左右把模板改成“a photo of a {species}, a type of bird”后准确率提升到了51%。后面我又在模板里加入拍摄场景的提示比如“a photo of a {species} in the wild”准确率又往上走了几个点。这个实验充分说明CLIP对“类别词周围的上下文”非常敏感。类别词前面加什么主语、后面加什么描述都会改变文本特征在语义空间中的位置。建议的做法是参考CLIP论文附录里的模板集合针对你的领域构造5到10个候选模板做集成而不是用一个通用模板走天下。5.4 CLIP的限制为什么不擅长细粒度分类和计数最后必须说清楚CLIP的能力边界否则你会踩很多预期管理的坑。CLIP本质上是一个“宽泛的语义匹配器”它擅长区分“猫”“狗”“汽车”这种大类但在区分两个长得极其相似的鸟类品种时可能还不如一个专门训练的细粒度分类模型。原因在于它的训练数据来自网页图文对图文对天然偏向粗粒度描述模型没有见过足够多“极其相似但不同类”的样本。CLIP对计数任务也不擅长。你问它“图里有几只鸟”它往往会给出一个和视觉内容关系不大的答案。空间关系理解同样薄弱比如“椅子右边的那只猫”CLIP能理解“猫”和“椅子”但“右边”这种细粒度空间关系就很难从全局特征里提取出来。我实际测试过CLIP在位置描述类的图文匹配上准确率并不高。所以在真正的业务里CLIP更适合做召回、粗筛和候选生成精细的判别工作还需要搭配专门的检测器、分割模型或微调后的分类头。写在最后我的一点实操体会做个简单收尾。我个人在实际项目中用得最多的组合是CLIP ViT-B/16做基础特征提取配合OpenAI的预训练权重下游任务先跑Linear Probe建立基线再视情况升级到LoRA微调。这样既不会动辄烧掉大把训练资源又能在多数任务上拿到可复现的结果。如果你也打算入坑多模态建议先从CLIP的推理代码跑通开始然后构造一个自己的图文匹配小实验把对比损失的计算流程亲手实现一遍再去读论文的其他细节。最后分享一个小技巧把CLIP文本端的输出特征和图像端的输出特征各存一份用中间的特征向量做聚类分析往往能帮你直观理解模型的语义边界在哪里这个习惯我一直用到现在。
返回列表