本文更多资料来源于罗韵北京深度学习资料和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)后面都有一个数字,这个数字是其名字的一个标签。这个数字就是用来在生成的二进制文件中搜索查询的标签。关于这个数字,1到15会花费1byte的编码空间,16到2047花费2byte。所以⼀一般建议把那些频繁使用的名字的标签设为1到15之间的值。而后面的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: 100backend: 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: truecrop_size: 227mean_file: "data/ilsvrc12/imagenet_mean.binaryproto"}window_data_param {source: "examples/finetune_pascal_detection/window_file_2007_trainval.txt"batch_size: 128fg_threshold: 0.5bg_threshold: 0.5fg_fraction: 0.25context_pad: 16crop_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: 2scale: 1shift: 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: 500weight_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 belowdim: 2dim: 3dim: -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: 0dim: 14dim: -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 最常用的卷积操作
层类型:Convolutionlr_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: 20kernel_size: 5stride: 1weight_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: LRNbottom: "pool1"top: "norm1"lrn_param {local_size: 5alpha: 0.0001beta: 0.75}
}
• PoolingLayer Pooling操作
池化层,为了减少运算量和数据维度而设置的一种层。
层类型:Pooling
必须设置的参数:kernel_size: 池化的核大小。也可以用kernel_h和kernel_w分别设定。
其它参数:pool: 池化方法,默认为MAX。目前可用的方法有MAX, AVE, 或STOCHASTICpad: 和卷积层的pad的一样,进行边缘扩充。默认为0stride: 池化的步长,默认为1。一般我们设置为2,即不重叠。也可以用stride_h和stride_w来设置。
示例
layer {name: "pool1"type: "Pooling"bottom: "conv1"top: "pool1"pooling_param {pool: MAXkernel_size: 3stride: 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: 100backend: 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: 100backend: 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 )。
- 全零初始化的错误原因:这会导致网络中每个神经元的输出结果一样,即经过完全相同的参数更新,得到的反向传播计算的梯度也相同。换言之,若初始权值相同,神经元就不具有非对称性(asymmetry)。
- 小随机数初始化:一开始时,每个神经元都是随机且独特的,所以它们
在训练中计算出的更新是不同的,并在之后整合自己的“贡献”作为整个
网络的不同部分。权重初始化的过程多少有点像 weights0.001×N(0,1),其中 N(0,1)N表示均值为 0 ,标准差为 1 的高斯分布。当然也可以使用服从均匀分布的随机小数,但在二者在实际中的性能表现上只有很微弱的差别。 - 推荐方法
先前通过校准神经元上的方差来初始化参数并未考虑使用 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。另外一个可能是直接手标更多数据,有时候糙快猛但是还挺好使的。