【翻译搬砖】本文为微软推荐系统实践项目的SAR样例的翻译文档(译者: Funco),原文地址: SAR Single Node on MovieLens。
github: microsoft/recommenders 是微软开源的推荐系统最佳实践案例,提供了大量的推荐系统算法讲解以及具体案例,并封装了一些在推荐系统中通用的方法以供我们在以后的实践中借鉴。这些特点,在当前文档中都用体现,欢迎交流,共同学习。
文中括号"()"包含一个数字,表示批注,每一段文本中的批注都会在该段文本后面补充具体内容,可能是名词解释,也可能是笔者关于不确定的翻译结果给出原文参照。此外,原文中有大量出现 Item 这个单词,我翻译为了 物品,但实际上,某些时候,翻译为 数据项 会使语句更通顺,不这么做的原因是,我希望规范名词——参照基于物品的协同过滤 (ItemCF,Item-based Collaborative Filtering)。
SAR(Simple Algorithm for Recommendation) 一种快速、可扩展的基于用户操作记录的个性化推荐算法,快速且可扩展。它能给出易于理解且可解释的推荐结果,同时还处理了『冷启动』问题**(1)。SAR是一种近邻算法(在阿加沃尔的《推荐系统》中有提及(2)),用于为每一位用户找到最需要的物品。关于SAR的更多细节,可以参阅 deep dive notebook(3)**。
- It produces easily explainable and interpretable recommendations and handles “cold item” and “semi-cold user” scenarios.
- 阿加沃尔(Aggarwal)所著的**《推荐系统原理与实践》(《Recommender Systems: The Textbook》)是推荐系统领域被广泛推崇的书籍之一。其书关于近邻算法**,尤其是近邻协同过滤算法有大量描述。
可以简单的理解,SAR的优点就是从各方面来讲都简单、快速,但又具备相当精度。
不关注用户特征或物品特征
它在需要用户特征或物品特征的场景下表现较差**(1)**
占用内存高
它需要创建多个稀疏矩阵**(2)。大多数基于矩阵分解的算法也存在该问题(3)**
它倾向于一个隐含的评分方案,但不预测评分**(4)**
- SAR本质是近邻协同过滤
- 原文说的是会创建MxM的稀疏矩阵(M是物品数量),这只是相似度矩阵,其实我们还需要用户-物品评分矩阵,经过一系列运算,还会产生作为中间产物的矩阵
- 矩阵分解,常见如奇异值分解(SVD)和特征分解
- 这句话我也不晓得怎么理解,原文是:it favors an implicit rating scenario and it does not predict ratings.
# set the environment path to find Recommenders
import sys
sys.path.append("../../")
import logging
import numpy as np
import pandas as pd
import papermill as pm
from reco_utils.common.timer import Timer
from reco_utils.dataset import movielens
from reco_utils.dataset.python_splitters import python_stratified_split
from reco_utils.evaluation.python_evaluation import map_at_k, ndcg_at_k, precision_at_k, recall_at_k
from reco_utils.recommender.sar import SAR
print("System version: {}".format(sys.version))
print("Pandas version: {}".format(pd.__version__))
注意
- reco_utils 是原文所在项目的包,无法通过 pip install 获取。
在这里,SAR处理符合下述数据结构的数据:
(1)。
每行记录表示一个用户关于一件物品的一次操作记录。该记录可能是电商网站中不同类型的事件,比如:点击浏览物品;添加至购物车;分享链接;诸如此类。每个事件我们可以指定不同的权值,比如:我们可以将『购买』定义为10,而『查看详情』可以定义为1 (2)。
数据集 MovieLens 是 用户对电影评分 的格式化数据,后续的示例代码中都会使用该数据集展示算法过程。
- SAR is intended to be used on interactions with the following schema: , ,,[], [].
- 这是 隐式评分 的案例,我只是想在这里强调及时收集并记录隐式评分对系统未来可能需要的分析很重要。
# MOVIELENS_DATA_SIZE = '100k'
data = movielens.load_pandas_df(
size=MOVIELENS_DATA_SIZE,
local_cache_path=sys.path[1] + '/downloads/'
)
# Convert the float precision to 32-bit in order to reduce memory consumption
data['rating'] = data['rating'].astype(np.float32)
data.head()
print(data)
输出结果
userID itemID rating timestamp
0 196 242 3.0 881250949
1 186 302 3.0 891717742
2 22 377 1.0 878887116
3 244 51 2.0 880606923
4 166 346 1.0 886397596
... ... ... ... ...
99995 880 476 3.0 880175444
99996 716 204 5.0 879795543
99997 276 1090 1.0 874795795
99998 13 225 2.0 882399156
99999 12 203 3.0 879959583
[100000 rows x 4 columns]
注意
movielens 是 reco_utils 的一个子包,**movielens.load_pandas_df() **会在每次被调用时都下载一部分资源到临时文件夹中。建议是
在项目 recommenders 目录下创建 downloads 文件夹
从 http://files.grouplens.org/datasets/movielens/ 下载对应zip
如果movielens.load_pandas_df() 的参数 size 为 ‘100k’,则下载 ml-100k.zip 至刚刚创建的 downloads 文件夹中
指明参数 local_cache_path 为刚刚创建的 downloads 文件夹
基于公共库的python随机拆分器拆分数据
我们将所有数据拆分为测试集 (test set) 和训练集 (train set),且在训练过程中保留集**(1)**不可见,从而衡量算法性能。SAR基于用户喜好生成推荐结果,因此,所有测试数据集中的用户必须存在于训练数据集中。为了解决这一问题,我们可以使用函数 python_stratified_split (2) 来决定每个用户保留数据在原数据中的占比(比如,取25%作为保留集),以此确保用户同时存在于训练集与测试集中。此外,dataset.python_splitters 中还提供了更多的数据切分方式。
- 保留集**(hold-out set):也称为保留数据(hold-out data)**,这里描述的其实是一种模型评估方法——留出法(hold-out),在这里,保留集即为测试集。
- python_stratified_split 和 dataset.python_splitters 只是微软推荐系统项目中,reco_utils中实现的数据切分工具,提供了多种数据切分方式,无法通过 pip install 获取。此外,python_stratified_split 主要通过 pandas.DataFrame.sample 实现随机取样。
train, test = python_stratified_split(data, ratio=0.75, col_user='userID', col_item='itemID', seed=42)
print("""
Train:
Total Ratings: {train_total}
Unique Users: {train_users}
Unique Items: {train_items}
Test:
Total Ratings: {test_total}
Unique Users: {test_users}
Unique Items: {test_items}
""".format(
train_total=len(train),
train_users=len(train['userID'].unique()),
train_items=len(train['itemID'].unique()),
test_total=len(test),
test_users=len(test['userID'].unique()),
test_items=len(test['itemID'].unique()),
))
输出结果
Train:
Total Ratings: 74992
Unique Users: 943
Unique Items: 1649
Test:
Total Ratings: 25008
Unique Users: 943
Unique Items: 1444
我们将单节点执行SAR,并配置每个索引列在数据集中对应的列名(实际使用中,其中的 timestamp 字段可以视具体情况忽略或保留)。
更多参数详情请参阅:deep dive notebook。
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(levelname)-8s %(message)s')
model = SAR(
col_user="userID",
col_item="itemID",
col_rating="rating",
col_timestamp="timestamp",
similarity_type="jaccard",
time_decay_coefficient=30,
timedecay_formula=True
)
- 雅卡尔系数(jaccard):在上述代码中,函数 SAR 的参数 similarity_type 被设置为 jaccard,这是雅卡尔系数,可用于比较两个集合的相似性。基本方法是两集合交集大小与并集大小之比。具体参阅:百度百科 - Jaccard系数。
SAR首先计算物品-物品共现矩阵 (Co-occurence matrix),其中的元素表示两个物品在任意用户的评分记录中共同出现的记录。之后,我们就可以通过某种度量重新调整共现矩阵来计算物品相似度矩阵(在下面的代码中,使用 Jaccard系数作为相似度度量)。
我们还可以通过一个喜好矩阵来获取用户-物品关联性**(1)**。喜好程度由不同类型和发生时间的事件决定(比如用户对电影的评分或是否浏览该电影)。
我们通过喜好矩阵 A 与相似矩阵 S 相乘,可以得到一个预测分矩阵(推荐值矩阵) R。之后,我们再通过下述案例代码中的函数 recommend_k_items,可以获取每位用户的top-k个最值得推荐的结果。
从这里我们可以找到对SAR算法的完整介绍:https://github.com/microsoft/recommenders/blob/de3782cce370a446e14e6b47e87686867fb7e069/notebooks/02_model/sar_deep_dive.ipynb
- 按我理解,这个关联矩阵,即为评价矩阵,不同于数据集,关联矩阵用行-列表示用户-物品。
with Timer() as train_time:
model.fit(train)
print("Took {} seconds for training.".format(train_time.interval))
执行结果
2020-02-18 20:55:32,377 INFO Collecting user affinity matrix
2020-02-18 20:55:32,392 INFO Calculating time-decayed affinities
2020-02-18 20:55:32,463 INFO Creating index columns
2020-02-18 20:55:32,587 INFO Building user affinity sparse matrix
2020-02-18 20:55:32,595 INFO Calculating item co-occurrence
2020-02-18 20:55:32,788 INFO Calculating item similarity
2020-02-18 20:55:32,789 INFO Using jaccard based similarity
2020-02-18 20:55:32,891 INFO Done training
Took 0.5264033849998668 seconds for training.
with Timer() as test_time:
top_k = model.recommend_k_items(test, remove_seen=True)
print("Took {} seconds for prediction.".format(test_time.interval))
执行结果
2020-02-18 20:55:35,758 INFO Calculating recommendation scores
2020-02-18 20:55:35,851 INFO Removing seen items
Took 0.14565897600004973 seconds for prediction.
top_k.head()
执行结果
userID | itemID | predictio | |
---|---|---|---|
0 | 1 | 204 | 3.313306 |
1 | 1 | 89 | 3.280465 |
2 | 1 | 11 | 3.233867 |
3 | 1 | 367 | 3.192575 |
4 | 1 | 423 | 3.131517 |
我们通过模块 reco_utils.python_evaluation ,从一些常见的指标来评价SAR的性能,包括:平均精度 (MAP)、归一化贴现累积增益 (NDCG)、精度和我们用SAR计算的每个用户的top-k项的召回率 (1)。每个评分方法都需要指明数据集中具体的用户id、物品id和评分的列名。
- 需要注意,精度 (Precision) 与 准确率 (Accuracy) 是有区别的。
eval_map = map_at_k(test, top_k, col_user='userID', col_item='itemID', col_rating='rating', k=TOP_K)
eval_ndcg = ndcg_at_k(test, top_k, col_user='userID', col_item='itemID', col_rating='rating', k=TOP_K)
eval_precision = precision_at_k(test, top_k, col_user='userID', col_item='itemID', col_rating='rating', k=TOP_K)
eval_recall = recall_at_k(test, top_k, col_user='userID', col_item='itemID', col_rating='rating', k=TOP_K)
print("Model:\t",
"Top K:\t%d" % TOP_K,
"MAP:\t%f" % eval_map,
"NDCG:\t%f" % eval_ndcg,
"Precision@K:\t%f" % eval_precision,
"Recall@K:\t%f" % eval_recall, sep='\n')
执行结果
Model:
Top K: 10
MAP: 0.110591
NDCG: 0.382461
Precision@K: 0.330753
Recall@K: 0.176385
# Now let's look at the results for a specific user
user_id = 876
ground_truth = test[test['userID']==user_id].sort_values(by='rating', ascending=False)[:TOP_K]
prediction = model.recommend_k_items(pd.DataFrame(dict(userID=[user_id])), remove_seen=True)
pd.merge(ground_truth, prediction, on=['userID', 'itemID'], how='left')
执行结果
2020-02-18 20:55:40,935 INFO Calculating recommendation scores
2020-02-18 20:55:40,951 INFO Removing seen items
userId | itemId | rating | timestamp | predition | |
---|---|---|---|---|---|
0 | 876 | 523 | 5.0 | 879428378 | NaN |
1 | 876 | 529 | 4.0 | 879428451 | NaN |
2 | 876 | 174 | 4.0 | 879428378 | 0.353567 |
3 | 876 | 276 | 4.0 | 879428354 | NaN |
4 | 876 | 288 | 3.0 | 879428101 | NaN |
以上,我们看到,前面通过模型top-k推荐得到的测试集的最高评分物品被调整,而其他物品未变。离线评估算法很困难,因为此时测试集仅包含历史记录,也不能代表用户在所有物品范围内的实际偏好。对于这一问题,我们也可以通过其他算法 (1) 或使用 hyper-parameters (2) 进行优化。
- 实际应用中,多为混合集成的推荐系统,比如 UserCF + ItemCF、ItemCF + Content-based 或其他一些独特的优化方式。关于这一部分,在 Aggarwal 的《推荐系统》中有详细描述。
- hyper-parameters:是机器学习 (ML, Machine Learning) 的概念,在 ML 中,已经设置的参数称为 hyper-parameters。按照我个人理解,这里原文想说的是通过 对增量数据进行机器学习 来持续优化模型。