prolog二分木における副詞の扱い

副詞の扱いに問題があることが判明した。例えば、「ロボットとともに人工知能も注目された」という二分木がこんな感じになってしまう。

testdoc(testline_0_0,
    node(と,
        [[ロボット, 'C:人工物-その他'], ともに],
        node(も,
            [[人工, 'C:抽象物'], [知能, 'C:抽象物']],
            node([],
                [[[注目, 'C:抽象物'], [さ, 'V:する']], れた],
                [ ]
            )
        )
    )
).

これがおかしいのは、「ロボットともに」が左の葉で、ノード値が「と」になっていることだ。もともと、「ロボットとともに」が一つの句の中にあったのに、「と」が助詞で、ノード値に入れられたのちに、副詞の「ともに」が現れ、副詞は一般に左右の葉の値となるものなので、ロボットに継ぎ足されたのである。

一般の副詞の場合、例えば次のようになる。

testdoc(testline_0_0,
    node(の,
        node(には,
            [広場, 'C:場所-施設'],
            かなり
        ),
        node(が,
            [人, 'C:人'],
            node([],
                [[集まって, 'V:集まる'], いた],
                [ ]
            )
        )
    )
).

この場合、「かなり」が副詞で、右の葉に入っていて、不自然さはない。

そこで、すでに、助詞がノード値として入っているときに副詞があらわれたら、それはノード値につなげるようにした。もともと、ノード値は、基本的に、体言や用言のリーフ値をつなげるものなので、品詞で厳密に分けているのではないから、それでいい。

そのようにフォーマットを変えると、次のようになる。

testdoc(testline_0_0,
    node(とともに,
        [ロボット, 'C:人工物-その他'],
        node(も,
            [[人工, 'C:抽象物'], [知能, 'C:抽象物']],
            node([],
                [[[注目, 'C:抽象物'], [さ, 'V:する']], れた],
                [ ]
            )
        )
    )
).

大きな問題はない。これでいこう。ただ、もう少し改定点がまとまってから、wikipediaやtwitterの作り直しをやろう。

prolog二分木のフォーマットを変更する

prolog二分木から初歩的な文章を作るようになったが、少しフォーマットを変更することにした。

一つは、動詞の原形を入れるときに、使われている表現形と同じ場合は、原形情報を与えなかったが、これを与えることにした。動詞と分かれば、原形がなければそれが原形と推測できるが、そもそも二分木には品詞情報を入れていないので、判断できないからだ。

なぜ、これが大事なのかといえば、動詞の原形は、それだけで文章終端になりうるからだ。文章を作るときに、終端が違和感なく終わることはとても大事なのである。このように「だ」「です」「である」などは、多分判定詞で、同士ではないが、例えば「書き」で終わったら、言葉としておかしいが「書く」で終わることは不自然ではない。だから、動詞の原形はとても重要なので、原形がある場合とない場合がはっきりしないというのは、良くないということで、改良した。

もう一つは、動詞の原形と名詞のカテゴリは、ほとんど同じフォーマットで入れ込んでいるが、ときに区別できなくなるときがあるので、それらの冒頭に、名詞のカテゴリの場合は「C:」というヘッダ、動詞の原形の場合は「V:」というヘッダーをつけることにした。例えば、次のようである。

jawiki(wiki_543_line_2261_1,node(を,小田,node([],[含む, 'V:含む'],node(は,['4', [名, 'C:抽象物']],node(に,node([],node(ばかりの,node(が,[放送, 'C:抽象物'],[[終了, 'C:抽象物'], [した, 'V:する']]),[アニメ, 'C:抽象物']),[[[機動, 'C:抽象物'], [戦士, 'C:人']], ガンダム]),node([],[[[熱中, 'C:抽象物'], [して, 'V:する']], おり],node(の,node([],node([],node(に,node(から,node([],node(が,node([],まだ,ガンプラ),[[[発売, 'C:抽象物'], [さ, 'V:する']], れる]),前),[同, [作品, 'C:抽象物']]),[[登場, 'C:抽象物'], [する, 'V:する']]),[[ロボット, 'C:人工物-その他'], [兵器, 'C:人工物-その他']]),'モビルスーツ(MS)'),node(を,[模型, 'C:人工物-その他'],node([],[[[自作, 'C:人工物-その他'], [して, 'V:する']], いた],[ ]))))))))).

二分木を一行にしているので少しわかりにくいのでインデントをつけて表すと次のようになる。

jawiki(wiki_543_line_2261_1,
    node(を,
        小田,
            node([],
                [含む, 'V:含む'],
                node(は,
                    ['4', [名, 'C:抽象物']],
                    node(に,
                        node([],
                            node(ばかりの,
                                node(が,
                                    [放送, 'C:抽象物'],
                                    [[終了, 'C:抽象物'], [した, 'V:する']]
                                ),
                                [アニメ, 'C:抽象物']
                            ),
                            [[[機動, 'C:抽象物'], [戦士, 'C:人']], ガンダム]
                        ),
                        node([],
                            [[[熱中, 'C:抽象物'], [して, 'V:する']], おり],
                            node(の,
                                node([],
                                    node([],
                                        node(に,
                                            node(から,
                                                node([],
                                                    node(が,
                                                        node([],
                                                            まだ,
                                                            ガンプラ
                                                        ),
                                                        [[[発売, 'C:抽象物'], [さ, 'V:する']], れる]
                                                    ),
                                                前
                                            ),
                                            [同, [作品, 'C:抽象物']]
                                        ),
                                        [[登場, 'C:抽象物'], [する, 'V:する']]
                                    ),
                                    [[ロボット, 'C:人工物-その他'], [兵器, 'C:人工物-その他']]
                                ),
                                'モビルスーツ(MS)'
                            ),
                            node(を,
                                [模型, 'C:人工物-その他'],
                                node([],
                                    [[[自作, 'C:人工物-その他'], [して, 'V:する']], いた],
                                    [ ]
                                )
                            )
                        )
                    )
                )
            )
        )
    )
).

これで、例えば、最初の「含む」は、原形と表現形が同じだが、原形を付加している。また、動詞なのでヘッダにV:が付いている。これで、処理がよりしやすくなった。

これで、一昨日から、日本語wikipedia本文とツイッターデータを全部作り直している。

 

NAOのIPアドレスを固定する

近々のオーディションに向けて、ロボットを準備している。データをロボット同士、パソコンとロボットでやり取りするのに、テレパシーライブラリを使うが、どうしてもwifiのIPアドレスを固定したい。前のwi-fiルーターは、ほとんど割り振るipアドレスを変えなかったのだが、新しいwi-fiルーターはすぐに変えてしまうのでとても不便。

そう思って、半日、格闘してなんとかなった。記録しておく。

当初、Gentoo LinuxでどうIPアドレスを固定するのかを見ていて、色ころ試みたが、うまくいかない。ifconfigで一時的な固定はできるが、初期時点で無理に設定しようとすると(/etc/local.d/に実行ファイルを入れるなど)うまくいかなくなる。そこで、次のサイトに行き当たった。

によれば、NAOの場合、ifconfigではなく、connmanというコマンドを使うということだ(今まで使ったことがない)。ただ、なぜか私のNAOは旧ウェッブページが開かれないので、sshで接続したNAOのターミナルで処理をした。

nao [0] ~ $ su -
Password: ←(1)

root@nao [0] ~ # connman services ←(2)
* AO Wired           { ethernet_xxxxxxxxxxxxxx_cable }
* AR HUAWEI-CC30     { wifi_xxxxxxxx_xxxxxxxxxxxx_managed_psk }

root@nao [0] ~ # connman remove wifi_xxxxxxxx_xxxxxxxxxxxx_managed_psk ←(3)

root@nao [0] ~ # /usr/lib/connman/test/set-ipv4-method wifi_xxxxxxxx_xxxxxxxxxxxx_managed_psk manual 192.168.X.XX 255.255.255.0 192.168.X.1 ←(4)

Setting method manual for wifi_xxxxxxxx_xxxxxxxxxxxx_managed_psk
New IPv4.Configuration:  {'Netmask': dbus.String(u'255.255.255.0', variant_level=1), 'Gateway': dbus.String(u'192.168.X.1', variant_level=1), 'Method': dbus.String(u'manual', variant_level=1), 'Address': dbus.String(u'192.168.X.XX', variant_level=1)} ←(5)

上の、手順は、(1)PC(私の場合はMacのターミナル)から、sshでロボットと接続し、rootにログインする。(2)connmanコマンドで、wifiのサービス設定を検索する。(3)そのサービスを、connman removeで削除する。(4)そのサービス名を使って、新たにwi-fiのネットワーク設定を行う。(5)結果が出力される

これで、再起動でも、この設定が使われて、IPアドレスは固定される。ただし、これだけでは、ネームサーバーが設定されておらず、接続はできるが、起動時にクラウドエラーなどを引き起こす。私の場合は、cloudに依存していることはないので、今のところ確認できる実害はないが、このエラーは避けるべきだ。そこで、/etc/resolve.confを設定する。

wi-fiルータの場合、大概、ゲートウェイアドレスを設定しておけば、ネームサーバーの用をなしてくれる。マニュアルに記載されていると思うが、ほとんどの場合、ゲートウェイアドレスを指定しておけば良いと思う。例えば、Xは、ネットワーク番号で、

nameserver 192.168.X.1

という感じか。

wifiの方(デバイス名、wlan0)は、このように固定しておいて、ワイヤーでつなぐ方(デバイス名 eth0)は、DHCPのままの方が、いざという時に、繋げることが可能だ。こちらまでIPを固定すると、固定したネットワークとは違うネットワーク環境で、ロボットにアクセスする方法がなくなってしまうからだ。

これで、ロボットのIPアドレスの割り当てが変わる心配がなくなった。よかった。

文章をprologの二分木で作る

ようやく、ここに到達した。まだ事始めではあるが、ぼちぼちとやっていこう。

次のようなprologプログラムを考える。

%% -----------------------
%% 文章(二分木化されたprolog宣言文)を組み立てる
%% 2019年4月22日
%% -----------------------

%% 空のツリーに、Nodeを与えると、それ自身を返す
insert(_,Node,[],Node).
%% 既存ツリーが語の場合
insert(left,node(Value,Left,[]),Word,node(Value,Left,Word)) :- 
        atom(Word).
insert(right,node(Value,[],Right),Word,node(Value,Word,Right)) :- 
        atom(Word).
%% すでにTreeがある場合
insert(left,Node,node(Value, Left, Right), node(Value, New, Right)) :-
        insert(left,Node,Left,New).
insert(right,node(Value0, Left0, []),node(Value, Left, Right), node(Value, Left, node(Value0, Left0, Right))).
insert(right,Node,node(Value, Left, Right), node(Value, Left, New)) :-
        insert(right,Node,Right,New).

特に工夫もない、アドホックなもので、文章のノードを与えて、ツリーを作るところから始めてみようというわけである。実行すると次のようになる。

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

?- insert(_,node(は,ロボット,[]),[],Subs).
Subs = node(は, ロボット, []) .

?- insert(right,node(です,機械,[]),node(は, ロボット, []),Subs).
Subs = node(は, ロボット, node(です, 機械, [])) .

?- insert(left,node(の,産業用,[]),node(は,ロボット,node(です,機械,[])),Subs).
Subs = node(は, node(の, 産業用, ロボット), node(です, 機械, [])) .

?- insert(right,node(の,産業用,[]),node(は,ロボット,node(です,機械,[])),Subs).
Subs = node(は, ロボット, node(の, 産業用, node(です, 機械, []))) ;
Subs = node(は, ロボット, node(です, 機械, node(の, 産業用, []))) .

最初のコマンドで、プログラムを読み込む。なんの条件も考えずに、ただ、ノードを付け加えている。後半の二つは、node(の,産業用,[]) というノードを作成済みツリーの左側と右側に付け加えている。

この結果を出すために、作ったようなプログラムなので、あえて議論しなくても良い。ただ、二分木で表されたprologの文章は、ある種プログラムであって、プログラムがプログラムを作る感じを確認したかっただけである。

AIどうしに会話させる

帽子を買いに、妻と池袋のサンシャイン通りを歩いていたときに思いついたことだ。

日本語wikipediaの本文全文や1億1千万のツイートのprolog二分木化された言語的知識はできている。これに基づいて会話的言葉を創作させるのが次の課題だ。どうして実現するのか、ぼんやりとしたイメージのようなものしかなかったが、言葉の文章を創造するAIどうしに会話させて、その会話にスコアを与えて、いい会話を作る能力を形成することができるような気がしてきた。

AIどうしに会話させれば、膨大な会話データを自動的に取得できる。スコアの作り方を改良していけば、会話がより自然なものになり、また、AIの会話的能力も高めることができる。

prologの二分木による宣言文を複数語で検索する

文章をロジカルに二分木として作成する課題の初めに、膨大な二分木を複数語で検索するというの自在にできるようにしたい。

複数のキーワードをリストで与えて、二分木の中からそれらを同時に持つものを抽出する。

例えば、次のような文章を考える(実際 wikipediaの一文)。
「これに基づいたゲンギスと呼ばれる六本足のロボットは、いわゆる脳を持たないにも関わらず、まるで生きているかのように行動する。」

これをすでに何度か述べた方法(javaで書いたプログラム)でprologの二分技化すると次のようになる。

node(の, 
    node([], 
        node(と, 
            node([], 
                node(に, 
                    これ, 
                    [基づいた, 基づく]
                ), 
                ゲンギス
            ), 
            [[呼ば, 呼ぶ], れる]
        ), 
        [[[六, 数量], 本], [足, '動物-部位']]
    ), 
    node(は, 
        [ロボット, '人工物-その他'], 
        node(を, 
            node(いわゆる, 
                [], 
                [脳, '動物-部位']
            ), 
            node(にも, 
                [[持た, 持つ], ない], 
                node(かの, 
                    node([], 
                        node(ず, 
                            [関わら, 関わる], 
                            まるで
                        ), 
                        [[生きて, 生きる], いる]
                    ), 
                    node(に, 
                        よう, 
                        node([], 
                            [[行動|...], する], 
                            []
                        )
                    )
                )
            )
        )
    )
).

これでは、二分技とは見えにくいと思うので、図で書くと次のようになる。

[ ]は、空リストだ。基本、ノードに体言(名詞、動詞)はこない。「の」は、強い助詞なので、全体のルートに来ている(この辺りの手続きは別の記事で書いている)。左側には個別的、特殊な事柄が、右側には、一般的な事柄が吐き出されている。しかし、こうした二分木の構造は全体としてみれば、二次的なものだ。

全体として、端末の左の葉の言葉が少ないという特徴も出ている。

検索用に、次のようなprologのプログラムを作成した。左からの検索プログラムしかまだ書いていない。

%%
%% キーワードのリストから部分二分木を拾ってくる
%% 2019年4月21日
%%

sentence(L) :- jawiki(_,Node),getsentence(L,Node).

getsentence([],_).
getsentence([S|T],Node) :- lsearch(S,Node,Out),
    format('~w ==> ~w ~n',[S,Out]),
    getsentence(T,Node).

%getSentence(S,Node,Out) :- jawiki(_,Node),
%    lsearch(S,Node,Out).

%% 見つかったら、node(A,B,S)を返す(改訂)
lsearch(S,node(A,S,B),node(A,S,B)).
% リストの場合
lsearch(S,node(A,[H|T],B),node(A,C,B)) :- getmember(S,[H|T]),
        getlist([H|T],F),
        flatten(F,G),
        atomic_list_concat(G,C).
% 上で一致しなかったら、左右のノードの内側を調べる
lsearch(S, node(_, Y, _), N) :- lsearch(S, Y, N).
lsearch(S, node(_, _, Z), N) :- lsearch(S, Z, N).

%% -----------------------
%% 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).
%% -----------------------
%% 直下のリストのHeadに入っていればそれでよし
getmember(X,[X|_]).
%% アトムになったら失敗 
getmember(_,[H|_]) :- atom(H),fail.
%% 直下になければ、その直下のHeadのリストの下に無いか再帰的に調べる 
getmember(X,[H|_]) :- getmember(X,H). 

このプログラムに基づいて、次のような処理をさせる。

?- sentence([これ,ロボット,持た,行動]).

これ ==> node(に,これ,[基づいた,基づく]) 

ロボット ==> node(は,ロボット,node(を,node(いわゆる,[],[脳,動物-部位]),node(にも,[[持た,持つ],ない],node(かの,node([],node(ず,[関わら,関わる],まるで),[[生きて,生きる],いる]),node(に,よう,node([],[[行動,抽象物],する],[])))))) 

持た ==> node(にも,持たない,node(かの,node([],node(ず,[関わら,関わる],まるで),[[生きて,生きる],いる]),node(に,よう,node([],[[行動,抽象物],する],[])))) 

行動 ==> node([],行動する,[]) 

true

swi-prologで実行している。ゴール sentenceの引数として、検索キーワードのリストを与えている。これらすべてのキーワードを抱えている二分木が引っかかってくる。プログラムは、引っかかった後のノードを出力するようにしている。

厳密にいうと、そのキーワードよりも深いところを構成する部分木を出力させている。

画像にあるように「これ」というキーワードは、最下層のノードにぶら下がっているので、その最下層の部分木だけが出力される。逆にロボットは、最も上位のノードにぶら下がっているので、右の部分木全体を出力している。

「これに基づく」という表現は、我々が日常的に使う表現であり、私がいう部分知識というものの最も小さい単位である。また、「行動する」も、行動という名詞が、するという補助語につながっている、小さい部分知識である。一方「ロボットは、いわゆる脳を持たないにも関わらず、まるで生きているかのように行動する」という表現は、部分知識ではあるが、かなりまとまった知識となっている。

この辺りの違いが重要な意味を持っている。

prolog二分木から、新たな文章を二分木で作る

先月からすったもんだしてやってきた、日本語wikipediaの本文全部とtwitterの一億一千万ツイートをprolog二分技化するという作業がひと段落したので、本来の目的であって、これらを部分知識として利用して言葉を作成するという作業に入りたい。

prologには、二分木を作り出すというアルゴリズムがある。言葉を作り出すというのを、二分木創造の考え方を利用する。言葉づくりの人工知能には、ディープラーニングを利用したものもあるが、あまり好かない。言葉は、左脳の作業、ロジカルな作業なのだから、ディープラーニングやニューラルネットワークを直接応用するのは少し外れている気がする。もちろん、いずれは使う。言葉を利用する人間の脳は、論理ばかりで操っているわけではないだろうから、直感的な作用の結果でもあると思うからである。

日本語wikipedia本文のprolog二分木も作り直した

前のバージョンでは、いくつか検索結果がおかしくなっていたことは書いたが、それを改良したので、日本語wikipediaの二分木データを全て作り直した。前回は、なんやかんやで、一週間か十日ほどかけていたが、今回は、半日で全部作り直せた。

これに伴い、検索結果を示すウェッブページも若干変えた。(ファイルを3分割し、ポートを変えて三つのサーバーを立てて検索するようにした)左のメニューから飛ぶことができる。

以下のような感じで出力される。前は「ロボット」「は」、の検索の最初の結果がおかしかったが、今度はちゃんと「wiki_1_line_3385_2: ロボットは/いわゆる脳を持たないにも関わらずまるで/生きているかのように行動する// 」と出力していることがわかるだろう。

1億1千万ツイートのprolog化が終わって

合計で、延べ40時間ほどcore-i9プロセッサを動かし続けて、ようやく全てのツイートの処理が終わった。一つのツイートに複数の文章があることも含めて、合計で、1億4084万5837行のprologの二分木ツリー宣言文となった。ファイルサイズは、42.96GBである。

ただ、絵文字が多数で文章的に意味をなさないものや、キャラクタコードがおかしいものなどがprologの読み込み時にエラーになるので、それを今取り除いている。

それはつまりswi-prologに読み込んでいるということなのだが、1000万行を読み込むのに15分かかるので、全部読み込んで、エラー行を確定するのに、3時間30分程度かかる予定だ。

それが終わったら、読み込めたものを実行可能ファイルにすることと、元のswiファイルから、それらの行を全て削除する予定だ。

JAVAのゴミ集め(ガベージコレクション)

JAVAのいいところは、メモリーリークを回避するようにガベージコレクション(以下、GC)をやってくれるところだ。

いつだったか、Objective-Cで、iphoneアプリを作っているときは、細かい神経の多くをメモリーリーク回避のために使って、人間が疲れてしまう感じだった。そういう意味ではGCはとてもありがたい。

prolog化のプログラムはjavaで書いているので、core-i9 9920Xと128Gメインメモリと合わされば、およそメモリ問題はあり得ない気になっていた。

しかし、この間、twitterテキストのprolog化のために一つのプログラムを10時間以上連続で動かし続けていると、プロセスがメモリがやたら食っていくのだ。

例えば、いま、動かしているのだが、こんな状況である。(ターミナルからtopを実行した結果)

KiB Mem : 13172568+total, 10442336 free, 11123846+used, 10044876 buff/cache
KiB Swap:  2097148 total,  2097148 free,        0 used. 19345396 avail Mem

111ギガ使って、残りは10ギガしかないって言っている。javaは、最大使用メモリを110Gバイトとして動かしているので、もう目一杯使っている感じだ。増えに増えていくので、ガベージコレクションはどうした!!という感情になる。

ただ、javaバーチャルマシン(VM)では、これは特に珍しいことではないようだ。VMは、実際のメモリを使い切らないように発表の場を探しているようだから。実メモリがなくなりそうな時まで、GCは、実行されないように見えると書いてあった。