数学」カテゴリーアーカイブ

自動微分(3) — 高階導関数の計算

前回まででは、1階の(偏)微分係数を考察した。今度は、高階の導関数の値(微分係数)も計算するように拡張しよう。簡単のため、1変数関数について考えることにする。

高階微分と演算、関数の合成との関係を見る。変数は t とする。

もとの関数 1階導関数、2階導関数
h(t)=const. h(t)=0,h(t)=0
h(t)=t h(t)=1,h(t)=0
h(t)=f1(t)±f2(t) h(t)=f1(t)±f2(t),h(t)=f1(t)±f2(t)
h(t)=f1(t)f2(t) h(t)=f1(t)f2(t)+f1(t)f2(t),h(t)=f1(t)f2(t)+2f1(t)f2(t)+f1(t)f2(t)
h(t)=f1(t)f2(t) h(t)=f1(t)f2(t)f1(t)f2(t)f2(t)2,h(t)=f1(t)f2(t)2f1(t)f2(t)f2(t)2f1(t)f2(t)f2(t)2+2f1(t)f2(t)2f2(t)3
h(t)=g(f(t)) h(t)=g(f(t))f(t),h(t)=g(f(t))f(t)2+g(f(t))f(t)
h(t)=g(f1(t),f2(t)) h(t)=gx(f1(t),f2(t))f1(t)+gy(f1(t),f2(t))f2(t),h(t)=gxx(f1(t),f2(t))f1(t)2+2gxy(f1(t),f2(t))f1(t)f2(t)+gyy(f1(t),f2(t))f2(t)2+gx(f1(t),f2(t))f1(t)+gy(f1(t),f2(t))f2(t)

2階微分の自動微分では、「値」「1階の微分係数」「2階の微分係数」の3つの値を保持する必要があるので、実装するには R×R×R に適切な演算を入れれば良い。上の表の f(t),f(t),f(t)R×R×R の成分で置き換えると、R×R×R に入れるべき演算が分かる。すなわち、(x,x,x)±(y,y,y)=(x±y,x±y,x±y)(x,x,x)(y,y,y)=(xy,xy+xy,xy+2xy+xy)(x,x,x)(y,y,y)=(xy,xyxyy2,xy2xyy2xyy2+2xy2y3),g:RR(微分可能) に対し、g((x,x,x))=(g(x),g(x)x,g(x)x2+g(x)x),g:R×RR(微分可能) に対し、g((x,x,x),(y,y,y))=(g(x,y),gx(x,y)x+gy(x,y)y,gxx(x,y)x2+2gxy(x,y)xy+gyy(x,y)y2+gx(x,y)x+gy(x,y)y),
とすればよい。ただし、変数についている は微分ではなく変数名の一部と考える。関数についている や添字は微分の意である。

これを実際のプログラミング言語で実装するのは容易だろう。

SpringerでCWMのPDFを買った

SpringerのCategories for the Working Mathematicianのページから、eBookとして同書のPDF版を購入した。以下、気づいた点など。

  • 購入するにはSpringerのアカウントが必要になる。アカウントはその場で作れる。
  • 支払い方法は、クレジットカード数社のほか、PayPalも利用できる。
  • ダウンロードしたPDFファイルには、各ページの下に、Springerのアカウントとして使ったメールアドレスが書き込まれている。”digitally watermarked”ということだろう。ファイルの属性かなにかとして購入者の情報が入っているかは確かめていない。
  • PDFはどうやらスキャンしたものにOCRを施したもののようだ。しおりは設定されていない。
  • PDFファイルの変更やもろもろは制限されていない。Acrobatなどのソフトウエアを持っていれば自分でしおりを設定できそう。

自動微分(2) — 偏微分係数への一般化

前回まで考察したのは,1変数関数とその導関数の値を計算する場合だった.今回は,2変数関数とその偏導関数を計算する場合について考える.

自動微分を考える前に、2変数関数の微分(偏微分)と演算、関数の合成との関係を見ておこう。2つの変数を u, v とおく.偏導関数は,変数を添字にして表すことにする.

もとの関数 偏導関数
h(u,v)=const. hu(u,v)=0,hv(u,v)=0
h(u,v)=u hu(u,v)=1,hv(u,v)=0
h(u,v)=v hu(u,v)=0,hv(u,v)=1
h(u,v)=f1(u,v)±f2(u,v) hu(u,v)=f1u(u,v)±f2u(u,v),hv(u,v)=f1v(u,v)±f2v(u,v)
h(u,v)=f1(u,v)f2(u,v) hu(u,v)=f1u(u,v)f2(u,v)+f1(u,v)f2u(u,v),hv(u,v)=f1v(u,v)f2(u,v)+f1(u,v)f2v(u,v)
h(u,v)=f1(u,v)/f2(u,v) hu(u,v)=(f1u(u,v)f2(u,v)f1(u,v)f2u(u,v))/f2(u,v)2,hv(u,v)=(f1v(u,v)f2(u,v)f1(u,v)f2v(u,v))/f2(u,v)2
h(u,v)=g(f(u,v)) hu(u,v)=g(f(u,v))fu(u,v),hv(u,v)=g(f(u,v))fv(u,v)
h(u,v)=g(f1(u,v),f2(u,v)) hu(u,v)=gx(f1(u,v),f2(u,v))f1u(u,v)+gy(f1(u,v),f2(u,v))f2u(u,v),hv(u,v)=gx(f1(u,v),f2(u,v))f1v(u,v)+gy(f1(u,v),f2(u,v))f2v(u,v)

2変数の自動微分を表すときに必要な情報は

  • 関数の値 f(u,v)
  • 関数の u による偏微分係数 fu(u,v)
  • 関数の v による偏微分係数 fv(u,v)

の3つである。そこで、R の自動微分を表す型を AutoDiff2R=R×R×R とおき、演算を定義する。上の関係式の f1,f2 とその微分係数を (x,xu,xv),(y,yu,yv)AutoDiff2R で置き換えると、AutoDiff2R に入れるべき演算が分かる:(x,xu,xv)±(y,yu,yv)=(x±y,xu±yu,xv±yv)(x,xu,xv)(y,yu,yv)=(xy,xuy+xyu,xvy+xyv)(x,xu,xv)/(y,yu,yv)=(x/y,(xuyxyu)/y2,(xvyxyv)/y2)g:RR に対し,g((x,xu,xv))=(g(x),g(x)xu,g(x)xv),
g:R×RR に対し,g((x,xu,xv),(y,yu,yv))=(g(x,y),gx(x,y)xu+gy(x,y)yu,gx(x,y)xv+gy(x,y)yv)

Haskellで実装すると,たとえば次のようになる:
[sourcecode lang=”plain”]
data AutoDiff2 a = AutoDiff2 a a a deriving(Eq)
instance Num a => Num (AutoDiff2 a) where
(AutoDiff2 f f_u f_v) + (AutoDiff2 g g_u g_v) = AutoDiff2 (f+g) (f_u+g_u) (f_v+g_v)
(AutoDiff2 f f_u f_v) – (AutoDiff2 g g_u g_v) = AutoDiff2 (f-g) (f_u-g_u) (f_v-g_v)
(AutoDiff2 f f_u f_v) * (AutoDiff2 g g_u g_v) = AutoDiff2 (f*g) (f_u*g+f*g_u) (f_v*g+f*g_v)
abs _ = undefined
signum _ = undefined
fromInteger n = AutoDiff2 (fromInteger n) 0 0
[/sourcecode]

さて、1変数の場合の自動微分(R×R に演算を入れる)についてもう一度見てみよう。(x0,x1)±(y0,y1)=(x0±y0,x1±y1)(x0,x1)(y0,y1)=(x0y0,x1y0+x0y1)(x0,x1)/(y0,y1)=(x0/y0,(x1y0x0y1)/y20),g:RR(微分可能) に対し、g((x0,x1))=(g(x0),g(x0)x1),g:R×RR(微分可能) に対し、g((x0,x1),(y0,y1))=(g(x0,y0),gx(x0,y0)x1+gy(x0,y0)y1),
であった。
よく見てみると、第2成分の x1, y1R でなくても,R-ベクトル空間なら何でもよいことが分かる。2変数の偏微分の自動微分は、x1, y1 として R2 の元をとったものだと考えることができる。一般に、n 変数の自動微分は x1, y1 として Rn の元をとることにより得られる。

自動微分(1) — 1変数の場合

前回の記事が適当すぎたので仕切り直しを。

「値と微分係数を同時に計算する型」AutoDiff型は,R×Rに適当な演算を入れたものと考えられる。微分を考える変数を x,微分を計算したい点を t とするとき,第一成分を「x=t における値」,第二成分を「x=t における微分係数」と考える。AutoDiff型の値を y=(y0,y1) とすると,y はある関数 f について y=(f(t),f(t)) となっている。h(x)=g(f(x)) という関数があったとき,すでに得られている f(t),f(t) の値を使って h(t),h(t) を計算したいとする。合成関数の微分はh(x)=g(f(x))f(x)で与えられるので、ここに x=t を代入すればh(t)=g(f(t))f(t)=g(y0)y1となる。h(t) はもちろん h(t)=g(y0) で与えられる。

今度は k(x)=g(l(x)) という関数を考えてみよう。x=t における l の値と l の値はすでに計算されているとして、これを z=(z0,z1)=(l(t),l(t)) とおく。このとき,先ほどと同じように k(t)=g(z0)k(t)=g(z0)z1 がわかる。

このようにして,g という関数は、AutoDiff型からAutoDiff型への関数を定める。この関数を g とすると,AutoDiff型の値 (y0,y1) を与えた時の g の値はg(y0,y1)=(g(y0),g(y0)y1)となる。AutoDiff型が「これまで計算した関数の値と微分係数」を表す型だとすれば,g という関数は「これまで計算した関数に g という関数を合成した時の、関数の値と微分係数」を表す型である。

上の説明では gg を区別して書いたが、実際のプログラミングでは関数や演算子のオーバーロード、あるいは型クラスを使って、g に相当する関数も同じ演算子・関数名で書けるようにするのが普通である。

いくつかの演算について、AutoDiff型を R×R とみなしたときの計算方法を与えておく。(x0,x1)±(y0,y1)=(x0±y0,x1±y1)(x0,x1)(y0,y1)=(x0y0,x1y0+x0y1)(x0,x1)/(y0,y1)=(x0/y0,(x1y0x0y1)/y20),g:RR(微分可能) に対し、g((x0,x1))=(g(x0),g(x0)x1),g:R×RR(微分可能) に対し、g((x0,x1),(y0,y1))=(g(x0,y0),gx(x0,y0)x1+gy(x0,y0)y1),となる。

文章書くのだるい。気が向いたら続く。

自動微分(0) — 導入

関数の値 f(t) とその微分係数 f(t) を同時に計算したい状況があるとする.ここでは,具体的な2次関数
f(x)=x2+2x
で話を進める.この関数は,C++を使うと次のように実装できる.

[sourcecode lang=”cpp”]
template<typename T>
constexpr T f(T x) {
return x*x+2*x;
}
[/sourcecode]

あるいは,Haskellでは次のように書けるだろう.

[sourcecode lang=”plain”]
f :: Num a => a -> a
f x = x*x+2*x
[/sourcecode]

さて,賢明な読者は,fの導関数が2x+2であることに気づいたかもしれない.そうすると,f(x)f(x)を同時に計算する関数gは次のように実装できる.

[sourcecode lang=”cpp”]
// in C++
template<typename T>
std::pair<T,T> g(T x) {
return {x*x+2*x, 2*x+2};
}
[/sourcecode]

 

[sourcecode lang=”plain”]
— in Haskell
g :: Num a => a -> (a,a)
g x = (x*x+2*x, 2*x+2)
[/sourcecode]

しかし,せっかくf(x)の定義式をプログラム中に書いているのに,f(x)の計算式を改めて書くのは無駄,と言わざるを得ない.また,プログラムを変更してf(x)の定義式を書き換えたときにf(x)の式を直し忘れる,というミスが起こる可能性もある.できることなら,f(x)の定義式を使って,f(x)を自動的に計算させたい.

そこで登場するのが,自動微分(automatic differentiation)と呼ばれるテクニックである。f(x)の定義の中で使っているのは和と積だけである.和と積の微分は高校で習ったと思うが,次の式で計算できる.

もとの関数 導関数
h(x)=f(x)±g(x) h(x)=f(x)±g(x)
h(x)=f(x)g(x) h(x)=f(x)g(x)+f(x)g(x)

では, f の中での和と積の挙動をいじくって,微分もついでに計算させることはできないだろうか?

幸い,C++には演算子オーバーロードという便利な機能がある.先のコードで, f はテンプレートを使って定義したので,引数の型は intdouble でなくてもよい.演算子をオーバーロードしてやった自前の型を与えても良い.そこで, AutoDiff というクラスを次のように定義しよう:

[sourcecode lang=”cpp”]
template<typename T>
class AutoDiff
{
public:
T value; // 値
T diff; // 微分係数の値
constexpr AutoDiff() : value(), diff() {} // 定数0
constexpr AutoDiff(T value) : value(value), diff() {} // 定数で初期化(微分係数は0)
constexpr AutoDiff(T value, T diff) : value(value), diff(diff) {} // 値 value, 微分係数 diff で初期化

friend constexpr AutoDiff<T> operator+(AutoDiff<T> f, AutoDiff<T> g) {
return {f.value+g.value, f.diff+g.diff};
}
friend constexpr AutoDiff<T> operator*(AutoDiff<T> f, AutoDiff<T> g) {
return {f.value*g.value, f.diff*g.value+f.value*g.diff};
}
};
[/sourcecode]

このクラスを使って,先ほどの関数 g を実装しなおしてみると,次のようになる:

[sourcecode lang=”cpp”]
template<typename T>
std::pair<T,T> g(T x) {
AutoDiff<T> y = f(AutoDiff<T>(x, 1));
return {y.value, y.diff};
}
[/sourcecode]

実際にこの定義し直した g が関数の値と微分係数を正しく計算することは,各自確かめてほしい.

Haskellでは,型クラスを使うと同じようなことができる.

[sourcecode lang=”plain”]
data AutoDiff a = AutoDiff a a deriving(Eq)
instance Num a => Num (AutoDiff a) where
(AutoDiff f f’) + (AutoDiff g g’) = AutoDiff (f+g) (f’+g’)
(AutoDiff f f’) – (AutoDiff g g’) = AutoDiff (f-g) (f’-g’)
(AutoDiff f f’) * (AutoDiff g g’) = AutoDiff (f*g) (f’*g+f*g’)
abs _ = undefined
signum _ = undefined
fromInteger = flip AutoDiff 0 . fromInteger

g :: Num a => a -> (a,a)
g x = case f (AutoDiff x 1) of AutoDiff f f’ -> (f,f’)
[/sourcecode]

ここでのコード例は加法と乗法しか定義しなかったが,残りの演算は,次の表を参考にすれば容易に実装できるだろう.

もとの関数 導関数
h(x)=f(x)±g(x) h(x)=f(x)±g(x)
h(x)=f(x)g(x) h(x)=f(x)g(x)+f(x)g(x)
h(x)=f(x)/g(x) h(x)=(f(x)g(x)f(x)g(x))/g(x)2
h(x)=g(f(x)) h(x)=g(f(x))f(x)
h(x)=g(f1(x),f2(x)) h(x)=gu(f1(x),f2(x))f1(x)+gv(f1(x),f2(x))f2(x)
ただしgu,gvgの偏導関数

文章を書くのに疲れたので,今日はここまで.(続く)