PyTorch 2.0 チュートリアル : 学習 : torch.autograd への易しいイントロ

PyTorch 2.0 チュートリアル : 学習 : torch.autograd への易しいイントロ (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 03/20/2023 (2.0.0)

* 本ページは、PyTorch 2.0 Tutorials の以下のページを翻訳した上で適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

クラスキャット 人工知能 研究開発支援サービス

クラスキャット は人工知能・テレワークに関する各種サービスを提供しています。お気軽にご相談ください :

◆ 人工知能とビジネスをテーマに WEB セミナーを定期的に開催しています。スケジュール
  • お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。

お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。

  • 株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション
  • sales-info@classcat.com  ;  Website: www.classcat.com  ;   ClassCatJP

 

 

PyTorch 2.0 チュートリアル : 学習 : torch.autograd への易しいイントロ

torch.autograd はニューラルネットワーク訓練にパワーを供給する PyTorch の自動微分エンジンです。このセクションでは、autograd がニューラルネットワーク訓練にどのように役立つかの概念的理解を得るでしょう。

 

背景

ニューラルネットワーク (NN) はある入力データ上で実行されるネストされた関数のコレクションです。これらの関数は PyTorch ではテンソルにストアされる (重みとバイアスから構成される) パラメータにより定義されます。

NN の訓練は 2 つのステップで発生します :

順伝播 (= Forward Propagation) : forward prop では、NN は正しい出力について最善の推測を行ないます。それはこの推測を行なうために入力データを関数の各々に通します。

逆伝播 (= Backward Propagation) : backprop では、NN はその推測の誤差に応じてそのパラメータを調整します。それは出力から逆に辿り、関数のパラメータに関する誤差の導関数 (勾配) を集め、そして勾配降下を使用してパラメータを最適化します。backprop の詳細なウォークスルーについては、3Blue1Brown からのこの動画 を確認してください。

 

PyTorch での使用方法

単一の訓練ループを見てみましょう。この例のために、torchvision から事前訓練済み resnet18 モデルをロードします。あるランダム値に初期化された、3 チャネル, 64 の高さ & 幅を持つ単一画像、そして対応するラベルを表すランダム・データテンソルを作成します。事前訓練済みのラベルは shape (1,1000) を持ちます。

Note : This tutorial work only on CPU and will not work on GPU (even if tensors are moved to CUDA).

import torch
from torchvision.models import resnet18, ResNet18_Weights
model = resnet18(weights=ResNet18_Weights.DEFAULT)
data = torch.rand(1, 3, 64, 64)
labels = torch.rand(1, 1000)
Downloading: "https://download.pytorch.org/models/resnet18-f37072fd.pth" to /var/lib/jenkins/.cache/torch/hub/checkpoints/resnet18-f37072fd.pth

  0%|          | 0.00/44.7M [00:00<?, ?B/s]
 23%|##3       | 10.5M/44.7M [00:00<00:00, 110MB/s]
 79%|#######9  | 35.5M/44.7M [00:00<00:00, 199MB/s]
100%|##########| 44.7M/44.7M [00:00<00:00, 198MB/s]

次に、予測を行なうために層の各々を通すようモデルに入力データを渡します。これが forward パス です。

prediction = model(data) # forward pass

誤差 (損失) を計算するためにモデルの予測と対応するラベルを利用します。次のステップはこの誤差をネットワークを通して逆伝播することです。逆伝播は誤差テンソル上で .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 つのテンソル 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 から もう一つのテンソル 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 はこれらの勾配を計算してそれらをそれぞれのテンソルの .grad 属性にストアします。

Q.backward() の gradient 引数に明示的に渡す必要があります、何故ならばそれはベクトルであるからです。gradient は Q と同じ shape のテンソルでそれはそれ自身に関する 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}\) を計算します。

\(\vec{v}\) がスカラー関数 \(l=g\left(\vec{y}\right)\) の勾配である場合

\[\vec{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{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) のデータ (テンソル) & (結果としての新しいテンソルと共に) 総ての実行される演算を記録し続けます。この DAG では、葉は入力テンソルで、根は出力テンソルです。このグラフを根から葉にトレースすることにより、連鎖律を使用して勾配を自動的に計算できます。

forward パスでは、autograd は 2 つのことを同時に行ないます :

  • 結果としてのテンソルを計算するために要求された演算を実行し、そして
  • DAG の演算の勾配関数を保持します。

backward パスは DAG の根の上で .backward() が呼び出されたときに開始されます。autograd はそれから :

  • 各 .grad_fn から勾配を計算し
  • それらをそれぞれのテンソルの .grad 属性に累積し、そして
  • 連鎖律を使用して、葉テンソルに all the way (道なり) に伝播します。

下は私達の例の DAG の視覚表現です。グラフでは、矢印は forward パスの方向にあります。ノードは forward パスの各演算の backward 関数を表します。青色の葉ノードは葉テンソル a と b を表します。

Note

DAG は PyTorch では動的です。 注意すべき重要なことはグラフはゼロから表現されることです ; 各 .backward() 呼び出し後、autograd は新しいグラフを追加 (= populate) し始めます。これは正確には、モデルで制御フローステートメントを使用することを貴方に可能にするものです。必要であれば総ての iteration で shape, サイズと演算を変更できます。

 

DAG からの除外

torch.autograd は総てのテンソル上の演算を追跡します、それらは requires_grad フラグを True に設定しています。勾配を必要としないテンソルについては、この属性を False に設定すれば勾配計算 DAG からそれを除外します。

演算の出力テンソルは、単一の入力テンソルだけが 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 計算を減じることによりある程度のパフォーマンスのメリットを供給します)。

再調整では、モデルの殆どを凍結してそして通常は新しいラベル上で予測を行なうために分類層だけを変更します。これを実演するために小さいサンプルをウォークスルーしましょう。前のように、事前訓練済み resnet18 モデルをロードし、そして総てのパラメータを凍結します。

from torch import nn, optim

model = resnet18(weights=ResNet18_Weights.DEFAULT)

# 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.parameters(), lr=1e-2, momentum=0.9)

optimizer 内に総てのパラメータを登録しましたが、勾配を計算している (そしてそれ故に勾配降下で更新される) 唯一のパラメータは分類器の重みとバイアスであることに注意してください。

同じ排他的な機能は torch.no_grad() のコンテキスト・マネージャとして利用可能です。

 

以上