原文来源: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
|
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或者准确率了。
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文件里获取实际定义的参数,之后在引用时就可以很方便的看到诸如学习率、测试周期等变量了
在通过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的这个接口用的人少还是什么情况。
需要保存模型时
1
|
solver.net.save(model_path)
|
此时会生成一个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文件里:
1
2
3
4
5
6
7
8
9
10
11
12
|
template
<
typename
Dtype>
void
MemoryDataLayer
const
vector
¦
const
vector
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文件。