知識にもとづき質問に答える: prolog二分木

みずからが持っている知識にもとづき、問いに答えるシステムのプリミティブなものを作った。持っている知識は次のような「アトムはロボットです」という知識だけであるとする。現実には、日本語wikipediaの膨大な知識を持っているのだが、それを利用するのはもう少し後にする。

知識は、knowledgeというfanctorで表現されているとする。%で始まる行は、prologのコメント文である。

%% 知識 = アトムはロボットです
%% phrases: [ r0 1 ] 
knowledge(testline_0_0,
    node(は,
        [アトム, 'S:普/C:自然物/D:科学・技術'],
        node(です,
            [ロボット, 'S:普/C:人工物-その他/D:科学・技術'],
            [ ]
        )
    )
).

ここで、「アトムとはなんですか」という問いがあったとする。それに回答するprologプログラムは次のようなものである。

% 
% 質問に答えるプログラム
% 

:- ['../lib/client.swi'].
:- ['../lib/wsprint.swi'].
:- create_client(localhost,25000).

reply(Sentence,Out) :- 
        chat_to_server('GETTREE',Sentence,Recv),
        %% write(Recv),nl,
        %% utf8string.swiから出てくる文字列は、string!! 
        %% このままでは、unificationに失敗するので、termに変換する
        term_string(Recv1,Recv),
        dialog(_,Tree) = Recv1,
        %% write(Tree),nl,
        isquestion(Tree),
        getsubject(Tree,Sub),
        getreply(Sub,Out),
        wsprint(Out).

%% ----- 疑問文 回答取得 -----
getreply(Subject,Out) :- knowledge(_,Tree),chkreply(Tree,Subject),Out=Tree.
%% 主語のフレーズが一致していたらそれを回答とみなす
chkreply(node(N,L,_),Subject) :- member(N,[は, とは, って]),L=Subject,!.
chkreply(node(_,L,_),Subject) :- chkreply(L,Subject),!.
chkreply(node(_,_,R),Subject) :- chkreply(R,Subject).

%% ----- 疑問文 主語取得 -----
getsubject(node(N,L,_),Out) :- member(N,[は, とは, って]),L=Out,!.
getsubject(node(_,L,_),Out) :- getsubject(L,Out),!.
getsubject(node(_,_,R),Out) :- getsubject(R,Out).

%% ----- 疑問文 チェック -----
%% ノード値がリストのいずれかの語で、右の葉が空リスト [ ] の場合、疑問文 
isquestion(node(N,_,[])) :- member(N,[ですか,なの,か,なのか]),!.
isquestion(node(_,L,_)) :- isquestion(L),!.
isquestion(node(_,_,R)) :- isquestion(R).

%% 知識 = アトムはロボットです
%% phrases: [ r0 1 ] 
knowledge(testline_0_0,
    node(は,
        [アトム, 'S:普/C:自然物/D:科学・技術'],
        node(です,
            [ロボット, 'S:普/C:人工物-その他/D:科学・技術'],
            [ ]
        )
    )
).

プログラムの最後に、先ほどの知識が加えられている。もしこの知識が多く、あるいは、複雑になれば、それらを調べることになる。

reply()が、topレベルのclauseである。質問文(Sentence)は、平文であり、それらはサーバーに問い合わせして二分木にして返してもらっている(chat_to_server('GETTREE',Sentence,Recv),)。サーバーから返ってきた二分木は、swi-prologのstringであり、このまま dialog(_,Tree) = Recv, などとunificationすると失敗する。この理由がわからなくて、半日くらい無駄にした。term_string(Recv1,Recv),で、stringを文字列に変換している。返ってきたものから、二分木だけを取り出し(dialog(_,Tree) = Recv1,)、質問文であるかどうかをチェックし(isquestion(Tree),)、質問文であるならば、質問の主語にあたるものを取り出し、知識に問い合わせして、回答に当たる知識を得る(getreply(Sub,Out),)、という段取りである。

実行結果は次のようになる。

?- ['reply.swi'].
true.

?- reply(アトムとはなんですか,Out).

アトムはロボットです/

Out = node(は, [アトム, 'S:普/C:自然物/D:科学・技術'], node(です, [ロボット, 'S:普/C:人工物-その他/D:科学・技術'], [])) .

文章を与えて、サーバーからprolog二分木を取ってくるクライアント

質問文二分木の作成をやってきたが、ここで、質問を受け取って、自分の知識に基づいて解答を作成するシステムを作る。

質問は、平文で受け取るので、そこから知識の二分木に結びつけなければならないのだが、一旦、質問文を二分木に変換するのがいいと思った。prologで作成しようと思ったが、いろいろ面倒なので、これまでのjavaの作成システムにサーバー機能も持たせて、そこにアクセスして単文を二分木にしようと思う。

prologのクライアントを作成した。

%
% 文章を与えて、サーバーからprolog二分木を取ってくるクライアント
%

% 文字列とutf8のバイトコードを相互に変換する
% http://www.ibot.co.jp/wpibot/?p=2681 など参照
:- ['utf8string.swi'].

% swi-prologモジュールの組み込み
:- use_module(library(streampool)).

% クライアントをスタートさせて、ストリームを取得、グローバル変数に保存する
create_client(Host, Port) :-
        setup_call_catcher_cleanup(tcp_socket(Socket),
            tcp_connect(Socket, Host:Port),
            exception(_),
            tcp_close_socket(Socket)),
        setup_call_cleanup(tcp_open_socket(Socket, In, Out),
            nb_setval(socketIn,In),
            nb_setval(socketOut,Out)).

% 単文を送信して、二分木を受け取る
chat_to_server(Term) :-
        % 送信文字列をコードに変換する
        utf8tring(Bytes,Term),
        nb_getval(socketIn,In),
        nb_getval(socketOut,Out),
        % コマンドをつけて、サーバーに送信
        format(Out, 'GETTREE:~s~n', [Bytes]),
        flush_output(Out),
        %read(In, ReplyCode), % これではうまくいかない
        % サーバーからコードを受信する
        read_line_to_codes(In, ReplyCode),
        %write(ReplyCode),
        % コードを文字列に変換
        utf8tring(ReplyCode,Reply),
        % 表示する
        format('Reply: ~s~n', [Reply]).

% ストリームを閉じる
close_connection :-
        nb_getval(socketIn,In),
        nb_getval(socketOut,Out),
        close(In, [force(true)]),
        close(Out, [force(true)]).

prologは、utf8のバイトコードで送受信するので、その変換が必要だった。すでに作成したシステムがあったので、簡単に済ますことができた。

実行結果は次のよう。

?- ['../client.swi'].
true.

?- create_client(localhost,25000).
true.

?- chat_to_server(アトムはロボットですか).
Reply: dialog(190520092729_0_0,node(ですか,node(は,[アトム, 'S:普/C:自然物/D:科学・技術'],[ロボット, 'S:普/C:人工物-その他/D:科学・技術']),[ ])).
true.