backends/default 教程

本教程演示如何把 backends/default 的稠密包装类型用作现成的数据,供针对 algebra trait 编写的泛型代码使用,以及在需要时如何下沉到完整的具体 API。背景知识见 backends/default 设计。

快速上手

moon add Luna-Flow/linear-algebra@0.5.0
///|
import {
  "Luna-Flow/linear-algebra/algebra",
  "Luna-Flow/linear-algebra/backends/default",
}
///|
test "multiply two dense matrices" {
  let a = @default.DenseMatrix::from_2d_array([[1, 2], [3, 4]])
  let b = @default.DenseMatrix::from_2d_array([[0, 1], [1, 0]])
  inspect((a * b).inner(), content="|2, 1|\n|4, 3|")
}

日常任务

在两种包装类型上运行泛型算法

针对 MatMulMatrix 编写的辅助函数同样接受可变和不可变的包装类型:

///|
fn[M : @algebra.MatMulMatrix] def_tut_commutator(a : M, b : M) -> M {
  a * b - b * a
}

///|
test "commutator on two representations" {
  let a = @default.DenseMatrix::from_2d_array([[0, 1], [0, 0]])
  let b = @default.DenseMatrix::from_2d_array([[0, 0], [1, 0]])
  inspect(def_tut_commutator(a, b).inner(), content="|1, 0|\n|0, -1|")
  let ai = @default.ImmutableDenseMatrix::from_2d_array([[0, 1], [0, 0]])
  let bi = @default.ImmutableDenseMatrix::from_2d_array([[0, 0], [1, 0]])
  inspect(def_tut_commutator(ai, bi).inner(), content="|1, 0|\n|0, -1|")
}

计算线性模型

matvec、dot 和 axpy 涵盖了线性模型 y=Wx+by = W x + b 中常见的向量算术:

///|
test "a tiny linear layer" {
  let w = @default.DenseMatrix::from_2d_array([[0.5, -1.0], [2.0, 0.0]])
  let x = @default.DenseVector::from_array([2.0, 1.0])
  let b = @default.DenseVector::from_array([0.1, 0.2])
  let y = w.matvec(x) + b
  inspect(y.inner(), content="|0.1, 4.2|")
  inspect(y.dot(y), content="17.650000000000002")
}

17.650000000000002 的最后一位来自舍入:0.12+4.220.1^2 + 4.2^2 无法被精确表示。

下沉到具体 API

包装类型只暴露 trait 层面的运算。其他功能请通过 inner() 使用,例如受检的求逆:

///|
test "checked inverse through inner" {
  let a = @default.DenseMatrix::from_2d_array([[4.0, 7.0], [2.0, 6.0]])
  let inv = @default.DenseMatrix::from_backend(a.inner().inverse().unwrap())
  let id = a * inv
  inspect((id.inner().get(0, 0) - 1.0).abs() < 1.0e-12, content="true")
  inspect(id.inner().get(0, 1).abs() < 1.0e-12, content="true")
}

不复制地包装已有矩阵

from_backend 共享内部值,因此之后的写入是可见的:

///|
test "wrapping shares storage" {
  let m = @mutable.Matrix::from_2d_array([[1, 2], [3, 4]])
  let d = @default.DenseMatrix::from_backend(m)
  m.set(0, 0, 10)
  inspect(d.inner().get(0, 0), content="10")
}

进一步了解

选择包装类型。 如果还要使用 @mutable 的原地与数值 API(视图、分解、统计),请使用 DenseMatrix/DenseVector。如果值在构造后不得改变,例如要在程序的不同部分之间共享,请使用 Immutable* 包装类型。

在包装类型之间转换。 container 适配器为全部四种包装类型提供了字典,因此 matrix_convert 可以在它们之间搬运数据。

编写新的后端。 稀疏或定长类型应当为自身实现 algebra trait(如 algebra 教程所示),而不是转换成这些包装类型。

常见陷阱

  • 以为 DenseVector::from_array 会复制。 它与 @mutable.Vector::from_array 一样共享数组。如果数组会被复用,请先复制。
  • 以为 axpy 会原地更新。 它返回新向量。
  • 对不兼容的形状做乘法。 * 和 matvec 会中止;请用 shape() 检查形状,或使用内部类型的受检方法。
  • 在包装类型上寻找分解。 它们在 inner() 上。

后续步骤