下記のような 係り受け解析 ができるオープンソースライブラリ3種類の処理速度比較しました。
$ 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使用せず
実際のソースコードは下記になります。
実装
題材
青空文庫の「走れメロス」を取得し、1文を1要素とするリストを定義しました。データの取得・処理にはこちらの記事のソースコードを参考にしました。
このリストにある460文をすべて処理するのにかかる時間をIPythonの%%time
マジックコマンドで計測しました。
from pprint import pprint
pprint(texts[:3], width=1)
#=> ['メロスは激怒した',
#=> '必ず、かの邪智暴虐の王を除かなければならぬと決意した',
#=> 'メロスには政治がわからぬ']
len(texts)
#=> 460
参考記録
参考記録として、list()
で1文字ずつのトークンに分割するだけの処理を計測してみます。
>>> list("毎日焼肉定食を食べます")
['毎', '日', '焼', '肉', '定', '食', 'を', '食', 'べ', 'ま', 'す']
%%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の結果も計測しておきます。
from janome.tokenizer import Tokenizer
tokenizer = Tokenizer()
[ [ str(token) for token in tokenizer.tokenize("毎日焼肉定食を食べます")] ]
#=> [['毎日\t名詞,副詞可能,*,*,*,*,毎日,マイニチ,マイニチ',
#=> '焼肉\t名詞,一般,*,*,*,*,焼肉,ヤキニク,ヤキニク',
#=> '定食\t名詞,一般,*,*,*,*,定食,テイショク,テイショク',
#=> 'を\t助詞,格助詞,一般,*,*,*,を,ヲ,ヲ',
#=> '食べ\t動詞,自立,*,*,一段,連用形,食べる,タベ,タベ',
#=> 'ます\t助動詞,*,*,*,特殊・マス,基本形,ます,マス,マス']]
%%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
GiNZA は、リクルートグループの研究開発機関Megagon Labsが公開しているライブラリです。2019年に初回リリースされた比較的新しいライブラリで、インストール方法やAPIもモダンに感じます。
本稿執筆時点での最新版は、2021年12月にリリースされた5.1.0です。
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)
analyze_ginza("毎日焼肉定食を食べます")
#=> {毎日: 食べ, 定食: 食べ}
測定
%%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という別のモデルがあります。こちらでの時間を計測してみます。
nlp = spacy.load("ja_ginza_electra")
%%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
KNP は、京都大学の黒橋・褚・村脇研究室が公開しているオープンソースのライブラリです。10年以上にわたり開発されているらしい、歴史のあるライブラリです。
本稿執筆時点での最新版は2020年4月にリリースされた4.20です。
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
analyze_knp("毎日焼肉定食を食べます")
#=> {'毎日': '食べます', '焼肉定食を': '食べます'}
測定
%%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
CaboCha は奈良先端科学技術大学院大学 松本研究室が公開しているライブラリです。本稿執筆時点での最新版は2015年1月にリリースされた0.69です。
なおCaboCha自体はBSDライセンスのオープンソースソフトウェアですが、今回利用するCaboCha標準付属のモデルは株式会社毎日新聞社より提供されたデータで学習したもののようで、オープンソースライセンスが適用されません。
実装にはこちらの記事を参考にしました。APIが比較的プリミティブで、すこし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("毎日焼肉定食を食べます")
analyze_cabocha("毎日焼肉定食を食べます")
#=> {'毎日': '食べます', '焼肉定食を': '食べます'}
測定
%%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文を処理できると謳っている超高速なライブラリです。
しかし以下の手順で環境構築を試みたもののうまくいかなかったため、今回実験することはできませんでした。
$ 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