Décomposons le graphe de calcul et définissons la différenciation d'origine
Je pense qu'il existe diverses situations dans lesquelles le graphe de calcul est coupé, mais je ne sais pas s'il convient ou non à une fonction compliquée, donc cette fois j'ai préparé une fonction qui évalue avec eval comme une chaîne de caractères d'une fonction simple. Faire. Peu importe la simplicité du calcul, PyTorch ne peut pas s'en occuper, le graphique de calcul sera donc coupé.
De plus, si vous le faites, vous devrez définir vous-même la différenciation, mais cette fois la méthode de la différence avant
Le code est compliqué en n'utilisant pas la différenciation automatique, qui est l'une des raisons d'utiliser PyTorch. Dans ce cas, vous n'êtes pas obligé de le faire et la vitesse sera plus lente.
Il n'y a aucun avantage à faire cette fois. Cependant, si vous voulez vraiment couper le graphe de calcul, il y a un avantage irremplaçable que vous pouvez le faire pour le moment. Par contre, si c'est absolument impossible, je pense que ce sera comme repenser le modèle, repenser si vous devez vraiment le faire avec PyTorch, mais comme vous pouvez le faire pour le moment, vous perdrez l'occasion de le repenser. ..
Si vous voulez toujours le faire, veuillez. (Je devais encore le faire)
** Faisons le **
Considérez quelque chose d'insensément simple.
Considérez une telle fonction
#Les deux sont les mêmes, mais f_str est comme ça, PyTorch ne peut pas prendre en charge la différenciation.
#L'entrée est x,On suppose que w et PyTorch sont de type Tensor.
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» est exactement ce que j'ai vu. f_str
est identique à f
, mais il est calculé en le convertissant en une chaîne de caractères et en le réinterprétant en une expression Python avec eval.
Compte tenu de la possibilité que l'entrée «x» arrive dans un lot, dans le cas de «f_str», le contenu est une fois séparé et le tenseur est refait.
f peut être automatiquement différencié
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 est intelligent, vous pouvez donc voir tout ce que vous avez fait avec f
, et w.grad
sera fait automatiquement aprèsy.backward ()
. Je pense que c'est l'une des raisons d'utiliser des frameworks d'apprentissage automatique tels que PyTorch.
Vous vous souvenez peut-être que j'ai rendu f_str
très stupide plus tôt. Dans ce cas, vous ferez face au deuil suivant.
f_str ne peut pas être différencié automatiquement
x = torch.tensor([1.])
w = torch.tensor([1., 1.]).requires_grad_()
f_str(x, w) # => tensor([3.]) grad_Il n'y a pas de fn! !! !! !!
y.backward() # RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn
w.grad # => None
Si je pense qu'il n'y a pas de grad_fn, j'obtiens une erreur quand je recule, et grad n'est pas défini et c'est terrible.
Création de données
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)
Définissez la valeur de manière appropriée sur true w. Ensuite, donnez un nombre aléatoire pour faire une paire de «x» et «f (x, true w)». Ce serait bien de mettre un nombre aléatoire gaussien là-dessus, mais c'est gênant, donc je ne le ferai pas cette fois. C'est exagéré d'utiliser PyTorch. Je pense que scipy.minimize.optimize ou quelque chose comme ça suffit.
Apprenons 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)
Le "model.weight" est finalement "1.1996, -3.3987". Depuis que j'ai défini 1.2, -3.4
comme valeur vraie, c'est presque la même chose. La perte pendant l'entraînement et la perte des données préparées pour la validation (réussie quand toutes sont presque 0)
C'est devenu comme. Ça sonne plutôt bien.
Maintenant, changeons f
en putain de fonction f_str
.
C'est une erreur plutôt déroutante. C'est facile à dire. Puisque le graphe de calcul a été coupé par «f_str» et que la différenciation automatique est devenue impossible, il est moussé par «en arrière». Vous devez donc définir vous-même la différenciation.
La documentation officielle de ces cas peut être trouvée ici [https://pytorch.org/docs/stable/notes/extending.html#extending-torch-autograd). Cependant, lorsque je l'ai essayé, je n'ai pas pu atteindre l'endroit qui me démange, alors je vais le présenter ici et là.
Définir la différenciation par différence directe pour les fonctions générales
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 #En fait, vous pouvez enregistrer quelque chose dans ctx et l'utiliser à l'envers
return ys
@staticmethod
def backward(ctx, grad_output):
xs, ys, weight = ctx.saved_tensors
f = ctx.f
dw = 0.001
diff = []
weight = weight.detach() #Détachez-le pour éviter de laisser un historique de calcul supplémentaire en poids.
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
Créez une classe qui hérite de torch.autograd.Function
, définissez forward
et backward
avec @ staticmethod
, et définissez le premier argument de chacun sur ctx
(un autre nom est également acceptable). Mais c'est une bonne idée de suivre ceci), etc., comme dans la documentation.
La documentation dit que vous pouvez enregistrer le tenseur avec ctx.save_for_backward
, mais cette méthode ne peut enregistrer que torch.Tensor
.
Mais cette fois, je veux passer f_str
comme argument à forward
et l'enregistrer pour backward
.
En fait, cela peut être sauvegardé sous la forme de «ctx. Nachara = ...», qui semble être utilisable en «arrière». Il est également utilisé dans Pytorch, donc je pense que c'est probablement correct de l'utiliser. Je vais.
La valeur renvoyée par «backward» correspond à l'argument de «forward». Renvoie le résultat différentiel de l'argument de forward
moins ctx
.
Si cela correspond à quelque chose qui ne nécessite pas de différenciation (non tenseur ou tenseur qui n'est pas required_grad = True
), vous pouvez renvoyer None. Cette fois, seul «w» a besoin d'être différencié.
Si l'entrée est tenseur $ {\ bf w} = [w_0, w_1, ..., w_ {n-1}] $, la valeur renvoyée est
[\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}}]
Ce sera. Cependant, $ \ sum_i $ dit que si l'entrée x vient dans un mini-lot $ [x_0, x_1, ...] $, les résultats de chacun seront additionnés. La dimension de grad_output
correspond à la taille du mini-lot, donc multipliez le résultat comme ceci.
f_Apprendre un modèle à l'aide de 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):
#C'est un peu ennuyeux à écrire.
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)
Notez que la Fonction
créée est appelée sous la forme de .apply
.
Un graphique similaire au précédent est tellement ressorti que j'ai cru faire une erreur. Les paramètres finaux sont également "1.1996, -3.3987", qui est également presque identique aux vraies valeurs "1.2, -3.4". Eh bien, je n'utilise pas de nombres aléatoires, je fais les mêmes données avec les mêmes valeurs initiales de paramètres, donc c'est probablement le cas. Je ne sais pas.
Au fait, j'ai essayé de dessiner les pertes les unes sur les autres et de faire la différence entre les valeurs de validation prédites.
C'est presque pareil. J'ai utilisé une différenciation bâclée, donc si vous pensez qu'il y aura une légère différence, il semble qu'il n'y ait pas beaucoup de différence. Je suis contente.
J'ai vu comment utiliser PyTorch de manière forcée en définissant vous-même un différentiel pour une fonction étrange qui traverse un graphique de calcul. Je ne veux plus le faire.
Je mettrai ce cahier ici [https://gist.github.com/gyu-don/f5cc025139312ccfd39e48400018118d)