javaでツイッターbotの作成

サリーのネタは、お客さんからもらったお題に、即座に謎かけで答えるというものだが、このデータを作っているうちに、どうせなら、twitterのbotも作成しようという気になって、javaで作成した。
その要点を記録しておこうと思う。javaで全てのことができるのが素晴らしい!!
アカウントは、 @aigeinin で、このアカウントを作成すると、それに伴って開発者サイトにログインできる。そのサイトで、作成するbotのアプリケーションを登録すると、4つのキー文字列がもらえる。私の場合、javaで作成するので、twitter4jのライブラリを使ってアプリを作成する。
twitter4jは、以下のサイトからダウンロードできる。
http://twitter4j.org/ja/index.html
初歩的な使い方、作り方はネットにたくさん情報があるので、ここでは記載しない。
まず、ツイッターオブジェクトを作成する。
Twitter twitter = new TwitterFactory().getInstance();
4つのキーをセットする(それぞれの文字変数に入れておく)
twitter.setOAuthConsumer(consumerKey, consumerSecret);
twitter.setOAuthAccessToken(new AccessToken(accessToken, accessTokenSecret));
ツイートを最新の20個取得する。
List<Status> statuses = twitter.getMentionsTimeline();
statusとは、以下よく出てくるが、ツイート(返信も含む)のことだ。statusesを一個一個表示(一部の内容だけ)させると以下のようになる。

for (Status status : statuses) {
    System.out..println(
        "《Statusの表示》\n"
        + "getName > " + status.getUser().getName() + " : \n"
        + "getScreenName > " + status.getUser().getScreenName() + " : \n"
        + "getInReplyToScreenName > " + status.getInReplyToScreenName() + " : \n"
        + "getInReplyToStatusId > " + status.getInReplyToStatusId() + " : \n" // 返信じゃない -1
        + "getInReplyToUserId > " + status.getInReplyToUserId() + " : \n"
        + "getCreatedAt > " + status.getCreatedAt().toString() + " : \n"
        + "getText > " + status.getText() + " : \n"
        + "getId > " + status.getId()
    );
}

status IDが大事だ。全てのツイートは、このIDを持っている。もしこのツイートが、他のツイートの返信ならば、getInReplyToStatusId()にその元ツイートのIDが入っている。新規ツイートならば、この値は-1だ。このことはとても重要。
getScreenName()が、@で始まる、いわゆるユーザーIDだ。ツイートの内容は、その全体が、getText()で取得できる。
ツイートのStatus IDがわかると、twitterオブジェクトから、
twitter.showStatus(statusID)
で、そのもとツイートがstatusで取得できる。showなのだが、statusそのものが取り出せるところが、最初戸惑った。
新規のツイートは、tweetにツイートすべきテキストを入れて、
twitter.updateStatus(tweet);
でできる。
リプライ(リプ、返信)は、まず、返信の内容を、tweetにテキストで入れて、StatusUpdateオブジェクトを作成する。
StatusUpdate supd = new StatusUpdate(tweet);
そのオブジェクトに返信の対象となったもとツイートのIDをセットする。
supd.inReplyToStatusId(status.getId());
そして、オブジェクトを引数にツイートさせる
twitter.updateStatus(supd);
となる。
リプライのところは、詳しいマニュアルがないので、勘でやったらうまくいったという感じだ。
これだけで、ツイッターbotのほとんどのことができるはずだ。

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で処理するように思っている。だからこんなにセンサーをあちらこちらにつけるのだが。

マルチスレッド化

オートエンコーダーなどのディープラーニングをマルチスレッド化してみたが、少しも速くならなかった。レイヤーにあるニューロンの出力計算やDeltaの計算をスレッドに分散させてみたのだが、それだけでは速くならないどころか、微妙に遅くなっている(笑)
スレッド化のオーバーヘッドが、そのメリットを上回っているのだと思われる。マルチスレッドの入れ方も悪いかもしれない。今の状態で十分速いと理解すべきだろう。当面、手を出さない。

ディープラーニング(Autoencoder)の事前学習段階における「過学習」問題:ネットワークの個性が表れたという問題

オートエンコーダーなどのディープラーニングは、元のニューラルネットワークが、勾配喪失や過学習の問題で行き詰ってしまったのを打ち破ったものだった。実際、私も、その有効性を確認できた。ただ、ひとつひとつの隠れ層の事前学習を過剰にやると、問題も起こってくることもわかった。
例えば、先の記事にも書いたが、入力層と出力層以外に、隠れ層の数を3層にして、それぞれのニューロン数を600、400、300にして、隠れ層の事前学習も、60000個の文字を一通りやるだけにする。すると、事前学習は収束しないのだが、Autoencoderののちに実施したテストでは、96%の正解率に上昇した。しかし、これで、各隠れ層の事前学習を収束するまで徹底的にやるとどうなるか。ここでは、ひとつの層を通常6万回のところを、それをさらに六回繰り返して、36万回データを使って事前学習させたら、正解率が88.7%まで落ちてしまったのだ。
これだと、ああ、事前学習も過学習に陥るのだな、ということになる。
しかし、実はそう単純なことではないのだ。そのことを見るために次の図を示す。
この図で、緑色の線が、Autoencoderで過度に学習させた先のネットワークだ。驚くべきことに、正解した出力ユニットの出力値が3000近くが0.999以上(最大値は1.0)で出力しているのだ。
つまり、自分が正しいと判定したことに強い確信を持っているということだ。あるいは、せいか率を犠牲にしても、そういう確信を持ちたがるネットワークだと言って良い。これに対して、過度に学習させなかったものは、正解率は高いが、だいたいそうだろう、という感じで答えを出してきていることがわかる。
結局、ネットワークに個性が出てきたのだ。正解ということに確信を持ちたいというネットワークと、いやいや、だいたいそうだろうと答えるタイプのネットワークが現れたことになる。
問題は、応用の場合、ロボットの小脳としてどちらのタイプのネットワークを使うべきかということだ。

MNISTの識字率、96%になったが

ディープラーニングの自己符号化手法で、入力層はピクセル数の784、隠れ層を順に、600、400,300、出力層は数字の0から9に対応させて10ユニットというネットワークの学習とテストをしたら識字率は、96%になった。これまでのをまとめると

モデル 隠れ層 確信度(正解ユニットの出力が0.8以上のデータ数) 正解率(%) 学習法
A 400 8435 93.1 通常
B 300-150 8557 94.13 自己符号化
C 600-300-150 7848 89.37 自己符号化
D 600-300-150-50 7893 90.06 自己符号化
E 600-400-300 8953 96.04 自己符号化

この確信度について説明しておこう。認識した数字は、最終層の10ニューロンのどれが発火しているかで判断するが、成果率は、単に一番出力値の大きなニューロンを選んでいる。例えば、他の九個のニューロンが0.1以下の値で、8番目のニューロンが0.15でも、これは8と認識したとしてそれが正解ならば正解としているのである。
そうではなくて、その出力ニューロンの発火を0.8以上の値を出していない限り発火と認めないとした場合の正解数を確信度としている。
すると、この確信度でもモデルEのパフォーマンスが最も高い。つまり正解率でも96%と最も高いが、0.8以上の発火に限定しても、ほぼ90%の正解率になる。
ただ、一月になるのは、このモデルE以外の自己符号化は、各層の自己符号化の学習で、36万回の学習をやらせて、やや過学習になったのかもしれないと思っている。モデルEは、データ数の60000回で、各層の事前学習もやめている。
その辺りのこともこれから確認したい。
正解ニューロンの発火出力ごとのヒストグラムは以下のようになっている。発火値が0.95以上のものをあげているが、ほとんどの正解が高い発火度で実現しているのは確認できるだろう。