[janome開発日誌] 省メモリ対応をした janome 0.3.1 をリリースしました

1年(以上)ぶりに,janome の新しいバージョンをリリースしました!

--------

【追記 2017.07.05】

Windows でインストール失敗する不具合を修正した v0.3.2 をリリースしました。ついでに, AppVeyor というサービスで Windows 環境での CI を回せるようにしました。

--------

API 変更はほぼないですが,解析結果に影響のある修正が入っているため,マイナーバージョンをアップグレード(0.2 => 0.3)しています。

PyPI: https://pypi.python.org/pypi/Janome/0.3.1

v0.3.0 も一応あるのですが…,v0.3.1 で色々改善しているので,v0.3.1 を使ってください :)

0.2 系からの主な変更点

① 大きなドキュメントを解析したときのメモリ消費を改善

janome 0.2 系までは,tokenize() メソッドに与えられたテキストを一気に解析していたため,文書サイズが大きくなるほどラティス(最適なパスを計算するための内部データ構造)が肥大化し,大量にメモリを消費する問題がありました。

0.3 では,大きなテキストをまとめて解析するのではなく,部分的に少しずつ解析していくことで,ラティスの肥大化を抑え,メモリ消費を大幅に削減しています。

【注】この修正の影響で,0.2 系とは解析結果が微妙に異なってくる可能性があります。なるべく句読点がある場所で区切るなど,工夫していますが,0.2 系と比べてあまりにおかしい結果になる場合は,教えてください!

併せて,generator を使って部分解析結果を順に返すモード(言いにくいので「ストリーミングモード」と名前をつけました)を追加しました。tokenize() メソッドに stream=True オプション(下記スニペット参照)をつけると,ストリーミングモードになります。ストリーミングモードでは,next() が呼ばれる都度,それまでの解析結果がメモリから破棄されるので,メモリ消費量は一定となります。

t = Tokenizer()
with open('very_large_text.txt') as f:
txt = f.read()
for token in t.tokenize(txt, stream=True):
print(token)

②「分かち書き」モードを追加

Token オブジェクトには品詞詳細や活用形,読みなど,色々な情報が含まれますが,そういうのは不要で「単語分割だけしたい」という時があるかもしれません。追加情報をまるっと省いて,表層形だけを返す分かち書きモードを追加しました。tokenize() メソッドに wakati=True オプション(下記スニペット参照)をつけると分かち書きモードになります。戻り値は Token ではなく string のリストです。

>>> t = Tokenizer()
>>> tokens = t.tokenize(u'分かち書きモードがつきました!', wakati=True)
>>> tokens
['分かち書き', 'モード', 'が', 'つき', 'まし', 'た', '!']

分かち書きモードしか使わない場合は,Tokenizer 初期化時に wakati=True を与えてください。このオプションつきで初期化すると,品詞詳細などの不要なデータをロードしないため,メモリ消費が抑えられます。

t = Tokenizer(wakati=True)

なお,このオプションをつけて Tokenizer を初期化すると,tokenize() メソッドは分かち書きモードでしか動作しなくなります。

③ API リファレンス

細かい話ですが,docstring の整理を始めています。とりあえず最低限必要なコメントを書いたので,sphinx-apidoc で API リファレンス にして公開しました。

変更があった箇所について,ドキュメントも全体的にアップデートしているので,詳しくは下記も参照してください。

http://mocobeta.github.io/janome/

メモリ消費量の比較

①,②の修正でメモリ消費がどのくらい改善したのか,簡単に測定しました。参考までに。。。

【環境】

【測定結果】

あまり厳密ではないですが,解析実行時の python プロセスの物理メモリ使用量(RES)を top コマンドで適当に目視してメモりました。

0.2.8 は,解析開始時は 300 MB くらいなのですが,どんどん使用メモリが大きくなって最終的に 900MB 近くまで達してます(怖)。作り上,もっと大きいドキュメントを与えると,さらに増えます(怖)。

0.3.1 では,消費量の増え方が緩やかになり,ストリームモードだとほぼ一定の使用量をキープ。分かち書きモードで OK なら,200MB 以下まで削減されました。

+-----------------------------+--------------+---------------+
| version(mode) | 解析開始時(*1) | 解析完了時(*2) |
+=============================+==============+===============+
| 0.2.8 | 290MB | 850MB |
+-----------------------------+--------------+---------------+
| 0.3.1 | 270MB | 320MB |
+-----------------------------+--------------+---------------+
| 0.3.1(stream) (*3) | 270MB | 270MB |
+-----------------------------+--------------+---------------+
| 0.3.1(stream & wakati) (*4) | 190MB | 190MB |
+-----------------------------+--------------+---------------+

(*1) tokenize() メソッド呼び出し直後

(*2) tokenize() メソッド完了直前

(*3) 呼び出し箇所抜粋

t = Tokenizer()
for token in t.tokenize(txt, stream=True):
print(token)

(*4) 呼び出し箇所抜粋

t = Tokenizer(wakati = True)
for token in t.tokenize(txt, stream=True, wakati=True):
print(token)

今後とか

0.3 系では,リソース周りや性能改善みたいなのをちょこちょこ入れていければと思っています。まずは積年の問題だったメモリリーク問題を改善できたので,他にも高速化とか辞書軽量化とか,運用に役立つ改善をやっていこうかなと。

PR 随時お待ちしています ( ̄ー ̄)bグッ!