菅間修正済み 2025/05/15
【原題】Autograd: Automatic Differentiation
【原著】Soumith Chintala
【元URL】https://pytorch.org/tutorials/beginner/blitz/autograd_tutorial.html
【翻訳】電通国際情報サービスISID AIトランスフォーメーションセンター 徳原 光
【日付】2020年10月27日
【チュトーリアル概要】
PyTorchによるニューラルネットワークの学習において、重要な概念でありパッケージでもあるautogradの機能、そしてその動作内容について解説します。
PyTorchによるニューラルネットワーク構築の土台となっているのが、autograd(自動微分)パッケージです。
このパッケージの概要をざっくりと確認し、本チュートリアルシリーズで初めてとなるニューラルネットワークの訓練を体験しましょう。
autogradパッケージはTensorの操作に対する自動微分機能を提供します。
Tensor操作に基づく自動微分はdefine-by-runフレームワークであり、ユーザーが実行したコードに対応して誤差逆伝搬が定義され、すべてのイテレーションの計算で異なる結果を生み出します。
ここからは、いくつかの実例を通してこの機能を簡単に見ていきましょう。
(日本語訳注:define-by-runはデータをニューラルネットワークに流しながら、モデルの構築を行っていく手法を指します。一方でdefine-and-runは先に誤差逆伝搬の形を実行する前に構築します。)
torch.Tensorはパッケージの中心的なクラスです。
.requires_grad属性がTrueに設定された場合、autogradパッケージによってすべての操作が追跡され、演算が終了した際は.backward()を呼び出すことで、すべての操作に対する勾配が自動的に計算されます。
このTensorに対する勾配は.grad属性に蓄積されていきます。
追跡履歴からTensorを切り離して、追跡を停止する場合は、 .detach()を呼び出します。
これにより、その後の演算ではこのこのTensorは追跡されないように設定可能です。
with torch.no_grad():でコードをブロックにまとめることで、追跡履歴(とメモリの利用)を省略することもできます。
これはモデルを評価する際、requires_grad=Trueにより学習可能なパラメータを持っているが、勾配の計算は必要ない場合に特に有効です。
そしてもう一つ、自動微分の実行に非常に重要なクラスにFunctionがあります。
TensorとFunctionは 相互に接続し、非巡回グラフで完全な計算履歴を記録しています。
各Tensorは、そのTensorを作成したFunction を参照する.grad_fn属性を持ちます(ユーザーが直接定義したテンソルの場合はgrad_fn is Noneとなります)。
導関数を算出する場合は、Tensorが持つ関数.backward()を呼び出します。
Tensorがスカラー(すなわち、要素数が1つだけ)の場合、.backward()に引数を指定する必要はありません。しかし、テンソルが複数要素を持つ場合は、Tensorと同じ大きさのTensorをgradientの引数に指定する必要があります。
import torch
Tensorを作成し、requires_grad=Trueと指定することによって演算を追跡してみましょう。
x = torch.ones(2, 2, requires_grad=True)
print(x)
tensor([[1., 1.],
[1., 1.]], requires_grad=True)
Tensorの計算を実行:
y = x + 2
print(y)
tensor([[3., 3.],
[3., 3.]], grad_fn=<AddBackward0>)
y は計算結果であり、 計算履歴としてgrad_fnを持っています。
print(y.grad_fn)
<AddBackward0 object at 0x7f7f3e698518>
さらに、yを用いた計算を実行します:
z = y * y * 3
out = z.mean()
print(z, out)
tensor([[27., 27.],
[27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward0>)
.requires_grad_( ... ) によって既存のTensorの requires_gradフラグを変更することができます。
テンソルの作成時に引数で何も指定していない場合は、requires_gradはデフォルト値として False に設定されています。
a = torch.randn(2, 2)
a = ((a * 3) / (a - 1))
print(a.requires_grad)
a.requires_grad_(True)
print(a.requires_grad)
b = (a * a).sum()
print(b.grad_fn)
False True <SumBackward0 object at 0x7f7ef1826320>
では、誤差逆伝搬を実行してみましょう。
変数outはスカラーの値を持っているため, out.backward() はout.backward(torch.tensor(1.))と同じ結果になります。
out.backward()
print(out)
tensor(27., grad_fn=<MeanBackward0>)
勾配 d(out)/dxを表示。
print(x.grad)
tensor([[4.5000, 4.5000],
[4.5000, 4.5000]])
結果としてすべての要素が 4.5の行列を得たのではないでしょうか。
この出力テンソルを“$o$”と記載します。 テンソル“$o$”を計算すると、
$o = \frac{1}{4}\sum_i z_i$
$z_i = 3(x_i+2)^2$
そして、
$z_i\bigr\rvert_{x_i=1} = 27$.
なので、
$\frac{\partial o}{\partial x_i} = \frac{3}{2}(x_i+2)$
$\frac{\partial o}{\partial x_i}\bigr\rvert_{x_i=1} = \frac{9}{2} = 4.5$
となります。
数学的には、ベクトル関数 $\vec{y}=f(\vec{x})$において、 $\vec{x}$に関する$\vec{y}$ の勾配はヤコビアンと呼ばれています。
\begin{align}J=\left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{1}}{\partial x_{n}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{m}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)\end{align}
一般的に、 torch.autograd はベクトルのヤコビアンの積を算出する計算エンジンになります。
これは、$v=\left(\begin{array}{cccc} v_{1} & v_{2} & \cdots & v_{m}\end{array}\right)^{T}$というベクトルに対して、行列積$v^{T}\cdot J$を計算することを意味します。
もし、 $v$ がスカラー関数の勾配 $l=g\left(\vec{y}\right)$であった場合、 $v=\left(\begin{array}{ccc}\frac{\partial l}{\partial y_{1}} & \cdots & \frac{\partial l}{\partial y_{m}}\end{array}\right)^{T}$と表されます。
そして連鎖律により、ベクトルのヤコビアンの積は$\vec{x}$に関する$l$の勾配となるのです。
\begin{align}J^{T}\cdot v=\left(\begin{array}{ccc} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{1}}\\ \vdots & \ddots & \vdots\\ \frac{\partial y_{1}}{\partial x_{n}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{array}\right)\left(\begin{array}{c} \frac{\partial l}{\partial y_{1}}\\ \vdots\\ \frac{\partial l}{\partial y_{m}} \end{array}\right)=\left(\begin{array}{c} \frac{\partial l}{\partial x_{1}}\\ \vdots\\ \frac{\partial l}{\partial x_{n}} \end{array}\right)\end{align}
( $v^{T}\cdot J$ は転置の公式から、 $J^{T}\cdot v$を計算して得られる列ベクトル、と同じ成分の行ベクトルを与えることに注意してください)
このベクトルのヤコビアンの積の性質は、スカラ量ではない出力を持つモデルに対して、外部から異なる勾配を追加して計算する際に、非常に有効に利用できます。
ベクトルのヤコビアンの積の例を見てみましょう
x = torch.randn(3, requires_grad=True)
y = x * 2
while y.data.norm() < 1000:
y = y * 2
print(y)
tensor([-100.0453, -496.9997, 898.9301], grad_fn=<MulBackward0>)
(日本語訳注:関数norm()はノルム≒距離を与えます、引数なしのnorm()は2乗ノルムです。 各要素を2乗して足し算し、そのルートを計算します)
よって上記では、乱数で発生させたxを2倍、4倍、8倍、・・・とyの3要素の2乗和の平均のルートが1000を超えるまで増加させています。
以下の計算結果を参照)
# 日本語訳注
torch.sqrt(y[0]*y[0] + y[1]*y[1] + y[2]*y[2])
tensor(1032.0334, grad_fn=<SqrtBackward>)
この場合、 y はスカラ量ではありません。 torch.autograd
では直接ヤコビアンの全要素を計算することはできませんが、ベクトルとヤコビアンの積を計算するだけの場合には、簡単に引数としてbackwardにベクトルを与えることで勾配が算出できます。
v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)
print(x.grad)
tensor([5.1200e+01, 5.1200e+02, 5.1200e-02])
# 日本語訳注
print("x:", x)
print("y:", y)
scale = y/x
print("倍数:", scale)
# このセルの出力から何倍して、yを求めたのか分かります。倍数: の出力部分です。
# このセルでは変数sclaeで表しています。
# y = scale * x なので、yのxに関する勾配は scaleです。
# yの勾配をvに対して計算した結果、xに対して溜まる勾配値がx.gradです。
# その値x.grad(上記のセルの出力結果)は、
# scaleにv=[0.1, 1, 0.0001]がかけ算された値となっているはずです。
x: tensor([-0.1954, -0.9707, 1.7557], requires_grad=True) y: tensor([-100.0453, -496.9997, 898.9301], grad_fn=<MulBackward0>) 倍数: tensor([512., 512., 512.], grad_fn=<DivBackward0>)
with torch.no_grad():でコードをまとめることで、.requires_grad=TrueとなっているTensorの追跡履歴からautogradを停止することができます。
print(x.requires_grad)
print((x ** 2).requires_grad)
with torch.no_grad():
print((x ** 2).requires_grad)
True True False
要素が同じTensorを作成した際も、勾配が必要ない場合は.detach()を用いることも可能です。
(日本語訳注:以下のセルの.eq()はTensorとして値が同じであればTrueを返します。いまは、xとyの要素は3つあるので、その3つに対して求めた結果を.all()でまとめて求めています 詳細)
print(x.requires_grad)
y = x.detach()
print(y.requires_grad)
print(x.eq(y).all())
True False tensor(True)
補足:
autograd.Function のドキュメントは
https://pytorch.org/docs/stable/autograd.html#function
を参照してください。