模态对齐投影层 本节摘要:视觉编码器产图像 token,文本解码器吃文本 token,两者活在不同的向量空间。小两层 MLP 把图像 token 投进文本嵌入空间,余弦对齐损失对配对字幕把两空间拉成一致。那个投影是视觉语言模型最小的件,也是迁移上最要紧的件。视觉编码器(第 5657 节)产维 的 token;文本解码器要嵌维 (任何其他数同样合理),解码器期望文本形 token。图像 token 不是文本形——它们活在编码器在视觉预训时学的基里,与解码器的词向量无关系。两层 MLP 投影(linear, GELU, linear)桥接。它小到(约 参数)几分钟单 GPU 训完,且是唯一在对齐阶段要学的件——视觉编码器冻、文本嵌入表冻,只有投影动。
本节摘要:视觉编码器产图像 token,文本解码器吃文本 token,两者活在不同的向量空间。小两层 MLP 把图像 token 投进文本嵌入空间,余弦对齐损失对配对字幕把两空间拉成一致。那个投影是视觉语言模型最小的件,也是迁移上最要紧的件。视觉编码器(第 56~57 节)产维
vision_hidden=768的 token;文本解码器要嵌维text_hidden=512(任何其他数同样合理),解码器期望文本形 token。图像 token 不是文本形——它们活在编码器在视觉预训时学的基里,与解码器的词向量无关系。两层 MLP 投影(linear, GELU, linear)桥接。它小到(约768*1024+1024*512=1.3M参数)几分钟单 GPU 训完,且是唯一在对齐阶段要学的件——视觉编码器冻、文本嵌入表冻,只有投影动。这是 LLaVA 2023 发的配方,BLIP-2 重框为 Q-Former,2023 后每个开源 VLM 以某种形式采纳。
对应原课程:Phase 19 · Lesson 60 ·
projection-layer-modality-align(原英文phases/19-capstone-projects/60-projection-layer-modality-align/docs/en.md)。本节属「多模态/VLM」赛道第三节。
阅读完本节,你应当能够:
你有视觉编码器(第 56~57 节)产维 vision_hidden=768 的 token;你要接的文本解码器嵌维 text_hidden=512。解码器期望文本形 token,图像 token 不是文本形——它们活在编码器在视觉预训时学的基里,与解码器的词向量无关系。
视觉编码器发 197 token。文本侧有单字幕级嵌入。要对齐两者你需每样本一个图像级向量。CLS 池最简:取编码器首 token 投影。197 token 均值池是另一选项,SigLIP 用它。两者都把 197 向量降到一。
单线性投影能旋转与重缩但不能在两空间有曲率不匹配时修基。两线性层间 GELU 给投影一个非线性弯,经验上够把 CLIP 风格特征对齐到语言模型嵌入。更深投影(LLaVA-NeXT 用 GLU;Qwen-VL 用注意力层栈)是扩展;两层 MLP 是规范基线,BLIP-2 的 Q-Former 投影头底层用它。
| 层 | 形状 | 参数 |
|---|---|---|
| fc1 | (vision_hidden, projection_hidden) |
768*1024 + 1024 |
| 激活 | GELU | 0 |
| fc2 | (projection_hidden, text_hidden) |
1024*512 + 512 |
768 -> 1024 -> 512 头约 1.3M 参数。
对齐不意味 image_emb == text_emb。对齐意味 image_emb 在联合空间与 text_emb 指同向。余弦损失 1 - cos_sim(image, text),范围 0(完美对齐)到 2(反向)。训练每对推此向零。第 60 节推广到对比批(InfoNCE),每图须比自己字幕近任何他字幕;本节用每对版使动力学可见。
视觉编码器 86M 参数,文本表再几百万。全从 mock 语料训它们行不通。冻两者意味投影的 1.3M 参数是唯一变的件,合成对上几百步够把损失拉下。这正是每个适配器 VLM 的操作形状:重件冻,轻桥训。
code/main.py 实现:
MLPProjector(in_dim, hidden_dim, out_dim):两层 linear MLP 带 GELU。MockTextEmbedding(vocab_size, dim):冻嵌入表,种子确定性初始化。make_pair(seed, vocab_size):合成一配对(图像,字幕)样本,字幕是短 id 序列,字幕嵌入是 token 嵌入均值池。cosine_alignment_loss(image_emb, text_emb):每对 1 - cos_sim 目标。class MLPProjector(nn.Module): def __init__(self, in_dim=768, hidden_dim=1024, out_dim=512): self.fc1 = nn.Linear(in_dim, hidden_dim) self.act = nn.GELU() self.fc2 = nn.Linear(hidden_dim, out_dim) def forward(self, x): return self.fc2(self.act(self.fc1(x))) # 768 -> 1024 -> 512 def cosine_alignment_loss(image_emb, text_emb): return 1 - F.cosine_similarity(image_emb, text_emb).mean() def train(projector, encoder, text_table, pairs, steps=200): for p in encoder.parameters(): p.requires_grad = False # 冻视觉 text_table.weight.requires_grad = False # 冻文本表 opt = torch.optim.Adam(projector.parameters(), lr=1e-3) # 只训投影 for s in range(steps): img, cap = pairs[s % len(pairs)] with torch.no_grad(): img_tokens, cls = encoder(img) img_emb = projector(cls) # CLS 池 -> 投影 txt_emb = text_table(cap).mean(dim=1) # 字幕均值池 loss = cosine_alignment_loss(img_emb, txt_emb) loss.backward(); opt.step(); opt.zero_grad()
code/test_main.py 覆盖:投影器输出形状匹配配置 out_dim;冻文本表零 requires_grad 参数;余弦损失在相同向量为零、反平行向量为二;投影器一次反向后梯度流;训练循环在步 0 与步 200 间降损失。
设计要点:对齐是方向非等值——余弦损失只管方向不管量级,故训练后投影器权重可任意缩放不损对齐(余弦尺度不变)。冻编码器与文本表是诀窍——86M + 几百万冻住,只 1.3M 投影动,几百步够。CLS 池把 197 token 降到一(均值池替选,SigLIP 用)。两层 MLP 给一非线性弯,经验够对齐 CLIP 特征到 LM 嵌入;单线性在曲率不匹配时不够,更深是扩展。
同模式在每个开源 VLM 出现:LLaVA 1.5(两层 GELU MLP 从 CLIP-ViT-L 隐到 LLaMA 嵌维,冻视觉、冻 LLM、只训投影,阶段二再解冻 LLM);BLIP-2(Q-Former 取 32 学习查询 token 经对图像 token 的交叉注意力,再投到 LM 嵌维,Q-Former 末的投影头是本节 MLP 的类比);MiniGPT-4(单线性从 BLIP-2 Q-Former 输出到 Vicuna 嵌维);Qwen-VL(几层交叉注意力适配器,末件仍是投到 LM 嵌维的投影)。形状变但角色同:池图像 token、投到文本嵌维、单独训。本节手写让你看清投影的非线性弯、余弦损失的方向性、冻编码器的操作形状——这些在用 LLaVA/timm 时是黑盒。
code/main.py + code/test_main.py。demo 在 CPU 上几十秒跑完:训练报从初损约 1.07 降到约 0.80(200 步内),证投影单独能把图像 token 拉向文本空间,打印每对终余弦相似度。MLPProjector 与 cosine_alignment_loss 可独立复用——任何「冻编码器 + 轻桥」适配器场景。第 60 节把每对余弦推广到对比批 InfoNCE。
cos/tau),观察 tau 太小(梯度噪)或太大(损失高原高)时如何。下一节,我们做「交叉注意力融合」——文本查询、视觉键值回答,每文本 token 能注意每 patch token 实现词到区域的定位。