本书以机器学习与计算统计为主题背景,专门讲述如何挖掘和分析web上的数据和资源并得出有用的结论。本书中的代码以Python 语言编写,介绍了协作过滤技术(实现关联产品的推荐功能)、集群数据分析(在大规模数据集中发掘相似的数据子集)、搜索引擎核心技术(爬虫、索引、查询引擎、PageRank算法等)、优化算法、贝叶斯过滤技术(文本过滤、垃圾邮件过滤)、用决策树实现预测和决策建模功能、社交网络的信息匹配技术、机器学习和人工智能应用等。
本人系信息管理与信息系统专业的本科大三学生,具有一定的相关知识背景,但是在此之前并未使用过python进行编程,缺乏相关经验,如果本文中出现部分错误,请多多指教。
(如果你和我一样此前没有接触过Python,可以参考一下下面的链接来帮助你更好地理解python的语法,和在编写python程序中出现的错误:菜鸟教程,python运行常见错误)
协作型过滤算法
在购物网站、音乐网站上你会很常见到它的身影,协作型过滤算法就是对一大群人进行搜索然后找到跟我们兴趣相近的一小群人,算法会对这些人所偏爱的其他内容进行考察,并将他们组合起来构造一个经过排名的推荐列表,简单来说就是一个推荐算法。
基于用户的过滤
第一步:搜集偏好(构造字典)
我们新建一个名为recommendations.py的文件,里面存放几位影评者对不同电影的评分情况:
critics={
'Lisa':{'Lady in the water':2.5, 'Snakes on a Plane':3.5, 'Just My Luck':3.0,
'Superman Returns':3.5, 'You, Me and Dupree':2.5, 'The Light Listener':3.0 },
'Mike':{'Lady in the water':3.0, 'Snakes on a Plane':3.5, 'Just My Luck':1.5,
'Superman Returns':5.0, 'You, Me and Dupree':3.0, 'The Light Listener':3.5 },
'Rose':{'Lady in the water':2.5, 'Snakes on a Plane':3.0,
'Superman Returns':3.5, 'The Light Listener':4.0 },
'Lily':{'Snakes on a Plane':3.5, 'Just My Luck':3.0,
'Superman Returns':4.0, 'You, Me and Dupree':2.5, 'The Light Listener':4.5 },
'Tom':{'Lady in the water':3.0, 'Snakes on a Plane':4.0, 'Just My Luck':2.0,
'Superman Returns':3.0, 'You, Me and Dupree':2.0, 'The Light Listener':3.0 },
'Jack':{'Lady in the water':3.0, 'Snakes on a Plane':4.0, 'Just My Luck':3.0,
'Superman Returns':5.0, 'You, Me and Dupree':3.5, 'The Light Listener':3.0 },
'Floria':{'Snakes on a Plane':4.5, 'Superman Returns':4.0, 'You, Me and Dupree':1.0,},
}
通过上述代码我们可以查看字典内的数据并修改字典内的数据
第二步 寻找相近用户(计算相似度评价值)
这里介绍两种计算相似度的方法:欧几里得距离和pearson相关度(当然还有很多其他计算相似度的算法可以参考:http://blog.csdn.net/zy825316/article/details/19129531,个人觉得这个博主写的文章都可以看一看)
欧几里得距离
欧几里得距离就是坐标系中两个点的直线距离,用数学公式表示就是
代码如下:
from math import sqrt
def sim_distance(prefs,person1,person2):
#计算两个人的相似度
si={}
#建立一个字典,里面记载两个人共同评价的电影的情况
for item in prefs[person1]:
if item in prefs[person2]:
si[item]=1
#如果两个人都有对这部电影做出过评价,则该电影取值为1
if len(si)==0:
return 0
#如果两个人没有共同评价过的电影,则返回0
sum_of_squares=sum([pow(prefs[person1][item]-prefs[person2][item],2)
for item in prefs[person1] if item in prefs[person2]])
#计算差值的平方和
return 1/(1+sqrt(sum_of_squares))
print recommendations.sim_distance(recommendations.critics,'Lisa','Tom')
Pearson相关度
下面介绍的是皮尔逊相关度评价。用图里讲是非常清晰的,我们用某两个用户作为x、y轴,然后以用户对电影的评分,画出每一点来。图中superman电影的坐标为(3.0,5.0),这是因为用户Gene Seymour对其的评分为5,而mick lasalle的评分为3.考虑所有的点:我们画一条线,叫最佳拟合线:画的原则是尽可能地靠近图中所有的坐标点。就是上图的线,试想一种情况就是两个用户的评分完成一样,我们将得到一条线:所有点都在线上,而且线的角度是45度。这时,两个用户的兴趣完全相同,我们称之为1,我想别的拟合线与之对比即可看出差距吧。
我来看一种情况较好的时候:上图的相关系数为0.4,下图为0.75
Pearson相关系数的数学公式
代码:
def sim_pearson(prefs,p1,p2):
#计算pearson相关系数
si={}
#建立一个字典,里面记载两个人共同评价的电影的情况
for item in prefs[p1]:
if item in prefs[p2]: si[item]=1
#如果两个人都有对这部电影做出过评价,则该电影取值为1
n=len(si)
if n==0: return 0
#如果两个人没有共同评价过的电影,则返回0
sum1=sum([prefs[p1][it] for it in si])
sum2=sum([prefs[p2][it] for it in si])
#求和
sum1Sq=sum([pow(prefs[p1][it],2) for it in si])
sum2Sq=sum([pow(prefs[p2][it],2) for it in si])
#求平方和
pSum=sum([prefs[p1][it]*prefs[p2][it] for it in si])
#求乘积之和
num=pSum-(sum1*sum2/n)
den=sqrt((sum1Sq-pow(sum1,2)/n)*(sum2Sq-pow(sum2,2)/n))
#计算Pearson相关系数
if den==0: return 0
r=num/den
return r
print recommendations.sim_pearson(recommendations.critics,'Lisa','Tom')
第三步:找寻最佳匹配结果
def topMatches(prefs,person,n=5,similarity=sim_pearson):
scores=[(similarity(prefs,person,other),other) for other in prefs if other!=person]
#循环比较自己与他人的相似度
scores.sort()
#结果排序
scores.reverse()
#结果反转
return scores[0:n]
#输出最终排名靠前的前n个结果
print recommendations.topMatches(recommendations.critics,'Lisa',n=3)
第四步:推荐物品
尽管上面我们可以获得与某人具有最相似品味的其他个体信息,但这并不是我们想要的最终结果,下面我们将介绍如何推荐电影
我们会采用一种利用加权的方式来为目标用户没有看过的电影的预测打分。加权的意思是指:无论与目标用户的相似度低还是高,都会影响着我们预测目标用户没看过的电影的分数,只是权重不一样而已。如下图所示:
最后一排中的3.35即是为Toby没看过的电影Night的预测得分。具体来的就是上两排的12.89处于3.84。12.89的来历是将所有看过该电影的用户的打分乘以与toby的相似度的和。而3.84仅仅就是所有相似用户的相似度的总和。我们可以看到对于电影Lady,由于puig没有看过,所以它会对预测toby的分有任何影响。这就是说:相似度越高的用户的评分越能影响着预测目标用户对某个电影的打分。
代码如下:
def getRecommendations(prefs,person,similarity=sim_pearson):
totals={}
simSums={}
for other in prefs:
if other==person: continue
#不用和自己比较
sim=similarity(prefs,person,other)
if sim<=0: continue
#忽略相似度小于0的情况
for item in prefs[other]:
if item not in prefs[person] or prefs[person][item]==0:
#只对自己未看过的电影进行推荐
totals.setdefault(item,0)
totals[item]+=prefs[other][item]*sim
#相似度*评价值
simSums.setdefault(item,0)
simSums[item]+=sim
#相似度之和
rankings=[(total/simSums[item],item) for item,total in totals.items()]
#得到最终权重列表
rankings.sort()
rankings.reverse()
return rankings
#返回经过排序的列表
匹配商品
上面我们已经知道了如何寻找和自己品味相近的评论者和为自己推荐电影,现在我们可以延续这一思想,寻找与某一电影相近的电影,这需要进行思维的转变。比如电影A给用户x打了4分,电影A又给用户y打了3分,结果电影B给用户x打了4分,电影B又给用户y打了3分。好吧,我们现在就说这个电影A和电影B相似度百分百。因为它们两个对用户的适应度一模一样。实现这一过程只需要将物品和人员的信息对调,然后继续调用topMatches的方法即可得到结果。
def transformPrefs(prefs):
result={}
for person in prefs:
for item in prefs[person]:
result.setdefault(item,{})
#将物品 和人员进行对调
result[item][person]=prefs[person][item]
return result
同样的,我们也可以调用getRecommendations的方法来为电影推荐评论者。
构建一个基于del.icio.us的推荐系统
(由于书中代码错误,未能成功构建)
基于物品的过滤
我们谈论的都是基于用户的协同过滤,他有一个缺点,就是当将用户与其他用户进行比较时,产生的计算时间也许会非常长。而且如果在一个物品特别多的情况下,也许会很难找出相似用户。在数据集非常多的情况,基于物品的协同过滤表现更好。这样做有2个好处:大量计算预先进行(计算物品相似度)、更快给出用户推荐的结果。
这一比较过程的总体思路是:为每件物品预先计算好相似的物品,当我们为每位用户做出推荐时可以查看其曾经评分过的物品,从中选出排位靠前者,构造出一个加权列表,其中包含了与选中物品相近的其他物品。
第一步:构造物品比较数据集
为了对物品进行比较,我们需要构造一个包含相近物品的完整数据集,这项工作不需要每次提供推荐时都做一遍,相反,都见完成后我们就以反复使用它。
def calculateSimilarItems(prefs,n=10):
#建立字典,给出与这些物品相近的其他物品
result={}
itemPrefs=transformPrefs(prefs)
#把用户对物品的评价改为物品对用户的适应度
c=0
for item in itemPrefs:
c+=1
if c%100==0:print "%d / %d" % {c,len(itemPrefs)}
#针对大数据集更新状态变量
scores=topMatches(itemPrefs,item,n=n,similarity=sim_distance)
result[item]=scores
#寻找相近物品
return result
print recommendations.calculateSimilarItems(recommendations.critics)
第二步:获得推荐
这与我们之前做的基于用户的过滤中获得推荐物品的思想一致
其中,3.183是预测toby对Night电影的打分(或者电影对toby的适应度),是由1.378/0.433产生的,加了权重的分的总和,除以相似度就可以得到预测的分。本来是分的总和除以个数现在改为了加权的分的总和除以加权的个数。0.433是所有的相似度相加,1.378是由toby对不同电影打分(电影对toby的适应度)乘以相似度之后的一个数,再把对所有已经评价的电影的这个数相加。
代码如下:
def getRecommendedItems(prefs,itemSim,user):
userRatings=prefs[user]
scores={}
totalSim={}
#循环遍历由当前用户评分的物品
for (item,rating) in userRatings.items():
#循环遍历与当前物品相近的物品
for (similarity,item2) in itemSim[item]:
#如果该用户已经对当前物品做过评价,则将其忽略
if item2 in userRatings:continue
#打分和相似度的加权之和
scores.setdefault(item2,0)
scores[item2]+=similarity*rating
#某一电影的与其他电影的相似度之和
totalSim.setdefault(item2,0)
totalSim[item2]+=similarity
#将经过加权的评分除以相似度,求出对这一电影的评分
rankings=[(score/totalSim[item],item) for item,score in scores.items()]
#排序后转换
rankings.sort()
rankings.reverse()
return rankings
print getRecommendedItems(critics,calculateSimilarItems(critics),'Toby')
基于用户、基于物品的过滤方案的评价
最后分析一下对于基于用户的协同过滤和基于物品的协同过滤的选择问题。
1. 生成推荐列表时,基于物品的方式比基于用户的方式速度更快,维护物品相似度表有着额外的开销
2. 在准确率上
· 对于稀疏数据集,基于物品的方式要优于基于用户的方式
· 对于密集数据集,两者效果几乎一样
关于是什么是密集什么是稀疏,我现在的理解就是。电影与评论电影的人相比,明显电影少,人多,所以每个用户都几乎对每一个电影做了评价,这就是密集型的。而书签多,用户少,大部分书签都被少量用户收集,这就是稀疏。