FC2カウンター FPGAの部屋 2017年06月

FPGAやCPLDの話題やFPGA用のツールの話題などです。 マニアックです。 日記も書きます。

FPGAの部屋

FPGAの部屋の有用と思われるコンテンツのまとめサイトを作りました。Xilinx ISEの初心者の方には、FPGAリテラシーおよびチュートリアルのページをお勧めいたします。

手書き数字認識用畳み込みニューラルネットワーク回路の製作2(手書き数字認識用四角枠表示回路)

手書き数字認識用畳み込みニューラルネットワーク回路の製作1(概要)”の続き。

前回は、カメラで撮影した画像から手書き数字を指定し、手書き数字認識用畳み込みニューラルネットワークを使用して認識する回路の概要を解説した。今回は、そのパーツとなる手書き数字認識用四角枠表示回路をVivado HLS を使用して作ってみよう。

手書き数字認識用四角枠表示回路は、ある一定の幅と高さのピンクの枠を画像に追加するIP となっている。入出力はAXI4-Stream で、引数はAXI4-Lile でレジスタにマップされている。
ヘッダファイルの square_frame_gen.h を示す。

// square_frame_gen.h
// 2017/06/28 by marsee
//

#ifndef __SQUARE_FRAME_GEN_H__
#define __SQUARE_FRAME_GEN_H__

#define HORIZONTAL_PIXEL_WIDTH    800
#define VERTICAL_PIXEL_WIDTH    600

#define ALL_PIXEL_VALUE    (HORIZONTAL_PIXEL_WIDTH*VERTICAL_PIXEL_WIDTH)

#endif


ソースコードのsquare_frame_gen.cpp を示す。

// square_frame_gen.cpp
// 2017/06/28 by marsee
//

#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>

#include "square_frame_gen.h"

// AXI4-Streamで流れてきた画像に1ピクセルの幅の四角い枠を書く
// x_pos : 四角枠の内側の左上のx位置
// y_pos : 四角枠の内側の左上のy位置
// width : 四角枠内側のピクセルの幅
// height : 四角枠内側のピクセルの高さ
// off_on : 四角枠OFF - 0, ON - 1
int square_frame_gen(hls::stream<ap_axis<32,1,1,1> >& ins,
        hls::stream<ap_axis<32,1,1,1> >& outs,
        int x_pos, int y_pos, int width, int height, int off_on){
#pragma HLS INTERFACE s_axilite port=off_on
#pragma HLS INTERFACE s_axilite port=width
#pragma HLS INTERFACE s_axilite port=x_pos
#pragma HLS INTERFACE s_axilite port=height
#pragma HLS INTERFACE s_axilite port=y_pos
#pragma HLS INTERFACE s_axilite port=return
#pragma HLS INTERFACE axis register both port=outs
#pragma HLS INTERFACE axis register both port=ins

    ap_axis<32,1,1,1> pix;
    int gray_pix, val, i, j, x, y;

    do {
#pragma HLS LOOP_TRIPCOUNT min=1 max=1 avg=1
    // user が 1になった時にフレームがスタートする
        ins >> pix;
    } while(pix.user == 0);

    for (y=0; y<VERTICAL_PIXEL_WIDTH; y++){
        for (x=0; x<HORIZONTAL_PIXEL_WIDTH; x++){
#pragma HLS PIPELINE II=1
            if (!(x==0 && y==0))    // 最初の入力はすでに入力されている
                ins >> pix;    // AXI4-Stream からの入力

            if (off_on){
                if (y==y_pos-1 && x>=x_pos-1 && x<=(x_pos+width+1)) // 上の枠線
                    pix.data = (0xff << 16)+0xff; // R = 0xff, B = 0xff, pink
                else if (y>=y_pos-1 && y<=y_pos+height+1 && (x==x_pos-1 || x==x_pos+width+1)) // 横枠線
                    pix.data = (0xff << 16)+0xff; // R = 0xff, B = 0xff, pink
                else if (y==y_pos+width+1 && x>=x_pos-1 && x<=(x_pos+width+1)) // 下の枠線
                    pix.data = (0xff << 16)+0xff; // R = 0xff, B = 0xff, pink
            }

            outs << pix;
        }
    }

    return(0);
}


Vivado HLS 2016.4 で square_frame_gen プロジェクトを作った。
hand_draw_num_44_170629.png

C シミュレーションを行った。
hand_draw_num_45_170629.png

画像の真ん中を指定したので、四角枠線が書かれている。中のサイズは 28 x 28 ピクセルだ。
hand_draw_num_46_170629.jpg

C コードの合成を行った。結果を示す。
hand_draw_num_47_170629.png

レイテンシは 480005 クロックで、画像サイズは 800 x 600 = 480000 なので、ほぼ1クロックで 1 ピクセルの処理が行えることになる。
AXI4-Lile でのレジスタ・マップを貼っておく。

//------------------------Address Info-------------------
// 0x00 : Control signals
//        bit 0  - ap_start (Read/Write/COH)
//        bit 1  - ap_done (Read/COR)
//        bit 2  - ap_idle (Read)
//        bit 3  - ap_ready (Read)
//        bit 7  - auto_restart (Read/Write)
//        others - reserved
// 0x04 : Global Interrupt Enable Register
//        bit 0  - Global Interrupt Enable (Read/Write)
//        others - reserved
// 0x08 : IP Interrupt Enable Register (Read/Write)
//        bit 0  - Channel 0 (ap_done)
//        bit 1  - Channel 1 (ap_ready)
//        others - reserved
// 0x0c : IP Interrupt Status Register (Read/TOW)
//        bit 0  - Channel 0 (ap_done)
//        bit 1  - Channel 1 (ap_ready)
//        others - reserved
// 0x10 : Data signal of ap_return
//        bit 31~0 - ap_return[31:0] (Read)
// 0x18 : Data signal of x_pos
//        bit 31~0 - x_pos[31:0] (Read/Write)
// 0x1c : reserved
// 0x20 : Data signal of y_pos
//        bit 31~0 - y_pos[31:0] (Read/Write)
// 0x24 : reserved
// 0x28 : Data signal of width
//        bit 31~0 - width[31:0] (Read/Write)
// 0x2c : reserved
// 0x30 : Data signal of height
//        bit 31~0 - height[31:0] (Read/Write)
// 0x34 : reserved
// 0x38 : Data signal of off_on
//        bit 31~0 - off_on[31:0] (Read/Write)
// 0x3c : reserved
// (SC = Self Clear, COR = Clear on Read, TOW = Toggle on Write, COH = Clear on Handshake)


次に、C/RTL協調シミュレーションを行った。
hand_draw_num_48_170629.png

C/RTL協調シミュレーション波形を示す。まずは全体の波形。
hand_draw_num_49_170629.png

最初のAXI4-Lile でのレジスタ設定時の波形を貼っておく。
hand_draw_num_50_170629.png

WDATA は10進数にしてある。x_pos に400、y_pos に 300 、四角枠の幅と高さに 28 を設定するのが分かると思う。

Export RTL を行った。
hand_draw_num_51_170629.png

インプリメントしてもタイミングは満たされているようだ。

最後に、テストベンチのsquare_frame_gen_tb.cpp を貼っておく。

// square_frame_gen_tb.cpp
// 2017/06/28 by marsee
//

#include <stdio.h>
#include <stdlib.h>
#include <ap_int.h>
#include <hls_stream.h>
#include <ap_axi_sdata.h>

#include "square_frame_gen.h"
#include "bmp_header.h"

int square_frame_gen(hls::stream<ap_axis<32,1,1,1> >& ins,
        hls::stream<ap_axis<32,1,1,1> >& outs,
        int x_pos, int y_pos, int width, int height, int off_on);

#define READ_BMP_FILE_NAME    "bmp_file0.bmp"
#define WRITE_BMP_FILE_NAME    "square_frame_gen.bmp"
#define X_POS    400
#define Y_POS    300
#define WIDTH    28
#define HEIGHT    28
#define ON        1

int main(){
    using namespace std;

    hls::stream<ap_axis<32,1,1,1> > ins;
    hls::stream<ap_axis<32,1,1,1> > outs;

    ap_axis<32,1,1,1> pix;
    ap_axis<32,1,1,1> vals;

    BITMAPFILEHEADER bmpfhr; // BMPファイルのファイルヘッダ(for Read)
    BITMAPINFOHEADER bmpihr; // BMPファイルのINFOヘッダ(for Read)
    FILE *fbmpr, *fbmpw;
    int *rd_bmp, *seq_frm;
    int blue, green, red;

    if ((fbmpr = fopen(READ_BMP_FILE_NAME, "rb")) == NULL){ // test.bmp をオープン
        fprintf(stderr, "Can't open ");
        fprintf(stderr, READ_BMP_FILE_NAME);
        fprintf(stderr, " by binary read mode\n");
        exit(1);
    }
    // bmpヘッダの読み出し
    fread(&bmpfhr.bfType, sizeof(uint16_t), 1, fbmpr);
    fread(&bmpfhr.bfSize, sizeof(uint32_t), 1, fbmpr);
    fread(&bmpfhr.bfReserved1, sizeof(uint16_t), 1, fbmpr);
    fread(&bmpfhr.bfReserved2, sizeof(uint16_t), 1, fbmpr);
    fread(&bmpfhr.bfOffBits, sizeof(uint32_t), 1, fbmpr);
    fread(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpr);

    // ピクセルを入れるメモリをアロケートする
    if ((rd_bmp =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate rd_bmp memory\n");
        exit(1);
    }
    if ((seq_frm =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
        fprintf(stderr, "Can't allocate seq_frm memory\n");
        exit(1);
    }

    // rd_bmp にBMPのピクセルを代入。その際に、行を逆転する必要がある
    for (int y=0; y<bmpihr.biHeight; y++){
        for (int x=0; x<bmpihr.biWidth; x++){
            blue = fgetc(fbmpr);
            green = fgetc(fbmpr);
            red = fgetc(fbmpr);
            rd_bmp[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] = (blue & 0xff) | ((green & 0xff)<<8) | ((red & 0xff)<<16);
        }
    }
    fclose(fbmpr);

    // ins に入力データを用意する
    for(int i=0; i<5; i++){    // dummy data
        pix.user = 0;
        pix.data = i;
        ins << pix;
    }

    // BMP画像をins に入力する
    for(int j=0; j < bmpihr.biHeight; j++){
        for(int i=0; i < bmpihr.biWidth; i++){
            pix.data = (ap_int<32>)rd_bmp[(j*bmpihr.biWidth)+i];

            if (j==0 && i==0)    // 最初のデータの時に TUSER を 1 にする
                pix.user = 1;
            else
                pix.user = 0;

            if (i == bmpihr.biWidth-1// 行の最後でTLASTをアサートする
                pix.last = 1;
            else
                pix.last = 0;

            ins << pix;
        }
    }


    square_frame_gen(ins, outs, X_POS, Y_POS, WIDTH, HEIGHT, ON);

    // 書き出すファイルをオープン
    if ((fbmpw=fopen(WRITE_BMP_FILE_NAME, "wb")) == NULL){
        fprintf(stderr, "Can't open ");
        fprintf(stderr, WRITE_BMP_FILE_NAME);
        fprintf(stderr, " by binary write mode\n");
        exit(1);
    }

    // BMPファイルヘッダの書き込み
    fwrite(&bmpfhr.bfType, sizeof(uint16_t), 1, fbmpw);
    fwrite(&bmpfhr.bfSize, sizeof(uint32_t), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved1, sizeof(uint16_t), 1, fbmpw);
    fwrite(&bmpfhr.bfReserved2, sizeof(uint16_t), 1, fbmpw);
    fwrite(&bmpfhr.bfOffBits, sizeof(uint32_t), 1, fbmpw);
    fwrite(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpw);

    for(int j=0; j < bmpihr.biHeight; j++){
        for(int i=0; i < bmpihr.biWidth; i++){
            outs >> vals;
            ap_int<32> val = vals.data;

            seq_frm[(j*bmpihr.biWidth)+i] = (int)val;
        }
    }

    // RGB データの書き込み、逆順にする
    for (int y=0; y<bmpihr.biHeight; y++){
        for (int x=0; x<bmpihr.biWidth; x++){
            blue = seq_frm[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] & 0xff;
            green = (seq_frm[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] >> 8) & 0xff;
            red = (seq_frm[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x]>>16) & 0xff;

            fputc(blue, fbmpw);
            fputc(green, fbmpw);
            fputc(red, fbmpw);
        }
    }
    fclose(fbmpw);

    return(0);
}

  1. 2017年06月29日 04:54 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

手書き数字認識用畳み込みニューラルネットワーク回路の製作1(概要)

手書き数字認識用畳み込みニューラルネットワークができたので、カメラで撮影した画像の中の手書き数字を認識してみようと思う。
使用するボードはPYNQボードとする。以前に、PYNQボードでFASTX コーナー検出IP を使用したプロジェクトがあるので、それをベースとする。

ブロック図を下に示す。
hand_draw_num_43_170627.png

上の図では、AXI4-Stream Switcher が2つ使用されているが、四角枠が出っ放しでよければ、この2 つのAXI4-Stream Switcher は除くかもしれない。
カメラからの入力をAXI4-Stream で出力しているが、それに手書き数字認識用四角枠表示回路で28 x 28 ピクセルの四角枠を書く。どの位置に四角枠を書いたか?の位置は、レジスタにマップされてAXI4-Lile で読めるようにしておく。
AXI4 Master インターフェースに手書き数字認識用畳み込みニューラルネットワークIP がつながっている。プロセッサ経由で手書き数字認識用四角枠表示回路から位置情報をもらって、四角枠の中のピクセルデータをAXI4 Master インターフェースでDMA してくる。手書き数字認識用畳み込みニューラルネットワークIP も四角枠をDMA するので、入力の記述を変更する必要がある。書き換えよう。
例を示す。ピンクの四角枠で囲われている中の手書き数字を手書き数字認識用畳み込みニューラルネットワークで認識する。
hand_draw_num_40_170627.jpg

四角枠は手書き数字認識用四角枠表示回路で、表示位置 X, Y をレジスタで持っていてその位置に描画するようにしよう。
人とのインターフェースだが、キーを押したら四角枠が動いていくようにしたい。
  1. 2017年06月28日 05:19 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

MNISTの畳み込みニューラルネットワークで自分の手書き数字を認識する3(カメラ画像)

MNISTの畳み込みニューラルネットワークで自分の手書き数字を認識する2”の続き。

前回はGIMP2 で幅 2 ピクセルの鉛筆ツールを使用して書いた手書き数字を認識させてみた。今回は、手書き数字をカメラで撮影した画像を切り出して認識させてみよう。

まずは、久しぶりにZYBO をブートして、”Zybot のカメラ画像をBMPファイルに変換するアプリケーションを作成した”の cam_capture_bmp.cpp をコンパイルして、実行ファイルの cam_capture_bmp を生成した。
cam_capture_bmp を起動して、BMP画像を取得した。
hand_draw_num_32_170627.png

取得した bmp_file0.bmp を示す。表示ツールは gthumb を使用している。
hand_draw_num_33_170627.png

WinSCP を使用して、bmp_file0.bmp をZYBOからパソコンに持ってきた。
GIMP2 を立ち上げて、手書き数字を切り出して 28 x 28 のBMPファイルにした。
hand_draw_num_34_170627.png

0 (test0.bmp) の画像を示す。
hand_draw_num_35_170627.png

手書き数字が大きいときは、32 x 32 位に切り取って、GIMP2 の「画像」メニューの「画像の拡大・縮小」を選択して、28 x 28 に縮小する。
BMPファイルにエクスポートするときは、「互換性のオプション」の「色空間の情報を書き込まない」にチェックを入れて、「詳細設定」は「24 ビット R8 G8 B8」のラジオボタンをクリックしてから、エクスポートボタンをクリックする。
hand_draw_num_36_170627.png

必要なBMPファイルができた。
hand_draw_num_37_170627.png

VirtualBox 上のUbuntu 16.04 のVivado HLS 2016.4 に持って行った。
hand_draw_num_38_170627.png

Vivado HLS で C シミュレーションを行った。
hand_draw_num_39_170627.png

レポートを示す。

INFO: [SIM 2] *************** CSIM start ***************
INFO: [SIM 4] CSIM will launch GCC as the compiler.
Compiling ../../../mnist_conv_nn_tb_bmp.cpp in debug mode
Compiling ../../../mnist_conv_nn10.cpp in debug mode
Generating csim.exe
test0.bmp
id = 0, max_id_hw = 0
id = 0, max_id_sw = 0
test1.bmp
id = 1, max_id_hw = 1
id = 1, max_id_sw = 1
test2.bmp
id = 2, max_id_hw = 3
id = 2, max_id_sw = 2
test3.bmp
id = 3, max_id_hw = 3
id = 3, max_id_sw = 3
test4.bmp
id = 4, max_id_hw = 4
id = 4, max_id_sw = 4
test5.bmp
id = 5, max_id_hw = 5
id = 5, max_id_sw = 5
test6.bmp
id = 6, max_id_hw = 6
id = 6, max_id_sw = 6
test7.bmp
id = 7, max_id_hw = 7
id = 7, max_id_sw = 7
test8.bmp
id = 8, max_id_hw = 8
id = 8, max_id_sw = 8
test9.bmp
id = 9, max_id_hw = 4
id = 9, max_id_sw = 4
INFO: [SIM 1] CSim done with 0 errors.
INFO: [SIM 3] *************** CSIM finish ***************


ハードウェアでは 2 が 3 に間違っていて、ソフトウェア、ハードウェア共に、9 を 4 に間違っていた。
まあ、精度はいまいちだが、一応、カメラで撮影した手書き数字を認識できているようなので、本格的にPYNQボードに組み込んで、カメラで撮影した画像から手書き数字を認識させてみようかな?
  1. 2017年06月27日 05:13 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

MNISTの畳み込みニューラルネットワークで自分の手書き数字を認識する2

MNISTの畳み込みニューラルネットワークで自分の手書き数字を認識する1”の続き。

前回はお絵描きソフトのPaint.net でブラシ幅 2 のペイントブラシ・ツールを使用して書いた手書き数字を認識してみたが、認識精度はいまいちだった。今回は、GIMP2 で幅 2 ピクセルの鉛筆ツールを使用して書いた手書き数字を認識させてみようと思う。

最初にGIMP2 で幅 2 ピクセルの鉛筆ツールを使用して 0 ~ 9 までの数字を書いた。
hand_draw_num_16_170626.png

hand_draw_num_17_170626.png

hand_draw_num_18_170626.png

hand_draw_num_19_170626.png

hand_draw_num_20_170626.png

hand_draw_num_21_170626.png

hand_draw_num_22_170626.png

hand_draw_num_23_170626.png

hand_draw_num_24_170626.png

hand_draw_num_25_170626.png

hand_draw_num_26_170626.png

これを前回のmnist_conv_nn10_bmp プロジェクトに入れる。
hand_draw_num_27_170626.png

早速、C シミュレーションを行った。
hand_draw_num_28_170626.png

INFO: [SIM 2] *************** CSIM start ***************
INFO: [SIM 4] CSIM will launch GCC as the compiler.
make: 'csim.exe' は更新済みです.
test0.bmp
id = 0, max_id_hw = 0
id = 0, max_id_sw = 6
test1.bmp
id = 1, max_id_hw = 5
id = 1, max_id_sw = 5
test2.bmp
id = 2, max_id_hw = 2
id = 2, max_id_sw = 2
test3.bmp
id = 3, max_id_hw = 3
id = 3, max_id_sw = 3
test4.bmp
id = 4, max_id_hw = 4
id = 4, max_id_sw = 4
test5.bmp
id = 5, max_id_hw = 5
id = 5, max_id_sw = 5
test6.bmp
id = 6, max_id_hw = 5
id = 6, max_id_sw = 5
test7.bmp
id = 7, max_id_hw = 7
id = 7, max_id_sw = 7
test8.bmp
id = 8, max_id_hw = 8
id = 8, max_id_sw = 8
test9.bmp
id = 9, max_id_hw = 2
id = 9, max_id_sw = 2
INFO: [SIM 1] CSim done with 0 errors.
INFO: [SIM 3] *************** CSIM finish ***************


前回よりも間違いが多い。1 は 5 に間違っているし、6 も 5 に間違っている。9 は 2 に間違っている。
こうしてみると、私の書く 6 はMNISTの 6 と違うのかもしれない? 6 の丸の上の線がもう少し下がっていなくてはいけないのかもしれない。
もう一度、GIMP2 で 6 を書いてみた。
hand_draw_num_29_170626.png

mnist_conv_nn10_bmp プロジェクトを示す。
hand_draw_num_30_170626.png

C シミュレーションを行った。
hand_draw_num_31_170626.png

今度は 6 が 3 として認識されてしまった。どう書けば良いのだろうか?

自分でマウスで書いた手書き数字を認識するのは難しい様だ。自分で本当に紙に書いた手書き数字をカメラで撮影した画像で認識させたらどうなのだろうか?
  1. 2017年06月26日 04:43 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

MNISTの畳み込みニューラルネットワークで自分の手書き数字を認識する1

「ゼロから作るDeep Learning」の畳み込みニューラルネットワークのハードウェア化8(性能向上を図る)”までで、「ゼロから作るDeep Learning」の畳み込みニューラルネットワークのハードウェア化は完了した。しかし、手書き数字を認識するとは言ってもMNISTの手書き数字を認識する精度が 99 % と言っても自分の手書き数字を認識できたわけではない。ということで、自分で手描きの数字を書いて認識させてみることにした。

手描き数字は、お絵描きソフトのPaint.net で書いた。書いたのはブラシ幅 2 のペイントブラシ・ツールだ。
hand_draw_num_1_170625.png

これだけだと、今まで使ってきたBMPファイル読み込みルーチンで読み込めないので、GIMP2 で「色空間の情報を書き込まない」にチェックを入れて、24ビット R8 G8 B8 でエクスポートした。
hand_draw_num_2_170625.png

これで、0 ~ 9 までの手書き数字を作成した。(temp0.bmp ~ temp9.bmp)
hand_draw_num_3_170625.png

hand_draw_num_4_170625.png

hand_draw_num_5_170625.png

hand_draw_num_6_170625.png

hand_draw_num_7_170625.png

hand_draw_num_8_170625.png

hand_draw_num_9_170625.png

hand_draw_num_10_170625.png

hand_draw_num_11_170625.png

hand_draw_num_12_170625.png

VirtualBox 上のUbuntu 16.04 に mnist_conv_nn10_bmp という名前のVivado HLS 2016.4 プロジェクトを作成して、mnist_conv_nn_tb_bmp.cpp テストベンチを作成した。ソースコードは以前と変わりは無い。
hand_draw_num_13_170625.png

hand_draw_num_14_170625.png

これで C シミュレーションを行った。結果を示す。
hand_draw_num_15_170625.png

大体行けてはいるのだが、精度はより低くなっている感じだ。特に 6 が 5 と誤認識されている。
MNISTデータセットと書いた人が違うのもあるが、ブラシ幅 2 のペイントブラシ・ツールで書いたという環境の違いなどがあると思う。汎化能力が足りないということも言えるが環境が違うので精度が落ちるのは仕方がないところだろう。精度を上げるには、ブラシ幅 2 のペイントブラシ・ツールで書いた6万枚くらいの手書き数字を集める必要があるのかもしれない?
もう一度、他のツールで書いた手書き数字で確かめてみよう。

最後に、mnist_conv_nn_tb_bmp.cpp を貼っておく。

// mnist_conv_nn_tb_bmp.cpp
// 2017/06/14 by marsee
// 2017/06/21 : test0.bmp ~ test9.bmpファイルを読み込んで結果を出力する
//

#include <stdio.h>
#include <ap_fixed.h>
#include <string.h>

#include "conv1_weight.h"
#include "conv1_bias.h"
#include "af1_weight.h"
#include "af1_bias.h"
#include "af2_weight.h"
#include "af2_bias.h"

#include "bmp_header.h"


int mnist_conv_nn(ap_ufixed<80, AP_TRN_ZERO, AP_SAT> in[784], ap_fixed<127, AP_TRN_ZERO, AP_SAT> out[10]);
int mnist_conv_nn_float(float in[784], float out[10]);
int max_ap_fixed(ap_fixed<127, AP_TRN_ZERO, AP_SAT> out[10]);
int max_float(float out[10]);
int conv_rgb2y(int rgb);

#define NUM_ITERATIONS    10 // C Simulation

int main(){
    ap_fixed<127, AP_TRN_ZERO, AP_SAT> result_ap_fixed[NUM_ITERATIONS][10];
    float result_float[NUM_ITERATIONS][10];
    int max_id_hw, max_id_sw, max_id_ref;
    BITMAPFILEHEADER bmpfhr; // BMPファイルのファイルヘッダ(for Read)
    BITMAPINFOHEADER bmpihr; // BMPファイルのINFOヘッダ(for Read)
    char namestr[100];
    FILE *fbmpr;
    int *rd_bmp;
    float *rd_bmpf;
    ap_ufixed<80, AP_TRN_ZERO, AP_SAT> *rd_bmp_apf;
    int blue, green, red;

    for(int i=0; i<NUM_ITERATIONS; i++){
        sprintf(namestr, "test%d.bmp", i);
        printf("%s\n", namestr);
        if ((fbmpr = fopen(namestr, "rb")) == NULL){ // test??.bmp をオープン
            fprintf(stderr, "Can't open test%d.bmp by binary read mode\n", i);
            exit(1);
        }
        // bmpヘッダの読み出し
        fread(&bmpfhr.bfType, sizeof(uint16_t), 1, fbmpr);
        fread(&bmpfhr.bfSize, sizeof(uint32_t), 1, fbmpr);
        fread(&bmpfhr.bfReserved1, sizeof(uint16_t), 1, fbmpr);
        fread(&bmpfhr.bfReserved2, sizeof(uint16_t), 1, fbmpr);
        fread(&bmpfhr.bfOffBits, sizeof(uint32_t), 1, fbmpr);
        fread(&bmpihr, sizeof(BITMAPINFOHEADER), 1, fbmpr);

        // ピクセルを入れるメモリをアロケートする
        if ((rd_bmp =(int *)malloc(sizeof(int) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
            fprintf(stderr, "Can't allocate rd_bmp memory\n");
            exit(1);
        }
        if ((rd_bmpf =(float *)malloc(sizeof(float) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
            fprintf(stderr, "Can't allocate rd_bmpf memory\n");
            exit(1);
        }
        if ((rd_bmp_apf =(ap_ufixed<80, AP_TRN_ZERO, AP_SAT> *)malloc(sizeof(ap_ufixed<80, AP_TRN_ZERO, AP_SAT>) * (bmpihr.biWidth * bmpihr.biHeight))) == NULL){
            fprintf(stderr, "Can't allocate rd_bmp_apf memory\n");
            exit(1);
        }

        // rd_bmp にBMPのピクセルを代入。その際に、行を逆転する必要がある
        for (int y=0; y<bmpihr.biHeight; y++){
            for (int x=0; x<bmpihr.biWidth; x++){
                blue = fgetc(fbmpr);
                green = fgetc(fbmpr);
                red = fgetc(fbmpr);
                rd_bmp[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] = (blue & 0xff) | ((green & 0xff)<<8) | ((red & 0xff)<<16);
                rd_bmp[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] = conv_rgb2y(rd_bmp[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x]);

                rd_bmpf[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] = 1.0 - (float)rd_bmp[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x]/256.0; // 白地に黒文字から黒字に白文字に変換
                //if (rd_bmpf[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] < 0.01)
                    //rd_bmpf[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] = 0.0;

                rd_bmp_apf[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x] = (ap_ufixed<80, AP_TRN_ZERO, AP_SAT>)rd_bmpf[((bmpihr.biHeight-1)-y)*bmpihr.biWidth+x];
            }
        }
        fclose(fbmpr);

        /* for (int y=0; y<bmpihr.biHeight; y++){            for (int x=0; x<bmpihr.biWidth; x++){                printf("%f ", (float)rd_bmp_apf[y*bmpihr.biWidth+x]);            }            printf("\n");        }        printf("\n"); */

        mnist_conv_nn(rd_bmp_apf, &result_ap_fixed[i][0]);
        mnist_conv_nn_float(rd_bmpf, &result_float[i][0]);

        max_id_hw = max_ap_fixed(&result_ap_fixed[i][0]);
        max_id_sw = max_float(&result_float[i][0]);
        printf("id = %d, max_id_hw = %d\n", i, max_id_hw);
        printf("id = %d, max_id_sw = %d\n", i, max_id_sw);

        free(rd_bmp);
        free(rd_bmpf);
        free(rd_bmp_apf);
    }

    return(0);
}

int mnist_conv_nn_float(float in[784], float out[10]){
    float buf[28][28];
    float conv_out[10][24][24];
    float pool_out[10][12][12];
    float dot1[100];
    float dot2[10];

    buf_copy1: for(int i=0; i<28; i++)
        buf_copy2: for(int j=0; j<28; j++)
            buf[i][j] = in[i*28+j];

    // Convolutional Neural Network 5x5 kernel, Stride = 1, Padding = 0
    // + ReLU
    CONV1: for(int i=0; i<10; i++){    // カーネルの個数
        CONV2: for(int j=0; j<24; j++){
            CONV3: for(int k=0; k<24; k++){
                conv_out[i][j][k] = 0;
                CONV4: for(int m=0; m<5; m++){
                    CONV5: for(int n=0; n<5; n++){
                        conv_out[i][j][k] += buf[j+m][k+n] * conv1_fweight[i][0][m][n];
                    }
                }
                conv_out[i][j][k] += conv1_fbias[i];

                if(conv_out[i][j][k]<0)    // ReLU
                    conv_out[i][j][k] = 0;
            }
        }
    }

    // Pooling Kernel = 2 x 2, Stride = 2
    POOL1: for(int i=0; i<10; i++){
        POOL2: for(int j=0; j<24; j += 2){
            POOL3: for(int k=0; k<24; k += 2){
                POOL4: for(int m=0; m<2; m++){
                    POOL5: for(int n=0; n<2; n++){
                        if(m==0 && n==0){
                            pool_out[i][j/2][k/2] = conv_out[i][j][k];
                        } else if(pool_out[i][j/2][k/2] < conv_out[i][j+m][k+n]){
                            pool_out[i][j/2][k/2] = conv_out[i][j+m][k+n];
                        }
                    }
                }
            }
        }
    }

    af1_dot1: for(int col=0; col<100; col++){
        dot1[col] = 0;
        af1_dot2: for(int i=0; i<10; i++){
            af1_dot3: for(int j=0; j<12; j++){
                af1_dot4: for(int k=0; k<12; k++){
                    dot1[col] += pool_out[i][j][k]*af1_fweight[i*12*12+j*12+k][col];
                }
            }
        }
        dot1[col] += af1_fbias[col];

        if(dot1[col] < 0)    // ReLU
            dot1[col] = 0;
    }

    af2_dot1: for(int col=0; col<10; col++){
        dot2[col] = 0;
        af2_dot2: for(int row=0; row<100; row++){
            dot2[col] += dot1[row]*af2_fweight[row][col];
        }
        dot2[col] += af2_fbias[col];

        out[col] = dot2[col];
    }

    return(0);
}

int max_ap_fixed(ap_fixed<127, AP_TRN_ZERO, AP_SAT> out[10]){
    int max_id;
    ap_fixed<127, AP_TRN_ZERO, AP_SAT> max;

    for(int i=0; i<10; i++){
        if(i == 0){
            max = out[0];
            max_id = 0;
        }else if(out[i]>max){
            max = out[i];
            max_id = i;
        }
    }
    return(max_id);
}

int max_float(float out[10]){
    int max_id;
    float max;

    for(int i=0; i<10; i++){
        if(i == 0){
            max = out[0];
            max_id = 0;
        }else if(out[i]>max){
            max = out[i];
            max_id = i;
        }
    }
    return(max_id);
}

// RGBからYへの変換
// RGBのフォーマットは、{8'd0, R(8bits), G(8bits), B(8bits)}, 1pixel = 32bits
// 輝度信号Yのみに変換する。変換式は、Y =  0.299R + 0.587G + 0.114B
// "YUVフォーマット及び YUV<->RGB変換"を参考にした。http://vision.kuee.kyoto-u.ac.jp/~hiroaki/firewire/yuv.html
// 2013/09/27 : float を止めて、すべてint にした
int conv_rgb2y(int rgb){
    int r, g, b, y_f;
    int y;

    b = rgb & 0xff;
    g = (rgb>>8) & 0xff;
    r = (rgb>>16) & 0xff;

    y_f = 77*r + 150*g + 29*b; //y_f = 0.299*r + 0.587*g + 0.114*b;の係数に256倍した
    y = y_f >> 8// 256で割る

    return(y);
}

  1. 2017年06月25日 06:39 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

Linux 版Vivado のアンインストール方法

Linux 版のVivado はUbuntu にインストールするとコマンドラインから起動しているので、アンインストール方法が GUI で指定できません。

そこで、検索したところ、「小ネタ: vivadoのアンインストール」が検索できました。
それによると、

sudo [installPath]/.xinstall/Vivado_[バージョン番号]/ xsetup –Uninstall

だそうです。
インストールパスはデフォルトでは、/opt/Xilinx/ ですね。
この xsetup を Uninstall モードで実行したところ、正常にアンインストールができました。ただし、スーパーユーザー・モードで実行する必要があります。
  1. 2017年06月24日 04:36 |
  2. Vivado
  3. | トラックバック:0
  4. | コメント:0

「ゼロから作るDeep Learning」の畳み込みニューラルネットワークのハードウェア化8(性能向上を図る)

「ゼロから作るDeep Learning」の畳み込みニューラルネットワークのハードウェア化7(C/RTL協調シミュレーション)”の続き。

前回は、C/RTL協調シミュレーションを行って、波形を観察し、IP 化を行った。今回は、実際に使用するインターフェースのAXI4-Stream 入力、AXI4-Lite 出力にして、少しチューニングをしてみよう。

書き換えた mnist_conv_nn10.cppを示す。

// mnist_conv_nn10.cpp
// 2017/06/12 by marsee
// 畳み込み層のカーネル数 10
//

#include <ap_fixed.h>

#include "conv1_weight.h"
#include "conv1_bias.h"
#include "af1_weight.h"
#include "af1_bias.h"
#include "af2_weight.h"
#include "af2_bias.h"

int mnist_conv_nn(ap_ufixed<80, AP_TRN_ZERO, AP_SAT> in[784], ap_fixed<127, AP_TRN_ZERO, AP_SAT> out[10]){
#pragma HLS INTERFACE s_axilite register port=out
#pragma HLS INTERFACE axis register both port=in
#pragma HLS INTERFACE s_axilite port=return
    ap_ufixed<80, AP_TRN_ZERO, AP_SAT> buf[28][28];
    ap_fixed<103, AP_TRN_ZERO, AP_SAT> conv_out[10][24][24];
#pragma HLS ARRAY_PARTITION variable=conv_out cyclic factor=2 dim=3
    ap_fixed<103, AP_TRN_ZERO, AP_SAT> pool_out[10][12][12];
#pragma HLS ARRAY_PARTITION variable=pool_out block factor=12 dim=3
    ap_fixed<137, AP_TRN_ZERO, AP_SAT> dot1[100];
    ap_fixed<137, AP_TRN_ZERO, AP_SAT> dot2[10];

    buf_copy1: for(int i=0; i<28; i++)
        buf_copy2: for(int j=0; j<28; j++)
#pragma HLS PIPELINE II=1
            buf[i][j] = in[i*28+j];

    // Convolutional Neural Network 5x5 kernel, Stride = 1, Padding = 0
    // + ReLU
    CONV1: for(int i=0; i<10; i++){    // カーネルの個数
        CONV2: for(int j=0; j<24; j++){
            CONV3: for(int k=0; k<24; k++){
                conv_out[i][j][k] = 0;
                CONV4: for(int m=0; m<5; m++){
#pragma HLS PIPELINE II=7
                    CONV5: for(int n=0; n<5; n++){
                        conv_out[i][j][k] += buf[j+m][k+n] * conv1_weight[i][0][m][n];
                    }
                }
                conv_out[i][j][k] += conv1_bias[i];

                if(conv_out[i][j][k]<0)    // ReLU
                    conv_out[i][j][k] = 0;
            }
        }
    }

    // Pooling Kernel = 2 x 2, Stride = 2
    POOL1: for(int i=0; i<10; i++){
        POOL2: for(int j=0; j<24; j += 2){
            POOL3: for(int k=0; k<24; k += 2){
                POOL4: for(int m=0; m<2; m++){
#pragma HLS PIPELINE II=4
                    POOL5: for(int n=0; n<2; n++){
                        if(m==0 && n==0){
                            pool_out[i][j/2][k/2] = conv_out[i][j][k];
                        } else if(pool_out[i][j/2][k/2] < conv_out[i][j+m][k+n]){
                            pool_out[i][j/2][k/2] = conv_out[i][j+m][k+n];
                        }
                    }
                }
            }
        }
    }

    af1_dot1: for(int col=0; col<100; col++){
        dot1[col] = 0;
        af1_dot2: for(int i=0; i<10; i++){
            af1_dot3: for(int j=0; j<12; j++){
                af1_dot4: for(int k=0; k<12; k++){
#pragma HLS PIPELINE II=3
                    dot1[col] += pool_out[i][j][k]*af1_weight[i*12*12+j*12+k][col];
                }
            }
        }
        dot1[col] += af1_bias[col];

        if(dot1[col] < 0)    // ReLU
            dot1[col] = 0;
    }

    af2_dot1: for(int col=0; col<10; col++){
        dot2[col] = 0;
        af2_dot2: for(int row=0; row<100; row++){
#pragma HLS PIPELINE II=4
            dot2[col] += dot1[row]*af2_weight[row][col];
        }
        dot2[col] += af2_bias[col];

        out[col] = dot2[col];
    }

    return(0);
}


このソースコードでのC コードの合成結果を示す。
nn_fpga_ch7_44_170623.png

Estimated は 13.48 ns で 10 ns をオーバーしている。
レイテンシは 690991 クロックで、100 MHz の場合は、6.91 ms となる。大幅にリソースを増やすこと無しに、これ以上速くならなかったのが残念だ。
リソースもBRAM_18K が 55 % から 59 % になった。FF も LUT も増えているが、AXI バスにしたので、少し増えたのもある。なお、Vivado HLS のバージョンは 2016.4 だ。
AXI バスにしたので、Address Info を貼っておく。これを見るとレジスタとそのオフセット・アドレスが分かる。

//------------------------Address Info-------------------
// 0x00 : Control signals
//        bit 0  - ap_start (Read/Write/COH)
//        bit 1  - ap_done (Read/COR)
//        bit 2  - ap_idle (Read)
//        bit 3  - ap_ready (Read)
//        bit 7  - auto_restart (Read/Write)
//        others - reserved
// 0x04 : Global Interrupt Enable Register
//        bit 0  - Global Interrupt Enable (Read/Write)
//        others - reserved
// 0x08 : IP Interrupt Enable Register (Read/Write)
//        bit 0  - Channel 0 (ap_done)
//        bit 1  - Channel 1 (ap_ready)
//        others - reserved
// 0x0c : IP Interrupt Status Register (Read/TOW)
//        bit 0  - Channel 0 (ap_done)
//        bit 1  - Channel 1 (ap_ready)
//        others - reserved
// 0x10 : Data signal of ap_return
//        bit 31~0 - ap_return[31:0] (Read)
// 0x20 ~
// 0x3f : Memory 'out_V' (10 * 12b)
//        Word n : bit [11: 0] - out_V[2n]
//                 bit [27:16] - out_V[2n+1]
//                 others      - reserved
// (SC = Self Clear, COR = Clear on Read, TOW = Toggle on Write, COH = Clear on Handshake)


次に、実際にインプリメントしてみて、10 ns に収まるかどうか?を見るために、Place and Route にチェックを入れてExport RTL を行った。
結果はちょっと危ないが、一応 10 ns に収まっている。
nn_fpga_ch7_45_170623.png

VirtualBox 上のUbuntu 16.04 上のVivado HLS 2016.4 でC/RTL協調シミュレーションをやってみた。結果を示す。
nn_fpga_ch7_46_170623.png

C/RTL協調シミュレーション波形を見た。全体の波形を示す。
nn_fpga_ch7_47_170623.png

入力部分のAXI4-Stream 部分を拡大した。8ビットで転送されている。
nn_fpga_ch7_48_170623.png

結果の出力部分だ。
nn_fpga_ch7_49_170623.png

0x2C 番地のデータが 0x0ee901b4 で、bit [11: 0] - out_V[2n] が 0x01b4 で最大なので、結果は 6 で正解だ。あとの数はすべてマイナスだった。

とりあえず、これでIP にしようと思う。IP 化しVivado のプロジェクトで使用する前に、私の書いた手描き数字を認識できるかどうか?のテストをしたいと思う。
  1. 2017年06月23日 05:01 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

「ゼロから作るDeep Learning」の畳み込みニューラルネットワークのハードウェア化7(C/RTL協調シミュレーション)

”「ゼロから作るDeep Learning」の畳み込みニューラルネットワークのハードウェア化6(再度Vivado HLS )”の続き。

前回は、畳み込みニューラルネットワークをVivado HLS でハードウェア化することができた。今回は、HDLコードを見たり、C/RTL協調シミュレーションをしてみてから、IP化を行ってみよう。ただし、デフォルトのインターフェースのままでは、IP にしたときに使いにくいので、実際に使用する際にはAXI バスにする。

最初に生成されたHDLコードを見てみよう。VHDL コードを見る。トップファイルのmnist_conv_nn.vhd はメモリのインターフェースとなっている。
nn_fpga_ch7_33_170621.png

さて、次にC/RTL協調シミュレーションをやってみよう。ただし、100 回は長すぎるので、1 回だけやってみる。
nn_fpga_ch7_34_170621.png

C/RTL協調シミュレーションの結果を示す。
nn_fpga_ch7_35_170621.png

172089 クロックかかっている。

C/RTL協調シミュレーションの波形を示す。まずは全体の波形から。
nn_fpga_ch7_43_170621.png

入力の部分を拡大してみた。
nn_fpga_ch7_36_170621.png

更に拡大した。値を見ることができる。
nn_fpga_ch7_37_170621.png

出力部分を見た。答えの 6 以外はMSB のビットが立っているので、マイナスの値になっているのが分かる。
nn_fpga_ch7_38_170621.png

試しに、テストベンチの固定小数点の最大値を調べる関数にプリント文を追加して値を見ることにした。
nn_fpga_ch7_39_170621.png

そして、C シミュレーションを行った結果を示す。
nn_fpga_ch7_40_170621.png

やはり、6 だけプラスの値で、13.625 だった。分かりやすい結果となった。

次に、試しにIP 化を行ってみた。
インプリメントして、動作周波数が間に合うのか?を調べるために、Place and Route にチェックを入れた。
nn_fpga_ch7_41_170621.png

その結果、問題ないようだった。
nn_fpga_ch7_42_170621.png
  1. 2017年06月21日 05:11 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

「ゼロから作るDeep Learning」の畳み込みニューラルネットワークのハードウェア化6(再度Vivado HLS )

「ゼロから作るDeep Learning」の畳み込みニューラルネットワークのハードウェア化5(再度学習)”の続き。

前回は、畳み込み層のカーネルを10個にして畳み込みニューラルネットワークを学習させたところ、固定小数点数用に量子化した精度も 98.69 % だった。
今回は、畳み込み層のカーネルを10個にしたときの重みやバイアスを使用してVivado HLSで畳み込みニューラルネットワークを実装してみよう。

まずは、Vivado HLS 2017.1 で新たにmnist_conv_nn10 プロジェクトを作成した。
畳み込みニューラルネットワークのC ソースコードのファイル名を mnist_conv_nn10.cpp とした。
nn_fpga_ch7_23_170620.png

早速、C コードの合成を行った。結果を示す。
nn_fpga_ch7_24_170620.png

レイテンシはクロックを100 MHz しているので、約 27.4 ms だった。リソースもBRAM_18K hが 55 % なので、PYNQボードに入る。

精度を見るために、テストベンチのmnist_conv_nn_tb.cpp を作成してC シミュレーションを行った。
nn_fpga_ch7_25_170620.png

結果はエラーだった。このエラーを検索すると”AR# 69196 Vivado HLS 2017.1 : cc1plus.exe: out of memory allocating 65536 bytes”が検索できた。それによると、Windows では 2016.4 を使えということなので、Vivado 2016.4 に切り替えてC シミュレーションを行った。
結果は2017.1 と同じエラーだった。
nn_fpga_ch7_26_170620.png

試しに、Vivado HLS 2016.4 で C コードの合成を行った。
nn_fpga_ch7_27_170620.png

Vivado HLS 2016.4 のレイテンシはクロックを100 MHz としたときは、17.2 ms になった。Vivado HLS 2017.1 で合成したときよりも 10 ms 少ない。
2017.1 との比較は、DSP48E は、2個から4個と増えているが、FF とLUT は減っている。ともかく、10 ms の処理時間の短縮は大きいので、2016.4 を使用することにした。誤解のないように書いておくと、30 fps のカメラ画像の手書き数字を認識するという用途には、少なくとも 33.3 ms の変換時間を満たしていれば良いため、27.4 ms の変換時間の 2017.1 でも十分な性能であるということが言える。2016.4 はオーバースペックであるということもできるが、やはり速いほうが気持ちよいし、DSP48E は倍の4個だがFF やLUT は少ないので、こっちを採用したい。

さて、C シミュレーションをする必要があるため、Linux のVivado HLS 2016.4 を使用することにした。Ubuntu 16.04 上のVivado HLS 2016.4 を使用する。
nn_fpga_ch7_28_170620.png

C シミュレーションを実行したところ、成功した。Linux のVivado HLS だと成功するようだ。
nn_fpga_ch7_29_170620.png

結果は手書き文字 100 個の比較を行って浮動小数点数で演算したソフトウェアは誤りが 1 個だったが、固定小数点数では、3 個間違っている。
少し間違えすぎかな?Python での固定小数点数のシミュレーションでは、シフトをし、integer に変換して量子化を行い、それを浮動小数点数に戻して、演算を行い、その出力を変域を指定するために再度、量子化していた。これは、演算は浮動小数点数で行っている。Vivado HLS で実装した任意精度固定小数点数では、演算は固定小数点数で行われるため精度が落ちてしまった可能性がある。試しに、小数部分の桁が少ない全結合層のビット幅を 12 から 13 にしてみた。これで、小数部分の桁が増える。
nn_fpga_ch7_30_170620.png

これでC シミュレーションを行った。
nn_fpga_ch7_31_170620.png

結果はソフトウェアとハードウェアの間違いはどちらも 1 個になった。これで行こうと思う。精度はどちらも 99 % だ。
修正して、Windows のVivado HLS 2016.4 でC コードの合成を行った結果を示す。
nn_fpga_ch7_32_170620.png

BRAM_18K とDSP48E は同じで、FF とLUT が少し増えている。

mnist_conv_nn10.cpp を貼っておく。

// mnist_conv_nn10.cpp
// 2017/06/12 by marsee
// 畳み込み層のカーネル数 10
//

#include <ap_fixed.h>

#include "conv1_weight.h"
#include "conv1_bias.h"
#include "af1_weight.h"
#include "af1_bias.h"
#include "af2_weight.h"
#include "af2_bias.h"

int mnist_conv_nn(ap_ufixed<80, AP_TRN_ZERO, AP_SAT> in[784], ap_fixed<127, AP_TRN_ZERO, AP_SAT> out[10]){
    ap_ufixed<80, AP_TRN_ZERO, AP_SAT> buf[28][28];
    ap_fixed<103, AP_TRN_ZERO, AP_SAT> conv_out[10][24][24];
    ap_fixed<103, AP_TRN_ZERO, AP_SAT> pool_out[10][12][12];
    ap_fixed<137, AP_TRN_ZERO, AP_SAT> dot1[100];
    ap_fixed<137, AP_TRN_ZERO, AP_SAT> dot2[10];

    buf_copy1: for(int i=0; i<28; i++)
        buf_copy2: for(int j=0; j<28; j++)
            buf[i][j] = in[i*28+j];

    // Convolutional Neural Network 5x5 kernel, Stride = 1, Padding = 0
    // + ReLU
    CONV1: for(int i=0; i<10; i++){    // カーネルの個数
        CONV2: for(int j=0; j<24; j++){
            CONV3: for(int k=0; k<24; k++){
                conv_out[i][j][k] = 0;
                CONV4: for(int m=0; m<5; m++){
                    CONV5: for(int n=0; n<5; n++){
                        conv_out[i][j][k] += buf[j+m][k+n] * conv1_weight[i][0][m][n];
                    }
                }
                conv_out[i][j][k] += conv1_bias[i];

                if(conv_out[i][j][k]<0)    // ReLU
                    conv_out[i][j][k] = 0;
            }
        }
    }

    // Pooling Kernel = 2 x 2, Stride = 2
    POOL1: for(int i=0; i<10; i++){
        POOL2: for(int j=0; j<24; j += 2){
            POOL3: for(int k=0; k<24; k += 2){
                POOL4: for(int m=0; m<2; m++){
                    POOL5: for(int n=0; n<2; n++){
                        if(m==0 && n==0){
                            pool_out[i][j/2][k/2] = conv_out[i][j][k];
                        } else if(pool_out[i][j/2][k/2] < conv_out[i][j+m][k+n]){
                            pool_out[i][j/2][k/2] = conv_out[i][j+m][k+n];
                        }
                    }
                }
            }
        }
    }

    af1_dot1: for(int col=0; col<100; col++){
        dot1[col] = 0;
        af1_dot2: for(int i=0; i<10; i++){
            af1_dot3: for(int j=0; j<12; j++){
                af1_dot4: for(int k=0; k<12; k++){
                    dot1[col] += pool_out[i][j][k]*af1_weight[i*12*12+j*12+k][col];
                }
            }
        }
        dot1[col] += af1_bias[col];

        if(dot1[col] < 0)    // ReLU
            dot1[col] = 0;
    }

    af2_dot1: for(int col=0; col<10; col++){
        dot2[col] = 0;
        af2_dot2: for(int row=0; row<100; row++){
            dot2[col] += dot1[row]*af2_weight[row][col];
        }
        dot2[col] += af2_bias[col];

        out[col] = dot2[col];
    }

    return(0);
}


mnist_conv_nn_tb.cpp を貼っておく。

// mnist_conv_nn_tb.cpp
// 2017/06/14 by marsee
// 畳み込み層のカーネル数 10
//

#include <stdio.h>
#include <ap_fixed.h>

#include "conv1_weight.h"
#include "conv1_bias.h"
#include "af1_weight.h"
#include "af1_bias.h"
#include "af2_weight.h"
#include "af2_bias.h"

#include "mnist_data.h"

int mnist_conv_nn(ap_ufixed<80, AP_TRN_ZERO, AP_SAT> in[784], ap_fixed<127, AP_TRN_ZERO, AP_SAT> out[10]);
int mnist_conv_nn_float(float in[784], float out[10]);
int max_ap_fixed(ap_fixed<127, AP_TRN_ZERO, AP_SAT> out[10]);
int max_float(float out[10]);

#define NUM_ITERATIONS    100 // C Simulation
//#define NUM_ITERATIONS    2 // C/RTL CoSimulation

int main(){
    float t_tran_float[NUM_ITERATIONS][784];
    ap_fixed<127, AP_TRN_ZERO, AP_SAT> result_ap_fixed[NUM_ITERATIONS][10];
    float result_float[NUM_ITERATIONS][10];
    int max_id_hw, max_id_sw, max_id_ref;

    for(int i=0; i<NUM_ITERATIONS; i++)
        for(int j=0; j<784; j++)
            t_tran_float[i][j] = (float)t_train[i][j];

    for(int i=0; i<NUM_ITERATIONS; i++){
        mnist_conv_nn(&t_train[i][0], &result_ap_fixed[i][0]);
        mnist_conv_nn_float(&t_tran_float[i][0], &result_float[i][0]);
    }

    int errflag=0;
    for(int i=0; i<NUM_ITERATIONS; i++){
        max_id_hw = max_ap_fixed(&result_ap_fixed[i][0]);
        max_id_sw = max_float(&result_float[i][0]);
        max_id_ref = max_float(&t_test[i][0]);

        if(max_id_ref != max_id_hw){
            printf("id = %d, max_id_ref = %d, max_id_hw = %d\n", i, max_id_ref, max_id_hw);
            errflag = 1;
        }
        if(max_id_ref != max_id_sw){
            printf("id = %d, max_id_ref = %d, max_id_sw = %d\n", i, max_id_ref, max_id_sw);
            errflag = 1;
        }
    }
    if(errflag == 0)
        printf("No Error\n");

    return(0);
}

int mnist_conv_nn_float(float in[784], float out[10]){
    float buf[28][28];
    float conv_out[10][24][24];
    float pool_out[10][12][12];
    float dot1[100];
    float dot2[10];

    buf_copy1: for(int i=0; i<28; i++)
        buf_copy2: for(int j=0; j<28; j++)
            buf[i][j] = in[i*28+j];

    // Convolutional Neural Network 5x5 kernel, Stride = 1, Padding = 0
    // + ReLU
    CONV1: for(int i=0; i<10; i++){    // カーネルの個数
        CONV2: for(int j=0; j<24; j++){
            CONV3: for(int k=0; k<24; k++){
                conv_out[i][j][k] = 0;
                CONV4: for(int m=0; m<5; m++){
                    CONV5: for(int n=0; n<5; n++){
                        conv_out[i][j][k] += buf[j+m][k+n] * conv1_fweight[i][0][m][n];
                    }
                }
                conv_out[i][j][k] += conv1_fbias[i];

                if(conv_out[i][j][k]<0)    // ReLU
                    conv_out[i][j][k] = 0;
            }
        }
    }

    // Pooling Kernel = 2 x 2, Stride = 2
    POOL1: for(int i=0; i<10; i++){
        POOL2: for(int j=0; j<24; j += 2){
            POOL3: for(int k=0; k<24; k += 2){
                POOL4: for(int m=0; m<2; m++){
                    POOL5: for(int n=0; n<2; n++){
                        if(m==0 && n==0){
                            pool_out[i][j/2][k/2] = conv_out[i][j][k];
                        } else if(pool_out[i][j/2][k/2] < conv_out[i][j+m][k+n]){
                            pool_out[i][j/2][k/2] = conv_out[i][j+m][k+n];
                        }
                    }
                }
            }
        }
    }

    af1_dot1: for(int col=0; col<100; col++){
        dot1[col] = 0;
        af1_dot2: for(int i=0; i<10; i++){
            af1_dot3: for(int j=0; j<12; j++){
                af1_dot4: for(int k=0; k<12; k++){
                    dot1[col] += pool_out[i][j][k]*af1_fweight[i*12*12+j*12+k][col];
                }
            }
        }
        dot1[col] += af1_fbias[col];

        if(dot1[col] < 0)    // ReLU
            dot1[col] = 0;
    }

    af2_dot1: for(int col=0; col<10; col++){
        dot2[col] = 0;
        af2_dot2: for(int row=0; row<100; row++){
            dot2[col] += dot1[row]*af2_fweight[row][col];
        }
        dot2[col] += af2_fbias[col];

        out[col] = dot2[col];
    }

    return(0);
}

int max_ap_fixed(ap_fixed<127, AP_TRN_ZERO, AP_SAT> out[10]){
    int max_id;
    ap_fixed<127, AP_TRN_ZERO, AP_SAT> max;

    for(int i=0; i<10; i++){
        if(i == 0){
            max = out[0];
            max_id = 0;
        }else if(out[i]>max){
            max = out[i];
            max_id = i;
        }
    }
    return(max_id);
}

int max_float(float out[10]){
    int max_id;
    float max;

    for(int i=0; i<10; i++){
        if(i == 0){
            max = out[0];
            max_id = 0;
        }else if(out[i]>max){
            max = out[i];
            max_id = i;
        }
    }
    return(max_id);
}

  1. 2017年06月20日 05:12 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

「ゼロから作るDeep Learning」の畳み込みニューラルネットワークのハードウェア化5(再度学習)

「ゼロから作るDeep Learning」の畳み込みニューラルネットワークのハードウェア化4(Vivado HLS1)”の続き。

前回、畳み込み層のカーネルが30個の畳み込みニューラルネットワークをVivado HLS で合成を行ったところ、BRAM_18K がオーバーフローしてしまった。今回は、畳み込み層のカーネルを10個にして畳み込みニューラルネットワークを学習させてみよう。

畳み込み層のカーネルを10個するのは、どうやるか?というと、畳み込み層のカーネル数を指定するパラメータのfilter_num を 10 に指定した。

conv_param = {'filter_num': 10,


nn_fpga_ch7_14_170619.png
nn_fpga_ch7_15_170619.png
精度は98.69 % となって、十分な精度が出ている。これで十分なようだ。

nn_fpga_ch7_16_170619.png
固定小数点数用に量子化した値も 98.69 % で同じなので、十分だ。
これで行くことにしよう。
そうそう、各層の重みとバイアスをもう一度出力しよう。
それぞれの重みとバイアスのキャプチャ図を貼っておく。
nn_fpga_ch7_17_170619.png
nn_fpga_ch7_18_170619.png
nn_fpga_ch7_19_170619.png
nn_fpga_ch7_20_170619.png
nn_fpga_ch7_21_170619.png
nn_fpga_ch7_22_170619.png

最後に畳み込み層のカーネルが10個の畳み込みニューラルネットワークをトレーニングするPython コードを貼っておく。

# train_convnet.py
# 2017/06/06 FPGAによるハードウェア化をにらんで、量子化を行う by marsee
# 元になったコードは、https://github.com/oreilly-japan/deep-learning-from-scratch にあります。
# 改変したコードもMITライセンスとします。 2017/06/19 by marsee

# coding: utf-8
import sys, os
sys.path.append(os.pardir)  # 親ディレクトリのファイルをインポートするための設定
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from trainer_int import Trainer
from simple_convnet_int import SimpleConvNet

# データの読み込み
(x_train, t_train), (x_test, t_test) = load_mnist(flatten=False)

# 処理に時間のかかる場合はデータを削減 
#x_train, t_train = x_train[:5000], t_train[:5000]
#x_test, t_test = x_test[:1000], t_test[:1000]

#max_epochs = 5
max_epochs = 20

network = SimpleConvNet(input_dim=(1,28,28), 
                        conv_param = {'filter_num': 10, 'filter_size': 5, 'pad': 0, 'stride': 1},
                        #conv_param = {'filter_num': 30, 'filter_size': 5, 'pad': 0, 'stride': 1},
                        hidden_size=100, output_size=10, weight_init_std=0.01)
                        
trainer = Trainer(network, x_train, t_train, x_test, t_test,
                  epochs=max_epochs, mini_batch_size=100,
                  optimizer='Adam', optimizer_param={'lr': 0.001},
                  evaluate_sample_num_per_epoch=1000)
trainer.train()

'''x_testn, t_testn = x_test[:500], t_test[:500]
test_accn = network.accuracy_msg(x_testn, t_testn)
print(test_accn)'''

'''train_acc = network.accuracy(x_train, t_train)
test_acc = network.accuracy(x_test, t_test)
print(train_acc, test_acc)
train_acc_int = network.accuracy_int(x_train, t_train)'''
#test_acc_int = network.accuracy_int(x_test, t_test)
#print(test_acc_int)

# パラメータの保存
network.save_params("params.pkl")
print("Saved Network Parameters!")

# グラフの描画
markers = {'train': 'o', 'test': 's'}
x = np.arange(max_epochs)
plt.plot(x, trainer.train_acc_list, marker='o', label='train', markevery=2)
plt.plot(x, trainer.test_acc_list, marker='s', label='test', markevery=2)
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
plt.legend(loc='lower right')
plt.show()

  1. 2017年06月19日 04:14 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

「ゼロから作るDeep Learning」の畳み込みニューラルネットワークのハードウェア化4(Vivado HLS1)

「ゼロから作るDeep Learning」の畳み込みニューラルネットワークのハードウェア化3”の続き。

前回は、重みやバイアスのパラメータをC の配列に出力した。今回は、畳み込みニューラルネットワークのC ソースコードを作成してVivado HLSでハードウェア化してみよう。

layer_int.py の量子化の倍率を示す。

AF_OUT_MAG = 2 ** 5 # 出力の小数部
AF_OUT_INT = 2 ** 6 # 出力の整数部(+符号1ビット)
AF_WB_MAG = 2 ** 8 # 重みとバイアスの小数部
AF_WB_INT = 2 ** 1 # 重みとバイアスの整数部(+符号1ビット)

COV_OUT_MAG = 2 ** 7 # 出力の小数部
COV_OUT_INT = 2 ** 2 # 出力の整数部(+符号1ビット)
COV_WB_MAG = 2 ** 8 # 重みとバイアスの小数部
COV_WB_INT = 2 ** 1 # 重みとバイアスの整数部(+符号1ビット)


これは、例えば全結合層の出力値の量子化の整数部は AF_OUT_INT = 2 ** 6 なので、1 ビットなのだが、正の場合と負の場合があるので、+1とする。そして小数部は 5 ビットなので、Vivado HLSの任意精度固定小数点デー タ型で表すと ap_fixed<12, 7, AP_TRN_ZERO, AP_SAT> になる。それよりもリソース使用量の関係でビット幅を縮小しているのもあるが、基本的にはそんなものだ。

さて、畳み込みニューラルネットワークのC ソースコードを下に示す。

// mnist_conv_nn.cpp
// 2017/06/12 by marsee
// 畳み込み層のカーネル数 30
//

#include <ap_fixed.h>

#include "conv1_weight.h"
#include "conv1_bias.h"
#include "af1_weight.h"
#include "af1_bias.h"
#include "af2_weight.h"
#include "af2_bias.h"

int mnist_conv_nn(ap_ufixed<8, 0, AP_TRN_ZERO, AP_SAT> in[784], ap_fixed<12, 7, AP_TRN_ZERO, AP_SAT> out[10]){
    ap_ufixed<8, 0, AP_TRN_ZERO, AP_SAT> buf[28][28];
    ap_fixed<10, 3, AP_TRN_ZERO, AP_SAT> conv_out[10][24][24];
    ap_fixed<10, 3, AP_TRN_ZERO, AP_SAT> pool_out[10][12][12];
    ap_fixed<12, 7, AP_TRN_ZERO, AP_SAT> dot1[100];
    ap_fixed<12, 7, AP_TRN_ZERO, AP_SAT> dot2[10];

    buf_copy1: for(int i=0; i<28; i++)
        buf_copy2: for(int j=0; j<28; j++)
            buf[i][j] = in[i*28+j];

    // Convolutional Neural Network 5x5 kernel, Stride = 1, Padding = 0
    // + ReLU
    CONV1: for(int i=0; i<30; i++){    // カーネルの個数
        CONV2: for(int j=0; j<24; j++){
            CONV3: for(int k=0; k<24; k++){
                conv_out[i][j][k] = 0;
                CONV4: for(int m=0; m<5; m++){
                    CONV5: for(int n=0; n<5; n++){
                        conv_out[i][j][k] += buf[j+m][k+n] * conv1_weight[i][0][m][n];
                    }
                }
                conv_out[i][j][k] += conv1_bias[i];

                if(conv_out[i][j][k]<0)    // ReLU
                    conv_out[i][j][k] = 0;
            }
        }
    }

    // Pooling Kernel = 2 x 2, Stride = 2
    POOL1: for(int i=0; i<30; i++){
        POOL2: for(int j=0; j<24; j += 2){
            POOL3: for(int k=0; k<24; k += 2){
                POOL4: for(int m=0; m<2; m++){
                    POOL5: for(int n=0; n<2; n++){
                        if(m==0 && n==0){
                            pool_out[i][j/2][k/2] = conv_out[i][j][k];
                        } else if(pool_out[i][j/2][k/2] < conv_out[i][j+m][k+n]){
                            pool_out[i][j/2][k/2] = conv_out[i][j+m][k+n];
                        }
                    }
                }
            }
        }
    }

    af1_dot1: for(int col=0; col<100; col++){
        dot1[col] = 0;
        af1_dot2: for(int i=0; i<30; i++){
            af1_dot3: for(int j=0; j<12; j++){
                af1_dot4: for(int k=0; k<12; k++){
                    dot1[col] += pool_out[i][j][k]*af1_weight[i*12*12+j*12+k][col];
                }
            }
        }
        dot1[col] += af1_bias[col];

        if(dot1[col] < 0)    // ReLU
            dot1[col] = 0;
    }

    af2_dot1: for(int col=0; col<10; col++){
        dot2[col] = 0;
        af2_dot2: for(int row=0; row<100; row++){
            dot2[col] += dot1[row]*af2_weight[row][col];
        }
        dot2[col] += af2_bias[col];

        out[col] = dot2[col];
    }

    return(0);
}


C ソースコードとしてはこのくらいだ。そして畳み込みとプーリングは5 重のループになった。なお、Softmax は未実装となっている。Zynq ならばソフトウェアで実装しても良いと思っている。
Vivado HLS 2017.1 で、PYNQボード用のプロジェクト mnist_conv_nn を作成した。下の図はC コードの合成の途中だ。
nn_fpga_ch7_12_170618.png

これで C コードの合成を行った。結果を示す。
nn_fpga_ch7_13_170618.png

レイテンシは、約 82.0 ms かかっている。30 fps の1フレームは33.3 ms なので、時間がかかりすぎる。
しかもBRAM_18Kが 106 % でリソースが足りなくなっている。
これでは問題なので、もう少しリソースを削減してみよう。
やはり元を少なくする必要があるということで、畳み込みニューラルネットワークの畳み込み層のカーネルが今は30個なのだが、10個程度に減らしてみようと思う。そうすれば全結合層1層目の入力数も減るので、リソースの削減ができるはずだ。
畳み込みニューラルネットワークの畳み込み層のカーネルを10個に減らして学習を行い、テストデータの精度を見てみよう。
  1. 2017年06月18日 05:35 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

「ゼロから作るDeep Learning」の畳み込みニューラルネットワークのハードウェア化3

「ゼロから作るDeep Learning」の畳み込みニューラルネットワークのハードウェア化2”の続き。

前回は、Python コードを公開した。今回は、Vivado HLSに持っていくための重みとバイアスのC の配列を生成するPython コードを紹介する。畳み込み層の重みとバイアス、全結合層の重みとバイアスをC の配列として出力するPython コードだ。ここでは、浮動小数点数の配列と固定小数点数の配列の2つの配列を出力する。浮動小数点数の配列はテストベンチで使用し、固定小数点数の配列を使用した演算との比較に使用される。固定小数点数の配列は、Vivado HLSによるハードウェア化時にも定数として使用される。
畳み込み層の重みのファイル名は、conv1_weight.h、バイアスのファイル名は conv1_bias.h、1層目の全結合層のファイル名は af1_weight.h、バイアスのファイル名は af1_bias.h、2層目の全結合層の重みファイル名は af2_weight.h、バイアスのファイル名は af2_bias.h となっている。

最初にJyputer Notebook の画像を貼る。
nn_fpga_ch7_7_170617.png
nn_fpga_ch7_8_170617.png
nn_fpga_ch7_9_170617.png
nn_fpga_ch7_10_170617.png
nn_fpga_ch7_11_170617.png

Python コードを貼っておきます。

# 畳み込み層の重みをCヘッダファイルに書き出す

def fwrite_conv_weight(weight, wfile_name, float_wt_name, fixed_wt_name, MAGNIFICATION):
    import datetime
    import numpy as np
    
    f = open(wfile_name, 'w')
    todaytime = datetime.datetime.today()
    f.write('// '+wfile_name+'\n')
    strdtime = todaytime.strftime("%Y/%m/%d %H:%M:%S")
    f.write('// {0} by marsee\n'.format(strdtime))
    f.write("\n")
    
    f.write('const float '+float_wt_name+'['+str(weight.shape[0])+']['+str(weight.shape[1])+']['+str(weight.shape[2])+']['+str(weight.shape[3])+'] = \n{\n')
    for i in range(weight.shape[0]):
        f.write("\t{\n")
        for j in range(weight.shape[1]):
            f.write("\t\t{\n")
            for k in range(weight.shape[2]):
                f.write("\t\t\t{")
                for m in range(weight.shape[3]):
                    f.write(str(weight[i][j][k][m]))
                    if (m==weight.shape[3]-1):
                        f.write("}")
                    else:
                        f.write(",")
                
                if (k==weight.shape[2]-1):
                    f.write("\n\t\t}\n")
                else:
                    f.write(",\n")

            if (j==weight.shape[1]-1):
                f.write("\t}\n")
            else:
                f.write(",\n")
        
        
        if (i==weight.shape[0]-1):
            f.write("};\n")
        else:
            f.write("\t,\n")

    f.write("\n")
    f.write('const ap_fixed<'+str(int(np.log2(MAGNIFICATION))+1)+', 1, AP_TRN_ZERO, AP_SAT> '+fixed_wt_name+'['+str(weight.shape[0])+']['+str(weight.shape[1])+']['+str(weight.shape[2])+']['+str(weight.shape[3])+'] = \n{\n')
    for i in range(weight.shape[0]):
        f.write("\t{\n")
        for j in range(weight.shape[1]):
            f.write("\t\t{\n")
            for k in range(weight.shape[2]):
                f.write("\t\t\t{")
                for m in range(weight.shape[3]):
                    w_int = int(weight[i][j][k][m]*MAGNIFICATION+0.5)
                    if (w_int > MAGNIFICATION-1):
                        w_int = MAGNIFICATION-1
                    elif (w_int < -MAGNIFICATION):
                        w_int = -MAGNIFICATION
                    f.write(str(w_int/MAGNIFICATION))
                    if (m==weight.shape[3]-1):
                        f.write("}")
                    else:
                        f.write(",")
                
                if (k==weight.shape[2]-1):
                    f.write("\n\t\t}\n")
                else:
                     f.write(",\n")

            if (j==weight.shape[1]-1):
                f.write("\t}\n")
            else:
                f.write(",\n")
        
        
        if (i==weight.shape[0]-1):
            f.write("};\n")
        else:
            f.write("\t,\n")
 
    f.close()


MAGNIFICATION = 2 ** (9-1)
fwrite_conv_weight(network.params['W1'], 'conv1_weight.h', 'conv1_fweight', 'conv1_weight', MAGNIFICATION)


# 畳み込み層と全結合層のバイアスをCヘッダファイルに書き出す

def fwrite_bias(bias, wfile_name, float_b_name, fixed_wt_name, MAGNIFICATION):
    import datetime
    import numpy as np
    
    f = open(wfile_name, 'w')
    todaytime = datetime.datetime.today()
    f.write('// '+wfile_name+'\n')
    strdtime = todaytime.strftime("%Y/%m/%d %H:%M:%S")
    f.write('// {0} by marsee\n'.format(strdtime))
    f.write("\n")

    f.write('const float '+float_b_name+'['+str(bias.shape[0])+'] = {\n\t')
    for i in range(bias.shape[0]):
        f.write(str(bias[i]))
        if (i < bias.shape[0]-1):
            f.write(", ")
    f.write("\n};\n")

    f.write("\n")
    f.write('const ap_fixed<'+str(int(np.log2(MAGNIFICATION))+1)+', 1, AP_TRN_ZERO, AP_SAT> '+fixed_wt_name+'['+str(bias.shape[0])+'] = {\n\t')
    for i in range(bias.shape[0]):
        b_int = int(bias[i]*MAGNIFICATION+0.5)
        if (b_int > MAGNIFICATION-1):
            b_int = MAGNIFICATION-1
        elif (b_int < -MAGNIFICATION):
            b_int = -MAGNIFICATION
        f.write(str(b_int/MAGNIFICATION))
        if (i < bias.shape[0]-1):
            f.write(", ")
    f.write("\n};\n")

    f.close()


fwrite_bias(network.params['b1'], 'conv1_bias.h', 'conv1_fbias', 'conv1_bias', MAGNIFICATION)


fwrite_bias(network.params['b2'], 'af1_bias.h', 'af1_fbias', 'af1_bias', MAGNIFICATION)


fwrite_bias(network.params['b3'], 'af2_bias.h', 'af2_fbias', 'af2_bias', MAGNIFICATION)


# 全結合層の重みをCヘッダファイルに書き出す

def fwrite_af_weight(weight, wfile_name, float_wt_name, fixed_wt_name, MAGNIFICATION):
    import datetime
    import numpy as np
    
    f = open(wfile_name, 'w')
    todaytime = datetime.datetime.today()
    f.write('// '+wfile_name+'\n')
    strdtime = todaytime.strftime("%Y/%m/%d %H:%M:%S")
    f.write('// {0} by marsee\n'.format(strdtime))
    f.write("\n")
    
    f.write('const float '+float_wt_name+'['+str(weight.shape[0])+']['+str(weight.shape[1])+'] = {\n')
    for i in range(weight.shape[0]):
        f.write("\t{")
        for j in range(weight.shape[1]):
            f.write(str(weight[i][j]))
            if (j==weight.shape[1]-1):
                if (i==weight.shape[0]-1):
                    f.write("}\n")
                else:
                    f.write("},\n")
            else:
                f.write(", ")
    f.write("};\n")

    f.write("\n")
    f.write('const ap_fixed<'+str(int(np.log2(MAGNIFICATION))+1)+', 1, AP_TRN_ZERO, AP_SAT> '+fixed_wt_name+'['+str(weight.shape[0])+']['+str(weight.shape[1])+'] = {\n')
    for i in range(weight.shape[0]):
        f.write("\t{")
        for j in range(weight.shape[1]):
            w_int = int(weight[i][j]*MAGNIFICATION+0.5)
            if (w_int > MAGNIFICATION-1):
                w_int = MAGNIFICATION-1
            elif (w_int < -MAGNIFICATION):
                w_int = -MAGNIFICATION
            f.write(str(w_int/MAGNIFICATION))
            if (j==weight.shape[1]-1):
                if(i==weight.shape[0]-1):
                    f.write("}\n")
                else:
                    f.write("},\n")
            else:
                f.write(", ")
    f.write("};\n")

    f.close()


fwrite_af_weight(network.params['W2'], 'af1_weight.h', 'af1_fweight', 'af1_weight', MAGNIFICATION)


fwrite_af_weight(network.params['W3'], 'af2_weight.h', 'af2_fweight', 'af2_weight', MAGNIFICATION)

  1. 2017年06月17日 07:15 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

「ゼロから作るDeep Learning」の畳み込みニューラルネットワークのハードウェア化2

「ゼロから作るDeep Learning」の畳み込みニューラルネットワークのハードウェア化1”の続き。

前回は、「ゼロから作るDeep Learning」の7章 畳み込みニューラルネットワークの2層のニューラルネットワークをハードウェア化してFPGA に実装してみようということで、重みなどを量子化して、その精度を確認する方法を書いた。
今回は、そのPython コードを貼っておく。

(注:今回のPython コードはoreilly-japan/deep-learning-from-scratch をクローンした環境下でないと動作しない。私は ch5 フォルダの下で動作させている。なお、「ゼロから作るDeep Learning」を購入してから試してほしい)

なお、紹介するPython コードはdeep-learning-from-scratch/ch07/に置いてあるものを変更している。それらのPython コードはMIT ライセンスなので、私が変更、追加したコードもMIT ライセンスを引き継ぐものとする。

最初に、layers_int.py から貼っておく。

# layers_int.py
# 元になったコードは、https://github.com/oreilly-japan/deep-learning-from-scratch にあります。
# 改変したコードもMITライセンスとします。 2017/06/14 by marsee

# coding: utf-8
import numpy as np
from common.functions import *
from common.util import im2col, col2im

AF_OUT_MAG = 2 ** 5 # 出力の小数部
AF_OUT_INT = 2 ** 6 # 出力の整数部(+符号1ビット)
AF_WB_MAG = 2 ** 8 # 重みとバイアスの小数部
AF_WB_INT = 2 ** 1 # 重みとバイアスの整数部(+符号1ビット)

COV_OUT_MAG = 2 ** 7 # 出力の小数部
COV_OUT_INT = 2 ** 2 # 出力の整数部(+符号1ビット)
COV_WB_MAG = 2 ** 8 # 重みとバイアスの小数部
COV_WB_INT = 2 ** 1 # 重みとバイアスの整数部(+符号1ビット)

DEBUG = 1;

class Relu:
    def __init__(self):
        self.mask = None

    def forward_int(self, x):
        self.mask = (x <= 0)
        out = x.copy()
        out[self.mask] = 0

        return out

    def forward(self, x):
        self.mask = (x <= 0)
        out = x.copy()
        out[self.mask] = 0

        return out

    def forward_msg(self, x):
        self.mask = (x <= 0)
        out = x.copy()
        out[self.mask] = 0

        return out

    def backward(self, dout):
        dout[self.mask] = 0
        dx = dout

        return dx


class Sigmoid:
    def __init__(self):
        self.out = None

    def forward_int(self, x):
        out = sigmoid(x)
        self.out = out
        return out

    def forward(self, x):
        out = sigmoid(x)
        self.out = out
        return out

    def forward_msg(self, x):
        out = sigmoid(x)
        self.out = out
        return out

    def backward(self, dout):
        dx = dout * (1.0 - self.out) * self.out

        return dx


class Affine:
    def __init__(self, W, b):
        self.W = W
        self.b = b

        self.W_int = W
        self.b_int = b

        self.x = None
        self.original_x_shape = None
        # 重み・バイアスパラメータの微分
        self.dW = None
        self.db = None

    def forward_int(self, x):
        if (DEBUG == 1):
            print("x shape ={0}".format(x.shape))
            print("np.max(self.W) = {0}".format(np.max(self.W)))

        # テンソル対応
        self.original_x_shape = x.shape
        x = x.reshape(x.shape[0], -1)
        self.x = x

        # x は量子化できているはず
        # wとbをINT8の範囲に修正 2017/05/22 by marsee
        self.W_int = np.array(self.W*AF_WB_MAG+0.5, dtype=int)
        self.b_int = np.array(self.b*AF_WB_MAG+0.5, dtype=int)

        for i in range(self.W_int.shape[0]):
            for j in range(self.W_int.shape[1]):
                if (self.W_int[i][j] > AF_WB_MAG*AF_WB_INT/2-1):
                    self.W_int[i][j] = AF_WB_MAG*AF_WB_INT/2-1
                elif (self.W_int[i][j] < -AF_WB_MAG*AF_WB_INT/2):
                    self.W_int[i][j] = -AF_WB_MAG*AF_WB_INT/2;

        for i in range(self.b_int.shape[0]):
            if (self.b_int[i] > AF_WB_MAG*AF_WB_INT/2-1):
                self.b_int[i] = AF_WB_MAG*AF_WB_INT/2-1
            elif (self.b_int[i] < -AF_WB_MAG*AF_WB_INT/2):
                self.b_int[i] = -AF_WB_MAG*AF_WB_INT/2
        
        self.W_int = np.array(self.W_int, dtype=float)
        self.b_int = np.array(self.b_int, dtype=float)
        
        self.W_int = self.W_int/AF_WB_MAG
        self.b_int = self.b_int/AF_WB_MAG

        out = np.dot(self.x, self.W_int) + self.b_int

        if (DEBUG == 1):
            print("np.max(self.W) = {0}".format(np.max(self.W)))
            print("np.max(self.b) = {0}".format(np.max(self.b)))

            print("x reshape ={0}".format(x.shape))
            print("np.max(x) = {0}".format(np.max(x)))
            print("np.min(x) = {0}".format(np.min(x)))        
            #print("x = {0}".format(self.x))
            print(self.W_int.shape)
            print("np.max(self.W_int) = {0}".format(np.max(self.W_int)))
            print("np.min(self.W_int) = {0}".format(np.min(self.W_int)))
            print(self.b_int.shape)
            print("np.max(self.b_int) = {0}".format(np.max(self.b_int)))
            print("np.min(self.b_int) = {0}".format(np.min(self.b_int)))
            print(out.shape)
            print("np.max(out) = {0}".format(np.max(out)))
            print("np.min(out) = {0}".format(np.min(out)))
            #print("out = {0}".format(out))

        out = np.array(out*AF_OUT_MAG+0.5, dtype=int)
        for i in range(out.shape[0]):
            for j in range(out.shape[1]):
                if (out[i][j] > AF_OUT_MAG*AF_OUT_INT/2-1):
                    out[i][j] = AF_OUT_MAG*AF_OUT_INT/2-1
                elif (out[i][j] < -AF_OUT_MAG*AF_OUT_INT/2):
                    out[i][j] = -AF_OUT_MAG*AF_OUT_INT/2
        out = np.array(out, dtype=float)
        out = out/AF_OUT_MAG

        if (DEBUG == 1):
            print("np.max(out2) = {0}".format(np.max(out)))
            print("np.min(out2) = {0}".format(np.min(out)))

        return out

    def forward(self, x):
        # テンソル対応
        self.original_x_shape = x.shape
        x = x.reshape(x.shape[0], -1)
        self.x = x

        out = np.dot(self.x, self.W) + self.b

        return out

    def forward_msg(self, x):
        print("x shape ={0}".format(x.shape))
        
        # テンソル対応
        self.original_x_shape = x.shape
        x = x.reshape(x.shape[0], -1)
        self.x = x

        out = np.dot(self.x, self.W) + self.b

        print("x reshape ={0}".format(x.shape))
        print("np.max(x) = {0}".format(np.max(x)))
        print("np.min(x) = {0}".format(np.min(x)))        
        #print("x = {0}".format(self.x))
        print(self.W.shape)
        print("np.max(self.W) = {0}".format(np.max(self.W)))
        print("np.min(self.W) = {0}".format(np.min(self.W)))
        print(self.b.shape)
        print("np.max(self.b) = {0}".format(np.max(self.b)))
        print("np.min(self.b) = {0}".format(np.min(self.b)))
        print(out.shape)
        print("np.max(out) = {0}".format(np.max(out)))
        print("np.min(out) = {0}".format(np.min(out)))
        #print("out = {0}".format(out))

        return out

    def backward(self, dout):
        dx = np.dot(dout, self.W.T)
        self.dW = np.dot(self.x.T, dout)
        self.db = np.sum(dout, axis=0)
        
        dx = dx.reshape(*self.original_x_shape)  # 入力データの形状に戻す(テンソル対応)
        return dx


class SoftmaxWithLoss:
    def __init__(self):
        self.loss = None
        self.y = None # softmaxの出力
        self.t = None # 教師データ

    def forward(self, x, t):
        self.t = t
        self.y = softmax(x)
        self.loss = cross_entropy_error(self.y, self.t)
        
        return self.loss

    def forward_msg(self, x, t):
        self.t = t
        self.y = softmax(x)
        self.loss = cross_entropy_error(self.y, self.t)
        
        return self.loss

    def forward_int(self, x, t):
        self.t = t
        self.y = softmax(x)
        self.loss = cross_entropy_error(self.y, self.t)
        
        return self.loss

    def backward(self, dout=1):
        batch_size = self.t.shape[0]
        if self.t.size == self.y.size: # 教師データがone-hot-vectorの場合
            dx = (self.y - self.t) / batch_size
        else:
            dx = self.y.copy()
            dx[np.arange(batch_size), self.t] -= 1
            dx = dx / batch_size
        
        return dx


class Dropout:
    """
    http://arxiv.org/abs/1207.0580
    """
    def __init__(self, dropout_ratio=0.5):
        self.dropout_ratio = dropout_ratio
        self.mask = None

    def forward_int(self, x, train_flg=True):
        if train_flg:
            self.mask = np.random.rand(*x.shape) > self.dropout_ratio
            return x * self.mask
        else:
            return x * (1.0 - self.dropout_ratio)

    def forward_msg(self, x, train_flg=True):
        if train_flg:
            self.mask = np.random.rand(*x.shape) > self.dropout_ratio
            return x * self.mask
        else:
            return x * (1.0 - self.dropout_ratio)

    def forward(self, x, train_flg=True):
        if train_flg:
            self.mask = np.random.rand(*x.shape) > self.dropout_ratio
            return x * self.mask
        else:
            return x * (1.0 - self.dropout_ratio)

    def backward(self, dout):
        return dout * self.mask


class BatchNormalization:
    """
    http://arxiv.org/abs/1502.03167
    """
    def __init__(self, gamma, beta, momentum=0.9, running_mean=None, running_var=None):
        self.gamma = gamma
        self.beta = beta
        self.momentum = momentum
        self.input_shape = None # Conv層の場合は4次元、全結合層の場合は2次元  

        # テスト時に使用する平均と分散
        self.running_mean = running_mean
        self.running_var = running_var  
        
        # backward時に使用する中間データ
        self.batch_size = None
        self.xc = None
        self.std = None
        self.dgamma = None
        self.dbeta = None

    def forward_int(self, x, train_flg=True):
        self.input_shape = x.shape
        if x.ndim != 2:
            N, C, H, W = x.shape
            x = x.reshape(N, -1)

        out = self.__forward(x, train_flg)
        
        return out.reshape(*self.input_shape)
            
    def forward_msg(self, x, train_flg=True):
        self.input_shape = x.shape
        if x.ndim != 2:
            N, C, H, W = x.shape
            x = x.reshape(N, -1)

        out = self.__forward(x, train_flg)
        
        return out.reshape(*self.input_shape)
            
    def forward(self, x, train_flg=True):
        self.input_shape = x.shape
        if x.ndim != 2:
            N, C, H, W = x.shape
            x = x.reshape(N, -1)

        out = self.__forward(x, train_flg)
        
        return out.reshape(*self.input_shape)
            
    def __forward(self, x, train_flg):
        if self.running_mean is None:
            N, D = x.shape
            self.running_mean = np.zeros(D)
            self.running_var = np.zeros(D)
                        
        if train_flg:
            mu = x.mean(axis=0)
            xc = x - mu
            var = np.mean(xc**2, axis=0)
            std = np.sqrt(var + 10e-7)
            xn = xc / std
            
            self.batch_size = x.shape[0]
            self.xc = xc
            self.xn = xn
            self.std = std
            self.running_mean = self.momentum * self.running_mean + (1-self.momentum) * mu
            self.running_var = self.momentum * self.running_var + (1-self.momentum) * var            
        else:
            xc = x - self.running_mean
            xn = xc / ((np.sqrt(self.running_var + 10e-7)))
            
        out = self.gamma * xn + self.beta 
        return out

    def backward(self, dout):
        if dout.ndim != 2:
            N, C, H, W = dout.shape
            dout = dout.reshape(N, -1)

        dx = self.__backward(dout)

        dx = dx.reshape(*self.input_shape)
        return dx

    def __backward(self, dout):
        dbeta = dout.sum(axis=0)
        dgamma = np.sum(self.xn * dout, axis=0)
        dxn = self.gamma * dout
        dxc = dxn / self.std
        dstd = -np.sum((dxn * self.xc) / (self.std * self.std), axis=0)
        dvar = 0.5 * dstd / self.std
        dxc += (2.0 / self.batch_size) * self.xc * dvar
        dmu = np.sum(dxc, axis=0)
        dx = dxc - dmu / self.batch_size
        
        self.dgamma = dgamma
        self.dbeta = dbeta
        
        return dx


class Convolution:
    def __init__(self, W, b, stride=1, pad=0):
        self.W = W
        self.b = b
        self.stride = stride
        self.pad = pad

        self.W_int = W
        self.b_int = b

        # 中間データ(backward時に使用)
        self.x = None   
        self.col = None
        self.col_W = None
        self.col_W_int = None
        
        # 重み・バイアスパラメータの勾配
        self.dW = None
        self.db = None

    def forward_int(self, x):
        # wとbをINT8の範囲に修正 2017/06/06 by marsee
        self.W_int = np.array(self.W*COV_WB_MAG+0.5, dtype=int)
        self.b_int = np.array(self.b*COV_WB_MAG+0.5, dtype=int)
        for i in range(self.W_int.shape[0]):
            for j in range(self.W_int.shape[1]):
                for k in range(self.W_int.shape[2]):
                    for m in range(self.W_int.shape[3]):
                        if (self.W_int[i][j][k][m] > COV_WB_MAG*COV_WB_INT/2-1):
                            self.W_int[i][j][k][m] = COV_WB_MAG*COV_WB_INT/2-1
                        elif (self.W_int[i][j][k][m] < -COV_WB_MAG*COV_WB_INT/2):
                            self.W_int[i][j][k][m] = -COV_WB_MAG*COV_WB_INT/2;
        for i in range(self.b_int.shape[0]):
            if (self.b_int[i] > COV_WB_MAG*COV_WB_INT/2-1):
                self.b_int[i] = COV_WB_MAG*COV_WB_INT/2-1
            elif (self.b_int[i] < -COV_WB_MAG*COV_WB_INT/2):
                self.b_int[i] = -COV_WB_MAG*COV_WB_INT/2
        
        self.W_int = np.array(self.W_int, dtype=float)
        self.b_int = np.array(self.b_int, dtype=float)
        
        self.W_int = self.W_int/COV_WB_MAG
        self.b_int = self.b_int/COV_WB_MAG


        FN, C, FH, FW = self.W_int.shape
        N, C, H, W = x.shape
        out_h = 1 + int((H + 2*self.pad - FH) / self.stride)
        out_w = 1 + int((W + 2*self.pad - FW) / self.stride)
       
        col = im2col(x, FH, FW, self.stride, self.pad)
        col_W_int = self.W_int.reshape(FN, -1).T

        out = np.dot(col, col_W_int) + self.b_int

        if (DEBUG == 1):
            print(x.shape)
            print("Conv col.shape = {0}".format(col.shape))
            print("Conv col_W.shape = {0}".format(col_W_int.shape))

            print("Conv np.max(x) = {0}".format(np.max(x)))
            print("Conv np.min(x) = {0}".format(np.min(x)))        
            #print("Conv x = {0}".format(self.x))
            print(self.W_int.shape)
            print("Conv np.max(self.W_int) = {0}".format(np.max(self.W_int)))
            print("Conv np.min(self.W_int) = {0}".format(np.min(self.W_int)))
            print(self.b_int.shape)
            print("Conv np.max(self.b_int) = {0}".format(np.max(self.b_int)))
            print("Conv np.min(self.b_int) = {0}".format(np.min(self.b_int)))
            print("Conv out.shape = {0}".format(out.shape))
            print("Conv np.max(out) = {0}".format(np.max(out)))
            print("Conv np.min(out) = {0}".format(np.min(out)))
            #print("Conv out = {0}".format(out))

        out = np.array(out*COV_OUT_MAG+0.5, dtype=int)
        for i in range(out.shape[0]):
            for j in range(out.shape[1]):
                if (out[i][j] > COV_OUT_MAG*COV_OUT_INT/2-1):
                    out[i][j] = COV_OUT_MAG*COV_OUT_INT/2-1
                elif (out[i][j] < -COV_OUT_MAG*COV_OUT_INT/2):
                    out[i][j] = -COV_OUT_MAG*COV_OUT_INT/2
        out = np.array(out, dtype=float)
        out = out/COV_OUT_MAG

        if (DEBUG == 1):
            print("Conv np.max(out2) = {0}".format(np.max(out)))
            print("Conv np.min(out2) = {0}".format(np.min(out)))


        out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)

        if (DEBUG == 1):
            print("Conv out.reshape = {0}".format(out.shape))

        self.x = x
        self.col = col
        self.col_W_int = col_W_int

        return out

    def forward(self, x):
        FN, C, FH, FW = self.W.shape
        N, C, H, W = x.shape
        out_h = 1 + int((H + 2*self.pad - FH) / self.stride)
        out_w = 1 + int((W + 2*self.pad - FW) / self.stride)

        col = im2col(x, FH, FW, self.stride, self.pad)
        col_W = self.W.reshape(FN, -1).T

        out = np.dot(col, col_W) + self.b
        out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)

        self.x = x
        self.col = col
        self.col_W = col_W

        return out

    def forward_msg(self, x):
        FN, C, FH, FW = self.W.shape
        N, C, H, W = x.shape
        out_h = 1 + int((H + 2*self.pad - FH) / self.stride)
        out_w = 1 + int((W + 2*self.pad - FW) / self.stride)

        col = im2col(x, FH, FW, self.stride, self.pad)
        col_W = self.W.reshape(FN, -1).T

        out = np.dot(col, col_W) + self.b

        print(x.shape)
        print("Conv col.shape = {0}".format(col.shape))
        print("Conv col_W.shape = {0}".format(col_W.shape))

        print("Conv np.max(x) = {0}".format(np.max(x)))
        print("Conv np.min(x) = {0}".format(np.min(x)))        
        #print("Conv x = {0}".format(self.x))
        print(self.W.shape)
        print("Conv np.max(self.W) = {0}".format(np.max(self.W)))
        print("Conv np.min(self.W) = {0}".format(np.min(self.W)))
        print(self.b.shape)
        print("Conv np.max(self.b) = {0}".format(np.max(self.b)))
        print("Conv np.min(self.b) = {0}".format(np.min(self.b)))
        print("Conv out.shape = {0}".format(out.shape))
        print("Conv np.max(out) = {0}".format(np.max(out)))
        print("Conv np.min(out) = {0}".format(np.min(out)))
        #print("Conv out = {0}".format(out))
        print("Conv np.max(out2) = {0}".format(np.max(out)))
        print("Conv np.min(out2) = {0}".format(np.min(out)))

        out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)

        print("Conv out.reshape = {0}".format(out.shape))

        self.x = x
        self.col = col
        self.col_W = col_W

        return out

    def backward(self, dout):
        FN, C, FH, FW = self.W.shape
        dout = dout.transpose(0,2,3,1).reshape(-1, FN)

        self.db = np.sum(dout, axis=0)
        self.dW = np.dot(self.col.T, dout)
        self.dW = self.dW.transpose(1, 0).reshape(FN, C, FH, FW)

        dcol = np.dot(dout, self.col_W.T)
        dx = col2im(dcol, self.x.shape, FH, FW, self.stride, self.pad)

        return dx


class Pooling:
    def __init__(self, pool_h, pool_w, stride=1, pad=0):
        self.pool_h = pool_h
        self.pool_w = pool_w
        self.stride = stride
        self.pad = pad
        
        self.x = None
        self.arg_max = None

    def forward_int(self, x):
        if (DEBUG == 1):
            print("Pooling x.shape = {0}".format(x.shape))

        N, C, H, W = x.shape
        out_h = int(1 + (H - self.pool_h) / self.stride)
        out_w = int(1 + (W - self.pool_w) / self.stride)

        col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)
        col = col.reshape(-1, self.pool_h*self.pool_w)

        arg_max = np.argmax(col, axis=1)
        out = np.max(col, axis=1)
        out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)

        self.x = x
        self.arg_max = arg_max

        if (DEBUG == 1):
            print("Pooling out.shape = {0}".format(out.shape))

        return out

    def forward_msg(self, x):
        print("Pooling x.shape = {0}".format(x.shape))

        N, C, H, W = x.shape
        out_h = int(1 + (H - self.pool_h) / self.stride)
        out_w = int(1 + (W - self.pool_w) / self.stride)

        col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)
        col = col.reshape(-1, self.pool_h*self.pool_w)

        arg_max = np.argmax(col, axis=1)
        out = np.max(col, axis=1)
        out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)

        self.x = x
        self.arg_max = arg_max

        print("Pooling out.shape = {0}".format(out.shape))

        return out

    def forward(self, x):
        N, C, H, W = x.shape
        out_h = int(1 + (H - self.pool_h) / self.stride)
        out_w = int(1 + (W - self.pool_w) / self.stride)

        col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)
        col = col.reshape(-1, self.pool_h*self.pool_w)

        arg_max = np.argmax(col, axis=1)
        out = np.max(col, axis=1)
        out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)

        self.x = x
        self.arg_max = arg_max

        return out

    def backward(self, dout):
        dout = dout.transpose(0, 2, 3, 1)
        
        pool_size = self.pool_h * self.pool_w
        dmax = np.zeros((dout.size, pool_size))
        dmax[np.arange(self.arg_max.size), self.arg_max.flatten()] = dout.flatten()
        dmax = dmax.reshape(dout.shape + (pool_size,)) 
        
        dcol = dmax.reshape(dmax.shape[0] * dmax.shape[1] * dmax.shape[2], -1)
        dx = col2im(dcol, self.x.shape, self.pool_h, self.pool_w, self.stride, self.pad)
        
        return dx


simple_convnet_int.py を貼っておく。

# simple_convnet_int.py
# 元になったコードは、https://github.com/oreilly-japan/deep-learning-from-scratch にあります。
# 改変したコードもMITライセンスとします。 2017/06/14 by marsee

# coding: utf-8
import sys, os
sys.path.append(os.pardir)  # 親ディレクトリのファイルをインポートするための設定
import pickle
import numpy as np
from layers_int import *
#from common.layers import *
from common.gradient import numerical_gradient
from collections import OrderedDict


class SimpleConvNet:
    """単純なConvNet

    conv - relu - pool - affine - relu - affine - softmax
    
    Parameters
    ----------
    input_size : 入力サイズ(MNISTの場合は784)
    hidden_size_list : 隠れ層のニューロンの数のリスト(e.g. [100, 100, 100])
    output_size : 出力サイズ(MNISTの場合は10)
    activation : 'relu' or 'sigmoid'
    weight_init_std : 重みの標準偏差を指定(e.g. 0.01)
        'relu'または'he'を指定した場合は「Heの初期値」を設定
        'sigmoid'または'xavier'を指定した場合は「Xavierの初期値」を設定
    """
    def __init__(self, input_dim=(1, 28, 28), 
                 conv_param={'filter_num':30, 'filter_size':5, 'pad':0, 'stride':1},
                 hidden_size=100, output_size=10, weight_init_std=0.01):
        filter_num = conv_param['filter_num']
        filter_size = conv_param['filter_size']
        filter_pad = conv_param['pad']
        filter_stride = conv_param['stride']
        input_size = input_dim[1]
        conv_output_size = (input_size - filter_size + 2*filter_pad) / filter_stride + 1
        pool_output_size = int(filter_num * (conv_output_size/2) * (conv_output_size/2))

        # 重みの初期化
        self.params = {}
        self.params['W1'] = weight_init_std * \
                            np.random.randn(filter_num, input_dim[0], filter_size, filter_size)
        self.params['b1'] = np.zeros(filter_num)
        self.params['W2'] = weight_init_std * \
                            np.random.randn(pool_output_size, hidden_size)
        self.params['b2'] = np.zeros(hidden_size)
        self.params['W3'] = weight_init_std * \
                            np.random.randn(hidden_size, output_size)
        self.params['b3'] = np.zeros(output_size)

        # レイヤの生成
        self.layers = OrderedDict()
        self.layers['Conv1'] = Convolution(self.params['W1'], self.params['b1'],
                                           conv_param['stride'], conv_param['pad'])
        self.layers['Relu1'] = Relu()
        self.layers['Pool1'] = Pooling(pool_h=2, pool_w=2, stride=2)
        self.layers['Affine1'] = Affine(self.params['W2'], self.params['b2'])
        self.layers['Relu2'] = Relu()
        self.layers['Affine2'] = Affine(self.params['W3'], self.params['b3'])

        self.last_layer = SoftmaxWithLoss()

    def predict(self, x):
        for layer in self.layers.values():
            x = layer.forward(x)

        return x

    def predict_msg(self, x):
        for layer in self.layers.values():
            x = layer.forward_msg(x)

        return x

    def predict_int(self, x):
        for layer in self.layers.values():
            x = layer.forward_int(x)

        return x

    def loss(self, x, t):
        """損失関数を求める
        引数のxは入力データ、tは教師ラベル
        """
        y = self.predict(x)
        return self.last_layer.forward(y, t)

    def loss_int(self, x, t):
        """損失関数を求める
        引数のxは入力データ、tは教師ラベル
        """
        y = self.predict_int(x)
        return self.last_layer.forward_int(y, t)

    def accuracy(self, x, t, batch_size=100):
        if t.ndim != 1 : t = np.argmax(t, axis=1)
        
        acc = 0.0
        
        for i in range(int(x.shape[0] / batch_size)):
            tx = x[i*batch_size:(i+1)*batch_size]
            tt = t[i*batch_size:(i+1)*batch_size]
            y = self.predict(tx)
            y = np.argmax(y, axis=1)
            acc += np.sum(y == tt) 
        
        return acc / x.shape[0]

    def accuracy_msg(self, x, t, batch_size=100):
        if t.ndim != 1 : t = np.argmax(t, axis=1)
        
        acc = 0.0
        
        for i in range(int(x.shape[0] / batch_size)):
            tx = x[i*batch_size:(i+1)*batch_size]
            tt = t[i*batch_size:(i+1)*batch_size]
            y = self.predict_msg(tx)
            y = np.argmax(y, axis=1)
            acc += np.sum(y == tt) 
        
        return acc / x.shape[0]

    def accuracy_int(self, x, t, batch_size=100):
        if t.ndim != 1 : t = np.argmax(t, axis=1)
        
        acc = 0.0
        
        for i in range(int(x.shape[0] / batch_size)):
            tx = x[i*batch_size:(i+1)*batch_size]
            tt = t[i*batch_size:(i+1)*batch_size]
            y = self.predict_int(tx)
            y = np.argmax(y, axis=1)
            acc += np.sum(y == tt) 
        
        return acc / x.shape[0]

    def numerical_gradient(self, x, t):
        """勾配を求める(数値微分)

        Parameters
        ----------
        x : 入力データ
        t : 教師ラベル

        Returns
        -------
        各層の勾配を持ったディクショナリ変数
            grads['W1']、grads['W2']、...は各層の重み
            grads['b1']、grads['b2']、...は各層のバイアス
        """
        loss_w = lambda w: self.loss(x, t)

        grads = {}
        for idx in (1, 2, 3):
            grads['W' + str(idx)] = numerical_gradient(loss_w, self.params['W' + str(idx)])
            grads['b' + str(idx)] = numerical_gradient(loss_w, self.params['b' + str(idx)])

        return grads

    def gradient(self, x, t):
        """勾配を求める(誤差逆伝搬法)

        Parameters
        ----------
        x : 入力データ
        t : 教師ラベル

        Returns
        -------
        各層の勾配を持ったディクショナリ変数
            grads['W1']、grads['W2']、...は各層の重み
            grads['b1']、grads['b2']、...は各層のバイアス
        """
        # forward
        self.loss(x, t)

        # backward
        dout = 1
        dout = self.last_layer.backward(dout)

        layers = list(self.layers.values())
        layers.reverse()
        for layer in layers:
            dout = layer.backward(dout)

        # 設定
        grads = {}
        grads['W1'], grads['b1'] = self.layers['Conv1'].dW, self.layers['Conv1'].db
        grads['W2'], grads['b2'] = self.layers['Affine1'].dW, self.layers['Affine1'].db
        grads['W3'], grads['b3'] = self.layers['Affine2'].dW, self.layers['Affine2'].db

        return grads
        
    def save_params(self, file_name="params.pkl"):
        params = {}
        for key, val in self.params.items():
            params[key] = val
        with open(file_name, 'wb') as f:
            pickle.dump(params, f)

    def load_params(self, file_name="params.pkl"):
        with open(file_name, 'rb') as f:
            params = pickle.load(f)
        for key, val in params.items():
            self.params[key] = val

        for i, key in enumerate(['Conv1', 'Affine1', 'Affine2']):
            self.layers[key].W = self.params['W' + str(i+1)]
            self.layers[key].b = self.params['b' + str(i+1)]


trainer_int.py は変更しようと思ったが、まだ変更はしていない。
  1. 2017年06月16日 04:42 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

「ゼロから作るDeep Learning」の畳み込みニューラルネットワークのハードウェア化1

ゼロから作るDeep Learning」の7章 畳み込みニューラルネットワークをハードウェア化してFPGA に実装してみることにした。

学習はdeep-learning-from-scratch/ch05/ のtrain_neuralnet.py のコードをそのまま使用する。
このままでは浮動小数点数なので、指定されたビット長の固定小数点に量子化するメソッドをsimple_convnet_int.py、layers.py に追加してある。それが、simple_convnet_int.py、layers_int.py だ。
nn_fpga_ch7_1_170614.png

上の図のように Class Relu に def forward_int が追加されているのが分かると思う。つまり、固定小数点に量子化してforward 処理を実行するメソッドを追加したわけだ。ただし、Relu はforward と同じだ。今回は、更に、コードはforward と同じだが、メッセージを出力するforward_msg() を追加した。

今回の量子化は、全結合層 AF_*****と畳み込み層 COV_***** を分けた。
さらに、重みとバイアスの量子化ビットと出力の量子化ビットを分けてある。
forward_int() を書き換えてあるのは、class Affine と class Convolution のみだ。
重みやバイアスにAF_WB_MAG または COV_WB_MAG を掛けて、0.5 を加算して(四捨五入のため)integer に変換した後で、飽和演算を行う。すると小数点以下が切れてるので、量子化できることになる。そして、float に戻して、AF_WB_MAG または COV_WB_MAG で割っている。
その後、量子化の目安のため値のmax と min を表示しているが、今回は、DEBUG = 1 の時のみ表示する仕様にしている。
これは表示がうるさいときがあったからだ。
nn_fpga_ch7_2_170614.png
nn_fpga_ch7_3_170614.png

simple_convnet_int.py をjyputer Notebook で実行した結果を示す。この場合は、浮動小数点数での精度の値となっており、0.988 だった。
nn_fpga_ch7_4_170614.png
nn_fpga_ch7_5_170614.png

train_convnet.py の学習はそのまま使って、simple_convnet_int.py にも def loss_int や def predict_int、def accuracy_int を追加してあるので、固定小数点に量子化してforward 処理したときのaccuracy(精度)を確認することができる。
更に、DEBUG = 1 にすると、max値やmin値、配列の要素数などを観察することができる。
nn_fpga_ch7_6_170614.png

なお、量子化による演算を行った結果の精度は、0.986 だった。

最初に畳み込み層に入ってくる入力 x の配列は、(100, 1, 28, 28)だった。バッチ数が 100 個あるようだ。
重みの配列は(30, 1, 5, 5)だったつまり、30個の1チャネルの5x5 のカーネルということになる。
im2col()された col の配列は(57600, 25)だった。57600 = 24 x 24 x 100 バッチだ。24 は 28 ピクセルの画素を 5x5 にカーネル、ストライド1、パッド0で畳み込みしたときの出力ピクセルの幅と高さになる。
重みの配列は(30,)で、30個分の重みだ。これを30個の出力のすべての要素に足し算する。
畳み込み層の出力の配列は、(57600, 30)で、これをreshape すると、(100, 30, 24, 24)になる。これは、100バッチの30個の24x24 ということになる。
プーリング層の入力は、同様に、(100, 30, 24, 24)で、これを 2 x 2 のマス目で最大値を取りながら、(100, 30, 12, 12)に縮める。
(100, 30, 12, 12)が全結合層1層目の入力で、reshape を行って、(100, 4320)に変換し、(4320, 100)の重みとの内積を取って、(100,) にしている。その後は、全結合層のニューラルネットワークと同様なので、省略する。
100バッチ1回のログを示す。

(100, 1, 28, 28)
Conv col.shape = (57600, 25)
Conv col_W.shape = (25, 30)
Conv np.max(x) = 1.0
Conv np.min(x) = 0.0
(30, 1, 5, 5)
Conv np.max(self.W_int) = 0.50390625
Conv np.min(self.W_int) = -0.8046875
(30,)
Conv np.max(self.b_int) = -0.01171875
Conv np.min(self.b_int) = -0.37890625
Conv out.shape = (57600, 30)
Conv np.max(out) = 3.2695772065781057
Conv np.min(out) = -4.958961396710947
Conv np.max(out2) = 1.9921875
Conv np.min(out2) = -2.0
Conv out.reshape = (100, 30, 24, 24)
Pooling x.shape = (100, 30, 24, 24)
Pooling out.shape = (100, 30, 12, 12)
x shape =(100, 30, 12, 12)
np.max(self.W) = 0.7367957391676244
np.max(self.W) = 0.7367957391676244
np.max(self.b) = 0.13286493647098715
x reshape =(100, 4320)
np.max(x) = 1.9921875
np.min(x) = 0.0
(4320, 100)
np.max(self.W_int) = 0.73828125
np.min(self.W_int) = -0.78125
(100,)
np.max(self.b_int) = 0.1328125
np.min(self.b_int) = -0.0859375
(100, 100)
np.max(out) = 38.628021240234375
np.min(out) = -45.4913330078125
np.max(out2) = 31.96875
np.min(out2) = -32.0
x shape =(100, 100)
np.max(self.W) = 0.34009935565012406
np.max(self.W) = 0.34009935565012406
np.max(self.b) = 0.06031450057979193
x reshape =(100, 100)
np.max(x) = 31.96875
np.min(x) = 0.0
(100, 10)
np.max(self.W_int) = 0.33984375
np.min(self.W_int) = -0.5234375
(10,)
np.max(self.b_int) = 0.05859375
np.min(self.b_int) = -0.0703125
(100, 10)
np.max(out) = 33.219970703125
np.min(out) = -61.742919921875
np.max(out2) = 31.96875
np.min(out2) = -32.0

  1. 2017年06月14日 05:32 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

「ゼロから作るDeep Learning」の2層ニューラルネットワークのハードウェア化8(浮動小数点数)

「ゼロから作るDeep Learning」の2層ニューラルネットワークのハードウェア化7(指示子を入れる2)”の続き。

前回は、使いそうなインターフェース指示子を入れてみた。入力はAXI4-Stream で出力は、AXI4-Lite だった。今回は、内部の処理を浮動小数点数で処理することで、どのくらいリソースを消費するのか確かめてみよう。

まずは、プロジェクトを新規作成して、mnist_nn_float とした。
nn_fpga_ch5_50_170612.png

mnist_nn_float.cpp を示す。

// mnist_nn.cpp
// 2017/06/01 by marsee
//

#include <stdio.h>
#include <ap_fixed.h>

#include "af1_weight.h"
#include "af1_bias.h"
#include "af2_weight.h"
#include "af2_bias.h"

int mnist_nn(ap_ufixed<80, AP_TRN_ZERO, AP_SAT> in[784], ap_fixed<135, AP_TRN_ZERO, AP_SAT> out[10]){
#pragma HLS INTERFACE s_axilite port=out
#pragma HLS INTERFACE s_axilite port=return
#pragma HLS INTERFACE axis register both port=in
    float buf[784];
    float dot1[50];
    float dot2[10];

    buf_copy: for(int i=0; i<784; i++)
        buf[i] = (float)in[i];

    af1_dot1: for(int col=0; col<50; col++){
        dot1[col] = 0;
        af1_dot2: for(int row=0; row<784; row++){
            dot1[col] += buf[row]*af1_fweight[row][col];
        }
        dot1[col] += af1_fbias[col];

        if(dot1[col] < 0)    // ReLU
            dot1[col] = 0;
    }

    af2_dot1: for(int col=0; col<10; col++){
        dot2[col] = 0;
        af2_dot2: for(int row=0; row<50; row++){
            dot2[col] += dot1[row]*af2_fweight[row][col];
        }
        dot2[col] += af2_fbias[col];

        if(dot2[col] < 0)    // ReLU
            dot2[col] = 0;
        out[col] = (ap_fixed<135, AP_TRN_ZERO, AP_SAT>)dot2[col];
    }

    return(0);
}


C コードの合成を行った。
nn_fpga_ch5_51_170612.png

レイテンシは444409 クロックで、クロックが100 MHz の時は、4.44 ms の処理時間だ。これでも問題ない。
リソースはBRAM_18K が 135 個で 48 % 消費している。DSP48E が 4 個になった。FF も LUT も固定小数点数の時よりは増えているが、それぞれ 2 % と 8 % でまだまだ余裕がある。
浮動小数点数演算にしてもPYNQ に入るのがびっくりした。なお、浮動小数点数になったことで、配列でメモリを取るとメモリ・オーバーフローになってしまうので、シミュレーションの結果は出ていない。配列から malloc() でメモリを確保することに変更すればシミュレーションできると思う。

最後にAnalysis 画面を貼っておく。
nn_fpga_ch5_52_170612.png
  1. 2017年06月12日 04:04 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

Vivado やVivado HLS がVisual C++ 2015 再頒布可能パッケージのエラーで起動しないときの対処法

Vivado や Vivado HLS がVisual C++ 再頒布パッケージのエラーで起動しないときがある。
VC2005_redit_error_1_170611.png

Lenovo のノートパソコンがそうなってしまい、2回ほど再インストールしたが治らない。
Vivado/Xilinx SDK Error Incorrect Visual C++ Version”を見て、他のパージョンのVisual C++ 再頒布パッケージをアンインストールして、Visual C++ 2015 再頒布可能パッケージを再度インストールしてみたが治らない。

Vivado/Xilinx SDK Error Incorrect Visual C++ Versionの2ページ目のXIL_PA_NO_REDIST_CHECK 環境変数を1 にセットするのをやってみることにした。

まずは、コントロールパネルを出す。私のノートパソコンは、Windows10の1703なので、Cortana にコントロールパネルを聞いて、コントロールパネルを開き、システムをクリックする。

システムが開くので、システムの詳細設定をクリックする。
VC2005_redit_error_2_170611.png

システムのプロパティの環境設定タブが開く。環境変数ボタンをクリックする。
VC2005_redit_error_3_170611.png

新規ボタンをクリックして、変数名に XIL_PA_NO_REDIST_CHECK を、変数値に 1 を入れて、XIL_PA_NO_REDIST_CHECK システム環境変数を作成した。
VC2005_redit_error_4_170611.png

そのあとで、Vivado や Vivado HLS を起動すると、問題なく立ち上がった。
  1. 2017年06月11日 14:44 |
  2. Vivado
  3. | トラックバック:0
  4. | コメント:0

リモートデスクトップで使用する2つのパソコンの使い方

私は家で3台のパソコンを使用している。
1台は古いWindows PC でAMD Athlon 64 x2 5200+ 2.7GHz で 8 GB メモリのマシンで、FPGAの部屋のブログのデータがすべて入っているので捨てられない。これを今メインというか、ディスプレイをつないでメインで使っているので、いかんせん遅い。

このメインPC からこの前GPU を増設したマウスコンピューターのデスクトップパソコン、インテル(R) Core(TM) i5-6500 プロセッサー ( 4コア / 4スレッド / 3.20GHz / TB時最大3.60GHz / 6MBキャッシュ )+16 GBがあって、Windows 10とUbuntu 16.04 のデュアルブートになっている。これが、FPGAのツールを使う時のメインマシンだ。FPGAマシンと呼ぼう。

もう1台、Lenovo のノートパソコンの Thinkpad 13 がある。

この3台のマシンをどう使うか?なのだが、メインマシンからWindows 10 のFPGAマシンとLenovo ノートを使う時は、リモートデスクトップで接続している。だが、2つのリモートデスクトップを使って3台を切り替えて使うのは面倒だ。
だが、”リモートデスクトップでウィンドウ表示と全画面表示を切り替える@Windows 7”で

Ctrl + Alt + Break

キーでリモートデスクトップの全画面、ウインドウ表示を切り替えられるというのを見て試してみたが、とっても具合が良い。
ウインドウ表示でFPGAマシンとLenovo ノートを並べておいて、そのウインドウで Ctrl + Alt + Break キーを押すと最大化して全画面表示になり、もう一度、Ctrl + Alt + Break キーを押すと、ウインドウ表示になって、メインマシンのウインドウに戻ってくる。
Remote_Disktop_Useage_1_170611.jpg

これだったら、3つのパソコンを渡り歩いて作業することが簡単になりそうだ。
ブログに忘れないように書いておこう。。。
  1. 2017年06月11日 06:03 |
  2. パソコン関連
  3. | トラックバック:0
  4. | コメント:0

「ゼロから作るDeep Learning」の2層ニューラルネットワークのハードウェア化7(指示子を入れる2)

”「ゼロから作るDeep Learning」の2層ニューラルネットワークのハードウェア化6(指示子を入れる)”の続き。

前回は、指示子を入れて、入力ポートと出力ポートを1個ずつバラバラにしてみた。今回は、実際に使えるように指示子を入れてみよう。
インターフェースを決定しよう。入力は、AXI4 Master かAXI4-Stream だろう?AXI4 Master をやってみたが、入力ポートのビット幅が8ビットなので、32ビット幅のAXI4 バスに4個の入力データが入力されてきた。やはり、32ビット幅で1ピクセルデータとしたいということもあり、AXI4-Stream で行くことにした。
出力ポートは、最大値をハードウェアで判定して、答えの数字をバイナリで出力しても良いのだが、10個の値をAXI4-Lite でARM プロセッサがRead して、最大値を判定することにしようと思う。そうすれば、ソフトウェアでSoftmax を取ることもできる。

それでは、buf[] へのコピーを復活する。そして、指示子を入れた状態のソースコードを示す。
nn_fpga_ch5_42_170610.png

C コードの合成を行った。
nn_fpga_ch5_43_170610.png

前回よりも、BRAM_18K 、FF、LUT は増えている。これは、AXI バスを実装しているので、増えてしまうのは仕方がない。レイテンシは変化が無かった。

mnist_nn.v のポート宣言部分を示す。in_V_TDATA は 8 ビット幅になっている。その下には、AXI4-Lite のSlave のポートが宣言されている。
nn_fpga_ch5_44_170610.png

AXI4-Lite のレジスタ設定を示す。out_V のレジスタマップが見たかった。
これによると、0x20 からbit 12 ~ bit 0 にout_V[2n] 、bit 28 ~ bit[16] に out_V[2n + 1] がマップされている。
つまりレジスタ数は 10 出力 / 2 = 5 個になった。つまり、0x20, 0x24, 0x28, 0x2c, 0x30 が使われているようだ。
nn_fpga_ch5_45_170610.png

C/RTL協調シミュレーションを行った。
nn_fpga_ch5_46_170610.png

C/RTL協調シミュレーションの波形を示す。
まずは全体波形から。
nn_fpga_ch5_47_170610.png

入力部分を拡大してみた。
nn_fpga_ch5_48_170610.png

最初のデータの結果の出力部分を示す。
nn_fpga_ch5_49_170610.png

0x24 番地の上の 16 ビットが 0xe15 で一番大きい。 n = 1 なので、out_V[2+1] が最大だ。つまり 3 ということで答えと合っている。
  1. 2017年06月10日 07:14 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

「ゼロから作るDeep Learning」の2層ニューラルネットワークのハードウェア化6(指示子を入れる)

「ゼロから作るDeep Learning」の2層ニューラルネットワークのハードウェア化5(推論の検証)”の続き。

前回は、FPGAに実装しようとしているMNISTの推論ハードウェアが本当にうまく行っているのか?を確かめた。300個の手書き数字を認識した結果、精度は97.3 % であることが分かった。しかし、人間が判別しやすい手書き文字が必ずしもMNISTの推論ハードウェアに判別しやすいわけではないということが分かった。
今回は、実際にIP として使用できるようにVivado HLS に指示子を入れてみよう。

最初に、入力と出力をバラバラにポートに出してみよう。それは、ARRAY_PARTITION complete 指示子を使用する。
その際に、入力にデータを入れたということを示せるように入力ポートのインターフェースを ap_hs にしてみよう。このように指定するとハンドシェーク信号が追加される。OUTポートも同様にバラバラにして、出力はデフォルトでも ap_vld は出ると思うのだが、一応 ap_ovld に指定する。入力ポート、出力ポート共にレジスタを入れる設定にする。
更に、buf に転送するコードは、入力ポートからたくさん入力しないようにするコードなので、これは削除しても問題ないし、かえってレイテンシが増えるので、削除する。
nn_fpga_ch5_27_170607.png

これで、C コードの合成を行った。
nn_fpga_ch5_28_170607.png

buf にコピーするコードを削除したせいもありレイテンシは 278711 から 277162 に減少した。BRAM_18K も使用量が減ったが、FF と LUT は大幅に増えている。これは、ハンドシェークのポートも増えたし、それに対しての回路も増えたせいだと思われる。

出力されたHDL コードを見てみよう。
nn_fpga_ch5_29_170607.png
nn_fpga_ch5_30_170607.png

入力ポートは、in_0_V, in_0_V_ap_vld, in_0_V_ap_ack というように、ap_vld のデータ有効信号とap_ack の応答信号が1つのポートごとに出力されている。入力ポートは 784 個ある。
出力ポートは、out_0_V, out_0_V_ap_vld というように、ap_vld のデータ有効信号のみ出ている。出力ポートは 10 個だ。

次に、これに、for ループの最初にPIPELINE指示子 II=1 を入れてみよう。
nn_fpga_ch5_31_170607.png

C コードの合成を行った。これは時間がかかった。
nn_fpga_ch5_35_170607.png

レイテンシは 1766 に劇的に減ったが、BRAM_18K は 5040 % 、FF は 118 % 、LUT は 398 % でPYNQ ボードのリソースを大幅に超過してしまった。
Analysis 画面を示す。
nn_fpga_ch5_36_170607.png

C/RTL協調シミュレーションをしようとしたが、エラーになってしまった。大きすぎるのかもしれない?

次に、BRAM_18Kのリソース消費量を0 にしてみよう。つまり、重みやバイアスのパラメータをLUT にマップしてみる。
重みやバイアスがBRAM_18Kに実装されているので、BRAMが使われているため、それにARRAY_PARTITION complete 指示子を使用する。なお、ヘッダファイルに重みやバイアスを置いておくと、指示子が付けられなかったので、mnist_nn.cpp に持ってきた。更にブロックレベルのインターフェースは AXI4 Lite に設定している。ただし、in, out ポートの指示子は削除した。out ポートはap_ovld で変化がないが、in ポートは ap_none となって、ハンドシェークポートが削除されている。
nn_fpga_ch5s_1_170609.png

これで、C コードの合成を行った。
nn_fpga_ch5s_2_170609.png

BRAM_18Kの使用量は 0 になった。その代り、FFとLUT がオーバーしているが、前回ほどではない。
  1. 2017年06月09日 05:17 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

「ゼロから作るDeep Learning」の2層ニューラルネットワークのハードウェア化5(推論の検証)

「ゼロから作るDeep Learning」の2層ニューラルネットワークのハードウェア化4(Vivado HLS)”の続き。

前回は、Vivado HLSを使用して、MNISTデータセットで、手書き数字を認識する2層のニューラルネットワークの推論のハードウェア化を行った。今回は、前回、ハードウェア化したニューラルネットワークは 8 の推論をミスってしまった。MNIST データの 0 個目から100 個のデータを使用して検証したので、もう少し、違ったデータで検証してみよう。

まずは、前回、8 の認識をミスってしまったので、200 個のMNIST データをC のヘッダファイルとして出力してC シミュレーションをしてみたが、メモリのオーバーフローでC シミュレーションが成功しなかった。
それでは、ということで、”MNISTデータセットの一部をC の配列に変換するPython コードの更新”で、100 番目から 100 個のMNIST データをC のヘッダファイルに変換して、C シミュレーションを行った。その結果を示す。
nn_fpga_ch5_25_170607.png

id = 15, max_id_ref = 4, max_id_sw = 9
id = 24, max_id_ref = 7, max_id_hw = 4
id = 49, max_id_ref = 2, max_id_hw = 4
id = 49, max_id_ref = 2, max_id_sw = 4


ハードウェアはID が24 と 49 の2つ間違っている。ソフトウェアはID が 15 と 49 の2つ間違っている。49 は共通だが、15 はソフトウェアだけ、24 はハードウェアだけが間違っている。
推論をミスった数字を見てみよう。
ID = 15
nn_fpga_ch5_32_170607.png

ID = 24
nn_fpga_ch5_33_170607.png

ID = 49
nn_fpga_ch5_34_170607.png

どれもミスっても仕方ないかもしれない?

次に 200 番目から 100 個のMNIST データをC のヘッダファイルに変換して、C シミュレーションを行った。
nn_fpga_ch5_37_170607.png

id = 33, max_id_ref = 8, max_id_sw = 7
id = 41, max_id_ref = 9, max_id_hw = 5
id = 47, max_id_ref = 4, max_id_hw = 2
id = 47, max_id_ref = 4, max_id_sw = 2
id = 59, max_id_ref = 6, max_id_hw = 0
id = 59, max_id_ref = 6, max_id_sw = 0
id = 90, max_id_ref = 8, max_id_hw = 4
id = 90, max_id_ref = 8, max_id_sw = 4


今回は間違いが多い。ハードウェア、ソフトウェア共に 4 個ずつ間違っている。
ID = 33, ソフトウェア間違い。
nn_fpga_ch5_38_170607.png
人間が見ると 8 だよね。

ID = 41 と 47、41 はハードウェアのミス、47 はソフトウェア、ハードウェア共にミス。
nn_fpga_ch5_39_170607.png
9 は 9 に見える。4 は微妙か?
でも、その間の数字は、良く認識できているな?と感じる。

ID = 59、ソフトウェア、ハードウェア共にミス。
nn_fpga_ch5_40_170607.png
6 には見えるが、丸が大きいかも?

ID = 90、ソフトウェア、ハードウェア共にミス。
nn_fpga_ch5_41_170607.png
これは微妙。8 なんだけど上が閉じていない。これは間違えるかも?

ということで、ソフトウェア、ハードウェア共に、良く認識できていると思うのがある反面、これを間違わないでよ、というのがある。やはり、学習の仕方が人間と違っているのだろう?
精度が 97.3 % と言っても、人間にとって見やすい文字が間違っているということがあり得るのかもしれない?
  1. 2017年06月08日 05:06 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

MNISTデータセットの一部をC の配列に変換するPython コードの更新

「ゼロから作るDeep Learning」の2層ニューラルネットワークのハードウェア化3”に貼った、「Vivado HLSのテストベンチに必要なMNISTデータセットの一部をC の配列に変換するPython コード」を更新した。
10000個のMNISTのテストデータの任意の位置の100個を抽出できるように変更したので、貼っておく。
(2017/08/27 : バグがあったので修正)

# MNISTのデータをCの配列に出力し、ファイルに書き込み

# coding: utf-8
import sys, os
sys.path.append(os.pardir)

import numpy as np
from dataset.mnist import load_mnist
import datetime

OUTPUT_DATA_NUM = 100 # 出力するMNISTのテストデータ数 10000までの数
OFFSET = 100 # MNISTデータセットのオフセット、100だったら100番目からOUTPUT_DATA_NUM個を出力する

# データの読み込み
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)

f = open("mnist_data.h", 'w')
todaytime = datetime.datetime.today()
f.write('// mnist_data.h\n')
strdtime = todaytime.strftime("%Y/%m/%d %H:%M:%S")
f.write('// {0} by marsee\n'.format(strdtime))
f.write("\n")

f.write('ap_ufixed<8, 0, AP_TRN_ZERO, AP_SAT> t_train['+str(OUTPUT_DATA_NUM)+']['+str(x_test.shape[1])+'] = {\n')
for i in range(OFFSET, OFFSET+OUTPUT_DATA_NUM):
    f.write("\t{")
    for j in range(x_test.shape[1]):
        f.write(str(x_test[i][j]))
        if (j==x_test.shape[1]-1):
            if (i==OUTPUT_DATA_NUM-1):
                f.write("}\n")
            else:
                f.write("},\n")
        else:
            f.write(", ")
f.write("};\n")

f.write('int t_train_256['+str(OUTPUT_DATA_NUM)+']['+str(x_test.shape[1])+'] = {\n')
for i in range(OFFSET, OFFSET+OUTPUT_DATA_NUM):
    f.write("\t{")
    for j in range(x_test.shape[1]):
        f.write(str(int(x_test[i][j]*256)))
        if (j==x_test.shape[1]-1):
            if (i==OUTPUT_DATA_NUM-1):
                f.write("}\n")
            else:
                f.write("},\n")
        else:
            f.write(", ")
f.write("};\n")

f.write("\n")
f.write('float t_test['+str(OUTPUT_DATA_NUM)+']['+str(t_test.shape[1])+'] = {\n')
for i in range(OFFSET, OFFSET+OUTPUT_DATA_NUM):
    f.write("\t{")
    for j in range(t_test.shape[1]):
        f.write(str(t_test[i][j]))
        if (j==t_test.shape[1]-1):
            if (i==OUTPUT_DATA_NUM-1):
                f.write("}\n")
            else:
                f.write("},\n")
        else:
            f.write(", ")
f.write("};\n")
f.close() 

  1. 2017年06月06日 04:45 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

「ゼロから作るDeep Learning」の2層ニューラルネットワークのハードウェア化4(Vivado HLS)

「ゼロから作るDeep Learning」の2層ニューラルネットワークのハードウェア化3”の続き。

Vivado HLSに持っていくための重みとバイアスのC の配列を生成するPython コードを紹介した。更に、Vivado HLSのテストベンチに必要なMNISTデータセットの一部をC の配列に変換するPython コードも紹介した。最後にMNISTデータセットを画像として見るためのPython コードも紹介した。
今回は、前回作成した重みやバイアス、MNISTデータセットの一部のヘッダファイルを使用して、Vivado HLS でMNISTの手書き数字を判定するハードウェアを作る。ただし、Softmax は実装が難しいし、最大値を取れば数字は推定できるので、実装していない。

さて、Vivado HLS 2017.1 で mnist_nn プロジェクトを作成した。そのプロジェクトを示す。
nn_fpga_ch5_13_170605.png

C シミュレーションを行った。
nn_fpga_ch5_14_170605.png

ハードウェア(固定小数点)では、id = 8 と id = 61 が間違っている。ソフトウェア(浮動小数点)では、id = 8 だけ間違っている。
ちなみに、id = 8 は数字の 5 だった。
nn_fpga_ch5_15_170605.png

id = 61 は数字の 8 だった。
nn_fpga_ch5_16_170605.png

この 8 を間違うのは人間にとってはいただけない気がするが、やはり飽和演算してしまった結果なのだろうか?
100 個やっての結果なので、ハードウェアの精度は 98 % で、ソフトウェアの精度は 99 % となった。

C コードの合成を行った。結果を示す。なお、ソースコードは下に貼ってあるが、指示子は1つも書いていないので、デフォルトのままである。
nn_fpga_ch5_17_170605.png

Latency は 278711 クロックとなった。これは、100 MHz 動作とすると、約 2.79 ms となる。28 x 28 画像の処理に 2.8 ms 程度なので、357 fps ということになり、カメラ画像の解析としては十分なスピードとなった。

リソース使用量を見ると、BRAM が38 個で 13 % 使用している。重みはここに入っているようだ。DSP48E が 1 個、FF が 447 個、LUT が 970 個使用している。全体的に見れば、少ないリソース使用量となっているのではないだろうか?

合成された HDLコードがどうなっているか?だが、C ソースコードは指示子を書いていないデフォルトの状態なので、配列はメモリのインターフェースになるので、アドレスとCE, WE, データの信号線がある。
nn_fpga_ch5_20_170606.png

Analysis 画面を示す。
nn_fpga_ch5_18_170605.png

af1_weight_V_U でBRAM が 36 個使用されているのが分かる。

NUM_ITERATIONS を 100 から 2 に変更して、C/RTL協調シミュレーションを行った。結果を示す。
nn_fpga_ch5_19_170605.png

C/RTL協調シミュレーション波形の全体を示す。
nn_fpga_ch5_21_170606.png

最初の in の入力部分を示す。
nn_fpga_ch5_22_170606.png

1番目の手書き数字認識の出力部分を示す。
nn_fpga_ch5_23_170606.png

out_V_address0 が 7 の時に最大値の 0b2c が出力されているのが分かる。結果は 7 となった。

Export RTL を行った。Vivado synthesis, place and route にチェックを入れたので、論理合成、インプリメントを行った結果を見ることができる。
nn_fpga_ch5_24_170606.png

タイミングはメットしている。

ソースコードの mnist_nn.cpp を示す。ソースコードはコメントを入れても 45 行だ。これは、元があったので、10分で書けた。

// mnist_nn.cpp
// 2017/06/01 by marsee
//

#include <stdio.h>
#include <ap_fixed.h>

#include "af1_weight.h"
#include "af1_bias.h"
#include "af2_weight.h"
#include "af2_bias.h"

int mnist_nn(ap_ufixed<80, AP_TRN_ZERO, AP_SAT> in[784], ap_fixed<135, AP_TRN_ZERO, AP_SAT> out[10]){
    ap_ufixed<80, AP_TRN_ZERO, AP_SAT> buf[784];
    ap_fixed<135, AP_TRN_ZERO, AP_SAT> dot1[50];
    ap_fixed<135, AP_TRN_ZERO, AP_SAT> dot2[10];

    buf_copy: for(int i=0; i<784; i++)
        buf[i] = in[i];

    af1_dot1: for(int col=0; col<50; col++){
        dot1[col] = 0;
        af1_dot2: for(int row=0; row<784; row++){
            dot1[col] += buf[row]*af1_weight[row][col];
        }
        dot1[col] += af1_bias[col];

        if(dot1[col] < 0)    // ReLU
            dot1[col] = 0;
    }

    af2_dot1: for(int col=0; col<10; col++){
        dot2[col] = 0;
        af2_dot2: for(int row=0; row<50; row++){
            dot2[col] += dot1[row]*af2_weight[row][col];
        }
        dot2[col] += af2_bias[col];

        if(dot2[col] < 0)    // ReLU
            dot2[col] = 0;
        out[col] = dot2[col];
    }

    return(0);
}


最後にテストベンチの mnist_nn_tb.cpp を示す。

// mnist_nn_tb.cpp
// 2017/06/02 by marsee
//

#include <stdio.h>
#include <ap_fixed.h>

#include "af1_weight.h"
#include "af1_bias.h"
#include "af2_weight.h"
#include "af2_bias.h"
#include "mnist_data.h"

int mnist_nn(ap_ufixed<80, AP_TRN_ZERO, AP_SAT> in[784], ap_fixed<135, AP_TRN_ZERO, AP_SAT> out[10]);
int mnist_nn_float(float in[784], float out[10]);
int max_ap_fixed(ap_fixed<135, AP_TRN_ZERO, AP_SAT> out[10]);
int max_float(float out[10]);

#define NUM_ITERATIONS    100 // C Simulation
// #define NUM_ITERATIONS    2 // C/RTL CoSimulation

int main(){
    float t_tran_float[NUM_ITERATIONS][784];
    ap_fixed<135, AP_TRN_ZERO, AP_SAT> result_ap_fixed[NUM_ITERATIONS][10];
    float result_float[NUM_ITERATIONS][10];
    int max_id_hw, max_id_sw, max_id_ref;

    for(int i=0; i<NUM_ITERATIONS; i++)
        for(int j=0; j<784; j++)
            t_tran_float[i][j] = (float)t_train[i][j];

    for(int i=0; i<NUM_ITERATIONS; i++){
        mnist_nn(&t_train[i][0], &result_ap_fixed[i][0]);
        mnist_nn_float(&t_tran_float[i][0], &result_float[i][0]);
    }

    int errflag=0;
    for(int i=0; i<NUM_ITERATIONS; i++){
        max_id_hw = max_ap_fixed(&result_ap_fixed[i][0]);
        max_id_sw = max_float(&result_float[i][0]);
        max_id_ref = max_float(&t_test[i][0]);

        if(max_id_ref != max_id_hw){
            printf("id = %d, max_id_ref = %d, max_id_hw = %d\n", i, max_id_ref, max_id_hw);
            errflag = 1;
        }
        if(max_id_ref != max_id_sw){
            printf("id = %d, max_id_ref = %d, max_id_sw = %d\n", i, max_id_ref, max_id_sw);
            errflag = 1;
        }
    }
    if(errflag == 0)
        printf("No Error\n");

    return(0);
}

int mnist_nn_float(float in[784], float out[10]){
    float dot1[50];
    float dot2[10];

    af1_dot1: for(int col=0; col<50; col++){
        dot1[col] = 0;
        af1_dot2: for(int row=0; row<784; row++){
            dot1[col] += in[row]*af1_fweight[row][col];
        }
        dot1[col] += af1_fbias[col];

        if(dot1[col] < 0)    // ReLU
            dot1[col] = 0;
    }

    af2_dot1: for(int col=0; col<10; col++){
        dot2[col] = 0;
        af2_dot2: for(int row=0; row<50; row++){
            dot2[col] += dot1[row]*af2_fweight[row][col];
        }
        dot2[col] += af2_fbias[col];

        if(dot2[col] < 0)    // ReLU
            dot2[col] = 0;
        out[col] = dot2[col];
    }

    return(0);
}

int max_ap_fixed(ap_fixed<135, AP_TRN_ZERO, AP_SAT> out[10]){
    int max_id;
    ap_fixed<135, AP_TRN_ZERO, AP_SAT> max;

    for(int i=0; i<10; i++){
        if(i == 0){
            max = out[0];
            max_id = 0;
        }else if(out[i]>max){
            max = out[i];
            max_id = i;
        }
    }
    return(max_id);
}

int max_float(float out[10]){
    int max_id;
    float max;

    for(int i=0; i<10; i++){
        if(i == 0){
            max = out[0];
            max_id = 0;
        }else if(out[i]>max){
            max = out[i];
            max_id = i;
        }
    }
    return(max_id);
}

  1. 2017年06月06日 04:09 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

BNN-PYNQ のトレーニングを試してみる3

BNN-PYNQ のトレーニングを試してみる2”の続き

前回は、MNISTのトレーニングをして、それをバイナリ化した。今回は、”マウスコンピューターのパソコンにHDDとGPUを追加してUbuntuをインストールした”で、Ubuntu 16.04 をネイティブにインストールして、CUDAもインストールすることができたので、GPUを使用してCIFAR-10をトレーニングしてみよう。

python cifar10.py を実行してトレーニングを開始したが、”ImportError: No module named nonlinearities”で停止してしまう。
BNN-PYNQ_63_170605.png

ググってみると、”No module named nonlinearities #624”がヒットした。

import lasagne
import theano

というようにimport の順番を逆にしたらエラーが解消したという投稿があったので、真似をしてみることにした。
下の図のように順番を入れ替えた。
BNN-PYNQ_64_170605.png

もう一度、python cifar10.py を実行すると、うまく行きました。
BNN-PYNQ_65_170605.png

1つのEpoch にかかる時間は58 秒から70 秒の間くらいだ。
BNN-PYNQ_66_170605.png

ただいまトレーニング中だ。2017 年6月5日の午前4時33分現在で、Epoch 488 だった。
BNN-PYNQ_67_170605.png

午後5時には終了した。
BNN-PYNQ_68_170605.png

best epoch は444 だそうだ。
BNN-PYNQ_69_170605.png
  1. 2017年06月05日 04:35 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

「ゼロから作るDeep Learning」の2層ニューラルネットワークのハードウェア化3

「ゼロから作るDeep Learning」の2層ニューラルネットワークのハードウェア化2”の続き。

前回は、Python コードを公開した。今回は、Vivado HLSに持っていくための重みとバイアスのC の配列を生成するPython コードを紹介する。更に、Vivado HLSのテストベンチに必要なMNISTデータセットの一部をC の配列に変換するPython コードも紹介する。最後にMNISTデータセットを画像として見るためのPython コードも紹介する。

全てのPython コードはJupyter Notebook で書いてあるので、画像を貼ることにする。
重みをC のヘッダファイルに書き込むメソッドは fwrite_weight で使い方も書いてあるので参照のこと。
バイアスをC のヘッダファイルに書き込むメソッドは fwrite_bias で同様に使い方も書いてあるので参照のこと。
MNISTのデータをCの配列に出力し、ファイルに書き込みするプログラムも作ってある。これでVivado HLSのテストベンチで使用するテストデータを作成することができる。
最後の view_mnist() は指定された範囲のMNISTデータセットを画像として表示する。これで、どのくらい間違いやすい画像だったか?を見ることができる。
nn_fpga_ch5_5_170604.png
nn_fpga_ch5_6_170604.png
nn_fpga_ch5_7_170604.png
nn_fpga_ch5_8_170604.png
nn_fpga_ch5_9_170604.png

重みのファイル af1_weight.h の一部を示す。
nn_fpga_ch5_10_170604.png

af1_bias.h を示す。
nn_fpga_ch5_11_170604.png

MNISTデータセットの一部、mnist_data.h を示す。
nn_fpga_ch5_12_170604.png

これらのデータはPython コードを実行すれば出力できるので、全部を貼っておくことはしない。
Python コードを貼っておく。

def fwrite_weight(weight, wfile_name, float_wt_name, fixed_wt_name, MAGNIFICATION, row_size, column_size):
    import datetime
    import numpy as np
    
    f = open(wfile_name, 'w')
    todaytime = datetime.datetime.today()
    f.write('// '+wfile_name+'\n')
    strdtime = todaytime.strftime("%Y/%m/%d %H:%M:%S")
    f.write('// {0} by marsee\n'.format(strdtime))
    f.write("\n")
    
    f.write('const float '+float_wt_name+'['+str(row_size)+']['+str(column_size)+'] = {\n')
    for i in range(weight.shape[0]):
        f.write("\t{")
        for j in range(weight.shape[1]):
            f.write(str(weight[i][j]))
            if (j==weight.shape[1]-1):
                if (i==weight.shape[0]-1):
                    f.write("}\n")
                else:
                    f.write("},\n")
            else:
                f.write(", ")
    f.write("};\n")

    f.write("\n")
    f.write('const ap_fixed<'+str(int(np.log2(MAGNIFICATION))+1)+', 1, AP_TRN_ZERO, AP_SAT> '+fixed_wt_name+'['+str(row_size)+']['+str(column_size)+'] = {\n')
    for i in range(weight.shape[0]):
        f.write("\t{")
        for j in range(weight.shape[1]):
            w_int = int(weight[i][j]*MAGNIFICATION+0.5)
            if (w_int > MAGNIFICATION-1):
                w_int = MAGNIFICATION-1
            elif (w_int < -MAGNIFICATION):
                w_int = -MAGNIFICATION
            f.write(str(w_int/MAGNIFICATION))
            if (j==weight.shape[1]-1):
                if(i==weight.shape[0]-1):
                    f.write("}\n")
                else:
                    f.write("},\n")
            else:
                f.write(", ")
    f.write("};\n")

    f.close()


MAGNIFICATION = 2 ** (9-1)
fwrite_weight(network.params['W1'], 'af1_weight.h', 'af1_fweight', 'af1_weight', MAGNIFICATION, 784, 50)


fwrite_weight(network.params['W2'], 'af2_weight.h', 'af2_fweight', 'af2_weight', MAGNIFICATION, 50, 10)


def fwrite_bias(bias, wfile_name, float_b_name, fixed_wt_name, MAGNIFICATION, size):
    import datetime
    import numpy as np
    
    f = open(wfile_name, 'w')
    todaytime = datetime.datetime.today()
    f.write('// '+wfile_name+'\n')
    strdtime = todaytime.strftime("%Y/%m/%d %H:%M:%S")
    f.write('// {0} by marsee\n'.format(strdtime))
    f.write("\n")

    f.write('const float '+float_b_name+'['+str(size)+'] = {\n\t')
    for i in range(bias.shape[0]):
        f.write(str(bias[i]))
        if (i < bias.shape[0]-1):
            f.write(", ")
    f.write("\n};\n")

    f.write("\n")
    f.write('const ap_fixed<'+str(int(np.log2(MAGNIFICATION))+1)+', 1, AP_TRN_ZERO, AP_SAT> '+fixed_wt_name+'['+str(size)+'] = {\n\t')
    for i in range(bias.shape[0]):
        b_int = int(bias[i]*MAGNIFICATION+0.5)
        if (b_int > MAGNIFICATION-1):
            b_int = MAGNIFICATION-1
        elif (b_int < -MAGNIFICATION):
            b_int = -MAGNIFICATION
        f.write(str(b_int/MAGNIFICATION))
        if (i < bias.shape[0]-1):
            f.write(", ")
    f.write("\n};\n")

    f.close()


fwrite_bias(network.params['b1'], 'af1_bias.h', 'af1_fbias', 'af1_bias', MAGNIFICATION, 50)


fwrite_bias(network.params['b2'], 'af2_bias.h', 'af2_fbias', 'af2_bias', MAGNIFICATION, 10)


# MNISTのデータをCの配列に出力し、ファイルに書き込み

# coding: utf-8
import sys, os
sys.path.append(os.pardir)

import numpy as np
from dataset.mnist import load_mnist
import datetime

OUTPUT_DATA_NUM = 100 # 出力するMNISTのテストデータ数 10000までの数

# データの読み込み
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)

f = open("mnist_data.h", 'w')
todaytime = datetime.datetime.today()
f.write('// mnist_data.h\n')
strdtime = todaytime.strftime("%Y/%m/%d %H:%M:%S")
f.write('// {0} by marsee\n'.format(strdtime))
f.write("\n")

f.write('ap_ufixed<8, 0, AP_TRN_ZERO, AP_SAT> t_train['+str(OUTPUT_DATA_NUM)+'][784] = {\n')
for i in range(OUTPUT_DATA_NUM):
    f.write("\t{")
    for j in range(x_test.shape[1]):
        f.write(str(x_test[i][j]))
        if (j==x_test.shape[1]-1):
            if (i==OUTPUT_DATA_NUM-1):
                f.write("}\n")
            else:
                f.write("},\n")
        else:
            f.write(", ")
f.write("};\n")

f.write('int t_train_256['+str(OUTPUT_DATA_NUM)+'][784] = {\n')
for i in range(OUTPUT_DATA_NUM):
    f.write("\t{")
    for j in range(x_test.shape[1]):
        f.write(str(int(x_test[i][j]*256)))
        if (j==x_test.shape[1]-1):
            if (i==OUTPUT_DATA_NUM-1):
                f.write("}\n")
            else:
                f.write("},\n")
        else:
            f.write(", ")
f.write("};\n")

f.write("\n")
f.write('float t_test['+str(OUTPUT_DATA_NUM)+'][784] = {\n')
for i in range(OUTPUT_DATA_NUM):
    f.write("\t{")
    for j in range(t_test.shape[1]):
        f.write(str(t_test[i][j]))
        if (j==t_test.shape[1]-1):
            if (i==OUTPUT_DATA_NUM-1):
                f.write("}\n")
            else:
                f.write("},\n")
        else:
            f.write(", ")
f.write("};\n")
f.close() 


def view_mnist(first_offset, last_offset):
    # MNISTデータセットのfirst_offset(画像の配列の番号)からlast_offset-1までの画像を表示する
    # 「ゼロから作るDeep_Learning」第8章のコードを一部引用しています
    
    # coding: utf-8
    import sys, os
    sys.path.append(os.pardir)

    import numpy as np
    from dataset.mnist import load_mnist
    import matplotlib.pyplot as plt

    # データの読み込み
    (x_train, t_train), (x_test, t_test) = load_mnist(normalize=False, one_hot_label=True)

    fig = plt.figure()
    fig.subplots_adjust(left=0, right=1, bottom=0, top=1, hspace=0.2, wspace=0.2)
    
    current_view = 1
    for i in range(first_offset, last_offset):
        ax = fig.add_subplot(4, 5, current_view, xticks=[], yticks=[])
        ax.imshow(x_test[i].reshape(28, 28), cmap=plt.cm.gray_r, interpolation='nearest')
        current_view += 1
    
    plt.show()


view_mnist(0, 10)


  1. 2017年06月04日 17:34 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

マウスコンピューターのパソコンにHDDとGPUを追加してUbuntuをインストールした

マウスコンピューターのパソコンにHDDとGPU(NVidia の GTX1060)を追加して、追加したHDD の 1.4TB のパーティションに Ubuntu 16.04 をインストールしたのだが、とっても大変だったのでブログに書いておく。

マウスコンピューターのパソコンを購入した記録だ。
マウスコンピューターにパソコンを注文しました” ”FPGAコンパイルマシンが届きました(新しいパソコン)
ちなみに、メモリは16GB に増設されている。

HDDとGPU を追加した写真を示す。
Ubuntu_GPU_1_170604.jpg

まずは、Windows の高速起動をOFF にしないと、パソコン起動時のBIOS の設定が難しいので、高速起動をOFFにした。

Windows 上から 「 BIOS 設定 」 画面を表示する方法 ( Windows10 )”を参照にしてBIOS 画面を表示した。

BIOS 設定が肝だった。これを間違うといろいろな不具合が生じた。
まずは、Boot 設定。
Fast Boot をDisabled にしないと、下のUEFI Hard Disk Drive Priorities が出てこない。
Boot 画面が見えるようにQuiet Boot をDisabled に設定した。
Hard Disk のブートはWindows Boot Manager になっているのが分かる。これをUbuntu に設定しないとGRUBの画面が出てこなかった。
Ubuntu_GPU_2_170604.jpg

UEFI Hard Disk Drive Priorities に入って、’-’ボタンを押して、ubuntu をBoot Option #1 に持ってきた。
Ubuntu_GPU_3_170604.jpg

Hard Disk のブートが ubuntu になっているのが分かる。これでGRUBが起動して、Ubuntu やWindows の起動を選ぶことできるようになった。
Ubuntu_GPU_4_170604.jpg

これでUbuntu を起動することができるようになった。
ちなみに、Ubuntu 16.04 のインストールは、USBメモリにUnetbootin を使用して、Ubuntu 16.04 のイメージを書きこんで、USBブートでインストールした。

ところが、USB ブートして、インストールメニューは出るのだが、どうしてインストールを選ぶとその後の画面が表示されない。とっても悩んだが、”nVidiaのGPU搭載PCにUbuntuを入れようとしてハマった”をやってみると画面が表示されるようになった。
結局、メニューが出たら e キーを押してコマンドの編集モードにして、”quiet splash を nomodeset に書き換える”をやれば、その後の画面が見えた。

NVidiaのドライバをインストールしないと 800 x 600 の画面しか表示できなかったので、インストールを行った。
GTX-1080 など GeForce を Ubuntu 16.04 LTS で CUDA-8.0RC と共に使う”を参考に、GPU のGTX1060 のドライバをインストールしたのだが、どうやってもドライバをインストール後にUbuntu を再起動した後にログインすることができなかった。2回ほどインストールのし直しをしてしまった。
原因は、BIOS のSecure Boot だった。これをDisabled にすると、GTX1060 のドライバを問題なくインストールすることができた。
Ubuntu_GPU_5_170604.jpg

BNN-PYNQ/bnn/src/training/ の設定を行って、mnist をトレーニングしてみた。
その結果、Epochが 6.5 秒程度だった。以前にVirtualBox 上にインストールしたUbuntu 16.04 のCPUでやった時は、約125 秒だったので、約19.2 倍にスピードアップしていることになる。
つまり、33時間20分が1時間44分で終了するということになる。計測してなかったが実際に速かった。Ubntu_GPU_6_170604.png

(2017/06/05:追記)
マウスホイールの移動量が少ないので、”「imwheel」でマウスホイールのスクロール移動量(スピード)を大きく”を参考にして、設定したら、快適になりました。
UPを7、DOWNを6に設定しました。
  1. 2017年06月04日 04:38 |
  2. パソコン関連
  3. | トラックバック:0
  4. | コメント:0

「ゼロから作るDeep Learning」の2層ニューラルネットワークのハードウェア化2

「ゼロから作るDeep Learning」の2層ニューラルネットワークのハードウェア化1”の続き。

前回は、「ゼロから作るDeep Learning」の5章 誤差逆伝播法の2層のニューラルネットワークをハードウェア化してFPGA に実装してみようということで、重みなどを量子化して、その精度を確認する方法を書いた。
今回は、そのPython コードを貼っておく。

(注:今回のPython コードはoreilly-japan/deep-learning-from-scratch をクローンした環境下でないと動作しない。私は ch5 フォルダの下で動作させている。なお、「ゼロから作るDeep Learning」を購入してから試してほしい)

なお、紹介するPython コードはdeep-learning-from-scratch/ch05/に置いてあるものを変更している。それらのPython コードはMIT ライセンスなので、私が変更、追加したコードもMIT ライセンスを引き継ぐものとする。

最初に、layers_int.py から貼っておく。

# layers_int.py
# 元になったコードは、https://github.com/oreilly-japan/deep-learning-from-scratch にあります。
# 改変したコードもMITライセンスとします。 2017/06/02 by marsee

# coding: utf-8
import numpy as np
from common.functions import *
from common.util import im2col, col2im

MAGNIFICATION = 2 ** (9-1)
RANGE = 2 ** 4

class Relu:
    def __init__(self):
        self.mask = None

    def forward_int(self, x):
        self.mask = (x <= 0)
        out = x.copy()
        out[self.mask] = 0

        return out

    def forward(self, x):
        self.mask = (x <= 0)
        out = x.copy()
        out[self.mask] = 0

        return out

    def backward(self, dout):
        dout[self.mask] = 0
        dx = dout

        return dx


class Sigmoid:
    def __init__(self):
        self.out = None

    def forward_int(self, x):
        out = sigmoid(x)
        self.out = out
        return out

    def forward(self, x):
        out = sigmoid(x)
        self.out = out
        return out

    def backward(self, dout):
        dx = dout * (1.0 - self.out) * self.out

        return dx


class Affine:
    def __init__(self, W, b):
        self.W =W
        self.b = b

        self.x = None
        self.original_x_shape = None
        # 重み・バイアスパラメータの微分
        self.dW = None
        self.db = None

        self.bw=MAGNIFICATION

    def forward_int(self, x):
        # テンソル対応
        self.original_x_shape = x.shape
        x = x.reshape(x.shape[0], -1)
        self.x = x

        # x は量子化できているはず
        # wとbをINT8の範囲に修正 2017/05/22 by marsee
        self.W = np.array(self.W*self.bw+0.5, dtype=int)
        self.b = np.array(self.b*self.bw+0.5, dtype=int)
        for i in range(self.W.shape[0]):
            for j in range(self.W.shape[1]):
                if (self.W[i][j] > self.bw-1):
                    self.W[i][j] = self.bw-1
                elif (self.W[i][j] < -self.bw):
                    self.W[i][j] = -self.bw;
        for i in range(self.b.shape[0]):
            if (self.b[i] > self.bw-1):
                self.b[i] = self.bw-1
            elif (self.b[i] < -self.bw):
                self.b[i] = -self.bw
        
        self.W = np.array(self.W, dtype=float)
        self.b = np.array(self.b, dtype=float)
        
        self.W = self.W/self.bw
        self.b = self.b/self.bw

        out = np.dot(self.x, self.W) + self.b

        print(x.shape)
        print("np.max(x) = {0}".format(np.max(x)))
        print("np.min(x) = {0}".format(np.min(x)))        
        #print("x = {0}".format(self.x))
        print(self.W.shape)
        print("np.max(self.W) = {0}".format(np.max(self.W)))
        print("np.min(self.W) = {0}".format(np.min(self.W)))
        print(self.b.shape)
        print("np.max(self.b) = {0}".format(np.max(self.b)))
        print("np.min(self.b) = {0}".format(np.min(self.b)))
        print(out.shape)
        print("np.max(out) = {0}".format(np.max(out)))
        print("np.min(out) = {0}".format(np.min(out)))
        #print("out = {0}".format(out))

        out = np.array(out*self.bw+0.5, dtype=int)
        for i in range(out.shape[0]):
            for j in range(out.shape[1]):
                if (out[i][j] > self.bw*RANGE-1):
                    out[i][j] = self.bw*RANGE-1
                elif (out[i][j] < -self.bw*RANGE):
                    out[i][j] = -self.bw*RANGE
        out = np.array(out, dtype=float)
        out = out/self.bw

        print("np.max(out2) = {0}".format(np.max(out)))
        print("np.min(out2) = {0}".format(np.min(out)))

        return out

    def forward(self, x):
        # テンソル対応
        self.original_x_shape = x.shape
        x = x.reshape(x.shape[0], -1)
        self.x = x

        out = np.dot(self.x, self.W) + self.b

        return out

    def backward(self, dout):
        dx = np.dot(dout, self.W.T)
        self.dW = np.dot(self.x.T, dout)
        self.db = np.sum(dout, axis=0)
        
        dx = dx.reshape(*self.original_x_shape)  # 入力データの形状に戻す(テンソル対応)
        return dx


class SoftmaxWithLoss:
    def __init__(self):
        self.loss = None
        self.y = None # softmaxの出力
        self.t = None # 教師データ

    def forward(self, x, t):
        self.t = t
        self.y = softmax(x)
        self.loss = cross_entropy_error(self.y, self.t)
        
        return self.loss

    def forward_int(self, x, t):
        self.t = t
        self.y = softmax(x)
        self.loss = cross_entropy_error(self.y, self.t)
        
        return self.loss

    def backward(self, dout=1):
        batch_size = self.t.shape[0]
        if self.t.size == self.y.size: # 教師データがone-hot-vectorの場合
            dx = (self.y - self.t) / batch_size
        else:
            dx = self.y.copy()
            dx[np.arange(batch_size), self.t] -= 1
            dx = dx / batch_size
        
        return dx


class Dropout:
    """
    http://arxiv.org/abs/1207.0580
    """
    def __init__(self, dropout_ratio=0.5):
        self.dropout_ratio = dropout_ratio
        self.mask = None

    def forward_int(self, x, train_flg=True):
        if train_flg:
            self.mask = np.random.rand(*x.shape) > self.dropout_ratio
            return x * self.mask
        else:
            return x * (1.0 - self.dropout_ratio)

    def forward(self, x, train_flg=True):
        if train_flg:
            self.mask = np.random.rand(*x.shape) > self.dropout_ratio
            return x * self.mask
        else:
            return x * (1.0 - self.dropout_ratio)

    def backward(self, dout):
        return dout * self.mask


class BatchNormalization:
    """
    http://arxiv.org/abs/1502.03167
    """
    def __init__(self, gamma, beta, momentum=0.9, running_mean=None, running_var=None):
        self.gamma = gamma
        self.beta = beta
        self.momentum = momentum
        self.input_shape = None # Conv層の場合は4次元、全結合層の場合は2次元  

        # テスト時に使用する平均と分散
        self.running_mean = running_mean
        self.running_var = running_var  
        
        # backward時に使用する中間データ
        self.batch_size = None
        self.xc = None
        self.std = None
        self.dgamma = None
        self.dbeta = None

    def forward_int(self, x, train_flg=True):
        self.input_shape = x.shape
        if x.ndim != 2:
            N, C, H, W = x.shape
            x = x.reshape(N, -1)

        out = self.__forward(x, train_flg)
        
        return out.reshape(*self.input_shape)
            
    def forward(self, x, train_flg=True):
        self.input_shape = x.shape
        if x.ndim != 2:
            N, C, H, W = x.shape
            x = x.reshape(N, -1)

        out = self.__forward(x, train_flg)
        
        return out.reshape(*self.input_shape)
            
    def __forward(self, x, train_flg):
        if self.running_mean is None:
            N, D = x.shape
            self.running_mean = np.zeros(D)
            self.running_var = np.zeros(D)
                        
        if train_flg:
            mu = x.mean(axis=0)
            xc = x - mu
            var = np.mean(xc**2, axis=0)
            std = np.sqrt(var + 10e-7)
            xn = xc / std
            
            self.batch_size = x.shape[0]
            self.xc = xc
            self.xn = xn
            self.std = std
            self.running_mean = self.momentum * self.running_mean + (1-self.momentum) * mu
            self.running_var = self.momentum * self.running_var + (1-self.momentum) * var            
        else:
            xc = x - self.running_mean
            xn = xc / ((np.sqrt(self.running_var + 10e-7)))
            
        out = self.gamma * xn + self.beta 
        return out

    def backward(self, dout):
        if dout.ndim != 2:
            N, C, H, W = dout.shape
            dout = dout.reshape(N, -1)

        dx = self.__backward(dout)

        dx = dx.reshape(*self.input_shape)
        return dx

    def __backward(self, dout):
        dbeta = dout.sum(axis=0)
        dgamma = np.sum(self.xn * dout, axis=0)
        dxn = self.gamma * dout
        dxc = dxn / self.std
        dstd = -np.sum((dxn * self.xc) / (self.std * self.std), axis=0)
        dvar = 0.5 * dstd / self.std
        dxc += (2.0 / self.batch_size) * self.xc * dvar
        dmu = np.sum(dxc, axis=0)
        dx = dxc - dmu / self.batch_size
        
        self.dgamma = dgamma
        self.dbeta = dbeta
        
        return dx


class Convolution:
    def __init__(self, W, b, stride=1, pad=0):
        self.W = W
        self.b = b
        self.stride = stride
        self.pad = pad
        
        # 中間データ(backward時に使用)
        self.x = None   
        self.col = None
        self.col_W = None
        
        # 重み・バイアスパラメータの勾配
        self.dW = None
        self.db = None

    def forward_int(self, x):
        FN, C, FH, FW = self.W.shape
        N, C, H, W = x.shape
        out_h = 1 + int((H + 2*self.pad - FH) / self.stride)
        out_w = 1 + int((W + 2*self.pad - FW) / self.stride)

        col = im2col(x, FH, FW, self.stride, self.pad)
        col_W = self.W.reshape(FN, -1).T

        out = np.dot(col, col_W) + self.b
        out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)

        self.x = x
        self.col = col
        self.col_W = col_W

        return out

    def forward(self, x):
        FN, C, FH, FW = self.W.shape
        N, C, H, W = x.shape
        out_h = 1 + int((H + 2*self.pad - FH) / self.stride)
        out_w = 1 + int((W + 2*self.pad - FW) / self.stride)

        col = im2col(x, FH, FW, self.stride, self.pad)
        col_W = self.W.reshape(FN, -1).T

        out = np.dot(col, col_W) + self.b
        out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)

        self.x = x
        self.col = col
        self.col_W = col_W

        return out

    def backward(self, dout):
        FN, C, FH, FW = self.W.shape
        dout = dout.transpose(0,2,3,1).reshape(-1, FN)

        self.db = np.sum(dout, axis=0)
        self.dW = np.dot(self.col.T, dout)
        self.dW = self.dW.transpose(1, 0).reshape(FN, C, FH, FW)

        dcol = np.dot(dout, self.col_W.T)
        dx = col2im(dcol, self.x.shape, FH, FW, self.stride, self.pad)

        return dx


class Pooling:
    def __init__(self, pool_h, pool_w, stride=1, pad=0):
        self.pool_h = pool_h
        self.pool_w = pool_w
        self.stride = stride
        self.pad = pad
        
        self.x = None
        self.arg_max = None

    def forward_int(self, x):
        N, C, H, W = x.shape
        out_h = int(1 + (H - self.pool_h) / self.stride)
        out_w = int(1 + (W - self.pool_w) / self.stride)

        col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)
        col = col.reshape(-1, self.pool_h*self.pool_w)

        arg_max = np.argmax(col, axis=1)
        out = np.max(col, axis=1)
        out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)

        self.x = x
        self.arg_max = arg_max

        return out

    def forward(self, x):
        N, C, H, W = x.shape
        out_h = int(1 + (H - self.pool_h) / self.stride)
        out_w = int(1 + (W - self.pool_w) / self.stride)

        col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)
        col = col.reshape(-1, self.pool_h*self.pool_w)

        arg_max = np.argmax(col, axis=1)
        out = np.max(col, axis=1)
        out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)

        self.x = x
        self.arg_max = arg_max

        return out

    def backward(self, dout):
        dout = dout.transpose(0, 2, 3, 1)
        
        pool_size = self.pool_h * self.pool_w
        dmax = np.zeros((dout.size, pool_size))
        dmax[np.arange(self.arg_max.size), self.arg_max.flatten()] = dout.flatten()
        dmax = dmax.reshape(dout.shape + (pool_size,)) 
        
        dcol = dmax.reshape(dmax.shape[0] * dmax.shape[1] * dmax.shape[2], -1)
        dx = col2im(dcol, self.x.shape, self.pool_h, self.pool_w, self.stride, self.pad)
        
        return dx


two_layer_net_int.py を貼っておく。

# two_layer_net_int.py
# 元になったコードは、https://github.com/oreilly-japan/deep-learning-from-scratch にあります。
# 改変したコードもMITライセンスとします。 2017/06/02 by marsee

# coding: utf-8
import sys, os
sys.path.append(os.pardir)  # 親ディレクトリのファイルをインポートするための設定
import numpy as np
from layers_int import *
#from common.layers import *
from common.gradient import numerical_gradient
from collections import OrderedDict


class TwoLayerNet:

    def __init__(self, input_size, hidden_size, output_size, weight_init_std = 0.01):
        # 重みの初期化
        self.params = {}
        self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
        self.params['b1'] = np.zeros(hidden_size)
        self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size) 
        self.params['b2'] = np.zeros(output_size)

        # レイヤの生成
        self.layers = OrderedDict()
        self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1'])
        self.layers['Relu1'] = Relu()
        self.layers['Affine2'] = Affine(self.params['W2'], self.params['b2'])

        self.lastLayer = SoftmaxWithLoss()
        
    def predict(self, x):
        for layer in self.layers.values():
            x = layer.forward(x)
        
        return x

    def predict_int(self, x):
        for layer in self.layers.values():
            x = layer.forward_int(x)
        
        return x    
        
    # x:入力データ, t:教師データ
    def loss_int(self, x, t):
        y = self.predict_int(x)
        return self.lastLayer.forward_int(y, t)
    
    def loss(self, x, t):
        y = self.predict(x)
        return self.lastLayer.forward(y, t)
    
    def accuracy_int(self, x, t):
        y = self.predict_int(x)
        y = np.argmax(y, axis=1)
        if t.ndim != 1 : t = np.argmax(t, axis=1)
        
        accuracy = np.sum(y == t) / float(x.shape[0])
        return accuracy
        
    def accuracy(self, x, t):
        y = self.predict(x)
        y = np.argmax(y, axis=1)
        if t.ndim != 1 : t = np.argmax(t, axis=1)
        
        accuracy = np.sum(y == t) / float(x.shape[0])
        return accuracy
        
    # x:入力データ, t:教師データ
    def numerical_gradient(self, x, t):
        loss_W = lambda W: self.loss(x, t)
        
        grads = {}
        grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
        grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
        grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
        
        return grads
        
    def gradient(self, x, t):
        # forward
        self.loss(x, t)

        # backward
        dout = 1
        dout = self.lastLayer.backward(dout)
        
        layers = list(self.layers.values())
        layers.reverse()
        for layer in layers:
            dout = layer.backward(dout)

        # 設定
        grads = {}
        grads['W1'], grads['b1'] = self.layers['Affine1'].dW, self.layers['Affine1'].db
        grads['W2'], grads['b2'] = self.layers['Affine2'].dW, self.layers['Affine2'].db

        return grads


ニューラルネットワークのトレーニングと量子化したときの評価を行った train_neuralnet.py を貼っておく。

# train_neuralnet.py
# 元になったコードは、https://github.com/oreilly-japan/deep-learning-from-scratch にあります。
# 改変したコードもMITライセンスとします。 2017/06/02 by marsee

# coding: utf-8
import sys, os
sys.path.append(os.pardir)

import numpy as np
from dataset.mnist import load_mnist
from two_layer_net_int import TwoLayerNet

# データの読み込み
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)

network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)

iters_num = 10000
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1

train_loss_list = []
train_acc_list = []
test_acc_list = []

iter_per_epoch = max(train_size / batch_size, 1)

for i in range(iters_num):
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    t_batch = t_train[batch_mask]
    
    # 勾配
    #grad = network.numerical_gradient(x_batch, t_batch)
    grad = network.gradient(x_batch, t_batch)
    
    # 更新
    for key in ('W1', 'b1', 'W2', 'b2'):
        network.params[key] -= learning_rate * grad[key]
    
    loss = network.loss(x_batch, t_batch)
    train_loss_list.append(loss)
    
    if i % iter_per_epoch == 0:
        train_acc = network.accuracy(x_train, t_train)
        test_acc = network.accuracy(x_test, t_test)
        train_acc_list.append(train_acc)
        test_acc_list.append(test_acc)
        print(train_acc, test_acc)
        test_mask = np.random.choice(train_size, 1)
        xtest_batch = x_train[test_mask]
        ttest_batch = t_train[test_mask]
        xtest_data = network.predict(xtest_batch)
        #print(train_acc, test_acc)
        #print(xtest_data)
        #print(ttest_batch)
          
"""print(network.params['W1'])
print(network.params['b1'])
print(network.params['W2'])
print(network.params['b2'])"""

print()
train_acc_int = network.accuracy_int(x_train, t_train)
test_acc_int = network.accuracy_int(x_test, t_test)
print(train_acc_int, test_acc_int)

  1. 2017年06月03日 04:22 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

「ゼロから作るDeep Learning」の2層ニューラルネットワークのハードウェア化1

「ゼロから作るDeep Learning」以前、すべての章をご紹介した。とっても良い本だ。

その5章 誤差逆伝播法の2層のニューラルネットワークをハードウェア化してFPGA に実装してみようと思う。
学習はdeep-learning-from-scratch/ch05/ のtrain_neuralnet.py のコードをそのまま使用する。
このままでは浮動小数点数なので、指定されたビット長の固定小数点に量子化するメソッドをtwo_layer_net.py、layers.py に追加してある。それが、two_layer_net_int.py、layers_int.py だ。
nn_fpga_ch5_1_170602.png

上の図のように Class Relu に def forward_int が追加されているのが分かると思う。つまり、固定小数点に量子化してforward 処理を実行するメソッドを追加したわけだ。ただし、Relu はforward と同じだ。
MAGNIFICATION が量子化のビット長を表す。つまり、全体が 9 ビットで、整数部が1ビットを表す。RANGE が内積を取った後の整数部に拡張されるビット長を表している。これらはいろいろとやってみて、精度があまり落ちない値にしてある。

今回、def forward_int を書き換えたのは、class Affine だけだ。
重みやバイアスにMAGNIFICATION を掛けて、0.5 を加算して(四捨五入のため)integer に変換した後で、飽和演算を行う。すると小数点以下が切れてるので、量子化できることになる。そして、float に戻して、MAGNIFICATION で割っている。
その後、量子化の目安のため値のmax と min を表示している。
nn_fpga_ch5_2_170602.png

先ほど書いたように、train_neuralnet.py の学習はそのまま使って、two_layer_net_int.py にも def loss_int や def predict_int、def accuracy_int を追加してあるので、固定小数点に量子化してforward 処理したときのaccuracy(精度)を確認することができる。
nn_fpga_ch5_3_170602.png
nn_fpga_ch5_4_170602.png

その出力を示す。

0.17975 0.1722
0.904483333333 0.9106
0.9247 0.9279
0.9357 0.9352
0.94515 0.9425
0.95155 0.9506
0.956733333333 0.9527
0.95995 0.9564
0.9649 0.9615
0.9663 0.9609
0.968883333333 0.9632
0.972766666667 0.9655
0.973883333333 0.9665
0.97535 0.968
0.97635 0.9678
0.9784 0.9696
0.979483333333 0.9692

(60000, 784)
np.max(x) = 1.0
np.min(x) = 0.0
(784, 50)
np.max(self.W) = 0.42578125
np.min(self.W) = -0.53515625
(50,)
np.max(self.b) = 0.28515625
np.min(self.b) = -0.22265625
(60000, 50)
np.max(out) = 10.925199185698148
np.min(out) = -12.404212626584922
np.max(out2) = 10.92578125
np.min(out2) = -12.3984375
(60000, 50)
np.max(x) = 10.92578125
np.min(x) = 0.0
(50, 10)
np.max(self.W) = 0.99609375
np.min(self.W) = -1.0
(10,)
np.max(self.b) = 0.421875
np.min(self.b) = -0.39453125
(60000, 10)
np.max(out) = 30.527862548828125
np.min(out) = -28.802261352539062
np.max(out2) = 15.99609375
np.min(out2) = -16.0
(10000, 784)
np.max(x) = 1.0
np.min(x) = 0.0
(784, 50)
np.max(self.W) = 0.42578125
np.min(self.W) = -0.53125
(50,)
np.max(self.b) = 0.28515625
np.min(self.b) = -0.21875
(10000, 50)
np.max(out) = 10.473299672572466
np.min(out) = -10.355300247856576
np.max(out2) = 10.47265625
np.min(out2) = -10.3515625
(10000, 50)
np.max(x) = 10.47265625
np.min(x) = 0.0
(50, 10)
np.max(self.W) = 0.99609375
np.min(self.W) = -0.99609375
(10,)
np.max(self.b) = 0.421875
np.min(self.b) = -0.390625
(10000, 10)
np.max(out) = 29.088897705078125
np.min(out) = -24.532501220703125
np.max(out2) = 15.99609375
np.min(out2) = -16.0
0.9774 0.9636


最後の”0.9774 0.9636”が固定小数点で量子化した場合のaccuracy だ。浮動小数点で計算したときのaccuracy は”0.979483333333 0.9692”なので、少し下がっているのが分かると思う。
  1. 2017年06月02日 04:55 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS の浮動小数点型を使用した内積の計算

Vivado HLS の任意精度固定小数点型を使用した内積の計算”のおまけ。

任意精度固定小数点型の比較のために作った浮動小数点数の内積の計算だが、思ったより使用リソースが少なかったので、書いておくことにした。

まずはnn_test_float プロジェクトを示す。
ap_fixed_dot_8_170601.png

次にソースコードを示す。
まずは、nn_test_float.h から示す。

// nn_test_float.h
// 2017/05/31 by marsee
//

#ifndef __NN_TEST_H__
#define __NN_TEST_H__

#include <ap_fixed.h>

const float wt[3][4]={
    {-0.10.2, -0.30.4},
    {-0.50.6, -0.70.8},
    {-0.20.4, -0.50.6}
};

const float b[4] ={
    -0.10.4, -0.30.5
};

#endif


nn_test.cpp を示す。

// nn_test_float.cpp
// 2017/05/29 by marsee
//

#include <stdio.h>
#include "nn_test_float.h"

int nn_test(float in[3], float out[4]){
    float dot[4];

    Loop1: for(int j=0; j<4; j++){
        dot[j] = 0;
        Loop2: for(int i=0; i<3; i++){
            dot[j] += in[i]*wt[i][j];
        }
        dot[j] += b[j];
        out[j] = dot[j];
    }

    return(0);
}


nn_test_tb_float.cpp を示す。

// nn_test_tb_float.cpp
// 2017/05/29 by marsee
//

#include <stdio.h>
#include "nn_test_float.h"

int nn_test(float in[3], float out[4]);
int nn_test_soft(float in[3], float out[4]);

int main(){
    float in[3] = {0.3906250.58593750.78125};
    float out[4];
    float out_soft[4];

    nn_test(in, out);
    nn_test_soft(in, out_soft);

    for(int i=0; i<4; i++){
        if(out[i] != out_soft[i]){
            printf("ERROR HW and SW results mismatch i = %d, HW = %f, SW = %f\n", i, (float)out[i], (float)out_soft[i]);
        }else{
            printf("out[%d] = %f\n", i, (float)out[i]);
        }
    }

    return(0);
}

int nn_test_soft(float in[3], float out[4]){
    float dot[4];

    for(int j=0; j<4; j++){
        dot[j] = 0;
        for(int i=0; i<3; i++){
            dot[j] += in[i]*wt[i][j];
        }
        dot[j] += b[j];
        out[j] = dot[j];
    }

    return(0);
}


C シミュレーションは、前回の”Vivado HLS の任意精度固定小数点型を使用した内積の計算”を参照のこと。

C コードの合成を行った。
ap_fixed_dot_9_170601.png

DSPを5個使っている以外は案外、リソース使用量が少ないのではないだろうか?

Analysis 画面を開いた。DSP は浮動小数点数の乗算に3個、加算の2個使用されている。
ap_fixed_dot_10_170601.png

C/RTL協調シミュレーションを行った。やはり、前回の 109 クロックよりも完了までのクロック数は増えている。
ap_fixed_dot_11_170601.png

C/RTL協調シミュレーションの出力も問題なさそうだ。

out[0] = -0.588281
out[1] = 1.142187
out[2] = -1.217969
out[3] = 1.593750


C/RTL協調シミュレーション結果を示す。
ap_fixed_dot_12_170601.png

浮動小数点数が出ているようだ。
  1. 2017年06月01日 21:17 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0

Vivado HLS の任意精度固定小数点型を使用した内積の計算

今回は、配列の内積をVivado HLS の任意精度固定小数点型を使用して計算してみようと思う。浮動小数点数の計算値と比較してみよう。

Vivado HLS の任意精度固定小数点型を使用した配列の内積を計算するVivado HLS 2017.1 のプロジェクトを示す。
ap_fixed_dot_1_170601.png

nn_test.h を示す。

// nn_test.h
// 2017/05/31 by marsee
//

#ifndef __NN_TEST_H__
#define __NN_TEST_H__

#include <ap_fixed.h>

const ap_fixed<91, AP_TRN_ZERO, AP_SAT> wt[3][4]={
    {-0.10.2, -0.30.4},
    {-0.50.6, -0.70.8},
    {-0.20.4, -0.50.6}
};

const ap_fixed<91, AP_TRN_ZERO, AP_SAT> b[4] ={
    -0.10.4, -0.30.5
};

#endif


nn_test.cpp を示す。

// nn_test.cpp
// 2017/05/29 by marsee
//

#include <stdio.h>
#include "nn_test.h"

int nn_test(ap_ufixed<80, AP_TRN_ZERO, AP_SAT> in[3], ap_fixed<135, AP_TRN_ZERO, AP_SAT> out[4]){
    ap_fixed<135, AP_TRN_ZERO, AP_SAT> dot[4];

    Loop1: for(int j=0; j<4; j++){
        dot[j] = 0;
        Loop2: for(int i=0; i<3; i++){
            dot[j] += in[i]*wt[i][j];
        }
        dot[j] += b[j];
        out[j] = dot[j];
    }

    return(0);
}


nn_test_tb.cpp を示す。

// nn_test_tb.cpp
// 2017/05/29 by marsee
//

#include <stdio.h>
#include "nn_test.h"

int nn_test(ap_ufixed<80, AP_TRN_ZERO, AP_SAT> in[3], ap_fixed<135, AP_TRN_ZERO, AP_SAT> out[4]);
int nn_test_soft(ap_ufixed<80, AP_TRN_ZERO, AP_SAT> in[3], ap_fixed<135, AP_TRN_ZERO, AP_SAT> out[4]);

int main(){
    ap_ufixed<80, AP_TRN_ZERO, AP_SAT> in[3] = {0.3906250.58593750.78125};
    ap_fixed<135, AP_TRN_ZERO, AP_SAT> out[4];
    ap_fixed<135, AP_TRN_ZERO, AP_SAT> out_soft[4];

    nn_test(in, out);
    nn_test_soft(in, out_soft);

    for(int i=0; i<4; i++){
        if(out[i] != out_soft[i]){
            printf("ERROR HW and SW results mismatch i = %d, HW = %f, SW = %f\n", i, (float)out[i], (float)out_soft[i]);
        }else{
            printf("out[%d] = %f\n", i, (float)out[i]);
        }
    }

    return(0);
}

int nn_test_soft(ap_ufixed<80, AP_TRN_ZERO, AP_SAT> in[3], ap_fixed<135, AP_TRN_ZERO, AP_SAT> out[4]){
    ap_fixed<135, AP_TRN_ZERO, AP_SAT> dot[4];

    for(int j=0; j<4; j++){
        dot[j] = 0;
        for(int i=0; i<3; i++){
            dot[j] += in[i]*wt[i][j];
        }
        dot[j] += b[j];
        out[j] = dot[j];
    }

    return(0);
}


C シミュレーションを行った。結果を示す。
ap_fixed_dot_2_170601.png

out[] の値を示す。
out[0] = -0.578125
out[1] = 1.128906
out[2] = -1.207031
out[3] = 1.582031

Vivado HLS の任意精度固定小数点型をそっくりそのまま float 型で実装したVivado HLS プロジェクトの結果を示す。
ap_fixed_dot_3_170601.png

out[] の値を示す。
out[0] = -0.588281
out[1] = 1.142187
out[2] = -1.217969
out[3] = 1.593750

このコードで本当に配列の内積が計算できているか?を調べるために、maxima で配列の内積を計算した。
ap_fixed_dot_4_170601.png

その結果 float 型で計算したVivado HLS のプロジェクトの値は、maxima で計算した値にほぼ等しい。
Vivado HLS の任意精度固定小数点型はやはり固定小数点なので、誤差があるが、おおむね値は合っていそうだ。

Vivado HLS の任意精度固定小数点型のVivado HLS プロジェクトで、C コードの合成を行った。
ap_fixed_dot_5_170601.png

全く指示子を与えていないので、デフォルトの状態になっている。

C/RTL協調シミュレーションを行った。
ap_fixed_dot_6_170601.png

C/RTL協調シミュレーションの波形を示す。
ap_fixed_dot_7_170601.png

このように、Vivado HLS の任意精度固定小数点型で書くと、固定小数点数のビット長や固定小数点位置が違っても、うまく計算してくれる。とっても便利だ。
うまく計算してくれるのは、以前のやった結果からわかってはいたのだが、もう一度、確認するためにやってみた。
  1. 2017年06月01日 04:05 |
  2. Vivado HLS
  3. | トラックバック:0
  4. | コメント:0