本文将利用 python 构建一个简单的推荐系统, 在此之前读者需要对 pandas 和 numpy 等数据分析包有所了解.
什么是推荐系统?
推荐系统的目的是通过发现数据集中的模式, 为用户提供与之最为相关的信息. 当你访问 Netflix 的时候, 它也会为你推荐电影. 音乐软件如 Spotify 及 Deezer 也使用推荐系统进行音乐推荐.
下图说明了推荐系统是如何在电子商务网站的上下文中工作的.
两名用户都在某电商网站购买了 A,B 两种产品. 当他们产生购买这个动作的时候, 两名用户之间的相似度便被计算了出来. 其中一名用户除了购买了产品 A 和 B, 还购买了 C 产品, 此时推荐系统会根据两名用户之间的相似度会为另一名用户推荐项目 C.
推荐系统的主要分类
目前, 主流的推荐系统包括基于内容的推荐以及协同过滤推荐. 协同过滤简单来说就是根据用户对物品或者信息的偏好, 发现物品或者内容本身的相关性, 或者是发现用户的相关性, 然后再基于这些关联性进行推荐.
举个简单的例子, 如果要向个用户推荐一部电影, 那么一定是基于他 / 她的朋友对这部电影的喜爱. 基于协同过滤的推荐又可以分为两类: 启发式推荐算法 (Memory-based algorithms) 及基于模型的推荐算法(Model-based algorithms). 启发式推荐算法易于实现, 并且推荐结果的可解释性强. 启发式推荐算法又可以分为两类:
基于用户的协同过滤(User-based collaborative filtering): 主要考虑的是用户和用户之间的相似度, 只要找出相似用户喜欢的物品, 并预测目标用户对对应物品的评分, 就可以找到评分最高的若干个物品推荐给用户. 举个例子, Derrick 和 Dennis 拥有相似的电影喜好, 当新电影上映后, Derick 对其表示喜欢, 那么就能将这部电影推荐给 Dennis.
基于项目的协同过滤(Item-based collaborative filtering): 主要考虑的是物品和物品之间的相似度, 只有找到了目标用户对某些物品的评分, 那么就可以对相似度高的类似物品进行预测, 将评分最高的若干个相似物品推荐给用户. 举个例子, 如果用户 A,B,C 给书籍 X,Y 的评分都是 5 分, 当用户 D 想要买 Y 书籍的时候, 系统会为他推荐 X 书籍, 因为基于用户 A,B,C 的评分, 系统会认为喜欢 Y 书籍的人在很大程度上会喜欢 X 书籍.
基于模型的推荐算法利用矩阵分解, 有效的缓解了数据稀疏性 https://en.wikipedia.org/wiki/Sparse_matrix 的问题. 矩阵分解是一种降低维度的方法, 对特征进行提取, 提高推荐准确度. 基于模型的方法包括[决策树](), 基于规则的模型 https://en.wikipedia.org/wiki/Rule-based_modeling , 贝叶斯方法 https://en.wikipedia.org/wiki/Bayesian_inference 和潜在因素模型.
基于内容的推荐系统会使用到元数据, 例如流派, 制作人, 演员, 音乐家等来推荐电影或音乐. 如果有人看过并喜欢范. 迪塞尔主演的《速度与激情》, 那么系统很有可能将他主演的另一部电影《无限战争》推荐给这些用户. 同样, 你也可以从某些艺术家那里得到音乐推荐. 基于内容的推荐的思想是: 如果你喜欢某样东西, 你很可能会喜欢与之相似的东西.
数据集
我们将使用到 MovieLes 数据集, 该数据集是关于电影评分的, 由明尼苏达大学的 Grouplens 研究小组整理, 分为 1M,10M,20M 三个规格. Movielens 还有一个网站, 可以注册, 撰写评论并获取电影推荐. 若不想用此数据集, 你也可以从 Dataquest 的数据资源中找到更多用于各种数据科学任务的数据集.
推荐系统构建
我们将使用 movielens 构建一个基于项目相似度的推荐系统, 首先导入 pandas 和 numpy.
- import pandas as pd
- import numpy as np
- import warnings
- warnings.filterwarnings('ignore')
接下来利用 pandas 中的 read_csv()对数据进行加载. 数据集中的数据以 tab 进行分隔, 我们需要设置 sep = t 来指定字符的分隔符号, 然后通过 names 参数传入列名.
df = pd.read_csv('u.data', sep='\t',names=['user_id','item_id','rating','titmestamp'])
接下来, 检查正在处理的数据.
df.head()
相比只知道电影的 ID, 能看到它们的标题更为方便. 接下来, 下载电影的标题并将它们整合到数据集中.
- movie_titles = pd.read_csv('Movie_Titles')
- movie_titles.head()
因为 item_id 列是相同的, 我们便可以在此列上对数据进行合并.
- df = pd.merge(df, movie_titles, on='item_id')
- df.head()
每列释义如下:
User_id: 用户 ID
Item_id: 电影 ID
Rating: 用户给电影的评分, 介于 1 到 5 分之间
Timestamp: 对电影进行评分的时间点
Title: 电影标题
使用 description 或 info 命令, 可以得到数据集的简要描述, 以帮助我们更好的理解数据集.
df.describe()
通过上一步, 可以知道电影的平均分为 3.52, 最高为 5 分.
接下来构建一个包含每部电影的平均评分和被评分次数的 dataframe, 用来计算电影间的相关性. 相关性是一种统计度量, 用来表示两个或多个变量在一起波动的程度, 电影之间的相关系数越高, 越相似.
在本例中, 我们将使用皮尔逊相关系数, 它的变化范围为 - 1 到 1. 当相关系数为 1 时, 为完全正相关; 当相关系数为 - 1 时, 为完全负相关; 相关系数越接近于 0, 相关度越弱. 利用 pandas 中的 groupby 功能创建 dataframe, 按标题列对数据集进行分组, 并计算每部电影的平均分.
- ratings = pd.DataFrame(df.groupby('title')['rating'].mean())
- ratings.head()
接下来计算每部电影被评分的次数, 观察它与电影平均评分之间的关系. 一部 5 分的电影很可能只有一个用户评分. 从统计学上来说, 把它视为 5 分电影是不合理的.
因此, 在构建推荐系统时, 我们需要为评分次数设置一个阈值. 使用 pandas 中的 groupby 功能创建 number_of_ratings 列, 按 title 列进行分组, 然后使用 count 函数计算每部电影的被评分次数. 之后, 使用 head()函数查看新的 dataframe.
- ratings['number_of_ratings'] = df.groupby('title')['rating'].count()
- ratings.head()
利用 pandas 中的绘图功能绘制直方图, 可视化评分分布.
- import matplotlib.pyplot as plt
- %matplotlib inline
- ratings['rating'].hist(bins=50)
从中可以看出, 多数电影的分值在 2.5 到 4 分之间. 接下来将以同样的方式对 number_of_ratings 进行可视化.
ratings['number_of_ratings'].hist(bins=60)
从直方图中可以清楚地看出大多数电影都只有较少的评分, 那些评分次数多的电影都拥有较高的知名度.
接下来探索电影评分和被评分次数之间的关系. 使用 seaborn 绘制散点图, 通过 jointplot()函数实现.
- import seaborn as sns
- sns.jointplot(x='rating', y='number_of_ratings', data=ratings)
从图中可以看出电影的平均评分和被评分次数之间呈正相关关系. 图表显示, 一部电影的评分越高, 平均分也就越高. 在为每部电影的评分设置阈值时, 这一点尤其重要.
接下来构建基于项目的推荐系统. 我们需要将数据集转换为一个矩阵, 以电影标题为列, 以 user_id 为索引, 以评分为值. 之后会得到一个 dataframe, 其中列是 movie 标题, 行是 user_id. 每列代表所有用户对所有电影的评分. 若评分为 NaN(Not a Number), 则表示用户没有对某一部电影进行评分. 矩阵被用来计算电影之间的相关性. 使用 pandas 中的 pivot_table 创建电影矩阵.
- movie_matrix = df.pivot_table(index='user_id', columns='title', values='rating')
- movie_matrix.head()
接下来, 使用 pandas 中的 sort_values 工具, 设置升序为 false, 以便从评分最高的电影中进行选择, 然后使用 head()函数查看分数前 10 的电影.
ratings.sort_values('number_of_ratings', ascending=False).head(10)
假设某用户看过《空军一号》和《超时空接触》, 我们想根据观看历史向该用户推荐电影. 通过计算这两个电影和数据集中其他电影的之间的相关性, 寻找与之最为相似的电影, 为用户进行推荐. 首先, 用 movie_matrix 中的电影评分创建一个 dataframe.
- AFO_user_rating = movie_matrix['Air Force One (1997)']
- contact_user_rating = movie_matrix['Contact (1997)']
Dataframe 中包含 user_id 和对应用户给这两个电影的评分. 利用如下代码进行查看.
- AFO_user_rating.head()
- contact_user_rating.head()
使用 pandas 中的 corwith 功能计算两个 dataframe 对象的行或列的两两相关关系, 从而得出每部电影与《空军一号》电影之间的相关性.
similar_to_air_force_one=movie_matrix.corrwith(AFO_user_rating)
可以看到,《空军一号》与《直到有你》之间的相关性是 0.867, 表明这两部电影有很强的相似性.
similar_to_air_force_one.head()
接下来, 计算《超时空接触》和其他电影之间的相关性. 程序与上面相同.
similar_to_contact = movie_matrix.corrwith(contact_user_rating)
通过计算, 我们发现《超时空接触》和《直到有你》之间的相关性更强, 为 0.904.
similar_to_contact.head()
由于只有部分用户对部分电影进行了评分, 导致矩阵中有许多缺失的值. 为了使结果看起来更有吸引力, 我们将删除 null 值并将 correlation results 转化为 dataframe.
- corr_contact = pd.DataFrame(similar_to_contact, columns=['Correlation'])
- corr_contact.dropna(inplace=True)
- corr_contact.head()
- corr_AFO = pd.DataFrame(similar_to_air_force_one, columns=['correlation'])
- corr_AFO.dropna(inplace=True)
- corr_AFO.head()
通过上述步骤, 计算出了与《超时空接触》和《空军一号》最为相似的电影. 然而, 有些电影被评价的次数很低, 最终可能仅仅因为一两个人给了 5 分而被推荐. 设置阈值可解决这个问题. 从之前的直方图中我们看到评分次数从 100 急剧下降, 于是我们将阈值设为 100, 不过你可以根据自己的需求进行调整. 接下来, 利用 number_of_ratings 列将两个 dataframe 连接起来.
- corr_AFO = corr_AFO.join(ratings['number_of_ratings'])
- corr_contact = corr_contact.join(ratings['number_of_ratings'])
- corr_AFO .head()
- corr_contact.head()
获取并查看前 10 部最为相关的电影.
corr_AFO[corr_AFO['number_of_ratings']> 100].sort_values(by='correlation', ascending=False).head(10)
由于阈值不同, 结果也会有所不同. 在设置阈值后, 与《空军一号》最相似的电影是《猎杀红色十月》, 相关系数为 0.554.
接下来获取并查看与《超时空接触》最为相关的前 10 部电影.
corr_contact[corr_contact['number_of_ratings']> 100].sort_values(by='Correlation', ascending=False).head(10)
《超时空接触》最相似的电影是《费城》, 相关系数为 0.446, 被评分次数为 137. 根据此结果, 我们可以向喜欢《超时空接触》的用户推荐列表中的电影.
来源: https://yq.aliyun.com/articles/647542