RecBole を用いてクックパッドマートのデータに対する50以上のレコメンドモデルの実験をしてみた

こんにちは。研究開発部の深澤(@fufufukakaka)です。

本記事では最近面白いなと思って watch しているレコメンド系のプロジェクト RecBole を紹介いたします。また、クックパッドが展開している事業の一つであるクックパッドマートのデータを使って数多くのレコメンドモデルを試す実験も行いました。その結果も合わせて紹介します。

TL;DR:

  • レコメンドモデルは作者実装に安定性がなく、またモデルをどのように評価したかも基準がバラバラで、再現性が難しいとされている(from RecSys 2019 Best Paper)
  • 再現性に取り組むプロジェクトとして 2020年12月に始まった RecBole がある。 RecBole を利用することでなんと 50個以上のレコメンドモデルを大体1コマンドで試せる
  • クックパッドマートでユーザに対してアイテムをレコメンドするシチュエーションを想定し実験を行った。その結果、テストデータの6000ユーザに対して2000ユーザ(三分の一)に正しい推薦を行うことができるモデルを発見できた

正しく強いレコメンドモデルを探すのは難しい

サービスの中で機械学習といえばレコメンド、といわれる機会は非常に多いかと思います。が、レコメンドは機械学習の中ではかなり特殊な問題設定です。クラス分類したり回帰したり、と様々な解き方をすることができるタスクなのがその要因です。「ユーザがこのアイテムを買ってくれる確率」を推定しても良いし「ユーザが好きなアイテムのランキング」を予測しても良い。そもそもアイテムの数が数万種類くらいある中で「このユーザはこれを買ってくれそう」を予測するのは非常に難しいです。つまり、レコメンド系は実社会サービスでの需要が高く非常に難しい問題、と言えます。

おかげでたくさんの研究が日々発表されています。それ自体は素晴らしいことです。RecSys というレコメンドのみを取り扱う国際会議も存在しています。最近ではDeep Learning を活用した研究が殆どを占めています。

ですが、この日々公開されている研究には再現性がないことが指摘されています。2019の RecSys ベストペーパーは「Are We Really Making Much Progress? A Worrying Analysis of Recent Neural Recommendation Approaches」でした。「本当にニューラルネットワーク系の手法で精度は上がっているのか?」というこの論文では衝撃の事実が明かされており

  • トップ会議(KDD, SIGIR, WWW, RecSys)のDNN関連研究18本を追試した
    • 18本のうち、現実的な努力を行った上で再現できたのが7本(半分以下!)
      • (RecSysでの発表によると、)実装が再現できない場合は、実装を原著者らに問い合わせて1ヶ月待った
    • 再現できたとしても 6/7がkNNベース(シンプルなモデル)+ハイパーパラメータ最適化に負けてしまった
    • 残りの1つもDNNではない線形の手法を調整したものに負ける場合もあった

(refs. https://qiita.com/smochi/items/98dbd9429c15898c5dc7 )

国際会議で「state-of-the-artだ」と主張している論文の殆どが、実際には10年以上前から存在しているシンプルな手法に負けてしまう、というサーベイ結果が出ており、非常に面白い論文でした。

この論文が示したように、レコメンドの研究は数多く発表されているものの殆どの実装に再現性がなく、また正しい比較ができていない、というのが現状です。当然ですが、論文内で提示されている GitHub リポジトリの実装は人によってまちまちです。再現実装は再利用が可能なものから環境構築自体が困難なものまで色々あります。前提としてコードを上げてくれることは非常にありがたいのですが、それぞれの手法で土台を揃えた実験を行うのはそもそもが難しい状況です。

RecBoleについて

RecBole は中国人民大学・北京大学の研究室が共同で始めたプロジェクトのようで、去年の11月に arxiv に登場しました。今年の8月に提供しているモジュールがv1を迎えて、本格的に色々な人が利用するようになったようです。

RecBole 最大の魅力は、上述してきた再現性の難しいレコメンドモデルを統一したインタフェースで実装し、比較を容易にしているところにあります。そして実装されているモデル、適用できるデータセットの数が凄まじいです。モデルは現時点で70以上(モデルリストがすごい )、データセットは20以上のものについて即座に試せます。どれくらい即座に試せるかと言うと

pip install recbole
python run_recbole.py --model=<your favorite model> --dataset_name ml-100k

これだけで、レコメンド界隈の中で最も有名なベンチマークである MovieLens-100k データセットに対して70以上のモデルを即座に(追加の設定が必要なやつもありますが)試せます。これだけのモデル・データを試すことができる環境はそうないと思われます。また70以上の収録されているモデルたちは全て PyTorch ベースで丁寧に再実装が行われており信頼性は非常に高いです。predict関数などの基本的なインタフェースは統一されており、実験のし易い環境が整えられています。

RecBole を自分たちのデータで使えるようにする

実際に RecBole を使えるようにするためにはどうするばよいのか、について簡単にまとめてみました。

  1. ユーザとアイテムのアクション履歴をまとめたデータを用意する
  2. データをコントロールするクラスを用意する
  3. 配布されているスクリプトを使ってデータを RecBole が読める形式に変換する
  4. 学習に必要な設定ファイルを用意する
  5. 学習スクリプトを走らせる

1. ユーザとアイテムのアクション履歴をまとめたデータを用意する

自分の使いたいデータを持ってきて、以下のようなファイルで保存しておきます。

今回はクックパッドマートを対象としてデータを作りました。interact.csv ではあるユーザがあるアイテムを購入したログが表現されています。MovieLens のような Rating (Explicit Feedback)のついていない Implicit Feedback なデータセットです。

なお、ここで紹介している user_id, item_id などはいずれもダミーとなっています。

interact.csv

user_id,item_id,timestamp(Unix timestamp)
1,1,1630461974
2,2,1630462246
3,2,1630462432

items.csv

item_id,item_name,item_category_id
1,豚バラ,9
2,にんじん,7

users.csv

user_id,feature1,feature2
1,286,130
2,491,3
3,342,32

2. データをコントロールするクラスを用意する

続いて、RecBole 内でこれらのデータを扱うためのクラスを用意します。基本的には BaseDataset と同じインタフェースを用意して、その内部をデータに合わせて調整するような作業になります。

import os

import pandas as pd
from src.dataset.base_dataset import BaseDataset  # https://github.com/RUCAIBox/RecSysDatasets/blob/master/conversion_tools/src/base_dataset.py をコピーして所定の場所に配置しておく

class CookpadMartDataset(BaseDataset):
    def __init__(self, input_path, output_path):
        super(CookpadMartDataset, self).__init__(input_path, output_path)
        self.dataset_name = "ckpd_mart"

        # input_path
        self.interact_file = os.path.join(self.input_path, "interact.csv")
        self.item_file = os.path.join(self.input_path, "items.csv")
        self.user_file = os.path.join(self.input_path, "users.csv")

        self.sep = ","

        # output_path
        output_files = self.get_output_files()
        self.output_interact_file = output_files[0]
        self.output_item_file = output_files[1]
        self.output_user_file = output_files[2]

        # selected feature fields
        # 型について -> https://recbole.io/docs/user_guide/data/atomic_files.html#format
        self.interact_fields = {
            0: "user_id:token",
            1: "item_id:token",
            2: "timestamp:float",
        }

        self.item_fields = {
            0: "item_id:token",
            1: "item_name:token",
            2: "item_category_id:token"
        }

        self.user_fields = {
            0: "user_id:token",
            1: "feature1:token",
            2: "feature2:token",
        }

    def load_inter_data(self):
        return pd.read_csv(self.interact_file, delimiter=self.sep, engine="python")

    def load_item_data(self):
        return pd.read_csv(self.item_file, delimiter=self.sep, engine="python")

    def load_user_data(self):
        return pd.read_csv(self.user_file, delimiter=self.sep, engine="python")

3. 配布されているスクリプトを使ってデータを RecBole が読める形式に変換する

https://github.com/RUCAIBox/RecSysDatasets/blob/master/conversion_tools/run.py

ここで公開されているスクリプトを使って RecBole 内で利用できる形式の Atomic Files に変換します。 refs

python src/dataset/convert.py --dataset ckpd_mart \
--input_path data/mart_data --output_path dataset/ckpd_mart \
--convert_inter --convert_item --convert_user

すると ckpd_mart.inter ckpd_mart.item ckpd_mart.user というファイルが所定の場所に配備されます。これでデータの準備は完了です。

4.学習に必要な設定ファイルを用意する

https://recbole.io/docs/user_guide/config_settings.html

RecBole が用意してくれている config 設定を読みながら自分のデータに合わせた設定ファイルを書いていきます。

# general
gpu_id: 0
use_gpu: False  # GPUを使う時はTRUEにする
seed: 2020
state: INFO
reproducibility: True
data_path: 'dataset/'  # 使うデータが格納されている場所
checkpoint_dir: 'saved/'  # モデル保存先
show_progress: True
save_dataset: False  # True にすればtrain, valid, test で使ったデータを保存してくれる
save_dataloaders: False

# Atomic File Format
field_separator: "\t"
seq_separator: "@" # 文字列があった場合この文字で区切られる。特徴量読み込み時にバグってしまう可能性があるため、できるだけデータを事前に処理しておき絶対に出現しない保障が取れている記号を書くべき(日本語の場合)

# Common Features
USER_ID_FIELD: user_id
ITEM_ID_FIELD: item_id
RATING_FIELD: ~  # implicit feedback の場合
TIME_FIELD: timestamp

# Selectively Loading
# 使うデータだけを選んで loadします
load_col:
    inter: [user_id, item_id, timestamp]
    user: [user_id, feature1, feature2]
    item: [item_id, item_name, item_category_id]
unused_col:  # データとしては読み込むけど学習には使いたくないカラムはここで指定する
    inter: [timestamp]

# Training and evaluation config
epochs: 50
stopping_step: 10  # 10 step valid_metric が改善しない場合は止める
train_batch_size: 4096
eval_batch_size: 4096
neg_sampling:  # implicit feedbackなデータを扱っていて positive,negative両方のラベルが必要な手法を試す際に、negative samplingすることでデータを用意できる
    uniform: 1
eval_args:
    group_by: user  # user 単位でアイテムを集約して評価に使う。基本的にこれ以外使うことはない
    order: TO  # Temporal Order。時系列順で train, valid, test を分けてくれる
    split: {'RS': [0.8,0.1,0.1]}  # 80%, 10%, 10% で分けてくれる
    mode: full
metrics: ['Recall', 'MRR', 'NDCG', 'Hit', 'Precision']
topk: 10
valid_metric: MRR@10  # この指標をtrackする
metric_decimal_place: 4

5. 学習スクリプトを実行する

おまたせしました。あとは実験をするだけです。

モデルによって与えるパラメータが微妙に違ったりするので、そこを吸収する以下のようなスクリプト(run_experiment.py)を用意して

import click
from recbole.quick_start import run_recbole

@click.command()
@click.option(
    "-m",
    "--model_name",
    required=True,
    type=str,
    help="Model Name(see recbole's model list)",
)
@click.option(
    "-d",
    "--dataset_name",
    required=True,
    type=str,
    help="Dataset Name(your custom dataset name or recbole's dataset name)",
)
@click.option(
    "-c",
    "--config_file_list",
    required=True,
    nargs=-1,
    help="config file path",
)
def main(model_name, dataset_name, config_file_list):
    if model_name in [
        "MultiVAE",
        "MultiDAE",
        "MacridVAE",
        "RecVAE",
        "GRU4Rec",
        "NARM",
        "STAMP",
        "NextItNet",
        "TransRec",
        "SASRec",
        "BERT4Rec",
        "SRGNN",
        "GCSAN",
        "GRU4RecF",
        "FOSSIL",
        "SHAN",
        "RepeatNet",
        "HRM",
        "NPE",
    ]:
        # これらは non-sampling method
        # https://recbole.io/docs/user_guide/model/general/macridvae.html などを参照
        parameter_dict = {
            "neg_sampling": None,
        }
        run_recbole(
            model=model_name,
            dataset=dataset_name,
            config_file_list=config_file_list,
            config_dict=parameter_dict,
        )
    else:
        run_recbole(
            model=model_name, dataset=dataset_name, config_file_list=config_file_list
        )

if __name__ == "__main__":
    main()

あとは python run_experiment.py --dataset_name ckpd_mart --model_name <your favorite model> --config_files config/ckpd_mart.yml するだけです。お疲れさまでした。

RecBole を試してみた結果

ここまで頑張って用意した土台を使って、早速 RecBole に収録されているモデルをクックパッドマートの購入履歴データ(2021年9月~10月)で試してみました。先程のスクリプトの引数を変えるだけで次々と実験を行うことができます。追加の設定ファイルが必要なものを除いて、50前後のレコメンドモデルを実験することができました。

それでは以下に結果の表を示します。モデル名と各指標、タイプ(行動データしか使わないgeneral・別の情報を使うcontext-aware、時間情報を用いるsequential)、論文名が一覧になっています。

モデル名 recall@10 mrr@10 ndcg@10 hit@10 precision@10 タイプ 論文名
RecVAE 0.2754 0.2626 0.2474 0.314 0.0367 general RecVAE: A New Variational Autoencoder for Top-N Recommendations with Implicit Feedback
MacridVAE 0.2651 0.2488 0.2364 0.303 0.0347 general MACRo-mIcro Disentangled Variational Auto-Encoder
NAIS 0.2324 0.2452 0.2244 0.2698 0.0325 general Neural Attentive Item Similarity Model for Recommendation
NNCF 0.2248 0.1755 0.1767 0.2567 0.0282 general A Neural Collaborative Filtering Model with Interaction-based Neighborhood
RepeatNet 0.2725 0.1468 0.1766 0.2725 0.0272 sequential RepeatNet: A Repeat Aware Neural Recommendation Machine for Session-based Recommendation.
NeuMF 0.2344 0.1638 0.1699 0.268 0.0304 general Neural Collaborative Filtering
LINE 0.1859 0.1556 0.1529 0.2156 0.0237 general LINE: Large-scale Information Network Embedding
BPR 0.1789 0.1455 0.1442 0.2088 0.0223 general BPR Bayesian Personalized Ranking from Implicit Feedback
SHAN 0.1738 0.1189 0.132 0.1738 0.0174 sequential SHAN: Sequential Recommender System based on Hierarchical Attention Network.
Item2vec 0.121 0.1183 0.112 0.1372 0.0148 general Item 2 Vec-based Approach to a Recommender System
DGCF 0.1703 0.0965 0.1099 0.1931 0.0201 general Disentangled Graph Collaborative Filtering
FFM 0.187 0.0922 0.1096 0.2099 0.0225 context-aware Field-aware Factorization Machines for CTR Prediction
FPMC 0.151 0.0935 0.107 0.151 0.0151 sequential Factorizing personalized Markov chains for next-basket recommendation
NARM 0.1664 0.0847 0.1039 0.1664 0.0166 sequential Neural Attentive Session-based Recommendation
LightGCN 0.1549 0.0794 0.0952 0.1715 0.0174 general LightGCN: Simplifying and Powering Graph Convolution Network for Recommendation
NGCF 0.126 0.0823 0.089 0.1416 0.0148 general Neural Graph Collaborative Filtering
SASRec 0.1142 0.0657 0.0771 0.1142 0.0114 sequential Self-Attentive Sequential Recommendation
HRM 0.0992 0.0684 0.0756 0.0992 0.0099 sequential HRM: Learning Hierarchical Representation Model for Next Basket Recommendation.
EASE 0.1205 0.0752 0.0751 0.1559 0.0204 general Embarrassingly Shallow Autoencoders for Sparse Data
MultiVAE 0.1113 0.0681 0.0751 0.1245 0.0126 general Variational Autoencoders for Collaborative Filtering
NPE 0.123 0.0597 0.0744 0.123 0.0123 sequential NPE: Neural Personalized Embedding for Collaborative Filtering
MultiDAE 0.1011 0.0596 0.0671 0.1127 0.0114 general Variational Autoencoders for Collaborative Filtering
SRGNN 0.1115 0.0515 0.0654 0.1115 0.0112 sequential Session-based Recommendation with Graph Neural Networks
ENMF 0.1075 0.0545 0.0629 0.1261 0.0131 general Efficient Neural Matrix Factorization without Sampling for Recommendation
DCN 0.1085 0.0508 0.06 0.1255 0.013 general Deep & Cross Network for Ad Click Predictions
FOSSIL 0.087 0.0481 0.0572 0.087 0.0087 sequential FOSSIL: Fusing Similarity Models with Markov Chains for Sparse Sequential Recommendation.
ItemKNN 0.0649 0.0666 0.0534 0.094 0.0126 general Item-based top-N recommendation algorithms
DeepFM 0.0873 0.0347 0.0442 0.1029 0.0106 context-aware DeepFM: A Factorization-Machine based Neural Network for CTR Prediction
PNN 0.0851 0.0353 0.0441 0.0994 0.0102 context-aware Product-based neural networks for user response prediction
FM 0.0817 0.0325 0.0412 0.0961 0.0098 context-aware Factorization Machines
BERT4Rec 0.0685 0.0303 0.0391 0.0685 0.0069 sequential BERT4Rec: Sequential Recommendation with Bidirectional Encoder Representations from Transformer
xDeepFM 0.0743 0.0281 0.0371 0.0858 0.0089 context-aware xDeepFM: Combining Explicit and Implicit Feature Interactions for Recommender Systems
NFM 0.0736 0.0288 0.0369 0.0867 0.0088 context-aware Neural Factorization Machines for Sparse Predictive Analytics
AutoInt 0.0741 0.0275 0.0362 0.0872 0.0089 context-aware AutoInt: Automatic Feature Interaction Learning via Self-Attentive Neural Networks
AFM 0.0718 0.0284 0.0361 0.0855 0.0086 context-aware Attentional Factorization Machines: Learning the Weight of Feature Interactions via Attention Networks
FNN 0.0703 0.0274 0.0349 0.0823 0.0083 context-aware Deep Learning over Multi-field Categorical Data
GRU4Rec 0.0682 0.0247 0.0348 0.0682 0.0068 sequential Improved Recurrent Neural Networks for Session-based Recommendations
SpectralCF 0.0745 0.0238 0.0343 0.0876 0.0089 general Spectral collaborative filtering
WideDeep 0.0704 0.0261 0.0342 0.0837 0.0085 context-aware Wide & Deep Learning for Recommender Systems
GCMC 0.0765 0.0229 0.0341 0.0891 0.009 general Graph Convolutional Matrix Completion
DMF 0.0633 0.0276 0.034 0.0767 0.0078 general Deep Matrix Factorization Models for Recommender Systems
FwFM 0.0703 0.0217 0.0315 0.0823 0.0084 context-aware Field-weighted Factorization Machines for Click-Through Rate Prediction in Display Advertising
STAMP 0.0607 0.0208 0.03 0.0607 0.0061 sequential STAMP: Short-Term Attention/Memory Priority Model for Session-based Recommendation
DSSM 0.0582 0.0217 0.0287 0.0693 0.007 context-aware Learning deep structured semantic models for web search using clickthrough data
SLIMElastic 0.0495 0.0226 0.0263 0.0646 0.007 general SLIM: Sparse Linear Methods for Top-N Recommender Systems
LR 0.0528 0.0167 0.0231 0.064 0.0065 context-aware Predicting Clicks Estimating the Click-Through Rate for New Ads
Pop 0.0474 0.0136 0.0201 0.0564 0.0057 general なし
CDAE 0.0026 0.0007 0.001 0.0033 0.0003 general Collaborative Denoising Auto-Encoders for Top-N Recommender Systems

各モデルについて、テストデータに対する以下の指標を掲載しました。 @10 は 10個レコメンドを表出した、という意味です。

  • recall ... ユーザが実際に嗜好したアイテムのうち、レコメンドリストでどれくらいカバーできたかの割合
  • precision ... レコメンドリストにあるアイテムのうち、ユーザが嗜好したアイテム(適合アイテム)の割合
  • hits ... 正解のアイテムを一つ以上含むレコメンドリストを作成できた割合
  • mrr ... mean reciprocal rank。レコメンドリストを上位から見て、最初にヒットしたアイテムの順位を逆数にしたものをスコアとする。それを平均したもの。
  • ndcg ... DCG: アイテムをおすすめ順に並べた際の実際のスコアの合計値 を正規化(normalize)したもの

また、いくつかの古典的なモデルを太文字にしています。

  • Pop ... popularity。人気のアイテムを表出する
  • ItemKNN ... アイテム間の類似度を行動履歴から簡単な計算で定義して(コサイン類似度)、それを使って「あるユーザが過去見ていたアイテムに近いアイテムを出す」というもの。2000年代くらいから。
  • BPR ... Bayesian Personalized Ranking 2009年の手法。行列分解をベイズ的なアプローチで解いてランキングを導出する。

今回試したモデルの全てがこれら3つの手法よりも後に発表され、Deep Learningを使い倒すためにGPUを何枚も用意して実績を積んでいます。当然全てのモデルが上回ってほしいところなのですが... 2019 RecSys ベストペーパーで報告された内容とほぼ同じく、古典的な手法(ItemKNNとBPR)は相当強かったです。

さて、他にもこの表からわかることがいくつかあるのでまとめてみました。

  • general(ユーザとアイテムのアクション履歴のみ使う)なモデルに対して、context-aware(ユーザとアイテムのside infomationも使う)・sequential(どの順番で購入したかの順序情報を使う)モデルは総じて低い結果となりました(付加情報を駆使しているのに...)。
  • RecVAEが圧倒的に強かった。これはユーザとアイテムのヒストリーを行列にした上で、 Variational Auto-Encoder というニューラルネットワークで圧縮・復元の学習を行い、ユーザとアイテムのヒストリー行列を正確に復元できるように学習したモデル(+いくつか工夫あり)です。
    • わかりやすい指標である hits@10 を題材にすると、一番良かった RecVAE が 0.3(30%は正解を含んだレコメンドリストを表出できる)だったのに対して、一番下の CDAE は 0.003(0.3%しか正解を含んだレコメンドができない)というのはかなり差が大きいと感じました
    • なおこの RecVAE の数字は、非常に優秀な数字です
  • タスクやデータの難易度に依存するものの、機械学習に取り組み上でモデル変更のみで20ポイント以上指標に差が開くことをみることはあまり多くはない
  • 推薦において、ユーザとアイテムのアクション履歴から情報を引き出すというタスクが、モデルによって得意不得意がはっきり分かれているのだと思う
  • 古典手法 BPR より良かったモデルはわずか 7モデル (50弱のモデルを実験して)

さて、50弱のモデルを実験するのにかかった時間は1日でした。本来であれば作者の参照実装を見に行って、その使い方を学んで、自分の適用したいデータセットをそれに合わせた方式に前処理して、動かそうとしてみてバグにあたって... 一つのモデルを動かすのに1日かかることのほうが多いです(むしろ1日で終わらない)。

それを非常に短い時間で網羅的に実験を行うことができる環境を得られるのは非常に良いことではないでしょうか。

各レコメンドモデルの挙動の違いについて

ではこれらの結果についてもう少し踏み込んでみましょう。以下のモデルについて様々な指標を見てみます。

  • BPR ... 古典的だが優秀な手法
  • ItemKNN ... 古典的だが優秀な手法2
  • Popularity ... 古典手法
  • Item2Vec ... 商品IDを単語、同じセッションで同時に購入された商品群をcontextとみなしてword2vecを学習するモデル → 実装
  • FFM ... Field-aware Factorization Machines。 context-aware モデル
  • RecVAE ... 今回のチャンピオンモデル

推薦リストに一つでも正解が含まれていたユーザ数

hitsを見れば大体わかりますが、グラフにしてみました。

f:id:fufufukakaka:20211102110803p:plain
(テストデータ)推薦リストに一つでも正解が含まれていたユーザ数

圧倒的に RecVAE でした。ちなみに今回のテストデータは 6000ユーザくらい。2位がBPRで古典手法でした。

過去出現したアイテムを推薦して正解している割合

レコメンドにおいて、そのユーザが過去アクションしたことがあるアイテムをどう出すか、はかなり重要です。RepeatNet というリピートに着目したモデルもあるくらい。EC系のサイトでよくあることなのですが、周期的に同じものを買っている、というのがドメインにもよりますが散見されます。マートはその例にもれず、「またあれ買って食べたい」がよく起きるサービスです。ということで、これをできるだけ取りこぼさずに推薦できると非常に良いだろうと推察できます。

ここでは割合を表示します。(過去出現したアイテムを推薦して正解している数)/(推薦が成功した数)

f:id:fufufukakaka:20211102110951p:plain
過去出現したアイテムを推薦して正解している割合

ここで面白いのは、RecVAE・BPRなど上位モデルの値がほとんど同じで90%以上であることです。RecVAEはたくさん推薦を成功させていますが、過去出現したことのあるアイテムを着実に当てて正解数を伸ばしていたということですね。成績の良かったモデルは取りこぼしが少なかった、と言えるかもしれません。

過去出現していないアイテムを表出して正解しているユーザ数

今度は反対に、そのユーザが一度もアクションしたことがないアイテムを表出して、しかもそれが正解だった、という数を見てみます。レコメンドに求められている新規機会創出という役割をまさに表している性能値だとも言えます。

割合にするとさっきと逆のグラフになるので、ここでは絶対数を見てみます。

f:id:fufufukakaka:20211102111012p:plain
過去出現していないアイテムを表出して正解しているユーザ数

200程度、と大分規模は小さくなりましたが相変わらず RecVAE は上位にいます。リピートも見逃さないし、いきなり今まで買ったことがないアイテムを買った、という人に対しても他のモデルよりは良い精度を出せています。

対してBPRはRecVAEの半分程度となっており、ここで差が開いたように思えます。

また、Item2vec は先程のリピートアイテムで推薦を成功した割合を見ると90%以上となっていました。ここでのグラフの数値を見る限り、ほとんどがリピートアイテムを当てることに特化していたようです。

レコメンドのバリエーション

次に、各レコメンドのバリエーション(coverage)を見てみます。バリエーションというのは、全アイテムを分母として、そのモデルが推薦したアイテムのユニーク数を分子とした時の値を指しています。要するに、同じ人気のアイテムばかり推薦していたら低くなります。

f:id:fufufukakaka:20211102111045p:plain
レコメンドモデルが表出するアイテムのバリエーション

  • popularityが一番低いのは、毎回同じアイテムしか出さないため

  • FFM(context-aware)が低い。point-wiseな推定をするモデルであるためだと思われる

    • point-wise ... Factorization Machine系は「こういう特徴を持っているユーザはこういう特徴を持っているアイテムを買うかどうか」という0,1の学習を行い、ユーザごとのアイテム購入確率を出します。その確率をソートしてレコメンドリストを生成するのですが、確率を点推定しているだけなので、順序関係などは全く気にしません。その結果、人気のアイテムの購入確率が高まりそればっかり出てくる、ということがよくあります。
  • 一番カバレッジが高いのは ItemKNN、ついで Item2Vec・RecVAE と続きます
    • ItemKNN ・Item2vec などアイテムの類似度を利用するモデルがいずれもバリエーション豊かな推薦を行う傾向にありました
    • Deep Learning を利用するモデルは学習設定を正しくしないと over fit により出力が偏ってしまうイメージがあったのですが、RecVAE が予想に反しており驚きました

まとめ

以上、RecBole を使ってクックパッドマートでのユーザに対するアイテムレコメンドを行う設定で、内部実験を行った結果をご紹介いたしました。多種多様なレコメンドモデルを比較検討する上で非常に良い選択肢ではないかと思います。開発したレコメンドモデルに対する有用なベンチマークとなるのではないでしょうか。

今後レコメンドが必要になった際にどんなモデルを実装すればよいのかについて、今回の結果を参考にしていきたいと思います。

最後に、クックパッドでは、サービス開発や基盤開発にチャレンジする就業型インターン・そして新卒採用・中途採用を通年で受付けております。気になった方は是非ウェブサイトよりご応募ください。

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/