深度学习之卷积神经网络编程实现(二)
2017-01-15 13:12
786 查看
目前网上深度学习有关资料巨多,但多数都是转载,就算原创,但很多也是光说不练,理论公式一堆,但是靠谱代码没几行,能用的更是少之又少。paper看的溜、code写的溜的人还是比较少的,本人应该属于paper看的磕磕绊绊,code写的凑合那种。
前文主要是一些卷积神经网络的原理及公式推导,本文将使用c语言实现经典LeNet-5(http://yann.lecun.com/exdb/lenet/
)网络。代码主要参考了tiny-cnn(https://github.com/nyanp/tiny-cnn ),tiny-cnn这份开源代码质量还是挺高的,但是由于整个项目采用面向对象程序设计思路,外加c++泛型技术,如果想吃透整个项目,除了需要花些时间外,还需要技术功底的。本人在编写LeNet-5网络过程中,有些代码摘录自该项目,相比较而言,本工程更小巧,代码非常容易懂,也非常容易移植,完全不依赖第三方库。
整个LeNet-5网络如下图所示,但注意,实际在开发过程中没有用到F6层。主要包括2个卷积层、2个池化层、1个全连接层,外加输入及输出,共7层网络。实际训练时采用最大值池化、双曲正切激活函数,经过8轮迭代训练,手写数字识别准确率即达到99%。
![](http://img.blog.csdn.net/20170115130701291?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZ3JhZng=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
因为核心的正向传播、反向传播计算在各个层间展开,涉及的数据比较多,所以首要就是设计数据结构,合理的数据结构将非常有利于编写代码,也便于阅读。这里主要定义了四种数据结构,分别用于保存训练样本、卷积核、特征map以及网络层。这里就不再讲算法原理了,原理部分可参考前文,代码中注释以及变量、方法命名比较详细,还是比较容易看懂的。当然,前提是一定要掌握卷积神经网络的算法原理。
数据结构:
初始化过程:
正向传播过程:
反向传播过程:
主函数:
训练过程如下图所示,经过两轮训练,准确度已经达到98.31%。
![](http://img.blog.csdn.net/20170115131021668?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZ3JhZng=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
工程下载地址:
https://github.com/0x7dc/LeNet-5
参考代码:
https://github.com/nyanp/tiny-cnn
https://github.com/nyanp/tiny-cnn/wiki/MNIST-Example
训练数据下载地址:
http://yann.lecun.com/exdb/mnist/index.html
前文主要是一些卷积神经网络的原理及公式推导,本文将使用c语言实现经典LeNet-5(http://yann.lecun.com/exdb/lenet/
)网络。代码主要参考了tiny-cnn(https://github.com/nyanp/tiny-cnn ),tiny-cnn这份开源代码质量还是挺高的,但是由于整个项目采用面向对象程序设计思路,外加c++泛型技术,如果想吃透整个项目,除了需要花些时间外,还需要技术功底的。本人在编写LeNet-5网络过程中,有些代码摘录自该项目,相比较而言,本工程更小巧,代码非常容易懂,也非常容易移植,完全不依赖第三方库。
整个LeNet-5网络如下图所示,但注意,实际在开发过程中没有用到F6层。主要包括2个卷积层、2个池化层、1个全连接层,外加输入及输出,共7层网络。实际训练时采用最大值池化、双曲正切激活函数,经过8轮迭代训练,手写数字识别准确率即达到99%。
因为核心的正向传播、反向传播计算在各个层间展开,涉及的数据比较多,所以首要就是设计数据结构,合理的数据结构将非常有利于编写代码,也便于阅读。这里主要定义了四种数据结构,分别用于保存训练样本、卷积核、特征map以及网络层。这里就不再讲算法原理了,原理部分可参考前文,代码中注释以及变量、方法命名比较详细,还是比较容易看懂的。当然,前提是一定要掌握卷积神经网络的算法原理。
数据结构:
typedef struct _Sample { double *data; double *label; int sample_w; int sample_h; int sample_count; } Sample; typedef struct _Kernel { double *W; double *dW; } Kernel; typedef struct _Map { double *data; double *error; double b; double db; } Map; typedef struct _Layer { int map_w; int map_h; int map_count; Map *map; int kernel_w; int kernel_h; int kernel_count; Kernel *kernel; double *map_common; } Layer;
初始化过程:
void init_kernel(double *kernel, int size, double weight_base) { for (int i = 0; i < size; i++) { kernel[i] = (genrand_real1() - 0.5) * 2 * weight_base; } } void init_layer(Layer *layer, int prevlayer_map_count, int map_count, int kernel_w, int kernel_h, int map_w, int map_h, bool is_pooling) { int mem_size = 0; 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 = prevlayer_map_count * kernel_w * kernel_h; fan_out = map_count * kernel_w * kernel_h; } int denominator = fan_in + fan_out; double weight_base = (denominator != 0) ? sqrt(scale / (double)denominator) : 0.5; layer->kernel_count = prevlayer_map_count * map_count; layer->kernel_w = kernel_w; layer->kernel_h = kernel_h; layer->kernel = (Kernel *)malloc(layer->kernel_count * sizeof(Kernel)); mem_size = layer->kernel_w * layer->kernel_h * sizeof(double); for (int i = 0; i < prevlayer_map_count; i++) { for (int j = 0; j < map_count; j++) { layer->kernel[i*map_count + j].W = (double *)malloc(mem_size); init_kernel(layer->kernel[i*map_count + j].W, layer->kernel_w*layer->kernel_h, weight_base); layer->kernel[i*map_count + j].dW = (double *)malloc(mem_size); memset(layer->kernel[i*map_count + j].dW, 0, mem_size); } } layer->map_count = map_count; layer->map_w = map_w; layer->map_h = map_h; layer->map = (Map *)malloc(layer->map_count * sizeof(Map)); mem_size = layer->map_w * layer->map_h * sizeof(double); for (int i = 0; i < layer->map_count; i++) { layer->map[i].b = 0.0; layer->map[i].db = 0.0; layer->map[i].data = (double *)malloc(mem_size); layer->map[i].error = (double *)malloc(mem_size); memset(layer->map[i].data, 0, mem_size); memset(layer->map[i].error, 0, mem_size); } layer->map_common = (double *)malloc(mem_size); memset(layer->map_common, 0, mem_size); }
正向传播过程:
#define O true #define X false 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 conv_fprop(Layer *prev_layer, Layer *layer, bool *pconnection) { int index = 0; int size = layer->map_w * layer->map_h; for (int i = 0; i < layer->map_count; i++) { memset(layer->map_common, 0, size*sizeof(double)); for (int j = 0; j < prev_layer->map_count; j++) { index = j*layer->map_count + i; if (pconnection != NULL && !pconnection[index]) { continue; } convn_valid( prev_layer->map[j].data, prev_layer->map_w, prev_layer->map_h, layer->kernel[index].W, layer->kernel_w, layer->kernel_h, layer->map_common, layer->map_w, layer->map_h); } for (int k = 0; k < size; k++) { layer->map[i].data[k] = activation_func::tan_h(layer->map_common[k] + layer->map[i].b); } } } void avg_pooling_fprop(Layer *prev_layer, Layer *layer) { int map_w = layer->map_w; int map_h = layer->map_h; int upmap_w = prev_layer->map_w; const double scale_factor = 0.25; for (int k = 0; k < layer->map_count; k++) { for (int i = 0; i < map_h; i++) { for (int j = 0; j < map_w; j++) { double sum = 0.0; for (int n = 2*i; n < 2*(i + 1); n++) { for (int m = 2*j; m < 2*(j + 1); m++) { sum += prev_layer->map[k].data[n*upmap_w + m] * layer->kernel[k].W[0]; } } sum *= scale_factor; sum += layer->map[k].b; layer->map[k].data[i*map_w + j] = activation_func::tan_h(sum); } } } } void max_pooling_fprop(Layer *prev_layer, Layer *layer) { int map_w = layer->map_w; int map_h = layer->map_h; int upmap_w = prev_layer->map_w; for (int k = 0; k < layer->map_count; k++) { for (int i = 0; i < map_h; i++) { for (int j = 0; j < map_w; j++) { double max_value = prev_layer->map[k].data[2*i*upmap_w + 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, prev_layer->map[k].data[n*upmap_w + m]); } } layer->map[k].data[i*map_w + j] = activation_func::tan_h(max_value); } } } } void fully_connected_fprop(Layer *prev_layer, Layer *layer) { for (int i = 0; i < layer->map_count; i++) { double sum = 0.0; for (int j = 0; j < prev_layer->map_count; j++) { sum += prev_layer->map[j].data[0] * layer->kernel[j*layer->map_count + i].W[0]; } sum += layer->map[i].b; layer->map[i].data[0] = activation_func::tan_h(sum); } } void forward_propagation() { // In-->C1 conv_fprop(&input_layer, &c1_conv_layer, NULL); // C1-->S2 max_pooling_fprop(&c1_conv_layer, &s2_pooling_layer);/*avg_pooling_fprop*/ // S2-->C3 conv_fprop(&s2_pooling_layer, &c3_conv_layer, connection_table); // C3-->S4 max_pooling_fprop(&c3_conv_layer, &s4_pooling_layer);/*avg_pooling_fprop*/ // S4-->C5 conv_fprop(&s4_pooling_layer, &c5_conv_layer, NULL); // C5-->Out fully_connected_fprop(&c5_conv_layer, &output_layer); }
反向传播过程:
void conv_bprop(Layer *layer, Layer *prev_layer, bool *pconnection) { int index = 0; int size = prev_layer->map_w * prev_layer->map_h; // delta for (int i = 0; i < prev_layer->map_count; i++) { memset(prev_layer->map_common, 0, size*sizeof(double)); for (int j = 0; j < layer->map_count; j++) { index = i*layer->map_count + j; if (pconnection != NULL && !pconnection[index]) { continue; } for (int n = 0; n < layer->map_h; n++) { for (int m = 0; m < layer->map_w; m++) { double error = layer->map[j].error[n*layer->map_w + m]; for (int ky = 0; ky < layer->kernel_h; ky++) { for (int kx = 0; kx < layer->kernel_w; kx++) { prev_layer->map_common[(n + ky)*prev_layer->map_w + m + kx] += error * layer->kernel[index].W[ky*layer->kernel_w + kx]; } } } } } for (int k = 0; k < size; k++) { prev_layer->map[i].error[k] = prev_layer->map_common[k] * activation_func::dtan_h(prev_layer->map[i].data[k]); } } // dW for (int i = 0; i < prev_layer->map_count; i++) { for (int j = 0; j < layer->map_count; j++) { index = i*layer->map_count + j; if (pconnection != NULL && !pconnection[index]) { continue; } convn_valid( prev_layer->map[i].data, prev_layer->map_w, prev_layer->map_h, layer->map[j].error, layer->map_w, layer->map_h, layer->kernel[index].dW, layer->kernel_w, layer->kernel_h); } } // db size = layer->map_w * layer->map_h; for (int i = 0; i < layer->map_count; i++) { double sum = 0.0; for (int k = 0; k < size; k++) { sum += layer->map[i].error[k]; } layer->map[i].db += sum; } } void avg_pooling_bprop(Layer *layer, Layer *prev_layer) { const double scale_factor = 0.25; int size = prev_layer->map_w * prev_layer->map_h; for (int i = 0; i < layer->map_count; i++) { kronecker(layer->map[i].error, layer->map_w, layer->map_h, prev_layer->map_common, prev_layer->map_w); // delta for (int k = 0; k < size; k++) { double delta = layer->kernel[i].W[0] * prev_layer->map_common[k]; prev_layer->map[i].error[k] = delta * scale_factor * activation_func::dtan_h(prev_layer->map[i].data[k]); } // dW double sum = 0.0; for (int k = 0; k < size; k++) { sum += prev_layer->map[i].data[k] * prev_layer->map_common[k]; } layer->kernel[i].dW[0] += sum * scale_factor; // db sum = 0.0; for (int k = 0; k < layer->map_w * layer->map_h; k++) { sum += layer->map[i].error[k]; } layer->map[i].db += sum; } } void max_pooling_bprop(Layer *layer, Layer *prev_layer) { int map_w = layer->map_w; int map_h = layer->map_h; int upmap_w = prev_layer->map_w; for (int k = 0; k < layer->map_count; k++) { // delta for (int i = 0; i < map_h; i++) { for (int j = 0; j < map_w; j++) { int row = 2*i, col = 2*j; double max_value = prev_layer->map[k].data[row*upmap_w + col]; for (int n = 2*i; n < 2*(i + 1); n++) { for (int m = 2*j; m < 2*(j + 1); m++) { if (prev_layer->map[k].data[n*upmap_w + m] > max_value) { row = n; col = m; max_value = prev_layer->map[k].data[n*upmap_w + m]; } else { prev_layer->map[k].error[n*upmap_w + m] = 0.0; } } } prev_layer->map[k].error[row*upmap_w + col] = layer->map[k].error[i*map_w + j] * activation_func::dtan_h(max_value); } } // dW // db } } void fully_connected_bprop(Layer *layer, Layer *prev_layer) { // delta for (int i = 0; i < prev_layer->map_count; i++) { prev_layer->map[i].error[0] = 0.0; for (int j = 0; j < layer->map_count; j++) { prev_layer->map[i].error[0] += layer->map[j].error[0] * layer->kernel[i*layer->map_count + j].W[0]; } prev_layer->map[i].error[0] *= activation_func::dtan_h(prev_layer->map[i].data[0]); } // dW for (int i = 0; i < prev_layer->map_count; i++) { for (int j = 0; j < layer->map_count; j++) { layer->kernel[i*layer->map_count + j].dW[0] += layer->map[j].error[0] * prev_layer->map[i].data[0]; } } // db for (int i = 0; i < layer->map_count; i++) { layer->map[i].db += layer->map[i].error[0]; } } void backward_propagation(double *label) { for (int i = 0; i < output_layer.map_count; i++) { output_layer.map[i].error[0] = loss_func::dmse(output_layer.map[i].data[0], label[i]) * activation_func::dtan_h(output_layer.map[i].data[0]); } // Out-->C5 fully_connected_bprop(&output_layer, &c5_conv_layer); // C5-->S4 conv_bprop(&c5_conv_layer, &s4_pooling_layer, NULL); // S4-->C3 max_pooling_bprop(&s4_pooling_layer, &c3_conv_layer);/*avg_pooling_bprop*/ // C3-->S2 conv_bprop(&c3_conv_layer, &s2_pooling_layer, connection_table); // S2-->C1 max_pooling_bprop(&s2_pooling_layer, &c1_conv_layer);/*avg_pooling_bprop*/ // C1-->In conv_bprop(&c1_conv_layer, &input_layer, NULL); }
主函数:
int main() { int kernel_w = 0, kernel_h = 0; double learning_rate = 0.01 * sqrt((double)batch_size); // 训练数据 Sample *train_sample = (Sample *)malloc(train_sample_count*sizeof(Sample)); memset(train_sample, 0, train_sample_count*sizeof(Sample)); train_sample->sample_w = width; train_sample->sample_h = height; train_sample->sample_count = train_sample_count; read_mnist_data(train_sample, "../TestLeNet/mnist/train-images.idx3-ubyte"); read_mnist_label(train_sample, "../TestLeNet/mnist/train-labels.idx1-ubyte"); // 测试数据 Sample *test_sample = (Sample *)malloc(test_sample_count*sizeof(Sample)); memset(test_sample, 0, test_sample_count*sizeof(Sample)); test_sample->sample_w = width; test_sample->sample_h = height; test_sample->sample_count = test_sample_count; read_mnist_data(test_sample, "../TestLeNet/mnist/t10k-images.idx3-ubyte"); read_mnist_label(test_sample, "../TestLeNet/mnist/t10k-labels.idx1-ubyte"); // 随机数 init_genrand((unsigned long)time(NULL)); // 输入层In kernel_w = 0; kernel_h = 0; init_layer(&input_layer, 0, 1, kernel_w, kernel_h, width, height, false); // 卷积层C1 kernel_w = 5; kernel_h = 5; init_layer(&c1_conv_layer, 1, 6, kernel_w, kernel_h, input_layer.map_w - kernel_w + 1, input_layer.map_h - kernel_h + 1, false); // 采样层S2 kernel_w = 1; kernel_h = 1; init_layer(&s2_pooling_layer, 1, 6, kernel_w, kernel_h, c1_conv_layer.map_w / 2, c1_conv_layer.map_h / 2, true); // 卷积层C3 kernel_w = 5; kernel_h = 5; init_layer(&c3_conv_layer, 6, 16, kernel_w, kernel_h, s2_pooling_layer.map_w - kernel_w + 1, s2_pooling_layer.map_h - kernel_h + 1, false); // 采样层S4 kernel_w = 1; kernel_h = 1; init_layer(&s4_pooling_layer, 1, 16, kernel_w, kernel_h, c3_conv_layer.map_w / 2, c3_conv_layer.map_h / 2, true); // 卷积层C5 kernel_w = 5; kernel_h = 5; init_layer(&c5_conv_layer, 16, 120, kernel_w, kernel_h, s4_pooling_layer.map_w - kernel_w + 1, s4_pooling_layer.map_h - kernel_h + 1, false); // 输出层Out kernel_w = 1; kernel_h = 1; init_layer(&output_layer, 120, 10, kernel_w, kernel_h, 1, 1, false); // 训练及测试 clock_t start_time = 0; const int epoch = 50; for (int i = 0; i < epoch; i++) { printf("train epoch is %d ************************************************\n", i + 1); start_time = clock(); train(train_sample, learning_rate); printf("train time is....%f s\n", (double)(clock() - start_time) / CLOCKS_PER_SEC); start_time = clock(); predict(test_sample); printf("predict time is....%f s\n\n", (double)(clock() - start_time) / CLOCKS_PER_SEC); learning_rate *= 0.85; } // 释放资源 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); release_layer(&input_layer); release_layer(&c1_conv_layer); release_layer(&c3_conv_layer); release_layer(&c5_conv_layer); release_layer(&s2_pooling_layer); release_layer(&s4_pooling_layer); release_layer(&output_layer); system("pause"); return 0; }
训练过程如下图所示,经过两轮训练,准确度已经达到98.31%。
工程下载地址:
https://github.com/0x7dc/LeNet-5
参考代码:
https://github.com/nyanp/tiny-cnn
https://github.com/nyanp/tiny-cnn/wiki/MNIST-Example
训练数据下载地址:
http://yann.lecun.com/exdb/mnist/index.html
相关文章推荐
- 深度学习与神经网络-吴恩达(Part1Week3)-单隐层神经网络编程实现(python)
- 深度学习与神经网络-吴恩达(Part1Week4)-深度神经网络编程实现(python)-基础篇
- 深度学习基础模型算法原理及编程实现--04.改进神经网络的方法
- 深度学习基础模型算法原理及编程实现--06.循环神经网络
- 深度学习-传统神经网络使用TensorFlow框架实现MNIST手写数字识别
- TensorFlow:实战Google深度学习框架(二)实现简单神经网络
- 深度学习之四:卷积神经网络基础
- 吴恩达老师深度学习视频课笔记:单隐含层神经网络公式推导及C++实现(二分类)
- 吴恩达Coursera深度学习(4-1)卷积神经网络基础
- 深度卷积神经网络学习
- [置顶] 【深度学习】RNN循环神经网络Python简单实现
- 【中文】【吴恩达课后编程作业】Course 1 - 神经网络和深度学习 - 第二周作业
- 【中文】【吴恩达课后编程作业】Course 1 - 神经网络和深度学习 - 第三周作业
- 深度学习基础模型算法原理及编程实现--09.自编码网络
- 【中文】【吴恩达课后编程作业】Course 1 - 神经网络和深度学习 - 第四周作业(1&2)
- MXNet动手学深度学习笔记:VGG神经网络实现
- MXNet动手学深度学习笔记:GoogLeNet神经网络实现
- MXNet动手学深度学习笔记:循环神经网络RNN实现
- 神经网络与深度学习编程练习(coursera 吴恩达 )(2)
- TensorFlow 深度学习笔记 TensorFlow实现与优化深度神经网络