Jubatusでラーメンレビューの点数推測

我が家に新しい猫が来ました。にゃー。

ということで、うちのPCにJubatusを入れてみました。
機械学習フレームワークということで、インストールから含めてなんかやたら難しげな印象がありましたが、
インストール自体はyumって終わりだった。意外と簡単。

使う場合は、各種言語のクライアントライブラリを別にインストールして、それを使ったプログラムを組まなきゃいけないけど、最近はjubash(うちの会社のJubatusエキスパートが開発したものらしい)という対話型クライアントがあるので、まずこれを使ってみることに。
ただ、Pythonに慣れていないので、Jubashを使えるようになるまで、ちょっと躓いたけど。

やってみること

手元にある、ラーメンデータベース内の結構な量のラーメンのレビューを使ってみよう。
まず、レビューの点数、レビューの本文を抜き出してみる。
レビューの点数は0点から100点までの1点刻みになっているが、今回は5点単位に集約して、0, 5, 10, ..., 100 に分類する。

で、レビューの点数と、レビュー本文(Mecab形態素解析して特徴抽出)を組として事前に学習させ、その後で別のレビュー本文のみを与えて、スコアを推測(正確にはclassifyで分類)させる。
といっても、今回は最初だから、細かいチューニングはなし。

やってみる

Jubatusサーバを分類機能(classify)で起動する。
起動時の設定ファイルは以下のようなJSONファイルを指定する。
(このファイルは、QiitaにあったJubatusでテキストに含まれる特徴語の傾向を学習し、入力テキストをカテゴライズするを参考にした。

{
  "method": "AROW",
  "parameter": {
    "regularization_weight": 0.001
  },
  "converter": {
    "num_filter_types": {
    },
    "num_filter_rules": [
    ],
    "string_filter_types": {
    },
    "string_filter_rules": [
    ],
    "num_types": {
    },
    "num_rules": [
    ],
    "string_types": {
        "bigram":  { "method": "ngram", "char_num": "2" },
        "mecab": {
          "method": "dynamic",
          "path": "libmecab_splitter.so",
          "function": "create"
        }
    },
    "string_rules": [
        { "key": "*", "type": "mecab", "sample_weight": "bin", "global_weight": "idf" }
    ]
  }
}
  • Jubatusサーバを起動する。
[nuko@localhost jubatus]$ jubaclassifier -f rdb.json 
2016-05-22 07:15:46,379 28285 INFO  [server_util.cpp:376] starting jubaclassifier 0.9.0 RPC server at 192.168.9.143:9199
    pid                  : 28285
    user                 : nuko
    mode                 : standalone mode
    timeout              : 10
    thread               : 2
    datadir              : /tmp
    logdir               : 
    log config           : 
    zookeeper            : 
    name                 : 
    interval sec         : 16
    interval count       : 512
    zookeeper timeout    : 10
    interconnect timeout : 10
(中略)
2016-05-22 07:15:46,395 28285 INFO  [server_helper.hpp:226] start listening at port 9199
2016-05-22 07:15:46,396 28285 INFO  [server_helper.hpp:233] jubaclassifier RPC server startup

サーバが起動したので、さっそく学習データを jubash に食わせてみる。

train 点数 "レビュー本文"

こんな感じのフォーマットで10000件ほどのラーメンレビューの点数と、レビュー本文を組にして学習させる。
例えば、数年前に俺がレビューしたもの(http://ramendb.supleks.jp/review/300073.html)だと、

train 70 "【入店状況】1954入店。先客4〜50名程、後続も数名あり。【注文】胡麻味噌ラーメンセット(1080円)。セットには半ライス、焼売3つ、ザーサイが付く。【待ち時間】7分。【麺】中程度の太さ、軽い縮れを残した麺。茹で加減は普通。麺の量も普通というところ。【スープ】少し甘みのある味噌スープ。といっても味噌の風味は控えめで、代わりに胡麻風味が強く感じられる。これはこれで悪くない。ご飯にも良く合う味のスープになっている。【具】チャーシュー、もやし&ニラ炒め、コーン、海苔。チャーシューは煮豚タイプで脂少なめ・固めの食感。あまり好みのチャーシューではない。もやしとニラは軽く炒めてある。結構、具の量も多い。【感想】胡麻味噌風味で少し変わった味わいの一杯だったが、なかなか悪くなかった。ご飯&焼売のセットをつけて、おかずラーメンとして食べたのも良い選択だったかもしれない。ご飯も一緒に食べたこともありボリュームも十分。食堂のラーメンとしては頑張っているのではないかと思えた。【備考】肉味噌ラーメンはメニューからなくなり、この胡麻味噌ラーメンに変更されたようだ。"

こんなふうにtrainコマンドを使う。
これをざっと10000件ほど登録しておく。

やってみた

登録させた状態で、登録したものと別のレビュー本文を与えて、学習結果からどの点数に分類されるか試してみる。
例えば、別レビュー(http://ramendb.supleks.jp/review/290716.html)のレビュー本文から、点数の分類をやってみる。

classify review "【入店状況】1345入店。先客なし(客だと思ったのは店員だった)。 【注文】ジャージャー麺(750円) 【待ち時間】6分。 【麺】細目の麺。茹で加減は少し柔目。麺の量は普通。もう少し固めに仕上げてあ るとなお良かったかもしれない。 【具】肉味噌、胡瓜、葱。肉味噌はやや緩めに仕上げ てあり、普通のジャージャー麺とジャージャースープ麺との中間形態になっている。肉味噌の量は十分に多く、丼になみなみと盛られている。肉味噌内の具は挽肉と刻み玉葱。味付けの強さは中庸。胡瓜と刻み葱は食感に適度な変化をあたえてくれた。 【感想】なか なか悪くない一杯。ジャージャー麺にも色々な形態があるものだ。この一杯はまさに肉味噌が主役。この肉味噌の味付けなら、もう少し太目の麺でも十分受けきれるだろうと思えた。"
70: 0.142693147063
60: 0.116121843457
55: 0.109682038426
50: 0.0359025709331
40: 0.01305016689
25: 0.0105530405417
20: 0.00953553896397
30: 0.00441248342395
5: 0.00240551866591
0: 0.0016572214663
45: -0.00519017456099
35: -0.01147844363
75: -0.013936586678
85: -0.0146971540526
10: -0.0233904644847
95: -0.0392095260322
65: -0.0501153841615
90: -0.0509259775281
100: -0.0881003141403
80: -0.120291739702

すると、点数と類似度(-1.0から1.0の値域)が出力される。
この結果から、類似度の値が最も高いものの分類(この場合だと70)だと判断できる。
おお、実際のレビューの点数合ってるな!

ということで、他の自分の過去レビューを何パターンかやってみた。

レビュー対象 実際の点数 推測された分類(上位3件)
ジャージャー麺 70 70, 60, 55
ラーメン 70 70, 60, 0
皆楽みそラーメン 80 70, 65, 60
五目そば 65 75. 70, 65
まぜそば 75 80, 60, 70

うーん、思ったより正解の分類にはならないものだな。
日本語の表現で、「不満はない」などの書き方だと、自分ではポジティブに書いたつもりでも、ネガティブな結果になりやすいのかもしれない。日本語は難しい。ある程度はフレーズを意識しないと精度の高い学習自体難しいのか。

自分のレビューはほぼ定型化されているのだけど、他人のレビューからの推測はどうだろう・・・?
他の人のレビュー結果を適当にサンプリングして再試行してみる。

レビュー対象 実際の点数 推測された分類(上位3件) 備考
ウルトラタンメン 79 60, 85, 80  
ラーメン(ヤサイ) 80 80, 85, 60 非常に文章が短いケース
ラーメン海苔増し味濃いめ半ライス 70 70, 80, 55  
豚骨らーめん(ネギ多め) 90 70, 60, 55  
燈郎+無料トッピング(ヤサイマシ) 81 80, 85, 90  

うーむ、こちらも意外と推測結果がブレるな・・・。4番目のケースなどはかなり高いスコアではあるのだが。
やはりもうちょっと、Mecab methodを使う場合のチューニングが必要なのかなあ。
もう一つのチューニングとしては、ユーザIDを学習データに組み込むことか。
結構、ユーザによってスコアの付けかた、平均点って異なるので、その傾向を加味しないといけないかも。

大訂正

  • classifyの見方を間違っていた!
    • 絶対値云々は間違いかも。
    • 正値の上位3件で集計しなおした。