推荐系统代码阅读:SIGIR 2023 Multi-behavior Self-supervised Learning for Recommendation

代码:GitHub - Scofield666/MBSSL

论文:https://arxiv.org/pdf/2305.18238.pdf

这是我写的第一篇代码阅读,在第一篇中我会对讲的稍微细致一点,并将代码与论文中的公式相结合起来,这样才方便理解,如果有理解错的地方欢迎各位指正(才疏学浅) 我会从数据处理部分开始,然后根据论文里的章节来讲代码,可以结合我的论文阅读一起看,地址:论文阅读总结:SIGIR 2023 Multi-behavior Self-supervised Learning for Recommendation_推荐系统YYDS的博客-CSDN博客

首先还是放一下模型图

 数据预处理(不想看的可以直接跳的模型部分)

在代码中数据预处理的部分在load_data文件中,(这部分代码太长,大致讲一下流程)

for i in range(len(self.behs) - 1):
    beh = self.behs[i]
    file_name = self.predir + '/' + beh + '.txt'
    with open(file_name) as f:
        for l in f.readlines():
            if len(l) > 0:
                l = l.strip('\n').split(' ')
                items = [int(i) for i in l[1:]]
                uid = int(l[0])
                self.n_items = max(self.n_items, max(items))
                self.n_users = max(self.n_users, uid)

加载行为数据文件,并统计用户数和物品数。循环遍历行为列表中的元素,并打开相应的数据文件。然后,对于每一行数据,获取用户ID和物品列表,并更新self.n_itemsself.n_users的最大值。

self.trnMats = [sp.dok_matrix((self.n_users, self.n_items), dtype=np.float32) for i in
                range(len(self.behs))]
self.tstMats = sp.dok_matrix((self.n_users, self.n_items), dtype=np.float32)
self.trnDicts = [dict() for i in range(len(self.behs))]
self.trnDicts_item = [dict() for i in range(len(self.behs))]
self.tstDicts = dict()
self.interNum = [0 for i in range(len(self.behs))]

介绍几个重要变量:训练和测试的稀疏矩阵self.trnMatsself.tstMats,以及用于存储训练和测试数据的字典self.trnDictsself.trnDicts_itemself.tstDicts。还有一个列表self.interNum用于存储每种行为的交互数量。

for i in range(len(self.behs) - 1):
    beh = self.behs[i]
    beh_filename = self.predir + '/' + beh + '.txt'
    with open(beh_filename) as f:
        for l in f.readlines():
            if len(l) == 0: break
            l = l.strip('\n')
            items = [int(i) for i in l.split(' ')]
            uid, items_list = items[0], items[1:]
            self.interNum[i] += len(items_list)
            for item in items_list:
                self.trnMats[i][uid, item] = 1.
            self.trnDicts[i][uid] = items_list

读取行为文件中的数据,并更新训练矩阵和训练字典。通过循环遍历行为列表self.behs中的元素,并打开对应的行为文件。然后,对于每一行数据,解析出用户ID和物品列表,并将训练矩阵中相应的位置置为1,同时更新训练字典和交互数量。

    def get_adj_mat(self):
        self.saveAdjMatPath = 'Adj_Mats/' + self.dataset_name
        os.makedirs(self.saveAdjMatPath, exist_ok=True)

        adj_mat_list = []
        norm_adj_mat_list = []
        mean_adj_mat_list = []
        pre_adj_mat_list = []
        try:
            t1 = time()
            for i in range(len(self.behs)):
                beh = self.behs[i]
                adj_mat = sp.load_npz(self.saveAdjMatPath + '/s_adj_mat_' + beh + '.npz')
                norm_adj_mat = sp.load_npz(self.saveAdjMatPath + '/s_norm_adj_mat_' + beh + '.npz')
                mean_adj_mat = sp.load_npz(self.saveAdjMatPath + '/s_mean_adj_mat_' + beh + '.npz')
                adj_mat_list.append(adj_mat)
                norm_adj_mat_list.append(norm_adj_mat)
                mean_adj_mat_list.append(mean_adj_mat)
            print('already load adj matrix', time() - t1)

        except Exception:
            for i in range(len(self.behs)):
                beh = self.behs[i]
                adj_mat, norm_adj_mat, mean_adj_mat = self.create_adj_mat(self.trnMats[i])
                adj_mat_list.append(adj_mat)
                norm_adj_mat_list.append(norm_adj_mat)
                mean_adj_mat_list.append(mean_adj_mat)
                sp.save_npz(self.saveAdjMatPath + '/s_adj_mat_' + beh + '.npz', adj_mat)
                sp.save_npz(self.saveAdjMatPath + '/s_norm_adj_mat_' + beh + '.npz', norm_adj_mat)
                sp.save_npz(self.saveAdjMatPath + '/s_mean_adj_mat_' + beh + '.npz', mean_adj_mat)

        try:
            for i in range(len(self.behs)):
                beh = self.behs[i]
                pre_adj_mat = sp.load_npz(self.saveAdjMatPath + '/s_pre_adj_mat_' + beh + '.npz')
                pre_adj_mat_list.append(pre_adj_mat)
            print('already load pre adj matrix')
        except Exception:
            for i in range(len(self.behs)):
                beh = self.behs[i]
                adj_mat = adj_mat_list[i]
                rowsum = np.array(adj_mat.sum(1))
                d_inv = np.power(rowsum, -0.5).flatten()
                d_inv[np.isinf(d_inv)] = 0.
                d_mat_inv = sp.diags(d_inv)

                norm_adj = d_mat_inv.dot(adj_mat)
                norm_adj = norm_adj.dot(d_mat_inv)
                print('generate pre adjacency matrix.')
                pre_adj_mat = norm_adj.tocsr()
                pre_adj_mat_list.append(pre_adj_mat)
                sp.save_npz(self.saveAdjMatPath + '/s_pre_adj_mat_' + beh + '.npz', norm_adj)

        return pre_adj_mat_list

这部分是邻接矩阵的代码。首先加载邻接矩阵。从保存的文件中加载已有的邻接矩阵。对于每种行为类型,使用sp.load_npz函数加载对应的邻接矩阵、规范化邻接矩阵和均值邻接矩阵,并将它们添加到对应的列表中。如果加载成功,打印已加载邻接矩阵的耗时。如果加载失败,调用self.create_adj_mat方法创建邻接矩阵,并将它们保存到文件中。得到了邻接矩阵和预处理邻接矩阵。返回三个邻接矩阵:原始邻接矩阵 adj_mat、单一归一化邻接矩阵 norm_adj_mat 和均值归一化邻接矩阵 mean_adj_mat

代码太长了。。。我直接放函数名字(模型部分会全放的)

get_unified_sim:获取统一相似度矩阵

create_adj_mat:返回三个邻接矩阵:原始邻接矩阵adj_mat 归一化邻接矩阵norm_adj_mat
和均值归一化邻接矩阵mean_adj_mat

negative_pool:为每个用户生成负样本池

sample:根据 batch_size 的大小确定需要采样的用户数量采样用户、正样本和负样本,之后定义了三个函数,分别是:

从用户的训练集中采样正样本物品sample_pos_items_for_u,随机选择一个索引,从用户的正样本列表中获取相应的物品ID;

从所有物品中采样负样本物品sample_neg_items_for_u,随机选择一个索引,从0到总物品数之间进行采样,并确保采样的物品既不在用户的训练集中;

从用户的负样本池中采样负样本物品sample_neg_items_for_u_from_pools,首先根据用户ID获取对应的负样本池,然后从负样本池中去除用户的训练集中已有的物品,并从剩余的负样本物品中随机采样指定数量的物品。

get_sparsity_split:获取稀疏性划分的用户列表,将用户分为不同的组或子集,使得每个子集中的用户具有相似的稀疏性特征(可以降低复杂度,提升模型预测的准确性)。

augment_adj_mat:数据增强。根据给定的数据增强类型和数据增强比例,判断是否需要进行数据增强操作。0表示Node Dropout(节点丢弃),1表示Edge Dropout(边丢弃),2表示Random Walk(随机游走)。之后得到对称的邻接矩阵adj_mat。进行归一化操作,计算行和、取行和的倒数、构建对角矩阵,并进行矩阵运算得到最终的归一化邻接矩阵adj_matrix。(归一化操作是为了在图数据中考虑节点的度信息,并使得节点之间的关系更加平衡。可以消除节点度数的差异对GNN的影响,提高算法的鲁棒性)

模型部分

class MBSSL(nn.Module):
    name = 'MBSSL'

    def __init__(self, max_item_list, data_config, args):
        super(MBSSL, self).__init__()
        # ********************** input data *********************** #
        self.max_item_list = max_item_list
        self.n_users = data_config['n_users'] 用户数量
        self.n_items = data_config['n_items'] 物品数量
        self.num_nodes = self.n_users + self.n_items 图中节点(用户和物品)的总数
        self.pre_adjs = data_config['pre_adjs'] 预处理的邻接矩阵
        self.pre_adjs_tensor = [self._convert_sp_mat_to_sp_tensor(adj).to(device) for adj in self.pre_adjs] 将预处理的邻接矩阵列表转换为稀疏张量
        self.behs = data_config['behs']
        self.n_relations = len(self.behs) 行为的数量
        self.coefficient = torch.tensor(eval(args.coefficient)).view(1, -1).to(device)
        self.emb_dim = args.embed_size 嵌入维度
        self.batch_size = args.batch_size 批处理大小
        self.weight_size = eval(args.layer_size)图卷积层的输入和输出维度
        self.n_layers = len(self.weight_size)图卷积层层数
        self.mess_dropout = eval(args.mess_dropout)  图卷积层的消息传递层的dropout
        self.aug_type = args.aug_type 数据增强类型
        self.nhead = args.nhead 注意力头数
        self.att_dim = args.att_dim 注意力维度

可学习的参数:

        self.all_weights = {}
        self.all_weights['user_embedding'] = Parameter(torch.FloatTensor(self.n_users, self.emb_dim))
        self.all_weights['item_embedding'] = Parameter(torch.FloatTensor(self.n_items, self.emb_dim))
        self.all_weights['relation_embedding'] = Parameter(torch.FloatTensor(self.n_relations, self.emb_dim))

        self.weight_size_list = [self.emb_dim] + self.weight_size

        for k in range(self.n_layers):
            self.all_weights['W_gc_%d' % k] = Parameter(
                torch.FloatTensor(self.weight_size_list[k], self.weight_size_list[k + 1]))
            self.all_weights['W_rel_%d' % k] = Parameter(
                torch.FloatTensor(self.weight_size_list[k], self.weight_size_list[k + 1]))

        self.all_weights['trans_weights_s1'] = Parameter(
            torch.FloatTensor(self.n_relations, self.emb_dim, self.att_dim))
        self.all_weights['trans_weights_s2'] = Parameter(torch.FloatTensor(self.n_relations, self.att_dim, 1))

user_embeddin 用户嵌入矩阵,形状为(n_users, emb_dim)

item_embedding 项目嵌入矩阵,形状为(n_items, emb_dim) relation_embedding同理

W_gc 图卷积层的权重矩阵 W_rel 行为嵌入矩阵与图卷积层的权重矩阵 根据GCN层计算得到

trans_weights_s1,2 图卷积层的输入和输出维度

    def reset_parameters(self):
        nn.init.xavier_uniform_(self.all_weights['user_embedding'])
        nn.init.xavier_uniform_(self.all_weights['item_embedding'])
        nn.init.xavier_uniform_(self.all_weights['relation_embedding'])
        nn.init.xavier_uniform_(self.all_weights['trans_weights_s1'])
        nn.init.xavier_uniform_(self.all_weights['trans_weights_s2'])
        for k in range(self.n_layers):
            nn.init.xavier_uniform_(self.all_weights['W_gc_%d' % k])
            nn.init.xavier_uniform_(self.all_weights['W_rel_%d' % k])

    def _convert_sp_mat_to_sp_tensor(self, X):
        coo = X.tocoo()
        values = coo.data
        indices = np.vstack((coo.row, coo.col))
        shape = coo.shape
        return torch.sparse.FloatTensor(torch.LongTensor(indices), torch.FloatTensor(values), torch.Size(shape))

对各个参数进行Xavier初始化并将稀疏矩阵转换为稀疏张量

行为感知图神经网络

    def forward(self, sub_mats, device):
        self.sub_mat = {}
        for k in range(1, self.n_layers + 1):
            if self.aug_type in [0, 1]:
                self.sub_mat['sub_mat_1%d' % k] = sub_mats['sub1'].to(device)
                self.sub_mat['sub_mat_2%d' % k] = sub_mats['sub2'].to(device)
            else:
                self.sub_mat['sub_mat_1%d' % k] = sub_mats['sub1%d' % k].to(device)
                self.sub_mat['sub_mat_2%d' % k] = sub_mats['sub2%d' % k].to(device)

        ego_embeddings = torch.cat((self.all_weights['user_embedding'], self.all_weights['item_embedding']),
                                   dim=0).unsqueeze(1).repeat(1, self.n_relations, 1)
        ego_embeddings_sub1 = ego_embeddings
        ego_embeddings_sub2 = ego_embeddings

        all_embeddings = ego_embeddings
        all_embeddings_sub1 = ego_embeddings_sub1
        all_embeddings_sub2 = ego_embeddings_sub2

        all_rela_embs = {}
        for i in range(self.n_relations):
            beh = self.behs[i]
            rela_emb = self.all_weights['relation_embedding'][i]
            rela_emb = torch.reshape(rela_emb, (-1, self.emb_dim))
            all_rela_embs[beh] = [rela_emb]

        total_mm_time = 0.

 初始化了self.sub_mat字典,用于存储矩阵。根据aug_type的不同取值,从sub_mats中获取不同的矩阵。初始化了用户物品和关系的嵌入向量。在这里作者首先将用户和物品的嵌入拼接成ego_embeds,在将ego_embeds赋值给ego_embeds_sub1,2;和all_ego_embeds_sub1,2;这里应该对应行为内监督学习,将分为了两个视图(后续还有操作的)

 for k in range(0, self.n_layers):
    embeddings_list = []
    for i in range(self.n_relations):
        st = time()
        embeddings_ = torch.matmul(self.pre_adjs_tensor[i], ego_embeddings[:, i, :])
        total_mm_time += time() - st
        rela_emb = all_rela_embs[self.behs[i]][k]
        embeddings_ = self.leaky_relu(
            torch.matmul(torch.mul(embeddings_, rela_emb), self.all_weights['W_gc_%d' % k]))
        embeddings_list.append(embeddings_)
    embeddings_st = torch.stack(embeddings_list, dim=1)
    embeddings_list = []
    attention_list = []
    

 这部分代码首先初始化了一个空的embeddings_list列表,用于存储每个行为对应的嵌入向量。然后,通过矩阵乘法计算节点的嵌入向量更新(预处理的邻接矩阵和ego_embeds),并将结果存储在embeddings_中。接着,将embeddings_通过LeakyReLU激活函数进行非线性变换。最后,将所有行为的嵌入向量拼接成一个张量embeddings_st。对应公式如下:

            for i in range(self.n_relations):
                attention = F.softmax(
                    torch.matmul(
                        torch.tanh(torch.matmul(embeddings_st, self.all_weights['trans_weights_s1'][i])),
                        self.all_weights['trans_weights_s2'][i]
                    ).squeeze(2),
                    dim=1
                ).unsqueeze(1)
                attention_list.append(attention)
                embs_cur_rela = torch.matmul(attention, embeddings_st).squeeze(1)
                embeddings_list.append(embs_cur_rela)
            embeddings_list.append(embs_cur_rela)
            ego_embeddings = torch.stack(embeddings_list, dim=1)
            attn = torch.cat(attention_list, dim=1)
            ego_embeddings = self.dropout(ego_embeddings)
            all_embeddings = all_embeddings + ego_embeddings

 在这段代码中,首先通过矩阵乘法计算行为之间的注意力权重(建模跨行为依赖性)公式(2)中的Wb对应trans_weight,ek对应embeddings_st,并经过Softmax函数归一化得到权重au,k(attention list)。最后将attention, embeddings_st相乘,更新eu,k。然后将所有行为的嵌入向量拼接成一个张量ego_embeddings,并进行dropout操作。最后,将更新后的嵌入向量加到all_embeddings中。对应公式如下:(在之后作者使用相同的操作应用于两个扩充视图ego_embeds_sub1,2,all_ego_embeds_sub1,2

all_embeddings /= self.n_layers + 1
        u_g_embeddings, i_g_embeddings = torch.split(all_embeddings, [self.n_users, self.n_items], 0)
        token_embedding = torch.zeros([1, self.n_relations, self.emb_dim], device=device)
        i_g_embeddings = torch.cat((i_g_embeddings, token_embedding), dim=0)

        all_embeddings_sub1 /= self.n_layers + 1
        u_g_embeddings_sub1, i_g_embeddings_sub1 = torch.split(all_embeddings_sub1, [self.n_users, self.n_items], 0)
        i_g_embeddings_sub1 = torch.cat((i_g_embeddings_sub1, token_embedding), dim=0)

        all_embeddings_sub2 /= self.n_layers + 1
        u_g_embeddings_sub2, i_g_embeddings_sub2 = torch.split(all_embeddings_sub2, [self.n_users, self.n_items], 0)
        i_g_embeddings_sub2 = torch.cat((i_g_embeddings_sub2, token_embedding), dim=0)

        attn_user, attn_item = torch.split(attn, [self.n_users, self.n_items], 0)

        for i in range(self.n_relations):
            all_rela_embs[self.behs[i]] = torch.mean(torch.stack(all_rela_embs[self.behs[i]], 0), 0)

        return u_g_embeddings, i_g_embeddings, u_g_embeddings_sub1, i_g_embeddings_sub1, u_g_embeddings_sub2, i_g_embeddings_sub2, all_rela_embs, attn_user, attn_item

 最后对应于一个主视图和两个扩充视图进行相同的特征分割拼接的工作,将all_embeddings分割为u_g_embeddings, i_g_embeddings,并mean pool集成GCN所有层的嵌入,对于公式如下:

 行为内自监督学习

class SSLoss(nn.Module):
    def __init__(self, data_config, args):
        super(SSLoss, self).__init__()
        self.ssl_temp = args.ssl_temp
        self.ssl_reg = args.ssl_reg
        self.ssl_mode = args.ssl_mode

 ssl_temp(温度参数,用于调整相似度分数的分布)、ssl_reg(对比学习损失的正则化系数)和 ssl_mode(对比学习的模式,用户or物品)下面我只介绍用户的,因为物品的是一样的

    def forward(self, input_u_list, input_i_list, ua_embeddings_sub1, ua_embeddings_sub2, ia_embeddings_sub1,
                ia_embeddings_sub2):
        if self.ssl_mode in ['user_side', 'both_side']:
            user_emb1 = ua_embeddings_sub1[input_u_list]
            user_emb2 = ua_embeddings_sub2[input_u_list]  # [B, dim]
            normalize_user_emb1 = F.normalize(user_emb1, dim=1)
            normalize_user_emb2 = F.normalize(user_emb2, dim=1)
            normalize_all_user_emb2 = F.normalize(ua_embeddings_sub2, dim=1)
            pos_score_user = torch.sum(torch.mul(normalize_user_emb1, normalize_user_emb2),
                                       dim=1)
            pos_score_user = torch.exp(pos_score_user / self.ssl_temp)
            ttl_score_user = torch.matmul(normalize_user_emb1,
                                          normalize_all_user_emb2.T)
            ttl_score_user = torch.sum(torch.exp(ttl_score_user / self.ssl_temp), dim=1) 
            ssl_loss_user = -torch.sum(torch.log(pos_score_user / ttl_score_user))

输入用户列表 input_u_list、输入的物品列表 input_i_list,以及两个扩充子图的用户嵌入和物品嵌入 ua_embeddings_sub1, ua_embeddings_sub2, ia_embeddings_sub1, ia_embeddings_sub2

首先,对user_emb1user_emb2进行归一化操作得到normalize_user_emb1normalize_user_emb2 。同时归一化了所有的 ua_embeddings_sub2,得到 normalize_all_user_emb2。接下来,计算正样本的相似度得分 pos_score_user,通过对 normalize_user_emb1normalize_user_emb2 进行逐元素相乘并求和得到。将 pos_score_user 除以 ssl_temp 并进行指数化。计算负样本的相似度得分 ttl_score_user,通过对 normalize_user_emb1normalize_all_user_emb2(转置)进行矩阵乘法,并对结果进行逐元素指数化并求和得到。最后,计算用户对比学习损失 ssl_loss_user,通过对正样本得分 pos_score_user 除以正样本得分得分 ttl_score_user 取对数并求和的负数。公式如下

        if self.ssl_mode == 'user_side':
            ssl_loss = self.ssl_reg * ssl_loss_user
        elif self.ssl_mode == 'item_side':
            ssl_loss = self.ssl_reg * ssl_loss_item
        else:
            ssl_loss = self.ssl_reg * (ssl_loss_user + ssl_loss_item)

        return ssl_loss

用户端的损失+项目端的损失 ,公式里没写的就是行为内的对比学习还乘了非采样损失Lrec,问题不大

  行为间自监督学习

lass SSLoss2(nn.Module):
    def __init__(self, data_config, args):
        super(SSLoss2, self).__init__()
        self.config = data_config
        self.ssl_temp = args.ssl_temp
        self.ssl_reg_inter = eval(args.ssl_reg_inter)
        self.ssl_mode_inter = args.ssl_inter_mode
        self.topk1_user = args.topk1_user
        self.topk1_item = args.topk1_item
        self.user_indices_remove, self.item_indices_remove = None, None

ssl_temp是温度参数,ssl_reg_inter是一个字典,用于指定不同辅助行为的对比学习正则化权重,ssl_mode_inter是指定对比学习模式的参数,可选值为user_sideitem_sideboth_sidetopk1_usertopk1_item是指定用户和物品推荐的Top-K值

    def forward(self, input_u_list, input_i_list, ua_embeddings, ia_embeddings, aux_beh, user_batch_indices=None,
                item_batch_indices=None):
        ssl2_loss = 0.

        if self.ssl_mode_inter in ['user_side', 'both_side']:
            emb_tgt = ua_embeddings[input_u_list, -1, :]  # [B, d]
            normalize_emb_tgt = F.normalize(emb_tgt, dim=1)
            emb_aux = ua_embeddings[input_u_list, aux_beh, :]  # [B, d]
            normalize_emb_aux = F.normalize(emb_aux, dim=1)  # [B, dim]
            normalize_all_emb_aux = F.normalize(ua_embeddings[:, aux_beh, :], dim=1)  # [N, dim]
            pos_score = torch.sum(torch.mul(normalize_emb_tgt, normalize_emb_aux),
                                  dim=1)  # [B, ]
            ttl_score = torch.matmul(normalize_emb_tgt, normalize_all_emb_aux.T)  # [B, N]

            ttl_score[user_batch_indices] = 0.
            pos_score = torch.exp(pos_score / self.ssl_temp)
            ttl_score = torch.sum(torch.exp(ttl_score / self.ssl_temp), dim=1)

            ssl2_loss += -torch.sum(torch.log(pos_score / ttl_score)) * self.ssl_reg_inter[aux_beh]

输入参数包括input_u_list(用户输入列表)、input_i_list(物品输入列表)、ua_embeddings(用户嵌入向量)、ia_embeddings(物品嵌入向量)、aux_beh(辅助行为)、user_batch_indices(用户批次索引)和item_batch_indices(物品批次索引)。

ua_embeddings中获取目标用户嵌入向量emb_tgt和辅助用户嵌入向量emb_aux,并进行归一化操作得到normalize_emb_tgt,normalize_emb_aux。正样本分数由目标行为和辅助行为的归一化嵌入向量相乘得到,负样本分数由目标行为和所有辅助行为的归一化嵌入的转置嵌入相乘得到,最后对正负样本分数应用温度参数进行指数化操作(增强正样本之间的相似度,从而使它们在相似度计算中占据更高的权重)得到行为间的对比损失。公式如下:

非采样损失Lrec

class RecLoss(nn.Module):
    def __init__(self, data_config, args):
        super(RecLoss, self).__init__()
        self.behs = data_config['behs']
        self.n_relations = len(self.behs)
        self.n_users = data_config['n_users']
        self.n_items = data_config['n_items']
        self.emb_dim = args.embed_size
        self.coefficient = eval(args.coefficient)
        self.wid = eval(args.wid)

    def forward(self, input_u, label_phs, ua_embeddings, ia_embeddings, rela_embeddings):
        uid = ua_embeddings[input_u]
        uid = torch.reshape(uid, (-1, self.n_relations, self.emb_dim))
        pos_r_list = []
        for i in range(self.n_relations):
            beh = self.behs[i]
            pos_beh = ia_embeddings[:, i, :][label_phs[i]]  # [B, max_item, dim]
            pos_num_beh = torch.ne(label_phs[i], self.n_items).float()
            pos_beh = torch.einsum('ab,abc->abc', pos_num_beh,
                                   pos_beh)  # [B, max_item] * [B, max_item, dim] -> [B, max_item, dim]
            pos_r = torch.einsum('ac,abc->abc', uid[:, i, :],
                                 pos_beh)  # [B, dim] * [B, max_item, dim] -> [B, max_item, dim]
            pos_r = torch.einsum('ajk,lk->aj', pos_r, rela_embeddings[beh])
            pos_r_list.append(pos_r)

        loss = 0.
        for i in range(self.n_relations):
            beh = self.behs[i]
            temp = torch.einsum('ab,ac->bc', ia_embeddings[:, i, :], ia_embeddings[:, i, :]) \
                   * torch.einsum('ab,ac->bc', uid[:, i, :], uid[:, i, :])  # [B, dim]' * [B, dim] -> [dim, dim]
            tmp_loss = self.wid[i] * torch.sum(
                temp * torch.matmul(rela_embeddings[beh].T, rela_embeddings[beh]))
            tmp_loss += torch.sum((1.0 - self.wid[i]) * torch.square(pos_r_list[i]) - 2.0 * pos_r_list[i])

            loss += self.coefficient[i] * tmp_loss

        regularizer = torch.sum(torch.square(uid)) * 0.5 + torch.sum(torch.square(ia_embeddings)) * 0.5
        emb_loss = args.decay * regularizer

        return loss, emb_loss

一开始就是用户数量行为数量等。输入用户,物品,行为嵌入矩阵,在 ua_embeddings 中获取输入用户 input_u 的嵌入向量 uid。然后,将 uid 进行形状重塑,变成一个三维的 (batch_size, n_relations, emb_dim)

遍历每个行为,计算正样本的嵌入向量 pos_r。对于每个行为,首先从物品嵌入矩阵 ia_embeddings 中选取对应关系的物品嵌入向量 pos_beh,索引为label_phs[i] 。然后,将物品嵌入进行加权,得到加权后的物品嵌入 pos_beh。接下来,将用户嵌入 uid 和加权后的物品嵌入 pos_beh 进行逐元素相乘,得到 pos_r。最后,将 pos_r 与关系嵌入 rela_embeddings[beh] 进行点积,得到最终的正样本嵌入向量 pos_r。将所有的 pos_r 存储在列表 pos_r_list 中。

根据temp矩阵 和关系嵌入计算 tmp_loss。这里使用了点积运算 torch.matmul(rela_embeddings[beh].T, rela_embeddings[beh]),将关系嵌入进行转置后与自身相乘,得到一个形状为 (emb_dim, emb_dim) 的矩阵。将temp矩阵与点积结果相乘,并乘以权重 self.wid[i],得到tmp_loss的第一部分。

接下来,计算tmp_loss的第二部分。首先,将正样本嵌入向量 pos_r_list[i] 的平方项和两倍的负样本嵌入项进行计算。然后,将权重 乘以该结果,并累加到 tmp_loss 上。

最后,将 tmp_loss 乘以权重 self.coefficient[i],并累加到总损失 loss 上。最后,将正则化项乘以超参数 args.decay,得到嵌入损失 emb_loss

模型损失函数计算如下: 

  • 7
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
自我监督学习是一种机器学习方法,通过对数据进行合理的预测任务,从中获得有用的表示。与传统的监督学习不同,自我监督学习不需要人工标注的标签来指导训练,而是利用数据自身的信息进行训练。 自我监督学习的基本思想是从未标记的数据中构造有意义的标签,然后将这些标签用作训练数据,以学习有用的特征表示。通过对输入数据进行某种形式的变换或遮挡,可以生成一对相关的样本。其中一个样本称为正样本,另一个则被视为负样本。例如,在图像领域中,可以通过将图像进行旋转、裁剪或遮挡等变换来生成正负样本对。模型的目标是通过学习从一个样本到另一个样本的映射,从而使得正样本对之间的相似度更高,负样本对之间的相似度更低。 自我监督学习在许多任务中都取得了很好的效果。例如,在自然语言处理任务中,可以通过遮挡句子中的某些单词或短语来生成正负样本对,然后通过学习从一个句子到另一个句子的映射来进行训练。在计算机视觉任务中,可以通过图像的旋转、裁剪、遮挡或色彩变换等方式来生成正负样本对。 自我监督学习的优点是不需要人工标注的标签,可以利用大量的未标记数据来进行训练,从而扩大训练数据的规模。此外,自我监督学习还可以通过学习到的特征表示来提高其他任务的性能,如分类、目标检测和语义分割等。 总之,自我监督学习是一种有效的无监督学习方法,通过构造有意义的预测任务,从未标记的数据中学习有用的特征表示。它在各种任务中都有广泛的应用,并具有很高的潜力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值