語とカテゴリのリストから、語だけ取り出すprologのルール

前の記事で、語とカテゴリのリストのことを書いたが、このリストから語だけを全て取り出すprologのルールをほぼ1日かけて作成した。単純なはずなのだが、なかなかうまくいかなかった。また、prologというものに、完全に馴染んでいないからだと思う。普通のc, java, pythonなどと、プログラムの考え方が、全く異なっているのだから。

ルールは、次のようになる。 %で、始まる行はコメントである。

%% headがアトムの場合、tailから取り出したものと結合する
%% 特殊な二分木なので、tailに複雑に構造化されたリストはあり得ない
%% atomか[a,b]の形のものしか、tailの要素にはならないのだ
getlist([H|[T]],[H,H1]) :- atom(H),[H1|_] = T,!.
%% headがatomならば、それを取り出す
getlist([H|[_]],H) :- atom(H),!.
%% tailがatomの場合:その場合でも、headからは、必ず1個以上のatomが該当する
getlist([H|[T]],[Z,T]) :- atom(T),getlist(H,Z),!.
%% 残されたのは、headもtailもリストの場合
getlist([H|[T]],[X1, X2]) :- getlist(H,X1),getlist(T,X2).

これをswi-prologで。いくつかの場合に適応してみた例が以下のものである。

1 ?- [user]. 
|: getlist([H|[T]],[H,H1]) :- atom(H),[H1|_] = T,!.
|: getlist([H|[_]],H) :- atom(H),!.
|: getlist([H|[T]],[Z,T]) :- atom(T),getlist(H,Z),!.
|: getlist([H|[T]],[X1, X2]) :- getlist(H,X1),getlist(T,X2).
|: ^D
% user://1 compiled 0.00 sec, 4 clauses
true.

2 ?- getlist([a,b],X).
X = a.

3 ?- getlist([a,[b,c]],X).
X = [a, b].

4 ?- getlist([[[a,b],[c,d]],[e,f]],X). 
X = [[a, c], e].

5 ?- getlist([[a,b],[c,d]],X).
X = [a, c].

6 ?- getlist([[a,b],c],X).
X = [a, c].

7 ?- getlist([[[下, '場所-機能'], [人, 人]],[菓子, '人工物-食べ物']],X).
X = [[下, 人], 菓子].

8 ?- 

最初は、ルールを[user].で読み込ませているところで、2 以降が実行結果である。4でリストが二重になっているところが気になる他は、きちんと頭を取り出して、リストにしている。7は、前の記事での例を実行させたもので、カテゴリを除く語だけ取り出していることが理解できるだろう。

ただし、すべての場合を尽くしていないので、不都合が現れるかもしれない。

swiprologで述語の入力

久しぶりに使ったら、swiprologを起動後に述語を入力する方法を忘れてしまっていたので、ここに記録しておく。

1 ?- [user].
|: jawiki(wiki_2_line_58616_1,node([],node(と,node(の,node(は,node([],node(や,node(で,一方,ソフトウェア階層),node(の,デバイス,node(と,プロトコル,[いった, いう]))),'論理レイヤー'),現行),'USB3.0'),共通で),node(では,'USB3.1Gen1モード',node(の,'5Gbps',node(と,'USB3.0',node([],同様に,node([],[使用, [でき, できる]],node(との,node(も,node(の,'Gen1モードGen2モード',いずれ),'USB3.0ハブデバイスケーブル'),node(性は,互換,node(れている,[保た, 保つ],node(ただしUSB3.0,[],node(下の,ハブ,node(は,機器,node(での,'5Gbps',node(と,転送,node([],なる,[ ])))))))))))))))).
|: ^D
% user://1 compiled 0.00 sec, 1 clauses
true.

2 ?-

日本語wikipedia全文prolog化に向けて

1月時点の日本語wikipediaのデータをxmlからテキスト化すると、10メガのファイルが544個できる。このうちの、一つをprolog化した。3時間くらいかかったと思う。swi-prologに読み込むのに数秒しかかからず、prolog上で検索させても、瞬間的に答えを出してくる。素晴らしい。

試したい方は、以下にデータを置いておきますので、ダウンロードしてみてください。
jawiki-1.zip
plologをインストールしたのち起動し、上記データを解凍して、スタートさせたprologで次のようにコマンドを打って、データと、ルールを組み込んでください。

?- ['jawiki-latest-pages-articles-001.swi','jawikirule.swi'].

例えば、?- left(言葉)とか?- left(ロボット)とか、適当にやってみてください。leftは、その言葉が他の言葉に能動的に働きかける場合、rightは、その言葉が、他の言葉を飾る場合の結果を出してきます。prologですから、セミコロンを入れると、次々に結果を拾ってきます。

ただ、これは、日本語wikipediaの1/544ののデータにすぎません。このまま544個全部をprolog化するのには、数ヶ月かかりそうだ(笑)。どうしたら早くできるのか、それが当面する課題だ。データを分割して、それぞれ別のスレッドで処理するという手はある。それでも10倍くらいのスピードになるだけだろう。それでも1週間、ぶっ続けで計算させる必要が出てくる。もう少し、速くしたい。

※その後、形態素解析、係り受け解析を、スレッド化に対応させることができる、上に書いた方向もうまくいかないことがわかった。

島崎藤村『夜明け前』の全文prolog化

文章をprolog化するというのは、文章をprologの宣言文として二分木に変換することを指している。このあたりのところの詳細は、これまでの記事をみていただくしかない。カテゴリのprologにぶら下がっている記事を参照していただきたい。

『夜明け前』は、かなりの長編である。幕末の木曽馬籠宿の名主、藤村の父親を想定した青山半蔵の人生を描いている。なぜ、この小説を選んだのか、私が高校の頃、夏、汗をかきながら必死で読んだ思い出の小説だからである。

なぜこんなことをやるのか。目指しているのは、日本語wikipedia全文をprologの宣言文に変換し。様々な形で検索できるように、そこにある「知識」を扱えるものにすることだ。そして、ロボットが、その概念的知識を使って、深みのある会話、さらには、自然なお笑いの会話をできるようにすることである。

最終目標に到達するための具体的なイメージは詰めきれていないが、当面、wikipediaのprolog化は、まずやってみようということである。が、その膨大なデータを本当に扱うことはできるのかは、最大の課題である。さしあたって、『夜明け前』をどの程度の速度で処理できるのかを見てみたかった。

青空文庫からダウンロードして、ルビを全て外したテキストは、2.4メガバイトだった。それをprologの宣言文に変換したら24.6メガ、10倍に膨らんだ。ただし、いろいろな余計な情報も出力しているので、実際には、もう少し絞れるだろうとは思う。swiprologで、これを読むこむのには、数秒しかかからなかった。十分早い、言葉を与えて、二分技を検索するのは、瞬く間に処理するので、十分早いことがわかった。

ただ、日本語wikipediaは、『夜明け前』の数百冊分はあるので、こんなスピードは実現できないのはわかっている。どこまで、手間がかかるかが知りたいところだ。

 

prolog化した『羅生門』を二分木検索する

prologのファクト文に変換した「羅生門」をどう使っていくか、使う過程で、さらにprolog化の方法をどう改良していくかが、次の課題となっている。

どう使っていくかの手始めに、先の羅生門の全文二分木化したものを検索するルールを使ってみよう。先の二分木を改良したもの(したがってJAVAプログラムそのものも改良)に、ルールを加えたswi-prolog用のスクリプトをzipで圧縮したものを以下においておく。

rashomon.zip

ダウンロードし、unzipする。冒頭に、検索のprologルールが記載され、その後、羅生門全文の二分木になっている。冒頭のルールだけを再掲すると以下のようなものである。

%%%%%%%%%%%%%%%%%%%%%%%%%
%% 「羅生門」の二分木探索
%%%%%%%%%%%%%%%%%%%%%%%%%
%%% 節(1)
% 右葉の探索
% 見つけたら、その語以外(AおよびB)を取得する
rsearch(S,node(A,B,S),A,B).
% リストの場合も受け入れる
rsearch(S,node(A,B,L),A,B) :- member(S,L). %% リストの場合の処理
% 上で一致しなかったら、左右のノードの内側を調べる
rsearch(S, node(_, Y, _), A, B) :- rsearch(S, Y, A, B).
rsearch(S, node(_, _, Z), A, B) :- rsearch(S, Z, A, B).
% 左葉の探索:基本右葉と同じ
lsearch(S,node(A,S,B),A,B).
lsearch(S,node(A,L,B),A,B) :- member(S,L).
% ここは、右と全く同じになる
lsearch(S, node(_, Y, _), A, B) :- lsearch(S, Y, A, B).
lsearch(S, node(_, _, Z), A, B) :- lsearch(S, Z, A, B).

%%% 検索、表示(2)
% 検索語を右側にした部分文章
right(X) :- rashomon(P,T),rsearch(X, T, A, B),write(P),write(': '),printnode(B),printnode(A),printnode(X),nl.
% 検索語を左側にした部分文章
left(X) :- rashomon(P,T),lsearch(X, T, A, B),write(P),write(': '),printnode(X),printnode(A),printnode(B),nl.

%%% ノードの表示(3)
% 対象がatomならば、そのまま表示
printnode(N) :- atom(N),write(N).
% 対象が空でないリストならば、最初の項の表示
printnode(N) :- [X|_] = N,printnode(X). 
% 対象が空リストならば'/'(半角スラッシュの表示)
printnode(N) :- [] = N,write('/'). %% 空リストでもtrueにする
% 対象がnodeならば、元の言葉の順序で表示(葉がnodeならば再帰的に表示する:ただし、node語がnodeは含まない)
printnode(N) :- node(X,Y,Z) = N,printnode(Y),printnode(X),printnode(Z).

このprologは、二分木の右の葉から語句を検索するものと、左の葉から検索するものの、二つのルールからなっている。

実行例を以下に示そう。

$ swipl ← ターミナルからswirl-prologを起動する
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).

1 ?- ['rashomon.swi']. ← 解凍したRashomon.swiを読み込む
true.

2 ?- left(下人). ←  左の葉から、「下人」を検索する、パラグラフ、行番号とともに結果が出力される。1行しか示されないので、次を示すためには; セミコロンを入れる
line_0_1: 下人が羅生門の下で雨やみを待っていた/
line_3_6: 下人は七段/ある/石段の一番上の段に洗い/ざらした/紺の襖の尻を据えて/右の頬に出来た//大きな面皰を気にしながらぼんやり/雨のふるのを眺めていた/
line_4_0: 下人が雨やみ
line_4_1: 下人は雨がやんでも格別/どう/しようと云う/当てはない//
line_4_6: 下人が雨やみ
line_4_6: 下人が行き所がなくて/途方にくれていたと云う/方が適当である//
line_4_7: 下人のSentimentalismeに影響//
line_4_9: 下人は何をおいても差当り明日の暮しをどうにか/しようと云わば/どうにも/ならない事をどうにか/しようととりとめもない/考えをたどりながらさっきから朱雀大路にふる/雨の音を聞くともなく/聞いていたのである/
line_6_3: 下人の考え
line_6_5: 下人は手段
line_7_0: 下人は/大きな嚔をして/それから大儀/そうに立/上った//
line_8_0: 下人は頸をちぢめながら山吹の汗袗に重ねた/紺の襖の肩を高くして門のまわりを見まわした/
line_8_4: 下人はそこで腰にさげた/聖柄の太刀が鞘/走らないように気をつけながら藁草履をはいた/足をその/梯子の一番下の段へふみかけた/
line_9_4: 下人は始め
line_10_0: 下人は守宮のように足音をぬすんで/やっと/急な/梯子を一番上の段まで這うようにして上り//
line_12_0: 下人はそれらの死骸の腐爛/した/臭気に思わず/鼻を掩った/
line_13_0: 下人の眼はその/時/はじめて/その/死骸の中に蹲っている人間を見た//
line_14_0: 下人は六分の恐怖と四分の好奇心とに動かされて暫時は呼吸をするのさえ忘れていた/
line_15_0: 下人の心から
line_16_0: 下人には勿論/何故/老婆が死人の髪の毛を抜くかわからなかった/
line_16_2: 下人にとってはこの/雨の夜にこの/羅生門の上で死人の髪の毛を抜くと云う/事がそれだけで既に/許す/べからざる悪であった/
line_17_0: 下人は両足
line_20_0: 下人は老婆が死骸につまずきながら慌てふためいて/逃げようと行手を塞いで/こう/罵った//
line_20_2: 下人はまたそれを行かすまいと押しもどす//
line_20_5: 下人はとうとう/老婆の腕をつかんで/無理に/そこへ○/じ/倒した//
line_22_0: 下人は老婆
line_22_3: 下人は始めて/明白に/この/老婆の生死が全然/自分の意志に支配れていると云う/事を意識//
line_22_6: 下人は老婆
line_26_0: 下人は老婆の答が存外/平凡なのに失望//
line_29_0: 下人は太刀を鞘におさめて/その/太刀の柄を左の手でおさえながら冷然として/この/話を聞いていた/
line_29_2: 下人の心には/ある勇気が生まれて来た/
line_29_5: 下人は饑死をするか盗人になるかに迷わなかったばかりではない/
line_31_0: 下人は嘲るような声で念を押した//
line_33_0: 下人はすばやく/老婆の着物を剥ぎとった//
line_33_3: 下人は剥ぎとった/檜皮色の着物をわきにかかえて/またたく間に/急な/梯子を夜の底へかけ/下りた//
line_35_0: 下人の行方は誰も知らない/

3 ?- right(下人). % 続いて、右の葉から「下人」を検索する
line_4_5: 今/この/下人
line_15_4: この/下人
line_15_4: 恐らく/下人
line_16_3: 勿論/下人
line_20_1: それでも下人
line_24_0: その/下人
line_24_4: 喘ぎ喘ぎ/下人

左の葉から下人を選択すると、下人についてのアクティブな文章を出力される。右の葉の場合は、もっと、受動的なものとなるが、下人がそのような受動的な扱いをされている文章がより少ないことがわかる。それがどこの文章に当たるかは、冒頭の line_XX_XX の部分でわかる。

どのような文章を引き出すかは、その文章に関するリスト構造と、ルートフレーズをどのように設定してあるかに依存する。

芥川龍之介『羅生門』全文をprolog宣言文に変換した

この間作成したJAVAの変換プログラムで、青空文庫にある芥川龍之介「羅生門」の全文を、二分木のprologの宣言文にした(プログラムは、その後改定されているが、まだgithubには反映されていない)。結果は、ベタでここに載せるのは長すぎるので、以下のzipファイルをダウンロードし解凍して、適当なテキストエディタでご覧ください。

jprolog.swi.zip

具体的な手法などは、前の記事を参照のこと。変換にかかった時間は、1分2.832秒で、許容範囲。

swiprologに読み込にかかる時間は、ほんの一瞬。listingした結果(一部)は以下のような感じだ。

washida:~/Project/Robot/MakeKnowledge/JProlog/data $ swipl -f jprolog.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).

1 ?- listing.

:- dynamic exception/3.
:- multifile exception/3.


plsample19(line0, node(へ, node([], [], どこ), node([], 行く, []))).
plsample19(line1, node([], [], [])).

plsample11(line0, node(あるが, node(の, node(と, 見る, 楼), node(には, 内, node(に, 噂, node([], [聞いた, 聞く], node([], 通り, node(つかの, 幾, node(が, 死骸, node([], 無造作に, [棄てて, 棄てる])))))))), node(の, 火, node(の, 光, node([], 及ぶ, node(が, 範囲, node(より, [思った, 思う], node(ので, 狭い, node(は, 数, node(つとも, 幾, node(ない, [わから, わかる], []))))))))))).
plsample11(line1, node(である, node(ながら, node([], ただ, おぼろげ), node(は, 知れるの, node(に, node([], その, 中), node(と, node(の, 裸, 死骸), node(を, 着物, node([], [着た, 着る], node(とが, 死骸, node(と, ある, node([], いう, 事))))))))), [])).
plsample11(line2, node(いるらしい, node(には, node([], 勿論, 中), node(も, 女, node(も, 男, [まじって, まじる]))), [])).
plsample11(line3, node(だと, node(は, node([], node([], そうして, その), 死骸), node(が, node([], 皆, それ), node([], かつて, node(いた, [生きて, 生きる], 人間)))), node([], 云う, node(さえ, 事実, node(れる, [疑わ, 疑う], node([], ほど, node(を, 土, node([], [捏ねて, 捏ねる], node([], [造った, 造る], node(のように, 人形, node([], node(を, 口, [開いたり, 開く]), node(を, 手, node(して, [延ばしたり, 延ばす], node(の, node([], ごろごろ, 床), node(に, 上, node(いた, [ころがって, ころがる], [])))))))))))))))).
plsample11(line4, node(しかも, [], node(とか, 肩, node(とかの, 胸, node(なっている, 高く, node(に, 部分, node(の, node([], node([], ぼんやり, [した, する]), 火), node(を, 光, node([], [うけて, うける], node(の, node(なっている, 低く, 部分), node(を, 影, node([], 一層, node(しながら, 暗く, node(に, 永久, node(の如く, 唖, node(いた, [黙って, 黙る], [])))))))))))))))).

plsample35(line0, node(の, 下人, node(は, 行方, node(も, 誰, node(ない, [知ら, 知る], []))))).
・・・・・・
・・・・・・
以下延々と続く

使い方のヒントは、例えばこの記事を参考に。

『吾輩は猫である』冒頭のprolog化

作成したJAVAプログラムJPrologで、夏目漱石「吾輩は猫である」の冒頭部分のパラグラフ全体をprolog化してみた。KNPが一度の処理を300字くらいを限界にしているので、パラグラフ全体を一挙にはできず、文章一つづつのprolog化になり、全体で3.5秒ほどかかった。

まだ、形態素解析の部分や、ノードとリーフの語の形成に不満はあるが、それは後でじっくり治していこうと思う。まず、どれだけやれるのかを確かめたい。この後、さらに長い文書をやらせようと思っている。

swiprologに読み込ませたが、いくつか問題を克服したのち、以下の状態では、正常に読み込んでいる。主に問題は、動詞の活用形と原型のリストをめぐってのものだった。仕様を、いかに記載したように改訂した。

plsample(line0,
    node(は,
        吾輩,
        node(である,
            猫,
            [ ]
        )
    )
).
plsample(line1,
    node(は,
        名前,
        node([],
            まだ,
            node([],
                無い,
                [ ]
            )
        )
    )
).
plsample(line2,
    node(か,
        node(で,
            どこ,
            [生れた, 生れる]
        ),
        node([],
            とんと,
            node(が,
                見当,
                node(ぬ,
                    [つか, つく],
                    [ ]
                )
            )
        )
    )
).
plsample(line3,
    node(だけは,
        node(いた,
            node([],
                node(で,
                    node([],
                        node([],
                            node([],
                                node([],
                                    何でも,
                                    薄暗い
                                ),
                                じめじめ
                            ),
                            [した, する]
                        ),
                        所
                    ),
                    ニャーニャー
                ),
                [泣いて, 泣く]
            ),
            事
        ),
        node(いる,
            [記憶, [して, する]],
            [ ]
        )
    )
).
plsample(line4,
    node(は,
        吾輩,
        node(で,
            ここ,
            node([],
                [始めて, 始める],
                node(を,
                    node([],
                        node(と,
                            人間,
                            いう
                        ),
                        もの
                    ),
                    node([],
                        [見た, 見る],
                        [ ]
                    )
                )
            )
        )
    )
).
plsample(line5,
    node(しかも,
        [],
        node(と,
            node(で,
                あと,
                聞く
            ),
            node(は,
                それ,
                node(と,
                    書生,
                    node([],
                        いう,
                        node(中で,
                            人間,
                            node([],
                                一番,
                                node(な,
                                    獰悪,
                                    node(であったそうだ,
                                        種族,
                                        [ ]
                                    )
                                )
                            )
                        )
                    )
                )
            )
        )
    )
).
plsample(line6,
    node(は,
        node(と,
            node([],
                この,
                書生
            ),
            いうの
        ),
        node(を,
            node([],
                時々,
                我々
            ),
            node([],
                [捕まえて, 捕まえる],
                node([],
                    [煮て, 煮る],
                    node(と,
                        食う,
                        node([],
                            いう,
                            node(である,
                                話,
                                [ ]
                            )
                        )
                    )
                )
            )
        )
    )
).
plsample(line7,
    node(しかし,
        [],
        node(は,
            node([],
                その,
                当時
            ),
            node([],
                node([],
                    node([],
                        node(から,
                            node(も,
                                node(何という,
                                    [],
                                    考
                                ),
                                なかった
                            ),
                            別段
                        ),
                        恐
                    ),
                    [し, する]
                ),
                node([],
                    いとも,
                    node(なかった,
                        [思わ, 思う],
                        [ ]
                    )
                )
            )
        )
    )
).
plsample(line8,
    node(の,
        node([],
            ただ,
            彼
        ),
        node(に,
            掌,
            node(られて,
                [載せ, 載せる],
                node(と,
                    スー,
                    node(られた,
                        [[持ち, 持つ], [上げ, 上げる]],
                        node([],
                            時,
                            node([],
                                何だか,
                                node([],
                                    フワフワ,
                                    node([],
                                        [した, する],
                                        node(が,
                                            感じ,
                                            node(ばかりである,
                                                [あった, ある],
                                                [ ]
                                            )
                                        )
                                    )
                                )
                            )
                        )
                    )
                )
            )
        )
    )
).
plsample(line9,
    node(の,
        掌,
        node(で,
            上,
            node(の,
                node([],
                    node([],
                        少し,
                        [落ちついて, 落ちつく]
                    ),
                    書生
                ),
                node(を,
                    顔,
                    node(が,
                        [[見た, 見る], の],
                        node(いわゆる,
                            [],
                            node(と,
                                人間,
                                node([],
                                    いう,
                                    node(の,
                                        もの,
                                        node(であろう,
                                            見始め,
                                            [ ]
                                        )
                                    )
                                )
                            )
                        )
                    )
                )
            )
        )
    )
).
plsample(line10,
    node(が,
        node([],
            node(だと,
                node([],
                    node([],
                        node([],
                            この,
                            時
                        ),
                        妙な
                    ),
                    もの
                ),
                [思った, 思う]
            ),
            感じ
        ),
        node(でも,
            今,
            node(いる,
                [残って, 残る],
                [ ]
            )
        )
    )
).
plsample(line11,
    node(れ,
        node([],
            node(を,
                第一毛,
                [もって, もつ]
            ),
            [装飾, [さ, する]]
        ),
        node(の,
            べきはず,
            node(が,
                顔,
                node([],
                    つる,
                    node([],
                        [つるして, つるす],
                        node([],
                            まるで,
                            node(だ,
                                薬缶,
                                [ ]
                            )
                        )
                    )
                )
            )
        )
    )
).
plsample(line12,
    node(が,
        node([],
            node(にも,
                node([],
                    その後,
                    猫
                ),
                だいぶ
            ),
            [逢った, 逢う]
        ),
        node(が,
            node([],
                node(度も,
                    node(には,
                        node([],
                            こんな,
                            片輪
                        ),
                        一
                    ),
                    [出会した, 出会す]
                ),
                事
            ),
            node([],
                ない,
                [ ]
            )
        )
    )
).
plsample(line13,
    node([],
        [のみ, のむ],
        node(ず,
            [なら, なる],
            node(の,
                顔,
                node(が,
                    真中,
                    node([],
                        あまりに,
                        node(いる,
                            [突起, [して, する]],
                            [ ]
                        )
                    )
                )
            )
        )
    )
).
plsample(line14,
    node([],
        そうして,
        node(の,
            node([],
                その,
                穴
            ),
            node(から,
                中,
                node(を,
                    node(と,
                        node([],
                            時々,
                            ぷうぷう
                        ),
                        煙けむり
                    ),
                    node([],
                        吹く,
                        [ ]
                    )
                )
            )
        )
    )
).
plsample(line15,
    node(くて,
        node([],
            どうも,
            咽むせぽ
        ),
        node([],
            実に,
            node([],
                [弱った, 弱る],
                [ ]
            )
        )
    )
).
plsample(line16,
    node(が,
        これ,
        node(は,
            node(である,
                node([],
                    node(と,
                        node([],
                            node(の,
                                人間,
                                飲む
                            ),
                            煙草
                        ),
                        いう
                    ),
                    もの
                ),
                事
            ),
            node([],
                ようやく,
                node([],
                    この頃,
                    node([],
                        [知った, 知る],
                        [ ]
                    )
                )
            )
        )
    )
).

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で書き直そうと思う。