linalg API

linalg 包计算作用于 Luna-Flow/linear-algebra 不可变向量的函数的梯度和雅可比矩阵。每个驱动函数都在对偶数向量上对函数求值,每次处理一个输入坐标。方法的原理见 linalg 设计。

源码:src/linalg/linalg.mbt。

导入

import {
  "Luna-Flow/autodiff/linalg",
  "Luna-Flow/linear-algebra/immut" @la,
}

示例中用 @linalg 表示本包,用 @la 表示 linear-algebra/immut。@linalg.Vector 和 @linalg.Matrix 与 @la.Vector 和 @la.Matrix 是同一类型。

函数形状与前置条件

驱动函数第一个参数是函数,第二个参数是点 x∈Tnx \in T^n:

驱动函数f结果
gradient(Vector[Dual[T]]) -> Dual[T]∇f(x)∈Tn\nabla f(x) \in T^n
value_and_gradient(Vector[Dual[T]]) -> Dual[T](f(x),∇f(x))(f(x), \nabla f(x))
jacobian(Vector[Dual[T]]) -> Vector[Dual[T]]Jf(x)∈Tm×nJ_f(x) \in T^{m \times n}
value_and_jacobian(Vector[Dual[T]]) -> Vector[Dual[T]](f(x),Jf(x))(f(x), J_f(x))

这四个函数都要求:

  • f 接受长度为 nn = x.length() 的向量,并且只读取小于 nn 的下标;
  • 向量值 f 每次调用都返回相同的长度 mm;
  • f 把所有捕获的值都视为常数(Dual::constant)。

驱动函数不会校验形状。违反条件时会因下标错误而中止;如果后续某次调用返回了更长的向量,多出的元素则会被静默忽略。

梯度

gradient

计算标量函数的梯度 ∇f(x)=(∂f/∂x1,…,∂f/∂xn)\nabla f(x) = \big(\partial f/\partial x_1, \dots, \partial f/\partial x_n\big)。

pub fn[T : @luna-generic.One + @luna-generic.Zero] gradient((@immut.Vector[@dual.Dual[T]]) -> @dual.Dual[T], @immut.Vector[T]) -> @immut.Vector[T]

对每个 ii,它调用一次 f,将坐标 ii 设为种子 Dual::new(x[i], 1),其余坐标设为 Dual::constant(x[j]),并把结果的切向分量存为第 ii 个分量。代价:在对偶数上对 f 求值 nn 次。当 n=0n = 0 时结果为空,且不会调用 f。

value_and_gradient

返回 f(x)f(x) 与 ∇f(x)\nabla f(x)。

pub fn[T : @luna-generic.One + @luna-generic.Zero] value_and_gradient((@immut.Vector[@dual.Dual[T]]) -> @dual.Dual[T], @immut.Vector[T]) -> (T, @immut.Vector[T])

值来自一次额外的、所有切向分量都为零的调用,因此代价为 n+1n + 1 次求值。

test "gradient of x0^2 + x0 x1" {
  let x = @la.Vector::from_array([2.0, 3.0])
  let f = (v : @la.Vector[@autodiff.Dual[Double]]) => v[0] * v[0] + v[0] * v[1]
  assert_eq(@linalg.gradient(f, x), @la.Vector::from_array([7.0, 2.0]))
  let (value, grad) = @linalg.value_and_gradient(f, x)
  assert_eq(value, 10.0)
  assert_eq(grad, @la.Vector::from_array([7.0, 2.0]))
}

雅可比矩阵

jacobian

计算向量值函数的雅可比矩阵 Jf(x)J_f(x),每个输出对应一行,每个输入对应一列:

Jf(x)ij=∂fi∂xj(x),0≤i<m,0≤j<n.J_f(x)_{ij} = \frac{\partial f_i}{\partial x_j}(x), \qquad 0 \le i < m, \quad 0 \le j < n .
pub fn[T : @luna-generic.One + @luna-generic.Zero] jacobian((@immut.Vector[@dual.Dual[T]]) -> @immut.Vector[@dual.Dual[T]], @immut.Vector[T]) -> @immut.Matrix[T]

它先以全零切向分量调用一次 f 以得知 mm,然后对每个输入 jj 各调用一次,将坐标 jj 设为种子;第 jj 次调用的输出切向分量构成第 jj 列。代价:n+1n + 1 次求值。结果满足 row() =m= m、col() =n= n。

value_and_jacobian

返回 f(x)f(x) 与 Jf(x)J_f(x)。

pub fn[T : @luna-generic.One + @luna-generic.Zero] value_and_jacobian((@immut.Vector[@dual.Dual[T]]) -> @immut.Vector[@dual.Dual[T]], @immut.Vector[T]) -> (@immut.Vector[T], @immut.Matrix[T])

值来自一次单独的零切向分量调用,雅可比矩阵来自 jacobian,因此代价为 n+2n + 2 次求值。

test "jacobian of (x0 + x1, x0 x1, x0^2)" {
  let x = @la.Vector::from_array([2.0, 3.0])
  let f = (v : @la.Vector[@autodiff.Dual[Double]]) => {
    @la.Vector::from_array([v[0] + v[1], v[0] * v[1], v[0] * v[0]])
  }
  let (value, j) = @linalg.value_and_jacobian(f, x)
  assert_eq(value, @la.Vector::from_array([5.0, 6.0, 4.0]))
  assert_eq(j.row(), 3)
  assert_eq(j.col(), 2)
  assert_eq(j[1][0], 3.0) // d(x0 x1)/dx0 = x1
  assert_eq(j[2][1], 0.0) // d(x0^2)/dx1
}

重新导出的类型

Dual

对偶数类型;见 dual API。

pub using @dual {type Dual}

Vector

linear-algebra 的不可变向量。

pub using @immut {type Vector}

Matrix

linear-algebra 的不可变矩阵。

pub using @immut {type Matrix}