知識にもとづき質問に答える: 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:科学・技術'], [])) .