本文记录学习使用Surprise库实现电影数据集MovieLens的推荐系统。

安装

pip

1
2
pip install numpy
pip install scikit-surprise

conda

1
conda install -c conda-forge scikit-surprise

先来看一下Surprise官方的例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from surprise import SVD
from surprise import Dataset
from surprise.model_selection import cross_validate

# 加载movielens-100k数据集,如果本地没有则会自动下载
data = Dataset.load_builtin('ml-100k')

# 使用SVD算法
algo = SVD()

# 对数据集切成5份进行交叉验证
cross_validate(algo, data, measures=['RMSE', 'MAE'], cv=5, verbose=True)

会看到类似如下的输出结果:

1
2
3
4
5
6
7
Evaluating RMSE, MAE of algorithm SVD on 5 split(s).

            Fold 1  Fold 2  Fold 3  Fold 4  Fold 5  Mean    Std
RMSE        0.9311  0.9370  0.9320  0.9317  0.9391  0.9342  0.0032
MAE         0.7350  0.7375  0.7341  0.7342  0.7375  0.7357  0.0015
Fit time    6.53    7.11    7.23    7.15    3.99    6.40    1.23
Test time   0.26    0.26    0.25    0.15    0.13    0.21    0.06

基于用户的推荐

基本思想是先找到给定用户User的相似用户,然后取各相似用户的观影记录推荐给那个给定的用户User。下面使用代码来实现。

引入必要的包及类

1
2
import pandas as pd
from surprise import Reader, Dataset, KNNBaseline

加载数据

1
2
3
4
5
6
7
8
9
# 使用pandas读取评分数据,以及电影数据
# 评分数据包含四列:userId, movieId, rating, timestamp
# 电影数据包含三列:movieId, title, genres
ratings = pd.read_csv('ml-latest-small/ratings.csv') 
movies = pd.read_csv('ml-latest-small/movies.csv')

# 把两个csv的数据合并成一份数据,以movieId为基准
df = pd.merge(ratings, movies, on='movieId')
df.head()

合并后的数据格式如下表

看下整个数据集的统计信息:

1
df.describe()

创建训练数据

1
2
3
4
5
6
7
8
# 需要使用Reader对象来指定输入数据的一些格式
reader = Reader(rating_scale=(0.5, 5.0))
# 通过surprise.Dataset类的load_from_df方法,加载pandas数据格式
# 只支持user, item, rating三列的数据,所以需要从pandas里面提取对应的三列
# 以rader指定的格式来加载数据
data = Dataset.load_from_df(df[['userId', 'movieId', 'rating']], reader)
# 把加载的数据全部作为训练数据生成训练数据集
trainset = data.build_full_trainset()

使用基于用户的算法

1
2
3
4
5
# 这里基于用户的算法,所以要指定user_bases为True
# 如果是基于物品的算法,user_base指定为False即可
sim_options = {'name': 'pearson_baseline', 'user_based': True}
algo = KNNBaseline(sim_options=sim_options)
algo.fit(trainset)

实现推荐

假设以用户1为指定用户,查找与userId为1的相似用户

1
2
3
4
5
6
7
8
# 首先需要把userId转换为surprise内部的id,因为真实的外部userId很有可能是不连续的,不利于计算
inner_user_id = algo.trainset.to_inner_uid(1)
# 通过内部用户id获取邻近(相似)的其他内部用户id
neighbor_users = algo.get_neighbors(inner_user_id, k=5)
print('neighbor users inner id: ', neighbor_users)
# 可能通过to_raw_uid把内部用户id转换为真实的用户id
for u in neighbor_users:
    print('real user id: ', algo.trainset.to_raw_uid(u))

查看需要被推荐的用户的记录

1
df[df.userId == 1].head()

指定的用户1的前5条记录如下表

查看其他一个相似用户的记录

1
2
uid = algo.trainset.to_raw_uid(neighbor_users[0])
df[df.userId == uid].head()

某个相似用户的影评记录如下表:

可以看到两个用户之间还是有很大的相似之处的

合并推荐结果

最后就是把查找到的相似的用户所有的电影取出来,去掉已经看过的,剩下的就可以推荐给用户1了。

如果觉得结果太多,还可以把所有相似的用户的电影评分求平均,然后取评分最高的几个推荐给用户1。