Lassen Sie uns das Berechnungsdiagramm aufbrechen und die ursprüngliche Differenzierung definieren
Ich denke, dass es verschiedene Situationen gibt, in denen das Berechnungsdiagramm abgeschnitten ist, aber ich weiß nicht, ob es übereinstimmt oder nicht, wenn es sich um eine komplizierte Funktion handelt. Deshalb habe ich dieses Mal eine Funktion vorbereitet, die mit eval ausgewertet wird, indem eine einfache Funktion in eine Zeichenfolge konvertiert wird. Machen. Unabhängig davon, wie einfach die Berechnung ist, kann PyTorch sich nicht darum kümmern, sodass das Berechnungsdiagramm abgeschnitten wird.
Wenn Sie dies tun, müssen Sie die Differenzierung selbst definieren, diesmal jedoch die Vorwärtsdifferenzmethode
Code wird dadurch kompliziert, dass keine automatische Differenzierung verwendet wird. Dies ist einer der Gründe für die Verwendung von PyTorch. In diesem Fall müssen Sie dies nicht tun und die Geschwindigkeit wird langsamer.
Diesmal ist es kein Vorteil. Wenn Sie das Berechnungsdiagramm jedoch wirklich ausschneiden möchten, gibt es einen unersetzlichen Vorteil, dass Sie dies vorerst tun können. Auf der anderen Seite, wenn es absolut unmöglich ist, denke ich, dass es so sein wird, als würde man das Modell überdenken und überlegen, ob man es wirklich mit PyTorch machen muss, aber da man es vorerst tun kann, wird man die Gelegenheit verlieren, es zu überdenken. ..
Wenn Sie es trotzdem tun möchten, bitte. (Ich musste es noch tun)
** Machen wir das **
Betrachten Sie etwas wahnsinnig Einfaches.
Betrachten Sie eine solche Funktion
#Beide sind gleich, aber f_str ist so, PyTorch kann sich nicht um Differenzierung kümmern.
#Eingabe ist x,Es wird angenommen, dass sowohl w als auch PyTorch vom Tensortyp sind.
def f(x, w):
return 2 * x * w[0] + x**2 * w[1]
def f_str(x, w):
return torch.tensor([eval(f'2 * {x_} * {w[0]} + {x_}**2 * {w[1]}') for x_ in x])
"f" ist genau das, was ich gesehen habe. f_str
ist dasselbe wie f
, wird jedoch berechnet, indem es in eine Zeichenkette konvertiert und mit eval in einen Python-Ausdruck neu interpretiert wird.
In Anbetracht der Möglichkeit, dass die Eingabe "x" in einem Stapel eingeht, wird im Fall von "f_str" der Inhalt einmal getrennt und der Tensor neu erstellt.
f kann automatisch unterschieden werden
x = torch.tensor([1.])
w = torch.tensor([1., 1.]).requires_grad_()
f(x, w) # => tensor([3.], grad_fn=<AddBackward0>)
y.backward()
w.grad # => tensor([2., 1.])
PyTorch ist intelligent, sodass Sie alles sehen können, was Sie mit f
gemacht haben, und w.grad
wird automatisch nachy.backward ()
ausgeführt. Ich denke, dies ist einer der Gründe für die Verwendung von Frameworks für maschinelles Lernen wie PyTorch.
Sie erinnern sich vielleicht, dass ich f_str
früher sehr dumm gemacht habe. In diesem Fall werden Sie der folgenden Trauer gegenüberstehen.
f_str kann nicht automatisch unterschieden werden
x = torch.tensor([1.])
w = torch.tensor([1., 1.]).requires_grad_()
f_str(x, w) # => tensor([3.]) grad_Es gibt kein fn! !! !! !!
y.backward() # RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn
w.grad # => None
Wenn ich denke, dass es kein grad_fn gibt, erhalte ich einen Fehler, wenn ich rückwärts gehe, und grad ist nicht gesetzt und es ist schrecklich.
Datenerstellung
actual_w = 1.2, -3.4
xs = np.random.rand(200).astype(np.float32)
ys = np.array([f(x, actual_w) for x in xs], dtype=np.float32)
train_d = torch.utils.data.TensorDataset(torch.from_numpy(xs), torch.from_numpy(ys))
train_loader = torch.utils.data.DataLoader(train_d, batch_size=10)
v_xs = np.random.rand(10).astype(np.float32)
v_ys = np.array([f(x, actual_w) for x in v_xs], dtype=np.float32)
valid_d = torch.utils.data.TensorDataset(torch.from_numpy(v_xs), torch.from_numpy(v_ys))
valid_loader = torch.utils.data.DataLoader(valid_d, batch_size=1)
Stellen Sie den Wert entsprechend auf true ein. W. Geben Sie dann eine Zufallszahl an, um ein Paar aus "x" und "f (x, wahres w)" zu bilden. Es wäre schön, eine Gaußsche Zufallszahl darauf zu setzen, aber es ist mühsam, also werde ich das diesmal nicht tun. Dies ist ein Overkill für die Verwendung von PyTorch. Ich denke, dass scipy.minimize.optimize oder so etwas ausreicht.
Lass uns lernen f
class Model(torch.nn.Module):
def __init__(self):
super().__init__()
self.weight = torch.nn.parameter.Parameter(torch.tensor([0., 0.]))
def forward(self, x):
return f(x, self.weight)
model = Model()
optimizer = torch.optim.Adam(model.parameters(), lr=0.1)
criterion = torch.nn.MSELoss()
loss_hist = []
model.train()
for epoch in range(20):
for i, (xs, l) in enumerate(train_loader):
out = model(xs)
loss = criterion(out, l)
loss_hist.append(loss)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(epoch, loss, model.weight)
Das model.weight
ist schließlich 1.1996, -3.3987
. Da ich "1.2, -3.4" als wahren Wert eingestellt habe, ist es fast dasselbe. Der Verlust während des Trainings und der Verlust der zur Validierung vorbereiteten Daten (erfolgreich, wenn alle fast 0 sind)
Es wurde wie. Hört sich ziemlich gut an.
Jetzt ändern wir f
in die verdammte Funktion f_str
.
Es ist ein ziemlich verwirrender Fehler. Es ist leicht zu sagen. Da der Berechnungsgraph durch "f_str" abgeschnitten wurde und eine automatische Differenzierung unmöglich wurde, ist er Moos durch "rückwärts". Sie müssen die Differenzierung also selbst definieren.
Die offizielle Dokumentation zu diesen Fällen finden Sie hier [https://pytorch.org/docs/stable/notes/extending.html#extending-torch-autograd]. Als ich es jedoch tatsächlich ausprobierte, konnte ich den juckenden Ort nicht erreichen, daher werde ich es hier und da vorstellen.
Definieren Sie die Differenzierung durch Vorwärtsdifferenz für allgemeine Funktionen
class GeneralFunctionWithForwardDifference(torch.autograd.Function):
@staticmethod
def forward(ctx, f, xs, weight):
ys = f(xs, weight)
ctx.save_for_backward(xs, ys, weight)
ctx.f = f #Tatsächlich können Sie etwas in ctx speichern und rückwärts verwenden
return ys
@staticmethod
def backward(ctx, grad_output):
xs, ys, weight = ctx.saved_tensors
f = ctx.f
dw = 0.001
diff = []
weight = weight.detach() #Abnehmen, um zu vermeiden, dass zusätzliche Berechnungshistorien im Gewicht verbleiben.
for i in range(len(weight)):
weight[i] += dw
diff.append(torch.sum(grad_output * (f(xs, weight) - ys)))
weight[i] -= dw
diff = torch.tensor(diff) / dw
return None, None, diff
Erstellen Sie eine Klasse, die torch.autograd.Function
erbt, definieren Sie forward
und backward
mit @ staticmethod
und setzen Sie das erste Argument von jeder auf ctx
(ein anderer Name ist ebenfalls akzeptabel). Es ist jedoch eine gute Idee, dies usw. wie in der Dokumentation zu befolgen.
Die Dokumentation besagt, dass Sie den Tensor mit ctx.save_for_backward
speichern können, aber diese Methode speichert nur torch.Tensor
.
Aber dieses Mal möchte ich "f_str" als Argument für "vorwärts" übergeben und es für "rückwärts" speichern. Tatsächlich kann dies in Form von "ctx. Nachara = ..." gespeichert werden, was in "rückwärts" verwendbar zu sein scheint. Es wird auch in Pytorch verwendet, daher denke ich, dass es wahrscheinlich in Ordnung ist, es zu verwenden. Ich werde.
Der von "rückwärts" zurückgegebene Wert entspricht dem Argument "vorwärts". Gibt das Differentialergebnis des Arguments von "forward" minus "ctx" zurück.
Wenn es etwas entspricht, das keine Differenzierung erfordert (Nicht-Tensor oder Tensor, der nicht "required_grad = True" ist), können Sie None zurückgeben. Dieses Mal muss nur "w" differenziert werden.
Wenn die Eingabe Tensor $ {\ bf w} = [w_0, w_1, ..., w_ {n-1}] $ ist, ist der zurückgegebene Wert
[\sum_i\mathrm{grad\_output}_i\frac{\partial f(x_i, {\bf w})}{\partial w_0}, \sum_i \mathrm{grad\_output}_i\frac{\partial f(x_i, {\bf w})}{\partial w_1}, ... \sum_i\mathrm{grad\_output}_i\frac{\partial f(x_i, {\bf w})}{\partial w_{n-1}}]
Es wird sein. $ \ Sum_i $ sagt jedoch, dass, wenn die Eingabe x in einem Mini-Batch $ [x_0, x_1, ...] $ kommt, die Ergebnisse von jedem addiert werden. Die Dimension von "grad_output" entspricht der Größe des Mini-Batches. Multiplizieren Sie das Ergebnis also wie folgt.
f_Lernen eines Modells mit str
class Model2(torch.nn.Module):
def __init__(self):
super().__init__()
self.weight = torch.nn.parameter.Parameter(torch.tensor([0., 0.]))
def forward(self, x):
#Es ist etwas nervig zu schreiben.
return GeneralFunctionWithForwardDifference.apply(f_str, x, self.weight)
model2 = Model2()
optimizer = torch.optim.Adam(model2.parameters(), lr=0.1)
criterion = torch.nn.MSELoss()
loss_hist2 = []
model2.train()
for epoch in range(20):
for i, (xs, l) in enumerate(train_loader):
out = model2(xs)
loss = criterion(out, l)
loss_hist2.append(loss)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(epoch, loss, model2.weight)
Beachten Sie, dass die erstellte Funktion
in Form von .apply
aufgerufen wird.
Eine Grafik ähnlich der vorherigen kam so oft heraus, dass ich dachte, ich hätte einen Fehler gemacht. Die endgültigen Parameter sind ebenfalls "1.1996, -3.3987", was ebenfalls fast den wahren Werten "1.2, -3.4" entspricht. Nun, ich verwende keine Zufallszahlen, ich mache die gleichen Daten mit den gleichen Parameteranfangswerten, also ist das wahrscheinlich der Fall. Ich weiß es nicht.
Übrigens habe ich versucht, den Verlust übereinander zu ziehen und die Differenz zwischen den vorhergesagten Validierungswerten zu ziehen.
Es ist fast das gleiche. Ich habe eine schlampige Differenzierung verwendet. Wenn Sie also glauben, dass es einen kleinen Unterschied geben wird, scheint es keinen großen Unterschied zu geben. Ich bin froh.
Ich habe gesehen, wie man PyTorch zwangsweise verwendet, indem man selbst ein Differential für eine seltsame Funktion definiert, die ein Berechnungsdiagramm durchschneidet. Ich will es nicht mehr machen.
Ich werde dieses Notizbuch hier ablegen [https://gist.github.com/gyu-don/f5cc025139312ccfd39e48400018118d)