zoukankan      html  css  js  c++  java
  • 机器学习笔记7:矩阵分解Recommender.Matrix.Factorization

    参考地址:
    贪心学院:https://github.com/GreedyAIAcademy/Machine-Learning

    1矩阵分解概述

    1.1用在什么地方

    推荐系统:最著名的就那个烂大街的啤酒和尿布的故事,还有现在头条的投喂用户使用的也是推荐系统。就不多说了。

    1.2推荐的原理

    设,矩阵R代表3个用户对4部影片的评分,矩阵U和P是通过算法分解出来的矩阵,R是预测出来的矩阵。
    此时我们可以看出, 矩阵R
    中的值很接近原始矩阵R中的值,这样填补之后的值就是我们要的数字。

    2矩阵分解的原理

    2.1目标函数

    如1.2所示,我们希望的结果就是R*中的结果与R中的结果差值最小。
    因此我们可以得到目标函数:

    [arg min_{U,P} sum_{(i,j) in Z}(R_{ij}-U_{i}P_{j}^{T})^2 \ \ Z = {(i,j):r_{ij} 已知} ]

    (U_i P_j)为行向量,分别来自于矩阵U和矩阵P的第i行和第j行;分别代表了第i个用户的画像向量,和第j个物品的画像向量。

    2.2 损失函数

    为了方便求导,我们乘个1/2,结果如下:

    [arg min_{U,P} sum_{(i,j) in Z}frac{1}{2}(R_{ij}-U_{i}cdot P_{j})^2 \ \ Z = {(i,j):r_{ij} 已知} ]

    继续计算结果如下:

    [L_{ij} = frac{1}{2}(R_{ij}-U_{i}cdot P_{j})^2 \ ]

    得到损失梯度如下:

    [frac{partial L_{ij}}{partial U_{i}}= frac{partial }{partial U_{i}} [frac{1}{2}(R_{ij}-U_{i}cdot P_{j})^2] = -P_j(R_{ij}-U_{i}cdot P_{j}) \ \ frac{partial L_{ij}}{partial P_{j}}= frac{partial }{partial P_{j}} [frac{1}{2}(R_{ij}-U_{i}cdot P_{j})^2] = -U_i(R_{ij}-U_{i}cdot P_{j}) ]

    为了防止过拟合和训练过程中的误差,加入正则项

    [arg min_{U,P} sum_{(i,j) in Z}frac{1}{2}(R_{ij}-U_{i}cdot P_{j})^2 + lambda [ sum_{i=1}^{m}left | U_i ight |^2 + sum_{i=1}^{n}left | P_j ight |^2] ]

    再求偏导可得:

    [frac{partial L_{ij}}{partial U_{i}}=-P_{j}(R_{ij}-U_{i}cdot P_{j}) + lambda U_{i} \ \ frac{partial L_{ij}}{partial P_{j}}=-U_{i}(R_{ij}-U_{i}cdot P_{j}) + lambda P_{j} \ ]

    2.3 通过梯度下降的方法求得结果

    设定k的值,设定学习步长(gamma)(learning rate),初始化U和P,重复以下步骤直到均方差满意为止:
    遍历Z中的(i,j),Z={(i,j):(r_{ij})已知}

    [U_{i}leftarrow U_{i} - gamma frac{partial L_{ij}}{partial U_{i}} \ P_{j}leftarrow P_{j} - gamma frac{partial L_{ij}}{partial P_{j}} \ ]

    3 代码实现

    看了上面的公式肯定是一知半解的,但看了矩阵分解函数,就会对梯度下降问题的解决方法豁然开朗
    代码:

    # 导入 nunpy 和 surprise 辅助库
    import numpy as np
    import surprise  
    
    # 计算模型
    class MatrixFactorization(surprise.AlgoBase):
        '''基于矩阵分解的推荐.'''
        
        def __init__(self, learning_rate, n_epochs, n_factors, lmd):
            
            self.lr = learning_rate  # 梯度下降法的学习率
            self.n_epochs = n_epochs  # 梯度下降法的迭代次数
            self.n_factors = n_factors  # 分解的矩阵的秩(rank)
            self.lmd = lmd # 防止过拟合的正则化的强度
            
        def fit(self, trainset):
            '''通过梯度下降法训练, 得到所有 u_i 和 p_j 的值'''
            
            print('Fitting data with SGD...')
            
            # 随机初始化 user 和 item 矩阵.
            u = np.random.normal(0, .1, (trainset.n_users, self.n_factors))
            p = np.random.normal(0, .1, (trainset.n_items, self.n_factors))
            
            # 梯度下降法
            for _ in range(self.n_epochs):
                for i, j, r_ij in trainset.all_ratings():
                    err = r_ij - np.dot(u[i], p[j])
                    # 利用梯度调整 u_i 和 p_j
                    u[i] -= -self.lr * err * p[j] + self.lr * self.lmd * u[i]
                    p[j] -= -self.lr * err * u[i] + self.lr * self.lmd * p[j]
                    # 注意: 修正 p_j 时, 按照严格定义, 我们应该使用 u_i 修正之前的值, 但是实际上差别微乎其微
            
            self.u, self.p = u, p
            self.trainset = trainset
    
        def estimate(self, i, j):
            '''预测 user i 对 item j 的评分.'''
            
            # 如果用户 i 和物品 j 是已知的值, 返回 u_i 和 p_j 的点积
            # 否则使用全局平均评分rating值(cold start 冷启动问题)
            if self.trainset.knows_user(i) and self.trainset.knows_item(j):
                return np.dot(self.u[i], self.p[j])
            else:
                return self.trainset.global_mean
    			
    # 应用
    from surprise import BaselineOnly
    from surprise import Dataset
    from surprise import Reader
    from surprise import accuracy
    from surprise.model_selection import cross_validate
    from surprise.model_selection import train_test_split
    import os
    
    # 数据文件
    file_path = os.path.expanduser('./ml-100k/u.data')
    
    # 数据文件的格式如下:
    # 'user item rating timestamp', 使用制表符 '	' 分割, rating值在1-5之间.
    reader = Reader(line_format='user item rating timestamp', sep='	', rating_scale=(1, 5))
    data = Dataset.load_from_file(file_path, reader=reader)
    
    # 将数据随机分为训练和测试数据集
    trainset, testset = train_test_split(data, test_size=.25)
    
    # 初始化以上定义的矩阵分解类.
    algo = MatrixFactorization(learning_rate=.005, n_epochs=60, n_factors=2, lmd = 0.2)
    
    # 训练
    algo.fit(trainset)
    
    # 预测
    predictions = algo.test(testset)
    
    # 计算平均绝对误差
    accuracy.mae(predictions)
    
    #结果:0.7871327139440717
    
    # 使用 surpise 内建的基于最近邻的方法做比较
    algo = surprise.KNNBasic()
    algo.fit(trainset)
    predictions = algo.test(testset)
    accuracy.mae(predictions)
    
    #结果:0.7827160139309475
    
    # 使用 surpise 内建的基于 SVD 的方法做比较
    algo = surprise.SVD()
    algo.fit(trainset)
    predictions = algo.test(testset)
    accuracy.mae(predictions)
    
    #结果:0.7450633876817936
    
  • 相关阅读:
    LeetCode-198-打家劫舍
    LeetCode-279-完全平方数
    LeetCode-91-解码方法
    Redis RDB 分析工具 rdbtools 说明(转载)
    ftp软件下载,ftp软件到底在哪里下载
    element-ui组件库二次开发——打包、上传npm
    ftp客户端工具,这6款好用的ftp客户端工具,站长们必不可少的常用工具!
    不会用Java Future,我怀疑你泡茶没我快, 又是超长图文!!(转载)
    ftp管理软件,ftp管理软件怎么使用
    Android连载22-自定义广播之标准广播发送(转载)
  • 原文地址:https://www.cnblogs.com/bugutian/p/11288673.html
Copyright © 2011-2022 走看看