先日まで、Luceneに付いているKuromojiを使って、mecab-ipadic-neologdの辞書を頑張って適用していましたが、最後にこちらに試してみたいと思います。
kuromoji
http://atilika.org/
GitHub
https://github.com/atilika/kuromoji
Lucene Kuromojiを使ったエントリはこちら
http://d.hatena.ne.jp/Kazuhira/20150316/1426520209
こちらのKuromojiは、Luceneに依存していない、単体で使える形態素解析器です。後にLuceneに寄贈、取り込まれた、と。
Lucene版のKuromoji同様、IPA辞書とNAIST辞書、そしてUnidicが使用できるようです。
※Lucene版Kuromojiは、Unidicは不可
このKuromojiが使用する辞書を、mecab-ipadic-neologdの提供するものに替えてみます。
mecab-ipadic-NEologd
https://github.com/neologd/mecab-ipadic-neologd
じゃなくて
で、それで終わっても何なので、一応やったことは載せておきます。
※mecab-ipadic-neologdに何か問題があったとか、そういう話ではありません
個人的には、KuromojiのDoubleArrayTrieをなんとかできるのなら、使えるのではないかなーとは思っています。
なので、以降は微妙な結果になることを踏まえてご覧ください。
mecab-ipadic-neologdによる辞書のビルドは、あらかじめ完了しているものとします。
また、Lucene Kuromojiの時のように、原形が15文字を超えていると取り込めない、なんてことはありません。
とりあえずサンプルプログラム
まずは、通常のKuromojiを使ったサンプルプログラムを書いてみます。ちなみに、AtilikaのKuromojiを使うのは初めてです。
sample.groovy
@GrabResolver(name = 'Atilika Open Source repository', root = 'http://www.atilika.org/nexus/content/repositories/atilika') @Grab('org.atilika.kuromoji:kuromoji:0.7.7') import org.atilika.kuromoji.Tokenizer def texts = ['すもももももももものうち', 'きゃりーぱみゅぱみゅは、2012年に「つけまつける」でデビュー!', '日本経済新聞でモバゲーの記事を読んだ', 'くりぃむしちゅーは、上田晋也と有田哲平の2人からなる日本のお笑いコンビ', '艦隊これくしょんは、角川ゲームスが開発し、DMM.comが配信しているブラウザゲーム'] def tokenizer = Tokenizer.builder().build() texts.each { text -> println("$text") println(' [' + tokenizer.tokenize(text).collect { it.surfaceForm }.join(', ') + ']') }
形態素解析する対象は、Lucene Kuromojiで試した時と同じです。
実行すると、このような結果に。
$ groovy sample.groovy すもももももももものうち [すもも, も, もも, も, もも, の, うち] きゃりーぱみゅぱみゅは、2012年に「つけまつける」でデビュー! [きゃ, り, ー, ぱみゅぱみゅは, 、, 2012, 年, に, 「, つけ, ま, つける, 」, で, デビュー, !] 日本経済新聞でモバゲーの記事を読んだ [日本経済新聞, で, モバゲー, の, 記事, を, 読ん, だ] くりぃむしちゅーは、上田晋也と有田哲平の2人からなる日本のお笑いコンビ [くり, ぃむしちゅ, ー, は, 、, 上田, 晋, 也, と, 有田, 哲, 平, の, 2, 人, から, なる, 日本, の, お笑い, コンビ] 艦隊これくしょんは、角川ゲームスが開発し、DMM.comが配信しているブラウザゲーム [艦隊, これ, くし, ょんは, 、, 角川, ゲームス, が, 開発, し, 、, DMM, ., com, が, 配信, し, て, いる, ブラウザゲーム]
品詞などの情報も出力するサンプルが多いと思いますが、今回は形態素解析された結果のみ出力しました。
Kuromojiをビルドする
続いて、Kuromojiをビルドします。まずは、普通にビルドしてみます。
GitHubからclone。
$ git clone https://github.com/atilika/kuromoji.git
最新版でタグがふられている、0.7.7を選びます。
$ cd kuromoji
$ git checkout 0.7.7
ビルド。-Ddownload=trueで、IPA辞書のダウンロードを含めて実行してくれます。
$ mvn -Ddownload=true package
ここは普通に終了します。
[INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 01:44 min [INFO] Finished at: 2015-03-18T22:52:35+09:00 [INFO] Final Memory: 31M/945M [INFO] ------------------------------------------------------------------------
ちなみに、カレントディレクトリのdictionaryディレクトリ内に、ダウンロードしてきたIPA辞書が置かれています。
$ ls -l dictionary 合計 11928 drwxrwxr-x 2 xxxxx xxxxx 4096 3月 18 22:51 mecab-ipadic-2.7.0-20070801 -rw-rw-r-- 1 xxxxx xxxxx 12208105 3月 18 22:51 mecab-ipadic-2.7.0-20070801.tar.gz
medab-ipadic-neologdの辞書を使って、Kuromojiをビルドする
では、このdictionaryディレクトリの中に、mecab-ipadic-neologdの辞書を放り込みます。対象は、ビルド時に作成される「build」ディレクトリの中身になります。
$ cp -Rp [mecab-ipadic-neologdをビルドしたディレクトリ]/build/mecab-ipadic-2.7.0-20070801-neologd-20150317 dictionary
辞書が大きいので、ヒープを広げます。
$ export MAVEN_OPTS='-Xmx2g'
辞書の置かれているディレクトリ、辞書のエンコーディングを指定して、パッケージング。-Ddownload=trueは不要です。また、上手くいった場合は形態素解析の結果が変わってしまい、テストが失敗するのでテストはスキップします。
$ mvn \ > -Dkuromoji.dict.dir=dictionary/mecab-ipadic-2.7.0-20070801-neologd-20150317 \ > -Dkuromoji.dict.encoding=utf-8 \ > -DskipTests=true \ > package
辞書のビルドが始まります。
dictionary builder dictionary format: IPADIC input directory: dictionary/mecab-ipadic-2.7.0-20070801-neologd-20150317 output directory: target/classes input encoding: utf-8 normalize entries: false
マシンスペックにもよると思いますが、けっこうな時間がかかります。で、根気強く待っていると…
コケてくれます。
building tokeninfo dict... building double array trie... done processing target map...[WARNING] java.lang.reflect.InvocationTargetException at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at org.codehaus.mojo.exec.ExecJavaMojo$1.run(ExecJavaMojo.java:297) at java.lang.Thread.run(Thread.java:745) Caused by: java.lang.ArrayIndexOutOfBoundsException: -1 at org.atilika.kuromoji.dict.TokenInfoDictionary.addMapping(TokenInfoDictionary.java:104) at org.atilika.kuromoji.util.DictionaryBuilder.build(DictionaryBuilder.java:61) at org.atilika.kuromoji.util.DictionaryBuilder.main(DictionaryBuilder.java:108) ... 6 more [INFO] ------------------------------------------------------------------------ [INFO] BUILD FAILURE [INFO] ------------------------------------------------------------------------ [INFO] Total time: 13:07 min [INFO] Finished at: 2015-03-18T23:17:18+09:00 [INFO] Final Memory: 16M/1727M
ここまで13分!Lucene Kuromojiでmecab-ipadic-neologdを使った場合は、3分くらいで済んだのにこの差は…。
で、どこでコケているかなのですが、この部分です。
// Prepare array -- extend the length of array by one int[] current = targetMap[sourceId];
このsourceIdが「-1」だとおっしゃっている…。
「-1」を返しているのはどこかというと、複数あるのですがどうも今回はここっぽいです。
private int matchTail(int base, int index, String key) { int positionInTailArr = base - TAIL_OFFSET; int keyLength = key.length(); for(int i = 0; i < keyLength; i++) { if(key.charAt(i) != tailBuffer.get(positionInTailArr + i)){ return -1; } } return tailBuffer.get(positionInTailArr + keyLength) == TERMINATING_CHARACTER ? index : 0; }
なんかですね、最初に構築した単語情報からDoubleArrayTrieを作っているのですが、一部の単語に対するインデックスをDoubleArrayTrieから引けなくなっているみたいなのですよ。
int doubleArrayId = trie.lookup(surfaceform);
作ったばかりの情報を見失ってる…。
このDoubleArrayTrieはbaseBuffer、checkBuffer、tailBufferという3つのNIOのBufferでできているのですが、このうち見に行ってはいけないパターンでtailBufferを見に行っているような気がします(代わりに、checkBufferを見るべきでは?という予想…)。
そこで、DoubleArrayTrieでのtailBufferを探索に行く閾値をちょっと変えてみます。ここを
src/main/java/org/atilika/kuromoji/trie/DoubleArrayTrie.java
private static final int TAIL_OFFSET = 10000000;
ちょっと10倍に(適当)。
private static final int TAIL_OFFSET = 100000000;
では、気を取り直して再実行!
$ mvn \ > -Dkuromoji.dict.dir=dictionary/mecab-ipadic-2.7.0-20070801-neologd-20150317 \ > -Dkuromoji.dict.encoding=utf-8 \ > -DskipTests=true \ > package
また待ちます…。
結果…
dictionary format: IPADIC input directory: dictionary/mecab-ipadic-2.7.0-20070801-neologd-20150317 output directory: target/classes input encoding: utf-8 normalize entries: false building tokeninfo dict... building double array trie... done processing target map... done done building unknown word dict...done building connection costs...done 〜省略〜 [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 13:40 min [INFO] Finished at: 2015-03-18T23:41:39+09:00 [INFO] Final Memory: 23M/1806M [INFO] ------------------------------------------------------------------------
今度は成功しました!!13分かかりましたけど…。
…ホントか??
一応、動かしてみる
それでは、先ほど作成したGroovyスクリプトから、Grapeの部分を除いたものを用意します。
sample_neologd.groovy
import org.atilika.kuromoji.Tokenizer def texts = ['すもももももももものうち', 'きゃりーぱみゅぱみゅは、2012年に「つけまつける」でデビュー!', '日本経済新聞でモバゲーの記事を読んだ', 'くりぃむしちゅーは、上田晋也と有田哲平の2人からなる日本のお笑いコンビ', '艦隊これくしょんは、角川ゲームスが開発し、DMM.comが配信しているブラウザゲーム'] def tokenizer = Tokenizer.builder().build() texts.each { text -> println("$text") println(' [' + tokenizer.tokenize(text).collect { it.surfaceForm }.join(', ') + ']') }
このスクリプトと同じディレクトリに、できあがったKuromojiのJARファイルをコピーします。
$ cp target/kuromoji-0.7.7.jar [スクリプトの置いてあるディレクトリ]
動かしてみます。
$ groovy -cp kuromoji-0.7.7.jar sample_neologd.groovy すもももももももものうち [すもももももももものうち] きゃりーぱみゅぱみゅは、2012年に「つけまつける」でデビュー! [きゃりーぱみゅぱみゅ, は, 、, 2012年, に, 「, つけまつける, 」, で, デビュー, !] 日本経済新聞でモバゲーの記事を読んだ [日本経済新聞, で, モバゲー, の, 記事, を, 読ん, だ] くりぃむしちゅーは、上田晋也と有田哲平の2人からなる日本のお笑いコンビ [くりぃむしちゅー, は, 、, 上田, 晋也, と, 有田哲平, の, 2, 人, から, なる, 日本, の, お笑いコンビ] 艦隊これくしょんは、角川ゲームスが開発し、DMM.comが配信しているブラウザゲーム [艦隊これくしょん, は, 、, 角川ゲームス, が, 開発, し, 、, DMM.com, が, 配信, し, て, いる, ブラウザゲーム]
一応、Lucene Kuromojiを使った時と同じような結果が出ています。
そんなわけで
なんともまあ、微妙な結果になりました。なんとなく、大量の単語が登録された辞書をKuromojiに食わせるとこうなるような気がするのですが、DoubleArrayTrieがちゃんと読めてない上に(読め)、辞書のビルドに失敗させるだけの単語量を与えるとかなり時間がかかるので、正直やってられません…。
この修正には全然自信がないので、DoubleArrayTrieを直せる人がいれば、お願いします…。
なお、Lucene Kuromojiの方だと、実装が別物になっていて、辞書まわりのクラスもまったく異なるものになっています(代わりに、原形の15文字制限が…)。
あと、Kuromojiのmasterブランチに対しては試していません。