autodiff 教程

本教程是了解本仓库的最快途径:只需导入 Luna-Flow/autodiff 这一个包,你就能对函数求导、针对重新导出的 trait 编写泛型代码,并处理带检查运算的失败。每一节都链接到更深入的对应包教程。

快速上手

moon add Luna-Flow/autodiff@0.2.0
import {
  "Luna-Flow/autodiff",
}
fn main {
  let (value, slope) = @autodiff.value_and_diff(x => x * x.exp(), 1.0)
  println("f(1) = \{value}")
  println("f'(1) = \{slope}")
}
f(1) = 2.718281828459045
f'(1) = 5.43656365691809

f(x)=xexf(x) = x e^x 的导数为 f′(x)=(1+x)exf'(x) = (1 + x)e^x,因此 f′(1)=2ef'(1) = 2e。

日常任务

对闭包求导

fn main {
  let slope = @autodiff.diff(x => x.sin() * x.cos(), 0.0)
  println("d/dx sin x cos x at 0 = \{slope}")
}
d/dx sin x cos x at 0 = 1

使用重新导出的 trait 编写泛型代码

所有约束都来自这一个导入:

fn[T : @autodiff.Ring + @autodiff.Exponential + @autodiff.IntegralHomomorphism] softplus_like(
  x : T,
) -> T {
  let one : T = @autodiff.IntegralHomomorphism::from_integral(1)
  one + @autodiff.Exponential::exp(x)
}

fn main {
  println("value at 0: \{softplus_like(0.0)}")
  println("slope at 0: \{@autodiff.diff(softplus_like, 0.0)}")
}
value at 0: 2
slope at 0: 1

使用数学常数

Constants 以对偶常数的形式提供 π\pi、τ\tau 和 ee:

fn main {
  let area_slope = @autodiff.diff(
    r => {
      let pi : @autodiff.Dual[Double] = @autodiff.Constants::pi()
      pi * r * r
    },
    2.0,
  )
  println("d/dr pi r^2 at 2 = \{area_slope}")
}
d/dr pi r^2 at 2 = 12.566370614359172

处理带检查运算的失败

fn main {
  let ctx = @autodiff.ArithmeticContext::new(53)
  let x = @autodiff.Dual::variable(2.0)
  match @autodiff.DivChecked::div_checked(x, x - x, ctx) {
    Ok(_) => println("unexpected")
    Err(e) => println("division by zero: \{e.is_division_by_zero()}")
  }
}
division by zero: true

深入学习

常见陷阱

  • 根包不提供梯度。 要使用 gradient 和 jacobian,请导入 Luna-Flow/autodiff/linalg。
  • 重新导出的 trait 就是原始 trait。 你为 @lg.Ring 编写的实例就是 @autodiff.Ring 的实例;不要两者都实现。
  • 在被求导的代码中,字面量需要用 Dual::constant 或 from_integral 包装。

后续阅读