読み取った言葉をBNFファイル内容に対応させる

ALDialogやALSpeechRecognitionで言葉を読み取った時、不思議なことは、一致した言葉をBNFファイルの内容に対応させれば、topicファイルなどに書かれているそれに対するリアクションの言葉の内容がすぐに探し出せるのに、それをせずに、あらためてtopicファイルの中のruleに戻って調べなおしていることだ。こんな面倒なことをする理由がわからない。
たとえば、BNFファイルに、
<r410>:((やすみなさい)|(すわりなさい));
というトリガー(rule)があったとして、ろぼっとが「やすみなさい」という言葉を認識したとする。もちろん、この行があって認識できたのである。しかし、このトリガーとしての<r410>が情報として出されていれば、topicファイルのどの行に対応しているかはすぐわかるはずだ。わかるようにプログラムを組むことができるはずだ。それをしていないのである。ログを見ている限り、もう一度topicファイルから調べ直している。
理由はわからない。BNFをコンパイルしたlcfファイルがらみなのかもしれなない。
だから、自前のDialogでは、これを直接捉えたいと思った。ただ、bnfファイルは、プログラミング構文に使われる文法なので、使うためには構文解析アルゴリズムを使わなければならない。ただし、普通の数式処理構文解析プログラムならば、たくさんサンプルが出ていて、わかりやすく解説されているが、bnfを通してある言葉を認識し、それがbnfのどの構文に一致したのかを理解するアルゴリズムは、サンプルがない。もしかして誰もやっていない。
なので、1から作らなければならない。再帰下降構文解析法を応用しようと思っている。できたらここに掲載するつもりだが。できるのか。だいぶできているが複雑。

kakasiのインストール

英語モードと日本語モードを、「話」と「聴き」において自由に切り替える際に、日本語をローマ字に変換するため、kakasiをインストールした。つまり、opennaoでコンパイルしたということだ。これはそのままnaoに持ち込める。
kakasi -Ja -Ha -Ka -Ea -iutf8 -outf8
で、漢字、ひらがな、カタカナ、記号の全てをローマ字に変換する。たとえば、
kakasi -Ja -Ha -Ka -Ea -iutf8 -outf8
お題はコンビニです
odaihakonbinidesu

ALDialogの代替モジュール

ALDialogに変わるモジュールを作成することにした。完全なものでなくてもいい、私が必要な機能が全て含まれるものにしたい。言語の切り替えを、聞き取り、話し、のなかで自由に切り替えることが基本だ。Topicファイルも新しいものにするが、コンセプトやルールの基本的なものはそのままにしたい。私がほとんど使わない機能は組み込まない。だから、結構シンプルなものになると思う。

英語と日本語の混在

英語と日本語の混在としては、ALSpeechRecognitionでは、言語設定を日本語にして、ALTextToSpeechでは、言語設定を英語にする、あるいはその逆にしたりできれば良い。
ALSpeechRecognitionは、文脈ファイルに依存させるので、その言語は、ALSpeechRecognitionの言語設定と同じにしておかなければならない。これについては、かなり自由に操作できるようになった。その認識した言語に沿って、もう一方、トピックファイルには、そのリアクションも書かれているので、それを読み取って、speechrecognitionのイベント処理をやるメソッドを作成すれば良い。
まず、トピックファイルから、うまく、リアクション情報、すなわち喋る情報を取り出す部分を完成させよう。もう、ほぼ、頭の中では出来上がっている。

TopicファイルからBNFファイルへのコンバーター作成

基本ALDialog軸にやってきたが、英語日本語を混在させたりすると、ALDialogではうまく対応できないことがわかって、ALSpeechRecognitionレベルで対応することが便利になってきた。
そこで、どうしても必要になったのが、音声認識の文脈情報を与えているBNFファイルの作成だった。直接作成してもいいのだが、一番いいのは、qichatで書かれたTOPICファイルをBNFファイルに変換することだ。もちろん、これはNAOQIでやれる。ある意味、ALDialogを使っているうちに作成してくれるのだが、そのタイミングが理解できない。実に面倒なのだ。一旦作成してくれれば、ALSPeechRecongnitionで、コンパイルしてlcfというバイナリファイルを作成して、極めて効率よく、ロボットは聞き取りしてくれる。
昨日から今日にかけて、必死で作って、ようやく、まあ、そこそこ正しく作れたようだ。いくつか問題もあるが。ちゃんとロボットは作成したBNFファイルをコンパイルできていて、また、アクティベイトすると、正しく聞き取りしてくれる。
作成した、IbotBnf170328Japanese.bnfというBNFファイルをロボットに転送して、文脈に加えて、活性化させて、登録する。手順をpython SDKでやると以下のようになる。これで聞き取りできるようになった。
>>> from naoqi import ALProxy
>>> spr = ALProxy("ALSpeechRecognition","192.168.1.xx",9559)
>>> spr.compile("/home/nao/.local/share/dialog/IbotBnf170328Japanese.bnf","/home/nao/.local/share/dialog/IbotBnf170328Japanese.lcf","Japanese")
>>> spr.addContext("/home/nao/.local/share/dialog/IbotBnf170328Japanese.lcf","IbotContext")
>>> spr.activateAllRules("IbotContext")
>>> spr.pause(False)
>>> spr.getRules("IbotContext","active")
['IbotBnf170328Japanese#start', 'IbotBnf170328Japanese#take170320 trig']
>>> spr.subscribe("MySpeechRecognition")
>>> spr.unsubscribe("MySpeechRecognition")
聞き取った内容を、受け止めるのは、WordRecognizedのイベントを受け取ってやるので、それはまた別な作業なのだ。

日本語と英語のクロス

ロボットに日本語で問いかけると英語で答える、英語で問いかけると日本語で答えるようにしたいと思って、前回の記事のように、ALDialogでやろうとしたが結局破綻した。
そこで、もっとCoreなところにかえって、ALSpeechRecognitionでやったら、うまくいった。
結果はYoutubeに掲載した。

日本語と英語の会話をさせる

(この記事の方法は破綻した -> 参照
日本語を聞き取って、英語を喋らせたり、英語を聞き取って日本語を喋らせたいと思った。
意外と面倒だった。本当は簡単にできるのかもしれないが。ここまで得た知識でなんとかなりそうなので、記録しておく。
これをやらせるためには、日本語を聞き取った後で、すぐに、ALDialogのsetLanguageを実行させて、英語に切り替え、英語を喋らせるようにすれば良い。ただし、一つのtopicファイルには、英語か日本語のどちらかしかプログラム化できないので、日本語を聞き取った後に、英語のtopicファイルにある、ルールをイベントで起動させることが必要だ。
何れにしても、英語と日本語のtopicファイルを同時にloadTopicをさせなければならない。
ここで大事なことをいかに整理する。
(1)聞き取りのためには、$HOME/.local/share/dialog/ 以下にあるbnfファイル(文脈処理)が必要だが、これはシステムが作る(自分で書いてもいいようだが、何しろ、このフォルダはALDialogが管理しているので、あまりいじらない方がいいよと思う)。このシステムファイルは、コンパイルされて、lcfファイルに変換される。
(2)この文脈処理ファイル群は、ロボットがスタートして、ALDialogがロードされて以降は、ALDialogが管理しているようで、その後勝手に、削除したりするとエラーが報告される。
(3)今まで日本語をやっていたのに、新たに英語のファイルを付け加えると、いろいろトラブルが起きる。
(4)一旦、文脈ファイルができると、別なトピックファイルが組み込まれても、その変更だけになり、新たにbnfファイルが作成されたりしない。
(5)ALDialogには、compileALLというメソッドがあって、これは文脈ファイルつくるのだが、bnfファイルがないときは、それを作成するきっかけにもなる。
(6)何もなければ、すべてのトピックファイルがloadTopicでロードした後に、compileAllを実行するとうまくいくみたいだ。
(7)その後、setLanguageで言語を切れ変えれば良い。
(8)日本語と英語を切り替える上では、それぞれのトピックファイルが共通のトピックを持っているようにするべきだ。
何しろ、文脈ファイルが決定的に重要な意味を持っている。
(追記)上の方法でもうまくいかない。どうしても、日本語のBNFしかつくらないときは、次の方法をとる。
(1)$HOME/.local/share/dialog/ にあるファイル(bnfファイル)等を一旦全て削除する。
(2)ロボットを再起動する
(3)pythonのnaoqi sdkを起動する(PCでいい)。そして、以下のようにやる。
>>> from naoqi import ALProxy
# ALdialogのオブジェクトを取得
>>> dlg = ALProxy("ALDialog","192.168.xxx.xxx",9559)
# 日本語と英語のトピックファイルをロードする
>>> dlg.loadTopic("/home/nao/ibot/user/topic/test170323-enu.top")
'test170323'
>>> dlg.loadTopic("/home/nao/ibot/user/topic/test170323-jpj.top")
'test170323'
# コンパイルする
>>> dlg.compileAll()
これでできるはずである。あとは、bnfファイルを気にせずにやれば良い。というか、二度とbnfファイルを直接いじらない

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という数字は大きかった。

gRpcについて

Google cloud platformの元になっているが、grpcというシステムなのだが、これがよくわからないので、理解しようと思って、
http://www.grpc.io/docs/quickstart/java.html
ここにおいてあるサンプルを動かそうと思ったら、つまずいたので、対処した方法を以下に書いておく。
(1)そこに書いてある以下を実行する。

$ # Clone the repository at the latest release to get the example code:
$ git clone -b v1.0.3 https://github.com/grpc/grpc-java
$ # Navigate to the Java examples:
$ cd grpc-java/examples

これは問題なくできるだろう。
(2)次の、

$ ./gradlew installDist

を実行すると、FAILURE: Build failed with an exception.がでて失敗する。
そこで、
https://github.com/grpc/grpc-java/issues/581
ここの下の方に書いてある対処を実行する。
すなわち、元のフォルダにある、
run-test-client.sh
run-test-server.sh
のそれぞれの最後に、
-PskipCodegen
を一行入れて、再度

$ ./gradlew installDist

を実行すると、うまくいくはずである。

$ ./build/install/examples/bin/hello-world-server
$ ./build/install/examples/bin/hello-world-client

を実行する。

JavaでGoogle Cloud API ストリーミング

ロボットのAI処理のサーバーをJAVAで作っているので、Pythonでやってきた、Google Clouf APIのストリーミング言語処理をJavaで動かす必要があった。まえから、Javaバージョンを試してみたかったのだが、解説を読んでもすぐにわからないところがあったので、pythonに流れた。ちょっと本気でやってみた。
一見わかりにくいと思ったのは、逆にpythonよりも動かすのは簡単だったからだ。記録のために書いておこう。何しろ老人だから、記録しないとすぐに忘れる。このサイトは、私の忘却防止のためにおいているようなものだから。
基本的に、
https://github.com/GoogleCloudPlatform/java-docs-samples/tree/master/speech/grpc
に記載されていることを実行するだけなのだが、Pythonより、そっけなく書かれている。
(1)google cloud platformの認証関係の手続きは、pythonのところで書いたことをやっておけば、全部スキップできるので、あえてここでは書く必要がないと思う。一つだけ書いておくと(自分のために)、もし、認証に失敗したら、環境変数に認証データファイルを定義し直す必要があるのかもしれない。環境変数が何気に消えているときがあるので。たとえば、次のように。
export GOOGLE_APPLICATION_CREDENTIALS=/Users/path/to/プロジェクト名のついた認証ファイル.json
認証ファイルはGoogle Cloud Platformの認証手続きを行えばもらえる。
(2)Mavenをインストールする。これは、そのサイトにアクセスして言われた通りにやれば良いと思う。
(3)ビルドだが、githubに慣れていない、と言うかほとんど知らないので、上記のアドレスを指定して、gitを実行してもうまくいかなかった。よくわかっている方がいたら、教えて欲しいが、自分でも、もう少し勉強したいと思う。ただ、パスを遡って、もう少し上で、gitを実行したらうまくいった。まず、
git clone https://github.com/GoogleCloudPlatform/java-docs-samples/
を実行する。すると、そこにjava-docs-samples/のフォルダができて、その下にspeech関係のサブフォルダ以下も作成されているはずだ。
(4)java-docs-samples/speech/grpcに降りて、そこにpom.xmlがあることを一応確認しよう。そして、解説によれば、mvn projectでも良いと書いてあったが、そっちではうまくいかなかったので、もう一つの、

$ mvn compile
$ mvn assembly:single

の二つのコマンドを順に実行する。
(5)次のコマンドで実行させる。
bin/speech-sample-streaming.sh --host=speech.googleapis.com --port=443 --sampling=16000
ただこれだと、マイクに向かって喋っても、ローマ字か英語で帰ってくるだけである。Ctl_Cで中止する。
(5)srcのフォルダを一番下まで辿っていくと、
StreamingRecognizeClient.java
というソースがあるので、それをエディタで開いて、197行目からの、
RecognitionConfig.newBuilder()
.setEncoding(AudioEncoding.LINEAR16)
.setSampleRate(samplingRate)
.setLanguageCode("ja-JP")  // ←※
.build();
上に”←※”で指示した一行を加える。これは、
https://cloud.google.com/speech/reference/rest/v1beta1/RecognitionConfig
に開設されているRecognitionConfigのオプションだ。
(6)以下のコマンドでビルドする。
$ mvn clean

$ mvn clean
$ mvn compile
$ mvn assembly:single

(7)再度実行する。
bin/speech-sample-streaming.sh --host=speech.googleapis.com --port=443 --sampling=16000
マイクに向かって喋ると、日本語のテキストになってコンソールに表示されるはずです。表示のされ方は、pythonの場合とほぼ同じ、中間状況が表示される。