无框架 使用c++从零开始实现 卷积 全连接 softmax 入门深度学习 CPU版 梯度下降

经过一段时间的努力,用C++把卷积写完了。因为深度学习太火了,所以 忍不住就试试。先是找资料看看需要什么,然后进行学习,恶补线性代数、微积分、概率论,感觉能用到的知识点学差不多就开始代码【真要等到全学会再动手写代码,以我的智商估计就没有然后了】。现在成熟框架很多,为了更好的理解原理选择自己造轮子,能力有限,所以这个过程很漫长也很痛苦!

太多的技术细节网上都有资料,我也不用多说,因为没有大神讲的透彻,理论是一回事,实际做又是另一回事。理论上的事儿我只说两点,第一个是多层卷积卷积核的数量,第二个是梯度下降和调参的关系,因为我在学习和编码的过程中出现了错误的想法,所以说一下。剩下的看代码。

一. 卷积核

先说一下多层卷积卷积核,程序用的都是数组,所以卷积的数据存储都是4维,分别用block、depth、row、column,可以理解为盒子里面插卡片,block 表示盒子个数,depth表示盒子里卡片张数、row 和column 表示卡片大小,看下面的示意图,这只是个简单的例子,注意红框里面的卷积核,是3个block,每个block里面有2个depth,应该是2*3个卷积核,我在最开始理解的时候理解为每个block里面有1个depth,所以正确率一直上不去。
如果说有2个input 希望输出10个map,那卷积核应该有多少呢?
2x10 = 20
无框架 使用c++从零开始实现 卷积 全连接 softmax 入门深度学习 CPU版 梯度下降_第1张图片

二. 梯度下降

再说一下梯度下降,可能数学好的同志们这都不叫事儿,但是不好的就另当别论了。
假如我们的目标函数:(X-1)^2+1,为了是目标函数的导数最小,更新X的值 ,开始我的想法是X越小越接近答案,现在想起来都觉得郁闷,居然有这种想法。实际情况是随着X变化找到最小的斜率,其实就是X=1,当学习速率是0.6的时候用计算机计算效果如下:

无框架 使用c++从零开始实现 卷积 全连接 softmax 入门深度学习 CPU版 梯度下降_第2张图片
无框架 使用c++从零开始实现 卷积 全连接 softmax 入门深度学习 CPU版 梯度下降_第3张图片
当我们设定学习速率是0.4的时候,应该是下面的效果:
无框架 使用c++从零开始实现 卷积 全连接 softmax 入门深度学习 CPU版 梯度下降_第4张图片
无框架 使用c++从零开始实现 卷积 全连接 softmax 入门深度学习 CPU版 梯度下降_第5张图片
目标就是X=1,程序很简单,就几行代码,有兴趣的可以自己试试,体会一下调参的感觉,网络再复杂也就这个意思吧!

long double  x = 20;
	long double a = 0.4;
	for (int i = 0; i < 10000;i++){
		cout << "X" << i + 1 << "=" << "X" << i << "-" << "a* df(" << "X" << i <<")"<< endl;
		cout <<"  ="<< x << "-" << a << "*" << "(2*" << x << "-2" << ")" << endl;
		cout << "  =" << x - a*(2 * x - 2) << endl;
		x = x - a*(2 * x - 2);
	}

三. 测试效果

先是拿MNIST数据集测试程序效果,都说这个数据集被玩坏了,我感觉这个挺好。
我用了三层卷积,每个卷积后面加POOL,POOL用的是平均值,没用最大值,有兴趣可以自己改一下就可以,三层全连接还有结尾的softmax,正确率99.33%,然后我又自己随意写了一些进行测试。代码可以调整,卷积和全连接可以增加或者减少,也可以只用全连接,测试各种搭配的效果。
无框架 使用c++从零开始实现 卷积 全连接 softmax 入门深度学习 CPU版 梯度下降_第6张图片

这个样子的测试正确率最高94%,这是在PS上面用柔性笔写的,还有一些模仿MNIST数据集,在模仿的像一点儿准确率会更高。

下面这个样子的数据测试正确率70%,这个跟MNIST的样式差距已经很大了,能到70%的正确率还算不错,MNIST这个是黑底白字,我写的是白底黑子,所以要对数据进行处理,代码里面只要修改一下ARRAY.cpp文件中的一个值就能完成转换。
无框架 使用c++从零开始实现 卷积 全连接 softmax 入门深度学习 CPU版 梯度下降_第7张图片
这是训练时更新W B 参数效果,速度还可以,正确率到80%左右的时候会变慢,90%之后会更慢,这也是符合规律的,数据太长了,我只复制了一部分。这里不得不说一下,最开始的时候程序到处都是错误,运行好久正确率没任何变化,后来随着优化和修改,正确率提升的越来越快,那种感觉就一个字“爽”。
无框架 使用c++从零开始实现 卷积 全连接 softmax 入门深度学习 CPU版 梯度下降_第8张图片
下图是softmax层loss函数曲线,整体还算可以,但是那几凸起看着还是挺扎心的,我在全连接的输入层和隐藏层都加入了Dropout处理,Dropout丢弃概率是50%,所以曲线一直跳动,曲线很难看但是识别效果确实不错。
下面是画曲线的代码,用的python,就这几行很简单

#!/usr/bin/env python
# -*- coding: GBK -*-
from numpy import *
import numpy as np
from matplotlib import pyplot as plt
yw=loadtxt('Arr_delta_layer_output.txt',dtype='float')
xw = np.arange(1,len(yw)+1)
plt.title("")
plt.xlabel("count")
plt.ylabel("value")
plt.plot(xw,yw)
plt.show()

无框架 使用c++从零开始实现 卷积 全连接 softmax 入门深度学习 CPU版 梯度下降_第9张图片
汉字识别
代码也可以进行汉字的识别,但是不能识别太多类别,因为程序是基于CPU运行的,没有做加速GPU处理,所以对好几千分类来说太困难了,速度是不能接受的,如果也做个10分类,20分类都还能接受,准确率不低于数字识别,随着分类得增加,训练速度越来越慢,到50分类的运行速度就已经不行了,这个实验过程也不错,你会体会到特征的数量直接影响分类的数量和准确率。10分类的情况下,三层卷积结束,数据传入全连接输入层有几千个特征,准确率肯定能很高,如果到全连接有几千的特征值,非要做3755个汉字分类那肯定是不行的。有空再造个轮子,写个GPU的版本,主要是学习,如果使用GPU速度会有极大的提升,那时候就有底气做几千分类的训练了。
下图是我做50分类的训练曲线截图,是不是很崩溃
无框架 使用c++从零开始实现 卷积 全连接 softmax 入门深度学习 CPU版 梯度下降_第10张图片
无框架 使用c++从零开始实现 卷积 全连接 softmax 入门深度学习 CPU版 梯度下降_第11张图片

四. 代码
如果已经看了很多资料,依然有很多不明白的地方,看下面的代码应该会有很大的帮助,技术文档对照代码,我感觉对程序员来说是再好不过的快速学习方法了。当然实践最重要,但是要踩很多坑。

开发环境:win10 、vs2010、 opencv2.4.8
Opencv配置可以看这个,写的很棒文章链接: http://blog.csdn.net/poem_qianmo/article/details/19809337
使用opencv为的是在开始做数据归一化的时候好处理,因为数据集的加载、图片大小的改变都很方便。
先看一下代码总体结构
无框架 使用c++从零开始实现 卷积 全连接 softmax 入门深度学习 CPU版 梯度下降_第12张图片

废话少说上代码
1、 ARRAY.h
将数组的操作进行封装方便调用,训练的时候可以在控制台输出,也可以输出到txt文件,看效果也方便,cpp文件太长了,后面会随工程打包,方便下载。

#ifndef ARRAY_H
#define ARRAY_H
#include 
#include 
#include 
#include 
#include 
#include
#include
using namespace cv;
/**
  @njm
  说明:该对象包括了一维、二维、三维、四维 矩阵的初始化、销毁、输出等功能,初始化的时候传入相应维度的数组指针,然后
        就能使用该对象将传入的数组指针带到其他想用使用或者操作的地方
        
      1、初始化:可以根据选择构造函数的不同定义不同维度的数组,【“A”初始值0,"B"初始值位1,否则为随机数】
	  2、销毁:可以使用声明的arry对象直接调用相应的方法对初始化的矩阵进行销毁,释放内存
	  3、输出:可以使用声明的arry对象直接调用相应方法输出当前的数组,查看数组数据

 */
class Array{

public:
	Array();
	/*一维构造函数
	 */
	Array( int COL_NUM, long double *Arr1D,char init);
	/*二维构造函数
	*/
	Array( int ROW_NUM, int COL_NUM, long double **Arr2D, char init);
	/*三维构造函数
	*/
	Array(int Depth, int ROW_NUM, int COL_NUM, long double ***Arr3D, char init);
	//Array(int Depth, int ROW_NUM, int COL_NUM,  char init);
	Array(Mat *mat,int Depth, int ROW_NUM, int COL_NUMC, long double ***Arr3D, char init);
	/*四维构造函数
	*/
	Array(int block,int Depth, int ROW_NUM, int COL_NUM, long double ****Arr4D, char init);
	Array(Mat *mat, int block, int Depth, int ROW_NUM, int COL_NUMC, long double ****Arr4D, char init);
	~Array();
	//Mat转数组的函数
	void MatToArray(Mat *mat,int Kernel_C, long double ***Arr3D);
	void MatToArray4D(Mat *mat, int block, int Depth, long double ****Arr4D);

	//值为【-1,1】的一维数组
	void getArray1D(int COL_NUM, long double *Arr1D);
	//值为【0】的一维数组
	void getArray1D0(int COL_NUM, long double *Arr1D);
	void getArray1D1(int COL_NUM, long double *Arr1D);

	//值为【-1,1】的二维数组
	void getArray2D(int ROW_NUM, int COL_NUM, long double **Arr2D);
	//值为【0】的二维数组
	void getArray2D0(int ROW_NUM, int COL_NUM, long double **Arr2D);
	//值为【1】的二维数组
	void getArray2D1(int ROW_NUM, int COL_NUM, long double **Arr2D);
	//值为【-1,1】的三维数组
	void getArray3D(int Kernel_C,int ROW_NUM,int COL_NUM,long double ***Arr3D);
	//3D数组 值都为0
	void getArray3D0(int Kernel_C,int ROW_NUM,int COL_NUM,long double ***Arr3D);
		//3D数组 值都为1
	void getArray3D1(int Kernel_C,int ROW_NUM,int COL_NUM,long double ***Arr3D);
	//平均值池化 数值为0.25
	void getArray3D25(int Kernel_C,int ROW_NUM,int COL_NUM,long double ***Arr3D);
	void Delete1D(long double *arr1D, Array* OBJ);
	void Delete2D(int x, long double **arr2D, Array* OBJ);
	void Delete3D(int x, int y, long double ***arr3D,Array* OBJ);
	void Delete3D_1(long double ***arr3D);
	void Delete4D(int b, int x, int y, long double ****arr4D, Array* OBJ);

	//输出 返回值为void是直接输出到控制台,返回值为string类型的是将文件写到本地的txt文件
	/*一维输出函数*/ 
	void OutArr1D();
	string OutArr1DTXT(int index);
	string OutArr1DTXT();
	/*二维输出函数*/
	void OutArr2D();
	string OutArr2DTXT();
	/*二维输出函数*/
	void OutArr3D();
	string OutArr3DTXT();
	/*四维输出函数*/
	void OutArr4D();
	void Array::OutArr4D(long double****Mat4D);
	string OutArr4DTXT();
public:
	//属性
	// 记录下当前对象声明的时候构造函数设定的当前矩阵的维度,调用arry对象的时候可以直接进行传递
	int block = 0;
	int depth = 0;
	int row = 0;
	int column = 0;
	int flag = 0;
	//声明ARRAY对象时 用来存储需要的维度矩阵,这样可以再调用arry对象的时候直接操作该矩阵。
	//这个地方的注释我没有删除掉,是因为我开始的时候声明数组居然声明了数组的大小,导致训练过程中内存占用越来越大,导致程序崩溃,以此为戒。
	long double *Mat1D;// = new long double[1];
	long double **Mat2D;// = new long double*[1];
	long double ***Mat3D;// = new long double**[1];
	long double ****Mat4D;// = new long double***[1];

};
#endif ARRAY_H

2、 Conv.h
文件中有同名函数有带下划线和不带下划线的,带下划线的是正确的,不带下划线的是错误的,为自己当时产生如此愚蠢的想法做个纪念,没有删除,有兴趣的可以看看,如果是你来写,会不会有那样错误的想法。

#ifndef CONV_H
#define CONV_H

class Array;
class Conv
{
public:
	Conv(); 
	Conv(int _depth, int _row, int _column);
	~Conv();

	//深度
	int depth;
	//行
	int row;
	//列
	int column;

	double jz[10];
	double *dt;// = new double[1];
	long double *Matrix1D;
	long double **Matrix2D;
	long double ***Matrix3D;

	/*
	函数说明:卷积向前传播函数
	参数说明:
	ARR_inMat:卷积输入矩阵
	ARR_kernel:卷积核矩阵
	ARR_outMat:卷积运算后的Feature Map
	ARR_bias:偏置值
	stride:卷积步幅
	ZeroPadd:矩阵外层补零层数。
	0代表不做处理,1代表外围补一圈0,2代表补两圈
	【一般是再最开始对原始图像处理的时候添加,以便更好的获取边缘特征,提高识别率】
	*/
	int forword_(Array *ARR_inMat, Array *ARR_kernel, Array *ARR_outMat, Array *ARR_bias, int stride, int ZeroPadd, char Activation);

	/*
	  函数说明:卷积向前传播函数
	  参数说明:
	      ARR_inMat:卷积输入矩阵
		  ARR_filter:卷积核矩阵
		  ARR_outMat:卷积运算后的Feature Map
		  ARR_bias:偏置值
		  stride:卷积步幅
		  ZeroPadd:矩阵外层补零层数。
		            0代表不做处理,1代表外围补一圈0,2代表补两圈
					【一般是再最开始对原始图像处理的时候添加,以便更好的获取边缘特征,提高识别率】
	*/
	int forword(Array *ARR_inMat, Array *ARR_filter, Array *ARR_outMat, Array *ARR_bias, int stride, int ZeroPadd);

	/* 函数说明:卷积反向梯度计算,更新ARR_filter和 ARR_bias,
	   参数说明:
	       向前计算输出: *ARR_outMat_forword_Out
		   样本标签:    *lable
		   过滤层:  *ARR_filter
		   修正值:  *ARR_bias
		   误差值δ:  *ARR_datle
		   步幅  :  stride
		   速率μ : rate
	*/
	int backwordUpdateGradient(Array *ARR_inMat, Array *ARR_outMat_forword_Out, Array *ARR_filter, Array *ARR_bias, Array *ARR_delta_feature_map, int stride, double rate);


	/* 函数说明:卷积反向梯度计算,更新ARR_filter和 ARR_bias,
	参数说明:
	向前计算输出: *ARR_outMat_forword_Out
	样本标签:    *lable
	过滤层:  *ARR_filter
	修正值:  *ARR_bias
	误差值δ:  *ARR_datle
	步幅  :  stride
	速率μ : rate
	*/
	int backwordUpdateGradient_(Array *ARR_inMat, Array *ARR_outMat_forword_Out, Array *ARR_kernel, Array *ARR_bias, Array *ARR_delta_feature_map, int stride, double rate);
	
	/* 函数说明:反向网络误差传播
	             因为二层以后的卷积涉及到卷积层从FeatureMap反向传播误差的操作,
				 第一层卷积不涉及到这步操作,为了使单个函数看起来更简洁,逻辑更简单,特意添加这个函数
	参数说明:
	向前计算输出: *ARR_outMat_forword_Out
	样本标签:    *lable
	过滤层:  *ARR_kernel
	修正值:  *ARR_bias
	误差值δ:  *ARR_datle
	pool反向δ:ARR_delta_feature_map 从pool层传递到feature_map的网络误差
	步幅  :  stride
	速率μ : rate
	*/
	int backwordSpreadGradient(Array *ARR_inMat, Array *ARR_outMat_forword_Out, Array *ARR_filter, Array *ARR_bias, Array *ARR_datle, Array *ARR_delta_feature_map, int stride, double rate);
	

	/* 函数说明:反向网络误差传播 误差从 fuetureMay 传递到 卷积输入层 input
	             函数是为多层卷积设计的,一层卷积时用不到这个函数,因为一层卷积
				 不涉及到featuremap层向input层传播误差δ
	参数说明:
	卷积输入矩阵:ARR_inMat  (方法中暂时没用到)
	向前计算输出: *ARR_outMat_forword_Out
	样本标签:    *lable
	过滤层:  *ARR_kernel
	修正值:  *ARR_bias
	卷积输入矩阵误差值δ:  *ARR_datle
	pool反向传回δ:ARR_delta_feature_map (从pool层传递到feature_map的网络误差)
	步幅  :  stride
	速率μ : rate
	激活函数:char Activation
	*/
	int backwordSpreadGradient_(Array *ARR_inMat, Array *ARR_outMat_forword_Out, Array *ARR_filter, Array *ARR_bias, Array *ARR_datle, Array *ARR_delta_feature_map, int stride, double rate, char Activation);

	/* 函数说明:单层卷积测试中的反向计算参数说明
	   参数说明:
			向前计算输出: *ARR_outMat_forword_Out
			样本标签:    *lable
			过滤层:  *ARR_filter
			修正值:  *ARR_bias
			误差值δ:  *ARR_datle
			步幅  :  stride
			速率μ : rate
	*/
	int backword_CS(Array *ARR_inMat, Array *ARR_outMat_forword_Out, Array *ARR_lable, Array *ARR_filter, Array *ARR_bias, Array *ARR_datle, int stride, double rate);
	
};

#endif CONV_H

3、Conv.cpp 向前卷积
这个图做的太好了,引用一下,如有冒犯我会删除
无框架 使用c++从零开始实现 卷积 全连接 softmax 入门深度学习 CPU版 梯度下降_第13张图片

可以随便在网上找一个讲的差不多的卷积例子,然后对照代码看一下就能明白,后面的POOL 层、全连接层、softmax层 都能找到解释,然后根据自己的理解和代码进行比对,再跑代码试试,很快就能理解!

#include "stdafx.h"
#include "Conv.h"
#include 
#include "ARRAY.h"
#include "Derivative.h"
#include 
#include
#include 

using namespace std;

Conv::Conv(){}
Conv::~Conv(){}
Conv::Conv(int _depth, int _row, int _column)
{
	Conv::depth = _depth;
	Conv::row = _row;
	Conv::column = _column;
	cout << "深度:" << Conv::depth << " 行数:" << Conv::row << "  列数:" << Conv::column << endl;
	Conv::Matrix1D = new long double[Conv::column];
	Conv::Matrix2D = new long double*[Conv::row];
	Conv::Matrix3D = new long double**[Conv::depth];
}


int Conv::forword_(Array *ARR_inMat, Array *ARR_kernel, Array *ARR_outMat, Array *ARR_bias, int stride, int ZeroPadd, char Activation){
	Derivative Der;
	//初始化输入,这个地方被我忽略了,找了好半天才找到是这里出错了,没有初始化,因为输出都是一大片小数,根本看不出来,最后还是都设定初始值为1才发现的
	for (int b = 0; b < ARR_outMat->block; b++){
		for (int d = 0; d < ARR_outMat->depth; d++){
			for (int r = 0; r < ARR_outMat->row; r++){
				for (int c = 0; c < ARR_outMat->column; c++){			
					ARR_outMat->Mat4D[b][0][r][c] = 0.0;	
				}
			}
		}
	}
	long double Net= 0.0;
	int map_row = 0;
	int map_column = 0;
	
	long double ***_temp = new long double**[ARR_kernel->depth];
	Array Arr_temp(ARR_kernel->depth, ARR_outMat->row, ARR_outMat->column, _temp, 'A');//临时存储
	//Arr_temp.OutArr3D();
	for (int b = 0; b < ARR_kernel->block; b++){ 
		for (int d = 0; d < ARR_kernel->depth; d++){ 
			//----------------------kernel 在输入的feature map 上滑动
			map_row = 0;
			for (int r = 0; r < ARR_inMat->row; r += stride){
				
				map_column = 0;
				for (int c = 0; c < ARR_inMat->column; c += stride){

					for (int kr = 0; kr < ARR_kernel->row; kr += stride){
						for (int kc = 0; kc < ARR_kernel->column; kc += stride){
							 
							Net+= ARR_inMat->Mat4D[d][0][r + kr][c + kc] * ARR_kernel->Mat4D[b][d][kr][kc];
							}
						}
					
					Arr_temp.Mat3D[d][map_row][map_column] = Net;			
					Net = 0.0;
					map_column++;
					if ((ARR_kernel->column+ c) == ARR_inMat->column){//当计算完当前行最后一个切片时退出
						break;
					}

				}
				if ((ARR_kernel->row + r) == ARR_inMat->row){//当计算完当前行最后一个切片时退出
					break;
				}
				
				map_row++;
			}
			//-----------------------
			//ARR_outMat->OutArr4D();
			
		}
	
		//++++++++++++++++++++++++++++++++
		//Arr_temp.OutArr3D();
	
			for (int D = 0; D < Arr_temp.depth; D++){
				for (int R = 0; R < Arr_temp.row; R++){
					for (int C = 0; C < Arr_temp.column; C++){

						ARR_outMat->Mat4D[b][0][R][C] += Arr_temp.Mat3D[D][R][C] ;
						Arr_temp.Mat3D[D][R][C] = 0.0;
					}
				}
			}
//ARR_outMat->OutArr4D();
	
	}
	//ARR_outMat->OutArr4D();
	Arr_temp.Delete3D(Arr_temp.depth, Arr_temp.row, _temp, &Arr_temp);
	//++++++++++++++++++++++++++++++++
	//ARR_outMat->OutArr4D();
	//对feature map 添加偏置值和激活
	for (int b = 0; b < ARR_outMat->block; b++){
		for (int d = 0; d < ARR_outMat->depth; d++){

			for (int r = 0; r < ARR_outMat->row; r++){
				for (int c = 0; c < ARR_outMat->column; c++){
					//ARR_outMat->Mat4D[b][0][r][c] = tanh(ARR_outMat->Mat4D[b][0][r][c] + ARR_bias->Mat1D[d]);
					//ARR_outMat->Mat4D[b][0][r][c] = Der.Relu_Fun(ARR_outMat->Mat4D[b][0][r][c] + ARR_bias->Mat1D[d]);
					if (Activation == 'T'){
						ARR_outMat->Mat4D[b][0][r][c] = tanh(ARR_outMat->Mat4D[b][0][r][c] + ARR_bias->Mat1D[d]);
						//ARR_outMat->Mat4D[b][0][r][c] = tanh(ARR_outMat->Mat4D[b][0][r][c]);
					}
					else if (Activation == 'R'){
						ARR_outMat->Mat4D[b][0][r][c] = Der.Relu_Fun(ARR_outMat->Mat4D[b][0][r][c] + ARR_bias->Mat1D[d]);
					}
				}
			}
		}
	}
	//ARR_outMat->OutArr4D();
	return 1;
}

int Conv::forword(Array *ARR_inMat, Array *ARR_filter, Array *ARR_outMat, Array *ARR_bias, int stride, int ZeroPadd){
	int js_count = 0;
	//卷积求和计算
	int row = 0;
	int col = 0;
	Derivative Der;
	for (int k = 0; k<ARR_inMat->depth; k++){
		row = 0;//初始化为0,对步幅大于1的进行处理
		for (int i = 0; i<ARR_inMat->row; i += stride){
			col = 0;
			for (int j = 0; j<ARR_inMat->column; j += stride){
				//====================================
				for (int kk = 0; kk<ARR_filter->depth; kk++){
					long double DX = 0.0;   //错误一:位置放错了导致初始化值错误
					for (int kr = 0; kr<ARR_filter->row; kr++){
						for (int kc = 0; kc<ARR_filter->column; kc++){
							//cout<
							//cout<<"["<
							DX += (ARR_filter->Mat3D[kk][kr][kc]) * (ARR_inMat->Mat3D[k][i + kr][j + kc]);
							//cout<<"input:["<
							js_count++;
						}
					}
					 //DX = tanh(DX + ARR_bias->Mat1D[kk]);
					 DX = Der.Relu_Fun(DX + ARR_bias->Mat1D[kk]);
					ARR_outMat->Mat3D[kk][row][col] = DX;
				}
				//ARR_outMat->OutArr3D();
				//====================================
				if (ARR_inMat->column == (j + ARR_filter->column)){//当计算完当前行最后一个切片时退出
					break;
				}
				col++;
			}
			if (ARR_inMat->row == (i + ARR_filter->row)){//当计算完最后一行切片时退出
				break;
			}
			row++;
		}
	}
//	ARR_outMat->OutArr3D();
	return 1;
}

4、Pool.cpp 平均值法下采样
代码也适应不同的filtres,例如:3*3,一定要注意,Feature Map一定要是filtres 的整倍数
无框架 使用c++从零开始实现 卷积 全连接 softmax 入门深度学习 CPU版 梯度下降_第14张图片

最大值的代码没写,有兴趣的可以自己添加

#include "stdafx.h"
#include 
#include 
#include 
#include "ARRAY.h"
#include "POOL.h"
#include "FILEOPR.h"
#include "Derivative.h"
#include 
using namespace std;

Pool::Pool(){}
Pool::~Pool(){}
int Pool::forward_(Array *ARR_IN, Array *ARR_OUT, Array *ARR_KERNEL, int method){
	if (ARR_IN->column%ARR_KERNEL->column != 0){
		cout << "当前pool层卷积输入矩阵与pool核不能整除!请核对两个矩阵的信息!!" << endl;
		return 1;
	}
	//ARR_IN->OutArr3D();
	int js_count = 0;
	//卷积求和计算
	int row = 0;
	int col = 0;
	long double max = 0.0;
	for (int b = 0; b < ARR_IN->block; b++){
		for (int k = 0; k < ARR_IN->depth; k++){
			row = 0;//初始化为0,对步幅大于1的进行处理    
			for (int i = 0; i < ARR_IN->row; i += ARR_KERNEL->row){
				col = 0;
				for (int j = 0; j < ARR_IN->column; j += ARR_KERNEL->column){
					long double DX = 0.0;
					//====================================
					for (int kr = 0; kr < ARR_KERNEL->row; kr++){
						for (int kc = 0; kc < ARR_KERNEL->column; kc++){
							if (method == 1){//平均值
								//cout << ARR_KERNEL->Mat3D[k][kr][kc] << "  " << ARR_IN->Mat3D[k][i + kr][j + kc] << endl;
								//求和
								DX += (ARR_KERNEL->Mat4D[b][k][kr][kc]) * (ARR_IN->Mat4D[b][k][i + kr][j + kc]);
								js_count++;
							}
							else if (method == 2){//最大值
								
							}
						}
					}
					if (method == 1){//平均值
						//求平均值
						ARR_OUT->Mat4D[b][k][row][col] = (DX / (ARR_KERNEL->row*ARR_KERNEL->column));
						//cout << "[" << k << row << col << "]值:" << ARR_OUT->Mat3D[k][row][col] << endl;
					}
					else if (method == 2){//最大值
					}
					//====================================
					if (ARR_IN->column == (j + ARR_KERNEL->column)){//当计算完当前行最后一个切片时退出
						break;
					}
					col++;
				}
				if (ARR_IN->row == (i + ARR_KERNEL->row)){//当计算完最后一行切片时退出
					break;
				}
				row++;
			}
		}
	}
	//ARR_OUT->OutArr4D();
	return 1;

}

5、FULLCONNECTION.cpp 全连接代码
这个就不上图了,比较好理解
代码包括一个 forward_input 输入层函数,还有一个forward_hide隐藏层函数,如果只需要一个全连接可以直接用forward_input函数,因为代码包含四维转一维的处理,如果要添加多个隐藏层,可以直接在后面调用forward_hide函数。forward_hide函数可以直接做输出连接softmax层。

#include "stdafx.h"
#include "FULLCONNECTION.h"
#include "ARRAY.h"
#include "FILEOPR.h"
#include 
#include 
#include 
#include "Derivative.h"
using namespace std;

FULLCONNECTION::FULLCONNECTION()
{
}

FULLCONNECTION::~FULLCONNECTION()
{
}

int FULLCONNECTION::forward_input(Array *ARR_full_in, Array *ARR_full_out, Array *ARR_full_filet, Array *ARR_full_bias, Array *ARR_full_net, char Activation){
	Derivative DE; 
	int count = ARR_full_in->block* ARR_full_in->depth*ARR_full_in->row*ARR_full_in->column;
	//用来存储POOL四维拍扁了之后的一维数组
	long double *full_in_1d_temp = new long double[count];
	Array ARR_full_in_1d_temp(count, full_in_1d_temp,'A');
	int index = 0; 
	for (int b= 0;b < ARR_full_in->block;b++){
		for (int i = 0; i < ARR_full_in->depth; i++){
			for (int j = 0; j < ARR_full_in->row; j++){
				for (int k = 0; k < ARR_full_in->column; k++){
					ARR_full_in_1d_temp.Mat1D[index] = ARR_full_in->Mat4D[b][i][j][k];
					index++;

				}
			}
		}
	}
	
	//全连接的向前计算
	for (int row = 0; row < ARR_full_filet->row; row++){
		long double DX = 0.0;
		for (int col = 0; col < ARR_full_filet->column; col++){
			
			DX += ARR_full_in_1d_temp.Mat1D[col] * ARR_full_filet->Mat2D[row][col];
			//if (1 == rand() % 2)
			//{ //50%的概率达成
			//	//cout << "测试" << endl;
			//}
			//else
			//{
			//	DX += ARR_full_in_1d_temp.Mat1D[col] * ARR_full_filet->Mat2D[row][col];
			//}

		}
		//用来计算误差的net的值 
		ARR_full_net->Mat1D[row] = DX + ARR_full_bias->Mat1D[row];
		
		//激活函数对net进行处理
		if (Activation=='T'){
			ARR_full_out->Mat1D[row] = tanh(ARR_full_net->Mat1D[row]);
		}
		else if (Activation == 'R'){
			ARR_full_out->Mat1D[row] = DE.Relu_Fun(ARR_full_net->Mat1D[row]);
		}

	}
	//内存释放
	ARR_full_in_1d_temp.Delete1D(full_in_1d_temp, &ARR_full_in_1d_temp);
	return 1;
}

int FULLCONNECTION::forward_hide(Array *ARR_full_in, Array *ARR_full_out, Array *ARR_full_filet, Array *ARR_full_bias, Array *ARR_full_net, char Activation){

	Derivative DE;
	//全连接隐藏层向前计算
	for (int row = 0; row < ARR_full_filet->row; row++){
		long double DX = 0;
		for (int col = 0; col < ARR_full_filet->column; col++){
			//这里是没有使用dropout的代码
			DX += ARR_full_in->Mat1D[col] * ARR_full_filet->Mat2D[row][col];

			//这里是使用dropout的代码,可以自己手动修体验一下效果
			//if (1 == rand() % 2)
			//{ //50%的概率达成
			//	//cout << "测试" << endl;
			//}
			//else
			//{
			//	DX += ARR_full_in->Mat1D[col] * ARR_full_filet->Mat2D[row][col];
			//
			//}

		}
		//全连接NET值,未进行激活函数处理,用来计算tanh 导数的时候需要用到
		ARR_full_net->Mat1D[row] = DX + ARR_full_bias->Mat1D[row];
		//全连接输出层经过
		if (Activation == 'T'){
			ARR_full_out->Mat1D[row] = tanh(ARR_full_net->Mat1D[row]);
		}
		else if (Activation == 'R'){
			ARR_full_out->Mat1D[row] = DE.Relu_Fun(ARR_full_net->Mat1D[row]);
		}

	} 

	return 1;
}
//测试函数
int FULLCONNECTION::backword_out_CS(Array *ARR_full_in_hide, Array *ARR_full_out, Array *ARR_full_filet, Array *ARR_full_bias, Array *ARR_full_delta, Array *ARR_full_lable, Array *ARR_full_net, double rate){

	Derivative Der;
	//网络误差计算
	for (int i = 0; i < ARR_full_lable->column; i++){
		ARR_full_delta->Mat1D[i] = (-1)*(Der.Tanh_Der_Fun(ARR_full_net->Mat1D[i])*(-1)*(ARR_full_lable->Mat1D[i] - ARR_full_out->Mat1D[i]));
	}
	//cout << "计算后的ARR_full_delta---------" << endl;
	//ARR_full_delta->OutArr1D();
	for (int i = 0; i < ARR_full_filet->row; i++){//10

		for (int j = 0; j < ARR_full_filet->column; j++){//200
			//更新w
			ARR_full_filet->Mat2D[i][j] = ARR_full_filet->Mat2D[i][j] + rate* ARR_full_delta->Mat1D[i] * ARR_full_in_hide->Mat1D[j];
		}
		//更新bias
		ARR_full_bias->Mat1D[i] = ARR_full_bias->Mat1D[i] + rate* ARR_full_delta->Mat1D[i];
	}
	//cout << ARR_full_delta->Mat1D[0] << "--" << ARR_full_delta->Mat1D[1] << "--" << ARR_full_delta->Mat1D[2] << "--" << ARR_full_delta->Mat1D[3] << "--" << ARR_full_delta->Mat1D[4] << "--" << ARR_full_delta->Mat1D[5] << "--" << ARR_full_delta->Mat1D[6] << "--" << ARR_full_delta->Mat1D[7] << "--" << ARR_full_delta->Mat1D[8] << "--" << ARR_full_delta->Mat1D[9] << endl;
	//ARR_full_delta->OutArr1D();

	return 1;
}

**6、SoftMax.cpp **
这个就是将输出转换成 概率

#include "stdafx.h"
#include 
#include "Conv.h"
#include "ARRAY.h"
#include 
#include  
#include "Derivative.h"
#include "FILEOPR.h"
#include 
#include 
#include "FULLCONNECTION.h"
#include "POOL.h"
#include 
#include 
#include "SoftMax.h"

using namespace std;

SoftMax::SoftMax(){
}
SoftMax::~SoftMax(){
}
//向前计算
void SoftMax::forword(Array *Arr_SoftMax_input, Array *Arr_SoftMax_output, Array *Arr_SoftMax_filter){
	int js_count = 0;
	//Arr_SoftMax_input->OutArr1D();

	long double DX = 0.0;
	long double ZS = 0.0;

	string saveFile = "";
	for (int i = 0; i<Arr_SoftMax_output->column; i++){       //10
		for (int j = 0; j<Arr_SoftMax_filter->column; j++){   //100
			// cout<<"["<
			DX += Arr_SoftMax_input->Mat1D[j] * Arr_SoftMax_filter->Mat2D[i][j];

		}
		Arr_SoftMax_output->Mat1D[i] = exp(DX);
		DX = 0.0;
		
	}
	//Arr_SoftMax_output->OutArr1D();
	for (int i = 0; i<Arr_SoftMax_output->column; i++){//10
		ZS += Arr_SoftMax_output->Mat1D[i];
	}

	for (int i = 0; i<Arr_SoftMax_output->column; i++){//10

		Arr_SoftMax_output->Mat1D[i] = Arr_SoftMax_output->Mat1D[i] / ZS;

	}
	//Arr_SoftMax_output->OutArr1D();
}

**7、TEST.cpp 测试主程序 **

// CNN.cpp : 定义控制台应用程序的入口点。
// Author:njm
//

#include "stdafx.h"
#include 
#include "Conv.h"
#include "ARRAY.h"
#include 
#include  
#include "Derivative.h"
#include "FILEOPR.h"
#include 
#include 
#include "FULLCONNECTION.h"
#include "POOL.h"
#include 
#include
#include "SoftMax.h"
#include   
#include   
#include   
#include   
#include
#include
#include
#include   
#include   
#include   

using namespace std;
using namespace cv;

int _tmain(int argc, _TCHAR* argv[])
{
	/*学习速率*/
	const double rate = 0.001;
	/*计算公式*/
	//W2=  (W1-F+2P)/S+1
	//H2 = (H1-F+2P)/S+1
	//  一层卷积
	long double ****filet = new long double***[6];
	Array ARR_filet(6, 1, 3, 3, filet, 'C');//C
	long double ****outMat = new long double***[6];
	Array ARR_outMat(6, 1, 56, 56, outMat, 'A');
	long double *bias = new long double[10];
	Array ARR_bias(6, bias, 'C');//C
	long double ****delta_feature_map = new long double***[6];
	Array ARR_delta_feature_map(6, 1, 56, 56, delta_feature_map, 'A');

	//  二层卷积 
	long double ****filet_two = new long double***[16];
	Array ARR_filet_two(16, 6, 3, 3, filet_two, 'C');
	long double ****outMat_two = new long double***[16];
	Array ARR_outMat_two(16, 1, 26, 26, outMat_two, 'A');
	long double *bias_two = new long double[16];
	Array ARR_bias_two(16, bias_two, 'C');

	/*long double ****delta_two = new long double***[6];
	Array ARR_delta_two(6, 1, 28, 28, delta_two, 'A');*/
	long double ****delta_feature_map_two = new long double***[16];
	Array ARR_delta_feature_map_two(16, 1, 26, 26, delta_feature_map_two, 'A');



	//  三层卷积 
	long double ****filet_three = new long double***[26];
	Array ARR_filet_three(26, 16, 4, 4, filet_three, 'C');
	long double ****outMat_three = new long double***[26];
	Array ARR_outMat_three(26, 1, 10, 10, outMat_three, 'A');
	long double *bias_three = new long double[26];
	Array ARR_bias_three(26, bias_three, 'C');//C

	//long double ****delta_three = new long double***[26];
	//Array ARR_delta_three(26, 1, 13, 13, delta_three, 'A');
	long double ****delta_feature_map_three = new long double***[26];
	Array ARR_delta_feature_map_three(26, 1, 10, 10, delta_feature_map_three, 'A');


	//一层pool
	long double ****outPool = new long double***[6];
	Array ARR_outPool(6, 1, 28, 28, outPool, 'A');
	long double ****Kernel_Pool = new long double***[6];
	Array ARR_Kernel_Pool(6, 1, 2, 2, Kernel_Pool, 'B');

	long double ****datle_pool_in = new long double***[6];
	Array ARR_datle_pool_in(6, 1, 56, 56, datle_pool_in, 'A');
	long double ****datle_pool_out = new long double***[6];
	Array ARR_datle_pool_out(6, 1, 28, 28, datle_pool_out, 'A');
	int methon = 1;//平均值法

	//二层pool
	long double ****outPool_two = new long double***[16];
	Array ARR_outPool_two(16, 1, 13, 13, outPool_two, 'A');//应该为0 暂定为B 1
	long double ****Kernel_Pool_two = new long double***[16];
	Array ARR_Kernel_Pool_two(16, 1, 2, 2, Kernel_Pool_two, 'B');

	long double ****datle_pool_in_two = new long double***[16];
	Array ARR_datle_pool_in_two(16, 1, 26, 26, datle_pool_in_two, 'A');
	long double ****datle_pool_out_two = new long double***[16];
	Array ARR_datle_pool_out_two(16, 1, 13, 13, datle_pool_out_two, 'A');

	int methon_two = 1;//平均值法



	//三层pool
	long double ****outPool_three = new long double***[26];
	Array ARR_outPool_three(26, 1, 5, 5, outPool_three, 'A');//应该为0 暂定为B 1
	long double ****Kernel_Pool_three = new long double***[26];
	Array ARR_Kernel_Pool_three(26, 1, 2, 2, Kernel_Pool_three, 'B');

	long double ****datle_pool_in_three = new long double***[26];
	Array ARR_datle_pool_in_three(26, 1, 10, 10, datle_pool_in_three, 'A');
	long double ****datle_pool_out_three = new long double***[26];
	Array ARR_datle_pool_out_three(26, 1, 5, 5, datle_pool_out_three, 'A');

	/*全连接输入层参数*/
	//全连接输入500输出400
	long double *full_out = new long double[120];
	Array ARR_full_out(120, full_out, 'A');
	//全连接过滤层
	long double **full_filet = new long double*[120];
	Array ARR_full_filet(120, 650, full_filet, 'C');//C
	//偏置值
	long double *full_bias = new long double[120];
	Array ARR_full_bias(120, full_bias, 'C');//C
	//存储用来计算导数的net值
	long double *full_net = new long double[120];
	Array ARR_full_net(120, full_net, 'A');
	//存储输入层网络误差值
	long double *full_delta = new long double[650];
	Array ARR_full_delta(650, full_delta, 'A');

	/*全连接隐藏层参数*/
	//全连接输入400输出200
	long double *full_out_hide = new long double[84];
	Array ARR_full_out_hide(84, full_out_hide, 'A');
	//全连接过滤层
	long double **full_filet_hide = new long double*[84];
	Array ARR_full_filet_hide(84, 120, full_filet_hide, 'C');//C
	//偏置值
	long double *full_bias_hide = new long double[84];
	Array ARR_full_bias_hide(84, full_bias_hide, 'C');//C
	//存储用来计算导数的net值
	long double *full_net_hide = new long double[84];
	Array ARR_full_net_hide(84, full_net_hide, 'A');
	//隐藏层datle
	long double *full_datle_hide = new long double[120];
	Array ARR_full_datle_hide(120, full_datle_hide, 'A');

	/*全连接输出层反向计算*/
	long double *full_datle_out = new long double[84];
	Array ARR_full_datle_out(84, full_datle_out, 'A');
	long double *full_lable_hide = new long double[84];
	Array ARR_full_lable_hide(84, full_lable_hide, 'B');

	/*softmax向前*/
	/*long double *SoftMax_input = new long double[100];
	Array Arr_SoftMax_input(100, SoftMax_input,'A');*/
	long double *SoftMax_output = new long double[10];
	Array Arr_SoftMax_output(10, SoftMax_output, 'A');
	long double **SoftMax_filter = new long double*[10];
	Array Arr_SoftMax_filter(10, 84, SoftMax_filter, 'C');//C
	int label_value = 0;
	long double *delta_layer_output = new long double[10];
	Array Arr_delta_layer_output(10, delta_layer_output, 'A');

	Conv CONV1;
	Pool pool1;
	Conv CONV2;
	Pool pool2;

	Conv CONV3;
	Pool pool3;

	FULLCONNECTION FullCon;
	SoftMax softmax;

	int count = 0;

	/*文件操作*/
	FILEOPR FL;
	vector<string> lab_str = FL.read("E:\\CJJ\\MNIST\\MNISTIMAGE\\train.txt");
	//训练轮数
	int MAX_ITER_NUM = 5000;
	//样本数
	int train_num = 30000;

	Array arr;
	stringstream str_ss;
	string str_i;
	string img_name;

	Mat MAT_GET_IMG;
	Mat temImage, dstImage1, dstImage2;
	Mat temImage_CS, dstImage1_CS, dstImage2_CS;

	FILEOPR fi_cs;
	vector<string> lab_str_cs;

	/*先读取一下WB文件,看一下有没有数据,有数据证明已经训练过,可以直接读取*/
	FILEOPR f_wb;
	cout << "开始 W B 的数据读取检查!" << endl;
	int count_B_CONV1 = f_wb.readWB("E:\\CJJ\\CNN01\\W_B_FILE\\B_CONV1.txt").size();
	int count_B_CONV2 = f_wb.readWB("E:\\CJJ\\CNN01\\W_B_FILE\\B_CONV2.txt").size();
	int count_W_CONV1 = f_wb.readWB("E:\\CJJ\\CNN01\\W_B_FILE\\W_CONV1.txt").size();
	int count_W_CONV2 = f_wb.readWB("E:\\CJJ\\CNN01\\W_B_FILE\\W_CONV2.txt").size();
	int count_B_FULL1 = f_wb.readWB("E:\\CJJ\\CNN01\\W_B_FILE\\B_FULL1.txt").size();
	int count_B_FULL2 = f_wb.readWB("E:\\CJJ\\CNN01\\W_B_FILE\\B_FULL2.txt").size();
	int count_W_FULL1 = f_wb.readWB("E:\\CJJ\\CNN01\\W_B_FILE\\W_FULL1.txt").size();
	int count_W_FULL2 = f_wb.readWB("E:\\CJJ\\CNN01\\W_B_FILE\\W_FULL2.txt").size();
	if (count_B_CONV1>1 && count_B_CONV2>1 && count_W_CONV1>1 && count_W_CONV2>1 && count_B_FULL1>1 && count_B_FULL2>1 && count_W_FULL1>1 && count_W_FULL2>1){

		f_wb.readWB1D(&ARR_bias, "E:\\CJJ\\CNN01\\W_B_FILE\\B_CONV1.txt");
		f_wb.readWB1D(&ARR_bias_two, "E:\\CJJ\\CNN01\\W_B_FILE\\B_CONV2.txt");
		f_wb.readWB1D(&ARR_bias_three, "E:\\CJJ\\CNN01\\W_B_FILE\\B_CONV3.txt");
		f_wb.readWB4D(&ARR_filet, "E:\\CJJ\\CNN01\\W_B_FILE\\W_CONV1.txt");
		f_wb.readWB4D(&ARR_filet_two, "E:\\CJJ\\CNN01\\W_B_FILE\\W_CONV2.txt");
		f_wb.readWB4D(&ARR_filet_three, "E:\\CJJ\\CNN01\\W_B_FILE\\W_CONV3.txt");
		f_wb.readWB1D(&ARR_full_bias, "E:\\CJJ\\CNN01\\W_B_FILE\\B_FULL1.txt");
		f_wb.readWB1D(&ARR_full_bias_hide, "E:\\CJJ\\CNN01\\W_B_FILE\\B_FULL2.txt");
		f_wb.readWB2D(&ARR_full_filet, "E:\\CJJ\\CNN01\\W_B_FILE\\W_FULL1.txt");
		f_wb.readWB2D(&ARR_full_filet_hide, "E:\\CJJ\\CNN01\\W_B_FILE\\W_FULL2.txt");
		f_wb.readWB2D(&Arr_SoftMax_filter, "E:\\CJJ\\CNN01\\W_B_FILE\\W_SOFTMAX.txt");
	}
	cout << "W B 的数据读取检查结束,开始识别!" << endl;
	//激活函数 
	char funtype = 'T';
	while (true){
		for (int j = 0; j < train_num; j++){


				//进行识别测试的数字数量
				int train_num_cs = 100;
				long double zq = 0.0;

				lab_str_cs = fi_cs.read("E:\\CJJ\\MNIST\\MNISTIMAGECS\\train.txt");
				//lab_str_cs = fi_cs.read("E:\\CJJ\\MNIST\\CS1\\train.txt");
				//读取要进行识别的数据,这里的循环没有去掉,可以测试再全连接层dropout的识别效果
				for (int j = 0; j<train_num_cs; j++){
					int i_lable = j;
					stringstream str_ss_cs;
					str_ss_cs << i_lable;
					string str_i = str_ss_cs.str();
					string img_name = "E:\\CJJ\\MNIST\\MNISTIMAGECS\\" + str_i + ".jpg";
					//string img_name = "E:\\CJJ\\MNIST\\CS1\\" + str_i + ".jpg";

					int label_value = atoi(lab_str_cs[j].c_str());

					Mat MAT_GET_IMG_cs = imread(img_name);


					temImage_CS = MAT_GET_IMG_cs;
					resize(temImage_CS, dstImage1_CS, Size(58, 58), 0, 0, INTER_LINEAR);

					long double ****inMat_cs = new long double***[1];
					Array ARR_inMat_cs(&dstImage1_CS, 1, 1, 58, 58, inMat_cs, 'C');

					str_ss.str("");

				   
					CONV1.forword_(&ARR_inMat_cs, &ARR_filet, &ARR_outMat, &ARR_bias, 1, 0, funtype);
					pool1.forward_(&ARR_outMat, &ARR_outPool, &ARR_Kernel_Pool, 1);
					CONV2.forword_(&ARR_outPool, &ARR_filet_two, &ARR_outMat_two, &ARR_bias_two, 1, 0, funtype);
					pool2.forward_(&ARR_outMat_two, &ARR_outPool_two, &ARR_Kernel_Pool_two, methon_two);
					CONV3.forword_(&ARR_outPool_two, &ARR_filet_three, &ARR_outMat_three, &ARR_bias_three, 1, 0, funtype);
					pool3.forward_(&ARR_outMat_three, &ARR_outPool_three, &ARR_Kernel_Pool_three, methon_two);
					FullCon.forward_input(&ARR_outPool_three, &ARR_full_out, &ARR_full_filet, &ARR_full_bias, &ARR_full_net, funtype);
					FullCon.forward_hide(&ARR_full_out, &ARR_full_out_hide, &ARR_full_filet_hide, &ARR_full_bias_hide, &ARR_full_net_hide, funtype);
					softmax.forword(&ARR_full_out_hide, &Arr_SoftMax_output, &Arr_SoftMax_filter);

					ARR_inMat_cs.Delete4D(1, 1, 58, inMat_cs, &ARR_inMat_cs);
					long double mid = 0.0;
					long double max = 0.0;
					int number = 0;
					for (int i = 0; i<10; i++){
						mid = Arr_SoftMax_output.Mat1D[i];
						if (max<mid){
							max = mid;
							number = i;
						}
					}
					cout << label_value << "  " << number << endl;
					if (label_value == number){
						zq++;
					}
					MAT_GET_IMG_cs.release();
				}
				double dl_zp = zq;
				double dou_train_num = train_num_cs;
				cout << "正确率:" << (dl_zp / train_num_cs) * 100 << "%" << endl;

		}
	}


	return 0;
}


在文件中会看到“_cs”的函数,这是我最开始做的时候添加的,因为代码写的错误太多,理解原理的混沌期写出来的不敢想象,怎么改正确率都上不去,于是我就把每一层都拆开,添加梯度验证,最开始我在网上发过类似求助,如何验证代码正确性,但是没有得到答案。其实原理很简单,你的网络再深不也都是链式求导吗,所以一层一层验证没有问题,就算代码只有一个卷积,如果代码正确,也可以用loss函数画出漂亮的曲线,根据曲线对代码做大概的判断。用什么激活函数怎么求导一定要正确,这里还是要提醒一下,一定要自己用笔在纸上面推导一下,太有用了,要不然心里总是没底,至少我是这样。有兴趣的可以自己改一下试试。
8、项目目录介绍
无框架 使用c++从零开始实现 卷积 全连接 softmax 入门深度学习 CPU版 梯度下降_第15张图片
无框架 使用c++从零开始实现 卷积 全连接 softmax 入门深度学习 CPU版 梯度下降_第16张图片
ARR_full_filet:里面是存放损失函数记录的损失值,可以用来生成曲线
W_B_FILE:存储每层的W B的最新参数,现在里面存放的参数正确率是99.33%
W_B_FILE null:这里面也是存储最新参数的txt文件,只是文件内容是空的,每次需要重新训练的时候可以直接把这个文件夹的复制到W_B_FILE里面,他里面的文件结构是这样的,看下图:

无框架 使用c++从零开始实现 卷积 全连接 softmax 入门深度学习 CPU版 梯度下降_第17张图片
第一个存储的是正确率,后面的依次存储每一层的值,如果你想要的做个5层卷积,就需要添加两个空的txt文件。
1、B_CONV5.txt
2、W_CONV5.txt
全连接也是这样做,添加带FULL的文件,然后再修改一下写入代码就好了,下面是写入文件代码,很简单。

//正确率提升了需要更新 W B 和正确率
				if ((dl_zp / dou_train_num) > zql){
					cout << "开始更新 W B 参数" << endl;
					ostringstream s_zql;
					s_zql << (dl_zp / dou_train_num);
					FL.write_cover("E:\\CJJ\\CNNOCR\\W_B_FILE\\B_CONV1.txt", ARR_bias.OutArr1DTXT());
					FL.write_cover("E:\\CJJ\\CNNOCR\\W_B_FILE\\B_CONV2.txt", ARR_bias_two.OutArr1DTXT());
					FL.write_cover("E:\\CJJ\\CNNOCR\\W_B_FILE\\B_CONV3.txt", ARR_bias_three.OutArr1DTXT());
					//FL.write_cover("E:\\CJJ\\CNNOCR\\W_B_FILE\\B_CONV4.txt", ARR_bias_four.OutArr1DTXT());

					FL.write_cover("E:\\CJJ\\CNNOCR\\W_B_FILE\\W_CONV1.txt", ARR_filet.OutArr4DTXT());
					FL.write_cover("E:\\CJJ\\CNNOCR\\W_B_FILE\\W_CONV2.txt", ARR_filet_two.OutArr4DTXT());
					FL.write_cover("E:\\CJJ\\CNNOCR\\W_B_FILE\\W_CONV3.txt", ARR_filet_three.OutArr4DTXT());
					//FL.write_cover("E:\\CJJ\\CNNOCR\\W_B_FILE\\W_CONV4.txt", ARR_filet_four.OutArr4DTXT());

					FL.write_cover("E:\\CJJ\\CNNOCR\\W_B_FILE\\B_FULL1.txt", ARR_full_bias.OutArr1DTXT());
					FL.write_cover("E:\\CJJ\\CNNOCR\\W_B_FILE\\B_FULL2.txt", ARR_full_bias_hide.OutArr1DTXT());
					FL.write_cover("E:\\CJJ\\CNNOCR\\W_B_FILE\\W_FULL1.txt", ARR_full_filet.OutArr2DTXT());
					FL.write_cover("E:\\CJJ\\CNNOCR\\W_B_FILE\\W_FULL2.txt", ARR_full_filet_hide.OutArr2DTXT());
					FL.write_cover("E:\\CJJ\\CNNOCR\\W_B_FILE\\W_SOFTMAX.txt", Arr_SoftMax_filter.OutArr2DTXT());
					FL.write_cover("E:\\CJJ\\CNNOCR\\W_B_FILE\\AccuracyRate.txt", s_zql.str() + " ");
					cout << " W B 参数更新结束!" << endl;
				}

无框架 使用c++从零开始实现 卷积 全连接 softmax 入门深度学习 CPU版 梯度下降_第18张图片
CS里面是存储的是
无框架 使用c++从零开始实现 卷积 全连接 softmax 入门深度学习 CPU版 梯度下降_第19张图片
CS1存储的是
无框架 使用c++从零开始实现 卷积 全连接 softmax 入门深度学习 CPU版 梯度下降_第20张图片
dataset存储的是中文训练和测试数据集,因为3755个字的数据集好几个G太大了,所以我只上传了前200个字,每个字的1586个模板尺寸是50*50,训练1268个,测试318个。
无框架 使用c++从零开始实现 卷积 全连接 softmax 入门深度学习 CPU版 梯度下降_第21张图片
MNISTMAGE 、MNISTMAGECS这两个就不说了,都懂得。
无框架 使用c++从零开始实现 卷积 全连接 softmax 入门深度学习 CPU版 梯度下降_第22张图片
TEST.CPP这个是测试主文件,LeNet.cpp是训练主文件,Derivative.CPP是求导用的,FILEOPR.cpp文件操作 ,SPLIT.cpp负责txt文件与程序中数组的相互转换。

注意
因为mnist图片是黑背景,所以在测试白背景的时候要修改一下ARRAY.CPP文件中代码,如果想要训练白背景数据集,也要对这儿的代码进行修改。代码中的路径是绝对路径,所以解压后要放到E盘,直接VS打开就能运行

Array::Array(Mat *mat,int block, int Depth, int ROW_NUM, int COL_NUMC, long double ****Arr4D, char init){
	
	//Array::TEMPPOINTER_4D = Array::Mat4D;
	Array::block = block;
	Array::depth = Depth;
	Array::row = ROW_NUM;
	Array::column = COL_NUMC;
	for (int b = 0; b < block; b++)
	{
		Arr4D[b] = new  long double **[Depth];
		for (int i = 0; i < Depth; i++)
		{
			Arr4D[b][i] = new  long double *[mat->rows];
			for (int j = 0; j < mat->rows; j++)
			{
				Arr4D[b][i][j] = new long double[mat->cols];
				for (int c = 0; c < mat->cols; c++){
					Vec3b pix = mat->at<Vec3b>(j, c);

					uchar B = mat->at<Vec3b>(j, c)[0];
					if ((int)B>150){
						Arr4D[b][i][j][c] = 1.0;//rand()*1.0/RAND_MAX;//2.0 * rand() / RAND_MAX - 1.0;
					}
					else{
						Arr4D[b][i][j][c] =0.0;
					}
				}
			}

		}
	}
	Array::Mat4D = Arr4D;
}

无框架 使用c++从零开始实现 卷积 全连接 softmax 入门深度学习 CPU版 梯度下降_第23张图片
源代码和数据集连接:
链接:https://pan.baidu.com/s/1YAQoNNbBRItWbbsURi7tEA
提取码:200Y

**在这里插入图片描述

[email protected]

你可能感兴趣的:(深度学习,神经网络,机器学习)