问题描述
在使用Python的Surprise库(scikit-surprise)搭建一个简单的TopN推荐系统对协同过滤推荐算法进行测试,计算分类预测准确度如召回率(Recall)、精确率(Precision)和覆盖率(Coverage)时,使用train_test_split()
方法对数据集进行的划分,遇到该库的Trainset模块报错为:ValueError: User 7746 is not part of the trainset.
这个问题是我使用Jester笑话数据集进行测试时遇到的,同时我还使用了MovieLens1M和MovieLens100K数据集,但都无此问题。
解决过程
在研究完这个库数据划分源码之后,分析发现问题确实出在数据集中。
def split(self, data):
"""Generator function to iterate over trainsets and testsets.
Args:
data(:obj:`Dataset<surprise.dataset.Dataset>`): The data containing
ratings that will be divided into trainsets and testsets.
Yields:
tuple of (trainset, testset)
"""
test_size, train_size = self.validate_train_test_sizes(
self.test_size, self.train_size, len(data.raw_ratings)
)
rng = get_rng(self.random_state)
for _ in range(self.n_splits):
if self.shuffle:
permutation = rng.permutation(len(data.raw_ratings))
else:
permutation = np.arange(len(data.raw_ratings))
raw_trainset = [data.raw_ratings[i] for i in permutation[:test_size]]
raw_testset = [
data.raw_ratings[i]
for i in permutation[test_size : (test_size + train_size)]
]
trainset = data.construct_trainset(raw_trainset)
testset = data.construct_testset(raw_testset)
yield trainset, testset
要使程序计算覆盖率时能够运行下去,划分之后的训练集中必须包含全部用户,观察MoviLens数据集可以发现每个用户几乎都有30条以上的评分记录所以在numpy.random.permutation(len(data.raw_ratings))
生成的随机序列中把所有用户都抽中几条评分记录划分进训练集的概率非常大,然后我根据报错ValueError: User 7746 is not part of the trainset.
去jester_ratings.dat
数据集中查看用户7746的评分记录,发现才3条,那么很有可能这名用户连一条记录都没被抽中划分进训练集中,把这名用户及其评分记录删除,发现报错的就是其它用户了,这些用户同样是评分记录非常少的。
解决方案
使用pandas读取jester_ratings.dat
,然后使用groupby().count()
功能统计每一位用户的评分记录数量,将评分记录数量少于20条的用户剔除,把剔除后的数据重新保存为数据集以便后面使用。
from surprise import Dataset, Reader
from surprise.model_selection import train_test_split
import pandas as pd
import csv
if __name__ == '__main__':
data = pd.read_csv('jester_ratings.dat', header = None, encoding ='utf - 8', delimiter = "\t\t", quoting = csv.QUOTE_NONE, engine = 'python')
# print(data.head())
df = data.iloc[:, 0].groupby(data.iloc[:, 0]).count()
# print(df.index)
for index in df.index:
if df[index] < 20:
# 从data中删除这个用户
data = data[data.iloc[:, 0] != index]
data.to_csv("jester_ratings.csv", index=False)
jester_data = Dataset.load_from_df(data, Reader(line_format='user item rating', rating_scale=(-10, 10)))
train, test = train_test_split(jester_data, test_size=0.2, random_state=42)
print(train.n_items, train.n_users)
这样就能正常运行了。运行完后发现Jester数据集计算覆盖率似乎意义不大,计算出来覆盖率几乎都是100%。