協調フィルタリングやSVDなどの鉄板の「推薦システム(レコメンドエンジン)」であれば、PythonのSurpriseライブラリで簡単に作れます。
このライブラリは推薦アルゴリズムの学習と予測を簡単に行うことができます。
そのため、商品や映画などのアイテムをユーザーに対して推薦するシステムを構築する際に有用です。
今回は、Surprise での「推薦システム(レコメンドエンジン)」の簡単な作り方について説明します。
Contents
Surpriseライブラリー
概要
Surprise(Simple Python RecommendatIon System Engine)は、様々な種類の推薦システムアルゴリズムを実装するためのライブラリです。
Surpriseには、例えば以下のような機能があります。
- 様々な推薦アルゴリズム:Collaborative filtering(協調フィルタリング)方法、Matrix Factorization-based(行列分解ベース)のアルゴリズム、などがあります。これらのアルゴリズムは、ユーザーの過去の行動や好みを基に、ユーザーがまだ評価していないアイテムを予測します。
- クロスバリデーション:データセットを分割してモデルの汎用性を評価するためのクロスバリデーションをサポートしています。
- チューニングパラメータ:Grid Searchなどの方法を用いてハイパーパラメータを最適化するためのツールも提供しています。
要は、推薦システム(レコメンドエンジン)を作る道具が一通り揃っているということです。
インストール
以下、condaでインストールするときです。
conda install -c conda-forge scikit-surprise
以下、pipでインストールするときです。
pip install scikit-surprise
利用できる推薦アルゴリズム例
以下、Surpriseで使用できるいくつかの主要な推奨アルゴリズムです。
分類 | アルゴリズム | 概要 | メリット | デメリット |
---|---|---|---|---|
近傍法 | KNNBasic | ユーザー間またはアイテム間の類似度に基づく協調フィルタリング | シンプルで理解しやすい | 大規模データに対する計算コストが高い |
KNNWithMeans | KNNBasicにユーザーまたはアイテムの平均評価を加味 | KNNBasicに比べて予測精度が向上 | 大規模データに対する計算コストが高い | |
KNNWithZScore | KNNWithMeansにzスコアを加味 | KNNWithMeansに比べて予測精度が向上 | 大規模データに対する計算コストが高い | |
KNNBaseline | KNNにベースライン評価を加味 | KNNの中では最も予測精度が高いとされる | 大規模データに対する計算コストが高い | |
行列分解 | SVD | 特異値分解を利用した協調フィルタリング | 映画評価などによく使われ、評価値の予測精度が高い | ハイパーパラメータ調整が必要 |
SVD++ | SVDを拡張し、ユーザーの評価傾向を加味 | SVDに比べて予測精度が向上 | 計算コストが高い | |
NMF | 非負値行列分解を利用した協調フィルタリング | 特徴の抽出に利用可能 | 評価値の予測精度が低い場合がある | |
その他 | BaselineOnly | ユーザーとアイテムのバイアスのみを考慮 | 計算コストが低い | 予測精度が他のアルゴリズムに比べて低い場合がある |
NormalPredictor | ランダムな評価を予測 | 計算コストが低い | 予測精度が非常に低い | |
Co-clustering | ユーザーとアイテムを同時にクラスタリング | ユーザーとアイテムのクラスタを得られる | ハイパーパラメータ調整が必要 | |
SlopeOne | アイテム間の評価の差に基づく協調フィルタリング | シンプルで計算コストが低い | アイテムの数が増えると計算コストが増大する |
それぞれのアルゴリズムが最適な状況は異なります。以下に、いくつかの主要なアルゴリズムとそれらが最適となる状況について説明します。
- 近傍法 (k-Nearest Neighbors):評価値の分布が一様でなく、ユーザーやアイテム間に明確な関連性が存在する場合に有効です。
- SVD (Singular Value Decomposition):データセットが大きく、スパース性(欠損値が多い)が高い場合に適しています。
- NMF (Non-negative Matrix Factorization):SVDと同様。元の評価行列の全ての要素が非負(0以上)。
- Slope One:スピーディーに妥当な精度の予測を行いたい場合に適しています。
- Co-clustering:データセットが明確なクラスタ構造を持っているときに有効です。
推薦システム(レコメンドエンジン)を作る
流れ
取り急ぎ、以下の手順で作って行きます。
- 必要なモジュールの読み込み
- データセットの読み込み(今回は、MovieLens)
- データセットの分割 (学習データとテストデータ)
- 推薦アルゴリズムの設定(今回は、SVD)
- レコメンドエンジンの学習
- レコメンドエンジンのテスト
- レコメンドの実施
必要なモジュールの読み込み
先ず、必要なモジュールを読み込みます。
以下、コードです。
from surprise import SVD from surprise import Dataset from surprise import accuracy from surprise.model_selection import train_test_split
from surprise import SVD
: SVD(Singular Value Decomposition、特異値分解)という行列因子化手法を用いた推薦アルゴリズムをインポートしています。このアルゴリズムは、ユーザーとアイテムの間の相互作用(たとえば評価スコア)を予測するために使用されます。from surprise import Dataset
: Surpriseライブラリで提供されているデータセットを操作するためのクラスをインポートしています。このクラスは、データセットの読み込みやユーザー-アイテム間の相互作用の管理などを行います。from surprise import accuracy
: 推薦アルゴリズムの精度を計算するための関数(例えばRMSEやMAEなど)をインポートしています。from surprise.model_selection import train_test_split
: データセットを学習セットとテストセットに分割するための関数をインポートしています。これは、アルゴリズムを学習データで訓練した後、テストデータでそのパフォーマンスを評価するために使用されます。
データセットの読み込み
次に、MovieLens データセットをロードします。
以下、コードです。
data = Dataset.load_builtin("ml-100k")
MovieLens データセットとは、映画の評価データセットで、推薦システム(レコメンドエンジン)を構築するサンプルデータとして最も広く使用されています。
具体的には、3つの変数からなります。
- user:ユーザーの識別子
- item:アイテム(今回は、映画)の識別子
- rating:評価値(今回は、1~5までの5段階評価)
100,000件の評価(MovieLens 100K)、1,000,000件の評価(MovieLens 1M)、10,000,000件の評価(MovieLens 10M)の3つのバージョンがあります。
今回は、100,000件の評価(MovieLens 100K)です。
データセットの分割
読み込んだデータセットを、学習データとテストデータに分割します。テストセットは全体の 20% を使用します。
以下、コードです。
trainset, testset = train_test_split(data, test_size=.20)
train_test_split
関数は、指定した比率でデータを学習データとテストデータに分割します。
この場合、train_test_split(data, test_size=.20)
は、入力されたデータdata
を学習データとテストデータに分割しています。ここで、test_size=.20
はテストデータの割合を全データの20%に設定しています。その結果、全データの80%が学習データ、残りの20%がテストデータとして利用されます。
戻り値としては、分割後の学習データ(trainset
)とテストデータ(testset
)が得られます。学習データはモデルの学習に、テストデータは学習したモデルの性能評価(一般化性能のチェック)に使用されます。
推薦アルゴリズムの設定
推薦アルゴリズムのインスタンスを作ります。
以下、コードです。
algo = SVD()
SVD(Singular Value Decomposition、特異値分解)アルゴリズムをインスタンス化しています。
algo = SVD()
とすることで、algo
という変数にSVDアルゴリズムの新しいインスタンスが作られます。デフォルトのパラメータでインスタンス化されますが、必要に応じて引数を指定してパラメータを調整することも可能です。
レコメンドエンジンの学習
レコメンドエンジンを学習します。
以下、コードです。
algo.fit(trainset)
algo
(ここでは、それはSVDという推薦アルゴリズムのインスタンス)に、fit
メソッドを使用して学習データセットtrainset
を用いて学習しています。
レコメンドエンジンのテスト
レコメンドエンジンをテストします。RMSE(Root Mean Square Error)とMAE(Mean Absolute Error)を使ってモデルの精度を評価しています。
- RMSE:予測誤差(実際の評価と予測評価の差)の二乗平均の平方根を取った値
- MAE:予測誤差の絶対値の平均を取った値
以下、コードです。
predictions = algo.test(testset) accuracy.rmse(predictions) accuracy.mae(predictions)
学習済みの推薦モデル(ここではalgo
という名前のSVDアルゴリズムのインスタンス)を用いてテストデータセットtestset
に対する予測を行い、その結果を評価しています。
predictions = algo.test(testset)
: ここでtest
メソッドを呼び出すことで、テストデータセットtestset
に含まれる各ユーザーとアイテムのペアに対する評価値(レーティング)の予測を行います。この予測結果はpredictions
という変数に格納されます。accuracy.rmse(predictions)
:accuracy.rmse
はSurpriseライブラリの評価関数で、予測結果predictions
のRoot Mean Square Error (RMSE、平均二乗誤差の平方根) を計算します。RMSEはモデルの予測エラーを評価する指標で、値が小さいほど予測精度が高いとされます。accuracy.mae(predictions)
:accuracy.mae
もSurpriseライブラリの評価関数で、予測結果predictions
のMean Absolute Error (MAE、平均絶対誤差) を計算します。MAEもモデルの予測エラーを評価する指標で、値が小さいほど予測精度が高いとされます。
以下、実行結果です。
RMSE: 0.9525 MAE: 0.7525
5段階評価で、予測と実測の乖離が1未満ということが分かります。悪くはありません。
レコメンドの実施
全ユーザに対しレコメンドを実施します。
先ず、レコメンド構築用のデータセットと、レコメンド用のデータセットを作ります。
以下、コードです。
trainset = data.build_full_trainset() testset = trainset.build_anti_testset()
data.build_full_trainset()
: このメソッドは全てのデータからレコメンド構築用のデータセットを構築します。ユーザーとアイテムの評価データ全体を含みます。これを使用することで、全てのデータを使ってモデルを学習できます。trainset.build_anti_testset()
: このメソッドはユーザーとアイテムの評価がまだされていない組み合わせからなるデータセットを構築します。つまり、ユーザーがまだ評価していないアイテムに対する予測を作成するためのデータセットです。評価値が不明なアイテムに対して、レコメンドエンジンがどのような評価を予測するために使用されます。
これらの2つのメソッドを組み合わせることで、全てのデータを用いてレコメンドエンジンを学習し(data.build_full_trainset()
)、そのレコメンドエンジンを使用してまだ評価されていないアイテムに対する評価を予測する(trainset.build_anti_testset()
)ことができます。この結果を基にユーザーに対する新たなレコメンデーションを生成することが可能になります。
レコメンドエンジンを学習します。
以下、コードです。
algo.fit(trainset)
では、各ユーザーに対して映画のレコメンドを行います。
各ユーザの評価を予測します。
以下、コードです。
predictions = algo.test(testset)
上位nまで抜き出し、取り扱いやすいように辞書形式で格納します。
以下、コードです。
from collections import defaultdict def get_top_n(predictions, n=10): top_n = defaultdict(list) for uid, iid, true_r, est, _ in predictions: top_n[uid].append((iid, est)) 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 top_n = get_top_n(predictions, n=10)
各ユーザーに対する上位n個のアイテムを予測評価値の高い順に抽出し、ユーザーごとにリストとしてtop_n
に格納しました。
このtop_n
は、ユーザーIDをキーとし、その値がユーザーにレコメンドされるアイテムのIDと予測評価のリストである辞書です。
これで完成です。
ただ、辞書ではなくデータフレームに変換したいな、と思う方もいるかと思います。
以下で、辞書からデータフレームに変換します。
import pandas as pd def get_top_n_df(top_n): top_n_df = [] for uid, user_ratings in top_n.items(): for iid, est in user_ratings: top_n_df.append([uid, iid, est]) return pd.DataFrame(top_n_df, columns=['User', 'Item', 'Estimate']) top_n_df = get_top_n_df(top_n) print(top_n_df) #確認
以下、実行結果です。
User Item Estimate 0 99 318 5.000000 1 99 302 4.821863 2 99 114 4.705067 3 99 357 4.657774 4 99 603 4.639556 ... ... ... ... 9425 1 528 4.619250 9426 1 285 4.608349 9427 1 589 4.568911 9428 1 513 4.553895 9429 1 508 4.520675 [9430 rows x 3 columns]
ちなみに、特定のユーザとアイテム(映画)を指定し、評価の予測するときには、以下のようにします。
uid = str(597) # ユーザ iid = str(1152) # アイテム(映画) pred = algo.predict(uid, iid, verbose=True)
以下、実行結果です。
user: 597 item: 1152 r_ui = None est = 3.57 {'was_impossible': False}
user: 597
: ユーザーIDが597item: 1152
: アイテムIDが1152r_ui = None
: 実際の評価値(ユーザーがアイテムに付けた評価)です。引数で設定していないのでNone
est = 3.57
: 推定値(予測された評価値)。つまり、アルゴリズムはユーザー597がアイテム1152に対して3.57の評価をすると予測{'was_impossible': False}
: 予測が不可能だったかどうかを示すフラグ。ここではFalse
なので、予測が可能だった
まとめ
今回は、Surprise での「推薦システム(レコメンドエンジン)」の簡単な作り方について説明しました。
他にも色々なアルゴリズムがあります。興味のある方は、試してみてください。
アルゴリズムの中にはハイパーパラメータのあるものもあります。
今回はデフォルトのまま構築しましたが、SVDなどのハイパーパラメータのある推薦アルゴリズムは、適切なチューニングをすることで、レコメンド精度が向上します。
次回は、ハイパーパラメータチューニングを簡単に実施する方法(グリッドサーチ×CV)について説明します。
Python surprise で作る らくらく「レコメンドエンジン」(その2)– ハイパーパラメータ調整しレコメンドエンジンを構築 –