加速度センサーのデータ落とし

JAVAでSPI経由で加速度センサーがなんとか制御できそうになってきたのに、加速度センサーがデータを落とすときがある。JAVAプログラムの原因ではなく、加速度センサーは複数で確認した、圧力センサーは同じSPIポートで正常なので、3.3Vの電圧不足なのではないかと、疑っている。

SPIの配線

シリアル接続が、サーボ群の制御用にはI2Cを使い、圧力センサーと3D加速度センサーと両方でSPIをパラレルに使うようになって、RaspberryPI上の配線が異様に複雑になってきた。
SPIをパラレルに幾つも使うために、クロック(SCLK)と入力(MOSI)及び出力(MISO)はバス方式で使うようにRaspberryPIの上のボードにコネクタをつけた。コネクタのどのピンがどれに対応しているかを忘れてしまいそうだから、上に画像をつけた。要するに、右からクロック(SCLK)、入力(MOSI)、出力(MISO)のピンとなっている。
なお、GPIOのCE0ピンを圧力センサ用に、CE1ピンを加速度センサ用に使っている。

I2Cを経由して、JAVAでサーボモータを制御する

この間、ディープラーニングは圧倒的にC++よりもJAVAが良いことがわかって、全てをロボットの制御システムを全てJAVAに変えようとしている。
センサー系は、SPIで制御するのだが、これはPi4Jプロジェクトのライブラリでなんとかなりそうなことはわかった。
サーボモーターは、I2C経由で制御する。これも、Pi4Jのライブラリを使えばなんとかなりそうだということで、昨夜から格闘して、ようやくなんとかなってきた。いつものように記録しておく。
pi4jには、サーボモーター制御のサンプルコードは、
PCA9685GpioServoExample.java
PCA9685GpioExample.java
の二つがある。Servoという言葉が入っている初めのものを使おうとしたが、結局、なぜかサーボが異常に熱くなってしまい、動かないので、2番目のを使った。
初めは、パルスの作り方などを細かく変えなくちゃいけないんじゃないかと思って色々いじったが、結局、ほとんど素のままで使えることがわかった。ありがたい。使うところだけ抜き出して、インターフェイスの部分の関数を付け加えると次のようになる。

package aicoro;
import com.pi4j.gpio.extension.pca.PCA9685GpioProvider;
import com.pi4j.gpio.extension.pca.PCA9685Pin;
import com.pi4j.io.gpio.GpioController;
import com.pi4j.io.gpio.GpioFactory;
import com.pi4j.io.gpio.GpioPinPwmOutput;
import com.pi4j.io.gpio.Pin;
import com.pi4j.io.i2c.I2CBus;
import com.pi4j.io.i2c.I2CFactory;
import java.io.IOException;
import java.math.BigDecimal;
/**
 * Pi4JのPCA9685GpioServoExample.javaを参照したもの
 */
public class Servo {
    // MG996R用に調整した
    private static final int SERVO_DURATION_MIN = 770;  // 1670/2=835
    private static final int SERVO_DURATION_NEUTRAL = 1570;
    private static final int SERVO_DURATION_MAX = 2370;
    final PCA9685GpioProvider provider;
    Servo() throws I2CFactory.UnsupportedBusNumberException, IOException{
        System.out.println("PCA9685を初期化します");
        //PCA9685GpioServoExample.javaの値をそのまま使っている
        BigDecimal frequency = new BigDecimal("48.828");
        BigDecimal frequencyCorrectionFactor = new BigDecimal("1.0578");
        // Create custom PCA9685 GPIO provider
        I2CBus bus = I2CFactory.getInstance(I2CBus.BUS_1);
        provider = new PCA9685GpioProvider(bus, 0x40, frequency, frequencyCorrectionFactor);
        // Define outputs in use for this example
        GpioPinPwmOutput[] myOutputs = provisionPwmOutputs(provider);
        // Reset outputs
        provider.reset();
    }
    void setAngle(int ch, double degree) {
        if (degree < -90 || degree > 90) {
            System.out.println("setAngle() range err give degree = " + degree);
            return;
        }
        int pulseWidth = SERVO_DURATION_NEUTRAL + (int) (SERVO_DURATION_NEUTRAL * (degree / 90.0));
        //cout << "setAngle() pulseWidth = " << pulseWidth << endl;
        Pin pin = PCA9685Pin.ALL[ch];
        provider.setPwm(pin,pulseWidth);
    }
    private static GpioPinPwmOutput[] provisionPwmOutputs(final PCA9685GpioProvider gpioProvider) {
        // サーボモータの名前を与える、どの関節に関わっているかを明確にする意味がある
        GpioController gpio = GpioFactory.getInstance();
        GpioPinPwmOutput myOutputs[] = {
                gpio.provisionPwmOutputPin(gpioProvider, PCA9685Pin.PWM_00, "Pulse 00"),
                gpio.provisionPwmOutputPin(gpioProvider, PCA9685Pin.PWM_01, "Pulse 01"),
                gpio.provisionPwmOutputPin(gpioProvider, PCA9685Pin.PWM_02, "Pulse 02"),
                gpio.provisionPwmOutputPin(gpioProvider, PCA9685Pin.PWM_03, "Pulse 03"),
                gpio.provisionPwmOutputPin(gpioProvider, PCA9685Pin.PWM_04, "Pulse 04"),
                gpio.provisionPwmOutputPin(gpioProvider, PCA9685Pin.PWM_05, "Pulse 05"),
                gpio.provisionPwmOutputPin(gpioProvider, PCA9685Pin.PWM_06, "Pulse 06"),
                gpio.provisionPwmOutputPin(gpioProvider, PCA9685Pin.PWM_07, "Pulse 07"),
                gpio.provisionPwmOutputPin(gpioProvider, PCA9685Pin.PWM_08, "Pulse 08"),
                gpio.provisionPwmOutputPin(gpioProvider, PCA9685Pin.PWM_09, "Pulse 09"),
                gpio.provisionPwmOutputPin(gpioProvider, PCA9685Pin.PWM_10, "Always ON"),
                gpio.provisionPwmOutputPin(gpioProvider, PCA9685Pin.PWM_11, "Always OFF"),
                gpio.provisionPwmOutputPin(gpioProvider, PCA9685Pin.PWM_12, "Servo pulse MIN"),
                gpio.provisionPwmOutputPin(gpioProvider, PCA9685Pin.PWM_13, "Servo pulse NEUTRAL"),
                gpio.provisionPwmOutputPin(gpioProvider, PCA9685Pin.PWM_14, "Servo pulse MAX"),
                gpio.provisionPwmOutputPin(gpioProvider, PCA9685Pin.PWM_15, "not used")};
        return myOutputs;
    }
}

必要ないかもしれないが、センターの位置だけ少し変えている。

void setAngle(int ch, double degree)

関数に、サーボモーターの番号と、角度を与えれば良い。javaのmain関数は、別に与えること。その中で、このクラスをインスタンス化して、上の関数を呼べば良い。
不要かもしれないが、動画をつけておく。

圧力センサーFSR402をRaspberryPi上のJAVAで制御する

ロボットの足の裏につける圧力センサーのデータを取得しようと、やってみた。データは取れているようなので、ここで記録のために書いておく。(ほんと、年取ると、やったことをすぐ忘れるので、このサイトに書いておかないと、継続性が全く確保できなくなる ^^;)
なず、ディープラーニングのAICOROがJAVAで高速で動くので、RASPVERRYPIのシリアルコントローラもJAVAにしたいと思っていたら、pi4jというプロジェクトがあって、そこにライブラリがあるので、それを使うことにした。
その前に、大事な、大事な配線である。
まず、
http://www.eleki-jack.com/FC/2011/10/arduinofsr2.html
の図を少し使わせていただく。

昨日作った、FSR402とADコンバーターMCP3208とを中継するデバイスは、上記の図のように接続する。(そういえば、電源3.3Vにつないでいたけど、ちょっとまずかったかな、5Vに変えてみよう)
電源とGNDは、特に問題ないと思う。出力電圧V0の線を、MCP3208のCH0に接続する。
図の1番ピンである。気持ちとしては、足の裏に合計8つの圧力センサーを貼るつもりだから、この8つのチャンネルを全て使えば良いと思っている(ただ、その辺りは誤解かもしれない)
16ピンVDDと15ピンVREFは、電源の3.3Vにつなぐ。仕様書によれば、5.5VまでOKだ。
14ピンと9ピンはアースする。
CLKは、RASPBERRYPIのSCLKにつなぐ。
DOUTは、RASPBERRYPIのMISOピンにつなぐ。
DINは、RASPBERRYPIのMOSIピンにつなぐ。
10ピンCSは、RASPBERRYPIのCE0につなぐ。CE1でもいいはずだがプログラムの変更が必要。

次にプログラムである。JAVAで動かすのだが、まず、
http://pi4j.com/
から、SPIコントロール用の、ライブラリを取ってこなければならない。ダウンロードのページから、

を取ってくる。pi4j-1.1という正式リリース版があるのだが、こちらは、RaspberryPIの最新カーネルに対応していなくてエラーになる。フォーラムで同じ問題にぶつかった人の応答でそのことがわかった。
それを解凍して、そのライブラリをNetbeansのライブラリに付け加える。そうすれば、ライブラリが使えるようになる。Netbeansを使わないやり方は、知らない。JAVAはもう何年もNetbeansでしかプログラミングしていないので(笑)
先に解凍した中に、examplesというフォルダがあって、なんとその中に、
MCP3208GpioExampleNonMonitored.java
というMCP3208を制御できるサンプルがあるではないか!!!!
これはそのまま使えます!!
というわけでできました。データを取っている動画は以下のようです。

ディープラーニングプログラム(AICORO)、RaspberryPiでマルチスレッド並列処理化

先に、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コンバーター

ロボットの足の裏に、片足四個ずつ、合計八個の圧力センサーをつけて、ロボットの実際の重心の状況をロボット自身が捉えられるようにしようと、センサーとADコンバーター中継する部品を製作し、コンバーターをRaspberryPiの基板上に置いた。データをSPIで撮ろうと思っているが、うまくいくだろうか。
加速度センサーも追加で四個つけるので、センサーだらけになるが、それらのセンサーは、人間側のプログラムが情報を処理するのではなく、ロボットのAIで処理するように思っている。だからこんなにセンサーをあちらこちらにつけるのだが。

ニューラルネットワークの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に近い数字で、ヒストグラムがより高く立っているので、パフォーマンスが良いといってもいい。ビット化されたデータをそれよりやや低いところで、パフォーマンスを稼いでいる。
次は、ディープラーニングを組み込む。