最近在一个新的数据集(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”,实际用也挺方便的:
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)
但是实际在写代码时有一些关键点需要注意:
- caffe.set_device一定要放在最前面,不要等Net或者SGDSolver变量都定义完了以后在执行。。。(悲伤的debug回忆)
- 如果模型定义时有区分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 }的网络层有区分;
- 训练数据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的图片数据放进去;
- solver.step(1)即迭代一次,包括了forward和backward,solver.iter标识了当前的迭代次数;
- 如果在solver.prototxt中设置了test_interval,那么在solver.test_interval的整数倍迭代时需要做一次完整的测试,此时设置数据应该是 <pre class="brush:python;">solver.test_nets[0].set_input_arrays(test_X, test_Y)</pre>
之后计算loss或者准确率时也不能使用solver.step函数,应为测试不需要backward过程
<pre class="brush:python;">solver.test_nets[0].forward()</pre>
这之后就可以访问solver.test\_nets[0].blobs['blob\_name'].data来获取loss或者准确率了。
- solver.prototxt文件中的参数可以通过google.protobuf这个模块实现
<pre class="brush:python;">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)</pre>
其中solver\_param首先从caffe.proto.caffe\_pb2.SolverParameter()获取默认参数,然后接下来从solver.prototxt文件里获取实际定义的参数,之后在引用时就可以很方便的看到诸如学习率、测试周期等变量了
- 在通过Python接口设置MemoryData时有时候会遇到一个奇怪的问题
<pre class="brush:bash;">Segmentation faults and Check failed: status == CUBLAS_STATUS_SUCCESS (14 vs. 0) CUBLAS_STATUS_INTERNAL_ERROR.</pre>
这个在Google了之后看到了<https://github.com/BVLC/caffe/issues/2334>这个帖子,题主说他已经解决了这个问题,通过在数据传输时使用deep copy,并且提交了一个[pr](https://github.com/TJKlein/caffe/commit/5f1bb97a587043dbe0892466b866abfe4c76804c),测试了一下居然真的可以,但奇怪的是这个request还没有合并到master分支里。。。不知道是不是因为Python的这个接口用的人少还是什么情况。
- 需要保存模型时
<pre class="brush:python;">solver.net.save(model_path)</pre>
此时会生成一个caffemodel和一个solverstate文件。
-
学习率及更新方式等参数只要在solver.prototxt中已经设置,不需要在代码中手动设置,比如我在solver.prototxt里设置了base_lr是0.005,lr_policy是”step”,stepsize是6000,gama是0.7,那么只要你不停的step,在6000次以后学习率就会自动变成0.005*0.7。
-
如果在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文件里:
<pre class="brush:cpp;">template <typename Dtype> void MemoryDataLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,
¦ const vector<Blob<Dtype>*>& 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; }</pre>
如果之前通过Reset函数设置的data shape是多个batch\_size,那么每次Step时就会调用到pos\_ = (pos\_+batch\_size\_)%n,不断feed下一个batch\_size的数据。
完整代码见https://github.com/nicklhy/CompCar_Analysis中的src/caffe/train.py文件。