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

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

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


原文:https://arxiv.org/abs/2202.06200

源码:https://github.com/rucaibox/ncl

1 数据

方法开始经过数据处理等进入 ncl.pycalculate_loss(self, interaction) 方法。interaction数据形式如下

interaction数据形式

首先获得交互正负样本对数据

#根据交互interaction数据,获得交互正对和负对
user = interaction[self.USER_ID] #2048*1
pos_item = interaction[self.ITEM_ID] #2048*1
neg_item = interaction[self.NEG_ITEM_ID] #2048*1

2 GCN嵌入表示

利用GCN进行前向传播得到user和item的嵌入表示

#获得所有节点(user和item)的嵌入表示
all_embeddings = self.get_ego_embeddings() #9748*64
embeddings_list = [all_embeddings]

for layer_idx in range(max(self.n_layers, self.hyper_layers * 2)): #max(3,2)
    #采用了LightGCNConv的方式
    all_embeddings = self.gcn_conv(all_embeddings, self.edge_index, self.edge_weight) # 9748*64/2*1610886/1610886*1
    embeddings_list.append(all_embeddings)

#得到的embeddings_list是4个9748*64.
lightgcn_all_embeddings = torch.stack(embeddings_list[:self.n_layers + 1], dim=1)
lightgcn_all_embeddings = torch.mean(lightgcn_all_embeddings, dim=1)

 user_all_embeddings, item_all_embeddings = torch.split(lightgcn_all_embeddings, [self.n_users, self.n_items])

3 Loss计算

#embeddings_list是4个9748*64.
center_embedding = embeddings_list[0] #得到初始的embedding,9748*64
context_embedding = embeddings_list[self.hyper_layers * 2] #得到第3个embedding,9748*64

#基于SSL(结构)得到loss (偶数跳的作为邻居正例),user:2048*1,pos_item:2048*1
ssl_loss = self.ssl_layer_loss(context_embedding, center_embedding, user, pos_item)
#基于语义计算loss,
proto_loss = self.ProtoNCE_loss(center_embedding, user, pos_item)

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)

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)

3.1【ssl_layer_loss方法介绍】

该代码首先从当前嵌入中提取用户和商品的表征。然后,将用户和商品的表征分别与基于GCN的第二跳表征进行匹配,计算其相似度。接着,使用softmax函数将所有商品与用户的相似度加权平均,得到用户对所有商品的兴趣得分。然后,对每个商品,同样计算其与当前和基于GCN的第二跳表征之间的相似度,并使用softmax函数将其与所有商品的相似度加权平均,得到商品的受欢迎度得分。

接下来,将用户和商品的得分分别用作分子和分母,计算用户和商品的损失。最后,将两个损失加权相加,并乘以一个正则化参数,得到最终的ssl损失。

注意:context_embedding, center_embedding是包含用户和item的embedding。

def ssl_layer_loss(self, current_embedding, previous_embedding, user, item):
        #user和item表征分开
        current_user_embeddings, current_item_embeddings = torch.split(current_embedding, [self.n_users, self.n_items])
        previous_user_embeddings_all, previous_item_embeddings_all = torch.split(previous_embedding, [self.n_users, self.n_items])
###################
        #获取当前user对应embedding
        current_user_embeddings = current_user_embeddings[user]
        previous_user_embeddings = previous_user_embeddings_all[user]
        #将这两个表征进行归一化,以保证它们具有相同的尺度,并计算它们的点积作为相似度得分。
        norm_user_emb1 = F.normalize(current_user_embeddings)
        norm_user_emb2 = F.normalize(previous_user_embeddings)
        norm_all_user_emb = F.normalize(previous_user_embeddings_all)
        pos_score_user = torch.mul(norm_user_emb1, norm_user_emb2).sum(dim=1)
        #计算用户对所有用户的得分。transpose(0, 1)表示对norm_all_user_emb的第0维和第1维进行转置操作,即将所有用户的表征转置,以便与当前用户的表征进行矩阵乘法。乘积的结果是一个大小为(n_items, 1)的向量,表示当前用户对所有所有用户的得分。
        ttl_score_user = torch.matmul(norm_user_emb1, norm_all_user_emb.transpose(0, 1))
        #self.ssl_temp=0.1
        pos_score_user = torch.exp(pos_score_user / self.ssl_temp)
        ttl_score_user = torch.exp(ttl_score_user / self.ssl_temp).sum(dim=1)
        #
        ssl_loss_user = -torch.log(pos_score_user / ttl_score_user).sum()
####################
        #同理计算
        current_item_embeddings = current_item_embeddings[item]
        previous_item_embeddings = previous_item_embeddings_all[item]
        norm_item_emb1 = F.normalize(current_item_embeddings)
        norm_item_emb2 = F.normalize(previous_item_embeddings)
        norm_all_item_emb = F.normalize(previous_item_embeddings_all)
        pos_score_item = torch.mul(norm_item_emb1, norm_item_emb2).sum(dim=1)
        ttl_score_item = torch.matmul(norm_item_emb1, norm_all_item_emb.transpose(0, 1))
        pos_score_item = torch.exp(pos_score_item / self.ssl_temp)
        ttl_score_item = torch.exp(ttl_score_item / self.ssl_temp).sum(dim=1)

        ssl_loss_item = -torch.log(pos_score_item / ttl_score_item).sum()

        ssl_loss = self.ssl_reg * (ssl_loss_user + self.alpha * ssl_loss_item)
        return ssl_loss

3.2【ProtoNCE_loss方法介绍】

给定一个节点嵌入向量 node_embedding,以及一个用户 ID user 和一个物品 ID item,该函数会使用这些输入计算出用户和物品的 proto-contrastive loss。proto-contrastive loss 是一个用于无监督学习的损失函数,用于在嵌入空间中学习出具有相似性质的节点之间的距离,并且可以通过聚类算法来获取节点的簇标签。

  • 这个函数首先将 node_embedding 分成两部分,分别对应于所有用户和所有物品的嵌入向量 user_embeddings_all 和 item_embeddings_all。
  • 然后,它会从 user_embeddings_all 中选择 user 对应的嵌入向量 user_embeddings,并对其进行归一化处理,得到 norm_user_embeddings。接着,函数会获取 user 对应的簇标签 user2cluster,并使用它来获取对应的簇中心 user2centroids。然后,函数会计算出用户与其所属簇中心的内积,得到 pos_score_user。pos_score_user 会经过指数函数处理,并除以一个温度参数 self.ssl_temp。接着,函数会计算用户与所有簇中心的内积,得到 ttl_score_user。ttl_score_user 也会经过指数函数处理,并按行求和。然后,函数会使用 pos_score_user 和 ttl_score_user 来计算用户的 proto-contrastive loss,将其取负数并求和。
  • 函数接下来会处理物品的嵌入向量 item_embeddings_all,方法与处理用户的嵌入向量类似,得到 pos_score_item 和 ttl_score_item,并计算出物品的 proto-contrastive loss。
  • 最后,函数会将用户和物品的 proto-contrastive loss 加权求和,得到最终的 proto-contrastive loss。这个加权系数由参数 proto_reg 决定。函数返回最终的 proto-contrastive loss。
def ProtoNCE_loss(self, node_embedding, user, item):
        user_embeddings_all, item_embeddings_all = torch.split(node_embedding, [self.n_users, self.n_items])

        user_embeddings = user_embeddings_all[user]     # [B, e]
        norm_user_embeddings = F.normalize(user_embeddings)

        user2cluster = self.user_2cluster[user]     # [B,]
        user2centroids = self.user_centroids[user2cluster]   # [B, e]
        pos_score_user = torch.mul(norm_user_embeddings, user2centroids).sum(dim=1)
        pos_score_user = torch.exp(pos_score_user / self.ssl_temp)
        ttl_score_user = torch.matmul(norm_user_embeddings, self.user_centroids.transpose(0, 1))
        ttl_score_user = torch.exp(ttl_score_user / self.ssl_temp).sum(dim=1)

        proto_nce_loss_user = -torch.log(pos_score_user / ttl_score_user).sum()

        item_embeddings = item_embeddings_all[item]
        norm_item_embeddings = F.normalize(item_embeddings)

        item2cluster = self.item_2cluster[item]  # [B, ]
        item2centroids = self.item_centroids[item2cluster]  # [B, e]
        pos_score_item = torch.mul(norm_item_embeddings, item2centroids).sum(dim=1)
        pos_score_item = torch.exp(pos_score_item / self.ssl_temp)
        ttl_score_item = torch.matmul(norm_item_embeddings, self.item_centroids.transpose(0, 1))
        ttl_score_item = torch.exp(ttl_score_item / self.ssl_temp).sum(dim=1)
        proto_nce_loss_item = -torch.log(pos_score_item / ttl_score_item).sum()

        proto_nce_loss = self.proto_reg * (proto_nce_loss_user + proto_nce_loss_item)
        return proto_nce_loss

3.3【簇标签以及簇中心】

在每个epoch的时候都会开始进行self.model.e_step()(Running E-step )

    def e_step(self):
        user_embeddings = self.user_embedding.weight.detach().cpu().numpy() #
        item_embeddings = self.item_embedding.weight.detach().cpu().numpy()
        #self.user_centroids:1000*64 ,self.user_2cluster:6041*1
        #self.item_centroids:1000*64, self.item_2cluster:3707*1
        # 给予了user对应的簇,以及簇中心
        self.user_centroids, self.user_2cluster = self.run_kmeans(user_embeddings)
        self.item_centroids, self.item_2cluster = self.run_kmeans(item_embeddings)
    # self.k=1000
    def run_kmeans(self, x):
        """Run K-means algorithm to get k clusters of the input tensor x
        """
        import faiss
        kmeans = faiss.Kmeans(d=self.latent_dim, k=self.k, gpu=True)
        kmeans.train(x)
        cluster_cents = kmeans.centroids

        _, I = kmeans.index.search(x, 1)

        # convert to cuda Tensors for broadcast
        centroids = torch.Tensor(cluster_cents).to(self.device)
        centroids = F.normalize(centroids, p=2, dim=1)

        node2cluster = torch.LongTensor(I).squeeze().to(self.device)
        return centroids, node2cluster
人工智能(AI)最近经历了复兴,在视觉,语言,控制和决策等关键领域取得了重大进展。 部分原因在于廉价数据和廉价计算资源,这些资源符合深度学习的自然优势。 然而,在不同的压力下发展的人类智能的许多定义特征仍然是当前方法无法实现的。 特别是,超越一个人的经验 - 从婴儿期开始人类智能的标志 - 仍然是现代人工智能的一项艰巨挑战。 以下是部分立场文件,部分审查和部分统一。我们认为组合概括必须是AI实现类似人类能力的首要任务,结构化表示和计算是实现这一目标的关键。就像生物学利用自然和培养合作一样,我们拒绝“手工工程”和“端到端”学习之间的错误选择,而是倡导一种从其互补优势获益的方法。我们探索如何在深度学习架构使用关系归纳偏差来促进对实体,关系和组成它们的规则的学习。我们为AI工具包提供了一个新的构建模块,具有强大的关系归纳偏差 - 图形网络 - 它概括和扩展了在图形上运行的神经网络的各种方法,并为操纵结构化知识和生成结构化行为提供了直接的界面。我们讨论图网络如何支持关系推理和组合泛化,为更复杂,可解释和灵活的推理模式奠定基础。作为本文的配套文件,我们还发布了一个用于构建图形网络的开源软件库,并演示了如何在实践使用它们。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值