迁移学习(八)—— 多模态迁移
Chen Kai BOSS

为什么 CLIP 能用自然语言描述实现零样本图像分类?为什么 DALL-E 能根据文本生成图像?这些突破性进展的核心是多模态迁移学习——让模型理解并关联不同模态(视觉、语言、音频等)的信息。

多模态迁移不仅是技术的融合,更是认知智能的关键。本文从对比学习的数学原理出发,系统讲解 CLIP 、 ALIGN 等视觉-语言预训练模型,深入探讨跨模态对齐、融合策略与下游任务应用,并提供从零实现多模态模型的完整代码。

多模态学习的动机与挑战

为什么需要多模态

单模态学习的局限性:

  1. 信息不完整:仅从图像无法理解"为什么",仅从文本无法感知"什么样子"
  2. 泛化性差:纯视觉模型难以处理概念性查询(如"找到所有危险场景")
  3. 数据效率低:图像标注昂贵,而文本描述(如网页上的图文对)天然存在且规模巨大

多模态的优势:

  • 互补性:不同模态提供互补信息(如图像的空间关系 + 文本的因果解释)
  • 鲁棒性:一个模态缺失或噪声时,其他模态可以补偿
  • 零样本泛化:通过语言描述,模型可以识别训练时未见过的类别

核心问题:如何让模型学习不同模态之间的对应关系?

多模态迁移的挑战

1. 模态异构性

视觉和语言在表示空间上本质不同:

  • 视觉:连续、高维、局部相关(像素级)
  • 语言:离散、符号化、全局依赖(句法结构)

数学描述:视觉特征 ,文本特征 (词索引序列),直接比较无意义。

2. 语义鸿沟

同一概念在不同模态中的表达差异:

  • 图像中的"猫"是像素模式
  • 文本中的"猫"是符号序列
  • 需要学习跨模态的语义对齐

3. 数据对齐

训练数据的对齐粒度不同:

  • 弱对齐:图像-文本对(如网页图文),但文本可能只描述部分内容
  • 强对齐:细粒度标注(如区域-短语对应),但标注成本极高

4. 模态融合策略

何时、如何融合不同模态的信息:

  • 早期融合:在输入层拼接特征
  • 晚期融合:分别提取特征后融合
  • 深度融合:在多个网络层交互

对比学习:多模态预训练的基石

对比学习的数学原理

对比学习的核心思想:拉近正样本对,推开负样本对

给定图像-文本对 ,批次大小为 ,定义对比损失( InfoNCE):

其中:

  • 是余弦相似度
  • 是温度参数(控制分布平滑度)
  • 是正样本对, 是负样本对

为什么对比学习有效?

从互信息最大化的角度理解:

对比学习等价于最大化视觉和文本编码之间的互信息

$$

I(V; T) = H(V) - H(V|T) $$

其中 是视觉特征的熵, 是给定文本后的条件熵。

对比损失通过:

  1. 最大化 :负样本对推开,保持特征空间的多样性
  2. 最小化 :正样本对拉近,减少给定文本后的不确定性

从而最大化互信息。

温度参数 的作用

温度参数控制相似度分布的"尖锐程度":

  • (如 0.01):分布尖锐,只关注最相似的样本,可能导致过拟合
  • (如 1.0):分布平滑,考虑所有样本,学习可能不充分

时, softmax 退化为 argmax(只选最大值)。

实践中,通常设为 0.07( CLIP 论文的经验值)。

CLIP: Connecting Text and Images

CLIP 的核心思想

CLIP( Contrastive Language-Image Pre-training)1 的设计哲学:

不预测具体类别,而是学习图像和文本的对应关系。

传统方法:图像 → 固定类别(如 ImageNet 的 1000 类)
CLIP 方法:图像 ↔︎ 任意文本描述

这种设计带来的优势: 1. 数据规模:可以利用互联网上的 4 亿图文对,远超人工标注数据集 2. 零样本泛化:通过文本描述识别训练时未见类别 3. 任务灵活性:同一个模型可以做分类、检索、生成等任务

CLIP 的架构

CLIP 由两个编码器组成:

  1. 图像编码器Double exponent: use braces to clarify f_v: ^{H W } ^d - 可以是 ResNet 或 Vision Transformer (ViT)
    • 输出固定维度的图像嵌入
  2. 文本编码器Double exponent: use braces to clarify f_t: ^L ^d - 使用 Transformer
    • 输出与图像嵌入相同维度的文本嵌入

训练过程: 1. 批次包含 个图像-文本对 2. 计算 的相似度矩阵 3. 对角线元素 是正样本对,非对角元素是负样本对 4. 同时优化图像→文本和文本→图像两个方向的对比损失

损失函数:

其中:

CLIP 的零样本分类

给定图像和 个候选类别, CLIP 的零样本分类流程:

  1. 将类别名转换为文本描述:
    • 简单版:类别名 → "a photo of a {class}"
    • 复杂版:多个模板集成(如"a photo of a {class}", "a picture of a {class}")
  2. 编码图像和所有文本描述:
    • - 3. 计算相似度并归一化:

$$

p(y = k | x) = $$4. 选择概率最大的类别

这种方法的优势:无需在目标数据集上训练,只需提供类别名。

CLIP 与传统方法的对比

维度 传统监督学习 CLIP
训练数据 固定类别标注(如 ImageNet) 图文对(如网页)
数据规模 百万级 亿级
泛化能力 仅限训练类别 零样本识别新类别
标注成本 高(需人工标注) 低(自然存在)
任务适配 需微调 零样本或少样本

ALIGN: 更大规模的对齐

ALIGN 的改进

ALIGN( A Large-scale ImaGe and Noisy text embedding)2 是 Google 提出的 CLIP 的改进版,核心差异:

  1. 数据规模: 18 亿图文对( CLIP 的 4.5 倍)
  2. 噪声数据:直接使用网页爬取的数据,不过滤噪声
  3. 简化架构:使用 EfficientNet 作为图像编码器

噪声鲁棒性

ALIGN 证明了一个重要发现:对比学习对噪声标注天然鲁棒

原因分析:

假设真实匹配对为 ,噪声标注为 ,其中 不匹配。

在大批次对比学习中: - 作为正样本对拉近,但 相似度低,梯度小 - 其他真实匹配对 提供正确信号,主导优化方向

数学表示:设噪声比例为 ,则期望梯度为:

较小且批次大时, 主导,噪声被平均掉。

实验表明:即使 30%的噪声, ALIGN 性能下降不到 5%。

跨模态对齐方法

对齐的层次

跨模态对齐可以在不同粒度进行:

  1. 全局对齐:整个图像 ↔︎ 整个句子( CLIP/ALIGN)
  2. 区域对齐:图像区域 ↔︎ 短语( Visual Genome)
  3. 像素对齐:像素 ↔︎ 词(密集对齐)

深度对齐: OSCAR

OSCAR( Object-Semantics Aligned Pre-training)3 提出了基于对象标签的对齐策略:

核心思想:引入对象标签作为"锚点"( anchor),连接视觉和语言。

输入表示:

其中: - 是文本词 - 是图像区域特征 - 是对象标签(如"dog", "car")

预训练任务: 1. 掩码语言建模( MLM):预测被掩码的词 2. 掩码区域建模( MRM):预测被掩码的图像区域 3. 对象标签分类:预测区域的对象类别

优势:对象标签提供了明确的语义对齐信号,加速收敛。

对齐损失的设计

除了对比损失,还有其他对齐损失:

1. 三元组损失

其中 是匹配文本, 是不匹配文本, 是间隔( margin)。

2. 循环一致性损失

用于图像描述和图像生成的联合训练:

其中 是图像描述模型, 是文本到图像生成模型。

3. 知识蒸馏对齐

使用预训练的单模态模型作为教师:

多模态融合策略

融合的时机

1. 早期融合( Early Fusion)

在输入层拼接不同模态的特征:

$$

h_0 = [v; t] $$

优点:简单,充分交互
缺点:无法利用预训练模型,对模态缺失脆弱

2. 晚期融合( Late Fusion)

分别提取特征后融合:

$$

h_v = f_v(v), h_t = f_t(t), h = g([h_v; h_t]) $$

优点:可以使用预训练编码器,灵活
缺点:交互不充分

3. 深度融合( Deep Fusion)

在多个层级交互:

$$

h_v^{(l)} = (h_v^{(l-1)}, h_t^{(l-1)}), h_t^{(l)} = (h_t^{(l-1)}, h_v^{(l-1)}) $$

优点:充分交互,灵活建模
缺点:计算复杂度高

注意力机制融合

Cross-Attention

视觉特征对文本特征的注意力:

$$

h_v = ( ) V_t $$

其中

Co-Attention

视觉和文本互相注意:

$$

A_{vt} = (h_v h_t^T), h_v' = A_{vt} h_t, h_t' = A_{vt}^T h_v $$

Self-Attention on Concatenation

将视觉和文本特征拼接后做自注意力( Transformer 风格):

$$

h = ([h_v; h_t]) $$

这是 BERT 等模型的典型做法(如 ViLBERT 、 LXMERT)。

下游任务应用

图像描述生成( Image Captioning)

任务定义:给定图像 ,生成描述文本

编码器-解码器架构

编码器:提取图像特征 解码器:自回归生成文本

$$

p(w_t | w_{<t}, v) = (W h_t) $$

其中 是解码器在时刻 的隐状态:

$$

h_t = (w_{t-1}, h_{t-1}, c_t) $$

上下文向量 由注意力机制计算:

$$

c_t = {i} {ti} h_v^{(i)}, _{ti} = $$

其中 是注意力得分。

强化学习优化

由于 BLEU 等评价指标不可微,使用策略梯度:

其中 是生成序列的奖励(如 CIDEr 分数)。

视觉问答( VQA)

任务定义:给定图像 和问题 ,预测答案

分类式 VQA

将 VQA 视为多分类问题(候选答案集大小 ):

$$

p(a | v, q) = (W [h_v; h_q]) $$

生成式 VQA

将 VQA 视为条件文本生成:

$$

p(a | v, q) = {t=1}^L p(w_t | w{<t}, v, q) $$

注意力机制

问题引导的视觉注意力:

最终预测:

$$

p(a | v, q) = (W [h_v'; h_q]) $$

图文检索( Image-Text Retrieval)

任务定义:给定文本,检索相关图像(或反之)。

基于相似度排序

计算查询文本 与所有候选图像 的相似度:

$$

s_i = (f_t(q), f_v(v_i)) $$

降序排列,取 Top-K 。

度量学习优化

三元组损失:

其中 是匹配图像, 是不匹配图像。

困难负样本挖掘

在批次中选择相似度最高的负样本:

$$

v^- = _{v_j: y_j y_i} s(q, v_j) $$

加速收敛并提升性能。

完整代码实现:从零构建 CLIP 模型

下面实现一个简化版 CLIP,包含图像编码器、文本编码器、对比学习训练和零样本分类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
"""
从零实现 CLIP:对比学习的视觉-语言预训练
包含:图像/文本编码器、对比损失、零样本分类、可视化
"""

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
import torchvision.transforms as transforms
import torchvision.models as models
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
import os
from typing import List, Tuple, Dict

# 设置随机种子
torch.manual_seed(42)
np.random.seed(42)

# ============================================================================
# 图像编码器:使用 ResNet50
# ============================================================================

class ImageEncoder(nn.Module):
"""
图像编码器: ResNet50 + 投影头
"""
def __init__(self, embed_dim=512, pretrained=True):
super().__init__()
# 加载预训练 ResNet50
resnet = models.resnet50(pretrained=pretrained)
# 移除最后的全连接层
self.backbone = nn.Sequential(*list(resnet.children())[:-1])

# 投影头: 2048 → embed_dim
self.projection = nn.Sequential(
nn.Linear(2048, embed_dim),
nn.ReLU(),
nn.Linear(embed_dim, embed_dim)
)

def forward(self, images):
"""
Args:
images: (batch_size, 3, H, W)
Returns:
embeddings: (batch_size, embed_dim)
"""
features = self.backbone(images) # (B, 2048, 1, 1)
features = features.view(features.size(0), -1) # (B, 2048)
embeddings = self.projection(features) # (B, embed_dim)
# L2 归一化
embeddings = embeddings / embeddings.norm(dim=-1, keepdim=True)
return embeddings

# ============================================================================
# 文本编码器: Transformer
# ============================================================================

class TextEncoder(nn.Module):
"""
文本编码器: Transformer + 投影头
"""
def __init__(self, vocab_size=10000, embed_dim=512, max_len=77,
num_heads=8, num_layers=6):
super().__init__()
self.embed_dim = embed_dim
self.max_len = max_len

# 词嵌入
self.token_embedding = nn.Embedding(vocab_size, embed_dim)
# 位置编码
self.position_embedding = nn.Parameter(torch.randn(max_len, embed_dim))

# Transformer 编码器
encoder_layer = nn.TransformerEncoderLayer(
d_model=embed_dim,
nhead=num_heads,
dim_feedforward=2048,
dropout=0.1,
batch_first=True
)
self.transformer = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)

# 投影头
self.projection = nn.Sequential(
nn.Linear(embed_dim, embed_dim),
nn.ReLU(),
nn.Linear(embed_dim, embed_dim)
)

def forward(self, text_tokens):
"""
Args:
text_tokens: (batch_size, seq_len)
Returns:
embeddings: (batch_size, embed_dim)
"""
batch_size, seq_len = text_tokens.shape

# 词嵌入 + 位置编码
token_embed = self.token_embedding(text_tokens) # (B, L, D)
position_embed = self.position_embedding[:seq_len, :] # (L, D)
x = token_embed + position_embed.unsqueeze(0) # (B, L, D)

# Transformer 编码
x = self.transformer(x) # (B, L, D)

# 取[CLS] token(第一个位置)的表示
cls_embed = x[:, 0, :] # (B, D)

# 投影
embeddings = self.projection(cls_embed) # (B, D)
# L2 归一化
embeddings = embeddings / embeddings.norm(dim=-1, keepdim=True)
return embeddings

# ============================================================================
# CLIP 模型
# ============================================================================

class CLIP(nn.Module):
"""
CLIP 模型:图像编码器 + 文本编码器
"""
def __init__(self, embed_dim=512, vocab_size=10000):
super().__init__()
self.image_encoder = ImageEncoder(embed_dim=embed_dim)
self.text_encoder = TextEncoder(vocab_size=vocab_size, embed_dim=embed_dim)

# 可学习的温度参数
self.logit_scale = nn.Parameter(torch.ones([]) * np.log(1 / 0.07))

def forward(self, images, text_tokens):
"""
Args:
images: (batch_size, 3, H, W)
text_tokens: (batch_size, seq_len)
Returns:
logits_per_image: (batch_size, batch_size)
logits_per_text: (batch_size, batch_size)
"""
# 编码
image_embeddings = self.image_encoder(images) # (B, D)
text_embeddings = self.text_encoder(text_tokens) # (B, D)

# 计算相似度矩阵
logit_scale = self.logit_scale.exp()
logits_per_image = logit_scale * image_embeddings @ text_embeddings.t() # (B, B)
logits_per_text = logits_per_image.t() # (B, B)

return logits_per_image, logits_per_text

# ============================================================================
# 对比损失
# ============================================================================

def contrastive_loss(logits_per_image, logits_per_text):
"""
对比损失: InfoNCE
"""
batch_size = logits_per_image.shape[0]
labels = torch.arange(batch_size, device=logits_per_image.device)

# 图像→文本方向的损失
loss_i2t = nn.CrossEntropyLoss()(logits_per_image, labels)
# 文本→图像方向的损失
loss_t2i = nn.CrossEntropyLoss()(logits_per_text, labels)

# 总损失
loss = (loss_i2t + loss_t2i) / 2
return loss

# ============================================================================
# 模拟数据集
# ============================================================================

class SyntheticImageTextDataset(Dataset):
"""
模拟图像-文本对数据集
"""
def __init__(self, num_samples=1000, image_size=224, vocab_size=10000, seq_len=77):
self.num_samples = num_samples
self.image_size = image_size
self.vocab_size = vocab_size
self.seq_len = seq_len

# 生成合成数据
self.images = []
self.texts = []

for i in range(num_samples):
# 生成随机图像(模拟)
img = np.random.randn(3, image_size, image_size).astype(np.float32)
self.images.append(img)

# 生成随机文本(模拟)
text = np.random.randint(1, vocab_size, size=seq_len)
self.texts.append(text)

def __len__(self):
return self.num_samples

def __getitem__(self, idx):
image = torch.FloatTensor(self.images[idx])
text = torch.LongTensor(self.texts[idx])
return image, text

# ============================================================================
# 训练函数
# ============================================================================

def train_clip(model, dataloader, optimizer, device, num_epochs=10):
"""
训练 CLIP 模型
"""
model.train()
losses = []

for epoch in range(num_epochs):
epoch_loss = 0
for batch_idx, (images, text_tokens) in enumerate(dataloader):
images = images.to(device)
text_tokens = text_tokens.to(device)

# 前向传播
logits_per_image, logits_per_text = model(images, text_tokens)

# 计算损失
loss = contrastive_loss(logits_per_image, logits_per_text)

# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()

epoch_loss += loss.item()

if (batch_idx + 1) % 10 == 0:
print(f"Epoch [{epoch+1}/{num_epochs}], Batch [{batch_idx+1}/{len(dataloader)}], Loss: {loss.item():.4f}")

avg_loss = epoch_loss / len(dataloader)
losses.append(avg_loss)
print(f"Epoch [{epoch+1}/{num_epochs}] Average Loss: {avg_loss:.4f}")

return losses

# ============================================================================
# 零样本分类
# ============================================================================

def zero_shot_classification(model, image, text_labels, device):
"""
零样本分类:给定图像和文本标签候选
Args:
model: CLIP 模型
image: (3, H, W)
text_labels: List[(seq_len,)] 文本标签的 token 序列列表
device: 设备
Returns:
probs: (num_classes,) 每个类别的概率
"""
model.eval()
with torch.no_grad():
# 编码图像
image = image.unsqueeze(0).to(device) # (1, 3, H, W)
image_embedding = model.image_encoder(image) # (1, D)

# 编码所有文本标签
text_embeddings = []
for text_tokens in text_labels:
text_tokens = text_tokens.unsqueeze(0).to(device) # (1, L)
text_embedding = model.text_encoder(text_tokens) # (1, D)
text_embeddings.append(text_embedding)

text_embeddings = torch.cat(text_embeddings, dim=0) # (K, D)

# 计算相似度
logit_scale = model.logit_scale.exp()
logits = logit_scale * image_embedding @ text_embeddings.t() # (1, K)

# Softmax 得到概率
probs = torch.softmax(logits, dim=-1).squeeze(0) # (K,)

return probs.cpu().numpy()

# ============================================================================
# 可视化
# ============================================================================

def visualize_similarity_matrix(model, dataloader, device, num_samples=16):
"""
可视化图像-文本相似度矩阵
"""
model.eval()

# 获取一个 batch
images, text_tokens = next(iter(dataloader))
images = images[:num_samples].to(device)
text_tokens = text_tokens[:num_samples].to(device)

with torch.no_grad():
logits_per_image, _ = model(images, text_tokens)
similarity_matrix = logits_per_image.cpu().numpy()

# 绘制热力图
fig, ax = plt.subplots(figsize=(10, 8))
im = ax.imshow(similarity_matrix, cmap='viridis', aspect='auto')

ax.set_xticks(np.arange(num_samples))
ax.set_yticks(np.arange(num_samples))
ax.set_xticklabels([f'Text {i}' for i in range(num_samples)])
ax.set_yticklabels([f'Image {i}' for i in range(num_samples)])

plt.setp(ax.get_xticklabels(), rotation=45, ha="right", rotation_mode="anchor")

# 添加颜色条
cbar = plt.colorbar(im, ax=ax)
cbar.set_label('Similarity Score', rotation=270, labelpad=20)

# 添加数值标注
for i in range(num_samples):
for j in range(num_samples):
text = ax.text(j, i, f'{similarity_matrix[i, j]:.2f}',
ha="center", va="center", color="white", fontsize=8)

ax.set_title("Image-Text Similarity Matrix")
plt.tight_layout()
plt.savefig('similarity_matrix.png', dpi=150, bbox_inches='tight')
plt.close()
print("Similarity matrix saved to similarity_matrix.png")

def plot_training_curve(losses):
"""
绘制训练曲线
"""
plt.figure(figsize=(10, 6))
plt.plot(losses, marker='o', linewidth=2, markersize=6)
plt.xlabel('Epoch', fontsize=12)
plt.ylabel('Loss', fontsize=12)
plt.title('CLIP Training Loss', fontsize=14, fontweight='bold')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('training_curve.png', dpi=150, bbox_inches='tight')
plt.close()
print("Training curve saved to training_curve.png")

# ============================================================================
# 主函数
# ============================================================================

def main():
# 超参数
embed_dim = 512
vocab_size = 10000
batch_size = 32
num_epochs = 20
learning_rate = 1e-4

# 设备
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"Using device: {device}")

# 创建数据集和 DataLoader
print("\nCreating synthetic dataset...")
dataset = SyntheticImageTextDataset(num_samples=1000, vocab_size=vocab_size)
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)

# 创建模型
print("Initializing CLIP model...")
model = CLIP(embed_dim=embed_dim, vocab_size=vocab_size).to(device)

# 计算参数量
num_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f"Total trainable parameters: {num_params:,}")

# 优化器
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

# 训练
print("\nStarting training...")
losses = train_clip(model, dataloader, optimizer, device, num_epochs=num_epochs)

# 绘制训练曲线
plot_training_curve(losses)

# 可视化相似度矩阵
print("\nVisualizing similarity matrix...")
visualize_similarity_matrix(model, dataloader, device)

# 零样本分类示例
print("\nZero-shot classification example:")
# 创建测试图像和文本标签
test_image = torch.randn(3, 224, 224)
text_labels = [
torch.randint(1, vocab_size, (77,)) for _ in range(5)
]

probs = zero_shot_classification(model, test_image, text_labels, device)
for i, prob in enumerate(probs):
print(f"Class {i}: {prob:.4f}")

print("\n" + "="*60)
print("Training completed!")
print("="*60)

if __name__ == "__main__":
main()

代码说明

核心组件

  1. 图像编码器: ResNet50 提取特征 + 投影层
  2. 文本编码器: Transformer + 位置编码 + 投影层
  3. 对比损失:双向 InfoNCE 损失
  4. 零样本分类:计算图像与所有类别文本的相似度

训练流程

  1. 批次内对比学习: 个图文对产生 相似度矩阵
  2. 对角线元素是正样本对,非对角线是负样本对
  3. 同时优化图像→文本和文本→图像两个方向

关键技巧

  • L2 归一化:确保相似度计算稳定
  • 可学习温度参数:自动调节 softmax 分布
  • 大批次训练:更多负样本,更好的对比效果

进阶话题

多模态 Transformer

ViLBERT (Vision-and-Language BERT)

ViLBERT4提出了双流 Transformer 架构:

  • 视觉流:处理图像区域特征
  • 语言流:处理文本 token
  • 跨模态连接:通过 Co-Attention 层交互

架构:

预训练任务: 1. 掩码语言建模( MLM) 2. 掩码区域建模( MRM) 3. 图像-文本匹配( ITM)

文本到图像生成

DALL-E

DALL-E 使用自回归 Transformer 生成图像:

  1. VQ-VAE 编码:将图像离散化为 token 序列
  2. 拼接输入3. 自回归生成:逐 token 预测图像

损失函数:

Diffusion Models + CLIP

Stable Diffusion 等模型使用 CLIP 文本编码器作为条件:

其中 是文本描述, 是 CLIP 文本嵌入。

跨语言多模态

mCLIP( multilingual CLIP)扩展 CLIP 到多语言:

  • 使用多语言文本编码器(如 mBERT 、 XLM-R)
  • 在多语言图文对上训练
  • 实现跨语言零样本迁移

优势: - 低资源语言可以利用高资源语言的知识 - 同一个模型支持 100+语言

常见问题解答

Q1: CLIP 的零样本能力从何而来?

零样本能力源于三个关键因素:

  1. 海量数据: 4 亿图文对覆盖了极其广泛的概念
  2. 自然语言监督:文本描述天然包含丰富的语义信息
  3. 对比学习:学习图像和文本的对应关系,而非固定类别

形式化理解:传统分类器学习 ,其中 固定; CLIP 学习 ,其中 可以是任意文本描述。

Q2: 为什么 CLIP 不需要标注数据?

CLIP 使用弱监督而非传统标注:

  • 传统标注:图像 → 离散类别标签(需人工)
  • CLIP 标注:图像 ↔︎ 文本描述(互联网自然存在)

图文对的对应关系本身就是监督信号,无需额外标注。

Q3: 多模态模型如何处理模态缺失?

三种策略:

  1. 模态补全:用生成模型填补缺失模态
  2. 鲁棒训练:训练时随机丢弃某些模态,强制模型学习单模态推理
  3. 集成方法:训练单模态和多模态模型,测试时根据可用模态选择

损失函数示例:

Q4: 对比学习的批次大小为什么重要?

批次大小决定了负样本数量:

  • 批次大小 :每个样本有 个负样本
  • 更多负样本 → 更准确的梯度估计 → 更好的对比效果

实验表明: CLIP 在批次大小 32768 时效果最好,但计算成本极高。

解决方案: - 梯度累积:累积多个小批次的梯度 - MoCo 队列:维护负样本队列,解耦批次大小和负样本数

Q5: 如何评估多模态模型?

常用评估任务:

  1. 零样本分类: ImageNet 、 CIFAR-100 等
  2. 图文检索: Recall@K 指标
  3. 图像描述: BLEU 、 CIDEr 、 SPICE
  4. VQA:准确率

跨任务一致性也很重要:好的多模态表示应该在多个任务上都表现优秀。

Q6: CLIP 在哪些场景下表现不好?

CLIP 的局限性:

  1. 细粒度分类:难以区分相似类别(如不同狗种)
  2. 计数和空间关系:对"三只猫"和"猫在左边"等理解较弱
  3. 抽象概念:对比学习更擅长具体物体,而非抽象概念
  4. 少见概念:预训练数据中罕见的概念效果差

原因:对比学习倾向于学习粗粒度、高频的视觉-语言对应关系。

Q7: 多模态模型的计算效率如何优化?

优化策略:

  1. 蒸馏:大模型蒸馏到小模型
  2. 剪枝:移除冗余注意力头
  3. 量化: FP16 或 INT8 推理
  4. 缓存:预计算图像特征,实时编码文本

示例: CLIP 的图像编码可以离线完成,检索时只需编码文本查询。

Q8: 如何在自己的数据上微调 CLIP?

微调策略:

  1. 冻结编码器,训练分类头:适用于小数据
  2. 低学习率全量微调:适用于中等数据
  3. LoRA 等参数高效微调:适用于大模型

注意事项: - 保持温度参数 不变 - 使用对比损失而非交叉熵 - 数据增强对多模态模型同样重要

Q9: 多模态预训练需要多大的数据量?

经验规律:

  • 百万级:可以学到基本的视觉-语言对应
  • 千万级:达到可用的零样本能力
  • 亿级:与监督学习相当甚至超越

CLIP 使用 4 亿对, ALIGN 使用 18 亿对。

但小数据也有价值:领域特定的数据(如医学影像+报告)可以在预训练基础上继续微调。

Q10: 多模态模型的偏见问题如何应对?

多模态模型继承了训练数据的偏见:

  1. 性别偏见:如"护士"多关联女性图像
  2. 种族偏见:某些职业或场景与特定种族关联
  3. 文化偏见:西方文化主导,其他文化代表性不足

缓解方法: - 数据平衡:增加少数群体的数据比例 - 去偏正则化:在损失函数中加入公平性约束 - 后处理:调整预测分布以减少偏见

Q11: CLIP 与 DALL-E 的区别?

维度 CLIP DALL-E
任务 图像理解(分类、检索) 图像生成
训练方式 对比学习 自回归生成
输入 图像或文本 文本
输出 嵌入向量 图像
可逆性 双向(图↔︎文) 单向(文→图)

DALL-E 2 和 Stable Diffusion 都使用 CLIP 作为文本编码器。

Q12: 多模态迁移的未来方向?

前沿趋势:

  1. 统一模型:一个模型处理所有模态(视觉、语言、音频、视频)
  2. 少样本学习:更高效的多模态适配
  3. 可解释性:理解模型如何关联不同模态
  4. 交互式学习:人机协作标注和学习
  5. 多模态推理:超越简单对应,实现逻辑推理

代表性工作: GPT-4V(视觉)、 Gemini(多模态统一)、 Flamingo(少样本)。

小结

本文全面介绍了多模态迁移学习的核心技术:

  1. 对比学习:通过 InfoNCE 损失学习跨模态对应关系
  2. CLIP/ALIGN:大规模视觉-语言预训练模型及其零样本能力
  3. 跨模态对齐:从全局到局部、从弱监督到强监督的对齐方法
  4. 融合策略:早期、晚期、深度融合及注意力机制
  5. 下游应用:图像描述、 VQA 、图文检索的技术细节
  6. 完整实现:从零构建 CLIP 模型的 200+行代码

多模态迁移学习正在重塑 AI 的应用边界,从搜索引擎到内容创作,从教育到医疗,无处不在。下一章我们将探讨参数高效微调技术,看 LoRA 、 Adapter 等方法如何在不改变预训练模型的情况下实现高效迁移。

参考文献


  1. Radford, A., Kim, J. W., Hallacy, C., et al. (2021). Learning transferable visual models from natural language supervision. ICML.↩︎

  2. Jia, C., Yang, Y., Xia, Y., et al. (2021). Scaling up visual and vision-language representation learning with noisy text supervision. ICML.↩︎

  3. Li, X., Yin, X., Li, C., et al. (2020). Oscar: Object-semantics aligned pre-training for vision-language tasks. ECCV.↩︎

  4. Lu, J., Batra, D., Parikh, D., & Lee, S. (2019). ViLBERT: Pretraining task-agnostic visiolinguistic representations for vision-and-language tasks. NeurIPS.↩︎

  • 本文标题:迁移学习(八)—— 多模态迁移
  • 本文作者:Chen Kai
  • 创建时间:2024-12-15 16:15:00
  • 本文链接:https://www.chenk.top/%E8%BF%81%E7%A7%BB%E5%AD%A6%E4%B9%A0%EF%BC%88%E5%85%AB%EF%BC%89%E2%80%94%E2%80%94-%E5%A4%9A%E6%A8%A1%E6%80%81%E8%BF%81%E7%A7%BB/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论