forward の設計

このページでは、スカラードライバー diff と value_and_diff が何を計算するか、そのコスト、そしてそれらが Dual[T] -> Dual[T] 上の単純な関数である理由を説明します。

設計目標

よくあるケースである 1 変数関数の導関数に対して、変数のシードを隠す 1 行の API を提供します。同時に、すべての意味論を Dual[T] に置くことで、ドライバーが型と食い違うことがないようにします。

数学的背景

フォワードモード

プログラムは中間値 v1,…,vmv_1, \dots, v_m を通じて y=f(x)y = f(x) を計算し、各中間値はそれ以前のものに対する演算です。フォワードモードは各 vkv_k と並べてその導関数 v˙k=dvk/dx\dot v_k = dv_k/dx を持ち運び、プログラムと同じ順序で連鎖律によって更新します:

vk=φk(vi,vj)⟹v˙k=∂φk∂vi v˙i+∂φk∂vj v˙j,x˙=1.v_k = \varphi_k(v_{i}, v_{j}) \quad\Longrightarrow\quad \dot v_k = \frac{\partial \varphi_k}{\partial v_i}\,\dot v_i + \frac{\partial \varphi_k}{\partial v_j}\,\dot v_j , \qquad \dot x = 1 .

双対数の算術はまさにこの更新を行います。vk+v˙kεv_k + \dot v_k\varepsilon は Dual::new(v_k, dv_k) であり、dual の設計 の規則は各 φk\varphi_k の偏導関数です。x˙=1\dot x = 1 のシードは Dual::variable(x) です。

入れ子による高階導関数

f がスカラー型についてジェネリックであれば、Dual[Dual[T]] に適用できます。Dual[T] 上の双対数で x↦f′(x)x \mapsto f'(x) を微分すると

ddx diff(f,x)=f′′(x),\frac{d}{dx}\,\mathrm{diff}(f, x) = f''(x),

が得られます。diff(f, ·) 自体が(内側のレベルの)双対数演算から構成されたプログラムだからです。外側と内側の接成分は異なる型に属するので、型のない入れ子のフォワードモードで起こる古典的な 摂動の混同(内側の導関数が外側の摂動を拾ってしまう問題)は型検査器によって拒否されます。11 J. M. Siskind and B. A. Pearlmutter, “Perturbation confusion and referential transparency”, IFL 2005 では、型のない実装におけるこの問題を説明しています。 入れ子のレベルが 1 つ増えるごとに成分の数は 2 倍になるので、kk 階導関数のコストは通常の評価の 2k2^k 倍です。

設計上の判断

トレイトやラッパー型ではなく関数

問題。 ユーザーはドライバーにどのように関数を渡すべきでしょうか。

選択肢。 ユーザー定義型が実装するトレイト、Double 上の関数と別個の導関数、Dual[T] 上のクロージャ。

選択。 クロージャ (Dual[T]) -> Dual[T] です。MoonBit のクロージャは軽量であり、型によって関数が双対数向けに書かれていなければならないことが明示され、ジェネリック関数はラッパーなしで Dual[T] にインスタンス化されます。

最小の境界

value_and_diff に必要なのは、シードの接成分のための T : One だけです。それ以外はすべて f 内部の演算が要求するものであり、コンパイラが呼び出し箇所でチェックします。

同じ評価から値を得る

値は導関数と同じ双対数評価から読み取られます。射影準同型 により、これは f が T 上で計算する値とビット単位で一致するので、2 回目の評価は不要です。

独立したパッケージに置く

dual パッケージを純粋な数値型に保つため、ドライバーは forward に置かれ、ルートパッケージがそれらを再エクスポートします。forward は dual と luna-generic にのみ依存します。

正しさと不変条件

  • value_and_diff(f, x) == (f(variable(x)).value(), f(variable(x)).tangent()) であり、diff(f, x) はその第 2 成分です。
  • 環演算、0 でない除数による除算、定義域内の初等関数からなるプログラムについて、結果は dual の設計 の丸めの上界のもとで計算された (f(x),f′(x))(f(x), f'(x)) です。
  • コストは Dual[T] 上での f の 1 回の評価で、T 上での 1 回の評価の小さな定数倍です。

却下した代替案

  • ドライバー内部での数値微分。 dual の設計 で導いた精度上の理由から却下しました。
  • nn 階導関数のための diff_n。 ジェネリック関数については入れ子ですでに得られ、効率的な解決策は打ち切りテイラー型でしょう。どちらも現時点で 2 つ目の API を設ける理由にはなりません。

境界

  • スカラー入力のみです。複数の入力は linalg のドライバーで扱うか、Dual::new を手動でシードして扱います。
  • チェック付きの版はありません。チェック付き演算を使うかどうかは f が決め、チェック付きの f は独自の Result を返します。
  • 高階のドライバーもリバースモードもありません。

Footnotes

  1. J. M. Siskind and B. A. Pearlmutter, “Perturbation confusion and referential transparency”, IFL 2005 では、型のない実装におけるこの問題を説明しています。 ↩