CLIP双塔结构图解析:从对比学习到跨模态检索实战

1次阅读
没有评论

共计 2251 个字符,预计需要花费 6 分钟才能阅读完成。

image.webp

背景痛点

跨模态检索的核心挑战在于图像和文本特征空间的不一致性。传统方法主要依赖手工特征工程,例如:

CLIP 双塔结构图解析:从对比学习到跨模态检索实战

  • 图像侧使用 SIFT/HOG 特征
  • 文本侧采用 TF-IDF 或 LDA 主题模型

这些方法的局限性非常明显:

  1. 人工设计的特征表达能力有限
  2. 不同模态的特征维度差异大
  3. 难以捕捉高层语义关联

技术对比

对比主流跨模态检索方案:

方案 优点 缺点
VSE++ 简单直接 单模态编码能力弱
Unicoder 统一编码架构 计算复杂度高
CLIP 大规模预训练 需要海量数据

双塔结构的核心优势:

  1. 允许模态特异性编码
  2. 推理时可预先计算特征
  3. 适合分布式部署

核心实现

模型架构

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 批次自动构造负样本:

  1. 每个 batch 包含 N 个图文对
  2. 同一 batch 内非配对样本自然成为负样本
  3. 典型 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%

延伸思考

视频文本扩展

  1. 使用 3D CNN 或 VideoSwin 处理视频
  2. 时间维度平均池化
  3. 增加时序对比损失

模型蒸馏

方案对比:

  1. 直接微调小模型(如 ResNet18+DistilBERT)
  2. 使用 KL 散度对齐特征空间
  3. 中间层注意力迁移

完整实现见 Colab: 实践链接

正文完
 0
评论(没有评论)