SVD奇异值分解:
SVD是一种可靠的正交矩阵分解法。可以把A矩阵分解成U,∑,VT三个矩阵相乘的形式。(Svd(A)=[U*∑*VT],A不必是方阵,U,VT必定是正交阵,S是对角阵<以奇异值为对角线,其他全为0>)
用途:
信息检索(LSA:隐性语义索引,LSA:隐性语义分析),分解后的奇异值代表了文章的主题或者概念,信息检索的时候同义词,或者说同一主题下的词会映射为同一主题,这样就可以提高搜索效率
数据压缩:通过奇异值分解,选择能量较大的前N个奇异值来代替所有的数据信息,这样可以降低噪声,节省空间。
推荐系统:主要是降噪,矩阵变换至低维空间,方便计算(目前没有意识到它对推荐精确度的提升有什么具体作用)。
原理:矩阵分解,矩阵变换,数据降维
基于协同过滤的推荐系统(相关知识):
相似度计算:A(a1,a2,a3),B(b1,b2,b3)
1.欧氏距离相似度:点到点的距离在多维空间的推广 ,||A-B||表示A-B的2范数。
,
2.皮尔逊相关系数:
3.余玄相似度:
SVD的矩阵空间变换:
1.奇异值分解
2.奇异值选择,数据矩阵重构:
协同过滤算法,就是在重构后的矩阵空间上做相似度计算。
下面就《机器学习实战》来看一下具体矩阵分解和奇异值选择的操作(后面会附上具体的代码,大家一看就懂,很多东西都被Python封装好了,直接调用):
原始数据data1:每一列代表一种商品,每一行代表一个用户,数据是用户对商品的评价
Data:(M*N)7*5
奇异值分解:
U:(M*M)7*7
∑:(M*N对角矩阵,前N*N是对角矩阵,对角线时奇异值,后M-N是0)7*5
VT:(N*N)5*5
奇异值选择:
∑=(e1,e2,e3...em)
从上图 分解后的∑可以看出前2个奇异值之和远大于后面的奇异值,所以说,前两个奇异值中代表的信息足以描述整个数据。我们可以计算前x个奇异值得平方和占所有奇异值的平方和的比例,如果大于90%,我们就选这x个奇异值重构矩阵(剩余的数据代表的可能是噪声,无用数据)
我们通过矩阵重构来看一下理论是否正确
矩阵重构:
U:(M*X)7*2
∑:(X*X)2*2,以前X个奇异值构建对角矩阵
VT:(x*n)2*5
A’:重构后的U*∑*VT
可以发现原始数据中非零的部分都完整的保存了下来,说明选择的奇异值几乎完整地保存了所有有用信息。其他部分都是近似为零的小数,将他们损失精度,强转成整形后就是0强转之后如下图:
原始数据Data:
可以看到相比较于原始数据出现了部分损失,这是由于强转后将损失信息放大所致,在浮点数情况下这些微小的损失被忽略掉了(个人理解)。
基于以下数据data2做商品推荐:行:用户,列:商品(由于上一个数据集维数较低已经用于展示了这个步骤中的操作,下面就直接放代码实现)
步骤:
1.进行矩阵奇异值分解
2.矩阵进行低维空间的映射 降维后的数据A’
3.在低维空间做相似度计算,并进行估计评分
贴代码:(没有代码说个卵呀!,最后会放上源码)python(才开始用可能风格有点怪异),代码是机器学习实战的内容,注释也很多,不做多说了
Exp: 用户A,评价了1,2,3,4,5这5个商品中的1,2,3
用户B,评价了1,2,3,4,5这5个商品中的1,3,4
现在要给A做推荐4,5号商品(未评价过的才需要推荐),首先我们遍历A评价过得商品的每一列(在矩阵中代表其他用户对这个商品的评价),然后和指定的4号商品所在的列做相似度计算。
在这里就是1,2,3,列分别于第四列做相似度计算给出一个评分。然后1,2,3列再与第5列做相似度评分。最终我们比较4,5的估计评分值,谁大,我们就说,喜欢1,2,3号商品的用户可能也喜欢4号。
就以上的说明并没有用到SVD,我们再取数据的列的时候并不是从原矩阵中去取,而是从利用SVD降维后的矩阵中去取(这是唯一用到SVD的部分)。
根据评分推荐:
遍历所有未评分的商品,进行评分,然后排序取TOPN(这里选三个),输出的结果就是给这个用户推荐的商品。
基于SVD实现的数据压缩:
SVD数据压缩说白了就是奇异值分解后,
A可以近似的用U’*∑’*VT’表示A,原始的A需要M*N个存储空间,我们现在只需要存储U’,∑’,VT’三个矩阵在使用的时候做乘积就可以得到A,而且U’,∑’,VT’需要的空间M*X+X*X+X*N远小于M*N,这就实现了数据压缩。从M*N压缩到了M*X+X*X+X*N
Exp:对一个图像数据进行压缩:32*32的图像数据 总空间需要:32*32=1024
压缩前:
压缩后还原:可以发现有微小的差异
压缩后的三个矩阵:sigma(2),VT(2*32),U(32*2)总空间=130相比1024极大缩小了占有空间
源代码:(py2.7可直接运行)
1 # -*- coding:utf-8 -*- 2 # Filename: svd.py 3 # Author:Ljcx 4 5 6 from numpy import* 7 8 9 class Svd(object): 10 11 def loadExData(self): 12 data = [[0, 0, 0, 2, 2], 13 [0, 0, 0, 3, 3], 14 [0, 0, 0, 1, 1], 15 [1, 1, 1, 0, 0], 16 [2, 2, 2, 0, 0], 17 [5, 5, 5, 0, 0], 18 [1, 1, 1, 0, 0]] 19 return data 20 21 def loadExData2(self): 22 """ 23 列表示商品,行表示用户的评分 24 """ 25 return[[0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 5], 26 [0, 0, 0, 3, 0, 4, 0, 0, 0, 0, 3], 27 [0, 0, 0, 0, 4, 0, 0, 1, 0, 4, 0], 28 [3, 3, 4, 0, 0, 0, 0, 2, 2, 0, 0], 29 [5, 4, 5, 0, 0, 0, 0, 5, 5, 0, 0], 30 [0, 0, 0, 0, 5, 0, 1, 0, 0, 5, 0], 31 [4, 3, 4, 0, 0, 0, 0, 5, 5, 0, 1], 32 [0, 0, 0, 4, 0, 4, 0, 0, 0, 0, 4], 33 [0, 0, 0, 2, 0, 2, 5, 0, 0, 1, 2], 34 [0, 0, 0, 0, 5, 0, 0, 0, 0, 4, 0], 35 [1, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0]] 36 37 # 相似度计算:inA,inB为列向量还是行向量,基于我们需要计算相似的维度 38 def ecludSim(self, inA, inB): 39 """norm()求范数 40 范数表示数值平方开方,inA-inB的范数 = inA和inB的欧氏距离 41 """ 42 return 1.0 / (1.0 + linalg.norm(inA - inB)) 43 44 def pearsSim(self, inA, inB): 45 """corrcoef()求皮尔逊相关系数 [-1,1] 46 皮尔逊相关系数:0.5+0.5*corrcoef()规范化到[0,1] 47 """ 48 if len(inA) < 3: 49 return 1.0 50 return 0.5 + 0.5 * corrcoef(inA, inB, rowvar=0)[0][1] 51 52 def cosSim(self, inA, inB): 53 """ 54 余玄相似度:即两个向量的余玄夹角值[-1,1] 55 """ 56 num = float(inA.T * inB) 57 denom = linalg.norm(inA) * linalg.norm(inB) 58 return 0.5 + 0.5 * (num / denom) 59 60 # 奇异值分解==》矩阵重构:可用于图像压缩 61 def svdMt(self, data): 62 """ 63 奇异值分解矩阵data = U * Sigma *VT (用分解后的矩阵可以近似地表示原矩阵 64 节省空间, 65 Sigma是个奇异值方阵) 66 """ 67 U, Sigma, VT = linalg.svd(data) 68 """ 前两个奇异值已经几乎包含了所有的信息远大于后三个数据,所以忽略掉后三个 69 数据 70 启发式搜索:选择奇异之平方和大于总平方和90%为标准 71 """ 72 num = 0 # 需要保存的奇异值个数 73 for i in range(len(Sigma)): 74 if (linalg.norm(Sigma[:i + 1]) / linalg.norm(Sigma)) > 0.9: 75 num = i + 1 76 break 77 # 构建对角矩阵 78 sig3 = mat(eye(num) * Sigma[:num]) 79 """选取前num个奇异值重构数据集 80 """ 81 newData = U[:, :num] * mat(sig3) * VT[:num, :] 82 print newData 83 print newData.astype(int) 84 return U, Sigma, VT, num, newData 85 86 """ 87 基于相似度的推荐引擎: 88 只需要对用户所购商品和其他商品做相似度计算,选取TOPn个作为推荐 89 基于SVD的推荐引擎: 90 先进行奇异值分解,选取前n个奇异值(能量之和大于90%,奇异之平方和大于总平方和 91 90%为标准),作为需要降维的维数,原数据往低维空间投影。Data.T*U[:,:n]*Sigma[:,:4] 92 寻找指定一个商品的所有评分x[,,,]和每一个商品的所有评分做相似度计算,相似度求和 93 """ 94 95 # 相似度推荐 96 def standEst(self): 97 pass 98 99 def svdEst(self, dataMat, xformedItems, user, simMeas, item): 100 """计算相似度并计算评分 101 # dataMat:原始数据 102 # user:用户编号 103 # simMeas:相似度计算方法 104 # item:商品编号 105 # xformedItems:降维后的数据 106 """ 107 n = shape(dataMat)[1] # 获取列,多少个商品 108 simTotal = 0.0 109 ratSimTotal = 0.0 110 # 计算指定用户评价过的商品与其他所有用户的评价过的商品做相似度计算,来估计 111 # 指定的未评价商品item的评分 112 for j in range(n): 113 userRating = dataMat[user, j] 114 if userRating == 0 or j == item: 115 continue 116 similarity = simMeas(xformedItems[item, :].T, xformedItems[j, :].T) 117 print 'the %d and %d similarity is: %f' % (item, j, similarity) 118 simTotal += similarity # 相似度求和 119 ratSimTotal += similarity * userRating # 相似度乘以评分在求和 120 if simTotal == 0: 121 return 0 122 else: 123 return ratSimTotal / simTotal # 根据相似度对一个指定商品给一个评分 124 125 def recommend(self, dataMat, user, N=3, simMeas=cosSim, estMethod=svdEst): 126 """ 127 # 根据SVD空间评分推荐:寻找所有该用户未评分的商品,对每个商品进行评分估计() 128 """ 129 unratedItems = nonzero(dataMat[user, :].A == 0)[1] # findunrated items 130 if len(unratedItems) == 0: 131 return 'you rated everything' 132 U, Sigma, VT, num, newData = self.svdMt(dataMat) 133 sig = mat(eye(num) * Sigma[:num]) # 构建对角矩阵 134 xformedItems = dataMat.T * U[:, :num] * sig.I # 数据投影降维 135 print "----xform---" 136 print xformedItems 137 itemScores = [] 138 for item in unratedItems: 139 estimatedScore = estMethod( 140 dataMat, xformedItems, user, simMeas, item) # 评分 141 itemScores.append((item, estimatedScore)) 142 return sorted(itemScores, key=lambda jj: jj[1], reverse=True)[:N] 143 144 def loadImageData(self): 145 """ 146 加载图像数据 147 """ 148 fp = open("image.txt", "r") 149 imageData = [] 150 for line in fp.readlines(): 151 lineData = [] 152 for i in range(len(line) - 1): 153 lineData.append(int(line[i])) 154 imageData.append(lineData) 155 return mat(imageData) 156 157 def imageCompress(self): 158 """svd图像压缩 == 分解矩阵之后 选择几个重要的奇异值对U ,Sigma ,VT 进行切割, 159 切割后的矩阵的乘积仍可以表示原矩阵,我们只需存储这三个矩阵就可以在使用的时候 160 还原原矩阵了 161 """ 162 data = self.loadImageData() 163 self.printMat(data, 0.8) # 压缩前数据 164 print"---------------------------------------------------------" 165 U, Sigma, VT, num, newData = self.svdMt(data) 166 self.printMat(newData, 0.8) # 压缩后还原的数据 167 print Sigma 168 print "num:" + str(num) 169 print '前 %d 个奇异值的平方和达到了所有奇异值平方和的0.9以上则2个奇异值重构矩阵可表示原矩阵:' % (num) 170 U = U[:, :num] 171 Sigma = Sigma[:num] 172 VT = VT[:num, :] 173 print "U:" + str(shape(U)) 174 print U 175 print "Sigma:" + str(shape(Sigma)) 176 print Sigma 177 print "VT:" + str(shape(VT)) 178 print VT 179 print "压缩前存储空间:", str(shape(data)[0] * shape(data)[1]) 180 print "压缩后存储空间:", str(shape(U)[0] * shape(U)[1] 181 + shape(Sigma)[0] * shape(Sigma)[0] 182 + shape(VT)[0] * shape(VT)[1]) 183 184 def printMat(self, inMat, thresh=0.8): 185 for i in range(32): 186 for k in range(32): 187 if float(inMat[i, k]) > thresh: 188 print 1, 189 else: 190 print 0, 191 print '' 192 193 194 if __name__ == "__main__": 195 sd = Svd() 196 data = sd.loadExData2() 197 sd.recommend(mat(data), 2, 3, sd.cosSim, sd.svdEst) 198 sd.imageCompress()