基于图神经网络的推荐系统核心是将用户、物品以及它们之间的关系构建为图结构,通过图神经网络学习节点嵌入表示,最终完成推荐任务。相比传统推荐方法,它能更好地捕捉用户和物品之间的高阶关联,提升推荐效果。

核心数据预处理步骤
首先需要整理用户行为数据,构建图结构所需的基础数据。通常需要三类数据:用户特征、物品特征、用户物品交互记录。交互记录可以是点击、购买、收藏等行为,需要转换为图中的边关系。
预处理的核心是将数据转换为图神经网络可接受的格式,这里以PyTorch Geometric库为例,先完成数据加载和图构建:
import torch
from torch_geometric.data import Data
# 假设用户数量为num_users,物品数量为num_items
num_users = 1000
num_items = 5000
# 生成模拟用户物品交互边,shape为[2, num_edges],第一行是用户节点索引,第二行是物品节点索引
# 用户节点索引从0到num_users-1,物品节点索引从num_users到num_users+num_items-1
edge_index = torch.randint(0, num_users, (1, 20000))
item_edges = torch.randint(num_users, num_users + num_items, (1, 20000))
edge_index = torch.cat([edge_index, item_edges], dim=0)
# 节点特征,用户和物品都有对应的特征维度,这里用随机特征模拟
x = torch.randn(num_users + num_items, 32)
# 构建图数据对象
graph_data = Data(x=x, edge_index=edge_index)
print(f"图数据节点数量: {graph_data.num_nodes}, 边数量: {graph_data.num_edges}")
图神经网络模型定义
推荐场景常用的图神经网络层包括GCNConv、GATConv等,这里选择GCN作为基础层搭建推荐模型,模型需要输出用户和物品的嵌入向量,用于后续相似度计算完成推荐。
import torch.nn as nn
import torch.nn.functional as F
from torch_geometric.nn import GCNConv
class GNNRecommender(nn.Module):
def __init__(self, in_channels, hidden_channels, out_channels):
super(GNNRecommender, self).__init__()
# 第一层GCN卷积
self.conv1 = GCNConv(in_channels, hidden_channels)
# 第二层GCN卷积
self.conv2 = GCNConv(hidden_channels, out_channels)
def forward(self, x, edge_index):
# 第一层卷积后经过ReLU激活
x = self.conv1(x, edge_index)
x = F.relu(x)
x = F.dropout(x, p=0.5, training=self.training)
# 第二层卷积输出最终嵌入
x = self.conv2(x, edge_index)
return x
# 初始化模型,输入特征维度32,隐藏层64,输出嵌入维度16
model = GNNRecommender(32, 64, 16)
print(model)
训练方案核心逻辑
训练过程需要定义损失函数和优化器,推荐场景常用的损失函数是BPR损失或者交叉熵损失,这里以BPR损失为例,通过正负样本对比优化模型参数。
import torch.optim as optim
# 定义BPR损失函数
def bpr_loss(pos_score, neg_score):
# pos_score是正样本得分,neg_score是负样本得分
return -torch.mean(torch.log(torch.sigmoid(pos_score - neg_score) + 1e-8))
# 初始化优化器
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 模拟训练循环
def train(model, graph_data, epochs=10):
model.train()
for epoch in range(epochs):
optimizer.zero_grad()
# 前向传播得到所有节点嵌入
embeddings = model(graph_data.x, graph_data.edge_index)
# 拆分用户嵌入和物品嵌入
user_emb = embeddings[:num_users]
item_emb = embeddings[num_users:]
# 随机采样正样本和负样本,这里用模拟数据
pos_user = torch.randint(0, num_users, (128,))
pos_item = torch.randint(0, num_items, (128,))
neg_item = torch.randint(0, num_items, (128,))
# 计算正样本得分和负样本得分
pos_score = torch.sum(user_emb[pos_user] * item_emb[pos_item], dim=1)
neg_score = torch.sum(user_emb[pos_user] * item_emb[neg_item], dim=1)
# 计算损失并反向传播
loss = bpr_loss(pos_score, neg_score)
loss.backward()
optimizer.step()
print(f"Epoch {epoch+1}, Loss: {loss.item():.4f}")
# 执行训练
train(model, graph_data)
训练方案优化要点
实际落地时还需要对训练方案做进一步优化,首先是负采样策略,不能随机选择负样本,需要根据物品热度做加权采样,提升训练效率。其次可以添加正则化项,避免模型过拟合,比如对嵌入向量做L2正则。
另外如果图规模较大,可以采用邻居采样、图划分等技巧减少单次训练的计算量,同时可以加入用户和物品的额外特征,比如用户年龄、物品类目等,拼接进初始节点特征,提升嵌入的表达能力。
常见问题处理
- 图数据过大会导致内存不足,可以采用小批量训练的方式,每次加载部分节点和边进行训练
- 训练损失不下降可以检查学习率是否合适,或者增加模型层数、调整隐藏层维度
- 推荐结果偏差大可以检查预处理阶段的交互边是否正确,是否存在数据泄露问题
需要注意的是,图神经网络的训练对超参数比较敏感,建议先在小规模数据集上调试好超参数,再迁移到全量数据上训练,同时定期评估模型在验证集上的推荐效果,及时调整训练策略。