命令式和符号式编程
命令式编程
命令式编程使用编程语句改变程序状态,如下:
def add(a, b):return a + bdef fancy_func(a, b, c, d):e = add(a, b)f = add(c, d)g = add(e, f)return gfancy_func(1, 2, 3, 4) # 10
在运行语句e = add(a, b)时,Python会做加法运算并将结果存储在变量e中,从而令程序的状态发生改变。类似地,后面的两条语句f = add(c, d)和g = add(e, f)会依次做加法运算并存储变量。
虽然使用命令式编程很方便,但
- 它的运行可能很慢
- 即使fancy_func函数中的add是被重复调用的函数,Python也会逐一执行这3条函数调用语句。
- 需要保存变量e和f的值直到fancy_func中所有语句执行结束。这是因为在执行e = add(a, b)和f = add(c, d)这2条语句之后我们并不知道变量e和f是否会被程序的其他部分使用。
符号式编程
与命令式编程不同,符号式编程通常在计算流程完全定义好后才被执行。多个深度学习框架,如Theano和TensorFlow,都使用了符号式编程。通常,符号式编程的程序需要下面3个步骤:
- 定义计算流程;
- 把计算流程编译成可执行的程序;
- 给定输入,调用编译好的程序执行。
下面我们用符号式编程重新实现之前的命令式编程代码。
def add_str():return '''
def add(a, b):return a + b
'''def fancy_func_str():return '''
def fancy_func(a, b, c, d):e = add(a, b)f = add(c, d)g = add(e, f)return g
'''def evoke_str():return add_str() + fancy_func_str() + '''
print(fancy_func(1, 2, 3, 4))
'''prog = evoke_str()
print(prog)
y = compile(prog, '', 'exec')
exec(y)
以上定义的3个函数都仅以字符串的形式返回计算流程。最后,我们通过compile函数编译完整的计算流程并运行。
由于在编译时系统能够完整地获取整个程序,因此有更多空间优化计算。例如,编译的时候可以将程序改写成print((1 + 2) + (3 + 4)),甚至直接改写成print(10)。这样不仅减少了函数调用,还节省了内存。
对比
对比这两种编程方式,我们可以看到以下两点。
- 命令式编程更方便。当我们在Python里使用命令式编程时,大部分代码编写起来都很直观。同时,命令式编程更容易调试。这是因为我们可以很方便地获取并打印所有的中间变量值,或者使用Python的调试工具。
- 符号式编程更高效并更容易移植。一方面,在编译的时候系统容易做更多优化;另一方面,符号式编程可以将程序变成一个与Python无关的格式,从而可以使程序在非Python环境下运行,以避开Python解释器的性能问题。
大部分深度学习框架在命令式编程和符号式编程之间二选一。例如,Theano和受其启发的后来者TensorFlow使用了符号式编程,Chainer和它的追随者PyTorch使用了命令式编程,而Gluon则采用了混合式编程的方式。
PyTorch仅仅采用了命令式编程