カエルでもわかる!Spark / MLlib でやってみる協調フィルタリング(後編)

はじめに

前編では MLlib で実装されている協調フィルタリングについて、アルゴリズムの面から解説してみました。 いわば理論編です。 後編は実践編として Java コードや性能評価実験の結果を見ていきます。

MLlib 協調フィルタリングの実行

MLlib の協調フィルタリング org.apache.spark.mllib.recommendation.ALS を利用する Java のコード例を以下に示します。

公式ドキュメントの協調フィルタリングの解説では静的メソッドでモデル学習を実行していますが、ここでは ALS インスタンスを明示的に作って実行してみました。1 ALS インスタンスには(一部を除いて)設定可能なパラメータを指定してみました。2 前編を参照していただくとパラメータの意味もだいたい分かるかと思います。

学習の入力データは org.apache.spark.mllib.recommendation.Rating の集合です。 Rating は int 型のユーザ ID、商品 ID、および double 型の評価値をメンバーに持つクラスで3、あるユーザがある商品につけた評価値がいくらであるといった意味合いを持ちます。

学習結果のモデルは org.apache.spark.mllib.recommendation.MatrixFactorizationModel インスタンスとなります。 このインスタンスは内部にユーザ特徴量行列と商品特徴量行列を持っています。 前編の図の  U  V にあたるものですね。 例1~3のように、この学習済み MatrixFactorizationModel のメソッドを呼び出すことで予測評価値の取得や商品推薦が行えるというわけです。

性能評価実験

せっかく学習と推薦(評価値の予測)の仕方がわかったので、性能評価してみましょう。 適当な量の「明示的な嗜好データ」を持つデータセットで、パラメータをいろいろ変えて性能を比べてみます。

実験に使うデータ

今回は MovieLens 1M を使用しました。 MovieLens ユーザがそれぞれの映画を1~5の5段階評価しているデータセットです。 つまり暗黙的でない、ユーザによる明示的な嗜好データを持ちます。4

  • ユーザ数: 6,040
  • 映画数: 3,883
  • 評価数: 1,000,209

評価尺度

評価尺度には嗜好予測の評価の尺度として最もよく用いられる MAE(Mean Absolute Error, 平均絶対誤差)を使用します。 次の式で定義されます。

 MAE = \left( \sum_{u \in U} \sum_{i \in testset_u} |pred(u,i) - r_{u,i}| \right) / \sum_{u \in U}|testset_u|

ここで  testset_u はユーザ  u の評価データ、  pred(u,i) は予測された評価値、  r_{u,i} は実際にユーザが与えた評価値です。 要は各ユーザ・アイテムの組み合わせの予測評価値と実際の評価値の差の絶対値の平均になります。 予測評価値が実際の評価値からどれくらい離れているかの尺度であり、したがってこの値が小さいほど評価値の予測精度が高いということになります。

実験手順

以下の手順で Shuffle & Split5 の交差検証(cross validation)を実施します。

  1. データセットをランダムに2つに分け、一方を学習用、他方を評価用とする6
  2. 学習用データを使ってモデル学習を実行
  3. 学習されたモデルを使い、評価用データに含まれるユーザ・映画の組み合わせについて評価値を予測
  4. 予測された評価値と実際の評価値から MAE を計算
  5. 1~4を繰り返す

今回は 学習データ : 評価データ = 9 : 1 で分割し、かつ10回繰り返しで cross validation します。 その10回の MAE の加重平均を評価結果とします。

これを ALS の入力パラメータを変えて実施し、それぞれの結果を比較しました。 明示的な嗜好データの学習・予測なので、 ALS インスタンスに設定可能なパラメータの中でも調査対象は潜在因子の数(rank)ALS 繰り返し回数(iterations)正則化項の係数 λ(lambda)の3つになります。7

実験結果

まず最初に結論から言ってしまうと、いくつかパラメータを振って実験したところ rank=10, iterations=10, lambda=0.05 のパラメータの組み合わせで MAE が最も良くなりそうだと分かりました。 この組み合わせを軸に3つのパラメータそれぞれの影響について見ていきます。

潜在因子の数

下図は iterations=10, lambda=0.05 に固定し、rank つまり潜在因子の数を変化させていった場合の MAE の推移を表します。 ここでいう潜在因子の数とは、前編の内容で言うところのユーザ特徴量行列  U と商品特徴量行列  V の列数  n_f です。

rank_vs_mae

潜在因子の数が増えるに従って MAE が良くなっていきますが、この場合では rank=10 あたりで収束が見られます。 潜在因子が少なすぎるとユーザの嗜好を表現できない一方、必要以上に増やしすぎても予測精度は改善しないということでしょう。

ALS 繰り返し回数

同様に次の図は rank=10, lambda=0.05 で固定し、iterations つまり ALS 繰り返し回数を変化させた場合の結果となります。 前編においては交差二乗法(ALS)の4つのステップのうちの2,3の繰り返し回数にあたります。

iterations_vs_mae

1つ前の図と同じようになりました。 これは学習が収束していく様子を表しており、この実験の場合では10回ぐらい回せば十分であるということを示しています。

正則化項の係数 λ

最後に rank=10, iterations=10 に固定し、lambda つまり 正則化項の係数  \lambda を変化させた場合の結果を示します。 前編における ALS-WR の最小化目的関数の中の  \lambda にあたり、Spark 1.1.0 から指定することが可能になっています。

lambda_vs_mae

lambda=0.05 付近で極小値を取る下に凸のグラフになりました。 これはモデルの表現力と overfitting のトレードオフを表しています。  \lambda が小さいときは overfitting が起こっており、学習データに非常に特化されたモデルになっていて評価データに対する MAE が高くなってしまいます。 0.05 に向かって  \lambda が大きくなっていくと、頑健性が改善されて評価データについての MAE が良くなっていきます。 一方、0.05 を過ぎたあたりから MAE が上がり始めます。 これは正則化項の影響力が大きくなり、学習過程でモデルの複雑度を小さく保つことが過度に重視された結果としてモデルの表現力が下がっているのだと考えられます。

考察など

おそらく3つのパラメータをどのように設定するかはデータ次第であるため、実用に際しては実験的に決定することになるかと思われます。 MLlib で提供されている ALS を使うだけなら簡単ですが、実質的な性能を考慮しないといけない場合はこのパラメータ決定にちょっぴり手がかかりそうです。 特に正則化項の係数  \lambda は大きくすればいいというものではなく、ちょうどいいポイントを見つける必要があります。

また、潜在因子の数が思ったよりも小さいところで頭打ちになった感があります。 このあたり協調フィルタリングで問題になるデータの疎性(sparsity)が関連するのでは…と推測します。 疎性は評価値行列  R のマス目の中で値を持たない、つまり評価されていない組み合わせの割合を意味し、評価値行列がどれくらい疎であるかを表します。 評価値を持つユーザ・商品の組み合わせの数を  n_{r \ne 0} 、ユーザ数と商品数をそれぞれ  n_u  n_v とした場合、次の式で定義されます。

 sparsity = 1 - n_{r \ne 0} / (n_u \times n_v)

ちなみに今回のデータでは 0.957353 でした。 疎なデータにどう対応するかという問題もいろいろな研究されているようで、勉強しないとなと思いました。

それと本当はメモリベース型の協調フィルタリングなどを対抗馬として MAE の比較ができれば良かったんですが、ちょっと手が回らなかったという言い訳をここでしておきます。。。

まとめ

  • Java から MLlib の協調フィルタリング(ALS)を使ってみた
  • 学習し予測した結果を MAE で評価してみた
  • MAE は潜在因子の数・ALS 繰り返し回数・正則化項の係数  \lambda などに依存
  • パラメータチューニングがたいへんそう

現状の Spark 1.1.0 では協調フィルタリングの実装は ALS クラスしかありませんが、MLlib を含む Spark 自体まだ発展途上であり、これ以外の推薦アルゴリズムもクラスとして実装されていく可能性があります。 今後にも期待です。

参考文献

  • Jannach et al., / 田中克己・角谷和俊監訳『情報推薦システム入門』(共立出版、2012)
  • Y. Zhou et al., “Large-Scale Parallel Collaborative Filtering for the Netflix Prize,” Proc. 4th Int’l Conf. Algorithmic Aspects in Information and Management, LNCS 5034, Springer, 2008, pp. 337-348

  1. 静的メソッドではサポートされていない機能もあり、例えば Spark 1.1.0 の時点では非負値行列因子分解の指定は ALS インスタンスを作らないと指定できないようです。 

  2. ここではすべてデフォルト値を設定しています。 alpha にも値を入れていますが、 implicitPrefs が false なので無効です。 

  3. ALS ではユーザと商品の ID を int 型として扱います。それぞれ user()product() で取得できます。 

  4. I want to express my appreciation for MovieLens dataset by the GroupLens Research Group. 

  5. scikit-learn の説明が分かりやすいかもしれません。 

  6. サンプルコードの例のように評価データが学習データに含まれてしまうと(close な評価)、overfitting を検知できません。実際パラメータを多くすればするほど MAE が小さくなるということになってしまいました。 

  7. 非負値行列因子分解のフラグは ON でも OFF でも結果にあまり影響がありませんでした。。。