系列 · 推荐系统 · 第 11 篇

推荐系统(十一)—— 对比学习与自监督学习

对比学习在推荐系统里到底怎么用?这一篇把 InfoNCE、温度系数、SimCLR/MoCo 的负样本来源、SGL 的图增广、CL4SRec 的序列增广、XSimGCL 的极简噪声扰动讲清楚,配 PyTorch 实现与原理图。

经典推荐系统只依赖一种信号:用户是否点击、观看或购买?这种信号固然宝贵,却也极其稀疏。大多数用户接触的商品不到总目录的 1%,大多数商品被触达的用户也不到 0.1%,而全新用户或商品则完全没有交互记录。直接用如此稀疏的标签优化模型,几乎注定会在热门头部过拟合,而在长尾部分毫无反应。

对比学习提供了一种不同的思路。它不再问“这个样本该打什么标签?”,而是问“哪两个样本应该相似,哪两个应该不同?”这个问题很容易回答——你可以通过对同一个用户、商品或行为序列施加两种不同的扰动,将得到的两个结果视为正样本对;同一批次中的其他所有样本则自动成为负样本。模型由此学习嵌入空间的几何结构:语义相近的样本在空间中彼此靠近,不相关的则相互远离。一旦这种几何结构建立起来,后续的有监督推荐头只需轻微调整即可。

本文将先拆解对比学习的核心组件(InfoNCE、温度系数、数据增强),再详细介绍推荐系统中真正实用的四类方法:SimCLR 风格的批次内对比、MoCo 风格的队列式对比、SGL 的图结构增强,以及 CL4SRec 的序列增强策略。最后,我们会介绍一个反直觉但重要的发现:XSimGCL 完全抛弃了复杂的增强操作,仅通过在嵌入向量上注入微小噪声,就能取得更优效果。

推荐系统(十一)—— 对比学习与自监督学习 — 章节概览图


你将学到什么#

  • 为什么对比学习能从全新角度应对稀疏性、冷启动和热度偏差问题,而非简单依赖“更多数据”
  • InfoNCE 详解:损失函数的来源、温度参数 $\tau$ 的重要性远超直觉,以及它如何塑造梯度
  • SimCLR vs MoCo:批次内负采样与动量编码器队列的对比,以及各自适用的场景
  • SGL(Wu 等,SIGIR 2021):通过节点丢弃、边丢弃、随机游走子图生成图视图的方法
  • CL4SRec(Xie 等,ICDE 2022):序列推荐中的裁剪(crop)、遮掩(mask)、重排序(reorder)三种增强方式
  • SimGCL / XSimGCL(Yu 等,2022/2023):为何简单的嵌入噪声技巧能胜过复杂的图增强
  • 每个模块均配有清晰可运行的 PyTorch 实现

前置知识#

  • PyTorch 基础(Module、autograd、损失函数)
  • 图神经网络,尤其是 LightGCN(第 7 篇
  • Embedding 空间与相似度计算(第 5 篇

为什么推荐系统需要对比学习?#

再谈数据稀疏性的本质#

稀疏性不只是“标签太少”,而是一种结构性困境:

  1. 冷启动问题:新用户和新物品没有任何交互记录,因此任何依赖历史行为构建特征的模型(如矩阵分解、双塔模型、GNN)都无法为其在嵌入空间中分配有意义的位置。
  2. 头部过拟合:点击分布高度长尾,损失函数几乎完全由少数热门物品主导。模型若只是记住了这些热门项,在训练指标上可能表现良好,但在长尾物品上的推荐效果却极差。
  3. 热度偏差:即使召回阶段提供了多样化的候选集,排序模型仍会倾向于将热门物品排得更高——因为这正是最小化训练损失的方向。系统最终陷入“千篇一律”的循环。

对比学习的权衡逻辑#

对比学习做了一个关键交换:放弃昂贵的监督标签,转而利用廉价的“扰动一致性”信号。例如,对一个用户的行为图随机删除 20% 的边后编码一次,再换另一组 20% 的边删除后再次编码。两次编码对应同一用户,规则很简单:这两个嵌入应尽可能接近,而与其他用户的嵌入应尽可能远离

这一目标同时带来三大收益:

  • 无限量的免费训练信号:任何用户都可被反复扰动,生成任意多的正样本对。
  • 鲁棒的表示先验:模型学到的是那些在扰动下依然稳定的特征——这正是冷启动和长尾物品最需要的。
  • 防止塌缩的正则化:损失函数显式地将不同用户推开,避免所有嵌入都挤向热门区域。

锚点用户被拉近两个增广视图(正样本),同时被推离 batch 内其他用户(负样本)

上图直观展示了这一思想:蓝色锚点代表一个用户,两个绿色点是其两个增强视图,损失函数将它们拉近;琥珀色点是同批次中的其他用户,损失函数将它们推开。对每个用户、每个批次重复此过程,最终形成的嵌入空间中,语义相似性与向量距离高度一致。

InfoNCE:真正驱动学习的损失函数#

推荐系统(十一)—— 对比学习与自监督学习 — 章节小结图

$$ \mathcal{L}_{\text{InfoNCE}} = -\log \frac{\exp\!\big(\mathrm{sim}(f(x), f(x^+)) / \tau\big)}{\exp\!\big(\mathrm{sim}(f(x), f(x^+)) / \tau\big) + \sum_{i} \exp\!\big(\mathrm{sim}(f(x), f(x_i^-)) / \tau\big)} $$

这本质上是一个 $(N+1)$ 分类任务的交叉熵,目标是让模型将“正样本”识别为正确类别。分子鼓励拉近正样本,分母则迫使模型将正样本排在所有负样本之上。正是这种排序压力,阻止了所有嵌入坍缩到同一向量的平凡解。

温度系数:最容易被低估的关键旋钮#

温度 $\tau$ 控制 softmax 区分正负样本的锐利程度。它绝非装饰性超参,而是直接影响优化目标。

左:不同温度下,InfoNCE 损失随正样本相似度的变化;右:相同情况下损失对正样本相似度的梯度

从右图可得出两点结论:

  • $\tau$ (如 0.05) 使梯度在决策边界附近非常尖锐,模型几乎将全部容量集中在最难区分的负样本上——即那些与正样本相似度接近的样本。这对细粒度判别很有利,但如果这些“困难负样本”实际是误标数据(如非随机缺失的点击),训练会变得不稳定。
  • $\tau$ (如 1.0) 将梯度均匀分配给所有负样本,包括容易区分的样本。优化更平滑,但嵌入聚类不够紧密。
  • 推荐系统的最佳温度通常在 $\tau \in [0.1, 0.2]$ :足够锐利以产生有效对比,又不至于过度追逐噪声。

一个有用的直觉是:$1/\tau$ 相当于“放大倍数”。将 $\tau$ 减半,所有相似度差距会被放大一倍,从而加倍推动近正样本与近负样本分离的梯度压力。

为何负样本不可或缺#

如果只优化分子(即单纯拉近正样本),所有嵌入会坍缩到同一常向量,损失迅速归零。负样本不是配角,而是支撑整个学习过程的关键约束。这也解释了为何 SimCLR 风格方法极度依赖大批次:batch size 为 256 时,每个锚点有 510 个负样本;batch size 为 4096 时,负样本增至 8190。负样本越多,分母越密集,对比信号越强。


SimCLR vs MoCo:负样本从何而来?#

视觉自监督领域的两大范式已被直接引入推荐系统:

SimCLR 使用 batch 内其他视图作为负样本(单一共享编码器);MoCo 维护一个动量更新的 key 编码器和 FIFO 队列缓存负样本

SimCLR(Chen 等,ICML 2020)设计简洁:单编码器、单投影头、每个样本两次增强,同一批次中其他所有视图均为负样本。缺点显而易见——负样本数量受限于 GPU 显存允许的最大 batch size。

MoCo(He 等,CVPR 2020)则解耦两者:query 编码器通过 SGD 更新,动量 key 编码器以指数滑动平均方式更新($\xi \leftarrow m\xi + (1-m)\theta$ ),并维护一个 FIFO 队列存储最近 $K$ 个 key 编码(通常 $K = 65{,}536$ )。负样本来自队列,因此 $K$ 与 batch size 无关。

在推荐系统中,SimCLR 更常用:CTR/CVR 训练本就使用大批次;编码器多为 GNN,前向计算开销主要在图遍历而非嵌入查找;且百万级用户 key 队列会快速过时——因为嵌入表本身持续更新。MoCo 风格的队列仅在长序列检索等编码器极重的场景中出现,此时确实需要摊销计算成本。

SimCLR 损失的参考实现#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import torch
import torch.nn as nn
import torch.nn.functional as F

def info_nce(z1: torch.Tensor, z2: torch.Tensor, tau: float = 0.2) -> torch.Tensor:
    """两视图对称的 SimCLR 风格 InfoNCE。

    z1、z2 形状为 (B, D),已 L2 归一化。z1[i] 的正样本是 z2[i],反之亦然;
    拼接后的 2B 大小 batch 中,其他位置都算负样本。
    """
    B = z1.size(0)
    z = torch.cat([z1, z2], dim=0)                  # (2B, D)
    sim = z @ z.T / tau                             # (2B, 2B)
    sim.fill_diagonal_(float("-inf"))               # 排除自身相似性
    # 第 i 行(i<B)的正样本在第 i+B 列;第 i 行(B<=i<2B)的正样本在第 i-B 列
    targets = torch.cat([torch.arange(B, 2 * B), torch.arange(0, B)]).to(z.device)
    return F.cross_entropy(sim, targets)

这段十行左右的代码有三个关键细节:

  1. 嵌入需预先归一化。此时余弦相似度即点积,温度 $\tau$ 才具有前述几何意义。
  2. 对角线用 $-\infty$ 屏蔽,而非 0。因 cross_entropy 在 log-softmax 空间工作,$-\infty$ 会从归一化项中消失;若用 0,则会引入 $e^0=1$ 的偏置项,悄悄扭曲梯度。
  3. 损失是对称的:$2B$ 行各贡献一个交叉熵项。一半行以第二视图为正样本,另一半以第一视图为正样本。

SGL:用户-物品图上的对比学习#

SGL(Wu 等,SIGIR 2021,《Self-supervised Graph Learning for Recommendation》)是将对比学习带入推荐主流的开创性工作。其核心是在 LightGCN 主干上附加一个 InfoNCE 头,并将两个扰动后的用户-物品图视为每个节点的正样本对。

三种图扰动方式#

原始用户-物品二部图与 SGL 的三种增广:边丢弃、节点丢弃、随机游走子图

  • 边丢弃(Edge Dropout, ED):每条边以概率 $1-p$ 独立保留。实现简单、结构保持性好,是最常用变体。
  • 节点丢弃(Node Dropout, ND):每个节点及其关联边以概率 $p$ 被移除。增强更强,但可能使低度节点不稳定。
  • 随机游走子图(Random Walk, RW):从每个锚点出发进行长度为 $L$ 的随机游走,采样子图作为视图。

原论文消融实验表明,边丢弃在多数数据集上表现稳定甚至略优,且实现最简。除非有特殊需求,否则直接选用 ED 即可。

完整的 SGL 训练步骤#

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import torch
import torch.nn as nn
import torch.nn.functional as F

def edge_dropout(edge_index: torch.Tensor, p: float) -> torch.Tensor:
    keep = torch.rand(edge_index.size(1), device=edge_index.device) > p
    return edge_index[:, keep]

class SGL(nn.Module):
    """LightGCN 主干 + 在两个边丢弃视图上的节点级 InfoNCE。"""

    def __init__(self, n_users, n_items, dim=64, n_layers=3,
                 drop_p=0.1, tau=0.2, lam=0.1):
        super().__init__()
        self.n_users, self.n_items = n_users, n_items
        self.n_layers, self.drop_p, self.tau, self.lam = n_layers, drop_p, tau, lam
        self.user_emb = nn.Embedding(n_users, dim)
        self.item_emb = nn.Embedding(n_items, dim)
        nn.init.normal_(self.user_emb.weight, std=0.1)
        nn.init.normal_(self.item_emb.weight, std=0.1)

    def _propagate(self, x, edge_index):
        """对称归一化的 LightGCN 传播,取各层平均。"""
        row, col = edge_index
        deg = torch.bincount(row, minlength=x.size(0)).clamp(min=1).float()
        norm = deg.pow(-0.5)
        layers = [x]
        for _ in range(self.n_layers):
            msg = x[col] * (norm[row] * norm[col]).unsqueeze(1)
            agg = torch.zeros_like(x).index_add_(0, row, msg)
            x = agg
            layers.append(x)
        return torch.stack(layers, dim=0).mean(dim=0)

    def encode(self, edge_index):
        x = torch.cat([self.user_emb.weight, self.item_emb.weight], dim=0)
        return self._propagate(x, edge_index)

    def cl_loss(self, z1, z2):
        z1, z2 = F.normalize(z1, dim=1), F.normalize(z2, dim=1)
        sim = z1 @ z2.T / self.tau
        targets = torch.arange(z1.size(0), device=z1.device)
        return F.cross_entropy(sim, targets)

    def bpr_loss(self, z, users, pos, neg):
        u, p, n = z[users], z[self.n_users + pos], z[self.n_users + neg]
        return -F.logsigmoid((u * p).sum(-1) - (u * n).sum(-1)).mean()

    def forward(self, edge_index, users, pos_items, neg_items):
        # 两个增广视图,提供对比信号
        z1 = self.encode(edge_dropout(edge_index, self.drop_p))
        z2 = self.encode(edge_dropout(edge_index, self.drop_p))
        # 一次干净前向传播,提供推荐信号
        z = self.encode(edge_index)
        return self.bpr_loss(z, users, pos_items, neg_items) \
             + self.lam * self.cl_loss(z1, z2)

几个非显而易见的实现细节:

  • 三次前向传播,而非两次。对比视图的扰动与 BPR 使用的原始图相互独立。若共享扰动图,监督信号会被偶然保留的边所偏置。
  • 对比损失在节点层面计算,作用于用户与物品嵌入的拼接向量。这确保二部图两侧都能获得自监督信号。
  • 损失权重 $\lambda$ 至关重要。过小($<10^{-2}$ )则对比信号消失;过大($>1$ )则 BPR 失去主导。SGL 论文在 $\{0.005, 0.05, 0.1, 0.5, 1.0\}$ 中测试,发现 $0.1$ 是稳健默认值——建议从此开始调参。

CL4SRec:序列推荐中的对比学习#

对于序列推荐模型(如 SASRec、BERT4Rec、GRU4Rec),核心问题是:“如何为同一条行为序列生成两个视图?” CL4SRec(Xie 等,ICDE 2022)提出的三种增强方式已成为标准。

行为序列的三种增强方式:Crop 截取连续片段,Mask 将随机位置替换为 [M],Reorder 打乱一段连续区间

  • Crop:保留长度为 $\eta L$ 的连续子序列。保留局部顺序,教会模型对“晚开始”或“早结束”保持不变性。
  • Mask:随机将 $\gamma$ 比例的物品替换为特殊标记 [M]。类似 BERT 的掩码语言建模,要求编码器从上下文推断被遮项。
  • Reorder:打乱一段长度为 $\beta L$ 的连续区间。强调会话中物品集合比精确顺序更重要。

CL4SRec 为每个视图随机选择一种增强,共形成九种(视图 A, 视图 B)组合。这种随机混合本身就是一种正则化,防止编码器过拟合单一增强策略。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import random
import torch
import torch.nn as nn
import torch.nn.functional as F

class SeqAugment:
    """CL4SRec 的三种序列增强方法。序列是 LongTensor 类型的物品 ID,
    ID=0 保留用于 padding/mask。"""

    def __init__(self, crop_eta=0.6, mask_gamma=0.3, reorder_beta=0.6, mask_id=0):
        self.crop_eta = crop_eta
        self.mask_gamma = mask_gamma
        self.reorder_beta = reorder_beta
        self.mask_id = mask_id

    def __call__(self, seq: torch.Tensor) -> torch.Tensor:
        op = random.choice(["crop", "mask", "reorder"])
        L = seq.size(0)
        if op == "crop":
            k = max(1, int(L * self.crop_eta))
            s = random.randint(0, L - k)
            return seq[s:s + k]
        if op == "mask":
            out = seq.clone()
            n = max(1, int(L * self.mask_gamma))
            idx = torch.randperm(L)[:n]
            out[idx] = self.mask_id
            return out
        # reorder
        out = seq.clone()
        k = max(2, int(L * self.reorder_beta))
        s = random.randint(0, L - k)
        chunk = out[s:s + k][torch.randperm(k)]
        out[s:s + k] = chunk
        return out

编码器可使用任意现有序列模型(Transformer、GRU 等)。将最终隐藏状态池化后,经投影、归一化,输入前述 info_nce 函数,作为辅助损失叠加到常规的 next-item 预测任务上即可。

XSimGCL:图增强本身并不重要#

Yu 等人(2022/2023)的一项惊人发现值得单独强调:他们追问 SGL 的性能提升究竟源于图增强,还是对比损失本身?答案是:几乎全部来自对比损失。将图边丢弃替换为在传播后的嵌入上添加微量均匀噪声,效果即可持平甚至超越 SGL,同时省去所有图扰动的复杂逻辑。

具体做法(SimGCL / XSimGCL,后者为简化版)如下:

  1. 正常执行 LightGCN 传播。
  2. 每层向嵌入添加微小噪声 $\Delta$ :方向为单位随机向量,缩放因子为小值 $\epsilon$ (如 0.1),且确保与原向量同半球(避免翻转)。
  3. 用不同噪声样本再传播一次,得到第二视图。无需图丢弃。
1
2
3
4
5
def add_noise(x: torch.Tensor, epsilon: float = 0.1) -> torch.Tensor:
    """SimGCL 的同半球均匀噪声扰动。"""
    noise = torch.rand_like(x)
    noise = F.normalize(noise, dim=-1) * torch.sign(x) * epsilon
    return x + noise

SimGCL 的深层洞见在于:对比损失同时完成两件事——

  • 对齐(Alignment):拉近正样本对(分子作用)。
  • 均匀性(Uniformity):推动所有嵌入在单位超球面上均匀分布(分母作用)。

其中,均匀性是打破热度偏差的主导效应。一旦实现均匀性,生成第二视图的具体方式(图丢弃、嵌入噪声等)几乎不再重要。


这些方法到底有多大提升?#

Recall@20 和 NDCG@20 在四个标准数据集上的对比:LightGCN 基线 vs +SGL vs +XSimGCL

上述数字源自 SGL(Wu 等,2021)和 SimGCL/XSimGCL(Yu 等,2022/2023)在 Yelp2018、Amazon-Book、Alibaba-iFashion 和 Gowalla 上的报告。结论高度一致:在相同主干模型上加入对比辅助损失,Recall@20 和 NDCG@20 可提升 5%–20%,且增益集中于长尾物品和冷启动用户——这正是我们最关心的部分。

这种提升在嵌入空间中如何体现?

对比学习训练前后的物品嵌入,t-SNE 投影:从纠缠成团到清晰分开的兴趣簇

训练前,物品嵌入松散聚集,主变化轴是热度,语义结构被掩盖;训练后,物品按兴趣类别形成紧密且分离的簇。下游打分模块因此受益——一个近似线性的决策边界就已足够。


常见问题#

为何不直接用更多数据,而非要用对比学习?#

冷启动用户无法获得更多数据——他们“冷”正是因为无交互。长尾物品同理——它们“尾”正是因为极少被触达。对比学习不依赖新交互,而是通过对已有交互做扰动生成信号。这是与“收集更多点击”本质不同的路径。

图增强和嵌入噪声如何选择?#

优先选嵌入噪声(XSimGCL):更快、更简,且近期研究一致表明其效果不输甚至优于图增强。仅当你希望正则化 GNN 的结构归纳偏置,或对关键边/节点有明确先验时,才考虑 SGL 的图增强。

温度系数如何设置?#

$\tau = 0.2$ 开始。若困难负样本基本可靠(如有明确停留时长信号),可降至 0.05–0.1 以增强区分;若负样本噪声大(如高维目录中随机采样的同批用户),保持 $\tau \geq 0.2$ 以防过拟合假负样本。在小网格上调参即可,损失对 $\tau$ 的变化较平滑。

是否需要投影头?#

在 GNN 上使用 SimCLR/SGL 时需要——但预训练后可丢弃。投影头让编码器保持通用性,而将对比任务特化交给投影层。XSimGCL 是例外:它直接对比 GNN 传播后的嵌入,无需投影头也能工作良好。

如何结合对比损失与推荐损失?#

$$ \mathcal{L}_{\text{total}} = \mathcal{L}_{\text{rec}} + \lambda \cdot \mathcal{L}_{\text{CL}} $$

初始设 $\lambda = 0.1$ 。若结果敏感,可在验证集上扫 $\{0.01, 0.05, 0.1, 0.5, 1.0\}$ 。相比 $\tau$$\lambda$ 的敏感度低得多,不必在此耗费过多调参预算。

对隐式反馈是否有效?#

不仅有效,甚至更适合。整个框架将每次交互视为正样本,由 InfoNCE 结构隐式处理负样本。SGL、SimGCL、XSimGCL 和 CL4SRec 均为隐式反馈(点击、播放、购买)设计。

数据量少到何种程度仍可用?#

对比学习恰在监督信号稀缺时最有效。SGL 在 Yelp2018(稀疏度 ~99.87%)上的增益大于稠密数据集。低于 ~10K 交互时,随机增强的方差可能主导,需更强先验(如跨域迁移、内容特征);高于 ~100K 交互时,通常可获稳定提升。

如何评估对比推荐系统?#

除标准 top-K 指标(Recall@K、NDCG@K、HR@K)外,还应关注:

  • 冷启动切片:按交互频次分桶,报告各桶指标。
  • 长尾覆盖:推荐列表中长尾物品占比(通常定义为流行度后 80%)。
  • 嵌入诊断:用 t-SNE/UMAP 可视化,或按 Wang & Isola(2020)计算均匀性($\log \mathbb{E}\,e^{-2\|z_i - z_j\|^2}$ )和对齐性($\mathbb{E}\,\|z - z^+\|^2$ )。高均匀性 + 低对齐性 = 健康嵌入。

离线与在线指标的差异#

这是我见过最多团队在上线对比推荐系统时踩的坑。

SGL/CL4SRec 的离线结果非常亮眼:在 Yelp、Amazon、MovieLens 上,NDCG@20 提升 5%–15%,Recall@20 提升 8%–20%。论文漂亮,结果喜人。但线上 A/B 测试时,CTR 仅提升 +0.3%,95% 置信区间为 $\pm 0.4\%$ 。离线碾压的模型,线上几乎无感。

原因何在?

离线评测基于留出点击,而这些点击来自一个已有热度偏差的系统(通常是流行度倾斜的 CF 基线)。当你的对比模型通过更锐利的嵌入召回更多长尾物品时,离线评测反而惩罚它——因为这些物品在历史日志中根本没机会被点击。离线 NDCG 实际奖励的是“预测旧系统已展示的内容”。

对比增益集中在冷尾部分。SGL 提升了嵌入空间的均匀性(Wang & Isola 2020),使低交互物品的向量更易与热门项区分。但在离线评测中,这些长尾物品极少出现在正样本中;在线上,它们虽获得新曝光并产生点击,但整体 CTR 仍由头部主导——而 SGL 对头部影响甚微。

位置偏差主导短期指标。一个仅重排 top-5 一位的模型,对 CTR 的影响可能超过修复冷启动的模型。多数场景中,位置 1 与位置 2 的 CTR 比约为 1.6×。

应对策略:

  1. 离线评测改用反事实估计(IPS 加权 NDCG),而非普通 NDCG,可弥合大部分差距。
  2. 按物品流行度五分位拆分 A/B 结果。即使大盘持平,SGL 在最低两个五分位通常能带来 +3%–8% CTR 提升。
  3. 实验至少运行 14 天。冷启动收益会累积:今日获曝光的新品产生互动数据,将优化下一轮训练。

若只能汇报一个数字,请报告首周活跃物品的提升(最近 7 天上线的物品)——这才是对比学习真正创造价值的地方。

推理成本:对比学习的轻量一面#

对比学习的训练成本众所周知——InfoNCE 加大批量负采样开销巨大。但推理成本却令人惊喜:线上服务几乎零额外负担

增强、投影头、对比损失均为训练专属。推理时,SGL 与 LightGCN 结构完全一致——相同 GNN 层数、相同嵌入维度、相同点积评分。QPS、延迟、内存均无差异。

真正的瓶颈在训练成本,它决定模型刷新频率。对比训练增加了:

  • 每种增强多一次前向(通常 2 种 → 前向翻倍)
  • InfoNCE 分母需 (batch, batch) 负样本矩阵 → 计算量随 batch size 平方增长

在 1 亿交互数据上,LightGCN 单卡 A100 训练约 3 小时;SGL(2 增强,batch 4096)则需约 9 小时。若生产环境每 4 小时重训,此成本不可忽视。缓解方案:用 MoCo 式动量队列缩小 batch,或直接弃用增强(XSimGCL——仅加嵌入噪声)。

XSimGCL 才是多数团队应选方案:它带来与 SGL 相当的均匀性增益,训练速度仅比原版 LightGCN 慢约 30%,而非 3 倍。

总结#

对比学习解决了一个“更多数据”无法解决的问题:它让模型能从稀疏交互中学习几何结构,方法是要求扰动下的一致性。2024 年的实践方案已很清晰:

  1. 选择一种增强方式,或干脆不用(如 XSimGCL 的噪声)。
  2. 应用 InfoNCE,温度设为 $\tau \approx 0.2$
  3. 作为辅助损失加入,权重 $\lambda \approx 0.1$ ,叠加于现有监督目标之上。

对于图推荐系统,从 XSimGCL 开始——这是最简有效的方案。若需可解释基线,SGL + 边丢弃仍是强选择。对于序列推荐,CL4SRec 的三增强组合已是标配。无论哪种场景,最大收益通常来自冷启动与长尾部分。


本系列

推荐系统 16 篇

  1. 01 推荐系统(一)—— 入门与基础概念
  2. 02 推荐系统(二)—— 协同过滤与矩阵分解
  3. 03 推荐系统(三)—— 深度学习基础模型
  4. 04 推荐系统(四)—— CTR 预估与点击率建模
  5. 05 推荐系统(五)—— Embedding 表示学习
  6. 06 推荐系统(六)—— 序列推荐与会话建模
  7. 07 推荐系统(七)—— 图神经网络与社交推荐
  8. 08 推荐系统(八)—— 知识图谱增强推荐系统
  9. 09 推荐系统(九)—— 多任务学习与多目标优化
  10. 10 推荐系统(十)—— 深度兴趣网络与注意力机制
  11. 11 推荐系统(十一)—— 对比学习与自监督学习 当前
  12. 12 推荐系统(十二)—— 大语言模型与推荐系统
  13. 13 推荐系统(十三)—— 公平性、去偏与可解释性
  14. 14 推荐系统(十四)—— 跨域推荐与冷启动解决方案
  15. 15 推荐系统(十五)—— 实时推荐与在线学习
  16. 16 推荐系统(十六)—— 工业级架构与最佳实践

读有所得?

GitHub 关注我 → 新文周更

GitHub