Caffe在Python中使用内存数据(MemoryData)进行训练

原文来源:http://closure11.com/caffe%E5%9C%A8python%E4%B8%AD%E4%BD%BF%E7%94%A8%E5%86%85%E5%AD%98%E6%95%B0%E6%8D%AEmemorydata%E8%BF%9B%E8%A1%8C%E8%AE%AD%E7%BB%83/


最近在一个新的数据集(CompCar)上需要做很多层次的分类任务,做baseline时不可避免的会用到caffe,最开始时是用官网教程的方式先生成带标签的list列表,然后转成lmdb,最后caffe train --solver=xxx --weights=xxx这样进行训练,然而。。。实验做着做着硬盘就满了。。。原因一是因为数据集很大,二是在不同分类任务里要把图片列表分配成不同的标签,三是训练集测试集的split也有很多种,四是我还要把原始图片生成LMDB,整个过程算起来原始数据集已经增大了好几倍,后来想到了每次训练时不使用lmdb形式的数据,而改用内存数据,即手动读入图片列表中所有图片,然后做train、test的split,再分配label,这样只需在硬盘中保存最原始的图片文件和几个list列表文件即可,不过到网上搜了一下,关于Caffe的MemoryDataLayer、Python接口进行训练的资料居然非常少,也没有直观的例子。。。最后跟了一下caffe的各种PR加上fast-rcnn的代码,总算大概搞了明白,在此记录一下,代码可以参看https://github.com/nicklhy/CompCar_Analysis,仍在更新中(好吧,其实没多少代码)。

一般做训练的话就是直接使用SGDSolver这个类了,它本身由C++实现的,是底层Net类的进一步封装,但是在Python接口中已经完美导出,可以很方便的使用。官方介绍非常简单“Optimizes the parameters of a Net using stochastic gradient descent (SGD) with momentum”,实际用也挺方便的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import caffe
 
# 定义solver对象,solver_prototxt是模型定义文件的路径,即/path/to/xxx_solver.prototxt
solver = caffe.SGDSolver(solver_prototxt)
 
# 如果存在预训练的权重,则载入
if pretrained_model is not None :
     solver.net.copy_from(pretrained_model)
 
while True :
     # 准备数据(如果模型定义的是MemoryData)
     solver.net.set_input_arrays(train_X, train_Y)
     # 迭代
     solver.step( 1 )

但是实际在写代码时有一些关键点需要注意:

  1. caffe.set_device一定要放在最前面,不要等Net或者SGDSolver变量都定义完了以后在执行。。。(悲伤的debug回忆)
  2. 如果模型定义时有区分training和validation的不同phase,那么在solver中实际上是存在两个表示网络的成员变量:solver.net和solver.test_nets,注意,前者直接就是一个Net的对象,而后者是Net对象的列表,如果像GoogleNet那样,存在一个training和一个testing(validation而不是真正的testing,做测试的文件其实是deploy.prototxt),那么应该通过solver.test_nets[0]来引用这个测试网络;另外,测试网络和训练网络应该是共享中间的特征网络层权重,只有那些标出include { phase: TRAIN }或者include { phase: TEST }的网络层有区分;
  3. 训练数据train_X, train_Y必须是numpy中的float32浮点矩阵,train_X维度是sample_num*channels*height*width,train_Y是sample_num维度的label向量,这里sample_num必须是trainning输入batch_size的整数倍,为了方便,我在实际使用时每次迭代只在整个训练集中随机选取一个batch_size的图片数据放进去;
  4. solver.step(1)即迭代一次,包括了forward和backward,solver.iter标识了当前的迭代次数;
  5. 如果在solver.prototxt中设置了test_interval,那么在solver.test_interval的整数倍迭代时需要做一次完整的测试,此时设置数据应该是

    1
    solver.test_nets[ 0 ].set_input_arrays(test_X, test_Y)

    之后计算loss或者准确率时也不能使用solver.step函数,应为测试不需要backward过程

    1
    solver.test_nets[ 0 ].forward()

    这之后就可以访问solver.test_nets[0].blobs['blob_name'].data来获取loss或者准确率了。

  6. solver.prototxt文件中的参数可以通过google.protobuf这个模块实现

    1
    2
    3
    4
    5
    6
    import caffe
    import google.protobuf as pb2
     
    solver_param = caffe.proto.caffe_pb2.SolverParameter()
    with open (solver_prototxt, 'rt' ) as fd:
         pb2.text_format.Merge(fd.read(), solver_param)

    其中solver_param首先从caffe.proto.caffe_pb2.SolverParameter()获取默认参数,然后接下来从solver.prototxt文件里获取实际定义的参数,之后在引用时就可以很方便的看到诸如学习率、测试周期等变量了

  7. 在通过Python接口设置MemoryData时有时候会遇到一个奇怪的问题

    1
    Segmentation faults and Check failed: status == CUBLAS_STATUS_SUCCESS (14 vs. 0) CUBLAS_STATUS_INTERNAL_ERROR.

    这个在Google了之后看到了https://github.com/BVLC/caffe/issues/2334这个帖子,题主说他已经解决了这个问题,通过在数据传输时使用deep copy,并且提交了一个pr,测试了一下居然真的可以,但奇怪的是这个request还没有合并到master分支里。。。不知道是不是因为Python的这个接口用的人少还是什么情况。

  8. 需要保存模型时

    1
    solver.net.save(model_path)

    此时会生成一个caffemodel和一个solverstate文件。

  9. 学习率及更新方式等参数只要在solver.prototxt中已经设置,不需要在代码中手动设置,比如我在solver.prototxt里设置了base_lr是0.005,lr_policy是"step",stepsize是6000,gama是0.7,那么只要你不停的step,在6000次以后学习率就会自动变成0.005*0.7。

  10. 如果在solver.prototxt文件里有average_loss这个参数(比如GoogleNet),也就是说要取多次forward操作的loss做平均,那么在set_input_arrays时记得输入average_loss*training_batch_size这么多的训练数据,然后调用solver.step(average_loss),如果仍然是每次step一次,那么average_loss参数就会失效,相当于没有起作用,具体实现可以在Caffe的源代码里找到,主要是memory_data_layer.cpp文件里:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    template < typename Dtype>
      void MemoryDataLayer::Forward_cpu( const vector*>& bottom,
          ¦ const vector*>& top) {
        CHECK(data_) << "MemoryDataLayer needs to be initalized by calling Reset" ;
        top[0]->Reshape(batch_size_, channels_, height_, width_);
        top[1]->Reshape(batch_size_, 1, 1, 1);
        top[0]->set_cpu_data(data_ + pos_ * size_);
        top[1]->set_cpu_data(labels_ + pos_);
        pos_ = (pos_ + batch_size_) % n_;
        if (pos_ == 0)
          has_new_data_ = false ;
    }

    如果之前通过Reset函数设置的data shape是多个batch_size,那么每次Step时就会调用到pos_ = (pos_+batch_size_)%n,不断feed下一个batch_size的数据。

完整代码见https://github.com/nicklhy/CompCar_Analysis中的src/caffe/train.py文件。


你可能感兴趣的:(Caffe在Python中使用内存数据(MemoryData)进行训练)