機械学習における重大な"仮定"と、アルゴリズムの評価

Mahoutシリーズを最初から読む場合はこちらApache Mahoutで機械学習してみるべ - 都元ダイスケ IT-PRESS

さて、前回までで、実際にMahoutのレコメンデーションエンジンを動かしてみつつ、その計算原理を軽く追いかけました。今回は、機械学習全般における大事な前提について。

仮定がいっぱい

通常プログラムを書く場合は、事実や仕様に基づいて、正確にプログラミングすることを求められます。可能性の大小や、大ざっぱな計算などに依存したプログラミングはあまり書く機会がありません。例えばあるソフトで扱う業務で、土日祝日料金と平日料金というものがあったとします。これを「1週間のうち、だいたい5日が平日で2日が休日だよね、祝日とかたまにしかないから、考慮すると大変だし、いいよね、べつに」ってことにはなりません。多分。

しかし、機械学習は違います。気づいていないだけで、実はかなり大きな仮定と近似で成り立っています。地盤ゆるゆるです。

  • 類似度にピアソン相関係数という指標を採用してよい(人と人の間の類似度はピアソン相関係数ではかることができる、という仮定です。ピアソンで良いなんて、誰がいいました? 他にもスピアマンとか色々理論はありますよ?)
  • 加重平均というアルゴリズム(ある人の好みは、他の人のそのアイテムに対する評価の(類似度を重さとした)加重平均で求めることができる、という仮定。加重平均って、相加平均*1がベースになったものですよね。平均にも色々あるんですけど。なぜ相乗平均や調和平均でなく、相加平均なんですか?)
  • 類似度ベスト2に絞るというやり方(結果は、Aさんにもっとも類似度が大きい人2人の加重平均でよい、という仮定。2人に限定しちゃっていいんですか?)
  • 類似度が大きい同士の好みは似ている(というのも、そもそも仮定なんです)


と、まぁ色々出て来ます。最後なんて、レコメンドの根底を揺るがす大事件ですよ。そもそも「事実」というのは、入力に使ったCSVのデータだけなのです。

ちなみに、これらの疑問に論理的に答えることはできません。きっちりとした事実に基づいて正しいロジックを用いて全てを計算しなければならないとしたら、機械学習なんて成り立たないんです。

というわけで、このような分野では「厳密にしすぎて結果が出ないくらいなら、仮定に基づいてでも何らかの意味のありそうな結果を出す」ことに重点を置きます。まずはやってみることが大事、ということで、上記の仮定に基づいて結果を出したわけです。

では、やってみたのは良いけれど、今後永遠にこの仮定に基づいたアルゴリズムだけでよいのか? 他を試してみる必要はないのか? と言われると、確かに試してみる必要性は感じます。実際に、運用で高い精度を出していくためには、継続的にアルゴリズムの見直しが必須になります。

だけど、試してみた結果、どっちが良いのかなんて、どうやって判定するんでしょうか?

レコメンデーションアルゴリズムの評価

理屈は簡単です。入力に使ったCSVデータのうち、いくつかのエントリを除去した上でデータモデルを作ります。その上で、評価を取り除いたアイテムについて、評価対象のアルゴリズムによって、予測評点を算出するのです。

除去した「リアル評点」と、算出した「予測評点」の差が小さければ小さいほど、良いアルゴリズムと言えます。例えば3つのエントリを除去して、3つのリアルと予測の差が出て来ます。これの平均値を評価結果とします。

org.apache.mahout.common.RandomUtils.useTestSeed(); // (A)
DataModel model = new FileDataModel(new File("src/main/resources/intro.csv"));
RecommenderEvaluator evaluator = new AverageAbsoluteDifferenceRecommenderEvaluator(); // (B)
RecommenderBuilder builder = new RecommenderBuilder() {
    
     @Override
     public Recommender buildRecommender(DataModel model) throws TasteException {
          UserSimilarity similarity = new PearsonCorrelationSimilarity(model);
          UserNeighborhood neighborhood = new NearestNUserNeighborhood(2, similarity, model);
          return new GenericUserBasedRecommender(model, neighborhood, similarity);
     }
};
double score = evaluator.evaluate(builder, null, model, 0.7, 1.0); // (C)
System.out.println("score=" + score);

上記のコードで、先日ご紹介したアルゴリズムの評価ができます。評価の結果 1.0 が得られたので、このアルゴリズムは平均で1.0の誤差があるよ、という意味になります。

コードのポイントを解説していきます。まず(A)ですが、評価には乱数を使います。というのも「いくつかのエントリを除去」する必要があるため、どのエントリを削除するのかを乱数で決めるのです。これによって評価の結果は実行する毎に異なると予測できます。今回はテストであるため、毎回同じ結果を出すために、乱数種を固定値で初期化するために、この(A)を実行しています。本番の評価では(A)は使いませんので注意。

次に(B)で RecommenderEvaluator として AverageAbsoluteDifferenceRecommenderEvaluator のインスタンスを作ります。そして RecommenderBuilder という、DataModelからRecommenderを作るロジックを作ります。これが「評価対象のアルゴリズム」ですね。evaluatorには「Recommender」を与えるのではなく、RecommenderBuilderを与える必要があります。

そして(C)で評価をかけます。第二引数は DataModelBuilder を与えることができます*2が今回はnullで。第四引数の0.7は、30%を「除去」します、という指定、第五引数の1.0は、全体の100%を評価に使います*3、という指定です。

さて、以上のようにして、アルゴリズムを評価できるようになりました。自分の持っているデータによって、最適なアルゴリズムは異なると思います。従って、随時レコメンデーションロジックを評価しつつ、ベターな*4ロジックを採用していくことが重要です。

無限回廊

さて。

この「評価」も大きな仮定に基づいていたことに気づきましたか? 以下、完全主義の人は半狂乱にならないように注意して読みましょう。

  • レコメンダの性能は、除去データのリアルと予測の「差の平均」で決まる (えっ、二乗和の平方根*5じゃだめ?)
  • データの除去率は30%でよい(なんで30%なんだよ)
  • (今回はやらなかったが)時間短縮のためにデータを間引いてもよい (へー、良いんだ)
  • 除去するデータは乱数で決めてよい (乱数っておいw)


  「評価ロジックの評価ロジックが必要だ!」
  「評価ロジックの評価ロジックの評価ロジックが必要だ!」

とならないようにしましょう。ほどほどで諦めが肝心です。機械学習に携わる人は、以下の事に早々に気づく必要があります。

  「機械学習というのは、そもそも多くの仮定に基づいているものである!」
  「完璧な機械学習というのは、すなわち100%の未来予測と等価なので、そもそも実現できない!」

*1:普通の人がイメージする平均のこと

*2:DataModelを独自実装した際に、データモデルの在り方を含めて評価したい場合に使うんだと思います。

*3:データ量が大量になると、評価にも時間が掛かる。評価時間を短縮するため、一部のデータを抽出した上で評価に使うことがある。

*4:完全なロジックは存在しなため、ベストという表現は使いませんでした。

*5:RMSRecommenderEvaluatorってのがありますw