先に、Javaで書いたAutoencoderのディープラーニングプログラム(以下AICORO)をマルチスレッド化して並列処理にしたら、あまり効果がなかったと書いた。それは、CpuがCore i7の3GhのMacでの話だった。「ああ、失敗したな〜」という感じで、終わった。
今日から、またロボットの方で、センサーをテストしようとRaspberyPi3を動かして、ついでだからAICOROを動かしてみた。(JAVAもNerbeansも入れてある)スレッド一個で処理するものを動かしたのだが、なんとなく遅い。784,600,400,300,10という隠れそう3つのネットワークをホワード処理だけで動かしたのだが、1サイクル25ミリ秒くらいかかる。ちょっと遅いなと思った。MACだと時間を測れないほどバリバリに速いのだが。
もしかしてと思って、昨日作ったマルチスレッドのAICOROを動かしてみた。10スレッドの並列処理をするバージョンだ。「あれ!!」1サイクル10ミリ秒くらいで処理する。「いいじゃない!!」
RaspberryPi3は、CPUが4コア持っているので、8スレッドあたりは十分使えそうなきがする。10スレッドはちょっと多いかもしれないが。
初めて、マルチスレッド、並列処理が役に立つことがわかって嬉しい!!
カテゴリー: ロボット製作
ロボット制御のイメージ
現在考えているAIを軸としたロボット制御のイメージを少し書いておこう。
基本、歩く立つなどの目的を与えると、AIコントローラー自身がサーボモータを制御するようにシステムを作る。AIコントローラーは、サーボモーター角度群、センサー情報とその変化を取得して、自ら学習する。それらの情報は、人がCOSMを通してロボットを動かしている状況の中で取得する。
AIコントローラーは、モーターとセンサーの与えられた状況の中で、次のステップでサーボモータのどのような動きのパターンに持っていくのかをニューラルネットワークから出力して、それに基づいてサーボモーターが動く。
AIコントローラーのネットワークは、事前学習とともに、取得する情報に基づいて常時学習する。
学習には、動きの成功と失敗のイメージも学習に組み込む。
圧力センサーとADコンバーター
ニューラルネットワークのC++プログラムの公開
作成したニューラルネットワークのC++プログラムを、
https://github.com/toyowa/neuralnet
に公開した。
Gnuのg++用のプログラムで、他では確かめていない。
ネットワーク全体は、Netクラスに、その層(レイヤー)はLayerクラスに、レイヤーの内容であるニューロンは、Neuronクラスになっている。それぞれ、オブジェクト化されて使われる。
細かい説明は、気が向いたらしよう。プログラムを使えるくらいの人は、見ればわかるだろうと思う。
余談だが、私は、このプログラムも含め、C++、JAVA、JAVASCRIPT、Html、PHPなどの開発は、全てNetbeans上でやっている。Netbeansは、最高の開発環境だと思っている。
このニューラルネットワークとバックプロパゲーションは、30年近く前に一度やっていたことなのだ。大きく変わったのは、コンピュータの凄まじい高速化だ。
MNIST手書き数字データで93%の識別率
作成した汎用ニューラルネットワークで、その辺りのパフォーマンスを図る標準データとなっているMNISTの手書き数字データをテストしてみた。
MNISTについては、
http://yann.lecun.com/exdb/mnist/
にデータそのものと解説がある。
手書き数字は、
こんな感じのもので、数字の一つ一つがデータ化されている。1ピクセルが1バイト(0-255)の値が与えられ、1文字、28X28ピクセルからできている。
データ数は、60000文字の学習用データと10000文字のテスト用データがある。それぞれ、ピクセルデータとそれが幾つの数字を表しているかというラベルデータがある。60000字でニューラルネットを学習させ、ネットワークウェイトを作成し、そのウェイトが、テスト用10000字を正しく認識するかどうかを調べるのである。
ニューラルネットワークは、入力レイヤーが、28X28の784ニューロン、隠れ層(中間レイヤー)は100ニューロン、出力は0から9までの値を出すので、10ニューロンにした。出力は、ネットワークがその画像について判定した値のニューロンだけが発火する(値1になる)ことを見越しているわけである。
78万4千個のウェイトからなる、従って、訓練手法であるバックプロパゲーション(誤差逆伝搬)で、6万個の文字について、毎回これだけのウェイトを微調整しながら最終的に望ましいウェイトを見つけるわけであるから、相当大きめのネットワークである。
データについては、それぞれのピクセルの値を、正ならば1層でないならば0に、ビット化したものと、(0-255)それぞれの値を0から1の間の数に正規化したものと2種類用意した。元のデータのままをネットワークに入れたら、途中で破綻している。正規化したものに全ての情報が入っているので、生データを使う必要はない。
予測的には、正規化して0から1の間の数字にした方が、情報を多く持っているので、良いパフォーマンスを示すのではないかと思われた。0と1にビット化すれば、情報を単純なものにしてしまうのだから。
学習に、3Gヘルツ、8コアの最高スペックのMac Proでも1時間以上かかった。と言ってもこのマックは16スレッド動かせるのだが、プログラムそのものがほとんど1スレッドで動かしているので、相当無駄にしているのだが。逆に、同時にいくつもの学習を同時にさせることはできる。
10000個のテスト用データのテスト結果は、以下のようである。
<正規化(0.0-1.0の間の値)されたデータを用いた場合>
正解数 = 9283 不正解数 = 717 正解率 = 0.9283
<ビット化(0.0か01.0の値)されたデータを用いた場合>
正解数 = 9300 不正解数 = 700 正解率 = 0.93
微妙に正解率がビット化した方がいい。誤差の範囲といってもいいが、何よりも、ビット化して情報を削ったにもかかわらず、正規化したものに匹敵するパフォーマンスを出していることが驚きである。MNISTに掲載されているパフォーマンスと比べるとやや低いが、何の微調整もしていない、ただ作成したものでいきなりテストしただけで、これだけのパフォーマンスを出せれば、私としては合格だ。
正解率は、最大出力を出したユニット番号が、その画像の数字に一致する場合に正解としているのだ、これだけでは、どこまで明確にその数字と判断しているのかがわかりにくい。そこで、正解の出力ユニットがどれくらいの値を出したのかをヒストグラムで示す。基本的にユニットは0から1の間の数字しか出さず、通常、関係ないときはほぼ0に近い値しか出さないことに注意されたい。
つまり、そのユニットが正解だという場合、ほぼ0.9以上の値を突然、出している(ニューロンが発火している)ということだ。このグラフを見ると、このヒストグラムで示されたパフォーマンスは、正規化されたデータの方が、より1に近い数字で、ヒストグラムがより高く立っているので、パフォーマンスが良いといってもいい。ビット化されたデータをそれよりやや低いところで、パフォーマンスを稼いでいる。
次は、ディープラーニングを組み込む。
ディープラーニング用にニューラルネットの汎用プログラムを書いた
ロボットの姿勢制御にディープラーニングを用いたいと思って、その手始めに、ニューラルネットワーク(誤差逆伝搬:バックプロパゲーション)の汎用プログラムを一昨日くらいから熱を込めて書いた。
汎用性を重視して、レイヤーの数や、その中でのニューロンの数を柔軟に変えられるようにすると同時に、C++らしいオブジェクト型のプログラムにしようとした。ようやくバグがないような感じになって、排他的論理和を識別させてみたが、結構学習に手間かかる。
細かくウェイトを変える(グラディエント値の0.05ずつ調整)ようにして、100万個のデータを学習させて認識させた。中間層は4個で、Neuron[0]というのが、最後の出力層のニューロンの値だ。バイアスニューロンをつけていないからかもしれない。
printOutput Neuron [ 0 ]
が、順伝搬の出力値だ。
-----------------No.999975------------------ 入力データ 1 0 Layer::printOutput Neuron [ 0 ] => 0.986243 教師出力データ 1 -----------------No.999976------------------ 入力データ 0 1 Layer::printOutput Neuron [ 0 ] => 0.986272 教師出力データ 1 -----------------No.999977------------------ 入力データ 0 1 Layer::printOutput Neuron [ 0 ] => 0.986272 教師出力データ 1 -----------------No.999978------------------ 入力データ 1 0 Layer::printOutput Neuron [ 0 ] => 0.986243 教師出力データ 1 -----------------No.999979------------------ 入力データ 0 0 Layer::printOutput Neuron [ 0 ] => 0.0137698 教師出力データ 0 -----------------No.999980------------------ 入力データ 0 0 Layer::printOutput Neuron [ 0 ] => 0.0137697 教師出力データ 0 -----------------No.999981------------------ 入力データ 0 0 Layer::printOutput Neuron [ 0 ] => 0.0137696 教師出力データ 0 -----------------No.999982------------------ 入力データ 1 0 Layer::printOutput Neuron [ 0 ] => 0.986243 教師出力データ 1 -----------------No.999983------------------ 入力データ 1 1 Layer::printOutput Neuron [ 0 ] => 0.0131976 教師出力データ 0 -----------------No.999984------------------ 入力データ 1 0 Layer::printOutput Neuron [ 0 ] => 0.986243 教師出力データ 1 -----------------No.999985------------------ 入力データ 0 1 Layer::printOutput Neuron [ 0 ] => 0.986272 教師出力データ 1 -----------------No.999986------------------ 入力データ 0 1 Layer::printOutput Neuron [ 0 ] => 0.986272 教師出力データ 1 -----------------No.999987------------------ 入力データ 0 1 Layer::printOutput Neuron [ 0 ] => 0.986272 教師出力データ 1 -----------------No.999988------------------ 入力データ 0 0 Layer::printOutput Neuron [ 0 ] => 0.01377 教師出力データ 0 -----------------No.999989------------------ 入力データ 1 0 Layer::printOutput Neuron [ 0 ] => 0.986244 教師出力データ 1 -----------------No.999990------------------ 入力データ 1 1 Layer::printOutput Neuron [ 0 ] => 0.0131984 教師出力データ 0 -----------------No.999991------------------ 入力データ 0 1 Layer::printOutput Neuron [ 0 ] => 0.986272 教師出力データ 1 -----------------No.999992------------------ 入力データ 1 0 Layer::printOutput Neuron [ 0 ] => 0.986244 教師出力データ 1 -----------------No.999993------------------ 入力データ 0 1 Layer::printOutput Neuron [ 0 ] => 0.986273 教師出力データ 1 -----------------No.999994------------------ 入力データ 0 1 Layer::printOutput Neuron [ 0 ] => 0.986273 教師出力データ 1 -----------------No.999995------------------ 入力データ 1 1 Layer::printOutput Neuron [ 0 ] => 0.013199 教師出力データ 0 -----------------No.999996------------------ 入力データ 1 1 Layer::printOutput Neuron [ 0 ] => 0.0131987 教師出力データ 0 -----------------No.999997------------------ 入力データ 0 0 Layer::printOutput Neuron [ 0 ] => 0.01377 教師出力データ 0 -----------------No.999998------------------ 入力データ 1 0 Layer::printOutput Neuron [ 0 ] => 0.986244 教師出力データ 1 -----------------No.999999------------------ 入力データ 1 1 Layer::printOutput Neuron [ 0 ] => 0.0131984 教師出力データ 0 -----------------No.1000000------------------ 入力データ 1 0 Layer::printOutput Neuron [ 0 ] => 0.986244 教師出力データ 1 -
ロボットの姿勢制御にディープラーニング
ロボットを動かす現在のシステムに限界を感じて最初から作り直すことにした。ある意味、原点に帰って、足郎の新しさをちゃんと戦略的に貫くことにある。
(1)外からシステムを押し付けるのではなく、足郎が感じている状況に、足郎自身が対応するようにする。センサーの適切な配置と処理システムが求められる。
(2)そのために、簡単なディープラーニングを取り入れて、与えられた状況の中で、目的適合的な行動を適切に選び出す能力を与える。
(3)サーボを制御するために、全てを動かすのではなく、引きだけを指定してステップ的に動かし、残りは全て完全緩和状態にしてしまう。効率化する。
というわけで、ディープラーニングの基本的内容である、ニューラルネットワークのプログラムを25年ぶりに、C++で書いている。当時は、ただのCだったが。
四半世紀前、経済データの読み取りにニューラルネットワークを使った研究をしていた。ただ、周りからほとんど理解されなかったが。
1。「ニューラルネットワークによる時系列データの特性認識」, 単著, 『経済理論』, 249号, 平成4年9月.
2。「コネクショニズムの経済学---ニューラルネットワークが示唆する新しい経済学」, 単著, 『情報処理技術の応用に関する調査研究』, 第4章,(財)日本情報処理開発協会, (財)関西情報センター,平成5年3月.
3。「環境・エネルギー・成長の経済構造分析---産業連関分析とニューラルネットワーク」, (植田和弘, 長谷部勇一, 寺西俊一, 宮崎誠司, 家田忠と共著),『経済分析』, 経済企画庁経済研究所編, 第134号,平成6年4月.
3番目の論文は経済企画庁経済研究所の正式研究報告書に書かれているものだが、この論文を当時の所長の前で説明したら、鼻であしらわれた。仕方がない。AIがこんなに流行るものだとは、誰も思わなかっただろう。私自身もだが。時代をあまりに先取りすると、辛い目にあうのだ。
もう一つの倒れるセンサーデータ
倒れるときの加速度センサーデータ、対応猶予時間
センサー(SPI通信)とサーボ制御(C2I通信)が別スレッドで、正常にできる様になった。
COSMで、サーボ制御とセンサー制御の両方のコマンドを新たに加えた。このコマンドで、ロボット歩行中のセンサーデータが取れる様になった。
ロボットが倒れる状況のデータが取れた。正確には、倒れるのを手で支える瞬間のデータが含まれているという意味だが。
こちらが、25秒間、10ミリ秒ごとにデータを取った図である。中央に、全ての軸が上下に大きく揺れているのが倒れるのを支えた瞬間のデータである。
その前後(500個分くらい)だけを表示させると以下の様になる。
こう見ると、倒れそうなことを一番特徴的に予見させるのがZ軸である。つまり、縦方向である。
倒れる状況になったのが、1132時点、つまり動き始めから11秒32の時点である。Z軸がおかしくなり始めたのが1107、つまり11秒07である。したがって、ほぼ250ミリ秒前に倒れそうになり始めたことがわかるのだ。1107の時点では、まだ予見できない。「注意」段階。はっきりと沈み込みがわかるのは、おそらく1116あたりだ。つまり、おかしくなり始めて、90ミリ秒で「倒れそうだ!!」という「警告」が出せそうである。倒れるまで、まだ、160ミリ秒もある。
倒れる160ミリ秒前に警告が出たら、なんとかなりそうなきがする。気象庁の地震や津波や台風の警報の様なものである。
ただ、この図では「どちらに倒れようとしているのか」が、わかりにくい。(1)X軸(+が前方)は、プラス側に偏っているので、前方に倒れそうだというのは、わかるかもしれない。(2)Y軸(+が右側)も、徐々にプラス側になっているので、右側に倒れそうだという予測がつくのかもしれない。(実際、その方に倒れたのだが、笑)
では、このとき何ができるのか?
(1)重心のある足を確認する。これは、本来、足の裏にでも圧力センサーをつけて、確認できる様にしたいが、現時点では安易すぎる解決だ。やはり、動かしているサーボモータの状況から理論的に予想できる「重心足」をチェックする。簡単だ。
(2)経験からだが、重心足の縦方向の姿勢を決定的に決めるのは、人間で言えば、足首の角度である。人間の場合は少し違うが、このロボットの場合は、重心足のくるぶし角度は決定的に重要だ。くるぶしの角度を規定しているのは4つのサーボモータである。これらのサーボ角度を微妙に転倒回避の方向に動かす。
(3)残りの全体もまた、影響する。なるべく、倒れる方向とは逆の方向に重心を持っていける様に調整する。どうするかはまだわからないが。
あと、スレッドが問題である。センサーを常時監視しているスレッドと、サーボモーターを制御しているスレッドが別々だ。センサーを監視しているスレッドが、直接サーボをいじると、トラブルになりそうだ。なるだろう。たとえ、mutexなどで、片方をロックしたりしてもダメだ。だから、サーボを制御しているスレッドが、センサーの警告を受け止める適切なメカニズムを作らなければならない。
センサースレッドが警告情報をどこかに書き込み、サーボスレッドがそれを細かく読んでチェックしているという状況だと、書き込みのバッティングも起こらない様な気がする。
二足歩行中の3軸加速度センサーのデータ
まだ、スレッド化はしていないのだが、センサーのspiによるシリアル通信がサーボを動かすi2cとバッティングしないかを見るために、ロボットが歩行中に、同じRaspberryPiから、センサープログラムも動かして影響を調べた。基本的に影響はない様だ。独立してデータを処理している。
歩行中のセンサーデータを初めてとったことになる。歩行の様子は、前の記事にあるゆっくりとしたものだ。データは20秒間の動きを10ミリ秒間隔で2000個とった。片側3歩くらいずつ動いている。
横方向(Y軸)は、揺れることが必要なので、まあいいが、縦の動き、前後の動き、X軸の動きの不安定さは、歩行の不安定さに直結しているが、これもまた、ある程度までは必要。
実際に速さを増やして、不安定歩行をさせてデータを取らなければならない。