Files
ailearning/src/python/14.SVD/svdRecommend.py
2017-09-08 20:00:21 +08:00

363 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/python
# coding: utf-8
'''
Created on Mar 8, 2011
Update on 2017-05-18
@author: Peter Harrington/山上有课树/片刻
《机器学习实战》更新地址https://github.com/apachecn/MachineLearning
'''
from numpy import linalg as la
from numpy import *
def loadExData3():
# 利用SVD提高推荐效果菜肴矩阵
return[[2, 0, 0, 4, 4, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 4, 0],
[3, 3, 4, 0, 3, 0, 0, 2, 2, 0, 0],
[5, 5, 5, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 5, 0, 0, 5, 0],
[4, 0, 4, 0, 0, 0, 0, 0, 0, 0, 5],
[0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4],
[0, 0, 0, 0, 0, 0, 5, 0, 0, 5, 0],
[0, 0, 0, 3, 0, 0, 0, 0, 4, 5, 0],
[1, 1, 2, 1, 1, 2, 1, 0, 4, 5, 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]]
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]]
"""
# # 原矩阵
# 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]]
# 原矩阵
return[[0, -1.6, 0.6],
[0, 1.2, 0.8],
[0, 0, 0],
[0, 0, 0]]
# 相似度计算假定inA和inB 都是列向量
# 基于欧氏距离
def ecludSim(inA, inB):
return 1.0/(1.0 + la.norm(inA - inB))
# pearsSim()函数会检查是否存在3个或更多的点。
# corrcoef直接计算皮尔逊相关系数范围[-1, 1],归一化后[0, 1]
def pearsSim(inA, inB):
# 如果不存在该函数返回1.0,此时两个向量完全相关。
if len(inA) < 3:
return 1.0
return 0.5 + 0.5 * corrcoef(inA, inB, rowvar=0)[0][1]
# 计算余弦相似度如果夹角为90度相似度为0如果两个向量的方向相同相似度为1.0
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):
"""standEst(计算某用户未评分物品中,以对该物品和其他物品评分的用户的物品相似度,然后进行综合评分)
Args:
dataMat 训练数据集
user 用户编号
simMeas 相似度计算方法
item 未评分的物品编号
Returns:
ratSimTotal/simTotal 评分05之间的值
"""
# 得到数据集中的物品数目
n = shape(dataMat)[1]
# 初始化两个评分值
simTotal = 0.0
ratSimTotal = 0.0
# 遍历行中的每个物品(对用户评过分的物品进行遍历,并将它与其他物品进行比较)
for j in range(n):
userRating = dataMat[user, j]
# 如果某个物品的评分值为0则跳过这个物品
if userRating == 0:
continue
# 寻找两个用户都评级的物品
# 变量 overLap 给出的是两个物品当中已经被评分的那个元素的索引ID
# logical_and 计算x1和x2元素的真值。
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'(iten,j,similarity)
# 相似度会不断累加,每次计算时还考虑相似度和当前用户评分的乘积
# similarity 用户相似度, userRating 用户评分
simTotal += similarity
ratSimTotal += similarity * userRating
if simTotal == 0:
return 0
# 通过除以所有的评分总和对上述相似度评分的乘积进行归一化使得最后评分在0~5之间这些评分用来对预测值进行排序
else:
return ratSimTotal/simTotal
# 基于SVD的评分估计
# 在recommend() 中这个函数用于替换对standEst()的调用,该函数对给定用户给定物品构建了一个评分估计值
def svdEst(dataMat, user, simMeas, item):
"""svdEst( )
Args:
dataMat 训练数据集
user 用户编号
simMeas 相似度计算方法
item 未评分的物品编号
Returns:
ratSimTotal/simTotal 评分05之间的值
"""
# 物品数目
n = shape(dataMat)[1]
# 对数据集进行SVD分解
simTotal = 0.0
ratSimTotal = 0.0
# 奇异值分解
# 在SVD分解之后我们只利用包含了90%能量值的奇异值这些奇异值会以NumPy数组的形式得以保存
U, Sigma, VT = la.svd(dataMat)
# # 分析 Sigma 的长度取值
# analyse_data(Sigma, 20)
# 如果要进行矩阵运算,就必须要用这些奇异值构建出一个对角矩阵
Sig4 = mat(eye(4) * Sigma[: 4])
# 利用U矩阵将物品转换到低维空间中构建转换后的物品(物品+4个主要的特征)
xformedItems = dataMat.T * U[:, :4] * Sig4.I
print 'dataMat', shape(dataMat)
print 'U[:, :4]', shape(U[:, :4])
print 'Sig4.I', shape(Sig4.I)
print 'VT[:4, :]', shape(VT[:4, :])
print 'xformedItems', shape(xformedItems)
# 对于给定的用户for循环在用户对应行的元素上进行遍历
# 这和standEst()函数中的for循环的目的一样只不过这里的相似度计算时在低维空间下进行的。
for j in range(n):
userRating = dataMat[user, j]
if userRating == 0 or j == item:
continue
# 相似度的计算方法也会作为一个参数传递给该函数
similarity = simMeas(xformedItems[item, :].T, xformedItems[j, :].T)
# for 循环中加入了一条print语句以便了解相似度计算的进展情况。如果觉得累赘可以去掉
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
# recommend()函数就是推荐引擎它默认调用standEst()函数产生了最高的N个推荐结果。
# 如果不指定N的大小则默认值为3。该函数另外的参数还包括相似度计算方法和估计方法
def recommend(dataMat, user, N=3, simMeas=cosSim, estMethod=standEst):
"""svdEst( )
Args:
dataMat 训练数据集
user 用户编号
simMeas 相似度计算方法
estMethod 使用的推荐算法
Returns:
返回最终 N 个推荐结果
"""
# 寻找未评级的物品
# 对给定的用户建立一个未评分的物品列表
unratedItems = nonzero(dataMat[user, :].A == 0)[1]
# 如果不存在未评分物品,那么就退出函数
if len(unratedItems) == 0:
return 'you rated everything'
# 物品的编号和评分值
itemScores = []
# 在未评分物品上进行循环
for item in unratedItems:
# 获取 item 该物品的评分
estimatedScore = estMethod(dataMat, user, simMeas, item)
itemScores.append((item, estimatedScore))
# 按照评分得分 进行逆排序获取前N个未评级物品进行推荐
return sorted(itemScores, key=lambda jj: jj[1], reverse=True)[: N]
def analyse_data(Sigma, loopNum=20):
"""analyse_data(分析 Sigma 的长度取值)
Args:
Sigma Sigma的值
loopNum 循环次数
"""
# 总方差的集合(总能量值)
Sig2 = Sigma**2
SigmaSum = sum(Sig2)
for i in range(loopNum):
SigmaI = sum(Sig2[:i+1])
'''
根据自己的业务情况,就行处理,设置对应的 Singma 次数
通常保留矩阵 80% 90% 的能量,就可以得到重要的特征并取出噪声。
'''
print '主成分:%s, 方差占比:%s%%' % (format(i+1, '2.0f'), format(SigmaI/SigmaSum*100, '4.2f'))
# 图像压缩函数
# 加载并转换数据
def imgLoadData(filename):
myl = []
# 打开文本文件,并从文件以数组方式读入字符
for line in open(filename).readlines():
newRow = []
for i in range(32):
newRow.append(int(line[i]))
myl.append(newRow)
# 矩阵调入后,就可以在屏幕上输出该矩阵
myMat = mat(myl)
return myMat
# 打印矩阵
def printMat(inMat, thresh=0.8):
# 由于矩阵保护了浮点数因此定义浅色和深色遍历所有矩阵元素当元素大于阀值时打印1否则打印0
for i in range(32):
for k in range(32):
if float(inMat[i, k]) > thresh:
print 1,
else:
print 0,
print ''
# 实现图像压缩,允许基于任意给定的奇异值数目来重构图像
def imgCompress(numSV=3, thresh=0.8):
"""imgCompress( )
Args:
numSV Sigma长度
thresh 判断的阈值
"""
# 构建一个列表
myMat = imgLoadData('input/14.SVD/0_5.txt')
print "****original matrix****"
# 对原始图像进行SVD分解并重构图像e
printMat(myMat, thresh)
# 通过Sigma 重新构成SigRecom来实现
# Sigma是一个对角矩阵因此需要建立一个全0矩阵然后将前面的那些奇异值填充到对角线上。
U, Sigma, VT = la.svd(myMat)
# SigRecon = mat(zeros((numSV, numSV)))
# for k in range(numSV):
# SigRecon[k, k] = Sigma[k]
# 分析插入的 Sigma 长度
analyse_data(Sigma, 20)
SigRecon = mat(eye(numSV) * Sigma[: numSV])
reconMat = U[:, :numSV] * SigRecon * VT[:numSV, :]
print "****reconstructed matrix using %d singular values *****" % numSV
printMat(reconMat, thresh)
if __name__ == "__main__":
# # 对矩阵进行SVD分解(用python实现SVD)
# Data = loadExData()
# print 'Data:', Data
# U, Sigma, VT = linalg.svd(Data)
# # 打印Sigma的结果因为前3个数值比其他的值大了很多为9.72140007e+005.29397912e+006.84226362e-01
# # 后两个值比较小,每台机器输出结果可能有不同可以将这两个值去掉
# print 'U:', U
# print 'Sigma', Sigma
# print 'VT:', VT
# print 'VT:', VT.T
# # 重构一个3x3的矩阵Sig3
# Sig3 = mat([[Sigma[0], 0, 0], [0, Sigma[1], 0], [0, 0, Sigma[2]]])
# print U[:, :3] * Sig3 * VT[:3, :]
"""
# 计算欧氏距离
myMat = mat(loadExData())
# print myMat
print ecludSim(myMat[:, 0], myMat[:, 4])
print ecludSim(myMat[:, 0], myMat[:, 0])
# 计算余弦相似度
print cosSim(myMat[:, 0], myMat[:, 4])
print cosSim(myMat[:, 0], myMat[:, 0])
# 计算皮尔逊相关系数
print pearsSim(myMat[:, 0], myMat[:, 4])
print pearsSim(myMat[:, 0], myMat[:, 0])
"""
# 计算相似度的方法
myMat = mat(loadExData3())
# print myMat
# 计算相似度的第一种方式
print recommend(myMat, 1, estMethod=svdEst)
# 计算相似度的第二种方式
print recommend(myMat, 1, estMethod=svdEst, simMeas=pearsSim)
# 默认推荐(菜馆菜肴推荐示例)
print recommend(myMat, 2)
"""
# 利用SVD提高推荐效果
U, Sigma, VT = la.svd(mat(loadExData2()))
print Sigma # 计算矩阵的SVD来了解其需要多少维的特征
Sig2 = Sigma**2 # 计算需要多少个奇异值能达到总能量的90%
print sum(Sig2) # 计算总能量
print sum(Sig2) * 0.9 # 计算总能量的90%
print sum(Sig2[: 2]) # 计算前两个元素所包含的能量
print sum(Sig2[: 3]) # 两个元素的能量值小于总能量的90%,于是计算前三个元素所包含的能量
# 该值高于总能量的90%,这就可以了
"""
# 压缩图片
# imgCompress(2)