来呀,快活呀~

Hack PyCaffe

这篇文章主要是Github: PyCaffe Tutorial中Hack Pycaffe的翻译整理。后续可能会加上一些使用boost和C++为Python接口提供后端的解释。这里主要讨论如何为Pycaffe添加自己想要的功能。至于Pycaffe的使用,留待以后的文章整理。
Python&&CPP binding

PyCaffe的代码组织结构

见Caffe的python目录。下面这张图是与PyCaffe相关的代码的分布。其中srcinclude是Caffe框架的后端C++实现,python目录中是与PyCaffe关系更密切的代码。可以看到,除了_caffe.cpp以外,其他都是纯python代码。_caffe.cpp使用boost提供了C++与python的绑定,而其他python脚本在此层的抽象隔离之上,继续完善了相关功能,提供了更加丰富的API、
代码组织结构

添加纯Python功能

首先,我们介绍如何在C++构建的PyCaffe隔离之上,用纯python实现想要的功能。

添加的功能和PyCaffe基本平行,不需要改变已有代码

有的时候想加入的功能和PyCaffe的关系基本是平行的,比如想仿照PyTorch等框架,加入对数据进行预处理的Transformer功能(这个API其实已经在PyCaffe中实现了,这里只是举个例子)。为了实现这个功能,我们可能需要使用numpyopencv等包装图像的预处理操作,但是和Caffe本身基本没什么关系。在这样的情况下,我们直接编写即可。要注意在python/caffe/__init__.py中import相关的子模块或函数。这个例子可以参考caffe.io的实现(见python/caffe/io.py文件)。

添加的功能需要Caffe的支持,向已有的类中添加函数

如果添加的功能需要Caffe的支持,可以在pycaffe.py内添加,详见Net的例子。由于python的灵活性,我们可以参考Net的实现方式,待函数实现完成后,使用<class>.<function> = my_function动态地添加。如下所示,注意_Net_forward函数的第一个参数必须是self

1
2
3
def _Net_forward(self, blobs=None, start=None, end=None, **kwargs):
# do something
Net.forward = _Net_forward

与之相似,我们还可以为已经存在的类添加字段。注意,函数用@property装饰,且参数有且只有一个self

1
2
3
4
5
6
7
8
9
10
11
12
13
# This function will be called when accessing net.blobs
@property
def _Net_blobs(self):
"""
An OrderedDict (bottom to top, i.e., input to output) of network
blobs indexed by name
"""
if not hasattr(self, '_blobs_dict'):
self._blobs_dict = OrderedDict(zip(self._blob_names, self._blobs))
return self._blobs_dict

# Set the field `blobs` to call _Net_blobs
Net.blobs = _Net_blobs

PyCaffe中已经实现的类主要有:Net, SGDSolver, NesterovSolver, AdaGradSolver, RMSPropSolver, AdaDeltaSolver, AdamSolver

使用C++添加功能

当遇到如下情况时,可能需要修改C++代码:

  • 为了获取更底层的权限控制,如一些私有字段。
  • 性能考虑。

这时,你应该去修改python/caffe/_caffe.cpp文件。这个文件使用了boost实现了python与C++的绑定。

为了添加一个字段,可以在Blob部分添加如下的代码。这样,就会将python中Blob类的num字段绑定到C++的Blob<Dtype>::num()方法上。

1
.add_property("num", &Blob<Dtype>::num)

使用.def可以为python相应的类绑定方法。在下面的代码中,首先实现了Net_Save方法,然后将其绑定到了python中Net类的save方法上。这样,通过python调用net.save(filename)即可。

注意,当你修改了_caffe,cpp后,记得使用make pycaffe重新生成动态链接库。

1
2
3
4
5
6
7
8
9
10
# Declare the function
void Net_Save(const Net<Dtype>& net, string filename) {
// ...
}

// ...

bp::class_<Net<Dtype>>("Net", bp::no_init)
# Now we can call net.save(file)
.def("save", &Net_Save)

当然,上面介绍的这些还很基础,关于boost的python绑定,可以参考官方的文档:boost: python binding