(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)
덧붙임1MeCab自体の問題っぽいので、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 해둘까나?)
댓글 없음:
댓글 쓰기