Go to list of users who liked
Share on X(Twitter)
Share on Facebook
More than 5 years have passed since last update.
PythonでのMeCabを速くするtips
ちゃお...Python Advent Calendar 2015 18日目の記事です...
Pythonといったらデータサイエンスに強いし、データサイエンスといったら形態素解析が必要になることがあるし、形態素解析といったらMeCabだし――ということで、今回はPythonでのMeCabの処理を少しでも速くする豆知識を共有したいと思います!
parseToNodeを捨てよ parseを使おう
MeCabの解析結果を得るにはparseとparseToNodeの2つのメソッドがあります。
わたしはもっぱらparseToNode使ってたのですが、なんか遅いなーって思って、本当に遅いのか確かめるために処理時間測ってみました。現実的な設定でやった方が実用的だと思ったので、今回は夢野久作のドグラマグラから名詞を抽出することにします。
コード
importMeCabtagger=MeCab.Tagger('-d /usr/local/lib/mecab/dic/ipadic')defpreprocessing(sentence):returnsentence.rstrip()defextract_noun_by_parse(path):withopen(path)asfd:nouns=[]forsentenceinmap(preprocessing,fd):forchunkintagger.parse(sentence).splitlines()[:-1]:(surface,feature)=chunk.split('\t')iffeature.startswith('名詞'):nouns.append(surface)returnnounsdefextract_noun_by_parsetonode(path):withopen(path)asfd:nouns=[]forsentenceinmap(preprocessing,fd):node=tagger.parseToNode(sentence)whilenode:ifnode.feature.startswith('名詞'):nouns.append(node.surface)node=node.nextreturnnouns結果
- Py2: Python 2.7.10
- Py3: Python 3.5.0
- ASIS:MeCabリポジトリそのまま
- NEW SWIG: SWIG 3.0.7でMeCabリポジトリのラッパーをつくりなおした場合
- mecab-python3: PyPIにあるmecab-python3
| parse | parseToNode | |
|---|---|---|
| Py2, ASIS | 531 ms | 642 ms |
| Py2, NEW SWIG | 604 ms | 630 ms |
| Py2, mecab-python3 | 547 ms | 652 ms |
| Py3, ASIS | 673 ms | 1610 ms |
| Py3, NEW SWIG | 684 ms | 1640 ms |
| Py3, mecab-python3 | 654 ms | 1610 ms |
Python 3のparseToNodeだけ明らかに遅い......!?
原因
詳細なプロファイルを取ってみると、
1362455 function calls in 2.177 seconds Ordered by: internal time ncalls tottime percall cumtime percall filename:lineno(function) 1 1.162 1.162 2.174 2.174 <ipython-input-5-e12642d808e1>:1(extract_noun_by_parsetonode) 313688 0.372 0.000 0.372 0.000 MeCab.py:35(_swig_setattr_nondynamic) 3416 0.251 0.000 0.262 0.000 {built-in method _MeCab.Tagger_parseToNode} 313688 0.131 0.000 0.629 0.000 MeCab.py:126(<lambda>) 313688 0.126 0.000 0.498 0.000 MeCab.py:48(_swig_setattr)以下省略...parseToNodeは形態素ごとにNodeインスタンスの生成時にNodeの全ての要素をあらかじめ取得するので、そのときのオーバーヘッドがだいぶ大きいようです。
結論
Python 3でMeCab使うときはparseToNodeじゃなくてparseを使いましょう (バッドノウハウだけど......)
joblibで並列化
さらに速くしたいとなったら並列化が頭をよぎりますよね。でも並列化っていうととっつきにくいイメージがあります。途中で失敗したときにうまくプロセスが死んでくれなかったり、途中でやめたいって思ってもKeyBoardInterruptが効かなかったり、データの分割数どれくらいがわからなかったり、今どれくらい処理してるのか経過わかんなかったり。。。
そこで、scikit-learnも採用しているjoblibというものを使います。joblibはいろいろできるんですけど、ここでは並列処理のjoblib.Parralelに着目します。これはざっくりいうとPython標準のmultiprocessingやthreadingを使いやすくしたものです。たとえば、各プロセスにどれくらいの粒度でデータを分割して渡すかを自動で調整したり、KeyBoardInterruptでちゃんと終わってくれたり、途中経過を標準出力とかに流したりできます。かゆいところに手が届く!これなら並列化こわくない!💪😤
比較
並列化するとどれくらい速くなるか比較するために、またドグラマグラから名詞抽出しました。ボリューム大きめのテキストでやった方がわかりやすいので今回は長さを10倍してます。
コード:https://gist.github.com/ikegami-yukino/68a741ef854de68871cc#file-asis_vs_joblib-ipynb
defextract_noun(sentence):nouns=[]sentence=preprocessing(sentence)forchunkintagger.parse(sentence).splitlines()[:-1]:(surface,feature)=chunk.split('\t')iffeature.startswith('名詞'):nouns.append(surface)returnnouns# 並列なし%timeitnouns=[extract_noun(sentence)forsentenceindoc.splitlines()]# 並列ありfromjoblibimportParallel,delayed%timeitnouns=Parallel(n_jobs=-1,pre_dispatch='all')(delayed(extract_noun)(sentence)forsentenceindoc.splitlines())結果
並列なし1 loops, best of 3: 6.78 s per loop並列あり1 loops, best of 3: 3.72 s per loop1.8倍くらい速くなりました!
ついでに並列化なしのparseToNodeで同じデータを処理させると
1 loops, best of 3: 1min 18s per loop並列ありのparseと比べて20倍遅いです😱
総括
Python3でMeCabを使うときはparseToNodeを使うとオーバーヘッドが大きいのでparseを使った方が速く処理できます。さらに並列化するともっと処理時間が短くなります。ワーストケース (Python 3で並列なしでparseToNodeで名詞抽出する場合) 78秒かかる処理が、今回紹介したやり方では3.72秒となり、およそ20倍の差がつきました。小規模のテキストを扱い場合なら誤差の内かもしれませんが、ちょっとした規模の量を処理するときなんかに恩恵を受けると思います^^
Register as a new user and use Qiita more conveniently
- You get articles that match your needs
- You can efficiently read back useful information
- You can use dark theme