Automatic Differentiation

在问题求解和优化中常需使用梯度,手工计算复杂易出错,因此有了自动微分方法。本文介绍了四种主要的自动微分方法,包括数值微分、符号微分、前向自动微分和反向自动微分,阐述了它们的定义、优缺点,并给出了相应的例子。

目录

自动微分

       在进行问题求解和优化时要不可避免的使用梯度。手工计算梯度过于复杂且容易出错,而各种编程环境提供了自动微分即自动计算梯度的方法。数值微分,符号微分,前向自动微分和后向自动微分是四种主要的自动微分方法。

1.1 数值微分

  • 定义
           用数字计算偏导的近似值,如函数 h ( x ) h(x) h(x) 的导数 h ′ ( x 0 ) h'(x_0) h(x0) 为函数在点 x 0 x_0 x0 处的斜率,或用如下方程计算: (1.1) h ′ ( x 0 ) = lim ⁡ ϵ → 0 h ( x 0 + ϵ ) − h ( x 0 ) ϵ h'(x_0)=\lim_{\epsilon \to 0} \frac{h(x_0+\epsilon)-h(x_0)}{\epsilon}\tag{1.1} h(x0)=ϵ0limϵh(x0+ϵ)h(x0)(1.1)

  • 代码

    def f(x,y):
          return(x**2*y+y+2)  
          
    def derivative(f, x, y, x_eps, y_eps)
          return(f(x+x_eps, y+y_eps)-f(x, y))/(x_eps+y_eps)
          
    df_dx = derivative(f, 3, 4, 0.00001, 0)
    df_dy = derivative(f, 3, 4, 0, 0.00001)
    
  • 优缺点
    优点:比较容易实现,是检查其它方法是否正确实施的好工具;可以处理用任意代码定义的函数。
    缺点:准确度比较差,实现较为琐碎。

       我个人理解数值微分就是为变量赋予特定的值及其变化量,然后利用公式(1.1)计算该点处的偏导值。

1.2 符号微分

  • 定义
           构建一个完全不同的图,通过微积分计算规则(如Mathematica, Maple等)自动求出函数导数的解析解1,本文将用一个例子进行较为详细的解释。

  • 例子
           下例通过符号微分的方式求方程 g ( x , y ) = 5 + x y g(x,y)=5+xy g(x,y)=5+xy x x x 的偏导数。

图1.1 符号微分

       程序先计算叶子节点的偏导数。常数节点的偏导为0。变量 x x x 的偏导为1,同时变量 y y y x x x的偏导为0。由微积分的知识可知,两个函数 u u u v v v的乘积的偏导为: ∂ ( u × v ) ∂ x = ∂ u ∂ x × v + ∂ v ∂ x × u \frac{\partial(u\times v)}{\partial x}=\frac{\partial u}{\partial x}\times v+\frac{\partial v}{\partial x}\times u x(u×v)=xu×v+xv×u,所以 x y xy xy的偏导为 0 × x + 1 × y 0\times x+1\times y 0×x+1×y。再由微积分知识可知,两个函数的和的偏导为它们各自偏导的和,所以 5 + x y 5+xy 5+xy的偏导为 0 + ( 0 × x + 1 × y ) = y 0+(0\times x+1\times y)=y 0+(0×x+1×y)=y

  • 优缺点
    优点:准确度较高。
    缺点:对于复杂函数会产生巨大的图,导致性能降低,并且它不能处理用任意代码定义的函数。

       我个人理解符号微分就是利用微积分计算规则将偏导数用公式或符号的形式表示出来。

1.3 前向自动微分

  • 定义
           前向自动微分是数值微分与符号微分的结合体。它依赖于对偶数,即形如 a + b ϵ a+b\epsilon a+bϵ 的数字,其中 a a a b b b 是实数, ϵ \epsilon ϵ 是使 ϵ 2 = 0 \epsilon^2=0 ϵ2=0 ϵ ≠ 0 \epsilon \neq 0 ϵ̸=0)的无限小数。类似于数值微分,都是为变量假设一个变化量。然后根据对偶数的相关操作可以得出 h ( a + b ϵ ) = h ( a ) + b h ′ ( a ) ϵ h(a+b\epsilon)=h(a)+b h'(a)\epsilon h(a+bϵ)=h(a)+bh(a)ϵ,建立计算图计算 h ( a + ϵ ) h(a+\epsilon) h(a+ϵ) 的表达式, ϵ \epsilon ϵ 前的系数即为函数的偏导数。具体使用可见下面的例子。

  • 例子
           下图通过正向微分的方式求方程 g ( x , y ) = x 2 y + y + 2 g(x,y)=x^2y+y+2 g(x,y)=x2y+y+2 x x x 的偏导数。

图1.2 前向微分

        当要求对 y y y 的偏导数时再次使用该图,但参数修改为 x = 3 , y = 4 + ϵ x=3, y=4+\epsilon x=3,y=4+ϵ

  • 优缺点
    优点:准确度较高;可以处理用任意代码定义的函数。
    缺点:当函数中参数较多时,需要多次遍历图求得所有微分。

1.4 反向自动微分

  • 定义
           正向自动微分只需遍历图两次便可以求得函数所有微分,其思路类似于神经网络中的反向传播算法。它首先正向遍历图的每一个节点,,然后再反向遍历第二遍。在第一次遍历期间,它会算出所有节点的值,而第二次遍历时它会算出下一节点对上一节点的偏导数(将节点假设为一个变量 n n n,即将节点视为一个整体,不关心其内部构成)。具体使用见如下例子。

  • 例子
           下图通过反向自动微分求函数 g ( x , y ) = x 2 y + y + 2 g(x,y)=x^2y+y+2 g(x,y)=x2y+y+2 的所有偏导数。

图1.2 反向微分

       节点右下角标示了当 x = 3 x=3 x=3 y = 4 y=4 y=4 时各节点的值。各节点被标记为 n 1 n_1 n1 n 7 n_7 n7。第二次遍历求偏导利用的是链式法则: ∂ f ∂ x = ∂ f ∂ n i × ∂ n i ∂ x \frac{\partial f}{\partial x}=\frac{\partial f}{\partial n_i}\times\frac{\partial n_i}{\partial x} xf=nif×xni

  1. 易知, f = n 7 f=n_7 f=n7,所以 ∂ f ∂ n 7 = 1 \frac{\partial f}{\partial n_7}=1 n7f=1
  2. 由图可知, n 7 = n 5 + n 6 n_7=n_5+n_6 n7=n5+n6,所以 ∂ n 7 ∂ n 5 = 1 \frac{\partial n_7}{\partial n_5}=1 n5n7=1,那么 ∂ f ∂ n 5 = ∂ f ∂ n 7 × ∂ n 7 ∂ n 5 = 1 \frac{\partial f}{\partial n_5}=\frac{\partial f}{\partial n_7}\times\frac{\partial n_7}{\partial n_5}=1 n5f=n7f×n5n7=1
  3. 与2同理,可以得到 ∂ f ∂ n 6 = ∂ f ∂ n 7 × ∂ n 7 ∂ n 6 = 1 \frac{\partial f}{\partial n_6}=\frac{\partial f}{\partial n_7}\times\frac{\partial n_7}{\partial n_6}=1 n6f=n7f×n6n7=1
  4. 由图可知, n 5 = n 4 × n 2 n_5=n_4\times n_2 n5=n4×n2,所以 ∂ n 5 ∂ n 4 = n 2 \frac{\partial n_5}{\partial n_4}=n2 n4n5=n2 ∂ n 5 ∂ n 2 = n 9 \frac{\partial n_5}{\partial n_2}=n_9 n2n5=n9,那么 ∂ f ∂ n 4 = ∂ f ∂ n 5 × ∂ n 5 ∂ n 4 = 1 × n 2 = 4 \frac{\partial f}{\partial n_4}=\frac{\partial f}{\partial n_5}\times\frac{\partial n_5}{\partial n_4}=1\times n_2=4 n4f=n5f×n4n5=1×n2=4 ∂ f ∂ n 2 = ∂ f ∂ n 5 × ∂ n 5 ∂ n 2 = 1 × n 4 = 9 \frac{\partial f}{\partial n_2}=\frac{\partial f}{\partial n_5}\times\frac{\partial n_5}{\partial n_2}=1\times n_4=9 n2f=n5f×n2n5=1×n4=9
  5. 与4同理,可以得到 ∂ f ∂ n 2 = ∂ f ∂ n 6 × ∂ n 6 ∂ n 2 = 1 \frac{\partial f}{\partial n_2}=\frac{\partial f}{\partial n_6}\times\frac{\partial n_6}{\partial n_2}=1 n2f=n6f×n2n6=1 ∂ f ∂ n 3 = ∂ f ∂ n 6 × ∂ n 6 ∂ n 3 = 1 \frac{\partial f}{\partial n_3}=\frac{\partial f}{\partial n_6}\times\frac{\partial n_6}{\partial n_3}=1 n3f=n6f×n3n6=1
  6. 由图可知, f = n 7 = n 5 + n 6 = n 2 × n 4 + n 2 + n 3 = y × n 4 + y f=n_7=n_5+n_6=n_2\times n_4+n_2+n_3=y\times n_4+y f=n7=n5+n6=n2×n4+n2+n3=y×n4+y,所以 ∂ f ∂ y = n 4 + 1 = 10 \frac{\partial f}{\partial y}=n_4+1=10 yf=n4+1=10
  7. 易知, ∂ n 4 ∂ x = 2 x \frac{\partial n_4}{\partial x}=2x xn4=2x,所以 f r a c ∂ f ∂ x = 4 × 2 x = 24 frac{\partial f}{\partial x}=4\times 2x=24 fracfx=4×2x=24
  • 优缺点
    优点:准确度较高;可以处理用任意代码定义的函数;速度快。
    缺点:暂时不知道。

1.5 参考

[1] inkfish-talk.计算机求导的四种方法
[2] Geron.机器学习实战

翻译下面的这段话 Hard Autograd for Algebraic Expressions 分数 100 作者 郑友怡 单位 浙江大学 The application of automatic differentiation technology in frameworks such as torch and tensorflow has greatly facilitated people's implementation and training of deep learning algorithms based on backpropagation. Now, we hope to implement an automatic differentiation program for algebraic expressions. Input Format First, input an infix expression composed of the following symbols. Operators Type Examples Notes Bracket ( ) Power ^ Multiplication & Division * / Addition & Subtraction + - Argument separator , optional, only used as argument separators in multivariate functions The above operators are arranged in order of operator precedence from top to bottom. For example, a+b^c*d will be considered the same as a + ( (b ^ c) * d ). Mathematical Functions (bonus) Function Description ln(A) log(A, B) logarithm. ln(A) represents the natural logarithm of A, and log(A, B) represents the logarithm of B based on A. cos(A) sin(A) tan(A) basic trigonometric functions. pow(A, B) exp(A) exponential functions. pow(A, B) represents the B power of A, and exp(A) represents the natural exponential of A. Operands Type Examples Notes Literal constant 2 3 0 -5 Just consider integers consisting of pure numbers and minus signs. Variable ex cosval xy xx Considering the above "mathematical functions" as reserved words, identifiers (strings of lowercase English letters) that are not reserved words are called variables. Output Method For each variable (as defined above) that appears in the expression, describe an arithmetic expression that represents the derivative of the input algebraic expression with respect to that variable, using the operators, mathematical functions, and operands defined in the input form. The output is arranged according to the lexicographical order of the variables. For each line print two strings, which are each variable and the corresponding derivative function. Separate the two strings
最新发布
03-27
<think>好的,我现在需要帮助用户了解如何基于自动微分(Autograd)技术为代数表达式构建求导程序,包括输入格式解析、操作符优先级处理以及数学函数的支持。首先,我得理清楚自动微分的基本概念实现步骤。 自动微分不同于符号微分数值微分,它通过计算图来分解表达式,利用链式法则逐层计算导数。用户提到的输入格式解析,可能需要将输入的代数表达式转换成计算机可以处理的结构,比如抽象语法树(AST)。这部分需要考虑如何解析表达式中的操作符优先级,比如乘法、除法比加减法优先级高,括号的处理等。可能需要使用递归下降解析器或者利用现有的解析库。 接下来是构建计算图。每个操作符函数都需要作为节点,记录其输入操作类型。例如,加法节点需要知道它的两个输入节点,乘法节点也是如此。数学函数如sin、cos等也需要被支持,因此需要为这些函数定义对应的节点类型前向计算、反向传播的方法。 在实现前向传播时,每个节点需要计算其输出值,并保存中间结果以便反向传播使用。反向传播阶段,需要从输出节点开始,按照链式法则计算梯度,并将梯度传递给各个输入节点。这里需要注意梯度累积的情况,比如一个节点被多个路径使用,梯度需要累加。 对于操作符优先级的处理,在解析表达式时,需要按照正确的优先级顺序来构建AST,确保乘除在加减之前处理,括号内的表达式优先计算。这可能需要使用运算符栈输出队列的调度场算法(Shunting-yard algorithm)来转换中缀表达式为后缀表达式,再构建AST。 数学函数的支持需要扩展解析器,识别函数名如sin、exp等,并在计算图中生成对应的函数节点。每个函数节点需要实现其前向计算导数计算的方法。例如,sin(x)的导数是cos(x),在反向传播时需要乘以来自上一层的梯度。 测试验证也是关键步骤。可以通过已知导数的函数来验证自动微分的结果是否正确,比如对sin(x²)求导,结果应该是2x*cos(x²)。此外,还要处理多变量情况,可能需要扩展为支持偏导数的计算。 在参考中,用户提供的引用[4]提到了使用SymPy库解决数学问题,这可能涉及到符号计算,但自动微分通常是数值计算,两者有所不同。不过,可以借鉴SymPy解析表达式的方法,或者利用其解析功能来构建AST,然后在此基础上实现自动微分的逻辑。 最后,实现这样的程序可能需要选择一种编程语言,比如Python,因为它有丰富的库易于处理动态结构。同时,需要考虑性能优化,比如使用反向模式自动微分(适用于多输入单输出)或者前向模式(适用于单输入多输出),具体取决于应用场景。 总结一下,步骤大致分为:解析输入表达式生成AST,构建计算图,实现前向传播反向传播,处理操作符优先级数学函数,以及验证正确性。需要注意的难点包括正确解析复杂的表达式,处理各种操作符函数的导数规则,以及高效地计算梯度。</think>### 基于自动微分的代数表达式求导程序实现 #### 1. **自动微分基础** 自动微分(Autograd)通过分解计算过程为原子操作,利用链式法则计算导数。其核心分为两步: - **前向传播**:计算表达式的值并记录操作路径。 - **反向传播**:从输出反向遍历路径,累加梯度。 例如,对表达式 $f(x) = \sin(x^2)$,计算图分解为: $$x \rightarrow x^2 \rightarrow \sin(x^2)$$ 反向传播时,梯度从 $\cos(x^2)$ 乘以 $2x$ 得到最终导数[^4]。 --- #### 2. **输入解析与语法树构建** **输入格式解析**需支持代数表达式(如 `sin(x^2 + 3*x)`)并转换为抽象语法树(AST)。实现步骤: 1. **词法分析**:将输入拆分为令牌(如数字、变量、运算符)。 - 示例:`sin(x^2)` → `[sin, (, x, ^, 2, )]` 2. **语法分析**:使用调度场算法处理操作符优先级括号,生成后缀表达式,再构建AST。 - 例如,`x^2 + 3*x` 的AST: ``` (+) / \ (^) (*) / \ / \ x 2 3 x ``` --- #### 3. **操作符优先级与数学函数支持 **操作符优先级**需在语法分析阶段处理,规则如下: - 括号 > 函数(如`sin`)> 幂运算(`^`)> 乘除(`*`, `/`)> 加减(`+`, `-`)。 - 例如,`x + 3*sin(x)^2` 会被正确解析为 `x + (3*(sin(x)^2))`。 **数学函数支持**需在AST中定义函数节点(如`SinNode`),并在反向传播时实现其导数规则: - `sin(x)` 的导数为 `cos(x) * x_grad`。 - `exp(x)` 的导数为 `exp(x) * x_grad`。 --- #### 4. **求导程序实现(Python示例)** ```python import math class Node: def __init__(self, value=None, grad=0.0, children=()): self.value = value self.grad = grad self.children = children self._backward = lambda: None def backward(self): self.grad = 1.0 visited = set() def _dfs(node): if node not in visited: visited.add(node) node._backward() for child in node.children: _dfs(child) _dfs(self) class Add(Node): def __init__(self, a, b): super().__init__(a.value + b.value, children=(a, b)) def _backward(): a.grad += self.grad b.grad += self.grad self._backward = _backward class Sin(Node): def __init__(self, a): super().__init__(math.sin(a.value), children=(a,)) def _backward(): a.grad += math.cos(a.value) * self.grad self._backward = _backward # 示例:计算 d/dx [sin(x^2)] 在x=2处的值 x = Node(value=2.0) square = x * x # 自定义乘法节点(类似Add实现) f = Sin(square) f.backward() print(f"f'(2) = {x.grad}") # 输出 2*2*cos(4) = 4*cos(4) ``` --- #### 5. **关键问题与优化 - **梯度累积**:若变量被多次引用(如 $x + x$),需累加梯度。 - **性能优化**:反向模式自动微分(Reverse-mode AD)适合多输入、单输出场景[^4]。 - **扩展性**:支持更多函数(如`log`, `tanh`)多元函数偏导数。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值