从零开始山寨Caffe·壹:仰望星空与脚踏实地
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从零开始山寨Caffe·壹:仰望星空与脚踏实地相关的知识,希望对你有一定的参考价值。
请以“仰望星空与脚踏实地”作为题目,写一篇不少于800字的文章。除诗歌外,文体不限。
——2010·北京卷
仰望星空
规范性
Caffe诞生于12年末,如果偏要形容一下这个框架,可以用"须敬如师长"。
这是一份相当规范的代码,这个规范,不应该是BAT规范,那得是Google规范。
很多自称码农的人应该好好学习这份代码,改改自己丑陋的C++编程习惯。
下面列出几条重要的规范准则:
★const
先说说const问题,Google为了增加代码的可读性,明确要求:
不做修改的量(涵盖函数体内、函数参数列表),必须以const标记。
相对的,对于那些改变的量,可选择用mutable标记。
因为mutable关键词不是很常用,所以一般在自设函数中使用。
严格的const不在于担心变量是否被误修改,而在于给代码阅读者一个清晰的思路:
这个值不会改变,这个值肯定要改变。
★引用
"引用"是C/C++设计的一个败笔,因为C/C++默认是深拷贝,这在大内存数据结构操作的时候,
容易让新手程序员写出弱智低能的代码。假设Datum结构A使用了2G内存,令:
Datum B=A;
那么,内存会占用4G空间,而且,我们大概需要几秒的时间去拷贝A的2G内存。
这个几秒看起来不是很成问题,但是在多线程编程中,两个异步线程共享数据:
如果你不用引用会怎么样?
很有趣,这个复制再赋值的操作会被CPU中断,变成无效指令。
这在Caffe的多线程I/O设计架构中,是个关键点。
另外,对于基本数据类型(char/int/float/double),引用是没用必要的。
但是,string、vector<int>等容器,引用就相当有必要了。
★const引用
const引用最常见于函数参数列表,用于传递常、大数据结构量。
与此相对的,如果你要修改一个大数据结构量,应当在参数列表中传入指针,而不是引用。
传入引用来修改是C规范,传入指针来修改是C++规范,Caffe严格遵照C++规范,这点要明确。
★常成员函数
常成员函数,在OO里通常容易被新手忽略掉。(Java就没那么复杂),通常写作:
void xxx() const,目的是:
const标记住传入成员函数的this指针。
常成员函数其实不是必要的,但是在一定情况下,就会变成必要的。
这个情况相当有趣,而且在Caffe中也经常发生:
void xxx(const Blob& blob){ blob.count(); }
如果我们遵照Google的编程规范,用const引用锁定传入的Blob。
那么,blob.count()这个成员函数的调用就会被编译器的语义分析为:成员变量不可修改。
如果你的代码写成这样,那就会被编译器拦下,错误信息为:this指针不一致。
class Blob{ public: int count() {} //错误 int count() const {} //正确 };
★public、private、protected
OO的封装性是比较难定位的一个规范,成员变量及成员函数如何访问权限是个问题。
Caffe严格遵照标准的OO封装概念:方法是public,变量是private或者是protected。
区别private和protected就一句话:
private成员变量或是函数,不可能被继承。通常只用在本Class独有,而派生类不直使用的函数/变量上。
比如im2col和col2im,这两个为卷积做Patch预变换的函数。
protected和private的成员函数和成员变量都不可能从外部被访问,应当在public里专门设置访问接口。
并且接口根据需要,恰当使用const标记,避免越权访问。
有趣的是,如果这么做,会增加相当多的代码量,而且都是一些复制粘贴的废品代码。
为了避免这种情况,Google开发了Protocol Buffer,将数据结构大部分访问接口自动生成,且独立安排。
这样,在主体代码里,我们不会因为数据的访问接口的规范,而导致阅读代码十分头疼(想想那一扫下来的废代码)。
独立性
如果你研究过Word2Vec的源码,应该就知道,为什么Word2Vec必须跑在Linux下。。
因为Mikolov同学在写代码的时候,用了POSIX OS的API函数pThread,来实现内核级线程。
这为跨平台带来麻烦,一份优秀的跨平台代码,必须具有相当出色的平台独立性。
在这点上,Caffe使用了C++最强大的Boost库,来避免对OS API函数的使用。
Boost库,又称为C++三千佳丽的后宫,内涵1W+头文件,完整编译完大小达3.3G,相当庞大。
它的代码来自世界上顶级的C++开发者,是C++最忠实的第三方库,并且是ISO C++新规范的唯一来源。
Boost在Caffe中的主要作用是提供OS独立的内核级线程。
当然,已经于C++11中被列入规范的boost::shared_ptr其实也算。
还有一个十分精彩的boost::thread_specific_ptr,也在Caffe中起到了核心作用。
不足之处也有,而且其中一处还成了Bug,那就是API函数之一的open。
Linux的open默认是以二进制打开的,而Windows则是以文本形式打开的。
移植到Windows时,需要补上 O_BINARY作为flag。
异构性
大家都知道Caffe能跑GPU,一个关键点是:
它是在何处,又是怎么进行CPU与GPU分离的?
这个模型实际上应当算是CUDA标准模型。
由于内存显存不能跨着访问(一个在北桥,一个在南桥),又要考虑的CPU和GPU的平衡。
所以,数据的读取、转换不仅要被平摊到CPU上,而且应当设计成多线程,多线程的生产者消费者模型。
并且具有一定的多重缓冲能力,这样保证最大化CPU/GPU的计算力。
在一个机器学习系统当中,我们要珍惜计算设备的每一个时钟周期,切实做到计算力的最大化利用。
设计模式
实际使用的设计模式只有两个。
第一个是MVC,这个其实是迫不得已。
异构编程决定着,数据、视图、控制三大块必须独立开来。
但视图和控制并不是很明显,在设计接口/可视化GUI的时候,将凸显重要性。
第二个称为工厂模式,这是一个存在于Java的概念,尽管C++也可以模仿。
具体来说,工厂模式是为了弥补面向对象型编译语言的不足,会被OO的多态所需要。
以Caffe为例:
我们当前有一个基类指针Layer* layer;
在程序运行之前,计算机并不知道这个指针究竟要指向何种派生类。是卷积层?Pooling层?ReLU层?
鬼才知道。一个愚蠢的方法:
if(type==CONV) {....} else if(type==POOLING) {....} else if(type==RELU} {.....} else {ERROR}
看起来,还是可以接受的,但是在软件工程专业看来,这种模式相当得蠢。
工厂模式借鉴了工厂管理产品的经验,将各种类型存在数据库中,需要时,拿出来看看。
这种模式相当得灵活,当然,在Caffe中作用不是很大,仅仅是为了花式好看。
要实现这个模式,你只需要一个关联容器(C++/JAVA),字典容器(Python)。
将string与创建指针绑定即可。
C/C++中有函数指针的说法,如:
typedef boost::shared_ptr< Layer<Dtype> > (*NEW_FUNC)(const LayerParameter& );
经过typdef之后,NEW_FUNC就可以指向函数:
boost::shared_ptr< Layer<Dtype> > xxx(const LayerParameter& x); NEW_FUNC yyy=boost::shared_ptr< Layer<Dtype> > xxx(const LayerParameter& x); yyy(); //相当于xxx() xxx();
需要访问工厂时,我们只需要访问这个代替工厂管理数据库的容器,而不是幼稚地使用if(.....)
序列化与反序列化
如果Caffe不使用Protocol Buffer,那么代码量将扩大一倍。
这不是危言耸听,在传统系统级程序设计中,序列化与反序列化一直是一个码农问题。
尤其是在机器学习系统中,复杂多变的数据结构,给序列化和反序列化带来巨大麻烦。
Protocol Buffer在序列化阶段,是一个高效的编码器,能将数据最小体积序列化。
而在反序列化阶段,它是一个强大的解码器,支持二进制/文本两类数据的解析与结构反序列化。
其中,从文本反序列化意义颇大,这就形成了Caffe著名的文本配置文件prototxt,用于net和solver。
相对灵活的配置方式,尤其适合超大规模神经网络,这点在早期机器学习系统中独领风骚(很多人认为这比图形界面还要方便)。
脚踏实地
编程手册
Caffe几乎是C++ Primer 第五版的鲜活例子,如果你需要读懂它,经常翻一翻C++ Primer是一个不错的主意。
(另:不要阅读C++ Primer Plus,它的作者仅仅是一个普通教师,
而C++ Primer作者则包含C++协发明者、ISO C++委员会的人,是权威圣经)
耐心阅读和模仿代码
注意你接触的是一个系统级程序,Windows还是全球5000位微软工程师开发的。
系统级程序相当庞大和复杂,切记不要心浮气躁,不要以套库的心理去学习。
更不要认为,看看高层代码就可以了,这简直是噩梦,最后你会发现你根本读不懂。
来一个响亮的名字
为自己的工程取个名字是一件有趣的事,本项目默认名为:Dragon。
因为深度神经网络活像一头蠢龙。
以上是关于从零开始山寨Caffe·壹:仰望星空与脚踏实地的主要内容,如果未能解决你的问题,请参考以下文章