查看全课大纲(第 36 / 40 节)
- 1.从规则到学习:深度学习解决什么问题
- 2.表示学习与端到端训练
- 3.张量、计算图与自动微分
- 4.训练三要素:数据、模型、目标函数
- 5.从感知机到多层感知机
- 6.反向传播:手推一个两层网络
- 7.激活函数与初始化
- 8.优化器:SGD → Momentum → Adam
- 9.正则化:权重衰减、Dropout、BN/LN
- 10.卷积的直觉与参数共享
- 11.池化、感受野与下采样
- 12.经典网络演进:LeNet → AlexNet → VGG → ResNet
- 13.残差连接与归一化为什么有效
- 14.迁移学习与特征可视化
- 15.RNN 与 BPTT
- 16.LSTM/GRU:门控如何缓解梯度问题
- 17.Seq2Seq 与编码器-解码器
- 18.注意力前史:Bahdanau 加性注意力
- 19.Self-Attention:QKV 与缩放点积
- 20.多头注意力:并行的多种关系
- 21.位置编码:正弦与可学习
- 22.残差、LayerNorm 与前馈层
- 23.从零实现一个最小 Transformer block
- 24.训练稳定性、复杂度与常见误区
- 25.自监督与语言建模目标
- 26.BERT:掩码语言模型与双向编码
- 27.GPT:自回归生成与缩放定律
- 28.指令微调与对齐:SFT → RLHF/DPO
- 29.参数高效微调:LoRA 与适配器
- 30.自编码器与 VAE
- 31.GAN:对抗训练与模式崩塌
- 32.扩散模型:前向加噪与反向去噪
- 33.扩散模型的应用与采样加速
- 34.对比学习与 CLIP
- 35.ViT:把图像当序列
- 36.多模态架构:对齐、融合与生成
- 37.能力边界、幻觉与评测
- 38.PyTorch 2.x 训练循环与混合精度
- 39.实验管理、评测与复现
- 40.推理部署与成本:量化/蒸馏概览
多模态架构:对齐、融合与生成
小象实战讲义 · 深度学习基础:从神经网络到 Transformer
第 8 章前两节分别看了 CLIP 的对比对齐与 ViT 的图像编码,这一节把两者拼起来:多模态架构到底怎么处理“对齐”与“融合”?本节是第 8 章《多模态与前沿》的第三节,以 CLIP 和 BLIP-2 为例,讲清共享语义空间怎么建、轻量适配器怎么把图像接到冻结的大语言模型上。
先修:Transformer 编码器(第 5 章)、预训练语言模型(第 6 章)、图像特征提取(第 3 章卷积网络)。 后续:下一节讨论这类架构的能力边界、幻觉与评测方法。
💡 核心导读
- 多模态的核心挑战是“对齐”:如何让模型学会“猫”的文本描述与一张猫的图片在语义上对应。
- CLIP 的“对比学习”范式:通过海量(图像,文本)对,训练模型将匹配的图文对拉近,不匹配的推远,从而学习到一个共享的语义空间。
- BLIP-2 的“轻量适配”策略:为了高效利用现有大模型,它冻结预训练的图像编码器和大语言模型(LLM),仅训练一个轻量的“查询变换器”(Q-Former)作为桥梁。
- 从“对齐”到“生成”:对齐学习到的联合表示,可以直接用于检索、分类(零样本),也可以作为条件输入给 LLM,实现可控的图文生成。
🧭 前置知识
- Transformer 编码器:理解自注意力机制和编码器的输出形式。
- 交叉熵损失与 softmax:用于计算对比学习中的相似度得分和损失。
- 向量的点积与余弦相似度:理解
torch.matmul和F.cosine_similarity的基本计算。 - 大语言模型(LLM)的基本原理:知道 LLM 以文本标记序列为输入,进行自回归生成。(不展开,见第 6、7 章)
什么是对比学习?CLIP 的图文对齐机制
在传统监督学习中,图像分类模型学习将图片映射到固定类别(如“猫”“狗”);自然语言则提供了更丰富、更灵活的监督信号。CLIP(Contrastive Language-Image Pre-training) 的核心思想是:不预测标签,而是判断一个图像和一段文本是否匹配。
CLIP 的训练目标:一个批次里有 N 对(图像,文本),模型要把第 i 张图与第 i 段文本对上,同时与其余 N-1 段区分开。本质是图文匹配,用对比学习实现。
具体来说,CLIP 包含两个独立的编码器:
- 图像编码器:可以是 ResNet 或 Vision Transformer (ViT),将图像
I编码为特征向量I_embed。 - 文本编码器:可以是 Transformer,将文本
T编码为特征向量T_embed。
训练时,对特征进行 L2 归一化,然后计算所有图像特征与所有文本特征的余弦相似度矩阵(即点积)。对角线上的元素是正样本对的相似度,其他是非匹配对的相似度。损失函数鼓励对角线相似度高,非对角线相似度低。
下面我们用代码模拟一个小批次的 CLIP 对比学习前向计算过程:
import torch
import torch.nn.functional as F
torch.manual_seed(0) # 固定随机种子,确保结果可复现
# 模拟一个批次的数据:batch_size = 2, 特征维度 d_model = 16
batch_size = 2
d_model = 16
# 1. 模拟图像编码器和文本编码器的输出(已归一化)
# 假设编码器已经将输出归一化到单位球面上
image_features = F.normalize(torch.randn(batch_size, d_model), dim=-1) # 形状 (2, 16)
text_features = F.normalize(torch.randn(batch_size, d_model), dim=-1) # 形状 (2, 16)
print("图像特征(前3维):", [[round(v.item(), 4) for v in vec[:3]] for vec in image_features])
print("文本特征(前3维):", [[round(v.item(), 4) for v in vec[:3]] for vec in text_features])
# 2. 计算图像-文本相似度矩阵(余弦相似度 = 归一化后的点积)
# logits_per_image: 每张图像与所有文本的相似度,形状 (batch_size, batch_size)
logits_per_image = image_features @ text_features.T # 矩阵乘法
print("\n图像-文本相似度矩阵 logits_per_image:")
for row in logits_per_image:
print([round(x.item(), 4) for x in row])
# 3. 计算对比损失(以图像侧为例)
# 目标标签是对角线位置 [0, 1, ..., batch_size-1]
labels = torch.arange(batch_size)
print("\n目标标签(图像索引):", labels.tolist())
# 使用交叉熵损失,将相似度矩阵视为 logits
loss_i = F.cross_entropy(logits_per_image, labels)
print("图像侧对比损失 loss_i:", round(loss_i.item(), 4))
# 对称地,也可以计算文本侧的损失(每段文本与所有图像的相似度)
logits_per_text = text_features @ image_features.T
loss_t = F.cross_entropy(logits_per_text, labels)
print("文本侧对比损失 loss_t:", round(loss_t.item(), 4))
# CLIP 的总损失是两侧损失的平均
total_loss = (loss_i + loss_t) / 2
print("总对比损失 total_loss:", round(total_loss.item(), 4))图像特征(前3维): [[-0.3077, -0.3149, -0.0685], [-0.3103, -0.389, 0.13]]
文本特征(前3维): [[-0.1738, 0.0089, -0.1395], [-0.1341, 0.1739, 0.2102]]
图像-文本相似度矩阵 logits_per_image:
[0.041, 0.0308]
[0.3396, -0.118]
目标标签(图像索引): [0, 1]
图像侧对比损失 loss_i: 0.818
文本侧对比损失 loss_t: 0.8119
总对比损失 total_loss: 0.815关键解读:
logits_per_image[0,0] = 0.041是第 0 张图与第 0 段文本的相似度(正样本)。logits_per_image[0,1] = 0.0308是第 0 张图与第 1 段文本的相似度(负样本)。这里负样本只比正样本低一点,说明随机初始化下模型还没把两者分开,所以损失停在log(2)≈0.693附近(实测 0.818)。- 训练过程就是通过反向传播调整两个编码器,使对角线元素远高于同行/同列的其他元素。
通过在海量图文对上这样训练,两个编码器被投射到同一个共享语义空间,语义相近的图文对距离很近。
零样本推理:训练完成后做“狗 vs 猫”分类,无需微调——把类别名套进提示模板(如“一张狗的照片”),用文本编码器得到特征,再与图像特征算相似度,取最高者作为预测。这就是 CLIP 零样本能力的来源。
如何高效融合?BLIP-2 的轻量级适配器设计
CLIP 实现了对齐,但它是“编码-检索”范式。如果我们希望模型能根据图像生成自然语言描述(如图像描述、视觉问答),就需要一个生成式架构。直接端到端训练一个庞大的多模态生成模型成本极高。BLIP-2(Li et al., 2023,arXiv:2301.12597)提出了一种高效策略:冻结强大的预训练图像编码器(如 ViT)和大语言模型(LLM),只训练一个轻量的中间模块——查询变换器(Q-Former) 来桥接两者。Q-Former 总参数量仅 188M,却让 BLIP-2 在零样本 VQAv2 上比 Flamingo80B 高 8.7 个点,可训练参数还少 54 倍。
Q-Former 的架构与两阶段训练
Q-Former 是一个轻量 Transformer,核心是一组可学习的查询向量:它们通过交叉注意力从冻结的图像编码器输出里取视觉信息,再通过自注意力与文本交互。
第一阶段:视觉-语言表示学习(冻结图像编码器) 目标:让查询向量学会从图像中提取与文本最相关的信息。 训练时使用三种损失(与 BLIP 类似):
- 图像-文本对比损失(ITC):对齐查询聚合的视觉特征与文本特征。
- 图像-文本匹配损失(ITM):二分类任务,判断查询提取的视觉特征与文本是否匹配。
- 基于图像的文本生成损失(ITG):以查询提取的视觉特征为条件,让 Q-Former 的文本解码器部分生成对应的文本描述。
第二阶段:视觉到语言的生成学习(冻结 LLM) 目标:将第一阶段学到的、富含文本相关信息的视觉表示,适配给冻结的 LLM,使其能基于视觉信息生成文本。 技术关键:将 Q-Former 输出的查询表示(视觉信息)作为软性提示(Soft Prompt),预先拼接到 LLM 的输入文本嵌入之前。LLM 将其视为特殊的上下文标记,并在此基础上进行自回归生成。只训练 Q-Former,LLM 的参数完全冻结。
下面的代码模拟了 BLIP-2 第二阶段中,Q-Former 输出如何作为软提示与 LLM 输入结合的过程:
import torch
import torch.nn as nn
torch.manual_seed(0)
# 定义超参数
num_queries = 4 # Q-Former 查询向量的数量,论文中用32
d_model = 8 # 特征维度(简化)
llm_vocab_size = 100 # LLM 词表大小(简化)
text_seq_len = 6 # 输入文本序列长度(简化)
# 1. 模拟 Q-Former 的输出(视觉特征)
# 假设 Q-Former 已训练好,输出形状 (batch, num_queries, d_model)
batch_size = 2
visual_features = torch.randn(batch_size, num_queries, d_model) # 形状 (2, 4, 8)
print("Q-Former 视觉特征形状:", visual_features.shape)
# 2. 将视觉特征投影到与 LLM 嵌入层相同的维度(可选,这里假设维度已匹配)
# visual_prompt = nn.Linear(d_model, d_model)(visual_features) # 如果维度不同需要投影
# 3. 模拟 LLM 的输入文本标记(token ids)
input_ids = torch.randint(0, llm_vocab_size, (batch_size, text_seq_len)) # 形状 (2, 6)
print("LLM 输入文本标记形状:", input_ids.shape)
print("示例输入标记:", input_ids[0].tolist())
# 4. 模拟 LLM 的嵌入层(冻结)
llm_embed = nn.Embedding(llm_vocab_size, d_model)
# 在实际 BLIP-2 中,llm_embed 是冻结的 LLM 的一部分
# 5. 构建 LLM 的最终输入嵌入
# 将视觉特征作为软提示,拼接到文本嵌入之前
text_embeddings = llm_embed(input_ids) # 形状 (2, 6, 8)
print("文本嵌入形状:", text_embeddings.shape)
# 拼接: [视觉软提示] + [文本嵌入]
# 最终输入 LLM 的序列长度 = num_queries + text_seq_len
llm_input_embeds = torch.cat([visual_features, text_embeddings], dim=1) # 形状 (2, 10, 8)
print("LLM 最终输入嵌入形状 (batch, seq_len, dim):", llm_input_embeds.shape)
print("序列构成: 4个视觉查询 + 6个文本标记 = 10个标记")
# 6. 此 llm_input_embeds 将被送入冻结的 LLM 进行前向传播,计算生成损失。
# 损失仅用于更新 Q-Former 的参数。Q-Former 视觉特征形状: torch.Size([2, 4, 8])
LLM 输入文本标记形状: torch.Size([2, 6])
示例输入标记: [75, 0, 18, 32, 68, 12]
文本嵌入形状: torch.Size([2, 6, 8])
LLM 最终输入嵌入形状 (batch, seq_len, dim): torch.Size([2, 10, 8])
序列构成: 4个视觉查询 + 6个文本标记 = 10个标记设计精髓:
- 效率:只训练 188M 的 Q-Former,避免端到端训练数百亿参数 LLM 的巨额成本。
- 通用性:可以灵活搭配不同的冻结图像编码器(CLIP ViT-L/14、EVA-CLIP ViT-g/14 等)与冻结 LLM(OPT、Flan-T5 等)。
- 能力继承:LLM 的语言理解与生成能力得以保留,并通过视觉软提示被“引导”到多模态任务上。
对比 CLIP 与 BLIP-2:架构范式演进
| 特性 | CLIP (Radford et al., 2021) | BLIP-2 (Li et al., 2023) |
|---|---|---|
| 核心目标 | 学习可迁移的视觉表示,实现零样本分类/检索。 | 实现高效的视觉条件语言生成(描述、问答等)。 |
| 架构 | 双编码器(图像+文本),并行独立。 | 编码器-解码器(图像编码器 + Q-Former + 冻结LLM),串行条件生成。 |
| 训练方式 | 端到端训练两个编码器。 | 冻结图像编码器和 LLM,仅训练轻量 Q-Former。 |
| 对齐机制 | 对比学习,在批次内拉近正样本对。 | 两阶段训练:先通过多任务学习对齐图文表示,再学习将视觉表示适配给 LLM。 |
| 输出形式 | 特征向量,用于计算相似度。 | 自然语言文本序列。 |
| 计算效率 | 相对较高,但需训练两个大型编码器。 | 极高,充分利用预训练模型,仅训练少量参数。 |
范式总结:CLIP 是“表示学习”的代表,靠对齐构建共享语义空间;BLIP-2 是“高效适配”的典范,用最小成本把已有大模型扩到新模态。两者合起来支撑了今天多模态基础模型的两条主线。
📝 动手练一练
题目:假设你有一个已经训练好的 CLIP 模型(图像和文本编码器)。现在有一张图片和三个候选文本描述:“一只猫在沙发上”、“一辆红色的汽车”、“阳光下的海滩”。请描述零样本预测“哪段文本最匹配图片”的完整计算步骤,并指出最关键的一个张量操作。
👉 点击查看参考答案
思路:
- 编码:将输入图片通过 CLIP 图像编码器,得到图像特征向量
img_feat,形状为(1, d_model)。将三段文本分别通过 CLIP 文本编码器,得到文本特征矩阵text_feats,形状为(3, d_model)。 - 归一化:对
img_feat和text_feats分别进行 L2 归一化(沿特征维度 dim=-1)。 - 计算相似度:计算归一化后的
img_feat与text_feats的点积(即余弦相似度)。这是最关键的操作:similarities = torch.matmul(img_feat, text_feats.T),得到一个形状为(1, 3)的相似度分数。 - 选择:取
similarities中值最大的索引pred_idx = torch.argmax(similarities),该索引对应的文本即为模型认为最匹配的文本。
关键张量操作:torch.matmul(img_feat, text_feats.T),它一次性计算了单张图像与所有文本描述的相似度。
常见错误
| 错误理解或写法 | 现象或报错 | 原因 | 正确做法 |
|---|---|---|---|
| 在计算 CLIP 相似度前忘记对特征进行 L2 归一化。 | 相似度分数范围不稳定,受特征向量模长影响大,对比学习效果差。 | 对比学习依赖余弦相似度,其前提是向量在单位球面上。 | 在编码器输出后或计算点积前,使用 F.normalize(features, dim=-1)。 |
| 误以为 BLIP-2 的 Q-Former 输出直接是文本标记。 | 把 Q-Former 输出直接送进 LLM 嵌入层,形状或类型对不上。 | Q-Former 输出是连续视觉向量,只能当“软提示”用。 | 把 Q-Former 输出与文本嵌入在序列维度(dim=1)拼接。 |
| 在零样本分类时,直接用单个词(如“狗”)作为文本提示。 | 分类准确率可能低于预期。 | CLIP 文本编码器是在自然句子描述上训练的,单个词可能与其训练分布不一致。 | 使用提示模板,如“一张{}的照片”,将类别词填入其中,形成更自然的句子。 |
本章小结
- 多模态学习的核心是建立不同模态(如图像和文本)在语义上的对齐。
- CLIP 采用对比学习范式,通过在海量图文对上训练双编码器,构建了一个共享的语义表示空间,实现了强大的零样本迁移能力。
- BLIP-2 采用高效适配策略,通过一个轻量级的 Q-Former 作为桥梁,冻结并利用了强大的预训练图像编码器和 LLM,以极低的成本实现了高质量的视觉条件文本生成。
- 从 CLIP 到 BLIP-2,反映了多模态架构从“表示对齐”到“条件生成”、从“端到端训练”到“冻结适配”的演进趋势。
📋 行动清单
- 运行并理解 CLIP 对比学习的代码示例,尝试将
batch_size改为 3,观察相似度矩阵和损失的变化。 - 在脑海中复述 BLIP-2 两阶段训练的目标分别是什么,以及 Q-Former 在每阶段扮演的角色。
- 思考:如果要为音频和文本设计一个类似 CLIP 的对比学习模型,输入和输出张量的形状应该如何设计?
延伸阅读
- Learning Transferable Visual Models From Natural Language Supervision (Radford et al., 2021, arXiv:2103.00020) — CLIP 原论文。
- BLIP-2: Bootstrapping Language-Image Pre-training with Frozen Image Encoders and Large Language Models (Li et al., 2023, arXiv:2301.12597) — BLIP-2 原论文。
—— 小象教研组