背景描述:
商品有:商品1: 电子产品 - 智能手机;商品2: 家居用品 - 双人床;商品3: 服装配饰 - 运动鞋;商品4: 图书 - 小说;商品5: 食品 - 咖啡豆;商品6: 家电 - 液晶电视;商品7: 美妆 - 口红;商品8: 运动户外 - 自行车;商品9: 宠物用品 - 猫粮;商品10: 儿童玩具 - 拼图玩具
Alice 购买了 智能手机 (商品1)
Bob 购买了 双人床 (商品2)
Charlie 购买了 运动鞋 (商品3)
David 购买了 小说 (商品4)
Emily 购买了 咖啡豆 (商品5)
初始化用户和物品的嵌入向量
# xavier init
import torch.nn as nn
emb_size = 62
initializer = nn.init.xavier_uniform_
embedding_dict = nn.ParameterDict({
'user_emb': nn.Parameter(initializer(torch.empty(n_users, emb_size))),
'item_emb': nn.Parameter(initializer(torch.empty(n_items, emb_size)))
})
weight_dict = nn.ParameterDict({
'W_gc_0': nn.Parameter(initializer(torch.empty(emb_size, emb_size))),
'b_gc_0': nn.Parameter(initializer(torch.empty(1, emb_size))),
'W_bi_0': nn.Parameter(initializer(torch.empty(emb_size, emb_size))),
'b_bi_0': nn.Parameter(initializer(torch.empty(1, emb_size)))
})
# (ParameterDict(
# (item_emb): Parameter containing: [torch.FloatTensor of size 5x62]
# (user_emb): Parameter containing: [torch.FloatTensor of size 5x62]
# ),
# ParameterDict(
# (W_bi_0): Parameter containing: [torch.FloatTensor of size 62x62]
# (W_gc_0): Parameter containing: [torch.FloatTensor of size 62x62]
# (b_bi_0): Parameter containing: [torch.FloatTensor of size 1x62]
# (b_gc_0): Parameter containing: [torch.FloatTensor of size 1x62]
# ))
embeddings = torch.cat([embedding_dict['user_emb'], embedding_dict['item_emb']], 0) # torch.Size([10, 62])
消息传递
sparse_norm_adj为【图神经网络】构建稀疏矩阵中的稀疏矩阵。
import torch.nn.functional as F
message_dropout = 0.1
# 获取每个节点的邻居节点信息,对该句的解释,数据实例在文章下面部分
side_embeddings = torch.sparse.mm(sparse_norm_adj, embeddings) # torch.Size([10, 62])
# 对邻居节点信息加权求和
sum_embeddings = torch.matmul(side_embeddings, weight_dict['W_gc_0']) + weight_dict['b_gc_0']
# 当前节点的嵌入与邻居节点的嵌入逐元素相乘。由于这是逐元素操作,每个元素的相乘结果独立于其他元素。
# 逐元素相乘的结果反映了当前节点与其邻居节点之间每个维度上的相互作用。
# 这个操作可以理解为一种元素级的交互,捕捉了节点与邻居节点之间的双向信息,因为每个节点都同时影响着它自己和邻居节点的表示。
bi_embeddings = torch.mul(embeddings, side_embeddings)
# 对双向信息的表示进行线性变换,得到双向信息的加和表示
bi_embeddings = torch.matmul(bi_embeddings, weight_dict['W_bi_0']) + weight_dict['b_bi_0']
# 将加和信息与双向信息的加和进行 Leaky ReLU 激活操作,得到新的节点表示 ego_embeddings
ego_embeddings = nn.LeakyReLU(negative_slope=0.2)(sum_embeddings + bi_embeddings)
# 对节点表示进行消息丢弃,以防止过拟合。
ego_embeddings = nn.Dropout(message_dropout)(ego_embeddings)
# 对节点表示进行 L2 范数归一化,以保证节点表示的稳定性。
norm_embeddings = F.normalize(ego_embeddings, p=2, dim=1)
为什么torch.sparse.mm(A, embeddings)可以表示邻居节点信息
假设现有5个节点,邻接矩阵为A,如下图所示:
A = [[0., 1., 0., 1., 0.],
[1., 0., 1., 0., 1.],
[0., 1., 0., 1., 0.],
[1., 0., 1., 0., 1.],
[0., 1., 0., 1., 0.]]
embeddings = [[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.],
[1., 1., 0.],
[0., 1., 1.]]
# 将邻接矩阵和节点嵌入转换为PyTorch张量
A_hat = torch.tensor(A_hat, dtype=torch.float32)
ego_embeddings = torch.tensor(ego_embeddings, dtype=torch.float32)
# 使用 torch.sparse.mm 计算邻居节点的表示
side_embeddings = torch.sparse.mm(A_hat, ego_embeddings)
# tensor([[1., 2., 0.],
# [1., 1., 2.],
# [1., 2., 0.],
# [1., 1., 2.],
# [1., 2., 0.]])
以0号节点为例,与0号节点相连的节点为1号和3号,1号节点的嵌入向量为[0., 1., 0.]
,3号节点的嵌入向量为[1., 1., 0.]
,由代码结果可知,0号节点的邻居信息为1号嵌入向量与3号嵌入向量对应元素相加而来。
Tips:
如有问题,麻烦指正,谢谢各位!
如有疑问,请留言,谢谢!
持续更新…