Pytorch、勾配の引数は何ですか


112

私はPyTorchのドキュメントを読んでいて、彼らが書く例を見つけました

gradients = torch.FloatTensor([0.1, 1.0, 0.0001])
y.backward(gradients)
print(x.grad)

ここで、xは初期変数であり、そこからyが作成されました(3ベクトル)。問題は、勾配テンソルの0.1、1.0、0.0001引数は何ですか?ドキュメントについては、あまり明確ではありません。

回答:


15

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.)

あなたは、単純な、あなたの設定方法に基づいて、ポイントで結果を取得しますabc当初テンソルを。

あなたはどのように初期化するように注意してくださいabc

例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は、フォワードパスで勾配を計算するときに動的な計算グラフを作成します。これは木のように見えます。

そのため、このツリーの入力テンソルであり、ルート出力テンソルであることがよくわかります。

グラデーションは、ルートからリーフまでグラフをトレースし、チェーンルールを使用してすべてのグラデーションを乗算することで計算されます。この乗算は、逆方向パスで発生します。


正解です。しかし、Pytorchが数値微分を行うとは思いません(「前の関数では、PyTorchはεが小さいb = 1およびb = 1 +εの場合、たとえばδy/δbを実行します。したがって、シンボリック数学のようなものは何もありません。 ")-私はそれが自動微分を行うと信じています。
max_max_mir

はい、ADまたは自動微分を使用します。後でこのPDFのようにADをさらに調査しましたが、この回答を設定したとき、私は完全には知らされていませんでした。
prosti

たとえば、例2ではRuntimeError:Mismatch in shape:grad_output [0]の形状はtorch.Size([3])で、output [0]の形状はtorch.Size([])です。
Andreas K.

@AndreasK。、あなたは正しかった、PyTorchは最近ゼロサイズのテンソルを導入し、これは以前の例に影響を与えた。これらの例は重要ではなかったため、削除されました。
prosti

100

説明

ニューラルネットワークの場合、通常、ネットワークを使用lossして、入力画像(または他のタスク)を分類するためにネットワークがどの程度学習したかを評価します。loss用語は通常、スカラー値です。ネットワークのパラメーターを更新するlossleaf nodeは、実際には計算グラフにあるパラメーターに対するwrt の勾配を計算する必要があります(ちなみに、これらのパラメーターは、主に、コンボリューション、線形、など)。

チェーンルールに従って、losswrtのリーフノードへの勾配を計算するために、lossある中間変数wrtの微分、および中間変数wrtのリーフ変数への勾配を計算し、ドット積を計算して、これらすべてを合計できます。

gradient引数Variablebackward()方法がするために使用される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]

重みベクトル[ loss1、1、1、1 ]は、wrtのz_1、z_2、z_3、およびz_4への微分です。losswrt の微分は次のように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番目の出力はprint3番目と同じprintです。

2 2 2 2
[サイズ1x4のTorch.FloatTensor]


1
間違いなのですが、損失またはzの勾配のx.grad.dataを計算するのはなぜですか
Priyank Pathak

7
多分私は何かを逃したかもしれません、しかし私は公式のドキュメンテーションが本当にgradient議論をよりよく説明することができたように感じます。ご回答有難うございます。
主人公

3
@jdhao なお、ウエイトベクトルがことに留意すべきである」[1, 1, 1, 1]の正確誘導体であるlossにWRT z_1z_2z_3及びz_4。」この発言が答えの鍵を握っていると思います。OPのコードを見るとき、大きな疑問符は勾配のこれらの任意の(魔法の)数値がどこから来るのかを示しています。あなたの具体的な例では、この例では値が任意ではないことがわかるように、たとえば[1, 0, 0 0]テンソルとloss関数の関係をすぐに指摘すると非常に役立つと思います。
a_guest 2018

1
@smwikipedia、それは真実ではありません。拡大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]
jdhao

1
わたしは、あなたを愛しています。私の疑問を解決しました!
ブラックジャック21

45

通常、計算グラフには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同時に計算することを意味します。


1
「異なる損失に重みまたは重要度を追加する場合は、loss.backward(torch.FloatTensor([-0.1、1.0、0.0001]))を使用できます。」->これは真実ですが、誤解を招く可能性があります。パスする主な理由は、grad_tensorsそれらを別々に重み付けすることではなく、対応するテンソルの各要素に対する勾配だからです。
Aerin

27

ここでは、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を計算します


5
答えてくれてありがとうございますが、これは実際にはどのように役立ちますか?つまり、バックプロップをハードコーディングする以外に[dz / dy1、dz / dy2、dz / dy3]が必要なのはどこですか?
hi15

指定された勾配引数は、ネットワークの後半で計算された勾配であると言って間違いありませんか?
Khanetor 2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.