推荐系统(2)—LFM(Latent Factor Model)模型

前面一篇随笔介绍了基于协同过滤的推荐系统的基本思想及其python实现,本文是上一篇的续集。本文先介绍评价推荐系统的离线指标,稍后主要讨论基于矩阵分解的LFM模型。

评价推荐系统的离线指标

1、F值得分

  推荐系统的目的是为客户提供可能喜欢(购买)的产品,但从本质上来说是一个聚类的过程(对客户聚类或者对商品聚类)。对于一个离线的推荐系统来说,为某个客户推荐出的产品我们可以通过某种方式知道是否为该客户喜欢的,也就是说我们可以从该客户处得到标准值。正因为如此,我们就可以借鉴检验分类模型的F值得分来做检验一个推荐系统的效能。
  我们可以通过将单个用户的准确率(或召回率)做累加,即可得到整个推荐系统的准确率(或召回率),如下:$$Precssion\left ( u \right )= \frac{\sum_{u\in U} R\left ( u \right )\bigcap T\left ( u \right )}{\sum _{u\in U}R\left ( u \right )}$$  

\[Recall\left ( u \right )= \frac{\sum_{u\in U} R\left ( u \right )\bigcap T\left ( u \right )}{\sum _{u\in U}T\left ( u \right )} \]

其中\(R(u)\)是为用户u做出的推荐列表,而\(T(u)\)是用户在测试集上的真正的行为列表,通过如下公式可以计算出整个推荐系统的效能:  

\[F_{\beta }= \frac{\left ( 1+\beta \right )^{2}Recall\cdot Precision}{\beta ^{2}\cdot Recall+Precision}$$  F得分取值范围为[0,1]之间,即权衡了准确率又兼顾了召回率,有了F得分就可以评价推荐系统的效能,尤其在多个推荐系统之间做比较时就能评判出推荐系统的优劣 ### 2、覆盖率   覆盖率是指推荐系统对所有的用户推荐的所有产品列表中,出现的产品与数据库中在销售的所有产品的占比(不考虑每个商品被推荐的次数)。在一个在线购物网站,我们希望推荐系统推荐的商品列表尽可能多地包含数据库中的在售商品,如果一个推荐系统的所有推荐商品列表都未出现某种商品,那么这种商品的销量将会越来越少,这是在线购物网站不愿意看到的。因此,我们希望提高覆盖率,覆盖率的计算公式如下:$$Coverage=\frac{\left |\cup _{u\in U} R\left ( u \right ) \right |}{\left | I \right |}$$其中I表示在售商品的总数     在考虑每个商品推荐次数的情况下,我们可以通过计算每个商品出现的频率,用信息论中的熵或者基尼系数来度量一个推荐系统的覆盖率:$$H=-\sum _{i=1}^{n}p_{i}\ln p_{i}$$或 $$Gini=\sum _{i=1}^{n}p_{i}\left ( 1-p_{i} \right )\]

3、多样性与惊喜度

  多样性指一个推荐系统根据用户购买行为为用户推荐的商品的种类情况,如果一个用户购买了某种商品(青岛啤酒),推荐系统老是为该用户推荐其他厂商的啤酒,那么我们说这个推荐系统推荐的产品不具有多样性(不是一个好的推荐系统),一个好的推荐系统可能会想到为用户推荐购买产品相近且不同类别的产品,如可以推荐一个开瓶器等。  
 与多样性相对应的另外一个指标是惊喜度,惊喜度来自于和客户喜欢的物品不相似,但是客户却觉得满意(会购买)的推荐,比如上面说的为买啤酒的客户推荐一个尿布等。  
上面几个指标是作为推荐系统效能的离线指标,一般指标值都达到最好时该推荐系统效果最好,但是一般情况下不可能都达到最好,因此在评价多个推荐系统效能时得综合考虑这几个指标,选择较好的部署。

基于隐变量的推荐(Latent Factor Model)

  假设已经有n个人对m个商品进行评价,其评价矩阵\(A_{mxn}\)反应了每个人对每个商品的喜好程度(行为商品,列为用户)。一般来说,一个人对商品或者某种事物的直观感受会受到该商品或事物属性值的影响,比如一个单身狗最近打算找一个对象,在众多的女生中她选中了女生D,而他本人喜欢女生的类型具有漂亮的脸蛋、火辣的身材,白皙的皮肤等,这些特点就是他对美眉特征的一种偏好,而每个女生在脸蛋、身材、皮肤等特征上都有一定值,对于一个理性的单身狗来说,他选择的D美眉一定是一个在他欣赏的属性中具有较高值的女生,而在商品推荐中也是如此。虽然我们从矩阵A中只看到了每个用户对每个商品的评价,但是我们可以认为这个评价值是用户对商品的属性值的偏爱程度与每个商品在这些属性上的表现综合结果,这里的属性就是我们所说的隐变量。基于这样的思想,我们可以将矩阵A分解成为“商品—商品属性”的评价矩阵\(U_{mxk}\)与“商品属性—客户喜好”矩阵\(V_{kxn}\)的乘积,及:$$A_{m\times n}=U_{m\times k} V_{K\times n}$$  
这里的k可以看做是分解的商品的隐属性个数,举个栗子,假设有Ben、Tom、John、Fred对6种商品的评价情况如下,评分越高代表对该商品越喜欢(0表示未评价)

Ben Tom John Fred
product1 5 5 0 5
product2 5 0 3 4
product3 3 4 0 3
product4 0 0 5 3
product5 5 4 4 5
product6 5 4 5 5
     转化为矩阵A为:$$A=\begin{bmatrix} 5 &5 &0 &5 \\ 5 &0 &3 &4 \\ 3 &4 &0 &3 \\ 0 &0 &5 &3 \\ 5 &4 &4 &5 \\ 5 &4 &5 &5 \end{bmatrix}$$

现在,我们的目的是要将矩阵A分解成为两个矩阵的乘积。在整个过程中有三个问题需要考虑。第一,采用什么样的分解方法,怎样使得分解后的矩阵乘积尽量逼进矩阵A;第二,用几个因变量来分解A矩阵合适,即分解后的矩阵U的列是多少合适;第三,对于一个客户,怎么通过矩阵分解后的矩阵为他提供推荐。  
  下面先来说说第一个问题,要使得矩阵A能分解成U与V的乘积,也就是说对于A矩阵中的所有元素,要与矩阵U与矩阵V的乘积对应元素差距尽可能小,这种想法我们可以借鉴一下做回归分析时求回归系数的训练方法,及构造一个损失函数,对损失函数采用梯度下降的方式求得分解矩阵元素值。对于K个因变量构造一个损失函数:$$J\left ( U,V;A \right )=\sum {i=1}^{m}\sum {j=1}^{n}\left ( a-\sum {r=1}^{k}u\cdot v \right )^{2}+\lambda \left ( \sum _{i=1}^{m} \sum _{r=1}{k}u_{ir}+\sum _{j=1}^{n}\sum _{r=1}{k}v_{r=1}\right )$$损失函数的右边部分是L2正则化项(对应于redge回归的正则化),可以降低解决过拟合问题导致分解后的矩阵元素太大,对损失函数求梯度:  

\[\left\{\begin{matrix} \frac{\partial J\left ( U,V;A \right )}{\partial u_{ir}}=-2\left ( a_{ij}-\sum _{r=1}^{k}u_{ir}v_{rj}\right )\cdot v_{rj}+2\lambda u_{ir}\\ \frac{\partial J\left ( U,V;A \right )}{\partial v_{rj}}=-2\left ( a_{ij}-\sum _{r=1}^{k}u_{ir}v_{rj}\right )\cdot u_{ir}+2\lambda v_{rj} \end{matrix}\right.,1\leq r\leqslant k\]

  有了梯度,我们就可以先随机给定“商品—商品属性”矩阵U和“商品属性—客户喜好”矩阵V一些初始值,然后对损失函数通过SGD方式得到,代码如下:

import numpy as np
import math

def lfm(a,k):
    '''
    参数a:表示需要分解的评价矩阵
    参数k:分解的属性(隐变量)个数
    '''
    assert type(a) == np.ndarray
    m, n = a.shape
    alpha = 0.01
    lambda_ = 0.01
    u = np.random.rand(m,k)
    v = np.random.randn(k,n)
    for t in range(1000):
        for i in range(m):
            for j in range(n):
                if math.fabs(a[i][j]) > 1e-4:
                    err = a[i][j] - np.dot(u[i],v[:,j])
                    for r in range(k):
                        gu = err * v[r][j] - lambda_ * u[i][r]
                        gv = err * u[i][r] - lambda_ * v[r][j]
                        u[i][r] += alpha * gu
                        v[r][j] += alpha * gv
    return u,v    
#对前面提到的评价矩阵A作分解,先选择三个隐变量(k=3)
A = np.array([[5,5,0,5],[5,0,3,4],[3,4,0,3],[0,0,5,3],[5,4,4,5],[5,4,5,5]])
b,c = lfm(A,3)

#查看“商品—商品属性”矩阵b和“商品属性—客户喜好”矩阵c
b,c
(array([[-0.2972852 ,  2.01461188,  1.0310134 ],
        [-0.41028699,  0.88314041,  1.70740435],
        [-0.69015017,  1.4846616 ,  0.22246443],
        [ 1.58422492,  1.15064457,  0.98881608],
        [-0.13167643,  1.63106585,  1.39457599],
        [ 0.36301204,  1.78414273,  1.44277207]]),
 array([[-0.81521492, -0.8423817 ,  1.25938865, -0.23881637],
        [ 1.32574586,  2.24986472,  1.49232807,  1.71803   ],
        [ 2.00802709,  0.18698412,  1.27586124,  1.42864871]]))
#查看b与c乘积
np.dot(b,c)
array([[ 4.98351751,  4.97581494,  3.94749428,  5.00511619],
       [ 4.933806  ,  2.65182221,  2.97963547,  4.054526  ],
       [ 2.97761928,  3.96325495,  1.63026864,  3.03333586],
       [ 2.21954795,  1.43916544,  4.97388618,  3.01117386],
       [ 5.07006975,  4.0413629 ,  4.047539  ,  4.82602576],
       [ 4.9665124 ,  3.97806056,  4.96047648,  5.03973199]])

  我们看到分解后的两个矩阵乘积可以大致还原矩阵A,而且还原后的矩阵对原来用户没有评价的商品已经有了评价值,我们或许可以相信,该值即为用户对该种商品的评价值的预测值(Tom对产品2的评价可能为2.7,John对产品1的评价可能为5.9),这就回答了上面提到的第三个问题。
  上面的第一个问题与第三个问题已经解决,而且真的按照我们的思路将矩阵A分解成了两个矩阵的乘积。我们再看看第二个问题,我们分解出几个商品属性(隐变量)合适呢?这个问题我确实没有在文献上的看到有较完整的说明,或许可以从实验的角度多取几个K值来人为地检测其效果,比如本例,如果知道这几个人的喜好,然后取不同的K值来检验结果。
  LFM仅矩阵分解中的一种方式,下一篇我们再讨论另一种特别有名气的分解—SVD分解。


posted @ 2017-03-12 21:39  雁渡的寒潭  阅读(11993)  评论(1编辑  收藏  举报