原文连接:
原始连接:
符号式编程 vs 命令式编程
在这一节,我们先来比较符号式程序(symbolic style programs)和命令式程序(imperative style programs)两种形式。如果你是一名Python或者C++程序员,那你应该很熟悉命令式程序了。命令式程序按照我们的命令来执行运算过程。大多数Python代码都属于命令式,例如下面这段numpy的计算。
importnumpy as np
a = np.ones(10)
b = np.ones(10) *2
c = b * a
d = c + 1
当程序执行到 c = b * a 这一行时,机器确实做了一次乘法运算。符号式程序略有不同。下面这段代码属于符号式程序,它同样能够计算得到d的值。
B = Variable('B')
C = B * A
D = C + Constant(1)
# compiles the function
f = compile(D)
d = f(A=np.ones(10), B=np.ones(10)*2)
符号式程序的不同之处在于,当执行 C = B * A 这一行代码时,程序并没有产生真正的计算,而是生成了一张计算图/符号图(computation graph/symbolic graph)来描述整个计算过程。下图就是计算得到D的计算图。
大多数符号式程序都会显式地或是隐式地包含编译步骤。这一步将计算图转换为能被调用的函数。在代码的最后一行才真正地进行了运算。符号式程序的最大特点就是清晰地将定义运算图的步骤与编译运算的步骤分割开来。
采用命令式编程的深度学习库包括Torch,Chainer, Minerva。采用符号式编程的库有Theano和CGT。一些使用配置文件的库,例如cxxnet和Caffe,也都被视为是符号式编程。因为配置文件的内容定义了计算图。
现在你明白两种编程模型了吧,我们接着来比较它们!
命令式程序更加灵活
这并不能算是一种严格的表述,只能说大多数情况下命令式程序比符号式程序更灵活。如果你想用Python写一段命令式程序的代码,直接写就是了。但是,你若想写一段符号式程序的代码,则完全不同了。看下面这段命令式程序,想想你会怎样把它转化为符号式程序呢。
a =2
b = a + 1
d = np.zeros(10)
foriinrange(d):
d += np.zeros(10)
你会发现事实上并不容易,因为Python的for循环可能并不被符号式程序的API所支持。你若用Python来写符号式程序的代码,那绝对不是真的Python代码。实际上,你写的是符号式API定义的领域特定语言(DSL)。符号式API是DSL的加强版,能够生成计算图或是神经网络的配置。照此说法,输入配置文件的库都属于符号式的。
由于命令式程序比符号式程序更本地化,因此更容易利用语言本身的特性并将它们穿插在计算流程中。例如打印输出计算过程的中间值,或者使用宿主语言的条件判断和循环属性。
符号式程序更高效
我们在上一节讨论中提到,命令式程序更灵活,对宿主语言的本地化也更好。那为何大部分深度学习函数库反而选择了符号式呢?主要原因还是内存使用和运算时间两方面的效率。我们再来回顾一下本文开头的小例子。
importnumpy as np
a = np.ones(10)
b = np.ones(10) *2
c = b * a
d = c + 1
...
假设数组的每个单元占据8字节。如果我们在Python控制台执行上述程序需要消耗多少内存呢?我们一起来做些算术题,首先需要存放4个包含10个元素的数组,需要4 * 10 * 8 = 320个字节。但是,若是运行计算图,我们可以重复利用C和D的内存,只需要3 * 10 * 8 = 240字节的内存就够了。
符号式程序的限制更多。当用户对D进行编译时,用户告诉系统只需要得到D的值。计算的中间结果,也就是C的值,对用户是不可见的。这就允许符号式程序重复利用内存进行同址计算(in-place computation)。
然而,命令式程序属于未雨绸缪的类型。如果上述程序在Python控制台执行,任何一个变量之后都有可能被用到,系统因此就不能对这些变量共享内存区间了。
当然,这样断言有些理想化,因为命令式程序在变量超出作用域时会启动垃圾回收机制,内存将得以重新利用。但是,受限于“未雨绸缪”这一特点,我们的优化能力还是有限。常见于梯度计算等例子,我们将在在下一节讨论。
符号式程序的另一个优化点是运算折叠。上述代码中,乘法和加法运算可以被折叠为一次运算。如下图所示。这意味着如果使用GPU计算,只需用到一个GPU内核(而不是两个)。这也正是我们在cxxnet和Caffe这些优化库中手工调整运算的过程。这样做能提升计算效率。
在命令式程序里我们无法做到。因为中间结果可能在未来某处被引用。这种优化在符号式程序里可行是因为我们得到了完整的计算图,对需要和不需要的变量有一个明确的界线。而命令式程序只做局部运算,没有这条明确的界线。
1. 基本概念
1.1 MXNet相关概念
深度学习目标:如何方便的表述神经网络,以及如何快速训练得到模型
CNN(卷积层):表达空间相关性(学表示)
RNN/LSTM:表达时间连续性(建模时序信号)命令式编程(imperative programming):嵌入的较浅,其中每个语句都按原来的意思执行,如numpy和Torch就是属于这种
声明式语言(declarative programing):嵌入的很深,提供一整套针对具体应用的迷你语言。即用户只需要声明要做什么,而具体执行则由系统完成。这类系统包括Caffe,Theano和TensorFlow。命令式编程显然更容易懂一些,更直观一些,但是声明式的更利于做优化,以及更利于做自动求导,所以都保留。
浅嵌入,命令式编程
深嵌入,声明式编程
如何执行a=b+1
需要b已经被赋值。立即执行加法,将结果保存在a中。
返回对应的计算图(computation graph),我们可以之后对b进行赋值,然后再执行加法运算
优点
语义上容易理解,灵活,可以精确控制行为。通常可以无缝的和主语言交互,方便的利用主语言的各类算法,工具包,bug和性能调试器。
在真正开始计算的时候已经拿到了整个计算图,所以我们可以做一系列优化来提升性能。实现辅助函数也容易,例如对任何计算图都提供forward和backward函数,对计算图进行可视化,将图保存到硬盘和从硬盘读取。
缺点
实现统一的辅助函数和提供整体优化都很困难。
很多主语言的特性都用不上。某些在主语言中实现简单,但在这里却经常麻烦,例如if-else语句 。debug也不容易,例如监视一个复杂的计算图中的某个节点的中间结果并不简单。
目前现有的系统大部分都采用上两种编程模式的一种。与它们不同的是,MXNet尝试将两种模式无缝的结合起来。在命令式编程上MXNet提供张量运算,而声明式编程中MXNet支持符号表达式。用户可以自由的混合它们来快速实现自己的想法。例如我们可以用声明式编程来描述神经网络,并利用系统提供的自动求导来训练模型。另一方便,模型的迭代训练和更新模型法则中可能涉及大量的控制逻辑,因此我们可以用命令式编程来实现。同时我们用它来进行方便的调式和与主语言交互数据。
1.2 深度学习的关键特点
(1)层级抽象
(2)端到端学习
2. 比较表
比较项
Caffe
Torch
Theano
TensorFlow
MXNet
主语言
C++/cuda
C++/Lua/cuda
Python/c++/cuda
C++/cuda
C++/cuda
从语言
Python/Matlab
-
-
Python
Python/R/Julia/Go
硬件
CPU/GPU
CPU/GPU/FPGA
CPU/GPU
CPU/GPU/Mobile
CPU/GPU/Mobile
分布式
N
N
N
Y(未开源)
Y
速度
快
快
中等
中等
快
灵活性
一般
好
好
好
好
文档
全面
全面
中等
中等
全面
适合模型
CNN
CNN/RNN
CNN/RNN
CNN/RNN
CNN/RNN?
操作系统
所有系统
Linux, OSX
所有系统
Linux, OSX
所有系统
命令式
N
Y
N
N
Y
声明式
Y
N
Y
Y
Y
接口
protobuf
Lua
Python
C++/Python
Python/R/Julia/Go
网络结构
分层方法
分层方法
符号张量图
符号张量图
?
注:1)使用符号张量图描述模型,增加新的层更加方便;而分层方法增加新的层需要自己实现(forward,backward和gradient更新函数)。
3.详细描述
3.1 MXNet
MXNet的系统架构如下图所示:
从上到下分别为各种主语言的嵌入,编程接口(矩阵运算,符号表达式,分布式通讯),两种编程模式的统一系统实现,以及各硬件的支持。
MXNet的设计细节包括:符号执行和自动求导;运行依赖引擎;内存节省。
3.2 Caffe
优点:
1)第一个主流的工业级深度学习工具。
2)它开始于2013年底,由UC Berkely的Yangqing Jia老师编写和维护的具有出色的卷积神经网络实现。在计算机视觉领域Caffe依然是最流行的工具包。
3)专精于图像处理
缺点:
1)它有很多扩展,但是由于一些遗留的架构问题,不够灵活且对递归网络和语言建模的支持很差。
2)基于层的网络结构,其扩展性不好,对于新增加的层,需要自己实现(forward, backward and gradient update)
3.3 TensorFlow
优点:
1) Google开源的其第二代深度学习技术——被使用在Google搜索、图像识别以及邮箱的深度学习框架。
2)是一个理想的RNN(递归神经网络)API和实现,TensorFlow使用了向量运算的符号图方法,使得新网络的指定变得相当容易,支持快速开发。
3)TF支持使用ARM/NEON指令实现model decoding
4)TensorBoard是一个非常好用的网络结构可视化工具,对于分析训练网络非常有用
5)编译过程比Theano快,它简单地把符号张量操作映射到已经编译好的函数调用
缺点:
1) 缺点是速度慢,内存占用较大。(比如相对于Torch)
2)支持的层没有Torch和Theano丰富,特别是没有时间序列的卷积,且卷积也不支持动态输入尺寸,这些功能在NLP中非常有用。
3.4 Torch
优点:
1)Facebook力推的深度学习框架,主要开发语言是C和Lua
2)有较好的灵活性和速度
3)它实现并且优化了基本的计算单元,使用者可以很简单地在此基础上实现自己的算法,不用浪费精力在计算优化上面。核心的计算单元使用C或者cuda做了很好的优化。在此基础之上,使用lua构建了常见的模型
5)支持全面的卷积操作:
-时间卷积:输入长度可变,而TF和Theano都不支持,对NLP非常有用;
-3D卷积:Theano支持,TF不支持,对视频识别很有用
缺点
1)是接口为lua语言,需要一点时间来学习。
2)没有Python接口
3)与Caffe一样,基于层的网络结构,其扩展性不好,对于新增加的层,需要自己实现(forward, backward and gradient update)
4)RNN没有官方支持
3.5 Theano
优点:
1)2008年诞生于蒙特利尔理工学院,主要开发语言是Python
2)Theano派生出了大量深度学习Python软件包,最著名的包括Blocks和Keras
3)Theano的最大特点是非常的灵活,适合做学术研究的实验,且对递归网络和语言建模有较好的支持
4)是第一个使用符号张量图描述模型的架构
5)支持更多的平台
6)在其上有可用的高级工具:Blocks, Keras等
缺点:
1)编译过程慢,但同样采用符号张量图的TF无此问题
2)import theano也很慢,它导入时有很多事要做
3)作为开发者,很难进行改进,因为code base是Python,而C/CUDA代码被打包在Python字符串中
参考资料: