いっしきまさひこBLOG

AI・機械学習関連、Web制作関連、プログラミング関連、旅行記録などなど。一色政彦。

TensorFlowやPyTorchで自動微分して勾配を取得する方法

この記事では、TensorFlowとPyTorchの「自動微分」機能を使って勾配(=微分係数)を計算します。

PyTorchの自動微分については「第1回 難しくない! PyTorchでニューラルネットワークの基本:PyTorch入門 - @IT」を、
TensorFlowの自動微分については「第5回 お勧めの、TensorFlow 2.0最新の書き方入門(エキスパート向け) (2/2):TensorFlow 2+Keras(tf.keras)入門 - @IT」 を参考にしてください。

以下で説明する内容は、以下で実行/ソースコード参照できます。

■準備

●Pythonバージョン:3.x

import sys
print('Python', sys.version)
# Python 3.6.9 (default, Nov  7 2019, 10:44:02)  …… などと表示される

●TensorFlowバージョン

# Google Colabで最新の2.xを使う場合、2.xに切り替える(Colab専用)
# %tensorflow_version 2.x

import tensorflow as tf
print('TensorFlow', tf.__version__)
# TensorFlow 2.1.0 ……などと表示される

●PyTorchバージョン

import torch
print('PyTorch', torch.__version__)
# PyTorch 1.4.0 ……などと表示される

■実装方法

●例として使う計算式

$$ y=2x^3+4x^2+5x+6 $$

○これをPythonの式にすると...

def calc(x):
  return (2 * x ** 3) + (4 * x ** 2) + (5 * x) + 6

●微分する導関数の式は...

$$ \frac{dy}{dx}=6x^2+8x+5 $$

○これをPythonの式にすると...

def der_calc(x):
  return (6 * x ** 2) + (8 * x) + 5

●導関数に$x=0.5$を入力すると...

$$ \begin{align} \frac{dy}{dx}&=6\times(0.5)^2+8\times(0.5)+5 \\ &=(6\times0.25)+(8\times0.5)+5 \\ &=1.5+4+5 \\ &=10.5 \end{align} $$

○Pythonのコードで計算すると...

result = der_calc(0.5)
result # 10.5

$10.5$が出力される。これと同じことをPyTorchとTensorFlowの自動微分機能を使って行う方法を紹介する。

■PyTorch編

  • データをPyTorchテンソル化して、計算式を作る
  • backward()メソッドで逆伝播する(自動微分)
  • データ(テンソル)のgrad変数で微分係数(=勾配)を取得できる

という流れで書けばよいです。具体的には以下の通り。

import torch
import torch.nn as nn  # 「ニューラルネットワーク」モジュールの別名定義

x = torch.tensor(0.5, requires_grad=True)  # 変数に対して「勾配計算が必要」とマーク
y = calc(x)    # 既存の計算式から計算グラフを動的に構築
print(y)       # tensor(9.7500, grad_fn=<AddBackward0>) ……などと表示される

y.backward()   # 逆伝播の処理として、上記式から微分係数(=勾配)を計算(自動微分:Autograd)
g = x.grad     # 与えられた入力(x)によって計算された勾配の値(grad)を取得
print(g)       # tensor(10.5000)  ……などと表示される

y.backward(); g = x.gradg = torch.autograd.grad(y, x) と書いてもよいです。

●グラフを描画

先ほどは単一のスカラーだったので、今度は複数のスカラー値で計算してみます。

# PyTorchの微分グラフ
import numpy as np
import matplotlib.pyplot as plt

x = torch.arange(-4.0, 4.0, 0.001, requires_grad=True)
y = calc(x)
y.backward(gradient=torch.ones_like(y)) # スカラーテンソルの場合は勾配を格納する場所が必要なのでテンソルを仮生成【重要】
g = x.grad
x_numpy = x.detach().numpy()  # デタッチして(=追跡しないようにして)からndarray化する
g_numpy = g.detach().numpy()
plt.plot(x_numpy, g_numpy, label = "Derivative")
plt.xlim(-4, 4)
plt.ylim(-2.0, 30.0)
plt.grid()
plt.legend()
plt.show()

f:id:misshiki:20200319003754p:plain
出力された微分係数のグラフ(PyTorch編)

■TensorFlow編

  • データをTensorテンソル化して、勾配テープ(GradientTape)内で計算式を作る
  • 勾配テープ(tape)のgradientメソッドで逆伝播する(自動微分)
  • 逆伝播により計算された微分係数(=勾配)が取得される

という流れで書けばよいです。具体的には以下の通り。

import tensorflow as tf
import tensorflow.keras.layers as layers  # 「レイヤー」モジュールの別名定義

x = tf.Variable(0.5)
with tf.GradientTape() as tape:  # 計算式に対して「勾配計算が必要」とマーク
  y = calc(x)  # 既存の計算式から計算グラフを動的に構築
print(y)       # tf.Tensor(9.75, shape=(), dtype=float32) ……などと表示される

g = tape.gradient(y, x)   # 逆伝播の処理として、上記式から微分係数(=勾配)を計算(自動微分:Autograd)
print(g)       # tf.Tensor(10.5, shape=(), dtype=float32)  ……などと表示される

●グラフを描画

先ほどは単一のスカラーだったので、今度は複数のスカラー値で計算してみます。

# TensorFlowの微分グラフ
import numpy as np
import matplotlib.pyplot as plt

x = tf.Variable(tf.range(-4.0, 4.0, 0.001), trainable=True)
with tf.GradientTape() as tape:
  #tape.watch(x) # tf.constantなど「trainable=False」な状態であれば x を記録する(手動追跡)
  # ↑tf.Variableであれば既に訓練可能な状態(自動追跡)なので不要
  y = calc(x)
g = tape.gradient(y, x)
x_numpy = x.numpy()
g_numpy = g.numpy()
plt.plot(x_numpy, g_numpy, label = "Derivative")
plt.xlim(-4, 4)
plt.ylim(-2.0, 30.0)
plt.grid()
plt.legend()
plt.show()

f:id:misshiki:20200319003818p:plain
出力された微分係数のグラフ(TensorFlow編)

簡単ですね。以上で終わりです。