经典 CNN 神经网络 LeNet-5 的 C++ 实现(MNIST数据集)

        前言:本文不对CNN卷积神经网络做深入探究,CNN卷积神经网络的基本知识请移步本文的相关链接;本文不对LeNet-5神经网络模型做深入探究,该部分的知识可以自行查阅或者查看本文的链接!MNIST数据集请自行在官网下载。此外,如果使用本文的代码,请将该数据集放置于源代码同级目录下。

        笔记:从单纯的BP算法,到DNN再到CNN,是一个奇妙的旅程。CNN于DNN的不同之处在于对于局部特征的抽取(个人理解),通过卷积运算,来压缩原有的特征数据,并将压缩后的数据通过池化操作,进一步提取特征。当然可以通过各种不一样的组合来进行这些操作,比如全连接,多次卷积,多次池化,采用不同的激活函数,采用不同的卷积核,控制feature map的数量,控制步长...最后经过训练以及优化完成想要的效果。

1.有关CNN卷积神经网络,请移步:CNN笔记

2.有关LeNet-5 神经网络模型(如下图),请移步:LeNet详解

3.程序说明:

        3.1 该程序只针对MNIST数据训练一次,准确率在0.95左右,当然,你可以试着更改一些参数来调试该网络,例如learning rate,epoch,batch size...。多次epoch后准确率更高!一次训练的时间大概在6-10分钟,使用visual studio 2019编译该代码比codeblocks自带的gcc编译后,运行的速度更快一些。(本人实测)。

        3.2 该程序提供了多个显示图像的方法,自己debug过程中可以方便调用观察,具体的使用已经在代码中注释。.当然你也可以自己写一些更好的方法加进去!

        3.3 log函数只为调试代码使用,运行时请将is_logging参数设置为false。

4.程序代码

#include <iostream>
#include <stdlib.h>
#include <memory.h>
#include <math.h>
#include <ctime>

#ifndef MAX
#define MAX(A,B)	(((A) > (B)) ? (A) : (B))
#endif

using namespace std;

typedef struct data_sample
{
    double* data;
    double* label;
    int sample_width;
    int sample_height;
    int sample_count;
}Sample;

typedef struct convolution_kernel
{
    double* weight;
    double* delta_weight;
}Kernel;

typedef struct layer_map
{
    double* data;
    double* error;
    double bias;
    double delta_bias;
}Map;

typedef struct layer
{
    int map_width;
    int map_height;
    int map_count;
    Map* map;

    int kernel_width;
    int kernel_height;
    int kernel_count;
    Kernel* kernel;

    double* map_common;
}Layer;

const int batch_size = 10;
const int class_count = 10;
const int width = 32;
const int height = 32;
const int train_sample_count = 60000;
const int test_sample_count = 10000;
const int zero_padding = 2;
const int padded_matrix_size = (28 + zero_padding * 2) * (28 + 2 * zero_padding);

Layer input_layer;
Layer output_layer;
Layer c1_convolution_layer;
Layer s2_pooling_layer;
Layer c3_convolution_layer;
Layer s4_pooling_layer;
Layer c5_convolution_layer;

bool is_logging = false;
void log_exec(const char* msg)
{
    if (is_logging)
    {
        cout << "exec part in -->> [ " << msg << " ]" << endl;
    }
}

void show_pic(Sample sample, int target)
{
    cout << "img:" << endl;
    for (int w = 0; w < 1024; w++)
    {
        cout << sample.data[w] << ",";
        if ((w + 1) % 32 == 0)
        {
            cout << endl;
        }
    }
    cout << endl << "target:" << target << endl;
    cout << endl << endl;
}

void show_pic(Sample sample)
{
    cout << "img:" << endl;
    for (int w = 0; w < 1024; w++)
    {
        cout << sample.data[w] << ",";
        if ((w + 1) % 32 == 0)
        {
            cout << endl;
        }
    }
    int index = 0;
    for (int i = 0; i < class_count; i++)
    {
        if (sample.label[i] == 1)
        {
            index = i;
        }
    }
    cout << endl << "target:" << index << endl;
    cout << endl << endl;
}

void show_map(Map map, int width, int height)
{
    cout << "map info:" << endl;
    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++)
        {
            cout << map.data[i * width + j] << ",";
        }
        cout << endl;
    }
    cout << endl << endl;
}

void load_mnist_data(Sample* sample, const char* file_name, int sample_count) //checked 2.0
{
    FILE* mnist_file = NULL;
    int err = fopen_s(&mnist_file, file_name, "rb");

    unsigned char image_buffer[784];    //保存图片信息

    int head_info[1000];    //读取出文件的头信息,该信息不参与计算
    fread(head_info, 1, 16, mnist_file);   //读取16字节头部信息

    if (mnist_file == NULL)
    {
        cout << "load data from your file err..." << endl;
        return;
    }
    else
    {
        cout << "loading data...[in func -->> load_mnist_data]" << endl;
    }
    for (int i = 0; i < sample_count; i++)
    {
        sample[i].data = (double*)malloc(padded_matrix_size * sizeof(double));
        memset(sample[i].data, 0, padded_matrix_size * sizeof(double));
        fread(image_buffer, 1, 784, mnist_file);
        unsigned int value;
        int index = 0;
        for (int j = 0; j < 28; j++)
        {
            for (int k = 0; k < 28; k++)
            {
                int shuffle_index = (j + zero_padding) * width + k + zero_padding;
                value = (unsigned int)image_buffer[index++];
                if (value < 128)
                {
                    sample[i].data[shuffle_index] = 0;
                }
                else
                {
                    sample[i].data[shuffle_index] = 1;
                }
            }
        }

    }
    fclose(mnist_file);
    mnist_file = NULL;
}

void load_mnist_label(Sample* sample, const char* file_name, const int sample_count) //checked 2.0
{
    FILE* mnist_file = NULL;
    int err = fopen_s(&mnist_file, file_name, "rb");
    unsigned char label;

    if (mnist_file == NULL)
    {
        cout << "load label from your file err..." << endl;
        return;
    }
    else
    {
        cout << "loading label...[in func -->> load_mnist_label]" << endl;
    }
    int head_info[1000];    //读取出文件的头信息,该信息不参与计算
    fread(head_info, 1, 8, mnist_file);    //读取8字节头部信息

    for (int i = 0; i < sample_count; i++)
    {
        sample[i].label = (double*)malloc(class_count * sizeof(double));
        for(int k = 0; k < class_count; k ++)
        {
            sample[i].label[k] = 0;
        }
        fread((char*)&label, sizeof(label), 1, mnist_file);
        int value_index = (unsigned int)label;
        sample[i].label[value_index] = 1;

        //show_pic(sample[i],value_index);  //test
    }
    fclose(mnist_file);
    mnist_file = NULL;
}
double weight_base_calc(int pre_map_count,int cur_map_count,int kernel_width,int kernel_height,bool is_pooling)
{
    const double scale = 6.0;
    int fan_in = 0;
    int fan_out = 0;
    if (is_pooling)
    {
        fan_in  = 4;
        fan_out = 1;
    }
    else
    {
        fan_in = pre_map_count * kernel_width * kernel_height;
        fan_out = cur_map_count * kernel_width * kernel_height;
    }
    int denominator = fan_in + fan_out;
    double weight_base = (denominator != 0) ? sqrt(scale / (double)denominator) : 0.5;
    return weight_base;
}
void init_kernel(double* kernel, int size, double weight_base)
{
    log_exec("init_kernel");

    for (int i = 0; i < size; i++)
    {
        kernel[i] = ((double)rand() / RAND_MAX - 0.5) * 2 * weight_base;
    }
}

void init_layer(Layer* layer, int prelayer_map_count, int map_count, int kernel_width, int kernel_height, int map_width, int map_height, bool is_pooling)
{
    log_exec("init_kernel");

    int single_kernel_memsize = 0;
    double weight_base = weight_base_calc(prelayer_map_count,map_count,kernel_width,kernel_height,is_pooling);

    //cout<<"weight base :"<<weight_base<<endl;

    layer->kernel_count = prelayer_map_count * map_count;//需要训练的kernel数目
    layer->kernel_width = kernel_width;
    layer->kernel_height = kernel_height;
    layer->kernel = (Kernel*)malloc(layer->kernel_count * sizeof(Kernel)); //为该层的所有卷积核分配内存
    single_kernel_memsize = layer->kernel_width * layer->kernel_height * sizeof(double);
    for (int i = 0; i < prelayer_map_count; i++) //初始化两层之间每一个卷积核的参数weight delta weight
    {
        for (int j = 0; j < map_count; j++)
        {
            int valued_index = (i * map_count) + j;
            layer->kernel[valued_index].weight = (double*)malloc(single_kernel_memsize);
            init_kernel(layer->kernel[valued_index].weight, kernel_height * kernel_width, weight_base);
            layer->kernel[valued_index].delta_weight = (double*)malloc(single_kernel_memsize);
            memset(layer->kernel[valued_index].delta_weight, 0, single_kernel_memsize);
        }
    }
    layer->map_count = map_count;
    layer->map_height = map_height;
    layer->map_width = map_width;
    layer->map = (Map*)malloc(layer->map_count * sizeof(Map));
    int single_map_size = layer->map_height * layer->map_width * sizeof(double);
    for (int i = 0; i < layer->map_count; i++) //初始化每一个map的参数 bias ,delta bias, data, error
    {
        layer->map[i].bias = 0.0;
        layer->map[i].delta_bias = 0.0;
        layer->map[i].data = (double*)malloc(single_map_size);
        memset(layer->map[i].data, 0, single_map_size);
        layer->map[i].error = (double*)malloc(single_map_size);
        memset(layer->map[i].error, 0, single_map_size);
    }
    layer->map_common = (double*)malloc(single_map_size);
    memset(layer->map_common, 0, single_map_size);
}

/*
    重置某layer层的所有map的delta_bias为0;
    重置某layer层所有的kernel的delta_weight为0
*/
void reset_layer_deltabias_kernel_deltaweight(Layer *layer) //checked 2.0
{

    log_exec("reset_layer_deltabias_kernel_deltaweight");

    int single_kernel_memsize = layer->kernel_height * layer->kernel_width * sizeof(double);
    for (int i = 0; i < layer->kernel_count; i++)
    {
        memset(layer->kernel[i].delta_weight, 0, single_kernel_memsize);
    }
    for (int i = 0; i < layer->map_count; i++)
    {
        layer->map[i].delta_bias = 0.0;
    }
}

void reset_all_layer() //checked 2.0
{
    log_exec("reset_all_layer");

    reset_layer_deltabias_kernel_deltaweight(&c1_convolution_layer);
    reset_layer_deltabias_kernel_deltaweight(&s2_pooling_layer);
    reset_layer_deltabias_kernel_deltaweight(&c3_convolution_layer);
    reset_layer_deltabias_kernel_deltaweight(&s4_pooling_layer);
    reset_layer_deltabias_kernel_deltaweight(&c5_convolution_layer);
    reset_layer_deltabias_kernel_deltaweight(&output_layer);
}
/*
    激活函数以及导函数计算
*/
struct activation_function  //checked 2.0
{
    inline static double tan_h(double x)
    {
        return ((exp(x) - exp(-x)) / (exp(x) + exp(-x)));
    }
    inline static double d_tan_h(double x)
    {
        return (1.0 - (x * x));
    }
    inline static double relu(double x)
    {
        return (x > 0.0 ? x : 0.0);
    }
    inline static double d_relu(double x)
    {
        return (x > 0.0 ? 1.0 : 0.0);
    }
    inline static double sigmod(double x)
    {
        return (1.0 / (1.0 + exp(-x)));
    }
    inline static double d_sigmod(double x)
    {
        return (x * (1.0 - x));
    }
};
/*
    损失函数
*/
struct loss_function //checked 2.0
{
    inline static double cal_loss(double real, double target)
    {
        return (real - target) * (real - target) / 2;
    }
    inline static double d_cal_loss(double real, double target)
    {
        return (real - target);
    }
};

/*
    单次计算map与kernel的卷积
*/
void convolution_calcu(double* input_map_data, int input_map_width, int input_map_height, double* kernel_data, int kernel_width, int kernel_height, double* result_map_data, int result_map_width, int result_map_height)
{
    log_exec("convolution_calcu");

    double sum = 0.0;
    for (int i = 0; i < result_map_height; i++)
    {
        for (int j = 0; j < result_map_width; j++)
        {
            sum = 0.0;
            for (int n = 0; n < kernel_height; n++)
            {
                for (int m = 0; m < kernel_width; m++)
                {
                    int index_input_reshuffle = (i + n) * input_map_width + j + m;
                    int index_kernel_reshuffle = n * kernel_width + m;
                    sum += input_map_data[index_input_reshuffle] * kernel_data[index_kernel_reshuffle];
                }
            }
            int index_result_reshuffle = i * result_map_width + j;
            result_map_data[index_result_reshuffle] += sum;
        }
    }
}

#define O true
#define X false
//S2到C3不完全连接映射表
bool connection_table[6 * 16] = {
                O, X, X, X, O, O, O, X, X, O, O, O, O, X, O, O,
                O, O, X, X, X, O, O, O, X, X, O, O, O, O, X, O,
                O, O, O, X, X, X, O, O, O, X, X, O, X, O, O, O,
                X, O, O, O, X, X, O, O, O, O, X, X, O, X, O, O,
                X, X, O, O, O, X, X, O, O, O, O, X, O, O, X, O,
                X, X, X, O, O, O, X, X, O, O, O, O, X, O, O, O
};
#undef O
#undef X

void convolution_forward_propagation(Layer* pre_layer, Layer* cur_layer, bool* connection_table) //checked and fixed /checked 2.0
{
    log_exec("convolution_forward_propagation");
    int index_layer = 0;
    int layer_size = cur_layer->map_height * cur_layer->map_width;
    for (int i = 0; i < cur_layer->map_count; i++)
    {
        memset(cur_layer->map_common, 0, layer_size * sizeof(double)); //清空公共map的暂存数据。
        for (int j = 0; j < pre_layer->map_count; j++)
        {
            index_layer = j * cur_layer->map_count + i;
            if (connection_table != NULL && !connection_table[index_layer])
            {
                continue;
            }
            //fix para 3 map height
            convolution_calcu(
                pre_layer->map[j].data, pre_layer->map_width, pre_layer->map_height,
                cur_layer->kernel[index_layer].weight, cur_layer->kernel_width, cur_layer->kernel_height,
                cur_layer->map_common, cur_layer->map_width, cur_layer->map_height
            );
        }
        for (int k = 0; k < layer_size; k++)
        {
            cur_layer->map[i].data[k] = activation_function::tan_h(cur_layer->map_common[k] + cur_layer->map[i].bias);
        }
    }
}
void max_pooling_forward_propagation(Layer* pre_layer, Layer* cur_layer) //checked 2.0
{
    log_exec("max_pooling_forward_propagation");

    int map_width = cur_layer->map_width;
    int map_height = cur_layer->map_height;
    int pre_map_width = pre_layer->map_width;

    for (int k = 0; k < cur_layer->map_count; k++)
    {
        for (int i = 0; i < map_height; i++)
        {
            for (int j = 0; j < map_width; j++)
            {
                double max_value = pre_layer->map[k].data[2 * i * pre_map_width + 2*j];
                for (int n = 2 * i; n < 2 * (i + 1); n++)
                {
                    for (int m = 2 * j; m < 2 * (j + 1); m++)
                    {
                        max_value = MAX(max_value, pre_layer->map[k].data[n * pre_map_width + m]);
                    }
                }
                cur_layer->map[k].data[i * map_width + j] = activation_function::tan_h(max_value);
            }
        }
    }
}

void fully_connection_forward_propagation(Layer* pre_layer, Layer* cur_layer)
{
    log_exec("fully_connection_forward_propagation");

    for (int i = 0; i < cur_layer->map_count; i++)
    {
        double sum = 0.0;
        for (int j = 0; j < pre_layer->map_count; j++)
        {
            sum += pre_layer->map[j].data[0] * cur_layer->kernel[j * cur_layer->map_count + i].weight[0];
        }
        sum += cur_layer->map[i].bias;
        cur_layer->map[i].data[0] = activation_function::tan_h(sum);
    }
}
void forward_propagation()
{
    log_exec("forward_propagation");

    convolution_forward_propagation(&input_layer, &c1_convolution_layer, NULL);
    //show_map(c1_convolution_layer.map[0],28,28);
    max_pooling_forward_propagation(&c1_convolution_layer, &s2_pooling_layer);
    convolution_forward_propagation(&s2_pooling_layer, &c3_convolution_layer, connection_table);
    max_pooling_forward_propagation(&c3_convolution_layer, &s4_pooling_layer);
    convolution_forward_propagation(&s4_pooling_layer, &c5_convolution_layer, NULL);
    fully_connection_forward_propagation(&c5_convolution_layer, &output_layer);
}
/*
    全连接层反向更新
*/
void fully_connection_backward_propagation(Layer* cur_layer, Layer* pre_layer) //checked
{
    log_exec("fully_connection_backward_propagation");

    //更新 c5 error值
    for (int i = 0; i < pre_layer->map_count; i++)
    {
        pre_layer->map[i].error[0] = 0.0;
        for (int j = 0; j < cur_layer->map_count; j++)
        {
            pre_layer->map[i].error[0] += cur_layer->map[j].error[0] * cur_layer->kernel[i * cur_layer->map_count + j].weight[0];
        }
        pre_layer->map[i].error[0] *= activation_function::d_tan_h(pre_layer->map[i].data[0]);
    }
    //更新c5 --> output kernel delta_weight
    for (int i = 0; i < pre_layer->map_count; i++)
    {
        for (int j = 0; j < cur_layer->map_count; j++)
        {
            cur_layer->kernel[i * cur_layer->map_count + j].delta_weight[0] += cur_layer->map[j].error[0] * pre_layer->map[i].data[0];
        }
    }
    //更新output delta_bias
    for (int i = 0; i < cur_layer->map_count; i++)
    {
        cur_layer->map[i].delta_bias += cur_layer->map[i].error[0];
    }
}
void convolution_backward_propagation(Layer* cur_layer, Layer* pre_layer, bool* connection_table) //checked checked 2.0 fixed
{
    log_exec("convolution_backward_propagation");

    int connected_index = 0;
    int pre_layer_mapsize = pre_layer->map_height * pre_layer->map_width;
    //更新S4 error
    for (int i = 0; i < pre_layer->map_count; i++)
    {
        memset(pre_layer->map_common, 0, sizeof(double) * pre_layer_mapsize);
        for (int j = 0; j < cur_layer->map_count; j++)
        {
            connected_index = i * cur_layer->map_count + j;
            if (connection_table != NULL && !connection_table[connected_index])
            {
                continue;
            }
            for (int n = 0; n < cur_layer->map_height; n++) //fixed cur_layer->kernel_height -->>  cur_layer->map_height
            {
                for (int m = 0; m < cur_layer->map_width; m++)
                {
                    int valued_index = n * cur_layer->map_width + m;
                    double error = cur_layer->map[j].error[valued_index];
                    for (int kernel_y = 0; kernel_y < cur_layer->kernel_height; kernel_y++)
                    {
                        for (int kernel_x = 0; kernel_x < cur_layer->kernel_width; kernel_x++)
                        {
                            int index_convoltion_map = (n + kernel_y) * pre_layer->map_width + m + kernel_x;
                            int index_kernel = connected_index;
                            int index_kernel_weight = kernel_y * cur_layer->kernel_width + kernel_x;
                            pre_layer->map_common[index_convoltion_map] += error * cur_layer->kernel[index_kernel].weight[index_kernel_weight];
                        }
                    }
                }
            }
        }
        for (int k = 0; k < pre_layer_mapsize; k++)
        {
            pre_layer->map[i].error[k] = pre_layer->map_common[k] * activation_function::d_tan_h(pre_layer->map[i].data[k]);
            //pre_layer->map[i].error[k] = pre_layer->map_common[k] * activation_func::dtan_h(prev_layer->map[i].data[k]); source
        }
    }
    //更新 S_x ->> C_x kernel 的 delta_weight
    for (int i = 0; i < pre_layer->map_count; i++)
    {
        for (int j = 0; j < cur_layer->map_count; j++)
        {
            connected_index = i * cur_layer->map_count + j;
            if (connection_table != NULL && !connection_table[connected_index])
            {
                continue;
            }
            //fixed cur_layer->map[i] -->> cur_layer->map[j]
            convolution_calcu(
                pre_layer->map[i].data, pre_layer->map_width, pre_layer->map_height,
                cur_layer->map[j].error, cur_layer->map_width, cur_layer->map_height,
                cur_layer->kernel[connected_index].delta_weight, cur_layer->kernel_width, cur_layer->kernel_height
            );
        }
    }
    //更新C_x 的delta_bias
    int cur_layer_mapsize = cur_layer->map_height * cur_layer->map_width;
    for (int i = 0; i < cur_layer->map_count; i++)
    {
        double delta_sum = 0.0;
        for (int j = 0; j < cur_layer_mapsize; j++)
        {
            delta_sum += cur_layer->map[i].error[j];
        }
        cur_layer->map[i].delta_bias += delta_sum;
    }
}
void max_pooling_backward_propagation(Layer* cur_layer, Layer* pre_layer)  //checked
{
    log_exec("max_pooling_backward_propagation");

    int cur_layer_mapwidth = cur_layer->map_width;
    int cur_layer_mapheight = cur_layer->map_height;
    int pre_layer_mapwidth = pre_layer->map_width;
    for (int k = 0; k < cur_layer->map_count; k++)
    {
        for (int i = 0; i < cur_layer_mapheight; i++)
        {
            for (int j = 0; j < cur_layer_mapwidth; j++)
            {
                int index_row = 2 * i;
                int index_col = 2 * j;
                double max_value = pre_layer->map[k].data[index_row * pre_layer_mapwidth + index_col];
                for (int n = 2 * i; n < 2 * (i + 1); n++)
                {
                    for (int m = 2 * j; m < 2 * (j + 1); m++)
                    {
                        if (pre_layer->map[k].data[n * pre_layer_mapwidth + m] > max_value)
                        {
                            index_row = n;
                            index_col = m;
                            max_value = pre_layer->map[k].data[n * pre_layer_mapwidth + m];
                        }
                        else
                        {
                            pre_layer->map[k].error[n * pre_layer_mapwidth + m] = 0.0;
                        }
                    }
                }
                pre_layer->map[k].error[index_row * pre_layer_mapwidth + index_col] = cur_layer->map[k].error[i * cur_layer_mapwidth + j] * activation_function::d_tan_h(max_value);
            }
        }
    }
}
void backward_propagation(double* label)  //checked
{
    log_exec("backward_propagation");

    //前面并未初始化输出层error,在此初始化
    for (int i = 0; i < output_layer.map_count; i++)
    {
        output_layer.map[i].error[0] = loss_function::d_cal_loss(output_layer.map[i].data[0], label[i]) * activation_function::d_tan_h(output_layer.map[i].data[0]);
    }
    fully_connection_backward_propagation(&output_layer, &c5_convolution_layer);
    convolution_backward_propagation(&c5_convolution_layer, &s4_pooling_layer, NULL);
    max_pooling_backward_propagation(&s4_pooling_layer, &c3_convolution_layer);
    convolution_backward_propagation(&c3_convolution_layer, &s2_pooling_layer, connection_table);
    max_pooling_backward_propagation(&s2_pooling_layer, &c1_convolution_layer);
    convolution_backward_propagation(&c1_convolution_layer, &input_layer, NULL);
}
//梯度下降
inline double gradient_descent(double para, double delta_para, double learning_rate) //checked
{
    log_exec("init_kernel");
    //     cout<<"exec fun -->> [gradient_descent]"<<endl;
    return para - learning_rate * delta_para;
}
/*
    梯度下降法
    更新某layer的 kernel 的weight 和 map的 bias
*/
void update_param(Layer* layer, double learning_rate) //checked and fixed
{
    log_exec("update_param");

    int kernel_size = layer->kernel_height * layer->kernel_width;
    //update weight
    for (int i = 0; i < layer->kernel_count; i++)
    {
        for (int k = 0; k < kernel_size; k++)
        {
            layer->kernel[i].weight[k] = gradient_descent(layer->kernel[i].weight[k], layer->kernel[i].delta_weight[k] / batch_size, learning_rate);
        }
    }
    for (int i = 0; i < layer->map_count; i++)  //fixed add :/ batch_size
    {
        layer->map[i].bias = gradient_descent(layer->map[i].bias, layer->map[i].delta_bias / batch_size, learning_rate);
    }
}

void update_all_layer_param(double learning_rate)
{
    log_exec("update_all_layer_param");

    update_param(&c1_convolution_layer, learning_rate);
    update_param(&s2_pooling_layer, learning_rate);
    update_param(&c3_convolution_layer, learning_rate);
    update_param(&s4_pooling_layer, learning_rate);
    update_param(&c5_convolution_layer, learning_rate);
    update_param(&output_layer, learning_rate);
}
void training(Sample* samples, double learning_rate)
{
    log_exec("training");

    int batch_count = samples->sample_count / batch_size;
    int single_sample_size = samples->sample_height * samples->sample_width * sizeof(double);
    cout << "training started!!!" << endl;
    cout << "sample count : " << samples->sample_count << "\t batch size :" << batch_size << " \t batch count : " << batch_count << endl;
    for (int i = 0; i < batch_count; i++)
    {

        reset_all_layer(); //重新初始化各层map的delta bias 和 kernel 的 delta weight
        for (int j = 0; j < batch_size; j++)
        {
            int sample_index = i * batch_size + j;
            memcpy(input_layer.map[0].data, samples[sample_index].data, single_sample_size);

            //show_map(input_layer.map[0],input_layer.map_height,input_layer.map_width); //test

            forward_propagation();
            backward_propagation(samples[sample_index].label);
        }
        update_all_layer_param(learning_rate);
        if (i % 1500 == 0)
        {
            // show_pic(samples[i]); //test

            cout << "training data process : " << i / (1.0 * batch_count) * 100.0 << " %" << endl;
        }
    }
    cout << "training data process : 100 %" << endl;
    cout << "training data over!!!!" << endl;
}

void testing(Sample* samples)
{
    log_exec("testing");

    int success_count = 0;
    int result_test = 0;
    int result_label = 0;
    int single_sample_size = samples->sample_height * samples->sample_width * sizeof(double);
    int* result_test_label_matrix = (int*)malloc(class_count * class_count * sizeof(int));
    memset(result_test_label_matrix, 0, sizeof(int) * class_count * class_count);
    for (int i = 0; i < samples->sample_count; i++)
    {
        memcpy(input_layer.map[0].data, samples[i].data, single_sample_size);
        forward_propagation();

        int index_result = 0;
        double max_value = *(output_layer.map[0].data);
        for (int k = 0; k < output_layer.map_count; k++)
        {
            if (*(output_layer.map[k].data) > max_value)
            {
                max_value = *(output_layer.map[k].data);
                index_result = k;
            }
        }
        result_test = index_result;

        int index_label = 0;
        max_value = samples->label[0];
        for (int k = 1; k < class_count; k++)
        {
            if (samples[i].label[k] > max_value)
            {
                max_value = samples[i].label[k];
                index_label = k;
            }
        }
        result_label = index_label;

        if (result_test == result_label)
        {
            success_count++;
        }
        //cout << "label value : " << result_label << "\t test value : " << result_test << endl;
        result_test_label_matrix[result_test * class_count + result_label] ++;
        if (i % 2500 == 0)
        {
            cout << "testing data process : " << i / (1.0 * samples->sample_count) * 100.0 << " %" << endl;
        }
    }

    //输出结果矩阵
    cout << "testing over !!! success rate : " << (1.0) * success_count / samples->sample_count << endl;
    cout << "\t ";
    for (int i = 0; i < class_count; i++)
    {
        cout << "\t" << i;
    }
    cout << endl;
    for (int i = 0; i < class_count; i++)
    {
        cout << "\t" << i;
        for (int j = 0; j < class_count; j++)
        {
            cout << "\t" << result_test_label_matrix[i * class_count + j];
        }
        cout << endl;
    }


    int sum = 0;
    for (int i = 0; i < class_count * class_count; i++)
    {
        sum += result_test_label_matrix[i];
    }
    cout << "total sum : " << sum << endl;
    free(result_test_label_matrix);
    result_test_label_matrix = NULL;
}

void release_layer(Layer* layer)
{
    log_exec("release_layer");

    for (int i = 0; i < layer->kernel_count; i++)
    {
        free(layer->kernel[i].weight);
        free(layer->kernel[i].delta_weight);
        layer->kernel[i].weight = NULL;
        layer->kernel[i].delta_weight = NULL;
    }
    free(layer->kernel);
    layer->kernel = NULL;

    for (int i = 0; i < layer->map_count; i++)
    {
        free(layer->map[i].data);
        free(layer->map[i].error);
        layer->map[i].data = NULL;
        layer->map[i].error = NULL;
    }
    free(layer->map_common);
    layer->map_common = NULL;
    free(layer->map);
    layer->map = NULL;
}

void free_memory(Sample *test_sample, Sample *train_sample)
{
    log_exec("free_memory");

    for (int i = 0; i < train_sample_count; i++) {
        free(train_sample[i].data);
        free(train_sample[i].label);
        train_sample[i].data = NULL;
        train_sample[i].label = NULL;
    }
    free(train_sample);

    for (int i = 0; i < test_sample_count; i++) {
        free(test_sample[i].data);
        free(test_sample[i].label);
        test_sample[i].data = NULL;
        test_sample[i].label = NULL;
    }
    free(test_sample);

}
int main()
{
    log_exec("MAIN");
    double learning_rate = 0.018;
    //初始化训练集样本
    Sample* train_sample = (Sample*)malloc(train_sample_count * sizeof(Sample));
    memset(train_sample, 0, train_sample_count * sizeof(Sample));
    train_sample->sample_height = height;
    train_sample->sample_width = width;
    train_sample->sample_count = train_sample_count;
    const char* train_sample_path = "./train-images.idx3-ubyte";
    const char* train_label_path = "./train-labels.idx1-ubyte";
    load_mnist_data(train_sample, train_sample_path, 60000);
    load_mnist_label(train_sample, train_label_path, 60000);
    //初始化测试集样本
    Sample* test_sample = (Sample*)malloc(test_sample_count * sizeof(Sample));
    memset(test_sample, 0, test_sample_count * sizeof(Sample));
    test_sample->sample_height = height;
    test_sample->sample_width = width;
    test_sample->sample_count = test_sample_count;
    const char* test_sample_path = "./t10k-images.idx3-ubyte";
    const char* test_label_path = "./t10k-labels.idx1-ubyte";
    load_mnist_data(test_sample, test_sample_path, 10000);
    load_mnist_label(test_sample, test_label_path, 10000);
    //初始化各层
    /*
    init_layer(*layer, prelayer_map_count, map_count, kernel_width, kernel_height, map_width, map_height, is_pooling)
    */
    init_layer(&input_layer, 0, 1, 0, 0, 32, 32, false);
    init_layer(&c1_convolution_layer, 1, 6, 5, 5, 28, 28, false);
    init_layer(&s2_pooling_layer, 1, 6, 1, 1, 14, 14, true);
    init_layer(&c3_convolution_layer, 6, 16, 5, 5, 10, 10, false);
    init_layer(&s4_pooling_layer, 1, 16, 1, 1, 5, 5, true);
    init_layer(&c5_convolution_layer, 16, 120, 5, 5, 1, 1, false);
    init_layer(&output_layer, 120, 10, 1, 1, 1, 1, false);
    //开始训练和测试
    training(train_sample, learning_rate);
    testing(test_sample);
    cout << "run over 1" << endl;
    //清理内存
    free_memory(test_sample,train_sample);
    release_layer(&input_layer);
    release_layer(&c1_convolution_layer);
    release_layer(&s2_pooling_layer);
    release_layer(&c3_convolution_layer);
    release_layer(&s4_pooling_layer);
    release_layer(&c5_convolution_layer);
    release_layer(&output_layer);
    return 0;
}

  • 6
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: LeNet-5是一种经典的卷积神经网络模型,常用于手写数字识别任务。HLS(High-Level Synthesis)是针对FPGA设计的一种高级综合工具,它可以将高级语言如C/C++代码转化为硬件描述语言。 要实现LeNet-5模型的HLS版本,我们可以按照以下步骤进行: 1. 首先,我们需要将LeNet-5的网络结构进行转换,以适应HLS工具的要求。LeNet-5由两个卷积层、两个池化层和三个全连接层组成。我们需要将这些层的相应操作转化为HLS中的硬件描述。例如,卷积操作可以使用HLS中的乘法器和加法器实现,池化操作可以使用选择器进行。 2. 接下来,我们可以使用HLS工具中的数据流和并行指令来优化LeNet-5的实现。通过合理地划分和并行化计算以及使用流水线技术,可以提高模型的效率和速度。 3. 在转换为HLS代码后,我们可以使用HLS工具提供的仿真功能进行测试和验证。通过在测试数据上运行HLS代码,我们可以确保模型在硬件上的实现与预期输出一致。 4. 最后,我们可以将HLS代码综合为目标FPGA设备上的位文件。通过将生成的位文件加载到FPGA上,我们就可以在硬件平台上运行LeNet-5模型进行手写数字识别任务了。 总之,通过使用HLS工具,我们可以将LeNet-5模型从C语言代码转化为硬件实现,并在FPGA上运行,以提高其性能和效率。这种转换过程需要注意HLS工具的特性和限制,并进行适当的优化和测试,以确保模型的正确性和可靠性。 ### 回答2: Lenet-5是一种经典的卷积神经网络CNN)结构,由Yann LeCun在1998年提出。要在HLS(高层次综合)中实现Lenet-5,主要需要将Lenet-5网络结构转换为可在HLS工具中使用的硬件描述语言(HDL)。 Lenet-5网络结构主要包含七个层级:两个卷积层,两个池化层和三个全连接层。在HLS实现Lenet-5时,每个层级都需要进行适当的转换。 首先是卷积层。在HLS中,可以使用具有适当参数的卷积内核来表示卷积层。通过在HDL描述文件中定义卷积核的大小、步长和填充,可以实现卷积层。 第二是池化层。池化层的操作可以通过在HDL描述文件中定义池化区域的大小和类型来实现。常用的是最大池化和平均池化。 第三是全连接层。全连接层可以通过使用适当的权重矩阵和偏置向量来实现。在HLS中,可以使用乘法和加法操作来实现全连接层。 最后,在HLS工具中,需要将输入数据流和输出数据流与适当的处理单元(如DSP、BRAM等)进行连接,以实现完整的Lenet-5网络结构。 实现完成后,可以对HLS代码进行综合、优化和验证,以生成可在FPGA上运行的硬件实现。在FPGA上运行Lenet-5可以进行图像分类、目标检测等任务。 总而言之,要在HLS中实现Lenet-5,需要将Lenet-5网络结构转化为HDL代码,并对其进行综合、优化和验证,最终生成可在FPGA上运行的硬件实现。这样就能够利用硬件加速的方式快速处理Lenet-5网络的图像识别任务。 ### 回答3: Lenet-5是一个经典的卷积神经网络模型,常用于手写数字识别任务。HLS(High-Level Synthesis)是一种用于FPGA(Field-Programmable Gate Array)的高级综合工具,可以将C/C++代码转化为硬件描述语言(HDL)。 实现Lenet-5模型的HLS过程主要包括以下几个步骤: 1. 定义Lenet-5的网络结构:Lenet-5由两个卷积层、两个池化层和三个全连接层组成。在C代码中,我们需要定义网络的各层结构、参数和激活函数。 2. 数据预处理:在Lenet-5中,输入图像为32x32的灰度图像。我们可以使用C代码读取输入图像,并对其进行预处理,如图像缩放和归一化等操作。 3. 卷积计算:在C代码中,我们可以使用循环嵌套来实现卷积计算。对于每个卷积层,我们需要定义卷积核的大小和数量,并对输入图像进行卷积操作。卷积操作可以通过计算每个卷积核与输入图像的点积得到输出特征图。 4. 池化计算:在C代码中,我们可以使用循环嵌套来实现池化计算。对于每个池化层,我们需要定义池化窗口的大小和步长,并对卷积层的输出特征图进行池化操作。常用的池化操作有最大池化和平均池化,可以根据需求选择适合的池化方法。 5. 全连接计算:在C代码中,我们可以使用矩阵乘法和激活函数来实现全连接计算。对于每个全连接层,我们需要定义权重矩阵和偏置向量,并对前一层的输出进行矩阵乘法运算,然后通过激活函数进行非线性映射。 6. 输出结果:在C代码中,我们可以将最后一个全连接层的输出结果与标签进行比较,计算损失并进行反向传播。 通过以上步骤,我们可以使用C语言编写Lenet-5模型的HLS实现。在完成C代码编写后,可以使用HLS工具将C代码转化为HDL代码,以便在FPGA上进行硬件加速计算。这样可以实现Lenet-5模型的高效部署和运行,提高模型的执行速度和计算效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值