LeNet 详解(修订中)
* 那么问题来了,如果我们要用程序来实现这些神经网络的话,使用什么样的形式去实现它呢?
* 1---结构体+结构体指针
* 2---类
* 总之,是一个比较困难的事情.现在caffe为我们提供了一种很方便的方法:使用网络配置文件去实现它,如果拿Mnist
* 手写数字识别所使用的CNN神经网络模型LeNet来说的话,它的神经网络模型的描述文件就是/home/wei/caffe/examples
* /mnist/文件夹下面的:lenet_train_test.prototxt
* (二)LeNet模型的详解
* 在我们具体看这个网络模型描述文件之前,我们首先使用caffe的可视化程序,将这个网络模型画出来.我们使用/home
* /wei/caffe/python/文件夹下的draw_net.py这个python脚本程序.
* 具体的命令如下所示:
* sudo python ./python/draw_net.py ./examples/mnist/lenet_train_test.prototxt ./examples/mnist/lenet_train_test.jpeg
OK,在开始讲解之前我们先说明几个问题:
1、输入的图片大小是28*28,;
2、我们将分三部分讲解,因为三部分计算方式不同;
3、由于偏置量b的个数与卷积核的个数相同,因此我们讲解的主要是权重,偏置量个数加上就可以了。
1、第一个卷积conv1,之所把第一个卷积单独拿出来,是因为他和后面的卷积计算方式不同,他训练参数个数计算并不关心输入,
这里的数据就是指data层中batch_size大小。也可以说第一个卷基层并不关心特征组合,只是提取特征。
在每一个卷积层中都以一个参数num_output,这个参数怎么理解呢?
两种理解方式1、卷积的种类个数;2、输出特征图的个数,我么可以认为一种卷积核提取一种特征,然后输出一张特征。
由于第一个卷积层只是简单的提取特征,并没有进行特征组合,因此训练参数个数计算只是num_output*kernel_size^2.
这里怎么理解呢?(由于我不会画图,需要大家一点想象力)假设我们的输入有5张图,num_output=3,kernel_size=5。
没有进行特征组合,只是简单提取特征,指的是一种卷积核对5张图的同一区域使用相同的权重进行卷积计算,这样每幅图使用相同的卷积核就能提取到相同的特征,然后相同的特征组成一张特征图。
2、第二个卷积至全连接层之间的卷积,这些卷积层的训练参数个数和输入特征图的数量有关,因为这些卷积层需要进行特征组合。
举个例子:conv1的num_output=20,说明卷积1层输出了20个特征图,那么卷积2层的输入就是20。conv2的num_output=50,kernel_size=5,那么计算公式是20*50*5*5.
为什么这些卷积层的训练个数和输入的特征图的数量有关呢?重点还是在特征组合。
输入的20个特征图,每个特征图代表一种特征,如果我们给每种特征不同的权重那是不是就进行了特征组合呢?
conv2的卷积核是5*5,对20个特征图进行卷积,那就会有20组(5*5)个连接(每张特征图是一组),
如果这20组卷积核的权重相同,那就回到了第一个卷积层的情况,没有对20个特征进行组合,因为权重相同嘛!
只能看成简单的相加,如果20组权重不同,是不是就进行了线性相加了呢?所以对于一个卷积核(5*5)我们要学习的参数不是25个,而是25*20个。说到这里我相信你应该已经明白了吧!
3、全连接层,全连接层就是普通的神经网络,全连接层的num_output和卷积层中num_output的理解不同,全连接层的num_output应该看成神经元的个数。
3.1、这里要细分一下,先说IP1也就是第一个全连接层。先讲一下ip1的输入,比如最后一个卷积层的num_output=50,那么IP1的输入是50吗?
注意这里不是,要理解这个问题,我们只需将全连接层看成是一些列的普通神经网络就可以。
比如IP1的num_output=500,也就是有500个神经元,每个神经元都和输入的每一个像素相连,
最后一个卷积层输出了50个特征图,每个特征图大小是4*4(输入图像是28*28)那么每个神经元连接的个数就是50*16=800个,
也就有800个参数需要学习。总共有500个神经元,因此对于IP1层共需要学习800*500=400,000个参数。
3.2、对于iP2层,iP2的输入就是IP1的输出了,因为IP1输出的不是图像了(或矩阵)而是500个数字。
比如ip2的num_output=10,也就是输出数据500维,输出10维的普通神经网络,那么需要学习的参数就是500*10=5000个。
以上,只是我的个人见解,如果有错误,欢迎大家指正!
每一层的blob shape,每一层的维数dims = c*h*w
# each output is (batch size, feature dim, spatial dim) [(k, v.data.shape) for k, v in solver.net.blobs.items()] [('data', (64, 1, 28, 28)), ('label', (64,)), ('conv1', (64, 20, 24, 24)), ('pool1', (64, 20, 12, 12)), ('conv2', (64, 50, 8, 8)), ('pool2', (64, 50, 4, 4)), ('ip1', (64, 500)), ('ip2', (64, 10)), ('loss', ())]
每一层的参数量
# just print the weight sizes (not biases) [(k, v[0].data.shape) for k, v in solver.net.params.items()] [('conv1', (20, 1, 5, 5)), ('conv2', (50, 20, 5, 5)), ('ip1', (500, 800)), ('ip2', (10, 500))]
//***********************************************************************************************
name: "LeNet" //[1]网络(Net)的名称为:LeNet
/***********************************************************************************************
*模块1:
* 1--数据层----Data Layer
* 2--该数据层只在[训练]阶段有效
***********************************************************************************************/
layer { /****[1]定义一个数据层****/
name: "mnist" //[1]数据层的名字为--mnist
type: "Data" //[2]层的层类型:Data(数据层)(数据库作为输入)
top: "data" //[3]数据层的输出blob有两个:data,label(对应生成的CNN图看)
top: "label"
include { //[4]include里面的数据说明,该层只在训练阶段有效
phase: TRAIN
}
transform_param { //[5]数据预处理,转换参数的定义
scale: 0.00390625 //[5]特征归一化系数,将范围为[0,255]的MNIST数据归一化为[0,1]
}
data_param { //[6]数据层的参数
source: "examples/mnist/mnist_train_lmdb"//[1]由于该数据层的数据来源是数据库(由层类型Data指定),
// 因此,source对应的就是数据库LMDB的路径,也就是训练
// 数据和测试数据的path
batch_size: 64 //[2]批量数目,表示caffe一次从数据库LMDB读入的图片的数量
backend: LMDB //[3]数据库的类型说明区别于LevelDB数据库
}
}
/***********************************************************************************************
*模块2:
* 1--数据层----Data Layer
* 2--一个新的数据层,名字也叫做mnist,输出的blob也是data和label,但是这个数据层只在分类阶段有效,Test
* 3--图片大小28*28
***********************************************************************************************/
layer {
name: "mnist"
type: "Data"
top: "data"
top: "label"
include {
phase: TEST
}
transform_param {
scale: 0.00390625
}
data_param {
source: "examples/mnist/mnist_test_lmdb"
batch_size: 100
backend: LMDB
}
}
/***********************************************************************************************
*模块3:
* 1--第一个卷积层---Convolution
* 2--定义一个新的卷积层,卷积层的输入blob为data;输出blob为conv1
* 3--Convolution层,使用一系列可训练的卷积核(相当于空间滤波的滤波算子)对输入图像进行卷积操作,每组
* 卷积核生成输出图像中的一个特征图(相当于对输入图像,使用20个不同的滤波算子(卷积)进行20次卷积
* 之后生成的20张经过滤波的特征图)
* 4--输出图片大小:24*24
* 输入:n*c0*w0*h0 => 输出:n*c1*w1*h1
* c1=num_output=20
* w1=(w0+2*pad-kernel_size)/stride+1=(28+2*0-5)/1+1=24
* h1=(h0+2*pad-kernel_size)/stride+1=(28+2*0-5)/1+1=24
* 5--num_output卷积特征量,人为指定,但必须与上一层的不同
* 6--参数量,param_num=(kernel_size*kernel_size+1)*num_output = (5*5+1)*20=520
* 7--连接量,link_num=param_num*(w1*h1)=520*(24*24)=299520
***********************************************************************************************/
layer {
name: "conv1"
type: "Convolution"
bottom: "data" //[1]卷积层的输入blob为data
top: "conv1" //[2]卷积层的输出blob为conv1
param { //[3]卷积层的:权值学习速率倍乘因子,1表示,保持与全局参数一致
lr_mult: 1
}
param { //[4]卷积层的:偏置项的学习速率倍乘因子,是全局参数的2倍
lr_mult: 2
}
convolution_param { //[5]卷积层的计算参数
num_output: 20 //[1]输出feature map的数目为20,对应的也就是卷积核的数量
kernel_size: 5 //[2]卷积核的尺寸为:5*5
stride: 1 //[3]卷积核在输入图片上滑动的步长为:1
weight_filler { //[6]指定权值的初始化方案为:xavier
type: "xavier"
}
bias_filler { //[7]偏执项的初始化方案为:constant,默认为0
type: "constant"
}
}
}
/***********************************************************************************************
*模块4:
* 1--第一个池化层---pool1
* 2--定义一个下采样层(池化层),这个池化层的输入blob为conv1,输出blob为pool1
* 3--输出图片的大小===12*12
* 输入:n*c*w0*h0 => 输出:n*c*w1*h1
* c=20
* w1=(w0+2*pad-kernel_size)/stride+1=(24+2*0-2)/2+1=12;
* h1=(h0+2*pad-kernel_size)/stride+1=(24+2*0-2)/2+1=12;
* 4--参数量,param_num=c*(1+1)=20*2=40 <== 1+1 为一个weight和一个bias
* 5--连接量,link_num=w1*h1*(kernel_size*kernel_size+1)*c=12*12*(2*2+1)*20=14400
***********************************************************************************************/
layer {
name: "pool1"
type: "Pooling"
bottom: "conv1"
top: "pool1"
pooling_param { //[1]池化层(下采样)的参数
pool: MAX //[1]目前提供了三种池化的方法:最大值池化,均值池化,随机池化
// 很明显,该池化层使用了最大值池化MAX
kernel_size: 2 //[2]指定池化窗口的宽度和高度:2*2
stride: 2 //[3]指定池化窗口在输入数据上滑动的步长为:2
}
}
/***********************************************************************************************
*模块5:
* 1--第二个卷积层:conv2
* 2--该卷积层的输入blob为pool1,输出blob为conv2
* 3--注意:该卷积层输出的feature map(特征图的数量)为:50
* 4--输出图片的大小为:8*8
* 输入:n*c0*w0*h0 => 输出:n*c1*w1*h1
* c1=num_output=50
* w1=(w0+2*pad-kernel_size)/stride+1=(24+2*0-5)/1+1=8
* h1=(h0+2*pad-kernel_size)/stride+1=(24+2*0-5)/1+1=8
* 5--num_output卷积特征量,人为指定,但必须与上一层的不同
* 6--参数量,param_num = a1 * (b1 * kernel_size * kernel_size + 1)
* + a2 * (b2 * kernel_size * kernel_size + 1)
* + ...
* a1 + a2 + ... = c1 = 50
* b1, b2, ... belong to B
* 1 <= B <= c0 = 20
* Eg. param_num = 19 * (12 * 25 + 1)
* + 30 * (7 * 25 + 1)
* + 1 * (20 * 25 + 1)
* = 11500
* 7--连接量,link_num=param_num*(w1*h1)=param_num*(8*8)=64*param_num
***********************************************************************************************/
layer {
name: "conv2"
type: "Convolution"
bottom: "pool1"
top: "conv2"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
convolution_param {
num_output: 50
kernel_size: 5
stride: 1
weight_filler {
type: "xavier"
}
bias_filler {
type: "constant"
}
}
}
/***********************************************************************************************
*模块6:
* 1--第二个池化层:pool2
* 2--该池化层的输入blob为conv2,输出blob为pool2
* 3--输出图片的大小===4*4
* 输入:n*c*w0*h0 => 输出:n*c*w1*h1
* c=50
* w1=(w0+2*pad-kernel_size)/stride+1=(8+2*0-2)/2+1=4;
* h1=(h0+2*pad-kernel_size)/stride+1=(8+2*0-2)/2+1=4;
* 4--参数量,param_num=c*(1+1)=50*2=100 <== 1+1 为一个weight和一个bias
* 5--连接量,link_num=w1*h1*(kernel_size*kernel_size+1)*c=4*4*(2*2+1)*50=4000
***********************************************************************************************/
layer {
name: "pool2"
type: "Pooling"
bottom: "conv2"
top: "pool2"
pooling_param {
pool: MAX
kernel_size: 2
stride: 2
}
}
/***********************************************************************************************
*模块7:
* 1--第一个全连接层
* 2--该层的输入blob为:pool2,输出blob为iP1
* 3--注意:全连接层的的输出节点数(num_output==500)可以理解为滤波器的个数(滤波算子的个数),对应的也
* 就是输出特征图的个数
* 4--参数量,param_num=c1*(c0*(w0*h0)+1)=500*(50*(4*4)+1)=400500
***********************************************************************************************/
layer {
name: "ip1"
type: "InnerProduct"
bottom: "pool2"
top: "ip1"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
inner_product_param { //[1]全连接层的参数:
num_output: 500 //[1]该层的输出元素的个数为:500
weight_filler { //[2]指定全连接层的初始化方案:xavier
type: "xavier"
}
bias_filler {
type: "constant"
}
}
}
/***********************************************************************************************
*模块8:
* 1--新的非线性层(激活函数)(规整化线性单元),此激活层采用的激活函数为:RELU
* 2--该激活层的输入blob为iP1,输出blob也为iP1
* 3--该(规整化线性单元)激活层的作用为:对全连接层的每一个输出数据进行判断,当x>0时,RELU的输出为x,
* 根据X的大小,说明这个单元的激活程度(兴奋程度);如果x<=0,则这个信号(特征图)被完全抑制
***********************************************************************************************/
layer {
name: "relu1"
type: "ReLU"
bottom: "ip1"
top: "ip1"
}
/***********************************************************************************************
*模块9:
* 1--第二个全连接层InnerProduct
* 2--该层的输入blob为iP1,输出blob为ip2
* 3--参数量,param_num=c1*(c0*(w0*h0)+1)=10*(500*(4*4)+1)=80010
***********************************************************************************************/
layer {
name: "ip2"
type: "InnerProduct"
bottom: "ip1"
top: "ip2"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
inner_product_param { //[1]全连接层的计算参数
num_output: 10 //[1]该层的输出为10个特征,对应0--9这10类数字
weight_filler { //[2]该层在网络初始化的初始化方案为:xavier
type: "xavier"
}
bias_filler { //[3]给该层添加偏置项,偏置项网络的初始化方案为:constant
type: "constant"
}
}
}
/***********************************************************************************************
*模块10:
* 1--Accuracy---分类准确率层
* 2--Accuracy层的作用:该层用来计算网络输出相对于目标值的准确率
* 3--该层的输入blob为iP2和label,输出blob为accuracy
* 4--注意:记住该层只在Test(测试)阶段有效,并且,它并不是一个Loss层,所以这次没有BP操作
***********************************************************************************************/
layer {
name: "accuracy"
type: "Accuracy"
bottom: "ip2"
bottom: "label"
top: "accuracy"
include {
phase: TEST
}
}
/***********************************************************************************************
*模块11:
* 1--Loss层,损失层
* 2--层类型:SoftnaxWithLoss---softmax损失层一般用于计算[多分类问题]的损失,在概念上等同于softmax
* 层后面跟一个多变量的logistic回归损失层,但能提供更稳定的梯度
***********************************************************************************************/
layer {
name: "loss"
type: "SoftmaxWithLoss"
bottom: "ip2"
bottom: "label"
top: "loss"
}
参考
[1] https://github.com/BVLC/caffe/tree/master/examples/mnist
[2] http://blog.csdn.net/dcxhun3/article/details/46878999
[3] http://yann.lecun.com/exdb/lenet/
[4] https://allenlu2007.wordpress.com/2015/11/28/mnist-database-%E6%89%8B%E5%AF%AB%E6%95%B8%E5%AD%97%E8%BE%A8%E8%AD%98/
[5] http://blog.csdn.net/strint/article/details/44163869
[6] http://blog.csdn.net/qiaofangjie/article/details/16826849
[7] http://blog.csdn.net/zouxy09/article/details/9993371
[8] http://blog.csdn.net/celerychen2009/article/details/8973218
[9] http://blog.csdn.net/zouxy09/article/details/9993371
[10] http://blog.csdn.net/celerychen2009/article/details/8973218
[11] http://blog.csdn.net/u010025211/article/details/49995035
浙公网安备 33010602011771号