【翻译】微软SAR——推荐系统实践笔记

        【翻译搬砖】本文为微软推荐系统实践项目的SAR样例的翻译文档(译者: Funco),原文地址: SAR Single Node on MovieLens

        github: microsoft/recommenders 是微软开源的推荐系统最佳实践案例,提供了大量的推荐系统算法讲解以及具体案例,并封装了一些在推荐系统中通用的方法以供我们在以后的实践中借鉴。这些特点,在当前文档中都用体现,欢迎交流,共同学习。

        文中括号"()"包含一个数字,表示批注,每一段文本中的批注都会在该段文本后面补充具体内容,可能是名词解释,也可能是笔者关于不确定的翻译结果给出原文参照。此外,原文中有大量出现 Item 这个单词,我翻译为了 物品,但实际上,某些时候,翻译为 数据项 会使语句更通顺,不这么做的原因是,我希望规范名词——参照基于物品的协同过滤 (ItemCF,Item-based Collaborative Filtering)

正文

SAR Single Node on MovieLens (Python, CPU)

        SAR(Simple Algorithm for Recommendation) 一种快速、可扩展的基于用户操作记录的个性化推荐算法,快速且可扩展。它能给出易于理解且可解释的推荐结果,同时还处理了『冷启动』问题**(1)。SAR是一种近邻算法(在阿加沃尔的《推荐系统》中有提及(2)),用于为每一位用户找到最需要的物品。关于SAR的更多细节,可以参阅 deep dive notebook(3)**。

  1. It produces easily explainable and interpretable recommendations and handles “cold item” and “semi-cold user” scenarios.
  2. 阿加沃尔(Aggarwal)所著的**《推荐系统原理与实践》(《Recommender Systems: The Textbook》)是推荐系统领域被广泛推崇的书籍之一。其书关于近邻算法**,尤其是近邻协同过滤算法有大量描述。

SAR的优点

  1. 易于训练和部署且高精度
  2. 快速训练——只需要简单的计算即可获得预测矩阵
  3. 快速预测评分——只需要将相似度矩阵与评分向量作乘法运算

可以简单的理解,SAR的优点就是从各方面来讲都简单快速,但又具备相当精度

如何正确使用SAR

  1. 不关注用户特征或物品特征

    它在需要用户特征或物品特征的场景下表现较差**(1)**

  2. 占用内存高

    它需要创建多个稀疏矩阵**(2)。大多数基于矩阵分解的算法也存在该问题(3)**

  3. 它倾向于一个隐含的评分方案,但不预测评分**(4)**

  1. SAR本质是近邻协同过滤
  2. 原文说的是会创建MxM的稀疏矩阵(M是物品数量),这只是相似度矩阵,其实我们还需要用户-物品评分矩阵,经过一系列运算,还会产生作为中间产物的矩阵
  3. 矩阵分解,常见如奇异值分解(SVD)特征分解
  4. 这句话我也不晓得怎么理解,原文是:it favors an implicit rating scenario and it does not predict ratings.

0 设置与依赖

# 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__))

注意

  1. reco_utils 是原文所在项目的包,无法通过 pip install 获取。

1 数据准备

        在这里,SAR处理符合下述数据结构的数据:, , (1)

        每行记录表示一个用户关于一件物品的一次操作记录。该记录可能是电商网站中不同类型的事件,比如:点击浏览物品;添加至购物车;分享链接;诸如此类。每个事件我们可以指定不同的权值,比如:我们可以将『购买』定义为10,而『查看详情』可以定义为1 (2)

        数据集 MovieLens 是 用户对电影评分 的格式化数据,后续的示例代码中都会使用该数据集展示算法过程。

  1. SAR is intended to be used on interactions with the following schema: , ,,[], [].
  2. 这是 隐式评分 的案例,我只是想在这里强调及时收集并记录隐式评分对系统未来可能需要的分析很重要。

1.1 加载电影数据

# 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]

注意

movielensreco_utils 的一个子包,**movielens.load_pandas_df() **会在每次被调用时都下载一部分资源到临时文件夹中。建议是

  1. 在项目 recommenders 目录下创建 downloads 文件夹

  2. 从 http://files.grouplens.org/datasets/movielens/ 下载对应zip

    如果movielens.load_pandas_df() 的参数 size‘100k’,则下载 ml-100k.zip 至刚刚创建的 downloads 文件夹中

  3. 指明参数 local_cache_path 为刚刚创建的 downloads 文件夹

1.2 拆分数据

基于公共库的python随机拆分器拆分数据

我们将所有数据拆分为测试集 (test set) 和训练集 (train set),且在训练过程中保留集**(1)**不可见,从而衡量算法性能。SAR基于用户喜好生成推荐结果,因此,所有测试数据集中的用户必须存在于训练数据集中。为了解决这一问题,我们可以使用函数 python_stratified_split (2) 来决定每个用户保留数据在原数据中的占比(比如,取25%作为保留集),以此确保用户同时存在于训练集与测试集中。此外,dataset.python_splitters 中还提供了更多的数据切分方式。

  1. 保留集**(hold-out set):也称为保留数据(hold-out data)**,这里描述的其实是一种模型评估方法——留出法(hold-out),在这里,保留集即为测试集
  2. python_stratified_splitdataset.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

2 训练SAR模型

2.1 SAR算法实例与索引配置

        我们将单节点执行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
)
  1. 雅卡尔系数(jaccard):在上述代码中,函数 SAR 的参数 similarity_type 被设置为 jaccard,这是雅卡尔系数,可用于比较两个集合的相似性。基本方法是两集合交集大小与并集大小之比。具体参阅:百度百科 - Jaccard系数。

2.2 通过训练集训练SAR模型并从我们的测试集获取top-k个测试结果

        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

  1. 按我理解,这个关联矩阵,即为评价矩阵,不同于数据集,关联矩阵用行-列表示用户-物品。
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

2.3 评价SAR性能

        我们通过模块 reco_utils.python_evaluation ,从一些常见的指标来评价SAR的性能,包括:平均精度 (MAP)、归一化贴现累积增益 (NDCG)、精度和我们用SAR计算的每个用户的top-k项的召回率 (1)。每个评分方法都需要指明数据集中具体的用户id、物品id和评分的列名。

  1. 需要注意,精度 (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) 进行优化。

  1. 实际应用中,多为混合集成的推荐系统,比如 UserCF + ItemCF、ItemCF + Content-based 或其他一些独特的优化方式。关于这一部分,在 Aggarwal 的《推荐系统》中有详细描述。
  2. hyper-parameters:是机器学习 (ML, Machine Learning) 的概念,在 ML 中,已经设置的参数称为 hyper-parameters。按照我个人理解,这里原文想说的是通过 对增量数据进行机器学习 来持续优化模型。

你可能感兴趣的:(推荐系统,ElasticSearch,推荐系统,python,微软,协同过滤,机器学习)