surprise
是Simple Python Recommendation System Engine
的缩写,是一个为了实现推荐系统的框架。它自带了SVD,user-based,item-based协同过滤算法等多种推荐算法,接口简单,功能强大。但官方文档写的并不好,笔者花了不少时间,都没有找到如何预测某个user对某个item进行打分这样的基础用法,所以把摸索后得到的经验分享于此。
为了验证算法结果的正确性,自己定义了一个数据集,如下所示(保存为mydata.csv)
1,1,1
1,2,2
1,3,3
1,4,4
1,5,5
2,1,1
2,2,2
2,3,3
2,4,4
2,5,5
3,1,1
3,2,2
3,3,3
3,4,4
3,5,5
4,1,1
4,2,2
4,3,3
4,4,4
4,5,5
5,2,2
5,3,3
这个数据集中,每一行说明了user对item的评分,比如“2,1,1”,是指user-2对item-1的评分是1。
可见,这个数据集中一共有5位user,5个item。每一位user对第i个item的打分都是i。这样的简化是为了验证算法的准确性。user-5只对item-2、item-3进行了评分,根据数据集中体现出来的规律,user-5没打分的item,应该满足如下规律:
如果我们的算法得到的打分和上面的一样,就说明算法的结果是100%正确的。如果算法得到的结果有所不同,但user-5对item-i的打分是依次升高的,也说明算法的结果是正确的。
接下来我们讲解如何实现user-5对item-i打分的预测值。每段代码的讲解写在代码段后面。
from surprise import SVD
from surprise import Dataset, Reader
import os
首先,导入surprise中必须的class:SVD是SVD算法,Dataset是创建满足surprise需要的数据集所需的类,Reader是做数据读取,类似pandas
# 指定文件所在路径
file_path = os.path.expanduser('mydata.csv')
# 告诉文本阅读器,文本的格式是怎么样的
reader = Reader(line_format='user item rating', sep=',')
# 加载数据
data = Dataset.load_from_file(file_path, reader=reader)
trainset = data.build_full_trainset()
上面这一段代码读取之前构建的本地数据集’mydata.csv’,并用Reader读取数据,将数据加载为Dataset结构。
这里要注意的是,Dataset结构并不是surprise结构直接进行计算的最终结构,还需要通过data.build_full_trainset()
将其转换为Trainset这样的数据结构,才能在后续过程中直接用于训练。
algo = SVD()
algo.fit(trainset)
这里用SVD对数据进行训练,也就是SVD推荐算法,将原始得分矩阵分解后,对未知得分进行重新计算的过程。
# we can now query for specific predicions
uid = str(5) # raw user id
iid = str(1) # raw item id
# get a prediction for specific users and items.
pred = algo.predict(uid, iid)
print('rating of user-{0} to item-{1} is '.format(uid, iid), pred.est)# rating of user-5 to item-1
接下来,我们就能计算user-5对item-1的打分(预测值)了。这里要注意,user-id和item-id,都要将id转换为string类型。然后调用algo.predict(uid, iid)
就能得到预测值了,原始的pred变量内容如下所示
user: 5 item: 1 r_ui = None est = 2.38 {'was_impossible': False}
这其中,est就是预测值,我们这里预测得到:
可见,user-5对item-i的打分是依次升高的,和预想的情况相同,说明算法的结果是正确的。
可以迭代用algo.predict(uid, iid)
对每个得分进行计算,然后遍历所有得分得到TOP-N。但这样做稍显笨拙,其实surprise作为一个优秀的推荐系统框架,已经给出了更好的接口,下文进行详述。每段代码的讲解写在代码段后面。
from collections import defaultdict
from surprise import SVD
from surprise import Dataset,Reader
import os
# 指定文件所在路径
file_path = os.path.expanduser('mydata.csv')
# 告诉文本阅读器,文本的格式是怎么样的
reader = Reader(line_format='user item rating', sep=',')
# 加载数据
data = Dataset.load_from_file(file_path, reader=reader)
trainset = data.build_full_trainset()
algo = SVD()
algo.fit(trainset)
首先是读入数据,转换为Trainset结构,并用SVD算法进行训练。这个过程和上一节的内容相同。
def get_top_n(predictions, n=10):
# First map the predictions to each user.
top_n = defaultdict(list)
# uid: 用户ID
# iid: item ID
# true_r: 真实得分
# est:估计得分
for uid, iid, true_r, est, _ in predictions:
top_n[uid].append((iid, est))
# Then sort the predictions for each user and retrieve the k highest ones.
# 为每一个用户都寻找K个得分最高的item
for uid, user_ratings in top_n.items():
user_ratings.sort(key=lambda x: x[1], reverse=True)
top_n[uid] = user_ratings[:n]
return top_n
接下来定义get_top_n()
函数,它能根据predictions
结果进行解析,获取top_n字典,该字典的key是user-id,value是该user打分(预测值)最高的n个item-id。
predictions
的数据结构,是surprise中的算法自带接口algo.test()
的输出值。
testset = [
('5','1',0),# 想获取第5个用户对第1个item的得分
('5','4',0),# 0这个位置是真实得分,不知道时可以写0
('5','5',0),# 但写0后,就没法进行算法评估了,因为不知道真实值
]
predictions = algo.test(testset)
这里是将需要计算的user-id,item-id,true-rating-value构建成测试集。注意真实得分true-rating-value
我们可能无法得到,那用0来代替也是可以的。但写0后,就没法进行算法评估了,因为不知道真实值,算法评估得到的值也是不准的。
algo.test()
的输出值predictions
中的数据如下所示:
[Prediction(uid='5', iid='1', r_ui=0, est=2.2154870570408325, details={'was_impossible': False}),
Prediction(uid='5', iid='4', r_ui=0, est=3.037552499639195, details={'was_impossible': False}),
Prediction(uid='5', iid='5', r_ui=0, est=3.434713829035328, details={'was_impossible': False})]
uid就是user-id,iid就是item-id,其中est是预测得分值。
top_n = get_top_n(predictions, n=2)
# Print the recommended items for each user
for uid, user_ratings in top_n.items():
print(uid, [iid for (iid, _) in user_ratings])
使用get_top_n()函数,获取测试集中所有用户得分最高的n(等于2)个item-id,并将print出来,得到结果如下
5 ['5', '4']
可见,对user-5来说,得分最高的是item-5和item-4。这与数据集要体现出来的结果是一样的。