经典 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;
}

LeNet-5神经网络 C源代码,这个写的比较好,可以用gcc编译去跑,结合理论可以对深度学习有更深刻的了解 介绍 根据YANN LECUN的论文《Gradient-based Learning Applied To Document Recognition》设计的LeNet-5神经网络,C语言写成,不依赖任何第三方库。 MNIST手写字符集初代训练识别率97%,多代训练识别率98%。 DEMO main.c文件为MNIST数据集的识别DEMO,直接编译即可运行,训练集60000张,测试集10000张。 项目环境 该项目为VISUAL STUDIO 2015项目,用VISUAL STUDIO 2015 UPDATE1及以上直接打开即可编译。采用ANSI C编写,因此源码无须修改即可在其它平台上编译。 如果因缺少openmp无法编译,请将lenet.c中的#include和#pragma omp parallel for删除掉即可。 API #####批量训练 lenet: LeNet5的权值的指针,LeNet5神经网络的核心 inputs: 要训练的多个图片对应unsigned char二维数组的数组,指向的二维数组的batchSize倍大小内存空间指针。在MNIST测试DEMO中二维数组为28x28,每个二维数组数值分别为对应位置图像像素灰度值 resMat:结果向量矩阵 labels:要训练的多个图片分别对应的标签数组。大小为batchSize batchSize:批量训练输入图像(二维数组)的数量 void TrainBatch(LeNet5 *lenet, image *inputs, const char(*resMat)[OUTPUT],uint8 *labels, int batchSize); #####单个训练 lenet: LeNet5的权值的指针,LeNet5神经网络的核心 input: 要训练的图片对应二维数组 resMat:结果向量矩阵 label: 要训练的图片对应的标签 void Train(LeNet5 *lenet, image input, const char(*resMat)[OUTPUT],uint8 label); #####预测 lenet: LeNet5的权值的指针,LeNet5神经网络的核心 input: 输入的图像的数据 labels: 结果向量矩阵指针 count: 结果向量个数 return 返回值为预测的结果 int Predict(LeNet5 *lenet, image input, const char(*labels)[LAYER6], int count); #####初始化 lenet: LeNet5的权值的指针,LeNet5神经网络的核心
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值