共计 2560 个字符,预计需要花费 7 分钟才能阅读完成。
1. 为什么需要对比学习?
传统监督学习在特征提取任务中面临两个核心问题:

- 标注成本高:ImageNet 级别的标注需要专业知识和大量人力,医疗 / 工业领域标注成本更高
- 样本利用效率低:每个样本仅用于单一类别学习,忽略样本间的潜在关联
以电商图片搜索为例,人工标注所有商品变体(颜色 / 角度)的代价不可承受,而对比学习能自动发现:” 红色包包 45 度图 ” 和 ” 同款蓝色包包正面图 ” 应具有相似特征。
2. 技术范式演进
三种学习方式的本质区别:
- 监督学习
- 需要:人工标注的 (x,y) 对
- 优化目标:最小化预测误差
-
典型架构:最后一层 softmax 分类器
-
自监督学习
- 需要:原始数据 x
- 优化目标:pretext 任务(如拼图、旋转预测)
-
局限:pretext 任务可能与下游任务无关
-
对比学习(Clap)
- 需要:原始数据 x + 数据增强策略
- 优化目标:拉近正样本对,推远负样本对
- 优势:直接优化特征空间结构
3. PyTorch 实战演练
3.1 数据增强管道
import torchvision.transforms as transforms
from PIL import Image
# 核心增强策略(SimCLR 原论文组合)aug_pipeline = transforms.Compose([transforms.RandomResizedCrop(224, scale=(0.2, 1.0)), # 随机裁剪并缩放到 224x224
transforms.RandomApply([transforms.ColorJitter(0.4, 0.4, 0.4, 0.1)], p=0.8), # 80% 概率调整亮度 / 对比度等
transforms.RandomGrayscale(p=0.2), # 20% 概率转灰度
transforms.RandomHorizontalFlip(p=0.5), # 50% 概率水平翻转
transforms.GaussianBlur(kernel_size=9, sigma=(0.1, 2.0)), # 高斯模糊
transforms.ToTensor() # 转为张量])
# 生成正样本对
def get_positive_pair(image_path):
img = Image.open(image_path)
return aug_pipeline(img), aug_pipeline(img) # 同张图片两次不同增强
3.2 模型架构
import torch.nn as nn
class ClapModel(nn.Module):
def __init__(self, backbone='resnet18'):
super().__init__()
# 主干网络(输出 512 维特征)self.encoder = torchvision.models.__dict__[backbone](pretrained=False)
self.encoder.fc = nn.Identity() # 移除原始分类头
# 投影头(关键组件!)self.projector = nn.Sequential(nn.Linear(512, 256), # 论文建议比特征维度小
nn.ReLU(),
nn.Linear(256, 128) # 最终投影空间维度
)
def forward(self, x):
features = self.encoder(x)
return self.projector(features)
3.3 InfoNCE 损失实现
def info_nce_loss(projections, temperature=0.1):
"""
projections: 2N x D 矩阵(N 为 batch_size,D 为投影维度)前 N 个是 anchor,后 N 个是对应的正样本
"""
device = projections.device
N = projections.shape[0] // 2
# 计算相似度矩阵
sim_matrix = torch.mm(projections, projections.T) / temperature
# 构建标签:位置 i 的正样本是 i +N
labels = torch.arange(N, device=device).repeat(2)
masks = torch.ones_like(sim_matrix).scatter_(1, labels.unsqueeze(1), 0)
# 计算对比损失
exp_sim = torch.exp(sim_matrix) * masks
pos_sim = torch.exp(torch.sum(projections[:N] * projections[N:], dim=1) / temperature)
loss = -torch.log(pos_sim / (exp_sim.sum(dim=1)[:N] + 1e-8))
return loss.mean()
4. 调参实战技巧
4.1 数据增强组合选择
- 基础组合(适合大多数场景):
- 随机裁剪 + 颜色扰动 + 水平翻转
-
避免过度增强导致语义失真
-
领域适配:
- 医学影像:慎用颜色扰动,增加弹性形变
- 文本数据:使用同义词替换、随机掩码
4.2 超参数经验值
| 参数 | 推荐范围 | 影响说明 |
|---|---|---|
| batch_size | 256-1024 | 决定负样本数量 |
| 温度系数 | 0.05-0.2 | 控制分布平滑度 |
| 投影维度 | 64-256 | 过小会导致信息损失 |
5. 常见问题排查
5.1 梯度爆炸
症状:loss 突然变为 NaN
解决方案:
- 梯度裁剪
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) - 调小学习率(建议从 3e- 4 开始)
5.2 特征坍缩
诊断方法:计算特征相似度矩阵的秩
features = torch.randn(100, 128) # 模拟特征矩阵
rank = torch.linalg.matrix_rank(features)
print(f"Effective rank: {rank}/{features.shape[1]}") # 理想情况应接近 128
预防措施:
– 在投影头添加 BatchNorm 层
– 使用可学习的温度系数
6. 进阶思考
尝试回答这些问题来深化理解:
1. 为什么 MoCo 等方法要使用动量编码器?
2. 当 batch_size 较小时,如何构建有意义的负样本?
3. 对比学习特征与监督学习特征在可视化上有何差异?
建议实验:在 CIFAR-10 上比较不同增强组合(仅裁剪 vs 裁剪 + 颜色扰动)对下游分类准确率的影响。
正文完
发表至: 未分类
近两天内
