PS:也可称之为复习笔记
一.原理
SVD的性质:
SVD计算举例:
二.
SVD(奇异值分解):
优点:简化数据,去除噪声点,提高算法的结果;
缺点:数据的转换可能难以理解;
适用于数据类型:数值型。
通过SVD对数据的处理,我们可以使用小得多的数据集来表示原始数据集,从有噪声的数据中抽取相关特征,这样做实际上是去除了噪声和冗余信息,以此达到了优化数据、提高结果的目的。
最早的SVD应用之一是信息检索。我们称利用SVD的方法为隐性语义检索(LSI)或隐形语义分析(LSA)。
在LSI中,一个矩阵是由文档和词语组成的,当我们在该矩阵上应用SVD时,就会构建出多个奇异值。这些奇异值代表了文档中的概念或主题,这一特点可以用于更高效的文档搜索。如果我们从上千篇相似的文档中抽取出概念,那么同义词就会映射为同一概念。
SVD的另一个应用就是推荐系统。简单版本的推荐系统能够计算项或者相似度。更先进的方法则先利用SVD从数据中构建一个主题空间,然后再在该空间下计算其相似度。
SVD也可用于特征压缩(数据降维)。将SVD用于PCA主成分分析,来做数据压缩和降噪。
三.SVD的库实现
SCV实现的相关线性代数在Numpy中有一个称为线性代数linalg的线性代数工具箱。下面演示其用法对于一个简单的矩阵:
from numpy import *
from numpy import linalg as la
df = mat(array([[1,1],[1,7]]))
U,Sigma,VT = la.svd(df)
print(U)
print(Sigma)
print(VT)
-------------------------------
# [[ 0.16018224 0.98708746]
# [ 0.98708746 -0.16018224]]
# [7.16227766 0.83772234]
# [[ 0.16018224 0.98708746]
# [ 0.98708746 -0.16018224]]
四.SVD实战应用-基于物品相似度推荐算法
原理见:https://www.jianshu.com/p/e871e741b560
代码:
from numpy import *
from numpy import linalg as la
# (用户x商品) # 为0表示该用户未评价此商品,即可以作为推荐商品
def loadExData():
return [[0, 0, 0, 2, 2],
[0, 0, 0, 3, 3],
[0, 0, 0, 1, 1],
[1, 1, 1, 0, 0],
[2, 2, 2, 0, 0],
[5, 0, 5, 0, 0],
[1, 1, 1, 0, 0]]
# 假定导入数据都为列向量
# 欧几里德距离 这里返回结果已处理 0,1 0最大相似,1最小相似 欧氏距离转换为2范数计算
def ecludSim(inA,inB):
return 1.0 / (1.0 + la.norm(inA-inB))
# 皮尔逊相关系数 numpy的corrcoef函数计算
def pearsSim(inA,inB):
if(len(inA) < 3):
return 1.0
return 0.5 + 0.5*corrcoef(inA,inB,rowvar=0)[0][1] # 使用0.5+0.5*x 将-1,1 转为 0,1
# 余玄相似度 根据公式带入即可,其中分母为2范数计算,linalg的norm可计算范数
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
# 对物品评分 (数据集 用户行号 计算误差函数 推荐商品列号)
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] # 获得相比较的两列同时都不为0的数据行号
if len(overLap) == 0:
similarity = 0
else:
# 求两列的相似度
similarity = simMeas(dataMat[overLap,item],dataMat[overLap,j]) # 利用上面求得的两列同时不为0的行的列向量 计算距离
# print('%d 和 %d 的相似度是: %f' % (item, j, similarity))
simTotal += similarity # 计算总的相似度
ratSimTotal += similarity * userRating # 不仅仅使用相似度,而是将评分当权值*相似度 = 贡献度
if simTotal == 0: # 若该推荐物品与所有列都未比较则评分为0
return 0
else:
return ratSimTotal/simTotal # 归一化评分 使其处于0-5(评级)之间
# 给出推荐商品评分
def recommend(dataMat, user, N=3, simMeas=cosSim, estMethod=standEst):
unratedItems = nonzero(dataMat[user,:].A==0)[1] # 找到该行所有为0的位置(即此用户未评价的商品,才做推荐)
if len(unratedItems) == 0:
return '所有物品都已评价...'
itemScores = []
for item in unratedItems: # 循环所有没有评价的商品列下标
estimatedScore = estMethod(dataMat, user, simMeas, item) # 计算当前产品的评分
itemScores.append((item, estimatedScore))
return sorted(itemScores, key=lambda jj: jj[1], reverse=True)[:N] # 将推荐商品排序
# 结果测试如下:
myMat = mat(loadExData())
myMat[0,1] = myMat[0,0] = myMat[1,0] = myMat[2,0] = 4 # 将数据某些值替换,增加效果
myMat[3,3] = 2
result1 = recommend(myMat,2) # 余玄相似度
print(result1)
result2 = recommend(myMat,2,simMeas=ecludSim) # 欧氏距离
print(result2)
result3 = recommend(myMat,2,simMeas=pearsSim) # 皮尔逊相关度
print(result3)
上面用了三种计算距离的函数,使用其中一种便可以了,下面使用SVD来做基于物品协同过滤。
SVD方法,用下面函数(svdEst)来替换上面的物品评价函数(standEst)即可,并且这里使用更复杂的数据集:
def loadExData2():
return [[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]]
# 替代上面的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矩阵将物品转换到低维空间中 (商品数行x选用奇异值列)Sig4.I求Sig4的逆矩阵
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('%d 和 %d 的相似度是: %f' % (item, j, similarity))
simTotal += similarity
ratSimTotal += similarity * userRating
if simTotal == 0:
return 0
else:
return ratSimTotal/simTotal
# 结果测试如下:
myMat = mat(loadExData2())
result1 = recommend(myMat,1,estMethod=svdEst) # 需要传参改变默认函数
print(result1)
result2 = recommend(myMat,1,estMethod=svdEst,simMeas=pearsSim)
print(result2)
上面的之所以使用4这个数字,是因为通过预先计算得到能满足90%的奇异值能量的前N个奇异值。判断计算如下:
# 选出奇异值能量大于90%的所有奇异值
myMat = mat(loadExData2())
U,sigma,VT = linalg.svd(myMat)
sigma = sigma**2 # 对奇异值求平方
cnt = sum(sigma) # 所有奇异值的和
print(cnt)
value = cnt*0.9 # 90%奇异值能量
print(value)
cnt2 = sum(sigma[:3]) # 2小于90%,前3个则大于90%,所以这里选择前三个奇异值
print(cnt2)
# 541.9999999999995
# 487.7999999999996
# 500.5002891275793
在函数svdEst中使用SVD方法,将数据集映射到低纬度的空间中,再做运算。其中的xformedItems = dataMat.T*U[:,:4]*Sig4.I可能不是很好理解,它就是SVD的降维步骤,通过U矩阵和Sig4逆矩阵将商品转换到低维空间(得到 商品行,选用奇异值列)。
以上是SVD的一个示例,但是对此有几个问题:
我们不必在每次评分是都做SVD分解,大规模数据上可能降低效率,可以在程序调用时运行一次,在大型系统中每天运行一次或频率不高,还要离线运行;
矩阵中有很多0,实际系统中0更多,可以通过只存储非0元素来节省空间和计算开销;
计算资源浪费来自于相似度的计算,每次一个推荐时都需要计算多个物品评分(即相似度),在需要时此记录可以被用户重复使用。实际中,一个普遍的做法是离线计算并保存相似度得分
参考资料:
https://blog.csdn.net/qq_36523839/article/details/82347332
https://www.jianshu.com/p/e871e741b560
https://blog.csdn.net/qq_36657751/article/details/82734617
https://www.jianshu.com/p/4fdd0e8e272b