简介
本文目的是简单整理一些图+协同过滤的论文找灵感,可能有些地方理解不对,望大佬们指正,正在更新中。
转载请标明原作者和源地址。
标题 | 简称 | 文章地址 | 代码地址 |
---|---|---|---|
Neural Graph Matching based Collaborative Filtering | GMCF | 地址 | 地址 |
Improving Graph Collaborative Filtering with Neighborhood-enriched Contrastive Learning | NCL | 地址 | 地址 |
GMCF
模型和模块
GMCF用协同过滤实现相关物品的推荐,它的模型主要分为三个部分:图的构建(1),基于节点匹配的GNN(2),图匹配(3)。
(1) 图的构建
分别构建了用户属性图和项目属性图,每个用户(或项目)都表示为一个属性图,其属性为节点,节点表示使用的是属性表示embedding;内部交互关系为边,实际上每对节点都会用一条边相连。
(2) 基于节点匹配的GNN
实际上这里的GNN考虑到了内部交互和交叉交互(比如用户和物品的交互关系)。
外部交互目的是实现协同过滤,也就是说用户1对物品1的属性有很高的偏好,它们的嵌入应该相似。采取了Bi-interaction算法建模交互属性:
s
i
,
j
=
u
i
⊙
u
j
^
s_{i,j}=u_i \odot \hat{u_j}
si,j=ui⊙uj^,即逐元素乘积。
class cross_GNN(MessagePassing):
def __init__(self, dim, hidden_layer):
super(cross_GNN, self).__init__(aggr='mean')
... ...
def message(self, x_i, x_j, edge_weight):
# x_i has shape [E, dim]
# x_j has shape [E, dim]
# pairwise analysis
pairwise_analysis = x_i * x_j
... ...
内部交互目的是捕获用户(项目属性图则目标是捕获项目)信息,采用的是MLP建模交互关系,在消息传递中实现 z i , j = f n e u r a l ( u i , u j ) z_{i,j}=f_{neural}(u_i,u_j) zi,j=fneural(ui,uj),代码里对应的是这部分:
# 只取用inner GNN相关部分,剩余部分用省略号进行表示
class inner_GNN(MessagePassing):
def __init__(self, dim, hidden_layer):
super(inner_GNN, self).__init__(aggr='mean')
#construct pairwise modeling network(MLP模块)
self.lin1 = nn.Linear(dim, hidden_layer)
self.lin2 = nn.Linear(hidden_layer, dim)
self.act = nn.ReLU()
self.drop = nn.Dropout(p=0.5)
... ...
def message(self, x_i, x_j, edge_weight):
# pairwise analysis(输入是2个节点特征,输出是交互关系)
pairwise_analysis = x_i * x_j
pairwise_analysis = self.lin1(pairwise_analysis)
pairwise_analysis = self.act(pairwise_analysis)
pairwise_analysis = self.lin2(pairwise_analysis)
pairwise_analysis = self.drop(pairwise_analysis)
... ...
节点聚合表示时,此时获得了初始节点表示
u
i
u_i
ui,消息传递的内容
z
i
z_i
zi(这个在inner_GNN里完成),节点匹配结果
s
i
s_i
si(这个是cross_GNN做的),使用了一个函数聚合它们,本文里采用了GRU。
(3) 图匹配
用函数
f
G
f_G
fG获得整体图表示之后,直接用点乘做预测(是否该给这个用户推荐这个商品)。
训练和预测
训练目标如下图所示:
也就是最小化损失,损失的来源是GMCF预测label和真实label的差异。
总结
这篇文章实际上把user和item建立了两种图,通过交互把它们连接起来。我觉得这篇文章好的地方是区别对待交互关系,也就是内部交互和交叉交互,下面的左图体现了它的新颖性。
NCL
模型和模块
(1)GNN的基本操作
采取多层GNN对特征进行提取,主要包含两个函数。
f
p
r
o
p
a
g
a
t
e
f_{propagate}
fpropagate负责聚合同一层的邻居的信息,以获得更高一层的表示;
f
r
e
a
d
o
u
t
f_{readout}
freadout负责获得最后的表示用于推荐,聚合的是多层的信息。
本文中的GNN负责建模用户和项目之间的交互,其中
f
p
r
o
p
a
g
a
t
e
做法为:
f_{propagate}做法为:
fpropagate做法为:
而
f
r
e
a
d
o
u
t
f_{readout}
freadout做法为:
这里的LOSS采用了Bayesian Personalized Ranking (BPR) ,找了一篇讲BPR损失的,之后我再补上 (挖坑+1) 。
代码里是在最后聚合所有损失的时候加上的:
def calculate_loss(self, interaction):
... ...
# 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)
u_ego_embeddings = self.user_embedding(user)
pos_ego_embeddings = self.item_embedding(pos_item)
neg_ego_embeddings = self.item_embedding(neg_item)
... ...
(2)结构邻居的对比损失
结构邻居是指通过高阶路径在结构上连接的节点,这个模块是想用结构邻居捕获user和item的潜在关系。具体做法是将用户自身的嵌入和偶数层GNN的相应输出的嵌入视为正对,因为图G是一个二部图,偶数次信息传播才能收集到同质邻居的信息(比如说我从用户出发,走偶数次才能走到另一个用户)。方法如下所示。
用户侧结构损失:
项目侧结构损失:
综合结构损失:
代码里对应的是这个部分:
# 截取了很少的一部分
def ssl_layer_loss(self, current_embedding, previous_embedding, user, item):
... ...
ssl_loss = self.ssl_reg * (ssl_loss_user + self.alpha * ssl_loss_item)
return ssl_loss
(3)语义邻居的对比损失
由于结构邻居对比损失对所有邻居一视同仁,所以可能会引入噪声,因此采用语义邻居扩展对比损失。语义邻居的识别采用潜在原型的方式,原型是一组语义邻居的中心(所以用聚类算法聚出来一个),实现方式是采用EM算法进行原型的学习。方法如下所示。
用户原型:
项目原型:
综合一下:
def ProtoNCE_loss(self, node_embedding, user, item):
... ...
proto_nce_loss = self.proto_reg * (proto_nce_loss_user + proto_nce_loss_item)
return proto_nce_loss
(4) 终极损失,就是把上面的再加点超参融合一下,用EM算法做优化。
在函数calculate_loss融合了一下:
def calculate_loss(self, interaction):
... ...
reg_loss = self.reg_loss(u_ego_embeddings, pos_ego_embeddings, neg_ego_embeddings)
return mf_loss + self.reg_weight * reg_loss, ssl_loss, proto_loss
训练和预测
优化是采用EM算法迭代进行的,之后再补 (挖坑+1) 。
总结
这篇是把user和item建模在一个图里,以二部图的方式构建图。利用多层GNN设计了结构损失,并且提出了语义邻居的概念。总之就是多层GNN和层间交互+两种角度的损失+对比学习的这么一个框架,感觉对于我这个小白而言真的挺精巧的,对比学习是好东西啊!