SVD(奇异值分解),可以实现用小得多的数据集来表示原始数据集,达到去除噪声和冗余信息,以及压缩数据的目的。
SVD的主要应用场景有:
在大多数情况下,原始数据集中很可能一小段就携带了大部分的信息,而其他的信息要么是噪声要么是毫不相关的信息,因此利用矩阵分解技术,可以将原始矩阵表示成新的易于处理的形式。在统计学习方法一书中对SVD方法的定义如下:
回顾上一章PCA中,得到的是矩阵的特征值,而此处得到的是矩阵的奇异值,实际上奇异值就是矩阵 A T A A^TA ATA 的特征值的平方根。并且,由于不要求矩阵 A A A是方阵,因此此处可能 m ! = n m!=n m!=n。另外,可能在某一个奇异值 σ r \sigma_r σr之后的奇异值都是0,也就是说数据集中仅有 r r r个重要特征。,而其余的特征都是噪声或者冗余特征。
在numpy库中,有现成的SVD函数,可以直接利用该函数来执行运行:
from numpy import *
if __name__ == "__main__":
U,Sigma,VT = linalg.svd([[1,1],[7,7]])
print("U = ",U)
print("Sigma = ",Sigma)
print("VT = ",VT)
返回值为:
U = [[-0.14142136 -0.98994949]
[-0.98994949 0.14142136]]
Sigma = [1.00000000e+01 2.82797782e-16]
VT = [[-0.70710678 -0.70710678]
[ 0.70710678 -0.70710678]]
可以看到 S i g m a Sigma Sigma原本应该是一个对角矩阵,但是其返回值为一个向量,这是因为对角矩阵除了其对角线元素外都为0,因此这样存储有利于节省空间。
下面对一个较大的数据集实行SVD:
def loadExData():
return [[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]]
if __name__ == "__main__":
Data = loadExData()
U, Sigma, VT = linalg.svd(Data)
print("Sigma = ",Sigma)
输出为:
Sigma = [9.72140007e+00 5.29397912e+00 6.84226362e-01 1.89194737e-15
1.35001244e-31]
可以看到前三个奇异值较大,后面两个奇异值接近于0,这说明可以将后面两个奇异值忽略,来恢复原来的矩阵:
即取明显不为0的奇异值来进行恢复,这称为紧奇异值分解,还有另外一种为截断奇异值分解,具体为:
那就有疑问,重构出来的矩阵大小与原来的相同,为什么仍然称为压缩呢?
其实,SVD降维不是体现在矩阵的大小上,而是减少了矩阵的秩。
这里引用[知乎]((29 封私信 / 50 条消息) SVD 降维体现在什么地方? - 知乎 (zhihu.com))某位大佬的回答:
协同过滤是通过将用户和其他用户的数据进行对比来实现推荐的。
在比较物品之间的相似程度时,如果我们采用的是“专家‘给出的描述物品的重要属性,例如对于食品来说有热量、配料等等,但这些属性当我们更换物品的时候就需要重新设置了,因此较为繁琐。
协同过滤中所使用的方法是:利用用户对于物品的意见来计算相似度,它不关心物品的描述属性,而是严格按照许多用户的观点来计算相似度。
计算相似度有以下几种常见方式:
具体代码如下:
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=False)[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)
在计算时如果取向量为列向量,那么就是计算物品之间的相似度;如果取为行向量那么就是计算用户之间的相似度。如何选择这两种方式就取决于数据中用户的数据多还是物品的数据多。如果用户的数目很多,那么会倾向于使用基于物品相似度的计算方法。
具体做法是将数据集划分为训练集和测试集,在测试集上预测已知的分数,查看偏差程度。
具体的流程大致为:
具体代码为:
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]) # 计算相似度
simTotal += similarity
ratSimTotal += similarity * userRating
if simTotal == 0:
return 0
else:
return ratSimTotal / simTotal
def recommend(dataMat,user,N = 3,simMeas = cosSim,estMethod = standEst):
unratedItems = nonzero(dataMat[user,:].A == 0)[1] # 寻找未评分的物品
print(nonzero(dataMat[user,:].A == 0))
if len(unratedItems) == 0: # 都评分过
return "you have rated everything"
itemScores = [] # 用来存储各个未评分物品的预测得分
for item in unratedItems: # item就是未评分的那些物品的索引,是列的索引
estimatedScore = estMethod(dataMat,user,simMeas,item)
itemScores.append((item,estimatedScore))
return sorted(itemScores,key=lambda jj:jj[1],reverse=True)[:N]
解释一下这行代码:
overLap = nonzero(logical_and(dataMat[:,item].A>0,dataMat[:,j].A>0))[0]
首先是 l o g i c a i _ a n d logicai\_and logicai_and,就是对两个向量按对应位置的元素进行逻辑与操作,例子如下:
A = np.mat([[1,0,1,0,1,1]])
B = np.mat([[0,0,1,1,0,1]])
np.logical_and(A,B)
Out[9]: matrix([[False, False, True, False, False, True]])
那么该行代码就是先找出 i t e m item item列(该用户未评分的物品那列)和当前循环的第j列,取出其中那些大于0的部分再按位与,再通过nonzero函数取出不为0,即两个物品都评分过的那些用户的索引。
再解释一下这行代码:
ratSimTotal += similarity * userRating
这里是因为该物品评分越高,且与未知物品越接近,那么用户对这个未知物品的评分也可能越高。另外分数是多个已评价的物品累计的,也就是:
再解释下这行代码为什么最后是[1]:
unratedItems = nonzero(dataMat[user,:].A == 0)[1] # 寻找未评分的物品
dataMat[user,:].A返回的是一个array类型的数据,可以看成一维的矩阵,那么对于矩阵,nonzero函数就会有两个参数:第一个参数为非零元素所在的行的索引,第二个为非零元素所在的列的索引,因为这里是一维矩阵,那么这里第一个元素就里面都是0。取出第二个元素也就是索引1才是我们想要的那些未评分的那些列的索引。
利用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]) # 创建对角矩阵,维度为4维度
xformedItems = dataMat.T * U[:,:4] * Sig4.I
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
此部分代码与之前很相似,只不过用来计算的矩阵是重构之后维度更低的矩阵。
具体代码为:
def printMat(inMat,thresh = 0.8):
for i in range(32):
for j in range(32):
if float(inMat[i,j]) > thresh:
print("1 ",end = ' ') # 不加end的话会自动换行
else:
print("0 ",end = ' ')
print(" ")
def imgCompress(numSV = 3,thresh = 0.8):
myl = []
fr = open("0_5.txt")
for line in fr.readlines():
newRow = []
for i in range(32):
newRow.append(int(line[i]))
myl.append(newRow)
myMat = mat(myl)
print("*"*10,"original matrix","*"*10)
printMat(myMat,thresh)
U,Sigma,VT = la.svd(myMat)
SigRecon = mat(zeros((numSV,numSV)))
for k in range(numSV):
SigRecon[k,k] = Sigma[k] # 取前numSV个奇异值形成对角矩阵
reconMat = U[:,:numSV] * SigRecon * VT[:numSV,:]
print("*"*10,"reconstructed matrix using %d singular values" % numSV,"*"*10)
printMat(reconMat,thresh)
输出结果为:
********** original matrix **********
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
********** reconstructed matrix using 2 singular values **********
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
处理后还是很明显的看出手写的0的
并且经过SVD处理之后,我们的 U U U和 V T V^T VT都是 32 × 2 32\times 2 32×2的矩阵,另外还需要两个奇异值,则总共需要存储数字的数目为130。和原来图像需要存储1024个数字相比压缩效果显著。