はじめに
Python での自動微分の実装例を紹介します。
ある関数 f(x) の計算をすると、その微分関数 f'(x) の数字を求めることができる、という手法です。
自動微分には、フォワードモードとリバースモードの2つのやり方があります。
フォワードモードは演算子のオーバーロードで実装することができます。
ただし、f(x1, x2, x3, …) のように微分したい変数が多い場合は計算効率が良くありません。
機械学習ではリバースモードという、もう少し難しいやり方が用いられます。
これは置いといて、今回はフォワードモードの紹介です。
サンプルコード
以下実装例です。
AutoDiff クラスを定義しており、x の値が f(x)、dx の値が f'(x) の計算結果になるように、各種演算子を定義しています。
ここで、AutoDiff.x の値には、今求めたい数字をインプットします。
AutoDiff.dx の値には、常に初期値1を与えるような利用方法になります。(x を x で微分すると常に1になるので)
サンプルの実行結果では、f(x) = x^2 + 2x + sin(x) という式に対して、微分値を計算しています。
f'(x) = 2x + 2 + cos(x) という関数を定義せずに、f'(0) = 3.0, f'(1) = 4.54…, f'(π) = 7.283… の数字を出力しました。
import math
import numbers
class AutoDiff:
def __init__(self, x, dx):
self.x = x
self.dx = dx
def __str__(self):
return f'({self.x}, {self.dx})'
def __pos__(self):
return self.__class__(self.x, self.dx)
def __neg__(self):
return self.__class__(-self.x, -self.dx)
def __add__(self, other):
if isinstance(other, numbers.Number):
return self.__class__(self.x + other, self.dx)
if isinstance(other, self.__class__):
return self.__class__(self.x + other.x, self.dx + other.dx)
return NotImplemented
def __radd__(self, other):
if isinstance(other, numbers.Number):
return self + other
return NotImplemented
def __sub__(self, other):
if isinstance(other, (numbers.Number, self.__class__)):
return self + -other
return NotImplemented
def __rsub__(self, other):
if isinstance(other, numbers.Number):
return other + -self
return NotImplemented
def __mul__(self, other):
if isinstance(other, numbers.Number):
return self.__class__(self.x * other, self.dx * other)
if isinstance(other, self.__class__):
return self.__class__(self.x * other.x, self.x * other.dx + self.dx * other.x)
return NotImplemented
def __rmul__(self, other):
if isinstance(other, numbers.Number):
return self * other
return NotImplemented
def __truediv__(self, other):
if isinstance(other, (numbers.Number, self.__class__)):
return self * other ** -1
return NotImplemented
def __rtruediv__(self, other):
if isinstance(other, numbers.Number):
return other * self ** -1
return NotImplemented
def __pow__(self, other):
if isinstance(other, numbers.Number):
return self ** AutoDiff(other, 0)
if isinstance(other, self.__class__):
x = self.x ** other.x
dx = other.x * self.x ** (other.x - 1) * self.dx
if other.dx != 0:
dx += self.x ** other.x * math.log(self.x) * other.dx
return self.__class__(x, dx)
return NotImplemented
def __rpow__(self, other):
if isinstance(other, numbers.Number):
return AutoDiff(other, 0) ** self
return NotImplemented
def exp(x):
return math.e ** x
def log(x):
return AutoDiff(math.log(x.x), x.dx / x.x)
def sqrt(x):
return x ** 0.5
def sin(x):
return AutoDiff(math.sin(x.x), math.cos(x.x) * x.dx)
def cos(x):
return AutoDiff(math.cos(x.x), -math.sin(x.x) * x.dx)
if __name__ == '__main__':
def f(x):
return x ** 2 + 2 * x + sin(x)
print(f(AutoDiff(0, 1)))
print(f(AutoDiff(1, 1)))
print(f(AutoDiff(math.pi, 1)))
❯ python3 autodiff_forward.py
(0.0, 3.0)
(3.8414709848078967, 4.54030230586814)
(16.152789708268944, 7.283185307179586)
おしまい!