Caffe构建神经网络模型
Caffe是一个非常强大的深度学习框架,此处记录了博主在使用过程中的一些经验,没有覆盖到的内容查查官网和Github 上的issue问答应该能有帮助。另外附上Caffe的官方Tutorial。更新于2018.10.24。
更多内容,欢迎加入星球讨论。
文章目录
Caffe层的使用
这里给出了一些博主使用过的Caffe层的范例。
基础知识
在具体说明Caffe各层的应用范例之前,先给出能够帮助理解或后续可能用到的一些基础知识。
Caffe中的数据存储格式
Caffe中数据存储在blob里,尺寸为N x C x H x W,含义从左到右分别为Batch Size、通道数(特征层数)、高、宽。
权重更新公式
下面是Caffe中更新权重所使用的公式,方便理解各层中设置的参数:
w i = w i − ( b a s e _ l r ∗ l r _ m u l t ) ∗ d w i − ( w e i g h t _ d e c a y ∗ d e c a y _ m u l t ) ∗ w i w_i=w_i-(base\_lr*lr\_mult)*dw_i - (weight\_decay*decay\_mult)*w_i wi=wi−(base_lr∗lr_mult)∗dwi−(weight_decay∗decay_mult)∗wi
因此原则上当 l r _ m u l t lr\_mult lr_mult 和 d e c a y _ m u l t decay\_mult decay_mult 都设置为0时,该层神经网络的参数就不会更新了。但是从博主在使用过程中的经验来看,不能过度依赖这个方法控制参数是否更新,因为在实践的过程中,即使博主将损失的权重、 l r _ m u l t lr\_mult lr_mult 和 d e c a y _ m u l t decay\_mult decay_mult 都设置为0,该层的参数还是出现了变化。这个可能跟Caffe中更深层的代码实现方式有关。
参数初始化方法
Caffe中的filter.hpp提供了7种参数的初始化方法,分别为:常量初始化(constant)、高斯分布初始化(gaussian)、positive_unitball初始化、均匀分布初始化(uniform)、xavier初始化、msra初始化、双线性初始化(bilinear)。详细的说明可以参考参数初始化方法。
划重点:
- 下面卷积层中用到的msra初始化方法是专门针对激活函数为ReLU的层设计的,是基于“均值为0,方差为2的高斯分布”产生的。
- 偏置初始化中用到的constant初始化方法是用常数作为初始值,具体的值可以自己确定,默认为0。
卷积层(Convolution)
关于卷积层的更多说明可以查看卷积层官方文档。
layer {
name: "conv_layer_name" //为当前层取名,日后调用训练好的网络参数时,Caffe就通过这个区分应用哪一层的参数。因此,在构建deploy.prototxt时,也需要用相同的名字。
type: "Convolution" //定义当前层的属性,此处为卷积层。
bottom: "input_blob_name" //这里定义当前层的输入blob叫什么名字
top: "output_blob_name" //这里定义当前层的输出blob叫什么名字
param { //第一组param定义weights的参数
lr_mult: 1 //确定weights学习率要乘以的系数
decay_mult: 0 //确定weights衰减要乘以的系数
}
param { //第二组param定义bias的参数
lr_mult: 0
decay_mult: 0
}
convolution_param { //定义卷积层的参数
num_output: 128 //输出通道数
pad: 1 //padding尺寸
kernel_size: 3 //卷积核尺寸
stride: 1 //步长
weight_filler { //确定卷积层参数的初始化方法
type: "msra" //使用msra初始化
}
bias_filler { //确定偏置初始化方法
type: "constant" //使用常量初始化
}
engine: CUDNN
}
}
激活函数层(ReLU)
name、type、bottom、top等与卷积层相同,次数定义的层的类型为ReLU激活函数。关于激活函数的说明可以参考激活函数学习和官方文档。
layer {
name: "ReLU_name"
type: "ReLU"
bottom: "input_blob_name"
top: "output_blob_name"
relu_param {
negative_slope: 0.1 //定义激活函数在x负半轴的斜率
}
}
按位计算层(Eltwise)
Caffe中也提供了求解按位计算的层Eltwise,其可以计算按位相乘PROD、按位相加SUM、求最大值。
layer {
name: "Eltwise_layer_name"
type: "Eltwise"
bottom: "input_blob_name1"
bottom: "input_blob_name2" //Eltwise的两个输入
top: "output_blob_name"
eltwise_param { //定义Eltwise的参数
operation: SUM //定义操作为求和
coeff: 1 //第一个输入每个元素乘以的数
coeff: -1 //第二个输入每个元素乘以的数
}
}
级联层(Concat)
将各级输入按照通道级联。需要注意的是,所有要级联的层需要具有相同的h和w。
layer {
name: "concat_layer_name"
type: "Concat"
bottom: "input_blob_name1"
bottom: "input_blob_name2"
bottom: "input_blob_name3"
bottom: "input_blob_name4"
bottom: "input_blob_name5"
top: "output_blob_name"
}
消音层(Silence)
在Caffe中,如果一个层的输出没有被调用,该层的输出就会被默认为网络输出,print到终端上。为了防止中间产物被当作输出,需要用一个层“接住”这些输出,下面的层就是这个作用的,它不产生任何输出。
layer {
name: "Silence_name"
type: "Silence"
bottom: "blob_name"
}
平铺扩展层(Tile)
在Caffe中,如果希望对某一个blob实现任意维度的平铺扩展(通过复制blob中的值实现)则需要用到Tile层。Caffe.help和官网都有具体的信息。其中axis定义对哪一个维度进行平铺,而tiles则是在这个维度上平铺几倍。
layer {
name: "tile"
type: "Tile"
bottom: "blob1"
top: "blob2"
tile_param {
axis: 2
tiles: 32
}
}
平铺层(Flatten)
作用:将维度为N x C x H x W的bottom平铺成指定维度(默认N x (C x H x W))。
layer {
name: "Flatten"
type: "Flatten"
bottom: "input_blob"
top: "output_blob"
flatten_param {
axis: 1 //指定开始维度
end_axis: -1 //指定结束维度
}
}
Reshape层
reshape层将输入blob按照指定方式重新排列。需要注意的是,Reshape层与Flatten层相同,不会对blob中的数据做改动,只是改变blob的维度。如下面的例子会将尺寸为N x C x H x W的输入变成N x (H x W) x 1 x 1的输出。
具体地,shape中可以指定尺寸,几个dim就是几维,dim后面的数字就是该维度上的维数。特别地,0表示继承输入blob这个维度上的维数;-1表示自动计算,补全维数,使得元素总数在变换前后相同。
axis和num_axis指定输入blob的哪一部分参与重新排布,默认是整个blob。具体示例可以参见官网的说明。
layer {
name: "Reshape"
type: "Reshape"
bottom: "disp_gt_aug"
top: "disp_gt_aug_reshaped"
propagate_down: false
reshape_param {
shape {
dim: 0
dim: -1
dim: 1
dim: 1
}
}
axis: 1
num_axis: 3
}
Pooling层
在进行pooling计算的时候,Caffe提供了两种方式定义卷积核的大小:第一种直接用kernel_size或kernel_height/kernel_width定义卷积核大小;第二种是用全局的方式实现,即设定global_pooling为真。pooling的种类有max(MAX)、average(AVE)等。
layer {
name: "maxpool"
type: "Pooling"
bottom: "blob1"
top: "blob2"
pooling_param {
pool: MAX
pad: 0
global_pooling: true
}
}
Softmax层
首先明确Softmax函数的功能:Softmax将原始分类器的输出转化成不同类别之间的相对概率。比如,原始的输出为 V = [ − 3 , 2 , − 1 , 0 ] V=[-3,2,-1,0] V=[−3,2,−1,0],那么经过Softmax的处理后,数值转化为 S = [ 0.0057 , 0.8390 , 0.0418 , 0.1135 ] S=[0.0057, 0.8390, 0.0418, 0.1135] S=[0.0057,0.8390,0.0418,0.1135]。此时可以看到,被判断成第二类别的概率更大。Softmax函数定义为: S i = e V i ∑ i C e V i S_i=\frac{e^{V_i}}{\sum_i^Ce^{V_i}} Si=∑iCeVieVi。
layer {
name: "Softmax"
type: "Softmax"
bottom: "input_bottom"
top: "output_bottom"
softmax_param{
axis: 1 ##用于指定对哪一维做处理,默认为1,即处理Channels。
}
}
Argmax层
Argmax层用于找到某一维度上最大值所对应的位置,如按通道数找最大值,返回的值是每个位置 ( x , y ) (x,y) (x,y)上拥有最大值的Channel的位置 C C C。
layer {
name: "Argmax"
type: "ArgMax"
bottom: "input_blob"
top: "output_blob"
argmax_param{
axis: 1
out_max_val: 1
top_k: 1
}
}
损失计算层
L1Loss
name、type、bottom、top等与卷积层相同,type定义的层的类型为L1损失层。损失的种类和具体说明参考官方文档。
layer {
name: "loss_name"
type: "L1Loss"
bottom: "input_blob_name1"
bottom: "input_blob_name2" //损失层需要2个输入
top: "output_blob_name"
loss_weight: 1 //损失权重,取决于损失是否回传(不回传设为0),回传的话以多少比例影响参数(损失需要乘以的权重)
l1_loss_param { //定义L1损失的参数
l2_per_location: false //是否计算2维损失(我的理解)
normalize_by_num_entries: true //是否根据有效输入求平均(因为在计算时,有的位置上的数值是无效的,比如nan,因此在计算损失的时候需要将这些数值的影响去掉)
}
}
Softmax with Loss
用于计算Softmax后的损失,其中前半部分的参数与Softmax相同,后半部分的参数见下面示例的注释。这个层特殊的地方在于:对于分类问题,通常是一个样本对应一个标量(label),然而softmax with loss则支持更高维度的类别标签。比如,当bottom[0]的维度为n x c x h x w,此时,如果将softmax的参数axis设为1,那么对应的label尺寸应为n x 1 x h x w,其中C的位置上存有一个数值范围在区间[0, c-1]的整数。至于后面损失的计算,则采用相同的处理。
layer {
name: "SoftmaxWithLoss"
type: "SoftmaxWithLoss"
bottom: "prediction"
bottom: "label"
propagate_down: true
propagate_down: false
top: "SoftmaxWithLoss"
softmax_param {
axis: 1 //将Channels这个维度视为互相独立的概率
}
loss_param {
ignore_label: 0 //int类型,默认为空。如果指定值,则所有样本都参与反向计算;否则,给定label的样本不参与损失计算,反向传播时梯度直接设为0。
normalize: 1 //bool类型。若设为1,损失会除以参与计算的样本总数,否则等于直接求和。
normalization: FULL //enum类型,默认为VALID,具体情况有FULL、VALID、BATCH_size和NONE四种,详细说明见下面的解释。
}
}
关于Normalization参数的设置说明:
enum NormalizationMode {
// Divide by the number of examples in the batch times spatial dimensions.
// Outputs that receive the ignore label will NOT be ignored in computing the normalization factor.
FULL = 0;
// Divide by the total number of output locations that do not take the
// ignore_label. If ignore_label is not set, this behaves like FULL.
VALID = 1;
// Divide by the batch size.
BATCH_SIZE = 2;
//
NONE = 3;
}
需要注意的是:
- 若未设置normalization,但是设置了normalize,则有如下对应关系:
normalize = 1 等同于 归一化方式为VALID;
normalize = 0 等同于 归一化方式为BATCH_SIZE。 - 若设置了normalization,则归一化方式由normalization确定,不再考虑normalize。
实验中用到的自定义层
在实验过程中,除了Caffe官方提供的层以外,往往需要用到用户自定义的层以实现具体的功能。这一部分列举了一些博主在实验过程中用到的Caffe层。
输入层
layer {
name: "CustomData_layer_name"
type: "CustomData"
top: "output_blob_name1"
top: "output_blob_name2"
top: "output_blob_name3"
include {
phase: TRAIN
}
data_param { //定义参数
source: "path/to/your/data"
batch_size: 4
backend: LMDB //定义输入数据类型
rand_permute: true
rand_permute_seed: 77
slice_point: 3 //按通道切割成几部分,定义切割的位置
slice_point: 6
encoding: UINT8
encoding: UINT8
encoding: UINT16FLOW
verbose: true
}
}
数据扩张层
数据扩张层的参数过多,不在这里详细列出来了,如果需要可以参考DispNet中提供的代码。