【Python】自動微分(フォワードモード)【数値解析】

  • 2021年12月19日
  • 2022年8月4日
  • Python
  • 185View
  • 0件

はじめに

Python での自動微分の実装例を紹介します。

ある関数 f(x) の計算をすると、その微分関数 f'(x) の数字を求めることができる、という手法です。

自動微分には、フォワードモードとリバースモードの2つのやり方があります。
フォワードモードは演算子のオーバーロードで実装することができます。
ただし、f(x1, x2, x3, …) のように微分したい変数が多い場合は計算効率が良くありません。

機械学習ではリバースモードという、もう少し難しいやり方が用いられます。
これは置いといて、今回はフォワードモードの紹介です。

Wikipedia 自動微分

サンプルコード

以下実装例です。

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)

おしまい!