欢迎关注我的个人博客blog.timene.com
打算从这篇开始,一边学习一边写些数据挖掘的东西,主要是督促自己学习和总结。
我最开始的网购是从china-pub买了一本《Unix/Linux编程实践教程》,书好,便宜,并且可以货到付款,很是吸引我这种懒穷学生,于是一发不可收拾买了很多书,后来转战dangdang,再后来就是amazon,现在基本都在jd买了,除了书,还会在yihaodian买一些日用品。后来发现这几家都会有推荐,dangdang和china-pub的推荐没什么印象,jd的推荐离我的兴趣点差的挺远,印象深刻的就是amazon,有次推送的邮件真是推到我心坎坎了。这次我也来做一个木偶推荐系统玩玩。
好吧,今晚闲来无聊,想找本书来消遣。现在书籍甚是丰富,买哪本书呢?
1)我会打电话问问朋友,请求推荐;
2)还会登陆网站看畅销top100,选一本自己感兴趣的;
慢慢就会发现问题,第一:朋友推荐和top100的书籍比较稳定,新意不多,看完了想看新的就没了,第二:这里面很合自己脾气的书不多,自己很厌倦太巨头或太学府派的书,还有就是现在的top100很多孕育和养生的东东,目前我就先忍忍吧。
好了,既然目前都在说大数据,怎么用大数据进行推荐呢?我首先想到的是,我喜欢《Unix/Linux编程实践教程》,和这本内容写作风格近似的也应该是我喜欢的,我喜欢packt出版社的一本书,可能这个出版社出版的其他书我也喜欢(事实也是我非常喜欢这个出版社的书),我喜欢《Think in C++》,我可能还会喜欢bruce eckel出的其他书(事实也是这样)。还有一种情况,我是码农,我有个码农朋友,我两兴趣爱好相投,他喜欢的书可能也是我喜欢的书,我喜欢的书也可能是他喜欢的书(事实也是这样)。好,总结一下,第一种基于物品的推荐,我买了A,很可能会喜欢和A相似的B,第二种基于用户推荐,我和C志趣相投,他喜欢D,我可能也喜欢D。
我们先来说说评价,最先想到的评价有两种,买与不买(1和0);买了打几颗星(通常满星是五颗,0,1,2,3,4,5),还有容许打半颗星的,这样就有(0, 0.5,1,1.5,2,2.5 。。。5)。下面我们用0--5之间的小数表示评价,值越打表示评价越高,比如,我喜欢A,我给他5星,一般喜欢B,我给他3.8星,我很讨厌C,我给他0星;于是有了下面一个变量ctitics:
critics = { 'user1': {'goods1': 2.5, 'goods2': 3.5, 'goods3': 3.0, 'goods4': 3.5, 'goods5': 2.5, 'goods6': 3.0}, 'user2': {'goods1': 3.0, 'goods2': 3.5, 'goods3': 1.5, 'goods4': 5.0, 'goods6': 3.0, 'goods5': 3.5}, 'user3': {'goods1': 2.5, 'goods2': 3.0, 'goods4': 3.5, 'goods6': 4.0}, 'user4': {'goods2': 3.5, 'goods3': 3.0, 'goods6': 4.5, 'goods4': 4.0, 'goods5': 2.5}, 'user5': {'goods1': 3.0, 'goods2': 4.0, 'goods3': 2.0, 'goods4': 3.0, 'goods6': 3.0, 'goods5': 2.0}, 'user6': {'goods1': 3.0, 'goods2': 4.0, 'goods6': 3.0, 'goods4': 5.0, 'goods5': 3.5}, 'user7': {'goods2': 4.5, 'goods5': 1.0, 'goods4': 4.0} }
下面来定义“相似”,“相似”就是在某些方面很接近,怎么考量相似呢,对上面的变量ctitics,怎么判断user1和那个其他user*相似,然后给出推荐呢,首先想到的是欧几里得距离评价,这就是最简单的求两点距离的公式,
下面来计算任意两个用户之间的“相似度”:
from math import sqrt def sim_distance(prefs,person1,person2): si={} for item in prefs[person1]: if item in prefs[person2]: si[item]=1 if len(si)==0: return 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))
好吧,我们中总会有一些人很挑剔,总有一些人不那么挑剔,这些挑剔的人总趋向于给出整体偏低的评价(3,2,1),有些不那么挑剔的人总趋向与给出整体偏高的评价(5,4,3),但是这两种人的整体偏好相似,都会给goods1一个在他们认为较高的星星数(3和5),都会给goods2一个在他们认为较差的星星数(1和3)。这种情况下,用欧几里得距离计算的结果就不那么具有竞争力了,如何修正这种偏差呢?我们来看看皮尔逊相关度评价。
下面来计算用户之间的皮尔逊相关度:
def sim_pearson(prefs,p1,p2): si={} for item in prefs[p1]: if item in prefs[p2]: si[item]=1 if len(si)==0: return 0 n=len(si) 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)) if den==0: return 0 r=num/den return r
下面我们就可以判断任意用户的相似度了:
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]注意topMatches的最后一个参数是一个函数名,是我们上面说的计算相似度的任意一种方法,只要他们有相同的函数签名即可,这样我们可以随时换用我们想用的相似度计算方式。
>>> import test >>> test.topMatches(test.critics, 'user1') [(0.9912407071619299, 'user7'), (0.7470178808339965, 'user6'), (0.5940885257860044, 'user5'), (0.5669467095138396, 'user4'), (0.40451991747794525, 'user3')] >>> test.topMatches(test.critics, 'user2') [(0.963795681875635, 'user6'), (0.41176470588235276, 'user5'), (0.39605901719066977, 'user1'), (0.38124642583151164, 'user7'), (0.31497039417435607, 'user4')] >>> test.topMatches(test.critics, 'user3') [(1.0, 'user4'), (0.40451991747794525, 'user1'), (0.20459830184114206, 'user2'), (0.13483997249264842, 'user6'), (-0.2581988897471611, 'user5')] >>> test.topMatches(test.critics, 'user4') [(1.0, 'user3'), (0.8934051474415647, 'user7'), (0.5669467095138411, 'user5'), (0.5669467095138396, 'user1'), (0.31497039417435607, 'user2')] >>> test.topMatches(test.critics, 'user5') [(0.9244734516419049, 'user7'), (0.5940885257860044, 'user1'), (0.5669467095138411, 'user4'), (0.41176470588235276, 'user2'), (0.21128856368212925, 'user6')] >>> test.topMatches(test.critics, 'user6') [(0.963795681875635, 'user2'), (0.7470178808339965, 'user1'), (0.66284898035987, 'user7'), (0.21128856368212925, 'user5'), (0.13483997249264842, 'user3')] >>> test.topMatches(test.critics, 'user7') [(0.9912407071619299, 'user1'), (0.9244734516419049, 'user5'), (0.8934051474415647, 'user4'), (0.66284898035987, 'user6'), (0.38124642583151164, 'user2')]
好了,终于找到了臭味相投的同志,给出一个推荐呢?
(1)我们可以从志投道和的user中,找一个他评价很高而我们没有看过的一个goods,
(2)我们在所有志同道合的user中,用相似度和评价的一个加权评价值来给googs打分,从而形成一个排名进而推荐。
很显然,我们采用第二种,为此,我们需要取得其他评论者相似度后,再乘以评论者为每个goods的评价值,就会得到我们想要的排名。
这中间会有一个问题,就是如果一个goods的评论user很多,那么最终排名会比较靠前,而较少评论的goods最最终的影响会比较小,通常这样没什么问题,我们在这里稍微做一些修正,就是用对这个goods评价的所有其他users的相似度之和除以最终的排名值,这样会公平一些,哈哈哈,来看代码:
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 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
>>> import test >>> test.getRecommendations(test.critics, 'user1') [] >>> test.getRecommendations(test.critics, 'user2') [] >>> test.getRecommendations(test.critics, 'user3') [(2.8092760065251268, 'goods3'), (2.694636703980363, 'goods5')] >>> test.getRecommendations(test.critics, 'user4') [(2.683756272799255, 'goods1')] >>> test.getRecommendations(test.critics, 'user5') [] >>> test.getRecommendations(test.critics, 'user6') [(2.1505590044630245, 'goods3')] >>> test.getRecommendations(test.critics, 'user7') [(3.3477895267131013, 'goods6'), (2.832549918264162, 'goods1'), (2.5309807037655645, 'goods3')]
上面我们不仅计算出了相似度,还给出了推荐。值得一提的是,计算相似度不仅可以计算user之间的相似度,还可以计算goods之间的相似度,根据user已买的物品推荐相似度较高的物品在jd和amazon也很常见。