Re:ゼロから始めるML生活

どちらかといえばエミリア派です

iALSを使ってみる

この記事は (1人で)基礎から学ぶ推薦システム Advent Calendar 2022の12日目の記事です。

最近iALSという、暗黙的FBを使った協調フィルタリングのアルゴリズムに関する記事を見かけました。

engineering.visional.inc

このアルゴリズムは結構昔からあるのに、未だに高い性能を達成するので今でも使用されていたりします。 また、最近はこの手法はハイパーパラメータのチューニングによって、非常に高い性能を達成できることを示した論文なども出ています。

arxiv.org

そんなわけで、今回はこの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で、非常に簡単に使用することができます。 アルゴリズムの詳細は下記の記事が非常に参考になるのでそちらをご参照ください。

engineering.visional.inc

iALSでは内部的に行列分解を行っています。

image

行列分解で学習されるパラメータはWとHになっており、iALSのLoss (下図L)を最小化するように学習することでこれらを求める。

image

  • LS:観測されたペアSに対して、観測されたラベルとどれくらい異なるかを表す
  • LI : すべてのペアに対して、予測スコアが0とどれだけ異なるかを表す
  • R:L2正則化

学習時には、WとHを交互に最適化していきます。 片方を固定したままもう一方を最適化することは1つの線形回帰問題を解くことと等価であり、このトリックを使用することで非常に効率的に計算することが可能になります。

またシンプルなALSよりSGDを使用した場合のほうが性能が高くなることが知られています。

その他、詳しくはこちらの論文に記載されています。

arxiv.org

baseline

iALSはimplicitというライブラリを使用することで非常に簡単に実装することができます。

github.com

benfred.github.io

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

自分が読んだメモはこちら。

github.com

論文で書かれている再現実装は下記にあります。

github.com

これらを適用することで更に高い性能が得られるかもしれません。

結果

結果こんな感じになりました。

アルゴリズム RMSE Recall@10 nDCG@10
iALS(baseline) 3.414 0.197 0.833

今回はimplicitを使っている関係でハイパラチューニングができる形式ではなかったですが、こちらの実装を元に覚えてたらハイパラチューニングした状態でやってみようと思います。

Github

github.com

参考文献

下記の文献を参考にさせていただきました。

自分が論文読んだときのメモはこちら

github.com

感想

なにかのタイミングでやってみたかったのでやってみた記事でした。 今回はできなかったですが、ハイパラのチューニングをすることでさらに高い性能を達成できることもわかっているので、機会があれば試してみたいと思います。