Approximer une fonction à l'aide d'un réseau de neurones⚓︎
Les 2 neurones
Nous allons maintenant réaliser un petit réseau de neurones pour approximer la fonction valeur absolue. Pour cela, nous n'utiliserons qu'une seule couche cachée composée de deux neurones. Intuitivement, l'idée est qu'un des neurones servira lorsque le nombre est positif et l'autre lorsque le nombre est négatif.
On codera les paramètres à l'aide d'une liste [a01, a02, a13, a23, b1, b2, b3]
où \(a_{ij}\) est le poids entre le neurone \(i\) et le neurone \(j\). Les \(b_i\) sont les biais des neurones \(N_1\) à \(N_3\). La fonction d'activation utilisée pour les neurones \(N_1\) et \(N_2\) sera une ReLU :
def relu(x):
return max(0, x)
Calcul des résultats
L'image d'un nombre par le réseau de neurone est donné par la fonction suivante :
def calcule_resultat(x, coeffs):
a01, a02, a13, a23, b1, b2, b3 = coeffs
y1 = relu(a01*x + b1)
y2 = relu(a02*x + b2)
return a13*y1 + a23*y2 + b3
On peut remarquer que les coefficients #py [1, -1, 1, 1, 0, 0, 0]
permettent d'obtenir exactement la fonction valeur absolue :
>>> calcule_resultat(5, [1, -1, 1, 1, 0, 0, 0])
5
>>> calcule_resultat(-5, [1, -1, 1, 1, 0, 0, 0])
5
Exercice 8
Écrire le code de la fonction calcule_cout
qui prend en paramètres 3 listes de nombres xi
, yi
et coeffs
et qui calcule le coût, avec la méthode des moindres carrés, du réseau de neurones avec les paramètres coeffs
et le nuage de points défini par xi
et yi
.
>>> calcule_cout(xi2, yi2, [1, -1, 1, 1, 0, 0, 0])
0.0
>>> calcule_cout(xi2, yi2, [1, 0, 1, 1, 0, 0, 0])
5.0
>>> calcule_cout(xi2, yi2, [0, 0, 0, 0, 0, 0, 0])
10.0
>>> calcule_cout(xi2, yi2, [0, 0, 1, 1, 1, 1, 2])
4.181818181818182
Pour visualiser le résultat
# Tests
(insensible à la casse)(Ctrl+I)
Rétropropagation et apprentissage
Pour pouvoir faire la rétropropagation, il faut pouvoir calculer les dérivées de la fonction de coût par rapport à chaque paramètre. Normalement, on utilise des fonctions qui calculent de façon exacte les dérivées. Pour simplifier le problème, nous utiliserons des valeurs approchées. Pour éviter de calculer le coût pour chaque paramètre, on le passe en paramètre de la fonction.
def derivee(xi, yi, coeffs, k, cout, h=0.0001):
coeffs2 = coeffs.copy()
coeffs2[k] += h
return (calcule_cout(xi, yi, coeffs2)-cout) / h
On fait maintenant une fonction qui calcule les nouvelles valeurs des paramètres :
def mise_a_jour(xi, yi, coeffs, c, pas=0.1):
deltas = []
c = calcule_cout(xi, yi, coeffs)
for k in range(len(coeffs)):
deltas.append(pas * derivee(xi, yi, coeffs, k, c))
return [coeffs[i] - deltas[i] for i in range(len(coeffs))]
Et enfin, on peut faire l’apprentissage :
def apprentissage(xi, yi, coeffs, pas=1, seuil=0.00001, nmax=10000):
n = 0 # compteur d'étapes
while pas > seuil:
cout = calcule_cout(xi, yi, coeffs)
coeffs2 = mise_a_jour(xi, yi, coeffs, cout, pas)
cout2 = calcule_cout(xi, yi, coeffs2)
if cout2 > cout: # On s'éloigne du minimum
pas = pas / 2
else:
cout = cout2
coeffs = coeffs2
n += 1
if n > nmax: # on a dépassé le nombre max d'étapes
break
return coeffs2
Exercice 9
Vous pouvez maintenant tester l'apprentissage de notre réseau. Il se peut que le réseau ne converge pas tout à fait vers les valeurs attendues. Vous pouvez tester plusieurs fois jusqu’à obtenir le résultat voulu. Vous pouvez alors regarder les valeurs des coefficients.
# Tests
(insensible à la casse)(Ctrl+I)
Pour aller plus loin
Vous pouvez tester ce fichier qui permet d'approximer des fonctions booléennes à deux paramètres à l'aide d'un réseau de neurones pouvant avoir plusieurs couches.
Vous pouvez aussi aller voir la fantastique série de vidéos de 3Blue1Brown sur les réseaux de neurones sur sa chaîne Youtube.
# Tests
(insensible à la casse)(Ctrl+I)