Réseau de neurones (version 2.4)

 

 

Introduction

Créer un réseau de neurones

Calculer la sortie d'un réseau de neurones

Erreur d'un réseau de neurones

Apprentissage d'un réseau de neurones

1. Paramètres de l'apprentissage

2. Lancement de l'apprentissage

3. Contrôle de l'apprentissage

Obtention d'un code C++ associé à un réseau de neurones

 

 

 

 

 

 

 Introduction

 

CarréMaths propose la manipulation simple de réseaux de neurones dédiés à la régression (linéaire ou non) et à la classification de données. La classe de modèles envisagée correspond aux perceptrons ou réseaux de neurones multicouches.

 

Il s'agit de créer ces modèles, de les estimer sur une base d'apprentissage, de les valider sur une base de test, puis de les utiliser. A tout moment, leur description figure à l'intérieur d'une feuille Excel sous une forme qui permet leur intégration dans d'autres applications.

 

Une barre d'outils intitulée CarréMaths-RN résume les quatre opérations qu'il est possible d'effectuer :

 

1.    Création

2.    Utilisation ou application

3.    Calcul de l'erreur moyenne entre le résultat attendu et celui que retourne le réseau de neurones

4.    Apprentissage guidé par une série de paramètres que l'utilisateur peut modifier depuis une feuille Excel.

5.    Obtention d'un code C++ permettant d'utiliser un réseau de neurones défini par CarréMaths dans une application C++.

 

 

Pour le moment, CarréMaths ne permet pas l'éparpillement du réseau de neurones et des données qu'il utilise sur plusieurs feuilles de calcul.

 

 Créer un réseau de neurones

 

La création d'un réseau de neurones est réalisée par la fonction Création. Cette fonction crée un tableau de quatre colonnes et ayant un nombre de lignes correspondant au nombre de coefficients du réseau de neurones augmenté de cinq. Cette fonction ne nécessite aucun paramètre, le tableau créé s'écrira dans la feuille active à l'endroit de la première case sélectionnée.

 

 

L'exécution de la fonction Création affiche une fenêtre requérant les paramètres suivant :

1.    le type de réseau de neurones désiré, à savoir :

a.     régression

b.     classification

2.    la description de son architecture

 

L'architecture est définie par une suite de nombres entiers séparés par des points virgules :

-         Le premier est le nombre d'entrées.

-         Le dernier est le nombre de sorties :

·                                                 Pour un réseau dédié à la régression, c'est la dimension de l'espace d'arrivée.

·       Pour un réseau dédié à la classification, c'est le nombre de classes.

-         Les autres nombres compris entre ces deux extrémités correspondent aux nombres de neurones sur les couches cachées intermédiaires.

 

 

 

Exemple :

 

2;1

2 entrées, 1 sortie, pas de couche cachée

4;10;15;3

4 entrées, 10 neurones sur la première couche cachée, 15 neurones sur la seconde couche cachée, 3 sorties

 

La fonction crée un tableau de ce type, avec un en-tête de 4 lignes décrivant l'architecture du réseau de neurones. Des noms propres à CarréMaths apparaissent :

 

HalNetwork

nom de l'objet réseau de neurones pour CarréMaths

entrées

nom de la couche d'entrées

HalNetworkLayerSigmo

nom d'une couche dont les fonctions de transfert sont sigmoïdes

HalNetworkLayerLinear

nom d'une couche dont les fonctions de transfert sont linéaires

HalNetworkLayerClassification

nom d'une couche de sortie dont les fonctions de transfert sont exponentielles et renormalisées de sorte que leur somme soit égale à 1

 

Par la suite, la liste de chaque coefficient est affichée, chacun repéré par trois coordonnées :

 

couche

couche à laquelle appartient le coefficient, la couche des entrées n'est pas numérotée, l'indice de la première couche est 0

neurone

neurone de cette couche auquel appartient le coefficient, le premier indice est 0

entrées

entrées de la couche à laquelle correspond le coefficients, le premier indice est 0, l'indice -1 correspond au biais du neurone

 

Exemple de résultat :

 

 

En pratique, ces coordonnées sont facultatives et ne sont données qu'à titre indicatif, une fois un réseau de neurones défini, les autres fonctions ne se servent que de l'en-tête et de la liste des coefficients.

 

Un réseau de neurones de classification en n classes possède également n sorties, chacune retournant les probabilités d'appartenance du vecteur d'entrées aux n classes.

 

 Calculer la sortie d'un réseau de neurones

 

L'utilisation d'un réseau de neurones nécessite tout d'abord la sélection de trois cases exactement (touche Ctrl + clic de souris) :

·       Une case correspond au réseau de neurones, celle contenant le mot HalNetwork.

·       Une case correspond aux vecteurs d'entrées, c'est à dire la première case d'une matrice :

a.     Chaque ligne contient un exemple et autant de cases que le réseau de neurones a d'entrées.

b.     CarréMaths n'utilise pas de plage de sélection, il détecte automatiquement la dernière ligne non vide de la matrice, l'exemple qui suit contient ainsi 6 exemples.

·       Une case vide à partir de laquelle seront stockées les sorties du réseau de neurones.

 

Exemple d'utilisation :

 

 

Résultat :

 

 

S'il y a plus ou moins de trois cases sélectionnées, le programme affichera le message suivant :

 

 

Si aucune des cases sélectionnées n'est vide, le message suivant s'affichera et ce afin d'éviter d'écraser des informations existantes :

 

 

 Erreur d'un réseau de neurones

 

Le calcul de l'erreur d'un réseau de neurones fonctionnent de manière similaire que celle utilisée pour le calcul des sorties, trois cases doivent être sélectionnées :

·       Une case correspond au réseau de neurones, celle contenant le mot HalNetwork.

·       Une case correspond aux vecteurs d'entrées et de sorties désirées, c'est à dire la première case d'une matrice :

c.     chaque ligne contient un exemple et autant de cases que le réseau de neurones a d'entrées et de sorties, le vecteur d'entrées et celui des sorties désirées sont juxtaposés.

d.     CarréMaths n'utilise pas de plage de sélection, il détecte automatiquement la dernière ligne non vide de la matrice, l'exemple qui suit contient ainsi 6 exemples.

·       Une case vide dans laquelle sera stockée l'erreur calculée.

 

Exemple :

 

 

 

Exemple de résultat :

 

 

S'il y a plus ou moins de trois cases sélectionnées, le programme affichera le message suivant :

 

 

 Apprentissage d'un réseau de neurones

 

L'apprentissage d'un réseau de neurones fait intervenir trois étapes :

1. Paramètres de l'apprentissage

2. Lancement de l'apprentissage

3. Contrôle de l'apprentissage

 

 

1. Paramètres de l'apprentissage

 

L'apprentissage d'un réseau de neurones est paramétrable. Pour obtenir et modifier les valeurs qui sont utilisées par défaut, il suffit d'exécuter la fonction Paramètres. Celle-ci recopie à l'endroit de la première case sélectionnée la liste de ces paramètres, leurs valeurs, ainsi qu'un commentaire succinct.

 

 

Cette liste contient les paramètres suivant :

 

Method

Ce paramètre précise la méthode d'optimisation qui doit être employée, il y en a quatre. L'aide mathématique fournit des explications plus exhaustives.

 

STOCHASTIQUE

méthode du gradient stochastique

 

GLOBAL1

méthode du gradient global

 

GLOBAL2_BFGS

méthode du second ordre (BFGS)

 

GLOBAL2_DFP

méthode du second ordre (DFP)

 

NbTry

Nombre de tentatives, au début de chacune sauf la première, les coefficients sont tirés aléatoirement.

 

MaxIter

Nombre d'itérations par tentatives, à chaque itération, l'ensemble de la base d'apprentissage est passée en revue.

 

Weight

Coefficient multiplicatif affecté au gradient avant la mise à jour des poids.

 

WeightDec

Vitesse de décroissance du paramètres Weight, pour 0, Weight ne décroît pas, entre chaque itération, Weight = Weight / (1 + WeightDec * Weight)

 

Stop

Condition d'arrêt, si la décroissance relative (ou la croissance relative) de l'erreur entre deux itérations séparées de nStop est inférieure à Stop, alors l'apprentissage est arrêté.

 

nStop

 

Reason

Précise la condition d'arrêt de l'apprentissage.

 

ReasonStop

 

STOP_LEARN

L'apprentissage s'arrête si l'erreur sur la base d'apprentissage a convergé selon les conditions définies par Weight et WeightDec, le réseau de neurones appris est celui minimisant l'erreur d'apprentissage.

 

STOP_TEST

Soit ea l'erreur moyenne sur les nStop dernières itérations sur la base d'apprentissage et et cette même moyenne sur la base de test, l'apprentissage s'arrête si :

ea* ReasonStop >  et

Dans ce cas, le réseau de neurones appris est celui qui minimise l'erreur sur la base de test.

 

BFGS_TestMax

Nombre d'essais pour trouver le pas de gradient maximum avec une méthode du second ordre

 

BFGS_Fact

Décroissance du poids lors de la recherche d'un extrema (méthodes du second ordre).

 

 

 

 

 

 

 

Remarque :

Si le de la méthode requise pour Method n'est pas l'une des quatre citées ci-dessus, l'apprentissage effectue des itérations sans modifier les poids du réseau de neurones.

 

2. Lancement de l'apprentissage

 

L'apprentissage est lancé de la même manière que les autres fonctions. Entre deux et quatre cases doivent être sélectionnées :

 

1.    Le réseau de neurones doit être sélectionné (case HalNetwork)

2.    La base d'apprentissage doit être sélectionnée, celle-ci correspond à une matrice dont chaque contient un exemple, soit le vecteur d'entrées et le vecteur des sorties désirées, cette base est précédée d'une case contenant le mot "app" et c'est cette case qui doit être sélectionnée.

3.    La base de test peut ou non être sélectionnée, celle-ci correspond à une matrice dont chaque contient un exemple, soit le vecteur d'entrées et le vecteur des sorties désirées, cette base est précédée d'une case contenant le mot "test" et c'est cette case qui doit être sélectionnée.

4.    Les paramètres peuvent ou non être sélectionné si les valeurs par défaut ne vous conviennent pas, c'est la case contenant le mot HalOptimisationGradientParam qui doit être sélectionnée.

 

Le réseau de neurones appris efface le réseau initial.

 

Exemple :

 

 

 

Lorsque le nombre de cases sélectionné est incorrect, le message suivant apparaît :

 

 

 

 

3. Contrôle de l'apprentissage

 

La fenêtre de contrôle de l'apprentissage est accessible depuis la fenêtre propre à CarréMaths, si elle est masquée, il suffit de cliquer sur HalParameterProcessing pour la faire apparaître.

 

 

Cette fenêtre est rafraîchie tous les 500 millisecondes et témoigne de l'avancement de l'apprentissage au travers de plus informations :

 

try

tentative en cours, commence à 0

iter

itération en cours, commence à 0

weight

poids multiplicatif en cours associé au gradient

weightdec

égal à WeightDec au début de chaque tentative mais peut être modifié en cours d'apprentissage

best error, learn

meilleure erreur sur la base d'apprentissage

best error, test

meilleure erreur sur la base de test

best iter, learn

itération ayant permis d'atteindre la meilleure erreur sur la base d'apprentissage

best iter, test

itération ayant permis d'atteindre la meilleure erreur sur la base de test

mean error, learn

moyenne de l'erreur sur la base d'apprentissage sur les nStop dernières itérations

mean error, test

moyenne de l'erreur sur la base de test sur les nStop dernières itérations

global2_init_last

dernière itération pour laquelle la matrice B (voir aide mathématique) associée à une méthode de second ordre a été remise à jour

global2_weight

ordre de grandeur du poids multiplicatif associé au gradient dans les méthodes du second ordre

actual error, learn

erreur actuelle sur la base d'apprentissage

actual error, test

erreur actuelle sur la base de test

 

 

Trois boutons permettent de modifier le cours de l'apprentissage :

 

Pause / Continue

Interrompt et reprend l'apprentissage.

Fin

Quitte l'apprentissage mais conserve la meilleure solution atteinte

Stop

Quitte l'apprentissage et ne conserve pas le meilleure résultat obtenue jusqu'à présent.

 

Il est également possible de modifier certains paramètres de l'apprentissage en cliquant sur l'un des cinq boutons suivant :

 

Essai suivant

Passe à la tentative suivante.

Poids

Modifie la valeur de weight, le poids multiplicatif associé au gradient, ce poids prend la valeur spécifiée dans le champ de saisie situé en bas de la fenêtre d'avancement (voir figure suivante)

Poids décroissance

Modifie la valeur de weightdec, le coefficient gérant la décroissance du poids multiplicatif associé au gradient, ce coefficient prend la valeur spécifiée dans le champ de saisie situé en bas de la fenêtre d'avancement.

Poids produit

Modifie la valeur de weight, le poids multiplicatif associé au gradient, ce poids est multipliée par la valeur spécifiée dans le champ de saisie situé en bas de la fenêtre d'avancement (voir figure suivante)

Tirage aléatoire

Tire de nouveaux coefficients aléatoirement.

 

 

 

Enfin, si l'option Aide est sélectionnée dans la liste située à gauche dans la fenêtre d'avancement, le champ de saisie reçoit un message d'aide relatif au bouton enfoncé, ci-dessous, le message d'aide apparu après avoir pressé le bouton Poids.

 

 

  

 

 Obtention d'un code C++ associé à un réseau de neurones

 

Afin de pouvoir utiliser les réseaux de neurones appris grâce à CarréMaths, il est possible de récupérer un code écrit en C++ implémentant un réseau de neurones dont les coefficients sont justement ceux écrits dans la feuille de calcul. Pour ce faire, il suffit de sélectionner la case HalNetwork du réseau de neurones et une case vide à partir de laquelle sera écrit le code C++.

 

Exemple :

 

 

 

La fenêtre suivante apparaît :

 

 

Elle demande tout d'abord un nom de classe pour le réseau de neurones à générer ainsi que les trois informations suivantes :

 

Totalité du code

L'implémentation proposée utilise deux classes. La première définit les structures ainsi que l'algorithme de rétropropagation, cette classe est commune à tous les réseaux de neurones générés par CarréMaths. La seconde dérive de la première et se contente d'initialiser les coefficients du réseau. Lorsque cette case est cochée, le code relatif aux deux classes est généré, dans le cas contraire, seul le code relative à la classe fille est généré.

Format du résultat

Trois choix sont possibles :

Code sous Excel

Retourne le résultat dans Excel, il faut pour cela qu'une case vide soit sélectionnée, les paramètres sont également insérées dans le code.

Code dans des fichiers

Génère cinq fichiers, quatre fichiers de code, deux par classes, et un fichier contenant les paramètres du réseau de neurones.

Fichiers de paramètres

Parmi les cinq fichiers précédemment cités, ne génère que le fichier de paramètres.

Répertoire

Répertoire où seront stockés les fichiers précédemment cités. Le bouton Change permet de sélectionner ce répertoire.

 

Lorsque le bouton Change est actionné, il fait apparaître une fenêtre classique de sélection de répertoire :

 

 

 

Exemple de résultat :

 

 

 

Voici le code global généré pour le réseau de neurones de l'exemple précédent :

 

///////////////////////////////////////////////////////////////////////////////////////////////////

// file CarreMaths_NN_User.h

///////////////////////////////////////////////////////////////////////////////////////////////////

 

#ifndef CARREMATHS_CARREMATHS_NN_USER_H

#define CARREMATHS_CARREMATHS_NN_USER_H

 

#include "CarreMaths_nn.h"

 

class CarreMaths_NN_User : public CarreMaths_NN

{

  public :

    CarreMaths_NN_User () ;

    virtual double Error (double * in, double *out) ;

    virtual void Apply (double * in, double *out) ;

} ;

 

#endif

 

 

///////////////////////////////////////////////////////////////////////////////////////////////////

// file CarreMaths_NN_User.cpp

///////////////////////////////////////////////////////////////////////////////////////////////////

 

#include "CarreMaths_NN_User.h"

 

CarreMaths_NN_User::CarreMaths_NN_User ()

{

  int neuron [3] ;

  CarreMaths_Function * f [3] ;

 

    double coef [] = {    -1.5941722459841,

    -0.038908667668976,

    -0.20401223026573,

    -0.84168511620949,

    0.046307200231557,

    0.024761690253819,

    -0.31174887849895,

    0.2275462549253,

    -0.21449601890745,

    0.64758146929593,

    0.37444913556589,

    0.38351242543835,

    0.22962356034314,

    0.231395013245,

    -0.15631208372586,

    0.50292490595199,

    -0.19303224698745} ;

 

 

  f [0]    = 0 ;

  neuron [0]  = 2 ;

  f [1]    = CarreMaths_sig ;

  neuron [1]  = 4 ;

  f [2]    = CarreMaths_lin ;

  neuron [2]  = 1 ;

 

  Create (3, neuron, f) ;

 

  int n ;

  for (n = 0 ; n < GetNbC () ; ++n) operator [] (n) = coef [n] ;

}

 

double CarreMaths_NN_User::Error (double * in, double *out)

{

    return CarreMaths_NN::Error (in, out) ;

 

}

 

void CarreMaths_NN_User::Apply (double * in, double *out)

{

    CarreMaths_NN::Apply (in, out) ;

 

}

 

 

///////////////////////////////////////////////////////////////////////////////////////////////////

// file CarreMaths_nn.h

///////////////////////////////////////////////////////////////////////////////////////////////////

 

#ifndef CARREMATHS_NN_H

#define CARREMATHS_NN_H

 

typedef double CarreMaths_Function (double) ;

 

/** classe de réseaux de neurones */

class CarreMaths_NN

{

  protected :

 

    int NbL ;                                 //!< nombre de couches

    int NbC ;                                 //!< nombre de coefficients

    int * NbN ;                               //!< nombre de neurones sur chaque couche (NbN [0] pour les entrées, ..., NbN [NbL-1] pour les sorties)

    CarreMaths_Function ** F ;                //!< pointeur sur les fonctions de transfert de chaque couche

    double * C ;                              //!< pointeur sur les coefficients

 

  public :

 

    int GetNbC () ;                           //!< retourne le nombre de coefficients

    int GetNbIn () ;                          //!< retourne le nombre d'entrées

    int GetNbOut () ;                         //!< retourne le nombre de sorties

    double & operator [] (int n) ;            //!< retourne une référence sur le coefficient n

 

  public :

 

    CarreMaths_NN () ;                        //!< constructeur vide

    CarreMaths_NN (CarreMaths_NN &copy) ;     //!< constructeur de copie

 

    /** constructeur

    *

    *     @param        nb          nombre de couches

    *     @param        neuron      nombre de neurones sur chaque couche :

    *                                   - neuron [0] : nombre d'entrées

    *                                   - neuron [1] : nombre de neurones sur la première couche

    *     @param        f           tableau de pointeurs de fonctions de transfert :

    *                                   - f [0] : importe peu

    *                                   - f [1] : fonction de transfert de la première couche

    */

    CarreMaths_NN (int nb, int * neuron, CarreMaths_Function ** f) ;

 

    ~CarreMaths_NN () ;                       //!< destructeur

 

 

    CarreMaths_NN & operator = (CarreMaths_NN &copy) ;                //!< affectation

 

    /** applique le réseau de neurones

    *   @param          in          vecteur des entrées (NbN [0])

    *   @param          out         vecteur où sont stockées les sorties (NbN [NbN-1])

    */

    void Apply (double * in, double *out) ;

 

    /** retourne l'erreur de prévision, ici, c'est l'erreur quadratique qui est implémentée

    *   @param          in          vecteur des entrées (NbN [0])

    *   @param          out         vecteur des sorties désirées

    *   @return                     erreur de prévision

    */

    double Error (double * in, double *out) ;

 

  protected :

 

    void Create (int nb, int * neuron, CarreMaths_Function ** f) ; //!< voir le constructeur qui a les mêmes paramètres

 

} ;

 

inline int        CarreMaths_NN::GetNbC ()              { return NbC ; }

inline int        CarreMaths_NN::GetNbIn ()             { return NbN [0] ; }

inline int        CarreMaths_NN::GetNbOut ()            { return NbN [NbL-1] ; }

inline double &   CarreMaths_NN::operator [] (int n)    { return C [n] ; }

 

double CarreMaths_exp (double x) ;

double CarreMaths_lin (double x) ;

double CarreMaths_sig (double x) ;

 

#endif

 

 

///////////////////////////////////////////////////////////////////////////////////////////////////

// file CarreMaths_nn.cpp

///////////////////////////////////////////////////////////////////////////////////////////////////

 

#include "CarreMaths_nn.h"

#include <memory.h>

#include <math.h>

 

double CarreMaths_exp (double x)

{

  if (x < 1e-50) return 0 ;

  else if (x > 1e50) return 1e300 ;

  else return ::exp (x) ;

}

 

double CarreMaths_lin (double x)

{

  return x ;

}

 

double CarreMaths_sig (double x)

{

  if (x < -1e50) return -1 ;

  else if (x > 1e50) return 1 ;

  else {

    double y = 2.0 / (1.0 + ::exp (-x)) - 1.0 ;

    return y ;

  }

}

 

CarreMaths_NN::CarreMaths_NN ()

{

  NbC   = 0 ;

}

 

CarreMaths_NN::CarreMaths_NN (CarreMaths_NN &copy)

{

  NbC   = 0 ;

  *this = copy ;

}

 

CarreMaths_NN::~CarreMaths_NN ()

{

  if (NbL > 0) {

    delete [] F ;

    delete [] NbN ;

    delete [] C ;

  }

}

 

CarreMaths_NN & CarreMaths_NN::operator = (CarreMaths_NN &copy)

{

  if (NbL > 0) {

    delete [] F ;

    delete [] NbN ;

    delete [] C ;

  }

 

  NbL   = copy.NbL ;

  NbC   = copy.NbC ;

 

  NbN   = new int [NbL] ;

  F     = new CarreMaths_Function* [NbL] ;

  C     = new double [NbC] ;

 

  memcpy (NbN,  copy.NbN, NbL * sizeof (int)) ;

  memcpy (F,    copy.F,   NbL * sizeof (CarreMaths_Function*)) ;

  memcpy (C,    copy.C,   NbC * sizeof (double)) ;

 

  return *this ;

}

 

CarreMaths_NN::CarreMaths_NN (int nb, int * neuron, CarreMaths_Function ** f)

{

  Create (nb, neuron, f) ;

}

 

void CarreMaths_NN::Create (int nb, int * neuron, CarreMaths_Function ** f)

{

  NbL   = nb ;

  NbN   = new int [NbL] ;

  F     = new CarreMaths_Function* [NbL] ;

 

  memcpy (NbN,  neuron, NbL * sizeof (int)) ;

  memcpy (F,    f,      NbL * sizeof (CarreMaths_Function*)) ;

 

  NbC = 0 ;

  int n ;

  for (n = 1 ; n < nb ; ++n) {

    NbC += NbN [n] * (NbN [n-1]+1) ;

  }

 

  C     = new double [NbC] ;

}

 

 

void CarreMaths_NN::Apply (double * in, double *out)

{

  double * temp   = new double [NbC] ;

  double * pin    = in ;

  double * pts    = temp ;

  double * c      = C ;

  double * other  = temp + NbC/2 ;

 

  double * pt ;

  double * kpin ;

 

  int n,i,j,ln,lb ;

  for (n = 1 ; n < NbL ; ++n) {

 

    pt    = pts ;

    lb    = NbN [n-1] ;

    ln    = NbN [n] ;

 

    for (i = 0 ; i < ln ; ++i, ++pt) {

      kpin  = pin ;

      *pt   = *c ;

      ++c ;

      for (j = 0 ; j < lb ; ++j, ++kpin, ++c) *pt += *kpin * *c ;

      *pt = (*(F [n])) (*pt) ;

    }

 

    pin = pts ;

    pts = (n == NbL-2) ? out : ((pts == temp) ? other : temp) ;

  }

 

  delete [] temp ;

}

 

double CarreMaths_NN::Error (double * in, double *out)

{

  int n = NbN [NbL-1]-1 ;

  double * z = new double [n+1] ;

  Apply (in, z) ;

  double res = 0 ;

  double t ;

  double *pz,*pout ;

  pz = z + n ;

  pout = out + n ;

  for ( ; n >= 0 ; --n, --pz, --pout) {

    t = *pz - *pout ;

    res += t * t ;

  }

  delete [] z ;

  return res ;

}