LeNet-5 研习 3 (进行C语言实现LeNet的前向传播的解读)

参考博客:

https://blog.csdn.net/tostq/article/details/51786265

参考代码:

https://github.com/tostq/DeepLearningC/tree/master/CNN

一 总结构介绍

在代码中,一共分为5层

//输入(不算入层数)-1卷积层-2池化层-3卷积层-4池化层-5输出层

二 C1卷积层

//输入 28*28图像 灰度图(0-255级) 深度为1(通道为1) 
//1卷积层  卷积模板 模板大小5*5  每一个深度对应一个不同模板  故  有1个模板
//同一图片,为了提取不同特征,使用不同模板,即多组模板对图像进行卷积处理
//该层 6组卷积提取特征  每组一个卷积模板 
//每组都有一个偏置项 共有参数 6*5*5+6*1=6*(5*5+1)= 156
// (高-卷积核的边长+2*图像边扩充大小)/滑动步长+1

// 卷积后的大小: 无扩展,步长为1    28-5+1=24
// 连接数 24*24*156 = 89856 

代码中,C1卷积层结束后

将x的值放入sigmod函数中

接着将得到的0-1的二值图

代码如下:

	// 第一层的传播
	int i,j,r,c;
	// 第一层输出数据
	nSize mapSize={cnn->C1->mapSize,cnn->C1->mapSize};
	nSize inSize={cnn->C1->inputWidth,cnn->C1->inputHeight};
	nSize outSize={cnn->S2->inputWidth,cnn->S2->inputHeight};
	for(i=0;i<(cnn->C1->outChannels);i++){
		for(j=0;j<(cnn->C1->inChannels);j++){
			float** mapout=cov(cnn->C1->mapData[j][i],mapSize,inputData,inSize,valid);
			addmat(cnn->C1->v[i],cnn->C1->v[i],outSize,mapout,outSize);
			for(r=0;r<outSize.r;r++)
				free(mapout[r]);
			free(mapout);
		}
		for(r=0;r<outSize.r;r++)
			for(c=0;c<outSize.c;c++)
				cnn->C1->y[i][r][c]=activation_Sigma(cnn->C1->v[i][r][c],cnn->C1->basicData[i]);
	}

三 S2池化层

#define AvePool 0
#define MaxPool 1
#define MinPool 2

代码中有三种池化方式,我们选择AvePool平均池化

 Ho=Wo=(H−F+2×P)/S+1=(高−卷积核的边长+2×图像边扩充大小)/滑动步长+1

=(24−2+2×0)/2+1=12   S2,没有扩充,且滑动步长为2,padding=0,stride=2

输入6个图像,输出6个图像

平均池化相当于,用全是1的卷积核进行卷积,然后求平均

步长为2,表示如下:






void avgPooling(float** output,nSize outputSize,float** input,nSize inputSize,int mapSize) // 求平均值
{
	int outputW=inputSize.c/mapSize;
	int outputH=inputSize.r/mapSize;
	if(outputSize.c!=outputW||outputSize.r!=outputH)
		printf("ERROR: output size is wrong!!");

	int i,j,m,n;
	for(i=0;i<outputH;i++)
		for(j=0;j<outputW;j++)
		{
			float sum=0.0;
			for(m=i*mapSize;m<i*mapSize+mapSize;m++)
				for(n=j*mapSize;n<j*mapSize+mapSize;n++)
					sum=sum+input[m][n];

			output[i][j]=sum/(float)(mapSize*mapSize);
		}
}

求平均后,输出6个图像,大小为12*12

四 C3卷积层

12组卷积模板,每组的深度(通道)为6,每个卷积核的大小为5*5

 Ho=Wo=(H−F+2×P)/S+1=(高−卷积核的边长+2×图像边扩充大小)/滑动步长+1

=(12−5+2×0)/1+1=8   没有扩充,且滑动步长为1,padding=0,stride=1

输入为6个12*12像素的图像

卷积核一共6*12个

输出为12个8*8像素的图像

这里没有使用下图中LeNet-5的那种方式:

而是使用了全连接

12组模板,有12个bias偏置,每组有6个卷积核,

每组模板的卷积核图示为:

故整个网络共有卷积核 6*12个,参数12*1+12*6*(5*5) = 2664

这里相当于把输入的6个12*12图像,看作是,深度(通道)为6的一个图像,对这个图像进行卷积

由前面LeNet-5 研习 1 我们讲解的多通道卷积

图示为:

那么就

输出的第一个8*8图像的第一个像素点

x= b

+X1*W11+X2*W12+X3*W13+X4*W14+X5*W15  

+X13*W16+X14*W17+X15*W18+X16*W19+X17*W110

+X25*W111+X26*W112+X27*W113+X28*W114+X29*W115  

+X37*W116+X38*W117+X39*W118+X40*W119+X41*W120

+X49*W121+X50*W122+X51*W123+X52*W124+X53*W125

下面的X是输入的第2个图像中的,W是第1组卷积模板中,6通道的卷积核

+X1*W11+X2*W12+X3*W13+X4*W14+X5*W15  +X13*W16+X14*W17+X15*W18+X16*W19+X17*W110

+X25*W111+X26*W112+X27*W113+X28*W114+X29*W115  +X37*W116+X38*W117+X39*W118+X40*W119+X41*W120

+X49*W121+X50*W122+X51*W123+X52*W124+X53*W125

下面的X是输入的第3个图像中的,W是第1组卷积模板中,6通道的卷积核

+X1*W11+X2*W12+X3*W13+X4*W14+X5*W15  +X13*W16+X14*W17+X15*W18+X16*W19+X17*W110

+X25*W111+X26*W112+X27*W113+X28*W114+X29*W115  +X37*W116+X38*W117+X39*W118+X40*W119+X41*W120

+X49*W121+X50*W122+X51*W123+X52*W124+X53*W125

下面的X是输入的第4个图像中的,W是第1组卷积模板中,6通道的卷积核

+X1*W11+X2*W12+X3*W13+X4*W14+X5*W15  +X13*W16+X14*W17+X15*W18+X16*W19+X17*W110

+X25*W111+X26*W112+X27*W113+X28*W114+X29*W115  +X37*W116+X38*W117+X39*W118+X40*W119+X41*W120

+X49*W121+X50*W122+X51*W123+X52*W124+X53*W125

下面的X是输入的第5个图像中的,W是第1组卷积模板中,6通道的卷积核

+X1*W11+X2*W12+X3*W13+X4*W14+X5*W15  +X13*W16+X14*W17+X15*W18+X16*W19+X17*W110

+X25*W111+X26*W112+X27*W113+X28*W114+X29*W115  +X37*W116+X38*W117+X39*W118+X40*W119+X41*W120

+X49*W121+X50*W122+X51*W123+X52*W124+X53*W125

下面的X是输入的第6个图像中的,W是第1组卷积模板中,6通道的卷积核

+X1*W11+X2*W12+X3*W13+X4*W14+X5*W15  +X13*W16+X14*W17+X15*W18+X16*W19+X17*W110

+X25*W111+X26*W112+X27*W113+X28*W114+X29*W115  +X37*W116+X38*W117+X39*W118+X40*W119+X41*W120

+X49*W121+X50*W122+X51*W123+X52*W124+X53*W125

整个网络共有卷积核 6*12个,参数12*1+12*6*(5*5) = 2664

连接数 8*8*2664  = 12* 8 *8 *( (5*5) *6 + 1)= 170496

代码实现:

// 第三层输出传播,这里是全连接
	outSize.c=cnn->S4->inputWidth;
	outSize.r=cnn->S4->inputHeight;
	inSize.c=cnn->C3->inputWidth;
	inSize.r=cnn->C3->inputHeight;
	mapSize.c=cnn->C3->mapSize;
	mapSize.r=cnn->C3->mapSize;
	for(i=0;i<(cnn->C3->outChannels);i++){
		for(j=0;j<(cnn->C3->inChannels);j++){
			float** mapout=cov(cnn->C3->mapData[j][i],mapSize,cnn->S2->y[j],inSize,valid);
			addmat(cnn->C3->v[i],cnn->C3->v[i],outSize,mapout,outSize);
			for(r=0;r<outSize.r;r++)
				free(mapout[r]);
			free(mapout);
		}
		for(r=0;r<outSize.r;r++)
			for(c=0;c<outSize.c;c++)
				cnn->C3->y[i][r][c]=activation_Sigma(cnn->C3->v[i][r][c],cnn->C3->basicData[i]);
	}

和C1一样,最后将x放入Sigmod中

得到12个8*8的二值图像

五 S4池化层

和S2一样,使用平均池化

Ho=Wo=(H−F+2×P)/S+1=(高−卷积核的边长+2×图像边扩充大小)/滑动步长+1

=(8−2+2×0)/2+1=4   S2,没有扩充,且滑动步长为2,padding=0,stride=2

即经过池化层,输出的是

12个 4*4像素的图像

六 O5输出层

这一层是将上一层的结果直接展开

变成一维数组,数组大小为12*4*4=192

代码如下:

	// 输出层O5的处理
	// 首先需要将前面的多维输出展开成一维向量
	float* O5inData=(float*)malloc((cnn->O5->inputNum)*sizeof(float)); 
	for(i=0;i<(cnn->S4->outChannels);i++)
		for(r=0;r<outSize.r;r++)
			for(c=0;c<outSize.c;c++)
				O5inData[i*outSize.r*outSize.c+r*outSize.c+c]=cnn->S4->y[i][r][c];

	nSize nnSize={cnn->O5->inputNum,cnn->O5->outputNum};
	nnff(cnn->O5->v,O5inData,cnn->O5->wData,cnn->O5->basicData,nnSize);
	for(i=0;i<cnn->O5->outputNum;i++)
		cnn->O5->y[i]=activation_Sigma(cnn->O5->v[i],cnn->O5->basicData[i]);
	free(O5inData);
void nnff(float* output,float* input,float** wdata,float* bas,nSize nnSize)
{
	int w=nnSize.c;
	int h=nnSize.r;
	
	int i;
	for(i=0;i<h;i++)
		output[i]=vecMulti(input,wdata[i],w)+bas[i];
}
// 单层全连接神经网络的前向传播
float vecMulti(float* vec1,float* vec2,int vecL)// 两向量相乘
{
	int i;
	float m=0;
	for(i=0;i<vecL;i++)
		m=m+vec1[i]*vec2[i];
	return m;
}

该层数据结构如下:

// 输出层 全连接的神经网络
typedef struct nn_layer{
    int inputNum;   //输入数据的数目
    int outputNum;  //输出数据的数目

    float** wData; // 权重数据,为一个inputNum*outputNum大小
    float* basicData;   //偏置,大小为outputNum大小

    // 下面三者的大小同输出的维度相同
    float* v; // 进入激活函数的输入值
    float* y; // 激活函数后神经元的输出
    float* d; // 网络的局部梯度,δ值

    bool isFullConnect; //是否为全连接
}OutLayer;

这里的性质相当于BP的隐藏层与输出层,全连接

输出为192个神经元,输出为10个神经元

参数192 *10 + 10 * 1= 1930   由代码可见,这层的偏置项是,每一个输出都与一个偏置项

连接数 10 * 192 + 10 * 1 = 1930

每次的w权重和b是不一样的,故是10*192+10*1=1930

代码的思想如上图

上图中有一点不一样的地方在于b使用了两次,可能loss会高一点,但是不会影响算法整体

b首先参与了全连接的 x = W*X+B 运算,又参与了 p = sigmod(x+b)的运算

至此,前向传播解析结束

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神经网络的核心
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值