Autograd
Dans ce chapitre, nous nous plaçons dans la configuration où tous les tenseurs sont stockés en mémoire RAM. Des informations spécifiques au GPU sont disponibles en fin de page.
Terminologie
Pour indiquer à PyTorch qu’un tenseur contient des valeurs dont le gradient doit être calculé, il est nécessaire de spécifier le paramètre requires_grad=True lors de sa création.
Ainsi, dans PyTorch, on rencontre deux types de tenseurs :
Les tenseurs sans gradient, correspondant par exemple, aux images en entrée
Les tenseurs créés avec l’option requires_grad=True, comme les poids du réseau et les tenseurs intermédiaires impliqués dans le calcul de la Loss
Graphe de calcul
La librairie Autograd interne à PyTorch est capable de construire un graphe de calcul utilisé pour calculer les gradients des tenseurs dont le paramètre requires_grad est actif.
Prenons un exemple :
Voici une manière de coder cette expression étape par étape :
I = torch.tensor([5,7])
W = torch.tensor([3.,11.],requires_grad=True)
a = I[0] * W[0]
b = 8 * W[1]
c = a + b
Loss = c + I[1]
Grâce aux informations suivantes :
print(Loss.grad_fn) # AddBackward0
print(c.grad_fn) # AddBackward0
print(b.grad_fn) # MulBackward0
print(a.grad_fn) # MulBackward0
On peut déduire le graphe de calcul généré par Autograd :
I[0] ────────┐
├─ (*) ─> a = I[0]·W[0] ──┐
W[0] ────────┘ │
├─ (+) ─> c = a + b ──┐
W[1] ────────┐ │ │
├─ (*) ─> b = 8·W[1] ─────┘ ├─ (+) ─> Loss
8 ──────────┘ │
│
I[1] ────────────────────────────────────────────────────────┘
Ainsi, la librairie Autograd conserve pour chaque noeud les informations nécessaires à la rétropropagation.
Expression complexe
La librairie Autograd est suffisamment puissante pour générer automatiquement un graphe à partir d’une expression complexe. Pour exhiber la structure du graphe sous-jacent, nous utilisons une fonction récursive traverse() :
import torch
I = torch.tensor([5,7])
W = torch.tensor([3.,11.],requires_grad=True)
Loss = I[0]*W[0] + W[1]*8 + I[1]
def traverse(fn, depth=0):
if fn is None : return
print(" " * depth + type(fn).__name__)
for parent, _ in fn.next_functions:
traverse(parent, depth + 1)
traverse(Loss.grad_fn)
Ainsi, nous pouvons examiner le graphe généré automatiquement par Autograd :
AddBackward0 # Loss = c + 7
AddBackward0 # I[0]·W[0] + 8·W[1]
MulBackward0 # I[0]·W[0]
SelectBackward0 # W[0]
AccumulateGrad # gradient à accumuler dans W.grad
MulBackward0 # 8 * W[1]
SelectBackward0 # W[1]
AccumulateGrad # gradient à accumuler dans W.grad
Gradient
L’appel de Loss.backward() déclenche le mécanisme de rétropropagation. Après cela, il est possible de connaître le gradient de W en lisant l’attribut W.grad :
Loss.backward()
print(W.grad)
>> tensor([5., 8.])
Pour rappel, la fonction Loss s’exprime ainsi :
Ce qui après application des formules de dérivation donne une réponse identique à la rétropopagation :
Récupérer des résultats
Si un tenseur participe à l’autograd, on ne peut le transférer directement vers Python sans déclencher une erreur. Pour cela, il faut utiliser la fonction item lorsqu’une seule valeur est présente ou alors la fonction detach :
Contexte |
Action |
Commentaire |
|---|---|---|
Valeur unique |
r = t.item() |
Utile pour récupérer la Loss |
CPU => Numpy |
r = t.detach().numpy() |
Uniquement si accès en lecture seule |
CPU => Numpy |
r = t.detach().numpy().copy() |
Isolation totale |
GPU => Numpy |
r = t.detach().cpu().numpy() |
Lecture seule |
GPU => Numpy |
r = t.detach().cpu().numpy().copy() |
Isolation totale |
PyTorch demande d’utiliser la fonction detach() pour retourner un tenseur-vue déconnecté d’Autograd. A noter que la fonction numpy() retourne un tableau NumPy correspondant le plus souvent à une vue.
Si les données sont présentes sur le GPU, il faut penser à les transférer sur le cpu pour les récupérer.