回答:
PyTorchのWebサイトで見つけられなかった元のコード。
gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients)
print(x.grad)
上記のコードの問題には、勾配の計算対象に基づく関数はありません。つまり、パラメーター(関数がとる引数)の数とパラメーターの次元がわかりません。
これを完全に理解するために、オリジナルに近い例を作成しました。
例1:
a = torch.tensor([1.0, 2.0, 3.0], requires_grad = True)
b = torch.tensor([3.0, 4.0, 5.0], requires_grad = True)
c = torch.tensor([6.0, 7.0, 8.0], requires_grad = True)
y=3*a + 2*b*b + torch.log(c)
gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients,retain_graph=True)
print(a.grad) # tensor([3.0000e-01, 3.0000e+00, 3.0000e-04])
print(b.grad) # tensor([1.2000e+00, 1.6000e+01, 2.0000e-03])
print(c.grad) # tensor([1.6667e-02, 1.4286e-01, 1.2500e-05])
私の関数はでy=3*a + 2*b*b + torch.log(c)
あり、パラメータは内部に3つの要素を持つテンソルであると想定しました。
gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
これはアキュムレータだと考えることができます。
聞いたことがあるかもしれませんが、PyTorchのオートグラッドシステム計算はヤコビアン積と同等です。
あなたが私たちがしたような関数がある場合:
y=3*a + 2*b*b + torch.log(c)
ヤコビアンは[3, 4*b, 1/c]
。ただし、このヤコビアンは、PyTorchが特定のポイントで勾配を計算する方法を実行しているわけではありません。
PyTorchは、フォワードパスとバックワードモードの自動微分(AD)を組み合わせて使用します。
象徴的な数学は含まれておらず、数値微分もありません。
数値微分を計算することであろう
δy/δb
ため、b=1
及びb=1+ε
εが小さい場合。
でグラデーションを使用しない場合y.backward()
:
例2
a = torch.tensor(0.1, requires_grad = True)
b = torch.tensor(1.0, requires_grad = True)
c = torch.tensor(0.1, requires_grad = True)
y=3*a + 2*b*b + torch.log(c)
y.backward()
print(a.grad) # tensor(3.)
print(b.grad) # tensor(4.)
print(c.grad) # tensor(10.)
あなたは、単純な、あなたの設定方法に基づいて、ポイントで結果を取得しますa
、b
、c
当初テンソルを。
あなたはどのように初期化するように注意してくださいa
、b
、c
:
例3:
a = torch.empty(1, requires_grad = True, pin_memory=True)
b = torch.empty(1, requires_grad = True, pin_memory=True)
c = torch.empty(1, requires_grad = True, pin_memory=True)
y=3*a + 2*b*b + torch.log(c)
gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients)
print(a.grad) # tensor([3.3003])
print(b.grad) # tensor([0.])
print(c.grad) # tensor([inf])
使用する場合と使用torch.empty()
しないpin_memory=True
場合では、毎回結果が異なる場合があります。
また、勾配はアキュムレータに似ているため、必要に応じて勾配をゼロにします。
例4:
a = torch.tensor(1.0, requires_grad = True)
b = torch.tensor(1.0, requires_grad = True)
c = torch.tensor(1.0, requires_grad = True)
y=3*a + 2*b*b + torch.log(c)
y.backward(retain_graph=True)
y.backward()
print(a.grad) # tensor(6.)
print(b.grad) # tensor(8.)
print(c.grad) # tensor(2.)
最後に、PyTorchが使用する用語に関するいくつかのヒント:
PyTorchは、フォワードパスで勾配を計算するときに動的な計算グラフを作成します。これは木のように見えます。
そのため、このツリーの葉が入力テンソルであり、ルートが出力テンソルであることがよくわかります。
グラデーションは、ルートからリーフまでグラフをトレースし、チェーンルールを使用してすべてのグラデーションを乗算することで計算されます。この乗算は、逆方向パスで発生します。
ニューラルネットワークの場合、通常、ネットワークを使用loss
して、入力画像(または他のタスク)を分類するためにネットワークがどの程度学習したかを評価します。loss
用語は通常、スカラー値です。ネットワークのパラメーターを更新するloss
にleaf node
は、実際には計算グラフにあるパラメーターに対するwrt の勾配を計算する必要があります(ちなみに、これらのパラメーターは、主に、コンボリューション、線形、など)。
チェーンルールに従って、loss
wrtのリーフノードへの勾配を計算するために、loss
ある中間変数wrtの微分、および中間変数wrtのリーフ変数への勾配を計算し、ドット積を計算して、これらすべてを合計できます。
gradient
引数Variable
のbackward()
方法がするために使用されるWRT変数の各要素の加重和を計算葉の変数を。これらの重みはloss
、中間変数の各要素に関する最終的な派生物にすぎません。
これを理解するために具体的で簡単な例を見てみましょう。
from torch.autograd import Variable
import torch
x = Variable(torch.FloatTensor([[1, 2, 3, 4]]), requires_grad=True)
z = 2*x
loss = z.sum(dim=1)
# do backward for first element of z
z.backward(torch.FloatTensor([[1, 0, 0, 0]]), retain_graph=True)
print(x.grad.data)
x.grad.data.zero_() #remove gradient in x.grad, or it will be accumulated
# do backward for second element of z
z.backward(torch.FloatTensor([[0, 1, 0, 0]]), retain_graph=True)
print(x.grad.data)
x.grad.data.zero_()
# do backward for all elements of z, with weight equal to the derivative of
# loss w.r.t z_1, z_2, z_3 and z_4
z.backward(torch.FloatTensor([[1, 1, 1, 1]]), retain_graph=True)
print(x.grad.data)
x.grad.data.zero_()
# or we can directly backprop using loss
loss.backward() # equivalent to loss.backward(torch.FloatTensor([1.0]))
print(x.grad.data)
上記の例では、最初の結果print
は
2 0 0 0
[サイズ1x4のTorch.FloatTensor]
これは、z_1 wrtのxへの微分です。
2番目の結果は次のprint
とおりです。
0 2 0 0
[サイズ1x4のTorch.FloatTensor]
これは、xに対するz_2 wrtの導関数です。
ここで、[1、1、1、1]の重みを使用して、xに対するz wrtの導関数を計算すると、結果はになり1*dz_1/dx + 1*dz_2/dx + 1*dz_3/dx + 1*dz_4/dx
ます。当然のことながら、3rdの出力は次のようになりますprint
。
2 2 2 2
[サイズ1x4のTorch.FloatTensor]
重みベクトル[ loss
1、1、1、1 ]は、wrtのz_1、z_2、z_3、およびz_4への微分です。loss
wrt の微分は次のようにx
計算されます。
d(loss)/dx = d(loss)/dz_1 * dz_1/dx + d(loss)/dz_2 * dz_2/dx + d(loss)/dz_3 * dz_3/dx + d(loss)/dz_4 * dz_4/dx
したがって、4番目の出力はprint
3番目と同じprint
です。
2 2 2 2
[サイズ1x4のTorch.FloatTensor]
gradient
議論をよりよく説明することができたように感じます。ご回答有難うございます。
[1, 1, 1, 1]
の正確誘導体であるloss
にWRT z_1
、z_2
、z_3
及びz_4
。」この発言が答えの鍵を握っていると思います。OPのコードを見るとき、大きな疑問符は勾配のこれらの任意の(魔法の)数値がどこから来るのかを示しています。あなたの具体的な例では、この例では値が任意ではないことがわかるように、たとえば[1, 0, 0 0]
テンソルとloss
関数の関係をすぐに指摘すると非常に役立つと思います。
loss = z.sum(dim=1)
すると、loss = z_1 + z_2 + z_3 + z_4
ます。あなたは、単純な計算を知っていれば、あなたはの派生ことを知っているだろうloss
にWRTがz_1, z_2, z_3, z_4
あります[1, 1, 1, 1]
。
通常、計算グラフには1つのスカラー出力がありますloss
。次に、によってloss
重み(w
)に関する勾配を計算できますloss.backward()
。のデフォルトの引数はbackward()
です1.0
。
出力に複数の値(たとえばloss=[loss1, loss2, loss3]
)がある場合、によって重み付けによる損失の勾配を計算できますloss.backward(torch.FloatTensor([1.0, 1.0, 1.0]))
。
さらに、さまざまな損失に重みまたは重要度を追加する場合は、を使用できますloss.backward(torch.FloatTensor([-0.1, 1.0, 0.0001]))
。
これは-0.1*d(loss1)/dw, d(loss2)/dw, 0.0001*d(loss3)/dw
同時に計算することを意味します。
grad_tensors
それらを別々に重み付けすることではなく、対応するテンソルの各要素に対する勾配だからです。
ここでは、forward()の出力、つまりyは3つのベクトルです。
3つの値は、ネットワークの出力での勾配です。yが最終出力の場合、これらは通常1.0に設定されますが、特にyがより大きなネットワークの一部である場合は、他の値を持つこともできます。
たとえば xが入力の場合、y = [y1、y2、y3]は最終出力zを計算するために使用される中間出力です。
そして、
dz/dx = dz/dy1 * dy1/dx + dz/dy2 * dy2/dx + dz/dy3 * dy3/dx
したがって、ここでは、逆方向の3つの値は
[dz/dy1, dz/dy2, dz/dy3]
そして、backward()はdz / dxを計算します