参与拿奖:本文已参与「新人创作礼」活动,一起开启掘金创作之路
1.相关概念
SVD:Singular Value Decomposition
奇异值分解:将一个矩阵划分为多个矩阵的乘积。
A=UxSigmaxV.T,其中Sigma矩阵只有对角元素,这些对角元素称为奇异值,对应了原始数据的奇异值。
奇异值分解优点:简化数据、去除噪声、提高算法结果;
奇异值分解缺点:数据的转换难以理解;
适用数据类型:数值型数据
推荐系统:简单版本的推荐系统能够计算item与person之间的相似度;更先进的则使用SVD。
2.案例
2.1 简单demo
from numpy import *
from numpy import linalg as la
print("SVD分解一个矩阵")
testMat=mat([[1,1],[7,7]])
U,Sigma,VT=la.svd(testMat)
# Sigma只返回对角上的元素
print("U:",U)
print("Sigma:",Sigma)
print("V转置:",VT)
2.2 分解之后重构
data=[[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]]
U,Sigma,VT=la.svd(data)
Sig3=eye(3)
for i in range(3):
Sig3[i,i]=Sigma[i]
# print(Sigma[0])
mat(U[:,:3])*mat(Sig3)*mat(VT[:3,:])
对于具体保留多少个奇异值的方法,有两种方法:
1)保留矩阵中90%的能量信息,总的能量信息等于奇异值平方求和,通过奇异值累加到90%即可。
2)对于较多(上万)奇异值时,可以采用保留前2000~3000个奇异值。
2.3 推荐领域
2.3.1 相关概念
通过将用户信息与其他用户进行比对来实现推荐:
相似度计算方法:第一种是欧氏距离,就是通过做差再平方求和之后开方,可以1/(1+k)将其固定在(0,1]。
第二种:皮尔逊相关系数:度量两个向量之间的相似度,其对用户的某些差异较大的数据不敏感,取值为[-1,1]。
第三种:余弦相似度:计算两个向量之间夹角的余弦值,如果两向量垂直,则相似度为0,方向相同则相似度为1。
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)
def loadExData():
dataMat=[[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, 5, 5, 0, 0],
[1, 1, 1, 0, 0]]
return dataMat
def test():
dataMat=mat(loadExData())
fishRice=dataMat[:,0]
pigMeat=dataMat[:,4]
print("欧式距离计算相似度:", ecludSim(fishRice, pigMeat))
print("皮尔逊系数计算相似度:", pearsSim(fishRice, pigMeat))
print('向量余弦计算相似度:',cosSim(fishRice, pigMeat))
test()
2.3.2 基于物品相似度的推荐
基于物品的相似度与基于用户的相似度,倾向于计算更少数据的相似度。
评价指标:RMSE(最小均方根误差)
推荐N个未品尝过的菜肴
1,寻找用户未评级的菜品
2,在未评级菜品中对于每一个物品预计一个评级分数,预测用户对其的打分
3,对这些评级物品排序,返回前N个菜品
from numpy import *
from numpy import linalg as la
# 参数分别为:数据矩阵、用户编号、相似度计算方法、物品编号
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])
# 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 recommend(dataMat, user, N=3, simMeas=cosSim, estMethod=standEst):
unratedItems = nonzero(dataMat[user,:].A==0)[1]#find unrated items
if len(unratedItems) == 0:
return 'you rated everything'
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]
def test():
myMat=mat(loadExData())
myMat[0,1]=myMat[0,0]=myMat[1,0]=myMat[2,0]=4
myMat[3,3]=2
for i in range(shape(myMat)[0]):
# ['ecludSim','pearsSim','cosSim']:
info=recommend(myMat,i,simMeas=ecludSim)
for k,v in info:
print("用户: %d 可能对菜品:%d 的评级为:%.2f"%(i,k,v))
test()
2.3.3 利用SVD提升推荐效果
### 基于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]) #arrange Sig4 into a diagonal matrix
# 构建转换后的矩阵
xformedItems = dataMat.T * U[:,:4] * Sig4.I #create transformed items
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 test():
myMat=mat(loadExData())
myMat[0,1]=myMat[0,0]=myMat[1,0]=myMat[2,0]=4
myMat[3,3]=2
for i in range(shape(myMat)[0]):
# ['ecludSim','pearsSim','cosSim']:
info=recommend(myMat,i,estMethod=svdEst)
for k,v in info:
print("用户: %d 可能对菜品:%d 的评级为:%.2f"%(i,k,v))
test()
构建推荐引擎的挑战
一个挑战是在做SVD分解时,会降低程序运行速度。
矩阵的表示方法、以及冷启动问题(缺乏足够的数据时)。
冷启动问题的解决方案就是将推荐看成搜索问题。
可以为物品贴标签,将这些标签属性作为相似度计算所需要的数据(基于内容的推荐)。
另外在新增物品时,在协同过滤场景下,由于缺乏用户对新物品的使用以及评价信息,无法判断用户对其喜好,无法利用该商品。
3.小结
SVD是一种强大的降维工具,可以利用SVD来逼近矩阵并提取重要特征。
通过保留矩阵80%~90%的能量,就可以获得重要特征,并去掉噪声。
协同过滤是一种基于用户喜好或行为数据的推荐算法,其核心是相似度计算方法。
在大规模数据集上,可以采用离线的方式来进行SVD分解和相似度计算。
参考资料
[1] 机器学习实战
[2] 书籍源码
[3] jupyter版本
[4] 本节代码