\( \newcommand{\R}{\mathbb{R}} \newcommand{\Rp}{\mathbb{R_+}} \newcommand{\Z}{\mathbb{Z}} \newcommand{\Rn}{\mathbb{R}^n} \newcommand{\N}{\mathbb{N}} \renewcommand{\to}{\rightarrow} \newcommand{\from}{\colon} \renewcommand{\Vec}{\mathsf{vec}} \newcommand{\Ker}{\mathrm{Ker}} \renewcommand{\Im}{\mathrm{Im}} \newcommand{\rang}{\mathrm{rang}} \newcommand{\O}{\mathcal{O}} \newcommand{\C}{\mathcal{C}} \newcommand{\U}{\mathcal{U}} \newcommand{\x}{\mathbf{x}} \newcommand{\X}{\mathbf{X}} \newcommand{\z}{\mathbf{z}} \newcommand{\w}{\mathbf{w}} \newcommand{\W}{\mathbf{W}} \newcommand{\a}{\mathbf{a}} \newcommand{\b}{\mathbf{b}} \newcommand{\B}{\mathbf{B}} \newcommand{\dpartial}[2]{{\partial #1 \over \partial #2}} \newcommand{\d}{\mathbf{d}} \)

Backpropagation

Programmation pour l’IA

Florent Capelli

Université d’Artois, CRIL

La une de la VdN!

Rappels

Neurone formel

Neurone calculant f(\w^T\x+b); \w,b sont les paramètres et f la fonction d’activation. b est le biais.

Plusieurs types de fonctions d’activation :

  • Sigmoïde \(\sigma(z) = {1 \over 1 + e^{-z}}\)
  • Rectified Linear Unit \(ReLU(z) = \max(0,z)\)
  • Linéaire \(f = Id\)
  • …

Rappels: réseaux de neurones

g F1 ReLU w1F3 2 F1->w1F3 w1F4 -1 F1->w1F4 F2 ReLU w2F3 1 F2->w2F3 w2F4 -1 F2->w2F4 F3 σ ws1 1 F3->ws1 F4 σ ws2 -1 F4->ws2 b1 b₁ b1->F1 b2 b₂ b2->F2 b3 b₃ b3->F3 b4 b₄ b4->F4 b5 b₅ s ReLU b5->s w1F1 2.5 w1F1->F1 w2F1 -1 w2F1->F1 w1F2 -1 w1F2->F2 w2F2 1 w2F2->F2 w1F3->F3 w2F3->F3 w1F4->F4 w2F4->F4 ws1->s ws2->s x₁ x₁ x₁->w1F1 x₁->w1F2 x₂ x₂ x₂->w2F1 x₂->w2F2
  • À gauche : les entrées du problème.
  • À droite : la sortie du réseau de neurone
  • Chaque boîte verte est un paramètre du réseau qu’on modifie pour apprendre.
  • Fonction d’erreur (loss) \(e_R(\w; (\x,y))\): mesure la réponse du réseau \(R\) sur l’exemple \((\x,y)\)
  • Erreur de \(R\) sur un ensemble d’exemples \(B \subseteq S\) comme \[E_R(\w;B) = {|B|}^{-1} \sum_{(\x,y) \in B} e_R(\w ; (\x,y)).\]
  • On trouve une bonne valeur de \(\w\) par descente de gradient stochastique.

Descente de gradient dans les Réseaux

On fait une hypothèse ici : \(e_R(\w;(\x,y))\) dépend de seulement de la valeur du réseau sur \(\x\) et de \(y\). C’est-à-dire :

\[ e_R(\w;(\x,y)) = e(R(\w,\x), y)\]

Par exemple : erreur quadratique \(e(y_0,y) = (y_0-y)^2\) et \(e_R(\w;(\x,y)) = e(R(\w;\x), y)\).

En posant \(z = R(\w;\x)\) : \[ \dpartial{e_R}{w} (\w; (\x,y)) = \dpartial{e}{w}(z,y) = \dpartial{e}{z}(z,y) \dpartial{z}{w}(\w) + \dpartial{e}{y}\dpartial{y}{w} \]

donc

\[\Delta_\w e_R = \dpartial{e}{z}(z,y) \times \Delta_\w r\]

Calcul du gradient

Réseaux de neurones en couches

Simplification de l’architecture

Réseau de neurones = couches successives de neurones ayant tous la même fonction d’activation.

Tous les neurones d’une couche sont connectés à ceux de la couche suivantes.

g n0_0 x₁ n1_0 ReLU n0_0->n1_0 n1_1 ReLU n0_0->n1_1 n1_2 ReLU n0_0->n1_2 n1_3 ReLU n0_0->n1_3 n0_1 x₂ n0_1->n1_0 n0_1->n1_1 n0_1->n1_2 n0_1->n1_3 n0_2 x₃ n0_2->n1_0 n0_2->n1_1 n0_2->n1_2 n0_2->n1_3 n2_0 σ n1_0->n2_0 n2_1 σ n1_0->n2_1 n2_2 σ n1_0->n2_2 n1_1->n2_0 n1_1->n2_1 n1_1->n2_2 n1_2->n2_0 n1_2->n2_1 n1_2->n2_2 n1_3->n2_0 n1_3->n2_1 n1_3->n2_2 n3_0 ReLU n2_0->n3_0 n3_1 ReLU n2_0->n3_1 n3_2 ReLU n2_0->n3_2 n3_3 ReLU n2_0->n3_3 n3_4 ReLU n2_0->n3_4 n2_1->n3_0 n2_1->n3_1 n2_1->n3_2 n2_1->n3_3 n2_1->n3_4 n2_2->n3_0 n2_2->n3_1 n2_2->n3_2 n2_2->n3_3 n2_2->n3_4 n4_0 σ n3_0->n4_0 n3_1->n4_0 n3_2->n4_0 n3_3->n4_0 n3_4->n4_0

Calcul d’un réseau en couche

g n0_0 x₁ n1_0 ReLU n0_0->n1_0 n1_1 ReLU n0_0->n1_1 n1_2 ReLU n0_0->n1_2 n1_3 ReLU n0_0->n1_3 n0_1 x₂ n0_1->n1_0 n0_1->n1_1 n0_1->n1_2 n0_1->n1_3 n0_2 x₃ n0_2->n1_0 n0_2->n1_1 n0_2->n1_2 n0_2->n1_3 n2_0 σ n1_0->n2_0 n2_1 σ n1_0->n2_1 n2_2 σ n1_0->n2_2 n1_1->n2_0 n1_1->n2_1 n1_1->n2_2 n1_2->n2_0 n1_2->n2_1 n1_2->n2_2 n1_3->n2_0 n1_3->n2_1 n1_3->n2_2 n3_0 ReLU n2_0->n3_0 n3_1 ReLU n2_0->n3_1 n3_2 ReLU n2_0->n3_2 n3_3 ReLU n2_0->n3_3 n3_4 ReLU n2_0->n3_4 n2_1->n3_0 n2_1->n3_1 n2_1->n3_2 n2_1->n3_3 n2_1->n3_4 n2_2->n3_0 n2_2->n3_1 n2_2->n3_2 n2_2->n3_3 n2_2->n3_4 n4_0 σ n3_0->n4_0 n3_1->n4_0 n3_2->n4_0 n3_3->n4_0 n3_4->n4_0

On note \(w^i_{u,v}\) le poids entre de la sortie du neurone \(v\) de la couche \(i\) vers le neurone \(u\) de la couche \(i+1\) et \(\W^i\) la matrix associée. \(\W^i\) a une ligne par neurones de la couche \(i+1\) et une colonne par neurones de la couche \(i\).

On note \(b^i_j\) le bias du \(j\)ème neurone de la couche \(i\), \(\b^i\) le vecteur colonne associé.

La couche \(1\) sort \(ReLU(\b^0 + \W^0 \times \begin{pmatrix} x_1 \\ x_2 \\ x_3 \end{pmatrix}) \in \R^4\) (\(\W^0\) est une matrice \(4 \times 3\)).

Calcul d’un réseau en couche

g n0_0 x₁ n1_0 ReLU n0_0->n1_0 n1_1 ReLU n0_0->n1_1 n1_2 ReLU n0_0->n1_2 n1_3 ReLU n0_0->n1_3 n0_1 x₂ n0_1->n1_0 n0_1->n1_1 n0_1->n1_2 n0_1->n1_3 n0_2 x₃ n0_2->n1_0 n0_2->n1_1 n0_2->n1_2 n0_2->n1_3 n2_0 σ n1_0->n2_0 n2_1 σ n1_0->n2_1 n2_2 σ n1_0->n2_2 n1_1->n2_0 n1_1->n2_1 n1_1->n2_2 n1_2->n2_0 n1_2->n2_1 n1_2->n2_2 n1_3->n2_0 n1_3->n2_1 n1_3->n2_2 n3_0 ReLU n2_0->n3_0 n3_1 ReLU n2_0->n3_1 n3_2 ReLU n2_0->n3_2 n3_3 ReLU n2_0->n3_3 n3_4 ReLU n2_0->n3_4 n2_1->n3_0 n2_1->n3_1 n2_1->n3_2 n2_1->n3_3 n2_1->n3_4 n2_2->n3_0 n2_2->n3_1 n2_2->n3_2 n2_2->n3_3 n2_2->n3_4 n4_0 σ n3_0->n4_0 n3_1->n4_0 n3_2->n4_0 n3_3->n4_0 n3_4->n4_0

\(\x\)

\(\b^0 + \W^0 \times \x\)

\(ReLU(\b^0 + \W^0 \times \x)\)

\(\b^1 + \W^1 \times ReLU(\W^0 \times \x)\)

\(\sigma(\b^1 + \W^1 \times ReLU(\b^0 + \W^0 \times \x))\)

\(\b^2 + \W^2 \times \sigma(\b^1 + \W^1 \times ReLU(\b^0 + \W^0 \times \x))\)

\(ReLU(\b^2 + \W^2 \times \sigma(\b^1 + \W^1 \times ReLU(\b^0 + \W^0 \times \x)))\)

\(\b^3 + \W^3 \times ReLU(\b^2 + \W^2 \times \sigma(\b^1 + \W^1 \times ReLU(\b^0 + \W^0 \times \x)))\)

\(\sigma(\b^3 + \W^3 \times ReLU(\b^2 + \W^2 \times \sigma(\b^1 + \W^1 \times ReLU(\b^0 + \W^0 \times \x))))\)

\[R(\W;\x) = f_L(\b^L+\W^{L} \times f_{L-1} (\b^{L-1}+\W^{L-1} \dots f_0(\b^0+\W^0 \x))\]

Implémentation vectorisée, parallélisable sur un GPU.

Évaluation par batch

Si on veut évaluer un minibatch \(\X\) donc les colonnes sont les différents éléments du jeu de données, on a :

\[R(\W; \X) := f_L(\b^L+\W^{L} \times f_{L-1} (\b^{L-1}+\W^{L-1} \dots f_0(\b^0+\W^0 \X))\]

est un vecteur colonne dont la ligne \(i\) est l’évaluation de \(R\) sur la colonne \(i\) de \(\X\).

Backpropagation

Idée

But :

g n0_0 x₁ n1_0 ReLU n0_0->n1_0 n1_1 ReLU n0_0->n1_1 n1_2 ReLU n0_0->n1_2 n1_3 ReLU n0_0->n1_3 n0_1 x₂ n0_1->n1_0 n0_1->n1_1 n0_1->n1_2 n0_1->n1_3 n0_2 x₃ n0_2->n1_0 n0_2->n1_1 n0_2->n1_2 n0_2->n1_3 n2_0 σ n1_0->n2_0 n2_1 σ n1_0->n2_1 n2_2 σ n1_0->n2_2 n1_1->n2_0 n1_1->n2_1 n1_1->n2_2 n1_2->n2_0 n1_2->n2_1 n1_2->n2_2 n1_3->n2_0 n1_3->n2_1 n1_3->n2_2 n3_0 ReLU n2_0->n3_0 n3_1 ReLU n2_0->n3_1 n3_2 ReLU n2_0->n3_2 n3_3 ReLU n2_0->n3_3 n3_4 ReLU n2_0->n3_4 n2_1->n3_0 n2_1->n3_1 n2_1->n3_2 n2_1->n3_3 n2_1->n3_4 n2_2->n3_0 n2_2->n3_1 n2_2->n3_2 n2_2->n3_3 n2_2->n3_4 n4_0 σ n3_0->n4_0 n3_1->n4_0 n3_2->n4_0 n3_3->n4_0 n3_4->n4_0
Backpropagation!
  • \(\W^3 = \W^3-\alpha \times \dpartial{e}{\W^3}\), \(\b^3 = \b^3 - \alpha \dpartial{e}{\W^3}\)
  • \(\W^2 = \W^2-\alpha \times \dpartial{e}{\W^2}\), \(\b^2 = \b^2 - \alpha \dpartial{e}{\W^2}\)
  • \(\W^1 = \W^1-\alpha \times \dpartial{e}{\W^1}\), \(\b^1 = \b^1 - \alpha \dpartial{e}{\W^1}\)
  • \(\W^0 = \W^0-\alpha \times \dpartial{e}{\W^0}\), \(\b^0 = \b^0 - \alpha \dpartial{e}{\W^0}\)

L’enjeu est de calculer \(\dpartial{e}{\W^k}\) et \(\dpartial{e}{\b^k}\) en fonction de \(\dpartial{e}{\W^{k+1}}\) et \(\dpartial{e}{\b^{k+1}}\) !

Backpropagation

Erreur de la forme \(e_R(\W; (\x,y)) = e(f_L(\W^{L} \times f_{L-1} (\W^{L-1} \dots f_0(\W^0 \x)), y) = e(a^L,y)\).

On note et précalcule (en évaluant le réseau, forward pass) :

On peut prouver en appliquant la dérivation de composition :

\[\Delta \W^i = \delta^{i} (a^{i})^T \text{ et } \Delta \b^i = \delta^{i}\]

où \(\delta^i = \begin{cases} \dpartial{e}{a^{L+1}}(a^{L+1},y) \times f_L'(z^L) \text{ if } i=L \\ ((\W^{i+1})^T \delta^{i+1}) \odot f'_{i}(z^{i}) \text{ if } i<L. \end{cases}\)

où \(\odot\) dénote le produit d’Hadamard (produit coordonnées par coordonnées).

Intêret

On voit qu’on peut calculer les \(\Delta \W^i\) après :

Calcul vectorisable sur CPU et parallélisable sur GPU.

Pytorch

Principe

Bibliothèque pour :

Un exemple

import torch
from torch import nn

m = nn.Sequential(
    nn.Linear(3,4), 
    nn.ReLU(), 
    nn.Linear(4,3), 
    nn.Sigmoid(),
    nn.Linear(3,5),
    nn.ReLU(),
    nn.Linear(5,1),
    nn.Sigmoid()
)

x = torch.Tensor([1,2,1])
m.forward(x)
g n0_0 x₁ n1_0 ReLU n0_0->n1_0 n1_1 ReLU n0_0->n1_1 n1_2 ReLU n0_0->n1_2 n1_3 ReLU n0_0->n1_3 n0_1 x₂ n0_1->n1_0 n0_1->n1_1 n0_1->n1_2 n0_1->n1_3 n0_2 x₃ n0_2->n1_0 n0_2->n1_1 n0_2->n1_2 n0_2->n1_3 n2_0 σ n1_0->n2_0 n2_1 σ n1_0->n2_1 n2_2 σ n1_0->n2_2 n1_1->n2_0 n1_1->n2_1 n1_1->n2_2 n1_2->n2_0 n1_2->n2_1 n1_2->n2_2 n1_3->n2_0 n1_3->n2_1 n1_3->n2_2 n3_0 ReLU n2_0->n3_0 n3_1 ReLU n2_0->n3_1 n3_2 ReLU n2_0->n3_2 n3_3 ReLU n2_0->n3_3 n3_4 ReLU n2_0->n3_4 n2_1->n3_0 n2_1->n3_1 n2_1->n3_2 n2_1->n3_3 n2_1->n3_4 n2_2->n3_0 n2_2->n3_1 n2_2->n3_2 n2_2->n3_3 n2_2->n3_4 n4_0 σ n3_0->n4_0 n3_1->n4_0 n3_2->n4_0 n3_3->n4_0 n3_4->n4_0

Les tenseurs

Objets de base de Pytorch : les tenseurs, torch.tensor.

  • Similaires aux tableaux numpy :
    • attention, différences de syntaxe !
    • t.to_numpy() pour envoyer un tensor vers numpy
    • torch.from_numpy(t) ou tensor(t) depuis numpy
  • Optimisés pour différentes architectures (vectorisés sur CPU, sur GPU etc.)
  • Supporte gradient (voir autograd)
x = torch.Tensor([[1,2,1], [3,4,5]]
>>> tensor([[1., 2., 1.],
           [3., 4., 5.]])
x.shape
>>> torch.Size([2, 3])
torch.sum(x, dim=1)
>>> tensor([ 4., 12.])
tensor([ 4., 12.])
torch.sum(x, dim=0)
>>> tensor([4., 6., 6.])

Autograd

Opération sur les tenseurs peuvent stocker des informations sur le gradient :

a = torch.tensor([1.], requires_grad=True)
a.grad is None
>>> True
q = a*a
>>> tensor([1.], grad_fn=<MulBackward0>)
a.grad is None
>>> True
q.backward()
a.grad
>>> tensor([2.])
a = torch.tensor([1.])
a.grad is None
>>> True
q = a*a
>>> tensor([1.])
q.backward()
>>> RuntimeError

Utilise des règles automatiques de calcul de différentielles pour calculer le gradient. Stocké dans les paramètres.

Une simple régression linéaire

x = torch.empty(100).uniform_(-20,20)
y = 3*x+2+torch.normal(mean=0., std=3., size=(100,))

a = torch.tensor([0.], requires_grad=True)
b = torch.tensor([0.], requires_grad=True)

for e in range(1000):
    loss = torch.mean((a*x+b-y)**2)
    loss.backward()
    with torch.no_grad():
        a -= (1e-3)*a.grad
        b -= (1e-3)*b.grad
    a.grad_zero_()
    b.grad_zero_()
  • On définit a et b comme des tenseurs avec information de gradient
  • Donc loss, en le calculant, maintient une trace du calcul.
  • loss.backward() propage le gradient dans l’arbre de calcul.

Réseaux de Neurones

import torch
from torch import nn

m = nn.Sequential(nn.Linear(2,3), nn.ReLU(), 
    nn.Linear(3,1), nn.Sigmoid())

x = torch.tensor([1., 2.])
y = 1
loss = (m(x)-y)**2
loss.backward()
with torch.no_grad():
    for f in m.parameters():
        f -= f.grad*0.01
        f.grad.zero_()
  • Comme tout à l’heure
  • m expose automtatiquement ses paramètres entraînables dans m.parameters()

Après les vacances

Exploration plus avancée de Pytorch