餐馆可划分为很多类别,比如美式、中式、日式、牛排馆、素食店,等等。你是否想过这些
类别够用吗?或许人们喜欢这些的混合类别,或者类似中式素食店那样的子类别。如何才能知道
到底有多少类餐馆呢?我们也许可以问问专家?但是倘若某个专家说应该按照调料分类,而另一
个专家则认为应该按照配料分类,那该怎么办呢?忘了专家,我们还是从数据着手吧。我们可以
对记录用户关于餐馆观点的数据进行处理,并且从中提取出其背后的因素。
这些因素可能会与餐馆的类别、烹饪时所用的某个特定配料,或其他任意对象一致。然后,
我们就可以利用这些因素来估计人们对没有去过的餐馆的看法。提取这些信息的方法称为奇异值分解(Singular Value Decomposition, SVD)。从生物信息学到金融学等在内的很多应用中,SVD都是提取信息的强大工具。
本章将介绍SVD的概念及其能够进行数据约简的原因。然后,我们将会介绍基于Python的
SVD实现以及将数据映射到低维空间的过程。再接下来,我们就将学习推荐引擎的概念和它们的
实际运行过程。为了提高SVD的精度,我们将会把其应用到推荐系统中去,该推荐系统将会帮助
人们寻找到合适的餐馆。最后,我们讲述一个SVD在图像压缩中的应用例子。
对于上述进行SVD处理会得到两个奇异值。因此就会仿佛有两个概念或主题与此数据集相关联
我们可以把奇异值想象成一个新空间。与图14-1中的矩阵给出的五维或者七维不同,我们最
终的矩阵只有二维。那么这二维分别是什么呢?创门能告诉我们数据的什么信息?这二维分别对
应图中给出的两个组,右图中已经标示出了其中的一个组。我们可以基于每个组的共同特征来命
名这二维,比如我们得到的美式BBQ和日式食品这二维。
如何才能将原始数据变换到上述新空间中呢?下一节我们将会进一步详细地介绍SVD,届时
将会了解到SVD是如何得到U和VT两个矩阵的。VT矩阵会将用户映射到BBQ/日式食品空间去。类
似地,u矩阵会将餐馆的菜映射到BBQ/日式食品空间去。真实的数据通常不会像图14-1中的矩阵
那样稠密或整齐.这里如此只是为了便于说明问题。
矩阵分解:
在很多情况下,数据中的一小段携带了数据集中的大部分信息,其他信息则要么是噪声,要
么就是毫不相关的信息。在线性代数中还有很多矩阵分解技术。矩阵分解可以将原始矩阵表示成
新的易于处理的形式,这种新形式是两个或多个矩阵的乘积。我们可以将这种分解过程想象成代
数中的因子分解。如何将12分解成两个数的乘积?(1,12), (2,6)和((3,4)都是合理的答案。
不同的矩阵分解技术具有不同的性质,其中有些更适合于某个应用,有些则更适合于其他应
用。最常见的一种矩阵分解技术就是SVD。 SVD将原始的数据集矩阵Data分解成三个矩阵U, E
和VT。如果原始矩阵Data是m行n列,那么U, E和VT就分别是m行m列、m行n列和n行n列。
源码如下:
[html] view plain copy print?
- <pre name="code" class="html">#coding=utf-8
- '''
- Created on Mar 8, 2011
- @author: Peter
- 利用SVD简化数据
- 奇异值分解
- 优点:简化数据,去除噪声,提高算法的结果。
- 缺点:数据的转换可能难以理解。
- 适用数据类型:数值型数据。
- 总结:
- 推荐引擎将物品推荐给用户,协同过滤则是一种基于用户喜好或行为数据的推荐的实现方
- 法。协同过滤的核心是相似度计算方法;有很多相似度计算方法都可以用于计算物品或用户之间
- 的相似度。通过在低维空间下计算相似度,SVD提高了推荐系引擎的效果。
- 在大规模数据集上,SVD的计算和推荐可能是一个很困难的工程问题。通过离线方式来进行
- SVD分解和相似度计算,是一种减少冗余计算和推荐所需时间的办法。
- 信息检索:
- SVD最早的应用是信息检索,我们称利用SVD的方法为隐性语义索引(LSI)或者隐性语义分析(LSA)
- LSI中一个矩阵是由文档和词语组成的,当我们在矩阵上应用SVD时,会构建出多个奇异值,这些奇异值代表了文档中
- 的概念或主题这一特点可以用于更高效的文档搜索。在词语拼写错误时只基于词语存在与否的简单搜索方法会遇到问题
- 简单搜索的另一个问题就是同义词的使用。当查找一个词时,其同义词所在的文档可能并不会匹配上
- 推荐系统:
- SVD的另一个应用就是推荐系统。简单版本的推荐系统能够计算项或者人之间的相似度,更先进的方法则
- 先利用SVD从数据中构建一个主题空间,然后再在该空间下计算其相似度。
- >>> from numpy import linalg as la
- >>> U,Sigma,VT = la.svd(mat(svdRec.loadExData2()))
- >>> Sigma
- array([ 15.77075346, 11.40670395, 11.03044558, 4.84639758,
- 3.09292055, 2.58097379, 1.00413543, 0.72817072,
- 0.43800353, 0.22082113, 0.07367823])
- >>> Sig2 = Sigma**2
- >>> sum(Sig2)
- 541.99999999999955
- >>> sum(Sig2)*0.9
- 487.79999999999961
- >>> sum(Sig2[:2])
- 378.8295595113579
- >>> Sig3 = mat([[Sigma[0],0,0],[0,Sigma[1],0],[0,0,Sigma[2]]])
- >>> U[:,:3]*Sig3*VT[:3,:]
- 基于协同过滤的推荐引擎
- 相似度的计算
- 不利用专家给出的重要属性来描述物品从而计算它们之间的相似度,而是利用用户对他们的意见来计算相似度,这就是协同过滤中所使用的方法
- 不关心物品的描述属性,而是严格地按照许多用户的观点来计算相似度
- 构建推荐引擎面临的挑战
- 我们不必每次估计评分时都做svd分解,对于上述数据集。snd分解在效率上没有太大的区别,
- 在更大规模的数据集上,svd分解会降低程序的速度。svd分解可以在程序调入时运行一次。
- 在大型系统中,svd每天运行一次或者其运行频率并不高,而且要离线运行
- 推荐引擎中还存在其他更多规模扩展性的挑战问题,比如矩阵的表示方法。在系统中存在很多0,
- 可以通过只存储非零元素来节省内存和计算开销
- 另一个潜在的计算资源浪费来自于相似度得分。在程序中,每次需要一个推荐的分时,都要计算多个物品的相似度得分
- 这些得分记录的是物品间的相似度。因此在需要时这些记录可以被另一个用户重复使用。在实际中,另一个普遍的做法就是
- 离线计算并保存相似度得分
- 推荐引擎面临的另一个问题就是如何在缺乏数据时给出好的推荐,称为冷启动问题,处理起来十分困难。
- 该问题的另一种说法是,用户不喜欢一个无效的物品,而用户不喜欢的物品又无效。
- 如果推荐只是一个可有可无的功能,那么上述问题不是很大。如果应用的成功与否和推荐的成功与否密切相连,那么问题非常严重
- '''
- from numpy import *
- from numpy import linalg as la
- 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, 5, 5, 0, 0],
- [1, 1, 1, 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]]
- '''
- 2范数
- 相似度计算
- 程序中的3个函数就是上面提到的几种相似度的计算方法。为了便于理解,NumPy的线性代
- 数工具箱linalg被作为la导人,函数中假定inA和inB都是列向量。perasSim()函数会检查是否
- 存在3个或更多的点。如果不存在,该函数返回1.0,这是因为此时两个向量完全相关。
- >>> reload(svdRec)
- <module 'svdRec' from 'C:\Users\kernel\Documents\python\ch14\svdRec.py'>
- >>> myMat = mat(svdRec.loadExData())
- >>> svdRec.ecludSim(myMat[:,0],myMat[:,4])
- 0.12973190755680383
- >>> svdRec.ecludSim(myMat[:,0],myMat[:,0])
- 1.0
- >>> svdRec.cosSim(myMat[:,0],myMat[:,4])
- 0.5
- >>> svdRec.cosSim(myMat[:,0],myMat[:,0])
- 1.0
- >>> svdRec.pearsSim(myMat[:,0],myMat[:,4])
- 0.20596538173840329
- >>> svdRec.pearsSim(myMat[:,0],myMat[:,0])
- 1.0
- '''
- 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)
- '''
- 推荐未尝过的菜肴
- 推荐系统的工作过程是:给定一个用户,系统会为此用户返回N个最好的推荐菜。为了实现
- 这一点,则需要我们做到:
- (1)寻找用户没有评级的菜肴,即在用户-物品矩阵中的0值;
- (2)在用户没有评级的所有物品中,对每个物品预计一个可能的评级分数。这就是说,我们
- 认为用户可能会对物品的打分(这就是相似度计算的初衷);
- (3)对这些物品的评分从高到低进行排序,返回前N个物品。
- 上述程序包含了两个函数。第一个函数是standEst(),用来计算在给定相似度计算方法的
- 条件下,用户对物品的估计评分值。第二个函数是recommend ( ),也就是推荐引擎.,它会调用
- standESt()函数。我们先讨论。tandEst()函数,然后讨论recommend()函数。
- 函数s七andEst()的参数包括数据矩阵、用户编号、物品编号和相似度计算方法。假设这里
- 的数据矩阵为图14-1和图14-2的形式,即行对应用户、列对应物品。那么,我们首先会得到数据
- 集中的物品数目,然后对两个后面用于计算估计评分值的变量进行初始化。接着,我们遍历行中
- 的每个物品。如果某个物品评分值为0,就意味着用户没有对该物品评分,跳过了这个物品。该
- 循环大体上是对用户评过分的每个物品进行遍历,并将它和其他物品进行比较。变量。verLap
- 给出的是两个物品当中已经被评分的那个元素0。如果两者没有任何重合元素,则相似度为。且
- 中止本次循环。但是如果存在重合的物品,则基于这些重合物品计算相似度。随后,相似度会不
- 断累加,每次计算时还考虑相似度和当前用户评分的乘积。最后,通过除以所有的评分总和,对
- 上述相似度评分的乘积进行归一化。这就可以使得最后的评分值在。到5之间,而这些评分值则用
- 于对预测值进行排序。
- 函数recommend)产生了最高的N个推荐结果。如果不指定N的大小,则默认值为3。该函
- 数另外的参数还包括相似度计算方法和估计方法。我们可以使用程序清单14-1中的任意一种相似
- 度计算方法。此时我们能采用的估计方法只有一种选择,但是在下一小节中会增加另外一种选择。
- 该函数的第一件事就是对给定的用户建立一个未评分的物品列表O。如果不存在未评分物品,那
- 么就退出函数;否则,在所有的未评分物品上进行循环。对每个未评分物品,则通过调用
- 。七andEst()来产生该物品的预测得分。该物品的编号和估计得分值会放在一个元素列表
- i七emScores中。最后按照估计得分,对该列表进行排序并返回O。该列表是从大到小逆序排列
- 的,因此其第一个值就是最大值。
- 基于物品相似度的推荐引擎
- 基于物品的相似度还是基于用户的相似度
- 计算两个餐馆菜肴之间的距离,基于物品的相似度
- 计算用户距离的方法称作基于用户的相似度
- 行与行之间标胶是基于物品的相似度,列与列之间的比较是基于物品的相似度
- 基于物品相似度计算会随着物品数量的增加而增加,基于用户的相似度的计算时间
- 会随着用户数量的增加而增加。如果用户数目很多,我们可能倾向于使用基于物品相似度的计算方法
- 推荐引擎的评价:
- 如何对推荐引擎进行评价,此时既没有预测的目标值,也没有用户来调查他们对预测的满意程度
- 使用交叉测试的方法,具体做法如下:将某些已知的评分值去掉,然后对他们进行预测,最后计算
- 预测值与真实值之间的差异
- 推荐引擎评价的指标是称为最小均方根误差的指标,首先计算均方误差的平均值然后取其平方根。如果评级在1~5星范围内
- 我们得到的RMSE为1.0,那么意味着我们的预测值和用户给出的真实评价相差了一个星级
- 餐馆菜肴推荐引擎
- 构建一个基本的推荐引擎,能够找到用户没有尝过额菜肴,然后通过SVD减少特征空间并提高推荐的效果。之后,将程序打包
- 并通过可读的人际界面提供给人们使用
- '''
- #计算在给定相似度计算方法的条件下,用户对物品的估计评分值
- def standEst(dataMat, user, simMeas, item): #数据矩阵,用户编号,物品编号和相似度计算方法
- n = shape(dataMat)[1] #数据集物品数目
- simTotal = 0.0; ratSimTotal = 0.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])
- #如果两者没有任何重合元素,则相似度为0,且终止本次循环 如果存在重合的物品,则基于这些重合物品进行相似度计算
- 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 #除以所有的评分总和,对上述相似度评分的乘积进行归一化。使得最后的评分值在0~5之间,这些评分值则用于对预测值进行排序
- '''
- 基于SVD的评分估计
- 上述程序中包含有一个函数svdEst()。在recommend)中,这个函数用于替换对。tand-
- Est()的调用,该函数对给定用户给定物品构建了一个评分估计值。如果将该函数与程序清单
- 14-2中的standEst()函数进行比较,就会发现很多行代码都很相似。该函数的不同之处就在于
- 它在第3行对数据集进行了SVD分解。在SVD分解之后,我们只利用包含了90%能量值的奇异值,
- 这些奇异值会以NumPy}C组的形式得以保存。因此如果要进行矩阵运算,那么就必须要用这些奇
- 异值构建出一个对角矩阵.。然后,利用u矩阵将物品转换到低维空间中)o
- 对于给定的用户,for循环在用户对应行的所有元素上进行遍历。这和。tandEst()函数中
- 的for循环的目的一样,只不过这里的相似度计算是在低维空间下进行的。相似度的计算方法也
- 会作为一个参数传递给该函数。然后,我们对相似度求和,同时对相似度及对应评分值的乘积求
- >>> import svdRec
- >>> myMat = mat(svdRec.loadExData())
- >>> myMat[0,1] = myMat[0,0] = myMat[1,0] = myMat[2,0] = 4
- >>> myMat[3,3] = 2
- >>> myMat
- matrix([[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],
- [5, 5, 5, 0, 0],
- [1, 1, 1, 0, 0]])
- >>> svdRec.recommend(myMat,2)
- the 1 and 0 similarity is: 1.000000
- the 1 and 3 similarity is: 0.928746
- the 1 and 4 similarity is: 1.000000
- the 2 and 0 similarity is: 1.000000
- the 2 and 3 similarity is: 1.000000
- the 2 and 4 similarity is: 0.000000
- [(2, 2.5), (1, 2.0243290220056256)]
- >>> svdRec.recommend(myMat,2,simMeas = svdRec.ecludSim)
- the 1 and 0 similarity is: 1.000000
- the 1 and 3 similarity is: 0.309017
- the 1 and 4 similarity is: 0.333333
- the 2 and 0 similarity is: 1.000000
- the 2 and 3 similarity is: 0.500000
- the 2 and 4 similarity is: 0.000000
- [(2, 3.0), (1, 2.8266504712098603)]
- >>> svdRec.recommend(myMat,2,simMeas = svdRec.pearsSim)
- the 1 and 0 similarity is: 1.000000
- the 1 and 3 similarity is: 1.000000
- the 1 and 4 similarity is: 1.000000
- the 2 and 0 similarity is: 1.000000
- the 2 and 3 similarity is: 1.000000
- the 2 and 4 similarity is: 0.000000
- [(2, 2.5), (1, 2.0)]
- '''
- def svdEst(dataMat, user, simMeas, item):
- n = shape(dataMat)[1]
- simTotal = 0.0; ratSimTotal = 0.0
- U,Sigma,VT = la.svd(dataMat) #进行svd分解 分解之后只利用包含了90%能量值的奇异值,这些奇异值以numpy数组的形式得以保存
- Sig4 = mat(eye(4)*Sigma[:4]) #arrange Sig4 into a diagonal matrix 用奇异值 建立对角矩阵
- xformedItems = dataMat.T * U[:,:4] * Sig4.I #create transformed items 用U矩阵将物品转换成低维空间 构建转换后的物品
- for j in range(n): #for循环相似度计算是在低维空间下进行的 相似度的计算作为一个参数传递给该函数
- 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
- #推荐引擎,调用standEst函数 函数recommend产生了最高的n个推荐结果,如果不指定N的大小,则默认值为3 参数还包括相似度计算方法和估计方法
- 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) #对每个未评分的物品,通过调用standEst产生该物品的预测得分,该物品的编号和估计得分值放在一个元素列表itemscores中
- itemScores.append((item, estimatedScore))
- return sorted(itemScores, key=lambda jj: jj[1], reverse=True)[:N] #寻找前n个未评级的物品 按照估计得分,对该列表进行排序并返回,该列表从大到小逆序排序,第一个值就是最大值
- '''
- 基于SVD的图像压缩
- 上述程序中第一个函数printMat()的作用是打印矩阵。由于矩阵包含了浮点数,因此必须
- 定义浅色和深色。这里通过一个阑值来界定,后面也可以调节该值。该函数遍历所有的矩阵元素,
- 当元素大于阑值时打印1,否则打印。。
- '''
- def printMat(inMat, thresh=0.8): #打印矩阵,矩阵包含了浮点数,因此必须定义浅色和深色 thresh做阈值界定,遍历所有的矩阵元素,当元素大于阈值时打印1,反之打印0
- for i in range(32):
- for k in range(32):
- if float(inMat[i,k]) > thresh:
- print 1,
- else: print 0,
- print ''
- '''
- 实验图像的压缩 允许基于任意给定的奇异值 数目来重构图像,该函数构建了一个列表,然后打开文本文件,从文件中以数值方式读入字符
- 在矩阵调入之后,就可以在屏幕上输出该矩阵了。接下来就开始对原始图像进行SVD分解并重构图像。在程序中,
- 通过将sigma重新否成sigrecon来实现这一点,digma是一个对角矩阵。因此需要建立一个全0矩阵
- 然后将前面的那些奇异值填充到对角线上。最后,通过阶段的U和V^2矩阵,用sigrecon得到重构后的矩阵,该矩阵通过printmat函数输出
- '''
- def imgCompress(numSV=3, thresh=0.8):
- myl = []
- for line in open('0_5.txt').readlines():
- newRow = []
- for i in range(32):
- newRow.append(int(line[i]))
- myl.append(newRow)
- myMat = mat(myl)
- print "****original matrix******"
- printMat(myMat, thresh)
- U,Sigma,VT = la.svd(myMat)
- SigRecon = mat(zeros((numSV, numSV)))
- for k in range(numSV):#construct diagonal matrix from vector
- SigRecon[k,k] = Sigma[k]
- reconMat = U[:,:numSV]*SigRecon*VT[:numSV,:]
- print "****reconstructed matrix using %d singular values******" % numSV
- printMat(reconMat, thresh)
<pre name="code" class="html">#coding=utf-8
'''
Created on Mar 8, 2011
@author: Peter
利用SVD简化数据
奇异值分解
优点:简化数据,去除噪声,提高算法的结果。
缺点:数据的转换可能难以理解。
适用数据类型:数值型数据。
总结:
推荐引擎将物品推荐给用户,协同过滤则是一种基于用户喜好或行为数据的推荐的实现方
法。协同过滤的核心是相似度计算方法;有很多相似度计算方法都可以用于计算物品或用户之间
的相似度。通过在低维空间下计算相似度,SVD提高了推荐系引擎的效果。
在大规模数据集上,SVD的计算和推荐可能是一个很困难的工程问题。通过离线方式来进行
SVD分解和相似度计算,是一种减少冗余计算和推荐所需时间的办法。
信息检索:
SVD最早的应用是信息检索,我们称利用SVD的方法为隐性语义索引(LSI)或者隐性语义分析(LSA)
LSI中一个矩阵是由文档和词语组成的,当我们在矩阵上应用SVD时,会构建出多个奇异值,这些奇异值代表了文档中
的概念或主题这一特点可以用于更高效的文档搜索。在词语拼写错误时只基于词语存在与否的简单搜索方法会遇到问题
简单搜索的另一个问题就是同义词的使用。当查找一个词时,其同义词所在的文档可能并不会匹配上
推荐系统:
SVD的另一个应用就是推荐系统。简单版本的推荐系统能够计算项或者人之间的相似度,更先进的方法则
先利用SVD从数据中构建一个主题空间,然后再在该空间下计算其相似度。
>>> from numpy import linalg as la
>>> U,Sigma,VT = la.svd(mat(svdRec.loadExData2()))
>>> Sigma
array([ 15.77075346, 11.40670395, 11.03044558, 4.84639758,
3.09292055, 2.58097379, 1.00413543, 0.72817072,
0.43800353, 0.22082113, 0.07367823])
>>> Sig2 = Sigma**2
>>> sum(Sig2)
541.99999999999955
>>> sum(Sig2)*0.9
487.79999999999961
>>> sum(Sig2[:2])
378.8295595113579
>>> Sig3 = mat([[Sigma[0],0,0],[0,Sigma[1],0],[0,0,Sigma[2]]])
>>> U[:,:3]*Sig3*VT[:3,:]
基于协同过滤的推荐引擎
相似度的计算
不利用专家给出的重要属性来描述物品从而计算它们之间的相似度,而是利用用户对他们的意见来计算相似度,这就是协同过滤中所使用的方法
不关心物品的描述属性,而是严格地按照许多用户的观点来计算相似度
构建推荐引擎面临的挑战
我们不必每次估计评分时都做svd分解,对于上述数据集。snd分解在效率上没有太大的区别,
在更大规模的数据集上,svd分解会降低程序的速度。svd分解可以在程序调入时运行一次。
在大型系统中,svd每天运行一次或者其运行频率并不高,而且要离线运行
推荐引擎中还存在其他更多规模扩展性的挑战问题,比如矩阵的表示方法。在系统中存在很多0,
可以通过只存储非零元素来节省内存和计算开销
另一个潜在的计算资源浪费来自于相似度得分。在程序中,每次需要一个推荐的分时,都要计算多个物品的相似度得分
这些得分记录的是物品间的相似度。因此在需要时这些记录可以被另一个用户重复使用。在实际中,另一个普遍的做法就是
离线计算并保存相似度得分
推荐引擎面临的另一个问题就是如何在缺乏数据时给出好的推荐,称为冷启动问题,处理起来十分困难。
该问题的另一种说法是,用户不喜欢一个无效的物品,而用户不喜欢的物品又无效。
如果推荐只是一个可有可无的功能,那么上述问题不是很大。如果应用的成功与否和推荐的成功与否密切相连,那么问题非常严重
'''
from numpy import *
from numpy import linalg as la
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, 5, 5, 0, 0],
[1, 1, 1, 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]]
'''
2范数
相似度计算
程序中的3个函数就是上面提到的几种相似度的计算方法。为了便于理解,NumPy的线性代
数工具箱linalg被作为la导人,函数中假定inA和inB都是列向量。perasSim()函数会检查是否
存在3个或更多的点。如果不存在,该函数返回1.0,这是因为此时两个向量完全相关。
>>> reload(svdRec)
<module 'svdRec' from 'C:\Users\kernel\Documents\python\ch14\svdRec.py'>
>>> myMat = mat(svdRec.loadExData())
>>> svdRec.ecludSim(myMat[:,0],myMat[:,4])
0.12973190755680383
>>> svdRec.ecludSim(myMat[:,0],myMat[:,0])
1.0
>>> svdRec.cosSim(myMat[:,0],myMat[:,4])
0.5
>>> svdRec.cosSim(myMat[:,0],myMat[:,0])
1.0
>>> svdRec.pearsSim(myMat[:,0],myMat[:,4])
0.20596538173840329
>>> svdRec.pearsSim(myMat[:,0],myMat[:,0])
1.0
'''
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)
'''
推荐未尝过的菜肴
推荐系统的工作过程是:给定一个用户,系统会为此用户返回N个最好的推荐菜。为了实现
这一点,则需要我们做到:
(1)寻找用户没有评级的菜肴,即在用户-物品矩阵中的0值;
(2)在用户没有评级的所有物品中,对每个物品预计一个可能的评级分数。这就是说,我们
认为用户可能会对物品的打分(这就是相似度计算的初衷);
(3)对这些物品的评分从高到低进行排序,返回前N个物品。
上述程序包含了两个函数。第一个函数是standEst(),用来计算在给定相似度计算方法的
条件下,用户对物品的估计评分值。第二个函数是recommend ( ),也就是推荐引擎.,它会调用
standESt()函数。我们先讨论。tandEst()函数,然后讨论recommend()函数。
函数s七andEst()的参数包括数据矩阵、用户编号、物品编号和相似度计算方法。假设这里
的数据矩阵为图14-1和图14-2的形式,即行对应用户、列对应物品。那么,我们首先会得到数据
集中的物品数目,然后对两个后面用于计算估计评分值的变量进行初始化。接着,我们遍历行中
的每个物品。如果某个物品评分值为0,就意味着用户没有对该物品评分,跳过了这个物品。该
循环大体上是对用户评过分的每个物品进行遍历,并将它和其他物品进行比较。变量。verLap
给出的是两个物品当中已经被评分的那个元素0。如果两者没有任何重合元素,则相似度为。且
中止本次循环。但是如果存在重合的物品,则基于这些重合物品计算相似度。随后,相似度会不
断累加,每次计算时还考虑相似度和当前用户评分的乘积。最后,通过除以所有的评分总和,对
上述相似度评分的乘积进行归一化。这就可以使得最后的评分值在。到5之间,而这些评分值则用
于对预测值进行排序。
函数recommend)产生了最高的N个推荐结果。如果不指定N的大小,则默认值为3。该函
数另外的参数还包括相似度计算方法和估计方法。我们可以使用程序清单14-1中的任意一种相似
度计算方法。此时我们能采用的估计方法只有一种选择,但是在下一小节中会增加另外一种选择。
该函数的第一件事就是对给定的用户建立一个未评分的物品列表O。如果不存在未评分物品,那
么就退出函数;否则,在所有的未评分物品上进行循环。对每个未评分物品,则通过调用
。七andEst()来产生该物品的预测得分。该物品的编号和估计得分值会放在一个元素列表
i七emScores中。最后按照估计得分,对该列表进行排序并返回O。该列表是从大到小逆序排列
的,因此其第一个值就是最大值。
基于物品相似度的推荐引擎
基于物品的相似度还是基于用户的相似度
计算两个餐馆菜肴之间的距离,基于物品的相似度
计算用户距离的方法称作基于用户的相似度
行与行之间标胶是基于物品的相似度,列与列之间的比较是基于物品的相似度
基于物品相似度计算会随着物品数量的增加而增加,基于用户的相似度的计算时间
会随着用户数量的增加而增加。如果用户数目很多,我们可能倾向于使用基于物品相似度的计算方法
推荐引擎的评价:
如何对推荐引擎进行评价,此时既没有预测的目标值,也没有用户来调查他们对预测的满意程度
使用交叉测试的方法,具体做法如下:将某些已知的评分值去掉,然后对他们进行预测,最后计算
预测值与真实值之间的差异
推荐引擎评价的指标是称为最小均方根误差的指标,首先计算均方误差的平均值然后取其平方根。如果评级在1~5星范围内
我们得到的RMSE为1.0,那么意味着我们的预测值和用户给出的真实评价相差了一个星级
餐馆菜肴推荐引擎
构建一个基本的推荐引擎,能够找到用户没有尝过额菜肴,然后通过SVD减少特征空间并提高推荐的效果。之后,将程序打包
并通过可读的人际界面提供给人们使用
'''
#计算在给定相似度计算方法的条件下,用户对物品的估计评分值
def standEst(dataMat, user, simMeas, item): #数据矩阵,用户编号,物品编号和相似度计算方法
n = shape(dataMat)[1] #数据集物品数目
simTotal = 0.0; ratSimTotal = 0.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])
#如果两者没有任何重合元素,则相似度为0,且终止本次循环 如果存在重合的物品,则基于这些重合物品进行相似度计算
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 #除以所有的评分总和,对上述相似度评分的乘积进行归一化。使得最后的评分值在0~5之间,这些评分值则用于对预测值进行排序
'''
基于SVD的评分估计
上述程序中包含有一个函数svdEst()。在recommend)中,这个函数用于替换对。tand-
Est()的调用,该函数对给定用户给定物品构建了一个评分估计值。如果将该函数与程序清单
14-2中的standEst()函数进行比较,就会发现很多行代码都很相似。该函数的不同之处就在于
它在第3行对数据集进行了SVD分解。在SVD分解之后,我们只利用包含了90%能量值的奇异值,
这些奇异值会以NumPy}C组的形式得以保存。因此如果要进行矩阵运算,那么就必须要用这些奇
异值构建出一个对角矩阵.。然后,利用u矩阵将物品转换到低维空间中)o
对于给定的用户,for循环在用户对应行的所有元素上进行遍历。这和。tandEst()函数中
的for循环的目的一样,只不过这里的相似度计算是在低维空间下进行的。相似度的计算方法也
会作为一个参数传递给该函数。然后,我们对相似度求和,同时对相似度及对应评分值的乘积求
>>> import svdRec
>>> myMat = mat(svdRec.loadExData())
>>> myMat[0,1] = myMat[0,0] = myMat[1,0] = myMat[2,0] = 4
>>> myMat[3,3] = 2
>>> myMat
matrix([[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],
[5, 5, 5, 0, 0],
[1, 1, 1, 0, 0]])
>>> svdRec.recommend(myMat,2)
the 1 and 0 similarity is: 1.000000
the 1 and 3 similarity is: 0.928746
the 1 and 4 similarity is: 1.000000
the 2 and 0 similarity is: 1.000000
the 2 and 3 similarity is: 1.000000
the 2 and 4 similarity is: 0.000000
[(2, 2.5), (1, 2.0243290220056256)]
>>> svdRec.recommend(myMat,2,simMeas = svdRec.ecludSim)
the 1 and 0 similarity is: 1.000000
the 1 and 3 similarity is: 0.309017
the 1 and 4 similarity is: 0.333333
the 2 and 0 similarity is: 1.000000
the 2 and 3 similarity is: 0.500000
the 2 and 4 similarity is: 0.000000
[(2, 3.0), (1, 2.8266504712098603)]
>>> svdRec.recommend(myMat,2,simMeas = svdRec.pearsSim)
the 1 and 0 similarity is: 1.000000
the 1 and 3 similarity is: 1.000000
the 1 and 4 similarity is: 1.000000
the 2 and 0 similarity is: 1.000000
the 2 and 3 similarity is: 1.000000
the 2 and 4 similarity is: 0.000000
[(2, 2.5), (1, 2.0)]
'''
def svdEst(dataMat, user, simMeas, item):
n = shape(dataMat)[1]
simTotal = 0.0; ratSimTotal = 0.0
U,Sigma,VT = la.svd(dataMat) #进行svd分解 分解之后只利用包含了90%能量值的奇异值,这些奇异值以numpy数组的形式得以保存
Sig4 = mat(eye(4)*Sigma[:4]) #arrange Sig4 into a diagonal matrix 用奇异值 建立对角矩阵
xformedItems = dataMat.T * U[:,:4] * Sig4.I #create transformed items 用U矩阵将物品转换成低维空间 构建转换后的物品
for j in range(n): #for循环相似度计算是在低维空间下进行的 相似度的计算作为一个参数传递给该函数
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
#推荐引擎,调用standEst函数 函数recommend产生了最高的n个推荐结果,如果不指定N的大小,则默认值为3 参数还包括相似度计算方法和估计方法
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) #对每个未评分的物品,通过调用standEst产生该物品的预测得分,该物品的编号和估计得分值放在一个元素列表itemscores中
itemScores.append((item, estimatedScore))
return sorted(itemScores, key=lambda jj: jj[1], reverse=True)[:N] #寻找前n个未评级的物品 按照估计得分,对该列表进行排序并返回,该列表从大到小逆序排序,第一个值就是最大值
'''
基于SVD的图像压缩
上述程序中第一个函数printMat()的作用是打印矩阵。由于矩阵包含了浮点数,因此必须
定义浅色和深色。这里通过一个阑值来界定,后面也可以调节该值。该函数遍历所有的矩阵元素,
当元素大于阑值时打印1,否则打印。。
'''
def printMat(inMat, thresh=0.8): #打印矩阵,矩阵包含了浮点数,因此必须定义浅色和深色 thresh做阈值界定,遍历所有的矩阵元素,当元素大于阈值时打印1,反之打印0
for i in range(32):
for k in range(32):
if float(inMat[i,k]) > thresh:
print 1,
else: print 0,
print ''
'''
实验图像的压缩 允许基于任意给定的奇异值 数目来重构图像,该函数构建了一个列表,然后打开文本文件,从文件中以数值方式读入字符
在矩阵调入之后,就可以在屏幕上输出该矩阵了。接下来就开始对原始图像进行SVD分解并重构图像。在程序中,
通过将sigma重新否成sigrecon来实现这一点,digma是一个对角矩阵。因此需要建立一个全0矩阵
然后将前面的那些奇异值填充到对角线上。最后,通过阶段的U和V^2矩阵,用sigrecon得到重构后的矩阵,该矩阵通过printmat函数输出
'''
def imgCompress(numSV=3, thresh=0.8):
myl = []
for line in open('0_5.txt').readlines():
newRow = []
for i in range(32):
newRow.append(int(line[i]))
myl.append(newRow)
myMat = mat(myl)
print "****original matrix******"
printMat(myMat, thresh)
U,Sigma,VT = la.svd(myMat)
SigRecon = mat(zeros((numSV, numSV)))
for k in range(numSV):#construct diagonal matrix from vector
SigRecon[k,k] = Sigma[k]
reconMat = U[:,:numSV]*SigRecon*VT[:numSV,:]
print "****reconstructed matrix using %d singular values******" % numSV
printMat(reconMat, thresh)
推荐引擎将物品推荐给用户,协同过滤则是一种基于用户喜好或行为数据的推荐的实现方
法。协同过滤的核心是相似度计算方法;有很多相似度计算方法都可以用于计算物品或用户之间
的相似度。通过在低维空间下计算相似度,SVD提高了推荐系引擎的效果。
在大规模数据集上,SVD的计算和推荐可能是一个很困难的工程问题。通过离线方式来进行
SVD分解和相似度计算,是一种减少冗余计算和推荐所需时间的办法。