mecabにユーザ辞書を追加して、MySQL5.1のフルテキストインデックスを更新するまでの話

環境

  • MySQL5.1
  • Homebrewでmecabをインストール済
  • DBに以下のようなテーブルを追加してある
CREATE TABLE stations (name CHAR(255), info TEXT, FULLTEXT(info) WITH PARSER mecab);
mysql> CREATE TABLE stations (name CHAR(255), info TEXT, FULLTEXT(info) WITH PARSER mecab);
Query OK, 0 rows affected (0.01 sec)

mysql> desc stations;
+-------+-----------+------+-----+---------+-------+
| Field | Type      | Null | Key | Default | Extra |
+-------+-----------+------+-----+---------+-------+
| name  | char(255) | YES  |     | NULL    |       |
| info  | text      | YES  | MUL | NULL    |       |
+-------+-----------+------+-----+---------+-------+
2 rows in set (0.00 sec)
    • 中身は、駅名とwikipediaから取ってきた適当なテキスト

駅名を正しく認識してくれない問題


標準の辞書では駅名を正しく形態素解析してくれない

$ echo "愛甲石田" | mecab
愛甲    名詞,固有名詞,人名,姓,*,*,愛甲,アイコウ,アイコー,,
石田    名詞,固有名詞,地域,一般,*,*,石田,イシダ,イシダ,,
EOS


なので、MySQL全文検索結果もおかしい

mysql> SELECT * FROM stations WHERE MATCH(info) AGAINST("愛甲石田駅")\G
*************************** 1. row ***************************
name: 愛甲石田駅
info:  愛甲石田駅 北口(2008713日) あいこういしだ - Aikō-Ishida ◄本厚木 (3.1km) (3.7km) 伊勢原► 所在地 神奈川県厚木市愛甲1丁目11号 (一部は同県伊勢原市石田) 北緯3525分3.5秒 東経1392038秒座標: 北緯3525分3.5秒 東経1392038秒 所属事業者 ■小田急電鉄 所属路線 小田原線 キロ程 48.5km(新宿起点) 駅構造 地上駅(橋上駅) ホーム 22線 乗降人員 -統計年度- 47,052人/日 -2011年- 開業年月日 1927年(昭和2年)41日 この表について この表はテンプレートを用いて表示しています。編集の仕方はTemplate:駅情報をごらんください。 
*************************** 2. row ***************************
name: 芦沢駅
info:  芦沢駅 駅舎(20055月) あしさわ - Ashisawa ◄北大石田 (2.9km) (6.6km) 舟形► 所在地 山形県尾花沢市大字芦沢1012 北緯3839分19.76秒 東経14021分39.83秒 所属事業者 東日本旅客鉄道(JR東日本) 所属路線 ■奥羽本線(山形線) キロ程 133.7km(福島起点) 電報略号 アハ 駅構造 地上駅 ホーム 22線 乗車人員 -統計年度- 66人/日(降車客含まず) -2011年- 開業年月日 1916年(大正5年)121日 備考 簡易委託駅 この表について この表はテンプレートを用いて表示しています。編集の仕方はTemplate:駅情報をごらんください。 
2 rows in set (0.00 sec)


特に隣接駅でもない芦沢駅が出てくる


なので、ユーザ辞書に駅名を追加して、全文検索の結果を正しくする

  • ちなみにブーリアンモードならば正しい結果が出るのだけど、速度的に問題があるので今回は見送り

ユーザ辞書の作成


以下のスクリプトでユーザ辞書生成に必要な形式のcsvファイルを作成する

  • 01.csvは、駅名と本文が入ったcsvファイル


このコードには不具合があるので、下の「追記」も参照してください

# -*- encoding: utf-8 -*-
require 'rubygems'
require 'csv'


CSV.open('./02.csv', 'w') do |csv_w|
  CSV.open('./01.csv', 'r:UTF-8').each do |csv|
    name = csv.first
    csv_w << [name.gsub(/$/, ''), nil, nil, 10, '名詞', '一般', '*', '*', '*', '*', 'ユーザ設定', 'ユーザセッテイ', 'ユーザセッテイ', '追加エントリ']
  end
end

こんなのが生成される

あいの里公園,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
あいの里教育大,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
あおば通,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
あかおか,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
あかぢ,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
あきた白神,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
あさぎり,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
あざみ野,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
あつみ温泉,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
あぶくま,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
あまや,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
あやめ公園,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
あわくら温泉,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
あわら湯のまち,,,10,名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
...
2012/01/11 追記


上記のようにコストを0に固定していると辞書の単語を含む長文の単語が形態素解析されない問題がある
例えば辞書に「循環器」「内科」「循環器内科」がある時に「循環器内科」を形態素解析すると、

$ echo "循環器内科" | mecab
循環器 名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
内科 名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
EOS

のように、「循環器内科」で形態素解析されない


なので、以下のように、長文の方のコストを低くしてあげる必要がある

  result.each do |name|
     cost = -400 * name.split(//u).size ** 1.5
     cost = -36000 if cost < -36000
     csv<< [name, nil, nil, cost, '名詞', '一般', '*', '*', '*', '*', 'ユーザ設定', 'ユーザセッテイ', 'ユーザセッテイ', '追加エントリ']
  end

ユーザ辞書のコンパイル


csvで生成した辞書ファイルをコンパイルする

$ /usr/local/Cellar/mecab/0.994/libexec/mecab/mecab-dict-index -d /usr/local/Cellar/mecab/0.994/lib/mecab/dic/naist-jdic/ -u usr.dic -f utf8 -t utf8 02.csv
reading 02.csv ... 479
emitting double-array: 100% |###########################################|

done!
  • mecab-dict-indexが辞書
  • -d オプションで使用する辞書を設定。今回の環境ではNAIST-jdicを使っているのでそっちを指定
  • その他のオプションは参考リンクを参照

コンパイルした辞書を使うように設定


生成した辞書を適当な場所に移動

$ cp usr.dic /usr/local/lib/mecab/dic/.


設定ファイルを更新

$ vim /usr/local/Cellar/mecab/0.994/etc/mecabrc


ユーザ辞書のpathを設定する

userdic = /usr/local/lib/mecab/dic/usr.dic

動作確認

$ echo "愛甲石田" | mecab              
愛甲石田        名詞,一般,*,*,*,*,ユーザ設定,ユーザセッテイ,ユーザセッテイ,追加エントリ
EOS


ちなみにmecabのユーザ辞書設定後、MySQLを更新するまでの間は正しい検索は期待できない様子

mysql> SELECT * FROM stations WHERE MATCH(info) AGAINST("愛甲石田駅")\G
Empty set (0.01 sec)

mysql> SELECT * FROM stations WHERE MATCH(info) AGAINST('+"愛甲石田駅"' IN BOOLEAN MODE)\G
Empty set (0.00 sec)

フルテキストインデックスの更新

mysql> REPAIR TABLE stations QUICK;
+---------------+--------+----------+----------+
| Table         | Op     | Msg_type | Msg_text |
+---------------+--------+----------+----------+
| test.stations | repair | status   | OK       |
+---------------+--------+----------+----------+
1 row in set (0.16 sec)

MySQLの動作確認


芦沢駅がヒットしなくなることを確認

mysql> SELECT * FROM stations WHERE MATCH(info) AGAINST("愛甲石田駅")\G
*************************** 1. row ***************************
name: 愛甲石田駅
info:  愛甲石田駅 北口(2008713日) あいこういしだ - Aik&#333;-Ishida &#9668;本厚木 (3.1km) (3.7km) 伊勢原&#9658; 所在地 神奈川県厚木市愛甲1丁目11号 (一部は同県伊勢原市石田) 北緯35253.5秒 東経1392038秒座標: 北緯35253.5秒 東経1392038秒 所属事業者 ■小田急電鉄 所属路線 小田原線 キロ程 48.5km(新宿起点) 駅構造 地上駅(橋上駅) ホーム 22線 乗降人員 -統計年度- 47,052人/日 -2011年- 開業年月日 1927年(昭和2年)41日 この表について この表はテンプレートを用いて表示しています。編集の仕方はTemplate:駅情報をごらんください。 
1 row in set (0.00 sec)

ユーザ辞書追加後、フルテキストインデックスが更新されるまでの挙動の考察


以下チラシの裏


愛甲石田は検索できなくなった

mysql> SELECT * FROM stations WHERE MATCH(info) AGAINST("愛甲石田駅")\G
Empty set (0.01 sec)

mysql> SELECT * FROM stations WHERE MATCH(info) AGAINST('+"愛甲石田駅"' IN BOOLEAN MODE)\G
Empty set (0.00 sec)


ただし、元々固有名詞として存在している名詞の検索は大丈夫だった

echo "あざみ野" | mecab
あざみ野        名詞,固有名詞,一般,*,*,*,あざみ野,アザミノ,アザミノ,,
EOS
mysql> SELECT * FROM stations WHERE MATCH(info) AGAINST("あざみ野")\G
*************************** 1. row ***************************
name: あざみ野駅
info:  あざみ野駅 田園都市線あざみ野駅西口(201111月) あざみの - Azamino 所在地 横浜市青葉区あざみ野二丁目 所属事業者 東京急行電鉄(駅詳細) 横浜市交通局(駅詳細) この表につ いて この表はテンプレートを用いて表示しています。編集の仕方はTemplate:駅情報をごらんください。 東急 あざみ野駅 あざみの - Azamino &#9668;たまプラーザ (1.1km) (1.1km) 江田&#9658; 所在地 横浜 市青葉区あざみ野二丁目1番地1 北緯35347.4秒 東経1393312.8秒座標: 北緯35347.4秒 東経1393312.8秒 駅番号 &#160;DT 16&#160; 所属事業者 東京急行電鉄(東急) 所属路線 ■田園都市線 キロ程 18.2km(渋谷起点) 駅構造 高架駅 ホーム 相対式 22線 乗降人員 -統計年度- 128,986人/日 -2011年- 開業年月日 1977年(昭和52年)525日 この表について この表はテンプレートを 用いて表示しています。編集の仕方はTemplate:駅情報をごらんください。 横浜市交通局 あざみ野駅 あざみの - Azamino &#9668;B31 中川 (1.5km) 所在地 北緯35345.1秒 東経1393312.5秒 駅番号 ○B32 所属事業者 横浜市交通局(横浜市営地下鉄) 所属路線 ■ブルーライン(3号線) キロ程 20.7km(関内*起点) 湘南台から40.4km 駅構造 地下駅 ホーム 12線 乗車人員 -統計年度- 38,897人/日(降車客含まず) -2010年- 開業年月日 1993年(平成5年)318日 備考 *3号線の起点として この表について この表はテンプレートを用いて表示しています。編集の仕方はTemplate:駅情 報をごらんください。
1 row in set (0.00 sec)
全文検索にヒットしなくなるワードの条件


多分、以下のようなワードだと思われる

  • ユーザ辞書で追加された単語を含むワードの中で
  • 今までと違う形で形態素解析が行われるようになったワード
愛甲石田」で検索した場合


以下のような処理になっていると思う

  • 愛甲石田」を「愛甲」「石田」に形態素解析
  • フルテキストインデックスの中に「愛甲」や「石田」を持つものを探して関連度が高い順に表示


なので、ユーザ辞書に「愛甲石田」が追加されると、
形態素解析、「愛甲石田」でフルテキストインデックスを探しに行く為、1件もヒットしないのではないか?

「あざみ野」で検索した場合


ユーザ辞書追加前から「あざみ野」で形態素解析されているので、きちんとヒットするのだと思う

ブーリアンモードでも検索出来ない理由


よくわからないけど、内部的に形態素解析されたフルテキストインデックスを使ってるはずだから、
あるはずの「愛甲石田」を探している為に0件になるのかな?