美文网首页
(1) caffe--使用篇

(1) caffe--使用篇

作者: Alfie20 | 来源:发表于2019-05-16 10:23 被阅读0次

本文主要从算法工程师角度如何使用caffe。主要分为训练和推理两大部分。

1. 训练

启动caffe的训练的命令示例如下:

caffe train --solver=*.prototxt  --weights=snapshots/*.caffemodel --snapshot=snapshots --gpu=5,6 2>&1 | tee log/*.log

入参说明:

--solver   此参数指明采取那个网络及相关超参
--weights  此参数指明基于那个参数集上开展
--gpu      此参数指明此动作使用GPU序号
--snapshot 此参数指明动作保存的快照地址

*.prototxt一般包含如下内容,相关字段的定义参考SolverParameter的说明, 见文章最后附的源码。

net: "pelee.prototxt"  #指明使用的网络结构
test_iter: 55     #测试迭代次数
test_interval: 210 #两次测试迭代次数
test_initialization: false #测试起始条件,如为真则在第一个迭代前进行初始化测试
display: 100 #指定多少次迭代进行打印
max_iter: 100000  #最大的迭代次数
base_lr:  0.01 #起始学习速率
lr_policy: "step" #学习速率更新策略, 具体含义见下方具体描述
gamma: 0.1 #学习速率更新参数, 在训练的过程中,如果loss开始出现稳定水平时,对learning rate乘以一个常数因子(比如,10),这样的过程重复多次。
momentum: 0.9 #让使用SGD的深度学习方法更加稳定以及快速,这次初始参数参论文ImageNet Classification with Deep Convolutional Neural Networks
weight_decay: 1e-06 #防止过拟合的惩罚项的权值,见下面详细描述
stepsize: 8000 #更新lr的步长,见下面详细描述
clip_gradients:40 #防止梯度下降的参数,,见下面详细描述
regularization_type: "L1"  #L1和L2正则都是比较常见和常用的正则化项,都可以达到防止过拟合的效果。L1正则化的解具有稀疏性,可用于特征选择。L2正则化的解都比较小,抗扰动能力强。
snapshot: 500 #训练中间结果的保存步长,即每 500 iterations就可以得到model_iter_xxx.caffemodel 和model_iter_xxx.solverstate的中间文件
snapshot_prefix: "snapshots" #设置快照保存路径
solver_mode: GPU   #运行的计算模式,GPU还是CPU

其中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))

关于超参,总结下:
(1) lr什么时候衰减与stepsize有关,减少多少与gamma有关,即:若stepsize=500, base_lr=0.01, gamma=0.1,则当迭代到第一个500次时,lr第一次衰减,衰减后的lr=lrgamma=0.010.1=0.001,以后重复该过程,所以stepsize是lr的衰减步长,gamma是lr的衰减系数;
(2) 在训练过程中,每到一定的迭代次数都会测试,迭代次数是由test-interval决定的,如test_interval=1000,则训练集每迭代1000次测试一遍网络,而 test_size, test_iter, 和test图片的数量决定了怎样test, test-size决定了test时每次迭代输入图片的数量,test_iter就是test所有的图片的迭代次数,如:500张test图片,test_iter=100,则test_size=5, 而solver文档里只需要根据test图片总数量来设置test_iter,以及根据需要设置test_interval即可。
(3) 在机器学习或者模式识别中,会出现overfitting,而当网络逐渐overfitting时网络权值逐渐变大,weight decay(权值衰减)使用的目的是防止过拟合。在损失函数中,weight decay是放在正则项(regularization)前面的一个系数,正则项一般指示模型的复杂度,所以weight decay的作用是调节模型复杂度对损失函数的影响,若weight decay很大,则复杂的模型损失函数的值也就大。
(4) clip_gradient 的引入是为了处理gradient explosion的问题。当在一次迭代中权重的更新过于迅猛的话,很容易导致loss divergence。clip_gradient 的直观作用就是让权重的更新限制在一个合适的范围。具体的细节是,
1). 在solver中先设置一个clip_gradient
2). 在前向传播与反向传播之后,我们会得到每个权重的梯度diff,这时不像通常那样直接使用这些梯度进行权重更新,而是先求所有权重梯度的平方和sumsq_diff,如果sumsq_diff > clip_gradient,则求缩放因子scale_factor = clip_gradient / sumsq_diff。这个scale_factor在(0,1)之间。如果权重梯度的平方和sumsq_diff越大,那缩放因子将越小。
3). 最后将所有的权重梯度乘以这个缩放因子,这时得到的梯度才是最后的梯度信息。
这样就保证了在一次迭代更新中,所有权重的梯度的平方和在一个设定范围以内,这个范围就是clip_gradient.

关于数据的输入指定是通过网络的protxt文件中输入层完成的,举例如下:

layer {
  name: "data"
  type: "ImageData"
  top: "data"
  top: "label"
  include {
    phase: TRAIN#TEST
  }
  image_data_param {
    source: "/root/data/total_data/train_seven_wuhe.txt"
    batch_size: 64
    shuffle: true
    label_size: 2
  }
}

从上面我们可以看出,这一个layer的top与自己的name相同,所以是输入层。在image_data_param中有对输入数据进行相关设置,比如路径,batch_size, shuffle, label_size等等。

2. 训练的评价

方式一:通过相应打印可以查看,下面2个图是典型的打印(通过log分析进行),表面训练过程中验证集的表现,如果发现训练过程中loss没有收敛,那说明训练失败,需要进行停止当前训练进行调整。

[solver.cpp:218] Iteration 87900 (1.3223 iter/s, 75.626s/100 iters), loss = 14.7485
[solver.cpp:237]     Train net output #0: lambda = 1
[solver.cpp:237]     Train net output #1: lambda2 = 1
[solver.cpp:237]     Train net output #2: loss = 7.81631 (* 1 = 7.81631 loss)
[solver.cpp:237]     Train net output #3: loss2 = 6.93214 (* 1 = 6.93214 loss)
[sgd_solver.cpp:105] Iteration 87900, lr = 1e-12
[solver.cpp:449] Snapshotting to binary proto file snapshots/*.caffemodel
[sgd_solver.cpp:273] Snapshotting solver state to binary proto file snapshots/*.solverstate

方式二:对于生成的一些模型,使用测试集进行测试推理结果,从中选出一个较好的模型。示例代码:

names = [name for name in files if name.endswith(".caffemodel")]
  names.sort()
  for caffemodel in names:
    caffe_model = os.path.join(root, caffemodel)
    net = caffe.Net(net_file, caffe_model, caffe.TEST)
    ......
    net.blobs['data'].data[...] = _in_[...]
    out = net.forward()
    resultData = net.blobs['prob'].data[0]
    output = np.argmax(resultData)
    .....
#log的分析进行模型优劣的对比

3. 推理

使用caffe进行网络推理的方式较简单,主要分为如下几个步骤

  1. 模型加载
    Caffe::set_mode(Caffe::GPU);  //CPU or GPU mode
    /* Load the network. */
    net_.reset(new Net<float>(model_file, TEST)); //model_file是指网络结构文件,即*.prototxt, 在训练阶段生产,必选项
    net_->CopyTrainedLayersFrom(trained_file);//trainded_file是指模型文件,即*.caffemodel, 在训练阶段生产,必选项

    std::shared_ptr<Net<float> > net_;    
    /*Get some Value*/
    Blob<float>* input_layer = net_->input_blobs()[0];
    num_channels_ = input_layer->channels();
    /* Load Param */
    SetParam(param_file); ;//trainded_file是指校准文件,即.binaryproto, 在训练阶段生产,可以没有

关于Blob的Reshape, Net的Reshap等相关的的定义和使用,请查阅本人文章https://www.jianshu.com/writer#/notebooks/36219573/notes/45233141
关于mean,指进行归一化处理,主要是从para_file中取出相关的值,对待推理的数据进行相应的预处理,给出一个示例如下:

SetParam(const std::string& param_file){
   input_geometry_ = cv::Size(256, 256);
    //读取param_file文件
    ......
    meanB_ = 127.5;
    meanG_ = 127.5;
    meanR_ = 127.5;
    scale_ = 0.0078125;
    cv::Mat(input_geometry_,CV_32FC3,cv::Scalar(meanB_,meanG_,meanR_));
}
  1. 模型调用
1. 预处理
Blob<float>* input_layer = net_->input_blobs()[0];
input_layer->Reshape(1, num_channels_, input_geometry_.height, input_geometry_.width);
net_->Reshape();/* Forward dimension change to all layers. */
2. 准备数据
std::vector<cv::Mat> input_channels;
WrapInputLayer(&input_channels);
Preprocess(img, &input_channels);
3. 推理
net_->Forward();
4. 获取推理后的数据 
/* Copy the output layer to a std::vector */
Blob<float>* output_layer = net_->output_blobs()[0];
const float* begin = output_layer->cpu_data();
const float* end = begin + output_layer->channels();
std::vector<float>(begin, end);

关于准备数据,无非就是按照要求把数据进行填充,示例如下:

void WrapInputLayer(std::vector<cv::Mat>* input_channels) {
    Blob<float>* input_layer = net_->input_blobs()[0];
    int width = input_layer->width();
    int height = input_layer->height();
    float* input_data = input_layer->mutable_cpu_data();
    for (int i = 0; i < input_layer->channels(); ++i)
    {
        cv::Mat channel(height, width, CV_32FC1, input_data);
        input_channels->push_back(channel);
        input_data += width * height;
    }
}

关于预处理,无非就是按照算法的要求对图片做灰度,resize,归一化,BGR拆分等。示例如下:

void Preprocess(const cv::Mat& img, std::vector<cv::Mat>* input_channels)
{
    /* Convert the input image to the input image format of the network. */
    cv::Mat sample;
    cv::cvtColor(img, sample, cv::COLOR_BGR2GRAY);
    cv::resize(sample, sample_resized, input_geometry_);
    cv::Mat sample_float;
    sample_resized.convertTo(sample_float, CV_32FC3);
    //减均值 归一化
    cv::subtract(sample_float, mean_, sample_normalized);
    sample_normalized *= scale_;
    /* This operation will write the separate BGR planes directly to the
    * input layer of the network because it is wrapped by the cv::Mat
    * objects in input_channels. */
    cv::split(sample_normalized, *input_channels);
}

4. SolverParameter 源码

附上caffe源码中SolverParameter的定义,便于查阅相关字段含义

message SolverParameter {
  //////////////////////////////////////////////////////////////////////////////
  // Proto filename for the train net, possibly combined with one or more
  // test nets.
  optional string net = 24;
  // Inline train net param, possibly combined with one or more test nets.
  optional NetParameter net_param = 25;

  optional string train_net = 1; // Proto filename for the train net.
  repeated string test_net = 2; // Proto filenames for the test nets.
  optional NetParameter train_net_param = 21; // Inline train net params.
  repeated NetParameter test_net_param = 22; // Inline test net params.

  optional NetState train_state = 26;
  repeated NetState test_state = 27;

  // The number of iterations for each test net.
  repeated int32 test_iter = 3;

  // The number of iterations between two testing phases.
  optional int32 test_interval = 4 [default = 0];
  optional bool test_compute_loss = 19 [default = false];
  // If true, run an initial test pass before the first iteration,
  // ensuring memory availability and printing the starting value of the loss.
  optional bool test_initialization = 32 [default = true];
  optional float base_lr = 5; // The base learning rate
  // the number of iterations between displaying info. If display = 0, no info
  // will be displayed.
  optional int32 display = 6;
  // Display the loss averaged over the last average_loss iterations
  optional int32 average_loss = 33 [default = 1];
  optional int32 max_iter = 7; // the maximum number of iterations
  // accumulate gradients over `iter_size` x `batch_size` instances
  optional int32 iter_size = 36 [default = 1];

  optional string lr_policy = 8;
  optional float gamma = 9; // The parameter to compute the learning rate.
  optional float power = 10; // The parameter to compute the learning rate.
  optional float momentum = 11; // The momentum value.
  optional float weight_decay = 12; // The weight decay.
  // regularization types supported: L1 and L2
  // controlled by weight_decay
  optional string regularization_type = 29 [default = "L2"];
  // the stepsize for learning rate policy "step"
  optional int32 stepsize = 13;
  // the stepsize for learning rate policy "multistep"
  repeated int32 stepvalue = 34;

  optional float clip_gradients = 35 [default = -1];

  optional int32 snapshot = 14 [default = 0]; // The snapshot interval

  optional string snapshot_prefix = 15;
 
  optional bool snapshot_diff = 16 [default = false];
  enum SnapshotFormat {
    HDF5 = 0;
    BINARYPROTO = 1;
  }
  optional SnapshotFormat snapshot_format = 37 [default = BINARYPROTO];
  // the mode solver will use: 0 for CPU and 1 for GPU. Use GPU in default.
  enum SolverMode {
    CPU = 0;
    GPU = 1;
  }
  optional SolverMode solver_mode = 17 [default = GPU];
  // the device_id will that be used in GPU mode. Use device_id = 0 in default.
  optional int32 device_id = 18 [default = 0];
  // If non-negative, the seed with which the Solver will initialize the Caffe
  // random number generator -- useful for reproducible results. Otherwise,
  // (and by default) initialize using a seed derived from the system clock.
  optional int64 random_seed = 20 [default = -1];
  // type of the solver
  optional string type = 40 [default = "SGD"];
  // numerical stability for RMSProp, AdaGrad and AdaDelta and Adam
  optional float delta = 31 [default = 1e-8];
  // parameters for the Adam solver
  optional float momentum2 = 39 [default = 0.999];
  optional float rms_decay = 38 [default = 0.99];
  optional bool debug_info = 23 [default = false];
  // If false, don't save a snapshot after training finishes.
  optional bool snapshot_after_train = 28 [default = true];
  // DEPRECATED: old solver enum types, use string instead
  enum SolverType {
    SGD = 0;
    NESTEROV = 1;
    ADAGRAD = 2;
    RMSPROP = 3;
    ADADELTA = 4;
    ADAM = 5;
  }
  // DEPRECATED: use type instead of solver_type
  optional SolverType solver_type = 30 [default = SGD];
  // Overlap compute and communication for data parallel training
  optional bool layer_wise_reduce = 41 [default = true];
  repeated string weights = 42;
}

相关文章

网友评论

      本文标题:(1) caffe--使用篇

      本文链接:https://www.haomeiwen.com/subject/cxfcnqtx.html