在之前的文章中,整理了 ubuntu18安装和caffe-cpu安装问题汇总(含详细流程),这篇文章则对caffe的各个层进行一个剖析。文章篇幅较长,可根据目录按层选择阅读。
Net是由Layer层层组成的
Net是由Solver文件支配的,Net的很多的hyper parameters都是由solver支配的。
第一步,用一个卷积网络,要有数据才行,首先要对数据进行一些预处理.比如你需要把数据做成(caffe支持的文件/夹格式)数据源的格式, 然后你再把数据集交给caffe, caffe再帮你做预测分类等
第二步, 需要定义一个网络,卷积神经网络是有各种各样的,如何定义各个层结构呢? 不需要!只需要去修改配置文件,按照一定流程去做。
所有的层都是按照流程图的格式,一层一层往下写的,每一层里都有很多可选项,参数项
第三步,由于net会受到一些hyper parameters的控制,所以我们需要定义一些solver控制hyper parameters
solver就是专门配置你的caffe网络参数和超参数的文件,也是个prototxt文件
第四步,训练网络,只运行一个脚本文件,如果是linux系统,那一行命令就ok
看一下caffe的网络配置文件,我们该如何从头到尾去写这个东西呢??每一层的每个参数都表达什么意思呢?
下载好caffe后,可以去examples文件夹查看一些示例
关于LMDB相关内容,参考文章:数据集转换成LMDB格式
layer {
name: "cifar" # 就是你想给这个层叫什么名字你自己定
type: "Data" # 写成data告诉caffe这个层是数据层
top: "data"
top: "label"
# 每一层既有输入又有输出, 我们用bottom表示输入,top表述输出,多个top表示多个输出
# 这是数据层,所以不存在输入的情况,只有输出
# 数据层输出两个东西,一个是数据data, 一个是标签label
include {
phase: TRAIN
}
}
# 一般一边训练一边测试,训练几千次之后测试一下,这样可以提前终止,
# 这个实现方法就在include里边phase(阶段):
# 写了个TRAIN 意思就是这层的数据,只有在我们训练层的时候才会用到,测试的时候不会用到TEST同理,测试层才用
transform_param {
mean_file: "examples/cifar10/mean.binaryproto"
transform_param{
scale:0.00390625
mirror: 1
crop_size: 227
}
}
# mean_file:这里求均值是为了对数据初始化操作, 这里可以思考正态分布转变成标准正态分布,当然是每个通道的像素减去这个通道的均值. 这里是一个配置文件, 通过这个配置文件来实现的,至于怎么做出来的,等会你就知道了
# scale是用来归一化的, 就是0-255到0-1,为什么是0.00396625呢?是通过1/255=0.0039得到的
# mirror镜像, 意思就是数据增强,把每个图片都镜面对称一下, 你不就有2倍的数据了吗, 1就是开启,0就是关闭
# crop剪裁:如果你觉得数据比较少,那你可以通过随机剪裁,进行数据增强, 一个图给你剪成好多个图,你不多了好多倍 的数据了吗
data_param {
source: "examples/cifar10/cifar10_train_lmdb"
batch_size: 111
backend: LMDB
}
# data_param 数据参数
# 一般情况下caffe要求我们的数据集格式是lmdb, 我们要把数据搞成数据库的形式,然后把数据库的源交给caffe,就是根目录,做成数据库的话方便caffe读取,你想想用规则读取快还是不用规则读取快呢????另外,你读取数据是cpu来搞的, 你gpu是用来计算数据的, 所以你读取数据也得很快才行,
# source:就是你创建好的LMDB格式数据集的根目录所在位置
# batch_size是每次喂给net的样本个数,一般是2的n次幂,一般是越大越好,但是你要考虑你的硬件限制,一般GPU是8G的,1080是8G, TitanX12G, 显存决定了你的batch_size
# backend:数据类型的名称 比如LMDB
layer {
name: "cifar"
type: "Data"
top: "data"
top: "label"
include {
phase: TEST
}
transform_param {
mean_file: "examples/cifar10/mean.binaryproto"
}
data_param {
source: "examples/cifar10/cifar10_test_lmdb"
batch_size: 1000
backend: LMDB
}
}
layer {
name: "data"
type: "HDF5Data"
top: "data"
top: "label"
include {
phase: TRAIN
}
hdf5_data_param {
source: "examples/hdf5_classification/data/train.txt"
#这个txt文件里写了每个小数据库的具体路径,这里后续还会介绍
batch_size: 10
}
}
layer {
name: "data"
type: "HDF5Data"
top: "data"
top: "label"
include {
phase: TRAIN
}
image_data_param {
source: "examples/xjh_images/data/train.txt"
batch_size: 10
new_height: 256
new_width:256
}
}
LMDB文件常用来做分类,HDF5常用来做回归
数据层介绍了三种数据层:HDF5、LMDB和直接输入图片。这里解释卷积层的相关信息。
layer {
name: "conv1" #该层的名字
type: "Convolution" #该层的类型,即卷积
bottom: "data" #输入层的名字
top: "conv1" #输出层的名字,注意这里就是本层
param {
lr_mult: 1
}
param {
lr_mult: 2
}
#param参数解释
#lr_mult 本层的学习率,并不是模型的学习率,模型的学习率是通过solver文件控制的
# 其实lr_mult表示的是模型学习率的系数
#如果该层有两个lr_mult参数,那么第一个表示模型学习率在本层的权值,第二个则表示对应偏置项,一般偏置=2*权值
convolution_param { #卷积参数
num_output: 32 #输出的通道数channels,即卷积核的个数,如果有c个核,那么得到c个featuremaps一般来说channels=c
pad: 2 #边缘处理方式,0表示不扩充
kernel_size: 5 #卷积核大小
stride: 1 #卷积核滑动步长
weight_filter { #卷积核的权值
type:"xvaier" #type表示卷积核的类型,即gauss核还是全0核等
#全0核则默认type:"constant"
#常用xvaier算法来初始化gaussian
}
bias_filter { #偏置项初始化
type:"constant" #默认则偏置项=0
}
}
}
输入如果是width=w, height=h
那么输出w' = [(w-k+2p)/s] +1
h'=[(h-k+2p)/s] +1
p就是padding边缘处理
stride就是步长
pool翻译成合并更能表示出pool层的作用:把kernel内覆盖的多个像素,合并成一个。
layer {
name: "pool1"
type: "Pooling"
bottom: "conv1"
top: "pool1"
pooling_param {
pool: MAX #池化方法,有取最值max,也有取覆盖点均值等方法
kernel_size: 3 #核大小
stride: 2 #步长
}
}
上述设定了卷积层,但是卷积层需要输入给激活函数,将激活函数的结果作为输出。
激活函数的输入层一般是池化后的卷积层,即卷积层-池化层。
layer {
name: "relu1"
type: "ReLU" #使用的函数是ReLU函数
bottom: "pool1" #池化层
top: "pool1"
}
相同名字的bottom和top这些blob就是同一个blob,占用的是同一个空间。
简单来解释就是:
int a;
a = 0;
a = 1;
你可以无数次对这个a进行改变。
对于blob来说也是一样。
至于谁先谁后,那就是看你的网络定义哪个layer在前,它就先计算。
如果有两个layer输出的blob名称是一样的,那么它们的输入blob也一定会有这个blob,也就是,如果layer不是对输入blob本身操作,就不允许输出blob同名。比如:
layer1和layer2的输入和输出blob都是blob1,它们都是对blob1进行操作,这是允许的,直接按顺序计算就可以了。
layer1的输入blob是blob1,输出blob是blob_out,layer2的输入blob是blob2,输出blob也是blob_out,那么这就是不允许的。因为它们不是对它们的输入blob本身进行操作,假设你允许这样的操作,那么后运算的layer会将blob_out覆盖成后运算的结果,前面运算的blob_out的结果就消失了。
当然,layer1和layer2的输入和输出blob都是blob1,它们都是对blob1进行操作,比如layer1先计算,然后layer2后计算,计算layer2的结果也是会把layer1的结果给覆盖,只不过是网络已经不需要这个layer1的结果而已,因为它已经前向传播过去了...
FC层即全链接层,下文type:“InnerProduct”。
全链接层就是把之前提取到的特征,进行一个简单的组合,包装成一个向量的形式,根据这个向量就可以对其特征向量进行分析。
layer {
name: "ip1"
type: "InnerProduct"
bottom: "pool3"
top: "ip1"
param {
lr_mult: 1
decay_mult: 250
}
param {
lr_mult: 2
decay_mult: 0
}
inner_product_param {
num_output: 500
weight_filter {
type:"xavier"
}
bias_filter {
type:"constant"
}
}
}
layer {
name:"accuracy"
type:"Accuracy"
bottom:"ip2"
bottom:"label"
top:"accuracy"
include {
phase:TEST
}
}
layer {
name: "loss"
type: "SoftmaxWithLoss"
bottom: "ip1"
bottom: "label"
top: "loss"
}
首先要把你预测的结果归一化,就是值域在0-1,相当于分类的概率值;
loss的计算方法你说了算,可以是softmax的loss数值也可以是softmax概率值;
loss层的输入是全连接层,和真实值层。
为什么存在reshape层,因为想要在不改变数据的情况下,改变维度
比如[3, 15, 28, 28] 变成[3*15, 28*28]
layer {
name:"reshape"
type:"Reshape"
bottom:"input"
top:"output"
reshape_param {
shape {
dim : 0
dim : 2
dim : 3
dim : -1
}
}
}
reshape_param参数:
上述shape{}中的竖排:
dim:a
dim:b
dim:c
dim:d
是常见写法的:[a, b, c, d]
dim后的数字表示:示例dim:x
如果是具体的数值,比如把b换成23,就是你制定了b原来的数改成23
还记得dropout吗?防止过拟合的,就是随机断开网络连接线,迭代一次
layer {
name: "drop7"
type: "Dropout"
bottom: "fc7-conv"
top: "fc7-conv"
dropout_param {
dropout_ratio: 0.5 #迭代一次断开的线数,0.5即50%
}
}
环境:ubuntu18.04,caffe-cpu
进入下载的caffe/examples/mnist/,选择lenet_train_test.prototxt,可以自行查看学习。
在下面代码中标注了,Net中哪个部分设置了网络输出的纬度。(FC层)
name: "LeNet"
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
}
}
layer {
name: "mnist"
type: "Data"
top: "data"
top: "label"
include {
phase: TEST
}
transform_param {
scale: 0.00390625
}
data_param {
source: "examples/mnist/mnist_test_lmdb"
batch_size: 100
backend: LMDB
}
}
layer {
name: "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"
}
}
}
layer {
name: "pool1"
type: "Pooling"
bottom: "conv1"
top: "pool1"
pooling_param {
pool: MAX
kernel_size: 2
stride: 2
}
}
layer {
name: "conv2"
type: "Convolution"
bottom: "pool1"
top: "conv2"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
convolution_param {
num_output: 50
kernel_size: 5
stride: 1
weight_filler {
type: "xavier"
}
bias_filler {
type: "constant"
}
}
}
layer {
name: "pool2"
type: "Pooling"
bottom: "conv2"
top: "pool2"
pooling_param {
pool: MAX
kernel_size: 2
stride: 2
}
}
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"
}
}
}
layer {
name: "relu1"
type: "ReLU"
bottom: "ip1"
top: "ip1"
}
layer {
name: "ip2"
type: "InnerProduct"
bottom: "ip1"
top: "ip2"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
inner_product_param {
num_output: 10 #注意:这里就是输出结果的纬度,即10个分类
weight_filler {
type: "xavier"
}
bias_filler {
type: "constant"
}
}
}
layer {
name: "accuracy"
type: "Accuracy"
bottom: "ip2"
bottom: "label"
top: "accuracy"
include {
phase: TEST
}
}
layer {
name: "loss"
type: "SoftmaxWithLoss"
bottom: "ip2"
bottom: "label"
top: "loss"
}
caffe提供了一个solver文件,根据这个solver进行修改,然后保存到项目根目录下即可。
solver是个.prototxt文件,通过mnist文件夹内的文件来看一下solver是如何控制haper parameters的。
# lenet_solver.prototxt文件一览
# The train/test net protocol buffer definition
net: "examples/mnist/lenet_train_test.prototxt"
# test_iter specifies how many forward passes the test should carry out.
# In the case of MNIST, we have test batch size 100 and 100 test iterations,
# covering the full 10,000 testing images.
test_iter: 100
# Carry out testing every 500 training iterations.
test_interval: 500
# The base learning rate, momentum and the weight decay of the network.
base_lr: 0.01
momentum: 0.9
weight_decay: 0.0005
# The learning rate policy
lr_policy: "inv"
gamma: 0.0001
power: 0.75
# Display every 100 iterations
display: 100
# The maximum number of iterations
max_iter: 10000
# snapshot intermediate results
snapshot: 5000
snapshot_prefix: "examples/mnist/lenet"
# solver mode: CPU or GPU
solver_mode: GPU
往往loss function是非凸函数,没有解析解,需要通过优化方法来求解析解
caffe提供了六种常用的优化算法, 需要在solver文件中进行配置,即通过type关键字来选择
Stochastic Gradient Descent (type:"SGD")
AdaDelta (type:"AdaDelta")
Adaptive Gradient (type:"AdaGrad")
Adam(type:"Adam")
Nesterov's Accelerated Gradient (type:"Nesterov")
RMSprop(type:"RMSProp")
上述就是六种算法名字及在solver文件中type关键字对应的参数名
# 自己写一个solver.prototxt文件
net: "examples/mnist/lenet_train_test.prototxt"
# net就是网络配置文件的路径
test_iter: 100
# 一次迭代100个测试batch,
# 测试和训练时,都是一个batch一个batch喂给net的,test_iter表示了一次给net是100个batch
# test_iter的大小 = (样本个数)/(batch包含样本数)
test_interval: 500
# 表示每训练500次,测试一次
base_lr: 0.01
# 基础学习率,就是上文提到的网络模型的学习率
# 实习学习率 = 每个conv层设置的学习率权值数 * base_lr + 每个conv层设置的偏置项
# 若想修改学习率,最好只修改此处。
momentum: 0.9
# 动量值,一般设置为0.9且常不修改
weight_decay: 0.0005
# 不知道是什么
lr_policy: "inv"
# 学习策略
gamma: 0.0001
# 不知道是什么
power: 0.75
# 不知道是什么
display: 100
# 每训练100次打印一次结果,display:0时不显示
max_iter: 10000
# 最大迭代次数,即迭代10000次时停止训练
snapshot: 5000
# 快照保存模型,即当迭代5000次时保存一下模型
snapshot_prefix: "examples/mnist/lenet"
# 快照存储位置
solver_mode: GPU
# net的运行模,CPU/GPU