コンセプトの管理システムの必要性

今、サリーには、2万語以上のコンセプトが組み込めるようになっている。ただ、もう、絶対使わないだろうなというコンセプトも入っている。それは無駄だ。よく使いそうなコンセプトに入れ替えて、まあ、全体として2万語になればいい。

そのためのシステムなり、データベースが必要だ。潜在的可能性のあるコンセプトを貯めておいて、スコアを与え、スコアの高いものから順番に入れておくというのが望ましい。実際に、wikipediaとか、twitterで使われる頻度、辞書への掲載などの基準でスコア化する。もちろん、ライブでお客さんから出されたお題の場合は、さらにスコアを大きくすべきだろう。

Qichatでヴァーチャルなif文を作る(2)

まとめると、Qichatで、もし 変数 $varが1の時は、Aという処理、0の時はBという処理をするというのは次のように書く。

u:(実行しなさい) わかりました $exec1=1 $exec2=1
u:(e:exec1) $var==1 Aを実行します 
u:(e:exec2) $var==0 Bを実行します 

これだけのことである。もちろん、これ以外にもいろんなやり方はあるだろう。

u:(実行しなさい) わかりました $var==1 Aを実行します 
u:(実行しなさい) わかりました $var==0 Bを実行します 

これでもいいかと思ったのだが、私がやった場合は、動かなかった。このような単純化すれば動くかもしれない。ただ、ぱっと見、「実行しなさい」が二つのイベントルールにマッチするような書き方が違和感がある。

NAOの認識コンセプトの上限

サリーやマリーが、即興漫才や謎かけのお題を取得するのは、音声認識機能を使うが、クラウドを全く使わないので、全てローカルでやっている。googleのクラウドなんかも使えなくはないのだが、テレビのスタジオやライブの舞台にネットが繋がるとは限らない。テレビの収録では、ほぼ期待できない。そういうことがわかっているのに、クラウドでしか音声認識ができないというのは、馬鹿げた戦略である。

ローカルでNAOがお題を認識するためには、qichatのconceptを使う。この夏までは、コンセプトに2千数百個を入れて、お客さんのお題に対応していた。少ない。そこで、システムを変えて、日本語wikipediaや数千万のツイートを使って、自力でネタをその場で生成するシステムに変更し、お題の認識可能性も8000語に増やした。それが、10月の初め。

さらに今日、AI的手法で、ウィキペディアやツイートから取得した関連語20000語を識別させようと入力したら、認識のための音素にコンパイルするのに3分以上かかったが、できたのだ。ちゃんと認識する。

NAOのコンピュータは相当古いが、ネタに必要な自作ライブラリやスマホからコントロールするためのHTML5関係のファイルしか入れていないので、メインメモリにもディスクにも比較的余裕があった、ので可能になったのではないかと思っている。

今、実際のネタ作りは、同じローカルネット内のパソコンにお題を送って作成させている。インターネットにつなぐ必要はなく、サリーとマリーとパソコンがローカルネットに繋がっていれば良いので、閉鎖空間でも対応できる。

何れにしても、今日はNAOが2万度も識別可能性を持っていることにただ驚愕した。下手すれば、メモリのある限り認識するのではないか。上限が推測できない。まだまだいけそうな気はする。

Intelのスティックコンピュータを購入した: BOXSTCK1A8LFC

ネタの会場に、ロボット二台と、パソコンも運ぶというのがとても面倒だ。サリーとハルのときは、ハルにネタデータの作成機能が入っていたので、ロボット二台のことだけを考えればよかったが、サリーとマリーになってから、ネタ作りコンピュータを会場まで持ち込まなくてはならなくなった。とても面倒。

そこで、もっとシンプルなコンピュータはないかと探すと、スティック型コンピュータというのが安く手に入る。次のような代物を購入した。


インテルの「Intel Compute Stick スティック型コンピューター LinuxUBUNTU搭載モデル BOXSTCK1A8LFC」という名前で、アマゾンで売っているもので、7000円弱の値段だった。まあ、試してみようという意味もある。

もう少し値段が高くて性能のいいスティックコンピュータもあるが、ウィンドウズが入っているので、linuxに入れ替えなければならなくなる。購入したものは、ubuntu-14.04だいぶ古いが、linuxなので、入れ替えが不可欠というわけではないので、これにした。(というか、ネットワークに繋ぐと、400数十メガバイトのアップデートがあるというメッセージが出てくるが、何気にやってみたら、途中でフリーズして、工場出荷時に戻さなくてはならなくなった、笑)

なお、工場出荷時に戻すためには、説明文にもなかったが、電源切った状態で、電源ボタンを押したままで電源コンセントをつないで通電する。しばらくでubuntuの初期化用のページが表示される。

ちょっと動かしてみると、CPUはそれほど悪くはない。印象としては、raspberrypi よりも少しいい感じがする。微妙だが。ただ、ストレージが、ubuntuの工場出荷時のものをストレージの一部に保存していて、いつでも戻せるようになっているぶん、実際のシステムとして使えるのは、10Gもないような感じで、そのままでは使い物にならない。

メインメモリも1Gでは弱い。

マイクロSDカードがさせるので、それを使うことにした。64ギガのものを用意した。昔の癖みたいなもので、fdiskでパーティションを設定して、と思っていたら、partedを使えと言われてしまった。使ったことがないが、使うと、結構便利だった。まあ、それで、全体を一つのパーティションにした。

ここを /altでマウントしてとか思っていたのだが、何しろ、システムの余裕が殊の外なく。ちょっとしたアプリを入れても、容量がなくなり、システムダウンになってしまう。そのほかも含めて、トライアルエラーで、5,6回は、工場出荷時に戻した。その度に1時間以上時間をロスしている。

そこで、こんなことは今までやったことはないのだが、一番システム的に容量を取っている /usrをmicroSDに載せるしかないと思った。/usrを64Gにおけば、まあ、だいぶ余裕が出てくる。ただ、手続きが面倒だ。

まず、/usrにあるものを全部、 cp -rpfa /usr/* /alt でsdに移した。これで、完コピになっているかといえば、多分なっていないんじゃないかと思うが、まあ、他にやり方がわからないのでそうした。

さらにマウントテーブル /etc/fstab に partedで作成したパーティションを/usrにマウントする手続きを記載しなければならない。以下の通りだ。

# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
#              
# / was on /dev/mmcblk0p3 during installation
UUID=a5973b07-3927-4d2c-8e88-da8c9f63f97d /               ext4    errors=remount-ro 0       0
# /boot/efi was on /dev/mmcblk0p1 during installation
UUID=59AE-C1C8  /boot/efi       vfat    defaults        0       1
# swap was on /dev/mmcblk0p4 during installation
UUID=b6e64128-dd57-461e-9904-43ac60967745 none            swap    sw              0       0
UUID=fb8aede3-36bd-4659-abb7-b7f8f03c7148 /usr ext4 defaults 0 0

最後の行が、付け加えたものだ。UUIDは、パーディションの固有IDだが、取得は次のようにする。/dev/mmcblk1p1と記載されているものは、今回作成したパーディションである。デバイスmmcblk1の第一パーティションの意味。

$ sudo blkid /dev/mmcblk1p1
/dev/mmcblk1p1: UUID="fb8aede3-36bd-4659-abb7-b7f8f03c7148" TYPE="ext4"

あっさり、現在の /usrは削除した。どうせ、何度も何度も工場出荷時に戻しているので、うまくいかなければまたそうすればいい。という、いい加減さ。

これで再起動したら、うまいこと/usrは、microSDにマウントされていた。現時点でのファイルシステムの使用状況は次のようになっている。/usrには、emacsやnetbeans、mariadbなど、apt-get install で重たいアプリをインストールしたが全然余裕である。

$ df
Filesystem     1K-blocks    Used Available Use% Mounted on
/dev/mmcblk0p3   4954848 1992248   2687864  43% /
none                   4       0         4   0% /sys/fs/cgroup
udev              448532       8    448524   1% /dev
tmpfs              92712     732     91980   1% /run
none                5120       0      5120   0% /run/lock
none              463540      76    463464   1% /run/shm
none              102400      32    102368   1% /run/user
/dev/mmcblk0p1     48698    4546     44152  10% /boot/efi
/dev/mmcblk1p1  61256732 4974448  53147564   9% /usr

最後の行に、/usrの 使用状況が記載されている。圧倒的なストレージの余裕感がある。邪道だが /usrの下に、私が所有者になっているフォルダーも用意して、ストレージを使うようなファイルはそこに置いている。

ストレージは以上だが、アプリでも手間取った。何よりも、javaを入れなければならないのだが、まあ、netbeansを入れることによって全てクリアしようとしたが、これがなかなか入らない。まあ、java開発環境が有料化するという大きな変わり目ということもある。いや、関係ないか。古いjdkでもいいのだ。sudo apt-get install netbeansとしてもさっぱりうまくは入らない。(本来は、jdkそのものじゃなくて、実行環境のjreだけでいいのだが、もしものためにnetbeansも入れたという感じ)

jdkとnetbeansをそれぞれのダウンロードサイトから入れようとしたのだが、理由がわからないままにうまくいかない。結局、以下のサイトを参考にして、ほぼうまくいった。
https://websiteforstudents.com/how-to-install-netbeans-on-ubuntu-16-04-17-10-18-04/

ubuntuのバージョンが上記サイトが想定しているものより、一段も二段も古いのだが、ほぼ、そんな感じでインストールできた。

ぞれでも、エラーが発生したのは、netbeansの起動時にjdkのフォルダが見つからないことだった。そこで、

/usr/local/netbeans-8.2/bin/netbeans --jdkhome /usr/lib/jvm/java-8-oracle/

と--jdkhome を指定してやれば、全てうまくいった。これは、環境変数JAVA_HOMEを指定すればいいはずなのだが、なぜかうまくいかないので、これを書いたシェルスクリプトで起動させることにした。素人か!(笑)

あと、mariadbも入れて、ただ、データのフォルダが/var/lib/mysqlになっているので、それを/usr配下に変更する必要があった。これは、/etc/mysql/my.cnfの

datadir        = /var/lib/mysql

を変更し、そこに cp -rfp で元データを丸ごとコピーするだけで済んだ。他のところは、特に容量を取らないのでそのままにした。というよりも、socketの位置などを変えたら、なんだかおかしくなった。私の場合は必要ない。

(追記:実際使ってわかったことで、1ギガとかでっかいデータを入れると、ログもまたとてつもなくでかくなるので、/var/lib/mysql 以下のログフォルダも、新しいmicroSDの場所に移さなくてはならなかった!my.cnfの該当箇所を変更。そうしないとrootパーティションを使い切って動きが取れなくなる)

mariadbのインストールは以下のサイトを参考させてもらった。
https://qiita.com/cherubim1111/items/61cbc72673712431d06e

私のubuntuは14.04で上記のは、18.04で相当違うのだが、ほぼ、その通りにできた。

NAOのUSBをストレージとして使う

NAO2台で、即興漫才はできるようになったが、HALと違って、NAOはCPUもストレージもパワーと容量がない。現在は、ノートパソコン(MAC)を持ち込んで、即興のネタ作りはそれにやらせる形になっているが、ロボットを3台扱うような面倒くささがある。

そこで、CPUパワーはどうしようもないが、NAOのストレージを頭にさせるUSBにできないかと思っている。頭にUSBをさせば醜いが、なるべく短くして、リボンでも飾れば、なんとかなるのではないかと思う。

今、NAOを今日のオーディション用にパッキングしてしまっているので、試せないが、
https://medium.com/@heatherkemp_89938/interfacing-nao-robots-with-the-arduino-in-depth-tutorial-284ecba0b0aa
に手続きが書いてある。全体が面白い記事だが、USBマウントのところだけメモしておく。

(1)NAOにrootユーザーとしてログインしておく。
(2)USBをマウントするフォルダを作っておく(フォルダ名は任意)

# mkdir /mnt/usb

(3)ブロックIDを確認する

# blkid /dev/sdb*
/dev/sdb1: UUID=”04D9–7E06" TYPE=”vfat”

(4)フォルダをマウントする

# mount /dev/sdb1 /mnt/usb

以上である。まだ試していない。

NAO、コンセプトの搭載可能数

Pepperは、クラウドで、qichatのワイルドカード認識に対応しているようなのだが、NAOについては、NAOQIバージョンもPepperほどにも更新されておらず、対応していないようだ。それはそれでいい。というのは、Qichatでは、conceptにリストアップしておけば、ロボットはかなり正確に人の言葉を認識できるからだ。だから、conceptにどれだけのワードが載せられるかは、非常に重要な仕様になっている。
まえから、幾つもに切り分けしていれば、500個以上のコンセプトを識別できることはわかっていた。それは、実際使っているものだ。
しかし、一つのconceptのなかに、幾つ詰め込むことができるかは調べたことがなかった。実は、多くのコンセプトに分けるよりも、一つに詰め込むと便利なことがある。とても便利になるのだ。今日試してみてとても驚いた。なんと、一つのコンセプトに、2000個以上のワードを詰め込んでも、識別できたのだ。つまり、トピックファイルの中に、
concept:(words) [今日 明日 ロボット などなど ・・・・・・]
と2000個以上の言葉をwordsというコンセプトで登録しておいても、たとえば
u:(お題は _~words です) なになに $1・・・・・・
というかたちで、言葉の認識、変数 $1 への代入が可能になるのだ。もちろん、ALDialogで、そのトピックファイルを読み込み、コンパイルするのには多少時間がかかる。20秒くらいか。それは、スタート時だけだから、全然大したロスではない。
2000個も入れると、言葉の中に変な文字、つまり、ロボットが発音できない文字が入っていると、コンパイルに失敗してトピックファイルが有効化されない。アンダースコアとか、全角の数字(これがエラーになるのはちょっと不思議だが)とか、「つ」の小さい版「っ」がワードの最後に発音しにくい形で入っていても、コンパイルエラーになった。しかし、数の大きさは平気だったのだ。
NAOがワイルドカードの認識が下手なので、やむ負えず、Googleのcloud APIで変換させたりしていたのだが、音源ファイルで送ろうとも、ストリーミングにしようとも、やはり、人が喋ってからその結果をロボットに返すまでに10秒余のロスが発生して、ネタの120秒などという短い尺には、耐えられない長さになってしまう。conceptにおいて、聞き取られレベ、ほぼリアルタイムで時間のロスがない。
2000個というのは相当のキーワード数だ。実際それ以上が可能かもしれない。それ以上を試す気になれないほど、2000という数字は大きかった。

研究室のロボット、ピッキーもノッキーも修理から帰ってきた

肩を脱臼して動かなくなっていたのっきーは少し前に修理が終わって帰ってきた。指が二本折れてしまっていたぴっきーも昨日帰ってきて、研究室のロボット2台は完全復活した。ぴっきーのOSがふるいままなので、時間が空いている日にバージョンアップしたい。上がぴっきー、下がのっきー。二人合わせてピノッキオ。(私の私的なロボットサリーは自宅に置いてある)

AIモジュールの作成

AIモジュールを作成しつつある。仕組みはこうだ。
(1)パソコン上に、情報、知識、ネタに関するデータベースをMysqlで作成しておく。
(2)パソコン上に、JAVAで、こうしたAI関係の情報を処理するサーバーを動かす。
(3)ロボットに、それに対応するクライアントモジュールを、C++のライブラリとして作成する。
(4)qichatのトピックファイルで、変数イベントを発生させてサーバー上から必要な情報を獲得する。獲得したことに対するイベント、変数を同じトピックファイル上で取得し、会話的に使用する。
ほぼできた。明日、多王するトピックファイルを作成して、ロボットで試そう。

人の問いかけにWikipediaを答えさせた

今日は1日、時間が取れたので、午後ずっと、ロボットのプログラミングをやっていた。なんとか、ロボットにお題を問いかけて、そのお題をWikipediaにアクセスして調べさせて、主要な答えを返すというところまでやらせたが、まだまだ不安定で、実用性が低い。他の言葉は、ランダムにボケさせただけ。
不安定さについては、また別に書こうと思う。

Google Cloud APIのストリーミングを継続して使う

Google Cloud APIのストリーミング音声認識を、ロボットとの継続した対話に使おうとすると、60秒の制限があって、それ以上使えずに、例外が発生してアボートしてしまう。これでは使えない。
そこで、gracefull(優美)に一つの会話を終了させながら、次の会話に入っていくということがどうしても必要になる。その基本的なやり方を以下に記しておく。
(1)ロボットからの音声データのストリーミング入力は、それとして生かしておくという戦略。
(2)Google Cloudからは、送られたーデータの解析結果が中間的に、最終的にの二つのバージョンで送られて来るが、最終バージョンが送られて来たら、それを一旦出力(エコーロボットとしてロボットに喋らせる、あるいは、人間入力の言葉を解析して応答を用意したりして)
(3)これで例外を発生させずに終わるが、これだと一回の聞き取りしかできなくなってしまう。そこで、
requests = request_stream(buffered_audio_data, RATE)
のスレッドの作成から、再度はじめ直す必要がある。そこでここからを関数化して、先の終了後に、再帰的にこの関数を呼び出すようにすればいい。すると、また60秒制限の新しいスレッドが立ち上がる。無限続けるのは良くないので、いったい何度ロボットやり取りするかを事前に与えておいて、その回数の会話をしたら全てを終了させるようにすれば良い。
(4)一つ問題は、(2)で、会話が入れば、一つのスレッドはグレースフルに終わるが、無言の時間が続くと終わるタイミングを逃して例外を発生させてしまう。そこで、60秒以内の一定時間無言が続くと、一旦gracefullに終わらせる。
そこで、次のようなタイマーイベントを、先に分離した関数部分の中に置いておく、
###############
def stopAPI():
print "ループを止めます"
recognize_stream.cancel()
#ここまでが関数、以下でタイマーでこのハンドラーを呼び出す
t=threading.Timer(MAX_SILENT_LENGTH,stopAPI)
t.start()
###############
関数の中にstopAPIという関数を定義して、MAX_SILENT_LENGTHの秒数が経過すると、
recognize_stream.cancel()
というイベントを発生させる。すると、CANCELLという例外が発生して、綺麗に終わるように例外処理手続きが組み込まれているのでうまくいく。そしてこの例外処理が終わった後、さいど、先に作成した関数、request_streamから始める関数をスタートさせれば良い。
これで、指定回数の会話が終わるまで、継続的にGoogle Cloud APIでストリーミング処理を続けることができる。
これで、人工無脳の会話ボットと、ロボットNAO、そしてGoogle Cloud APIの三つをつなぐ準備が80%ほど、整った。