PyTorch 1.8 チュートリアル : PyTorch の学習 : 基本 – torch.autograd による自動微分 (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 03/16/2021 (1.8.0)
* 本ページは、PyTorch 1.8 Tutorials の以下のページを翻訳した上で適宜、補足説明したものです:
- Learning PyTorch : Learn the Basics : Automatic Differentiation with 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 による自動微分
ニューラルネットワークを訓練するとき、最も頻繁に使用されるアルゴリズムは 逆伝播 です。このアルゴリズムでは、パラメータ (モデル重み) は与えられたパラメータに関する損失関数の 勾配 に従って調整されます。
これらの勾配を計算するため、PyTorch は torch.autograd と呼ばれる組込み微分エンジンを持ちます。それは任意の計算グラフの勾配の自動計算をサポートします。入力 x、パラメータ w と b、そしてある損失関数を伴う最も単純な 1-層ニューラルネットワークを考えます。それは次の流儀で PyTorch で定義できます :
import torch
x = torch.ones(5) # input tensor
y = torch.zeros(3) # expected output
w = torch.randn(5, 3, requires_grad=True)
b = torch.randn(3, requires_grad=True)
z = torch.matmul(x, w)+b
loss = torch.nn.functional.binary_cross_entropy_with_logits(z, y)
Tensors, 関数と計算グラフ
このコードは次の計算グラフを定義します :
このネットワークでは、w と b が パラメータ で、これらを最適化する必要があります。そして、それらの変数に関する損失関数の勾配を計算できる必要があります。それを行なうため、それらの tensor の requires_grad プロパティを設定します。
Note
tensor を作成するとき、あるいは後で x.requires_grad_(True) メソッドを使用することにより requires_grad の値を設定できます。
計算グラフを構築するために tensor に適用する関数は実際にはクラス Function のオブジェクトです。このオブジェクトは forward 方向で関数をどのように計算するか、そして逆伝播ステップの間にその導関数をどのように計算するかも知っています。逆伝播関数への参照は tensor の grad_fn プロパティにストアされます。ドキュメント で Function のより多くの情報を見つけることができます。
print('Gradient function for z =',z.grad_fn)
print('Gradient function for loss =', loss.grad_fn)
Gradient function for z = <AddBackward0 object at 0x7f1a827dc450> Gradient function for loss = <BinaryCrossEntropyWithLogitsBackward object at 0x7f1a827dc3d0>
勾配を計算する
ニューラルネットワークのパラメータの重みを最適化するためには、パラメータに関する損失関数の導関数を計算する必要があります、つまり、x と y のある固定された値のもとで $\frac{\partial loss}{\partial w}$ と $\frac{\partial loss}{\partial b}$ を必要とします。それらの導関数を計算するため、loss.backward() を呼び出し、そして w.grad と b.grad から値を取得します :
loss.backward()
print(w.grad)
print(b.grad)
tensor([[0.0931, 0.0312, 0.2207], [0.0931, 0.0312, 0.2207], [0.0931, 0.0312, 0.2207], [0.0931, 0.0312, 0.2207], [0.0931, 0.0312, 0.2207]]) tensor([0.0931, 0.0312, 0.2207])
NOTE
- 計算グラフの葉 (= leaf) ノードのための grad プロパティを得られるだけです、それは requires_grad プロパティを True に設定します。グラフの総ての他のノードについては、勾配は利用可能ではありません。
- パフォーマンス的な理由で、与えられたグラフ上 backward を一度使用して勾配計算を遂行できるだけです。同じグラフ上で幾つかの backward 呼び出しを行なう必要がある場合、backward 呼び出しに retain_graph=True を渡す必要があります。
勾配追跡を無効にする
デフォルトでは、requires_grad=True を持つ総ての tensor はそれらの計算履歴を追跡していてそして勾配計算をサポートします。
けれども、それを行なう必要がない幾つかのケースがあります、例えば、モデルを訓練してある入力データにそれを適用することを望むだけ、i.e. ネットワークを通して forward 計算を行なうことを望むだけであるときです。torch.no_grad() ブロックで計算コードを囲むことにより計算を追跡することをやめられます :
z = torch.matmul(x, w)+b
print(z.requires_grad)
with torch.no_grad():
z = torch.matmul(x, w)+b
print(z.requires_grad)
True False
同じ結果を達成するためのもう一つの方法は tensor 上で detach() メソッドを使用することです :
z = torch.matmul(x, w)+b
z_det = z.detach()
print(z_det.requires_grad)
False
勾配追跡を無効にすることを望むかもしれない理由があります :
- 凍結されたパラメータにおいてニューラルネットワークの幾つかのパラメータをマークするため。これは 事前訓練されたネットワークを再調整する ための非常に一般的なシナリオです。
- forward パスを行なっているだけのとき計算をスピードアップするため、何故ならば勾配を追跡しない tensor 上の計算はより効率的であるからです。
More on 計算グラフ
概念的には、autograd は Function オブジェクトから成る有向非巡回グラフ (DAG) のデータ (tensor) の記録と (結果としての新しい tensor と一緒に) 総ての実行された演算を保持します。この DAG では、葉 (= leaves) は入力 tensor で、根ノードは出力 tensor です。このグラフを根から葉にトレースすることにより連鎖律を使用して勾配を自動的に計算できます。
forward パスでは、autograd は 2 つのことを同時に行ないます :
- 結果としての tensor を計算するために要求された演算を実行する
- DAG の演算の勾配関数を保持する。
backward パスは DAG ルート上で .backward() が呼び出されたときに開始されます。autograd はそれから :
- 各 .grad_fn から勾配を計算します
- それぞれの tensor の .grad 属性でそれらを累積します。
- 連鎖律を使用して、葉 tensor まで伝播します。
NOTE
DAG は PyTorch では動的です。注意すべき重要なことはグラフからスクラッチから再作成されることです ; 各 .backword() 呼び出し後、autograd は新しいグラフを投入し始めます。これはモデルで制御フローステートメントを使用することを正確に可能にするものです ;必要であれば総ての iteration で shape、サイズと演算を変更できます。
Optional Reading: Tensor 勾配とヤコビアン積
多くの場合、スカラー損失関数を持ち、そしてあるパラメータに関する勾配を計算する必要があります。けれども、出力関数が任意の tensor であるケースがあります。この場合、PyTorch は実際の勾配ではなくいわゆる ヤコビアン積 を計算する必要があります。
ベクトル関数 \(\vec{y}=f(\vec{x})\) について、ここで \(\vec{x}=\langle x_1,\dots,x_n\rangle\) と \(\vec{y}=\langle y_1,\dots,y_m\rangle\)、\(\vec{x}\) に関する \(\vec{y}\) の勾配はヤコビ行列で与えられます :
\[
\begin{split}\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}\end{split}
\]
ヤコビ行列自身を計算する代わりに、PyTorch は与えられた入力ベクトル \(v=(v_1 \dots v_m)\) に対して ヤコビアン積 \(v^T\cdot J\) を計算することを可能にします。これは引数として \(v\) と共に backward を呼び出すことにより達成されます。\(v\) のサイズは元の tensor のサイズと同じであるべきです、積を計算することを望むものに関して :
inp = torch.eye(5, requires_grad=True)
out = (inp+1).pow(2)
out.backward(torch.ones_like(inp), retain_graph=True)
print("First call\n", inp.grad)
out.backward(torch.ones_like(inp), retain_graph=True)
print("\nSecond call\n", inp.grad)
inp.grad.zero_()
out.backward(torch.ones_like(inp), retain_graph=True)
print("\nCall after zeroing gradients\n", inp.grad)
First call tensor([[4., 2., 2., 2., 2.], [2., 4., 2., 2., 2.], [2., 2., 4., 2., 2.], [2., 2., 2., 4., 2.], [2., 2., 2., 2., 4.]]) Second call tensor([[8., 4., 4., 4., 4.], [4., 8., 4., 4., 4.], [4., 4., 8., 4., 4.], [4., 4., 4., 8., 4.], [4., 4., 4., 4., 8.]]) Call after zeroing gradients tensor([[4., 2., 2., 2., 2.], [2., 4., 2., 2., 2.], [2., 2., 4., 2., 2.], [2., 2., 2., 4., 2.], [2., 2., 2., 2., 4.]])
同じ引数で backward を 2 回目に呼び出すとき、勾配の値は異なることに注意してください。これは逆伝播を行なうとき、PyTorch が勾配を累積するために発生します、i.e. 計算された勾配の値が計算グラフの総ての葉ノードの grad プロパティに追加されます。正しい勾配を計算することを望む場合、前に grad プロパティをゼロにする必要があります。実生活での訓練では optimizer がこれを行なうことを手助けします。
Note
以前に backward() 関数をパラメータなしで呼び出しました。これは本質的には backward(torch.tensor(1.0)) を呼び出すことと等値です、これはニューラルネットワーク訓練の間の損失のような、スカラー値関数の場合、勾配を計算する有用な方法です。
以上