はじめに
Pure Rubyな形態素解析器Suikaを作成したが、未知語処理がバグだらけで、リリースして1週間で4回もバージョンアップしてしまった。現在 ver. 0.1.4 だが、もう安定して動くはず。
suika | RubyGems.org | your community gem host
未知語処理の勘違い
バグの原因は、もともと試験実装していたスクリプトからの移設ミスだったりタイポだったりもあるが、根本的なところでの勘違いがあった。
MeCabの未知語処理の設定は、char.defに定義されている。このうち、文字種ごとに未知語処理をどう起動するかが定義されている部分がある。IPAdicでは以下のように定義されている(一部抜粋)。
... KANJI 0 0 2 SYMBOL 1 1 0 NUMERIC 1 1 0 ALPHA 1 1 0 HIRAGANA 0 1 2 KATAKANA 1 1 2 ...
行頭は文字種のカテゴリで「KANJI」は漢字を「KATAKANA」は片仮名を表す。その後ろの数字が、未知語処理の起動に重要となる。
文字種カテゴリ 動作タイミング グルーピング 長さ
動作タイミングは、1であれば、常に未知語処理を起動する。0であれば、現在チェックしている文字から始まる既知語がある場合は起動しない。グルーピングは、1であれば、同じ文字種でまとめ、0であれば、まとめない。長さは、まとめるときの未知語の長さを表す。0であれば、長さで未知語をまとめない。
勘違いしていたのは、グルーピングに「1」が指定されていた場合の、長さの指定で、例えば片仮名は以下の様になっているが、
KATAKANA 1 1 2
これの解釈は、片仮名の未知語が来た場合に2文字でまとめる、ではなく、グルーピングが「1」なので、とにかく同じ文字種でまとめる、が正解だった。つまり、グルーピングが「1」の場合には、後ろの長さは「0」を指定した場合と同様となる。片仮名の未知語が、2文字で分割されるので、どうしたものかと思っていたら、ここを勘違いしていた。
未知の文字コードによるバグ
くわえて「㋿」が与えられると、エラーになることがわかった。これは、本当に新しい未知の文字がきたときに、文字種認識メソッドが、nilを返していたのが原因だった。nilではなく、本来返すべき文字種カテゴリの「DEFAULT」を返すよう修正した。あわせて、㋿を他の年号の記号と同様に認識されるようにした。
irb(main):001:0> require 'suika' => true irb(main):002:0> tagger = Suika::Tagger.new irb(main):003:0> puts tagger.parse('時代は㍻から㋿へ') 時代 名詞,一般,*,*,*,*,時代,ジダイ,ジダイ は 助詞,係助詞,*,*,*,*,は,ハ,ワ ㍻ 名詞,サ変接続,*,*,*,*,* から 助詞,格助詞,一般,*,*,*,から,カラ,カラ ㋿ 名詞,サ変接続,*,*,*,*,* へ 助詞,格助詞,一般,*,*,*,へ,ヘ,エ => nil irb(main):004:0>
動作確認
Suikaに、様々な文章を与えて、思わぬエラーで落ちることなく処理を終えるかを確認したい。今回は、株式会社ロンウイットが提供する、livedoorニュースコーパスのデータをひたすら与えて、エラーで落ちないかを確認した。
require 'suika' tagger = Suika::Tagger.new Dir.glob('ldcc-20140209/text/*/*.txt').each do |filename| puts "--- #{filename}" File.foreach(filename) do |sentence| sentence.strip! puts tagger.parse(sentence) unless sentence.empty? end end
これを実行したのが、以下のようになる。結果として、エラーで落ちることなく、無事に完走した。よほど変な文章を与えない限りは、Suikaは落ちないと考える。
おわりに
以上により、未知語が与えられるとエラーで落ちることがなくなった。ひととおり完成したので、次は、Suika::Tagger.newをした際の、辞書の読み込みが遅い問題を対処しようと思う。