共计 2251 个字符,预计需要花费 6 分钟才能阅读完成。
背景痛点
跨模态检索的核心挑战在于图像和文本特征空间的不一致性。传统方法主要依赖手工特征工程,例如:

- 图像侧使用 SIFT/HOG 特征
- 文本侧采用 TF-IDF 或 LDA 主题模型
这些方法的局限性非常明显:
- 人工设计的特征表达能力有限
- 不同模态的特征维度差异大
- 难以捕捉高层语义关联
技术对比
对比主流跨模态检索方案:
| 方案 | 优点 | 缺点 |
|---|---|---|
| VSE++ | 简单直接 | 单模态编码能力弱 |
| Unicoder | 统一编码架构 | 计算复杂度高 |
| CLIP | 大规模预训练 | 需要海量数据 |
双塔结构的核心优势:
- 允许模态特异性编码
- 推理时可预先计算特征
- 适合分布式部署
核心实现
模型架构
import torch
import torch.nn as nn
from transformers import AutoTokenizer, AutoModel
class ImageEncoder(nn.Module):
def __init__(self):
super().__init__()
self.backbone = torch.hub.load('pytorch/vision', 'resnet50', pretrained=True)
self.proj = nn.Linear(2048, 512) # 投影到共同空间
def forward(self, x):
features = self.backbone(x)
return self.proj(features)
class TextEncoder(nn.Module):
def __init__(self):
super().__init__()
self.model = AutoModel.from_pretrained('bert-base-uncased')
self.proj = nn.Linear(768, 512)
def forward(self, input_ids, attention_mask):
outputs = self.model(input_ids, attention_mask)
return self.proj(outputs.last_hidden_state[:,0])
对比损失实现
损失函数公式:
$$
\mathcal{L} = -\log\frac{e^{\text{sim}(q,k^+)/\tau}}{\sum_{i=1}^N e^{\text{sim}(q,k_i)/\tau}}
$$
def info_nce_loss(image_emb, text_emb, temperature=0.07):
# 归一化特征向量
image_emb = F.normalize(image_emb, dim=-1)
text_emb = F.normalize(text_emb, dim=-1)
# 计算相似度矩阵
logits = torch.matmul(image_emb, text_emb.T) / temperature
# 构建标签
batch_size = image_emb.shape[0]
labels = torch.arange(batch_size).to(logits.device)
# 对称损失计算
loss_i = F.cross_entropy(logits, labels)
loss_t = F.cross_entropy(logits.T, labels)
return (loss_i + loss_t) / 2
性能优化
批处理负采样
通过单 GPU 批次自动构造负样本:
- 每个 batch 包含 N 个图文对
- 同一 batch 内非配对样本自然成为负样本
- 典型 batch size 设为 1024-4096
混合精度训练
scaler = torch.cuda.amp.GradScaler()
with torch.cuda.amp.autocast():
image_features = image_encoder(images)
text_features = text_encoder(input_ids, attention_mask)
loss = info_nce_loss(image_features, text_features)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
避坑指南
数据预处理
常见问题:
- 图像缩放未保持宽高比
- 文本截断长度不一致
- 未做均值方差归一化
推荐做法:
# 图像处理
transform = Compose([Resize(256, interpolation=Image.BICUBIC),
CenterCrop(224),
ToTensor(),
Normalize((0.48145466, 0.4578275, 0.40821073),
(0.26862954, 0.26130258, 0.27577711))
])
# 文本处理
tokenizer = AutoTokenizer.from_pretrained('bert-base-uncased')
text = tokenizer(text, padding='max_length', truncation=True, max_length=77, return_tensors='pt')
超参数设置
经验值参考:
- 初始学习率:3e-5
- batch size:与 GPU 显存匹配
- warmup 步数:总 step 的 10%
延伸思考
视频文本扩展
- 使用 3D CNN 或 VideoSwin 处理视频
- 时间维度平均池化
- 增加时序对比损失
模型蒸馏
方案对比:
- 直接微调小模型(如 ResNet18+DistilBERT)
- 使用 KL 散度对齐特征空间
- 中间层注意力迁移
完整实现见 Colab: 实践链接
正文完
发表至: 未分类
近三天内
