如果觉得我的分享有一定帮助,欢迎关注我的微信公众号 “码农的科研笔记”,了解更多我的算法和代码学习总结记录。或者点击链接扫码关注【RecBole-GNN/源码】RecBole-GNN中lightGCN源码解析
【RecBole-GNN/源码】RecBole-GNN中lightGCN源码解析
原文:https://arxiv.org/pdf/2002.02126.pdf
源码:伯乐工具箱
输入数据源(图节点仅仅使用了用户或者物品的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
函数会对输入的 x
和 edge_weight
执行消息传递,按照以下步骤进行:
- 根据输入的
edge_index
和edge_weight
构造一个稀疏权重矩阵edge_index
,形状为 N × N N \times N N×N,其中 N N N 表示节点数量,矩阵中的每个元素表示一条边的权重。 - 调用
message
函数,将源节点的特征和边权重作为输入,计算出每条边传递的消息。 - 将每个节点收到的消息进行聚合,并更新节点的特征。具体来说,对于每个节点 i i i,将其所有邻居节点 j j j 的消息按照一定的方式聚合起来,得到一个新的特征向量,用于更新节点 i i i 的特征。
- 返回更新后的节点特征矩阵。
在实际应用中,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进行模型搭建和训练。