• 1. FPGAでニューラルネットワークする。まずC言語でやってみる2

    前回は同じ値で何度も学習させたら100回以内に収束していました。
    今回はちゃんと違うデータを渡して関数の近似が出来るのかやってみます。

    ちょっと変えたところ

    前回最終段はただのSUMでしたが、そうすると誤差がより伝搬しないと思ったので(最終段で値の調整がかなり効くので)最終段も普通のノードにしました。
    下の画像で言うと黄色いのが全てシグモイド関数で動き、赤いのが重み付けの存在する経路です。

    Screen Shot 2015-01-25 at 10.44.17

    x^2

    最終段をシグモイド関数にしたことで出力範囲が0~1となりました。増幅してもいいですが、とりあえずx^2におけるx=0~1の範囲で学習させてみます。

    Screen Shot 2015-01-25 at 10.36.57

    これが結果です。左下のがプロットしたもので、赤がx^2の曲線で、緑がニューラルネットワークの出力です。
    教師データはx^2の0~1にある等間隔な100個のデータです。100このデータを回して使って1000回やった結果(同じデータは10回使われていることになります)がこれになります。
    グラフを見ると全く近似できてないですね。わずかに右肩上がりにはなってますが。
    同じデータなら30回もやれば同じ値になったのに(前回ね)全くだめですね。
    プログラムがおかしいのかと思ってさんざん見なおしたんですが、どうも回数の問題だったようです。10倍にして10,000回やってみます

    Screen Shot 2015-01-25 at 10.37.06

    割と近似できてますね。およそ5,000回目ぐらいで収束し始めているのが分かります。
    こんなにやらなきゃいけないんですね^^;

    レイヤー数を下げてみる

    ちょっとレイヤー数を下げてみようと思いました。

    Screen Shot 2015-01-25 at 11.01.54

    こうしたらどうなるんでしょう。
    同じく最初は1000回
    Screen Shot 2015-01-25 at 10.36.38

    いきなり曲線っぽくなってますね。レイヤーが少ないほうが柔軟に対応するんでしょうか。
    回数を上げてみましょう。

    Screen Shot 2015-01-25 at 10.36.47

    10,000回にしてみました。さっきのレイヤー数の時は5,000回ぐらいで収束していきましたが、今回は1,000回ぐらいでかなり収束していますね。さらに10倍にしてみましょう。

    Screen Shot 2015-01-25 at 10.37.45

    おぉ。こんなに近似できるもんなんですね。あんなに簡単な回路なのにね(笑)
    まぁ、学習量は半端無いわけですがそれでもすごい。

    sin(x)

    x^2だとキレイすぎるのでsin(x)をやってみました。sin(x)/2+0.5の関数を近似してみます。
    レイヤー数は少ない方にしてみましょう。10,000回やった結果がこちら
    Screen Shot 2015-01-25 at 10.39.45

    それっぽいですね。でもx^2のときは10,000回やったらほぼ似てたのに、それに比べて大分ズレてますね。さらに10倍の100,000回にしてみましょう。

    Screen Shot 2015-01-25 at 10.39.55

    割りといい感じです。やっぱり最初の方はちょっと微妙ですが、かなり近いですね。
    こんな簡単な回路でもここまで近似できるんですね。

    速度的な話

    何秒ぐらいかかったのか見てみます。さっきのレイヤーを減らした方でみてみましょう。
    ちなみに重み付けできる経路は12ですね。
    Screen Shot 2015-01-25 at 11.01.54

    100,000回で

    0.037208 sec

    でした。1秒でおよそ 2,687,594回出来そうですね。
    教師データはその都度計算してますから、x^2の計算も100,000回してるのですが、まぁ大したことはないでしょう。

    ちなみにレイヤーの多い方は
    Screen Shot 2015-01-25 at 10.44.17

    100,000回で

    0.060529 sec

    でした。重み付けできる経路数が12から21になってますからこんなもんでしょうか。

    FPGAでの実装に向けて

    今回はfloatを使ってますが、浮動小数はめんどくさそうですね。signed shortとかでちょっと実装しなおしてみましょうか。

  • 0. FPGAでニューラルネットワークする。まずC言語でやってみる

    そもそもFPGAが好きなのは特別な回路を作って並列でできることを高速化できるからです。僕が興味のあるのは人工知能なので、せっかく勉強した初歩のニューラルネットワークを回路にしてみようと思います。

    まずは比較用にC言語のプログラムを

    とはいえ、「1秒間に1万回学習出来ました!」って言っても早いのか遅いのか分からないので比較用にまずC言語で書いてみることにします。誤差逆伝搬法の復習にもなるしね。
    なので、今回はFPGAに興味が無い人でも読めるはず。Cでのニューラルネットワークの実装です。

    ミッション

    1次元関数の近似をさせてみたいと思います。y=x^2みたいなね。
    そのためにこんな回路にしようと思います。Screen Shot 2015-01-23 at 18.28.05

     

    第1レイヤー(以降Layer1)は入力された実数をそのまま出力するものです。
    Layer2とLayer3は学習します。またLayer4である最終の+ってなってるやつも学習しますが出力はシグモイド関数でなく入力値の加算値を出力します。つまり重み付けが変更されるのは

    Screen Shot 2015-01-23 at 18.28.33

    このオレンジのところってことになります。
    実際にプログラムする時はレイヤーの数とそれぞれのレイヤーに存在するノードの数を変更できるようにします。例えば1層目は10個、 2層目は6個、3層目は10個みたいプログラムの変更なしでできるようにね。

    最後のレイヤーが合計値を出力したり、重み付けも考慮されていたりってのはどうなんだろうね。あとで考える。

    早速作ってみた

    ということで説明を飛ばして完成したのがこちら(3分クッキングみたいですね!)
    https://github.com/yuki-sato/neural-network-c-language/tree/dev-1

    ちょっと普通のニューラルネットワークと違う点として、評価関数を変更しています。
    評価関数は普通
    E=(y(理想値) – y (現実値))^2
    という形をとっていますが、「絶対値にしたいだけじゃん」って思ってるので、
    E=|y(理想値) – y(現実値)|
    を評価関数として利用しています。それによって微分値が変わりますから重み付けの補正値が少し変わってます。

    ちなみにそれぞれの重み付けは0~Nの範囲で乱数で初期化するようにしました。0だと若干ダメな気もしますが、とりあえず動かしてみましょう。

    試しに動かす

    さっきの画像と同じ3ノードがつながってる構造にしました。
    関数はy=x-2で、x=3(y=9)を教師データとして、この教師データで100回誤差逆伝搬法で学習させてみました。
    シグモイド関数のアルファ値は1(標準シグモイド関数ってことだね)。学習時に使うイプシロンは0.1としました。
    結果はこんなの。

    Screen Shot 2015-01-23 at 20.06.43

     

    右下のError Improvementってのが誤差がどのぐらい小さくなっているかです。横軸が試行回数で、縦軸が誤差です。誤差が直線的に小さくなりある回数から定常状態になっていますね。
    数値で見てみましょう。学習していない1回めの出力は

    22.999849

    でした。9が正しい結果ですからだいぶ大きいですね。
    これを元に1回学習させた結果の出力は

    22.699852

    と、確かに補正されています。100回目の出力は

    8.900009

    で、かなり近くなっています。どうやら小さく振動しているみたいで99回目は

    9.200006

    だったのですが、98回目の出力は100回目の出力と同じになっています。

    ちょっと考える

    まず、何故振動したのかですが、まずイプシロンが少し大きかったんでしょう。floatの値の範囲の問題があります。重み付けの補正値がfloatで表現できないほど小さな値になってしまっています。これはイプシロンが大きいのにも少し関係しています。

    誤差伝搬しない問題

    ニューラルネットワークは層を多くしても誤差が後ろまで伝わらないという既知の問題があります。それはすごく当たり前の話で、ニューラルネットワークというのは1層だけでも数学的に完全なので(この辺で話したやつ)そうなっちゃいます。
    今回どのぐらい”伝搬しなかったのか”を見てみましょう。

     

    Screen Shot 2015-01-23 at 20.04.50

    これが乱数で生成したそれぞれの重み付け係数の初期値です。
    行がそれぞれのレイヤーです。なので3層目は最後の出力なので1つになっています。
    また、それぞれ4こ数字がありますが、それが前のレイヤーとの接続の重み付け値になっています。1個多いのは閾値用です(このへんで話したやつ)。
    どれがどれかは置いておいて、これがどのぐらい変わったかを見てみましょう。100回学習させた後はこうなりました。

    Screen Shot 2015-01-23 at 20.04.57

     

    驚くべきことにLayer1(入力値をそのまま出力するノード)からLayer2につながってる部分は全く学習されませんでした。floatのせいでしょうね。
    Layer2-Layer3は1つのノードだけ値が変更されています。
    そして、Layer3-Layer4の最後の加算するノードへ繋がってる部分の値だけやたらと変更されています。

    次に向けて

    ちょっと予想外ですね。ここまで伝搬しないもんなんですね。これで果たして関数を再現できるんでしょうか。最後のSUMノードへの重み付けは調整したらいけないもんなのか。それとも同じデータで何回もやるとこうなるってだけで違うデータも入れて学習させたら意外とちゃんと関数を再現してくれるのか。ノードの数が足りないのか。
    次回もうちょっと実験してみましょう。
    次へ

  • 絵本を読んでもらってたけど

    ずっとベッドで寝てたんだけど、小さい時は3個ぐらい横に並べてあって親2人と僕と妹で寝てた。弟ができた時には僕は別の部屋だったけどね。それでも絵本の時間には戻って一緒に見てた。絵本の時間ってのは毎日一冊母さんが母さんなりの迫真の演技で絵本を1冊読むというやつで、声色を変えたりするから面白かった。割と最近になって、その時の本の教訓みたいなのがすごく影響してるみたいで「え、それ普通そう思うよね」ってのが本から来てるってのに気づいた。

     

    例えば僕は何かを取り合ったり決まるのに時間がかかるのがすごい嫌で、「決まらないぐらいならどっちでもいいから早く決めたい」って思うんだけど、絵本にそんなのがあって、2匹の犬が肉を取り合うやつ。

     

    ある日道を歩いてた二匹の犬が道に肉が落ちてるのを見つけて、取り合いになるのね。で喧嘩が始まるんだけど、そこにキツネが来て「2つに分ければいいんだよ」って言うわけ。「天秤で測ってみよう」と言って適当に切った肉を乗せるとどっちかが重いんだよね。「ん〜じゃあ重い方をすこしかじってしまうか。ただ、公平にしないといけないから僕が食べてあげるよ」とキツネが食べる。そうすると今度は反対側が重くなるので「しょうがないなぁ」と食べる。最後には全部キツネが食べるって話。

     

    これを聞くと「多少どっちかが重くても妥協して食べれば食べられない今よりマシじゃん」って思うよね。それは今でもそう思ってて、とにかく争うってのは何もいいこと無くて、それと同じぐらい決定が遅れるってのも何かちょっと嫌で、そのチャンスって肉と一緒で迷ってる間に消える可能性があるんだよね。消えちゃうぐらいなら妥協してでも早く決定したほうが得をすると思う。

     

    大人になってくると選択して失敗するのが嫌だからわざと迷ってチャンスが消えちゃっても「いや〜悩ましい感じだったからなぁ、迷ってたんだからしょうがない」みたいな言い訳に活用したりするけど、それは子供が欲しいものが手にはいらなくても「そ、そんなの欲しくなかったんだもん」っていうのと一緒だよね。負け惜しみってやつだね。それするぐらいなら早く決めたほうが良かったわけです。で、それはこの絵本から来てるってのをすごく感じた。読んでもらってる時の感覚を覚えてるからね。あー、これかぁって。

     

    株の取引とかに向かない性格だろうね。
    それにしても絵本におけるキツネは何故いつも悪いやつなのか。

  • 10. Zynq デバイスドライバを使うC言語のプログラムからLEDを操作してみる

    前回はシェルからデバイスドライバ経由でledを操作しましたが、C言語から操作してみましょう。基本的にはチュートリアルに従うだけです

    user_app

    digilentディレクトリにuser_appを作成します

    [shell]
    mkdir user_app
    cd user_app
    [/shell]

    led_blink.cを作ります。

    [c]

    #include
    #include
    #include

    int main()
    {
    FILE* fp;
    while(1) {
    fp = fopen(“/proc/myled”, “w”);
    if(fp == NULL) {
    printf(“Cannot open /proc/myled for write\n”);
    return -1;
    }
    fputs(“0x0F\n”, fp);
    fclose(fp);
    sleep(1);
    fp = fopen(“/proc/myled”, “w”);
    if(fp == NULL) {
    printf(“Cannot open /proc/myled for write\n”);
    return -1;
    }
    fputs(“0x00\n”, fp);
    fclose(fp);
    sleep(1);
    }
    return 0;
    }

    [/c]

    そして、Makefile

    [shell]

    CC = arm-linux-gnueabi-gcc
    CFLAGS = -g

    all : led_blink

    led_blink : led_blink.o
    $(CC) $(CFLAGS) $^ -o $@

    clean :
    rm -rfv *.o
    rm -rfv led_blink

    .PHONY : clean

    [/shell]

    CC = の値がチュートリアルとは違います。vivadoの方でなくgccを使います。
    Makefileは同じくタブに気をつけて下さい。$(CC)の前の空白などは全てスペースでなくタブです。
    それぞれ作成できたらmakeします

    Screen Shot 2015-01-12 at 16.14.23

    led_blinkという実行ファイルが出来ると思います。

    SDカードに入れて実行

    早速sdカードに入れて実行しましょう。まず、sdカードにled_blinkを入れます

    Screen Shot 2015-01-12 at 16.19.25

    で、ZYBOに入れて実行してみましょう。

    [shell]
    mount /dev/mmcblk0p1 /mnt/
    cd /mnt
    insmod myled.ko
    ./led_blink

    [/shell]

    これでledが4つともチカチカするはず!

    これでチュートリアルは終了です。
    今後、自分の作ったIPをLinux側から使うとしてもLinuxのコンパイルはよほどのことがないかぎりもう1回ってことにはならないでしょう。あとu-bootも変更不要のはずです。なので、今後は

    1. Vivadoからbitstreramを作りBOOT.binを作る
    2. devicetreeを更新する
    3. デバイスドライバを書いてkoファイルを作る
    4. sdに書き実行

    になるでしょう。

  • 9. Zynq Linuxからecho 0x0F > /proc/myled でLEDをつけてみる

    ここまでで作ったファイルは最初のほうで説明した通り

    • BOOT.bin ・・・FSBLとbitstream(FPGAの設定ファイル)が入っている。これが起動してLinuxを動かし始める
    • devicetree.dtb・・・デバイスツリー。どのアドレスに何があるかなどLinuxにデバイスについて教えるためのもの
    • myled.ko ・・・・今回作るLEDのLinuxにおけるデバイスドライバ
    • uImage・・・Linuxカーネル。今回はこれも0からビルドしてみる
    • uramdisk.image.gz・・・HDDはZYBOにないので、ディスクイメージをRAMに展開する。その時の最初のディスクイメージがこれ。

    です。
    これらをSDカードに入れます。

    SDカード準備

    SDカードに余計なファイルがあっても問題ありませんが、気になる人はフォーマットしましょう
    Macであればディスクユーティルからフォーマットできます。MS-DOSで全体をEraseします。

    Screen Shot 2015-01-11 at 11.09.03

     

    SDカードに入れていく

    SDカードにはファイルとしてそれぞれ普通に入れればOKです。
    ZYBOボードにある最初のブートローダがSDカードのBOOT.binを読んでくれるので心配いりません。
    入れるべきファイルと、それぞれが今まで作ってきてどこに作成されたかはこんな感じです

    1. BOOT.bin  zybo_base_system/source/vivado/hw/zybo_bsd/zybo_bsd.sdk/FSBL/bootimage
    2. devicetree.dtb (ubuntuの)digilent/drivers
    3. myled.ko (Ubuntuの)digilent/drivers
    4. uImage (Ubuntuの)digilent/Linux-Digilent-Dev/arch/arm/boot
    5. uramdisk.image.gz (Ubuntuの)digilent

    これらを普通にSDカードにファイルとして入れてしまいます。

    Screen Shot 2015-01-11 at 14.35.18

     

    Linuxを起動する

    ZYBOボードにsdカードを刺して、ブート元切り替えをsdカードにします(RGBコネクタのすぐ下のやつね)そして、USBを接続し、パソコンではターミナルソフト(Teratermなど)をつないでおきます。

    電源をONにします。すると電源確認LEDとFPGAのコンフィグレーションLED両方共点灯すると思います。また、ターミナルソフトには文字が出るはず。
    ちなみにTeratermだと、ボードの電源をいれるたびにCOM6などに接続しないといけないので注意が必要です。速度は115kです。

    LEDをつけてみる

    LEDをつけるためには

    1. sdカードをマウントする(myled.koをつかうので)
    2. insmodでmyled.koを入れる
    3. echo

    の手順が必要です。

    [shell]

    mount /dev/mmcblk0p1 /mnt/
    cd /mnt
    insmod myled.ko
    echo 0xf > /proc/myled

    [/shell]

    これでLEDが点灯するはず!

    2015-01-12 14.56.15

    ちなみに echo 0x5 > /proc/myledとやるとちゃんと変わります
    2015-01-12 14.56.30

    やったね!
    ここまでのシェルはこんな感じでした。

    lb86

    ちなみにここで僕はよくつまづいたのでトラブルシューティングを書いていきます

    トラブルシューティング insmodできない

    [shell]
    zynq> insmod myled.ko
    [  168.884046] myled: no symbol version for module_layout

    insmod: can’t insert ‘myled.ko’: invalid module format
    [/shell]

    こんなエラーが出てinsmod出来ませんでした。
    デバイスドライバーにはコンパイルした時のカーネルの情報が載っているのですが、そのカーネルのバージョンと今動かそうとしているバージョンが違うとエラーになります。カーネルのバージョンは

    [shell]
    uname -r
    3.14.0-xilinx-13567-g906a2c9
    [/shell]

    で、モジュールのバージョンは(これはUbuntuで調べた)

    [shell]
    modinfo myled.ko
    filename: myled.ko
    alias: myled
    description: myled: MYLED driver (Simple Version)
    license: GPL
    author: Digilent, Inc.
    alias: of:N*T*Cdglnt,myled-1.00.a*
    depends:
    vermagic: 3.14.0-xilinx-13567-g906a2c9-dirty SMP preempt mod_unload modversions ARMv7 p2v8
    [/shell]

    でした。
    モジュールの方にだけdirtyというバージョンがついてます。
    dirtyと言うのはカーネルのgitにcommitしていない変更が入っていると自動的に入るもので( http://blogger.tempus.org/2009/05/linux-kernel-version-dirty.html )、カーネルをコンパイルしてドライバをコンパイルするまでに何か変更されちゃったのでしょうか。。。

    強制的に入れようと insmod -f myled.koをしてもダメでした。
    結局カーネルを再度コンパイルしたら成功。再コンパイルしたカーネルにはdirtyがついていました。

    トラブルシューティング echo 0xf > /proc/myledでLEDがつかない

    僕はここまできてledがつきませんでした。
    ただ、echoで書いた値はcatで出てきます。なので、アドレスはあっていて、FPGAにあるLEDのIPモジュールのDフリップフロップには値を読み書きはできているようです。どうやらフリップフロップから外部の端子までの接続がおかしいらしい。

    vivadoに戻ります。ピンアサインを見てもアドレスを見ても問題なさそうでした。
    結局間違っていたのは2箇所で、まず新しく作ったLEDのIPのverilogで
    assign led = slv_reg0[3:0];
    とするべきところを
    assign led = slv_reg3[3:0];
    としていました。
    これだと echo 0xFFFFFFFF > /proc/myled
    としない限りLEDは点灯しません。直してIPを再度作りなおしました

    また、プロジェクト内に削除したはずの古い外部led端子が残っていました。xdcファイルで変更したあれです。
    プロジェクト内検索で全てledに直しました。

    以上2点でBOOT.binを作りなおしたところLED無事点灯!

    デバイスドライバを使わない確認方法

    ちなみにデバイスドライバを使わないでダイレクトに物理メモリを読み書きする方法があります。

    [shell]

    devmem 0x43c30000 32 0x00000001

    [/shell]

    これで0x43c30000という物理アドレスに32bit幅で1という値を書き込めます。
    デバイスドライバがうまく入らないけど、LEDがつくかだけを確認したいときには便利です