こんにちは、エンジニアの中村(@tn1031)です。弊社のプロダクト「iQON」には「for You」というレコメンド機能が実装され、個々のユーザに毎日おすすめのファッションアイテムを届けています。
今回はこの「for You」に関連して、レコメンドを実現するアルゴリズムのひとつであるBayesian Personalized Ranking (BPR)を紹介したいと思います。
本記事ではひとつの手法に話題を絞りますが、一般的な協調フィルタリングやレコメンド自体について詳しく知りたい方は、こちらのNetflix Prizeで使われた手法のまとめがとても参考になります。
協調フィルタリングとBPR
行動ベースの協調フィルタリングではユーザ x アイテムの行列の行列分解(Matrix Factorization)を考えます。 評価の行列をとします。行のインデックスと列のインデックスがそれぞれ1人のユーザ、1個のアイテムと対応しており、行列の成分の値はユーザがアイテム に対して下した評価です。ファクターの個数を与えた時、評価の行列をユーザ x ファクター行列とアイテム x ファクター行列の積に分解します。
これを解く為の手法は様々ありますが、有名なものといえばspark.mllibにも実装されているAlternating Least Squares (ALS)やトピックモデルが一番最初に思い浮かびます。
ALSは観測されたデータと予測した評価値の2乗誤差を最小にするような行列分解を与える手法であり、トピックモデルは観測されたデータの背後に確率分布を仮定し、分布のパラメータを求める事で行列分解を行います。
BPRも行列分解を与える部分は同じですが、上記の手法とは異なるアプローチをとります。 そもそもPersonalized Rankingは、ユーザごとの趣味趣向をランキングとして学習します。アイテムリストをユーザの好みでソートしたリストは、結果としてそのユーザに対するレコメンドになっているということです。BPRはPersonalized Rankingを解くための枠組みであり、今回紹介するのはこの手法を行列分解に適用した場合のアルゴリズムです。
BPRの詳細
ユーザ x アイテムの行列の行列分解の問題をBPRで解くことを考えます。 はじめに扱うデータを定義します。続いてベイズ的アプローチによって問題を定式化し、パラメータの更新式を導出します。
データ
BPRで扱う学習データは以下のように表現します。
はすべてのアイテムの集合、はユーザが好む(評価が正の)アイテムの集合、はからを除いたものの集合です。したがって、の意味は、「ユーザはアイテムよりアイテムを好む」となります。
のサイズはになります。すべてのデータを学習に用いることは不可能なので、BPRでは学習データを与えられたデータからサンプリングします。
# sample a user u = np.random.randint(userCount) itemList = trainMatrix.getrowview(u).rows[0] if len(itemList) == 0: continue # sample a positive item i = random.choice(itemList) # sample a negative item j = np.random.randint(itemCount) while trainMatrix[u, j] != 0: j = np.random.randint(itemCount)
定式化
分解後の行列を (ただし、はファクターの数)、ユーザについての全アイテムの順序を、と表記すれば「ユーザはアイテムよりアイテムを好む」ことを表すとします。 尤度関数は、
と定義します。ここで、
です。の事前分布をを単位行列として
で定義すると、事後確率最大化の式は以下で与えられます。
この式を最大化するを求めることがBPRの目的となります。 第1項で好きなアイテムに関する予測値とそうでないアイテムに関する予測値の差を大きくするように学習します。第2項、第3項は正則化項として機能します。
更新式
勾配法でを求めます。とおくと、を学習率として、
となります。としてを代入すると、最終的に以下のようになります。
ただし、
です。
ここまでをコードに起こします。
# BPR update rules y_pos = np.dot(W[u], H[i]) # target value of positive instance y_neg = np.dot(W[u], H[j]) # target value of negative instance exp_x = np.exp(-(y_pos-y_neg)) mult = -exp_x / (1.0 + exp_x) for f in xrange(factors): grad_u = H[i, f] - H[j, f] W[u, f] -= lr * (mult * grad_u + reg * W[u, f]) grad = U[u, f] H[i, f] -= lr * (mult * grad + reg * H[i, f]) H[j, f] -= lr * (-mult * grad + reg * H[j, f])
アルゴリズム
データのサンプリングとパラメータの更新を収束するまで交互に繰り返します。
repeat 1. (u,i,j)のサンプリング 2. W,Hの更新 until convergence
コードの全量はGitHubに公開しておきます。 なお、上記のコードをそのまま実行するとあまりに遅かったため、一部実装を見直しました。 そのときの試行錯誤はQiitaに書いたのでよろしければご覧ください。
BPRの魅力
BPRの魅力は何と言っても計算コストです。をそれぞれユーザ数、アイテム数、評価の数、ファクターの数とすると、例えばALSは、速いものだとであるのに対し、BPRはで計算できます。
また、目的関数やアルゴリズムがシンプルで拡張を考えやすい事もメリットといえます。例えば、行動データの他にアイテムの画像情報をモデルに取り入れたいときはこちらの論文で提案されているような拡張が考えられます。
一方、ALSに比べると並列化が難しいです。ファクター毎であれば可能ですが、ALSの様に全ユーザ/アイテムに対して並列計算させるのは大変かもしれません。
計算資源を惜しみなく投入できる環境であれば、分散環境下において近似なしで計算可能なALSは非常に強力ですが、頻繁に更新したい・1回あたりの計算コストを(金額的にも時間的にも)抑えたいという要件があればBPRも選択肢に含まれると思います。
まとめ
軽量なレコメンドアルゴリズムのひとつであるBPRを紹介しました。Personalized Rankingをベイズ的に定式化したもので、行列分解に適用することでレコメンドを達成します。
最後に
VASILYでは一緒にiQONを開発してくれる仲間を募集しています。少しでもご興味のある方は以下のリンク先をご確認ください。
また、VASILYでは今年もエンジニア向けインターンシップを行います。データサイエンスチームでのインターンシップについては、以下の記事で紹介していますので、ご興味のあるかたは是非ご覧ください。