SVD++推荐系统

原文地址:https://blog.csdn.net/turing365/article/details/80544594

 

原理
我们把用户的评分行为表示成一个评分矩阵RR,其中R[u][i]R[u][i]就是用户uu对物品ii的评分。但用户不会对所有的物品评分,所以这个矩阵里有很多元素都是空的

因此,评分预测从某种意义上说就是对这些元素填空,如果一个用户对一个物品没有评过分,那么推荐系统就要预测这个用户是否是否会对这个物品评分以及会评几分。

所以我们需要寻找一种对矩阵扰动小的补全矩阵的方法。一般认为,如果 补全后矩阵的特征值和补全之前矩阵的特征值相差不大,就算是扰动比较小。所以,早期的矩阵分解模型就是从数学上的SVD(奇异值分解)开始的。但奇异值分解由于计算复杂度很高,所以很难在实际系统上有所应用。

在多次探索之后,我们选择了Simon Funk提出的的SVD分解法(该方法简称为LFM)。其原理简要如下:

从矩阵分解的角度说,如果我们将评分矩阵RR分解为两个低维矩阵相乘: 
R^=PTQ
R^=PTQ
其中P∈Rf∗mP∈Rf∗m,Q∈Rf∗nQ∈Rf∗n是两个降维后的矩阵。则对用户uu对物品ii的评价预测值R^(u,i)=r^uiR^(u,i)=r^ui,可通过以下公式计算: 
r^ui=∑fpufqtf
r^ui=∑fpufqtf

其中puf=P(u,f)puf=P(u,f),qif=Q(i,f)qif=Q(i,f),则:可以直接通过训练集中 的观察值利用小化RMSE学习PP、QQ矩阵。
既然我用RMSE作为评测指标,那么如果能找到合适的PP、QQ来小化训练集的预测误差,那么应该也能小化测试集的预测误差。因此,我们定义损失函数为: 
C(p,q)=∑(u,i)∈Train(rui−r^ui)2=∑(u,i)∈Train(rui−∑f=1Fpufqif)2
C(p,q)=∑(u,i)∈Train(rui−r^ui)2=∑(u,i)∈Train(rui−∑f=1Fpufqif)2
直接优化上面的损失函数可能会导致学习的过拟合,因此还需要加入防止过拟合项λ(||pu||2+||qi||2)λ(||pu||2+||qi||2),其中λλ为正则化参数,从而得到: 
C(p,q)=∑(u,i)∈Train(rui−r^ui)2=∑(u,i)∈Train(rui−∑f=1Fpufqif)2+λ(||pu||2+||qi||2)
C(p,q)=∑(u,i)∈Train(rui−r^ui)2=∑(u,i)∈Train(rui−∑f=1Fpufqif)2+λ(||pu||2+||qi||2)
要最小化上面的损失函数,我们可以利用随机梯度下降法,它首先通过求参数的偏导数找到速下降方向,然后通过迭代法不断地优化参数。

上面定义的损失函数里有两组参数(pufpuf和qifqif),最速下降法需要首先对它们分别求偏导数, 可以得到: 
∂C∂puf=−2qik+2λpuk
∂C∂puf=−2qik+2λpuk
∂C∂pif=−2puk+2λqik
∂C∂pif=−2puk+2λqik
然后,根据随机梯度下降法,需要将参数沿着速下降方向向前推进,因此可以得到如下递推公式:

puf=puf+α(qik−λpuk)
puf=puf+α(qik−λpuk)

qif=qif+α(puk−λpik)
qif=qif+α(puk−λpik)

其中,αα是学习速率,它的取值需要通过反复实验获得。
加入偏置项后的LFM 
上面的LFM预测公式通过隐类将用户和物品联系在了一起。但是,实际情况下,一个评分系统有些 固有属性和用户物品无关,而用户也有些属性和物品无关,物品也有些属性和用户无关。因此, Netflix Prize中提出了另一种LFM,其预测公式如下: 
r^ui=μ+bu+bi+pTu∗qi
r^ui=μ+bu+bi+puT∗qi

这个预测公式中加入了3项μμ、 ubub、ibib 。我们将这个模型称为BiasSVD。这个模型中新增加的三项的作用如下。 
- μμ:训练集中所有记录的评分的全局平均数。它可以表示网站本身对用户评分的影响。 
- bubu: 用户偏置项。这一项表示了用户的评分习惯中和物品没有关系的那种因素。 
- bibi:物品偏置项。这一项表示了物品接受的评分中和用户没有什么关系的因素。
考虑邻域影响的LFM 
前面的LFM模型中并没有显式地考虑用户的历史行为对用户评分预测的影响。为此,Koren 在Netflix Prize比赛中提出了一个模型,将用户历史评分的物品加入到了LFM模型中,Koren将该模型称为SVD++。

我们将将ItemCF的预测算法改成如下方式: 
r^ui=1|N(u)|−−−−−√∑j∈N(u)wij
r^ui=1|N(u)|∑j∈N(u)wij
这里,wijwij不再是根据ItemCF算法计算出的物品相似度矩阵,而是一个和PP、QQ一样的参数,它可以通过优化如下的损失函数进行优化: 
C(w)=∑(u,i)∈Train(rui−∑j∈N(u)wijruj)2+λw2ij
C(w)=∑(u,i)∈Train(rui−∑j∈N(u)wijruj)2+λwij2
不过,这个模型有一个缺点,就是ww将是一个比较稠密的矩阵,存储它需要比较大的空间。此外,如果有nn个物品,那么该模型的参数个数就是n2n2个,这个参数个数比较大,容易造成结果 的过拟合。因此,Koren提出应该对ww矩阵也进行分解,将参数个数降低到2∗n∗F2∗n∗F个,模型如下: 
r^ui=1|N(u)|−−−−−√∑j∈N(u)xTiyj=1|N(u)|−−−−−√xTi∑j∈N(u)yj
r^ui=1|N(u)|∑j∈N(u)xiTyj=1|N(u)|xiT∑j∈N(u)yj
这里xixi、yiyi是两个FF维的向量。由此可见,该模型用xTiyjxiTyj代替了wijwij,从而大大降低了参 数的数量和存储空间。 
再进一步,我们可以将前面的LFM和上面的模型相加,从而得到如下模型: 
r^ui=μ+bu+bi+pTu∗qi+1|N(u)|−−−−−√xTi∑j∈N(u)yj
r^ui=μ+bu+bi+puT∗qi+1|N(u)|xiT∑j∈N(u)yj

Koren又提出,为了不增加太多参数造成过拟合,可以令x=qx=q,从而得到终的SVD++模型: 
r^ui=μ+bu+bi+qTi∗(pu+1|N(u)|−−−−−√xTi∑j∈N(u)yj)
r^ui=μ+bu+bi+qiT∗(pu+1|N(u)|xiT∑j∈N(u)yj)
关键部分代码分析
1.学习LFM模型时的迭代过程
在LearningLFM函数中,输入train是训练集中的用户评分记录,F是隐类的格式,n是迭代次数。

def LearningBiasLFM():
    step=0
    InitBiasLFM(S,F)
    #15为迭代次数
    for step in range(15):
        for message,rui in S.items():
            u=message[0]
            i=message[1]
            pui = Predict(u,i)
            eui=rui-pui
            bu[u]+=alpha*(eui-lambd*bu[u])
            bi[i]+=alpha*(eui-lambd*bi[i])
            pi=p[u]
            qi=q[i]
            p[u]=pi+alpha*(qi*eui-lambd*pi)
            q[i]=qi+alpha*(pi*eui-lambd*qi)
        print(step,getrmse())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
如上面的代码所示,LearningBiasLFM主要包括两步。 
首先,需要对P、Q矩阵进行初始化。 
然后后需要通过随机梯度下降法的迭代得到终的P、Q矩阵。在迭代时,需要在每一步对学习参数αα进行衰减。

2.矩阵初始化
初始化P、Q矩阵的方法很多,一般都是将这两个矩阵用随机数填充,但随机数的大小还是有讲究的,根据经验,随机数需要和1/sqrt(F)1/sqrt(F)成正比。下面的代码实现了初始化功能:

def InitBiasLFM(S,F):
    for message,rui in S.items():
        u=message[0]
        i=message[1]
        bu[u]=0
        bi[i]=0
        if u not in p:
            p[u] =np.array([0.1*random.random()/math.sqrt(F) for k in range(0,F)])
        if i not in q:
            q[i] =np.array([0.1*random.random()/math.sqrt(F) for k in range(0,F)])
1
2
3
4
5
6
7
8
9
10
11
3.预测用户uu对物品ii的评分
def Predict(u,i):
    if u not in bu and i not in bi:
        return mu
    if u not in bu:
        return mu+bi[i]
    if i not in bi:
        return mu+bu[u]
    return mu+bu[u]+bi[i]+np.dot(p[u],q[i])
--------------------- 
版权声明:本文为CSDN博主「turing365」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/turing365/article/details/80544594

你可能感兴趣的:(SVD++推荐系统)