neural transfer pytorch tutorial を実行していますが、retain_variable
(非推奨、現在はretain_graph
と呼ばれる)の使用について混乱しています。コード例は次のとおりです。
class ContentLoss(nn.Module):
def __init__(self, target, weight):
super(ContentLoss, self).__init__()
self.target = target.detach() * weight
self.weight = weight
self.criterion = nn.MSELoss()
def forward(self, input):
self.loss = self.criterion(input * self.weight, self.target)
self.output = input
return self.output
def backward(self, retain_variables=True):
#Why is retain_variables True??
self.loss.backward(retain_variables=retain_variables)
return self.loss
ドキュメント から
retain_graph(bool、オプション)– Falseの場合、gradの計算に使用されたグラフは解放されます。ほとんどすべての場合、このオプションをTrueに設定する必要はなく、多くの場合、はるかに効率的な方法で回避できることに注意してください。デフォルトはcreate_graphの値です。
したがって、retain_graph= True
を設定することにより、バックワードパスでグラフに割り当てられたメモリを解放しません。このメモリを保持する利点は何ですか、なぜ必要なのですか?
@clerosは_retain_graph=True
_の使用についてかなり重要です。本質的に、特定の変数を計算するために必要な情報を保持するため、その変数を逆方向に渡すことができます。
上記の計算グラフがあるとします。変数d
およびe
は出力であり、a
は入力です。例えば、
_import torch
from torch.autograd import Variable
a = Variable(torch.Rand(1, 4), requires_grad=True)
b = a**2
c = b*2
d = c.mean()
e = c.sum()
_
d.backward()
を実行すると、問題ありません。この計算の後、d
を計算するグラフの部分は、メモリを節約するためにデフォルトで解放されます。したがって、e.backward()
を実行すると、エラーメッセージが表示されます。 e.backward()
を実行するには、_d.backward()
のパラメーター_retain_graph
_をTrue
に設定する必要があります。
_d.backward(retain_graph=True)
_
後方メソッドで_retain_graph=True
_を使用している限り、いつでも後方に実行できます。
_d.backward(retain_graph=True) # fine
e.backward(retain_graph=True) # fine
d.backward() # also fine
e.backward() # error will occur!
_
より有用な議論は here にあります。
現在、実際のユースケースはマルチタスク学習であり、異なる層にある可能性のある複数の損失があります。 _loss1
_と_loss2
_の2つの損失があり、それらが異なるレイヤーにあるとします。 _loss1
_と_loss2
_ w.r.tの勾配を、ネットワークの学習可能な重みに個別にバックプロップするため。最初の逆伝播損失では、backward()
メソッドで_retain_graph=True
_を使用する必要があります。
_# suppose you first back-propagate loss1, then loss2 (you can also do the reverse)
loss1.backward(retain_graph=True)
loss2.backward() # now the graph is freed, and next process of batch gradient descent is ready
optimizer.step() # update the network parameters
_
これは、ネットワークの出力が複数ある場合に非常に便利な機能です。完全に構成された例を次に示します。入力画像に猫が含まれているか、画像に車が含まれているかという2つの質問をすることができる、ランダムな畳み込みネットワークを構築するとします。
これを行う1つの方法は、畳み込み層を共有するネットワークを持つことですが、それには2つの並行する分類層があります(私のひどいASCIIグラフを許してください。ただし、これは3つのconvlayer猫用と車用の3つの完全に接続されたレイヤー):
_ -- FC - FC - FC - cat?
Conv - Conv - Conv -|
-- FC - FC - FC - car?
_
ネットワークをトレーニングするときに、両方のブランチを実行したいという状況を考えて、いくつかの方法でそれを行うことができます。最初に(おそらくここが最良の例で、例がどれほど悪いかを説明します)、両方の評価で損失を計算し、損失を合計してから、逆伝搬します。
ただし、別のシナリオがあります-これを順番に実行したい場合です。最初に1つのブランチを介してバックプロップし、次に他のブランチを介してバックプロップします(以前にこのユースケースを使用したことがあるため、完全には構成されていません)。その場合、1つのグラフで.backward()
を実行すると、畳み込み層の勾配情報も破壊され、2番目のブランチの畳み込み計算(これらは他のブランチと共有される唯一のものであるため)には含まれませんもうグラフ!つまり、2番目のブランチを逆方向に戻そうとすると、Pytorchは入力を出力に接続するグラフを見つけることができないため、エラーをスローします。これらの場合、最初の逆方向パスでグラフを保持するだけで問題を解決できます。グラフは消費されず、保持する必要のない最初の逆方向パスでのみ消費されます。
編集:すべての逆方向パスでグラフを保持する場合、出力変数に付加された暗黙的なグラフ定義は決して解放されません。ここにもユースケースがあるかもしれませんが、私はそれを考えることはできません。したがって、一般的には、グラフ情報を保持しないことにより、最後の逆方向パスでメモリが解放されるようにする必要があります。
複数のバックワードパスで何が起こるかについて:ご想像のとおり、pytorchは(変数/パラメーター_.grad
_プロパティに)インプレースで勾配を追加することで勾配を蓄積します。これは、バッチをループして一度に1回処理し、最後に勾配を蓄積することで、完全なバッチ更新を行うのと同じ最適化手順を実行することを意味するため、非常に便利です。まあ)。完全にバッチ処理された更新はより並列化できるため、一般的に望ましいのですが、バッチ処理が非常に、非常に実装が困難であるか、単に不可能な場合があります。ただし、この蓄積を使用すると、バッチ処理によってもたらされるナイス安定化プロパティのいくつかに依拠することができます。 (パフォーマンスが向上しない場合)