码迷,mamicode.com
首页 > 其他好文 > 详细

Caffe关键技术之仿真(一)

时间:2015-12-11 06:49:26      阅读:2136      评论:0      收藏:0      [点我收藏+]

标签:

从当年流行的”编程高手必读Linux源码“,到市面上各色各样的XXX源码解析、剖析,我们已经看过太多太烂的源码分析。

读一份源码最痛之处在于,突然蹦出一大段代码,数据结构一个认不得,也不知道变量从何而来,函数更看都不懂。

似乎,那些很烂的作者,总喜欢迎难而上,你越不喜欢大段代码,他就每次首先贴一大段代码。

丝毫不对顺序做优化,也不知道标记出什么是重要的,什么是不重要的,连一份概况都没有。

这不叫源码分析,这叫随堂笔记。

 

高中时候,曾经拜读过侯捷老师的《深入浅出MFC》,其中最有趣的是它的第二章”MFC六大关键技术之仿真“。

这与许多写源码分析的作者大相庭径,既没有一大段代码,也不做什么概括。

它的”深入“精髓很简单,就是拆源码,将复杂、严谨的源码实现,结合自己的理解,以一种简洁、传神的方式仿真。

读这样的代码,是很轻松的,因为本来很复杂的源码,经过作者精心编排和二次改写后,看起来要和蔼可亲的多。

 

当然,我们不必去写书,但起码要学会拆源码,去仿真、简化、二次改写源码。

读一份复杂的源码,切记眼高手低。我在二次改写Caffe时,遇到的最大问题就是,尽管同一段代码看了许多遍,

每次理解竟然都不同,有错的,也有不足的,只有亲手编译、Debug之后,才发现,原来自己一直读”错“了。

”勿在浮台筑高楼“,这是《深入浅出MFC》这本书的精髓,其实也可以拓展到大部分源码分析问题上。

 

Caffe关键技术①·开放且先进的设计理念

1.1 引用库:瞧瞧你为什么配置Caffe要一天

Caffe是基于C++写成的,它不是C,它是严格的C++,严格到几乎是与C++ Primer提倡的,相似的现代C++编程风格。

C++标准库由ISO C++委员会严格审查控制,不像Java,所以它的内置库资源受到了严格的限制。

但这丝毫不会阻止C++的强大引用库的能力,但给配置这些库带来很大的麻烦。

广义上来说,Caffe引用的库分为如下几类:

 

1. 并行数学计算:cblas(CPU)、cublas(GPU)

2. GPU调度:CUDA

3. 本地数据库:LMDB/LevelDB

4、C++标准库扩展:Boost库(开源社区维护,史上最炫、最庞大的C++功能库集合)

5、日志管理与调试:Google GLog

6、执行命令解析:Google Gflags

7、图像处理与变换:OpenCV

8、数据结构设计,及高速序列化/反序列化:Google Protocol Buffer

9、参数扩展与可移植性:HDF5

 

这些库虽然你可能都没有用过,但是它们分别在处理相关任务上,速度绝对是No.1

当然,这些库有一个很不争的事实,和Google有撇不清的关系,而且Google还被墙了。这与作者Yangqing Jia的工作环境有关。

Caffe写于2013年末,作者2012年在Google MTV实习过,并且参与了Google Brain项目。

Google Brain项目的最大贡献,就是第一代大规模机器学习系统DistBelief(第二代为TensorFlow,已开源)

所以,Caffe就有了一个特殊性,它是学术界的人写的,但是这个人又精通工业界的那些技术。

我称之为,Caffe是一个半工业半学术级的框架。

你在Caffe的代码中,可以看到一些典型的工业级代码的思路和做法,比如 [严格的函数参数传入]:

Google内部对其程序员的代码,为了保持很好的可读性,特作以下规定:

void fun(std::string &str);
这样的函数我们很难分清楚str究竟是输入参数还是输出参数。

因此推荐这样写:
1、输入参数
void fun(const std::string &str);
2、输出参数
void fun(std::string *str);


 

 

 

 

 

这种代码风格,在Caffe中是严格执行的。

C++是严谨的语言,而良好的C++代码,则必须具有等量的严谨性。

1.2 CPU/GPU混合编程

Google Brain的DistBelief系统硬伤在于,它是基于分布式CPU节点计算的。

总负责人Andrew Ng在跳槽到Baidu之后,很懊悔自己当初的设计理念,他这样说道:

几年前我启动并主导了一个项目,当时还在谷歌,这个项目叫谷歌大脑。该项目利用谷歌的计算基础设施来构建神经网络。

规模大概比之前的神经网络扩大了一百倍,我们的方法是用约一千台电脑。这确实使深度学习取得了相当大的进展。用到相当多的计算机。不久之后我发现,之前我并没意识到,用一千台电脑是一项非常昂贵的技术。因此,我和我的朋友,意识到,利用一种不同的技术,仅用三台电脑,而非一千台,就可以做到这点,而秘诀就是利用GPU技术。

 

 

 

 

 

 

所以,Caffe在原始的设计理念上,就是以GPU为核心计算,CPU为辅助控制和I/O的这样框架。

由C/C++提供的编译宏功能,使得Caffe可以灵活的混合编程,仅仅添加一条宏,就能Build出不同平台需求的代码。

最新版Caffe,在CPU与GPU上,平衡的非常好。CPU多线程控制与I/O,GPU的多线程计算,两者水乳交融。

与之相对的是Theano,它几乎没有释放整台机器的全部计算力,无论你在其上二次封装什么样的库(Keras,Lasagne)

充其量也只是升级版的玩具,所以,Theano适合入门学习,但不要过度的依赖它,因为它实在太慢了。

1.3 数据结构

面向对象的设计理念有时是件麻烦事。

“类变量应该是私有的,仅且应当只由公有的方法提供接口,用于外部的控制”,Caffe依然严格的执行面向对象的设计原则。

这不是一个好主意,因为源码中会遍布着get、set方法,造成恶劣的不可读性。

所以Google提供了一个思路——将所有数据结构统一管理,以脚本形式快速定义,由机器自动生成冗繁的get、set。

这就是使用Google Buffer Protocol的第一个原因。

 

经典的系统级应用程序设计,对于数据的管理,必然要实现可持久化——频繁地在内存、硬盘之间进行数据交流。

这在应用程序设计中,称之为序列化、反序列化。

传统的序列化过程,需要程序员人工记忆序列顺序内容。当反序列化时,需要严格遵照顺序恢复,十分麻烦。

更重要的是,一般通用程序框架的反序列化的效率并不好(如Qt、MFC)

Google在Buffer Protocol中,使用了一种高效的编码方式,使得反序列化效率非常高,显著地增强了I/O能力。

这是使用Google Buffer Protocol的第二个,也是最重要的原因。

1.4 数据库

Google Buffer Protocol能够使得任意复杂的数据结构数据,快速编码为单个字符串,这就为“键-值”型数据库造桥铺路。

长期以来,应用级程序开发,通常会使用SQLite作为本地数据库(最典型的是安卓开发)。

SQL方式为数据的存储提供了方便,但同时它是慢的,慢到几乎不可以用于为高性能大数据计算提供I/O缓冲。

此时,Google 又开发了配套的快速 “键-值”型本地数据库LevelDB,跳过SQL,直接底层实现BST来存储。

LevelDB的I/O速度几乎是SQLite的几十倍,所以,早期的Caffe,数据封装由LevelDB负责。

 

在最新版Caffe中,广泛换成了LMDB。它多牺牲了一点内存空间用于缓存,却带来了比LevelDB更优

的I/O带宽。在如今内存贬值、白菜价的年代,LMDB显然受到主流追捧。毕竟I/O带宽,对于并行程序设计太重要了。

1.5 日志记录与调试

为程序的执行进行详细的日志记录,这是工业级代码的基本做法。

因为一旦程序崩溃了,作为主管,你不仅可以迅速定位位置、查找原因,还可以揪出隐藏在幕后的“背锅侠”。

Caffe中,几乎是遍布了日志记录代码,GLOG的简洁易用性,功不可没。

GLOG的日志记录分为四级,INFO、WARNING、ERROR、FATAL。

你可以通过 google::SetLogDestination 指定输出你要的日志级别,这四个级别是有趣的。

INFO级通常都是一些执行流程信息,WARNING级则是你需要注意的地方,但实际用的并不多。

ERROR级属于严重错误,但是并不会终止程序,作者无法确定接下来会发生什么事。

FATAL级属于致命错误,必须终止程序,这是不可争辩的事实。

 

FATAL级日志的实现,本质是封装了C/C++提供的assert(断言)功能。

assert代码编写过程中常用手段,GLOG利用assert,还定义了一些条件检查宏,如:

CHECK、CHECK_EQ,CHECK_LT,这些宏能够简化程序逻辑,给Debug带来方便。

毕竟,一个严谨的程序,需要杜绝任何以外情况。凡是估料到执行问题,都应当

使用CHECK宏加以断言标记,来保证程序的严谨性。

1.6 C++核心的一千零一夜

Boost库是C++最为激动人心的库之一,不仅仅是因为它编译完居然占用了3G空间,存在着一万多个引用的头文件。

最重要的是它提供了一些非常便利的功能,在Caffe中使用最广的,就是四大智能指针:

shared_ptr(全局自动释放)、scoped_ptr(局部自动释放)

weak_ptr(多线程安全访问)、thread_specific_ptr(多线程副本指针)

这些指针,让Caffe的代码秀气,安全,且充满灵活性。

 

其次,多线程程序设计一直是操作系统的兵家必争之地。现代操作系统的多线程功能,一般封装在操作系统内核中。

一些开发者会直接利用Linux内核提供的pThread进行代码设计,比如Tomas Mikolov的Word2Vec。

这给跨平台编译源码,带来了麻烦(很多人为了编译Word2Vec,不惜装Linux,或者找虚拟机)。

Caffe默认使用了Boost库的多线程功能,同Qt的设计理念一样,Boost库为Windows/Linux的内核。

以统一的接口,封装了多线程内核函数。这其实为Caffe for Windows的移植工作提供了便利。

除此之外,Boost库的多线程方案十分强大,足以设计大型的、安全的多线程应用程序。

1.7 数学计算

CBLAS与CUBLAS的函数接口几乎是一致的。

实际上,NVIDIA在设计CUBLAS的时候,是故意为之的模仿,减轻了CPU/GPU混合编程的代码混乱问题。

Caffe在底层封装了一些数学函数,也实现了一些高级的数学函数。

不要去死记硬背他们,也不要囫囵吞枣式的一步实现。

它们贯穿了Caffe的计算代码全部,所以,你在改写Caffe的初期,只需要选择实现其中的一小部分功能编程实现。

Caffe关键技术之仿真(一)

标签:

原文地址:http://www.cnblogs.com/neopenx/p/5037850.html

(1)
(11)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!