FC2カウンター FPGAの部屋 MNISTの畳み込みニューラルネットワークで自分の手書き数字を認識する1

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

FPGAの部屋

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

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

コメント

コメントの投稿


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

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