阅读 5124

PyCon 2018: SVD推荐系统在Python中的实践

引言

继搜索引擎之后,推荐系统改变了用户与网站之间的交互方式,在提高用户参与度和多样化推荐产品方面有重要的应用。亚马逊有35%的利润来源于它的推荐系统,Netflix有75%的用户根据推荐系统选择电影。

推荐系统是一个非常大的话题,本文介绍一种常用的基于模型的协同过滤算法——SVD(奇异值分解),在python中的使用。

假设我们用m个用户,n个商品,每个用户对每个商品的评分可以组成一个m*n的二维矩阵。当然,这个矩阵中会有非常多的值是不知道的,可能是用户没有用过这个商品,也有可能用户使用后没有进行评分。如下图所示

图中空白位置即未知的值。接下来,我们需要做的是根据这个残缺的二维矩阵中已知的值,预测出未知的值,即预测出每一个用户对每一个商品的评分。

可以想象,当矩阵被预测值补充完整之后,矩阵的每一行即表示一个用户对所有商品的评分,可以从这些评分中提取评分最高的几个商品推荐给用户,这样我们就完成了一个推荐系统模型。

接下来,就是如何通过已知值预测未知值的问题了,这里我们采用矩阵分解的方式,如图所示

中间矩阵可以拆分为左边和上边两个矩阵的乘积,这就是奇异值分解,一个矩阵总是可以拆分成两个矩阵相乘,SVD的原理可以见这篇博客,SVD方法在推荐系统中应用的原理可以参考这篇博客,下面,我们主要来讲讲如何在python中使用。

初始化及建立模型

首先需要安装surprise库,用下面这条命令

pip install scikit-surprise
复制代码

假设我们现在有这样的数据(注:load_movielens函数不是库内置的,需要拿数据文件并自己定义,详情见文末链接中的作者源码)

movielens_df: pd.DataFrame = load_movielens()
movielens_df.head(5)

        user_id	    movie_title	            rating
36649	User 742	Jerry Maguire (1996)	4
2478	User 908	Usual Suspects, The (1995)	3
82838	User 758	Real Genius (1985)	4
69729	User 393	Things to Do in Denver when You're Dead (1995)	3
36560	User 66	    Jerry Maguire (1996)	4
复制代码

即用户与电影之间的评分对应关系,下面导入需要的模块

from surprise import SVD
from surprise import Dataset, Reader
from surprise.model_selection import cross_validate, train_test_split
复制代码

下面开始正式建模

第一步:初始化reader,指定评分范围为1分到5分

reader = Reader(rating_scale=(1, 5))
复制代码

第二步:初始化数据,传入的数据只能有3列,必须按照这样的顺序[user_id, product_id, rating]

data = Dataset.load_from_df(movielens_df, reader)
复制代码

这里需要注意:data变量已经不是DataFrame类型了,而是surprise库中的一种数据类型。从上面movielens_df的结果可以看出,我们的数据不是本文最初提到的矩阵形式,所以这一步转换就会将数据转化成surprise库需要的形式,便于之后的算法求解。

第三步:拆分训练集与测试集,75%的样本作为训练集,25%的样本作为测试集

trainset, testset = train_test_split(data, test_size=.25)
复制代码

这里的trainset的类型是surprise.dataset.Trainset类型,我们可以查看数据的基本信息

trainset.n_users # 943
trainset.n_items # 596
复制代码

这说明我们要用于训练的样本共有943个用户,596个商品。

第四步:训练模型,指定有100个隐含特征,使用训练集进行训练

model = SVD(n_factors=100)
model.fit(trainset)
复制代码

这里需要说明一下,100个隐含特征是指,原本943*596的矩阵会被拆分成943*100和100*596的两个矩阵乘积,n_factors值可以任意指定只要不超过596即可,但是设置不同的值将会拟合出不同的模型,需要选择使结果较优的值。

我们也可以查看拆分出来的两个矩阵

model.pu.shape # (943, 100)
model.qi.shape # (596, 100)
复制代码

根据模型结果进行推荐

预测一个用户对一个电影的评分

指定用户和电影名即可

a_user = "User 196"
a_product = "Toy Story (1995)"
model.predict(a_user, a_product)

# Prediction(uid='User 196', iid='Toy Story (1995)', r_ui=None, est=3.93380711688207, details={'was_impossible': False})
复制代码

电影之间相关性

这里我们需要写get_vector_by_movie_titlecosine_distance函数(详情见文末链接中作者源码)

之后我们就可以实现输入两个电影名称,即可获得他们之间的相关性

toy_story_vec = get_vector_by_movie_title('Toy Story (1995)', model)
wizard_of_oz_vec = get_vector_by_movie_title('Wizard of Oz, The (1939)', model)

similarity_score = cosine_distance(toy_story_vec, wizard_of_oz_vec)
similarity_score
# 0.9461284008856982
复制代码

这是完全不考虑导演等电影特征计算出来的电影相似度,因为我们只使用了评分数据。

寻找与一个电影最相似的电影

首先需要实现get_top_similarities函数,获得最相似的五个电影,最后效果如下

get_top_similarities('Star Wars (1977)', model)

	vector cosine distance	movie title
0	0.000000	            Star Wars (1977)
1	0.262668	            Empire Strikes Back, The (1980)
2	0.295667	            Return of the Jedi (1983)
3	0.435423	            Raiders of the Lost Ark (1981)
复制代码

参考资料

1.视频 Daniel Pyrathon - A practical guide to Singular Value Decomposition in Python - PyCon 2018

2.surprise帮助文档

3.视频配套代码