Perceptron

Un article de Wikipédia, l'encyclopédie libre.
Aller à : navigation, rechercher

Le perceptron a été inventé en 1957 par Frank Rosenblatt au laboratoire d'aéronautique de l'université Cornell. C'est un modèle inspiré des théories cognitives de Friedrich Hayek et de Donald Hebb.

Définition[modifier | modifier le code]

Perceptron avec 2 entrées et une fonction d'activation à seuil

Le perceptron peut être vu comme le type de réseau de neurones le plus simple. C'est un classifieur linéaire. Ce type de réseau neuronal ne contient aucun cycle (en anglais feedforward neural network). Dans sa version simplifiée, le perceptron est mono-couche et n'a qu'une seule sortie à laquelle toutes les entrées sont connectées. Les entrées et la sortie sont booléennes.

Le potentiel post-synaptique biaisé est représenté par Z = \sum W_i X_i - \theta. Ici, θ définit le seuil (ou biais) à dépasser pour que la sortie Y soit à 1, et W_i est le poids de l'entrée X_i.

La fonction d'activation est la fonction de Heaviside (la fonction signe est parfois utilisée)

Y = H(Z)=\left\{\begin{matrix} 0 & \mathrm{si} & Z < 0 \\ 1 & \mathrm{si} & Z \ge 0\end{matrix}\right.

La règle de Hebb[modifier | modifier le code]

La règle de Hebb établie par Donald Hebb est une règle d'apprentissage des réseaux de neurones artificiels dans le contexte de l'étude d'assemblées de neurones.

Cette règle suggère que lorsque deux neurones sont excités conjointement, il se crée ou renforce un lien les unissant.

Dans le cas d'un neurone artificiel seul utilisant la fonction signe comme fonction d'activation cela signifie que :

W'_i = W_i + \alpha (Y . X_i)

W'_i représente le poids i corrigé et \alpha représente le pas d'apprentissage.

Cette règle n'est malheureusement pas applicable dans certains cas bien que la solution existe.

La règle d'apprentissage du perceptron[modifier | modifier le code]

Le perceptron de Frank Rosenblatt est très proche de la règle de Hebb, la grande différence étant qu'il tient compte de l'erreur observée en sortie.

W'_i = W_i + \alpha (Y_t - Y)  X_i

Y_t représente la sortie attendue, W'_i le poids i corrigé et \alpha le pas d'apprentissage.

Exemple de programme[modifier | modifier le code]

Ceci est un programme en C++, apprenant la fonction NAND appliquée à un ensemble de 2 bits (x1 et x2), auxquels on rajoute 1 bit (x0), qui est une constante à 1, on a donc 100,101,110,111 comme inputs, les résultats attendus étant respectivement 1,1,1,0. Pour une autre fonction (AND, OR ou NOR mais pas le XOR en raison des limitations du perceptron), le seul changement à opérer est le tableau des résultats attendus.

Le programme fonctionne par cycles où chaque exemple est testé une fois, tant que tous les exemples ne sont pas corrects (résultat produit = résultat attendu) dans le cycle, on en refait un. Par contre les poids sont modifiés à chaque exemple qui s'avère incorrect. Ils sont ici initialisés à 0, mais on pourrait tout aussi bien les mettre à 1 ou encore à une valeur aléatoire, cela modifie juste le nombre d'étapes avant d'arriver au résultat.

Il s'avère que ce programme est inefficace et incohérent pour des trop faibles seuils d'apprentissage. En effet on remarque que les valeurs ne convergent pas directement vers la solution : on risque de tendre vers un minimum d'erreur local plutôt que global[1].

#include <iostream>

using namespace std;

int const X(3);         // x1 et x2 les entrées, x0 est une constante à 1
int const NB_EX(4);  	// nombre d'exemples donnés
float const SEUIL(0.5);	// seuil d'activation
float const PAS(0.1); 	// le pas d'apprentissage

void affichage(float*,int,int,int);      // affiche l'état actuel des poids

int main () {

   int tabEntree[NB_EX][X] = { {1,0,0},{1,0,1},{1,1,0},{1,1,1} }; // les exemples donnés avec x0, x1, x2
   int tabResPrevu[NB_EX] = {1,1,1,0}; // les résultats prévus correspondant aux entrées
   float tabPoids[X] = {0,0,0};        // les poids synaptiques
   int res, cptrCycle(0);     // res est le résultat produit, à comparer au résultat prévu
   float somme(0);                     // la somme pondérée
   bool erreurs(true);                 // pour détecter fin apprentissage

   affichage(tabPoids,X,0,cptrCycle);

   // Phase d'apprentissage
   while (erreurs) {                // on continue tant qu'on a au moins une erreur par cycle
      erreurs = false;
      cptrCycle++;

      for (int j=0; j<NB_EX; j++) {         // un cycle
         somme = 0;

	 for(int i=0; i<X; i++) {
	    somme += tabEntree[j][i] * tabPoids[i];  // somme pondérée de l'exemple
         }
         res = (somme > SEUIL) ? 1 : 0;  // fonction de seuil

         for (int i=0; i<X; i++) {
             tabPoids[i] += (tabResPrevu[j]-res) * PAS * tabEntree[j][i]; // modification des poids
         }
	 erreurs |= ((tabResPrevu[j] - res) != 0);   // détection de fin d'apprentissage

         affichage(tabPoids,X,j,cptrCycle);
      }
   }
   cout << "Apprentissage terminé en " << cptrCycle << " cycles" << endl;
   return 0;
}

void affichage(float* poids, int n, int cptr, int cptr2) {
   int i;
   cout << "----- Cycle " << cptr2 << " Etape " << cptr << " -----" << endl;
   for (i=0; i<n; i++) {
      cout << "poids[" << i << "] = " << poids[i] << endl;
   }
}

Le même programme en Ruby

SEUIL = 0.5
PAS = 0.1

poids = Array.new [0,0,0]
Entrees = Array.new [ [1,0,0], [1,0,1], [1,1,0], [1,1,1] ]
Reponses = Array.new [ 1, 1, 1, 0 ]

def afficher(poids, cycle, etape)
  p "--- Cycle #{cycle} - Etape #{etape}"
  poids.each_index do |i|
    p "Poids #{i} = #{poids[i]}"
  end
end

cycle_n = 0
erreur = true
while erreur do
  erreur = false

  4.times do |exemple_n|

    # Prediction
    somme = 0
    3.times do |i|
      somme += poids[i] * Entrees[exemple_n][i] 
    end
    resultat = if (somme > SEUIL) then 1 else 0 end

    # Correction
    diff = Reponses[exemple_n] - resultat
    3.times do |i|
      poids[i] += PAS * diff * Entrees[exemple_n][i]
    end
    
     
     erreur = true if Reponses[exemple_n] != resultat

    afficher(poids, exemple_n, cycle_n)

  end

  cycle_n = cycle_n + 1
end

p "Apprentissage terminé en #{cycle_n} cycles"

Notes et références[modifier | modifier le code]

  1. « TIPE sur les réseaux de neurones artificiels »,‎ 2009 (consulté le 21 janvier 2014)

Voir aussi[modifier | modifier le code]

Le perceptron : Algorithme et théorèmes

Réseau neuronal artificiel

Bibliographie[modifier | modifier le code]

  • F. Rosenblatt (1958), "The perceptron: a probabilistic model for information storage and organization in the brain",
- repris dans J.A. Anderson & E. Rosenfeld (1988), Neurocomputing. Foundations of Research, MIT Press