奇异值分解SVD(Singular Value Decomposition)

来源:互联网 发布:windows 2008 r2 ad域 编辑:程序博客网 时间:2024/05/16 08:20

一、SVD概念

利用SVD实现,我们能够用小得多的数据集来表示原始数据集。这样做,实际上是去除了噪声和冗余数据。

当我们试图节省空间时,去除信息就是很有用了。但是在这里我们则是从数据中抽取信息。基于这个视角,我们就可以把SVD看成是从噪声数据中抽取相关特征。

二、SVD能够进行数据约减的原因

1、在很多情况下,数据中的一小段携带了数据集中的大部分信息,其他信息则要么是噪声,要么是毫不相关的信息。

2、SVD是一种常见的矩阵分解技术,SVD将原始的数据集矩阵Data分解成三个矩阵,如下公式所示:


由公式可以看出如果原始矩阵Data是m行n列,那么分解成的三个矩阵U,,V依次是m行m列,m行n列,n行n列。

矩阵的对角元素是从大到小排列的。这些对角元素成为奇异值(Singular Value),他们对应了原始数据集矩阵Data的奇异值。奇异值和特征值是有关系的,这里的奇异值就是矩阵Data*(Data的转置)特征值的平方根。


因为矩阵的对角元素是从大到小排列的,在科学和工程中,一直存在这样一个普遍事实:在某个奇异值的数目(r个)之后,其他的奇异值都置为0。这就意味着数据集中仅有r个重要特征,而其余特征则都是噪声或者冗余特征。


三、基于Python的SVD实现以及将数据映射到低维空间的过程

1、Python实现:

<span style="font-size:18px;">dataMat = [[1,1,1,0,0],            [2,2,2,0,0],            [1,1,1,0,0],            [5,5,5,0,0],            [1,1,0,2,2],            [0,0,0,3,3],            [0,0,0,1,1]]>>> dataMat = mat(dataMat)>>> U,Simga,VT = linalg.svd(dataMat)>>> Umatrix([[ -1.77939726e-01,  -1.64228493e-02,   1.80501685e-02,           9.53086885e-01,  -3.38915095e-02,   2.14510824e-01,           1.10470800e-01],        [ -3.55879451e-01,  -3.28456986e-02,   3.61003369e-02,          -5.61842993e-02,  -6.73073067e-01,  -4.12278297e-01,           4.94783103e-01],        [ -1.77939726e-01,  -1.64228493e-02,   1.80501685e-02,          -2.74354465e-01,  -5.05587078e-02,   8.25142037e-01,           4.57226420e-01],        [ -8.89698628e-01,  -8.21142464e-02,   9.02508423e-02,          -1.13272764e-01,   2.86119270e-01,  -4.30192532e-02,          -3.11452685e-01],        [ -1.33954753e-01,   5.33527340e-01,  -8.35107599e-01,           6.10622664e-16,   1.11022302e-16,   8.88178420e-16,           1.11022302e-16],        [ -2.15749771e-02,   7.97677135e-01,   5.13074760e-01,          -6.06319451e-03,  -2.14803071e-01,   1.00648733e-01,          -2.09028015e-01],        [ -7.19165903e-03,   2.65892378e-01,   1.71024920e-01,           1.81895835e-02,   6.44409213e-01,  -3.01946200e-01,           6.27084044e-01]])>>> Simgaarray([  9.72140007e+00,   5.29397912e+00,   6.84226362e-01,         1.52344501e-15,   2.17780259e-16])>>> VTmatrix([[ -5.81200877e-01,  -5.81200877e-01,  -5.67421508e-01,          -3.49564973e-02,  -3.49564973e-02],        [  4.61260083e-03,   4.61260083e-03,  -9.61674228e-02,           7.03814349e-01,   7.03814349e-01],        [ -4.02721076e-01,  -4.02721076e-01,   8.17792552e-01,           5.85098794e-02,   5.85098794e-02],        [ -7.06575299e-01,   7.06575299e-01,  -2.22044605e-16,           2.74107087e-02,  -2.74107087e-02],        [  2.74107087e-02,  -2.74107087e-02,   2.18575158e-16,           7.06575299e-01,  -7.06575299e-01]])</span>

上面可以看到:Simga只有一行,这是因为,由于矩阵除了对角元素其他均为0,因此这种仅返回对角元素的方式能够节省空间,这就是由Numpy的内部机制产生的。我们所要记住的就是:一旦看到Sigma就知道他是一个矩阵。

上面可以看到:Simga中前三个数值比其他的值大多了,所以我们的原始数据集Data可以用如下结果近似:


下面看我们的近似结果(接着上面的代码):

>>> Sig3 = mat([[Simga[0],0,0],[0,Simga[1],0],[0,0,Simga[2]]])>>> Sig3matrix([[ 9.72140007,  0.        ,  0.        ],        [ 0.        ,  5.29397912,  0.        ],        [ 0.        ,  0.        ,  0.68422636]])>>> U[:,:3]*Sig3*VT[:3,:]matrix([[  1.00000000e+00,   1.00000000e+00,   1.00000000e+00,          -1.51788304e-17,  -1.02999206e-17],        [  2.00000000e+00,   2.00000000e+00,   2.00000000e+00,           1.73472348e-18,   1.12757026e-17],        [  1.00000000e+00,   1.00000000e+00,   1.00000000e+00,           7.61977287e-16,   7.66747776e-16],        [  5.00000000e+00,   5.00000000e+00,   5.00000000e+00,           6.59194921e-17,   9.02056208e-17],        [  1.00000000e+00,   1.00000000e+00,  -7.21644966e-16,           2.00000000e+00,   2.00000000e+00],        [  1.66533454e-16,   1.30451205e-15,  -8.88178420e-16,           3.00000000e+00,   3.00000000e+00],        [  6.24500451e-17,   4.57966998e-16,  -3.33066907e-16,           1.00000000e+00,   1.00000000e+00]])>>> 
可以看到和原始数据没什么差别。

问题:我们是如何知道保留前三个奇异值的呢?

策略1:确保要保留的奇异值的数目有很多启发式的策略,其中一个典型的做法就是保留矩阵中90%的能量信息。为了计算总能量信息,我们将所有的奇异值求其平方和。于是可以将奇异值的平方和累加到总值的90%为止。

策略2:当数据有上万的奇异值时,那么就保留前面的2000或3000个。
四:基于协同过滤的推荐引擎

1、协同过滤(collaborative filtering):是通过用户和其他用户的数据进行对比来实现推荐的。通俗点说就是:

我们不利用用于描述物品的属性来计算物品之间的相似度,而是利用用户对他们的意见来计算相似度。

2、相似度的计算(这里介绍了三种):

种1:欧氏距离

我们希望相似度的值在0--1之间变化,并且物品越相似,他们的相似度值也就越大,我们可以用相似度=1/(1+距离)来表示,当距离为0时,相似度为1.0,当距离非常大时,相似度也就趋近于0

种2:皮尔逊相关系数(pearson correlation)

它度量的是两个向量之间的相似度。它相对于欧氏距离的一个优势是:它对用户评级的量级并不敏感。比如:一个狂躁者对所有物品的评分都是5分,而另一个忧郁着对所有物品的评分都是1分,它会认为这两个向量是相等的。在Numpy中,它的计算是由函数corrcoef进行的。可以看代码实现

种3:余弦相似度(cosine similarity)

计算的是两个向量夹角的余弦值.如果夹角为90度,则相似度为0;如果两个向量的方向相同,则相似度为1.0。我们采用两个向量的预选相似度的定义如下:


下面代码实现:

from numpy import *from numpy import linalg as la#欧氏距离def ecludSim(inA, inB):    return 1.0/(1.0 + la.norm(inA - inB))#皮尔逊相关系数def pearsSim(inA, inB):    if len(inA) < 3 : return  1.0    return 0.5+0.5*corrcoef(inA,inB,rowvar=0)[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)

测试代码:
>>> dataMatmatrix([[1, 1, 1, 0, 0],        [2, 2, 2, 0, 0],        [1, 1, 1, 0, 0],        [5, 5, 5, 0, 0],        [1, 1, 0, 2, 2],        [0, 0, 0, 3, 3],        [0, 0, 0, 1, 1]])>>> import svdRec>>> svdRec.ecludSim(dataMat[:,0],dataMat[:,4])0.13367660240019172>>> svdRec.ecludSim(dataMat[:,0],dataMat[:,1])1.0
其他两个测试,同理。

3、基于物品的相似度还是基于用户的相似度?

一般是倾向于使用基于物品的相似度的计算方法,因为用户的数目通常很多,这个看实际情况而定。

4、推荐引擎的评价

我们采用交叉测试的方法,具体做法是:我们将某些已知的评分值去掉,然后对他们进行测试,然后计算出预测值和真实值之间的差异。

四、在推荐系统中的应用

1、首先我们构建一个基本的推荐引擎,它能够寻找用户没有尝过的菜肴。然后,通过SVD来减少特征空间并提高推荐的效果。

思路:给定一个用户,系统会为此用户返回N个最好的推荐菜。为了实现这一点,我们需要做:

(1)寻找用户没有评级的菜肴,即在用户--物品矩阵中的0值

(2)在用户没有评级的所有物品中,对每个物品预计一个可能的评级分数。

(3)对这些物品从高到底进行排序,返回前N个物品。

代码实现:

#对每一个未评分菜肴预测得分def standEst(dataMat, user, simMeas, item):    n = shape(dataMat)[1]    simTotal = 0.0;ratSimTotal = 0.0    for j in range(n):        userRating = dataMat[user,j]        if userRating == 0: continue        overlap = nonzero(logical_and(dataMat[:,item].A>0,dataMat[:,j].A>0))[0]#寻找两个物品都评级的物品        if len(overlap) == 0:            similarity = 0        else:            similarity = simMeas(dataMat[overlap,item],dataMat[overlap,j])        #print("the %d and %d similarity is : %f" % (item, j, similarity))        simTotal += similarity        ratSimTotal += similarity*userRating#归一化,是所有评分值在0--5之间    if simTotal == 0:        return 0    else:        return ratSimTotal/simTotal#给user推荐的菜肴函数def recommend(dataMat, user, N=3,simMeas=cosSim,estMethod=standEst):    unratedItems = nonzero(dataMat[user,:].A==0)[1]#寻找未评级的物品    if len(unratedItems) == 0:        return 'you rated everything'    itemScores = []    for item in unratedItems:        estimatedScore = estMethod(dataMat, user, simMeas, item)        print("item:",item,"estimatedScore:",estimatedScore)        itemScores.append((item, estimatedScore))    return sorted(itemScores,key=lambda jj:jj[1],reverse=True)[:N]
下面对用户2没有评分的菜肴进行评分,并将评分高的推荐

>>> myMat = mat([[4,4,0,2,2],[4,0,0,3,3],[4,0,0,1,1],[1,1,1,2,0],[2,2,2,0,0],[1,1,1,0,0],[5,5,5,0,0]])>>> svdRec.recommend(myMat,2)('item:', 1, 'estimatedScore:', 2.0243290220056256)('item:', 2, 'estimatedScore:', 2.5)[(2, 2.5), (1, 2.0243290220056256)]<span style="color:#ff6666;">#余弦相似度:对物品2的预测评分值为2.5,对物品1的预测评分值为2.0243</span>>>> svdRec.recommend(myMat,2,simMeas=svdRec.ecludSim)('item:', 1, 'estimatedScore:', 2.8266504712098603)('item:', 2, 'estimatedScore:', 3.0)[(2, 3.0), (1, 2.8266504712098603)]<span style="color: rgb(255, 102, 102); font-family: Arial, Helvetica, sans-serif;">#欧氏距离:对物品2的预测评分值为3.0,对物品1的预测评分值为2.8266</span>>>> svdRec.recommend(myMat,2,simMeas=svdRec.pearsSim)('item:', 1, 'estimatedScore:', 2.0)('item:', 2, 'estimatedScore:', 2.5)[(2, 2.5), (1, 2.0)]<span style="color: rgb(255, 102, 102); font-family: Arial, Helvetica, sans-serif;">#皮尔逊相关系数:对物品2的预测评分值为:2.5,对物品1的预测评分值为2.0</span>>>> 

2、利用SVD提高推荐效果
为了更加接近实际情况,假设我们现在又有一个新的数据集,我们首先要知道保留几个奇异值,根据我们前面说的,保留前90%的。计算代码如下:

dataSet = [[2,0,0,4,4,0,0,0,0,0,0],      [0,0,0,0,0,0,0,0,0,0,5],      [0,0,0,0,0,0,0,1,0,4,0],      [3,3,4,0,3,0,0,2,2,0,0],      [5,5,5,0,0,0,0,0,0,0,0],      [0,0,0,0,0,0,5,0,0,5,0],      [4,0,4,0,0,0,0,0,0,0,5],      [0,0,0,0,0,4,0,0,0,0,4],      [0,0,0,0,0,0,5,0,0,5,0],      [0,0,0,3,0,0,0,0,4,5,0],      [1,1,2,1,1,2,1,0,4,5,0]]>>> U, Sigma, VT = la.svd(mat(dataSet))>>> Sigmaarray([  1.34342819e+01,   1.18190832e+01,   8.20176076e+00,         6.86912480e+00,   5.29063022e+00,   3.91213561e+00,         2.94562509e+00,   2.35486137e+00,   2.08702082e+00,         7.08715931e-01,   1.15779137e-16])>>> Sig2 = Sigma**2>>> sum(Sig2)496.99999999999966>>> sum(Sig2)*0.9<span style="color:#ff0000;">#前90%是这么多</span>447.29999999999973>>> sum(Sig2[:3])387.43953785565753>>> sum(Sig2[:4])434.62441339532046>>> sum(Sig2[:5])#前3、4列的值不满足90%,前5列的值满足,所以保留前5列的值。462.61518152879387

下面我们实现的函数,standEst()函数的功能一样,实现对物品的估计评分值,只不过他用到了SVD来降维

def svdEst(dataMat, user, simMeas, item):    n = shape(dataMat)[1]    simTotal = 0.0;ratSimTotal = 0.0    U, Sigma, VT = la.svd(dataMat)    Sig4 = mat(eye(4)*Sigma[:4])    xformedItems = dataMat.T * U[:,:4] * Sig4.I#利用U矩阵将物品转换到低维空间,道理在哪?    for j in range(n):        userRating = dataMat[user,j]        if userRating == 0 or j == item:            continue        similarity = simMeas(xformedItems[item,:].T,xformedItems[j,:].T)        print("the %d and %d similarity is : %f" % (item,j,similarity))        simTotal += similarity        ratSimTotal += similarity*userRating    if simTotal == 0:        return 0    else:        return ratSimTotal/simTotal

至此,所要说的就说完了,经过我自己的一些对比,我觉得用不用SVD的效果不是特别明显,可能是我数据小的问题吧,而且用不用SVD的结果会有一些不同,但是我不知道哪个好一些,也没有进行测试。有兴趣的可以测试下,按照我们推荐引擎评价中所说的办法。


0 0
原创粉丝点击