赞
踩
MovieLens其实是一个推荐系统和虚拟社区网站,它由美国 Minnesota 大学计算机科学与工程学院的GroupLens项目组创办,是一个非商业性质的、以研究为目的的实验性站点。GroupLens研究组根据MovieLens网站提供的数据制作了MovieLens数据集合,这个数据集合里面包含了多个电影评分数据集,分别具有不同的用途。本文均用MovieLens数据集来代替整个集合。
MoveLens是一个数据集合,其中根据创建时间、数据集大小等分为了若干个子数据集。每个数据集的格式、大小、用途均有所差异。本文以MovieLens 1M Dataset为例,具体介绍下此数据集,其它MovieLens数据集也大都类似。MoveLens 1M 数据集包含了来自6040位在2000年加入MovieLens的用户,对大约3900部电影的1000209条匿名评价。
数据集文件解压之后,可以得到4个文件,分别是:
RATING文件
其文件格式: UserID::MovieID::Rating::Timestamp
- UserID范围是1~6040,代表了6040个MovieLens用户
- MovieID范围是1到3952,代表了3952部电影
- Rating范围是1到5,代表了用户对电影的评级,最高5颗星,不允许半颗星存在
- Timestamp是以秒为单位的时间戳
USERS文件
其文件格式:UserID::Gender::Age::Occupation::Zip-code
- UserID范围是1~6040,代表了6040个MovieLens用户
- Gender 'M'代表男性,'F'代表女性
- Age 年龄的范围如下
- 1: 18岁以下
- 18: 18~24岁
- 25: 25~34岁
- 35: 35~44岁
- 45: 45~49岁
- 50: 50~55岁
- 56: 56岁以上
- Occupation 的范围如下
- 0:其他或者未指定
- 1: 学者/教育行业
- 2:艺术家
- 3: 办事员/行政人员
- 4: 大学生/研究生
- 5:服务业
- 6:医疗医护业
- 7:执行官/管理者
- 8:农民
- 9:家庭主妇
- 10:中小学生
- 11:律师
- 12:程序员
- 13:退休人员
- 14:销售人员/市场人员
- 15:科学家
- 16:自主创业
- 17:技术人员/工程师
- 18:商人/手工工作者
- 19:失业
- 20:作家
- Zip-dode 邮政编码
MOVIES 文件
其文件格式为:MovieID::Title::Genres
- MovieID 范围是1到3952,代表了3952部电影
- Title 是电影名称,由IMDB提供,包括了发行年份
- Genres 电影题材由竖线分开,从以下类别中选取
- Action
- Adventure
- Animation
- Children's
- Comedy
- Crime
- Documentary
- Drama
- Fantasy
- Film-Noir
- Horror
- Musical
- Mystery
- Romance
- Sci-Fi
- Thriller
- War
- Western
整个算法流程分为训练和推荐阶段。
对于训练阶段,可分为以下几步:
- 数据预处理,建立User-Item表
- 建立物品整体共现矩阵
- 建立物品相似度矩阵
对于推荐阶段,可分为以下几步:
- 寻找与被推荐用户喜爱物品集最相似的N个物品
- 计算用户对这N个物品的感兴趣程序列表并逆序排列
采用MovieLens数据集中的ratings.dat文件,因为这里面包含了用户对电影的评分数据,注意我们忽略掉评分那一栏,将其简化成用户喜欢或者不喜欢。只要用户有参与过评分的电影,无论分值如何,我们都认为这部电影是用户喜欢的。
为了减少训练时间,可以只读取部分数据,本文读取了前29415条数据,即前200个用户的评价数据。ratings.dat原始数据每行包含了4列,本文中只取了’UserID‘、’MovieID‘这两列。
这里并没有采用对每个用户都建立共现矩阵再累加的方式,而是直接采用了两重dict来实现一个Matrix,然后在此基础上直接建立共现矩阵。
这一步是是直接在物品共现矩阵的基础上除以两个物品各自喜爱人数的乘积,并且包含了数据归一化的处理。
- import math
- import random
- import pandas as pd
- from collections import defaultdict
- from operator import itemgetter
-
- def LoadMovieLensData(filepath, train_rate):
- ratings = pd.read_table(filepath, sep="::", header=None, names=["UserID", "MovieID", "Rating", "TimeStamp"],\
- engine='python')
- ratings = ratings[['UserID','MovieID']]
-
- train = []
- test = []
- random.seed(3) #随机数生成器的种子为3
- '''
- 用于将 ratings DataFrame 中的每一行作为一个元组进行迭代。
- 然后在每次迭代中,我们从当前行中取出 UserID 和 MovieID,并以一定的概率将其添加到 train 或 test 列表中。
- 最后,我们将 train 和 test 列表作为参数传递给 PreProcessData() 函数,
- 返回经过预处理的训练数据和测试数据。
- '''
- for idx, row in ratings.iterrows():
- user = int(row['UserID'])
- item = int(row['MovieID'])
- if random.random() < train_rate:
- train.append([user, item])
- else:
- test.append([user, item])
- return PreProcessData(train), PreProcessData(test)
-
- def PreProcessData(originData):
- """
- 建立User-Item表,结构如下:
- {"User1": {MovieID1, MoveID2, MoveID3,...}
- "User2": {MovieID12, MoveID5, MoveID8,...}
- ...
- }
- 在这个数据结构中,用户 user 是字典的 key,其 value 是一个集合,表示该用户评分过的电影集合。
- 因此,如果该用户之前出现过,则直接将电影添加到该用户的集合中;
- 如果该用户是第一次出现,则先将其加入到字典中,并将其电影集合初始化为空集合。
- """
- trainData = dict()
- for user, item in originData:
- trainData.setdefault(user, set())
- trainData[user].add(item)
- return trainData
-
-
- class ItemCF(object):
- ''' 初始化 '''
- def __init__(self, trainData, similarity="cosine", norm=True):
- self._trainData = trainData
- self._similarity = similarity
- self._isNorm = norm
- self._itemSimMatrix = dict() # 物品相似度矩阵
-
- def similarity(self):
- N = defaultdict(int) #记录每个物品的喜爱人数
- for user, items in self._trainData.items():
- for i in items:
- self._itemSimMatrix.setdefault(i, dict())
- N[i] += 1
- for j in items:
- if i == j:
- continue
- self._itemSimMatrix[i].setdefault(j, 0)
- if self._similarity == "cosine":
- self._itemSimMatrix[i][j] += 1 #计算两个物品之间的共现次数的
- elif self._similarity == "iuf": #另一种避免火热错误推荐方法
- self._itemSimMatrix[i][j] += 1. / math.log1p(len(items) * 1.)
- for i, related_items in self._itemSimMatrix.items():
- for j, cij in related_items.items():
- self._itemSimMatrix[i][j] = cij / math.sqrt(N[i]*N[j])
- # 是否要标准化物品相似度矩阵
- if self._isNorm:
- for i, relations in self._itemSimMatrix.items():
- max_num = relations[max(relations, key=relations.get)]
- # 对字典进行归一化操作之后返回新的字典
- self._itemSimMatrix[i] = {k : v/max_num for k, v in relations.items()}
-
- def recommend(self, user, N, K):
- """
- :param user: 被推荐的用户user
- :param N: 推荐的商品个数
- :param K: 查找的最相似的用户个数
- :return: 按照user对推荐物品的感兴趣程度排序的N个商品
- """
- recommends = dict()
- # 先获取user的喜爱物品列表
- items = self._trainData[user]
- '''
- 遍历用户已经喜欢的物品列表 items对于每一个物品 item,
- 在物品相似度矩阵 _itemSimMatrix 中找到与其最相似的K个物品,
- 即遍历该物品在 _itemSimMatrix 中的行,得到 (j, sim) 的键值对,
- 其中 j 表示另一个物品,sim 表示两个物品之间的相似度。
- 将 (j, sim) 的键值对按照相似度进行排序,选择相似度最高的前K个物品。
- 将选出来的K个物品放入推荐字典 recommends 中,若该物品在 items 中出现过,则跳过不计。返回排序后的推荐字典。
- '''
- for item in items:
- # 对每个用户喜爱物品在物品相似矩阵中找到与其最相似的K个
- for i, sim in sorted(self._itemSimMatrix[item].items(), key=itemgetter(1), reverse=True)[:K]:
- if i in items:
- continue # 如果与user喜爱的物品重复了,则直接跳过
- recommends.setdefault(i, 0.)
- recommends[i] += sim
- # 根据被推荐物品的相似度逆序排列,然后推荐前N个物品给到用户
- return dict(sorted(recommends.items(), key=itemgetter(1), reverse=True)[:N])
-
- def train(self):
- self.similarity()
-
- if __name__ == "__main__":
- train, test = LoadMovieLensData("Data/ratings1.dat", 0.8)
-
- print("train data size: %d, test data size: %d" % (len(train), len(test)))
- ItemCF = ItemCF(train, similarity='iuf', norm=True)
- ItemCF.train()
-
- # 分别对以下4个用户进行物品推荐
- print(ItemCF.recommend(1, 5, 80))
- print(ItemCF.recommend(2, 5, 80))
- print(ItemCF.recommend(3, 5, 80))
- print(ItemCF.recommend(4, 5, 80))
上述代码对测试集中前4个用户进行了电影推荐,对每个用户而言,从与他们喜爱电影相似的80部电影中挑选出5部推荐。
输出结果是一个dict,里面包含了给用户推荐的电影以及用户对每部电影的感兴趣程度,按照逆序排列。
参考链接:
https://www.jianshu.com/p/f306a37a7374
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。