Caffe工厂模式解析
Caffe有五个基本组件,分别是Blob,Solver,Net,Layer和Proto,其中Solver和Layer使用了工厂模式,下面以Slover为例说明下。
Solver的工厂模式在注册和调用的过程中体现,所以在说明工厂模式之前,我们首先要弄明白Solver在Caffe内部是如何被使用的。
Solver注册机制
什么是Solver注册
我们都知道Layer和Slover是需要被注册的,而所谓的注册就是把这个类型的Slover(比如SDGSlover)找个地方记录下来,好告诉后面的过程,有这个Slover了,需要的话可以来这里调用。
这就和在CSDN注册会员一样,我们成功注册为会员,“用户名”和“密码”就被记录下来了,然后可以进一步的完善信息,写博客等等,这些都是我们这个账户里面的内容了。下一次登录的时候,我们需要使用“用户名”来匹配,登录我们的账户,而密码只是一个安全措施。
Caffe中Slover有SGDSlover,AdaGradSolver,AdaDeltaSolver,AdamSolver,NesterovSolver,RMSPropSolver这六种,注册的代码在它们各自的源文件中,比如SGDSlover的注册在sgd_solver.cpp的最下面:
REGISTER_SOLVER_CLASS(SGD);
SGD的就是solver.proto中type对应的字符串。
下面我们就从这行代码开始,往前追踪SGDSlover的注册。
Solver如何被注册
在solver_factory.hpp中可以找到REGISTER_SOLVER_CLASS
的定义,它是一个宏
#define REGISTER_SOLVER_CLASS(type) \template <typename Dtype> \Solver<Dtype>* Creator_##type##Solver( \const SolverParameter& param) \{ \return new type##Solver<Dtype>(param); \} \REGISTER_SOLVER_CREATOR(type, Creator_##type##Solver)
define 里的 ##是一个连接符号,用于把参数连在一起 。而type其实就是SGD,编译的时候这个宏会被替换,并将type换成SGD
,所以实际上这个宏就是完成了。
template <typename Dtype> Solver<Dtype>* Creator_SGDSolver(const SolverParameter& param){ return new SGDSolver<Dtype>(param); } REGISTER_SOLVER_CREATOR(SGD, Creator_SGDSolver)
它定义了一个函数Creator_SGDSolver()
,参数为SolverParameter&
类型的引用,返回值为SGDSolver<Dtype>(param)
。
最后又调用了另一个宏REGISTER_SOLVER_CREATOR
#define REGISTER_SOLVER_CREATOR(type, creator) \static SolverRegisterer<float> g_creator_f_##type(#type, creator<float>); \static SolverRegisterer<double> g_creator_d_##type(#type, creator<double>) \
还是想上面那样替换它:
static SolverRegisterer<float> g_creator_f_SGD("SGD", Creator_SGDSolver<float>); static SolverRegisterer<double> g_creator_d_SGD("SGD", Creator_SGDSolver<double>);
最后的目的就是要实例化SolverRegisterer
类的两个对象。SolverRegisterer
是一个模板类,所以在实例化时候有SolverRegisterer<float>
和SolverRegisterer<double>
,以支持两种Slove的数据类型,分别对应float和double。
实例化时会调用SolverRegisterer
类的构造函数,通过SolverRegisterer
类定义,发现构造函数里面调用了AddCreator()
方法。
template <typename Dtype>
class SolverRegisterer {public:SolverRegisterer(const string& type,Solver<Dtype>* (*creator)(const SolverParameter&)) {// LOG(INFO) << "Registering solver type: " << type;SolverRegistry<Dtype>::AddCreator(type, creator);}
};
AddCreator()
方法是另一个类SolverRegistry
的成员,我们暂时只看SolverRegistry
类下面这些成员就够了,细节的地方做了注释。
// LayerRegistry:注册类,主要实现两个方法,AddCreator()和CreateSolver(),下面代码只有AddCreator()
template <typename Dtype>
class SolverRegistry {public://定义名为Creator的函数指针类型,参数为SolverParameter&类型的引用,返回值为一个Solver类型的指针typedef Solver<Dtype>* (*Creator)(const SolverParameter&);//将一个map类型定义一个别名,叫做CreatorRegistry//map将“字符串-函数指针”行成映射typedef std::map<string, Creator> CreatorRegistry;// Registry()静态函数,只创建一个map实例,仅第一次调用时会new,其它直接return//创建的map其实就是solver的内部注册表static CreatorRegistry& Registry() {static CreatorRegistry* g_registry_ = new CreatorRegistry();return *g_registry_;}// Adds a creator.// AddCreator函数用来向Registry列表中添加一组<type, creator>static void AddCreator(const string& type, Creator creator) {CreatorRegistry& registry = Registry();CHECK_EQ(registry.count(type), 0)<< "Solver type " << type << " already registered.";// 向map中加入一个映射registry[type] = creator;}
};
所以,当我们看到了 registry[type] = creator;
这一行代码时,也就找到了slover的注册到底在做什么,他其实就是在往registry
变量里添加一组映射,registry
是静态的,它只有一个,就是slover的注册表;一组映射是CreatorRegistry
,它实际是一个map,建立映射的两个值分别string
和Creator
,string不用说,他就是像“SGD”,“Adam”,“AdaDelta”这样的一个字符串,关键是和它建立映射的东西:Creator
。
Creator
是一个函数指针,这个指针可以指向的函数要以SolverParameter&类型的引用作为参数,并且返回值为一个Solver类型的指针,Caffe里面那个函数是这个样子呢?就是在宏里定义的那个函数:Creator_SGDSolver()
。
最终,SGDSlover的注册是将字符串"SGD"和指向函数Creator_SGDSolver()
的指针成对存储到registry变量里面。
Solver的调用
说完了注册的部分,下面说明下调用,也就是程序的运行过程。
caffe的程序入库在caffe.cpp的main()函数中,比如执行train的时候,调用了SolverRegistry
类的CreateSolver()
函数:
shared_ptr<caffe::Solver<float> >solver(caffe::SolverRegistry<float>::CreateSolver(solver_param));
此时的Dtype已经指定为了float类型,solver_param是从slover.proto里面解析出来的。
CreateSolver()
也在SolverRegistry
类中定义:
template <typename Dtype>
class SolverRegistry {public:// Get a solver using a SolverParameter.static Solver<Dtype>* CreateSolver(const SolverParameter& param) {const string& type = param.type();CreatorRegistry& registry = Registry();CHECK_EQ(registry.count(type), 1) << "Unknown solver type: " << type<< " (known types: " << SolverTypeListString() << ")";return registry[type](param);}
}
它实现了registry[type](param)
的操作,实际上就是AddCreator()
反过来的过程,一个是取,一个是存。同样在"SGD"的时候,取出来的就应该是上面提到的Creator_SGDSolver()
,而Creator_SGDSolver()
的返回值是SGDSolver<Dtype>(param)
。
这个SGDSolver<Dtype>(param)
就在sgd_solvers.hpp中定义,就是SGDSolver的构造函数:
/*** @brief Optimizes the parameters of a Net using* stochastic gradient descent (SGD) with momentum.*/
template <typename Dtype>
class SGDSolver : public Solver<Dtype> {public:explicit SGDSolver(const SolverParameter& param): Solver<Dtype>(param) { PreSolve(); }explicit SGDSolver(const string& param_file): Solver<Dtype>(param_file) { PreSolve(); }virtual inline const char* type() const { return "SGD"; }const vector<shared_ptr<Blob<Dtype> > >& history() { return history_; }
}
通过main()中的调用,Dtype指定为了float。
Solver注册发生在什么时候
通过上面的分析,我们知道了所谓的注册就是往map里面存入,调用就是从map取出来,那就会有一个问题,注册是在什么时候发生的?
因为registry就是个静态变量,它的生命周期的开始一定在程序运行起来之后,但是程序运行起来就要从入口执行train了,这就要求在这之前registry里就要完成注册了,我们加个断点调试一下。
一个断点打在程序的入口处:
一个断点打在注册的地方:
启动调试之后,先断到了注册的地方:
此时的type是"AdaDelta",因为还没有存入,所以registy的size=0,再走一步的话:
type变成了"AdaGrad",因为已经存入了"AdaDelta",所以registy的size=1。
于是可以得到一个结论是,注册的过程是在进入main函数之前完成。
此外,还可以用代码图的当时看下,首先改一下断点的位置到:
开始执行调试,直到代码执行到main中,生成代码图,就像下面这样:
Solver的工厂模式
最后就是Solver的工厂模式了,上面的说明包含了工厂模式思想,下面我们工厂模式的角度再说明下。
Caffe中Slover的工厂模式是一种简单工厂模式,只有一个工厂,负责生产多种产品。在solver_factory.hpp中SolverRegistry
类定义了一个工厂,前面提到的注册,是在完善工厂中选择的逻辑,在很多简单工厂的例子中,这个逻辑可以靠switch,case来实现,只是在caffe中它变成了一个“字符串”-“函数指针”的映射。
上面提到的调用的过程,就是工厂生产产品的过程,还拿SDG的例子:
shared_ptr<caffe::Solver<float> >solver(caffe::SolverRegistry<float>::CreateSolver(solver_param));
尽管solver_param
参数的不同,但是都调用工厂中的方法CreateSolver()
,最终将生产的过程交给了产品的子类去实现,产品的子类实现就在各个优化器对应的源码中。
Caffe工厂模式解析相关推荐
- C++设计模式详解之工厂模式解析
C++ 工厂方法模式解析 工厂方法模式原则 让子类决定该创建的对象是什么,来达到将对象创建的过程.封装了对象创建的过程.实现了封装的目的.主要就是创建者类和产品类,让创建者自动绑定产品. 工厂方法模式 ...
- java 工厂模式的写法_[java设计模式] 工厂模式解析
什么是工厂模式? 我的总结是: 遵守软件设计中的开闭原则和依赖反转原则, 并且客户端只需通过参数来创造多个对象, 并且在创建过程中,创建对象的过程对客户端是透明的. 这种开发模式叫做工厂模式. 出现原 ...
- 抽象工厂模式解析例子
抽象工厂模式中的有以下的四种角色:<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office&qu ...
- C++设计模式详解之抽象工厂模式解析
抽象工厂模式概念 提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类 抽象工厂模式实例 以组装电脑为例,一般来说,电脑组装都不可能是一家公司的零件,简单的来说,显卡就有NVIDIA和A ...
- Java设计模式之 工厂模式解析
工厂模式一般用于当我们创建复杂对象时,通过简单的new来创建会比较麻烦,这时候我们就可以使用工厂模式来创建一个工厂类,我们只需向工厂类中传入需要创建的类的有关信息即可,工厂类中实现了创建对象的复杂细节 ...
- 抽象工厂模式,工厂方法模式区别
抽象工厂模式 转载自:https://www.shiyanlou.com/courses/document/867 一.本节目标 我们本节课程主要介绍[抽象工厂模式]的相关知识,将会涉及以下内容: 什 ...
- 从pyh看Python的工厂模式
[设想] 在做selenium前端页面测试时,想到生成html报告,需要编写个类,实现在Python内编辑html,具体思路如下: 1.编写各种tag类型,如head.title.body: 2.重载 ...
- 解析Spring IOC原理——工厂模式与反射机制的综合应用
(一)工厂模式 从一个例子开始讲起: 首先我们建立一个Chinese.java类,该类的sayHelloWorld(String name)方法,用中文对名为name的人问好,其内容如下: [java ...
- 结合案例深入解析:抽象工厂模式
一.基本概念 当涉及到产品族的时候,就需要引入抽象工厂模式了. 每一个模式都是针对一定问题的解决方案.抽象工厂模式与工厂方法模式的最大区别就在于,工厂方法模式针对的是一个产品等级结构:而抽象工厂模式则 ...
最新文章
- Python 内建函数 - sorted(iterable[, key][, reverse])
- ORACLE逻辑DATAGUARD创建表
- python算法详解张玲玲电子版_算法之路该如何学习?
- Eclipse配置C++时的三个关键环境变量
- ·必须《飞鸽~飞鸽传书》
- Science子刊:喝酒脸红的人,患胃癌风险大增,他们都有同一个基因突变
- 利用numpy.gradient计算图像梯度
- 格拉布斯法—异常值判断(异常值)
- Programer or Coder?
- JS+CSS实现幻灯片
- 决策树ID3算法实现与讨论(完整代码与数据)
- 【C++】黑马程序员 C++学习课程—C++基础入门
- 15.6 模板全特化与偏特化(局部特化)
- 【转】刀锋一样的眼神
- api接口文档中的签名是什么
- 19-10-15(msgbox、inputbox、注释)
- Tomcat环境搭建与常见问题详解
- 微信——产品设计分析报告
- 质量与服务兼顾 大型网吧组网方案推荐(转)
- 动态规划--数位dp--二进制状态压缩
热门文章
- DPDK——REORDER LIBRARY 排序库
- 〖Linux〗iptables端口转发(11.11.136.80:5552 == 10.10.136.1:8055/11.11.136.1:8055)
- java poi打印excel_java中对poi导出的Excel进行打印设置
- 章鱼网络社区治理的4种方式
- SCP和SFTP相同点和区别
- 零基础怎么学习抖音运营
- vue项目美食杰 -- 发布菜谱
- ASP.ENT前台更改绑定数据的日期格式
- 机器学习系列 五 Classification 分类
- 【PCBA方案】汽车车胎数显胎压计PCBA方案开发