LightGCN:简化图卷积用于协同过滤推荐
LightGCN:简化图卷积用于协同过滤推荐
1. 什么是协同过滤与图推荐
协同过滤(Collaborative Filtering,CF)是推荐系统的经典范式,其核心假设是:用户过去喜欢过的物品,与相似用户喜欢的物品也值得推荐。传统方法如矩阵分解(MF)学习用户和物品的隐向量,通过向量内积预测评分。然而,MF 只考虑用户与物品之间的直接交互,忽略了高阶连通性。
随着图神经网络(GNN)的兴起,基于图的协同过滤将用户-物品交互建模为二分图。图中的节点为用户和物品,边代表交互(如点击、购买)。通过图卷积操作,节点的表示能聚合其多跳邻居的信息,从而捕捉用户-物品-用户等协同信号。
2. 图卷积网络(GCN)的回顾
GCN 的核心是层间传播规则:
[ H^{(k+1)} = \sigma\left( \tilde{D}^{-\frac{1}{2}} \tilde{A} \tilde{D}^{-\frac{1}{2}} H^{(k)} W^{(k)} \right) ]
- (\tilde{A} = A + I):添加自环的邻接矩阵
- (\tilde{D}):(\tilde{A}) 的度矩阵
- (H^{(k)}):第 (k) 层节点特征
- (W^{(k)}):可学习的特征变换矩阵
- (\sigma):非线性激活函数(如 ReLU)
在协同过滤中,(A) 就是用户-物品交互矩阵构成的二分图邻接矩阵。早期工作 NGCF(Neural Graph Collaborative Filtering)直接套用这样的 GCN 结构来做推荐,取得了不错的提升。
3. LightGCN 的诞生:为什么需要简化?
3.1 NGCF 的“冗余”操作
NGCF 虽然有效,但通过大量消融实验发现,其中特征变换矩阵 (W) 和非线性激活 (\sigma) 对协同过滤任务并无明显增益,反而会增加训练难度。这是因为:
- 用户和物品的初始嵌入已经是可训练的参数向量(即 ID 嵌入),不需要像视觉任务那样进行复杂的特征提取和变换。
- 协同过滤的核心在于邻居信息的聚合与平滑,非线性反而可能破坏这种平滑性。
- 过度的参数化还容易导致过拟合,尤其在数据稀疏的场景下。
3.2 LightGCN 的设计哲学
LightGCN 大胆地去除了 GCN 中的特征变换和非线性激活,只保留最本质的邻居聚合。这让模型极大简化,更容易训练,效果却超越了 NGCF 和诸多多模态模型。其层定义如下:
[ e_u^{(k+1)} = \sum_{i \in \mathcal{N}_u} \frac{1}{\sqrt{|\mathcal{N}_u|}\sqrt{|\mathcal{N}i|}} e_i^{(k)} ] [ e_i^{(k+1)} = \sum{u \in \mathcal{N}_i} \frac{1}{\sqrt{|\mathcal{N}_i|}\sqrt{|\mathcal{N}_u|}} e_u^{(k)} ]
其中,(e_u^{(k)}) 和 (e_i^{(k)}) 分别是用户 (u) 和物品 (i) 在第 (k) 层的嵌入,(\mathcal{N}_u) 是用户 (u) 交互过的物品集合,(\mathcal{N}_i) 是物品 (i) 被哪些用户交互的集合。对称归一化系数 (\frac{1}{\sqrt{|\mathcal{N}_u||\mathcal{N}_i|}}) 遵循标准 GCN 设计,防止度数大的节点使嵌入尺度膨胀。
4. LightGCN 模型架构详解
4.1 初始嵌入层
每个用户和物品都分配一个可学习的嵌入向量 (e_u^{(0)} \in \mathbb{R}^d) 和 (e_i^{(0)} \in \mathbb{R}^d),其中 (d) 是嵌入维度。这些向量随机初始化。
4.2 多层线性传播
对于 (K) 层 LightGCN,嵌入递归地按照第 3 节的公式传播。因为没有可训练权重和激活函数,整个过程完全线性,可以通过矩阵形式高效实现。
令用户-物品交互矩阵为 (R \in \mathbb{R}^{M \times N}),其中 (M) 为用户数,(N) 为物品数。则二分图邻接矩阵可以表示为:
[ A = \begin{pmatrix} 0 & R \ R^T & 0 \end{pmatrix} ]
归一化邻接矩阵为 (\tilde{A} = D^{-\frac{1}{2}} A D^{-\frac{1}{2}}),其中 (D) 是度矩阵。那么第 (k) 层的传播等价于:
[ E^{(k+1)} = \tilde{A} E^{(k)} ]
这里的 (E^{(k)} \in \mathbb{R}^{(M+N) \times d}) 是所有节点在第 (k) 层的嵌入堆叠。
4.3 层组合
经过 (K) 层传播,我们得到每一层的嵌入 (E^{(0)}, E^{(1)}, \dots, E^{(K)})。LightGCN 组合它们以形成最终的节点表示:
[ E = \alpha_0 E^{(0)} + \alpha_1 E^{(1)} + \alpha_2 E^{(2)} + \dots + \alpha_K E^{(K)} ]
最常见也最简单的组合方式是求和(所有 (\alpha_k = 1) 或采用加权求和)。论文中采用 (\alpha_k = \frac{1}{K+1}) 的简单平均值,效果就已经很好。
组合的原因:
- 避免深层出现过度平滑问题。
- 保留了不同阶(1阶、2阶、高阶)的邻域信息,像一个携带了自连接的多尺度特征。
4.4 预测层
最终预测用户 (u) 对物品 (i) 的得分采用简单的内积:
[ \hat{y}_{ui} = e_u^T e_i ]
其中 (e_u) 和 (e_i) 是层组合后的最终嵌入。整个模型只用到了内积作为预测函数,没有复杂的 MLP 预测头。
5. 模型训练
5.1 损失函数
LightGCN 采用贝叶斯个性化排序(BPR)损失进行优化,这个损失鼓励观测到的交互得分高于未观测到的交互:
[ L = -\sum_{u=1}^M \sum_{i \in \mathcal{N}u} \sum{j \notin \mathcal{N}u} \ln \sigma(\hat{y}{ui} - \hat{y}_{uj}) + \lambda ||E^{(0)}||^2 ]
- (\sigma) 是 sigmoid 函数。
- 负样本 (j) 从用户未交互的物品中均匀采样。
- 正则化项仅施加在第0层嵌入上(因为其他层嵌入由传播产生),系数 (\lambda) 控制强度。
5.2 训练流程
- 构造邻接矩阵并归一化。
- 初始化所有用户和物品的第0层嵌入。
- 对于每个训练批次,采样一批用户及其交互物品和负样本。
- 通过多层传播得到用户的最终嵌入(可以提前缓存整个图所有节点的嵌入,因为邻域聚合不依赖批次)。
- 计算 BPR 损失并反向传播,只更新第0层嵌入参数。
- 达到预设的迭代轮数后停止。
6. 与其它模型的关系
LightGCN 可以被视为对以下模型的抽象:
- 矩阵分解(MF):仅使用第0层嵌入和内积,即 (K=0) 的特例。
- SVD++ 和 FISM:可以看作手工设计的1阶邻居加权和,LightGCN 将其推广到任意阶并自动学习权重。
- PinSage 等工业图推荐模型:使用带权重的邻居聚合,LightGCN 证明去掉变换和非线性依然强大。
7. 代码实现示例(PyTorch 伪代码)
为了方便理解,下面给出核心传播部分的代码结构。
import torch
import torch.nn as nn
class LightGCN(nn.Module):
def __init__(self, num_users, num_items, embed_dim, n_layers):
super().__init__()
self.num_users = num_users
self.num_items = num_items
self.embed_dim = embed_dim
self.n_layers = n_layers
# 第0层嵌入
self.user_embeddings = nn.Embedding(num_users, embed_dim)
self.item_embeddings = nn.Embedding(num_items, embed_dim)
# 初始化
nn.init.xavier_uniform_(self.user_embeddings.weight)
nn.init.xavier_uniform_(self.item_embeddings.weight)
# 归一化邻接矩阵将作为外部数据传入
self.norm_adj = None
def forward(self, users, items, neg_items):
# 获取所有0层嵌入
all_embeddings = torch.cat([self.user_embeddings.weight, self.item_embeddings.weight], dim=0)
embeddings_list = [all_embeddings]
# 多层传播
for layer in range(self.n_layers):
all_embeddings = torch.sparse.mm(self.norm_adj, all_embeddings)
embeddings_list.append(all_embeddings)
# 层组合(平均)
final_embeddings = torch.stack(embeddings_list, dim=1).mean(dim=1)
# 拆分用户和物品嵌入
user_final, item_final = torch.split(final_embeddings, [self.num_users, self.num_items])
# 获取批次嵌入
user_emb = user_final[users]
pos_item_emb = item_final[items]
neg_item_emb = item_final[neg_items]
# 预测得分
pos_scores = (user_emb * pos_item_emb).sum(dim=1)
neg_scores = (user_emb * neg_item_emb).sum(dim=1)
return pos_scores, neg_scores
def bpr_loss(self, pos_scores, neg_scores):
loss = -torch.log(torch.sigmoid(pos_scores - neg_scores) + 1e-10).mean()
# L2 正则化仅施加于第0层
reg_loss = (self.user_embeddings.weight.norm(2) + self.item_embeddings.weight.norm(2)) / 2
return loss + reg_loss * 1e-4
请注意,实际实现需要提前计算 norm_adj 并将其转换为稀疏张量(如 torch.sparse.FloatTensor)以支持高效矩阵乘法。
8. 效果与优势
LightGCN 在多个公开数据集(Gowalla、Yelp2018、Amazon-Book)上,对比 NGCF、Mult-VAE 等强基线,在 Recall@20 和 NDCG@20 上均有显著提升。其优势可归纳为:
- 极简结构:只有初始嵌入是可训练参数,模型体积极小。
- 训练高效:去掉了非线性与权重变换,前向传播速度大幅加快,内存占用更低。
- 泛化更强:简化带来的正则化效果使其在稀疏数据下不容易过拟合。
- 理论清晰:直接揭示了基于图的协同过滤的核心——邻域平滑与多层组合。
9. 总结与进阶方向
LightGCN 是对基于图的协同过滤的一次优雅简化。它告诉我们,在像推荐这样的 ID 场景中,图神经网络的效能主要来源于图结构上的相邻节点信息平滑,而非特征变换和非线性激活。
后续拓展方向:
- 社会关系与知识图谱:纳入社交网络或物品属性图,构造异构图进行 LightGCN 风格的传播。
- 自监督学习:结合对比学习,利用数据增强生成更鲁棒的节点表示(如 SGL 模型)。
- 超图结构:用超图建模多用户共同点击行为,进一步捕捉高阶协同模式。
- 实时更新:针对新交互动态更新图结构及节点嵌入,实现在线服务。
LightGCN 不仅是学术上的经典基线,也是工业界落地的优选模型。建议你从复现论文开始,将上述伪代码改造为可运行的完整项目,深入感受图推荐的核心思想。