FC2カウンター FPGAの部屋 カーブ、直線用白線間走行用畳み込みニューラルネットワーク11(Vivado HLS でCNN を実装)
FC2ブログ

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

FPGAの部屋

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

カーブ、直線用白線間走行用畳み込みニューラルネットワーク11(Vivado HLS でCNN を実装)

カーブ、直線用白線間走行用畳み込みニューラルネットワーク10(重みとバイアスをC のヘッダファイルに変換)”の続き。

前回は、カーブと直線の白線間を走行するためのデータセットで畳み込みニューラルネットワークを学習したときの重みとバイアスを C のヘッダファイルに変換して、Vivado HLS で使用できるようにした。これでVivado HLS でカーブと直線の白線間走行用畳み込みニューラルネットワークを実装する準備が整ったので、今回は、Vivado HLS 2017.3 で畳み込みニューラルネットワークを実装しよう。

(2018/03/15:テストベンチの C ソースコードと結果を修正した)

Vivado HLS 2017.3 で curve_conv_nn2 プロジェクトを作成した。
curve_tracing_cnn_51_171220.png

カーブ、直線用白線間走行用畳み込みニューラルネットワーク10(重みとバイアスをC のヘッダファイルに変換)”で作成した重みとバイアスの C ヘッダ・ファイルの af1_bias.h、af1_weight.h、af2_bias.h、af2_weight.h、conv1_bias.h、conv1_weight.h がプロジェクトの source ディレクトリに追加されている。更に、畳み込みニューラルネットワークを C で実装した curve_conv_nn2.cpp も source ディレクトリに追加している。
Test Bench ディレクトリには、”カーブ、直線用白線間走行用畳み込みニューラルネットワーク11(画像データをCのヘッダファイルに変換)”で作成した curve_data_0_100.h と curve_data_2500_2600.h 、 curve_data_5000_5100.h を追加してある。そして、テストベンチ・ファイルの curve_conv_nn_tb.cpp も追加されている。

curve_conv_nn2.cpp を貼っておく。なお、ビット精度が大きくなっているが、”CNNのVivado HLS実装のstraight_conv_nn2 の演算精度を変更する”で検証したビット幅を使用している。

// curve_conv_nn2.cpp
// 2017/12/06 by marsee
// 畳み込み層のカーネル数 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"

#define NUM_OF_KERNELS 2
#define COULMN_PIXELS 56
#define ROW_PIXELS 10
#define ALL_PIXELS 560
#define NUM_OF_OUTPUT 3

int curve_conv_nn(ap_ufixed<80, AP_TRN_ZERO, AP_SAT> in[ALL_PIXELS], ap_fixed<127, AP_TRN_ZERO, AP_SAT> out[NUM_OF_OUTPUT]){
    ap_ufixed<80, AP_TRN_ZERO, AP_SAT> buf[ROW_PIXELS][COULMN_PIXELS];
    ap_fixed<166, AP_TRN_ZERO, AP_SAT> conv_out[NUM_OF_KERNELS][ROW_PIXELS-4][COULMN_PIXELS-4];
    ap_fixed<166, AP_TRN_ZERO, AP_SAT> pool_out[NUM_OF_KERNELS][(ROW_PIXELS-4)/2][(COULMN_PIXELS-4)/2];
    ap_fixed<197, AP_TRN_ZERO, AP_SAT> dot1[100];
    ap_fixed<197, AP_TRN_ZERO, AP_SAT> dot2[NUM_OF_OUTPUT];

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

    // Convolutional Neural Network 5x5 kernel, Stride = 1, Padding = 0
    // + ReLU
    CONV1: for(int i=0; i<NUM_OF_KERNELS; i++){    // カーネルの個数
        CONV2: for(int j=0; j<ROW_PIXELS-4; j++){
            CONV3: for(int k=0; k<COULMN_PIXELS-4; 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<NUM_OF_KERNELS; i++){
        POOL2: for(int j=0; j<ROW_PIXELS-4; j += 2){
            POOL3: for(int k=0; k<COULMN_PIXELS-4; 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<NUM_OF_KERNELS; i++){
            af1_dot3: for(int j=0; j<(ROW_PIXELS-4)/2; j++){
                af1_dot4: for(int k=0; k<(COULMN_PIXELS-4)/2; k++){
                    dot1[col] += pool_out[i][j][k]*af1_weight[i*((ROW_PIXELS-4)/2)*((COULMN_PIXELS-4)/2)+j*((COULMN_PIXELS-4)/2)+k][col];
                }
            }
        }
        dot1[col] += af1_bias[col];

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

    af2_dot1: for(int col=0; col<NUM_OF_OUTPUT; 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);
}


curve_conv_nn_tb.cpp を貼っておく。

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

#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 "curve_data_0_100.h"
//#include "curve_data_2500_2600.h"
//#include "curve_data_5000_5100.h"

#define ALL_DATA_NUM   300
#define NUM_OF_KERNELS 2
#define COULMN_PIXELS 56
#define ROW_PIXELS 10
#define ALL_PIXELS 560
#define NUM_OF_OUTPUT 3

int curve_conv_nn(ap_ufixed<80, AP_TRN_ZERO, AP_SAT> in[ALL_PIXELS], ap_fixed<127, AP_TRN_ZERO, AP_SAT> out[NUM_OF_OUTPUT]);
int curve_conv_nn_float(float in[ALL_PIXELS], float out[NUM_OF_OUTPUT]);
int max_ap_fixed(ap_fixed<127, AP_TRN_ZERO, AP_SAT> out[NUM_OF_OUTPUT]);
int max_float(float out[NUM_OF_OUTPUT]);

#define NUM_ITERATIONS    300 // C Simulation
//#define NUM_ITERATIONS    1 // C/RTL CoSimulation

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

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

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

    int errflag=0;
    int sw_err_cnt = 0;
    int hw_err_cnt = 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;
            hw_err_cnt++;
        }
        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;
            sw_err_cnt++;
        }
    }

    if(errflag == 0){
        printf("No Error\n");
    } else {
        printf("\nhw_err_cnt = %d, sw_err_cnt = %d\n", hw_err_cnt, sw_err_cnt);
        printf("hw accuracy = %f\%, sw accuracy = %f\%\n",
                (((float)ALL_DATA_NUM-(float)hw_err_cnt)/(float)ALL_DATA_NUM)*100.0,
                (((float)ALL_DATA_NUM-(float)sw_err_cnt)/(float)ALL_DATA_NUM)*100.0);
    }
    printf("\n");

    return(0);
}

int curve_conv_nn_float(float in[ALL_PIXELS], float out[NUM_OF_OUTPUT]){
    float buf[ROW_PIXELS][COULMN_PIXELS];
    float conv_out[NUM_OF_KERNELS][ROW_PIXELS-4][COULMN_PIXELS-4];
    float pool_out[NUM_OF_KERNELS][(ROW_PIXELS-4)/2][(COULMN_PIXELS-4)/2];
    float dot1[100];
    float dot2[NUM_OF_OUTPUT];

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

    // Convolutional Neural Network 5x5 kernel, Stride = 1, Padding = 0
    // + ReLU
    CONV1: for(int i=0; i<NUM_OF_KERNELS; i++){    // カーネルの個数
        CONV2: for(int j=0; j<ROW_PIXELS-4; j++){
            CONV3: for(int k=0; k<COULMN_PIXELS-4; 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<NUM_OF_KERNELS; i++){
        POOL2: for(int j=0; j<ROW_PIXELS-4; j += 2){
            POOL3: for(int k=0; k<COULMN_PIXELS-4; 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<NUM_OF_KERNELS; i++){
            af1_dot3: for(int j=0; j<(ROW_PIXELS-4)/2; j++){
                af1_dot4: for(int k=0; k<(COULMN_PIXELS-4)/2; k++){
                    dot1[col] += pool_out[i][j][k]*af1_fweight[i*((ROW_PIXELS-4)/2)*((COULMN_PIXELS-4)/2)+j*((COULMN_PIXELS-4)/2)+k][col];
                }
            }
        }
        dot1[col] += af1_fbias[col];

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

    af2_dot1: for(int col=0; col<NUM_OF_OUTPUT; 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[NUM_OF_OUTPUT]){
    int max_id;
    ap_fixed<127, AP_TRN_ZERO, AP_SAT> max;

    for(int i=0; i<NUM_OF_OUTPUT; 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[NUM_OF_OUTPUT]){
    int max_id;
    float max;

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


curve_conv_nn_tb.cpp のカーブと直線の画像データ・セットを curve_data_0_100.h と curve_data_2500_2600.h 、 curve_data_5000_5100.h にコメントアウトしている文を切り替えて 3 回 C シミュレーションしてみよう。
curve_tracing_cnn_52_171220.png

最初に、 curve_data_0_100.h で C シミュレーションを実行した。
curve_tracing_cnn_53_171220.png

ハードウェアの固定小数点演算では、300 画像の内で 7 個間違って、その精度は (300 - 7) / 300 X 100 = 97.666669% だった。
ソフトウェアの浮動小数点演算では、 300 画像の内で 20 個間違って、その精度は、93.333334% だった。

hw_err_cnt = 7, sw_err_cnt = 20
hw accuracy = 97.666669%, sw accuracy = 93.333334%



次に、curve_data_2500_2600.h で C シミュレーションを実行した。
curve_tracing_cnn_54_171220.png

ハードウェアの固定小数点演算では、300 画像の内で 16 個間違って、その精度は 94.666666 % だった。
ソフトウェアの浮動小数点演算では、 300 画像の内で 11 個間違って、その精度は、96.333331 % だった。

hw_err_cnt = 16, sw_err_cnt = 11
hw accuracy = 94.666666%, sw accuracy = 96.333331%



次に、curve_data_5000_5100.h で C シミュレーションを実行した。
curve_tracing_cnn_55_171220.png

ハードウェアの固定小数点演算では、300 画像の内で 47 個間違って、その精度は 84.333330 % だった。
ソフトウェアの浮動小数点演算では、 300 画像の内で 15 個間違って、その精度は、94.999999 % だった。

hw_err_cnt = 47, sw_err_cnt = 15
hw accuracy = 84.333330%, sw accuracy = 94.999999%


今回は、特にハードウェアの間違いが増えてしまった。3 回のテストの間違いの合計は、7 + 16 + 47 = 70 個。900 個のテスト画像の内で 70 個間違ったのだから、(900 - (7 + 16 + 47)) / 900 * 100 = 92.222222 % となった。


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

Estimated は 8.68 ns で Target の 10 ns を満足している。
Latency は約 1.72 ms で 30 fps のカメラ画像を解析するには十分だ。

DSP48E は 8 使用されている。FF と LUT 使用量は割と少ない。BRAM_18K は 16 個使用している。

大体これで行けそうだ。後は、DMAエンジンを組み込んで、実際に使う状態でのVivado HLS プロジェクトを作成しよう。
  1. 2017年12月20日 06:39 |
  2. DNN
  3. | トラックバック:0
  4. | コメント:0

コメント

コメントの投稿


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

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