当前位置:网站首页>Jouer avec la classe de fonctions de pytorch

Jouer avec la classe de fonctions de pytorch

2022-06-10 17:46:00 Wu Lele


Préface

 pytorchOffreautogradMécanisme de dérivation automatique,EtautogradLa réalisation de la dérivation automatique passe essentiellement parFunctionClasse implémentée.Et les partenaires qui ont l'habitude de construire des blocs n'écrivent pasbackward.Créer une situation où vous avez besoin d'étendre l'opérateur et vous serez submergé.Cet article commence par des exemples simples,Apprendre à réaliser unFunctionLes éléments les plus fondamentaux de la classe,Il y a aussi quelques considérations,Enfin, je combine un vrai combat pour apprendreFunctionUtilisation de la classe.

1、y=w*x+b

import torch
from torch.autograd import Function

# y = w*x + b Une propagation vers l'avant et une dérivation inverse de
class Mul(Function):
    @staticmethod
    def forward(ctx, w, x, b, x_requires_grad = True): # ctx Peut être compris comme l'ancêtre. , Variable de cache intermédiaire utilisée pour stocker les gradients .
        ctx.save_for_backward(w,b)     # Parce quedy/dx = w; dy/dw = x ; dy/db = 1; Les variables intermédiaires doivent être sauvegardées pour une Rétropropagation ultérieure w,x
        output = w*x + b
        return output
    @staticmethod
    def backward(ctx,grad_outputs):    # Ici.grad_outputs Analyse spécifique des problèmes spécifiques
        w = ctx.saved_tensors[0]      # Enlevez - le.ctxSauvegardé dans w = 2
        b = ctx.saved_tensors[1]      # Enlevez - le.ctxSauvegardé dans b = 3
        grad_w = grad_outputs * x     # 1 * 1 = 1
        grad_x = grad_outputs * w     # 1 * 2 = 2
        grad_b = grad_outputs * 1     # 1 * 1 = 1
        return grad_w, grad_x, grad_b, None  #  Paramètres retournés et forwardLes paramètres correspondent un par un,Pour les paramètresx_requires_grad Retour direct sans gradient None.

if __name__ == '__main__':
    x = torch.tensor(1.,requires_grad=True)
    w = torch.tensor(2.,requires_grad=True)
    b = torch.tensor(3., requires_grad=True)
    y = Mul.apply(w,x,b)              # y = w*x + b = 2*1 + 3 = 5
    print('forward:', y)
    # Écrire un
    loss = y.sum()                    #  Convertir en scalaire 
    loss.backward()                   # Rétropropagation:Parce que loss = sum(y),Donc...grad_outputs = dloss/dy = 1,Peut être omis
    print(' Gradient d'écriture 1 :',x.grad, w.grad, b.grad)      # tensor(2.) tensor(1.) tensor(1.)

 Pour résumer: Commentaires dans le Code .Parmi euxy=w*x+b. La propagation vers l'avant est facile à comprendre . Ce qui est déroutant ici, c'est que ctxCe truc., En fait, c'est un ancêtre. ,Par la méthodesave_for_backward() Pour enregistrer les variables de cache intermédiaires propagées vers l'avant , Conditions de Rétropropagation ultérieure . Et en Rétropropagation, ,D'abord à partir dectx Méthode d'appel saved_tensors[]Viens.w,b. Gradient des paramètres suivants :dy/dx = w; dy/dw = x; dy/db = 1.
 En plus,En Rétropropagation, La confusion est le paramètre grad_outputs. En fait, la valeur de ce paramètre est liée à la fin de l'appel de classe .Dans le Code,Utiliserloss.backward(), Vous pouvez voir que le paramètre entrant est vide . C'est parce que la propagation vers l'avant yAprès,loss = y.sum(),C'est - à - dire:grad_outputs = dloss/dy = 1; EttorchMoyenne,Peut être omis. Donc ici grad_outputs=1.
 Bien sûr., On peut aussi y participer explicitement. .

    # Écrire un
    loss1 = y.sum()                    #  Convertir en scalaire 
    loss1.backward()                   # Rétropropagation:Parce que loss = sum(y),Donc...grad_outputs = dloss/dy = 1,Peut être omis
    print(' Gradient d'écriture 1 :',x.grad, w.grad, b.grad)      # tensor(2.) tensor(1.) tensor(1.)
    # écriture II
    loss2 = y.sum()
    loss2.backward(torch.tensor(1.))
    print(' Gradient d'écriture II :',x.grad, w.grad, b.grad)      # tensor(4.) tensor(2.) tensor(2.)

  Mais c'est une erreur. ,Les informations d'erreur sont les suivantes::

RuntimeError: Trying to backward through the graph a second time, but the saved intermediate results have already been freed. Specify retain_graph=True when calling backward the first time.

  En gros, le même graphique ne peut pas être inversé deux fois. .Parce que, La première fois que vous appelez backwardAprès, Les calculs sont détruits. . Donc vous devez définir les paramètres retain_graph Diagramme de calcul de l'enregistrement des paramètres ,Le Code modifié est le suivant::

    # Écrire un
    loss1 = y.sum()                    #  Convertir en scalaire 
    loss1.backward(retain_graph = True)                   # Rétropropagation:Parce que loss = sum(y),Donc...grad_outputs = dloss/dy = 1,Peut être omis
    print(' Gradient d'écriture 1 :',x.grad, w.grad, b.grad)      # tensor(2.) tensor(1.) tensor(1.)
    # écriture II
    loss2 = y.sum()
    loss2.backward(torch.tensor(1.))
    print(' Gradient d'écriture II :',x.grad, w.grad, b.grad)      # tensor(4.) tensor(2.) tensor(2.)

 Malheureusement,, À ce stade, les résultats du calcul du gradient entre la méthode d'écriture 2 et la méthode d'écriture 1 ne sont pas cohérents. , On trouve que le gradient d'écriture 2 est deux fois plus élevé que celui d'écriture 1. .C'est parce quepytorch Deux fois différentes loss Au noeud foliaire lors de l'inversion du gradient Les gradients sont cumulatifs.Donc,, Nous devons éliminer le gradient de perte 1 entre la propagation de la perte 2 0.Les codes sont les suivants::

    # Écrire un
    loss1 = y.sum()                    #  Convertir en scalaire 
    loss1.backward(retain_graph = True)                   # Rétropropagation:Parce que loss = sum(y),Donc...grad_outputs = dloss/dy = 1,Peut être omis
    print(' Gradient d'écriture 1 :',x.grad, w.grad, b.grad)      # tensor(2.) tensor(1.) tensor(1.)
    #  Dégagement du gradient du noeud foliaire 0
    x.grad.zero_()
    w.grad.zero_()
    b.grad.zero_()
    # écriture II
    loss2 = y.sum()
    loss2.backward(torch.tensor(1.))
    print(' Gradient d'écriture II :',x.grad, w.grad, b.grad)      # tensor(2.) tensor(1.) tensor(1.)

 OK,Un grand succès.Le code complet est le suivant:

import torch
from torch.autograd import Function

# y = w*x + b Une propagation vers l'avant et une dérivation inverse de
class Mul(Function):
    @staticmethod
    def forward(ctx, w, x, b, x_requires_grad = True): # ctx Peut être compris comme l'ancêtre. , Variable de cache intermédiaire utilisée pour stocker les gradients .
        ctx.save_for_backward(w,b)     # Parce quedy/dx = w; dy/dw = x ; dy/db = 1; Les variables intermédiaires doivent être sauvegardées pour une Rétropropagation ultérieure w,x
        output = w*x + b
        return output
    @staticmethod
    def backward(ctx,grad_outputs):    # Ici.grad_outputs Analyse spécifique des problèmes spécifiques
        w = ctx.saved_tensors[0]      # Enlevez - le.ctxSauvegardé dans w = 2
        b = ctx.saved_tensors[1]      # Enlevez - le.ctxSauvegardé dans b = 3
        grad_w = grad_outputs * x     # 1 * 1 = 1
        grad_x = grad_outputs * w     # 1 * 2 = 2
        grad_b = grad_outputs * 1     # 1 * 1 = 1
        return grad_w, grad_x, grad_b, None  #  Paramètres retournés et forwardLes paramètres correspondent un par un,Pour les paramètresx_requires_grad Retour direct sans gradient None.

if __name__ == '__main__':
    x = torch.tensor(1.,requires_grad=True)
    w = torch.tensor(2.,requires_grad=True)
    b = torch.tensor(3., requires_grad=True)
    y = Mul.apply(w,x,b)              # y = w*x + b = 2*1 + 3 = 5
    print('forward:', y)
    # Écrire un
    loss1 = y.sum()                    #  Convertir en scalaire 
    loss1.backward(retain_graph = True)                   # Rétropropagation:Parce que loss = sum(y),Donc...grad_outputs = dloss/dy = 1,Peut être omis
    print(' Gradient d'écriture 1 :',x.grad, w.grad, b.grad)      # tensor(2.) tensor(1.) tensor(1.)
    #  Dégagement du gradient du noeud foliaire 0
    x.grad.zero_()
    w.grad.zero_()
    b.grad.zero_()
    # écriture II
    loss2 = y.sum()
    loss2.backward(torch.tensor(1.))
    print(' Gradient d'écriture II :',x.grad, w.grad, b.grad)      # tensor(4.) tensor(2.) tensor(2.)

2、Niveau avancé:y=exp(x)*2

import torch
from torch.autograd import Function

class Exp(Function):
    @staticmethod
    def forward(ctx,x):
        output = x.exp()
        ctx.save_for_backward(output) # dy/dx = exp(x)
        return output
    @staticmethod
    def backward(ctx, grad_outputs):  # dloss/dx = grad_outputs* exp(x)
        output = ctx.saved_tensors[0]
        return output*grad_outputs

if __name__ == '__main__':
    x = torch.tensor(1.,requires_grad=True)
    y = Exp.apply(x)
    print(y)
    y = y * 2
    loss = y.sum()
    loss.backward()
    print(x.grad)

  La seule chose à noter est :dloss/dy = 1 * 2 = 2;Parce queloss = sum(2y).

3、Sur le terrain:GuideReLUFonctions

  ReLUFonctions:y=max(x,0), Uniquement lorsque le gradient est inversé x>0 C'est là qu'il y a un gradient. , Et la valeur du gradient est 1.Parce quey=x,Alors...dy/dx=1;EtGuideReLUOui.ReLUBase,Non seulementx>0 La position peut inverser le gradient , Le Gradient doit également être satisfait >0 La position peut inverser le gradient .dloss/dx = dloss/dy * (x>0) * (grad_output>0).Les codes sont les suivants::

import torch
from torch.autograd import Function

class GuideReLU(Function):
    @staticmethod
    def forward(ctx,input):
        output = torch.clamp(input,min=0)
        ctx.save_for_backward(output)
        return output
    @staticmethod
    def backward(ctx, grad_outputs):  # dloss/dx = dloss/dy * !(x > 0) * (dloss/dy > 0)
        output = ctx.saved_tensors[0]  # dloss/dy
        return grad_outputs * (output>0).float()* (grad_outputs>0).float()

if __name__ == '__main__':
    x = torch.randn(2,3,requires_grad=True)
    print('input:',x)
    y = GuideReLU.apply(x)
    print('forward:',y)
    grad_y = torch.randn(2,3)
    y.backward(grad_y)               #  Une valeur de gradient est reçue ici ,C'est - à - dire:grad_outputs
    print('grad_y:',grad_y)          #  C'est - à - dire seulement si vous entrez x Et le gradient de retour grad_yEn même temps>0 La position a une valeur de gradient .
    print('grad_x:',x.grad)

4、Contrôle du gradient:torch.autograd.gradcheck()

 pytorch Fournit un contrôle de gradient api, Il est facile de vérifier la propagation correcte de votre écriture .

import torch
from torch.autograd import Function
class Sigmoid(Function):

    @staticmethod
    def forward(ctx, x):
        output = 1 / (1 + torch.exp(-x))
        ctx.save_for_backward(output)
        return output

    @staticmethod
    def backward(ctx, grad_output):
        output, = ctx.saved_tensors
        grad_x = output * (1 - output) * grad_output
        return grad_x


test_input = torch.randn(4, requires_grad=True)  # tensor([-0.4646, -0.4403, 1.2525, -0.5953], requires_grad=True)
print(torch.autograd.gradcheck(Sigmoid.apply, (test_input,), eps=1e-3))

Résumé

  Ce chapitre est une introduction pytorch Le premier chapitre de la conduction inverse , Présentation et développement des réunions de suivi C++/CUDAOpérateur. Si vous avez des questions, bienvenue. +vx:wulele2541612007, Vous amener dans un groupe pour discuter de la communication .

原网站

版权声明
本文为[Wu Lele]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/161/202206101653417562.html