因为前一篇描述的是layer层,其实应该先学习工厂模式,最早我也学习过了23中模式设计,不熟悉这个模式的可以看一下下面这段代码。
#include "stdafx.h"
#include <string>
#include<iostream>
using namespace std;
class Product
{
public:
virtual void use(){};
};
class ConcreteProductA :public Product
{
public:
void use()
{
cout << "使用A" << endl;
};
};
class ConcreteProductB:public Product
{
public:
void use()
{
cout << "使用B" << endl;
};
};
class Factory
{
public:
Product* Factory::createProduct(string proname){
if ("A" == proname)
{
return new ConcreteProductA();
}
else if ("B" == proname)
{
return new ConcreteProductB();
}
return NULL;
}
};
int main()
{
Product *P = NULL;
Product *Q = NULL;
Factory f;
P=f.createProduct("A");
Q = f.createProduct("B");
P->use();
Q->use();
return 0;
}
这个是最简单的工厂模式。Layer_factory的主要作用是负责Layer的注册,已经注册完事的Layer在运行时可以通过传递一个 LayerParameter 给 CreaterLayer 函数的方式来调用:
LayerRegistry::CreateLayer(param);//可以参考net.cpp 中的调用方法
假设有一个如下的Layer:
template <typename Dtype>
class MyAwesomeLayer : public Layer<Dtype> {
// your implementations
};
它的type就是C++类名,除去后缀”Layer”:
(“MyAwesomeLayer” -> “MyAwesome”).
我们可以通过以下两种方式来注册一个Layer:
只有一个构造函数
如果这个Layer只能通过它的构造函数来创建的话,在对应的C++文件里面加入以下行就行:
REGISTER_LAYER_CLASS(MyAwesome);
有可选的构造函数
如果这个Layer还可以通过另外一个如下形式的构造函数构造:
template <typename Dtype>
Layer<Dtype*> GetMyAwesomeLayer(const LayerParameter& param) {
// your implementation
}
具体可以参考GetConvolutionLayer,Layer_factory.cpp。
这种情况下,我们可以通过注册构造函数的方式来进行Layer的注册:
REGISTER_LAYER_CREATOR(MyAwesome, GetMyAwesomeLayer)
以上是layer_factory要做的事情。
==================================================================================================================================
商业转载请联系作者获得授权,非商业转载请注明出处。
作者:Vinjn张静
EUCLIDEAN LOSS
EUCLIDEAN LOSS,就是计算欧式距离作为网络的 lossfunction。可以说这个是既常用又简单的网络层。
常用的原因是,我们判断2个向量的相似性时,需要考虑它们的夹角,长度比等很多因子,在实际应用中,我们有时候发现,归一化以后,我们简单的用欧式距离来衡量向量的相似性,有时候可以以较小的运算得到较好的结果。而且相比其它的网络层,比如convolution层,它不带权重,所以在做backward propagation的时候,只需简单的推导对输入的偏微分就可以,不用计算权重的偏微分,适合作为我们学习的第一个layer。
开始看代码,依然从caffe.proto看起,每个layer共享相同的LayerParam结构,不同type是通过LayerType来定义,所以我们的EUCLIDEAN LOSS层只是在这里加了一个新的EUCLIDEAN LOSS Layer type。
下一个涉及euclidean loss的文件是 upgrade_proto.cpp,说到这个文件就要讲起caffe的接口改动的兼容性问题,作为一个开源框架,使用的人越多,就会出现越多的应用场景,需求随之改变,所以大家再把合理的改动并回主分支的时候,不可避免的会发现最初设计的不足之处,有时候当一个扩展只能够使接口越来越脏的时候,我们就需要对放弃老接口,用新版本的接口重新设计,但是考虑向前兼容,我们需要一个类似adapter的模块来做旧配置到新配置的转化,upgrade_proto.cpp就是用来做这件事情的,转化的过程中,当然需要根据type来创建对应的layer,所以我们在这里加入对euclidean loss layer的处理。
工厂模式
到目前为止,我们知道我们加入的新的type,可以通过反序列化到一个type为EUCLIDEAN LOSS的LayerParam结构,但是怎么样把这个LayerParam绑定上forward和backward的操作实现呢?
caffe使用了工厂模式,代码在 layer_factory.hpp 中,宏 REGISTER_LAYER_CLASS为每个type生成了create方法,并和type一起注册到了LayerRegistry中,保存在一个map里面。这样以后就可以通过Type,我们就能够创建带forward/backward实现的节点了。
了解完layer的factory模式,我们就可以来看 EUCLIDEAN LOSS 的实现类 EuclideanLossLayer,声明在loss_layers.hpp,CPU实现和GPU实现分别在euclidean_loss_layer.cpp和euclidean_loss_layer.cu。我们这里关注的是forward和backward俩个方法。对于forward,就是在计算欧式距离,假设网络输出向量为[x1, x2, … xn], label为 [l1, l2, … ln], 则loss function 为
½ *[ (x1 – l1)*(x1-l1) + … (xn -ln)*(xn -ln)] / n
所以代码实现是一个向量的减法,紧跟着一个点乘,以CPU为例是:
caffe_sub(count, bottom[0]->cpu_data(), bottom[1]->cpu_data(), diff_.mutable_cpu_data());
Dtype dot = caffe_cpu_dot(count, diff_.cpu_data(), diff_.cpu_data());
Dtype loss = dot / bottom[0]->num() / Dtype(2);
做backward的时候,因为没有权重,所以只需要分别对x和t解偏微分,得到
Derive (X) = [(x1-l1)+…+(xn-ln)]/n
Derive (L) = [(l1-x1)+…+(ln-xn)]/n
所以代码实现是
for (int i = 0; i < 2; ++i) {
const Dtype sign = (i == 0) ? 1 : -1;
const Dtype alpha = sign * top[0]->cpu_diff()[0] / bottom[i]->num();
caffe_cpu_axpby(bottom[i]->count(), alpha, diff_.cpu_data(), Dtype(0), bottom[i]->mutable_cpu_diff());
}
这里的sign是因为X和L的偏微分差一个符号,axpby是BLAS常用函数
y = ax + by
这里a即为偏微分,b为0,所以等价为
Bottom(X) = derive(X)*diff_
Bottom(L) = derive(L)*diff_
到此为止我们了解了caffe的protocol,并挑了EuclideanLossLayer为例子了解caffe自定义扩展。
经过上面的阐述之后看看头文件和实现部分:
layer_factory.hpp:
/**
* @brief A layer factory that allows one to register layers.
* During runtime, registered layers could be called by passing a LayerParameter
* protobuffer to the CreateLayer function:
*
* LayerRegistry<Dtype>::CreateLayer(param);
*
* There are two ways to register a layer. Assuming that we have a layer like:
*
* template <typename Dtype>
* class MyAwesomeLayer : public Layer<Dtype> {
* // your implementations
* };
*
* and its type is its C++ class name, but without the "Layer" at the end
* ("MyAwesomeLayer" -> "MyAwesome").
*
* If the layer is going to be created simply by its constructor, in your c++
* file, add the following line:
*
* REGISTER_LAYER_CLASS(MyAwesome);
*
* Or, if the layer is going to be created by another creator function, in the
* format of:
*
* template <typename Dtype>
* Layer<Dtype*> GetMyAwesomeLayer(const LayerParameter& param) {
* // your implementation
* }
*
* (for example, when your layer has multiple backends, see GetConvolutionLayer
* for a use case), then you can register the creator function instead, like
*
* REGISTER_LAYER_CREATOR(MyAwesome, GetMyAwesomeLayer)
*
* Note that each layer type should only be registered once.
*/
#ifndef CAFFE_LAYER_FACTORY_H_
#define CAFFE_LAYER_FACTORY_H_
#include <map>
#include <string>
#include "caffe/common.hpp"
#include "caffe/proto/caffe.pb.h"
namespace caffe {
template <typename Dtype>
class Layer;
//LayerResistry的功能很简单,就是将类和对应的字符串类型放入到一个map当中去,以便灵活调用。主要就是注册类的功能,注意:每一个Layer type 只允许注册一次
template <typename Dtype>
class LayerRegistry {
public:
// 函数指针Creator,返回的是Layer<Dtype>类型的指针
typedef shared_ptr<Layer<Dtype> > (*Creator)(const LayerParameter&);
// CreatorRegistry是字符串与对应的Creator的映射
typedef std::map<string, Creator> CreatorRegistry;
static CreatorRegistry& Registry() {
static CreatorRegistry* g_registry_ = new CreatorRegistry();
return *g_registry_;
}
// Adds a creator.
// 给定类型,以及函数指针,加入一个Creator到注册表
static void AddCreator(const string& type, Creator creator) {
CreatorRegistry& registry = Registry();
CHECK_EQ(registry.count(type), 0)
<< "Layer type " << type << " already registered.";
registry[type] = creator;
}
// Get a layer using a LayerParameter.
//给定层的类型,创建层
static shared_ptr<Layer<Dtype> > CreateLayer(const LayerParameter& param) {
LOG(INFO) << "Creating layer " << param.name();
// 从参数中获得类型字符串
const string& type = param.type();
// 测试是否查找到给定type的Creator
CreatorRegistry& registry = Registry();
CHECK_EQ(registry.count(type), 1) << "Unknown layer type: " << type
<< " (known types: " << LayerTypeList() << ")";
// 调用对应的层的Creator函数
return registry[type](param);
}
private:
// Layer registry should never be instantiated - everything is done with its
// static variables.
// 禁止实例化,因为该类都是静态函数,所以是私有的
LayerRegistry() {}
//返回层的类型列表
static string LayerTypeList() {
// 获得注册表
CreatorRegistry& registry = Registry();
string layer_types;
// 遍历注册表压入layer_types字符串容器
for (typename CreatorRegistry::iterator iter = registry.begin();
iter != registry.end(); ++iter) {
if (iter != registry.begin()) {
layer_types += ", ";
}
layer_types += iter->first;
}
return layer_types;
}
};
// LayerRegisterer
// 自己定义层的注册器
// 以供后面的宏进行使用
template <typename Dtype>
class LayerRegisterer {
public:
// 层的注册器的构造函数
LayerRegisterer(const string& type,
shared_ptr<Layer<Dtype> > (*creator)(const LayerParameter&)) {
// LOG(INFO) << "Registering layer type: " << type;
// 还是调用的层注册表中的加入Creator函数加入注册表
LayerRegistry<Dtype>::AddCreator(type, creator);
}
};
//为了方便作者还弄了个宏便于注册自己写的层类
// 生成g_creator_f_type(type, creator<Dtype>)的两个函数 (double和float类型)
#define REGISTER_LAYER_CREATOR(type, creator) \
static LayerRegisterer<float> g_creator_f_##type(#type, creator<float>); \
static LayerRegisterer<double> g_creator_d_##type(#type, creator<double>) \
/* 注册自己定义的类,类名为type,
假设比如type=bias,那么生成如下的代码
下面的函数直接调用你自己的类的构造函数生成一个类的实例并返回
CreatorbiasLayer(const LayerParameter& param)
下面的语句是为你自己的类定义了LayerRegisterer<float>类型的静态变量g_creator_f_biasLayer(float类型,实际上就是把你自己的类的字符串类型和类的实例绑定到注册表)
static LayerRegisterer<float> g_creator_f_biasLayer(bias, CreatorbiasLayer)
下面的语句为你自己的类定义了LayerRegisterer<double>类型的静态变量g_creator_d_biasLayer(double类型,实际上就是把你自己的类的字符串类型和类的实例绑定到注册表)
static LayerRegisterer<double> g_creator_d_biasLayer(bias, CreatorbiasLayer)
*/
#define REGISTER_LAYER_CLASS(type) \
template <typename Dtype> \
shared_ptr<Layer<Dtype> > Creator_##type##Layer(const LayerParameter& param) \
{ \
return shared_ptr<Layer<Dtype> >(new type##Layer<Dtype>(param)); \
} \
REGISTER_LAYER_CREATOR(type, Creator_##type##Layer)
} // namespace caffe
#endif // CAFFE_LAYER_FACTORY_H_
layer_factory.cpp:
// Make sure we include Python.h before any system header
// to avoid _POSIX_C_SOURCE redefinition
#ifdef WITH_PYTHON_LAYER
#include <boost/python.hpp>
#endif
#include <string>
#include "caffe/layer.hpp"
#include "caffe/layer_factory.hpp"
#include "caffe/proto/caffe.pb.h"
#include "caffe/vision_layers.hpp"
#ifdef WITH_PYTHON_LAYER
#include "caffe/python_layer.hpp"
#endif
namespace caffe {
// 写一个获取卷积层实例的函数
// Get convolution layer according to engine.
template <typename Dtype>
shared_ptr<Layer<Dtype> > GetConvolutionLayer(
const LayerParameter& param) {
// 从参数中获取是使用什么引擎进行计算CUDNN还是CAFFE还是DEFAULT
// engine可从caffe.proto中看出是枚举类型的
ConvolutionParameter_Engine engine = param.convolution_param().engine();
if (engine == ConvolutionParameter_Engine_DEFAULT) {
engine = ConvolutionParameter_Engine_CAFFE;
#ifdef USE_CUDNN
engine = ConvolutionParameter_Engine_CUDNN;
#endif
}
if (engine == ConvolutionParameter_Engine_CAFFE) {
// 直接初始化Caffe的卷积层
return shared_ptr<Layer<Dtype> >(new ConvolutionLayer<Dtype>(param));
#ifdef USE_CUDNN
} else if (engine == ConvolutionParameter_Engine_CUDNN) {
// 初始化CUDNN的卷积层
return shared_ptr<Layer<Dtype> >(new CuDNNConvolutionLayer<Dtype>(param));
#endif
} else {// 否则就是出错了
LOG(FATAL) << "Layer " << param.name() << " has unknown engine.";
}
}
// 注册该卷积层,类型名为Convolution,获取卷积层的实例为GetConvolutionLayer函数
REGISTER_LAYER_CREATOR(Convolution, GetConvolutionLayer);
// 获取池化层的实例,同卷积层的逻辑
// Get pooling layer according to engine.
template <typename Dtype>
shared_ptr<Layer<Dtype> > GetPoolingLayer(const LayerParameter& param) {
PoolingParameter_Engine engine = param.pooling_param().engine();
if (engine == PoolingParameter_Engine_DEFAULT) {
engine = PoolingParameter_Engine_CAFFE;
#ifdef USE_CUDNN
engine = PoolingParameter_Engine_CUDNN;
#endif
}
if (engine == PoolingParameter_Engine_CAFFE) {
return shared_ptr<Layer<Dtype> >(new PoolingLayer<Dtype>(param));
#ifdef USE_CUDNN
} else if (engine == PoolingParameter_Engine_CUDNN) {
PoolingParameter p_param = param.pooling_param();
if (p_param.pad() || p_param.pad_h() || p_param.pad_w() ||
param.top_size() > 1) {
LOG(INFO) << "CUDNN does not support padding or multiple tops. "
<< "Using Caffe's own pooling layer.";
return shared_ptr<Layer<Dtype> >(new PoolingLayer<Dtype>(param));
}
return shared_ptr<Layer<Dtype> >(new CuDNNPoolingLayer<Dtype>(param));
#endif
} else {
LOG(FATAL) << "Layer " << param.name() << " has unknown engine.";
}
}
// 注册池化层
REGISTER_LAYER_CREATOR(Pooling, GetPoolingLayer);
// 注册ReLU层
// Get relu layer according to engine.
template <typename Dtype>
shared_ptr<Layer<Dtype> > GetReLULayer(const LayerParameter& param) {
ReLUParameter_Engine engine = param.relu_param().engine();
if (engine == ReLUParameter_Engine_DEFAULT) {
engine = ReLUParameter_Engine_CAFFE;
#ifdef USE_CUDNN
engine = ReLUParameter_Engine_CUDNN;
#endif
}
if (engine == ReLUParameter_Engine_CAFFE) {
return shared_ptr<Layer<Dtype> >(new ReLULayer<Dtype>(param));
#ifdef USE_CUDNN
} else if (engine == ReLUParameter_Engine_CUDNN) {
return shared_ptr<Layer<Dtype> >(new CuDNNReLULayer<Dtype>(param));
#endif
} else {
LOG(FATAL) << "Layer " << param.name() << " has unknown engine.";
}
}
REGISTER_LAYER_CREATOR(ReLU, GetReLULayer);
// 注册sigmoid层
// Get sigmoid layer according to engine.
template <typename Dtype>
shared_ptr<Layer<Dtype> > GetSigmoidLayer(const LayerParameter& param) {
SigmoidParameter_Engine engine = param.sigmoid_param().engine();
if (engine == SigmoidParameter_Engine_DEFAULT) {
engine = SigmoidParameter_Engine_CAFFE;
#ifdef USE_CUDNN
engine = SigmoidParameter_Engine_CUDNN;
#endif
}
if (engine == SigmoidParameter_Engine_CAFFE) {
return shared_ptr<Layer<Dtype> >(new SigmoidLayer<Dtype>(param));
#ifdef USE_CUDNN
} else if (engine == SigmoidParameter_Engine_CUDNN) {
return shared_ptr<Layer<Dtype> >(new CuDNNSigmoidLayer<Dtype>(param));
#endif
} else {
LOG(FATAL) << "Layer " << param.name() << " has unknown engine.";
}
}
REGISTER_LAYER_CREATOR(Sigmoid, GetSigmoidLayer);
// 注册softmax层
// Get softmax layer according to engine.
template <typename Dtype>
shared_ptr<Layer<Dtype> > GetSoftmaxLayer(const LayerParameter& param) {
SoftmaxParameter_Engine engine = param.softmax_param().engine();
if (engine == SoftmaxParameter_Engine_DEFAULT) {
engine = SoftmaxParameter_Engine_CAFFE;
#ifdef USE_CUDNN
engine = SoftmaxParameter_Engine_CUDNN;
#endif
}
if (engine == SoftmaxParameter_Engine_CAFFE) {
return shared_ptr<Layer<Dtype> >(new SoftmaxLayer<Dtype>(param));
#ifdef USE_CUDNN
} else if (engine == SoftmaxParameter_Engine_CUDNN) {
return shared_ptr<Layer<Dtype> >(new CuDNNSoftmaxLayer<Dtype>(param));
#endif
} else {
LOG(FATAL) << "Layer " << param.name() << " has unknown engine.";
}
}
REGISTER_LAYER_CREATOR(Softmax, GetSoftmaxLayer);
// 注册tanh层
// Get tanh layer according to engine.
template <typename Dtype>
shared_ptr<Layer<Dtype> > GetTanHLayer(const LayerParameter& param) {
TanHParameter_Engine engine = param.tanh_param().engine();
if (engine == TanHParameter_Engine_DEFAULT) {
engine = TanHParameter_Engine_CAFFE;
#ifdef USE_CUDNN
engine = TanHParameter_Engine_CUDNN;
#endif
}
if (engine == TanHParameter_Engine_CAFFE) {
return shared_ptr<Layer<Dtype> >(new TanHLayer<Dtype>(param));
#ifdef USE_CUDNN
} else if (engine == TanHParameter_Engine_CUDNN) {
return shared_ptr<Layer<Dtype> >(new CuDNNTanHLayer<Dtype>(param));
#endif
} else {
LOG(FATAL) << "Layer " << param.name() << " has unknown engine.";
}
}
REGISTER_LAYER_CREATOR(TanH, GetTanHLayer);
// 注册PYTHON层
#ifdef WITH_PYTHON_LAYER
template <typename Dtype>
shared_ptr<Layer<Dtype> > GetPythonLayer(const LayerParameter& param) {
Py_Initialize();
try {
bp::object module = bp::import(param.python_param().module().c_str());
bp::object layer = module.attr(param.python_param().layer().c_str())(param);
return bp::extract<shared_ptr<PythonLayer<Dtype> > >(layer)();
} catch (bp::error_already_set) {
PyErr_Print();
throw;
}
}
REGISTER_LAYER_CREATOR(Python, GetPythonLayer);
#endif
// Layers that use their constructor as their default creator should be
// registered in their corresponding cpp files. Do not register them here.
} // namespace caffe
这一个函数学习方便以后添加层的实现。