• 初学推荐系统-03- 隐语义模型与矩阵分解


    1. 隐语义模型/矩阵分解模型

    出发点: 弥补基于用户或者物品的协同过滤模型(UserCF/ItermCF)处理稀疏矩阵能力不足的问题

    隐语义/矩阵分解模型: 从协同过滤中衍生出矩阵分解模型(Matrix Factorization,MF),Plus版本

    特点: 使用更加稠密的隐向量来表示用户和物品,挖掘用户和物品的隐含兴趣和隐含特征

    矩阵分解模型的分解的大致意思: 把协同过滤矩阵分解成用户兴趣矩阵和物品属性分类矩阵, 然后就可以基于这两个分解后的矩阵去预测谋和用户对某个新物品的评分,然后基于这些评分去推荐.(如下图,k1/k2就代表隐向量,举栗子:在音乐中可以是歌手/风格/时间/口味等属性)

    矩阵分解

    2. 隐语义模型

    隐语义的核心思想: 通过隐含特征联系用户兴趣或者物品,基于用户的行为找出潜在的主题或者分类,然后对物品进行自动聚类,划分到不同类别、主题(用户的兴趣)。

    说人话: 通过一些角度把用户兴趣或者物品进行归类(建立用户、物品的兴趣/偏好等成分的隐向量模型),当用户来了以后,直接分析用户的兴趣分类,然后从这个分类中挑选Ta可能喜欢的物品。

    举个网抑云的栗子

    2.3.1 潜在因子——用户口味 偏好矩阵Q

    在这里插入图片描述

    2.3.2 潜在因子——音乐口味 成分矩阵Q

    在这里插入图片描述

    2.3.3 计算出用户——音乐的隐向量的关联矩阵Q

    利用上面的两个矩阵,可以得出张三的对音乐A的打分:

    张三对小清新的偏好 * 音乐A含有小清新的成分 + 张三对重口味的偏好 * 音乐A含有重口味的成分 + 张三对优雅的偏好 * 音乐A含有优雅的成分....,
    根据隐向量其实就可以得到张三对音乐A的打分,即: 0.6 * 0.9 + 0.8 * 0.1 + 0.1 * 0.2 + 0.1 * 0.4 + 0.7 * 0 = 0.69.

    依次类推,可以得出用户-音乐的评分矩阵:

    在这里插入图片描述

    3. 矩阵分解算法的原理

    如下图, 可以通过分解协同过滤的共现矩阵来得到用户和物品的隐向量

    k是隐向量维度, 也就是隐含特征个数,K的大小决定了隐向量表达能力的强弱; k越大, 表达信息就越强, 理解起来就是把用户的兴趣和物品的分类划分的越具体。
    还是这张图

    4. 矩阵分解算法的求解

    太难了, 直接PASS~~

    谈到矩阵分解, 最常用的方法是特征值分解(EVD)或者奇异值分解(SVD), 关于这两个的具体原理可以参考下面的链接奇异值分解(SVD)的原理详解及推导,但是这两种方式在这里不适用。
    首先是EVD, 它要求分解的矩阵是方阵, 显然用户-物品矩阵不满足这个要求, 而传统的SVD分解, 会要求原始矩阵是稠密的, 而我们这里的这种矩阵一般情况下是非常稀疏的, 如果想用奇异值分解, 就必须对缺失的元素进行填充, 而一旦补全, 空间复杂度就会非常高, 且补的不一定对。 然后就是SVD分解计算复杂度非常高, 而我们的用户-物品矩阵非常大, 所以基本上无法使用。

    5. Basic SVD (基础的奇异值分解)

    一个矩阵分解算法叫做Funk-SVD, 后来被Netflix Prize的冠军Koren称为Latent Factor Model(LFM,潜在因素模型)。
    Funk-SVD的思想很简单: 把求解上面两个矩阵的参数问题转换成一个最优化问题,可以通过训练集里面的观察值利用最小化来学习用户矩阵和物品矩阵。

    简述SVD分解矩阵找出隐向量的主要思想步骤

    左边推右边

    1. 准备用户-物品评分矩阵, 求解k1,k2隐向量
    2. 随机初始化一个用户矩阵U和物品矩阵V,求积得出一个猜测的用户-物品矩阵r,可以得出一个猜测值和真实值的误差
    3. 有了误差,我们可以计算出总的误差平方和SSE
    4. SSE作为损失值,我们想办法进行训练(梯度下降),把SSE下降到最小,那么我们的两个矩阵参数可以通过逼近求解出来;所以这个问题就可以转换为最优化的问题,而我们的目标函数就是:

    [min _{oldsymbol{q}^{*}, oldsymbol{p}^{*}} sum_{(u, i) in K}left(oldsymbol{r}_{mathrm{ui}}-p_{u}^{T} q_{i} ight)^{2} ]

    注:

    • q、p 为两个多维且维数相等的隐向量,
    • K为所有用户评分样本的集合,
    • u、i表示枚举下标,
    • r表示真实的用户-物品评分矩阵(待分解的矩阵。)
    1. 有了目标函数,我们就可以使用梯度下降算法来降低损失;那么我们就需要对目标函数进行偏导,得到梯度下降的公式: ((mu)表示学习率,用于控制梯度下降的步长)

    [p_{u, k}=p_{u,k}-eta (-e_{ui}q_{k,i})=p_{u,k}+eta e_{ui}q_{k,i} \ q_{k, i}=q_{k, i}-eta (-e_{ui}p_{u,k})=q_{k, i}+eta e_{ui}p_{u,k} ]

    1. 在目标函数上面加上正则化的损失,避免过拟合, 就变成了RSVD
    2. Netfix Prize中提出了另一种LFM, 在原来的基础上加了偏置项, 来消除用户和物品打分的偏差, 即预测公式如下:

    [hat{r}_{u i}=mu+b_{u}+b_{i}+p_{u}^{T} cdot q_{i} ]

    这个预测公式,加入了3项转置(mu,b_u,b_i), 作用如下:

    • (mu): 表示训练集中所有记录的评分的全局平均数; 表示网站本身对用户评分的影响.
    • (b_u): 用户偏差系数,可以使用用户(u)给出的全部的评分的均值,来表示该用户的评分习惯高低.
    • (b_i): 物品偏差系数,可以使用物品(i)给出的全部的评分的均值,来表示该物品的评分习惯高低.
      加入以上的用户和物品的打分偏差后,矩阵分解得到的隐向量更能反映不同用户对不同物品的真实的态度差异,也就更加容易捕捉到数据中有价值的信息,从而避免推荐结果偏差.

    此时如果把(b_u)(b_i)当做训练参数的话, 那么它俩的梯度是:

    [frac{partial}{partial b_{u}} S S E=-e_{u i}+lambda b_{u} \ frac{partial}{partial b_{i}} S S E=-e_{u i}+lambda b_{i} ]

    更新公式为:

    [egin{aligned} oldsymbol{b}_{u}&=oldsymbol{b}_{oldsymbol{u}}+oldsymbol{eta}left(oldsymbol{e}_{u i}-lambda oldsymbol{b}_{oldsymbol{u}} ight) \ oldsymbol{b}_{oldsymbol{i}} &=oldsymbol{b}_{oldsymbol{i}}+oldsymbol{eta}left(oldsymbol{e}_{oldsymbol{u} i}-lambda oldsymbol{b}_{oldsymbol{i}} ight) end{aligned} ]

    而对于(p_{u,k})(p_{k,i}),导数没有变化,更新公式也没有变化。

    6. 编程实现

    举个例子,任务就是根据这个评分矩阵, 猜测Alice对物品5的打分。

    在这里插入图片描述

    使用带有偏置项和正则项的那个SVD算法:

    # -*- coding: utf-8 -*-
    import math
    import random
    
    class SVD():
        def __init__(self, rating_data, F=5, alpha=0.1, lmbda=0.1, max_iter=100):
            self.F = F           # 这个表示隐向量的维度
            self.P = dict()          #  用户矩阵P  大小是[users_num, F]
            self.Q = dict()     # 物品矩阵Q  大小是[item_nums, F]
            self.bu = dict()   # 用户偏差系数
            self.bi = dict()    # 物品偏差系数
            self.mu = 0.0        # 全局偏差系数
            self.alpha = alpha   # 学习率
            self.lmbda = lmbda    # 正则项系数 (截距等)
            self.max_iter = max_iter    # 最大迭代次数
            self.rating_data = rating_data # 评分矩阵
    
            # 初始化矩阵P和Q, 方法很多, 一般用随机数填充, 但随机数大小有讲究, 根据经验, 随机数需要和1/sqrt(F)成正比
            cnt = 0    # 统计总的打分数, 初始化mu用
            for user, items in self.rating_data.items():
                self.P[user] = [random.random() / math.sqrt(self.F)  for x in range(0, F)]
                self.bu[user] = 0
                cnt += len(items)
                for item, rating in items.items():
                    if item not in self.Q:
                        self.Q[item] = [random.random() / math.sqrt(self.F) for x in range(0, F)]
                        self.bi[item] = 0
            self.mu /= cnt
    
        # 有了矩阵之后, 就可以进行训练, 这里使用随机梯度下降的方式训练参数P和Q
        def train(self):
            for step in range(self.max_iter):
                for user, items in self.rating_data.items():
                    for item, rui in items.items():
                        rhat_ui = self.predict(user, item)   # 得到预测评分
                        # 计算误差
                        e_ui = rui - rhat_ui
    
                        self.bu[user] += self.alpha * (e_ui - self.lmbda * self.bu[user])
                        self.bi[item] += self.alpha * (e_ui - self.lmbda * self.bi[item])
                        # 随机梯度下降更新梯度
                        for k in range(0, self.F):
                            self.P[user][k] += self.alpha * (e_ui*self.Q[item][k] - self.lmbda * self.P[user][k])
                            self.Q[item][k] += self.alpha * (e_ui*self.P[user][k] - self.lmbda * self.Q[item][k])
    
                self.alpha *= 0.1    # 每次迭代步长要逐步缩小
    
        # 预测user对item的评分, 这里没有使用向量的形式
        def predict(self, user, item):
            return sum(self.P[user][f] * self.Q[item][f] for f in range(0, self.F)) + self.bu[user] + self.bi[item] + self.mu
    
    # 定义数据集, 也就是那个表格, 注意这里我们采用字典存放数据, 因为实际情况中数据是非常稀疏的, 很少有情况是现在这样
    def loadData():
        rating_data={1: {'A': 5, 'B': 3, 'C': 4, 'D': 4},
                     2: {'A': 3, 'B': 1, 'C': 2, 'D': 3, 'E': 3},
                     3: {'A': 4, 'B': 3, 'C': 4, 'D': 3, 'E': 5},
                     4: {'A': 3, 'B': 3, 'C': 1, 'D': 5, 'E': 4},
                     5: {'A': 1, 'B': 5, 'C': 5, 'D': 2, 'E': 1}
                     }
        return rating_data
    
    if __name__ == '__main__':
        # 接下来就是训练和预测
        rating_data = loadData()
        basicsvd = SVD(rating_data, F=10)
        basicsvd.train()
        for item in ['E']:
            print(item, basicsvd.predict(1, item))
            # E 3.0170498955524176
    

    7. 课后思考

    1. 矩阵分解算法后续有哪些改进呢?针对这些改进,是为了解决什么的问题呢?
      RSVD,消除用户和物品打分偏差等。
    2. 矩阵分解的优缺点分析
      • 优点:
        • 泛化能力强: 一定程度上解决了稀疏问题
        • 空间复杂度低: 由于用户和物品都用隐向量的形式存放, 少了用户和物品相似度矩阵, 空间复杂度由n^2降到了(n+m)*f
        • 更好的扩展性和灵活性:矩阵分解的最终产物是用户和物品隐向量, 这个深度学习的embedding思想不谋而合, 因此矩阵分解的结果非常便于与其他特征进行组合和拼接, 并可以与深度学习无缝结合。

    但是, 矩阵分解算法依然是只用到了评分矩阵, 没有考虑到用户特征, 物品特征和上下文特征, 这使得矩阵分解丧失了利用很多有效信息的机会, 同时在缺乏用户历史行为的时候, 无法进行有效的推荐。 所以为了解决这个问题, 逻辑回归模型及后续的因子分解机模型, 凭借其天然的融合不同特征的能力, 逐渐在推荐系统领域得到了更广泛的应用。

    8. 参考资料

    • Datawhale 推荐系统 github手册
    • 王喆 - 《深度学习推荐系统》
    • 项亮 - 《推荐系统实战》
    • 奇异值分解(SVD)的原理详解及推导
    • Matrix factorization techniques for recommender systems论文
    • 隐语义模型(LFM)和矩阵分解(MF)
    你不逼自己一把,你永远都不知道自己有多优秀!只有经历了一些事,你才会懂得好好珍惜眼前的时光!
  • 相关阅读:
    oracle单表选择率(selectivity)——计算执行计划的基数
    不该建索引及不走索引的原因
    SQL语言:DDL/DML/DQL/DCL
    HDU 4521 间隔》=1的LIS 线段树+dp
    九度OnlineJudge之1032:ZOJ
    FileUpload的使用案例
    【C++第三课】---新的关键字
    Clash of Clans(COC)资源压缩解密
    jquery第一期:运行第一个jquery
    Java 的zip压缩和解压缩
  • 原文地址:https://www.cnblogs.com/zhazhaacmer/p/13882096.html
Copyright © 2020-2023  润新知