リストによる接続詞の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プログラムを作成した。ただし、まだ、接続詞の適切な処理、構文のツリー化における最適化などを十分行えていない。現時点でのパフォーマンス報告にとどまる。

(メインのJAVAクラスだけは、以下で見ることができる。ただし、他にcabochaを動かし、parseするクラスが必要で、これだけでは動かない。ただ、作るためのアルゴリズムなどはすべて書かれている)
Prolog.java → 古い、次の最新版を参照 phrasetree/PhraseTree.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)を採用することが妥当のように思える。

wikipedia「芸人」の定義とprolog(3)リスト処理追加

先の記事の段階で無視した、prolog化した文書の中にあるリストを処理可能にした。

%%%%%%%%%%%%%%%%
% geinin3.swi
% 2019年1月26日
% リストの場合の処理を付加した
%%%%%%%%%%%%%%%%

%%% 元の文章:
% 芸人とは、なんらかの技芸や芸能の道に通じている人、
% または身に備わった技芸や芸能をもって職業とする人
% のことを指す日本特有の概念である
%%%

%%% 節(1)
search(S,node(A,B,S),A,B).
search(S,node(A,B,L),A,B) :- member(S,L). %% リストの場合の処理
search(S,node(A,B,S),A,B).
search(S, node(_, Y, _), A, B) :- search(S, Y, A, B).
search(S, node(_, _, Z), A, B) :- search(S, Z, A, B).

%%% 受動語を検索、表示(2)
passive(X) :- sentence(T),search(X, T, A, B),printnode(B),write(A),printnode(X),nl.

%%% ノードの表示(3)
printnode(N) :- atom(N),write(N).
printnode(N) :- [X|Y] = N,write(N). %% リストの場合の表示
printnode(N) :- node(X,Y,Z) = N,printnode(Y),write(X),printnode(Z).

%%% 文章データ(4)
sentence(
    node(とは,
        芸人,
        node(ところの,
            node(を,
                node(の,
                    node(または,
                        node(いる,
                            node(に,
                                node(の,
                                    node(や,
                                        node(の,
                                            なんらか,
                                            技芸
                                        ),
                                        芸能
                                    ),
                                    道
                                ),
                               [通じて,通じる]
                            ),
                            人
                        ),
                        node(とする,
                            node(て,
                                node(を,
                                    node(や,
                                        node(た,
                                            node(に,
                                                身,
                                                [備わっ,備わる]
                                            ),
                                            技芸
                                        ),
                                        芸能
                                    ),
                                    [もっ,もつ]
                                ),
                                職業
                            ),
                            人
                        ) %%%カンマいらない
                    ),
                    こと
                ),
                指す
            ),
            node(の,
                日本特有,
                概念
            )
        )
    )
).

追加したことは、(1)の二行目に、目的とする従属語がリストの場合でもtrueになるようにしたこと。swi-prologの組み込みルールのmemberを使っている。さらに、(2)の表示ルールで、項目がリストの場合も表示するように二行目を加えた。

実行例は次のようになる。

1 ?- passive(人).
なんらかの技芸や芸能の道に[通じて,通じる]いる人
true .

2 ?- passive(通じて).
なんらかの技芸や芸能の道に通じて
true .

3 ?- passive(技芸).
なんらかの技芸
true .

4 ?- passive(職業).
身に[備わっ,備わる]た技芸や芸能を[もっ,もつ]て職業
true .

5 ?- passive(もつ).
身に[備わっ,備わる]た技芸や芸能をもつ
true .

1.は、出力時のリスト処理で、リストをそのまま出力している例だ。2.は、リストの中の文字を検索し、出力している。リストの中にはもともと[通じて,通じる]という、表現型と原型が含まれていたのだが、その表現型の方を検索し、出力している。この場合は、表現型が指定されているので、リストの出力はない。3.は、末端の語を出力させただけである。4.は、二つのリストを出力した例。5.は、「または」の二つ目の山の中にある語を出力させてみた例である。

全て、問題なく、狙った通りに出力している。パチパチ!!

wikipedia「芸人」の定義とprolog(2)改良版

定義をprologの宣言文にするプロセスを書く予定だったが、少し後回しにして、前の記事の宣言文フォーマットを改訂する。

前のような構成では、「は」、「と」とか言葉をつなぐ助詞のような項目が関係子の名前として使ったが、こうすると、助詞そのものを扱うときに面倒になる。そこで、普通の二分木のように、ノードの名前を統一する。ここでは、関係子をnodeと言うように記載しよう。

日本語の文章を、完全な二分木で表現するような試みは、他にあるのだろうが、私はみていない。

二分木を構成するノードは全て次のようになっている。

node(結合子,左項,右項)

結合子は、助詞など、名詞や動詞をノリのようにつなげるものである。ノリがノードになっているのがこのシステムの最大の特徴であると言っても良い。左項や右項は、「語」か「ノード」である。

改良したプログラムの全体を以下に示しておく。

%%%%%%%%%%%%%%%%
% geinin3.swi
% 2019年1月26日
% リストの場合の処理はしていない(リスト処理バージョンは次の記事で)
%%%%%%%%%%%%%%%%

%%% 元の文章:
% 芸人とは、なんらかの技芸や芸能の道に通じている人、
% または身に備わった技芸や芸能をもって職業とする人
% のことを指す日本特有の概念である
%%%

%%% (1)節、再帰的検索
search(S,node(A,B,S),A,B).
search(S, node(_, Y, _), A, B) :- search(S, Y, A, B).
search(S, node(_, _, Z), A, B) :- search(S, Z, A, B).

%%% (2)受動語を検索、表示
passive(X) :- sentence(T),search(X, T, A, B),printnode(B),write(A),printnode(X),nl.

%%% (3)ノードの表示:再帰的処理
printnode(N) :- atom(N),write(N).
%% printnode(N) :- [X|Y] = N,write(X). %% リストの場合の表示、使っていない
printnode(N) :- node(X,Y,Z) = N,printnode(Y),write(X),printnode(Z).

%%% (4)文章データ
sentence(
    node(とは,
        芸人,
        node(ところの,
            node(を,
                node(の,
                    node(または,
                        node(いる,
                            node(に,
                                node(の,
                                    node(や,
                                        node(の,
                                            なんらか,
                                            技芸
                                        ),
                                        芸能
                                    ),
                                    道
                                ),
                               [通じて,通じる]
                            ),
                            人
                        ),
                        node(とする,
                            node(て,
                                node(を,
                                    node(や,
                                        node(た,
                                            node(に,
                                                身,
                                                [備わっ,備わる]
                                            ),
                                            技芸
                                        ),
                                        芸能
                                    ),
                                    [もっ,もつ]
                                ),
                                職業
                            ),
                            人
                        ) %%%カンマいらない
                    ),
                    こと
                ),
                指す
            ),
            node(の,
                日本特有,
                概念
            )
        )
    )
).

後半部分が、宣言に再構成された文章である。プログラムそのものの説明は後回しにして、まず、動かしてみよう。上のプログラムを geinin2.swi というテキストファイルにしたとする。

$ swipl -f geinin3.swi

で、swi-prologに読み込み、コンパイルする。以下のように、実行を試みる。

1 ?- passive(道 ).
なんらかの技芸や芸能の道
true .

2 ?- passive(人).
なんらかの技芸や芸能の道に身に
false.

3 ?- passive(概念).
日本特有の概念
true .

最初のものは、「道」にかかる言葉をフレーズとして表示している。2番目のものは、人にかかる言葉を表示させようとしているのであるが、 [通じて,通じる]にぶつかっているので、そこで表示が止まって、falseになっている。このリストの場合の処理は、次の記事(3)で対応しているので参照されたい。最後のものは、最後のフレーズから取り出してみたものだ。

まだ、完璧ではない。リストに一旦引っかかると、エラーを出してしまう面もあるようだ。それもまた、チェックしていこうと思う。

プログラムの解説をしておこう。まず(1)の部分は、基本的なルールである。その最初のものは、終端条件である。すなわち、指定された用語と、ノードの右側の項目が一致した場合は終了である。それと同時に、A,Bに当たる情報を保持するようにしている。2番目の指揮は、左側のノードへの検索であり、3番目のものは、右側のノードへの検索である。再帰的に定義されている。A,Bの語情報を維持している。

(2)は、質問の開始節である。sentence(T)は、のちに作成した二分木データをTに取り組むための関係子(?)である。sentenceは、ルートノードと考えれば良い。X,A,Bから、検索によって得られた知識を表示させている。

(3)は、再帰的に、ノードの内容を表示させて、元の文章を再現している。最初の説は、アトムの場合は、そのまま表示させるというものである。node(X,Y,Z) = N は、Nがノードであることがわかっているので、ユニフィケーションによって、X,Y,Zの値を得ているのである。

(4)は、二分木で表された文章である。二分木の作り方は他にも色々ある。これは、私が直感的に作ったものであるが、本来目指しているのは、実際のデータをcabocha等で構文解析し、JAVAのプログラムで作成しようと考えているものである。

 

wikipedia「芸人」の定義とprolog(1)

これまで説明した、prologを自然言語処理に用いる手法を少し応用することを考える。一例として、wikipediaの「芸人」という項目冒頭にある次のような文章への応用を念頭におく。

「芸人とは、なんらかの技芸や芸能の道に通じている人、または身に備わった技芸や芸能をもって職業とする人のことを指す日本特有の概念である」

これを、これまでの記事を踏まえた形で、直感的な形でprologの宣言文にする。括弧がネストしているので、タブでその関係を表現する。基本的に、すべてオペレーターをoperator、項目をpredic_x,predic_yとすると、

operator(predic_x, predic_y)

という形になっている。二分木(Binary Tree)である。

芸人のwikipedia表現したんは、prologで次のように表される。

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

かっこの対応関係が複雑で、何度も修正した。でも、最終的にprologのコンパイルに成功した。

washida:~/Project/Prolog $ swipl -f geinin.swi 
Welcome to SWI-Prolog (threaded, 64 bits, version 7.6.4)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.
Please run ?- license. for legal details.

For online help and background, visit http://www.swi-prolog.org
For built-in help, use ?- help(Topic). or ?- apropos(Word).

エラーがないではないか!!と敢えて書きたくなるくらい何度も失敗した(笑)。結局、かっこの対応関係の問題であって、基本的なフレームに差し障りがなかったのだ(かっこの対応関係のチェックは netbeansでやった)。文章をほとんどそのまま、prolog化できるというのは、とても重要なことである。

?- listing. してみると次のように、インデントが取れた形で組み込まれている。

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

素晴らしい(ほぼ自画自賛)。

クエリ(問い合わせ)もしてみる。「芸人とはなんですか?」

?- とは(芸人,X).

X = ところの(を(または(いる(に(の(や(の(なんらか, 技芸), 芸能), 道), [通じて, 通じる]), 人), の(と(て(を(や(た(に(..., ...), 技芸), 芸能), [もっ, もつ]), 職業), する), 人), こと), 指す), の(日本特有, 概念)).

ちゃんと答えてくる。ただし、述語をそのまま出していることと、..., ...の部分が省略されていることが気になる。述語をそのまま出していることについては、これからなんとでもなるだろう。述語を日本語に変換するシステムは難しくない気がするから。

先の宣言文に戻って、説明すべきことを付け加えておこう。あくまでもこれは、私が直感的に(日本語を使っている私の感覚での意味)変換した。(システマティックな変換については、次の記事にもちこそうと思っている。)

(1)三つのリストが加わっている。 [通じて,通じる]、[備わっ,備わる]、[もっ,もつ]の動詞については、使われている表現系の他に、原型を次に示しておいた。これについては、活用形をすべてリスト化するつもりでいる。また、直接的な形容詞や副詞なども、関連語とともになんらかの形で、リスト化する予定である。

(2)本来の文書にはない「ところの」という、英語の関係代名詞的なこのを付け加えて、整理している。その辺りが、日本語の曖昧さの無視できないところで、実際のwikipediaのテキストからprolog述語にするときに、こういう処理も必要になるのではないかと考えている。

prologは、リスト処理の強みも持っているので、名詞や動詞もなんらかの形でリスト化する、不可分の言葉をリストで処理するなどの、システマティックな使いからを考えていきたい。

構文解析をもとに、prolog化することについては、次の記事にする。

記憶に残ったprologのプログラム

なるほどと思ったprologのプログラムをメモがわりに記載しておく。

引用元は以下で、文字コードをEUCにする必要がある。(prologでネットを漁っていると、よくEUCで書かれていて、文字コードのメタ情報が入っていないサイトにぶつかる)
http://bach.istc.kobe-u.ac.jp/prolog/intro/list.html

% 翻訳器
replace([], []).
replace([X|L], [X1|L1]) :- rule(X, X1), replace(L, L1).
% データ
rule(i, '私').
rule(you, 'あなた').
rule(have, '持つ').
rule(love, '愛する').
rule(a, '一つの').
rule(pen, 'ペン').
rule(book, '本').

実行してみる。

?- replace([i,have,a,book], L).
L = ['私','持つ','一つの','本'] ? 

?- replace([i,love,you], L).
L = ['私','愛する','あなた'] ? 

変数Lで、リストを受け取る。最初の言葉に置き換えをして、残りに自分を再帰的に呼び出している。

語順とprolog

要は、前の記事の続きである。
次のような、prologファイルを作ろう。

%%%%%%%%%%%%%%%%%%%%%
% 事実の宣言
は(ニワトリ,生む).
で(庭,生む).
を(卵,生む).
は(学生,読む).
で(図書館,読む).
を(本,読む).
% ルール
は(X,で(Y,を(Z,A))) :- は(X,A),で(Y,A),を(Z,A).
は(X,を(Z,で(Y,A))) :- は(X,A),で(Y,A),を(Z,A).
で(Y,を(Z,は(X,A))) :- は(X,A),で(Y,A),を(Z,A).
で(Y,は(X,を(Z,A))) :- は(X,A),で(Y,A),を(Z,A).
を(Z,は(X,で(Y,A))) :- は(X,A),で(Y,A),を(Z,A).
を(Z,で(Y,は(X,A))) :- は(X,A),で(Y,A),を(Z,A).
%%%%%%%%%%%%%%%%%%%%

要するに、構文解析で、すべてが最終フレーズにかかっている、先の例の場合、語順をどう変えても、意味は通じるということを示しているのである。

例えば、

?- は(学生,を(本,で(図書館,読む))).
?- で(図書館,を(本,は(学生,読む))).
?- を(本,は(学生,で(図書館 ,読む))).

は、すべて「true」となる。ただし、この三つの言葉は、ほとんど同じ意味なのだが、逆に、微妙に違ってもいる。それは、表現できていない。