KNPのサーバーモードとJPrologの処理速度

先の記事で示した、JPrologのソースを見ていただければわかるように、JumanとKNPは、サーバーモードで起動している。理由は、その方が、JAVAからストリーム処理を真剣にしなくてもいいので、プログラムが簡素になることと、何よりも、スピードが違うらしいからである。

確かにcabochaと比べるとKNPは遅い感じはある。一つの文章を処理させるのにかかるトータルな時間は、実感としてKNPは遅いが、KNPをサーバーで立ち上げて、多分、データの読み込みなどのオーバーヘッドを吸収するので、複数の文章を処理させれば早くなりそうだ、ということである。

それでもcabochaよりは遅いのだろうが、実感としての遅さは感じない。

JAVAによるKNPをベースにしたprolog化

KNPをベースに、文章をprolog化するJPrologが一通りできた。

これまでも用いている芸人の定義をprolog化するとその出力は次のようになった。なお、プログラムそのものは、GitHubのJPrologレポジトリにおいてある。Cabochaバージョンは、これによって上書きされたので消去された。
https://github.com/toyowa/JProlog

run:
Jumanをサーバーモードでスタートさせました
KNPをサーバーモードでスタートさせました
Juman および KNP クライアントを開始しました
Juman: KILLシグナルを送りました PID = 25064
KNP: KILLシグナルを送りました PID = 25063
%%--------------------------------
%% 「芸人とは、なんらかの技芸や芸能の道に通じている人、または身に備わった技芸や芸能をもって職業とする人のことを指す日本特有の概念である」のprolog化
%%--------------------------------
No.0 芸人,とは 	Kakari:16 type:D score:991
No.1 なんらか,の 	Kakari:3 type:D score:1000
No.2 技芸,や 	Kakari:3 type:P score:1000
No.3 芸能,の 	Kakari:4 type:D score:1000
No.4 道,に 	Kakari:5 type:D score:997
No.5 [通じて, 通じる],いる 	Kakari:6 type:D score:400
No.6 人,または 	Kakari:14 type:P score:1100
No.7 身,に 	Kakari:8 type:D score:997
No.8 [備わった, 備わる],[] 	Kakari:10 type:D score:-1
No.9 技芸,や 	Kakari:10 type:P score:1000
No.10 芸能,を 	Kakari:11 type:D score:1000
No.11 [もって, もつ],[] 	Kakari:13 type:D score:-1
No.12 職業,と 	Kakari:13 type:D score:988
No.13 する,[] 	Kakari:14 type:D score:-1
No.14 人,の 	Kakari:15 type:D score:1000
No.15 こと,を 	Kakari:16 type:D score:1000
No.16 指す,[] 	Kakari:19 type:D score:-1
No.17 日本,[] 	Kakari:18 type:D score:-1
No.18 特有の,[] 	Kakari:19 type:D score:-1
No.19 概念,である 	Kakari:-1 type: score:400
%% フレーズ番号リストのトークン = [ r0 [ 1 2 3 4 5 r6 [ 7 8 r9 10 11 12 13 ] 14 15 ] 16 17 18 19 ] 
%% Prolog宣言
pl001(a01,
    node(とは,
        芸人,
        node(または,
            node(いる,
                node(に,
                    node(の,
                        node(や,
                            node(の,
                                なんらか,
                                技芸
                            ),
                            芸能
                        ),
                        道
                    ),
                    [通じて, 通じる]
                ),
                人
            ),
            node(や,
                node([],
                    node(に,
                        身,
                        [備わった, 備わる]
                    ),
                    技芸
                ),
                node(を,
                    芸能,
                    node([],
                        [もって, もつ],
                        node(と,
                            職業,
                            node([],
                                する,
                                node(の,
                                    人,
                                    node(を,
                                        こと,
                                        node([],
                                            指す,
                                            node([],
                                                日本,
                                                node([],
                                                    特有の,
                                                    node(である,
                                                        概念,
                                                        [ ]
                                                    )
                                                )
                                            )
                                        )
                                    )
                                )
                            )
                        )
                    )
                )
            )
        )
    )
).

後半のprologの宣言文は、swiprologで問題なく解釈されている。listingすると、次のようになっている。

pl001(a01, node(とは, 芸人, node(または, node(いる, node(に, node(の, node(や, node(の, なんらか, 技芸), 芸能), 道), [通じて, 通じる]), 人), node(や, node([], node(に, 身, [備わった, 備わる]), 技芸), node(を, 芸能, node([], [もって, もつ], node(と, 職業, node([], する, node(の, 人, node(を, こと, node([], 指す, node([], 日本, node([], 特有の, node(である, 概念, [])))))))))))))).

KNPの係り受け解析をもとにしている。「フレーズ番号リストのトークン」のところにある、リスト構造がわかりやすい。

(1)「芸人とは」の主語は、係り受け解析の結果から、そこでの第16番目の句にかかっているので、そこは、うまくとらえている。

(2)または、という接続詞が大きな構造を作っているが、それが「人」にかかっているところは、まあまあ、とらえている。

この後のものは、特に、cabochaでは、うまくとらえられなかったので、大いに良い。が、
「wikipedia「芸人」の定義とprolog(3)リスト処理追加」

の記事で示した、私の直感的なものよりも、何かしらもう一つ劣っている感は否めない。形態素解析ツールがmecabからjumanに変わったので、ノード語とリーフ語で、取れてないものが増えて空リスト [ ]になっているが、これは修正可能で、また、大きな問題でもない。

KNPの解説サイトで使われていた、「私は傘を買い、そして家に帰った」という文章を解析して、出力した結果は次のようなものである。

%%--------------------------------
%% 「私は傘を買い、そして家に帰った。」のprolog化
%%--------------------------------
No.0 私,は 	Kakari:4 type:D score:991
No.1 傘,を 	Kakari:2 type:D score:1000
No.2 [買い, 買う],そして 	Kakari:4 type:P score:1100
No.3 家,に 	Kakari:4 type:D score:997
No.4 [帰った, 帰る],[] 	Kakari:-1 type: score:-1
%% フレーズ番号リストのトークン = [ r0 [ 1 r2 3 ] 4 ] 
%% Prolog宣言
pl001(a01,
    node(は,
        私,
        node(そして,
            node(を,
                傘,
                [買い, 買う]
            ),
            node(に,
                家,
                node([],
                    [帰った, 帰る],
                    [ ]
                )
            )
        )
    )
).

「私は」が二つの文節にかかっている状況は捉えている。KNPの構文解析が踏まえられている。

全体として、もう少し調整する必要がある。

構文解析器の変更:CabochaからKNP

これまで数年、一貫して構文解析器としては、Cabochaを使ってきた。最初にこれを選んだ理由はもう思い出せない。ロボットネタの作成にか交わすところも、すべてこれでやってきた。

他にKNPという解析ツールがあることはもちろん知っていた。

この間、ここでの文章知識のprolog化をするにあたって、構文解析器が重要な役割を果たすはずなのだが、Cabochaがどうにも思い通りに結果を表示しないので、Cabochaは、フレーズを出力する手段としてのみ使っていた。

例えば、この間使っていた例文「芸人とは、なんらかの技芸や芸能の道に通じている人、または身に備わった技芸や芸能をもって職業とする人のことを指す日本特有の概念である」をこのCabochaにかけ、構造だけ出力させると次のようになる。

のびたラーメンのような結果で、構造は何も表現されていない。

フレーズを切り取るだけならば、Macabから、そう難儀せずにできそうで、あえてCabochaを使うまでもないという気持ちはあったが、そこも面倒なので、Cabochaを使っているという感じだった。

JPrologで、どう構造化するという時に、ある意味仕方なく、自前の、必要な範囲の構文解析を行なっているのだが、なんとも不満足なものであることは確かだ。

そこで、改めて、KNPを試してみた。正直、驚くほど凄かった。例えば先の芸人の定義をKNPにかけると次のようになる。(構造だけ出力させる)

ほぼ、完璧である!!

「または」という接続詞が果たしている役割や、主語の「とは」の捉え方、最後の「日本特有の概念」のところ、「や」の並列性の捉え方、全部正しい。

これほどの違いとは知らなかった。今までがなんだったのか。衝撃が大きすぎる。

JPrologのすべてを、KNPで書き直そうと思う。

文章をprolog化するJAVAプログラム(3)

前の記事では、仮想的文章をprolog化することを示したが、実際の文章を与えてprolog化するJAVAプログラムを作成した。以下から、ダウンロードして実行できる。

https://github.com/toyowa/JProlog

ただし、cabochaが必要なので、そのREADMEテキストの指示に従って呼び出せるようにする必要がある。

これまでも例に使ったwikipediaの芸人の定義文書をprolog化する。
「芸人とは、なんらかの技芸や芸能の道に通じている人、または身に備わった技芸や芸能をもって職業とする人のことを指す日本特有の概念である」

まず、cabochaで、フレーズに分解すると次のようになる(JAVAプログラム出力の一部)。

%% ------ フレーズリスト ---------
% 助詞・助動詞,名詞・動詞,接続詞,原型
% No.0: とは, 芸人, , 
% No.1: , [[なんらかの], 技芸], や, 
% No.2: の, 芸能, , 
% No.3: に, 道, , 
% No.4: , 通じ, ている, 通じる
% No.5: , 人, または, 
% No.6: に, 身, , 
% No.7: た, 備わっ, , 備わる
% No.8: , 技芸, や, 
% No.9: を, 芸能, , 
% No.10: , もっ, て, もつ
% No.11: と, 職業, , 
% No.12: [ところの,という], する, , する
% No.13: の, 人, , 
% No.14: を, こと, , 
% No.15: [ところの,という], 指す, , 指す
% No.16: の, 日本特有, , 
% No.17: である, 概念, , 
%%--------------------------------

ただし、これまでと少し改定した点は、cabocha(あるいは、mecab)の助詞の第二品詞が「副助詞/並立助詞/終助詞」、「接続助詞」、「並列助詞」を助詞ではなく、接続詞扱いしている点にある(詳細な手続きは、プログラムを見ていただきたい)。その理由は、ここでの目的は、文章の中にある部分知識を得ることで、その点で、接続詞の前後が部分知識になっている可能性が高いからである。

その視点から、フレーズリストをリストに構造化すると次のようになる(JAVAプログラム出力の一部)。

[ [ 0 1 2 r3 ] r4 [ r5 [ 6 7 r8 [ 9 r10 [ 11 12 13 r14 15 16 17 ] ] ] ] ] 

rのついたフレーズ番号は、その部分リストのルートフレーズである(この辺りはこれまでの記事で解説ずみ)。ルールとしてフレーズが3個より短いサブリストは作らないことにした。

重要な点は、通常助詞よりも、接続詞をルートフレーズ化する場合の優先度を高めた。つまり、接続詞があれば、それを助詞よりも優先的にルートフレーズかするということである。

サブリストも含めたルートフレーズは、改めて具体的に表示すると、次のようになる。

%% ルート句: No.4 助詞/動詞: 語:通じ 接続詞:ている 原型:通じる
%% ルート句: No.3 助詞/動詞:に 語:道 接続詞: 原型:
%% ルート句: No.5 助詞/動詞: 語:人 接続詞:または 原型:
%% ルート句: No.8 助詞/動詞: 語:技芸 接続詞:や 原型:
%% ルート句: No.10 助詞/動詞: 語:もっ 接続詞:て 原型:もつ
%% ルート句: No.14 助詞/動詞:を 語:こと 接続詞: 原型:

これを先の記事で示したツリー化と表示クラスに咥えこませると、次の結果を得る。

%% Prolog宣言
pl000(
    node(ている,
        node(に,
            node(の,
                node(や,
                    node(とは,
                        芸人,
                        [[なんらかの], 技芸]
                    ),
                    芸能
                ),
                道
            ),
            通じ
        ),
        node(または,
            人,
            node(や,
                node(た,
                    node(に,
                        身,
                        備わっ
                    ),
                    技芸
                ),
                node(て,
                    node(を,
                        芸能,
                        もっ
                    ),
                    node(を,
                        node(の,
                            node([ところの,という],
                                node(と,
                                    職業,
                                    する
                                ),
                                人
                            ),
                            こと
                        ),
                        node([ところの,という],
                            指す,
                            node(の,
                                日本特有,
                                node(である,
                                    概念,
                                    [ ]
                                )
                            )
                        )
                    )
                )
            )
        )
    )
).

swiprologは、問題なく通過する。listingで表示させると、次のような1行になっている。

pl000(node(ている, node(に, node(の, node(や, node(とは, 芸人, [[なんらかの], 技芸]), 芸能), 道), 通じ), node(または, 人, node(や, node(た, node(に, 身, 備わっ), 技芸), node(て, node(を, 芸能, もっ), node(を, node(の, node([ところの, という], node(と, 職業, する), 人), こと), node([ところの, という], 指す, node(の, 日本特有, node(である, 概念, []))))))))).

以前の記事の出力結果と比べると大きく違っている。うねりが蛇のように複雑になった。それは、フレーズリストがサブリストの複雑な再帰的構造を持っている結果である。

われわれがもともと意識したというか、直感的に確実に持っている「または」という接続詞が二つの「人」を分けていることは表現できなかった。ここは、本当に文章の意味がわからなければ捉えられない。意識的にそのような構造をフレーズリストに反映させればそれは実ガン可能なのだが、今のところはそうしない。

右側の山の出っ張りのあたりに、この「芸人」の定義の大事な要素がにじみ出ている。その理由は、この山を小刻みに作っているのが、接続詞による分割が行われているためである。

また、「国境の長いトンネルを抜けると雪国であった」をprolog化すると次のようになる。

%% ------ フレーズリスト ---------
% 助詞・助動詞,名詞・動詞,接続詞,原型
% No.0: の, 国境, , 
% No.1: を, [[長い], トンネル], , 
% No.2: , 抜ける, と, 抜ける
% No.3: であった, 雪国, , 
%%--------------------------------
%% 「国境の長いトンネルを抜けると雪国であった」のprolog化
%%--------------------------------
%% ルート句: No.1 助詞/動詞:を 語:[[長い], トンネル] 接続詞: 原型:
%% フレーズ番号リスト = [ 0 r1 2 3 ] 
%% Prolog宣言
pl000(
    node(を,
        node(の,
            国境,
            [[長い], トンネル]
        ),
        node(と,
            抜ける,
            node(であった,
                雪国,
                [ ]
            )
        )
    )
).

こちらは、これまでとほぼ同じ結果になる。接続詞が一つもないからである。

課題としては、次のようなものがある。(1)接続詞を無条件にルート化するというのは、やや無謀だ。(2)「は」とか「とは」は、接続詞よりも高めたほうがいい。(3)あっさり、プライオリティー水準をいろいろな基準で作ったほうがいいかもしれない。

文章をprolog化するJAVAプログラム(2)

前の(1)の記事で示したプログラムは、ごちゃごちゃして醜かった。この記事の前の二つの記事を踏まえて、基幹部分を全面的に書き直した。要点は次のとおりである。

(1)二分木であることをしっかりと踏まえる。句番号のリストを与えると、二分木が自然にできることを示したが、そのアルゴリズムはシンプルなのだ。
(2)二分木の作成と、それに文章のフレーズを実際に乗せてprolog化するプロセスを完全に分離すること。前者は上に述べたような単純さがあり、後者は複雑な一面を持つ。分離することにより、後者の複雑さに対応しやすくなる。

javaで二分木を扱うについては以下のページがとても参考になる。そこに記載されているアルゴリズムは、基本、そのままここでも使うことができる。これを踏まえて、これまでも使った抽象的文章をprolog化するプログラムを以下のように作成した。(以下のGitHubディレクトリのPhraseTree.java)を参照。

phrasetree/PhraseTree.java

このプログラムは、仮想的な文章を使っていて、cabochaなどの構文解析やparseの必要がないので、このjavaファイルだけで実行できる。

やり方は、このファイルを PhraseTree.java という名前で保存し、jdkがインストールしてあれば、javacで

$ javac PhraseTree.java

とすれば、コンパイルされclassファイルができるので、引数など何も指定せずに、

$ java PhraseTree

とやれば、仮想的文章の解析を行った結果を出力するはずである。($はコマンどうプロンプトである)

プログラムに組み込まれている仮想文章は、

「aあbいcうdえeおfかgきhく」

というもので、小文字のアルファベットが名詞や動詞、ひらがなが助詞や助動詞、接続詞などを表している。アルファベット1文字と、ひらがな1文字のペアを一つの句とみなすと、全部で8個ある。句に0番から7番までの番号をつける。(アルファベットの小文字にしたのは、大文字にすると、prologの変数と間違われてしまうからである)

ここで、これまでの記事の中の例と同様に句リストを、プログラム中に次のようなものとして与えている。

[ [ 0 1 r2 3 4 ] r5 6 7 ]

全体のルート句が「か」が助詞となった5番目の句、0番から4番がサブリストで、それは2番目の句が「または」のような接続詞になっていると同時に、それがサブリストのルートにもなっているというものである。(ルートの句番号にはrがついている)

そのまま実行すると、出力は、次のようになる。後半部分に、prolog の宣言文となっているものが出力されている。プログラム中の310行目あたりにある、クリストを変えると、文章の構造が変わる。試して見られるといい。

リストのトークン化
[ [ 0 1 r2 3 4 ] r5 6 7 ] 
rootから左側のフレーズ番号を調べます
rootから左側のフレーズ番号を調べます
リスト/サブリストのフレーズシーケンスを作成しました
rootから右側のフレーズ番号を調べます
リスト/サブリストのフレーズシーケンスを作成しました
リスト/サブリストのフレーズシーケンスを作成しました
rootから右側のフレーズ番号を調べます
リスト/サブリストのフレーズシーケンスを作成しました
フレーズ番号: 5
フレーズ番号: 2
フレーズ番号: 1
フレーズ番号: 0
フレーズ番号: 3
フレーズ番号: 4
フレーズ番号: 6
フレーズ番号: 7
pl000(
    node(か,
        node(う,
            node(い,
                node(あ,
                    a,
                    b
                ),
                c
            ),
            node(え,
                d,
                node(お,
                    e,
                    f
                )
            )
        ),
        node(き,
            g,
            node(く,
                h,
                [ ]
            )
        )
    )
).

 

文章の二分木とprolog

これまでのシステムを、二分木だとは考えていたが、とても特殊なものだと思っていた。しかし、前の記事までの考察を踏まえると、実は、普通の二分木であることに気付いた。

前の記事と同様に、次のような抽象的文章(あるいは、一般化された日本語文章)を考える。

句番号、リーフ語、ノード語
0, A, あ
1, B, い
2, C, う
3, D, え
4, E, お
5, F, か
6, G, き
7, H, く

この抽象的文章は次のようなものである。

「AあBいCうDえEおFかGきHく」

これをルート語を5番の句として、2番の句を接続詞として、0,1番と3,4番のサブツリーをつなげているとし、句番号のリストとすると(カンマを省略)、

[[0 1 (2) 3 4] (5) 6 7]

という表し方が可能である。全体のリストのルートは5番、サブリスト[0 1 2 3 4]のルートが2番となっている。

今この情報をもとに、再帰的にツリーを作り上げていくことを考える。このツリーは、常に左側の句番号が、右側の句番号よりも小さいことを前提にする。そして、ルートから順番に値を入れていく。

入れていく句番号については、次のようなルールで入れる。

(1)そのリストのルート句番号を最初に入れる
(2)ルートの句番号に近いものから値をとばさず順次入れる
(3)サブリストに入った時は、そのリストを上記二つのルールで先行して処理する

ツリーが作られていく様子は次のように表される。

出来上がったものは、先の記事で書いた抽象的文章の二分木に他ならない。

右に接続詞があった場合も、基本的にストーリーは同じである。