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)サブリストに入った時は、そのリストを上記二つのルールで先行して処理する

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

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

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

リストによる接続詞のprolog化

(この記事を理解するためには、prologに関するこれまでの記事を見ておく必要があります)

ここでリストというのは、prologのリストのことではなく、文章を複数の句のリストからなるものとするという意味である。

文章を構成する句とは、基本的に 動詞または名詞のリーフ語に、助詞や動詞あるいは接続詞のノード語からなっていると考える。例えば「私はみかんが好き」は、「私」というリーフ語に「は」というノードが続いた句が最初にあり、次に「みかん」というリーフ語に、「が」というノード語がついたものだ。最後の「好き」はリーフ語しかない。こういう場合もある。

もっとこのような関係を抽象化しよう。ひらがな1文字をノード語としアルファベット1文字をリーフ語化し、ある文章が、次のような句のリストから成り立っているとする。

句番号、リーフ語、ノード語
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番の「Fか」だったとし、また、第2番目の句のノード語「う」が「または」という接続詞だったとしよう。このとき、次のようなツリーを描くことができる。

αのサブツリーとβのサブツリーが「または」によって並立する形になっている。先の抽象的文章がこのツリーにどのようにはめ込まれているかを確認することは、大切な作業である。

サブツリーαで、Cは句の中で「う」に繋がるのであるが、「う」は、「または」でツリーαからはじき出されている。抽象化文章を書き下すと、

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

という文章なのである。日本語は、またはという接続詞が来て、どこからどこまでを並立するかをルール上は決定できない。文章の意味の中にしかその答えはないのである。ここでは、次のような構造を想定している。

[[AあBいC]または[DえEおF](か)GきHく]

ただし、「か」はルートノード語である。

この構造化された抽象文章は、その構造を確定的にそして一意に、曖昧さなく表現している。その意味では、前の記事に書いたようなリストによる構造の表現は、句のノード語とリーフ語が、別れる場合を描ききれないので、こちらの表現の方が望ましいと思える。これについては、別にまた考察する。

さらにルートノード語の「か」と同じくにあるリーフ語はFであり、それは「または」で並立した右のサブツリーの右端の語から繋がっている。遠くになるのである。これも、必然なのである。

さらに、右側のツリーに接続詞が来た場合も考察しておこう。

抽象文章は同じとしよう。そして、ルートノード語は第2フレーズの「う」であり、第5フレーズのノード語「か」が「または」であるとする。このとき、ツリーは次の図のようになる。

もちろん、「または」が一意にこのようなサブツリーを生み出すわけではなく、意味によって並立の範囲は異なってくる。ここではその中の一例に過ぎない。

抽象化された文章のリストは次のようになる。

[AあBいC(う)[DえEおF]または[GきHく]]

「または」から、左に伸びたツリーは、ルートの「う」から左に伸びたツリーと同じ特徴を持っている。

ここでは「う」の繋がるノード語がDで、真下の奥になっているところが面白い。これも先と同様に必然なのである。

抽象化文章を用いることによって、具体的言葉の意味に惑わされずに、的確に文章構造を捉えることができる。構文解析なのだ。

サブツリーによる文章の構造とprolog化

これまで書いた文章のprolog化では、ルートとなるべきフレーズ(句)が決まると、文章全体を表すフレーズリストから、プログラムによって一意にprolog化が実行された。

ただ、このプログラムでは接続詞がうまく取り扱われていない。「または」などの接続詞は、文章の大き化構造を作り出す大切な役割を果たしている。文章の大きな構造をどのようにして生み出すのか、そこを取り上げる。

今、0番から9番までの番号がついた10個のフレーズからなる文章があったとしよう。そして、6番目の句がルートであったとする。これを単純に次のように表す。

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

今、このうち、2,3,4番目のフレーズをその中で相対的に自立させて、サブツリーとし、そのルートを3番とする。それは次のように書ける。

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

一般的ルールはすぐに想像つくだろう。サブツリーの中の、サブサブツリーというのもあり得る。

重要な原則は「あるツリーのルートは、そのサブツリーの中にあってはならない」ということである。ただし、厳密に言うと、少し違う。

これまでの記事の例を見てみよう。極端に短いものだが「私はりんごまたはみかんが好きだ」(変な日本語だが)の場合、フレーズリストは次のようになる

0 私 は
1 りんご または
2 みかん が
3 好き だ

ここで、最初は、フレーズ番号、2番目は名詞または動詞の語(以下、「リーフ語」)、最後は助詞、助動詞、接続詞(以下「ノード語」と言おう)である。ルート句を2にすると、サブツリーがない状態では

[0, 1, (2), 3,]

となる。もし、1,2をサブツリーとし、1をルートとすると、

[0, [(1), (2)], 3,]

と言う書き方が許される。基本、一つのサブツリーに二つのルートはあり得ないが、サブツリーの右端のフレーズは、ルートになれるのである。

サブとメインの関係は上記のようになっている。厳密には、サブツリーの右端のノード語は、そのサブツリー全体のノード語なのである。ここがポイントである。

 

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

この間の記事で示してきた、文章情報のprolog化を、cabochaによるフレーズ化された情報を参考に、私が書いたものだった。ただし、cabochaの係り受け解析は用いず、ただ、フレーズの区分だけを用いている。

大量の文書情報をいちいち手動でprolog化したのでは意味がない。元々の予定である、prolog化のためのJAVAプログラムを作成した。ただし、まだ、接続詞の適切な処理、構文のツリー化における最適化などを十分行えていない。現時点でのパフォーマンス報告にとどまる。

まず、この間の記事で用いた「国境の長いトンネルを抜けると雪国であった」をprolog化させてみた。以下のようになった。

%%--------------------------------
%% 「国境の長いトンネルを抜けると雪国であった」のprolog化
%% ルート句: No.1 助詞/動詞:を 語:[[長い], トンネル] 接続詞: 原型:
%%--------------------------------
pl0000(
    node(を,
        node(の,
            国境,
            [[長い], トンネル]
        ),
        node(と,
            抜ける,
            node(であった,
                雪国,
            []
            )
        )
    )
).

少し解析ルールを変えたが、問題なくswi-prologが受け止める。pl0000 は文章の識別子で特に意味は持たない。ポイントをいくつか書いておこう。上記の文書には出ていないものも含めて書いておく。(次の記事で、メインのJAVAクラスを公開しているので、そちらを参照していただきたい。)

(1)ルート句の設定である。ルート句については、先の記事で「を、に、が、は」の助詞の強さについては書いたが、実はそれは、http://www.ibot.co.jp/wpibot/?p=2121の中で示した、wikipediaの助詞の頻度分析の上位にほぼ一致しているのである。ただし、一番上位には「の」があり、群を抜いているが、「の」の助詞の句は、さほど重要な役割を果たさない場合もあるので、基本、「の」を除いた先の頻度分析の上位のものがある句をルート句にしてツリーを作ることにしている。もし、どれもなければ「の」のある句をルートにしている。

(2)後の末尾に、右の葉が無い場合は、そこを [ ] の空リストにする場合がある。大した問題では無いのだが。

(3)連体詞は形容詞と同様の扱いをする(以前の記事で書いた)。形容詞や連体詞がひとつのフレーズの中で連続する場合は、リスト化する。

(4)助詞、助動詞、名詞、動詞、がひとつのフレーズの中で連続する場合は、ただ、繋げる。

(5)ひとつのフレーズの中で、名詞や動詞が単独で存在して、助詞、助動詞が無い場合は、暫定的に、擬似的な助詞、助動詞として、"[ところの,という]"というprologリストを付け加えておく。英語で言えば、関係代名詞みたいなものである。もちろん、単に [ ] と空リストにしたほうがいい場合もある。大した問題では無い。prologプログラム上のERRORを回避することが最大の目的である。

(6)フレーズの中に、「する」など、非自立の動詞が挟まれている時がある。例えば「職業とする人のことを指す」の中の「する」は非自立の動詞である。意味的に重要ではなく、実際に何かをするわけでも無いので、これも助詞、助動詞扱いにしてしまう。

今後、さらに細かいルールを入れてどのような文章にも対応するようにしたいが、さしあたって、上記のルールのもとに、先の芸人の定義もこのJAVAプログラムでprolog化した。結果は以下のようになった。

%%--------------------------------
%% 「芸人とは、なんらかの技芸や芸能の道に通じている人、または身に備わった技芸や芸能をもって職業とする人のことを指す日本特有の概念である」のprolog化
%% ルート句: No.9 助詞/動詞:を 語:芸能 接続詞: 原型:
%%--------------------------------
pl0000(
    node(を,
        node(や,
            node(た,
                node(に,
                    node(または,
                        node(ている,
                            node(に,
                                node(の,
                                    node(や,
                                        node(とは,
                                            芸人,
                                            [[なんらかの], 技芸]
                                        ),
                                        芸能
                                    ),
                                    道
                                ),
                                通じ
                            ),
                            人
                        ),
                        身
                    ),
                    備わっ
                ),
                技芸
            ),
            芸能
        ),
        node(て,
            もっ,
            node(と,
                職業,
                node([ところの,という],
                    する,
                    node(の,
                        人,
                        node(を,
                            こと,
                            node([ところの,という],
                                指す,
                                node(の,
                                    日本特有,
                                    node(である,
                                        概念,
                                    []
                                    )
                                )
                            )
                        )
                    )
                )
            )
        )
    )
).

これもswi-prologが正常に読み込めるprologの宣言文となっている。swi-prologに読み込むと、インデントのない次のような宣言文になっている。(:- listing.で表示させた)

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

ルート句を「を」にしたので、その右と左をそれぞれprolog化している。私のprolog化の場合とは、「芸人とは」をルートにしていることと「または」を接続詞として生かしているところが異なっているが、全体としてさほど深刻な違いがあるとも思えない。

接続詞「または」をもっと生かせば、最終目標である、部分知識を拾い出すという点でも、遜色ないものになるはずである。

 

接続詞とprolog化

「芸人」の定義の文書でもでてきた接続詞について、もう少し調べることがあると思った。

「私はりんごまたはみかんが好きだ」の構文解析結果は次のようになる。両方好きなのか、どちらかが好きなのかはっきりわからない曖昧な文章だが、そこは問題にせずに「または」がどうなるかを見る。

* 0 3D 0/1 -2.400531
私	名詞,代名詞,一般,*,*,*,私,ワタシ,ワタシ
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
* 1 2D 1/1 0.754827
りんご	名詞,一般,*,*,*,*,りんご,リンゴ,リンゴ
または	接続詞,*,*,*,*,*,または,マタハ,マタワ
* 2 3D 0/1 -2.400531
みかん	名詞,一般,*,*,*,*,みかん,ミカン,ミカン
が	助詞,格助詞,一般,*,*,*,が,ガ,ガ
* 3 -1D 0/1 0.000000
好き	名詞,形容動詞語幹,*,*,*,*,好き,スキ,スキ
だ	助動詞,*,*,*,特殊・ダ,基本形,だ,ダ,ダ
EOS

こうなると、「が」をルートにすると、次のようにprolog化する。

conjunction1(
    node(が,
        node(は,
            私,
            node(または,
               りんご,
               みかん
            )
        ),
        %% 次のnodeの代わりに単に「好きだ」としてもいい
        node(だ,
            好き,
            nil
        )
    )
).

接続詞「または」が、助詞と同じような使われ方になる。また、右端の「好きだ」については助動詞「だ」を独立させて、上記のように"node"にしたが、それ自体を一つのatomとして扱っても良い。どちらでもあまり知識化という点では大した問題ではないのだ。

ただし、これにはもう一つのバージョンがありうる。prologではなく、次の図を見ていただきたい。

(1)が先に述べたものだが、(2)の場合もありうるのである。しかしこちらは、独立した「私はりんご」というフレーズに知識的内容がない上に、「または」が持っている並列作用が消えてしまって、りんごとみかんが切り離されてしまっている。

つまり、こうした接続詞は、演算で言えば掛け算のように足し算などから比べて高い優先順位で処理しなければならなくなるということである。ただし、こうした優先度が高くても、左右どちらかの項が、演算でいう、括弧に入った状態だと、そちらの中を先に処理するということがでて切るので、そのあたりの見極めがどうなるかが難しい。

また、この文章を「私はりんごかみかんが好きだ」という、「または」を「か」に置き換えてもほとんど同じ意味になる。この場合の「か」は、並立助詞といって、接続詞とほぼ同じ機能を持つ。ただ、cabochaは形態素解析で「か」が並立助詞として明確化せず品詞2で「副助詞/並立助詞/終助詞」と出してきて、そのどれかだよ、みたいな書き方しかしないので、困ったものだ。文中に出てくるこうした「か」は、並立助詞を疑う必要がある。

接続詞の優先性が保留される例として、「今日は大学に行く、または映画に行く」を解析する。構文解析結果は次のようになる。

* 0 5D 0/1 -1.000291
今日	名詞,副詞可能,*,*,*,*,今日,キョウ,キョー
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
* 1 2D 0/1 2.454284
大学	名詞,一般,*,*,*,*,大学,ダイガク,ダイガク
に	助詞,格助詞,一般,*,*,*,に,ニ,ニ
* 2 5D 0/0 -1.000291
行く	動詞,自立,*,*,五段・カ行促音便,基本形,行く,イク,イク
、	記号,読点,*,*,*,*,、,、,、
* 3 5D 0/0 -1.000291
または	接続詞,*,*,*,*,*,でなければ,デナケレバ,デナケレバ
* 4 5D 0/1 -1.000291
映画	名詞,一般,*,*,*,*,映画,エイガ,エイガ
に	助詞,格助詞,一般,*,*,*,に,ニ,ニ
* 5 -1D 0/0 0.000000
行く	動詞,自立,*,*,五段・カ行促音便,基本形,行く,イク,イク
EOS

係り受け解析は、ほとんど失敗しているようにも見える。頼りにならない、困った、がまたそれは後で考える。

二分技の図を見てみよう。

優先性をただ尊重すると、「いく、でなければ、映画」になるが、ほとんど意味を持たない関係性であるから、図のように「大学に行く」というノードと「映画に行く」というノードを並立させることが、この「でなければ」の機能なのである。どうやってこれを読み取るかが問題である。

二つのノードが一つのノードからぶら下がっている構造は、今まで出てこなかった。なぜか。一つの句の中にある助詞なり助動詞は、同じくの中に名詞化同士が必ず存在したからだ。ところが、cabochaの出力を見てもわかるように、この場合の接続詞の句の中には、名詞も動詞もない。だから、前のフレーズまでもと、後のフレーズを自分の中に取り込むことができるのである。

prolog化すると次のようになる。

conjunction2(
    node(は,
        node(または,
            node(に,
                大学,
                行く
            ),
            node(に,
                映画,
                行く
            )
        ),
        今日
    )
).

「または」ノードのの中に二つの子ノードが含まれていることがわかるだろう。

このように見ると、接続詞というのがとても重要な役割を果たすことがわかるのだ。

「雪国」冒頭の文章とprolog

前の記事まででやった、文章のprolog化、二分木化する場合に、もっといろいろ考えるべきことがあるのに気づいた。

題材として、川端康成「雪国」の冒頭の一文をprolog化しよう。

まず、cabochaで、形態素解析および構文解析しておこう。

* 0 2D 0/1 1.211902
国境	名詞,一般,*,*,*,*,国境,コッキョウ,コッキョー
の	助詞,格助詞,一般,*,*,*,の,ノ,ノ
* 1 2D 0/0 2.895743
長い	形容詞,自立,*,*,形容詞・アウオ段,基本形,長い,ナガイ,ナガイ
* 2 3D 0/1 1.600771
トンネル	名詞,一般,*,*,*,*,トンネル,トンネル,トンネル
を	助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
* 3 4D 0/1 1.600771
抜ける	動詞,自立,*,*,一段,基本形,抜ける,ヌケル,ヌケル
と	助詞,接続助詞,*,*,*,*,と,ト,ト
* 4 -1D 0/3 0.000000
雪国	名詞,一般,*,*,*,*,雪国,ユキグニ,ユキグニ
で	助動詞,*,*,*,特殊・ダ,連用形,だ,デ,デ
あっ	助動詞,*,*,*,五段・ラ行アル,連用タ接続,ある,アッ,アッ
た	助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
EOS

英語のような明確な主語を持たない、日本語らしい文章だ。1フレーズの形容詞句を除いて、全て、助詞か助動詞があり、フレーズをシーケンシャルに結合させている。*のラインの第2項に示されている2Dなどが示している係り受け解析は、単純に語順を再現しているだけである。雪国を含むフレーズにある、-1D は、その句が、それ以上、どこにもかかっていないことを示している。

そこで、これを前と同様にprologの二分木に変更してみよう。ただし、ここでは、4バージョンの二分木があるのを全て書き下すことにする。(前の記事までの芸人の定義では、一つの直感的prolog化だけを用いた)

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 川端康成『雪国』冒頭
%% 「国境の長いトンネルを抜けると雪国であった」
%% 一行で表示する場合をコメントで示したのちに、インデント付きで表示する
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%% (1)
%% 一行表示
%% yukiguni1(node(の, 国境, node(を, [[長い], トンネル], node(と, 抜ける, node(で, 雪国, あった))))).
%% インデント表示
yukiguni1(
    node(の,
        国境,
        node(を,
            [[長い],トンネル],
            node(と,
                抜ける, 
                node(で,
                    雪国,
                    あった
                )
            )
        )
    )
).

%% (2)
%% yukiguni2(node(を, node(の, 国境, [[長い], トンネル]), node(と, 抜ける, node(で, 雪国, あった)))).
yukiguni2(
    node(を,
        node(の,
            国境,
            [[長い],トンネル]
        ),
        node(と,
            抜ける, 
            node(で,
                雪国,
                あった
            )
        )
    )
).

%% (3)
%% yukiguni3(node(と, node(を, node(の, 国境, [[長い], トンネル]), 抜ける), node(で, 雪国, あった))).
yukiguni3(
    node(と,
        node(を,
            node(の,
                国境,
                [[長い],トンネル]
            ),
            抜ける
        ),
        node(で,
            雪国,
            あった
        )
    )
).

%% (4)
%% 
%% yukiguni4(node(で, node(と, node(を, node(の, 国境, [[長い], トンネル]), 抜ける), 雪国), あった)).
yukiguni4(
    node(で,
        node(と,
            node(を,
                node(の,
                    国境,
                    [[長い],トンネル]
                ),
                抜ける
            ),
            雪国
        ),
        あった
    )
).

これをyukiguni.swiファイルとして保存し、swi-prologに問題なく読み込ませることができる。これでけでは少しイメージしにくいと思うので、二分木をノートに書いたのが以下の画像である。

4つのバージョンができることがややこしい。この違いついて議論する前に、フォーマットの若干の拡張について触れておく。

基本、名詞や動詞に直接かかる形容詞や副詞は、その名詞や動詞と一体のものとして扱う。cabochaの構文解析でも、「い」は助詞にもなっていない。

トンネルにかかる形容詞は、「長い」の他のも「暗い」などたくさんありうるので、かかる言葉をprologのリストとして扱う。例えば、それは [[長い,暗い],トンネル]でも良いわけである。トンネルは名詞で、ないと思うが、それが活用などの語形変化があれば、トンネルもリスト化すれば良い。そこは、「芸人」の定義分析でやったところである。

また、今回は、動詞の活用形が問題になる語が「あった」と「ある」しかなく、これは記述の簡単化のために無視した(本番では [あった, ある]のリストにすると思う)。

各バージョンについて見ていこう。

(1)cabochaによる構文解析ともっとも整合的なものがこのバージョンである。「雪国であった」というフレーズが、他のノードに依存しない独立化可能なロバスト(頑健:外部に影響されない)なものになっている(これは、(2)と(3)のバージョンに共通している。)。他に、ロバストなフレーズはない。もっとも際立った特徴は、この唯一のロバストなフレーズに、抜ける、長いトンネル、国境という名詞、動詞がシーケンシャルにかかっていることだ「雪国であった」というフレーズが、絶対的存在感を持つような構造になっている。さらに、全ての左の語から、言葉を始められることである。ちょっとわかりにくい言い方だが、先の芸人の記事において、検索は、右の語を探しに行って、一致したものがあったら、右の語を含めて文章化できるものをその下に向かって作成していった。右の語に独立して存在しなければ、見つけられない仕組みになっていた。しかし、この(1)バージョンでは、そのような独立した右の語は、「あった」しかなく、そこから構成できる文章は「雪国であった」だけだが、同じように左語からの検索と文章構成システムを用いると(それは、前回の記事の中にあるプログラムを若干改訂すればすぐにできる)、全ての部分文章(部分知識)が作り出されることになる。その意味するところは、全ての他のフレーズが「雪国であった」に従属しているのである。

(2)の特徴は、「国境の長いトンネル」というのが、ロバストなフレーズとして独立したことである。そして、「抜けると」というフレーズを接着剤にして「雪国だった」というメインフレーズとつなげられていることになる。

(3)は、(2)と比べて「抜けると」というフレーズが、「国境の長いトンネル」の側により強く繋がっていることが特徴だ。

(4)は、(1)の対称系になっている。「雪国」が単なる飾りのようになってしまった。右語からの検索で、全ての部分文章、部分知識が再現できる。

prologによる文章の知識化としてみたとき、この四つのバージョンのうちのどれを選ぶかが問題である。小説「雪国」冒頭としてみたとき、(4)はしっくりこない。

四つ全てを保持するのは、非効率であることは明らかなので、何らかの知識、言葉、文章を構成するときに、どの形で保持することが効果的かという基準になる。

もともと、このような情報の知識化をprologで行おうという動機になったのは、ロボットに知識を喋らせるときに、どうしても一つの文章を短く、縮約する必要があるのに、単文要約の適切なアルゴリズムが見つからなかったことである。ディープラーニングなどのコンテンポラリな手法にも、最終解決は託せなかった。その過程で読んだ論文に「大規模データを用いた日本語文圧縮」(長谷川駿氏他、2017)があって、その中で「を、に、が、は」は、かかり先文節の必須語になりやすい、と指摘されていた。

それは、言い換えれば、それくらい強い結びつきを作る言葉であるということは「そのような強さを持っていなければならないほど、それによって繋がる二つのフレーズは、相対的に強い独立性を持っている」ということではないか。

この意味をどう解釈するかは別にして、まず、助詞に強さがあることが興味深い。さらに、ここでのシステムに応じた表現に変えれば、これらの語は、二分木ツリーのルートに近いところにあるべきだと解釈しても良さそうである。実際、前節までで使っていたwikipedia芸人の定義は、私が直感的な判断で「芸人とは」の「とは」をルートにしていた。「とは」は「は」とほぼ同じか、それよりも強めの助詞である。

というようなことを考え合わせると、この雪国冒頭の文書の場合も、助詞「を」を二分木のルートとしているバージョン(2)を採用することが妥当のように思える。