参考博客:
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)的运算
至此,前向传播解析结束