Python实现个性化推荐二

基于内容的推荐引擎是怎么工作的

基于内容的推荐系统,正如你的朋友和同事预期的那样,会考虑商品的实际属性,比如商品描述,商品名,价格等等。如果你以前从没接触过推荐系统,然后现在有人拿枪指着你的头,强迫你在三十秒之内描述出来,你可能会描述这样一个基于内容的系统:呃,呃,我可能会给你看一大堆来自同一个厂家,并且拥有类似的说明的产品。

你正在利用商品本身的属性来推荐类似的商品。这样做非常合理,因为这就是我们在真实世界中买东西的方式。我们去卖烤箱的那一排货架,然后看这些烤箱,它们可能根据不同的品牌,价格,或着能在30分钟之内烤熟一只完整的火鸡,等等特点在货架上摆放。

现今,推荐系统被用来个性化你在网上的体验,告诉你买什么,去哪里吃,甚至是你应该和谁做朋友。人们口味各异,但通常有迹可循。人们倾向于喜欢那些与他们所喜欢的东西类似的东西,并且他们倾向于与那些亲近的人有相似的口味。推荐系统试图捕捉这些模式,以助于预测你还会喜欢什么东西。电子商务、社交媒体、视频和在线新闻平台已经积极的部署了它们自己的推荐系统,以帮助它们的客户更有效的选择产品,从而实现双赢。

两种最普遍的推荐系统的类型是 基于内容协同过滤(CF)。协同过滤基于用户对产品的态度产生推荐,也就是说,它使用“人群的智慧”来推荐产品。与此相反,基于内容的推荐系统集中于物品的属性,并基于它们之间的相似性为你推荐。一般情况下,协作过滤(CF)是推荐引擎的主力。该算法具有能够自身进行特征学习的一个非常有趣的特性,这意味着它可以开始学习使用哪些特性。CF可以分为 基于内存的协同过滤基于模型的协同过滤。在本教程中,你将使用奇异值分解(SVD)实现基于模型的CF和通过计算余弦相似实现基于内存的CF。 Python实现的具体实例

文章内容:基于物品过滤与基于用户过滤。

数据稀疏时候,用物品过滤最优;数据密集,两者效果一样。

下面以电影推荐为例:

一、原始数据处理:

  原始数据为二维矩阵:行是用户,列是电影:

  Lady in the Water Snakes on a Plane Just My Luck Superman Returns You, Me and Dupree The Night Listener
Lisa Rose 2.5 3.5 3.5 3.5 2.5 3
Gene Seymour 3 3.5 1.5 5 3.5 3
Michael Phillips 2.5 3   3.5   4
Claudia Puig   3.5 3 4 2.5 4.5
Mick LaSalle 3 4 2 3 2 3
Jack Matthews 3 4   5 3.5 3
Toby   4.5   4 1  

  采用数据结构:字典套字典:如此即可表现出数据二维矩阵:结构即data[user][movie]=rating

   此数据整体是一个字典,key=用户,value=该用户看过的电影信息 ,其中value又是一个字典(key=电影,value=评分)
复制代码
data ={'Lisa Rose': {'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 Night Listener': 3.0},
             
    'Gene Seymour': {'Lady in the Water': 3.0, 'Snakes on a Plane': 3.5, 
     'Just My Luck': 1.5, 'Superman Returns': 5.0, 'The Night Listener': 3.0, 
     'You, Me and Dupree': 3.5}, 
             
    'Michael Phillips': {'Lady in the Water': 2.5, 'Snakes on a Plane': 3.0,
     'Superman Returns': 3.5, 'The Night Listener': 4.0},
             
    'Claudia Puig': {'Snakes on a Plane': 3.5, 'Just My Luck': 3.0,
     'The Night Listener': 4.5, 'Superman Returns': 4.0, 
     'You, Me and Dupree': 2.5},
             
    'Mick LaSalle': {'Lady in the Water': 3.0, 'Snakes on a Plane': 4.0, 
     'Just My Luck': 2.0, 'Superman Returns': 3.0, 'The Night Listener': 3.0,
     'You, Me and Dupree': 2.0},
              
    'Jack Matthews': {'Lady in the Water': 3.0, 'Snakes on a Plane': 4.0,
     'The Night Listener': 3.0, 'Superman Returns': 5.0, 'You, Me and Dupree': 3.5},
             
    'Toby': {'Snakes on a Plane':4.5,'You, Me and Dupree':1.0,'Superman Returns':4.0}
}
复制代码

二、物品过滤

  1.先计算所有电影(物品)之间的相似度,构造一个包含相近电影(物品)的完整数据集。

     1.1  匹配电影物品,找出那些电影物品是彼此相近的

                 因为计算以电影物品为主,所以先将上面data[user][movie]数据转换成newdata[movie][user]格式,也即二维矩阵进行行列对换

复制代码
def transformdata(data):
    '''
    物品之间的相似度 与 用户之间的相似度 求解 一样。故只需要将用户换成物品即可
    '''   
    newdata = {}
    users ={}
    for person in data:
        for movie in data[person]:
            #初始化
            newdata.setdefault(movie,{})
            #物品与用户对调
            newdata[movie][person] = data [person][movie]  #字典可以直接写[key],就表示插入key值了。非常简便
    return newdata
复制代码
 '''
调用此方法例子:
print transformdata(data)
结果是:
{'Lady in the Water': {'Lisa Rose': 2.5, 'Jack Matthews': 3.0, 'Michael Phillips': 2.5, 'Gene Seymour': 3.0, 'Mick LaSalle': 3.0}, 
'Snakes on a Plane': {'Jack Matthews': 4.0, 'Mick LaSalle': 4.0, 'Claudia Puig': 3.5, 'Lisa Rose': 3.5, 'Toby': 4.5, 'Gene Seymour': 3.5, 'Michael Phillips': 3.0},
 'Just My Luck': {'Claudia Puig': 3.0, 'Lisa Rose': 3.0, 'Gene Seymour': 1.5, 'Mick LaSalle': 2.0}, 
 'Superman Returns': {'Jack Matthews': 5.0, 'Mick LaSalle': 3.0, 'Claudia Puig': 4.0, 'Lisa Rose': 3.5, 'Toby': 4.0, 'Gene Seymour': 5.0, 'Michael Phillips': 3.5}, 
 'The Night Listener': {'Jack Matthews': 3.0, 'Mick LaSalle': 3.0, 'Claudia Puig': 4.5, 'Lisa Rose': 3.0, 'Gene Seymour': 3.0, 'Michael Phillips': 4.0}, 
'You, Me and Dupree': {'Jack Matthews': 3.5, 'Mick LaSalle': 2.0, 'Claudia Puig': 2.5, 'Lisa Rose': 2.5, 'Toby': 1.0, 'Gene Seymour': 3.5}}
    '''   
复制代码
复制代码
    1.2 用calSimilarItems()获得所有电影之间的相似度。

               1.2.1 计算相似度,就要涉及相似距离度量,这里列举两种:欧氏距离sim_distance  ,皮尔逊sim_pearson 。两种都已设法表示距离越大,越相似

复制代码
from math import sqrt
def sim_distance(data,person1,person2):
    '''欧氏距离求相似度,距离越大,越相似'''
    commonmovies = [ movie for movie in data[person1] if movie in data[person2]] 
    if len(commonmovies)== 0: return 0 
    #平方和
    sumSq =sum([pow(data[person1][movie] -data[person2][movie],2) for movie in commonmovies ] )
    #使最终结果是,越相似,距离越大。所以将上面距离取倒数即可
    sim = 1/(1+ sqrt(sumSq))
    return sim 

def sim_pearson(data,person1,person2):
    '''
    计算上面格式的数据 里的  两个用户 相似度.
    基于用户过滤思路:找出两个用户看过的相同电影的评分,从而进行按pearson公式求值。那些非公共电影不列入求相似度值范围。
基于物品过滤思路:找过两部电影相同的观影人给出的评分,从而按pearson公式求值 返回:评分的相似度,[-1,1]范围,0最不相关,1,-1为正负相关,等于1时,表示两个用户完全一致评分 这里的data格式很重要,这里计算相似度是严格按照上面data格式所算。 此字典套字典格式,跟博客计算单词个数 存储格式一样
''' #计算pearson系数,先要收集两个用户公共电影名单 #commonmovies = [ movie for movie in data[person1] if movie in data[person2]] 分解步骤为如下: commonmovies = [] #改成列表呢 for movie in data[person1]: #data[person1]是字典,默认第一个元素 in (字典)是指 key.所以这句话是指 对data[person1]字典里遍历每一个key=movie if movie in data[person2]: #data[person2]也是字典,表示该字典有key是movie. commonmovies.append(movie) # commonmovie是 两个用户的公共电影名的列表 #看过的公共电影个数 n = float(len(commonmovies)) if n==0: return 0 '''下面正是计算pearson系数公式 ''' #分布对两个用户的公共电影movie分数总和 sum1 = sum([data[person1][movie]for movie in commonmovies ]) sum2 = sum([data[person2][movie]for movie in commonmovies]) #计算乘积之和 sum12 = sum([data[person1][movie]*data[person2][movie] for movie in commonmovies]) #计算平方和 sum1Sq = sum([ pow(data[person1][movie],2 ) for movie in commonmovies ]) sum2Sq = sum([ pow(data[person2][movie],2 ) for movie in commonmovies ]) #计算分子 num = sum12 - sum1*sum2/n #分母 den = sqrt((sum1Sq - pow(sum1,2)/n)*(sum2Sq - pow(sum2,2)/n)) if den==0: return 0 return num/den
复制代码

                   1.2.2  为单个电影物品返回最匹配结果

复制代码
def topmatches(data,givenperson ,returnernum = 5,simscore = sim_pearson):
    '''
    用户匹配推荐:给定一个用户,返回对他口味最匹配的其他用户
    物品匹配: 给定一个物品,返回相近物品
    输入参数:对person进行默认推荐num=5个用户(基于用户过滤),或是返回5部电影物品(基于物品过滤),相似度计算用pearson计算
    '''
    #建立最终结果列表
    usersscores =[(simscore(data,givenperson,other),other) for other in data if other != givenperson ]
    #对列表排序
    usersscores.sort(cmp=None, key=None, reverse=True)
    
    return usersscores[0:returnernum]

复制代码
    '''
调用以前方法:找物品相关匹配:
moviedata = transformdata(data)
#找出跟“超人回归”这电影相关的电影
print topmatches(moviedata, 'Superman Returns')

结果是: 
[(0.6579516949597695, 'You, Me and Dupree'), (0.4879500364742689, 'Lady in the Water'), 
(0.11180339887498941, 'Snakes on a Plane'), (-0.1798471947990544, 'The Night Listener'), (-0.42289003161103106, 'Just My Luck')]
其中负数表示,讨厌此电影
    '''  
复制代码
复制代码
       1.2.3 基于上面1.2.2,从为单一物品返回匹配结果 扩展到 为所有物品返回匹配结果
复制代码
def calSimilarItems(data,num=10):
#以物品为中心,对偏好矩阵转置
moviedata = transformdata(data) ItemAllMatches = {} for movie in moviedata: ItemAllMatches.setdefault(movie,[]) #对每个电影 都求它的匹配电影集,求电影之间的距离用欧式距离,用pearson距离测出的结果是不一样的 ItemAllMatches[movie] = topmatches(moviedata, movie, num,simscore = sim_distance) return ItemAllMatches
复制代码
   '''
    构造一个物品匹配完整集:包含所有物品的对应的匹配物品
     即是电影相似度的字典: key = movie ,value = [(othermovie,simscore)]。其中value是一个元组对的列表
    调用例子;
    print calSimilarItems(data)
    结果为:
{'Lady in the Water': [(0.7637626158259785, 'Snakes on a Plane'), (0.4879500364742689, 'Superman Returns'), (0.3333333333333333, 'You, Me and Dupree'), (-0.6123724356957927, 'The Night Listener'), (-0.9449111825230676, 'Just My Luck')],
 'Snakes on a Plane': [(0.7637626158259785, 'Lady in the Water'), (0.11180339887498941, 'Superman Returns'), (-0.3333333333333333, 'Just My Luck'), (-0.5663521139548527, 'The Night Listener'), (-0.6454972243679047, 'You, Me and Dupree')], 
'Just My Luck': [(0.5555555555555556, 'The Night Listener'), (-0.3333333333333333, 'Snakes on a Plane'), (-0.42289003161103106, 'Superman Returns'), (-0.4856618642571827, 'You, Me and Dupree'), (-0.9449111825230676, 'Lady in the Water')],  .........
    '''
复制代码
复制代码

      2.推荐用户没看过的电影

         某一部未看过电影分数= sum(该部未看过的电影与每一部已看电影之间相似度*已看电影的评分) /sum(未看电影与每一部已看电影之间相似度)

   例如:未看电影A,已看电影B,C:

                  则,电影A分数 =  [sim(A,B)*rating(B) +sim(A,C)*rating(C)] / [ sim(A,B) + sim(A,C)]

复制代码
def getrecommendations(data,targetperson,moviesAllsimilarity):
    '''
    输入movieAllSimilarity就是上面calsimilarItems已经计算好的所有物品之间的相似度数据集:
     '''
    #获得所有物品之间的相似数据集
    scoresum = {}
    simsum = {}
    #遍历所有看过的电影
    for watchedmovie in data[targetperson]:
        rating = data[targetperson][watchedmovie]
        #遍历与当前电影相近的电影
        for(similarity,newmovie) in moviesAllsimilarity[watchedmovie]:   #取一对元组
            #已经对当前物品评价过,则忽略
            if newmovie in data[targetperson] :continue
           
scoresum.setdefault(newmovie,0) simsum.setdefault(newmovie,0)
#全部相似度求和 simsum[newmovie] += similarity #评价值与相似度加权之和 scoresum[newmovie] += rating * similarity rankings = [(score/simsum[newmovie] , newmovie) for newmovie,score in scoresum.items() ] rankings.sort(cmp=None, key=None, reverse=True) return rankings
复制代码
'''调用此方法如下:
itemsAllsim = calSimilarItems(data) #这个值会事先计算好 print '基于物品过滤,为用户Toby推荐的电影是:' print getrecommendations(data, 'Toby',itemsAllsim) 返回结果是: [(3.1667425234070894, 'The Night Listener'), (2.936629402844435, 'Just My Luck'), (2.868767392626467, 'Lady in the Water')]

这样基于物品过滤推荐,为Toby用户推荐的电影结果 就完成了。
'''
复制代码
 
    
复制代码

三、基于用户过滤

   3.1 推荐品味相近的用户

 #假设为用户Toby进行推荐品味相当的用户,则调用userstopmatcher方法,会返回一个影评人及其相似度 的列表
 print topmatches(data, 'Toby', 3)
 #返回结果为:
[(0.9912407071619299, 'Lisa Rose'), (0.9244734516419049, 'Mick LaSalle'), (0.8934051474415647, 'Claudia Puig')]
#所以lisa分数最高,应该推荐lisa Rose品味跟Toby最近

   3.2 推荐未看过的电影:

    未看过电影分数=sum(被推荐用户与其他用户之间相似度*用户对该电影评分)/sum(被推荐用户与其他用户之间相似度)

复制代码
def recommendItems(data,givenperson,num =5 ,simscore = sim_pearson):
    '''
    物品推荐:给定一个用户person,默认返回num=5物品
    要两个for,对用户,物品 都进行 遍历
    '''
    #所有变量尽量用字典,凡是列表能表示的字典都能表示,那何不用字典
    itemsimsum={} 
    #存给定用户没看过的电影的其他用户评分加权
    itemsum={}
#遍历每个用户,然后遍历该用户每个电影 for otheruser in data : #不要和自己比较 if otheruser == givenperson: continue #忽略相似度=0或小于0情况 sim = simscore(data,givenperson,otheruser) if sim <=0: continue for itemmovie in data[otheruser]: #只对用户没看过的电影进行推荐,参考了其他用户的评价值(协同物品过滤是参考了历史物品相似度值) if itemmovie not in data[givenperson]: #一定要初始化字典:初始化itemsum与itemsimsum itemsum.setdefault(itemmovie,0) itemsimsum.setdefault(itemmovie,0) #用户相似度*评价值 itemsum[itemmovie] += sim * data[otheruser][itemmovie] itemsimsum[itemmovie] += sim #最终结果列表,列表包含一元组(item,分数) rankings = [(itemsum[itemmovie] / itemsimsum[itemmovie],itemmovie) for itemmovie in itemsum] #结果排序 rankings.sort(cmp=None, key=None, reverse=True); return rankings #调用此方法如下:
#
print recommendItems(data, 'Toby', 3) # 返回结果:
#[(3.3477895267131013, 'The Night Listener'), (2.8325499182641614, 'Lady in the Water'), (2.5309807037655645, 'Just My Luck')]
复制代码
 
   

四、用真实数据:Movielens数据集

4.1数据原型:
复制代码
u.item 文件:
记录了 电影id与电影名的映射关系
其内容节选:
1|Toy Story (1995)|01-Jan-1995||http://us.imdb.com/M/title-exact?Toy%20Story%20(1995)|0|0|0|1|1|1|0|0|0|0|0|0|0|0|0|0|0|0|0
2|GoldenEye (1995)|01-Jan-1995||http://us.imdb.com/M/title-exact?GoldenEye%20(1995)|0|1|1|0|0|0|0|0|0|0|0|0|0|0|0|0|1|0|0

u.data 文件:
每行信息是:用户id,电影id,评分,评分时间(这里我们只用到前三个属性)
其内容节选:
196    242    3    881250949
186    302    3    891717742
22    377    1    878887116
244    51    2    880606923
复制代码

 4.2 将movielens数据转换成上面data数据同样格式:

复制代码
def formatMovieLens(filepath='D:/eclipse 3.62/workspace/Commendations/data/ml-100k'):
    '''
    这里用的是ml100k数据,只需要里面u.item和u.data两个文件
    把movielens数据转换成此代码要求的数据格式,即转换成如同上面data字典套字典结构:dic[user] = {movie:score}
    '''
    movie = {}
    #获取影片标题。u.item记录着影片id与影片名映射信息
    for line in file(filepath+'/u.item'): 
        (id,title) = line.split("|")[0:2]
        movie[id] = title
        
    resultdata = {}
    for line in file(filepath +'/u.data'):
        (user,movieid,rating) = line.split("\t")[0:3]
        resultdata.setdefault(user,{})
        movietitle = movie[movieid]
        resultdata[user][movietitle] = float(rating)
    return resultdata 

复制代码

  4.3 测试结果,为用户进行物品推荐。分别尝试物品过滤,用户过滤

复制代码
mldata= formatMovieLens()
# print "格式化movielens数据集后,取用户id=87看过的电影:"
print mldata['87']
#结果是:{'Birdcage, The (1996)': 4.0, 'E.T. the Extra-Terrestrial (1982)': 3.0, 'Bananas (1971)': 5.0, 'Sting, The (1973)': 5.0, 'Bad Boys (1995)': 4.0, 'In the Line of Fire (1993)': 5.0, 'Star Trek: The Wrath of Khan (1982)': 5.0, 'Speechless (1994)': 4.0,  .....}
 

#基于用户过滤推荐
print "基于用户过滤推荐:为用户87推荐的电影排名前4部是:"
print getrecommendation(mldata, '87' )[0:30] 
#结果是: [(5.0, 'They Made Me a Criminal (1939)'), (5.0, 'Star Kid (1997)'), (5.0, 'Santa with Muscles (1996)'), (5.0, 'Saint of Fort Washington, The (1993)'), (5.0, 'Marlene Dietrich: Shadow and Light (1996) '), (5.0, 'Great Day in Harlem, A (1994)'), (5.0, 'Entertaining Angels: The Dorothy Day Story (1996)'), (5.0, 'Boys, Les (1997)'), (4.89884443128923, 'Legal Deceit (1997)'), ...]

#基于物品过滤推荐
#计算所有物品之间的相似度
itemsim = calSimilarItems(mldata, 50)
print "基于物品过滤推荐:为用户87推荐的电影排名前4部是:"
print getrecommendedItems(mldata, '87', itemsim)[0:10]
# 结果是:[(5.0, "What's Eating Gilbert Grape (1993)"), (5.0, 'Vertigo (1958)'), (5.0, 'Usual Suspects, The (1995)'), (5.0, 'Toy Story (1995)'), (5.0, 'Titanic (1997)'), (5.0, 'Sword in the Stone, The (1963)'), (5.0, 'Stand by Me (1986)'), (5.0, 'Sling Blade (1996)'), (5.0, 'Silence of the Lambs, The (1991)'), (5.0, 'Shining, The (1980)')]  

猜你喜欢

转载自blog.csdn.net/u013185349/article/details/78121301