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件で集計しなおした。