推荐场景下,矩阵分解方法:SVD、FunkSVD、BiasSVD、SVD++、ALS。
本篇内容主要介绍SVD,包含:EVD、SVD、SVD在推荐中的应用demo;
矩阵分解模型在推荐系统中有非常不错的表现,相对于传统的协同过滤方法,它不仅能通过降维增加模型的泛化能力,也方便加入其他因素(如数据偏差、时间、隐反馈等)对问题建模,从而产生更佳的推荐结果。
先来说说矩阵分解几个明显的特点,它具有协同过滤的 “集体智慧”,隐语义的 “深层关系”,以及机器学习的 “以目标为导向的有监督学习”。在了解了基于邻域的协同过滤算法后,集体智慧自不必多说,我们依次从 “隐因子” 和 “有监督学习” 的角度来了解矩阵分解的基本思路。
基于矩阵分解的推荐算法的核心假设是用隐语义(隐变量)来表达用户和物品,他们的乘积关系就成为了原始的元素。这种假设之所以成立,是因为我们认为实际的交互数据是由一系列的隐变量的影响下产生的(通常隐变量带有统计分布的假设,就是隐变量之间,或者隐变量和显式变量之间的关系,我们往往认为是由某种分布产生的。),这些隐变量代表了用户和物品一部分共有的特征,在物品身上表现为属性特征,在用户身上表现为偏好特征,只不过这些因子并不具有实际意义,也不一定具有非常好的可解释性,每一个维度也没有确定的标签名字,所以才会叫做 “隐变量”。而矩阵分解后得到的两个包含隐变量的小矩阵,一个代表用户的隐含特征,一个代表物品的隐含特征,矩阵的元素值代表着相应用户或物品对各项隐因子的符合程度,有正面的也有负面的。
随着人们的不断探索和研究,衍生出了矩阵分解的一系列算法,接下来的几篇文章,分别讲讲矩阵分解的几种方法。
对于奇异值,它跟我们特征分解中的特征值类似,在奇异值矩阵中也是按照从大到小排列,而且奇异值的减少特别的快,在很多情况下,前10%甚至1%的奇异值的和就占了全部的奇异值之和的99%以上的比例。也就是说,我们也可以用最大的K个的奇异值和对应的左右奇异向量来近似描述矩阵。其中K要比n小很多,也就是一个大的矩阵A可以用三个小的矩阵来表示。数学之美中有说,会减少很大的存储资源。
多启发式的算法,当然,最直接的是直接用肉眼观察。除此之外,一个典型做法是保留矩阵中90%的能量信息。具体来讲,我们可以对奇异值求平方和。于是可以对奇异值的平方和累加直至总和的90%为止。
在讨论SVD之前先讨论矩阵的特征值分解(EVD)(eigenvalue decomposition),对称阵有一个很优美的性质:它总能相似对角化,对称阵特征值对应的特征向量两两正交。
其中A的对称矩阵,D是对角矩阵,对角元素是A的特征值(几何意义:变换时的缩放),V的列是A的特征向量(几何意义:特征向量经过矩阵A的变换,只进行缩放(特征值的大小),不改变其方向),特征向量两两正交,可以理解为一个高维的空间。
我们看看上面的公式怎么得来的:
设特征值为,单位特征向量为x,那么对于所有的特征值和单位特征向量都有:,写成矩阵的形式:,所以可得到A的特征值分解(由于对称阵特征向量两两正交,所以U为正交阵,正交阵的逆矩阵等于其转置):
A不是满秩的话,那么就是说对角阵的对角线上元素存在0,这时候就会导致维度退化,这样就会使映射后的向量落入m维空间的子空间中。
svd是上面这个式子的演化,放松了对矩阵A的要求,不要求A为对称矩阵。
(在svd中,有左右两个矩阵,那么带有隐语义信息的向量使用哪一个矩阵?这两个矩阵分别代表什么?user和item表示有差别吗?)
上面的特征值分解的A矩阵是对称阵,根据EVD可以找到一个(超)矩形使得变换后还是(超)矩形,也即A可以将一组正交基映射到另一组正交基!那么现在来分析:对任意M*N的矩阵,能否找到一组正交基使得经过它变换后还是正交基?答案是肯定的,它就是SVD分解的精髓所在。
现在假设存在M*N矩阵A,事实上,A矩阵将n维空间中的向量映射到k(k<=m)维空间中,k=Rank(A)。现在的目标就是:在n维空间中找一组正交基,使得经过A变换后还是正交的。
如果A的特征值中,部分为0,形如下面:
可以得到A矩阵的奇异值分解,如下所示:
其中A就是被分解的矩阵,A矩阵是m* n维的,U是一个正交矩阵m * m维的,是一个对角矩阵,m* n维,V也是一个正交矩阵n* n维的。(几何角度理解,实质是一个矩阵变换的操作,U中的列向量原始空间中的坐标轴向量,通过变换,将其变换到V空间中,变换的尺度是,中的元素大小,表示V空间中,对应列向量的重要程度。)
对应到推荐场景中,分解得到的三个矩阵含义:
U:user的语义向量,每一行代表一个用户向量。
:表示user和item的相关性。
V:item的语义向量,每一列代表一个item向量。
奇异值
中的对角元素称为奇异值,并且按照从大到小排列,U矩阵的列称为矩阵A的左奇异向量(left singular vectors),V中的列称为矩阵A的右奇异向量(right singular vectors)。一定要区别异常值和奇异值,概念上貌似差不多,但是意义上相差万里。
1)EVD针对对角化矩阵而言,而SVD更加通用,对于任意矩阵m*n,都可以进行分解。
2)矩阵乘法对应了一个变换,一个矩阵乘以一个向量后得到新的向量,相当于这个向量变成了另一个方向或者长度都不同的新向量。如果一个矩阵与某一个向量或者多个向量相乘,该向量只发生了缩放变换,不对该向量产生旋转的效果,则称该向量为这个矩阵的特征向量,伸缩比例是特征值。而奇异值分解的使用范围更广泛,将M从V向量空间旋转到U空间(M右边乘以了V矩阵,也就是从V空间做变换,旋转缩放到U空间),不仅仅发生了缩放,还有旋转,旋转的时候,空间维度会发生变化,从m变化到n。可以认为这是EVD和SVD的本质区别。
用途(为什么要做矩阵分解)
1)SVD可用于矩阵降维。我们可以利用SVD来逼近矩阵并从中提取重要的特征。再通过保留矩阵80%-90%的能量,又或者只保留前2千-3千的奇异值(当奇异值上万时),来实现得到重要的特征和去除其余噪声。
2)降噪。多应用在图像领域。可以看[3]中的例子。
3)数据分析。我们搜集的数据中总是存在噪声:无论采用的设备多精密,方法有多好,总是会存在一些误差的。如果你们还记得上文提到的,大的奇异值对应了矩阵中的主要信息的话,运用SVD进行数据分析,提取其中的主要部分的话,还是相当合理的。比如得到两个奇异值σ1 = 6.04,σ2 = 0.22,由于第一个奇异值远比第二个要大,数据中有包含一些噪声,第二个奇异值在原始矩阵分解相对应的部分可以忽略。经过SVD分解后,保留了主要样本点。就保留主要样本数据来看,该过程跟PCA( principal component analysis)技术有一些联系,PCA也使用了SVD去检测数据间依赖和冗余信息.
4)用于推荐领域。
数据集中行代表用户user,列代表物品item,其中的值代表用户对物品的打分。基于SVD的优势在于:用户的评分数据是稀疏矩阵,可以用SVD将原始数据映射到低维空间中,然后计算物品item之间的相似度,可以节省计算资源。
整体思路:先找到用户没有评分的物品,然后再经过SVD“压缩”后的低维空间中,计算未评分物品与其他物品的相似性,得到一个预测打分,再对这些物品的评分从高到低进行排序,返回前N个物品推荐给用户。
主要分为5步骤:
第1部分:加载测试数据集;
第2部分:定义三种计算相似度的方法;
第3部分:通过计算奇异值平方和的百分比来确定将数据降到多少维才合适,返回需要降到的维度;
第4部分:在已经降维的数据中,基于SVD对用户未打分的物品进行评分预测,返回未打分物品的预测评分值;
第5部分:产生前N个评分值高的物品,返回物品编号以及预测评分值。
说说第3部分:很多时候,维度是随便定义,这里给出了确定K的理论依据,如果你看到这篇博客,牢记确定K的依据在此。维度越大包含的信息越多,资源和计算时间也耗费比较多;维度较小,丢失一部分信息,信息更加精炼,会去掉部分噪声信息,资源和时间耗费较少,这里确定时要有一个取舍了。(现在存在一个问题,MF(ALS)中的K是怎么确定的呐?依据是什么?依稀记得,论文中没有给出精确的确定方法,只说可大可小。工作的时候,落地了MF算法,确定维度也是很随意,根据公司组件能容纳接受的最大维度来确定K值,现在思考一下,感觉缺失理论依据。把第3部分的内容拿出来作为理论依据,或许更靠谱)
SVD的优缺点
优点:简化数据,去除噪声,提高算法的结果。
缺点:数据的转换可能难以理解。
适用数据类型:数值型数据。
关系式的右边描述了关系式左边的特征值分解。于是:
V的列向量(右奇异向量)是MTM的特征向量。
U 的列向量(左奇异向量)是MMT 的特征向量。
Σ的非零对角元(非零奇异值)是MTM或者MMT的非零特征值的平方根。
数学之美中p168页面:
目的:由一个稀疏的评分矩阵推算出其中的空缺分数。举个例子,在电影评分中,我们的目的是得到用户对未评价电影的分数,而这种user-Item的评分矩阵中,用户对Item的分数是没有直接关系的,我们需要寻找一种隐空间,使得将用户和Item联系起来。比如我们无法得知user1对《利刃出鞘》这部电影的分数,但是我们可以通过用户的历史信息(即用户对别的电影的分数)得到用户对“悬疑”类电影的爱好程度,我们也可以得到某个电影的“悬疑类”的程度有多大,这样,我们就可以将这俩个关系联系到一起了。
一句话:通过用户对电影A的有评分,对B没有评分,那么用户喜欢B吗?从SVD得到的item向量中,通过计算A与B的相似度,能得到用户对B的喜欢程度。代码:ratSimTotal+=similarity*userRating,ratSimTotal表示对B的评分。
#coding=utf-8
from numpy import *
from numpy import linalg as la
'''加载测试数据集'''
def loadExData():
return mat([[0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 5],
[0, 0, 0, 3, 0, 4, 0, 0, 0, 0, 3],
[0, 0, 0, 0, 4, 0, 0, 1, 0, 4, 0],
[3, 3, 4, 0, 0, 0, 0, 2, 2, 0, 0],
[5, 4, 5, 0, 0, 0, 0, 5, 5, 0, 0],
[0, 0, 0, 0, 5, 0, 1, 0, 0, 5, 0],
[4, 3, 4, 0, 0, 0, 0, 5, 5, 0, 1],
[0, 0, 0, 4, 0, 4, 0, 0, 0, 0, 4],
[0, 0, 0, 2, 0, 2, 5, 0, 0, 1, 2],
[0, 0, 0, 0, 5, 0, 0, 0, 0, 4, 0],
[1, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0]])
'''以下是三种计算相似度的算法,分别是欧式距离、皮尔逊相关系数和余弦相似度,
注意三种计算方式的参数inA和inB都是列向量'''
def ecludSim(inA,inB):
return 1.0/(1.0+la.norm(inA-inB)) #范数的计算方法linalg.norm(),这里的1/(1+距离)表示将相似度的范围放在0与1之间
def pearsSim(inA,inB):
if len(inA)<3: return 1.0
return 0.5+0.5*corrcoef(inA,inB,rowvar=0)[0][1] #皮尔逊相关系数的计算方法corrcoef(),参数rowvar=0表示对列求相似度,这里的0.5+0.5*corrcoef()是为了将范围归一化放到0和1之间
def cosSim(inA,inB):
num=float(inA.T*inB)
denom=la.norm(inA)*la.norm(inB)
return 0.5+0.5*(num/denom) #将相似度归一到0与1之间
'''按照前k个奇异值的平方和占总奇异值的平方和的百分比percentage来确定k的值,
后续计算SVD时需要将原始矩阵转换到k维空间'''
def sigmaPct(sigma,percentage):
sigma2=sigma**2 #对sigma求平方
sumsgm2=sum(sigma2) #求所有奇异值sigma的平方和
sumsgm3=0 #sumsgm3是前k个奇异值的平方和
k=0
for i in sigma:
sumsgm3+=i**2
k+=1
if sumsgm3>=sumsgm2*percentage:
return k
'''函数svdEst()的参数包含:数据矩阵、用户编号、物品编号和奇异值占比的阈值,
数据矩阵的行对应用户,列对应物品,函数的作用是基于item的相似性对用户未评过分的物品进行预测评分'''
def svdEst(dataMat,user,simMeas,item,percentage):
n=shape(dataMat)[1]
simTotal=0.0;ratSimTotal=0.0
u,sigma,vt=la.svd(dataMat)
k=sigmaPct(sigma,percentage) #确定了k的值
sigmaK=mat(eye(k)*sigma[:k]) #构建对角矩阵
xformedItems=dataMat.T*u[:,:k]*sigmaK.I #根据k的值将原始数据转换到k维空间(低维),xformedItems表示物品(item)在k维空间转换后的值
for j in range(n):
userRating=dataMat[user,j]
if userRating==0 or j==item:continue
similarity=simMeas(xformedItems[item,:].T,xformedItems[j,:].T) #计算物品item与物品j之间的相似度
simTotal+=similarity #对所有相似度求和
ratSimTotal+=similarity*userRating #用"物品item和物品j的相似度"乘以"用户对物品j的评分",并求和
if simTotal==0:return 0
else:return ratSimTotal/simTotal #得到对物品item的预测评分
'''函数recommend()产生预测评分最高的N个推荐结果,默认返回5个;
参数包括:数据矩阵、用户编号、相似度衡量的方法、预测评分的方法、以及奇异值占比的阈值;
数据矩阵的行对应用户,列对应物品,函数的作用是基于item的相似性对用户未评过分的物品进行预测评分;
相似度衡量的方法默认用余弦相似度'''
def recommend(dataMat,user,N=5,simMeas=cosSim,estMethod=svdEst,percentage=0.9):
unratedItems=nonzero(dataMat[user,:].A==0)[1] #建立一个用户未评分item的列表
if len(unratedItems)==0:return 'you rated everything' #如果都已经评过分,则退出
itemScores=[]
for item in unratedItems: #对于每个未评分的item,都计算其预测评分
estimatedScore=estMethod(dataMat,user,simMeas,item,percentage)
itemScores.append((item,estimatedScore))
itemScores=sorted(itemScores,key=lambda x:x[1],reverse=True)#按照item的得分进行从大到小排序
return itemScores[:N] #返回前N大评分值的item名,及其预测评分值
将文件命名为svd2.py,在python提示符下输入:
>>>import svd2
>>>testdata=svd2.loadExData()
>>>svd2.recommend(testdata,1,N=3,percentage=0.8)#对编号为1的用户推荐评分较高的3件商品
最后解析下怎么生成item的k维向量:
xformedItems=dataMat.T*u[:,:k]*sigmaK.I
假设:dataMat=[m,n],这里的m表示用户的维度,n表示item维度;他的转置data.Mat.T=[n,m],u= [m,k],sigmaK = [k,n],这三个矩阵相乘得到xformaedItems=[k, n],其中每一列表示一个商品的向量,这里k <<< m。
其他:
这里使用到得SVD的思想只是说任何一个矩阵均可以分解成两个矩阵相乘的形式(SVD是三个,但也可以合并俩个得到两个矩阵,但是合并之后的意义,目前我还不是很理解,边学习边记录~~),这两个子矩阵分别代表了用户的偏好程度和电影的成分程度,将这两个矩阵相乘即可得到用户对电影的评分矩阵。
参考:
1.矩阵分解模型https://iamhere1.github.io/2018/01/03/mf/
2.卢神的博客 https://lumingdong.cn/recommendation-algorithm-based-on-matrix-decomposition.html
3.从集合角度理解svd http://blog.chinaunix.net/uid-20761674-id-4040274.html
4.论文 http://www-users.math.umn.edu/~lerman/math5467/svd.pdf
5.EVD&SVDhttps://blog.csdn.net/zhongkejingwang/article/details/43053513
6.https://www.jianshu.com/p/9613b26c8b03
7.论文 Matrix Factorization Techniques for Recommender Systems