1.生成lmdb
lmdb是caffe训练网络用的数据格式,因此我们需要将原始的图片数据转换成lmdb(当然caffe中也可以直接用jpg进行训练)。利用上文Caffe | 你的第一个分类网络之数据准备中得到的train.txt和test.txt结合GitHub上caffe自带的批处理文件create_imagenet.sh就可以生成lmdb文件,该批处理文件存在如下所示的路径中。
基于train.txt,test.txt以及原始的图片,并根据下面代码所示修改后在命令窗口使用sh create_imagenet.sh就可以生成赌对应的lmdb文件了(具体修改策略看下边代码中的中文注释)。
#!/usr/bin/env sh
# Create the imagenet lmdb inputs
# N.B. set the path to the imagenet train + val data dirs
set -e
EXAMPLE=/home/YL/DataSet #该路径为lmdb存储路径
DATA=/home/YL/DataSet #该路径为train.txt所在路径
TOOLS=/home/caffe/build/tools #该路径为编译完caffe的路径(就是你安装的caffe路径)
TRAIN_DATA_ROOT=/home/YL/DataSet/
VAL_DATA_ROOT=/home/YL/DataSet/
# Set RESIZE=true to resize the images to 256x256. Leave as false if images have
# already been resized using another tool.
RESIZE=false #该参数表示是否要改变图片的大小
if $RESIZE; then
RESIZE_HEIGHT=256
RESIZE_WIDTH=256
else
RESIZE_HEIGHT=0
RESIZE_WIDTH=0
fi
if [ ! -d "$TRAIN_DATA_ROOT" ]; then
echo "Error: TRAIN_DATA_ROOT is not a path to a directory: $TRAIN_DATA_ROOT"
echo "Set the TRAIN_DATA_ROOT variable in create_imagenet.sh to the path" \
"where the ImageNet training data is stored."
exit 1
fi
if [ ! -d "$VAL_DATA_ROOT" ]; then
echo "Error: VAL_DATA_ROOT is not a path to a directory: $VAL_DATA_ROOT"
echo "Set the VAL_DATA_ROOT variable in create_imagenet.sh to the path" \
"where the ImageNet validation data is stored."
exit 1
fi
echo "Creating train lmdb..."
rm -rf $EXAMPLE/train_lmdb #这两句表示在生成lmdb前先把老的删除
rm -rf $EXAMPLE/test_lmdb #因为生成lmdb时,若同路径下有同名文件会出错
GLOG_logtostderr=1 $TOOLS/convert_imageset \
--resize_height=$RESIZE_HEIGHT \
--resize_width=$RESIZE_WIDTH \
--shuffle \
$TRAIN_DATA_ROOT \
$DATA/train.txt \
$EXAMPLE/train_lmdb
echo "Creating val lmdb..."
GLOG_logtostderr=1 $TOOLS/convert_imageset \
--resize_height=$RESIZE_HEIGHT \
--resize_width=$RESIZE_WIDTH \
--shuffle \
$VAL_DATA_ROOT \
$DATA/test.txt \
$EXAMPLE/test_lmdb
echo "Done."
该过程中可能会遇到的问题:
- 问题1:
若是遇到 a total of 0 images,类似问题,基本就是图片路径的问题,尽量把sh文件中的路径换成绝对路径,所以我上述的例子中所有的路径均为绝对路径。 - 问题2:
若是遇到 Check failed: mkdir(source.c_str(), 0744) == 0 (-1 vs 0),类似问题,基本就是没有在sh文件中删掉上一次生成的lmdb文件,在之前sh文件中加入上述注释中的两句rm指令就可以解决这个问题了。 - 问题3:
有的时候会遇到permission denied,这个时候修改一下文件夹权限(利用chmod指令)
2.生成均值文件
图片减去均值后,归一化后,再进行训练和测试,会提高速度和精度。因此,一般在各种模型中都会有这个操作。那么这个均值怎么来的呢,主要有两种方式第一种就是直接将均值设置为128,但若遇到一些填充过的样本,那么均值就会和128相差较多,这种情况下就要用第二种方法。第二种方法:实际上就是计算所有训练样本的平均值,计算出来后,保存为一个均值文件,在以后的测试中,就可以直接使用这个均值来相减,而不需要对测试图片重新计算。而利用第二种方法时,可以用caffe自带的策略。当然某些场景下需要得到Python中可用的均值文件,那也可以用Python脚本自己计算。
(1)caffe计算均值文件
caffe中使用的均值数据格式是binaryproto, caffe的作者为我们提供了一个计算均值的文件compute_image_mean.cpp,放在caffe根目录下的tools文件夹里面。编译后的可执行体放在 build/tools/ 下面,安装如下mnist实例所示调用即可。
#注意下面3句话要写在一行上,用空格分开(此处便于展示,进行了分段)
sudo
build/tools/compute_image_mean examples/mnist/mnist_train_lmdb
examples/mnist/mean.binarypro
主要就是两个参数:
- 第一个参数:examples/mnist/mnist_train_lmdb, 表示需要计算均值的数据,格式为lmdb的训练数据。
- 第二个参数:examples/mnist/mean.binaryproto, 计算出来的结果保存文件。
(2)Python计算均值文件
如果我们要进行特征可视化等操作,可能就会用到npy形式的文件。整体思路为:先用lmdb格式的数据,计算出对应的二进制格式的均值,最后再转换成npy格式的均值。首先先将下述代码保存为convert_mean.py。
#!/usr/bin/env python
import numpy as np
import sys,caffe
if len(sys.argv)!=3:
print "Usage: python convert_mean.py mean.binaryproto mean.npy"
sys.exit()
blob = caffe.proto.caffe_pb2.BlobProto()
bin_mean = open( sys.argv[1] , 'rb' ).read()
blob.ParseFromString(bin_mean)
arr = np.array( caffe.io.blobproto_to_array(blob) )
npy_mean = arr[0]
np.save( sys.argv[2] , npy_mean )
在得到convert_mean.py文件后,在命令行输入如下所示的指令即可生成对应的npy格式的均值文件了。
sudo python convert_mean.py mean.binaryproto mean.npy
3.构建train_test.prototxt文件
得到lmdb或者均值文件之后,就可以构建如下所示的train_test.prototxt文件了。该文件需要更改的就是lmdb文件,若要添加均值文件,就把下述的mean_file:中的128改成对应的均值文件。还需要修改的就是最后输出的类别个数,分几类就可写几类。该prototxt文件其实就是网络的整体结构,根据下面的prototxt文件就可以得到对应的网络(比如lenet,vgg,mobilenet等等),我们用不同的网络结构就会有不同的train_test.prototxt。下述的网络为lenet的网络结构。
name: "LeNet"
layer {
name: "Input"
type: "Data"
top: "data"
top: "label"
include {
phase: TRAIN
}
transform_param {
scale: 0.00390625
mean_file:128
}
data_param {
source: "/home/YL/DataSet/train_lmdb"
batch_size: 64
backend: LMDB
}
}
layer {
name: "Input"
type: "Data"
top: "data"
top: "label"
include {
phase: TEST
}
transform_param {
scale: 0.00390625
mean_file:128
}
data_param {
source: "/home/YL/DataSet/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: 4
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"
}
就想上面说到的,我们可以利用train_test.prototxt文件得到对应的网络结构。具体操作为我们可以打开链接:http://ethereon.github.io/netscope/#/editor
进入下述链接后,会展示出如下所示的界面:
得到该界面后,将上述的train_test.prototxt复制到界面左边黑色的部分。将鼠标的光标定位在黑色部分,并同时按下Enter和Shift就会显示出网络结构图了,如下图所示。
4.构建solver.prototxt文件
构建完train_test.prototxt,也就是网络输入和结构之后,就需要构建solver.prototxt文件。solver算是caffe的核心的核心,它协调着整个模型的运作。该文件主要包含的是一些深度网络训练的超参数。比如学习率,学习率下降规则,优化器,多少步训练后展示一次,多少步训练后进行一次测试。下面将详细解释各个参数的作用。
#train_test.prototxt的路径
net: "/home/YL/DataSet/train_test.prototxt"
#测试间隔和每batch图片数
test_iter: 100
test_interval: 500
# 基础学习率和学习率策略
base_lr: 0.01
lr_policy: "inv"
gamma: 0.0001
power: 0.75
base_lr: 0.01
momentum: 0.9
weight_decay: 0.0005
# 下面是multistep的示例
#lr_policy: "multistep"
#gamma: 0.9
#stepvalue: 5000
#stepvalue: 7000
#stepvalue: 8000
#stepvalue: 9000
#stepvalue: 9500
#优化器选择
type:"SGD"
# momentum and the weight decay of the network.
momentum: 0.9
weight_decay: 0.0005
# Display every 100 iterations
display: 100
# The maximum number of iterations
max_iter: 10000
# snapshot intermediate results
snapshot: 5000
snapshot_prefix: "/home/YL/DataSet"
# solver mode: CPU or GPU
solver_mode: GPU
(1)test_iter和test_interval
- test_iter:这个要与测试层中的batch_size结合起来理解。假设测试样本总数为10000,一次性执行全部数据效率很低,因此就需要测试数据分成几个批次来执行,每个批次的数量就是batch_size。假设我们设置batch_size为100,则需要迭代100次才能将这10000个数据全部执行完。因此test_iter设置为100。测试完这10000个数据才叫做一次测试完成。
- test_interval:测试间隔。也就是每训练500次,才按照上述所说的过程进行一次完整的测试。
(2)base_lr和lr_policy
- base_lr:值得是初始化的学习率
-
lr_policy:lr_policy可以设置为下面这些值,相应的学习率的计算为(下图还有其中两个策略的可视化图):
(3)type
优化器的选择。因为默认值就是SGD,所以可以不写,但选择其他优化器时就要写了。到目前的版本,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")
(4)其他参数
- momentum:上一次梯度更新的权重(所谓的惯性)。
- weight_decay:权重衰减项,防止过拟合的一个参数。
- display:每训练100次,在屏幕上显示一次。如果设置为0,则不显示。
- max_iter:最大迭代次数。这个数设置太小,会导致没有收敛,精确度很低。设置太大,会导致震荡,浪费时间。
- snapshot: 快照。将训练出来的model和solver状态进行保存,snapshot用于设置训练多少次后进行保存,默认为0,不保存。snapshot_prefix设置保存路径。还可以设置snapshot_diff,是否保存梯度值,默认为false,不保存。也可以设置snapshot_format,保存的类型。有两种选择:HDF5 和BINARYPROTO ,默认为BINARYPROTO
5.构建train.sh文件
构建完train_test.prototxt和solver.prototxt两个文件后,基本网络和解决策略就搭建完了。就可以利用如下代码进行训练了。
#!/usr/bin/env sh
./build/tools/caffe train --solver=examples/mnist/lenet_solver.prototxt
caffe的c++主程序(caffe.cpp)放在根目录下的tools文件夹内, 当然还有一些其它的功能文件,如:convert_imageset.cpp, train_net.cpp, test_net.cpp等也放在这个文件夹内。经过编译后,这些文件都被编译成了可执行文件,放在了 ./build/tools/ 文件夹内。因此我们要执行caffe程序,都需要加 ./build/tools/ 前缀。
caffe程序的命令行执行格式如下:
caffe
其中的
- train:训练或finetune模型
- test:测试模型
- device_query:显示gpu信息
- time:显示程序执行时间
其中的
- solver:必选参数。一个protocol buffer类型的文件,即模型的配置文件。
./build/tools/caffe train -solver examples/mnist/lenet_solver.prototxt
- gpu:可选参数。该参数用来指定用哪一块gpu运行,根据gpu的id进行选择,如果设置为'-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
- iteration:可选参数,迭代次数,默认为50。 如果在配置文件文件中没有设定迭代次数,则默认迭代50次。
- model:可选参数,就是train_test.prototxt的protocol buffer类型的文件。也可以在solver配置文件中指定。
- sighup_effect:可选参数。用来设定当程序发生挂起事件时,执行的操作,可以设置为snapshot, stop或none, 默认为snapshot
- sigint_effect:可选参数。用来设定当程序发生键盘中止事件时(ctrl+c), 执行的操作,可以设置为snapshot, stop或none, 默认为stop。
发现一篇写caffe写的很好的博客,强烈推荐!:地址