前言
一本比较老的书,偏实用,正好打算入手python机器学习,实现一遍代码。
关于协作型过滤
介绍他的一个引用场景:已有用户对自己感兴趣的电影评分的情况下,怎么合理的为用户推荐新电影?或者一个新用户在浏览电影时,推荐其他相关的电影?
协作性过滤 的思想处理上述问题的逻辑是这样的:
- 两个用户对共同感兴趣的电影评分都相近,显然可以互相推荐对方未看过的电影。
- 如果两部电影用户对它们的评分很接近,显然这两部电影就更值得同时出现。
具体来说:
- 相关性指标:计算两个用户对共同感兴趣的电影的评分相关系数来衡量两个用户之间的相关程度,可以用皮尔逊相关系数,距离倒数等。
- 相关系数加权:为A推荐未看过的电影d时,如果B和C同时对电影d打过分,则以B、C与A的评分相关系数来加权各自对电影d的打分,作为A喜欢电影d的程度的预测。
- 基于物品的过滤:推荐与用户喜欢的电影d相关程度高的电影,意义在于不用计算该用户与所有其他用户的评分相关程度。
数据集
用户电影评分数据 MovieLens
zip压缩包
——movies.csv:电影信息表,主要是电影id和电影标题
——ratings.csv:打分表,主要用户id,电影id和评分
代码:
__author__ = "xqx"
"""
新建recommendations.py,下载数据集解压到本地
"""
import recommendations as rd
import json
# 导入用户电影评分数据
prefs = rd.loadMovieLens(path = r'E:\桌面space\space\学习ing\python学习\集体智慧编程\提供推荐\data\ml-latest-small')
if input("是否重新开始计算物品")=='y':
#计算物品相似度矩阵并保存
print("开始计算物品相似度字典.....")
itemMatch = rd.calculateSimilarItems(prefs,10)
print("计算完成!")
with open('itemMatch.json','w') as outfile:
json.dump(itemMatch,outfile,ensure_ascii=False)
outfile.write('\n')
else:
#导入物品相似度矩阵
f=open("itemMatch.json","r")
for line in f:
itemMatch=json.loads(line)
f.close()
userid = '87'
print( "为用户%s推荐的电影如下:"%userid )
recommends = rd.getRecommendedItems(prefs,itemMatch,userid)
for i in range(len(recommends)):
print('%.3f\t%s'%(recommends[i][0],recommends[i][1]))
结果
是否重新开始计算物品n
为用户87推荐的电影如下:
5.000 xXx (2002)
5.000 loudQUIETloud: A Film About the Pixies (2006)
5.000 Zombieland (2009)
5.000 Zodiac (2007)
5.000 Zenon: Z3 (2004)
5.000 Zenon: The Zequel (2001)
5.000 Zenon: Girl of the 21st Century (1999)
5.000 Young People Fucking (a.k.a. YPF) (2007)
5.000 Yossi & Jagger (2002)
5.000 Wrong Cops (2013)
5.000 Wrong (2012)
5.000 Winnebago Man (2009)
5.000 Willie & Phil (1980)
5.000 Wild at Heart (1990)
5.000 Wild Zero (2000)
5.000 Where’s Marlowe? (1998)
5.000 What Happened Was… (1994)
4.667 Zulu (1964)
附件
recommendations.py
"""
数据集:姓名:电影:评分
"""
critics={'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}}
from math import sqrt
"""
通过两个人对同一物品的评分来定义两人的相似度
内在逻辑是:两个相似的人喜欢同一个东西,对电影的评分应该相似
"""
# 返回两个人基于距离的相似度评价
def sim_distance(prefs,person1,person2):
"""
距离的倒数来定义形似度
"""
# 得到共有属性的列表
si = {}
# 计算所有差值的平方和
sum_of_squres = 0
for item in prefs[person1]:
if item in prefs[person2]:
si[item]=1
sum_of_squres += pow(prefs[person1][item] - prefs[person2][item], 2)
# 如果两者没有共同之处,则返回0
if len(si)==0:return 0
return 1/(1+sqrt(sum_of_squres))
# 返回p1和p2的皮尔逊相关系数
def sim_pearson(prefs, p1, p2):
"""
相关系数
"""
# 得到双方都曾评价过的物品列表
si = {}
for item in prefs[p1]:
if item in prefs[p2]: si[item] = 1
# 得到列表元素的个数
n = len(si)
# 如果没有共同之处,返回1
if n==0: return 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[p2][it]*prefs[p1][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
return num/den
# 从反映偏好的字典中返回最匹配者
# 返回结果的个数和相似度函数均可最为可选参数
# 默认返回前5个,计算皮尔逊相关系数
def topMatches(prefs,person,n=5,similarity=sim_pearson):
"""
返回与指定客户的相似性最高的前n位
"""
scores = [(similarity(prefs, person, other),other)
for other in prefs if other!=person]
# 对列表进行排序
scores.sort()
scores.reverse()
return scores[:n]
# 相似系数加权的影片打分值
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
def transforPrefs(prefs):
result = {}
for person in prefs:
for item in prefs[person]:
result.setdefault(item, {})
# 将人和物品对调
result[item][person] = prefs[person][item]
return result
###导入本地的MovieLens数据集
def loadMovieLens(path='/data/ml-latest-small'):
# 获取影片标题
movies = {}
file = open(path + '/movies.csv')
file.readline()
for line in file:
(ids, title)=line.split(',')[:2]
movies[ids] = title
file.close()
# 加载数据
prefs = {}
file = open(path + '/ratings.csv')
file.readline()
for line in file:
(user,movieid,rating,ts)=line.split(',')
prefs.setdefault(user,{})
prefs[user][movies[movieid]]=float(rating)
file.close()
return prefs
def calculateSimilarItems(prefs,n = 10):
# 建立字典,以给出与这些物品最为相似的所有其他物品
result = {}
# 以物品为中心对偏好矩阵实施倒置处理
itemPrefs = transforPrefs(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
def getRecommendedItems(prefs,itemMatch,user):
userRatins = prefs[user]
scores={}
totalSim={}
# 循环遍历由当前用户评分的物品
for (item,rating) in userRatins.items():
# 循环遍历与当前物品相近的物品
for (similarity,item2) in itemMatch[item]:
# 如果该用户已经对当前物品做过评价,则将其忽略
if item2 in userRatins: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