2016년 8월 31일 수요일

MeCabをPython3から使う(続報)


MeCabをPython3から使う(続報)
(DEC 20TH, 2015 1:03 AM)
Mecab를 Python3에서 사용 (이어지는 소식/게시물)
(2015-12-20 01:03) - //글쓴이가 야행성임을 알 수 있다.

Python3からMeCabを扱おうとして挫折していたのですが (MeCabをPython3から使う(中間報告))、 改めて調査して、上手くいかなかった原因が分かったのでご報告します。
이전 글에서 Python3에서 Mecab을 다뤄보려고 하다가 좌절했었지만, (관련게시글: Mecab을 Python3에서 사용 (중간 보고)), 하다보니 문제의 원인을 찾아냈기에 알려드립니다. //의역


おさらい

당시상황

Python3で以下のようにMeCabを使おうとすると
Python3에서 아래처럼 Mecab를 사용하려 하면,


1
2
3
4
5
6
7
import MeCab
tagger = MeCab.Tagger('')
text = u'MeCabで遊んでみよう!'
node = tagger.parseToNode(text)
while node:
    print(node.surface + '\t' + node.feature)
    node = node.next




surfaceが全く読み取れないという現象に遭遇していました。
surface를 전혀 읽지 못하는 현상이 발생하고 있었습니다.


1
2
3
4
5
6
7
8
9
BOS/EOS,*,*,*,*,*,*,*,*
名詞,一般,*,*,*,*,*
助詞,格助詞,一般,*,*,*,で,デ,デ
動詞,自立,*,*,五段・バ行,連用タ接続,遊ぶ,アソン,アソン
助詞,接続助詞,*,*,*,*,で,デ,デ
Traceback (most recent call last):
  File "m.py", line 10, in <module>
  print( node.surface + '\t' + node.feature )
  UnicodeDecodeError: 'utf-8' codec can't decode byte 0xa3 in position 1: invalid start byte



解決策

해결책


詳しい原因なんてどうでもいいからMeCabを使いたい人向けに、最初に解決方法を書いておきます。 以下のように本当に解析したい対象を解析する前に、一度parseをしておけばOKです。
자세한 원인은 아무래도 상관 없으니까, Mecab을 사용하고 싶은 사람을 위한 첫 번째 해결 방법을 기록해 둡니다. 아래과 같이 정말 분석하고 싶은 대상을 분석하기 전에 한 번 parse을 해두면 OK입니다. //즉, tagger.parse('') 이 부분을 공(空)으로 한줄 넣어주면 된다는 의미.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import MeCab
tagger = MeCab.Tagger('')

tagger.parse('') # これ重要!!!!

text = u'MeCabで遊んでみよう!'
node = tagger.parseToNode(text)
while node:
    print(node.surface + '\t' + node.feature)
    node = node.next



解析結果を全く使わずに捨てていて無駄のように思えますが、この一行が重要です! これを入れると以下のように正常に解析ができます。
분석결과를 전혀 사용치 않고 버리는 낭비처럼 생각되지만, 이 한줄이 중요합니다! 이것을 넣으면 아래과 같이 올바르게 해석 할 수 있습니다.


1
2
3
4
5
6
7
8
9
    BOS/EOS,*,*,*,*,*,*,*,*
MeCab   名詞,一般,*,*,*,*,*
      助詞,格助詞,一般,*,*,*,で,デ,デ
遊ん    動詞,自立,*,*,五段・バ行,連用タ接続,遊ぶ,アソン,アソン
      助詞,接続助詞,*,*,*,*,で,デ,デ
みよ    動詞,非自立,*,*,一段,未然ウ接続,みる,ミヨ,ミヨ
      助動詞,*,*,*,不変化型,基本形,う,ウ,ウ
!       名詞,サ変接続,*,*,*,*,*
    BOS/EOS,*,*,*,*,*,*,*,*



解析を行うメソッドであればparseToNodeでも構いません。
분석을 실행하는 메소드라면, parseToNode라도 상관 없습니다.



原因

원인


結果が壊れる直接的な原因はMeCabをPythonから使う注意点とかで紹介したように、 解析対象の文字列がPythonの管理下から外れGCされてしまったからです。 高速化のために余計なメモリーアロケーションを避けており、メモリ管理は利用者の責任というわけです。
결과가 잘못 나오는 직접적 원인은 "관련게시글: Mecab를 Python에서 사용 주의점 등"에서 소개 한 바와 같이, 분석 대상 문자열이 Python의 관리에서 벗어나 GC되어 버렸기 때문입니다. 고속화를 위해 불필요한 Memory allocation을 피하고 있으며 (이러한) 메모리 관리는 이용자의 책임이라는 것입니다.

なんとかならないものかと、よくソースコードを追ってみるとMECAB_ALLOCATE_SENTENCEというフラグをONにすれば メモリ管理をMeCabに任せることができるということがわかりました。 これはTaggerを作るときの引数から指定でき、-Cもしくは--allocate-sentenceというオプションがこのフラグに対応します。 これを有効にすれば解決だ!と思ったのですが、実は各種言語バインディングからMeCabを利用する場合はデフォルトで有効になっています
어떻게 든 방법이 없을까 면밀히 소스코드를 쫓아 보니 MECAB_ALLOCATE_SENTENCE 플래그를 ON으로하면 메모리 관리를 MeCab에 맡길 수 있다는 것을 알 수 있었습니다. 이것은 Tagger를 만들 때 인수에서 지정할 수 있으며 -C 또는 --allocate-sentence라는 옵션이 플래그를 지원합니다. 이걸 활성화하면 해결이다! 라고 생각했는데, 실은 각종 언어 바인딩에서 MeCab를 이용하는 경우는 기본적으로 활성화되어 있습니다.

何故だ・・・とさらにコードを追ってみるとparseToNodeの実装が以下のようになっていることがわかりました。
왜지? 그러면서 더 코드를 쫓아 보면, parseToNode의 구현은 다음과 같이 되어있는 것을 알 수 있었습니다.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const Node *TaggerImpl::parseToNode(const char *str, size_t len) {
  Lattice *lattice = mutable_lattice();
  lattice->set_sentence(str, len); // このなかでMECAB_ALLOCATE_SENTENCEフラグが立ってるか確認している
  initRequestType();               // このなかでMECAB_ALLOCATE_SENTENCEフラグを立ててる
  if (!parse(lattice)) {
    set_what(lattice->what());
    return 0;
  }
  return lattice->bos_node();
}



MECAB_ALLOCATE_SENTENCEフラグを立てる前に、立っているかを確認しています。

MECAB_ALLOCATE_SENTENCE 플래그 앞에 (정의되어) 있는지 확인해보았습니다.

解析対象の文字列を渡す前にinitRequestType()を呼んでMECAB_ALLOCATE_SENTENCEフラグを立てれば良いのですが、 残念ながらinitRequestType()もmutable_lattice()もprivateなメソッドなのでPythonから直接呼ぶことはできません。 そこでparse()を使ってinitRequestType()を間接的に呼び出せば問題解決というわけです。
분석 대상의 문자열을 전달받기 전에 initRequestType()를 불러 MECAB_ALLOCATE_SENTENCE 플래그를 (정의해) 주면 되겠다 싶지만, 불행히도 initRequestType()도 mutable_lattice()도 private 메서드이기에 Python에서 직접 부를 수 없습니다. 그래서 parse()를 사용하여 initRequestType()를 간접적으로 호출하면 문제 해결이라는 것입니다. //글쓴이의 잔머리에 탄복한다.ㅋ




別解

다른 해결안


mutable_lattice()は触れなくても、自分で作ったlatticeなら自由にいじれるので、 以下のようにlatticeをPython側で作るのも手ですね。
mutable_lattice()를 굳이 건드리지 않는대도, 스스로 만든 lattice이라면 자유롭게 유지됩니다. 아래과 같이 lattice을 Python측에서 만드는 방법도 있습니다만 손이 많이가네요.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
lattice = MeCab.Lattice()
import MeCab
tagger = MeCab.Tagger('')
lattice = MeCab.Lattice()
text = u'MeCabで遊んでみよう!'
lattice.set_sentence(text)
tagger.parse(lattice)
node = lattice.bos_node()
while node:
    print(node.surface+"\t"+node.feature)
    node = node.next



いずれの方法でもnodeからlatticeやtaggerへの参照がない(実際はあるけどPythonはそのことを知らない)ので、 解析結果を読んでいる最中にlatticeやtaggerがGCで回収されないよう注意しましょう。
어떤 방법으로도 node에서 lattice과 tagger에 대한 참조가 없기 때문에 (사실 있지만 Python은 모르는), 분석 결과를 읽는 동안 lattice과 tagger가 GC에서 회수되지 않도록 주의하여야 합니다.



追記(2015-12-20)

덧붙임1


MeCab自体の問題っぽいので、MeCabにpullreq送って直してもらおうとソースいじってたけど、すでにpatchあったpatchを取り込んだブランチを用意したので、 GCされて困っている方はgit cloneしてお試し下さい。
Mecab의 문제 같아보여서, Mecab에 pullreq를 보내 알리려 소스를 확인하고 했지만, 이 문제는 이미 patch되어 있었습니다. patch를 끌어들은 브런치를 준비했기 때문에 GC로 낭패를 보시고 계신 분은 git clone에서 직접 가져다가 써보세요.


追記その2(2016-02-08)

덧붙임2


なんとか取り込んでもらおうとPull Requestにしてマージしてもらいました。 まだリリースはされていませんが、2016-02-08現在のmasterブランチをビルドすれば、ガーベージコレクションの問題はなくなるはずです。 Twitterで作者に聞いてくれた人がいたみたいで、僕のpulllreq以外もたくさんマージされたようです。 よかったよかった。リリースを心待ちにしています。 (が、Python3対応のpullreqはマージされていない・・・一応試してみてから+1しておこうかな)

어떻게든 넣어보려고 Pull Request하고 병합해 봤습니다. 아직 출시되지 않지만 2016-02-08 현재의 master 브랜치를 빌드하면 가비지 컬렉션의 문제는 없게 될 것입니다. Twitter에서 저작자에게 물어봐주었다는 사람이 있다는 듯 해서, 내 pulllreq 이외도 많이 병합 된 것 같습니다. (요갓따ㅋ 요갓따ㅋ). 릴리즈를 손꼽 아 기다리고 있습니다. (하지만, Python3 지원 pullreq는 병합되지 않은;; 일단 시도했다는데에 +1 해둘까나?)






댓글 없음:

댓글 쓰기