- 数值微分:利用导数极限公式,计算导数。优点:简单直观,对复杂函数也有效。缺点:计算量大,舍入误差(round-off error,浮点计算过程带来的误差),截断误差(truncation error,近似带来的误差)。
- 符号微分:对常见函数给出求导函数,然后利用链式法则生成复杂函数的求导函数。优点:准确无误。缺点:生成的求导函数复杂,计算量大。
- 自动微分:利用链式法则逐步计算导数(以数值方式保留中间计算结果,而非生成求导函数)。包含前向自动微分、反向自动微分两种形式。
前向自动微分:先对内侧/底层函数求导,以数值方式保留导数,利用链式法则,依次向外/向上求导。外侧的局部导数乘以内侧的全局导数,得到外侧的全局导数(此处全局是指当前函数对内侧根部参数求导)。优点:随着前向计算,可以同时进行微分计算,简单直观。缺点:对每个参数,都要进行一遍计算(因为内侧全局导数是针对某一个参数的)。
反向自动微分:先对外侧/顶层函数求导,以数值方式保留导数,利用链式法则,依次向内/向下求导。外侧的全局导数乘以内侧的局部导数,得到内侧的全局导数(此处全局是指外部叶子函数对当前节点求导)。当前深度学习框架主流的自动微分形式,和我们手动对复杂公式求导的过程类似。优点:一遍计算,即可得到每个册数的导数(因为外侧全局导数不是针对某一个参数的)。缺点:需要保留前向计算结果,直到最内侧导数计算完成。
Demo实现
- Automatic differentiation in machine learning: a survey:文献解读
- Kaslanarian/PyDyNet: NumPy实现类PyTorch框架:动态图Demo。相关解读如下,仅供参考。代码比解读更超前,以代码为主。
- dlsys-course/assignment1-2018: Assignment 1: automatic differentiation:静态图Demo。
PyTorch计算图
- Pytorch Turorials:A Gentle Introduction to torch.autograd — PyTorch Tutorials 1.12.1+cu102 documentation
- Overview of PyTorch Autograd Engine | PyTorch
- How Computational Graphs are Constructed in PyTorch | PyTorch
- How Computational Graphs are Executed in PyTorch | PyTorch
- Pytorch自动微分实现示例:https://colab.research.google.com/drive/1VpeE6UvEPRz9HmsHh1KS0XxXjYu533EC
Pytorch Codes:
- torch/_tensor.py
- torch/autograd/init.py:
- torch/csrc/autograd/autograd.h
- torch/csrc/autograd/autograd.cpp:run_backward()调用engine执行反向传播
- torch/csrc/autograd/engine.h:反向传播图遍历执行
- torch/csrc/autograd/function.h:Node定义,以及建立Edge(next_edge同反向传播方向)。
- torch/csrc/autograd/variable.h:gradient_edge同反向传播方向
- torch/csrc/autograd/edge.h:Edge定义
- aten/src/ATen/core/TensorBase.h
custom_function:
- 针对前向传播,Function::apply -> 1. next_edges=collect_next_edges(inputs) 2. set_ctx_grad_fn(node) 3. node->set_next_edges(std::move(next_edges)) 4. forward -> _wrap_outputs -> _process_backward_mode_ad:给output设置grad_fn -> Variable.set_gradient_edge(variable, edge)/grad_accumular
- 针对反向传播,CppNode::apply -> backward
Function: collect_next_edges(variable):获取node的input的edges -> Variable.gradient_edge(variable)
autograd: backward()/grad() -> run_backward -> Engine::excute
engine: Engine::excute -> GraphTask::execute_with_graph_task -> GraphTask::thread_main(graph_task) -> while(NodeTask task = local_ready_queue->pop();) -> Engine::evaluate_function -> 1. output=Engine::call_function 2. input_buffer.add(std::move(output)) -> queue->push(NodeTask(next.function, input_buffer))
Edge(grad_fn, input_nr)
Node.next_edges[]: 针对反向计算图,第i个next_edge代表node_current/grad_fn_current的第i个输出(grad_input),该next_edge的grad_fn代第i个输出对应的grad_fn_next,input_nr代表grad_fn_next的第几个输入(grad_output)
Variable:针对反向计算图,root(如loss)和leaf(如weight)的Variable都加入到计算图中,设置edge。
- Variable.set_gradient_edge(variable, edge):给variable设置gran_fn。
- Variable.set_grad_accumulator(variable, grad_accumulator):给variable设置给variable设置grad_accumulator。
- Variable.gradient_edge(variable):返回variable的(grad_fn/grad_accumulator, input_nr), 针对反向计算图,对于非叶子节点,grad_fn代表产生这个variable的fn对应的grad_fn,input_nr代表grad_fn的第几个输入。对于叶子节点,返回variable的grad_accumulator(累加梯度),input_nr只能为0。