zoukankan      html  css  js  c++  java
  • Neural Collaborative Filtering 神经网络协同过滤

    论文的翻译:https://www.cnblogs.com/HolyShine/p/6728999.html

    一、MF协同过滤的局限性

    The innerproduct, which simply combines the multiplication of latent features linearly, may not be sufficient to capture the complex structure of user interaction data.
    简单地将潜在特征的乘积线性组合的内积可能不足以捕捉用户交互数据的复杂结构。
    捕获不到更高阶的信息,本质上还是建模方式比较单一。


    我们首先关注的图 1(a) 中的前三行(用户)。很容易可以计算出 (s_{23}(0.66)>s_{12}(0.5)>s_{13}(0.4)) 。这样,(p1)(p2)(p3) 在潜在空间中的几何关系可绘制成图1(b)。现在,让我们考虑一个新的用户 (u4),它的输入在图1(a)中的用虚线框出。我们同样可以计算出 (s_{41}(0.6)>s_{43}(0.4)>s_{42}(0.2)) ,表示 (u4) 最接近 (u1),接着是 (u3) ,最后是 (u2) 。然而,如果MF模型将 (p4) 放在了最接近 (p1) 的位置(图1(b) 中的虚线展示了两种不同的摆放 $p4 $的方式,结果一样),那么会使得 (p4) 相比与 (p3) 更接近于 (p2) (显然,根据图1(a),(u4) 应该更接近 (u3),这会导致很大的排名误差(ranking loss)。
      上面的示例显示了MF因为使用一个简单的和固定的内积,来估计在低维潜在空间中用户-项目的复杂交互,从而所可能造成的限制。我们注意到,解决该问题的方法之一是使用大量的潜在因子 K (就是潜在空间向量的维度)。然而这可能对模型的泛化能力产生不利的影响(e.g. 数据的过拟合问题),特别是在稀疏的集合上。在论文的工作中,通过使用DNNs从数据中学习交互函数,突破了这个限制。

    二、NCF的实现


    输入层:两个特征向量:用户特性向量 ({f v}_u^U) 和 项目特征向量 ({f v}_i^I),one-hot编码的二值化稀疏向量
    嵌入层:输入层稀疏向量映射为稠密向量。输入层向量和潜在因素矩阵相乘得到。
    神经协同过滤层:多层神经网络
    输出层:一个数值

    [widehat{y}_{ui}=f({f P}^{T}{f v}_u^U,{f Q}^{T}{f v}_i^I|{f P},{f Q},Theta_{f}), (1) ]

    ({f Q}in mathbb{R}^{N imes K}) 分别为用户和项目的潜在因素矩阵。(Theta_{j})为神经网络模型参数。
    学习过程:

    [L_{sqr}=sum_{(u,i)in{f{y}cupf{y^{-}}}}w_{ui}(y_{ui}-widehat{y}_{ui})^{2}, (2) ]

    其中,(f{y})表示训练集数据(例如电影有明确的评分,评级(user,item,score)),作为正样本,(f{y^{-}}) 表示负样本。可以将为观察到的样本全体视为负样本,或者采样抽取的方式标记为负样本。
    (w_{ui}) 一个超参数,表示训练集(user,item)的权重。

    当评分数据为隐式数据,将输出值作为一个标签————1表示项目i和用户u相关。此时,预测分数(widehat{y}_{ui})代表项目i和用户u的相关性大小。
    故,使用概率函数作为最后的激活函数。使用交叉熵作为损失函数:

    [L=-sum_{(u,i)inf{y}}logwidehat{y}_{ui}-sum_{(u,i)inf{y}^{-}}logleft(1-widehat{y}_{ui} ight)=sum_{(u,i)inf{y}cupf{y}^{-}}y_{ui}logwidehat{y}_{ui}+left(1-y_{ui} ight)logleft(1-widehat{y}_{ui} ight). (3) ]

    2.2 广义矩阵分解

    [widehat{y}_{ui}=a_{out}left({f h}^{T}left({f{p}}_{u}odot{f{q}}_{i} ight) ight), (4) ]

    用户潜在向量和项目潜在向量逐元素相乘,加权,经过激活函数得到输出值。如果不考虑权值({f h})和激活函数,原式为矩阵分解。这里称其为GMF(Generalized Matrix Factorization,广义矩阵分解)

    2.3 多层感知机

    即为一般的多层感知机网络

    [{f{z}}_{1}=phi_{1}left({{f{p}}_{u}},{{f{q}}_{i}} ight)=egin{bmatrix}{{{f{p}}_{u}}}\{{{f{q}}_{i}}}end{bmatrix} \ phi_{2}({f{z}}_{1})=a_{2}left({f{W}}_2^T{f{z}}_{1}+{f b}_{2} ight), \ phi_{L}({f{z}}_{L-1})=a_{L}left({f{W}}_L^T{f{z}}_{L-1}+{f b}_{L} ight), \ widehat{y}_{ui}=sigmaleft({f{h}}^{T}phi_{L}left({f{z}}_{L-1} ight) ight), (5)]

    2.4 结合GMF和MLP
    GMF和MLP使用不同的嵌入矩阵得到不同的嵌入层。

    [widehat{y}_{ui}=sigma({f h}^{T}a({f p}_uodot{f q}_i)+{f W}egin{bmatrix}{{f p}_u}\{{f q}_i}end{bmatrix}+{f b}). (6) ]

    三、数据预处理

    主要参考:https://github.com/ZiyaoGeng/Recommender-System-with-TF2.0#1-neural-network-based-collaborative-filteringncf

    3.1 数据集
    MovieLens http://grouplens.org/datasets/movielens/1m/
    Pinterest https://sites.google.com/site/xueatalphabeta/ academic-projects
    隐式数据集,用于评估基于内容的图像推荐。原始数据非常大但是很稀疏。 例如,超过20%的用户只有一个pin(pin类似于赞一下),使得难以用来评估协同过滤算法。 因此,我们使用与MovieLens数据集相同的方式过滤数据集:仅保留至少有过20个pin的用户。处理后得到了包含55,187个用户和1,580,809个项目交互的数据的子集。 每个交互都表示用户是否将图像pin在自己的主页上。

    以MovieLens为例,原数据(1000209)处理为:
    train.rating: 994169
    test.rating: 6040
    test.negative: 6040 每个测试集 包含 99个负例样本

    3.2 读取数据

    import scipy.sparse as sp
    import numpy as np
    
    # 1. 训练集ml-1m.train.rating
    def load_rating_file_as_matrix(filename):
        """
        读取.rating文件,返回sp.dok_matrix矩阵
        """
        # Get number of users and items
        num_users, num_items = 0, 0
        with open(filename, "r") as f:
            line = f.readline()
            while line is not None and line != "":
                arr = line.split("	")
                u, i = int(arr[0]), int(arr[1])
                num_users = max(num_users, u)
                num_items = max(num_items, i)
                line = f.readline()
        # 用户数*电影数的矩阵,遍历用户-电影-评分数据,有评分的位置置1,其它位置置0。
        mat = sp.dok_matrix((num_users + 1, num_items + 1), dtype=np.float32)
        with open(filename, "r") as f:
            line = f.readline()
            while line is not None and line != "":
                arr = line.split("	")
                user, item, rating = int(arr[0]), int(arr[1]), float(arr[2])
                if rating > 0:
                    mat[user, item] = 1.0
                line = f.readline()
        return mat
    # 2 测试集 ml-1m.test.rating
    # 只有6040个样本,保存为列表,其元素为[用户ID,电影ID]
    def load_rating_file_as_list(filename):
        ratingList = []
        with open(filename, "r") as f:
            line = f.readline()
            while line is not None and line != "":
                arr = line.split("	")
                user, item = int(arr[0]), int(arr[1])
                ratingList.append([user, item])
                line = f.readline()
        return ratingList   
    # 3 负采样
    # 保存到列表,列表元素为[负样本电影ID,...]
    def load_negative_file(filename):
        negativeList = []
        with open(filename, "r") as f:
            line = f.readline()
            while line is not None and line != "":
                arr = line.split("	")
                negatives = []
                for x in arr[1:]:
                    negatives.append(int(x))
                negativeList.append(negatives)
                line = f.readline()
        return negativeList 
    

    四、构建模型(基于TF2.0)

    class NeuMF(keras.Model):
        def __init__(self, num_users, num_items, mf_dim, layers, reg_layers, reg_mf):
            super(NeuMF, self).__init__()
            self.MF_Embedding_User = keras.layers.Embedding(
                input_dim=num_users, #用户数
                output_dim=mf_dim,  # 嵌入维度8
                name='mf_embedding_user', # MF中的用户嵌入层name
                embeddings_initializer='random_uniform', # 均匀分布初始化
                embeddings_regularizer=regularizers.l2(reg_mf[0]), # l2正则化
            )
            self.MF_Embedding_Item = keras.layers.Embedding(
                input_dim=num_items, # 电影数
                output_dim=mf_dim, # 嵌入维度 8
                name='mf_embedding_item', MF中的项目嵌入层name
                embeddings_initializer='random_uniform', # 均匀分布随机初始化
                embeddings_regularizer=regularizers.l2(reg_mf[1]), # l2正则化
            )
            self.MLP_Embedding_User = keras.layers.Embedding(
                input_dim=num_users,
                output_dim=int(layers[0] / 2), # MLP嵌入层维度64/2 = 32
                name='mlp_embedding_user', # MLP中的用户嵌入层
                embeddings_initializer='random_uniform',
                embeddings_regularizer=regularizers.l2(reg_layers[0]),
            )
            self.MLP_Embedding_Item = keras.layers.Embedding(
                input_dim=num_items,
                output_dim=int(layers[0] / 2), # MLP输入层维度 64/2 = 32
                name='mlp_embedding_item', # MLP中的用户嵌入层名称
                embeddings_initializer='random_uniform',
                embeddings_regularizer=regularizers.l2(reg_layers[0]),
            )
            self.flatten = keras.layers.Flatten() #
            self.mf_vector = keras.layers.Dot(axes=1) # GMF层为用户向量和项目向量点积。
            self.mlp_vector = keras.layers.Concatenate(axis=-1) 
            self.layer1 = keras.layers.Dense( 
                layers[1], # 第一层输出维度32
                name='layer1',
                activation='relu',
                kernel_regularizer=regularizers.l2(reg_layers[1]),
            )
            self.layer2 = keras.layers.Dense(
                layers[2], # 第二层输出维度16
                name='layer2',
                activation='relu',
                kernel_regularizer=regularizers.l2(reg_layers[2]),
            )
            self.layer3 = keras.layers.Dense(
                layers[3], # 第三层输出维度8
                name='layer3',
                activation='relu',
                kernel_regularizer=regularizers.l2(reg_layers[3]),
            )
            self.predict_vector = keras.layers.Concatenate(axis=-1)
            self.layer4 = keras.layers.Dense(
                1, # 最好一层,即NeuMF层的输出维度为1
                activation='sigmoid',
                kernel_initializer='lecun_uniform',
                name='prediction'
            )
    
        @tf.function
        def call(self, inputs):
            # Embedding,四个嵌入层,输入皆为一个ID,然后转换为到对应维度的嵌入向量
            MF_Embedding_User = self.MF_Embedding_User(inputs[0]) # (1,8)
            MF_Embedding_Item = self.MF_Embedding_Item(inputs[1]) # 1
            MLP_Embedding_User = self.MLP_Embedding_User(inputs[0]) # (1,32)
            MLP_Embedding_Item = self.MLP_Embedding_Item(inputs[1]) # 1
    
            # MF MF层输出为用户电影的点积
            mf_user_latent = self.flatten(MF_Embedding_User) #(1,8)
            mf_item_latent = self.flatten(MF_Embedding_Item)
            mf_vector = self.mf_vector([mf_user_latent, mf_item_latent]) # (1,1)
    
            # MLP 
            mlp_user_latent = self.flatten(MLP_Embedding_User) # (1,32)
            mlp_item_latent = self.flatten(MLP_Embedding_Item)
            mlp_vector = self.mlp_vector([mlp_user_latent, mlp_item_latent]) #两个向量concatenate为一个长向量 (1,64)
            mlp_vector = self.layer1(mlp_vector)
            mlp_vector = self.layer2(mlp_vector)
            mlp_vector = self.layer3(mlp_vector) # 第三层的输出维度为8
    
            # NeuMF
            vector = self.predict_vector([mf_vector, mlp_vector]) # MF层输出和MLP层输出合并得到NeuMF的输入 (1,9)
            output = self.layer4(vector) # 输出维度(1,1),[0,1]
    
            return output
    

    五、build模型

    model.build(input_shape=[1,1]) # 建立模型,并指明输入的维度及其形状
    moodel.compile(optimizer=optimizers.Adam(lr=0.001), loss='binary_crossentropy') # 用于使用损失函数,优化,损失指标,损失重量等配置模型
    

    六、训练模型&保存模型

    # 1. 输入userID,输入电影ID,标签。正样本 + 4个负样本
    user_input, item_input, labels = get_train_instances(train, configs.num_negatives)
    # model.fit(x,y)
    hist = model.fit([np.array(user_input), np.array(item_input)],
                             np.array(labels),
                             batch_size=256,
                             epochs=1,
                             verbose=1,
                             shuffle=True)
    
    # 到达某些条件,保存模型
    model_out_file = 'Save/%s_NeuMF_%d_%s_%d.h5' % (ml-1m, 8, [64,32,16,8], time())
    model.save_weights(model_out_file, overwrite=True)
    

    七、评估模型

    对于测试集中的一个样本即 (用户ID,电影ID),[99个负例电影ID]组合为一个电影ID列表 [99个负例电影ID,正例电影ID]
    输入的用户ID扩展为长度为100的数组

    map_item_score = {}
    gtItem # 正例
    items = [99个负例itemID,gtItem]
    users = np.full(100,userID,dtype='int32')
    predictions = model.predict(x=[users,np.array(items)],batch_size=100,verbose=0)
    for i in range(len(items)):
        item = items[i]
        map_item_score[item] = predictions[i] # 每个item 的预测得分。
    items.pop() # 移除最后一个
    ranklist = heapq.nlargest(_K, map_item_score, key=map_item_score.get) # 返回前10个最大值元素。
    

    得到排序后的每个item的预测评分。计算其hit ratio 和 NDCG

    # HitRatio
    def getHitRatio(ranklist, gtItem):
        for item in ranklist:
            if item == gtItem:
                return 1
        return 0
    # NDCG
    def getNDCG(ranklist, gtItem):
        for i in range(len(ranklist)):
            item = ranklist[i]
            if item == gtItem:
                return np.log(2) / np.log(i+2)
    
  • 相关阅读:
    vue-打包为webapp,如何解决应用内跳转外部链接返回导致退出应用
    vue-引入mui.js报错如何处理
    微信小程序中-折线图
    Docker基础命令
    retry示例
    authenticate验证的流程
    django生产环境启动问题
    redis基本操作
    DBUtils数据库连接池
    外部程序调用Django模块的解决办法
  • 原文地址:https://www.cnblogs.com/leimu/p/13559849.html
Copyright © 2011-2022 走看看