どうせ和布蕪るなら、の続き、もあるってこった (ライブラリらしい利用)

跳べない鳥はただの皇帝だ。フンボルトペンギンの「フンボルト」は動物学者。その兄は言語学者だそうな。

Tagger.parse しか使わない「ライブラリ」では、mecab.exe を使ってるのと何ら変わりはなくて。subprocess 経由で mecab.exe を使うのと比較すれば、確かに「効率はいい」けれど、ちょっと前に言った通り、ワタシの使い方だと「一ドキュメントに付きたった一回の呼び出し」で済ませちゃうわけで、「効率」のおいしさですら疑わしい、と。

mecab.exe としての利用だけだと粒度が大き過ぎて、「ライブラリとして使わないと出来ないこと」てのは結構ある。ある、んだけれど、それよりも前に、まずは「ライブラリらしい使い方」をちょっとばかしやってやろうと。

この手のものは大抵そうだが、「ライブラリとして使う」のと、実行形式として使うのとでの、プログラマからみて最もわかりやすい違いは「出力の文字列解析を要するか否か」である。標準入出力だけをインターフェイスとするならば、必然的に「出力はフォーマットされた文字列、その結果の解釈のための出力のパースが必要」となる。ライブラリとして利用する場合、最低でも「出力の文字列解析」は(普通は)いらない。まぁ見たことあるけどね、「どこまでいっても~解析が必要」というバカなライブラリ。xml 関係のライブラリで、「出力の解読に xml パーサが必須」ってバカなやつね。けどさすがにそういうのは稀。

今回は、そのおいしみを味わってやろうじゃないかと。

まずは mecab.exe と大差ないままのこれから:

 1 # -*- coding: utf-8 -*-
 2 #
 3 # これでもまだ mecab.exe と大差ない使い方。
 4 #
 5 from MeCab import Tagger
 6 
 7 t = Tagger()
 8 node = t.parseToNode(
 9     "フンボルトペンギンの「フンボルト」は動物学者。その兄は言語学者だそうな。")
10 while node:
11     print(node.feature)
12     node = node.next
実行結果
 1 BOS/EOS,*,*,*,*,*,*,*,*
 2 名詞,一般,*,*,*,*,フンボルトペンギン,フンボルトペンギン,フンボルトペンギン
 3 助詞,連体化,*,*,*,*,の,ノ,ノ
 4 記号,括弧開,*,*,*,*,「,「,「
 5 名詞,一般,*,*,*,*,フンボルト,フンボルト,フンボルト
 6 記号,括弧閉,*,*,*,*,」,」,」
 7 助詞,係助詞,*,*,*,*,は,ハ,ワ
 8 名詞,一般,*,*,*,*,動物,ドウブツ,ドーブツ
 9 名詞,一般,*,*,*,*,学者,ガクシャ,ガクシャ
10 記号,句点,*,*,*,*,。,。,。
11 連体詞,*,*,*,*,*,その,ソノ,ソノ
12 名詞,一般,*,*,*,*,兄,アニ,アニ
13 助詞,係助詞,*,*,*,*,は,ハ,ワ
14 名詞,一般,*,*,*,*,言語,ゲンゴ,ゲンゴ
15 名詞,一般,*,*,*,*,学者,ガクシャ,ガクシャ
16 助動詞,*,*,*,特殊・ダ,基本形,だ,ダ,ダ
17 名詞,接尾,助動詞語幹,*,*,*,そう,ソウ,ソー
18 助詞,終助詞,*,*,*,*,な,ナ,ナ
19 記号,句点,*,*,*,*,。,。,。
20 BOS/EOS,*,*,*,*,*,*,*,*

個別に取れるからライブラリを使ってるうまみがある:

 1 # -*- coding: utf-8 -*-
 2 #
 3 # やっと mecab.exe より細かな使い方。
 4 #
 5 from MeCab import Tagger
 6 
 7 t = Tagger()
 8 node = t.parseToNode(
 9     "フンボルトペンギンの「フンボルト」は動物学者。その兄は言語学者だそうな。")
10 while node:
11     print("{},{},{}".format(
12             node.surface,  # morpheme surface
13             node.posid,    # part-of-speech ID (IPADIC)
14             node.wcost,    # word cost
15             ))
16     node = node.next
実行結果
 1 ,0,0
 2 ・38,3617
 3 の,24,4816
 4 「,5,1146
 5 フンボルト,38,3653
 6 」,6,1161
 7 は,16,3865
 8 動物,38,655
 9 学者,38,4817
10 。,7,215
11 その,68,3869
12 兄,38,5845
13 は,16,3865
14 言語,38,3280
15 学者,38,4817
16 だ,25,6813
17 そう,54,5132
18 な,17,5934
19 。,7,215
20 ,0,0

まず、最初の「フンボルトペンギン」の表層形がオカシイのは気になるが、野良ビルドだからなのか? まぁ多少おかしくても代わりになるものがあればいいと考えるかどうか。それと、ちいと feature との距離が遠いなぁ。剥き身の id だと、スクリプトが理解不能になるからねぇ。これの読み替えのために left-id.def を読む…のは別に大変なことではないけれど…、feature が出来ているわけだから、それの実現に使った翻訳機能を曝してて欲しいもんだが、ないんかね? まぁなけりゃないで仕方ないけれど…、と唸りながら、ようやく探り当てた宝:

 1 # -*- coding: utf-8 -*-
 2 #
 3 # もっと細かい使い方。
 4 #
 5 from MeCab import Tagger
 6 
 7 # mecab.exe のコマンドラインオプションとまったく同じものを渡せる…、
 8 # というよりは、「コマンドラインオプションとまったく同じものでしか
 9 # 渡せない」というか。
10 t = Tagger.create("-F%m,%f[0],%f[1]")  # feature の出力フォーマット
11 # (https://taku910.github.io/mecab/format.html)
12 # Tagger::formatNode はこのフォーマット指定が前提、かつ、デフォルト
13 # のフォーマットは空。なんでやねん。
14 
15 node = t.parseToNode(
16     "フンボルトペンギンの「フンボルト」は動物学者。その兄は言語学者だそうな。")
17 while node:
18     print(t.formatNode(node))
19     node = node.next
実行結果
 1  2 の,助詞,連体化
 3 「,記号,括弧開
 4 フンボルト,名詞,一般
 5 」,記号,括弧閉
 6 は,助詞,係助詞
 7 動物,名詞,一般
 8 学者,名詞,一般
 9 。,記号,句点
10 その,連体詞,
11 兄,名詞,一般
12 は,助詞,係助詞
13 言語,名詞,一般
14 学者,名詞,一般
15 だ,助動詞,
16 そう,名詞,接尾
17 な,助詞,終助詞
18 。,記号,句点
19 EOS

インターフェイスはちょっと「なにこれ使いにくい」ね。それに、これってさ、mecab.exe でも出来ることなわけで、「ライブラリらしい使い方」というテーマにはそぐわないかもしらんね。でもまぁ、mecab.exe の場合は、「ノード単位にばらした状態で」にするために結果のパースが必要なわけだから…、まぁ一応「ライブラリとして使ってるからオイシイ」ではあるのか。まぁこれでいいとしよう。

まぁひとまずはこんくらい、かな。少なくともこれで「パース結果のパース」はいらなくなったってわけだ。ほかは色々遊びながら理解していくとしよう。また、最初のノードの表層形がおかしい問題についても、余力があったら調べよう。(というか問題がほんとに最初のノードの surface だけなら、誤魔化して使えるので、ほっとく手はある。)


2019-11-22 12:10追記:
最初のノードの surface がオカシイ件に関して、以下だと期待通りなのよね:

 1 # -*- coding: utf-8 -*-
 2 #
 3 # もっと細かい使い方。
 4 #
 5 import MeCab
 6 from MeCab import Tagger
 7 
 8 # mecab.exe のコマンドラインオプションとまったく同じものを渡せる…、
 9 # というよりは、「コマンドラインオプションとまったく同じものでしか
10 # 渡せない」というか。
11 t = Tagger.create("-F%f[6],%f[0],%f[1],%f[2],%f[3],%f[4],%f[5]")  # formatNodeに影響
12 # (https://taku910.github.io/mecab/format.html)
13 # Tagger::formatNode はこのフォーマット指定が前提、かつ、デフォルト
14 # のフォーマットは空。なんでやねん。
15 
16 node = t.parseToNode(
17     "フンボルトペンギンの「フンボルト」は動物学者。その兄は言語学者だそうな。")
18 while node:
19     print(t.formatNode(node))
20     node = node.next
 1 フンボルトペンギン,名詞,一般,,,,
 2 の,助詞,連体化,,,,
 3 「,記号,括弧開,,,,
 4 フンボルト,名詞,一般,,,,
 5 」,記号,括弧閉,,,,
 6 は,助詞,係助詞,,,,
 7 動物,名詞,一般,,,,
 8 学者,名詞,一般,,,,
 9 。,記号,句点,,,,
10 その,連体詞,,,,,
11 兄,名詞,一般,,,,
12 は,助詞,係助詞,,,,
13 言語,名詞,一般,,,,
14 学者,名詞,一般,,,,
15 だ,助動詞,,,,特殊・ダ,基本形
16 そう,名詞,接尾,助動詞語幹,,,
17 な,助詞,終助詞,,,,
18 。,記号,句点,,,,
19 EOS

Node の surface アトリビュートとフォーマットコード「%m」を使うとおかしくて、「%f[6]」だと問題ない。なんだろうかこれは、と思うんだけれど、「%f[6]」でやりたいことが出来るならそれでいいか、という気はする。

にしてもアレだ。なんでこのインターフェイスが使いにくいか。それは、「フォーマット指定がグローバルだから」である。なんで formatNode 時に指定できるようにしなかったのか、ちょいと理解不能。そう、「結果の文字列解析をしたくない」ためには、formatNode で細かに指定したいわけだ。これじゃぁ Tagger インスタンスを複数駆使するとかそういうことをしないといかんじゃぁないか。まぁ諦めてそうする、てことならいいけどさ、釈然とはしないわなぁ。



Related Posts