[Groovy]たった1スクリプトで形態素解析！


これはG* Advent Calendar 2015の1日目の記事です。

# Groovyってなに？
Javaです。
Javaに凄くイケてる機能をいっぱい被せた、Javaです。
さぁあなたもGroovyに興味が出てきましたね？

# 形態素解析ってなに？
大雑把に言うと、文章を単語に分割して、それぞれの単語の種類を調べることができるものです。
例えば 「**プログラムを勉強する** 」とう文章は `プログラム`,　`を`, `勉強`, `する`とう単語に分割できますね？
我々人間はコレをなんとなく頭の中で処理できますが、プログラムにはそんな高度な芸当は出来ません。
そこで、形態素解析という方法を用いて、上記のように文章を分割してあげます。
形態素解析を行うライブラリは、各種言語用にすでに山のように存在しています。
今回はその中からJava用の形態素解析ライブラリである **kuromoji**を利用してみます。

# Groovyで形態素解析？Javaじゃないの？Javaのライブラリでしょ？
kuromojiはJavaのライブラリなので、当然Javaから利用されることが想定されています。
しかし、上記で述べたようにGroovyはイケてるJavaなので、なんとJavaのライブラリをそのまま利用することが出来ます。
さらにさらに、今回作成するファイルは基本的に 
**1ファイルだけです**。

Maven用のXMLを書く必要はありません。Gradle用のbuild.gradleを書く必要はありません。
というよりもむしろ
**ビルドツールが必要ありません。** 

さらに嘘のようですが
**kuromojiをダウンロードしてくる必要すらありません。**

おまけに
**実行時にkuromojiにclasspathを通す必要すらありません。**

Groovyを使うとこんな夢のようなことが実現できるんです。
さぁあなたもGroovyに興味が出てきましたね？

# 準備
さて、それでは早速始めましょう。
前提条件として、Java8が動き、Groovy2.4系がインストールされていることを想定しています。
Groovyのインストールってどうするの？という人は、 [SDKMAN](http://sdkman.io/)をインストールして、SDKMAN経由でGroovyをインストールしましょう。

作成するGroovyスクリプトのファイル名は何でもいいのですが、とりあえず`kuromoji.groovy`としておきましょう。
その中に以下の内容を記述します。
テストとして、今回 **私は車で帰社した** を形態素解析してみます。

```groovy
@Grapes([
    @Grab(group='com.atilika.kuromoji', module='kuromoji-ipadic', version='0.9.0')
])

import com.atilika.kuromoji.ipadic.Token
import com.atilika.kuromoji.ipadic.Tokenizer

// ロジック部分
Tokenizer tokenizer = new Tokenizer()
tokenizer.tokenize("私は車で帰社した").each {
    println it
}
```

ファイルを保存したら`groovy kuromoji.groovy`を実行してみてください。（初回実行時はちょっと時間がかかります）

```
Token{surface='私', position=0, type=KNOWN, dictionary=com.atilika.kuromoji.dict.TokenInfoDictionary@6f6745d6, wordId=95685}
Token{surface='は', position=1, type=KNOWN, dictionary=com.atilika.kuromoji.dict.TokenInfoDictionary@6f6745d6, wordId=259527}
Token{surface='車', position=2, type=KNOWN, dictionary=com.atilika.kuromoji.dict.TokenInfoDictionary@6f6745d6, wordId=89054}
Token{surface='で', position=3, type=KNOWN, dictionary=com.atilika.kuromoji.dict.TokenInfoDictionary@6f6745d6, wordId=259425}
Token{surface='帰社', position=4, type=KNOWN, dictionary=com.atilika.kuromoji.dict.TokenInfoDictionary@6f6745d6, wordId=252725}
Token{surface='し', position=6, type=KNOWN, dictionary=com.atilika.kuromoji.dict.TokenInfoDictionary@6f6745d6, wordId=316898}
Token{surface='た', position=7, type=KNOWN, dictionary=com.atilika.kuromoji.dict.TokenInfoDictionary@6f6745d6, wordId=30475}
```

なんということでしょう！文章 **私は車で帰社した** が名詞や動詞や接続詞やらで分割されています！！
たったあれだけのコードで一体何が起こったのでしょう。。。
少し詳しく見てみましょう。

## kuromojiのダウンロードと実行時のクラスパスの自動設定
コレを行ってくれているのが以下の部分です

```groovy
@Grapes([
    @Grab(group='com.atilika.kuromoji', module='kuromoji-ipadic', version='0.9.0')
])
```
Groovyがこのファイルを実行した時に、kuromoji-ipadicがなければ勝手にMavenリポジトリからバージョン0.9.0ライブラリをダウンロードしてきてくれて、実行時にクラスパスも設定してくれます。
この動作...まさにビルドツール！Groovy自身がGrabとういシンプルなビルドツールの機能を提供してくれているのです。
さぁあなたもGroovyに興味が出てきましたね？

## import
今回のkuromojiを利用した形態素解析では最低限以下の2つのクラスをインポートする必要があります。

```groovy
import com.atilika.kuromoji.ipadic.Token
import com.atilika.kuromoji.ipadic.Tokenizer
```
Grabの部分を見ても分かるとおり、kuromojiには、利用する辞書ごとにパッケージが分けられて存在しているようです。
今回はipadicを利用しています。

## 実際のロジック部分
さて、実際に形態素解析を行っているロジック部分が下記の部分です。

```groovy
// ロジック部分
Tokenizer tokenizer = new Tokenizer()
tokenizer.tokenize("私は車で帰社した").each {
    println it
}
```

Tokenizerのtokenizeメソッドに、形態素解析したい文章を単純に渡してあげるだけです。
すると、その結果をリストに突っ込んで返してくれるので、GroovyのListに生えているeachメソッドを使って、各結果をこんなにもシンプルにエレガントに表示できています。まさにkuromojiとGroovyのシンフォニー。
さぁあなたもGroovyに興味が出てきましたね？

# さらに詳しく
なんか分割した結果が見づらいぞ？と思われるかもしれません。だって見辛いから。
それでは、更にtokenizeした結果の詳細情報を取得してみましょう。

ロジック部分を以下のように書き換えます。

```groovy
// ロジック部分
Tokenizer tokenizer = new Tokenizer()
List<Token>tokens = tokenizer.tokenize("私は車で帰社した")
tokens.each  {Token token ->
    println "Surface: ${token.surface}"
    println "All Features: ${token.allFeatures}"
    println "allFeaturesArray: ${token.allFeaturesArray}"
    println "Position: ${token.position}"
    println "Part of speech level 1: ${token.partOfSpeechLevel1}"
    println "Part of speech level 2: ${token.partOfSpeechLevel2}"
    println "Part of speech level 3: ${token.partOfSpeechLevel3}"
    println "Part of speech level 4: ${token.partOfSpeechLevel4}"
    println "ConjugationType: ${token.conjugationType}"
    println "ConjugationForm: ${token.conjugationForm}"
    println "BaseForm: ${token.baseForm}" // 動詞の場合、その動詞の原型。    
    println "Reading: ${token.reading}"
    println "Pronunciation: ${token.pronunciation}" // 日本語の発音なので基本的にReadingの値と同じだが、"私は"の"は"と言った値がここでは"ワ"になる。
    println "Is there in dictionary?: ${token.known}"
    println "declared by user?: ${token.user}"
    println "*"*30
}
```

それでは再度kuromoji.groovyを実行してみてください。
ズラッと何かでましたね？各単語で取得できるすべての情報を、アスタリスク区切りで出力しています。
一番最初の値は以下のようになっていると思います。

```
Surface: 私
All Features: 名詞,代名詞,一般,*,*,*,私,ワタシ,ワタシ
allFeaturesArray: [名詞, 代名詞, 一般, *, *, *, 私, ワタシ, ワタシ]
Position: 0
Part of speech level 1: 名詞
Part of speech level 2: 代名詞
Part of speech level 3: 一般
Part of speech level 4: *
ConjugationType: *
ConjugationForm: *
BaseForm: 私
Reading: ワタシ
Pronunciation: ワタシ
Is there in dictionary?: true
declared by user?: false
******************************
```

凄いですね！各単語にはコレだけの情報が含まれているんですね！
それぞれの項目の意味をザックリ以下にまとめます。（私もよくわかってないですが。。。）

|Key|Value|memo|
|---|-----|----|
|Surface|元の文章から分割された単語||
|All Features|解析した結果、単語が有する各情報をまとめたもの。項目に該当する情報が無い単語の場合、その項目は＊になる||
|allFeaturesArray|上記のAll Featuresをリストに格納したもの||
|Position|インデックス。分割された格納されたリストのどのインデックス化を表す。||
|Part of speech level 1|単語の基本情報（名詞とか動詞とか）||
|Part of speech level 2|単語の基本情報（コレはもう日本語の文法の話）||
|Part of speech level 3|単語の基本情報（コレはもう日本語の文法の話）||
|Part of speech level 4|単語の基本情報（コレはもう日本語の文法の話）||
|ConjugationType|単語の基本情報（コレはもう日本語の文法の話）||
|ConjugationForm|単語の基本情報（コレはもう日本語の文法の話）||
|BaseForm|基本的にSurfaceと同じ値だけど、動詞の場合、ここにはその原型が入る！||
|Reading|読み方。漢字の場合に便利！||
|Pronunciation|発音。日本語の場合必要ないと思いますが、例えば、`は`は`わ`になります||
|Is there in dictionary?|辞書にある単語かどうか||
|declared by user?|ユーザ定義された単語かどうか||

allFeaturesArrayに格納されている値は、先頭からそれぞれ上記の表の`Part of speech level 1`から`Pronunciation`を順番に格納したものになります。
`Part of speech level 2`から`ConjugationForm`までは、プログラミングというより国語の話になります。そのため私の国語力ではコレがどういったものなのかを説明することができず。。。
`Surface`, `Part of speech level 1`, `BaseForm`の3つがあれば基本的なアプリケーションには必要十分じゃないかな？と思っています。
人工知能的に会話するロボットなどを作成する際には、おそらく上記のすべての情報をフルに用いて文章を構築する必要があるのかなと思います。

## 文章から名詞だけ抜き出す。
さて、では少し実践的に、**私は車で帰社した** から名詞だけを抜き出してみましょう。
ロジック部分を以下のように書き換えます。

```groovy
// ロジック部分
Tokenizer tokenizer = new Tokenizer()
tokenizer.tokenize("私は車で帰社した").findAll {
    it.partOfSpeechLevel1 == "名詞"
}.each {
    println it.surface
}
```
さぁ、このスクリプトを実行してみましょう。

```
私
車
帰社
```
完璧だ...非の打ち所なし！

## 文章から動詞だけ抜き出す
じゃあ動詞も抽出してみましょう！
上記のロジック部分を再度以下のように書き換えます。

```groovy
// ロジック部分
Tokenizer tokenizer = new Tokenizer()
tokenizer.tokenize("私は車で帰社した").findAll {
    it.partOfSpeechLevel1 == "動詞"
}.each {
    println "${it.surface} (${it.baseForm})"
}
```

実行します！

```
し (する)
```

できた！けど少し説明が必要ですね。なんで名詞と違って`baseForm`っていうプロパティを使ったの？という。
動詞の場合、名詞と違って現在形と過去形が存在します。`xxxをする`という文章の場合、単純に`する`が動詞になりますが、過去形の`xxxをした`という場合には、`し`の部分が国語的には動詞になります。
なので、あくまで`した`は`する`という動詞として扱いたいんだ！という場合には、分割した情報をそのまま保持している`surface`だとちょっと都合が悪いですよね。
そこで、動詞の場合にその原型を保持している`baseForm`を使っているわけです。

# まとめ
どうでしょうか。kuromojiという素晴らしいJavaライブラリを用いれば、Javaからは当然、ScalaやClojureと言ったJVM上で動く言語から簡単に形態素解析を行うことが出来ます。
そしてGroovyの素晴らしいイケイケな機能を利用することで、今回のように1ファイル且つシンプルに実装できてしまいます。

**さぁあなたもGroovyに興味が出てきましたね？**

# 参考
[kuromoji](http://www.atilika.com/ja/products/kuromoji.html)
[kuromoji(Github)](https://github.com/atilika/kuromoji)
[kuromoji-ipadic(Maven repository)](http://mvnrepository.com/artifact/com.atilika.kuromoji/kuromoji-ipadic/0.9.0)