自然言語二分木を平文に変換するprologプログラム

書き忘れていたが、この間、以前も使っていた二分木を平文にするプログラムを少し改良している。AIサーバー、AIクライアントで使っているので掲載しておく。

%%
%% prologツリーをベタな文章に変換して表示する
%% 全て、変数に出力されるように改訂
%%

wsprint(Sent,Out) :- 
    nb_setval(console,''),
    printnode(Sent),
    nb_getval(console,Out).
    %% write(Out),nl.

%% グローバル変数 console に出力をつなげていく
wsappend(S) :-
    nb_getval(console,Tmp),
    atom_concat(Tmp,S,Out),
    nb_setval(console,Out).

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

%% -----------------------
%% getlistは、リストが[語, カテゴリ]から構成されているのから、語だけのリストを作る
%% 一つのフレーズに複数の語があると
%% [[[語, カテゴリ],語],[語, カテゴリ]] などのように繋がってリスト化される
%% knpがカテゴリを出力しない場合は、語が単独になることもある
%% HeadとTailをから、それぞれの語を取り出して、結合したのを出力
%% -----------------------
getlist([H|[T]],[X1, X2]) :- getlist(H,X1),
        getlist(T,X2),!.
%% 構造的に、Tailには、単位リストしか入っていない
getlist([H|[T]],[H,H1]) :- atom(H),[H1|_] = T,!.
%% tailがリストでない場合は、atomであるHeadのチェック
getlist([H|[_]],H) :- atom(H).
%% tailが構造化されたリストの場合にはここで処理する
getlist([H|[T]],[Z,T]) :- atom(T),
        getlist(H,Z).

%% -----------------------
%% ベタなリストに変換するのがprintlist
%% swi-prologにflattenという組み込み関数がある
%% -----------------------
printlist(L) :- atom(L),
        wsappend(L).
%% ベタなリスト化は、以下のclauseで単純に作れる
printlist(L) :- [H|[T]] = L,
        printlist(H),
        printlist(T),!.

%% -----------------------
%%  getlistとprintlistを繋げるのがshowlist
%% -----------------------
showlist(L) :- getlist(L,X),
        printlist(X).

実行例を示しておく。prolog二分木化したwikipediaからとってきているかなり複雑な二分木がだ、綺麗に日本語の平文に直している。

?- ['../lib/wsprint.swi'].                                                                              true.

?- wsprint(node(は,[['デビュー', 'S:サ/C:抽象物/D:メディア'], 前],node(で,[[紫苑, 'S:普/C:植物'], [名義, 'S:普/C:抽象物']],node(と,[[杏樹, 'S:人'], [紫苑, 'S:普/C:植物']],node([],[いう, 'V:いう'],node(で,[コ ンビ, 'S:普/C:組織・団体'],node(の,[[耽美, 'S:普/C:抽象物'], [系, 'S:普/C:抽象物']],node([],[漫画, 'S:普/C:人工物-その他;抽象物/D:文化・芸術'],node(を,node(の,[],[パロディ, 'S:普']),node([],[[描いて, 'V:描く'], おり],node([],[[要, 'S:普/C:人工物-その他;抽象物'], [出典, 'S:普/C:抽象物/D:文化・芸術']],node(の,[[ 江口, 'S:人'], [寿史, 'S:人']],node([],[単行本, 'S:普/C:人工物-その他;抽象物/D:文化・芸術'],node(の,[[江口, 'S:地'], [寿史, 'S:人']],node(で,なんとかなる,node([],[ショ, 'S:サ'],node(の,[[江口, 'S:地'], [寿史, 'S:人']],node(に,[[[爆発, 'S:サ/C:抽象物'], ['ディナー', 'S:普/C:人工物-食べ物/D:料理・食事']], ['ショ ー', 'S:普/C:抽象物/D:文化・芸術']],node([],[[[[収録, 'S:サ/C:抽象物/D:文化・芸術;メディア'], [さ, 'V:する']], れて], いる],[ ])))))))))))))))))),Out).

Out = デビュー前は紫苑名義で杏樹紫苑というコンビで耽美系の漫画のパロディを描いており要出典江口寿史の単行本江口寿史のなんとかなるでショ江口寿史の爆発ディナーショーに収録されている .


AIサーバーとAIクライアント:prolog二分木

先の記事では、クライアント側がjavaだったが、クライアント側もprologにした。クライアントプログラムは、先の平文を二分木にするクライアントを少し変えたものだ。

%
% prologのテレパシーサーバーに質問し、回答を得るクライアント
%

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

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

% クライアントをスタートさせて、ストリームを取得、グローバル変数に保存する
telepathy_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)).

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

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

サーバー側は、前の記事と同じで変更ない。実行結果は以下のようである。

まず、クライアント側。

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

?- telepathy_client(localhost,30000).
true.

?- telepathy_to_server(アトムとはなんですか,Reply).
Reply = アトムはロボットです.

続いて、サーバー側。

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

?- server(30000).
Receive = > アトムとはなんですか 
Send = > アトムはロボットです

これからは、知識が多い場合の処理はもう少し後にして、単純な知識が様々に利用される状況を想定してみる。

知識サーバー(プロトタイプ): prolog二分木

AI同士の対話が当面のターゲットなのだが、そのサーバー部分の作成とテストを実行した。相変わらず知識は「アトムはロボットです」しか持っていない。

まず、ネットワーク上で質問を受け取って、自らの持つ知識でそれに返答するというprologを示すと次のようになる。すでに示している reply.swi を組み込む。私は、ロボット同士のTCP-IPの通信システムは、基本、Telepathyという名前をつけている。

%%%%
%% telepathy_server.swi
%% prologサーバー
%% 参照:
%% http://www.swi-prolog.org/pldoc/man?section=stream-pools
%% utf8string.swiを使用する
%% 記事 http://www.ibot.co.jp/wpibot/?p=2681
%%%%

:- ['reply.swi'].

%% home brewでインストールしたswi-prologでは、次のライブラリが読めない可能性がある
%% その場合は、ソースからコンパイルし直し、最新バージョンを入れる
:- use_module(library(streampool)).

server(Port) :-
    %% tcpソケットの作成
    tcp_socket(Socket),
    %% ソケットをアドレスにつなげる 第二引数は、portのみか、HostPort
    tcp_bind(Socket, Port),
    %% ソケットからリクエストを受け取る、第二引数は、ペンディングリクエストの上限
    tcp_listen(Socket, 5),
    %% ソケットとコミュニケーションのためのストリームを作成する
    tcp_open_socket(Socket, In, _Out),
    %% Inが利用可能になったら、accept(Socket)が呼び出される
    add_stream_to_pool(In, accept(Socket)), 
    %% loopが空になるまでdispatch_stream_poolが呼び出される
    %% dispatch_stream_poolは入力があるとadd_stream_to_poolのGoalを呼び出す
    stream_pool_main_loop.

accept(Socket) :-
    %% ソケットからのクライアントからのリクエストを待つ
    tcp_accept(Socket, Slave, Peer),
    tcp_open_socket(Slave, In, Out), 
    add_stream_to_pool(In, client(In, Out, Peer)).

client(In, Out, _Peer) :-
    %% 入力ストリームから次の行を読み出す 結果はCommandにユニファイされる
    %% 改行までか、ファイルの終わりまで読み込まれる 改行コードは削除される
    %% 改行を含むブロックを読み込むときは read_line_to_codes/3 を使う
    read_line_to_codes(In, Codes),
    %% 入力ストリームを閉じる
    close(In),
    %% バイトシーケンスを文字列に変換する
    utf8tring(Codes,Request),
    %% コンソール出力
    format('Receive = > ~s ~n',[Request]),
    %% 回答を取得
    wsreply(Request,Reply),
    %% 回答を送信
    utf8tring(ReplyCodes,Reply),
    %% write(Out,ReplyCodes),nl(Out),
    %% write(Out,Reply),nl(Out),
    format(Out, '~s~n', [ReplyCodes]),
    format('Send = > ~s~n',[Reply]),
    flush_output(Out),
    %% 出力ストリームを閉じる
    close(Out),
    %%write('Close socket stream ...'),
    %% プールからストリームを削除する
    delete_stream_from_pool(In).        
	

このサーバー、立ち上げるとターミナルをブロックして、他のプロセスを実行させる余地がない感じになるが、prologは、簡単に実行をスレッド化できるので、大きな問題ではない。

クライアントは、ここではjavaで書いた簡単なものを使う(次の段階でこちらもprologにする)。

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;

/**
 *
 * @author washida
 */
public class JPClient {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        try {
            String server = "localhost";
            int port = 30000;
            Socket soc = new Socket(server, port);

            OutputStream os = soc.getOutputStream();
            PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os, "UTF-8")));
            InputStream is = soc.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String question = "アトムとはなんですか";
            System.out.println("質問: "+question);
            pw.print(question+"\r\n");
            pw.flush();
            String line;
            while((line = br.readLine()) != null){
                System.out.println("回答: "+line);
            }
            pw.close();
            os.close();
            br.close();
            is.close();
        } catch (IOException e) {
            System.out.println("Exception: " + e);
        }
    }
    
}

実行結果を示す。まず、javaクライアント側のコンソールは次のようになる。

質問: アトムとはなんですか
回答: アトムはロボットです

回答は、prologサーバーから送り返されてきたものである。

telepathy_server側のコンソールは次のようになる。

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

?- server(30000).
Receive = > アトムとはなんですか 
Send = > アトムはロボットです

ということである。