【RecBole-GNN/源码】RecBole-GNN中lightGCN源码解析

如果觉得我的分享有一定帮助,欢迎关注我的微信公众号 “码农的科研笔记”,了解更多我的算法和代码学习总结记录。或者点击链接扫码关注【RecBole-GNN/源码】RecBole-GNN中lightGCN源码解析

【RecBole-GNN/源码】RecBole-GNN中lightGCN源码解析


原文:https://arxiv.org/pdf/2002.02126.pdf

源码:伯乐工具箱

【RecBole-GNN/源码】RecBole-GNN中lightGCN源码解析_第1张图片

输入数据源(图节点仅仅使用了用户或者物品的ID进行模型搭建):

  • ml-1m.inter
  • ml-1m.item
  • ml-1m.user

GCN聚合消息需要定义节点特征以及边

1 节点

节点特征(是需要经过训练得到合适的embedding):得到所有节点特征all_embeddings(9748(6041+3707)*64)

#定义user嵌入:6041*64
self.user_embedding = torch.nn.Embedding(num_embeddings=self.n_users, embedding_dim=self.latent_dim)
#定义item嵌入:3707*64
self.item_embedding = torch.nn.Embedding(num_embeddings=self.n_items, embedding_dim=self.latent_dim)
user_embeddings = self.user_embedding.weight
item_embeddings = self.item_embedding.weight
#进行组合得到:9748(6041+3707)*64
all_embeddings = torch.cat([user_embeddings, item_embeddings], dim=0)

2 边

得到所有边edge_index(1610886-1) 以及权重 edge_weight(1610886-1)

#根据.iter交互文件,获取user_id那一列作为row(805443*1)
row = self.inter_feat[self.uid_field]
#根据.iter交互文件,获取item_id那一列作为col(计数id需要加self.user_num)(805443*1)
col = self.inter_feat[self.iid_field] + self.user_num
edge_index1 = torch.stack([row, col])
edge_index2 = torch.stack([col, row])
#得到所有边矩阵2*1610886(805443+805443)
# row col //因为边是双向的
# col row 
edge_index = torch.cat([edge_index1, edge_index2], dim=1)
# 获得每个节点的度(节点的连边)
deg = degree(edge_index[0], self.user_num + self.item_num)
#对于每个节点,如果其度数为 $0$,则将其规范化因子设为 $1$,否则将其规范化因子设为 $1/\sqrt{\text{degree}}$。最终,得到的 #norm_deg 张量表示了每个节点的规范化因子。
norm_deg = 1. / torch.sqrt(torch.where(deg == 0, torch.ones([1]), deg))
#为每条边计算一个权重,该权重等于该边两个节点的规范化因子之积。(1610886*1)
edge_weight = norm_deg[edge_index[0]] * norm_deg[edge_index[1]]

3 GCN聚合

for layer_idx in range(self.n_layers):
    all_embeddings = self.gcn_conv(all_embeddings, self.edge_index, self.edge_weight)
    embeddings_list.append(all_embeddings)
#多轮嵌入求均值
lightgcn_all_embeddings = torch.stack(embeddings_list, dim=1)
lightgcn_all_embeddings = torch.mean(lightgcn_all_embeddings, dim=1)
#获得user和item节点的最终嵌入表示
user_all_embeddings, item_all_embeddings = torch.split(lightgcn_all_embeddings, [self.n_users, self.n_items])

self.propagate(edge_index, x=x, edge_weight=edge_weight) 是 PyTorch Geometric(简称 PyG)库中定义的一个函数。该函数的作用是对输入的节点特征矩阵 x 进行消息传递,更新节点特征矩阵,并返回更新后的节点特征矩阵。

其中,edge_index 是一个形状为 2 × E 2 \times E 2×E 的张量,表示图中所有边的起始节点和结束节点的编号, E E E 表示边的数量;x 是一个形状为 N × F N \times F N×F 的节点特征矩阵,表示图中所有 N N N 个节点的特征, F F F 表示每个节点的特征向量的维度;edge_weight 是一个形状为 E E E 的张量,表示图中每条边的权重。

在该函数中,消息传递的方式是通过定义一个 message 函数和一个 update 函数来实现的。message 函数的作用是将源节点的特征和边权重作为输入,计算出每条边传递的消息;update 函数的作用是将每个节点收到的消息进行聚合,并更新节点的特征。

具体来说,该函数中的 propagate 函数会对输入的 xedge_weight 执行消息传递,按照以下步骤进行:

  1. 根据输入的 edge_indexedge_weight 构造一个稀疏权重矩阵 edge_index,形状为 N × N N \times N N×N,其中 N N N 表示节点数量,矩阵中的每个元素表示一条边的权重。
  2. 调用 message 函数,将源节点的特征和边权重作为输入,计算出每条边传递的消息。
  3. 将每个节点收到的消息进行聚合,并更新节点的特征。具体来说,对于每个节点 i i i,将其所有邻居节点 j j j 的消息按照一定的方式聚合起来,得到一个新的特征向量,用于更新节点 i i i 的特征。
  4. 返回更新后的节点特征矩阵。

在实际应用中,propagate 函数通常会被多次调用,用于实现多轮消息传递,并最终得到图中所有节点的特征表示。

4 推荐任务

#获得正例和负例的各自embedding
u_embeddings = user_all_embeddings[user]
pos_embeddings = item_all_embeddings[pos_item]
neg_embeddings = item_all_embeddings[neg_item]

# calculate BPR Loss
pos_scores = torch.mul(u_embeddings, pos_embeddings).sum(dim=1)
neg_scores = torch.mul(u_embeddings, neg_embeddings).sum(dim=1)
mf_loss = self.mf_loss(pos_scores, neg_scores)

# calculate regularization Loss
u_ego_embeddings = self.user_embedding(user)
pos_ego_embeddings = self.item_embedding(pos_item)
neg_ego_embeddings = self.item_embedding(neg_item)

reg_loss = self.reg_loss(u_ego_embeddings, pos_ego_embeddings, neg_ego_embeddings, require_pow=self.require_pow)
loss = mf_loss + self.reg_weight * reg_loss

5 实验

  • 和NGCF进行实验对比:
  • 和最优模型进行对比:NGCF、Mult-VAE、GRMF
  • 消融实验:证明了非线性激活和特征转换这些GCN的结构在推荐系统中并不适用,这很可能是因为推荐系统中每个图节点仅仅使用了用户或者物品的ID进行模型搭建和训练。

你可能感兴趣的:(python,深度学习,人工智能,lightGCN)