Enhanced Graph Learning for Collaborative Filteringvia Mutual Information Maximization

一、前言

图神经协同过滤(CF,Collaborative Filtering)能够根据user-item二部交互图同时学习user和item的embedding,目前已被证明是有效的。在交互图中,考虑到节点间的隐式反馈(没有边相连)依然有可能在某种程度上反映出用户的积极行为(例如相同偏好用户更有可能互相分享相似的item,尽管它们在user-item二部图上没有直接相连的边),因此传统的图协同过滤方法直接使用二部图作为输入显然是不够用的。为此,作者试图通过一种增强图的训练方式提高CF的表现能力,将节点embedding的学习和图结构的学习进行互相增强。

二、EGLN模型

1、传统邻接矩阵

R\epsilon \mathbb{R}^{M\times N},首先表示 M 个user和 N 个item交互行为的二部无向图可以描述为下面邻接矩阵 A ,它是显式的(已经存在的交互行为):

 目标是找到一个具有边权重矩阵a的更好的残差图, 这样我们可以更好地为用户和项目嵌入学习服务,从而提高CF性能。目前,基于图的CF由两个模块组成:残差图学习和节点嵌入学习。这两个模块不是孤立的,而是密切相关的。一方面,残差图学习模块需要依赖当前学习的用户和项目嵌入,以便找到A的可能链接

2、通过学习嵌入增强图形学习

由于先前的图协同过滤GCF模型(例如NGCF、LightGCN)直接将显式交互图 A 作为输入来学习节点embedding,而忽略了节点间的隐式关系(没有显示边相连,但存在潜在的积极行为),作者提出通过学习一个增强图  A^{E}=A+A^{R}来提高GCF的表现能力,可以看出 A^{E} 同时包含了显式 A 和隐式 A^{R} 两个方面的图结构信息。由于 A 是确定的,因此 AE 的学习就放在 AR 的学习上

 其中的子矩阵 S 表示user-item二部图中的潜在的交互可能性,作者使用cos相似度进行计算,并保留每个用户topk相似度的边作为潜在交互:

 这里\sigma \left ( x \right )是一个sigmoid函数,它将计算出的相似性转换为范围(0,1),W1和W2是两个可训练的矩阵

然而,学习到的相似矩阵S是稠密的,很难用于图卷积。与使用阈值截取,因为它可以灵活地控制学习图的边缘。实际上,对于每个用户,我们都会保留具有top-K计算相似度的边缘。稀疏相似矩阵计算如下:

原始图形中的边权重A等于1,但在具有权重矩阵A的增强图形A^{E}中有所不同. 原因是学习的残差图有两种边:一种是已经出现在原始图中的旧边,另一种是与原始图相比新添加的边。因此,在具有残差图结构的增强图中,旧边用大于1的权重值重新加权,而新边用小于1的值加权。这表明我们的增强图可以同时添加缺少的边和重新加权现有边。

 3、使用增强的图形结构嵌入学习

一个 K 跳GNN的节点embedding更新/学习规则定义为堆叠 K 层的邻居聚合器 AGG(⋅)

 作者抛弃传统GNN涉及非线性变换的操作,只保留邻居聚合过程:

 4、用户的偏好预测如下

 5、矩阵形式

 则经过第 K 层AGG 得到GCF的输出结果,相当于已经聚合了 K 阶邻居信息的user和item的embedding(分别是 u 和 v ),然后就可以用在下游推荐任务上,通过求取内积的方式预测未来状态下user对item的交互可能性。

6、优化器

(1)边缘约束

一个直接的想法是最小化实际存在的交互图 A 与相似度计算后得到的潜在预测图A^{R}之间的均方损失,先确保预测的结果和实际的结果趋于一致:

 我们将原始图的边缘视为基本真值正反馈。因此,学习的残差图应该保留这些边。然而,此边向约束仅学习单个链接相关性,但无法捕获全局图形属性。下面,我们通过局部-全局一致性学习来介绍全局图的属性。

(2)图形约束

由于上式只考虑了图的局部结构而忽略了图的全局属性信息(可以先看Deep Graph Infomax这篇文章),作者尝试最大化节点局部embedding与全局embedding之间的互信息来保持图的全局信息,具体的操作就是对一条边相连的两个节点(一端为user另一端为item)进行 concatenation+MLP 操作后得到局部embedding hai⊂ℜ2d ,则增强图 AE 上所有 Z 条边得到的全局embedding g 则是通过 Sign(⋅) 函数计算得到:

 

7、模型训练

首先是常用的BPR损失,让已观测到的交互得分大于未观测到的交互得分:

 然后将BPR损失与上面用于增强图学习优化的目标函数(局部重构+全局优化)进行联合优化训练:

 三、实验部分

1、实验结果

 2、不同数据稀疏度下的性能

用户分成五组,每个用户的交互次数分别超过0、8、16、32和64

当交互次数增加时,所有模型的性能都会增加,这意味着高质量的用户表示需要更多的交互。一般来说,我们提出的EGLN在大多数组上表现出最佳性能,但在最密集的组中失败。我们猜测原因是CF模型可以在充分交互的情况下实现良好的性能,因此我们提出的增强型图学习模块在该场景中不需要。

3、详细模型分析

(1)层数影响

 对于稀疏数据集,更深的图卷积可以帮助聚集更多的邻居,这有利于表示学习。然而,对于密集的数据集,太深的传播层很容易导致图上的过度平滑。

(2)参数敏感性

 总而言之,选择合理的参数很重要

 四、EGLN模型代码实现

1、计算相似度(公式3)

def get_simi_matrix_old(user_matrix, item_matrix, w1, w2, adj_matrix, topk1, topk2, gama):#adj 是 dense-u-i
    user_rep = tf.matmul(user_matrix, w1)#6040*16   做个特征变换
    item_rep = tf.matmul(item_matrix, w2)#6040*16
    user_emb1 = tf.nn.l2_normalize(user_rep, axis=1)  #做个标准化
    item_emb1 = tf.nn.l2_normalize(item_rep, axis=1)
    sim_matrix = tf.nn.sigmoid(tf.matmul(user_emb1, tf.transpose(item_emb1)))#6040*3952  每个用户对每个物品的相似度值 # [m,n]

    loss_simi_adj = gama * tf.reduce_mean(tf.square(sim_matrix - adj_matrix))#用稠密矩阵 算loss
    user_topk = tf.nn.top_k(sim_matrix, topk1)#6040*4  得到
    user_topk_values = tf.reshape(user_topk.values, [-1])#24160  存储user-topk4的值
    user_topk_columns = tf.cast(tf.reshape(user_topk.indices, [-1, 1]), dtype=tf.int64)#(24160,1)  得到topk中每个值得索引   tf.cast数据类型转换
    user_all_rows = np.reshape(np.arange(user_count), [-1, 1])#6040,1
    user_all_rows = tf.to_int64(user_all_rows,name='ToInt64')
    user_topk_rows = tf.reshape(tf.tile(user_all_rows, multiples=[1, topk1]), [-1, 1])# 0000 1111 2222 3333......  24160
    user_topk_indexs = tf.concat([user_topk_rows, user_topk_columns], 1)#24160*2   存储topk行标 +每个用户top-4的索引值
    user_item_sparse_simi = tf.SparseTensor(indices=user_topk_indexs, values=user_topk_values,
                                            dense_shape=[user_count, item_count])#6040*3952

    item_topk = tf.nn.top_k(tf.transpose(sim_matrix), topk2)#3952*6
    item_topk_values = tf.reshape(item_topk.values, [-1])
    item_topk_columns = tf.cast(tf.reshape(item_topk.indices, [-1, 1]), dtype=tf.int64)
    item_all_rows = np.reshape(np.arange(item_count), [-1, 1])
    item_all_rows = tf.to_int64(item_all_rows, name='ToInt64')
    item_topk_rows = tf.reshape(tf.tile(item_all_rows, multiples=[1, topk2]), [-1, 1])
    item_topk_indexs = tf.concat([item_topk_rows, item_topk_columns], 1)#23712*2
    item_user_sparse_simi = tf.SparseTensor(indices=item_topk_indexs, values=item_topk_values,
                                            dense_shape=[item_count, user_count]) #3952*6040
    return user_item_sparse_simi, item_user_sparse_simi, loss_simi_adj, user_topk_indexs
user_item_simi_matrix, item_user_simi_matrix, loss_s, add_edges = get_simi_matrix_old(input_user_emb, input_item_emb,  #add_edges 24160*2
                                                                                    sim_w1, sim_w2, adj_matrix_dense,
                                                                                      topk_u, topk_v, gama)

2、loss_s

公式11,Loss_s

    loss_simi_adj = gama * tf.reduce_mean(tf.square(sim_matrix - adj_matrix))#用稠密矩阵 算loss

3、原始adj+潜在交互adj

add_sparse_user_matrix = tf.sparse_add(user_item_adj_matrix, user_item_simi_matrix)#6040*3952  #原始稀疏adj+潜在交互稀疏adj
add_sparse_item_matrix = tf.sparse_add(item_user_adj_matrix, item_user_simi_matrix)#3952*6040
user_item_final_matrix = nor_sparse_matrix(add_sparse_user_matrix)
item_user_final_matrix = nor_sparse_matrix(add_sparse_item_matrix)

4、计算原始表示

def model_gcn(_user_emb, _item_emb, _layer):
    user_emb_layer1 = tf.sparse_tensor_dense_matmul(user_item_final_matrix, _item_emb) + _user_emb
    item_emb_layer1 = tf.sparse_tensor_dense_matmul(item_user_final_matrix, _user_emb) + _item_emb
    user_emb_layer2 = tf.sparse_tensor_dense_matmul(user_item_final_matrix, item_emb_layer1) + user_emb_layer1
    item_emb_layer2 = tf.sparse_tensor_dense_matmul(item_user_final_matrix, user_emb_layer1) + item_emb_layer1
    user_emb_layer3 = tf.sparse_tensor_dense_matmul(user_item_final_matrix, item_emb_layer2) + user_emb_layer2
    item_emb_layer3 = tf.sparse_tensor_dense_matmul(item_user_final_matrix, user_emb_layer2) + item_emb_layer2
    user_emb_layer4 = tf.sparse_tensor_dense_matmul(user_item_final_matrix, item_emb_layer3) + user_emb_layer3
    item_emb_layer4 = tf.sparse_tensor_dense_matmul(item_user_final_matrix, user_emb_layer3) + item_emb_layer3
    if _layer == 1:
        final_user_emb, final_item_emb = user_emb_layer1, item_emb_layer1
    if _layer == 2:
        final_user_emb, final_item_emb = user_emb_layer2, item_emb_layer2
    if _layer == 3:
        final_user_emb, final_item_emb = user_emb_layer3, item_emb_layer3
    if _layer == 4:
        final_user_emb, final_item_emb = user_emb_layer4, item_emb_layer4
    return final_user_emb, final_item_emb


final_user_emb, final_item_emb = model_gcn(user_emb, item_emb, layer)#原始user—embed  item-embed

5、图属性扰动

按行随机打乱embedding矩阵 F 得到负属性矩阵 F~ ,然后通过聚合操作得到 h~ ;

def shuffle_embedding(input_emb_u, input_emb_v, rate): #输入初始的user—embed   item——embed
    mid = int(dimension * rate)
    mid = 29
    fixed_emb_u, dynamic_emb_u = tf.split(input_emb_u, [mid, dimension - mid], 1) #6040*32    固定6040*29  动态6040*3
    dynamic_emb_u = tf.gather(tf.transpose(dynamic_emb_u), tf.random.shuffle(tf.range(dimension - mid)))#3*6040
    out_emb_u = tf.concat([fixed_emb_u, tf.transpose(dynamic_emb_u)], 1)#6040*32
    fixed_emb_v, dynamic_emb_v = tf.split(input_emb_v, [mid, dimension - mid], 1)
    dynamic_emb_v = tf.gather(tf.transpose(dynamic_emb_v), tf.random.shuffle(tf.range(dimension - mid)))
    out_emb_v = tf.concat([fixed_emb_v, tf.transpose(dynamic_emb_v)], 1)
    return out_emb_u, out_emb_v  #  扰乱后的6040*32   3952*32
# 32=29+3    将后三个进行随机组合,再贴回去,达到embedd扰动

_shuffle_user_emb, _shuffle_item_emb = shuffle_embedding(user_emb, item_emb, shuffle_rate)

6、计算扰动后的表示

def model_gcn(_user_emb, _item_emb, _layer):
    user_emb_layer1 = tf.sparse_tensor_dense_matmul(user_item_final_matrix, _item_emb) + _user_emb
    item_emb_layer1 = tf.sparse_tensor_dense_matmul(item_user_final_matrix, _user_emb) + _item_emb
    user_emb_layer2 = tf.sparse_tensor_dense_matmul(user_item_final_matrix, item_emb_layer1) + user_emb_layer1
    item_emb_layer2 = tf.sparse_tensor_dense_matmul(item_user_final_matrix, user_emb_layer1) + item_emb_layer1
    user_emb_layer3 = tf.sparse_tensor_dense_matmul(user_item_final_matrix, item_emb_layer2) + user_emb_layer2
    item_emb_layer3 = tf.sparse_tensor_dense_matmul(item_user_final_matrix, user_emb_layer2) + item_emb_layer2
    user_emb_layer4 = tf.sparse_tensor_dense_matmul(user_item_final_matrix, item_emb_layer3) + user_emb_layer3
    item_emb_layer4 = tf.sparse_tensor_dense_matmul(item_user_final_matrix, user_emb_layer3) + item_emb_layer3
    if _layer == 1:
        final_user_emb, final_item_emb = user_emb_layer1, item_emb_layer1
    if _layer == 2:
        final_user_emb, final_item_emb = user_emb_layer2, item_emb_layer2
    if _layer == 3:
        final_user_emb, final_item_emb = user_emb_layer3, item_emb_layer3
    if _layer == 4:
        final_user_emb, final_item_emb = user_emb_layer4, item_emb_layer4
    return final_user_emb, final_item_emb



shuffle_user_emb, shuffle_item_emb = model_gcn(_shuffle_user_emb, _shuffle_item_emb, layer)

7、Loss-r

u_input = tf.placeholder("int32", [None, 1])
i_input = tf.placeholder("int32", [None, 1])
j_input = tf.placeholder("int32", [None, 1])
ua = tf.gather_nd(final_user_emb, u_input)  #原始GCN聚合后的表示
vi = tf.gather_nd(final_item_emb, i_input)   #原始GCN聚合后的表示
vj = tf.gather_nd(final_item_emb, j_input)
Rai = tf.reduce_sum(tf.multiply(ua, vi), 1, keepdims=True)
Raj = tf.reduce_sum(tf.multiply(ua, vj), 1, keepdims=True)
auc = tf.reduce_mean(tf.to_float((Rai - Raj) > 0))
bprloss = -tf.reduce_mean(tf.log(tf.clip_by_value(tf.nn.sigmoid(Rai - Raj), 1e-9, 1.0)))
regulation = lamda * tf.reduce_mean(tf.square(ua) + tf.square(vi) + tf.square(vj))
loss_r = bprloss + regulation

8、loss_d

def make_discriminator_bilinear1(_lo_emb, _gl_emb):
    '''
    input: _lo_emb[None,64], _gl_emb[None,64]
    output: label[None,1]
    '''
    emb_d1 = tf.matmul(_lo_emb, bilinear_w1)  #特征变换变换   ?*64
    emb_d2 = tf.multiply(emb_d1, _gl_emb)    #计算局部与全局互信息   20480*64
    emb_d3 = tf.reduce_sum(emb_d2, 1, keepdims=True) + bilinear_b1  #20480*1
    return emb_d3

def local_global_v1():
    '''
    pos:<u,i>, neg:<u,j>
    '''
    pos_local_emb = tf.concat([tf.sigmoid(ua), tf.sigmoid(vi)], 1)
    neg_local_emb = tf.concat([tf.sigmoid(ua), tf.sigmoid(vj)], 1)
    #    avg_global_emb = tf.reduce_mean(pos_local_emb, 0, keepdims=True)

    add_user_list, add_item_list = tf.split(add_edges, 2, axis=1)#24160  #user-topk  索引  row(user)   col(item)
    all_user_emb_1 = tf.gather_nd(final_user_emb, all_user_list)  # [178725,32]  原始adj的 user索引 0(*14)
    all_item_emb_1 = tf.gather_nd(final_item_emb, all_item_list)  # [178725,32]  原始adj的 item索引
    all_user_emb_2 = tf.gather_nd(final_user_emb, add_user_list)# [24160,32]  潜在图adj的 user索引
    all_item_emb_2 = tf.gather_nd(final_item_emb, add_item_list)# [24160,32]  潜在图adj的 item索引
    all_user_emb = tf.concat([all_user_emb_1, all_user_emb_2], 0)  #202885*32
    all_item_emb = tf.concat([all_item_emb_1, all_item_emb_2], 0)  #202885*32
    all_edge_emb = tf.concat([tf.sigmoid(all_user_emb), tf.sigmoid(all_item_emb)], 1)  # [202885,64]
    avg_global_emb = tf.reduce_mean(all_edge_emb, 0, keepdims=True)#1*64

    get_shape = tf.reduce_sum(ua, 1, keepdims=True)
    global_emb = tf.tile(avg_global_emb, [batch_size, 1])#20480*64   将1*64的扩展为20480*64,行复制
    one_label = tf.ones_like(get_shape, dtype=tf.float32)
    zero_label = tf.zeros_like(get_shape, dtype=tf.float32)
    real_predict = make_discriminator_bilinear1(pos_local_emb, global_emb)
    fake_predict = make_discriminator_bilinear1(neg_local_emb, global_emb)
    d_loss_all = tf.nn.sigmoid_cross_entropy_with_logits(logits=real_predict, labels=one_label) + \
                 tf.nn.sigmoid_cross_entropy_with_logits(logits=fake_predict, labels=zero_label)
    loss_d = alpha * tf.reduce_mean(d_loss_all)
    return loss_d

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值