SVD优点:简化数据、去除噪声、提高算法的结果
SVD是一个强大的降维工具,我们可以利用SVD来逼近矩阵并从中提取重要特征。通过保留矩阵80%~90%的能量,就可以得到重要的特征并去掉噪声。其中一个重要应用案例就是推荐引擎。
协同过滤的核心式相似度计算方法,有很多相似度计算方法都可以用于计算物品和用户之间的相似度。
3. 原理——矩阵分解
将原始的数据集矩阵data(m*n)分解成三个矩阵U(m*n), Sigma(n*m), VT(m*n):
对于Sigma矩阵:
- 该矩阵只用对角元素,其他元素均为零
- 对角元素从大到小排列。这些对角元素称为奇异值,它们对应了原始数据集矩阵的奇异值
- 这里的奇异值就是矩阵data特征值的平方根。
- 在某个奇异值的数目( 1个 )之后,其他的奇异值都置为0。这就意味着数据集中仅有r个重要特征,而其余特征则都是噪声或冗余特征。
- 确认r——启发式策略
- 保留矩阵中90%的能量信息,将奇异值平方和累加加到90%
- 若有上万奇异值,则保留2-3k
- 确认r——启发式策略
svd分解计算公式在numpy中会有
输入:
import numpy as np
from numpy import linalg as la
data = np.array(
[[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]])
U, Sigma, VT = la.svd(data)
print('Sigma:',Sigma)
print(U.shape,Sigma.shape,VT.shape)
Sig = np.mat([
[Sigma[0],0,0,0,0],
[0,Sigma[1],0,0,0],
[0,0,Sigma[2],0,0],
[0,0,0,Sigma[3],0],
[0,0,0,0,Sigma[4]]])
arr = U[:,:5] * Sig * VT[:5,:]
print(arr)
输出:
Sigma: [11.90341619 5.99130503 2.59510658 1.92662869 0.98309238]
(7, 7) (5,) (5, 5)
[[ 4.00000000e+00 4.00000000e+00 2.20640467e-15 2.00000000e+00
2.00000000e+00]
[ 4.00000000e+00 1.13034751e-16 7.17358258e-16 3.00000000e+00
3.00000000e+00]
[ 4.00000000e+00 -8.39303269e-16 -5.24128439e-16 1.00000000e+00
1.00000000e+00]
[ 1.00000000e+00 1.00000000e+00 1.00000000e+00 2.00000000e+00
-3.81525350e-15]
[ 2.00000000e+00 2.00000000e+00 2.00000000e+00 3.01940761e-16
8.63819106e-16]
[ 1.00000000e+00 1.00000000e+00 1.00000000e+00 3.68346319e-16
3.32088398e-16]
[ 5.00000000e+00 5.00000000e+00 5.00000000e+00 9.08535912e-16
1.06916635e-15]]
推荐未尝过的菜肴:
个人觉得 基于物品相似度推荐算法的核心在于
1:寻找用户未评价菜肴
2:菜肴对评价菜肴的评分 = (用户吃A菜肴的评分 * A和未评价菜肴的形似度 +....+ 用户吃n菜肴的评分 * n和未评价菜肴的形似度)/ (A和未评价菜肴的形似度+...+n和未评价菜肴的形似度)
3:根据从大到小依次推荐
输入:
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]
# corrcoef直接计算皮尔逊相关系数
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():
return[[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]]
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]]
# 协同过滤算法
# dataMat 用户数据 user 用户 simMeas 相似度计算方式 item 物品
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]
# print(dataMat[user, j])
if userRating == 0:
continue # 如果用户u没有对物品j进行打分,那么这个判断就可以跳过了
overLap = nonzero(logical_and(dataMat[:, item] > 0, dataMat[:, j] > 0))[0] # 找到对物品 j 和item都打过分的用户
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 recommand(dataMat, user, N=3, simMeas=pearsSim, estMethod=standEst):
unratedItem = nonzero(dataMat[user, :]==0)[0]
print('unretedItem:',unratedItem)
if len(unratedItem) == 0:
return 'You rated everything'
else:
itemScores=[]
# 对于未评分的item,对他进行评分
for item in unratedItem:
estimatedScore = estMethod(dataMat, user, simMeas, item)
itemScores.append((item, estimatedScore))
itemScores = sorted(itemScores, key=lambda jj: jj[1], reverse=True)[:N]
print(itemScores)
if __name__ == '__main__':
data = array(loadExData())
recommand(data, 2)
输出:
unretedItem: [1 2]
[(2, 2.5), (1, 2.0)]
先对数据集进行svd降维处理,得到较重要的/相似度较高的菜肴,再进行推荐
添加
def svdEst(dataMat,user,simMea,item):
n = shape(dataMat)[1]
simTotal = 0.0
ratSimTotal = 0.0
u,sigma,vt = la.svd(dataMat) #sigma是行向量
sig4 = mat(eye(4) * sigma[:4]) #只利用最大的3个奇异值,将其转换为4*4矩阵,非对角元素为0
# xformedItems = dataMat.T * u[:,:4] * sig4.I #得到n*4
xformedItems = dot(dot(dataMat.T, u[:, :4]), sig4)
for j in range(n):
userRate = dataMat[user,j]
if userRate == 0 or j == item:
continue
#得到对菜item和j都评过分的用户id,用来计算物品item和j之间的相似度
#overlap = nonzero(logical_and(dataMat[:,item].A>0,dataMat[:,j].A>0))[0]
#if len(overlap) == 0:
# similarity = 0
#else:
#计算物品item和j之间的相似度
# similarity = simMea(dataMat[overlap,item],dataMat[overlap,j])
similarity = simMea(xformedItems[item,:].T,xformedItems[j,:].T)
simTotal += similarity
ratSimTotal += similarity * userRate
if simTotal ==0:
return 0
else:
return ratSimTotal/simTotal #归一化处理
if __name__ == '__main__':
# data = array(loadExData())
# recommand(data, 2)
data = array(loadExData2())
# 通过这个分解就可以判断 该转换到多少维了
# # SVD 分解
# U, Sigma, VT = la.svd(data)
# # 计算取几个奇异值
# # 计算90%能量:487.83
# Sig2 = Sigma ** 2
# s = sum(Sig2) * 0.9
# print(s)
# # 计算前两个奇异值包含能量:378(<487.83)
# print(sum(Sig2[:2]))
# # 计算前三个奇异值包含能量:500(>487)
# print(sum(Sig2[:3]))
# #所以选择前三个就可以了
recommand(data, 1,estMethod = svdEst)
输出:
unretedItem: [0 1 2 4 6 7 8 9]
[(4, 3.3309010344353416), (9, 3.330264957905896), (6, 3.32699627129725)]
构建推荐引擎面临的挑战
1:但是在大数据集中,svd的分解反而会降低程序运行速度,
解决方法:在大型系统中,SVD每天运行一次或者频率更低,并且要离线运行
2:另一种潜在的计算资源浪费来自于相似度得分
解决方法:离线计算并保存相似度得分
3:冷启动
解决方法:基于内容的推荐 如菜肴中的素食、美式BBQ、价格很贵等等都可以作为相似度计算所需要的数据