命令式编程VS符号式编程

转自:

https://blog.csdn.net/z0n1l2/article/details/80873608

https://www.cnblogs.com/anliven/p/10349356.html

什么是symblic/imperative style编程

使用过python或C++对imperative programs比较了解.imperative-stype programs在运行时计算.大部分python代码都是imperative.比如下面的例子

import numpy as np
    a = np.ones(10)
    b = np.ones(10) * 2
    c = b * a
    d = c + 1

当程序执行到 c=b∗a时,代码开始做对应的数值计算.
symbolic programs于此不同.symbolic-stype program中,需要先给出一个函数的定义(可能十分复杂).当我们定义这个函数时,并不会做真正的数值计算.这类函数的定义中使用数值占位符.当给定真正的输入后,才会对这个函数进行编译计算.上面的例子用symbolic-stype program重新写

A = Variable('A')
    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并不会触发真正的数值计算,但会生成一个计算图(也称symbolic graph)描述这个计算.下图时计算D的计算图

这里写图片描述

大部分symbolic-style program都显性或隐性的包含一个编译的步骤,把计算转换成可以调用的函数.上面的例子中,数值计算仅仅在代码最后一行进行.symbolic program一个重要特点是其明确有构建计算图和生成可执行代码两个步骤.对于神经网络,一般会用一个计算图描述整个模型.
其他流行深度学习框架中,Torch/Chainer/Minerva使用imperative style.symbolic-styple的框架包括Theano/CGT/TensorFlow. CXXNet/Caffe这一类依赖配置文件的框架也看作symbolic-style库.此时配置文件被当作计算图的定义.下面我们对比一下二者的优劣:

命令式编程(imperative style programs)

使用编程语句改变程序状态,明确输入变量,并根据程序逻辑逐步运算。

  • 易于理解:在Python里使用命令式编程时,大部分代码编写起来都很直观。
  • 容易调试:可以很方便地进行单步跟踪,获取并分析所有中间变量,或者使用Python的调试工具。

虽然使用命令式编程很方便,但它的运行可能很慢,会存在重复调用函数和长时间保存变量值等问题,耗费内存。

用python调用imperative-style库十分简单,编写方式和普通的python代码一样,在合适的位置调用库的代码实现加速.如果用python调用symbolic-style库,代码结构将出现一些变化,比如iteration可能无法使用.尝试把下面的例子转换成symbolic-style

 a = 2
    b = a + 1
    d = np.zeros(10)
    for i in range(d):
        d += np.zeros(10)

如果symblic-style API不支持for循环,转换就没那个直接.不能用python的编码思路调用symblic-style库.需要利用symblic API定义的domain-specific-language(DSL).深度学习框架会提供功能强大的DSL,把神经网络转化成可被调用的计算图.
感觉上imperative program更加符合习惯,使用更加简单.例如可以在任何位置打印出变量的值,轻松使用符合习惯的流程控制语句和循环语句.

符号式编程更加有效

符号式编程(symbolic style programs)

通常在计算流程完全定义好后才被执行。

  • 更高效:在编译的时候系统容易做更多优化。
  • 更容易移植:符号式编程可以将程序变成一个与Python无关的格式,从而可以使程序在非Python环境下运行,以避开Python解释器的性能问题。

一般来说,符号式编程的程序需要下面3个步骤:

  1. 定义计算流程;
  2. 把计算流程编译成可执行的程序;
  3. 给定输入,调用编译好的程序执行。

由于在编译时系统能够完整地获取整个程序,因此有更多空间优化计算,不仅减少了函数调用,还节省了内存。
深度学习框架TensorFlow和Theano采用了符号式编程的方法。

既然imperative pragrams更加灵活,和计算机原生语言更加贴合,那么为什么很多深度学习框架使用symbolic风格? 最主要的原因式效率,内存效率和计算效率都很高.比如下面的例子

import numpy as np
    a = np.ones(10)
    b = np.ones(10) * 2
    c = b * a
    d = c + 1
    ...

这里写图片描述

如果数组每个元素内存中占据8字节,在python中需要多少内存?
对于imperative programs中,需要在每一行上都分配必要的内存.一共4个数组,每个数组10个元素,一共4∗10∗8=320

字节. 如果事先知道只有d是需要的结果,构造计算图时可以重复利用一些中间变量的空间.比如利用原址计算,我们可以把b的内存借给c使用,同样c的内存可以给d用,如此可以节省一半内存,仅仅需要2∗10∗8=160字节

symbolic programs限制更多.因为只需要d,构建计算图后,一些中间量,比如c

,的值将无法看到.

通过symbolic program,使用原址计算可以安全的重用内存.但牺牲了对c

的访问可能. imperative program可以处理各种访问可能.如果在python执行上述例子,任何中间量都可以方便访问.

symbolic program还可以通过operation folding优化计算.在上述的例子中,乘法和加法可以展成一个操作,如下图所示.

这里写图片描述

如果在GPU上运算,计算图只需要一个kernel,节省了一个kernel.在很多优化库,比如caffe/CXXNet,人工编码进行此类优化操作. operation folding可以提高计算效率.

imperative program中不能自动operation folding,因为不知道中间变量是否会被访问到. symbolic program中可以做operation folding,因为获得了完整的计算图,而且明确哪些量以后会被访问,哪些量以后都不会被访问.

计算图/符号图(computation graph/symbolic graph)

符号式编程将计算过程抽象为一张计算图(符号图)来描述整个计算过程。

  • 易于描述计算过程,所有输入节点、运算节点、输出节点均符号化处理。
  • 通过建立输入节点到输出节点的传递闭包,从输入节点出发,沿着传递闭包完成数值计算和数据流动,直到达到输出节点。
  • 经过计算图优化,以数据(计算)流方式完成,节省内存空间使用,计算速度快,但不适合程序调试,通常不用于编程语言中。

大多数符号式程序都会显式地或是隐式地包含编译步骤,将计算图转换为能被调用的函数,在代码的最后一行才真正地进行运算。
也就是说,符号式程序清晰地将定义运算图的步骤与编译运算的步骤分割开来。

混合式编程

简而言之,命令式编程容易理解和调试,命令语句基本没有优化,按原有逻辑执行。
符号式编程涉及较多的嵌入和优化,不容易理解和调试,但运行速度有同比提升。

有没有可能既得到命令式编程的好处,又享受符号式编程的优势?
开发者们认为,用户应该用纯命令式编程进行开发和调试;
当需要产品级别的计算性能和部署时,用户可以将大部分命令式程序转换成符号式程序来运行。
深度学习框架caffe和mxnet采用了两种编程模式混合的方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章