2015年4月29日水曜日

Mecabの辞書に単語を登録する方法

Mroongaを使ってタグクラウドを作った。
(作ったのはこちら⇒http://www.direc.biz/home/tags)

よく見ると「足つぼマッサージ」という文字列の分解結果に「つぼ」がでてこない。

Mroongaのトークナイザーに使っているMecabに「足つぼマッサージ」を投げてみると、
「つぼ」は「つ」と「ぼ」になっていた。「つぼ」という単語が辞書にない模様。

[goy@myserver tmp]$ echo "足つぼマッサージ" | mecab
足      名詞,一般,*,*,*,*,足,アシ,アシ
つ      助動詞,*,*,*,下二・タ行,基本形,つ,ツ,ツ
ぼ      動詞,自立,*,*,五段・ラ行,体言接続特殊2,ぼる,ボ,ボ
マッサージ      名詞,一般,*,*,*,*,マッサージ,マッサージ,マッサージ

Mecabの辞書にはシステム辞書とユーザー辞書がありどちらにも追加登録できるが、システム辞書は
しくったらめんどそうだったので今回はユーザー辞書に登録する。


ユーザー辞書への登録


/tmp/foo.csv をつくり以下のように記載。4項目は検索スコアだが、いったん適当にいれる。
つぼ,,,5279,名詞,一般,*,*,*,*,つぼ,ツボ

辞書に登録するコマンドを実行する。実行ユーザーのホームディレクトリに拡張子が.dicのユーザー辞書ができる。

[goy@myserver tmp]$ /usr/libexec/mecab/mecab-dict-index -d /usr/lib64/mecab/dic/ipadic -u goy.dic -f utf-8 -t utf-8 /tmp/foo.csv
emitting double-array: 100% |###########################################|



Mecabにユーザー辞書を読み込ませる


/usr/lib64/mecab/dic/ipadic/dicrc を開き 末尾にユーザー辞書の設定を追加


; ChaSen (include spaces)
node-format-chasen2 = %M\t%f[7]\t%f[6]\t%F-[0,1,2,3]\t%f[4]\t%f[5]\n
unk-format-chasen2 = %M\t%m\t%m\t%F-[0,1,2,3]\t\t\n
eos-format-chasen2 = EOS\n

userdic = /home/goy/goy.dic # ←ここを追加


再度Mecabで形態素解析をしてみると、、、

[goy@myserver ~]$ echo "足つぼマッサージ" | mecab
足      名詞,一般,*,*,*,*,足,アシ,アシ
つぼ    名詞,一般,*,*,*,*,つぼ,ツボ
マッサージ      名詞,一般,*,*,*,*,マッサージ,マッサージ,マッサージ

今度は「つぼ」に分解された!



関連記事:
Mroongaを使ったタグクラウドの作り方

2015年4月28日火曜日

Mroongaを使ったタグクラウドの作り方

Mroongaを使って全文検索を導入した。
レスポンスが早くなった気がするので満足。

http://www.direc.biz/search?q=%E8%AA%BF%E6%9F%BB

しかし該当箇所を強調するためのsnippet機能は使えなかった


MronngaのトークナイザーにはMecabを使った。Mecabを使って文字列を形態素解析で
単語に分解できる。この機能を使って登録済みのテキストから自動でタグクラウドを作る仕組みを紹介。



単語への分解


DBに保存した文字列をMroongaを通してMecabで形態素解析にかけるには下記のようなSQLを実行する。

mysql> SELECT mroonga_command("tokenize TokenMecab '我思う故に我あり'");
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| mroonga_command("tokenize TokenMecab '我思う故に我あり'") |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| [{"value":"我","position":0},{"value":"思う","position":1},{"value":"故","position":2},{"value":"に","position":3},{"value":"我","position":4},{"value":"あり","position":5}] |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.07 sec)


Mroongaのmroonga_commandはgroongaのコマンドを呼び出しているので実際は、

ruby(Rails) → Mroonga → Groonga → Mecab

という流れ。


上記で「我思う故に我あり」になっている部分にテーブルに入ったデータを入れれたらいいのだが、できない見たいなので
いったんselectしてからmroonga_commandの実行のために再度SQLを呼び出する形に。


結果をrubyで処理


Mecabは形態素解析の結果をjsonで返すが、
mroonga_commandをActiveRecordを通して実行した結果をrubyに格納すると、文字化けていた。


irb(main):022:0> dum = SitesMroonga.connection.execute %! SELECT mroonga_command("tokenize TokenMecab '我思う故に我あり'")!
(23.1ms) SELECT mroonga_command("tokenize TokenMecab '我思う故に我あり'")
=> #
irb(main):023:0> dum.first.first
=> "[{\"value\":\"\xE6\x88\x91\",\"position\":0},{\"value\":\"\xE6\x80\x9D\xE3\x81\x86\",\"position\":1},{\"value\":\"\xE6\x95\x85\",\"position\":2},{\"valu"


これではこの後処理できない。どうしたものかと結構なやんだがエンコードをUTF-8として読み込めば大丈夫だった。


irb(main):026:0> JSON.parse dum.first.first.force_encoding( "UTF-8" )
=> [{"value"=>"我", "position"=>0}, {"value"=>"思う", "position"=>1}, {"value"=>"故", "position"=>2}, {"value"=>"に", "position"=>3}, {"value"=>"我", "posiあり", "position"=>5}]


形態素解析の結果のjsonをパースし、Hashとしてrubyで読み込めた。


名詞以外を除去


形態素解析の結果は名詞だけではなく、助詞なども含まれるのでこのままでは「は」や「だが」などが出現回数の上位を占める。
「は」や「や」が並ぶタグクラウド見ても ? である。
なので、簡単なフィルタを作ってそれらを除去した。



NOT_NOUNS = %w(

いかに
いる

おり

かしら
かも
から

ください
くらい
けれど

こそ
こと
ことよ

さえ

しか
する


たい
たり
だけ
だに
だの
ため
つつ

です
ても
てよ

# 一部省略
)


# -------------------------------------------------------------
# 文章より以下のルールに従って、タグの候補を抽出する
# ・名詞のみ
# ・3回以上でてくる
# -------------------------------------------------------------
def tag_candidates
wards = self.tokenize
wards = wards.reject{ |w| NOT_NOUNS.include? w }

ret = []
wards.dup.uniq.each do | w |
if wards.count( w ) >= 3
unless [
Moji::HAN_ASYMBOL,
Moji::HAN_JSYMBOL,
Moji::ZEN_ASYMBOL,
Moji::ZEN_JSYMBOL,
].include? Moji.type( w )
ret << w end end end ret end


NOT_NOUNSに助詞など、結果から省く単語を配列で入れておく。
また、?(クエスチョンマーク)などの記号や句読点なども1単語になるためこのままでは結果に含まれてしまう。

mojiを使えば単語が記号かどうかを判定できる。これを使い記号の場合は除外する。


残った単語をタグとしてidと紐付けてテーブルに保存すれば準備よし。あとはテーブルから出てくる回数の多い順に単語をとってくればタグクラウドのできあがり♪

2015年4月12日日曜日

Mroonga導入してみた。mroonga_snippetがうまく動かない。

mroongaを入れてみた。
Railsで作っているディレクトリサービスサイトの検索機能をリニューアルしようと導入中。

使いはじめは簡単で、データを入れて FULLTEXTインデックスを張れば
すぐに全文検索できるようになった。

まだいつも間違ってしまうのが、例えば サイト名と、紹介テキストをそれぞれ、name,
descriptionカラムに持つテーブルがあるとして、FULLTEXTインデックスを(name, description)に対して張った場合、
SELECT時AGAINST句で両方のカラムを指定しなければいけないこと。

Googleでもそうだけど検索結果のテキストで該当部分を目出させるようにしたい。
mroonga_snippetを使うとそういうことができるのだろうけど、試してみるとwordNに日本語を指定したら

Can't initialize function 'mroonga_snippet'; Failed to add a condition to grn_snip: <>

とメッセージをだしてエラーになった。

まぁ実験的機能ということだしまだなのかな。しかし国産なのに日本語がだめとは。。。

ではどうするか。


Modelの方で、該当文字列をspanタグで囲って返す処理を作った。


def description_for_search_result keyword

desc = self.description.strip

first_pos = desc.index( keyword )
first_pos = 0 if first_pos.nil?
max_length = 200
prefix = "..."

if first_pos <= 5 start_pos = 0 elsif desc.length - first_pos < max_length start_pos = desc.length - max_length else start_pos = first_pos end start_pos = 0 if start_pos < 0 prefix = "" if start_pos == 0 postfix = "" if start_pos + max_length < desc.length postfix = "..." end text = self.description[ start_pos, max_length ] text = text.gsub(/(#{keyword})/, %!\\1!)

dum = start_pos + max_length
ret = %!#{prefix}#{text}#{postfix}!.html_safe

end


はじめに対象文字列があった場所から、200バイト分を出力。
抽出される文字列が200バイトより少なくなるようなら、開始位置を調整。
紹介分自体が200文字より短いなら全文を表示。
表示の前後で省略される部分があるなら「...」を表示。

該当箇所はで囲まれてでてくるので、
CSSで目立つようにしてやる。


まぁこれでいったんいいんだが、
mroongaの結果では「すぽーつ」で検索しても「スポーツ」を含む結果が
返されるが、この置き換えメソッドは強調できない。

カタカナ、ひらがな程度なら対応もできるが、自然言語検索した場合は
どうしようもない。検索結果毎に処理が走るのもできれば避けたいし、早くmroonga_snippetが
使えるようになったほしい。