SVD在推荐系统中的应用
参考自:http://www.igvita.com/2007/01/15/svd-recommendation-system-in-ruby/
线性代数相关知识:
任意一个M*N
的矩阵A(M行*N列
,M>N
),可以被写成三个矩阵的乘积:
1. U:(M行M列的列正交矩阵)
2. S:(M*N
的对角线矩阵,矩阵元素非负)
3. V:(N*N
的正交矩阵的倒置)(倒置即转置)
即 A=U*S*V'
(注意矩阵V需要倒置)
直观地说:
假设我们有一个矩阵,该矩阵每一列代表一个user,每一行代表一个item(可理解成电影评分系统)。
如上图,ben,tom….代表user,season n代表item。
矩阵值代表评分(0代表未评分):如 ben对season1评分为5,tom对season1 评分为5,tom对season2未评分。
1.机器学习和信息检索:
机器学习的一个最根本也是最有趣的特性是数据压缩概念的相关性。
如果我们能够从数据中抽取某些有意义的感念,则我们能用更少的比特位来表述这个数据。
从信息论的角度则是数据之间存在相关性,则有可压缩性。
SVD就是用来将一个大的矩阵以降低维数的方式进行有损地压缩。
降维:
下面我们将用一个具体的例子展示svd的具体过程。
首先是A矩阵(6*4):
A = [[5, 5, 0, 5], [5, 0, 3, 4], [3, 4, 0, 3], [0, 0, 5, 3], [5, 4, 4, 5], [5, 4, 5, 5]] u, s, vtranspose = np.linalg.svd(A) print u, s, vtranspose
使用python调用svd函数:
注意svd的输出的结果中v returned by this function is V.H
[[-0.44721867 0.53728743 0.00643789 -0.50369332 -0.38572204 -0.32982993] [-0.35861531 -0.24605053 -0.86223083 -0.14584826 0.07797125 0.20015231] [-0.29246336 0.40329582 0.22754042 -0.10376096 0.4360044 0.70652449] [-0.20779151 -0.67004393 0.3950621 -0.58878098 0.02599042 0.06671744] [-0.50993331 -0.05969518 0.10968053 0.28687443 0.59460659 -0.53714128] [-0.53164501 -0.18870999 0.19141061 0.53413013 -0.54845844 0.24290419]]
[ 17.71392084 6.39167145 3.09796097 1.32897797]
[[-0.57098887 -0.4274751 -0.38459931 -0.58593526] [ 0.22279713 0.51723555 -0.82462029 -0.05319973] [-0.67492385 0.69294472 0.2531966 -0.01403201] [ 0.41086611 0.26374238 0.32859738 -0.80848795]]
分解矩阵之后我们首先需要明白S的意义。
可以看到S很特别,是个对角线矩阵。每个元素非负,而且依次减小,具体要讲明白元素值的意思大概和线性代数的特征向量、特征值有关。
但是可以大致理解如下:在线性空间里,每个向量代表一个方向。
所以特征值是代表该矩阵向着该特征值对应的特征向量的方向的变化权重。
所以可以取S对角线上前k个元素。
当k=2时候即将S(6*4)
降维成S(2*2)
,同时U(6*6)
,V(4*4)
相应地变为U(6*2)
,V(4*2)
.
如下图:
注:上图中V.transpose为V
此时我们用降维后的U,S,V来相乘得到A2:
print u[0:6, 0:2] print np.diag(s[0:2]) print vtranspose[0:2, 0:4] v = vtranspose[0:2, 0:4].T print v
[[-0.44721867 0.53728743] [-0.35861531 -0.24605053] [-0.29246336 0.40329582] [-0.20779151 -0.67004393] [-0.50993331 -0.05969518] [-0.53164501 -0.18870999]] [[ 17.71392084 0. ] [ 0. 6.39167145]] [[-0.57098887 -0.4274751 -0.38459931 -0.58593526] [ 0.22279713 0.51723555 -0.82462029 -0.05319973]] [[-0.57098887 0.22279713] [-0.4274751 0.51723555] [-0.38459931 -0.82462029] [-0.58593526 -0.05319973]]
A2 = np.dot(np.dot(u[0:6, 0:2], np.diag(s[0:2])), vtranspose[0:2, 0:4]) print A2
[[ 5.28849359 5.16272812 0.21491237 4.45908018] [ 3.27680994 1.90208543 3.74001972 3.80580978] [ 3.53241827 3.54790444 -0.13316888 2.89840405] [ 1.14752376 -0.64171368 4.94723586 2.3845504 ] [ 5.07268706 3.66399535 3.78868965 5.31300375] [ 5.10856595 3.40187905 4.6166049 5.58222363]]
此时我们可以很直观地看出,A2和A很接近,这就是之前说的降维可以看成一种数据的有损压缩。
2.接下来我们开始分析该矩阵中数据的相关性。
我们将u的第一列当成x值,第二列当成y值。即u的每一行用一个二维向量表示,同理v的每一行也用一个二维向量表示。
如下图:
从图中可以看出:
Season5,Season6特别靠近。Ben和Fred也特别靠近。
同时我们仔细看一下A矩阵可以发现,A矩阵的第5行向量和第6行向量特别相似,Ben所在的列向量(第1列)和Fred所在的列向量(第4列)也特别相似。
所以从直观上我们发现U矩阵和V矩阵可以近似来代表A矩阵,换句话说就是将A矩阵压缩成U矩阵和V矩阵,至于压缩比例得看当时对S矩阵取前k个数的k值是多少。
寻找相似用户:
我们假设,现在有个名字叫Bob的新用户,并且已知这个用户对season n的评分向量为:[5 5 0 0 0 5]。(此向量为列向量)
我们的任务是要对他做出个性化的推荐。
我们的思路首先是利用新用户的评分向量找出该用户的相似用户。
注:如上图(图中第二行式子有错误,Bob的转置应为行向量)。
对图中公式不做证明,只需要知道结论,结论是得到一个Bob的二维向量,即知道Bob的坐标。
Bob = np.array([5, 5, 0, 0, 0, 5])#此处省略转换,直接初始化为行向量 print Bob Bob2D = np.dot(np.dot(Bob, u[0:6, 0:2]), (np.linalg.inv(np.diag(s[0:2])))) print Bob2D
[-0.37752201 0.08020351]
将Bob坐标添加进原来的图中:
然后从图中找出和Bob最相似的用户。
注:最相似并不是距离最近的用户,这里的相似用余弦相似度计算。(关于相似度还有很多种计算方法,各有优缺点),即夹角与Bob最小的用户坐标。
可以计算出最相似的用户是ben。
3.接下来的推荐策略就完全取决于个人选择了。
这里介绍一个非常简单的推荐策略:找出最相似的用户,即ben。
观察ben的评分向量为:【5 5 3 0 5 5】。
对比Bob的评分向量:【5 5 0 0 0 5】。
然后找出ben评分过而Bob未评分的item并排序,即【season 5:5,season 3:3】。
即推荐给Bob的item依次为 season5 和 season3。
4.总结:
最后还有一些关于整个推荐思路的可改进的地方:
a.svd本身就是时间复杂度高的计算过程,如果数据量大的情况恐怕时间消耗无法忍受。 不过可以使用梯度下降等机器学习的相关方法来进行近似计算,以减少时间消耗。
b.相似度计算方法的选择,有多种相似度计算方法,每种都有对应优缺点,对针对不同场景使用最适合的相似度计算方法。
c.推荐策略:首先是相似用户可以多个,每个由相似度作为权重来共同影响推荐的item的评分。