Juman++をサーバーモードで利用しよう

# この記事の内容

- Juman++をサーバーモードで利用すると、はかどる話
- [形態素解析を簡単に実行するPythonパッケージ](http://qiita.com/Kensuke-Mitsuzawa/items/1d2837b374152c9758c9)でJuman++を利用可能にした話

# Juman++とは？

Juman++とは京大・黒橋研究室で開発された形態素解析器です。
「それ、Mecabと何が違うん？」と言う点ですが、Juman++では「RNN（いわゆるディープラーニング系のやつ）言語モデルを利用している」点が異なります。

Qiitaでも紹介記事が徐々に増えつつあり、今後の普及が楽しみです。

- [新形態素解析器JUMAN++を触ってみたけど思ったより高精度でMeCabから乗り換えようかと思った話](http://qiita.com/riverwell/items/438e88427363511e9f28)
- [複数の形態素解析器を見比べる](http://qiita.com/tkngue/items/b32ddcaf3ad80bf93d8c)


## Juman++のちょっと気になる点

1. 依存ライブラリを新しくしないといけない。特にgcc周り
2. 遅い

依存ライブラリ問題は、gccを更新して、他のコード郡が動かなくなるかもしれない・・・という懸念はあります。
そういう場合は[Docker環境を用意する](http://qiita.com/kyo-bad/items/f4e426d08f4a64ff5d45)というクールな解決法を使いましょう。

さて、問題は2の速度面です。
[こちらの記事](http://qiita.com/trtd56/items/2f62a390eb8fab92e801)では、
> Mecabは10秒ほどで終わったのに対し、JUMAN++は 10時間以上 かかってしまい

とのことで、速度面に懸念があるのは確かです。

私の環境でも計測比較をしてみました。

```
time echo "外国人参政権が認可された。私もあさって日曜最終日。" | mecab

echo   0.00s user 0.00s system 26% cpu 0.005 total
mecab  0.00s user 0.00s system 49% cpu 0.007 total
```

```
time echo "外国人参政権が認可された。私もあさって日曜最終日。" | jumanpp

echo   0.00s user 0.00s system 31% cpu 0.004 total
jumanpp  0.14s user 0.35s system 53% cpu 0.931 total
```

Mecabと比較して3桁も違う数値が出てしまいました。

この要因は設計云々ではなく、モデル読み込みに時間がかかっているため（らしいです。某所よりの話）
つまり、RNN言語モデルを利用する上で、仕方がないことではあります。

では、どうすればいいのでしょうか？


## サーバーモードを利用しよう！

解決策は簡単で、「サーバースクリプトを使う」以上！です。

実はこれ、[ver.1.0.1 マニュアル](http://lotus.kuee.kyoto-u.ac.jp/nl-resource/jumanpp/jumanpp-manual-1.01.pdf)にきちんと書いてあります。
5ページを参照してください。

Juman++のtarに__同封の__Rubyスクリプトを利用して、サーバーモードとして起動しっぱなしにしておきます。

マニュアルによると、

```
$ ruby script/server.rb --cmd "jumanpp -B 5" --host host.name --port 1234
```

でサーバー起動。クライアントして呼び出すには

```
echo "ケーキを食べる" | ruby script/client.rb --host host.name --port 1234
```

です。

では、サーバーモードを利用すると時間はどれくらいの短縮になるのでしょうか？

```
time echo "外国人参政権が認可された。私もあさって日曜最終日。" | ruby client.rb --host localhost

echo   0.00s user 0.00s system 21% cpu 0.006 total
ruby client.rb --host localhost  0.04s user 0.01s system 47% cpu 0.092 total
```

約10分の1の時間になりました!スゴイですね！
ちなみに、ネットワーク越しのJuman++だと、どうなるのでしょうか？
ローカルネットワーク内に存在するサーバーマシンにJuman++サーバーを起動し、計測してみました。

```
time echo "外国人参政権が認可された。私もあさって日曜最終日。" | ruby client.rb --host sever.hogehoge

echo   0.00s user 0.00s system 22% cpu 0.005 total
ruby client.rb --host sever.hogehoge 0.03s user 0.01s system 26% cpu 0.167 total
```

。。。。まあ、ネットワークレスポンスの考慮すると、こんなものでしょうか。
とにかく、サーバーモードを利用すると、ボトルネックは解決できることがわかったわけです。

__みなさん、Juman++はサーバーモードで利用しましょう__


# PythonからJuman++のサーバーモードを利用する

先のクライアントスクリプトはRubyで記述されています。
なので、Rubyの人はそのまま使えばいいんじゃないっすかねー（鼻くそほじほじ）

ただ、私はPythonを常用する人なので、やっぱりPythonから呼び出ししたいわけです。
（`client.rb`をPythonにして使いたい場合は、下の方にコードをはっつけておくので、そっちを見てください）
公式で[pyknp](http://nlp.ist.i.kyoto-u.ac.jp/index.php?PyKNP)というPythonパッケージが公開されていますが、実はjuman++用にはsubprocess呼び出ししか用意されていません。(pyknp-0.3での話)
これでは、サーバーモードの恩恵を受けることができないわけです。


私は[JapaneseTokenizers](https://github.com/Kensuke-Mitsuzawa/JapaneseTokenizers)というPythonパッケージを公開しています。
このPythonパッケージに組み込んじゃいました。

Python2xとPython3xの両方で利用可能です。

## できること

- 1行で形態素分割結果をリスト取得
- 1行で形態素分割 -> 品詞フィルタリング -> ストップワード除去 -> リスト化
- Mecab, Juman, Juman++, Kyteaを同じ記法で呼び出し可能


## インストール方法

1. Mecab, Juman, Juman++をインストールする。[このREADME](https://github.com/Kensuke-Mitsuzawa/JapaneseTokenizers#setting-up)を見てください。
2. Juman++をサーバーモードで起動する。Juman++同封の`server.rb`を使ってください。
3. `pip install JapaneseTokenizer`

でおしまいです。

## 使い方

Juman++をサーバーモードで呼び出すには1行で済みます。

```python3
>>> from JapaneseTokenizer import JumanppWrapper
>>> sentence = 'テヘラン（ペルシア語: تهران  ; Tehrān Tehran.ogg 発音[ヘルプ/ファイル]/teɦˈrɔːn/、英語:Tehran）は、西アジア、イランの首都でありかつテヘラン州の州都。人口12,223,598人。都市圏人口は13,413,348人に達する。'
>>> list_result = JumanppWrapper(server='localhost', port=12000).tokenize(sentence, return_list=True)
>>> print(list_result)
['テヘラン', 'ペルシア', '語', '発音', 'ヘルプ', 'ファイル', '英語', 'Ｔｅｈｒａｎ', '西', 'アジア', 'イラン', '首都', 'テヘラン', '州都', '人口', '１２，２２３，５９８', '都市', '圏', '人口', '１３，４１３，３４８']
```

品詞で形態素選択をするには、選択したい品詞を `List[Tuple[str]]` で渡してやります。
Juman++の品詞体系は[このページ](http://www.unixuser.org/~euske/doc/postag/)を見てください。

```python3
>>> from JapaneseTokenizer import JumanppWrapper
>>> sentence = 'テヘラン（ペルシア語: تهران  ; Tehrān Tehran.ogg 発音[ヘルプ/ファイル]/teɦˈrɔːn/、英語:Tehran）は、西アジア、イランの首都でありかつテヘラン 州の州都。人口12,223,598人。都市圏人口は13,413,348人に達する。'
>>> pos_condition = [('名詞', '地名')]
>>> JumanppWrapper(server='localhost', port=12000).tokenize(sentence, return_list=False).filter(pos_condition=pos_condition).convert_list_object()
['テヘラン', 'アジア', 'イラン', 'テヘラン']
```

他にも品詞情報、表層系、その他、Juman++が出力する情報をまとめて獲得もできます。

詳しくは[examples.py](https://github.com/Kensuke-Mitsuzawa/JapaneseTokenizers/blob/master/examples/examples.py)を参照ください。

## [前回の記事](http://qiita.com/Kensuke-Mitsuzawa/items/1d2837b374152c9758c9)からの改良点

- Juman++を利用できるようにした
- Juman serverモードで発生するバグを解消
- 1行で品詞フィルタリングまでを完結させるシンタックスシュガーの導入