Les réseaux de neurones artificiels

Les réseaux de neurones artificiels sont simplement des systèmes inspirés du fonctionnement des neurones biologiques.

Le réseau neuronal multicouche (appelé « Perceptron multicouche » ou MLP : Multi Layer Perceptron ou encore « réseau de propagation avant ») est un système capable d’apprendre par l’expérience.

Il s’agit un ensemble de neurones organisés en couches. Le signal d’entrée se propage d’une couche à l’autre jusqu’à la sortie, en activant ou pas les neurones du réseau.

Les neurones d’un couche sont connectés aux neurones de la couche précédente.  Le neurone traite les données reçues et si la somme pondérée dépasse un seuil un signal est envoyé à la couche suivante.

Le principe d’apprentissage est de regarder la sortie par rapport à ce qui était attendu et de mettre à jour les liaisons entre les neurones (les renforcer ou les inhiber) pour améliorer le résultat final. La sortie est donc une prédiction de la valeur attendue.

Exemples

Reconnaissance de visages

  1. En entrée, on donne les pixels d’une image. En sortie, un neurone donne 1 si l’image était un visage et 0 sinon.
  2. On entraîne un réseau à estimer la valeur d’un ou exclusif sur 3 entrées (e1⊕e2⊕e3). La sortie indiquera 0 ou 1, prédiction du résultat de l’opération logique.

Ce type de réseau neuronal est organisé en couches :

  1. Couche d’entrée, composée de neurone d’entrée ;
  2. Couches cachées (plusieurs en général) ;
  3. Couche de sortie, composée du neurone de sortie, présentant la prédiction du résultat.

Tous les neurones d’une couche sont connectés aux neurones de la couche suivante.

Pour chaque neurone on fait la somme pondérée par le poids des signaux arrivant. Pour les neurones cachés on ajoute le biais et la sortie est activée si le résultat dépasse une certain seuil (activation du neurone ou non).

Par exemple, ci-dessus, en sortie du 1er neurone d’entrée on a : \(e_1\)

On a donc

\(x_1=f_{seuil}(biais_1+(u_0\cdot e_{1}+v_0\cdot e_{2}+w_0\cdot e_{3})) \)

et

\(x_2=f_{seuil}(biais_2+(u_1\cdot e_{1}+v_1\cdot e_{2}+w_1\cdot e_{3}))\)

\(u_i\), \(v_i\) et \(w_i\) sont les poids des liaisons.

Ce sont ces poids qui vont être « adaptés », tout au long de l’apprentissage, jusqu’à permettre au réseau de prédire efficacement le résultat (en général il reste entre 0 et 1 ou -1 et 1).

Le biais permet de décaler la fonction d’activation, ainsi le réseau aura de plus grandes opportunités d’apprentissage.

La fonction d’activation \(f_{seuil}\) détermine les conditions dans lesquels le neurone va émettre un signal. Elle peut être une fonction  :

  • telle que \(f_{seuil}(x)=\begin{cases}
    0 & \text{si $x < seuil$} \\
    1 & \text{sinon}
    \end{cases}
    \)
    (fonction de Heaviside),
  • telle que \(f_{seuil}(x)=\begin{cases}
    0 & \text{si $x<seuil_1$} \\
    1 & \text{si $x>seuil_2$} \\
    x & \text{si \(seuil_1 \leq x \leq seuil_2\)}
    \end{cases}
    \)
    (fonction linéaire),
  • etc …
    (fonction sigmoïde, tangente hyperbolique …)

 

Processus d’apprentissage

La phase d’apprentissage repose sur une méthode mathématique qui permet, à partir du résultat souhaité et du résultat obtenu, de revenir à l’entrée en modifiant les poids et les biais.

Lorsque l’on regarde l’erreur commise par le réseau sur une propagation (= un essai avec poids et biais fixés), on peut évaluer l’impact qu’a eu un poids en particulier sur cette erreur. Grâce à cette information, on peut également évaluer, si augmenter ou diminuer ce poids va améliorer ou empirer notre erreur.

On met alors les poids à jour un par un à partir de cet écart, en partant de la fin et en remontant. C’est ce qu’on appelle la rétro-propagation.

Il faut donc faire de nombreux apprentissage pour obtenir une bonne prédiction.

 

Cas du perceptron mono-couche

Le perceptron mono-couche ne possède qu’une seule sortie (booléenne) à laquelle toutes les entrées (booléennes) sont connectées.

Un neurone possède des entrées, chaque entrée possède un poids et la sortie est une fonction du poids et des entrées :

Exemple : perceptron mono-couche à 3 entrées


\(\large{S = f(u_1 \cdot e_1 + u_2 \cdot e_2 + u_3 \cdot e_3 )}\)

 

Algorithme d’apprentissage

  1. Initialiser aléatoirement les poids ;
  2. Calculer la sortie pour un couple d’entrée donné ;
  3. Calculer la prédiction, si la différence entre la valeur attendue et la prédiction est positive alors \(erreur_{prédiction}\) = 1, sinon erreur =0
  4. Mettre à jour les poids : \(\Delta u_{i} = \eta (S_{attendue} – erreur) \times e_{i}\) avec \(\eta\) le taux d’apprentissage (\(0<\eta<1\))
  5. Recommencer jusqu’à que l’erreur de prédiction soit acceptable

Remarque : \(\eta\) représente le taux d’apprentissage. S’il est mal choisi il peut y avoir oscillation ou le calcul très long !

 

Exemple AND

On cherche à ce que le Perceptron simple ci-dessous reproduise la fonction logique AND :

Cela revient à déterminer \(u_1, u_2 et u_3\) pour obtenir la table de vérité suivante :

\(e_2\) \(e_3\) \(S_{attendue}\)
0 0 0
0 1 0
1 0 0
1 1 1

En prenant comme fonction d’activation une fonction de Heaviside telle que \(h(x)=0\) si \(x < 0\) et \(h(x)=1\) si \(x \geq 0\), on peut déterminer les poids \(u_1\) et \(u_2\) en utilisant l’algorithme précédent d’apprentissage :

  1. Initialiser aléatoirement les poids :
    • \(u_1=0,1 ; u_2=0,2 ; u_3=0,05\)
  2. Calculer la sortie pour un couple d’entrée donné :
    • \(e_2=0 ; e_3=0\) alors \(S_{prediction_1}=0,1 \times 1 + 0,2 \times 0 + 0,05 \times 0 = 0,1 > 0 \) soit \(S_{prediction_1} = h(0,1) = 1\)
  3. Calculer l’erreur de prédiction : ici
    • \(erreur_{prediction_1}=S_{attendue} – S_{prediction_1} = 0 – 1 = -1\)
  4. Mettre à jour les poids :
    • \(\Delta u_{1} = \eta \times (-1) \times e_1 = 0,1 \times (-1) \times 1 = -0,1\) car \(e_1=1\) pour cet apprentissage.
    • On adapte \(u_1=u_1+ \Delta u_1 = 0,1 + (-0,1) = 0\)
    • \(\Delta u_{2} = \eta \times (-1) \times e_2 = 0,1 \times (-1) \times 0 = 0\) car \(e_2=0\) pour cet apprentissage.
    • On adapte \(u_2=u_2+ \Delta u_2 = 0,2+0 = 0,2\)
    • \(\Delta u_{3} = \eta \times (-1) \times e_3 = 0,1 \times (-1) \times 0 = 0\) car \(e_3=0\) pour cet apprentissage.
    • On adapte \(u_3=u_3+ \Delta u_3 = 0,05+0 = 0,05\)
  5. Recommencer jusqu’à que l’erreur de prédiction soit acceptable

 

Code Python

import random

#Poids du perceptron
u1=random.uniform(0,1)
u2=random.uniform(0,1)
u3=random.uniform(0,1)
#Entrées
e1=[1,1,1,1]
e2=[0,0,1,1]
e3=[0,1,0,1]
#Sorties de la fonction AND
s=[0,0,0,1]
#Sorties calculées et erreurs de prédiction
sp=[0,0,0,0]
err=[1,1,1,1]
#Taux d'apprentissage
n=0.1

def h(x):
#fonction de Heaviside
    if x >= 0:
        return 1
    else:
        return 0

for i in range(4):

    while abs(err[i])>0.01:
        #Prédiction de la sortie
        sp[i]=h(u1*e1[i]+u2*e2[i]+u3*e3[i])
        #Calcul de l'erreur
        err[i]=s[i]-sp[i]
        #Mise à jour des poids
        u1 += n*err[i]*e1[i]
        u2 += n*err[i]*e2[i]
        u3 += n*err[i]*e3[i]

print("Poids (u1,u2 et u3) : ",u1,u2,u3)
print("Sorties : ")
for i in range(4):
    print(e2[i]," AND ",e3[i]," = ",sp[i]) 

 

Cas du Perceptron multicouche

Exemple XOR

On peut réaliser le OU exclusif avec un réseau de neurones. voici la table de vérité :

A B Q
0 0 0
0 1 1
1 0 1
1 1 0

On voit que le réseau de neurones proposé ci-dessous, avec ces poids et biais correspond bien à la table de vérité ci-dessus.

Code Arduino – XOR

La bibliothèque NeuralNetwork pour carte Arduino permet d’effectuer les calculs de propagation mais surtout de rétro-propagation, c’est à dire de faire l’apprentissage d’un réseau de neurones de type MLP.

La fonction FeedForward alimente le réseau de neurones avec les entrées et retourne les sorties du réseau.

La fonction BackProp effectue l’apprentissage du réseau de neurones (ajuste les poids et les biais) par comparaison des valeurs souhaitées en sortie et celles calculées.

On va implémenter, sur la carte Arduino, un réseau de neurones à 3 couches, avec 2 neurones d’entrée, 1 de sortie, et 4 neurones sur la couche cachée.

 

#define NumberOf(arg) ((unsigned int) (sizeof (arg) / sizeof (arg [0]))) //Calcule le nombre de couche (ici 3)
#include <NeuralNetwork.h>

/*
3 couches : 
1ere couche avec 2 neurones d'entrée
2e couche avec 4 neurones cachées
3e couche avec 1 neurone de sortie
On peut mettre plusieurs couches cachées et augmenter le nombre de neurones, mais attention à la place en mémoire !
*/
const unsigned int layers[] = {2,4,1}; 
float *outputs; // sorties de la couche de sortie (ici une seule sortie)

const float inputs[4][2] = { //Entrées, ici les valeurs de A et B
  {0, 0}, //0 xor 0 = 0
  {0, 1}, //0 xor 1 = 1
  {1, 0}, //1 xor 0 = 1
  {1, 1}  //1 xor 1 = 0
};
// valeurs souhaitées en couche de sortie ici Q (résultats du xor sur les entrées) :
const float expectedOutput[4][1] = {{0},{1},{1},{0}}; 

NeuralNetwork NN(layers,NumberOf(layers)); // Creation du réseau de neurones

void setup() {
  Serial.begin(115200);
  
  //Apprentissage du réseau de neurones avec 3000 retropropagations 
  for(int i=0; i < 3000; i++) { // Apprentissage

    for (int j = 0; j < NumberOf(inputs); j++) {
    NN.FeedForward(inputs[j]); // Propagations des entrées dans le réseau et calcul des sorties
    NN.BackProp(expectedOutput[j]); // Rétropropagation et ajustement des poids et biais
  }
  }
  
  //Affichage des résultats par propagation des entrées après apprentissage
  for (int i=0; i < NumberOf(inputs); i++) {
    outputs = NN.FeedForward(inputs[i]);  //Propagation de la données n°i depuis la couche d'entrée et calcul de la sortie
    Serial.println(outputs[0], 7); // Affichage de la sortie avec 7 chiffres significatifs
  }
  NN.print(); // Affichage des poids et biais des couches cachées
}

void loop() {
    outputs = NN.FeedForward(inputs[0]);//Entrées : 0, 0 et 0 souhaité en sortie
    Serial.print("0 xor 0 = ");
    Serial.println(outputs[0], 7);
    
    outputs = NN.FeedForward(inputs[1]);//Entrées : 0, 1 et 1 souhaité en sortie
    Serial.print("0 xor 1 = ");
    Serial.println(outputs[0], 7);

    outputs = NN.FeedForward(inputs[2]);//Entrées : 1, 0 et 1 souhaité en sortie
    Serial.print("1 xor 0 = ");
    Serial.println(outputs[0], 7);

    outputs = NN.FeedForward(inputs[3]);//Entrées : 1, 1 et 0 souhaité en sortie
    Serial.print("1 xor 1 = ");
    Serial.println(outputs[0], 7);
    
    delay(1000);
}

 

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *