网络模型的结构以及求解器内容(记录一些超参数)记录在prototxt文件内,下面主要以LeNet-5模型的描述文件lenet_train_test.prototxt和lenet_solver.prototxt为基础,说明一些参数的含义。
网络模型描述文件
【定义网络名称】
## Define the MNIST Network name: "LeNet" // 网络(Net)的名称
【数据层】
### Writing the Data Layer layer { // 定义一个层(Layer) name: "mnist" // 层的名称为mnist type: "Data" // 层的类型为数据层 top: "data" // 层的输出Blob有两个:data和label top: "label" include { phase: TRAIN // 该层参数只在训练阶段有效 } transform_param { scale: 0.00390625 // 数据变换使用的数据缩放因子 # mean_file: "../../examples/cifar10/mean.binaryproto" // 均值文件;(图像像素值-均值) } data_param { // 数据层参数 source: "examples\\mnist\\mnist_train_lmdb" //LMDB路径,一定要配置好相对路径 batch_size: 64 // 批量数据,一次读取64张图 backend: LMDB // 数据格式为LMDB } } layer { // 一个新数据层,名字也叫mnist,输出blob也是data和label,但这里定义的参数只在分类阶段有效 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 } } layer { name: "data" type: "Input" top: "data" input_param { shape: { dim: 10 dim: 3 dim: 227 dim: 227 } } }
数据层中重要参数:
- 数据层的数据类型有很多,常见有type:"Data"或type:"Input";前者是在训练模型阶段使用,在caffe中必须是LMDB或LEVELDB数据类型;后者是输入层的定义,一般作为训练好的模型的输入层使用,可以定义输入图像的大小等;详细参考: Caffe实战(十四):数据层及参数设置
- transform_param:包含一些对数据进行尺度变换等的参数;
- data_param:主要设置数据格式,路径,以及batch_size;
- input_param:输入数据的shape
【卷积层】
### Writing the Convolution Layer layer { // 定义一个新的卷积层conv1,输入blob为data,输出为conv1 name: "conv1" type: "Convolution" bottom: "data" top: "conv1" param { lr_mult: 1 // 权值学习速率倍乘因子,1倍表示保持与全局参数一致;该层lr=lr_mult*base_lr decay_mult: 1 // 权重的衰减值的倍乘因子 } param { lr_mult: 2 // bias学习速率倍乘因子,是全局参数的2倍;一般偏置项的学习率是权值学习率的两倍。 decay_mult: 1 // 权重的衰减值倍乘因子 } convolution_param { // 卷积计算参数 num_output: 20 // 输出feature map数目为20 kernel_size: 5 // 卷积核尺寸,5*5 stride: 1 // 卷积输出跳跃间隔(即滑动窗口平移步进量),1表示连续输出,无跳跃 # pad:2 // 扩充边缘,默认为0,不扩充。 扩充的时候是左右、上下对称的,比如卷积核的大小为5*5,那么pad设置为2,则四个边缘都扩充2个像素,即宽度和高度都扩充了4个像素,这样卷积运算之后的特征图就不会变小。也可以通过pad_h和pad_w来分别设定。 bias_term: ture // 是否开启偏置项,默认为true, 开启 # group:2 // 分组,默认为1组。如果大于1,我们限制卷积的连接操作在一个子集内。如果我们根据图像的通道来分组,那么第i个输出分组只能与第i个输入分组进行连接。 weight_filler { // 权值使用xavier填充器 type: "xavier" # type: "gaussian" # std: 0.0001 // 权值使用gaussian填充器 } bias_filler { // bias使用常数填充器,默认为0 type: "constant" value:0 //可以指定填充值 } } }
卷积层中重要参数:
- lr_mult是权值学习速率的倍乘因子,对于该层最终学习速率lr = lr_mult * base_lr,其中base_lr是基础学习速率;
- decay_mult对应的weight_decay(惩罚项的系数)的倍乘系数。这个惩罚项是加入到损失函数中,防止过拟合的。可以针对不同的层设置不同的decay_mult(也就是说求取不同权值更新量时对应的惩罚项系数不同)。又或者类似与AlexNet模型中作为权值更新的正则项。(参考 【深度学习系列】大话CNN经典模型:AlexNet)
在caffe中,卷积运算就是先对数据进行im2col操作,再进行内积运算(inner product)。这样做,比原始的卷积操作速度更快。(可以学习这种处理方式)
卷积层输出特征图(feature map)大小跟卷积核大小(kernel)、填充(padding)和步长(stride)有关,输入特征图大小与输出特征图大小之间的关系如下所示:
注意池化过程是向上取整。
如果设置pad=(kernel_size-1)/2,则运算后,宽度和高度不变。
【im2col层】
im2col是一个辅助操作,用来实现图像到“列向量”的转换。im2col用在CAFFE早期卷积的矩阵乘法中,即将所有图像块组成一个矩阵。
在caffe中,执行卷积操作时,将图像中与卷积核作用的图像块写成列向量,然后将这些列向量按行的方向依次排开组成一个二维的矩阵,同时,每组卷积核写成一个行向量,多个输出通道对应的多组卷积核按列的方向依次排列形成一个二维矩阵,这样,卷积操作转化为矩阵乘法操作。
【ReLU层】
### Writing the ReLU Layer layer { // 新的非线性层,用ReLU方法 name: "relu1" type: "ReLU" bottom: "conv1" top: "conv1" }
激活层除了Sigmoid层,TanH层,AbsVal层(绝对值)之外,还有
Power层使用函数(shift + scale * x) ^ power 计算每个输入数据 x 的输出;
BNLL 层使用函数 log(1 + exp(x))计算每个输入数据 x 的输出。
【池化层】
### Writing the Pooling Layer layer { // 定义新的下采样层pool1,输入blob为conv1,输出blob为pool1 name: "pool1" type: "Pooling" bottom: "conv1" top: "pool1" pooling_param { // 下采样参数 pool: MAX // 使用最大值下采样方法;有三种池化方法:MAX,AVG,STOCHASTIC kernel_size: 2 // 下采样窗口尺寸2*2 stride: 2 // 下采样输出跳跃间隔2*2(每次平移的步进量是2) } }
池化层的输出特征图大小计算公式为:
O= (i - kernel_size) / stride + 1
由于池化层不需要进行边界填充,即padding = 0;(其实跟卷积层计算输出特征图大小的公式从形式上是一致的)
注意:卷积层和池化层计算输出特征图大小的区别在于:
- 卷积层(i - kernel_size + 2*padding) / stride 是向下取整的;
- 池化层(i - kernel_size) / stride 是向上取整的。
Caffe中卷积层输出维度计算:
Caffe中池化层输出维度计算:
参考Caffe实战(十二):模型分类测试以及特征图和参数可视化示例(python)中的技巧,一般设置池化重叠结构可以提高训练精度,同时也会引入更多的噪声,为此,针对重叠的特征图可以采用均采样降低噪声的影响。所以一般比较好的策略是(三个卷积层为例):conv1-->max pool-->relu-->conv2-->relu-->avg pool-->conv3-->relu-->avg pool
【局部归一化层】
### Writing the Local Response Normalization(LRN) Layer 局部归一化层 layer { name: "norm1" type: "LRN" bottom: "pool1" top: "norm1" lrn_param { local_size: 3 // 对于cross channel LRN,表示需要求和的channel的数量;对于within channel LRN,表示需要求和的空间区域的边长。默认为5 alpha: 5e-05 beta: 0.75 norm_region: WITHIN_CHANNEL // 默认为ACROSS_CHANNELS。有两个选择,ACROSS_CHANNELS表示在相邻的通道间求和归一化。WITHIN_CHANNEL表示在一个通道内部特定的区域内进行求和归一化。与前面的local_size参数对应。 } }
LRN即可以进行跨通道归一化求和,也可以在通道内归一化求和(可以看做普通的二维窗口内归一化)
【全连接化层】
### Writing the Fully Connected Layer layer { // 新的全连接层,输入blob为pool2,输出blob为ip1 name: "ip1" type: "InnerProduct" bottom: "pool2" top: "ip1" param { lr_mult: 1 # decay_mult: 250 // 损失函数中正则项的的系数,防止过拟合???? } param { lr_mult: 2 } inner_product_param { // 全连接层参数 num_output: 500 // 该层输出元素个数为500 weight_filler { type: "xavier" } bias_filler { type: "constant" } } }
【准确率层】
### Writing the Accuracy Layer layer { // 分类准确率层,只在Testing阶段有效,输入blob为ip2和label,输出blob为accuracy,该层用于计算分类准确率。统计所有测试数据中的分类正确的概率 name: "accuracy" type: "Accuracy" bottom: "ip2" bottom: "label" top: "accuracy" include { phase: TEST } accuracy_param { top_k: 5 // 默认为top_1,添加该项后,选择测试top_k准确率。 } }
【损失层】
### Writing the Loss Layer layer { // 损失层,损失函数采用SoftmaxLoss,输入blob为ip2和label,输出blob为Loss name: "loss" type: "SoftmaxWithLoss" bottom: "ip2" bottom: "label" top: "loss" }
Layer中的loss_weight为每个Top Blob分配对损失函数的权重,每个Layer都有默认值,要么为0,表示不参与目标函数计算;要么为1,表示参与损失函数计算。注意主要表示的不是损失函数的权重大小,而是是否参与目标函数的计算。
【Reshape层】
在不改变数据的情况下,改变输入的维度。
层类型:Reshape
layer { name: "reshape" type: "Reshape" bottom: "input" top: "output" reshape_param { shape { dim: 0 # copy the dimension from below dim: 2 dim: 3 dim: -1 # infer it from the other dimensions } } }
有一个可选的参数组shape, 用于指定blob数据的各维的值(blob是一个四维的数据:ncw*h)。
- -dim:0 表示维度不变,即输入和输出是相同的维度。
- -dim:2 或 dim:3 将原来的维度变成2或3
- -dim:-1 表示由系统自动计算维度。数据的总量不变,系统会根据blob数据的其它三维来自动计算当前维的维度值 。
假设原数据为:64,3,28,28, 表示64张3通道的28*28的彩色图片,经过reshape变换:
reshape_param { shape { dim: 0 dim: 0 dim: 14 dim: -1 } }
输出数据为:64,3,14*56
【Dropout层】
Dropout是一个防止过拟合的trick。可以随机让网络某些隐含层节点的权重不工作。
layer { name: "drop7" type: "Dropout" bottom: "fc7-conv" top: "fc7-conv" dropout_param { dropout_ratio: 0.5 } }
只需要设置一个dropout_ratio就可以了。
【小结】
几点说明
- 定义的层默认是在训练和测试阶段都有效的,除非特殊说明在哪个阶段有效;
- 即使相同的层结构也可以设置不同的参数,这需要分开定义层内容,如输入data层;
- caffe中定义的都是最基本的层结构,比如卷积层仅仅进行卷积操作,全连接层只是进行加权求和,并不包括非线性操作,如果需要还需要单独定义非线性层,不要与理论研究混淆;这样做的好处是,可以通过最基本的模块组合出不同的网络结构。也可以自己定义复杂的层结构;
-
loss层是计算损失函数的,评估真实值和预测结果之间的差异(在模型训练阶段有用);而accuracy是计算分类准确率的,在多个测试中分类正确的概率(在测试阶段输出);二者的定义以及目的是不同的,不要混淆。
超参数描述文件
网络训练过程,需要指定求解器类型,而训练超参数内容存在lenet_solver.prototxt文件中。
## Define the MNIST Solver Check out the comments explaining each line in the prototxt `$CAFFE_ROOT/examples/mnist/lenet_solver.prototxt`: # The train/test net protocol buffer definition # 用于训练/预测的网络描述文件(ProtoBuffer文件格式) net: "examples/mnist/lenet_train_test.prototxt" # test_iter specifies how many forward passes the test should carry out. # In the case of MNIST, we have test batch size 100 and 100 test iterations, # covering the full 10,000 testing images. # 也测阶段迭代次数。在MNIST例程下,预测样本组(test batch)大小为100; # 这里设置预测阶段迭代次数为100可以覆盖全部10000个测试集。 test_iter: 100 # Carry out testing every 500 training iterations. # 训练时每迭代500次,进行一次预测 test_interval: 500 # The base learning rate, momentum and the weight decay of the network. # 网络的基础学习速度、冲量(遗忘因子)和衰减量(损失函数中惩罚项系数,即正则项的系数) base_lr: 0.01 momentum: 0.9 weight_decay: 0.0005 # The learning rate policy # 学习速率的衰减策略 lr_policy: "inv" gamma: 0.0001 power: 0.75 # Display every 100 iterations # 每经过100次迭代,在屏幕上打印一次运行log display: 100 # The maximum number of iterations # 最大迭代次数 max_iter: 10000 # snapshot intermediate results # 每5000次迭代打印一次快照 snapshot: 5000 snapshot_prefix: "examples/mnist/lenet" # solver mode: CPU or GPU # caffe求解模式为CPU还是GPU solver_mode: GPU # 求解器类型,默认是SGD type: "AdaDelta" # 迭代次数,控制权值更新的次数,默认为1 iter_size:1
【测试迭代】
test_iter: 在测试的时候,需要迭代的次数,即test_iter* batchsize(测试集的)=测试集的大小。
test_interval:interval是区间的意思,该参数表示:训练的时候,每迭代500次就进行一次测试。
caffe在训练的过程是边训练边测试的,训练过程中每500次迭代(也就是32000个训练样本参与了计算,batchsize为64),计算一次测试误差。计算一次测试误差就需要包含所有的测试图片(这里为10000),这样可以认为在一个epoch里,训练集中的所有样本都遍历了一遍,但测试集的所有样本至少要遍历一次,至于具体要多少次,也许不是整数次。
一定要注意,测试的时候,是对所有测试集数据进行测试,而不是对该次迭代的batch_size数据进行测试。
【权值更新迭代次数】
iter_size:默认为1.若>1,则进行iter_size次forward和backward(考虑batch_size)才进行一次梯度更新,而此时梯度保存的是两次iteration的总和,因此需要归一化梯度。
iter_size这个参数的目的:若显卡显存较小,无法装载大的batch size,可以利用iter_size来实现模拟大batch size的作用。
for example:batch size = 128,iter_size=1近似等价于batch size = 64,iter_size=2。
【学习速率更新测量】
caffe中支持的学习速率更新策略(lr_policy)
- fixed(固定学习速率):always return base_lr.
- step(步进衰减): return base_lr * gamma ^ (floor(iter / step))
- exp(指数衰减): return base_lr * gamma ^ iter
- inv(倒数衰减): return base_lr * (1 + gamma * iter) ^ (- power)
- multistep(多步衰减): similar to step but it allows non uniform steps defined by stepvalue
- poly(多项式衰减): the effective learning rate follows a polynomial decay, to be zero by the max_iter. return base_lr (1 - iter/max_iter) ^ (power)
- sigmoid(sigmoid衰减): the effective learning rate follows a sigmod decay return base_lr ( 1/(1 + exp(-gamma * (iter - stepsize))))
【求解器类型】
Net:完成数据前向传播,误差反向传播。
Solver(求解器):有监督的优化过程,利用每层的梯度生成权值增量。求解器负责对模型优化---->使得损失函数达到全局最小。
caffe中求解器类型有:随机梯度下降法SGD(最常用)、AdaDelta、自适应梯度法(ADAGRAD)、Adam、Nestenov加速梯度法(NAG)和RMSprop。
Vt其实就是上一次更新的权值增量。
超参数的调节:
- 初始学习速率lr=0.01.当损失函数不再下降时,对lr进行固定常数的衰减(如乘上0.1),如此反复训练;
- 遗忘因子momentum=0.9.遗忘因子可以随着迭代对权值增量进行平滑,这样可以让基于SGD算法的深度学习系统更加稳定,收敛更快。如果上一的momentum与这一次的负梯度deltaL的方向相同,则这次下降的幅度会很大,加快收敛过程。
- 需要指出的是,这里给出的base_lr以及weight_decay都是基础值,最终使用时不一定是这个值。例如,在卷积层时可以设置lr_mult和decay_mult倍乘因子,最终的lr=bas_lr*lr_mult,当然不同的学习策略计算的结果不同。
详细参考: 【Caffe实战】求解器优化过程-Solver