对比损失InfoNCE

wen IT资讯 35

本文目录导读:

对比损失InfoNCE

  1. 核心思想
  2. 数学公式
  3. 直观理解
  4. 为什么叫 InfoNCE?
  5. 关键超参数:温度系数 (\tau)
  6. 与 Triplet Loss 的区别
  7. 代码示例 (PyTorch)

对比损失(Contrastive Loss)中的 InfoNCE(Info Noise-Contrastive Estimation)是自监督学习(特别是对比学习)中最核心的损失函数之一,它由 Oord 等人于 2018 年在论文《Representation Learning with Contrastive Predictive Coding》中提出。

下面从原理、数学公式、直观理解及实战技巧等方面进行详细解析。

核心思想

InfoNCE 的目标是在一个高维空间中,拉近“正样本对”的距离,同时推远“负样本对”的距离。

在自监督学习场景中(如 SimCLR、MoCo):

  • 正样本对:同一张图片经过不同数据增强(如裁剪、颜色抖动)后的两个视图。
  • 负样本对:不同图片的视图。

InfoNCE 本质上是一个分类任务:模型需要从一大群候选样本中,正确地找出那个唯一的正样本。

数学公式

InfoNCE 损失函数的典型形式如下:

[ L = -\log \frac{ \exp(\text{sim}(q, k+) / \tau) }{ \exp(\text{sim}(q, k+) / \tau) + \sum_{i=1}^{N} \exp(\text{sim}(q, k_i^-) / \tau) } ]

  • ( q ):查询(Query)向量,例如第一张图的表征。
  • ( k_+ ):正键(Positive Key),与 ( q ) 来自同一原始样本的表征。
  • ( k_i^- ):负键(Negative Keys),来自不同样本的表征。
  • ( \text{sim}(u, v) ):相似度函数,常用余弦相似度
  • ( \tau )温度系数(Temperature),一个超参数,控制分布的平滑程度。
  • ( N ):负样本的数量。

关键点:分母中的求和项包括了 1 个正样本 + N 个负样本,总共 (N+1) 个样本,取 log 后,实际上是一个交叉熵损失,类别数为 (N+1),正样本对应的标签为 0。

直观理解

把 InfoNCE 想象成一场考试:

  • Query(问题)q 代表“请从下面 K 个选项中,选出与我对应的那张图片”。
  • Positive Key(正确答案)k+ 是正确答案。
  • Negative Keys(干扰项/错误答案)k- 是其他所有不相干的图片。
  • Softmax 分类器:模型需要计算所有这些选项与问题 q 的匹配概率,并最大化正确答案的概率。

最小化 InfoNCE 损失,就是让模型:

  1. 最大化 ( \text{sim}(q, k_+) ) (让正确答案与问题高度相关)。
  2. 最小化 所有 ( \text{sim}(q, k_i^-) ) (让错误答案与问题不相关)。

为什么叫 InfoNCE?

  • NCE(Noise-Contrastive Estimation):将正样本视为“信号”,负样本视为“噪声”,通过对比信号和噪声来学习特征,NCE 解决了传统 Softmax 在类别数极大(如百万级词汇表)时计算量过大的问题。
  • InfoNCE 的改进:InfoNCE 将 NCE 的思想引入到互信息(Mutual Information)的最大化中。Info 代表它是在优化一个互信息的下界,优化 InfoNCE 等价于最大化 Query (X) 和 Key (Y) 之间的互信息 (I(X; Y))。

关键超参数:温度系数 (\tau)

温度系数 (\tau) 的作用非常关键:

  • (\tau) 很小(如 0.07):
    • 模型会更关注难分负样本(与 Query 相似度较高的负样本)。
    • 梯度较大,训练更有挑战性,容易学到更细粒度的特征。
    • 风险:容易导致模型崩溃或对噪声敏感。
  • (\tau) 很大(如 1.0):
    • 所有样本的概率分布会趋于均匀。
    • 模型失去了区分正负样本的动力,难以收敛。

经验值:通常设置在 (0.07) 到 (0.2) 之间,在 SimCLR 论文中,最常用的值是 (0.5)。

与 Triplet Loss 的区别

特性 Triplet Loss InfoNCE
样本组成 (Anchor, Positive, Negative) 一个三元组 (Query, 1个Positive, 多个Negative)
负样本数量 1 个,或通过 Hard Mining 选取 大量(数千个),所有 Batch 内样本
处理策略 硬性给定一个 Margin:( \text{dist}(a,p) + \alpha < \text{dist}(a,n) ) 基于 Softmax 的概率分布,无硬边界
计算效率 需要复杂采样策略 利用 In-Batch Negatives,计算高效
效果 对小 Batch 和特定任务有效 在自监督和大规模学习上显著更优

代码示例 (PyTorch)

以下是 SimCLR 中 InfoNCE 损失的标准实现:

import torch
import torch.nn.functional as F
def info_nce_loss(features, batch_size, temperature=0.5):
    """
    Args:
        features: Tensor of shape [2*batch_size, dim] 
                  前batch_size个是view1,后batch_size个是view2
        batch_size: 原始batch的大小
        temperature: 温度系数
    Returns:
        loss: 标量损失
    """
    # 1. 归一化特征向量
    features = F.normalize(features, dim=1)
    # 2. 计算相似度矩阵 (2N x 2N)
    similarity_matrix = torch.matmul(features, features.T) / temperature
    # 3. 构建标签:对于第i个样本,其正样本是i+batch_size(如果i< batch_size)
    #  更简洁的方式:生成 (2N,) 的索引,正样本对是 (i, (i+N) % 2N)
    labels = torch.cat([torch.arange(batch_size) for _ in range(2)], dim=0)
    labels = (labels.unsqueeze(0) == labels.unsqueeze(1)).float()
    # 将对角线设为0 (自己与自己的相似度不算作正样本,应该忽略)
    mask = torch.eye(labels.shape[0], dtype=torch.bool)
    labels = labels[~mask].reshape(labels.shape[0], -1)
    similarity_matrix = similarity_matrix[~mask].reshape(similarity_matrix.shape[0], -1)
    # 4. 选出一行中正样本对应的位置: 对于view1[i],其正样本在位置[i+batch_size]
    #  由于移除了对角线,索引需要重新计算,更标准的方法是使用下面的方式:
    positives = torch.cat([
        similarity_matrix[:batch_size, batch_size:2*batch_size].diag(),  # view1与view2对应
        similarity_matrix[batch_size:2*batch_size, :batch_size].diag()   # view2与view1对应
    ])
    # 负样本:整行除了正样本和自身
    # 对于 view1[i],负样本是: 所有除了 i 和 i+batch_size 的样本
    # 构建选择 mask
    negatives = []
    for i in range(batch_size):
        # view1的负样本: 排除 i (自身) 和 i+batch_size (正样本)
        view1_neg = torch.cat([
            similarity_matrix[i, :i], 
            similarity_matrix[i, i+1:batch_size],
            similarity_matrix[i, batch_size:batch_size+i], 
            similarity_matrix[i, batch_size+i+1:2*batch_size]
        ])
        # view2的负样本: 排除 batch_size+i (自身) 和 i (正样本)
        view2_neg = torch.cat([
            similarity_matrix[batch_size+i, :i], 
            similarity_matrix[batch_size+i, i+1:batch_size],
            similarity_matrix[batch_size+i, batch_size:batch_size+i], 
            similarity_matrix[batch_size+i, batch_size+i+1:2*batch_size]
        ])
        negatives.append(view1_neg)
        negatives.append(view2_neg)
    negatives = torch.stack(negatives) # shape [2N, 2N-2]
    # 5. 计算损失: -log( exp(pos) / (exp(pos) + sum(exp(neg))) )
    logits = torch.cat([positives.unsqueeze(1), negatives], dim=1) # shape [2N, 2N-1]
    labels = torch.zeros(logits.shape[0], dtype=torch.long, device=features.device)
    loss = F.cross_entropy(logits, labels)
    return loss

实际应用中(如 PyTorch Lightning 的 SimCLR),有更高效的实现方式,SimCLR 论文中使用的 LARS 优化器和更大的 batch size(4096)。

  • InfoNCE = 对比学习 + NCE 采样 + 互信息最大化
  • 它通过动态构造大量负样本(通常是 Batch 内的其他样本),将问题转化为一个多分类任务
  • 关键在于选择合适的温度系数 (\tau) 和负样本数量 (N)。

抱歉,评论功能暂时关闭!