.数据层及参数

要运行caffe,需要先创建一个模型(model),如比较常用的Lenet,Alex等, 而一个模型由多个屋(layer)构成,每一屋又由许多参数组成。所有的参数都定义在caffe.proto这个文件中。要熟练使用caffe,最重要的就是学会配置文件(prototxt)的编写。

层有很多种类型,比如Data,Convolution,Pooling等,层之间的数据流动是以Blobs的方式进行。

今天我们就先介绍一下数据层.

数据层是每个模型的最底层,是模型的入口,不仅提供数据的输入,也提供数据从Blobs转换成别的格式进行保存输出。通常数据的预处理(如减去均值, 放大缩小, 裁剪和镜像等),也在这一层设置参数实现。

数据来源可以来自高效的数据库(如LevelDBLMDB),也可以直接来自于内存。如果不是很注重效率的话,数据也可来自磁盘的hdf5文件和图片格式文件。

所有的数据层的都具有的公用参数:先看示例

layer {

  name: "cifar"

  type: "Data"

  top: "data"

  top: "label"

  include {

    phase: TRAIN

  }

  transform_param {

    mean_file: "examples/cifar10/mean.binaryproto"

  }

  data_param {

    source: "examples/cifar10/cifar10_train_lmdb"

    batch_size: 100

    backend: LMDB

  }

}

name: 表示该层的名称,可随意取

type: 层类型,如果是Data,表示数据来源于LevelDBLMDB。根据数据的来源不同,数据层的类型也不同(后面会详细阐述)。一般在练习的时候,我们都是采用的LevelDBLMDB数据,因此层类型设置为Data

topbottom: 每一层用bottom来输入数据,用top来输出数据。如果只有top没有bottom,则此层只有输出,没有输入。反之亦然。如果有多个 top或多个bottom,表示有多个blobs数据的输入和输出。

data label: 在数据层中,至少有一个命名为datatop如果有第二个top,一般命名为label 这种(data,label)配对是分类模型所必需的。

include: 一般训练的时候和测试的时候,模型的层是不一样的。该层(layer)是属于训练阶段的层,还是属于测试阶段的层,需要用include来指定。如果没有include参数,则表示该层既在训练模型中,又在测试模型中。

Transformations: 数据的预处理,可以将数据变换到定义的范围内。如设置scale0.00390625,实际上就是1/255, 即将输入数据由0-255归一化到0-1之间

其它的数据预处理也在这个地方设置:

transform_param {

    scale: 0.00390625

    mean_file_size: "examples/cifar10/mean.binaryproto"

    # 用一个配置文件来进行均值操作

    mirror: 1  # 1表示开启镜像,0表示关闭,也可用turefalse来表示

    # 剪裁一个 227*227的图块,在训练阶段随机剪裁,在测试阶段从中间裁剪

    crop_size: 227

  }

caffe中,如果定义了crop_size,那么在train时会对大于crop_size的图片进行随机裁剪,而在test时只是截取中间部分(详见/caffe/src/caffe/data_transformer.cpp):

 

 

1、数据来自于数据库(如LevelDBLMDB

层类型(layer type:Data

必须设置的参数:

  source: 包含数据库的目录名称,如examples/mnist/mnist_train_lmdb

  batch_size: 每次处理的数据个数,如64

可选的参数:

  rand_skip: 在开始的时候,路过某个数据的输入。通常对异步的SGD很有用。

  backend: 选择是采用LevelDB还是LMDB, 默认是LevelDB.

示例:

layer {

  name: "mnist"

  type: "Data"

  top: "data"

  top: "label"

  include {

    phase: TRAIN

  }

  transform_param {

    scale: 0.00390625

  }

  data_param {

    source: "examples/mnist/mnist_train_lmdb"

    batch_size: 64

    backend: LMDB

  }

}

2、数据来自于内存

层类型:MemoryData

必须设置的参数:

 batch_size:每一次处理的数据个数,比如2

 channels:通道数

  height:高度

   width: 宽度

示例:

layer {

  top: "data"

  top: "label"

  name: "memory_data"

  type: "MemoryData"

  memory_data_param{

    batch_size: 2

    height: 100

    width: 100

    channels: 1

  }

  transform_param {

    scale: 0.0078125

    mean_file: "mean.proto"

    mirror: false

  }

}

3、数据来自于HDF5

层类型:HDF5Data

必须设置的参数:

source: 读取的文件名称

batch_size: 每一次处理的数据个数

示例:

layer {

  name: "data"

  type: "HDF5Data"

  top: "data"

  top: "label"

  hdf5_data_param {

    source: "examples/hdf5_classification/data/train.txt"

    batch_size: 10

  }

}

4、数据来自于图片

层类型:ImageData

必须设置的参数:

  source: 一个文本文件的名字,每一行给定一个图片文件的名称和标签(label)

  batch_size: 每一次处理的数据个数,即图片数

可选参数:

  rand_skip: 在开始的时候,路过某个数据的输入。通常对异步的SGD很有用。

  shuffle: 随机打乱顺序,默认值为false

  new_height,new_width: 如果设置,则将图片进行resize

 示例:

layer {

  name: "data"

  type: "ImageData"

  top: "data"

  top: "label"

  transform_param {

    mirror: false

    crop_size: 227

    mean_file: "data/ilsvrc12/imagenet_mean.binaryproto"

  }

  image_data_param {

    source: "examples/_temp/file_list.txt"

    batch_size: 50

    new_height: 256

    new_width: 256

  }

}

5、数据来源于Windows

层类型:WindowData

必须设置的参数:

  source: 一个文本文件的名字

  batch_size: 每一次处理的数据个数,即图片数

示例:

layer {

  name: "data"

  type: "WindowData"

  top: "data"

  top: "label"

  include {

    phase: TRAIN

  }

  transform_param {

    mirror: true

    crop_size: 227

    mean_file: "data/ilsvrc12/imagenet_mean.binaryproto"

  }

  window_data_param {

    source: "examples/finetune_pascal_detection/window_file_2007_trainval.txt"

    batch_size: 128

    fg_threshold: 0.5

    bg_threshold: 0.5

    fg_fraction: 0.25

    context_pad: 16

    crop_mode: "warp"

  }

}

二.视觉层(Vision Layers)及参数

本文只讲解视觉层(Vision Layers)的参数,视觉层包括Convolution, Pooling, Local Response Normalization (LRN), im2col等层。

1Convolution层:

就是卷积层,是卷积神经网络(CNN)的核心层。

层类型:Convolution

  lr_mult: 学习率的系数,最终的学习率是这个数乘以solver.prototxt配置文件中的base_lr。如果有两个lr_mult, 则第一个表示权值的学习率,第二个表示偏置项的学习率。一般偏置项的学习率是权值学习率的两倍。

在后面的convolution_param中,我们可以设定卷积层的特有参数。

必须设置的参数:

    num_output: 卷积核(filter)的个数

    kernel_size: 卷积核的大小。如果卷积核的长和宽不等,需要用kernel_hkernel_w分别设定

其它参数:

     stride: 卷积核的步长,默认为1。也可以用stride_hstride_w来设置。

     pad: 扩充边缘,默认为0,不扩充。 扩充的时候是左右、上下对称的,比如卷积核的大小为5*5,那么pad设置为2,则四个边缘都扩充2个像素,即宽度和高度都扩充了4个像素,这样卷积运算之后的特征图就不会变小。也可以通过pad_hpad_w来分别设定。

      weight_filler: 权值初始化。 默认为“constant",值全为0,很多时候我们用"xavier"算法来进行初始化,也可以设置为”gaussian"

      bias_filler: 偏置项的初始化。一般设置为"constant",值全为0

      bias_term: 是否开启偏置项,默认为true, 开启

      group: 分组,默认为1组。如果大于1,我们限制卷积的连接操作在一个子集内。如果我们根据图像的通道来分组,那么第i个输出分组只能与第i个输入分组进行连接。

 

输入:n*c0*w0*h0

输出:n*c1*w1*h1

其中,c1就是参数中的num_output,生成的特征图个数

 w1=(w0+2*pad-kernel_size)/stride+1;

 h1=(h0+2*pad-kernel_size)/stride+1;

如果设置stride1,前后两次卷积部分存在重叠。如果设置pad=(kernel_size-1)/2,则运算后,宽度和高度不变。

示例:

layer {

  name: "conv1"

  type: "Convolution"

  bottom: "data"

  top: "conv1"

  param {

    lr_mult: 1

  }

  param {

    lr_mult: 2

  }

  convolution_param {

    num_output: 20

    kernel_size: 5

    stride: 1

    weight_filler {

      type: "xavier"

    }

    bias_filler {

      type: "constant"

    }

  }

}

2Pooling

也叫池化层,为了减少运算量和数据维度而设置的一种层。

层类型:Pooling

必须设置的参数:

     kernel_size: 池化的核大小,如何确定大小?。也可以用kernel_hkernel_w分别设定。

其它参数:

   pool: 池化方法,默认为MAX。目前可用的方法有MAX, AVE, STOCHASTIC

  pad: 和卷积层的pad的一样,进行边缘扩充。默认为0

  stride: 池化的步长,默认为1。一般我们设置为2,即不重叠(步长=窗口大小)。也可以用stride_hstride_w来设置。

 示例:

layer {

  name: "pool1"

  type: "Pooling"

  bottom: "conv1"

  top: "pool1"

  pooling_param {

    pool: MAX

    kernel_size: 3

    stride: 2

  }

}

pooling层的运算方法基本是和卷积层是一样的。

输入:n*c*w0*h0

输出:n*c*w1*h1

和卷积层的区别就是其中的c保持不变

 w1=(w0+2*pad-kernel_size)/stride+1;

 h1=(h0+2*pad-kernel_size)/stride+1;

如果设置stride2,前后两次卷积部分重叠。

3Local Response Normalization (LRN)

此层是对一个输入的局部区域进行归一化,达到“侧抑制”的效果。可去搜索AlexNetGoogLenet,里面就用到了这个功能

 层类型:LRN

参数:全部为可选,没有必须

  local_size: 默认为5。如果是跨通道LRN,则表示求和的通道数;如果是在通道内LRN,则表示求和的正方形区域长度。

  alpha: 默认为1,归一化公式中的参数。

  beta: 默认为5,归一化公式中的参数。

  norm_region: 默认为ACROSS_CHANNELS。有两个选择,ACROSS_CHANNELS表示在相邻的通道间求和归一化。WITHIN_CHANNEL表示在一个通道内部特定的区域内进行求和归一化。与前面的local_size参数对应。

归一化公式:对于每一个输入, 去除以,得到归一化后的输出

 

示例:

layers {

  name: "norm1"

  type: LRN

  bottom: "pool1"

  top: "norm1"

  lrn_param {

    local_size: 5

    alpha: 0.0001

    beta: 0.75

  }

}

4、im2col层

如果对matlab比较熟悉的话,就应该知道im2col是什么意思。它先将一个大矩阵,重叠地划分为多个子矩阵,对每个子矩阵序列化成向量,最后得到另外一个矩阵。

看一看图就知道了:

 

caffe中,卷积运算就是先对数据进行im2col操作,再进行内积运算(inner product)。这样做,比原始的卷积操作速度更快。

看看两种卷积操作的异同:

 

三.激活层(Activiation Layers)及参数

在激活层中,对输入数据进行激活操作(实际上就是一种函数变换),是逐元素进行运算的。从bottom得到一个blob数据输入,运算后,从top输入一个blob数据。在运算过程中,没有改变数据的大小,即输入和输出的数据大小是相等的。

输入:n*c*h*w

输出:n*c*h*w

常用的激活函数有sigmoid, tanh,relu等,下面分别介绍。

1Sigmoid

对每个输入数据,利用sigmoid函数执行操作。这种层设置比较简单,没有额外的参数。

 

层类型:Sigmoid

示例:

layer {

  name: "encode1neuron"

  bottom: "encode1"

  top: "encode1neuron"

  type: "Sigmoid"

}

2ReLU / Rectified-Linear and Leaky-ReLU

ReLU是目前使用最多的激活函数,主要因为其收敛更快,并且能保持同样效果。

标准的ReLU函数为max(x, 0),当x>0时,输出x; x<=0时,输出0

f(x)=max(x,0)

层类型:ReLU

可选参数:

negative_slope:默认为0. 对标准的ReLU函数进行变化,如果设置了这个值,那么数据为负数时,就不再设置为0,而是用原始数据乘以negative_slope

layer {

  name: "relu1"

  type: "ReLU"

  bottom: "pool1"

  top: "pool1"

}

RELU层支持in-place计算,这意味着bottom的输出和输入相同以避免内存的消耗。

3TanH / Hyperbolic Tangent

利用双曲正切函数对数据进行变换。

 

层类型:TanH

layer {

  name: "layer"

  bottom: "in"

  top: "out"

  type: "TanH"

}

4Absolute Value

求每个输入数据的绝对值。

f(x)=Abs(x)

层类型:AbsVal

layer {

  name: "layer"

  bottom: "in"

  top: "out"

  type: "AbsVal"

}

5Power

对每个输入数据进行幂运算

f(x)= (shift + scale * x) ^ power

层类型:Power

可选参数:

  power: 默认为1

  scale: 默认为1

  shift: 默认为0

layer {

  name: "layer"

  bottom: "in"

  top: "out"

  type: "Power"

  power_param {

    power: 2

    scale: 1

    shift: 0

  }

}

6BNLL

binomial normal log likelihood的简称

f(x)=log(1 + exp(x))

层类型:BNLL

layer {

  name: "layer"

  bottom: "in"

  top: "out"

  type: BNLL

}

四.其它常用层及参数

本文讲解一些其它的常用层,包括:softmax_loss层,Inner Product层,accuracy层,reshape层和dropout层及其它们的参数配置。

1softmax-loss

softmax-loss层和softmax层计算大致是相同的。softmax是一个分类器,计算的是类别的概率(Likelihood),是Logistic Regression 的一种推广。Logistic Regression 只能用于二分类,而softmax可以用于多分类。

softmaxsoftmax-loss的区别:

softmax计算公式:

 

softmax-loss计算公式:

 

关于两者的区别更加具体的介绍,可参考:softmax vs. softmax-loss

用户可能最终目的就是得到各个类别的概率似然值,这个时候就只需要一个 Softmax层,而不一定要进行softmax-Loss 操作;或者是用户有通过其他什么方式已经得到了某种概率似然值,然后要做最大似然估计,此时则只需要后面的 softmax-Loss 而不需要前面的 Softmax 操作。因此提供两个不同的 Layer 结构比只提供一个合在一起的 Softmax-Loss Layer 要灵活许多。

不管是softmax layer还是softmax-loss layer,都是没有参数的,只是层类型不同而也

softmax-loss layer:输出loss

layer {

  name: "loss"

  type: "SoftmaxWithLoss"

  bottom: "ip1"

  bottom: "label"

  top: "loss"

}

softmax layer: 输出似然值

layers {

  bottom: "cls3_fc"

  top: "prob"

  name: "prob"

  type: “Softmax"

}

2Inner Product

全连接层,把输入当作成一个向量,输出也是一个简单向量(把输入数据blobswidthheight全变为1)。

输入: n*c0*h*w

输出: n*c1*1*1

全连接层实际上也是一种卷积层,只是它的卷积核大小和原数据大小一致。因此它的参数基本和卷积层的参数一样。

层类型:InnerProduct

lr_mult: 学习率的系数,最终的学习率是这个数乘以solver.prototxt配置文件中的base_lr。如果有两个lr_mult, 则第一个表示权值的学习率,第二个表示偏置项的学习率。一般偏置项的学习率是权值学习率的两倍。

必须设置的参数:

    num_output: 过滤器(filfter)的个数

其它参数:

      weight_filler: 权值初始化。 默认为“constant",值全为0,很多时候我们用"xavier"算法来进行初始化,也可以设置为”gaussian"

      bias_filler: 偏置项的初始化。一般设置为"constant",值全为0

      bias_term: 是否开启偏置项,默认为true, 开启

layer {

  name: "ip1"

  type: "InnerProduct"

  bottom: "pool2"

  top: "ip1"

  param {

    lr_mult: 1

  }

  param {

    lr_mult: 2

  }

  inner_product_param {

    num_output: 500

    weight_filler {

      type: "xavier"

    }

    bias_filler {

      type: "constant"

    }

  }

}

3accuracy

输出分类(预测)精确度,只有test阶段才有,因此需要加入include参数。

层类型:Accuracy

layer {

  name: "accuracy"

  type: "Accuracy"

  bottom: "ip2"

  bottom: "label"

  top: "accuracy"

  include {

    phase: TEST

  }

}

4reshape

在不改变数据的情况下,改变输入的维度。

层类型: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是一个四维的数据:n*c*w*h)。

dim:0  表示维度不变,即输入和输出是相同的维度。

dim:2 dim:3 将原来的维度变成23

dim:-1 表示由系统自动计算维度。数据的总量不变,系统会根据blob数据的其它三维来自动计算当前维的维度值 。

假设原数据为:64*3*28*28, 表示643通道的28*28的彩色图片

经过reshape变换:

reshape_param {

      shape {

        dim: 0

        dim: 0

        dim: 14

        dim: -1

      }

}

输出数据为:64*3*14*56

5Dropout

Dropout是一个防止过拟合的trick。可以随机让网络某些隐含层节点的权重不工作。

先看例子:

layer {

  name: "drop7"

  type: "Dropout"

  bottom: "fc7-conv"

  top: "fc7-conv"

  dropout_param {

    dropout_ratio: 0.5

  }

}layer {

  name: "drop7"

  type: "Dropout"

  bottom: "fc7-conv"

  top: "fc7-conv"

  dropout_param {

    dropout_ratio: 0.5

  }

}

只需要设置一个dropout_ratio就可以了

五.Blob,Layer and Net以及对应配置文件的编写

深度网络(net)是一个组合模型,它由许多相互连接的层(layers)组合而成。Caffe就是组建深度网络的这样一种工具,它按照一定的策略,一层一层的搭建出自己的模型。它将所有的信息数据定义为blobs,从而进行便利的操作和通讯。Blobcaffe框架中一种标准的数组,一种统一的内存接口,它详细描述了信息是如何存储的,以及如何在层之间通讯的。

1blob

Blobs封装了运行时的数据信息,提供了CPUGPU的同步。从数学上来说, Blob就是一个N维数组。它是caffe中的数据操作基本单位,就像matlab中以矩阵为基本操作对象一样。只是矩阵是二维的,而BlobN维的。N可以是234等等。对于图片数据来说,Blob可以表示为(N*C*H*W)这样一个4D数组。其中N表示图片的数量,C表示图片的通道数,HW分别表示图片的高度和宽度。当然,除了图片数据,Blob也可以用于非图片数据。比如传统的多层感知机,就是比较简单的全连接网络,用2DBlob,调用innerProduct层来计算就可以了。

在模型中设定的参数,也是用Blob来表示和运算。它的维度会根据参数的类型不同而不同。比如:在一个卷积层中,输入一张3通道图片,有96个卷积核,每个核大小为11*11,因此这个Blob96*3*11*11. 而在一个全连接层中,假设输入1024通道图片,输出1000个数据,则Blob1000*1024

2layer

层是网络模型的组成要素和计算的基本单位。层的类型比较多,如Data,Convolution,Pooling,ReLU,Softmax-loss,Accuracy等,一个层的定义大至如下图:

 

bottom进行数据的输入 ,计算后,通过top进行输出。图中的黄色多边形表示输入输出的数据,蓝色矩形表示层。

每一种类型的层都定义了三种关键的计算:setup,forward and backword

setup: 层的建立和初始化,以及在整个模型中的连接初始化。

forward: bottom得到输入数据,进行计算,并将计算结果送到top,进行输出。

backward: 从层的输出端top得到数据的梯度,计算当前层的梯度,并将计算结果送到bottom,向前传递。

3Net

就像搭积木一样,一个net由多个layer组合而成。

现给出 一个简单的2层神经网络的模型定义( 加上loss 层就变成三层了),先给出这个网络的拓扑。

 

第一层:namemnist, typeData,没有输入(bottom),只有两个输出(top),一个为data,一个为label

第二层:nameiptypeInnerProduct, 输入数据data, 输出数据ip

第三层:nameloss, typeSoftmaxWithLoss,有两个输入,一个为ip,一个为label,有一个输出loss,没有画出来。

对应的配置文件prototxt就可以这样写:

name: "LogReg"

layer {

  name: "mnist"

  type: "Data"

  top: "data"

  top: "label"

  data_param {

    source: "input_leveldb"

    batch_size: 64

  }

}

layer {

  name: "ip"

  type: "InnerProduct"

  bottom: "data"

  top: "ip"

  inner_product_param {

    num_output: 2

  }

}

layer {

  name: "loss"

  type: "SoftmaxWithLoss"

  bottom: "ip"

  bottom: "label"

  top: "loss"

}

第一行将这个模型取名为LogReg, 然后是三个layer的定义,参数都比较简单,只列出必须的参数。

六.Solver及其配置

solver算是caffe的核心的核心,它协调着整个模型的运作。caffe程序运行必带的一个参数就是solver配置文件。运行代码一般为

# caffe train --solver=*_slover.prototxt

Deep Learning中,往往loss function是非凸的,没有解析解,我们需要通过优化方法来求解。solver的主要作用就是交替调用前向(forward)算法和后向(backward)算法来更新参数,从而最小化loss,实际上就是一种迭代的优化算法。

到目前的版本,caffe提供了六种优化算法来求解最优参数,在solver配置文件中,通过设置type类型来选择。

Stochastic Gradient Descent (type: "SGD"),

AdaDelta (type: "AdaDelta"),

Adaptive Gradient (type: "AdaGrad"),

Adam (type: "Adam"),

Nesterovs Accelerated Gradient (type: "Nesterov") and

RMSprop (type: "RMSProp")

  具体的每种方法的介绍,请看本系列的下一篇文章, 本文着重介绍solver配置文件的编写。

Solver的流程:

1.设计好需要优化的对象,以及用于学习的训练网络和用于评估的测试网络。(通过调用另外一个配置文件prototxt来进行)

2.通过forwardbackward迭代的进行优化来跟新参数。

3.定期的评价测试网络。(可设定多少次训练后,进行一次测试)

4.在优化过程中显示模型和solver的状态

在每一次的迭代过程中,solver做了这几步工作:

1、调用forward算法来计算最终的输出值,以及对应的loss

2、调用backward算法来计算每层的梯度

3、根据选用的slover方法,利用梯度进行参数更新

4、记录并保存每次迭代的学习率、快照,以及对应的状态。

接下来,我们先来看一个实例:

net: "examples/mnist/lenet_train_test.prototxt"

test_iter: 100

test_interval: 500

base_lr: 0.01

momentum: 0.9

type: SGD

weight_decay: 0.0005

lr_policy: "inv"

gamma: 0.0001

power: 0.75

display: 100

max_iter: 20000

snapshot: 5000

snapshot_prefix: "examples/mnist/lenet"

solver_mode: CPU

接下来,我们对每一行进行详细解译:

net: "examples/mnist/lenet_train_test.prototxt"

设置深度网络模型。每一个模型就是一个net,需要在一个专门的配置文件中对net进行配置,每个net由许多的layer所组成。每一个layer的具体配置方式可参考本系列文文章中的(2-5)。注意的是:文件的路径要从caffe的根目录开始,其它的所有配置都是这样。

也可用train_nettest_net来对训练模型和测试模型分别设定。例如:

train_net: "examples/hdf5_classification/logreg_auto_train.prototxt"

test_net: "examples/hdf5_classification/logreg_auto_test.prototxt"

接下来第二行:

test_iter: 100

这个要与test layer中的batch_size结合起来理解。mnist数据中测试样本总数为10000,一次性执行全部数据效率很低,因此我们将测试数据分成几个批次来执行,每个批次的数量就是batch_size。假设我们设置batch_size100,则需要迭代100次才能将10000个数据全部执行完。因此test_iter设置为100。执行完一次全部数据,称之为一个epoch

test_interval: 500

测试间隔。也就是每训练500次,才进行一次测试。

base_lr: 0.01

lr_policy: "inv"

gamma: 0.0001

power: 0.75

这四行可以放在一起理解,用于学习率的设置。只要是梯度下降法来求解优化,都会有一个学习率,也叫步长。base_lr用于设置基础学习率,在迭代的过程中,可以对基础学习率进行调整。怎么样进行调整,就是调整的策略,由lr_policy来设置。

lr_policy可以设置为下面这些值,相应的学习率的计算为:

 

- fixed:保持base_lr不变.

- step: 如果设置为step,则还需要设置一个stepsize,  返回

      base_lr * gamma ^ (floor(iter / stepsize)),其中iter表示当前的迭代次数

- exp: 返回base_lr * gamma ^ iteriter为当前迭代次数

- inv:如果设置为inv,还需要设置一个power, 返回base_lr * (1 + gamma * iter) ^ (- power)

- multistep: 如果设置为multistep,则还需要设置一个stepvalue。这个参数和step很相似,step是均匀等间隔变化,而multistep则是根据 stepvalue值变化

- poly:学习率进行多项式误差, 返回 base_lr (1 - iter/max_iter) ^ (power)

- sigmoid:学习率进行sigmod衰减,返回 base_lr ( 1/(1 + exp(-gamma * (iter - stepsize))))

multistep示例:

base_lr: 0.01

momentum: 0.9

weight_decay: 0.0005

# The learning rate policy

lr_policy: "multistep"

gamma: 0.9

stepvalue: 5000

stepvalue: 7000

stepvalue: 8000

stepvalue: 9000

stepvalue: 9500

接下来的参数:

momentum 0.9

上一次梯度更新的权重,具体可参看下一篇文章。

type: SGD

优化算法选择。这一行可以省掉,因为默认值就是SGD。总共有六种方法可选择,在本文的开头已介绍。

weight_decay: 0.0005

权重衰减项,防止过拟合的一个参数

display: 100

每训练100次,在屏幕上显示一次。如果设置为0,则不显示。

max_iter: 20000

最大迭代次数。这个数设置太小,会导致没有收敛,精确度很低。设置太大,会导致震荡,浪费时间。

snapshot: 5000

snapshot_prefix: "examples/mnist/lenet"

快照。将训练出来的modelsolver状态进行保存,snapshot用于设置训练多少次后进行保存,默认为0,不保存。snapshot_prefix设置保存路径。

还可以设置snapshot_diff,是否保存梯度值,默认为false,不保存。

也可以设置snapshot_format,保存的类型。有两种选择:HDF5 BINARYPROTO ,默认为BINARYPROTO

solver_mode: CPU

设置运行模式。默认为GPU,如果你没有GPU,则需要改成CPU,否则会出错。

 注意:以上的所有参数都是可选参数,都有默认值。根据solver方法(type)的不同,还有一些其它的参数,在此不一一列举。

七.Solver优化方法

上文提到,到目前为止,caffe总共提供了六种优化方法:

Stochastic Gradient Descent (type: "SGD"),

AdaDelta (type: "AdaDelta"),

Adaptive Gradient (type: "AdaGrad"),

Adam (type: "Adam"),

Nesterovs Accelerated Gradient (type: "Nesterov") and

RMSprop (type: "RMSProp")

Solver就是用来使loss最小化的优化方法。对于一个数据集D,需要优化的目标函数是整个数据集中所有数据loss的平均值。

 

其中,fW(x(i))计算的是数据x(i)上的loss, 先将每个单独的样本xloss求出来,然后求和,最后求均值。 r(W)是正则项(weight_decay),为了减弱过拟合现象。

如果采用这种Loss 函数,迭代一次需要计算整个数据集,在数据集非常大的这情况下,这种方法的效率很低,这个也是我们熟知的梯度下降采用的方法。

 

 

在实际中,通过将整个数据集分成几批(batches), 每一批就是一个mini-batch,其数量(batch_size)N<<|D|,此时的loss 函数为:

 

 

 

有了loss函数后,就可以迭代的求解loss和梯度来优化这个问题。在神经网络中,用forward pass来求解loss,用backward pass来求解梯度。

caffe中,默认采用的Stochastic Gradient DescentSGD)进行优化求解。后面几种方法也是基于梯度的优化方法(like SGD),因此本文只介绍一下SGD。其它的方法,有兴趣的同学,可以去看文献原文。

1Stochastic gradient descentSGD)

随机梯度下降(Stochastic gradient descent)是在梯度下降法(gradient descent)的基础上发展起来的,梯度下降法也叫最速下降法,具体原理在网易公开课《机器学习》中,吴恩达教授已经讲解得非常详细。SGD在通过负梯度和上一次的权重更新值Vt的线性组合来更新W,迭代公式如下:

 

 
其中,  是负梯度的学习率(base_lr)是上一次梯度值的权重(momentum),用来加权之前梯度方向对现在梯度下降方向的影响。这两个参数需要通过tuning来得到最好的结果,一般是根据经验设定的。如果你不知道如何设定这些参数,可以参考相关的论文。

在深度学习中使用SGD,比较好的初始化参数的策略是把学习率设为0.01左右(base_lr: 0.01),在训练的过程中,如果loss开始出现稳定水平时,对学习率乘以一个常数因子(gamma),这样的过程重复多次。

对于momentum,一般取值在0.5--0.99之间。通常设为0.9momentum可以让使用SGD的深度学习方法更加稳定以及快速。

关于更多的momentum,请参看Hinton的《A Practical Guide to Training Restricted Boltzmann Machines》。  

实例: 

base_lr: 0.01

lr_policy: "step"

gamma: 0.1   

stepsize: 1000  

max_iter: 3500

momentum: 0.9

lr_policy设置为step,则学习率的变化规则为 base_lr * gamma ^ (floor(iter / stepsize))

即前1000次迭代,学习率为0.01; 1001-2000次迭代,学习率为0.001; 2001-3000次迭代,学习率为0.00001,第3001-3500次迭代,学习率为10-5  

上面的设置只能作为一种指导,它们不能保证在任何情况下都能得到最佳的结果,有时候这种方法甚至不work。如果学习的时候出现diverge(比如,你一开始就发现非常大或者NaN或者infloss值或者输出),此时你需要降低base_lr的值(比如,0.001),然后重新训练,这样的过程重复几次直到你找到可以workbase_lr

2AdaDelta

AdaDelta是一种鲁棒的学习率方法,是基于梯度的优化方法(like SGD)。

具体的介绍文献:

M. Zeiler ADADELTA: AN ADAPTIVE LEARNING RATE METHOD. arXiv preprint, 2012.

示例:

net: "examples/mnist/lenet_train_test.prototxt"

test_iter: 100

test_interval: 500

base_lr: 1.0

lr_policy: "fixed"

momentum: 0.95

weight_decay: 0.0005

display: 100

max_iter: 10000

snapshot: 5000

snapshot_prefix: "examples/mnist/lenet_adadelta"

solver_mode: GPU

type: "AdaDelta"

delta: 1e-6

从最后两行可看出,设置solver typeAdadelta时,需要设置delta的值。

3AdaGrad

自适应梯度(adaptive gradient)是基于梯度的优化方法(like SGD

具体的介绍文献:

Duchi, E. Hazan, and Y. Singer. Adaptive Subgradient Methods for Online Learning and Stochastic Optimization. The Journal of Machine Learning Research, 2011.

示例:

net: "examples/mnist/mnist_autoencoder.prototxt"

test_state: { stage: 'test-on-train' }

test_iter: 500

test_state: { stage: 'test-on-test' }

test_iter: 100

test_interval: 500

test_compute_loss: true

base_lr: 0.01

lr_policy: "fixed"

display: 100

max_iter: 65000

weight_decay: 0.0005

snapshot: 10000

snapshot_prefix: "examples/mnist/mnist_autoencoder_adagrad_train"

# solver mode: CPU or GPU

solver_mode: GPU

type: "AdaGrad"

4Adam

是一种基于梯度的优化方法(like SGD)。

 具体的介绍文献:

D. Kingma, J. Ba. Adam: A Method for Stochastic Optimization. International Conference for Learning Representations, 2015.

5NAG

Nesterov 的加速梯度法(Nesterov’s accelerated gradient)作为凸优化中最理想的方法,其收敛速度非常快。

 具体的介绍文献:

 I. Sutskever, J. Martens, G. Dahl, and G. Hinton. On the Importance of Initialization and Momentum in Deep Learning. Proceedings of the 30th International Conference on Machine Learning, 2013.

示例:

net: "examples/mnist/mnist_autoencoder.prototxt"

test_state: { stage: 'test-on-train' }

test_iter: 500

test_state: { stage: 'test-on-test' }

test_iter: 100

test_interval: 500

test_compute_loss: true

base_lr: 0.01

lr_policy: "step"

gamma: 0.1

stepsize: 10000

display: 100

max_iter: 65000

weight_decay: 0.0005

snapshot: 10000

snapshot_prefix: "examples/mnist/mnist_autoencoder_nesterov_train"

momentum: 0.95

# solver mode: CPU or GPU

solver_mode: GPU

type: "Nesterov"

6RMSprop

RMSpropTieleman在一次 Coursera课程演讲中提出来的,也是一种基于梯度的优化方法(like SGD

具体的介绍文献:

T. Tieleman, and G. Hinton. RMSProp: Divide the gradient by a running average of its recent magnitude. COURSERA: Neural Networks for Machine Learning.Technical report, 2012.

 示例:

net: "examples/mnist/lenet_train_test.prototxt"

test_iter: 100

test_interval: 500

base_lr: 1.0

lr_policy: "fixed"

momentum: 0.95

weight_decay: 0.0005

display: 100

max_iter: 10000

snapshot: 5000

snapshot_prefix: "examples/mnist/lenet_adadelta"

solver_mode: GPU

type: "RMSProp"

rms_decay: 0.98

最后两行,需要设置rms_decay值。

八.命令行解析

caffe的运行提供三种接口:c++接口(命令行)、Python接口和matlab接口。本文先对命令行进行解析,后续会依次介绍其它两个接口。

caffec++主程序(caffe.cpp)放在根目录下的tools文件夹内, 当然还有一些其它的功能文件,如:convert_imageset.cpp, train_net.cpp, test_net.cpp等也放在这个文件夹内。经过编译后,这些文件都被编译成了可执行文件,放在了 ./build/tools/ 文件夹内。因此我们要执行caffe程序,都需要加 ./build/tools/ 前缀。

如:

# sudo sh ./build/tools/caffe train --solver=examples/mnist/train_lenet.sh

caffe程序的命令行执行格式如下:

caffe <command> <args>

其中的<command>有这样四种:

train

test

device_query

time

对应的功能为:

train----训练或finetune模型(model),

test-----测试模型

device_query---显示gpu信息

time-----显示程序执行时间

其中的<args>参数有:

-solver

-gpu

-snapshot

-weights

-iteration

-model

-sighup_effect

-sigint_effect

注意前面有个-符号。对应的功能为:

-solver:必选参数。一个protocol buffer类型的文件,即模型的配置文件。如:

# ./build/tools/caffe train -solver examples/mnist/lenet_solver.prototxt

-gpu: 可选参数。该参数用来指定用哪一块gpu运行,根据gpuid进行选择,如果设置为'-gpu all'则使用所有的gpu运行。如使用第二块gpu运行:

# ./build/tools/caffe train -solver examples/mnist/lenet_solver.prototxt -gpu 2

-snapshot:可选参数。该参数用来从快照(snapshot)中恢复训练。可以在solver配置文件设置快照,保存solverstate。如:

# ./build/tools/caffe train -solver examples/mnist/lenet_solver.prototxt -snapshot examples/mnist/lenet_iter_5000.solverstate

-weights:可选参数。用预先训练好的权重来fine-tuning模型,需要一个caffemodel,不能和-snapshot同时使用。如:

# ./build/tools/caffe train -solver examples/finetuning_on_flickr_style/solver.prototxt -weights models/bvlc_reference_caffenet/bvlc_reference_caffenet.caffemodel

-iterations: 可选参数,迭代次数,默认为50。 如果在配置文件文件中没有设定迭代次数,则默认迭代50次。

-model:可选参数,定义在protocol buffer文件中的模型。也可以在solver配置文件中指定。

-sighup_effect:可选参数。用来设定当程序发生挂起事件时,执行的操作,可以设置为snapshot, stopnone, 默认为snapshot

-sigint_effect: 可选参数。用来设定当程序发生键盘中止事件时(ctrl+c), 执行的操作,可以设置为snapshot, stopnone, 默认为stop

 

刚才举例了一些train参数的例子,现在我们来看看其它三个<command>

test参数用在测试阶段,用于最终结果的输出,要模型配置文件中我们可以设定需要输入accuracy还是loss. 假设我们要在验证集中验证已经训练好的模型,就可以这样写

# ./build/tools/caffe test -model examples/mnist/lenet_train_test.prototxt -weights examples/mnist/lenet_iter_10000.caffemodel -gpu 0 -iterations 100

这个例子比较长,不仅用到了test参数,还用到了-model, -weights, -gpu-iteration四个参数。意思是利用训练好了的权重(-weight),输入到测试模型中(-model),用编号为0gpu(-gpu)测试100(-iteration)

time参数用来在屏幕上显示程序运行时间。如:

# ./build/tools/caffe time -model examples/mnist/lenet_train_test.prototxt -iterations 10

这个例子用来在屏幕上显示lenet模型迭代10次所使用的时间。包括每次迭代的forwardbackward所用的时间,也包括每层forwardbackward所用的平均时间。

# ./build/tools/caffe time -model examples/mnist/lenet_train_test.prototxt -gpu 0

这个例子用来在屏幕上显示lenet模型用gpu迭代50次所使用的时间。

# ./build/tools/caffe time -model examples/mnist/lenet_train_test.prototxt -weights examples/mnist/lenet_iter_10000.caffemodel -gpu 0 -iterations 10

利用给定的权重,利用第一块gpu,迭代10lenet模型所用的时间。

device_query参数用来诊断gpu信息。

# ./build/tools/caffe device_query -gpu 0

最后,我们来看两个关于gpu的例子

# ./build/tools/caffe train -solver examples/mnist/lenet_solver.prototxt -gpu 0,1

# ./build/tools/caffe train -solver examples/mnist/lenet_solver.prototxt -gpu all

这两个例子表示: 用两块或多块GPU来平行运算,这样速度会快很多。但是如果你只有一块或没有gpu, 就不要加-gpu参数了,加了反而慢。

最后,在linux下,本身就有一个time命令,因此可以结合进来使用,因此我们运行mnist例子的最终命令是(一块gpu)

$ sudo time ./build/toos/caffe train -solver examples/mnist/lenet_solver.prototxt