概要

Javaの比較的新しい形態素解析器、Kuromoji

lucene-gosenやGomokuのように辞書内包で、jarを落とせばその場で利用でき、Unidicに対応していて、ソースがLuceneのtrunkにコミットされているという、何かと気になる特徴の持ち主。

複数のモードを持っているようで、Searchモードを使うと「日本経済新聞」を「日本 | 経済 | 新聞」のように検索で利用しやすい形にばらして解析してくれたり、Extendedモードを使うと未知語をuni-gramにしてくれたりもするらしい。

今日はそんなKuromojiさんの導入から簡易な使い方までをさらっと追いかけてみた。

@CretedDate 2012/01/31
@Env kuromoji0.7.5 / Lucene3.5.0 / Solr3.5.0 / Java1.6

導入

まずは下記ページからダウンロード。今回はkuromoji-0.7.5.tar.gzを利用。

Downloads - atilika/kuromoji
https://github.com/atilika/kuromoji/downloads

落としたファイルを解凍して中のjarをクラスパスに入れれば導入完了。とても簡単。

Mavenも利用できる。

    <repository>
        <id>ATILIKA dependencies</id>
        <url>http://www.atilika.org/nexus/content/repositories/atilika</url>
    </repository>

    <dependency>
        <groupId>org.atilika.kuromoji</groupId>
        <artifactId>kuromoji</artifactId>
        <version>0.7.6</version>
        <type>jar</type>
        <scope>compile</scope>
    </dependency>

Luceneと同じくApacheライセンス。ソースはgithubから落とせる。

$ git clone https://github.com/atilika/kuromoji.git

形態素解析してみる

以下のようなコードで形態素解析できる。

// この2行で解析できる
Tokenizer tokenizer = Tokenizer.builder().build();
List<Token> tokens = tokenizer.tokenize("もう眠い");

// 結果を出力してみる
for (Token token : tokens) {
    System.out.println("==================================================");
    System.out.println("allFeatures : " + token.getAllFeatures());
    System.out.println("partOfSpeech : " + token.getPartOfSpeech());
    System.out.println("position : " + token.getPosition());
    System.out.println("reading : " + token.getReading());
    System.out.println("surfaceFrom : " + token.getSurfaceForm());
    System.out.println("allFeaturesArray : " + Arrays.asList(token.getAllFeaturesArray()));
    System.out.println("辞書にある言葉? : " + token.isKnown());
    System.out.println("未知語? : " + token.isUnknown());
    System.out.println("ユーザ定義? : " + token.isUser());
}

上記のコードは「もう眠い」という言葉を解析しています。結果は以下。

==================================================
allFeatures : 副詞,一般,*,*,*,*,もう,モウ,モー
partOfSpeech : 副詞,一般,*,*
position : 0
reading : モウ
surfaceFrom : もう
allFeaturesArray : [副詞, 一般, *, *, *, *, もう, モウ, モー]
辞書にある言葉? : true
未知語? : false
ユーザ定義? : false
==================================================
allFeatures : 形容詞,自立,*,*,形容詞・アウオ段,基本形,眠い,ネムイ,ネムイ
partOfSpeech : 形容詞,自立,*,*
position : 2
reading : ネムイ
surfaceFrom : 眠い
allFeaturesArray : [形容詞, 自立, *, *, 形容詞・アウオ段, 基本形, 眠い, ネムイ, ネムイ]
辞書にある言葉? : true
未知語? : false
ユーザ定義? : false

サンプルコード全文

モードを使い分けてみる

Builderにmodeを設定することで、解析結果が変化する。

Searchモードは「日本経済新聞」のような複数の言葉が組み合わさった単語を「日本 | 経済 | 新聞」のように分けて解析してくれる。全文検索エンジンと組み合わせて利用する場合に、日本経済新聞が「経済」や「新聞」でも検索可能になるので便利。

Extendedモードはそれに加えて、未知語をuni-gramとして扱う。たとえば「モバゲー」は「モ | バ | ゲ | ー」と1文字ごとに分割される。未知語を検索し損ねることを減らしてくれそうな機能。

String parseWord = "日本経済新聞でモバゲーの記事を読んだ";
Builder builder = Tokenizer.builder();

// ノーマルモード
Tokenizer normal = builder.build();
List<Token> tokensNormal = normal.tokenize(parseWord);
  //=> 日本経済新聞 | で | モバゲー | の | 記事 | を | 読ん | だ

// Searchモード
builder.mode(Mode.SEARCH);
Tokenizer search = builder.build();
List<Token> tokensSearch = search.tokenize(parseWord);
  //=> 日本 | 経済 | 新聞 | で | モバゲー | の | 記事 | を | 読ん | だ

// Extendsモード
builder.mode(Mode.EXTENDED);
Tokenizer extended = builder.build();
List<Token> tokensExtended = extended.tokenize(parseWord);
  //=> 日本 | 経済 | 新聞 | で | モ | バ | ゲ | ー | の | 記事 | を | 読ん | だ

サンプルコード全文

ソースを見る限りSearchモードは、漢字のみで構成される4文字以上の単語、もしくは7文字以上の単語に対してコストを重くすることで、分割を促しているようだ。

例えばカタカナ6文字の「ボトムアップ」は「ボトム | アップ」に分割されないが、9文字の「ボトムアップテスト」(IPA辞書にいる)はSearchモードにすると「ボトムアップ | テスト」に分割される。

ユーザ辞書の利用

Builderにユーザ辞書を渡して、その場で辞書をカスタマイズすることもできるらしい。

ユーザ辞書の書き方は、githubからソースを落としてくると「src/example/resources/userdict.txt」にサンプルが置いてある。

例えば「稀勢の里寛」を「稀勢の里 | 寛」に分割したい場合は、以下のように書くらしい。

稀勢の里寛,稀勢の里 寛,キセノサト ユタカ,カスタム人名

ユーザ辞書は以下のようにBuilderのuserDictionaryで指定する。

// ユーザ辞書を指定して解析
Builder builder = Tokenizer.builder();
builder.userDictionary("userdic.txt");
Tokenizer userTokenizer = builder.build();

List<Token> userTokens = userTokenizer.tokenize("稀勢の里寛");
  //=> 稀勢の里 | 寛

ユーザ辞書はコストは設定しないらしい。ソースのUserDictionary.javaを見たら-100000と書いてあったので、ユーザ辞書に入れた単語は高確率で優先されることになりそう。

例えば「日,日,ヒ,カスタム名詞」をユーザ辞書に登録すると、「今日」が「今 | 日」にばらされたり、「日本」が「日 | 本」にばらされたりした。

UniDicを利用してみる

KuromojiはUniDicにも対応しているらしい。

まずは下記サイトから個別ファイルの方のunidic-mecabのsrcをダウンロード(要登録)して解凍する。パッケージ版の方には辞書生成に必要な生ファイルがいないので注意。

形態素解析辞書 UniDic
http://www.tokuteicorpus.jp/dist/

次にKuromojiのソースをチェックアウト。

$ git clone https://github.com/atilika/kuromoji.git

チェックアウトしたソースのpom.xmlのpropertiesを以下のように変更する。

    <properties>
        <!-- unidicを指定 --> 
        <kuromoji.dict.format>unidic</kuromoji.dict.format>
        <!-- 特に変更しない --> 
        <kuromoji.dict.url>http://atilika.com/releases/mecab-ipadic/mecab-ipadic-2.7.0-20070801.tar.gz</kuromoji.dict.url>
        <!-- 解凍したUniDicのディレクトリのパスを指定する -->
        <kuromoji.dict.dir>dictionary/unidic-mecab1312src</kuromoji.dict.dir>
        <!-- utf-8を指定 --> 
        <kuromoji.dict.encoding>utf-8</kuromoji.dict.encoding>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

Mavenが必要なので入ってない場合はインストールして、以下のコマンドを実行する。

$ mvn compile
$ mvn jar:jar

これでtargetディレクトリ配下にjarが生成される。出来上がったjarにはUniDicが適用されている。

Luceneで利用してみる

下記URLからkuromoji-solrのjarを落としてくる。

http://atilika.org/confluence/pages/viewpage.action?pageId=131141

Mavenの場合は以下のような感じ。

    <repository>
        <id>ATILIKA dependencies</id>
        <url>http://www.atilika.org/nexus/content/repositories/atilika</url>
    </repository>

    <dependency>
        <groupId>org.atilika.kuromoji</groupId>
        <artifactId>kuromoji-solr</artifactId>
        <version>0.5.3</version>
        <type>jar</type>
    </dependency>

下記サイトからlucene-coreのjarも落としてくる。あと、CommonsIOも必要らしい。

http://lucene.apache.org/java/docs/

Kuromojiでは以下のようにTokenizerを引数で渡すことで初期化する。

// せっかくなのでSearchモードに設定してみる
Builder builder = Tokenizer.builder();
builder.mode(Mode.SEARCH);
Tokenizer tokenizer = builder.build();

// AnalyzerにTokenizerを渡して初期化
Analyzer analyzer = new KuromojiAnalyzer(tokenizer);

サンプルコード全文

lucene-gosenでやっているような、動詞の揺れを吸収する(「思った」を「思う」で検索してもヒットする)ような処理は見た限りではやってないっぽかった。でも、luceneのtrunkの中にはKuromojiBaseFormFilterというそれらしきクラスがいたりした。

Searchモードはインデックスのサイズもそれほど肥大化せず、そこそこに救える文字列が増えるので良いと思った。

Solrで利用してみる

Solr用のTokenizerもちゃんと用意されている。

とりあえずSolrを落としてきて解凍する。

http://lucene.apache.org/solr/

$ tar xzvf apache-solr-3.5.0.tgz

解凍したフォルダのexample/solrディレクトリに、kuromoji-x.x.x.jarと、kuromoji-solr-x.x.x.jarを入れる。

schema.xmlには以下のように記述する。

    <fieldType name="text_kuromoji" class="solr.TextField">
      <analyzer>
        <tokenizer class="org.atilika.kuromoji.solr.KuromojiTokenizerFactory" mode="search" user-dictionary=""/>
      </analyzer>
    </fieldType>

modeのところはnormalやextendedが選べるっぽい。またuser-dictionaryにユーザ辞書のパスを指定することもできるようだ。

lucene-gosenも一緒に入れて、JapaneseWidthFilterFactoryとかJapaneseKatakanaStemFilterFactoryとかを併用してみたところ、軽く動かした感じでは想定通りの動きをしてくれていた。

参考URL

Kuromoji関連のLuceneのIssues
https://issues.apache.org/jira/browse/LUCENE-3305

Kuromojiのgithub
https://github.com/atilika/

Kuromojiを調べてみた | johtaniの日記
http://johtani.jugem.jp/?eid=15