本文思想和代码基于https://coding.m.imooc.com/classindex.html?cid=297 该课程写成
个人代码参考: https://github.com/yuanzhiqj/recom
基于内容的推荐系统是根据用户的之前的偏好,计算推荐项目中与用户偏好相似度最高的项目,推荐给用户。
下面通过一个具体的电影推荐系统的示例来初步了解它
给定两个输入文件,rating.txt和movies。分别代表用户的评分文件和电影的列表。截取部分数据解释如下:
userId | movieId | rating | timestamp |
---|---|---|---|
1 | 31 | 2.5 | 1260759144 |
userid: 用户编号
movid: 电影编号
rating:评分
timestamp:时间戳,越大表示越近时间评分
movie | title | genres |
---|---|---|
1 | Toy Story (1995)A | Adventure|Animation|Children|Comedy|Fantasy |
move:电影编号
title:电影名称
genres:电影分类,以’|'号分隔
处理数据是比较麻烦的一部分,我们把它分为几步
通过输入ratings.txt文件,返回一个包含所有电影平均分的字典score。
record: 临时存数电影得分和评分次数的字典。
def get_avg(input_file):
"""
获取平均分
args:
input_file:输入文件
return:
一个字典,key:itemid value:avg
"""
record = {
}
score = {
}
if not os.path.exists(input_file):
print("no path")
return {
}
linenum = 0
fp = open(input_file)
for line in fp:
if linenum == 0:
linenum += 1
continue
item = line.strip().split(",")
if len(item) < 4:
continue
userid, itemid, rating = item[0],item[1],float(item[2])
if itemid not in record:
record[itemid] = [0,0]
record[itemid][0] += rating
record[itemid][1] += 1
fp.close()
for itemid in record:
score[itemid] = round(record[itemid][0]/record[itemid][1],3)
return score
假如一部电影有多个分类,比如同时属于冒险,动作。那么它们分别占0.5的比例。
即分别拥有1/n的权重,n为电影所属的种类数。
用字典item_cata存储
在完成上两步的操作后,我们得到了两个字典:
我们先创建一个临时字典record,该字典存储着如下信息:
value:电影种类
key:a dict{value: 电影名称, key: 平均分} #即一个嵌套字典。
对该临时字典排序,得到一个新的有序字典cate_item_sort
for cate in record:
if cate not in cate_item_sort:
cate_item_sort[cate] = []
for combo in sorted(record[cate].items(),key=operator.itemgetter(1),reverse=True)[:topk]:
cate_item_sort[cate].append(combo[0]+"_"+str(combo[1]))
它的键值对为:value: 电影种类, key: 电影名称。按照评分从高到底排序
def get_item_cate(input_file, avg_score):
"""
得到不同种类的电影的评分排序 和 同一电影的种类
"""
record = {
}
item_cate = {
}
cate_item_sort = {
}
linenum = 0
topk = 100
if not os.path.exists(input_file):
return {
},{
}
fp = open(input_file)
for line in fp:
if linenum == 0:
linenum += 1
continue
item = line.strip().split(",")
if(len(item) < 3):
continue
itemid = item[0]
cate_str = item[-1]
cate_list = cate_str.strip().split("|")
ratio = round(1/len(cate_list),3)
if itemid not in item_cate:
item_cate[itemid] = {
}
for fix_cate in cate_list:
item_cate[itemid][fix_cate] = ratio
fp.close()
for itemid in item_cate:
for cate in item_cate[itemid]:
if cate not in record:
record[cate] = {
}
itemid_rating_score = avg_score.get(itemid,0)
record[cate][itemid] = itemid_rating_score
for cate in record:
if cate not in cate_item_sort:
cate_item_sort[cate] = []
for combo in sorted(record[cate].items(),key=operator.itemgetter(1),reverse=True)[:topk]:
cate_item_sort[cate].append(combo[0]+"_"+str(combo[1]))
return item_cate, cate_item_sort
这一步的目的是获得值最大的时间戳,方便我们与其它的时间戳进行比较。得到用户最近时间内的评分。在该文件中,最大时间戳为1476086345。
def get_latest_timestamp(input_file):
"""
Args:
input_file:user rating file
only need run once
"""
if not os.path.exists(input_file):
return
linenum = 0
latest = 0
fp = open(input_file)
for line in fp:
if linenum == 0:
linenum += 1
continue
item = line.strip().split(",")
if len(item) < 4:
continue
timestamp = int(item[3])
if timestamp > latest:
latest = timestamp
fp.close()
print(latest)
数据处理完后,在对用户推荐之前,我们需要获得用户之前的偏好信息。步骤如下:
分值=用户评分 * 时间权重 * 种类占比。
比如用户给a电影打了4分,时间权重为0.1,该电影动作类占比0.5,则对动作类的喜好程度上升0.2。
def get_up(item_cate, input_file):
linenum = 0
if not os.path.exists(input_file):
return {
}
record = {
}
up = {
}
score_thr = 4.0
topk = 2
fp = open(input_file)
for line in fp:
if linenum == 0:
linenum += 1
continue
item = line.strip().split(",")
if(len(item) < 4):
continue
userid,itemid,rating,time = item[0],item[1],float(item[2]),int(item[3])
if rating < score_thr:
continue
if itemid not in item_cate:
continue
time_score = get_time_score(time)
if userid not in record:
record[userid] = {
}
for fix_cate in item_cate[itemid]:
if fix_cate not in record[userid]:
record[userid][fix_cate] = 0
record[userid][fix_cate] += rating * time_score * item_cate[itemid][fix_cate]
fp.close()
for userid in record:
if userid not in up:
up[userid]=[]
total = 0
for combo in sorted(record[userid].items(),key=operator.itemgetter(1),reverse=True)[:topk]:
up[userid].append((combo[0],combo[1]))
total += combo[1]
for index in range(len(up[userid])):
up[userid][index] = (up[userid][index][0],round(up[userid][index][1]/total,3))
return up
获得用户id后,我们只需查询用户偏好的种类,按照此种类查找cate_item_sort字典获得排名靠前的电影即可。
def recom(cate_item_sort,up,userid,topk=10):
if userid not in up:
return {
}
recom_result = {
}
if userid not in recom_result:
recom_result[userid] = []
for zuhe in up[userid]:
cate = zuhe[0]
ratio = zuhe[1]
num = int(topk*ratio) + 1
if cate not in cate_item_sort:
continue
recom_list = cate_item_sort[cate][:num]
recom_result[userid] += recom_list
return recom_result