☰
BLIP模型核心解析:CLS token与MED架构的协同机制
2026/10/16 11:50:03 网站建设 项目流程

如果你在复现BLIP的时候仔细读过那段模型结构图,估计会对“CLS → Encoder → Decoder”这条链路印象很深。三个词看似平平无奇,但恰恰是理解这篇论文为什么能同时把“理解”和“生成”两类任务揉进同一个框架里的钥匙。这篇我打算直接从一个复现代码的人视角,把CLS这个token在三种不同分支里的真实行为、Encoder和Decoder的关注力差异、以及三个损失函数怎么把这一套结构训起来这件事掰开揉碎讲清楚。

1. 从任务割裂到统一:BLIP想解决的那个别扭问题

先花点篇幅把BLIP出场时的背景说清楚,因为它决定了一切结构设计的动机。

1.1 当时的视觉语言预训练是什么状态

在BLIP之前,视觉语言预训练(VLP)大体分两派。一派做“理解型”任务,比如图文检索、图文匹配这类,模型主干是Encoder架构,输入图文再输出特征或得分;另一派做“生成型”任务,比如图像描述、视觉问答里的开放生成,模型主干是Decoder架构,输入视觉特征再自回归吐出文本token。

问题在于,这两派模型的骨干网络不通用。理解派模型迁移到生成任务效果很差,因为它的训练目标里没有生成能力;生成派模型反过来做理解,特征表达又不够细粒度。而且当时很多方法在预训练阶段用了公开爬取的图文对,数据里存在大量噪声——文本和图像相关度低的样本会让模型学到错误的对齐信号,导致下游效果进一步打折。

所谓统一,说穿了就三件事:模型结构能不能同时输出“用于区分的特征”和“用于生成的token”;训练目标能不能把对比学习、匹配判别、语言建模放在一个模型里一起优化;数据质量能不能在训练过程中自我校正。BLIP当时踩的是一个很实在的坑——别的模型总是告诉你“在这个任务上我们赢了”,却没告诉你换个任务你得把网络拆掉重拼。BLIP想做到的是拆掉重拼的成本为零。

1.2 MED架构的出现:一个模型,三种形态

BLIP给出的方案是MED,全称Multimodal Mixture of Encoder-Decoder,多模态混合编解码结构。它同时包含三种分支:单模态编码器分支、图像文本联合编码器分支、图像文本联合解码器分支。这三个分支共享一部分权重,而不是各干各的独立网络。

这个设计最微妙的一点是,它用“共享Transformer层”的方式,让同一个模型既有Encoder的双向注意力来提取全局语义,又有Decoder的单向注意力来生成文本。论文里最核心的那张架构图,画的就是这三个分支如何在同一个主干上做切换。而贯穿这三个分支的一个关键符号,就是CLS。

很多人读论文时把CLS当成ViT里常见的那个分类token,觉得“哦,就是加在序列开头做个特征汇聚嘛”。但如果你去看实现代码和训练逻辑,会对CLS有更复杂的认知:它在三个分支中扮演的角色、传递的信息粒度、以及它在每个分支里要不要被丢弃,都是有讲究的。在动手写代码前,我建议先把这三个词固定下来:CLS是“信息汇聚点”,Encoder是“交互加工区”,Decoder是“逐步生成器”。整篇论文和技术实现都在围绕这三者的协作关系展开。

2. CLS的三种角色:一个token在三个分支里完全不重样

下面进入正题,从代码视角逐一拆解CLS在BLIP三个分支中的真实行为。这是我认为理解BLIP结构最关键的环节,也是很多复现代码时最容易被带偏的地方。

2.1 视觉编码器分支:CLS是图像全局特征的出口

BLIP的视觉编码器用的是ViT或者ResNet作为backbone。以ViT为例,输入图像会先切成patch,然后线性映射成patch embedding,再加一个可学习的CLS token拼在序列最前面,一起送入标准的Transformer Encoder层。

在这个分支里,CLS不参与具体的局部patch语义判断,它只是每层Transformer通过自注意力不断聚合所有patch token信息的容器。到最后一层,CLS对应位置的输出向量就是整张图像的全局表示。BLIP在做ITC(Image-Text Contrastive)损失计算时,正是从视觉编码器取出这个CLS向量,和文本编码器输出的文本表示做对比学习,拉近匹配图文对的距离,推远不匹配对。

这里有一个容易忽略的实现细节:如果你用同样一份ViT权重去做所谓的“共享视觉主干”,那在计算ITC损失时,视觉CLS一定不能直接拿去做下游分类任务。它训练时被拉向“与文本全局特征对齐”的子空间,和纯视觉自监督训练得到的CLS含义不同。BLIP作者在论文里提到通过加权token交互来增强表征,实际代码中常表现为对CLS输出做额外的LayerNorm和线性投影后再进对比损失,这个投影是不参与下游生成的。从工程角度看,这个CLS更像一个专用的“对比学习接头”。

2.2 图文联合编码器分支:CLS是匹配二分类的判官

第二个分支,图像与文本拼接后一起进入Encoder。结构上就是把图像特征序列和文本token序列在序列维度拼起来,在文本开头放一个独立的CLS token,一起经过交叉注意力层做双向交互。

这个CLS和视觉编码器里的那个CLS不是同一个,至少输入来源不同。联合编码器里的CLS更像“融合特征的总调度”。论文中的ITM(Image-Text Matching)任务就是从这个分支的CLS输出接一个二分类头,判断输入的图文对是否匹配。

如果你复现时打印CLS的输出维度,会发现一个问题:如果图像端64个patch,文本端有20个token,那拼接后的序列长度是85(含CLS)。这个CLS要在双向注意力里看到图像patch和文本token的全部内容,才能做出“匹配/不匹配”的二分类判断。实际操作中,ITM损失用的是Hard Negative Mining策略,会刻意构造“难负例”让CLS去硬扛。所以这个分支的CLS,本质上是整个ED中训练难度最大的角色。

2.3 图文联合解码器分支:CLS在这里基本不干活

到了第三个分支,视觉特征作为cross-attention的KV输入,文本token序列在Decoder里做自回归生成。这时候序列开头通常也会放一个[BOS]或CLS,但由于解码器用的是带因果掩码的自注意力,序列开头token的注意力范围受限,它的表征质量并不高。

真正用于逐个token预测的核心,是Decoder对每个目标token位置的输出。也就是说,在Decoder分支里,CLS更像是“占位符”,负责启动生成流程,而不是生成质量的关键。如果你把BLIP的LM(Language Modeling)损失实现打开看,会发现计算loss时通常是忽略序列开头这个token的,它的隐状态不会作为预测目标。理解这一点很解压:同一个词根CLS,在不同形态的Transformer中承担的任务天差地别。

很多文章讲到BLIP只说“CLS是分类token”,这种简化放在联合编码器分支勉强成立,放在解码器分支就站不住脚了。这也是我从代码阅读中得到的一个强烈体会:永远先看某个token在具体分支中的掩码范围,再判断它的作用。

3. Encoder侧的发生过程:双向注意力如何让图文真正交互

理解了CLS的三重身份,我们再往细看Encoder侧到底怎么做特征交互。BLIP不只是一个结构创新,它在输入构造和注意力机制上也做了很多嵌入式细节。

3.1 输入构造:图像patch和文本token怎么拼在一起

代码层面,一张图会被resize到固定分辨率,比如224x224,然后切成14x14的patch。对ViT-B/16来说,序列长度是196,每个patch映射为768维向量,加上位置编码。文本侧,通过WordPiece或BPE分词得到token序列,也映射到相同维度。

在联合编码器分支,它们会拼成一个长序列:[CLS, 图patch1, 图patch2, ..., 图patchN, 文token1, 文token2, ...]。Transformer自注意力在这个长序列上任意交互,图像token能看到文本token,文本token也能看到图像token,这就是双向注意力。

有一个重要的点,就是图片patch这侧通常会额外加一个模态类型嵌入,文本token也加对应的模态类型嵌入,以便Transformer区分当前token来自哪种模态。虽然ViT本身已经有位置编码了,但模态嵌入能让模型更快学到差异。BLIP的代码中,embedding层包含token embedding、位置embedding、模态embedding三部分,缺一个都会让训练收敛明显变慢。这是很多复现版和原版指标差距大的常见原因,不是模型改坏了,是输入构造少了东西。

3.2 自注意力里的“跨模态流动”如何被建模

联合编码器里的交互不靠额外模块,靠的是纯自注意力。每个图像patch可以attend到所有其他patch和所有文本token,文本token同样如此。这种信任机制在大模型时代我们说过很多次,就是让两个模态的信息在同一个特征空间做soft对齐。

但BLIP相比早期多模态模型不一样的地方,在于它的联合编码器分支不是从零训练的,而是用预训练的ViT和BERT做初始化,再在图文数据上微调。所以它的收敛速度和最终效果,在早期VLP工作里都属于第一梯队。尤其实操时的体会是:初始化用ViT-B/16和BERT-Base,对比从头训练,在图文检索的R@1上大概能高出四五个点。这说明跨模态交互很吃单模态初始化的底子。

3.3 为什么联合编码器要保留完整双向注意力

部分方法为了加快跨模态对齐,会在浅层用单模态编码、深层再拼起来做交互。BLIP选择从头到尾都保持双向全交互,好处是语义融合充分,坏处是计算量大。论文里给的解释是,统一特征提取和交互对于同时做理解和生成才能保持一致性。我的看法是,在预训练阶段数据规模还不足以支撑深层分离式设计时,全交互是稳妥的选择。

实际操作中,如果你考虑并行效率和显存,会发现图像token数196加上文本token几十个,对Transformer来说并不算大负担。ViT-B的序列长度在224分辨率下就是几十万次乘加运算,完全可接受。真正不满意的点在于物体多、图像细节多的场景,196个patch不够细,BLIP后来的一些优化工作也往高分辨率继续卷了。这就是后话了。

4. Decoder侧的自回归生成:为什么CLS留在编码器,解码器靠shifted embedding

理解与生成的区别在于,Decoder的目标是给定视觉信息,逐词生成文本。它不能让当前词看到未来词,所以必须用因果掩码。本节的要点是:为什么解码器里不依赖CLS来启动预测,以及代码里到底怎么处理生成序列。

4.1 因果掩码和cross-attention的组合逻辑

BLIP的Decoder分支,每个解码层包含两部分注意力:先是自注意力,mask成因果形式,只看自己和之前的token;然后是cross-attention,Q来自解码器当前token,KV来自视觉编码器的输出序列。这样每个文本token在生成时都能融入图像语义,但文本侧不泄漏未来信息。

如果你看过实现代码,会看到Decoder分支的forward里,视觉特征通过一个cross-attention层插入,而不是像Fusion Encoder那样直接用双向注意力混合。这是“Encode”和“Decode”最本质的分界线:Encode是双向全交互,Decode是单向生成式交互。

4.2 所谓的shifted embedding在做什么

文本生成任务里常见一种处理:训练时输入序列整体右移一位作为decoder输入,让当前位置的输入是前一个真实token,把当前位置的真实token作为预测目标。这就是shifted embedding的由来。

BLIP的LM损失就沿用了这种做法。训练时,输入到解码器的文本序列是右移后的真值token(比如“a cat on the grass”变成“[BOS] a cat on the grass”作为输入,目标是“a cat on the grass [EOS]”)。推理时,每步用已生成token序列作为输入,采样下一个token,再把新token拼回输入序列。

这解释了为什么在实践中,如果你把CLS token硬塞进解码器的输入开头,然后不加右移,效果会大幅下降。解码器是严格按时间顺序来的,开头位置放CLS但预测目标却是第二个token,等于强制模型“预测自己已经看到的东西”,训练与推理不一致。BLIP的实现里严格遵循右移逻辑,CLS只作为BOS占位,不承担语义判断。如果你复现时loss掉不下去,先检查你的输入是否右移了。

4.3 预训练阶段的LM目标:真是“懂图像才能生成”的试金石

Decoder的LM损失本质是一个标准语言模型目标,只不过条件是图像特征。这就迫使模型不能只做检索式匹配,而是要真正“理解”图像内容并组织成通顺描述。BLIP把这种能力直接用于下游图像描述生成任务,效果在相当长时间里都是SOTA级别的。

我自己的复现体感是,visual feature质量直接决定生成上限。如果你只做了ITC/ITM训练,没有单独把LM训练到位,生成文本会很生硬、丢主语或者瞎编属性。BLIP把这三个损失放在一起训练,形成结构上的互相促进:ITC让特征对齐,ITM让特征能区分,LM让特征能描述。三个目标解决了三个不同维度的能力,这也是后面五个损失函数协同的铺垫。

5. 三个损失函数怎么让CLS、Encoder、Decoder协同收敛

BLIP的核心结构跑通之后,训练目标的重要性就浮出来了。我见过不少朋友把MED结构搭得很像那么回事,但loss怎么配比、什么时候冻结哪个分支,完全是凭感觉。这一节从ITC、ITM、LM各自的角色和联动逻辑说起。

5.1 ITC:拉近匹配特征,推远不匹配特征

ITC即Image-Text Contrastive Loss,做法和CLIP一致,从视觉编码器取CLS,从文本编码器取文本CLS(或最后一层平均),计算两者cosine相似度,再用InfoNCE损失拉近正样本、推远负样本。BLIP的做法是在batch内构造负样本,比如一个batch有32个图文对,那么某张图的负样本就是其他31个文本。

一般认为ITC的梯度会同时更新图像编码器和文本编码器,让它们学会把共享语义映射到同一个子空间。BLIP在实现中为了过滤数据噪声,还会计算一个相似度分数来对样本加权。这种加权不是直接丢弃低分样本,而是让低分样本的梯度贡献更小,相当于是软性去噪。

5.2 ITM:训练一个更严格的匹配判别器

ITM是二分类任务,输入是联合编码器分支的完整图文拼接,CLS输出过一个线性层,输出匹配概率。这个任务比ITC更难,因为它要求模型基于细粒度的token级交互来判断匹配,而不是仅凭全局特征相似。

值得一提的Hard Negative Mining:构造负样本对时,不是随便拉一个不匹配的文本,而是选那些与当前图有部分共享语义但整体不搭配的文本作为负例。比如某张图上有“一只猫和一只狗”,硬负例会选描述道“只有一只猫”的文本。通过这种负例,CLS才能学会抓住细微差别,匹配判别的鲁棒性才会强。

5.3 LM:把语义对齐变成生成能力

LM是Decoder分支的autoregressive loss,在每个step计算交叉熵,优化的是给定图像条件下生成正确文本的概率。它与ITC/ITM的区别是,后面两者做的是判别式任务,LM是生成式任务,它需要模型真的把图像信息组织成语言输出。

这三个loss在BLIP里是相加的,原文中LM的权重通常会设置得比ITC/ITM略高一些,具体数值可以在一些公开配置里看到。一个合理的解释是,生成任务对模型容量的要求最高,如果权重太低,模型倾向走捷径只做检索式匹配,不学真实生成。实践上,如果发现生成描述重复度高或空泛,把LM权重适当加大是一个直接有效的调法。

5.4 三个损失如何反向协同:同一套CLS,不同梯度流

很多第一次接触BLIP的人会有疑问:同一个视觉编码器CLS,同时被ITC拉向对齐、被ITM作为匹配判据、被Decoder的cross-attention作为KV来源,这三个梯度会不会打架?

实测下来基本不会,因为ITC是全局对比,ITM是配对判别,LM是生成,三个损失在特征空间优化的方向并不冲突。ITC提供一个松散的对齐先验,ITM收紧判别边界,LM迫使表征信息完备。这个结构到今天还被许多工作沿用,不是偶然的。

6. 复现BLIP实战:源码阅读顺序和注意力掩码避坑清单

最后这部分,我想把代码实践里踩过的坑整理一下,帮助读者从“看懂论文”过渡到“跑通代码”。

6.1 建议的github源码阅读顺序

拿到一份BLIP实现后的推荐路径是:先看懂config文件和模型初始化逻辑,再找visual_encoder的定义,搞清楚ViT和文本编码器分别是什么结构;然后看forward_image_text函数,注意自注意力掩码的类型;最后再去看loss计算部分。

如果一开始就一头扎进train函数看数据加载和优化器,容易迷失。真正信息密度最大的函数就是forward_text、forward_image、forward_image_text和forward_image_text_decoder。把这四个函数对照三幅结构图看,逻辑会清晰很多。

6.2 注意力掩码:最容易出错且最难排查的地方

我说一个真实经历。有次复现BLIP,发现预训练loss在迭代了一两万步后突然上涨,训练震荡剧烈。我查遍了学习率、数据顺序,都没找到问题。最后逐层对比才知道是在Decoder分支的cross-attention里,我无意间用了全连接mask而不是因果mask,导致当前token看到了未来token的信息。

生成任务中这种泄漏的后果是:训练阶段模型看起来很会“预测”,但验证时因无法看到未来而性能暴跌。排查方式其实很简单,自己写个小样本测试case,打印每一步生成的结果,看是否存在提前输出后续内容的迹象。出现这种情况基本就是mask配错了。BLIP对self-attention和cross-attention分别维护不同的mask,前者是下三角,后者是全1矩阵,千万别为了省事共用一套。

6.3 数据加载时的文本清洗与长度截断

影响复现效果的另一大因素是文本数据质量。BLIP训练用的图文对文本需要做标准化:去掉多余空格、统一小写、处理特殊字符。文本最大长度一般截断到30个token或35个token,太长的描述尾部噪声很大,Decoder预测起来也吃力。

在数据加载时实现随机采样负样本的难度也不低。ITM负样本要按批次做shuffle,还需要排除正样本索引,否则会出现“标签错了”的漏洞,直接影响ITC和ITM的稳定性。很多复现版本跑出来结果偏低,本质上不是结构问题,而是负样本采样没做好。

6.4 训练资源与收敛观察的体感

BLIP的常见配置是:batch_size 64或128,训练数据集规模大概在千万级图文对。我自己在单机多卡跑的时候,显存主要消耗在视觉编码器分支和联合编码器分支,预训练初期ITC和LM会先下降,ITM会在负样本构造得好时快速收敛,之后再缓慢下降。如果ITM一直在0.5附近徘徊,先检查负样本逻辑。

如果资源有限,可以先固定视觉编码器、只训文本侧和联合交互层,等ITM收敛到一定水平再解冻视觉侧。这样能显著减少显存开销,但会牺牲最终效果上限,属于“实验跑通”和“完整复现”的折中选择。做迁移学习时,这种渐进解冻策略反而更稳,具体可以根据你的设备来权衡。

收尾的一个实战建议

如果你准备上手复现BLIP,我建议先把前三张结构图配着四个forward函数过一遍再碰训练脚本,别看一行代码就先跑。CLS在不同分支的角色差异、Decoder的因果掩码、三个loss的梯度流,这三个点想通了,BLIP的80%内容就抓住了。之后想往ImageCaptioning、VQA、图文检索这些下游应用走,也基本是在这套骨架上换任务头的事。BLIP不是一个“多任务硬拼”的方法,它的功夫在于让理解与生成两个方向共享一套参数还能互不干扰,这一点从结构到loss都值得反复琢磨。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询