日本語係り受け解析Pythonライブラリの速度比較

下記のような **係り受け解析** ができるオープンソースライブラリ3種類の処理速度比較しました。

```sh
$ cat input.txt 
毎日焼肉定食を食べます

$ juman  < input.txt | knp
# S-ID:1 KNP:4.20-CF1.1 DATE:2022/03/13 SCORE:-12.92395
        毎日─────┐　<体言>
焼肉─────┐　　　 │　<体言>
      定食を─────┤　<体言><Wikipediaエントリ:焼き肉定食>
            食べます<用言:動><格解析結果:ガ/-;ヲ/定食;ニ/-;ト/-;デ/-;カラ/-;ヘ/-;時間/毎日;ノ/->
EOS
```

先に結論を書くと、速度だけならば CaboCha > GiNZA > KNP の順番で速いという結果になりました。

## 環境
- Windows 11 WSL2 Debian Bullseye
- Docker Python:3.9-slim
- Ryzen 5 3600
- 32GB RAM
- GPU使用せず

実際のソースコードは下記になります。

https://github.com/s2terminal/nlp_dependency_analysis

## 実装
### 題材
[青空文庫の「走れメロス」](https://www.aozora.gr.jp/cards/000035/card1567.html)を取得し、1文を1要素とするリストを定義しました。データの取得・処理には[こちら](https://qiita.com/makaishi2/items/63b7986f6da93dc55edd)の記事のソースコードを参考にしました。
このリストにある460文をすべて処理するのにかかる時間を[IPythonの`%%time`マジックコマンド](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit)で計測しました。


```python
from pprint import pprint
pprint(texts[:3], width=1)
#=> ['メロスは激怒した',
#=>  '必ず、かの邪智暴虐の王を除かなければならぬと決意した',
#=>  'メロスには政治がわからぬ']
```

```python
len(texts)
#=> 460
```

### 参考記録
参考記録として、`list()`で1文字ずつのトークンに分割するだけの処理を計測してみます。

```
>>> list("毎日焼肉定食を食べます")
['毎', '日', '焼', '肉', '定', '食', 'を', '食', 'べ', 'ま', 'す']
```


```python
%%timeit
result = [ lambda t: list(t) for t in texts ]
#=> 39.5 µs ± 277 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
```

また、係り受け解析を行わない単なる形態素解析であるJanomeの結果も計測しておきます。

```python
from janome.tokenizer import Tokenizer
tokenizer = Tokenizer()

[ [ str(token) for token in tokenizer.tokenize("毎日焼肉定食を食べます")] ]
#=> [['毎日\t名詞,副詞可能,*,*,*,*,毎日,マイニチ,マイニチ',
#=>   '焼肉\t名詞,一般,*,*,*,*,焼肉,ヤキニク,ヤキニク',
#=>   '定食\t名詞,一般,*,*,*,*,定食,テイショク,テイショク',
#=>   'を\t助詞,格助詞,一般,*,*,*,を,ヲ,ヲ',
#=>   '食べ\t動詞,自立,*,*,一段,連用形,食べる,タベ,タベ',
#=>   'ます\t助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス']]
```

```python
%%timeit
results = [ [ str(token) for token in tokenizer.tokenize(text)] for text in texts ]
#=> 479 ms ± 6.12 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
```


## 速度比較
### GiNZA
https://megagonlabs.github.io/ginza/

**GiNZA** は、リクルートグループの研究開発機関Megagon Labsが公開しているライブラリです。2019年に初回リリースされた比較的新しいライブラリで、インストール方法やAPIもモダンに感じます。
本稿執筆時点での最新版は、2021年12月にリリースされた5.1.0です。

```python
import spacy
import ginza
nlp = spacy.load("ja_ginza")

def analyze_ginza(text: str):
    ret = {}
    doc = nlp(text)
    for sentence in doc.sents:
        for token in ginza.bunsetu_head_tokens(sentence):
            for _relation, sub_phrase in ginza.sub_phrases(token):
                ret[str(sub_phrase)] = str(token)
    return dict(ret)
```

```python
analyze_ginza("毎日焼肉定食を食べます")
#=> {毎日: 食べ, 定食: 食べ}
```

測定

```python
%%timeit
result = [ analyze_ginza(t) for t in texts ]
#=> 4.37 s ± 22.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
```

なおGiNZAには、Transformersを利用することで実行速度が下がるものの精度を向上させたelectraという別のモデルがあります。こちらでの時間を計測してみます。

```python
nlp = spacy.load("ja_ginza_electra")
```

```python
%%timeit
result = [ analyze_ginza(t) for t in texts ]
#=> 28.5 s ± 668 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
```

結構遅くなりました。

### KNP
https://nlp.ist.i.kyoto-u.ac.jp/?KNP

**KNP** は、京都大学の黒橋・褚・村脇研究室が公開しているオープンソースのライブラリです。10年以上にわたり開発されているらしい、歴史のあるライブラリです。
本稿執筆時点での最新版は2020年4月にリリースされた4.20です。

```python
from pyknp import KNP

knp = KNP()

def analyze_knp(text: str):
    ret = {}
    result = knp.parse(text)
    for bnst in result.bnst_list():
        parent = bnst.parent
        if parent is not None:
            ret[bnst.midasi] = parent.midasi
    return ret
```

```python
analyze_knp("毎日焼肉定食を食べます")
#=> {'毎日': '食べます', '焼肉定食を': '食べます'}
```

測定

```python
%%timeit
result = [ analyze_knp(t) for t in texts ]
#=> 1min 3s ± 500 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
```

### CaboCha
https://taku910.github.io/cabocha/

**CaboCha** は奈良先端科学技術大学院大学 松本研究室が公開しているライブラリです。本稿執筆時点での最新版は2015年1月にリリースされた0.69です。
なおCaboCha自体はBSDライセンスのオープンソースソフトウェアですが、今回利用するCaboCha標準付属のモデルは株式会社毎日新聞社より提供されたデータで学習したもののようで、オープンソースライセンスが適用されません。


実装には[こちら](https://yu-nix.com/blog/2020/11/11/python-cabocha/)の記事を参考にしました。APIが比較的プリミティブで、すこしPythonコードの量が多くなります。

```python
def analyze_cabocha(text: str):
    ret = {}
    parser = CaboCha.Parser()
    tree = parser.parse(text)
    for chunk_num in range(tree.chunk_size()):
        token = tree.token(chunk_num)
        if not token.chunk:
            continue
        from_chunk = token.chunk
        if from_chunk.link < 0:
            continue
        from_surface = get_surface(tree, from_chunk)

        to_chunk = tree.chunk(from_chunk.link)
        to_surface = get_surface(tree, to_chunk)
        ret[from_surface] = to_surface
    return ret

analyze_cabocha("毎日焼肉定食を食べます")
```

```python
analyze_cabocha("毎日焼肉定食を食べます")
#=> {'毎日': '食べます', '焼肉定食を': '食べます'}
```

測定

```python
%%timeit
result = [ analyze_cabocha(t) for t in texts ]
#=> 653 ms ± 34.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
```

## まとめ

実装|秒|文/秒
--|--:|--:
GiNZA|4.37|105
GiNZA (electra)|28.5|16
KNP|63|7
CaboCha|0.653|704
(参考) Janome|0.479|960
(参考) list|0.0000395|11,645,569

冒頭に書いたとおり CaboCha > GiNZA > KNP の順番となりました。特にCaboChaによる解析は、形態素解析器であるJanomeとほとんど変わらず非常に速いものになっています。

本稿では速度のみで比較しましたが、実際には求められる精度や、他に必要となる情報が取得できるかどうか等も鑑みてライブラリを選択しましょう。また、KNPやCaboChaは自分で依存関係を揃えて`make`が必要であるなど環境構築に比較的手がかかるため、手軽に使いたい場合は`pip`等でインストールするだけで動かせるGiNZAを選ぶ方が良いと思います。

### J.DepP
**J.DepP** という東京大学が公開している係り受け解析器があります。こちらは1秒間に10,000文を処理できると謳っている超高速なライブラリです。

http://www.tkl.iis.u-tokyo.ac.jp/~ynaga/jdepp/

しかし以下の手順で環境構築を試みたもののうまくいかなかったため、今回実験することはできませんでした。

<details>

```
$ docker run --interactive --tty --rm --volume $(pwd):/app --workdir /app python:2.7-slim bash

# wget http://www.tkl.iis.u-tokyo.ac.jp/~ynaga/jdepp/jdepp-2018-08-16.tar.gz
# tar zxvf jdepp-2018-08-16.tar.gz
# cd jdepp-2018-08-16

# apt-get install mecab libmecab-dev mecab-ipadic mecab-ipadic-utf8 ipadic
# mkdir -p /usr/lib/x86_64-linux-gnu/mecab/dic
# ln -s /var/lib/mecab/dic/debian/sys.dic /usr/lib/x86_64-linux-gnu/mecab/dic/ipadic

# wget --no-check-certificate http://nlp.ist.i.kyoto-u.ac.jp/kuntt/KNBC_v1.0_090925.tar.bz2
# tar -jxvf KNBC_v1.0_090925.tar.bz2

# ./configure --with-postagger=mecab --with-mecab-dict=IPA

# make model
# make install
```

</details>


## 参考文献
- [mecab, cabocha, juman, knpの処理速度とか \- Into the Horizon](https://nullnull.hatenablog.com/entry/20130202/1359777263)
- [CaboChaで始める係り受け解析 \- Qiita](https://qiita.com/nezuq/items/f481f07fc0576b38e81d#%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%AE%E3%82%B3%E3%83%84ubuntu1404--python3)