自动微分

数学计算机代数中,自动微分有时称作演算式微分,是一种可以借由计算机程序计算一个函数导数的方法。两种传统做微分的方法为:

  • 对一个函数的表示式做符号上的微分,并且计算其在某一点上的值。
  • 使用差分

使用符号微分最主要的缺点是速度慢及将计算机程序转换成表示式的困难。此外,很多函数在要计算更高阶微分时会变得复杂。 使用差分的两个重要的缺点是舍弃误差数值化过程和相消误差。此两者传统方法在计算更高阶微分时,都有复杂度及误差增加的问题。自动微分则解决上述的问题。

自动微分使用这个事实:任何实作一个向量函数 y=F(x)的计算机程序,一般而言,可以被分解成由基本指定运算所成的序列,而其中每一个都可以借由查表而轻易地微分。这些计算某一特定项的 "基本偏微分" 是依照微积分中的复合函数求导法则来合并成某个 F 的微分资讯(如梯度切线雅可比矩阵等)。这个过程会产生确实(数值上准确)的导数。因为只在最基础的层面做符号转换,自动微分避免了复杂的符号运算的问题。

复合函数求导法则,前向和反向积累

自动微分的基础是,根据复合函数求导法则来合并微分值。以 为例,根据复合函数求导法则,我们有:

 

通常有两个不同的模式:“前向积累”(或“前向模式”)和“反向积累”(或反向模式)。 前向积累由右到左地使用复合函数求导法则,即先计算   ,然后才 。 反向积累则是由左到右。

前向积累

前向积累式的自动微分是最容易理解和实作的。  这个函数是可被电脑(或程序员) 解释成一连串对变数 的运算。 前向积累式自动微分的工具则会增加相对应的作用于第二项上的运算。

原本程式码叙述 为导数而新增的叙述
    (种子)
    (种子)
   
   
   

计算 的导数需要初始化, 以区别是要对  来求导数。 上述表格则以  来初始化, 并且我们可以看到其结果 正是对 的导数。 注意,虽然表格列出了符号微分, 但以电脑的角度而言,电脑总是储存数值。图2则以图形表明上述的叙述。

为了计算这个例子的导数,其分别为  , 需要计算两次,一次是以  做初始值, 另一次则以  做为初始值。

前向积累的计算复杂度则正比于原来程式的计算复杂度。

对于函数  来说, 前向积累只要计算一次,优于需要计算 m 次的反向积累。

反向积累

前向式积累自动微分的由来与二元数

前向式积累自动微分可借由扩充实数中的代数并得到一个新的算术系统来达成。 每一个数都会新增另一数,用来表示一函数在这数上导数的数。 而每一个算术运算都被扩充于此新的代数。 这个扩充后的代数就是二元数的代数。


将每一个数 替换成数 ,其中 是一个实数,但 则只是一个据有 这个特性的符号。 使用这特性,我们可以有运算

 
 

减法和除法则类似。

现在,我们可以计算多项式。 如果 ,则

     
   
 
   

其中   表示   对第一个参数的导数。 而   则称作“种子”,可以任意选择。

新的算术是由有序对、写成  及对第一项的运算和对第二项的第一阶微分运算所组成。 将上述结果应用于多项式的解析函数上, 我们可以得到一系列关于基本算术和一些标准函数的新算术:

 
 
 
 
 
 
 
 
 
 

并且,一般而言,对于一个函数  ,我们会有

 

其中  分别是 对其第一项和第二项的导数。

对一个二元算术运算作用于混合的参数时(数对 和实数 ), 实数会先被转成 。 函数  上的导数 则为以上述算术计算 ,其结果为 

向量参数和函数

借由采取方向导数的运算, 多变数函数也可以同单变数函数的效率和机制来处理。 亦即,函数  这点, 和 这个方向上的方向导数 , 可以使用上述相同的算术来计算   而求得。

更高阶微分

以上的算术可以被一般化,以用于二阶及三阶导数。 然而,此算术的规则将会迅速变得复杂。 其复杂度将与最高阶导数阶数成平化。 取而代之的是使用限缩泰勒级数。 这是可行的,因为函数的泰勒级数中的通项为己知系数和函数导数的乘积。 使用自动微分来计算黑塞矩阵在某些最佳化已被证明是可行的。

实作

前向式积累是由对程式的非标准化转译程序来实作。 即将实数替换成二元数,常数则换成有第二项为零系数的二元数。 而数值上基本运算则被换成二元数的运算。 非标准化转译程序一般使用两者策略之一:程式码转换运算符重载

程式码转换

 
Figure 4: Example of how source code transformation could work

一个函数的程式码会被自动产生的程式码所替换, 新生成用来计算导数的程式码则会插入原程式码中。

程式码转换可实作在所有的编程语言上,且它对编译器而言,是容易最佳化的。 然而,实作自动微分的工具则是比较困难的。

例子:

  • ADIC[1] (C/C++, 前向积累)
  • ADIFOR[2] (Fortran77)
  • OpenAD[3] (Fortran77, Fortran95, C/C++)
  • TAPENADE[4] (Fortran77, Fortran95)

运算符重载

 
Figure 5: Example of how operator overloading could work

如果所使用的编程语言支持,运算符重载是个可行的方法。 实数的物件跟基本数学运算必须重载以满足上述 augmented 算术。 这不须要改变要被微分的函数的程式码。

运算符重载对前向积累是容易实作的,并且可能对反向积累亦如此。 然而,与前向积累相比,现有的编译器在最佳化程式码方面则是较为落后。

例子:

  • ADC Version 4.0[5] (C/C++)
  • ADF Version 4.0[6] (Fortran 77, Fortran 95)
  • ADOL-C[7] (C/C++)
  • FADBAD++[8] (C/C++)
  • CppAD[9] (C/C++)
  • MAD[10] (Matlab)
  • Sacado (Trilinos[11]的一部分) (C++, forward/reverse)

参考文献

  • Rall, Louis B. Automatic Differentiation: Techniques and Applications. Lecture Notes in Computer Science 120. Springer. 1981. ISBN 978-3-540-10861-0. 
  • Griewank, Andreas. Evaluating Derivatives: Principles and Techniques of Algorithmic Differentiation. Frontiers in Applied Mathematics 19. SIAM. 2000. ISBN 0-89871-451-6. 

外部链接