1) 从网站下载数据
链接: https://grouplens.org/datasets/movielens/.
有好几种版本,对应不同数据量,本文所用的数据为1M的数据。
MovieLens 1M Dataset
MovieLens数据集包含多个用户对多部电影的评级数据,也包括电影元数据信息和用户属性信息。
这个数据集经常用来做推荐系统,机器学习算法的测试数据集。尤其在推荐系统领域,很多著名论文都是基于这个数据集的。(PS: 它是某次具有历史意义的推荐系统竞赛所用的数据集)。
1m的数据解压后,可以看到四个主要的csv文件,分别是links.csv,movies.csv,ratings.csv,tags.csv。links介绍了该数据集中的movieId和imdb、tmdb中电影的对应关系。tags是用户的打标签数据。本文的介绍主要基于ratings.csv movies.csv
ratings数据
文件里面的内容包含了每一个用户对于每一部电影的评分。数据格式如下:
userId, movieId, rating, timestamp
userId: 每个用户的id
movieId: 每部电影的id
rating: 用户评分,是5星制,按半颗星的规模递增(0.5 stars - 5 stars)
timestamp: 自1970年1月1日零点后到用户提交评价的时间的秒数
数据排序的顺序按照userId,movieId排列的。
movies数据
movieId:每部电影的id
title:电影的标题
genres:电影的类别
协同过滤推荐算法是诞生最早,并且较为著名的推荐算法。主要的功能是预测和推荐。算法通过对用户历史行为数据的挖掘发现用户的偏好,基于不同的偏好对用户进行群组划分并推荐品味相似的商品。协同过滤推荐算法分为两类,分别是基于用户的协同过滤算法(user-based collaboratIve filtering),和基于物品的协同过滤算法(item-based collaborative filtering)。简单的说就是:人以类聚,物以群分。下面我们将分别说明这两类推荐算法的原理和实现方法。
基于用户的协同过滤就是给用户推荐“和他兴趣相投的其他用户”喜欢的物品。
通过用户的历史行为数据发现用户喜欢的物品,并对这些偏好进行度量和评分,然后根据不同用户的评分或者偏好程度来评测用户之间的相似性,对相同骗号的用户进行物品推荐。
算法总结
一、找到和目标用户兴趣相似的用户集合——计算两个用户的兴趣相似度
二、找到这个集合中的用户喜欢的,且目标用户没有听说过的物品推荐给目标用户——找出物品推荐
计算用户相似度的方法:这里我采用的是余弦相似度
下面我拿这个图举例
同理
背景分析:上面计算存在一个问题-需要计算每一个用户的相似度,在实际生产环境中,很多用户之间并没有交集,也就是并没有对同一样物品产生过行为,所以很多情况分子为0,这样的稀疏数据就没有计算的必要。
优化思路:首先计算出|N(u) 并 N(v)| != 0 的用户对(u,v),然后对这种情况计算分母以得到两个用户的相似度
实施步骤:
(1)建立物品到用户的倒查表T,表示该物品被哪些用户产生过行为;
(2)根据倒查表T,建立用户相似度矩阵W:在T中,对于每一个物品i,设其对应的用户为j,k,在W中,更新相应的元素值,w[j][k]=w[j][k]+1,w[k][j]=w[k][j]+1,以此类推,扫描完倒查表T中的所有物品后,就可以得到最终的用户相似度矩阵W,这里的W是余弦相似度中的分子部分,然后将W除以分母可以得到最终的用户兴趣相似度。
对一件冷门物品有过相同行为比对一件热门物品有过相同行为更能说明两个用户兴趣相似。
如果两个用户都买过《新华字典》,这并不能说明他们兴趣相投,因为绝大多数中国人都买过《新华字典》。
但是如果两个用户都买过《机器学习实战》,那可以认为他们兴趣比较相似,因为只有研究机器学习的人才会购买这本书。
该公式通过1/log1+|N(i)|惩罚了用户u和用户v共同兴趣列表中的热门物品i,实验表明,改进的UserCF算法的各预测指标都有所提升。
首先使用训练数据得到用户的偏好矩阵和物品的特征信息矩阵,然后计算用户对未进行评分电影的偏好分,选取前K个推荐用户。
创建一个UserCFRec类 添加如下内容:
class UserCFRec:
def __init__(self,datafile):
self.datafile = datafile
self.data = self.loadData()
self.trainData,self.testData = self.splitData(3,47) # 训练集与数据集
self.users_sim = self.UserSimilarityBest()
加载评分数据到data
def loadData(self):
print("加载数据...")
data=[]
for line in open(self.datafile):
userid,itemid,record,_ = line.split("::")
data.append((userid,itemid,int(record)))
return data
拆分数据集为训练集和测试集
k: 参数
seed: 生成随机数的种子
M: 随机数上限
def splitData(self,k,seed,M=8):
print("训练数据集与测试数据集切分...")
train,test = {},{}
random.seed(seed)
for user,item,record in self.data:
if random.randint(0,M) == k:
test.setdefault(user,{})
test[user][item] = record
else:
train.setdefault(user,{})
train[user][item] = record
return train,test
计算用户之间的相似度,采用惩罚热门商品和优化算法复杂度的算法
def UserSimilarityBest(self):
print("开始计算用户之间的相似度 ...")
if os.path.exists("./data/user_sim.json"):
print("用户相似度从文件加载 ...")
userSim = json.load(open("./data/user_sim.json","r"))
else:
# 得到每个item被哪些user评价过
item_users = dict()
for u, items in self.trainData.items():
for i in items.keys():
item_users.setdefault(i,set())
if self.trainData[u][i] > 0:
item_users[i].add(u)
# 构建倒排表
count = dict()
user_item_count = dict()
for i, users in item_users.items():
for u in users:
user_item_count.setdefault(u,0)
user_item_count[u] += 1
count.setdefault(u,{})
for v in users:
count[u].setdefault(v, 0)
if u == v:
continue
count[u][v] += 1 / math.log(1+len(users))
# 构建相似度矩阵
userSim = dict()
for u, related_users in count.items():
userSim.setdefault(u,{})
for v, cuv in related_users.items():
if u==v:
continue
userSim[u].setdefault(v, 0.0)
userSim[u][v] = cuv / math.sqrt(user_item_count[u] * user_item_count[v])
json.dump(userSim, open('./data/user_sim.json', 'w'))
return userSim
为用户user进行物品推荐
user: 为用户user进行推荐
k: 选取k个近邻用户
nitems: 取nitems个物品
def recommend(self, user, k=8, nitems=40):
result = dict()
have_score_items = self.trainData.get(user, {})
for v, wuv in sorted(self.users_sim[user].items(), key=lambda x: x[1], reverse=True)[0:k]:
for i, rvi in self.trainData[v].items():
if i in have_score_items:
continue
result.setdefault(i, 0)
result[i] += wuv * rvi
return dict(sorted(result.items(), key=lambda x: x[1], reverse=True)[0:nitems])
计算准确率
k: 近邻用户数
nitems: 推荐的item个数
def precision(self, k=8, nitems=10):
print("开始计算准确率 ...")
hit = 0
precision = 0
for user in self.trainData.keys():
tu = self.testData.get(user, {})
rank = self.recommend(user, k=k, nitems=nitems)
for item, rate in rank.items():
if item in tu:
hit += 1
precision += nitems
return hit / (precision * 1.0)
--------------开始计时... -----------
--------------基于MovieLens的推荐系统 运行中... -----------
加载数据...
训练数据集与测试数据集切分...
开始计算用户之间的相似度 ...
user '1' recommend result is {'1907': 1.1719770420320046, '2078': 1.0771247187392352, '2081': 1.0758836648976486, '1029': 1.0711008152996346, '2096': 1.0625630426343857, '596': 1.0451797898417066, '2085': 0.9577988630036637, '2080': 0.9410112225805409, '364': 0.9193391674871346, '1270': 0.8780339152128267, '480': 0.8681534812717586, '1282': 0.8674376643580475, '2028': 0.8547482945805895, '783': 0.7351700653028361, '1032': 0.7315950136253785, '593': 0.720277373175779, '1265': 0.6967484368684826, '1198': 0.6954032288177417, '1580': 0.6872105901375891, '2137': 0.6749673909596987, '1584': 0.6504546954200493, '2396': 0.6223822374562248, '539': 0.6216743169985597, '1196': 0.614253658798736, '1073': 0.614253658798736, '1148': 0.599029462528578, '110': 0.5979119650391214, '1210': 0.5770033651370033, '34': 0.5609050960671571, '2087': 0.5563225830541934, '296': 0.5189526708406325, '2571': 0.48716409090204715, '3751': 0.4779821383770259, '920': 0.47310567081152793, '2858': 0.4653183808365773, '3578': 0.4653183808365773, '3000': 0.4633607993152704, '1617': 0.4522073324341529, '1721': 0.4443252923900383, '3615': 0.4408193455994457}
--------------结束计时... -----------
总耗时:0:21:00.978540
开始计算准确率 ...
precision is 0.1909437086092715
Process finished with exit code 0
本文是基于用户的协同过滤的简单案列,采用Movielens数据集作为实验,本文代码实现起来比较简单,最后的结果我们可以看出UserCF准确率并不高 可以尝试使用不同的k值(近邻用户)来调节推荐算法的准确率。以后可以结合发掘更多相关因素 比如加入时间衰退因子 进行加强相似度的准确率,或者我们可以继续探讨另一种协同过滤算法 -基于物品的协同过滤(ItemCF)。