caffe源码学习
学习调试时间: 2017-06-11~2017-06-12
博客编写时间: 2017-06-13
概述
前段时间对机器学习的学习过程中,主要是基于caffe的框架进行学习的。在下载完caffe-master后调试了在LeNet网络上运行mnist的例子。虽然代码调试成功,但是感觉简直是一头雾水。坐在实验室想着:当自己搭建网络,调试每一层的数据,我该怎么做。当拿到Net的python类的时候,我可以对网络做些什么。拿到了一个对象实例,又该怎么知道里面有哪些成员。于是便兴冲冲地去caffe-master/include下进行查看,然而c++中定义的成员名和python中的完成不同。打击万分,caffe的官网上也只有c++的API,用python调用时不知所措。因此,此博客主要介绍了caffe中python语言调用c++底层代码的功能。然后仿照caffe的封装过程,来封装自己的c++类来供python调用。在调试之前在boost的官网将boost库的使用熟悉了一遍,有些函数的使用还不是很清晰,因此建议学习的同学对boost没接触过的。可以看看boost的官网的python使用。
- 系统环境为Ubuntu14.0.4
- c++编译器为g++4.8
- python编译器为python2.7
caffe工程有关封装的文件
在caffe-master/python/caffe文件下:
1. _caffe.cpp:此文件主要是解决如何通过boost库来将c++类或者函数封装为python可以调用的类或者函数.
2. _caffe.so:该文件是_caffe.cpp文件经过编译链接后的文件.在python工程中import caffe(同级文件夹路径下的init.py文件中查看)实际就导入了该文件,python调用c++的底层时,该文件就是程序的入口.
3. pycaffe.py:当用户需要扩展caffe的c++一些类的功能时,改底层的源码的话可能会使得许多地方需要修改.此文件就是扩展c++的功能.通过python中的关键字@property的特点来扩展c++.
学习内容
此次学习主要通过源码来自己学会对c++来进行封装,以供python来进行调用.学习的结果是封装类似与Net类的blobs_和 blob_names_两个成员变量.然后在python中将其封装为OrderedDict.其效果如图:
其中最后一行显示了封装后的效果[blobs_names_,blobs_].
接下来将把caffe中相对应的源码拿出来进行模仿,这里只粘贴出模仿过程中使用到的caffe源码.过程如下:
- 定义Net类
- 利用boost库导出c++接口为python使用
- 用python语言扩展c++代码
Net类的定义
Net类的封装在caffe-sdk/caffe-master/include/caffe/net.hpp文件中.这里,我们主要引用了类中的blobs_names_以及blobs_成员变量.
namespace bp = boost::python;
namespace caffe {
template <typename Dtype>
class Net {
public:
inline const vector<string>& blob_names() const { return blob_names_; }
inline const vector<shared_ptr<Blob<Dtype> > >& blobs() const {
return blobs_;
}
protected:
vector<shared_ptr<Blob<Dtype> > > blobs_;
vector<string> blob_names_;
}
}
利用boost库将c++导出为python
然后就是c++的类如何封装为python可以调用的接口.使用boost库进行封装.源代码在_caffe.cpp文件中.代码如下:
#define BP_REGISTER_SHARED_PTR_TO_PYTHON(PTR) do { \
const boost::python::type_info info = \
boost::python::type_id<shared_ptr<PTR > >(); \
const boost::python::converter::registration* reg = \
boost::python::converter::registry::query(info); \
if (reg == NULL) { \
bp::register_ptr_to_python<shared_ptr<PTR > >(); \
} else if ((*reg).m_to_python == NULL) { \
bp::register_ptr_to_python<shared_ptr<PTR > >(); \
} \
} while (0)
shared_ptr<Net<Dtype> > Net_Init_Load(string param_file, string pretrained_param_file, int phase)
{
LOG(WARNING) << "DEPRECATION WARNING - deprecated use of Python interface";
LOG(WARNING) << "Use this instead (with the named \"weights\""
<< " parameter):";
LOG(WARNING) << "Net('" << param_file << "', " << phase
<< ", weights='" << pretrained_param_file << "')";
CheckFile(param_file);
CheckFile(pretrained_param_file);
shared_ptr<Net<Dtype> > net(new Net<Dtype>(param_file,
static_cast<Phase>(phase)));
net->CopyTrainedLayersFrom(pretrained_param_file);
return net;
}
以下代码表明
1. 将Net类导出.在python中的类名叫做Net,没有初始化函数
2. 定义python中类Net的初始化函数,在c++中为Net_Init_Load函数,注意c++中Net_Init_Load不是c++类Net的成员函数
3. 将c++中函数Net::blobs的返回值导出,在python中类Net中以属性名为_blobs存在,即python中调用_blobs成员相当于在c++中调用Net::blobs()函数
/*注意:此处将c++中的Net<Dtype>类和Net<Dtype>类的共享指针类(shared_ptr<Net<Dtype> >在c++中也是一个类,boost中的类,并不是指针)在python中都定义为了类Net,即在python中模糊了指针的概念*/
bp::class_<Net<Dtype>, shared_ptr<Net<Dtype> >, boost::noncopyable >("Net",bp::no_init)
.def("__init__",bp::make_constructor(&Net_Init_Load)) .add_property("_blobs",bp::make_function(&Net<Dtype>::blobs,bp::return_internal_reference<>()))
.add_property("_blob_names",bp::make_function(&Net<Dtype>::blob_names,bp::return_value_policy<bp::copy_const_reference>()))
BP_REGISTER_SHARED_PTR_TO_PYTHON(Net<Dtype>);
然后就是将c++函数中使用到的类进行注册,各种std::vector类型
bp::class_<vector<shared_ptr<Blob<Dtype> > > ("BlobVec")
.def(bp::vector_indexing_suite<vector<shared_ptr<Blob<Dtype> > >, true>())
.def("add_blob",bp::raw_function(&BlobVec_add_blob));
bp::class_<vector<Blob<Dtype>*> >("RawBlobVec")
.def(bp::vector_indexing_suite<vector<Blob<Dtype>*>, true>());
bp::class_<vector<string> >("StringVec")
.def(bp::vector_indexing_suite<vector<string> >());
bp::class_<vector<int> >("IntVec")
.def(bp::vector_indexing_suite<vector<int> >());
bp::class_<vector<Dtype> >("DtypeVec")
.def(bp::vector_indexing_suite<vector<Dtype> >());
bp::class_<vector<shared_ptr<Net<Dtype> > > >("NetVec")
.def(bp::vector_indexing_suite<vector<shared_ptr<Net<Dtype> > >, true>());
至此c++的接口已经通过boost库完成了封装.现在,再来看看pycaffe.py文件对类的扩展.
对Net类用python进行扩展
from collections import OrderedDict
@property
def _Net_blobs(self):
if not hasattr(self, '_blobs_dict'):
self._blobs_dict = OrderedDict(zip(self._blob_names, self._blobs))
return self._blobs_dict
Net.blobs = _Net_blobs
注意:最后一行代码中的Net是python中的类,该文件中给python中的Net类添加了一个blobs变量,通过该变量来获取_blobs_dict.
这里,总结一下c++中Net类的成员变量到python中类的变量的映射过程:
- 在net.hpp中定义c++中的成员变量:Net::blobs_ ,Net::blob_names_
- 在_caffe.cpp中导出为python变量:Net._blobs, Net._blob_names
- 在pycaffe.py中通过@property添加blobs:Net.blobs = _Net_blobs
注意:通过property导出的python变量,在调用时实际上是调用了相应的函数.
仿造源代码封装自己的c++类
以下代码相当于完成了对c++类的定义和对类的导出.即是net.hpp和_caffe.cpp的功能.文件命名为net.cpp
#include<Python.h>
#include<boost/python.hpp>
#include<boost/shared_ptr.hpp>
#include <boost/python/raw_function.hpp>
#include <boost/make_shared.hpp>
#include<boost/python/suite/indexing/vector_indexing_suite.hpp>
#include<iostream>
#include<vector>
using namespace boost;
using namespace boost::python;
#define BP_REGISTER_SHARED_PTR_TO_PYTHON(PTR) do { \
const boost::python::type_info info = \
boost::python::type_id<shared_ptr<PTR > >(); \
const boost::python::converter::registration* reg = \
boost::python::converter::registry::query(info); \
if (reg == NULL) { \
boost::python::register_ptr_to_python<shared_ptr<PTR > >(); \
} else if ((*reg).m_to_python == NULL) { \
boost::python::register_ptr_to_python<shared_ptr<PTR > >(); \
} \
} while (0)
boost::shared_ptr<int> blob_pointer(new int(10));
const std::string& name = "hello";
class Net{
public:
Net(int a)
{
blobs_.push_back(blob_pointer);
blob_names_.push_back(name);
std::cout<<"Net()"<<std::endl;
}
inline const std::vector<boost::shared_ptr<int> >& blobs() const {
std::cout<<"blobs()"<<std::endl;
return blobs_;
}
inline const std::vector<std::string>& blob_names() const {
std::cout<<"blob_names()"<<std::endl;
return blob_names_;
}
protected:
std::vector<boost::shared_ptr<int> > blobs_;
std::vector<std::string> blob_names_;
};
shared_ptr<Net> Init()
{
std::cout<<"Init()"<<std::endl;
shared_ptr<Net> net(new Net(10));
std::cout<<net<<std::endl;
return net;
}
BOOST_PYTHON_MODULE(net)
{
class_<Net,boost::shared_ptr<Net>,boost::noncopyable >("Net",no_init)
.def("__init__",make_constructor(&Init,default_call_policies()))
.add_property("_blobs",make_function(&Net::blobs,return_internal_reference<>()))
.add_property("_blob_names",make_function(&Net::blob_names,return_value_policy<copy_const_reference>()));
BP_REGISTER_SHARED_PTR_TO_PYTHON(Net);
class_<std::vector<std::string> >("StringVec")
.def(boost::python::vector_indexing_suite<std::vector<std::string> >());
class_<boost::shared_ptr<int> >("IntShare",no_init);
class_<std::vector<boost::shared_ptr<int> > >("IntVec")
.def(boost::python::vector_indexing_suite<std::vector<boost::shared_ptr<int> > >());
}
接下来将完成对pycaffe.py文件的模仿
#!/usr/bin/env python
from collections import OrderedDict
@property
def _Net_blobs(self):
if not hasattr(self, '_blobs_dict'):
self._blobs_dict = OrderedDict(zip(self._blob_names, self._blobs))
return self._blobs_dict
Net.blobs = _Net_blobs
然后对封装结果的测试如下:
import net
ne = net.Net() #调用了python类的初始化函数,实际调用的是c++中的Init()函数
blo = ne.blobs #此句实际调用了python函数_Net_blobs
print blo
注意net.cpp文件的编译指令
- 在与net.cpp同级的文件夹路径下,以下是我的makefile文件的代码
PYTHON_INCLUDE = /usr/include/python2.7
FILE_NAME = net
$(FILE_NAME).so : $(FILE_NAME).o
g++ -shared -Wl,-soname,$(FILE_NAME).so -o $(FILE_NAME).so $(FILE_NAME).o -lpython2.7 -lboost_python
$(FILE_NAME).o : $(FILE_NAME).cpp
g++ -c -fPIC $(FILE_NAME).cpp -o $(FILE_NAME).o -I $(PYTHON_INCLUDE)