Ruby 1.9.2でMeCabを使う

MeCab形態素解析

ツイートなんかを分析したいのなら、まずは形態素解析しなきゃオハナシにならないでしょ、と。たとえば「僕は杉山です。」って文を形態素っていう単位に分けて、その形態素の情報を得る、と。形態素の情報っていうのは読み方だとか品詞だとか活用だとか。
形態素解析をするには、いまではMeCabというソフトウェアを使うのがデファクトスタンダードです。

なので、僕のUbuntuにもMeCabを入れます。MeCabを使った開発をするための開発環境や形態素の辞書も入れます。

$ sudo apt-get install mecab libmecab-dev mecab-ipadic-utf8 mecab-jumandic-utf8

ここまでやるとMeCabが動くはずなので、動かしてみます。
※実際には僕は別の方法を試した後にこれらのパッケージがあることを知ったので、この通りには試していません。たぶん動くとは思うけれど、動かなかったらごめんなさい。

$ mecab
僕は杉山です。
僕	名詞,代名詞,一般,*,*,*,僕,ボク,ボク
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
杉山	名詞,固有名詞,人名,姓,*,*,杉山,スギヤマ,スギヤマ
です	助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
。	記号,句点,*,*,*,*,。,。,。
EOS

ちゃんと形態素にわかれて、品詞や読みも表示されました。いい感じ。

mecab-rubyのインストール

つぎはRubyからMeCabを使ってみたいのですが、Ruby用のMeCabインタフェースとなるものを、さらにインストールする必要があります。mecab-rubyというものです。これはapt-getで入るパッケージにはなっていないようなので、tarボールを http://sourceforge.net/projects/mecab/files/mecab-ruby/ から落としてきました。
mecab-ruby-0.98.tar.gz というファイルです。これを解凍してライブラリを作ります。

$ tar xvfz mecab-ruby-0.98.tar.gz
$ cd mecab-ruby-0.98
$ ruby extconf.rb
$ make

エラーが出ました。

MeCab_wrap.cpp: 関数 ‘VALUE _wrap_DictionaryInfo_filename_get(int, VALUE*, VALUE)’ 内:
MeCab_wrap.cpp:2229:5: 警告: 書式が文字列リテラルでは無く、かつ書式引数ではありません [-Wformat-security]
MeCab_wrap.cpp: 関数 ‘VALUE _wrap_DictionaryInfo_charset_get(int, VALUE*, VALUE)’ 内:
MeCab_wrap.cpp:2253:5: 警告: 書式が文字列リテラルでは無く、かつ書式引数ではありません [-Wformat-security]
……

MeCab_wrap.cppの該当行を見てみると

 SWIG_exception_fail(SWIG_ArgError(res1), Ruby_Format_TypeError( "", "mecab_dictionary_info_t *","type", 1, self ));

となっていました。
defineを探っていくと、SWIG_exception_fail()の第2引数は rb_raise(VALUE err, const char *fmt, ...) のfmtになっていくようですが、これが Ruby Format_TypeError() の戻り値であって、文字列リテラルでもないし、書式引数でもないということでしょうか。
自分に優しい僕は、こんなwarningはもちろん無視します。

で、付属のテストスクリプトを動かしてみます。

$ ruby test.rb
test.rb:4: invalid multibyte char (US-ASCII)
test.rb:4: invalid multibyte char (US-ASCII)

うう……。これはRubyが新しめの1.9.xになのに、test.rbがそれに対応していないからでしょうね。 test.rb の2行目にCoding指定行を挿入します。

#Coding:utf-8

では、再実行。

$ ruby test.rb 
<internal:lib/rubygems/custom_require>:29:in `require': no such file to load -- MeCab (LoadError)
	from <internal:lib/rubygems/custom_require>:29:in `require'
	from test.rb:4:in `<main>'

MeCab.soはカレントディレクトリにあるのですが、それを見ることができていないようです。面倒なので、test.rb の require行を変えました。

require './MeCab'

再々実行。

$ ruby test.rb 
0.98
太郎	名詞,固有名詞,人名,名,*,*,太郎,タロウ,タロー
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
この	連体詞,*,*,*,*,*,この,コノ,コノ
本	名詞,一般,*,*,*,*,本,ホン,ホン
を	助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
二	名詞,数,*,*,*,*,二,ニ,ニ
郎	名詞,一般,*,*,*,*,郎,ロウ,ロー
を	助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
……

やっと動いた……。ってことで、インストールします。

$ sudo make install

test.rb内のMeCabのパスの記述を元に戻して再々々実行してみましたが、ちゃんと動作しました。よしよし。

解析対象となる文を抽出

現在、得られているツイートは次の形式のファイルで保存してあります。

$ head data/20111214
2011/12/14	12:55:49	P	xxxxxxxx	こんにちは♪皆さん、風邪に気をつけましょう!"@xxxxxxxx: @xxxxxxxx @xxxxxxxx @xxxxxxxx @xxxxxxxx @xxxxxxxx おはよー(^^)"
2011/12/14	12:55:49	P	xxxxxxxx	良い感じ!RT @xxxxxxxx: おっ!!RT @xxxxxxxx: http://t.co/51pNTQ5F デンプンの曲が入ってくるぞ。@xxxxxxxx
2011/12/14	12:55:49	R	xxxxxxxx	@xxxxxxxx 拗ねちゃいや!花嫁姿見せるから!
2011/12/14	12:55:50	P	xxxxxxxx	食後のジュースじゃんけんに初参戦したけど負ける気がしなかった。りこなんじゃないところでの勝負は心に余裕が持てる。
2011/12/14	12:55:50	P	xxxxxxxx	バス内で若妻を集団痴漢! http://t.co/NrXhzUBn #エロ動画 #アダルト #スマホ #携帯
2011/12/14	12:55:50	R	xxxxxxxx	@xxxxxxxx 何ぺ??
2011/12/14	12:55:50	P	xxxxxxxx	ちょっ!!(^◇^;)RT @xxxxxxxx: @xxxxxxxx いえ、ダブルクォーターパウンダーチーズ2つ重ねです。
2011/12/14	12:55:52	R	xxxxxxxx	@xxxxxxxx カッコいいからダメです(笑)
2011/12/14	12:55:53	R	xxxxxxxx	@xxxxxxxx メモリ差?
2011/12/14	12:55:53	P	xxxxxxxx	先生やたらめったら褒めてくれるから調子のりそう(笑)

これを処理していきたいのですが、とりあえずツイート本文以外は使いません。また、「@〜」というメンション先アカウントや「#〜」というハッシュタグも無視したいところ。「http〜」というURL記述も削除してしまおうと思います。解析の邪魔になりそうな情報はあらかじめ消してしまうほうが良いと思います。
そのあたりをRubyスクリプトに入れてもいいのですが、ひとまず次のようにして試してみました。

$ head data/20111214 | awk -F'\t' 'NF==5 {print $5}' | sed 's/[@#][^ ]\+//g' | sed 's/http[^ ]*//g'
こんにちは♪皆さん、風邪に気をつけましょう!"      おはよー(^^)"
良い感じ!RT  おっ!!RT   デンプンの曲が入ってくるぞ。@denpunn
 拗ねちゃいや!花嫁姿見せるから!
食後のジュースじゃんけんに初参戦したけど負ける気がしなかった。りこなんじゃないところでの勝負は心に余裕が持てる。
バス内で若妻を集団痴漢!     
 何ぺ??
ちょっ!!(^◇^;)RT   いえ、ダブルクォーターパウンダーチーズ2つ重ねです。
 カッコいいからダメです(笑)
 メモリ差?
先生やたらめったら褒めてくれるから調子のりそう(笑)

悪くないです。上の例ではメンションが残っていますが、これは@がいわゆる全角文字ですね。こういったものも除去できますが、こういうことを追求していくとキリがないので、いまはこのままにしておきます。顔文字や「(笑)」なんかも消した方が良いのですけれども。
用途に応じて各種記号や「RT」に続く文章を消すことも必要になりますね。

3日分のログファイルをそれぞれ上記フィルタをかけて、処理対象の文だけにしたファイルにしました。

$ cat data/20111214 | awk -F'\t' 'NF==5 {print $5}'|sed 's/[@#][^ ]\+//g' |sed 's/http[^ ]*//' > tmp20111214
$ cat data/20111215 | awk -F'\t' 'NF==5 {print $5}'|sed 's/[@#][^ ]\+//g' |sed 's/http[^ ]*//' > tmp20111215
$ cat data/20111216 | awk -F'\t' 'NF==5 {print $5}'|sed 's/[@#][^ ]\+//g' |sed 's/http[^ ]*//' > tmp20111216

ツイートから名詞だけを抽出する

これらのファイルを処理していくのですが、まずMeCabで名詞と判定された形態素だけを抽出するということをやってみます。次のmorpher.rbというRubyスクリプトを書きました。

#!/usr/bin/ruby
#Coding:utf-8

require 'MeCab'

mc = MeCab::Tagger.new('--node-format=%m\t%f[0]\n --eos-format=\tEOS\n')

while gets
  morphs = mc.parse($_).force_encoding("utf-8").split("\n")
  morphs.each do |morph|
    morph_inf = morph.split("\t")
    puts morph_inf[0] if morph_inf[1].chomp == "名詞"
  end
end

もっとRubyっぽくて効率が良くてカッコいいスクリプトが書けたら良いのですが、追求しないことにします。最初はforce_encoding()なしでやってみたのですが、MeCabの解析結果の文字列をRubyがちゃんとUTF-8だと捉えてくれず、うまくいきませんでした。でも、force_encoding()を使わずにうまくいく方法もありそうです。

なお、RubyによるMeCabを扱ったコードを検索すると、parseToNode()というメソッドを使っている方が多いようです。MeCabインスタンスは直前の解析結果を保持していて、ノードとして扱う場合にはそれに依存することになります。その方法は僕の性に合わないので今回は使っていません。

この簡単なスクリプトを使ってツイートから名詞だけを抽出します。

$ cat tmp20111214 | ./morpher.rb > nouns20111214

得られた名詞の頻度を調べる

得られた名詞の出現回数(頻度)を数えて、頻度の多い順に並べてみます。

$ sort nouns20111214 | uniq -c | sort -nr > count_nouns20111214

得られた結果のファイルから、上位10番までの名詞を表示してみます。

$ head count_nouns20111214|cat -n
     1	  20350 RT
     2	  19846 ー
     3	  15534 ん
     4	  15138 (
     5	  14090 の
     6	  13050 )
     7	   9384 さん
     8	   8080 人
     9	   7169 笑
    10	   6873 こと

順位、頻度、名詞そのものの順番で表示しています。想定内ではあったのですが、これではあまり良い情報とは言えません。
こういった感じで、名詞ではあってもあまり情報として美味しくないものはどこかのタイミングで除外する必要があります。また、2番目の「ー」や4番と6番の括弧のような記号も除外したいところです。
「ん」が名詞として現れていたり、「の」も助詞ではなく名詞として解析されたようです。これは元の文によります。ひらがな1文字が本当に名詞であったとしても、あまり情報としては重要ではないので、こういったものも目的に応じて除外して良いと思います。

頻度の多い名詞

頻度順が300番までの名詞から、ある程度意味のありそうなものを手で抽出してみました。

    13	   5260 私
    14	   5168 今日
    36	   2889 今
    37	   2875 俺
    40	   2713 自分
    42	   2683 好き
    46	   2423 明日
    49	   2303 時間
    67	   1769 円
    74	   1603 仕事
    77	   1569 大丈夫
    79	   1524 地震
    88	   1381 誰
    89	   1362 うち
    90	   1353 みんな
    91	   1337 最近
    94	   1283 話
   103	   1210 フォロー
   105	   1207 ミタ
   106	   1191 なに
   107	   1176 僕
   108	   1159 日本
   110	   1130 お願い
   122	   1025 感じ
   123	   1012 わたし
   125	   1005 顔
   126	   1005 一緒
   129	    982 先生
   132	    941 いま
   133	    925 ところ
   134	    924 本
   138	    913 君
   139	    906 バイト
   141	    898 楽しみ
   143	    881 店
   144	    880 手
   145	    860 男
   146	    851 クリスマス
   148	    846 流星
   149	    840 頭
   150	    837 情報
   156	    810 友達
   158	    806 絶対
   159	    806 意味
   163	    795 世界
   168	    770 無理
   169	    767 夜
   174	    759 ここ
   177	    753 ツイート
   178	    745 昨日
   179	    736 風呂
   180	    736 声
   181	    735 あたし
   182	    734 曲
   185	    715 メール
   186	    714 写真
   187	    712 問題
   188	    710 東京
   189	    710 ゲーム
   193	    695 あなた
   194	    690 マジ
   199	    671 更新
   200	    670 定期
   201	    668 電車
   202	    664 一番
   203	    664 こちら
   204	    661 今年
   205	    660 女子
   206	    658 普通
   207	    658 希望
   208	    658 学校
   211	    654 ブログ
   213	    646 駅
   214	    646 まじ
   215	    645 ライブ
   216	    642 予定
   217	    642 お前
   218	    641 大好き
   219	    640 気持ち
   222	    635 県
   223	    633 発売
   224	    630 歳
   228	    613 名前
   229	    609 女
   230	    608 性
   234	    593 震度
   235	    592 帰宅
   236	    591 心
   238	    588 ダメ
   239	    586 数
   240	    584 以上
   241	    583 久しぶり
   243	    582 来年
   245	    580 ご飯
   246	    576 音
   248	    575 ネイマール
   249	    573 本日
   254	    568 勉強
   255	    560 そこ
   259	    551 人間
   260	    547 無料
   262	    546 さっき
   263	    545 韓国
   264	    545 金
   265	    544 プレゼント
   268	    535 最後
   269	    530 朝
   270	    530 お腹
   273	    523 大阪
   275	    521 会社
   276	    520 言葉
   277	    520 映画
   281	    515 拡散
   282	    515 ポイント
   284	    512 子供
   285	    509 電話
   288	    506 全部
   291	    505 動画
   294	    501 部屋
   296	    497 幸せ
   297	    497 今度
   298	    497 いつ
   300	    493 確か

105番の「ミタ」はドラマ「家政婦のミタ」の話題によるものでしょう。248番の「ネイマール」もブラジルから来日している注目のサッカー選手ですね。いまはクリスマス前なので146番に「クリスマス」が入っているのもうなずけます。

単純な名詞抽出のあとに

上記の名詞の上から5つは「私」「今日」「今」「俺」「自分」です。こんな名詞は毎日常に上位にあるはずです。もしトレンドワードなんかを調べたかったとすると、これらの単語は不要ですよね。そんなときは、頻度値の変化を見るべきです。時系列の頻度値の微分してもいいし、別の方法もいろいろあるのでしょう。

複合語

たとえば「福島第一原発がたいへんだ」という文があると、「福島第一原発」で1つの固有名詞として扱いたかったりするのですが、辞書になければそううまく解析できません。「福島第一原発」が「福島」「第」「一」「原発」と、4つの形態素として解析されたりします。ほかにも「プロ野球」が話題の時期に「プロ」がトレンドワードになっても意味不明ですし。
こういったいわゆる複合名詞をうまく扱うには、辞書に登録したり、もしくは解析後に連結させたりします。辞書登録するにしても連結させるにしても、そこにはいろいろなワザがあると思います。知らない単語だからこそ辞書になかったりするのに、どうやればいいのか。すべて人手でやるのはたいへんなので統計的な手法が必要になってきますよね、たぶん。