「 Autograd(自動微分)」¶

菅間修正済み 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の機能、そしてその動作内容について解説します。


Autograd: 自動微分¶

PyTorchによるニューラルネットワーク構築の土台となっているのが、autograd(自動微分)パッケージです。

このパッケージの概要をざっくりと確認し、本チュートリアルシリーズで初めてとなるニューラルネットワークの訓練を体験しましょう。

autogradパッケージはTensorの操作に対する自動微分機能を提供します。

Tensor操作に基づく自動微分はdefine-by-runフレームワークであり、ユーザーが実行したコードに対応して誤差逆伝搬が定義され、すべてのイテレーションの計算で異なる結果を生み出します。

ここからは、いくつかの実例を通してこの機能を簡単に見ていきましょう。


(日本語訳注:define-by-runはデータをニューラルネットワークに流しながら、モデルの構築を行っていく手法を指します。一方でdefine-and-runは先に誤差逆伝搬の形を実行する前に構築します。)

テンソル(Tensor)¶

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の引数に指定する必要があります。

In [ ]:
import torch

Tensorを作成し、requires_grad=Trueと指定することによって演算を追跡してみましょう。

In [ ]:
x = torch.ones(2, 2, requires_grad=True)
print(x)
tensor([[1., 1.],
        [1., 1.]], requires_grad=True)

Tensorの計算を実行:

In [ ]:
y = x + 2
print(y)
tensor([[3., 3.],
        [3., 3.]], grad_fn=<AddBackward0>)

y は計算結果であり、 計算履歴としてgrad_fnを持っています。

In [ ]:
print(y.grad_fn)
<AddBackward0 object at 0x7f7f3e698518>

さらに、yを用いた計算を実行します:

In [ ]:
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 に設定されています。

In [ ]:
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>

勾配(Gradients)¶

では、誤差逆伝搬を実行してみましょう。

変数outはスカラーの値を持っているため, out.backward() はout.backward(torch.tensor(1.))と同じ結果になります。

In [ ]:
out.backward()
In [ ]:
print(out)
tensor(27., grad_fn=<MeanBackward0>)

勾配 d(out)/dxを表示。

In [ ]:
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$を計算して得られる列ベクトル、と同じ成分の行ベクトルを与えることに注意してください)

このベクトルのヤコビアンの積の性質は、スカラ量ではない出力を持つモデルに対して、外部から異なる勾配を追加して計算する際に、非常に有効に利用できます。

ベクトルのヤコビアンの積の例を見てみましょう

In [ ]:
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を超えるまで増加させています。

以下の計算結果を参照)

In [ ]:
# 日本語訳注
torch.sqrt(y[0]*y[0] + y[1]*y[1] + y[2]*y[2])
Out[ ]:
tensor(1032.0334, grad_fn=<SqrtBackward>)

この場合、 y はスカラ量ではありません。 torch.autograd では直接ヤコビアンの全要素を計算することはできませんが、ベクトルとヤコビアンの積を計算するだけの場合には、簡単に引数としてbackwardにベクトルを与えることで勾配が算出できます。

In [ ]:
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])
In [ ]:
# 日本語訳注
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を停止することができます。

In [ ]:
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()でまとめて求めています 詳細)

In [ ]:
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 を参照してください。