y = ax + b

  • Pour comprendre les réseaux de neurones, il n’est pas indispensable, contrairement aux idées reçues, de disposer d’un background mathématique très élevé. Le niveau du Lycée suffit. Si vous avez compris l’équation de la droite et la régression linéaire, alors vous comprendrez les réseaux de neurones.

C’est ce que cet article tente de démontrer.

Cet article s’appuie sur le cours (GCP) de Jeremy Howard @jeremyphoward, sur celui de Rachel Thomas @math_rachel et celui de la Khan Academy.

L’équation d’une droite est : y = ax + b

a est la pente de la droite (ou son coefficient directeur) et b est l’ordonnée à l’origine.

On va dans cet exercice, après avoir créé un nuage de points ayant approximativement la forme d’une droite, chercher les paramètres de la droite (a, b) qui approchent au mieux ce nuage de points par une droite des moindres carrés.

y = ax + b, on remplace a par a1 et b par a2 (a2=b), donc on obtient y = a1x + a2 (on sait que a2 = a2*1) donc si X est une matrice dont la 2ème colonne est à 1 (pour b) alors on peut écrire : [latex] y = a_{}1 x_{}1 + a_{}2 x_{}2 [/latex] et même ou plus généralement [latex] \vec{y} = X \vec{a} [/latex] (produit matriciel)

Quelques notions

Multiplication de matrices

Python a introduit l’opérateur @dans la version 3.5. Cet opérateur permet de multiplier des matrices.

PEP 465 introduced the @ infix operator that is designated to be used for matrix multiplication. The acceptance and implementation of this proposal in Python 3.5 was a signal to the scientific community that Python is taking its role as a numerical computation language very seriously

https://alysivji.github.io/python-matrix-multiplication-operator.html
import numpy as np
A = np.matrix('3 1; 8 2')
B = np.matrix('6 1; 7 9')
A @ B
matrix([[25, 12],
        [62, 26]])

Tenseur

n=100
x = torch.ones(n,2)

Returns a tensor filled with the scalar value 1, with the shape defined by the variable argument sizes.

https://pytorch.org/docs/stable/torch.html

Tout d’abord on crée un tenseur de shape (forme) 2 (en clair, une matrice). Ce tenseur est initialisé à 1.

Ensuite, on remplace la colonne 1 par un nombre aléatoire compris entre -1 et 1.

x[:,0].uniform_(-1.,1)

Fills self tensor with numbers sampled from the continuous uniform distribution:

https://pytorch.org/docs/stable/tensors.html#torch.Tensor.uniform_
x[:5]
tensor([[ 0.1205,  1.0000],
        [ 0.1748,  1.0000],
        [ 0.9000,  1.0000],
        [-0.4400,  1.0000],
        [ 0.6781,  1.0000]])

Bruit

Returns a tensor filled with random numbers from a uniform distribution on the interval [0,1]https://pytorch.org/docs/stable/torch.html?highlight=torch%20rand#torch.rand

Pour créer un vecteur de n (100) nombres aléatoires.

 torch.rand(n)

Nuage de points

On crée le nuage de points par calcul matriciel. On initialise a (notre paramètre). C’est un vecteur de 2 lignes * 1 colonne. On a donc un vecteur [3, 2}

a = tensor(3.,2);

On fait le produit de la matrice de dimension (100,2) par le vecteur (2,1), ce qui nous donne un vecteur de dimension (100)

y = x@a + torch.rand(n)
Produit Matriciel

Notre nuage a cette forme :

plt.scatter(x[:,0], y);

Gradient

On cherche la valeur des paramètres de la droite, c’est à dire les poids (a et b). Ici c’est plus exactement [latex]a_{1}[/latex] et [latex]a_{2}[/latex] (car il comprend b, il a 2 valeurs)

Nous allons chercher la valeur des paramètres par itération en comparant l’écart entre les résultats calculés et les valeurs connues. Pour cela, il nous faut une fonction de coût. On choisit comme fonction, la moyenne des carrés des écarts.

fonction de coût

def mse(y_hat, y): return ((y_hat-y)**2).mean()

Par exemple si :

a = tensor(-1.,1)
y_hat = x@a
mse(y_hat, y)

alors MSE (mean square error) =

tensor(6.2704)

graphiquement on a :

plt.scatter(x[:,0],y)
plt.scatter(x[:,0],y_hat);

calcul du gradient

On initialise a :

a = nn.Parameter(a)

On définit la fonction update() de la façon suivante :

def update():
    y_hat = x@a
    loss = mse(y, y_hat)
    if t % 10 == 0: print(loss)
    loss.backward()
    with torch.no_grad():
        a.sub_(lr * a.grad)
        a.grad.zero_()
  • tout d’abord on calcule y_hat (la droite avec les nouveaux paramètres)
  • ensuite on calcule l’erreur (la perte) loss
  • on affiche cette perte toutes les 10 fois
  • on calcule le gradient, c’est ce que permet backward()
  • puis on on remplace a par sa valeur réduire du gradient multiplié par le learning rate
  • et on remet à zero le gradient

gradient : Computes the gradient of current tensor 

https://pytorch.org/docs/stable/autograd.html?highlight=backward#torch.Tensor.backward

no.grad() Context-manager that disabled gradient calculation.

https://pytorch.org/docs/stable/autograd.html?highlight=no_grad#torch.autograd.no_grad

L’instruction with est utilisée pour encapsuler l’exécution d’un bloc avec des méthodes définies par un gestionnaire de contexte 

https://docs.python.org/fr/3/reference/compound_stmts.html#the-with-statement

La fonction étant définie, il ne reste qu’à boucler :

lr = 1e-1
for t in range(100): update()

résultat

plt.scatter(x[:,0],y)
plt.scatter(x[:,0],x@a);

Animation

L’objet n’est pa de décrire ici matplotlib. Les informations sont minimales.

from matplotlib import animation, rc
rc('animation', html='jshtml')

On initialise a, on définit la figure, on affiche le nuage de points, et la première droite.

a = nn.Parameter(tensor(-1.,1))
fig = plt.figure()
plt.scatter(x[:,0], y, c='orange')
line, = plt.plot(x[:,0], x@a)
plt.close()

puis on itère

def animate(i):
    update()
    line.set_ydata(x@a)
    return line,
animation.FuncAnimation(fig, animate, np.arange(0, 100), interval=20)

Résumé

Si vous avez compris, la descente du gradient, alors sachez qu’un réseau de neurones fonctionne de la même façon. On compare le résultat calculé avec celui qu’il devrait être grâce à une fonction de coût, on cherche de nouveaux paramètres qui minimise la fonction de coût, avec le calcul du gradient, puis on itère.

On a les étapes suivantes :

  • intialisation des paramètres de notre modèle (ici une droite, a et b sont les paramètres)
  • calcul des valeurs y_hat avec ces paramètres
  • calcul du coût (erreur)
  • minimisation de la fonction de coût, calcul du gradient
  • mise à jour des paramètres a.sub_(lr * a.grad) en soustrayant le gradient multiplié par le learning rate (pas d’apprentissage)
  • itération

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.