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 :

\[Loss = I_0 \cdot W_0 + 8 \cdot W_1 + I_1\]

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 :

\[Loss(W) = 5 \cdot W_0 + 8 \cdot W_1 + 7\]

Ce qui après application des formules de dérivation donne une réponse identique à la rétropopagation :

\[\frac{\partial Loss}{\partial W_0} = 5 \qquad \frac{\partial Loss}{\partial W_1} = 8\]

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.