FC2カウンター FPGAの部屋 手書き数字認識用畳み込みニューラルネットワーク回路の製作3(畳み込みNN)

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

FPGAの部屋

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

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

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

前回は、手書き数字認識用四角枠表示回路が完成した。これで画像中に認識する四角枠を表示することができた。この四角枠の中に手書き数字を入れれば認識することができる。今回は、四角枠の中の手書き数字を認識する畳み込みニューラルネットワークを作成しよう。
この畳み込みニューラルネットワークは、画像中の四角枠の中の手書き数字をDMA で持ってきて、畳み込みニューラルネットワークを通すことになる。するとDMA はストライド付きのDMA となる。
下の図で画像は 800 ピクセル x 600 行なので、0 の手書き数字の領域は最初の行で 28 ピクセルを読み込んだら次の行の手書き数字のピクセルは 800 - 28 個目にあることになる。よって、畳み込みニューラルネットワークには、下の図の緑色の領域のピクセルを渡して、ストライドを付けてDMAすることにする。
hand_draw_num_52_170630.png

C シミュレーションができないため、VirtualBox 上のUbuntu 16.04 でVivado HLS 2016.4 を起動してmnist_conv_nn10_sDMA プロジェクトを作成した。なお、カメラ・インターフェース回路からDDR3 メモリ上にDMA Write されたデータを直接DMA でRead するため入力ポートになる引数の型はap_ufixed からint に変更した。
hand_draw_num_53_170630.png

最初に C シミュレーションを行った。
最初に表示される 28 x 28 の数字は画像から切り出された 28 x 28 の手書き数字のピクセル値を表している。これは手書き数字の 8 だ。他にも0, 5, 7 について同様にC シミュレーションを行ったが正解だった。
その後で、ハードウエアでの認識結果(max_id_hw)とソフトウェアでの認識結果(max_id_sw)が表示されている。両方とも正しい。
hand_draw_num_61_170701.jpg

hand_draw_num_54_170630.png

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

最低限の最適化だが、100 MHz 動作で 12.8 ms 程度なので、 60 fps は満足している。
なお、WindowsのVivado HLS 2016.4 でもC コードの合成を行ってみた。すると、Estimated が違って、こちらは 10 nsをオーバーしてしまった。リソース使用量も微妙に違う。Windowsの結果とLinuxでの結果が一致しないようだ。
hand_draw_num_59_170630.png

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

Export RTL を行って、IP化した。Place and Route を行って結果を見たが、問題なさそうだ。
hand_draw_num_58_170630.png

Windowsでもやってみたがやはり結果が微妙に違う。
hand_draw_num_60_170630.png

Linux の結果を使用することにした。

mnist_conv_nn10_sDMA.cpp を貼っておく。

// mnist_conv_nn10_sDMA.cpp
// 2017/06/12 by marsee
// 畳み込み層のカーネル数 10
// 2017/06/29 : アドレスオフセット導入 800x600 画像中の 28x28 を切り取ってDMAする
// |      アドレスオフセット      |
// *************************-手書き数字1行目-****************
// *************************-手書き数字2行目-****************
//

#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"

ap_ufixed<80, AP_TRN_ZERO, AP_SAT> conv_rgb2y(int rgb);

int mnist_conv_nn(int in[22400], int addr_offset, ap_fixed<127, AP_TRN_ZERO, AP_SAT> out[10]){
#pragma HLS INTERFACE s_axilite port=addr_offset
#pragma HLS INTERFACE s_axilite register port=out
#pragma HLS INTERFACE m_axi depth=22400 port=in  offset=slave
#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];
    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<800; j++){
#pragma HLS PIPELINE II=1
            if (j>=addr_offset && j<addr_offset+28)
                buf[i][j-addr_offset] = (ap_ufixed<80, AP_TRN_ZERO, AP_SAT>)0.99609375 - conv_rgb2y(in[i*800+j]);
                // 1.0 にならないように 1/256を引いておく
        }
    }

    // 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++){
#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++){
            dot2[col] += dot1[row]*af2_weight[row][col];
        }
        dot2[col] += af2_bias[col];

        out[col] = dot2[col];
    }

    return(0);
}

// 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 にした
// 2017/06/30 : ap_ufixed<8, 0, AP_TRN_ZERO, AP_SAT> 出力とした
ap_ufixed<80, AP_TRN_ZERO, AP_SAT> conv_rgb2y(int rgb){
    int r, g, b, y_f;
    int y;
    ap_ufixed<168, AP_TRN_ZERO, AP_SAT> y_ap_ufixed;

    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で割る

    if (y >= 256)
        y = 255;

    y_ap_ufixed = (ap_ufixed<168, AP_TRN_ZERO, AP_SAT>)y / 256;

    return((ap_ufixed<80, AP_TRN_ZERO, AP_SAT>)y_ap_ufixed);
}


mnist_conv_nn_sDMA_tb.cpp を貼っておく。

// mnist_conv_nn_sDMA_tb.cpp
// 2017/06/14 by marsee
// 畳み込み層のカーネル数 10
// 2017/06/29 : ストライドDMAのためのテストベンチ
//

#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 "bmp_header.h"

int mnist_conv_nn(int in[22400], int addr_offset, ap_fixed<127, AP_TRN_ZERO, AP_SAT> out[10]);
int mnist_conv_nn_float(int in[22400], int addr_offset, float out[10]);
int max_ap_fixed(ap_fixed<127, AP_TRN_ZERO, AP_SAT> out[10]);
int max_float(float out[10]);
float conv_rgb2y_soft(int rgb);

#define READ_BMP_FILE_NAME    "bmp_file0.bmp"

// 8
#define X_POS    560
#define Y_POS    183
// 7
//#define X_POS    504
//#define Y_POS    184
// 5
//#define X_POS    390
//#define Y_POS    138
// 0
//#define X_POS    390
//#define Y_POS    70
#define WIDTH    28
#define HEIGHT    28

int main(){
    ap_fixed<127, AP_TRN_ZERO, AP_SAT> result_ap_fixed[10];
    float result_float[10];
    int max_id_hw, max_id_sw, max_id_ref;
    int *in;
    int *inf;

    BITMAPFILEHEADER bmpfhr; // BMPファイルのファイルヘッダ(for Read)
    BITMAPINFOHEADER bmpihr; // BMPファイルのINFOヘッダ(for Read)
    FILE *fbmpr;
    int *rd_bmp;
    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 ((in =(int *)malloc(sizeof(int) * (800 * 28))) == NULL){
        fprintf(stderr, "Can't allocate (ap_ufixed<8, 0, AP_TRN_ZERO, AP_SAT>)in memory\n");
        exit(1);
    }

    if ((inf =(int *)malloc(sizeof(int) * (800 * 28))) == NULL){
        fprintf(stderr, "Can't allocate (float)inf 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);

    // rd_bmp を in と inf に入力
    for (int y=Y_POS; y<Y_POS+HEIGHT; y++){
        for (int x=0; x<bmpihr.biWidth; x++){
            in[(y-Y_POS)*bmpihr.biWidth+x] = rd_bmp[y*bmpihr.biWidth+x];
            inf[(y-Y_POS)*bmpihr.biWidth+x] = rd_bmp[y*bmpihr.biWidth+x];
        }
    }

    mnist_conv_nn(in, X_POS, result_ap_fixed);
    mnist_conv_nn_float(inf, X_POS, result_float);

    max_id_hw = max_ap_fixed(result_ap_fixed);
    max_id_sw = max_float(result_float);

    printf("max_id_hw = %d\n", max_id_hw);
    printf("max_id_sw = %d\n", max_id_sw);

    return(0);
}

int mnist_conv_nn_float(int in[22400], int addr_offset, 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];

    // 手書き数字の値を表示
    for (int i=0; i<28; i++){
        for (int j=0; j<800; j++){
            if (j>=addr_offset && j<addr_offset+28)
                printf("%2x, ", (int)(conv_rgb2y_soft(in[i*800+j])*256.0));
        }
        printf("\n");
    }

    buf_copy1: for(int i=0; i<28; i++){
        buf_copy2: for(int j=0; j<800; j++){
            if (j>=addr_offset && j<addr_offset+28)
                buf[i][j-addr_offset] = (float)0.99609375 - (float)conv_rgb2y_soft(in[i*800+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 にした
// 2017/06/30 : retval を float にした
float conv_rgb2y_soft(int rgb){
    int r, g, b, y_f;
    int y;
    float y_float;

    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で割る

    if (y >= 256)
        y = 255;

    y_float = (float)y/256.0;

    return(y_float);
}

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

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック URL
http://marsee101.blog19.fc2.com/tb.php/3843-06cca748
この記事にトラックバックする(FC2ブログユーザー)