PyTorch 2.0 チュートリアル : 入門 : torch.autograd による自動微分 (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 03/18/2023 (2.0.0)
* 本ページは、PyTorch 2.0 Tutorials の以下のページを翻訳した上で適宜、補足説明したものです:
- Introduction to PyTorch : Learn the Basics : Automatic Differentiation with torch.autograd
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
- 人工知能研究開発支援
- 人工知能研修サービス(経営者層向けオンサイト研修)
- テクニカルコンサルティングサービス
- 実証実験(プロトタイプ構築)
- アプリケーションへの実装
- 人工知能研修サービス
- PoC(概念実証)を失敗させないための支援
- お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
◆ お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。
- 株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション
- sales-info@classcat.com ; Website: www.classcat.com ; ClassCatJP
PyTorch 2.0 チュートリアル : 入門 : torch.autograd による自動微分
ニューラルネットワークを訓練するとき、最も頻繁に使用されるアルゴリズムは 逆伝播 (back propagation) です。このアルゴリズムでは、パラメータ (モデル重み) は与えられたパラメータに関する損失関数の 勾配 (gradient) に従って調整されます。
これらの勾配を計算するため、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)
テンソル, 関数と計算グラフ
このコードは次の計算グラフを定義します :
このネットワークでは、w と b が パラメータ で、これらを最適化する必要があります。そして、それらの変数に関する損失関数の勾配を計算できる必要があります。それを行なうため、それらのテンソルの requires_grad プロパティを設定します。
Note : テンソルを作成するとき、あるいは後で x.requires_grad_(True) メソッドを使用することにより requires_grad の値を設定できます。
計算グラフを構築するためにテンソルに適用する関数は実際にはクラス Function のオブジェクトです。このオブジェクトは forward 方向で関数をどのように計算するか、そして逆伝播ステップの間にその導関数をどのように計算するかも知っています。逆伝播関数への参照はテンソルの grad_fn プロパティにストアされます。ドキュメント で Function の詳細を見つけることができます。
print(f"Gradient function for z = {z.grad_fn}")
print(f"Gradient function for loss = {loss.grad_fn}")
Gradient function for z = <AddBackward0 object at 0x7f64949ec940> Gradient function for loss = <BinaryCrossEntropyWithLogitsBackward0 object at 0x7f64949efa90>
勾配の計算
ニューラルネットワークのパラメータの重みを最適化するためには、パラメータに関する損失関数の導関数を計算する必要があります、つまり、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.0795, 0.1649, 0.2479], [0.0795, 0.1649, 0.2479], [0.0795, 0.1649, 0.2479], [0.0795, 0.1649, 0.2479], [0.0795, 0.1649, 0.2479]]) tensor([0.0795, 0.1649, 0.2479])
NOTE
- 私たちは計算グラフの葉 (= leaf) ノードのための grad プロパティを得られるだけです、それは requires_grad プロパティを True に設定します。グラフの総ての他のノードについては、勾配は利用可能ではありません。
- パフォーマンス的な理由で、私たちは与えられたグラフ上 backward を一度使用して勾配計算を実行できるだけです。同じグラフ上で幾つかの backward 呼び出しを行なう必要がある場合、backward 呼び出しに retain_graph=True を渡す必要があります。
勾配追跡を無効にする
デフォルトでは、requires_grad=True を持つ総てのテンソルはそれらの計算履歴を追跡していてそして勾配計算をサポートします。けれども、それを行なう必要がない幾つかのケースがあります、例えば、モデルを訓練してある入力データにそれを適用することだけを望むとき、つまりネットワークを通して 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
同じ結果を実現するためのもう一つの方法はテンソル上で detach() メソッドを使用することです :
z = torch.matmul(x, w)+b
z_det = z.detach()
print(z_det.requires_grad)
False
勾配追跡を無効にすることを望むかもしれない理由があります :
- 凍結されたパラメータ としてニューラルネットワークの幾つかのパラメータをマークするため。
- forward パスを行なっているだけのとき 計算をスピードアップする ため、何故ならば勾配を追跡しないテンソル上の計算はより効率的であるからです。
More on 計算グラフ
概念的には、autograd は Function オブジェクトから構成される有向非巡回グラフ (DAG) のデータ (テンソル) の記録と (結果としての新しいテンソルを伴う) 総ての実行された演算を保持します。この DAG では、葉 (= leaves) は入力テンソルで、根ノードは出力テンソルです。このグラフを根から葉にトレースすることにより連鎖律を使用して勾配を自動的に計算できます。
forward パスでは、autograd は 2 つのことを同時に行ないます :
- 結果としてのテンソルを計算するために要求された演算を実行する
- DAG の演算の勾配関数を保持する。
backward パスは DAG ルート上で .backward() が呼び出されたときに開始されます。autograd はそれから :
- 各 .grad_fn から勾配を計算します
- それぞれのテンソルの .grad 属性でそれらを累積します。
- 連鎖律を使用して、葉テンソルまで伝播します。
NOTE
DAG は PyTorch では動的です。注意すべき重要なことはグラフはゼロから再作成されることです ; 各 .backword() 呼び出し後、autograd は新しいグラフを投入し始めます。これはモデルで制御フローステートメントを使用することを正確に可能にするものです ; 必要であれば総ての iteration で shape、サイズと演算を変更できます。
オプションの読み物 : テンソル勾配とヤコビアン積
多くの場合、スカラー損失関数を持ち、そしてあるパラメータに関する勾配を計算する必要があります。けれども、出力関数が任意のテンソルである場合があります。この場合、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\) のサイズは元のテンソルのサイズと同じである必要があります、積を計算することを望むものに関して :
inp = torch.eye(4, 5, requires_grad=True)
out = (inp+1).pow(2).t()
out.backward(torch.ones_like(out), retain_graph=True)
print(f"First call\n{inp.grad}")
out.backward(torch.ones_like(out), retain_graph=True)
print(f"\nSecond call\n{inp.grad}")
inp.grad.zero_()
out.backward(torch.ones_like(out), retain_graph=True)
print(f"\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.]]) Second call tensor([[8., 4., 4., 4., 4.], [4., 8., 4., 4., 4.], [4., 4., 8., 4., 4.], [4., 4., 4., 8., 4.]]) 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.]])
同じ引数で backward を 2 回目に呼び出すとき、勾配の値は異なることに注意してください。これは逆伝播を行なうとき、PyTorch が勾配を累積するために発生します、i.e. 計算された勾配の値が計算グラフの総ての葉ノードの grad プロパティに追加されます。正しい勾配を計算することを望む場合、前に grad プロパティをゼロにする必要があります。実生活での訓練では optimizer がこれを行なうことを手助けします。
Note
以前に backward() 関数をパラメータなしで呼び出しました。これは本質的には backward(torch.tensor(1.0)) を呼び出すことと等値です、これはニューラルネットワーク訓練の間の損失のような、スカラー値関数の場合、勾配を計算する有用な方法です。
以上