PyTorch 1.8 チュートリアル : PyTorch の学習 : torch.autograd への易しいイントロ (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 03/20/2021 (1.8.0)
* 本ページは、PyTorch 1.8 Tutorials の以下のページを翻訳した上で適宜、補足説明したものです:
- Learning PyTorch : a Gentle Introduction to torch.autograd
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
- お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
- Windows PC のブラウザからご参加が可能です。スマートデバイスもご利用可能です。
人工知能研究開発支援 | 人工知能研修サービス | テレワーク & オンライン授業を支援 |
PoC(概念実証)を失敗させないための支援 (本支援はセミナーに参加しアンケートに回答した方を対象としています。) |
◆ お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。
株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション |
E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/ ; Facebook |
PyTorch の学習 : torch.autograd への易しいイントロ
torch.autograd はニューラルネットワーク訓練にパワーを供給する PyTorch の自動微分エンジンです。このセクションでは、autograd がニューラルネットワーク訓練をどのように手助けするかの概念的理解を得るでしょう。
背景
ニューラルネットワーク (NN) はある入力データ上で実行されるネストされた関数のコレクションです。これらの関数は PyTorch では tensor にストアされる (重みとバイアスから成る) パラメータにより定義されます。
NN の訓練は 2 つのステップで発生します :
順伝播 (= Forward Propagation) : forward prop では、NN は正しい出力について最善の推測を行ないます。それはこの推測を行なうために関数の各々を通して入力データを実行します。
逆伝播 (= Backward Propagation) : backprop では、NN はその推測の誤差に相応してそのパラメータを調整します。それは出力から逆に辿り、関数のパラメータに関する誤差の導関数 (勾配) を集め、そして勾配降下を使用してパラメータを最適化します。backprop のより詳細なウォークスルーについては、3Blue1Brown からのこの動画 を確認してください。
PyTorch での使用方法
単純な訓練ループを見ましょう。この例のために、torchvision から事前訓練された resnet18 モデルをロードします。あるランダム値に初期化された、3 チャネル、と 64 の高さ & 幅を持つ単一画像、そして対応するラベルを表すランダム・データ tensor を作成します。
import torch, torchvision
model = torchvision.models.resnet18(pretrained=True)
data = torch.rand(1, 3, 64, 64)
labels = torch.rand(1, 1000)
次に、予測を行なうために層の各々を通してモデルに入力データを通します。これは forward パス です。
prediction = model(data) # forward pass
誤差 (損失) を計算するためにモデルの予測と対応するラベルを利用します。次のステップはこの誤差をネットワークを通して逆伝播することです。逆伝播は誤差 tensor 上で .backward() を呼び出すときに開始されます。それから autograd はパラメータの .grad 属性の各モデルパラメータのための勾配を計算してストアします。
loss = (prediction - labels).sum()
loss.backward() # backward pass
次に、optimizer をロードします、この場合 0.01 の学習率と 0.9 のモメンタムを持つ SGD です。optimizer にモデルの総てのパラメータを登録します。
optim = torch.optim.SGD(model.parameters(), lr=1e-2, momentum=0.9)
最後に、勾配降下を初期化するために .step() を呼び出します。optimizer は .grad にストアされる勾配により各パラメータを調整します。
optim.step() #gradient descent
この時点で、ニューラルネットワークを訓練するために必要な総てを持ちます。下のセクションは autograd の動作を詳述します – それらを自由にスキップしてください。
Autograd の微分
autograd がどのように勾配を集めるかを見ましょう。2 つの tensor a と b を requires_grad=True で作成します。これはそれらの上の総ての演算が追跡されるべきであることを autograd に伝えます。
import torch
a = torch.tensor([2., 3.], requires_grad=True)
b = torch.tensor([6., 4.], requires_grad=True)
a と b から もう一つの tensor Q を作成します。
\[
Q = 3a^3 – b^2
\]
Q = 3*a**3 - b**2
a と b が NN のパラメータで、Q が誤差であると仮定しましょう。NN 訓練では、パラメータに関する誤差の勾配を望みます、i.e.
\[
\frac{\partial Q}{\partial a} = 9a^2 \\
\frac{\partial Q}{\partial b} = -2b
\]
Q 上で .backward() を呼び出すとき、autograd はこれらの勾配を計算してそれらをそれぞれの tensor の .grad 属性にストアします。
Q.backward() の gradient 引数に明示的に渡す必要があります、何故ならばそれはベクトルであるからです。gradient は Q と同じ shape の tensor でそれはそれ自身に関する Q の勾配を表します、i.e.
\[
\frac{dQ}{dQ} = 1
\]
同値に、Q をスカラーに合計して暗黙的に backward を呼び出すこともできます、Q.sum().backward() のように。
external_grad = torch.tensor([1., 1.])
Q.backward(gradient=external_grad)
勾配は今では a.grad と b.grad に deposit されます。
# check if collected gradients are correct
print(9*a**2 == a.grad)
print(-2*b == b.grad)
tensor([True, True]) tensor([True, True])
オプションの読み物 – autograd を使用したベクトル微積 (= Calculus)
数学的には、ベクトル値関数 \(\vec{y}=f(\vec{x})\) を持つ場合、\(\vec{x}\) に関する \(\vec{y}\) の勾配はヤコビ行列 \(J\) です :
\[
\begin{split}J
=
\left(\begin{array}{cc}
\frac{\partial \bf{y}}{\partial x_{1}} &
… &
\frac{\partial \bf{y}}{\partial x_{n}}
\end{array}\right)
=
\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{split}
\]
一般的に言えば、torch.autograd はベクトル-ヤコビアン積を計算するためのエンジンです。つまり、任意のベクトル \(\vec{v}\) が与えられたとき、積 \(J^{T}\cdot \vec{v}\) を計算します。
\(v\) がスカラー関数の勾配である場合
\[
l
=
g\left(\vec{y}\right)
=
\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{split}J^{T}\cdot \vec{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{split}
\]
ベクトル-ヤコビアン積のこの特性は上のサンプルで利用しているものです ; external_grad は (\vec{v}\) を表します。
計算グラフ
概念的には、autograd は Function オブジェクトから成る有向非巡回グラフ (DAG) のデータ (tensor) & 総ての実行される演算を記録し続けます。この DAG では、葉は入力 tensor で、根は出力 tensor です。このグラフを根から葉にトレースすることにより、連鎖律を使用して勾配を自動的に計算できます。
forward パスでは、autograd は 2 つのことを同時に行ないます :
- 結果としての tensor を計算するために要求された演算を実行し、そして
- DAG の演算の勾配関数を維持します。
backward パスは DAG の根の上で .backward() が呼び出されたときに開始されます。autograd はそれから :
- 各 .grad_fn から勾配を計算し
- それらをそれぞれの tensor の .grad 属性に累積し、そして
- 連鎖律を使用して、葉 tensor にずっと伝播します。
下は私達の例の DAG の可視表現です。グラフでは、矢印は forward パスの方向にあります。ノードは forward パスの各演算の backward 関数を表します。青色の葉ノードは葉 tensor a と b を表します。
Note
DAG は PyTorch では動的です。 注意すべき重要なことはグラフはスクラッチから表現されることです ; 各 .backward() 呼び出し後、autograd は新しいグラフを追加 (= populate) し始めます。これは正確に、貴方のモデルで制御フローステートメントを使用することを可能にするものです。必要であれば総ての iteration で shape, サイズと演算を変更できます。
DAG からの除外
torch.autograd は総ての tensor 上の演算を追跡します、それらは requires_grad フラグを True に設定しています。勾配を必要としない tensor については、この属性を False に設定すれば勾配計算 DAG からそれを除外します。
演算の出力 tensor は単一の入力 tensor だけが requires_grad=True を持つ場合でさけ勾配を必要とします。
x = torch.rand(5, 5)
y = torch.rand(5, 5)
z = torch.rand((5, 5), requires_grad=True)
a = x + y
print(f"Does `a` require gradients? : {a.requires_grad}")
b = x + z
print(f"Does `b` require gradients?: {b.requires_grad}")
Does `a` require gradients? : False Does `b` require gradients?: True
NN では、勾配を計算しないパラメータは通常は 凍結されたパラメータ と呼ばれます。それらのパラメータの勾配を必要としないことを前もって知るのであればモデルの一部を「凍結する」ことは有用です (これは autograd 計算を減じることによりあるパフォーマンスのメリットを供給します)。
DAG からの除外が重要であるもう一つの一般的なユースケースは 事前訓練されたネットワークを再調整する ためです。
再調整では、モデルの殆どを凍結してそして典型的には新しいラベル上で予測を行なうために分類層だけを変更します。これを実演するために小さいサンプルをウォークスルーしましょう。前のように、事前訓練された resnet18 モデルをロードし、そして総てのパラメータを凍結します。
from torch import nn, optim
model = torchvision.models.resnet18(pretrained=True)
# Freeze all the parameters in the network
for param in model.parameters():
param.requires_grad = False
モデルを 10 ラベルを持つ新しいデータセット上で再調整することを望むとしましょう。resnet では、分類器は最後の線形層 model.fc です。それを単純に (デフォルトで未凍結の) 新しい線形層で置き換えることができます、それが私達の分類器として動作します。
model.fc = nn.Linear(512, 10)
今はモデルの総てのパラメータは model.fc のパラメータを除いて、凍結されました。勾配を計算する唯一のパラメータは model.fc の重みとバイアスです。
# Optimize only the classifier
optimizer = optim.SGD(model.fc.parameters(), lr=1e-2, momentum=0.9)
optimizer に総てのパラメータを登録しましたが、勾配を計算している (そしてそれ故に勾配降下で更新される) 唯一のパラメータは分類器の重みとバイアスであることに注意してください。
同じ排他的な機能は torch.no_grad() のコンテキスト・マネージャとして利用可能です。
以上