この記事は (1人で)基礎から学ぶ推薦システム Advent Calendar 2022の12日目の記事です。
最近iALSという、暗黙的FBを使った協調フィルタリングのアルゴリズムに関する記事を見かけました。
このアルゴリズムは結構昔からあるのに、未だに高い性能を達成するので今でも使用されていたりします。 また、最近はこの手法はハイパーパラメータのチューニングによって、非常に高い性能を達成できることを示した論文なども出ています。
そんなわけで、今回はこのiALSを使ってみたいと思います。
Implicit Alternating Least Squares (iALS)
ざっくり紹介
協調フィルタリングの応用ケースとしてALSというアルゴリズムがあります。(implicitなデータを扱うことを強調してiALSとも呼ばれ、今回はiALSと呼ぶことにします)
iALS is an algorithm for learning a matrix factorization model for the purpose of top-n item recommendation from implicit feedback. [2110.14037] Revisiting the Performance of iALS on Item Recommendation Benchmarks
iALSはimplicitなFeedbackを考慮したmatrix factorizationで、非常に簡単に使用することができます。 アルゴリズムの詳細は下記の記事が非常に参考になるのでそちらをご参照ください。
iALSでは内部的に行列分解を行っています。
行列分解で学習されるパラメータはWとHになっており、iALSのLoss (下図L)を最小化するように学習することでこれらを求める。
- LS:観測されたペアSに対して、観測されたラベルとどれくらい異なるかを表す
- LI : すべてのペアに対して、予測スコアが0とどれだけ異なるかを表す
- R:L2正則化
学習時には、WとHを交互に最適化していきます。 片方を固定したままもう一方を最適化することは1つの線形回帰問題を解くことと等価であり、このトリックを使用することで非常に効率的に計算することが可能になります。
またシンプルなALSよりSGDを使用した場合のほうが性能が高くなることが知られています。
その他、詳しくはこちらの論文に記載されています。
baseline
iALSはimplicitというライブラリを使用することで非常に簡単に実装することができます。
implicitのドキュメントに記載されているサンプル実装はこんな感じです。
import implicit # initialize a model model = implicit.als.AlternatingLeastSquares(factors=64) # train the model on a sparse matrix of user/item/confidence weights model.fit(user_item_data) # recommend items for a user recommendations = model.recommend(userid, user_item_data[userid]) # find related items related = model.similar_items(itemid)
非常に簡単に使えそうに見えますね。
これを使用して今回の実装はこんな感じにしてみました。
class iALS: def __init__(self, user_df, item_df) -> None: self.user_df = user_df self.item_df = item_df self.users = self.user_df["user_id"].unique() self.movies = self.item_df["item_id"].unique() self.shape = (len(self.users), len(self.movies)) # Create indices for users and movies self.user_cat = CategoricalDtype(categories=sorted(self.users), ordered=True) self.movie_cat = CategoricalDtype(categories=sorted(self.movies), ordered=True) self.user_index = train_df["user_id"].astype(self.user_cat).cat.codes self.movie_index = train_df["item_id"].astype(self.movie_cat).cat.codes def train(self, train_df): train_df = train_df[["user_id", "item_id", "rating"]] # Conversion via COO matrix coo = sparse.coo_matrix( (train_df["rating"], (self.user_index, self.movie_index)), shape=self.shape ) self.csr = coo.tocsr() self.model = implicit.als.AlternatingLeastSquares(factors=64) # train self.model.fit(self.csr) user_index = self.user_df["user_id"].drop_duplicates().astype(self.user_cat).cat.codes ids, scores = self.model.recommend( userid=user_index, user_items=self.csr[user_index], N=len(self.movies), ) self.result = pd.DataFrame() for user_id, id, score in zip(user_index, ids, scores): user = self.user_cat.categories[user_id] id = [self.movie_cat.categories[i] for i in id] df = pd.DataFrame( {"user_id": [user] * len(id), "item_id": id, "value": score} ) self.result = pd.concat([self.result, df]) self.result = self.result.set_index(["user_id", "item_id"]) def predict(self, test_df): return [self.predict_score(user_id, item_id) for user_id, item_id in test_df.values] def predict_score(self, user_id:int, item_id:int) -> float: """ user_id, item_id毎の推論したratingを返す """ try: return self.result.loc[(user_id, item_id)]["value"] except: return np.nan
ハイパーパラメータチューニング
参考程度ですが、iALSはハイパーパラメータをうまく調整することでNN系の推薦モデルと肩を並べるほどの性能を達成できることが示唆されています。
[2110.14037] Revisiting the Performance of iALS on Item Recommendation Benchmarks
自分が読んだメモはこちら。
論文で書かれている再現実装は下記にあります。
これらを適用することで更に高い性能が得られるかもしれません。
結果
結果こんな感じになりました。
アルゴリズム | RMSE | Recall@10 | nDCG@10 |
---|---|---|---|
iALS(baseline) | 3.414 | 0.197 | 0.833 |
今回はimplicitを使っている関係でハイパラチューニングができる形式ではなかったですが、こちらの実装を元に覚えてたらハイパラチューニングした状態でやってみようと思います。
Github
参考文献
下記の文献を参考にさせていただきました。
- iALSによる行列分解の知られざる真の実力 - Visional Engineering Blog
- [2110.14037] Revisiting the Performance of iALS on Item Recommendation Benchmarks
- http://yifanhu.net/PUB/cf.pdf
- Alternating Least Square for Implicit Dataset with code | by Himanshu Kriplani | Towards Data Science
- 推薦システムにおいて線形モデルがまだまだ有用な話 | CyberAgent Developers Blog
- google-research/ials at master · google-research/google-research · GitHub
自分が論文読んだときのメモはこちら
感想
なにかのタイミングでやってみたかったのでやってみた記事でした。 今回はできなかったですが、ハイパラのチューニングをすることでさらに高い性能を達成できることもわかっているので、機会があれば試してみたいと思います。