MeCab の形態素解析誤りを修正する生起コストの求め方
「かつおたたき」、「りんごジュース」、「ロース肉薄切り」を MeCab + IPA 辞書で形態素解析すると以下のようになります。
% mecab
かつおたたき
かつ 接続詞,*,*,*,*,*,かつ,カツ,カツ
お 接頭詞,名詞接続,*,*,*,*,お,オ,オ
たたき 名詞,一般,*,*,*,*,たたき,タタキ,タタキ
EOS
りんごジュース
りん 副詞,助詞類接続,*,*,*,*,りん,リン,リン
ご 接頭詞,名詞接続,*,*,*,*,ご,ゴ,ゴ
ジュース 名詞,一般,*,*,*,*,ジュース,ジュース,ジュース
EOS
ロース肉薄切り
ロース 名詞,一般,*,*,*,*,ロース,ロース,ロース
肉薄 名詞,サ変接続,*,*,*,*,肉薄,ニクハク,ニクハク
切り 名詞,接尾,一般,*,*,*,切り,ギリ,ギリ
EOS
日本人の感覚とはだいぶずれた結果になってしまっていますね!人間であれば連続する単語 (単語 bigram) の出現頻度に関する感覚や、各単語の意味に関する感覚を持っているので正しく解析できますが、MeCab は単語の出現しやすさ(生起コスト)と品詞のつながりやすさ(連接コスト)のみから最適解を求めているので、このような結果になります。
MeCab の動作原理に関しては以前別のブログで書いたので、興味のある方はこちらを参照してください。
日本語形態素解析の裏側を覗く!MeCab はどのように形態素解析しているか - クックパッド開発者ブログ
この解析結果を所望のものにするには、主に 2 つの選択肢があります。
- MeCab をドメイン適応させる1
- 既存の単語よりも低い生起コストの単語をユーザ辞書に追加する
ドメイン適応させようと思うと、ある程度の規模のアノテーション付きデータが必要なので、ピンポイントで修正したい問題に対処するにしては大仰です。
そうすると、アドホックな対応としてはユーザ辞書に単語を追加することになるわけですが、副作用を少なくするためにも生起コストは不必要に低くしたくないものです。
というわけで、だいぶ前にこんなツイートをしたわけですが、
「かつ / お / たたき」とか「りん / ご / ジュース」とか「ロース / 肉薄 / 切り」とか、「かつお」の生起コストを834、「りんご」を382、「薄切り」を1965下げたユーザ辞書を作るだけで所望の結果になるので、それが手っ取り早い
— Takeshi Arabiki (@a_bicky) October 15, 2016
これらの生起コストの求め方について説明します。
コストの確認方法
mecab コマンドでは -F オプションを指定することで出力フォーマットを変更することができます。2
次の例では標準のフォーマットの末尾に単語生起コスト (%pw)、連接コスト (%pC)、文頭からの累積コスト (%pc) を追加しています。
% mecab -F '%m\t%H\t%pw,%pC,%pc\n' -E 'EOS\t%pw,%pC,%pc\n'
かつおたたき
かつ 接続詞,*,*,*,*,*,かつ,カツ,カツ 4281,-2789,1492
お 接頭詞,名詞接続,*,*,*,*,お,オ,オ 6374,-484,7382
たたき 名詞,一般,*,*,*,*,たたき,タタキ,タタキ 8441,-3097,12726
EOS 0,-573,12153
りんごジュース
りん 副詞,助詞類接続,*,*,*,*,りん,リン,リン 4705,-1137,3568
ご 接頭詞,名詞接続,*,*,*,*,ご,ゴ,ゴ 6655,-451,9772
ジュース 名詞,一般,*,*,*,*,ジュース,ジュース,ジュース 3637,-3097,10312
EOS 0,-573,9739
ロース肉薄切り
ロース 名詞,一般,*,*,*,*,ロース,ロース,ロース 3692,-283,3409
肉薄 名詞,サ変接続,*,*,*,*,肉薄,ニクハク,ニクハク 4456,-557,7308
切り 名詞,接尾,一般,*,*,*,切り,ギリ,ギリ 8907,-5447,10768
EOS 0,-156,10612
N-Best 解の累積コストはバグっているので、所望の解析結果のコストは制約付き解析によって求める必要があります。
「かつおたたき」の場合は次のように求めます。
% mecab -p -F '%m\t%H\t%pw,%pC,%pc\n' -E 'EOS\t%pw,%pC,%pc\n'
かつお *
たたき *
EOS
かつお 名詞,一般,*,*,*,*,かつお,カツオ,カツオ 7269,-283,6986
たたき 名詞,接尾,一般,*,*,*,たたき,タタキ,タタキ 11246,-5090,13142
EOS 0,-156,12986
「たたき」は名詞一般な気がしますが、ひとまず分割単位が正しくなれば良しとしましょう。
最適解の累積コストは 12153、所望の解析結果の累積コストは 12986 なので、12986 - 12153 + 1 = 834 だけ「かつお」の生起コストを下げれば良いことになります。
適切なコストを求める Ruby スクリプト
手作業で適切な生起コストを求めるのはちょっと大変ですよね。というわけで、適切な生起コストのユーザ辞書を作成するための CSV を吐き出す簡易スクリプトを書いてみました。
用途に合わせていじってもらえればと思います。
#!/usr/bin/env ruby
require 'mecab'
def parse_to_nodes(sentence)
node = MeCab::Tagger.new.parseToNode(sentence)
build_nodes(node)
end
def convert_to_nodes(expected_result)
expected = "#{expected_result.split(' ').map { |w| "#{w}\t*" }.join("\n")}\nEOS"
node = MeCab::Tagger.new('-p').parseToNode(expected)
build_nodes(node)
end
def build_nodes(node)
nodes = []
while node
nodes << node
node = node.next
end
nodes
end
$stdin.each do |line|
line.chomp!
# e.g. "かつおたたき かつお たたき かつお"
sentence, expected_result, target_surface = line.split("\t")
best_cost = parse_to_nodes(sentence).last.cost
expected_nodes = convert_to_nodes(expected_result)
target_node = expected_nodes.find { |n| n.surface == target_surface }
if target_node.stat == MeCab::MECAB_UNK_NODE
raise RuntimeError, "'#{target_surface}' is unknown"
end
new_wcost = target_node.wcost - (expected_nodes.last.cost - best_cost) - 1
$stderr.puts "#{target_surface}: #{target_node.wcost} => #{new_wcost}"
puts [
target_node.surface,
target_node.lcAttr,
target_node.rcAttr,
new_wcost,
target_node.feature,
].join(',')
end
次のような、左から入力文、所望の分かち書き文、生起コストを求めたい単語の TSV ファイルを入力として渡すと、ユーザ辞書の CSV ファイルを出力します。
かつおたたき かつお たたき かつお
りんごジュース りんご ジュース りんご
ロース肉薄切り ロース 肉 薄切り 薄切り
実行してみます。
% ruby /path/to/script < /path/to/tsv > user_dic.csv
かつお: 7269 => 6435
りんご: 7277 => 6895
薄切り: 4377 => 2412
生成された CSV ファイルの内容は次のようになっています。
% cat user_dic.csv
かつお,1285,1285,6435,名詞,一般,*,*,*,*,かつお,カツオ,カツオ
りんご,1285,1285,6895,名詞,一般,*,*,*,*,りんご,リンゴ,リンゴ
薄切り,1283,1283,2412,名詞,サ変接続,*,*,*,*,薄切り,ウスギリ,ウスギリ
この CSV ファイルからユーザ辞書を生成します。
$(mecab-config --libexecdir)/mecab-dict-index -d$(mecab-config --dicdir)/ipadic -u user.dic -f utf8 -t utf8 user_dic.csv
生成したユーザ辞書を使って解析してみます。
% mecab -u user.dic
かつおたたき
かつお 名詞,一般,*,*,*,*,かつお,カツオ,カツオ
たたき 名詞,接尾,一般,*,*,*,たたき,タタキ,タタキ
EOS
りんごジュース
りんご 名詞,一般,*,*,*,*,りんご,リンゴ,リンゴ
ジュース 名詞,一般,*,*,*,*,ジュース,ジュース,ジュース
EOS
ロース肉薄切り
ロース 名詞,一般,*,*,*,*,ロース,ロース,ロース
肉 名詞,一般,*,*,*,*,肉,ニク,ニク
薄切り 名詞,サ変接続,*,*,*,*,薄切り,ウスギリ,ウスギリ
EOS
良い感じですね!
上記の知見が、ちょっとでもサービスの辞書のメンテナンスのお役に立てば幸いです。