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 や数値 API(ビュー、分解、統計量)も使う場合は DenseMatrix/DenseVector を使ってください。構築後に値が変わってはならない場合、たとえばプログラムの各部分で値を共有する場合は、Immutable* ラッパーを使ってください。

ラッパー間の変換。 container のアダプタ には 4 つのラッパーすべての辞書があるので、matrix_convert でそれらの間でデータを移せます。

新しいバックエンドを書く。 疎な型や固定サイズの型は、これらのラッパーに変換するのではなく、algebra のチュートリアル のように自身に algebra の trait を実装すべきです。

よくある落とし穴

  • DenseVector::from_array がコピーすると期待する。 @mutable.Vector::from_array と同様に配列を共有します。配列を再利用する場合は先にコピーしてください。
  • axpy がインプレースで更新すると期待する。 新しいベクトルを返します。
  • 互換性のない形状を掛ける。 * と matvec は中断します。shape() で形状を検査するか、内側の型の検査付きメソッドを使ってください。
  • ラッパーで分解を探す。 分解は inner() にあります。

次のステップ