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

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

FPGAの部屋

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

「ゼロから作る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. DLNN
  3. | トラックバック:0
  4. | コメント:0

コメント

コメントの投稿


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

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