caffe 框架梳理(待续)

作者:jiandanjinxin

本文更多资料来源于罗韵北京深度学习资料和Ian Goodfellow的书籍《Deep Learning》
链接: http://pan.baidu.com/s/1jIRJ6mU
提取密码:xehi

caffe:全称Convolutional Architecture for Fast Feature Embedding,是一个计算CNN 相关算法的框架,用C++和Python实现的。

Caffe的优点与局限性

优点:
• 1. 第一个主流的工业级深度学习工具。
• 2. 专精于图像处理
• 局限性:
• 1. 它有很多扩展,但是由于一些遗留的架构问题,不够灵活且对递归网络和语言建模的支持很差。
• 2. 基于层的网络结构,其扩展性不好,对于新增加的层,需要
自己实现(forward, backward and gradient update)

Caffe目录结构

• data/ 用于存放下载的训练数据
• docs/ 帮助文档
• examples/ 代码样例
• matlab/ MATLAB接文件
• python/ PYTHON接文件
• models/ 一些配置好的模型参数
• scripts/ 一些文档和数据会用到的脚本核心代码
• tools/ 保存的源码是用于生成二进制处理程序的,caffe在训练时实际是直接调用这些二进制文件
• include/ Caffe的实现代码的头文件
• src/ 实现Caffe的源文件

src/ 文件结构

gtest/ google test 一个用于测试的库,你make runtest时看见的很多绿⾊色RUN OK就是它,这个与caffe的学习无关,不过是个有用的库

caffe/ 关键代码

• test/ 用gtest测试caffe的代码
• util/ 数据转换时用的一些代码。caffe速度快,很大程度得益于内存设计上的优化(blob数据结构采用proto)和对卷积的优化(部分与im2col相
关)
• proto/ 即所谓的“Protobuf”,全称“Google Protocol Buffer”,是一种数据存储格式,帮助caffe提速
• layers/ 深度神经网络中的基本结构就是一层层互不相同的网络了,这个
文件夹下的源文件以及目前位置“src/caffe”中包含所有.cpp文件就是caffe的核心目录下的核心代码了。

Caffe核心代码

• blob[.cpp .h] 基本的数据结构Blob类
• common[.cpp .h] 定义Caffe类
• internal_thread[.cpp .h] 使用boost::thread线程库
• net[.cpp .h] 网络结构类Net
• solver[.cpp .h] 优化方法类Solver
• data_transformer[.cpp .h] 输入数据的基本操作类DataTransformer
• syncedmem[.cpp .h] 分配内存和释放内存类CaffeMallocHost,用于同步GPU,CPU数据
• layer[.cpp .h] 层类Layer
• layers/ 此文件夹下面的代码全部至少继承了类Layer, 从layer_factory中注册继承

1. Caffe三级结构(Blobs,Layers,Nets)

• Blob:用于数据的保存、交换和操作,Caffe基础存储结构
• Layer:用于模型和计算的基础
• Net:整合连接Layers

1.1 Blobs结构

• 在内存中表示4维数组,在caffe/blob.hpp中,维度包括
(width_,height_,channels_,num_)
• num_用于存储数据或权值(data)和权值增量(diff)

• Blob 在caffe源码 blob.hpp中是一个模板类。
• protected 的成员变量有:data_ , diff_ , shape_ , count_ , capacity_ ,其中data_ 和diff_ 是共享SyncedMemory 类(在syncedmem的源码中定义)的智能指针,shape_是int型的vector,count_ 和capacity_ 是整型变量。
• 其成员函数主要有:Reshape 、ReshapeLike、SharedData、 Updata 等等
• blob.hpp 包含了caffe.pb.h ,说明caffe protobuf 会向blob 传递参数。

“caffe/proto/caffe.pb.h”
• caffe.pb.h是google protocol buffer根据caffe.proto自动生成的,可以到src/caffe/proto/caffe.proto里看下caffe里面用到的各个数据的定义,比如BlobProto,Datum,NetParameter等。使用这个protocol buffer看起来确实方便,一方面可以用文本文件定义结构化的数据类型,另一方面可以生成查询效率更高、占空间更小的二进制文件。

“caffe/common.hpp”
• 主要singleton化Caffe类,并封装了boost和CUDA随机数生成的函数,提供了统一的接口。

“caffe/syncedmem.hpp”
• 定义了以下的接⼝口:
• inline void CaffeMallocHost(void** ptr, size_t size)
• inline void CaffeFreeHost(void* ptr)
• 主要是分配内存和释放内存的。而class SyncedMemory定义了内存分配管理和CPU与GPU之间同步的函数。

“caffe/util/math_functions.hpp”
• 封装了很多cblas矩阵运算

caffe.proto里面BlobProto的定义

message BlobProto {
  optional BlobShape shape = 7;
  repeated float data = 5 [packed = true];
  repeated float diff = 6 [packed = true];
  repeated double double_data = 8 [packed = true];
  repeated double double_diff = 9 [packed = true];

  // 4D dimensions -- deprecated.  Use "shape" instead.
  optional int32 num = 1 [default = 0];
  optional int32 channels = 2 [default = 0];
  optional int32 height = 3 [default = 0];
  optional int32 width = 4 [default = 0];
}

• 对于BlobProto,可以看到定义了四个optional的int32类型的名字(name)num、channels、height和width,optional意味着Blob可以有一个或者没有这个参数,每个名字(name)后面都有一个数字,这个数字是其名字的一个标签。这个数字就是用来在生成的二进制文件中搜索查询的标签。关于这个数字,115会花费1byte的编码空间,162047花费2byte。所以⼀一般建议把那些频繁使用的名字的标签设为115之间的值。而后面的repeated意味着float类型的data和diff可以重复任意次,而加上[packed = true]是为了更高效的编码。
• 主要数据有两个data和diff,用num、channels、height和width这四个维度来确定数据的具体位置,做一些数据查询和Blob reshape的操作。

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

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


1.2 Layer的五种类型

• Layer
• 所有的Pooling,Convolve,apply nonlinearities等操作都在这里实现。在Layer中input data用bottom表示,output data用top表示。每一层定义了三种操作setup(Layer初始化), forward(正向传导,根据input计算output), backward(反向传导计算,根据output计算input的梯度)。forward和backward有GPU和CPU两个版本的实现。


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

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

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

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

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

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

正向传播的是数据,反向传播的是误差损失和梯度。


• 5种衍生Layers:

• data_layer


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,表示数据来源于LevelDB或LMDB。根据数据的来源不同,数据层的类型也不同。一般都是采 用的LevelDB或LMDB数据,因此层类型设置为Data。

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

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

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

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

mirror # 1表示开启镜像,0表示关闭,也可用ture和false来表示
crop_size: 227剪裁一个 227*227的图块,在训练阶段随机剪裁,在测试阶段从中间裁剪

data_layer主要包含与数据有关的文件。在官方文档中指出data是caffe数据的入口是网络的最低层,并且支持多种格式,在这之中又有5种LayerType

• DATA 用于LevelDB或LMDB数据格式的输入的类型,输入参数有source,batch_size, (rand_skip), (backend)。后两个是可选。

数据来自于数据库(如LevelDB和LMDB)
层类型(layer type):Data
必须设置的参数:
source: 包含数据库的目录名称,如examples/mnist/mnist_train_lmdb
batch_size: 每次处理的数据个数,如64
可选的参数:
rand_skip: 在开始的时候,路过某个数据的输入。通常对异步的SGD很有用。
backend: 选择是采用LevelDB还是LMDB, 默认是LevelDB.

• MEMORY_DATA 这种类型可以直接从内存读取数据使用时需要调用MemoryDataLayer::Reset,输入参数有batch_size, channels, height, width。
数据来自于内存层类型: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
}
}

• HDF5_DATA HDF5数据格式输入的类型,输入参数有source, batch_size。
• HDF5_OUTPUT HDF5数据格式输出的类型,输入参数有file_name。

数据来自于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
}
}

• IMAGE_DATA 图像格式数据输入的类型,输入参数有source, batch_size,(rand_skip), (shuffle), (new_height), (new_width)。

数据来自于图片
层类型: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
}

• 其实还有两种WINDOW_DATA, DUMMY_DATA⽤用于测试和预留的接⼝口,不重要。

数据来源于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"
  }
}

• neuron_layer

同样是数据的操作层,neuron_layer实现里大量激活函数,主要是元素级别的操作,具有相同的bottom,top size。
• Caffe中实现了大量激活函数GPU和CPU的都有很多。它们的父类都是
NeuronLayer

• template <typename Dtype>
• class NeuronLayer : public Layer<Dtype>

1、Sigmoid

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

这里写图片描述

示例

layer {
  name: "encode1neuron"
  bottom: "encode1"
  top: "encode1neuron"
  type: "Sigmoid"
}

2、ReLU / 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

一般的参数设置格式如下(以ReLU为例)

layers {
name: "relu1"
type: RELU
bottom: "conv1"
top: "conv1"
}

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

3、TanH / Hyperbolic Tangent

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

这里写图片描述

层类型:TanH

layer {
  name: "layer"
  bottom: "in"
  top: "out"
  type: "TanH"
}

4、Absolute Value

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

f(x)=Abs(x)

层类型:AbsVal

layer {
  name: "layer"
  bottom: "in"
  top: "out"
  type: "AbsVal"
}

5、Power

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

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
  }
}

6、BNLL

binomial normal log likelihood的简称

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

层类型:BNLL

layer {
  name: "layer"
  bottom: "in"
  top: "out"
  type: “BNLL”
}

• loss_layer

Loss层计算网络误差,loss_layer.hpp头文件调用情况

#include “caffe/blob.hpp"#include “caffe/common.hpp"#include “caffe/layer.hpp"#include “caffe/neuron_layers.hpp"#include “caffe/proto/caffe.pb.h"

可以看见调用了neuron_layers.hpp,估计是需要调用里面的函数计算Loss,一般来说Loss放在最后一层。caffe实现了大量loss function,它们的父类都是LossLayer。

• template <typename Dtype>
• class LossLayer : public Layer<Dtype>

softmax-loss

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

softmax与softmax-loss的区别:

softmax计算公式:
这里写图片描述

而softmax-loss计算公式
这里写图片描述

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

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

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

layer {
  name: "loss"
  type: "SoftmaxWithLoss"
  bottom: "ip1"
  bottom: "label"
  top: "loss"
}

softmax layer: 输出似然值

layers {
  bottom: "cls3_fc"
  top: "prob"
  name: "prob"
  type: “Softmax"
}

• common_layer

这一层主要进行的是vision_layer的连接
声明了9个类型的common_layer,部分有GPU实现

• InnerProductLayer 常常用来作为全连接层
• SplitLayer ⽤用于一输入对多输出的场合(对blob)
• FlattenLayer 将n * c * h * w变成向量的格式n * ( c * h * w ) * 1 * 1
• ConcatLayer ⽤用于多输入一输出的场合
• SilenceLayer ⽤用于一输入对多输出的场合(对layer)
• (Elementwise Operations) 这里面是我们常说的激活函数层Activation Layers。
• EltwiseLayer
• SoftmaxLayer
• ArgMaxLayer
• MVNLayer

Inner Product

全连接层,把输入当作成一个向量,输出也是一个简单向量(把输入数据blobs的width和height全变为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"
    }
  }
}

accuracy

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

layer {
  name: "accuracy"
  type: "Accuracy"
  bottom: "ip2"
  bottom: "label"
  top: "accuracy"
  include {
    phase: TEST
  }
}

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是一个四维的数据:n*c*w*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即可。


• vision_layer

主要是实现Convolution和Pooling操作, 主要有以下几个类:
• ConvolutionLayer 最常用的卷积操作

层类型:Convolution

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

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

必须设置的参数:

    num_output: 卷积核(filter)的个数

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

其它参数:

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

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

      weight_filler: 权值初始化。 默认为“constant",值全为0,很多时候我们用"xavier"算法来进行初始化,也可以设置为”gaussian"
      bias_filler: 偏置项的初始化。一般设置为"constant",值全为0。
      bias_term: 是否开启偏置项,默认为true, 开启

输入:n*c0*w0*h0
输出:n*c1*w1*h1
其中,c1就是参数中的num_output,生成的特征图个数
 w1=floor((w0+2*pad-kernel_size)/stride)+1;
 h1=floor((h0+2*pad-kernel_size)/stride)+1;
如果设置stride为1,前后两次卷积部分存在重叠。如果设置pad=(kernel_size-1)/2,则运算后,宽度和高度不变。
由pad, kernel_size和stride三者共同决定。

示例

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"
    }
  }
}

• Im2colLayer 与MATLAB里面的im2col类似,即image-tocolumn
transformation,转换后方便卷积计算

它先将一个大矩阵,重叠地划分为多个子矩阵,对每个子矩阵序列化成向量,最后得到另外一个矩阵。
在caffe中,卷积运算就是先对数据进行im2col操作,再进行内积运算(inner product)。这样做,比原始的卷积操作速度更快。

• LRNLayer 全称local response normalization layer,在Hinton论文中有详细介绍ImageNet Classification with Deep Convolutional Neural Networks 。

此层是对一个输入的局部区域进行归一化,达到“侧抑制”的效果。
参数:全部为可选,没有必须
  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
  }
}

• PoolingLayer Pooling操作

池化层,为了减少运算量和数据维度而设置的一种层。
层类型:Pooling
必须设置的参数:
     kernel_size: 池化的核大小。也可以用kernel_h和kernel_w分别设定。
其它参数:
   pool: 池化方法,默认为MAX。目前可用的方法有MAX, AVE, 或STOCHASTIC
  pad: 和卷积层的pad的一样,进行边缘扩充。默认为0
  stride: 池化的步长,默认为1。一般我们设置为2,即不重叠。也可以用stride_h和stride_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=floor((w0+2*pad-kernel_size)/stride)+1;
 h1=floor((h0+2*pad-kernel_size)/stride)+1;
如果设置stride为2,前后两次卷积部分不重叠。100*100的特征图池化后,变成50*50.
由pad, kernel_size和stride三者共同决定。

1.3 Nets结构

Net由一系列的Layer组成(无回路有向图DAG),Layer之间的连接由一个文本文件描述。模型初始化Net::Init()会产生blob和layer并调用Layer::SetUp。在此过程中Net会报告初始化进程。这里的初始化与设备无关,在初始化之后通过Caffe::set_mode()设置Caffe::mode()来选择运行
平台CPU或GPU,结果是相同的。

Net
就像搭积木一样,一个net由多个layer组合而成。
现给出 一个简单的2层神经网络的模型定义( 加上loss 层就变成三层了),先给出这个网络的拓扑。

这里写图片描述

第一层:name为mnist, type为Data,没有输入(bottom),只有两个输出(top),一个为data,一个为label
第二层:name为ip,type为InnerProduct, 输入数据data, 输出数据ip
第三层:name为loss, type为SoftmaxWithLoss,有两个输入,一个为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配置文件。运行代码一般为

./build/tools/caffe train --solver=examples/myfile/solver.prototxt -gpu all &> examples/myfile/output/output.log
#或者
./build/tools/caffe train -solver examples/myfile/solver.prototxt -gpu all &> examples/myfile/output/output.log

在Deep Learning中,往往loss function是非凸的,没有解析解,我们需要通过优化方法来求解。

• 求解器Solver是什么?
• Caffe的重中之重(核心)——Solver
• 负责对模型优化,让损失函数(loss function)达到全局最小。
• solver的主要作用就是交替调用前向(forward)算法和后向(backward)算法来更新参数,实际上就是一种迭代的优化算法。

优化算法

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

Stochastic Gradient Descent (type: "SGD"),
AdaDelta (type: "AdaDelta"),
Adaptive Gradient (type: "AdaGrad"),
Adam (type: "Adam"),
Nesterov’s Accelerated Gradient (type: "Nesterov") and
RMSprop (type: "RMSProp")

Solver的流程

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

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

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,需要在一个专门的配置文件中对net进行配置,每个net由许多的layer所组成。注意的是:文件的路径要从caffe的根目录开始,其它的所有配置都是这样。

net: "examples/mnist/lenet_train_test.prototxt"

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

train_net:"examples/hdf5_classification/logreg_auto_train.prototxt"
test_net: "examples/hdf5_classification/logreg_auto_test.prototxt"

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

test_iter: 100

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

test_interval: 500

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

base_lr: 0.01
lr_policy: "inv"
gamma: 0.0001
power: 0.75

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


- fixed:   保持base_lr不变.
- step:    如果设置为step,则还需要设置一个stepsize,  返回 base_lr * gamma ^ (floor(iter / stepsize)),其中iter表示当前的迭代次数
- exp:     返回base_lr * gamma ^ iter, iter为当前迭代次数
- 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上一次梯度更新的权重

momentum :0.9

优化算法选择。这一行可以省掉,因为默认值就是SGD,总共有六种方法可选择。

type: SGD

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

weight_decay: 0.0005

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

display: 100

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

max_iter: 20000

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

还可以设置snapshot_diff,是否保存梯度值,默认为false,不保存。
也可以设置snapshot_format,保存的类型。有两种选择:HDF5 和BINARYPROTO ,默认为BINARYPROTO

snapshot: 5000
snapshot_prefix: "examples/mnist/lenet"

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

solver_mode: CPU

solver优化方法

caffe的六种优化方法:

Stochastic Gradient Descent (type: "SGD"),
AdaDelta (type: "AdaDelta"),
Adaptive Gradient (type: "AdaGrad"),
Adam (type: "Adam"),
Nesterov’s Accelerated Gradient (type: "Nesterov") and
RMSprop (type: "RMSProp")

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

这里写图片描述

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

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

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

这里写图片描述

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

在caffe中,默认采用的Stochastic Gradient Descent(SGD)进行优化求解。后面几种方法也是基于梯度的优化方法(like SGD),因此本文只介绍一下SGD。

1、Stochastic gradient descent(SGD)

随机梯度下降(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.9,momentum可以让使用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))

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或者inf的loss值或者输出),此时你需要降低base_lr的值(比如,0.001),然后重新训练,这样的过程重复几次直到你找到可以work的base_lr。

2、AdaDelta

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 type为Adadelta时,需要设置delta的值。

3、AdaGrad

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

自适应梯度(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"

4、Adam

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

具体的介绍文献:

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

5、NAG

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"

6、RMSprop

RMSprop是Tieleman在一次 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值。


2. Caffe 的 I/O 模块

将图片数据转化为LMDB数据

■ 第一步:创建图片文件列表清单,一般为一个txt文件,一行一张图片
■ 第二步:使用Caffe工具命令
convert_imageset [FLAGS] [ROOTFOLDER/] [LISTFILE] [DB_NAME]
需要带四个参数:
FLAGS: 图片参数组
-gray: 是否以灰度图的方式打开图片。程序调用opencv库中的imread()函数来打开图片,默认为false
-shuffle: 是否随机打乱图片顺序。默认为false
-backend:需要转换成的db文件格式,可选为leveldb或lmdb,默认为lmdb
-resize_width/resize_height: 改变图片的大小。在运行中,要求所有图片的尺寸一致,因此需要改变图片大小。 程序调用
opencv库的resize()函数来对图片放大缩小,默认为0,不改变
-check_size: 检查所有的数据是否有相同的尺寸。默认为false,不检查
-encoded: 是否将原图片编码放入最终的数据中,默认为false

示例文件及代码见首行中的百度云共享。

#Image to txt:at the path of "scripts"
ls IO2LMDB/blue | sed "s:^:blue/:" | sed "s:$: 0:" >> data.txt
ls IO2LMDB/red | sed "s:^:red/:" | sed "s:$: 1:" >> data.txt

#txt to LMDB : at the path of "OI2LMDB"
convert_imageset -shuffle:'true' ./data.txt db_train_lmdb

对Layer做参数配置(Data Layer参数配置为例)

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,表示数据来源于LevelDB或LMDB。根
据数据的来源不同,数据层的类型也不同(后面会详细阐述)。一
般在练习的时候,我们都是采用的LevelDB或LMDB数据,因此层
类型设置为Data。

top或bottom: 每一层用bottom来输入数据,用top来输出数据。如
果只有top没有bottom,则此层只有输出,没有输⼊入。反之亦然。如
果有多个 top或多个bottom,表示有多个blobs数据的输入和输出。
data 与 label: 在数据层中,至少有一个命名为data的top。如果有
第二个top,一般命名为label。 这种(data,label)配对是分类模型所
必需的。

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

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

所有数据预处理都在这里设置:

通常数据的预处理(如减去均值, 放大缩小, 裁剪和镜像等),Caffe使用OpenCV做处理

layer {
  name: "cifar"
  type: "Data"
  top: "data"
  top: "label"
  include {
    phase: TEST
  }
  transform_param {
    mean_file: "examples/cifar10/mean.binaryproto"#用一个配置⽂文件来进行均值操作
mirror: 1 # 1表示开启镜像,0表示关闭,也可用ture和false来表示
crop_size: 227 # 剪裁一个227*227的图块,在训练阶段随机剪裁,在测试阶段从中间裁剪
  }
  data_param {
    source: "examples/cifar10/cifar10_test_lmdb"
    batch_size: 100
    backend: LMDB
  }
}

3. Caffe 中五种层的实现和参数配置


3.1 卷积层参数配置

layer {
name: "conv2"
type: "Convolution"
bottom: "pool1"
top: "conv2"
param {
lr_mult: 1 #学习率1,和权值更新相关
}
param {
lr_mult: 2 #学习率2,和权值更新相关
}
convolution_param {
num_output: 50 # 50个输出的map
kernel_size: 5 #卷积核大小为5*5
stride: 1 #卷积步⻓长为1
weight_filler { #权值初始化方式
type: “xavier" #默认为“constant",值全为0,很多时候我们也可以⽤用"xavier"或者”gaussian"来进行初始化
}
bias_filler { #偏置值的初始化⽅方式
type: “constant"#该参数的值和weight_filler类似,
⼀一般设置为"constant",值全为0
}
}
}

3.2 池化层参数配置

layer {
name: "pool1"
type: "Pooling"
bottom: "conv1"
top: "pool1"
pooling_param {
pool: MAX #Pool为池化方式,默认值为MAX,可以选择的参数有MAX、AVE、STOCHASTIC
kernel_size: 2 #池化区域的大小,也可以用kernel_h和kernel_w分别设
置长和宽
stride: 2 #步长,即每次池化区域左右或上下移动的距离,一般和kernel_size相同,即为不重叠池化。也可以也可以小于kernel_size,即为重叠池化,Alexnet中就用到了重叠池化的方法
}
}

3.3 全连接层参数配置

#参数和卷积层表达⼀一样
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"
}
}
}

3.4 激活函数层参数配置

激活函数作用:激活函数是用来引入非线性因素的。
激活函数一般具有以下性质:
■ 非线性: 线性模型的不足我们前边已经提到。
■ 处处可导:反向传播时需要计算激活函数的偏导数,所以要求激活函数除个别点外,处处可导。
■ 单调性:当激活函数是单调的时候,单层网络能够保证是凸函数。
■ 输出值的范围: 当激活函数输出值是有限的时候,基于梯度的优化方法会更加稳定,因为特征的表示受有限权值的影响更显著

layer {
name: "relu1"
type: "ReLU"
bottom: "ip1"
top: "ip1"
}

Type为该层类型,可取值分别为:
(1)ReLU:表示我们使用relu激活函数,relu层支持in-place计算,这意味
着该层的输入和输出共享一块内存,以避免内存的消耗。
(2)Sigmoid:代表使用sigmoid函数;
(3) TanH:代表使用tanh函数;
(4) AbsVal:计算每个输入的绝对值f(x)=Abs(x)
(5)power对每个输入数据进行幂运算
f(x)= (shift + scale * x) ^

layer {
name: "layer"
bottom: "in"
top: "out"
type: "Power"
power_param
{
power: 2
scale: 1
shift: 0
}
}

power
层类型:Power
可选参数:
  power: 默认为1
  scale: 默认为1
  shift:默认值为0
  

3.5 softmax层

#可以计算给出每个样本
对应的损失函数值
layer {
name: "loss"
type:
"SoftmaxWithLoss"
bottom: "ip2"
bottom: "label"
top: "loss"
}

#输出为每个类别的概率值
layers {
name: "prob"
type: “Softmax"
bottom: " ip2"
top: "prob"
}

4. Caffe的可视化

4.1 网络结构可视化和特征可视化

可参考
http://blog.csdn.net/jiandanjinxin/article/details/50686461

http://blog.csdn.net/jiandanjinxin/article/details/50410290

4.2 可视化loss和accurary 曲线

cd caffer-master
sh data/mnist/get_mnist.sh
sh examples/mnist/create_mnist.sh
sh examples/mnist/train_lenet.sh &> output.log
plot_training_log.py.example -h #可查看用法
./tools/extra/plot_training_log.py.example 0 testaccuracyvsiters.png output.log
./tools/extra/plot_training_log.py.example 2 testaccuracyvsiters.png output.log

这里写图片描述

这里写图片描述

使用训练好的模型

均值文件mean file

■ 将所有训练样本的均值保存为文件
■ 图片减去均值后,再进⾏行训练和测试,会提高速度和精度
■ 运行方法:(使用Caffe⼯工具)
compute_image_mean [train_lmdb] [mean.binaryproto]

fine-turn微调网络

■ 1. 准备新数据的数据库(如果需要用mean file,还要准备对应的新的mean file), 具体方法和图片转换lmdb方式一样。
■ 2. 调整网络层参数:
■ 将来训练的网络配置prototxt中的数据层source换成新数据的数据库。
■ 调整学习率,因为最后一层是重新学习,因此需要有更快的学习速率相比较其他层,因此我们将,weight和bias的学习速率加快。
■ 3. 修改solver参数
■ 原来的数据是从原始数据开始训练的,因此一般来说学习速率、步长、迭代次数都比较大,fine turning微调时,因为数据量可能减少了,所以一般来说,test_iter,base_lr,stepsize都要变小一点,其他的策略可以保持不
变。
■ 4. 重新训练时,要指定之前的权值文件:
■ # caffe train –solver [新的solver文件] –weights [旧的caffemodel]

基本上,finetuning的想法就是说,在imagenet那么大的数据集上train好一个很牛的网络了,那别的task上肯定也不错,所以我们可以把pretrain的网络拿过来,然后只重新train最后几层,重新train的意思是说,比如我以前需要classify imagenet的一千类,现在我只想识别是狗还是猫,或者是不是车牌,于是我就可以把最后一层softmax从一个4096*1000的分类器变成一个4096*2的分类器,这个strategy在应用中非常好使,所以我们经常会先在imagenet上pretrain一个网络,因为我们知道imagenet上training的大概过程会怎么样。

fine tuning过程是用已有的模型来初始化现有的模型,那在fine tuning的过程中,怎么在fine tuning的时候,不更新某些层的参数呢?
这个在caffe里面可以设置一些layer的learning rate为零来实现

5. 深度学习模型训练 Tricks

更多trick可见书籍《Neural Networks: Tricks of the Trade》

5.1 数据准备与扩增

1.1 数据准备:
一般数据集可能不会给出验证集,所以自⼰己会从给的训练集中按照一定比例(9:1)分离出验证集。

1.2 数据的扩增
因为深度网络需要在大规模的训练图像上来满足性能,所以当原始图像中的训练数据集规模不够多时,较好的办法是扩增数据来提升模型性能。换言之,数据扩增对于训练深度网络来说是必须的。

常用的方法:
1. 沿着x轴将图片左右翻转
2. 随机的剪切、缩放、旋转
3. 颜色抖动
4. 提高图像中像素的饱和度和值(即 HSV颜色空间的 S 和 V 成分)到 0.250.25 和44 之间(在一个样本图像内要保证各个像素该值是一样的),再在图像上加上一个范围在 [−0.1,0.1][−0.1,0.1] 之间的值给 H( hue ,即 HSV 中的色调)这个成分。
5. 用pca来改变RGB的强度值,产生分别对应的特征值和特征向量,然后用均值为0方差为0.1的随机数与特征值和特征向量相乘得到新的数据。(《ImageNet Classification with Deep Convolutional Neural Networks》)

5.2 图像预处理

常见的是减均值、除方差,还有变化到-1〜1,主要针对不同尺度的特征,进行尺度变换normaliz。
常用的预处理方法:
1. 去均值和规范化
通常作为第一步且较简单的一种方式是去均值(zero-centered ,通俗地说:让每个样本都减去整体样本的均值,使整体样本的新均值为 0),并规范化(normalize)它们。
另一种在预处理用于规范化(normalize)数据的方法是将每一个维度的最大最小值分别限定为1 和 −1 。

在此过程中,数据先经过去均值,然后计算出(能刻画数据内部相关结果的)协方差矩阵:

>>> X -= np.mean(X, axis = 0) # 去均值
>>> cov = np.dot(X.T, X) / X.shape[0] # 计算协⽅方差矩阵

之后对数据去相关,⽅方法是将(刚刚去均值后的)原始数据投影到特征基(eigenbasis )上:

>>> U,S,V = np.linalg.svd(cov) # 对数据的协⽅方差矩阵计算 SVD 分解
>>> Xrot = np.dot(X, U) # 对数据去相关

最后一步是白化,它对去相关后的数据在每个维度上的特征值做尺度规范化处理:

>>> Xwhite = Xrot / np.sqrt(S + 1e-5) # 除以特征值(其实是奇异值的开平方根)

5.3 参数初始化

■ 训练网络前对参数做初始化。
■ 常用的初始化方法:
1. 全零初始化 —> 错误
2. 小随机数初始化
是一种接近 0 但不是 0的权重初始化方法。
做法是初始化权重为接近 0 的随机小数,因为很接近 0 但不相等,这也被称为“对称破缺”( symmetry breaking )。

  1. 全零初始化的错误原因:这会导致网络中每个神经元的输出结果一样,即经过完全相同的参数更新,得到的反向传播计算的梯度也相同。换言之,若初始权值相同,神经元就不具有非对称性(asymmetry)。
  2. 小随机数初始化:一开始时,每个神经元都是随机且独特的,所以它们
    在训练中计算出的更新是不同的,并在之后整合自己的“贡献”作为整个
    网络的不同部分。权重初始化的过程多少有点像 weights0.001×N(0,1),其中 N(0,1)N表示均值为 0 ,标准差为 1 的高斯分布。当然也可以使用服从均匀分布的随机小数,但在二者在实际中的性能表现上只有很微弱的差别。
  3. 推荐方法
    先前通过校准神经元上的方差来初始化参数并未考虑使用 ReLUs这样的激活函数。最近一篇论⽂文《Surpassing Human-Level Performance on ImageNet Classification》讨论了如何为 ReLUs这样的激活函数做参数初始化,从而使网络中神经元的方差为 2.0/n,初始化⽅方式如下:
>>> w = np.random.randn(n) * sqrt(2.0/n) # ⺫⽬目前推荐做法

5.4 卷积参数的Tricks

■ 1. 图片输入是2的幂次方,例如32、64、96、224等。
■ 2. 卷积核大小是3*3或者5*5。
■ 3. 输入图片上下左右需要用0补充,即padding,且假如卷积核大小是5
那么padding就是2(图⽚片左右上下都补充2),卷积核大小是3padding
大小就是1。

5.5 池化层参数初始化Tricks

■ 1. poolin层也能防⽌止过拟合,使用overlapped pooling,即用来池化
的数据有重叠,但是pooling的⼤大小不要超过3,常用的池化是2X2。
■ 2. max pooling比avg pooling效果会好一些。

5.6 学习率

■ 1. 0.1是学习率的常用值
■ 2. 在实际中,如果在验证集上看不到性能的提升(如损失函数值下降或者准确率上升),那就可以对当前的学习率除以 2(或 5 )看看效果并循环这一过程,或许能给你一个惊喜。

5.7 正则化:防止过拟合

过拟合,就是拟合函数需要顾忌每一个点,最终形成的拟合函数波动很大。在某些很小的区间里,函数值的变化很剧烈。这就意味着函数在某些小区间里的导数值(绝对值)非常大,由于自变量值可大可小,所以只有系数足够大,才能保证导数值很大。而正则化是通过约束参数的范数使其不要太大,所以可以在一定程度上减少过拟合情况。

常用防止过拟合方式:
1. L2正则化
2. L1正则化
3. 最大模限制
4. Dropout

Dropout 是一个超级有效、简单且是前阵子由 Srivastava 等人提出
《Dropout: A Simple Way to Prevent Neural Networks from
Overfitting》的,它是其它正则方法(如 L1 、 L2 、 最大模限制 )的
补充。在训练中, dropout 可理解为对整个神经网络进行抽样(出的
网络),并基于输入数据仅仅更新抽样网络的参数。(因为这些抽样
得到的网络是共享参数的,所以这些抽样出的网络的权重参数并非是
独立的)。

5.8 观察损失曲线:学习率
这里写图片描述

5.9 放大损失曲线:学习率、batch大小

这里写图片描述
5.10 观察准确率曲线
这里写图片描述
5.11 fine-turn的方法

■ 如果你的数据量有限,那么,一般不建议自己完全从头训练起caffe
模型。一般是找相关的项目或者模型,先finetuning一下,之后再慢
慢的调整。一般fine tuning的方式,都是把learning rate(solver.prototxt)调低(为原来的十分之一),之后把训练模型的
最后一层或者两层的学习速率调大一点————这就相当于,把模
型的前面那些层的学习调低,使得参数更新的慢一点以达到微调的目的。
■ 微调的时候,有时候训练数据特别少,而且希望模型的前面几层的
参数保持不变。方法是使得这几个层的学习速率为0就可以了,比
如设定lr_mult为0。

5.12 模型集成

■ 在机器学习中,集成方法( ensemble methods )是指训练多个学习器并在之后将它们组合使用,最终得到一个强有力的分类器的方法。

■ 几种集成方式的技巧:
■ 1. 集成不同初始化的模型
使用交叉验证集来确定最佳的超参数,再在基于最佳超参数的情况下,使用不同的随机初始化方法来初始化权重来训练多个模型。该方法的风险在于权重初始化方法的不同产生的差异。
■ 2.集成 topN 表现的模型
使用交叉验证集确定了最佳的超参数后,再选取表现最佳的前 topN 个模型进行集成。这可以提升集成模型的多样性,但⻛风险就是这几个模型都是局部最优模型。实际实践中,这种做法可以达到不错的性能,因为不需要(在交叉验证后)对模型进行额外的重新训练。实际上,可以直接在 Caffe Model Zoo 中选择表现性能在 topN 的几个深度模型进行集成。

问:“目前deep learning用在小数据集上有什么好的方法吗?在小数据集的问题上是不是可以通过减少网络的层数来减少过拟合?”
答:小数据集基本上需要通过小的模型来防止overfit,当然如果数据集是图像等等,也可以通过finetuning。另外一个可能是直接手标更多数据,有时候糙快猛但是还挺好使的。
发表评论

0个评论

我要留言×

技术领域:

我要留言×

留言成功,我们将在审核后加至投票列表中!

提示x

人工智能开发框架知识库已成功保存至我的图谱现在你可以用它来管理自己的知识内容了

删除图谱提示×

你保存在该图谱下的知识内容也会被删除,建议你先将内容移到其他图谱中。你确定要删除知识图谱及其内容吗?

删除节点提示×

无法删除该知识节点,因该节点下仍保存有相关知识内容!

删除节点提示×

你确定要删除该知识节点吗?