Mahoutで分散レコメンド(3)


前回は 5ユーザ, 7アイテム, 21評価 という非常に小さいデータでした。さて、今回は大きめのデータを使ってみましょう。6040ユーザ, 3900アイテム, 100万評価です。

データの準備

GroupLensというラボが、評価データを公開してくれています。研究開発目的に限り無償で利用できるので、今回はこちらを利用させてもらいましょう。

MovieLens | GroupLens

このデータはMovieLensと言い、映画の評価情報の塊です。データ規模別に3つに分かれています。

  • 943ユーザ, 1682アイテム, 10万評価
  • 6040ユーザ, 3900アイテム, 100万評価
  • 71567ユーザ, 10681アイテム, 1000万評価(+10万タグ)

ここでは、(何の根拠もないですが)100万評価を選びました。「1M Ratings Data Set (.tar.gz)」ってのをDLしましょう。このデータはREADMEを除いて3つのファイルから構成されています。

  • movies.dat : 要するにアイテムマスタです。"::"区切りで、ID::タイトル::ジャンルですね。ジャンルは"|"区切りで複数指定です。
1::Toy Story (1995)::Animation|Children's|Comedy
2::Jumanji (1995)::Adventure|Children's|Fantasy
3::Grumpier Old Men (1995)::Comedy|Romance
...(略)...
  • users.dat : 要するにユーザマスタ。やはり"::"区切りで、ID::性別::年齢層::職業::郵便番号です。今回はあまり使いません。
1::F::1::10::48067
2::M::56::16::70072
3::M::25::15::55117
...(略)...
  • ratings.dat : これが評価データです。ユーザID::アイテムID::評点(1〜5)::タイムスタンプ(epoch)
1::1193::5::978300760
1::661::3::978302109
1::914::3::978301968
...(略)...

で、rating.datは、このままの形でMahoutのRecommenderJobに食わせることができません。フォーマットが違います。ユーザID,アイテムID,評点(float)って感じなので、まぁコードでも書いて上手く変換してください。

BufferedReader in = null;
BufferedWriter out = null;
try {
  in = new BufferedReader(new InputStreamReader(new FileInputStream(INPUT)));
  out = new BufferedWriter(new FileWriter(OUTPUT));
 
  String buf;
  while ((buf = in.readLine()) != null) {
    // UserID::MovieID::Rating::Timestamp
    String[] split = buf.split("::");
    out.write(String.format("%s,%s,%s%n", split[0], split[1], split[2]));
  }
} finally {
  Closeables.closeQuietly(in);
  Closeables.closeQuietly(out);
}

で、この変換後のファイルをratings.txtと呼びましょう。

データの追加

まぁ、ratings.txtを使って、ユーザ1〜6040の誰かに対してレコメンドを計算させてもいいんですけど、それだとなんだかつまらない。ということで、自分の見た事ある映画の評価情報も混ぜてみました。

9999,47,2
9999,50,5
9999,126,3
...(略)...

意外とこのデータ作るのは大変だと思いますが、実際回してみる際は是非、やってみると面白いと思います。こいつをmyratings.txtとしましょう。ユーザIDの9999は自分です。

そしてusers.txt(前回参照)も用意しましょう。

9999

いざ計算

まずは必要なデータをHDFSに転送。

前回のレコメンドを回した人は、inputディレクトリに既に既に色々ファイルがあると思いますが、一回全部消しましょう。outputディレクトリやtempディレクトリなども出来ていると思いますが、これも存在すると勝手に上書きはしてくれないので、消しておきましょう。

$ hadoop fs -put /path/to/ratings.txt input/ratings.txt
$ hadoop fs -put /path/to/myratings.txt input/myratings.txt
$ hadoop fs -put /path/to/users.txt users.txt

ちなみに、前回はHadoopの起動コマンドラインに入力ファイルを指定しましたが、今回はファイルが複数なので、入力ディレクトリとしてinputを指定します。このinputディレクトリ内にある全ファイルを読み込むため、inputディレクトリにこれ以外のファイルが入らないようにしましょう。上記の例でもusers.txtはinputディレクトリに入れてません。

$ hadoop jar /path/to/mahout-core-0.5-job.jar \
        org.apache.mahout.cf.taste.hadoop.item.RecommenderJob \
        -Dmapred.output.dir=output \
        -Dmapred.input.dir=input \
        --usersFile users.txt \
        --similarityClassname SIMILARITY_PEARSON_CORRELATION

今回はこんな感じ。前回は5分で済みましたが、今回は77分かかりました。結果はこんなかんじ。

$ hadoop fs -cat output/part-r-00000
9999    [1038:5.0,1565:5.0,2638:5.0,3184:5.0,3106:5.0,2063:5.0,3092:5.0,2049:5.0,630:5.0,37:5.0]

元の評価データが1〜5の整数だったのでバリエーションが少ないのかな…。アイテムベースだからか…? なぜか全部5.0という予想評点でした。まだまだ5予想のアイテムが数多くある中の10個なんでしょう。100個でやってみても全部5.0でした。なんか微妙に納得いきませんが…。とりあえず挙げられた映画をリストアップ。

1038::Unhook the Stars (1996)::Drama
1565::Head Above Water (1996)::Comedy|Thriller
2638::Mummy's Tomb, The (1942)::Horror
3184::Montana (1998)::Action|Comedy|Crime|Drama
3106::Come See the Paradise (1990)::Drama|Romance
2063::Seventh Heaven (Le Septième ciel) (1997)::Drama|Romance
3092::Chushingura (1962)::Drama
2049::Happiest Millionaire, The (1967)::Comedy|Musical
630::Carried Away (1996)::Drama|Romance
37::Across the Sea of Time (1995)::Documentary

ふーん…。まぁ、いまいち映画に興味がない都元でありました。

さて、次回は

次回は疑似分散じゃなくて本格的にこいつを分散させてみます。Amazon Elastic MapReduce っていうサービス(有料)を使いますので、実際に試してみたい方はAmazon Web Servicesの以下のサービスを申し込んでおきましょう。

追記

と、思ってたら、ボヤっとしているうちにsuz-labさんに先を越されてしまいましたwww 続きはあちらで(ぉ