🚀

Mecab のコスト推定自動機能を使って UniDic のユーザ辞書をビルドする

2023/11/17に公開

Mecabのコスト自動推定機能を使ってUniDicユーザ辞書を作成しようとしたところ、そのままではうまくビルドできませんでした。
一応の対応策が見つかったので、その方法と試行錯誤経緯を備忘録として残します。

やりたいこと

ユーザ辞書機能を使って、unidic システム辞書に対して任意の語を追加したい。
また、この時コストの自動推定機能を使いたい。

対応手順

直接ユーザー辞書をビルドせず、まずコスト推定結果を一旦CSVファイルに書き戻す。その後、モデルを指定せずに単純にCSVファイルからユーザ辞書をビルドする。

事前準備: unidic

コスト推定機能用に、フルパッケージ版をダウンロードして展開 (e.f. unidic3full/
https://clrd.ninjal.ac.jp/unidic/back_number.html#unidic_bccwj

e.g. unidic-cwj-202302_full.zip

また、後続のユーザ辞書ビルドの際に必要となるため、

  • char.bin
  • dicrc
  • matrix.bin
  • sys.dic
  • unk.dic

のファイルを別ディレクトリ (e.g. unidic3/) にコピーしておく。シンボリックリンクでも可

例: unidic ディレクトリを作成し、unidic3full から必要なファイルだけシンボリックリンクを張る

$ mkdir unidic3
$ cd unidic3
$ ln -s ../unidic3full/char.bin .
$ ln -s ../unidic3full/dicrc .
$ ln -s ../unidic3full/matrix.bin .
$ ln -s ../unidic3full/sys.dic .
$ ln -s ../unidic3full/unk.dic .

手順1: matrix.def の書き換え

unidic3full/matrix.def の1行目に記載されている値を確認する

$ head -n 1 unidic3full/matrix.def
21202 18859

unidic-cwj-202302 の時点では、この2つ値が mecab の想定と逆順になっているようなので、手動で書き換え

# (GNU sed)
$ sed -i -e "1c18859 21202" unidic3full/matrix.def

(ファイルサイズが大きいため、書き換えに時間がかかる点に注意)

2. コスト自動推定結果のCSV出力

unidic3full のシステム辞書およびモデルを使って、追加したい語のコスト推定を行い、結果を一旦CSVファイルに出力

ユーザ辞書に登録したい内容 (words.csv) :

$ cat words.csv
伊豆のロドリコ,,,,名詞,固有名詞,人名,一般,*,*,イズノロドリコ,イズノロドリコ,伊豆のロドリコ,イズノロドリコ,伊豆のロドリコ,イズノロドリコ,固,*,*,*,*,*,*,人名,イズノロドリコ,イズノロドリコ,イズノロドリコ,イズノロドリコ,1,*,*,0,0

コスト推定:

# words.csv に対してコスト自動推定した結果をfilled.csvに出力
$ libexec/mecab/mecab-dict-index -m unidic3full/model.bin -d unidic3full -u filled.csv -f utf-8 -t utf-8 -a words.csv

出力結果の確認:

$ cat filled.csv
伊豆のロドリコ,15125,15918,11045,名詞,固有名詞,人名,一般,*,*,イズノロドリコ,イズノロドリコ,伊豆のロドリコ,イズノロドリコ,伊豆のロドリコ,イズノロドリコ,固,*,*,*,*,*,*,人名,イズノロドリコ,イズノロドリコ,イズノロドリコ,イズノロドリコ,1,*,*,0,0

=> 空白だった2~4列目に、left-id, right-id, コスト推定結果の値(15125,15918,11045)が想定通りにセットされている

left/right id 詳細確認
# left-id
$ grep '15125' unidic3full/left-id.def
15125 名詞,固有名詞,人名,一般,*,*,*,*,固,*,*,*,1,*,*

# right-id
$ grep '15918' unidic3full/right-id.def
15918 名詞,固有名詞,人名,一般,*,*,*,*,固,*,*,*,1,*,*

left-id, right-id ともに、 words.csv で指定した "名詞,固有名詞,人名,一般,..." のものがセットされている

3. 退避しておいたオリジナルのunidicシステム辞書でユーザ辞書をビルド

$ mecab/libexec/mecab/mecab-dict-index -d unidic -u user.dic -f utf-8 -t utf-8 filled.csv

動作確認

ユーザ辞書に登録した語を含む文に対して、作成したユーザ辞書の有無で形態素解析結果が変わることを確認する

例: "伊豆のロドリコと遭遇"

  • ユーザ辞書なし(Unidic システム辞書のみ, before)
$ echo "伊豆のロドリコと遭遇" | mecab -d unidic3
伊豆	名詞,固有名詞,地名,一般,*,*,イズ,イズ,伊豆,イズ,伊豆,イズ,固,*,*,*,*,*,*,地名,イズ,イズ,イズ,イズ,0,*,*,528598838682112,1923
の	助詞,格助詞,*,*,*,*,ノ,の,の,ノ,の,ノ,和,*,*,*,*,*,*,格助,ノ,ノ,ノ,ノ,*,名詞%F1,*,7968444268028416,28989
ロド	名詞,固有名詞,一般,*,*,*,ロド,ロド,ロド,ロド,ロド,ロド,固,*,*,*,*,*,*,固有名,ロド,ロド,ロド,ロド,1,*,*,66516888691286528,241987
リコ	名詞,普通名詞,一般,*,*,*,リコ,リコ,リコ,リコ,リコ,リコ,外,*,*,*,*,*,*,体,リコ,リコ,リコ,リコ,1,C1,*,85585169095991808,311357
と	助詞,格助詞,*,*,*,*,ト,と,と,ト,と,ト,和,*,*,*,*,*,*,格助,ト,ト,ト,ト,*,"名詞%F1,動詞%F1,形容詞%F2@-1",*,7099014038299136,25826
遭遇	名詞,普通名詞,サ変可能,*,*,*,ソウグウ,遭遇,遭遇,ソーグー,遭遇,ソーグー,漢,*,*,*,*,*,*,体,ソウグウ,ソウグウ,ソウグウ,ソウグウ,0,C2,*,5772719547359744,21001
EOS
  • ユーザ辞書あり (after)
$ echo "伊豆のロドリコと遭遇" | mecab -d unidic3 -u user.dic
伊豆のロドリコ	名詞,固有名詞,人名,一般,*,*,イズノロドリコ,イズノロドリコ,伊豆のロドリコ,イズノロドリコ,伊豆のロドリコ,イズノロドリコ,固,*,*,*,*,*,*,人名,イズノロドリコ,イズノロドリコ,イズノロドリコ,イズノロドリコ,1,*,*,0,0
と	助詞,格助詞,*,*,*,*,ト,と,と,ト,と,ト,和,*,*,*,*,*,*,格助,ト,ト,ト,ト,*,"名詞%F1,動詞%F1,形容詞%F2@-1",*,7099014038299136,25826
遭遇	名詞,普通名詞,サ変可能,*,*,*,ソウグウ,遭遇,遭遇,ソーグー,遭遇,ソーグー,漢,*,*,*,*,*,*,体,ソウグウ,ソウグウ,ソウグウ,ソウグウ,0,C2,*,5772719547359744,21001
EOS

=> 想定通り、ユーザ辞書に登録した語を1語として取得できた

念の為、辞書情報を出力

$ mecab -D -d unidic3 -u user.dic
filename:	unidic3/sys.dic
version:	102
charset:	UTF-8
type:	0
size:	876803
left size:	21202
right size:	18859

filename:	user.dic
version:	102
charset:	utf-8
type:	1
size:	1
left size:	21202
right size:	18859

調査と試行錯誤の経過

問題1: Unidicのモデルデータを指定するとコスト推定およびユーザ辞書のビルドに失敗する

mecabの公式ドキュメントで紹介されている、下記のようなユーザ辞書ビルドコマンドを実行すると、https://github.com/taku910/mecab/issues/10 と同様のエラーが出る

実行コマンドとエラー:

# システム辞書として unidic, 入出力文字エンコードを utf-8 で指定
$ libexec/mecab/mecab-dict-index -m model_file -d unidic3 \
-u foo.dic -f utf-8 -t utf-8 foo.csv
reading foo.csv ... dictionary.cpp(356) [cid->left_size() == matrix.left_size() && cid->right_size() == matrix.right_size()] Context ID files(unidic3full/left-id.def or unidic3/right-id.def may be broken

=> https://github.com/taku910/mecab/issues/42 で言及されている通り、 matrix.def に記載されている left_id.def と right_id.def のサイズが逆になっているため、実行時にファイルが壊れていると判断され、コスト推定の時点でエラーとなり、辞書ビルドに失敗する

回避策

matrix.def の先頭行にそれらしき数値があったので、一旦手動で書き換えてみる

$ head -n 1 unidic3full/matrix.def
21202 18859

$ sed -i -e "1c18859 21202" unidic3full/matrix.def

書き換えを行った結果、 mecab-dict-index コマンドによるコスト推定およびユーザ辞書ビルドが成功するようになった

$ libexec/mecab/mecab-dict-index -m unidic3full/model.bin -d unidic3full -u user.dic -f utf-8 -t utf-8 foo.csv
...
reading foo.csv ... 3
emitting double-array: 100% |###########################################|

done!

問題2: ビルドしたユーザ辞書が利用できない

↑の方法でビルドしたユーザ辞書を指定すると "incompatible dictionary" というエラーが出て、mecabを起動できない

$ mecab -d unidic3 -u user.dic
viterbi.cpp(50) [tokenizer_->open(param)] tokenizer.cpp(130) [sysdic->isCompatible(*d)] incompatible dictionary: user.dic

確認したところ、システム辞書と文字コードなどがズレている場合に発生するエラーのようだったので、unidicのシステム辞書情報を確認

$ mecab -D -d unidic3full
filename:	unidic3full/sys.dic
version:	102
charset:	UTF-8
type:	0
size:	876803
left size:	21202
right size:	18859

charset が UTF-8 と大文字になっていたので、mecab-dict-index コマンド引数の文字コードを変えて再ビルドしてみるも、結果変わらず。

よくみると、 left sizeright size の情報も含まれていて、これが↑で書き換える前のmatrix.defの値の順と一致している(= mecabの想定とズレた状態?)ことが判明

システム辞書ファイル (sys.dic) と ユーザ辞書ファイル (user.dic) の中身を見てみると、それらしき数値の順番が逆になっていた。
(21202 == 0x52d2, 18859 == 0x49ab)

# システム辞書: 52d2 -> 49ab
$ hexdump unidic3full/sys.dic
0000000 e0d5 e104 0066 0000 0000 0000 6103 000d
0000010 52d2 0000 49ab 0000 28f8 0139 1030 00d6
...

# ユーザ辞書: 49ab -> 52d2
$ hexdump user.dic
0000000 9cd0 ef71 0066 0000 0001 0000 0003 0000
0000010 49ab 0000 52d2 0000 1068 0000 0030 0000
...

=> matrix.def を書き換えないとコスト推定に失敗するため、ユーザ辞書をビルドできないが(問題1)、matrix.defを書き換えたモデルデータとシステム辞書データを使ってビルドしたユーザ辞書は、元の(ビルド済みの)システム辞書とは併用できない

回避策

コスト推定した結果をCSVファイルに出力する機能があるので、matrix.def を書き換えたモデルでコスト推定を行い、出力結果を使ってモデルを指定せずに(コスト推定プロセスをスキップして)通常のユーザ辞書ビルドを行う。

元々、コスト推定を行わない場合は、matrix.def や left-id.def, right-id.def など学習用フルセットデータがなくても、ビルド済みのシステム辞書関連データさえあればユーザ辞書はビルド可能だった。
ただ、ビルド時に指定するシステム辞書ディレクトリ配下にそれらのファイルがあると、優先的に読み込まれてしまうため、結果としてビルドしたユーザ辞書とビルド済み辞書で一部データがズレて不具合を起こしてしまう。

そこで、まずシステム辞書を、フル版と軽量版でディレクトリ分けする。
さらに、コスト推定とユーザ辞書ビルドを2段階に分けて実行し、前者ではフル版のシステム辞書を、後者では軽量版のシステム辞書を指定することにより、結果としてコスト推定結果を含むユーザ辞書の作成ができることが判明した。

# 軽量版システム辞書ディレクトリの作成
$ mkdir unidic3
$ cd unidic3
$ ln -s ../unidic3full/char.bin .
$ ln -s ../unidic3full/dicrc .
$ ln -s ../unidic3full/matrix.bin .
$ ln -s ../unidic3full/sys.dic .
$ ln -s ../unidic3full/unk.dic .

# full版辞書 (unidic3full) を使ってコスト推定を行い、結果をCSVに出力
$ libexec/mecab/mecab-dict-index -m unidic3full/model.bin -d unidic3full -u foo_filled.csv -f utf-8 -t utf-8 -a foo.csv

# 軽量版辞書 (unidic3) を使って、ユーザ辞書をビルド
$ libexec/mecab/mecab-dict-index -d unidic3 -u user.dic -f utf-8 -t utf-8 foo_filled.csv

その他、未確認な仮説

問題2の対処別案

もし、システム辞書とユーザ辞書でleft sizeright sizeが異なっていることが問題ということであれば、コスト推定結果を直接ユーザ辞書にビルドした後、バイナリエディタなどで該当箇所を編集してしまえば、わざわざ2段階ビルドしなくても動く、かもしれない

before:

# 49ab -> 52d2 の順
$ hexdump user.original.dic
0000000 9cd0 ef71 0066 0000 0001 0000 0003 0000
0000010 49ab 0000 52d2 0000 1068 0000 0030 0000
...

after:

# 52d2 -> 49ab の順になるように手動編集
$ hexdump user.modified.dic
0000000 9cd0 ef71 0066 0000 0001 0000 0003 0000
0000010 52d2 0000 49ab 0000 1068 0000 0030 0000
...

Discussion