Caffe | 你的第一个分类网络之Caffe训练

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写的很好的博客,强烈推荐!:地址

你可能感兴趣的:(Caffe | 你的第一个分类网络之Caffe训练)