深度学习:网络的编程模式比较
文章对原始文章有修改,如有歧义,请移步原文。
原文连接:
MXNet设计笔记之:深度学习的编程模式比较
原始连接:
Programming Models for Deep Learning
符号式编程的一个特点是延迟运行,先生成计算图,再次进行一次运行整个计算模型。因为涉及到延迟运行,可以在真正运行来临之前进行运算图的优化,比如多个乘法和加法累计为一个运算,优化计算效率。
有得必有失。
符号式编程 vs 命令式编程
在这一节,我们先来比较符号式程序(symbolic style programs)和命令式程序(imperative style programs)两种形式。如果你是一名Python或者C++程序员,那你应该很熟悉命令式程序了。命令式程序按照我们的命令来执行运算过程。大多数Python代码都属于命令式,例如下面这段numpy的计算。
import numpy 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) for i in range(d): d += np.zeros(10)
你会发现事实上并不容易,因为Python的for循环可能并不被符号式程序的API所支持。你若用Python来写符号式程序的代码,那绝对不是真的Python代码。实际上,你写的是符号式API定义的领域特定语言(DSL)。符号式API是DSL的加强版,能够生成计算图或是神经网络的配置。照此说法,输入配置文件的库都属于符号式的。
由于命令式程序比符号式程序更本地化,因此更容易利用语言本身的特性并将它们穿插在计算流程中。例如打印输出计算过程的中间值,或者使用宿主语言的条件判断和循环属性。
符号式程序更高效
我们在上一节讨论中提到,命令式程序更灵活,对宿主语言的本地化也更好。那为何大部分深度学习函数库反而选择了符号式呢?主要原因还是内存使用和运算时间两方面的效率。我们再来回顾一下本文开头的小例子。
import numpy 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这些优化库中手工调整运算的过程。这样做能提升计算效率。
在命令式程序里我们无法做到。因为中间结果可能在未来某处被引用。这种优化在符号式程序里可行是因为我们得到了完整的计算图,对需要和不需要的变量有一个明确的界线。而命令式程序只做局部运算,没有这条明确的界线。
后续:
......................................................
...........................................................