NewBeeNLP原创出品 公众号专栏作者@上杉翔二 悠闲会 · 信息检索
本篇文章follow一些 Graph in Rec 的文章,以前博主整理过的系列可以见:
- 万物皆可Graph | 当推荐系统遇上图神经网络
- 万物皆可Graph | 当推荐系统遇上图神经网络(二)
IMP-GCN
- 论文:Interest-aware Message-Passing GCN for Recommendation
- 地址:https://arxiv.org/abs/2102.10044
- 代码:https://github.com/liufancs/IMP_GCN
来自WWW2021的文章,探讨推荐系统中的过平滑问题。从何向南大佬的NGCF开始一直强调的就是高阶邻居的协作信号是可以学习良好的用户和项目嵌入。虽然GCN容易「过平滑」(即叠加更多层时,节点嵌入变得更加相似,最终无法区分,导致性能下降),也可以用一些方法来缓解如LightGCN和LR-GCN模型,但作者认为他们忽略了一个很重要的问题:「用户的嵌入学习也可以涉及到与用户没有共同兴趣的高阶邻域用户。」 所以多层图卷积会使不同兴趣的用户具有相似的嵌入性。
应对这一点,作者提出一种新的「兴趣感知消息传递GCN (IMP-GCN)」,该模型将用户及其交互项分组到不同的子图中,在子图中进行高阶图卷积 。而这里的子图是由具有相似兴趣的用户及其交互项组成的。
子图由无监督的子图生成模块生成,该模块集成了用户特征和图结构,以识别具有相似兴趣的用户,然后通过保留这些用户及其交互项来构造子图。为此,其可以过滤掉在高阶图卷积运算中的负信息传播,从而通过叠加更多的图卷积层来保持用户的唯一性。
模型图如上,简单来看就是一阶和多阶子图的融合,最后combination之后做预测。
- 「Interest-aware Message-passing Strategy」 文章使用的传播都是LightGCN,因为它的有效性已经得到了很好的证明。
最后经过图卷积后item?的最终表示是它在不同子图s中学习到的嵌入的组合:
- 「Layer Combination and Prediction」将每一层获得的嵌入结合起来,形成用户?和项目?的最终表示。
- 「Subgraph Generation Module」 子图用于对具有共同兴趣的用户进行分组,所以这一任务很容易被抽象为为一个分类任务,即每个用户都被分类为一个组。具体地说,每个用户都由一个特征向量表示,此处融合ID(e0)和图传播后表示(e1)组成为以下:
将获得的用户特征转换为具有2层神经网络去分类即可:
Uo即表示了用户所属的组/子图。值得注意的是,这里将用户分为不同的组是一种无监督的方法,不需要真实标签。此处博主个人的理解是,有相似embedding的用户将生成的预测向量,所以会被归类为同一组。
LR-GCN
- 论文:Revisiting Graph based Collaborative Filtering: A Linear Residual Graph Convolutional Network Approach
- 地址:https://arxiv.org/abs/2001.10167v1
- 代码:https://github.com/newlei/LR-GCCF
补一下前文说到的LR-GCN,来自AAAI2020。LR-GCN也始于两个问题:
- 对于用户和项嵌入,GCN遵循使用图卷积操作和非线性变换进行邻域聚合的两个步骤。虽然图的卷积操作对聚合邻域信息和建模高阶图结构是有效的,但在GCN中的非线性特征变换所引入的额外复杂性有必要吗?
- 目前大多数基于GCN的模型只能堆叠很少的层(例如,2层)。事实上,存在过度平滑效应从而导致每个节点的高阶邻居往往无法区分。虽然作者任务随着叠加层的增加,平滑效应最初可以缓解CF的数据稀疏性,但更多层引入的过度平滑效应会忽略每个用户的独特性,最终会降低推荐性能。
针对这两个问题,作者提出残差图卷积方法重新研究了基于图的CF模型(Linear Residual Graph Convolutional Collaborative Filtering)。模型图如上,其实解决方案已经十分的明显了,在每一步的传播中进行残差连接,并且没有非线性...
- 在特征传播步骤的每一层上,使用一个简单的线性嵌入传播,而没有任何非线性转换。
其中d是度,R是邻居。
- 为了预测用户对项目的偏好,提出了一种基于残余的网络结构来克服过平滑。
简要看看关键代码吧:一些注意点已经备注在代码里
代码语言:javascript复制 def forward(self, user, item_i, item_j):
#得到embedding
users_embedding=self.embed_user.weight
items_embedding=self.embed_item.weight
#直接开始gcn,这里直接实现上面的那个公式,1无非线性2残差也是在这里一起做的,先user,再item。
#然后尝试搭建多层的GCN。
gcn1_users_embedding = (torch.sparse.mm(self.user_item_matrix, items_embedding) users_embedding.mul(self.d_i_train))#*2. # users_embedding
gcn1_items_embedding = (torch.sparse.mm(self.item_user_matrix, users_embedding) items_embedding.mul(self.d_j_train))#*2. # items_embedding
gcn2_users_embedding = (torch.sparse.mm(self.user_item_matrix, gcn1_items_embedding) gcn1_users_embedding.mul(self.d_i_train))#*2. users_embedding
gcn2_items_embedding = (torch.sparse.mm(self.item_user_matrix, gcn1_users_embedding) gcn1_items_embedding.mul(self.d_j_train))#*2. items_embedding
gcn3_users_embedding = (torch.sparse.mm(self.user_item_matrix, gcn2_items_embedding) gcn2_users_embedding.mul(self.d_i_train))#*2. gcn1_users_embedding
gcn3_items_embedding = (torch.sparse.mm(self.item_user_matrix, gcn2_users_embedding) gcn2_items_embedding.mul(self.d_j_train))#*2. gcn1_items_embedding
# gcn4_users_embedding = (torch.sparse.mm(self.user_item_matrix, gcn3_items_embedding) gcn3_users_embedding.mul(self.d_i_train))#*2. gcn1_users_embedding
# gcn4_items_embedding = (torch.sparse.mm(self.item_user_matrix, gcn3_users_embedding) gcn3_items_embedding.mul(self.d_j_train))#*2. gcn1_items_embedding
gcn_users_embedding= torch.cat((users_embedding,gcn1_users_embedding,gcn2_users_embedding,gcn3_users_embedding),-1)# gcn4_users_embedding
gcn_items_embedding= torch.cat((items_embedding,gcn1_items_embedding,gcn2_items_embedding,gcn3_items_embedding),-1)# gcn4_items_embedding#
#然后实现bpr的loss计算,即先得到i和j
user = F.embedding(user,gcn_users_embedding)
item_i = F.embedding(item_i,gcn_items_embedding)
item_j = F.embedding(item_j,gcn_items_embedding)
# # pdb.set_trace()
prediction_i = (user * item_i).sum(dim=-1)
prediction_j = (user * item_j).sum(dim=-1)
#再相减,补上一个L2
# loss=-((rediction_i-prediction_j).sigmoid())**2#self.loss(prediction_i,prediction_j)#.sum()
l2_regulization = 0.01*(user**2 item_i**2 item_j**2).sum(dim=-1)
# l2_regulization = 0.01*((gcn1_users_embedding**2).sum(dim=-1).mean() (gcn1_items_embedding**2).sum(dim=-1).mean())
loss2= -((prediction_i - prediction_j).sigmoid().log().mean())
# loss= loss2 l2_regulization
loss= -((prediction_i - prediction_j)).sigmoid().log().mean() l2_regulization.mean()
# pdb.set_trace()
return prediction_i, prediction_j,loss,loss2