共计 1640 个字符,预计需要花费 5 分钟才能阅读完成。
为什么需要对比学习?
假设你正在构建一个推荐系统,新用户注册后没有任何历史行为数据(即冷启动问题)。传统监督学习需要大量标注数据才能训练,而对比学习通过自动构造正负样本对,仅需原始数据即可学习有效特征表示。这种自监督特性使其成为解决数据稀缺场景的利器。

监督学习 vs 对比学习
- 监督学习 :依赖人工标注的(x,y) 数据对,模型直接学习从 x 到 y 的映射
- 典型损失函数:交叉熵 $L=-\sum y_i\log(p_i)$
- 对比学习:自动构造相似样本(正对)和不相似样本(负对)
- 核心目标:拉近正对距离,推开负对距离
- InfoNCE 损失函数:
$$L = -\log\frac{e^{sim(q,k^+)/\tau}}{e^{sim(q,k^+)/\tau} + \sum_{k^-} e^{sim(q,k^-)/\tau}}$$
其中 $\tau$ 为温度系数,控制分布尖锐程度
PyTorch 实现详解
import torch
import torch.nn as nn
import torch.nn.functional as F
class BHPC(nn.Module):
def __init__(self, backbone, feat_dim=128):
super().__init__()
self.backbone = backbone # 例如 ResNet
self.projector = nn.Sequential(nn.Linear(backbone.output_dim, 256),
nn.ReLU(),
nn.Linear(256, feat_dim)
)
def forward(self, x1, x2):
# 数据增强后的两个视图
z1 = self.projector(self.backbone(x1))
z2 = self.projector(self.backbone(x2))
return F.normalize(z1, dim=1), F.normalize(z2, dim=1)
def info_nce_loss(z1, z2, temperature=0.1):
batch_size = z1.size(0)
# 拼接所有样本构造负对
z = torch.cat([z1, z2], dim=0)
# 计算相似度矩阵(O(n^2)时间复杂度)sim = torch.mm(z, z.T) / temperature
# 消除对角线自相似度
sim.fill_diagonal_(-float('inf'))
# 正对是 [i, i+batch_size] 和[i+batch_size, i]
labels = torch.arange(batch_size, device=z.device)
labels = torch.cat([labels + batch_size, labels])
return F.cross_entropy(sim, labels)
关键超参数调优
- Batch Size:
- 越大则负样本越多,效果通常更好
- 但 GPU 显存占用呈平方增长(相似度矩阵为[2B,2B])
-
建议:在显存允许下尽可能大(至少 256)
-
温度系数 $\tau$:
- 值越小,概率分布越尖锐,对困难负样本更敏感
- 典型值范围:0.05-0.2
- 可通过网格搜索确定最优值
生产环境避坑指南
- GPU 内存优化:
- 使用梯度累积:小 batch 多次计算后再更新
-
混合精度训练:
torch.cuda.amp自动管理 fp16/fp32 -
负样本偏差问题:
- 同 batch 内负样本可能含潜在正样本
-
解决方案:
- 使用 Memory Bank 存储历史特征
- 采用动量编码器生成负样本
-
特征坍塌(Collapse):
- 所有样本映射到同一点
- 预防措施:
- 添加预测头(如 SimSiam)
- 正则化投影头权重
思考题
- 当类别极度不均衡时,对比学习是否会放大多数类的优势?
- 如何设计面向多模态数据(图文 / 视频)的对比学习框架?
- 对比学习学到的特征是否可解释?能否与知识图谱结合?
通过本文的实践,你会发现对比学习在特征提取方面展现惊人潜力。虽然调参过程需要耐心,但当看到模型在没有标注数据的情况下学会区分相似内容时,这种成就感正是机器学习的魅力所在。
正文完
