next up previous contents
suivant: Traitement du signal Création monter: SOUTENANCE FINALE MARVIN (Modest-encoding précédent: Le projet   Table des matières

Sous-sections

Structures d'apprentissage &
Réseaux neuronaux
Reconnaissance de l'empreinte vocale

Introduction

Cette partie expose le fonctionnement du système de vérification du locuteur de MARVIN. Outre la présentation du schéma général et de la sélection des empreintes vocales nous développerons également l'étude, l'implémentation et les premiers tests de notre structure d'apprentissage. Celle-ci est basée sur les réseaux de neurones, une technique peu courante en vérification du locuteur bien que ceux-ci présentent des caractéristiques très intéressantes leur permettant de fonctionner en traitement du signal.
Nous nous sommes intéressé dans un premier temps aux réseaux de neurones de type perceptron multi-couches, l'élaboration de celui-ci n'a pas posé de problèmes particuliers (du fait des nombreuses documentation existant à son sujet) mais lors de la phase de test ceux-ci se sont révélés peu performant nous avons donc dû développer un autre type de réseau de neurone le TDNN (Time Delay Neural Network ou réseau à convolution) qui, comme nous le verrons par la suite possède d'intéressantes propriétés de robustesse vis-à-vis en particulier de la translation. Mais l'implémentation du TDNN a posé de nombreux problèmes du fait de la difficulté de trouver des informations sur celui-ci.
La suite de cette partie présente les différentes optimisations que nous avons effectué sur le TDNN et l'inter-opérabilité C/CAML.

Schéma général de fonctionnement

Apprentissage des empreintes - Création du TDNN

\includegraphics[]{apprentissage.ps}

Vérification de l'utilisateur

\includegraphics[]{recognition.ps}

La sélection des empreintes vocales

Introduction

Afin de pouvoir procéder par la suite à une reconnaissance efficace nous devons avant tout "apprendre" des échantillons de voix "justes". En effet, lors de l'enregistrement il est possible qu'il y ait une perturbation, un bruit de fond rendant cet échantillon de voix faux. Il nous est donc rapidement apparut la nécessité de mettre au point un mécanisme de sélection des empreintes vocales qui seront apprises par le réseau de neurone.
Nous avons dans un premier lieu recherché des algorithmes servant à la classification tel la LVQ (Linear Vector Quantization) mais nous nous sommes rapidement rendu compte que de telles méthodes n'étaient pas nécessaires pour un problème aussi simple. En effet nous devons avant tout reconnaître une forme aussi simplement que possible et l'une des méthodes que nous avons développé en un premier temps est de baser cette reconnaissance sur les variations de l'empreinte. Cependant cette méthode nous a vite montré ses limites étant donné qu'elle ne prend pas compte les amplitudes. Nous allons ci-après vous détailler le fonctionnement de celle-ci et présenter la nouvelle méthode de préselection adoptée.

Première méthode

Afin de détecter les variations nous devons concevoir un algorithme permettant de reconnaître un pic dans un signal. Pour cela nous définissons les formes primitives :
\includegraphics[height=2cm]{prim.ps}
Par la suite nous définissons un pic par les prédicats suivant :


Nous allons donc traduire notre empreinte vocale en un mot composé d'un alphabet de trois lettres dans lequel nous trouvons la grammaire suivante :


Par la suite le fonctionnement de l'algorithme est simple : après la traduction de toutes les empreintes vocales en mots nous sélectionnons le mot qui ressort le plus souvent comme étant la bonne empreinte.

Exemple

\includegraphics[height=10cm]{emp.ps}
six empreintes du même sujet
Dans les six empreintes suivantes nous souhaitons sélectionner les ``meilleures''. L'algorithme nous donne les résultats suivants :


Finalement les empreintes sélectionnées seront les empreintes 1, 3, 4 et 6 ce qui paraît cohérent.

Deuxième méthode

La nouvelle méthode utilisée est basée sur l'utilisation du TDNN et de la reconnaissance des empreintes, cela constitue donc un système de sélection beaucoup plus proche des objectifs que nous nous étions fixé soit l'identification de l'empreinte. Son fonctionnement est simple : il s'agit pour chaque empreinte de créer le TDNN associé et de tester l'ensemble des empreintes avec ce réseau neuronal en notant les empreintes reconnues. Au final nous sélectionnons l'empreinte qui a en reconnu le plus ainsi que les empreintes reconnues.
Exemple : Soit un ensemble de dix empreintes $m0 \dots m9$ Nous sélectionnerons donc ici les empreintes $m1,m3,m4,m5,m6,m8,m9$.

Conclusion

Nous avons donc réussi à mettre au point un mécanisme simple nous permettant de sélectionner les empreintes les plus réussies. Par la suite nous calculons la moyenne des empreintes sélectionnées, celle-ci constituera l'empreinte générale qui servira à la création du TDNN.

Le perceptron multi-couches (PMC)

Un réseau neuronal repose sur la notion de graphe; un graphe consiste en des noeuds représentant les agents, et des connexions représentant les interactions entre ces agents. Les connexions sont le plus souvent "plastiques", en ce sens que les poids liés à ces connexions sont modifiables. Ils consistent en des modèles plus ou moins inspirés du fonctionnement cérébral de l'être humain, en se basant particulièrement sur le concept de neurone. Un neurone est une cellule particulière possédant des extensions par lesquelles il peut distribuer des signaux (axones) ou en recevoir (dendrites).

Fondements théoriques

Structure

Un perceptron multi-couches est un réseau formé de couches (i.e. groupe de neurones) avec une connectivité particulière, chaque couche est reliée à la suivante (chaque couches reliées entre elles ont une connectivité totale). Par exemple pour un réseau à trois couches la première est reliée à la deuxième qui est reliée à la troisième. La première couche s'appelle couche d'entrée, la dernière est nommée couche de sortie et les couches intermédiaires sont désignées par le terme de "couches cachées".
\includegraphics[]{pmc.ps}

Notations

On définit un perceptron multi-couches par :


\includegraphics[height=5cm]{neurone.ps}

Apprentissage

L'apprentissage dans un perceptron multi-couches est de type supervisé c'est à dire que l'on procède par correction d'erreur (i.e. nous "donnons" au réseau un vecteur d'entrée, le propageons puis calculons l'erreur en sortie par rapport à un vecteur de "sorties désirées" afin de modifier les poids en fonction de cette erreur).
L'apprentissage dans un perceptron peut donc se décomposer en quatre phases :



L'apprentissage va donc consister en une minimisation d'une fonction d'erreur en utilisant l'algorithme de rétro-propagation de l'erreur qui repose sur une minimisation par descente de gradient d'un critère d'erreur de type moindres-carrés.

\includegraphics[]{rppmc.ps}

Notations

Pour cette partie, de nouvelles notations s'imposent :

Propagation

Soit $e$ le vecteur des signaux d'entrée du réseau et $o$ le vecteur des signaux de sortie. La phase de propagation est assez simple car il s'agit simplement de remplacer les activations des neurones d'entrée par les valeur de $e$ puis de propager le signal en procédant couches par couches. Par exemple, pour un réseau à 2 couches cachées, les valeurs du vecteur des signaux de sortie $o$ sont égales à :

\begin{displaymath}o = W_{3}.S(W_{2}.S(W_{1}.e))\end{displaymath}

avec $S$ la fonction de transfert du réseau et $W_{i}$ la matrice de connexion reliant la couche $i$ à la couche $i+1$.

Fonction de coût

L'erreur en sortie correspond à une fonction de coût notée C (fonction d'erreur), que l'on doit minimiser au cours de l'apprentissage. Posons ici la fonction appelée critère des moindres-carrés :

\begin{displaymath}C = 1/2 \sum_{x \in A}\sum_{i \in à N}(d_{i}^{x} - x_{i}^{x})^2\end{displaymath}

Cette fonction d'erreur n'est pas la seule possible, toute fonction dérivable en $d$ et $x$ est valable.

Rétro-propagation de l'erreur

Un des points important dans la conception d'une application à base de réseaux de neurones concerne la méthode d'apprentissage. Celle-ci doit idéalement nous permettre de converger rapidement vers le minimum global de la fonction de coût choisie. Cependant les méthodes permettant de trouver le minimum global d'une fonction sont en général très lentes en considérant le nombre de paramètres que nous devrons prendre en compte. Nous avons donc utilisé l'algorithme de rétro-propagation du gradient qui ne garantit pas d'aboutir au minimum global de la fonction $C$, mais celui-ci est beaucoup plus rapide et est donc utilisable dans le cadre de notre application.
Le but de l'algorithme de rétro-propagation du gradient est de minimiser notre fonction de coût (i.e. de minimiser l'erreur). Pour cela il utilise le concept de dérivées partielles et calcule le gradient de la fonction de coût qui fournit une indication sur la direction et l'amplitude du changement à effectuer sur les poids $w$ pour minimiser $C$. En effet cet algorithme utilise le fait qu'en tout point le gradient est normal aux lignes de niveau ce qui permet de connaître la direction d'un des minimum de $C$.
\includegraphics[]{gradient.ps}
La modification des poids suivra donc une loi du type :

\begin{displaymath}\Delta w = - \eta \frac{\partial{C}}{\partial{w}}\end{displaymath}

La complexité de cet algorithme est donc fonction du nombre de paramètres (poids) à traiter et augmente très vite avec la taille du réseau neuronal.

Algorithme

L'algorithme se décompose donc en quatre phases : On note le gradient

\begin{displaymath}
G_{i}^{t} = \frac{\partial C}{\partial w_{ij}^t}
\end{displaymath}

On a vu plus haut que

\begin{displaymath}
\Delta w = - \eta \frac{\partial C}{\partial w}
\end{displaymath}

or

\begin{displaymath}
G_{i}^{t} = \frac{\partial C}{\partial x_{i}^{t}}\frac{\partial x_{i}^{t}}{\partial w_{ij}^t}
\end{displaymath}

avec

\begin{displaymath}
\frac{\partial x_{i}^{t}}{\partial w_{ij}^t} = S'(sum_{i}^{t})x_{j}^{t}
\end{displaymath}

On pose le terme d'erreur

\begin{displaymath}
\gamma_{i}^{t} = -\frac{\partial C}{\partial x_{i}^{t}}
\end{displaymath}

finalement

\begin{displaymath}
\Delta w = \eta \gamma_{i} S'(sum_{i}^{t})x_{j}^{t}
\end{displaymath}

Ceci est la forme générale de la correction d'erreur à effectuer quelque soit la couche. Cependant le terme d'erreur varie selon que nous soyons sur la dernière couche ou sur une des couches cachées.
Ce terme d'erreur $\gamma_{i}$ peut être obtenu récursivement par rétro-propagation.

Méthodes de correction des poids

On peut effectuer la modification des poids selon plusieurs méthodes, les deux principales diffèrent selon la manière de calculer le gradient, soit en utilisant le gradient total c'est à dire que l'on modifie les poids une fois l'ensemble des exemples $A$ traités (on somme les modifications durant tout l'apprentissage de $A$ puis on met a jour les poids à la fin d'une époque). L'autre méthode qui est appelée la rétro-propagation du gradient stochastique utilise un gradient partiel c'est à dire que les modifications de poids se font au fur et à mesure de l'apprentissage (après chaque propagation d'un exemple on modifie les poids).

Implémentation

Définition

Un patron est un vecteur qui englobe l'ensemble des exemples d'apprentissage.
Un corpus est un ensemble d'apprentissage il contient plusieurs patrons comportant les vecteurs d'entrée et les vecteurs de sorties désirées correspondants.

Structure

/* Pour les patrons */

typedef struct
{
        double          *donnees;
        int             nombre, largeur;
  /* La largeur est utilisée afin de retrouver le nième patron */
} t_patrons;

/* Pour les corpus */

typedef struct
{
        int             nombre_patrons;
        t_patrons       entrees, sorties_desirees;
} t_corpus;

/* Pour les couches */

typedef struct
{
        int             nombre_neurones;
        int             premier_neurone;
} t_couche_neurones;

/* Pour les réseaux */

typedef struct
{
        /* Neurones */
  int             nombre_neurones;
  double          *activation;
  double          *activation_ponderee;
  double          *erreur;
  double          *erreur_ponderee;
  double          *entree;
  double          *sortie_desiree;
  
        /* Liens */
  int             nombre_liens;
  double          *poids;

        /* Couches de Neurones */
  int             nombre_couches;
  t_couche_neurones *couches;
} t_reseau;

Optimisations

Dans un premier temps le PMC a été conçu avec un type de donnée hétérogène (de type structure lourde avec pointeurs et allocations). Celui-ci marchait mais était très lent, nous l'avons donc modifié et recodé entièrement en ce que nous pourrions appeler une structure homogène (la structure utilisée la plus lourde est le vecteur). L'apprentissage consiste donc uniquement en une modification d'un tableau de liens et de seuils. La vitesse en a été considérablement augmentée.

Tests

Lors des premiers test nous avons testé l'apprentissage d'une fonction de type sinus entre 0 et 1 en lui donnant en entrée uniquement 500 exemples de cette fonction dans l'intervalle donné. Nous avons réussi avec un réseau de type [1,20,20,1] à obtenir une approximation de la fonction à 0.02 près. Cependant lors du test de l'apprentissage d'une empreinte vocale le système reconnaît très bien l'empreinte de base qui a servi à l'apprentissage mais il suffisait de décaler le signal pour que les résultats soient faux.

Conclusion

L'utilisation du perceptron multi-couches classique dans le cadre de l'identification d'un signal paraît donc proscrite, et nous impose donc une structure plus évoluée évoquée précédement le Time Delay Neural Network (TDNN). Cette structure devra prendre en compte une notion de temps afin de pouvoir être adaptée à l'apprentissage d'un signal vocal.

TDNN (Time delay neural network : réseaux de neurones à délai)

Introduction

Nous avons donc dû rechercher un type de réseau de neurone qui pouvait correspondre à nos contraintes : une robustesse vis-à-vis de la translation mais aussi une capacité de généralisation. Lors de nos recherches au début de l'année nous avions déjà rencontré ce type de réseau de neurones en particulier dans la thèse de Benani (1992). Les TDNN ont été développé à l'origine dans le cadre de la reconnaissance de phonème (Lang et Hinton 1988 ; Waibel 1989) mais ont été depuis adaptés à la reconnaissance vocale (Bottou 1990) et à la reconnaissance de l'écriture manuscrite (Guyon 1991).
Le TDNN se singularise d'un perceptron multi-couches classique par le fait qu'il prend en compte une certaine notion de temps. C'est à dire qu'au lieu de prendre en compte tous les neurones de la couche d'entrée en même temps il va effectuer un balayage temporel. La couche d'entrée du TDNN prend une fenêtre du spectre et balaye l'empreinte. Le TDNN permet ainsi de reconnaître le signal tout en étant moins strict que le PMC classique (c'est à dire qu'il pourra y avoir des petits décalages).

Fondements théoriques

Structure

Les TDNN sont constitués comme les PMC d'une couche d'entrée, de couches cachées et d'une couches de sortie mais ils se différencie de part l'organisation des liaisons inter-couches. Les TDNN introduisent des contraintes qui leurs permettent d'avoir un certain degré d'invariance par décalage temporel et déformation. Celles-ci utilisent trois idées : poids partagés, fenêtre temporelle et délai.


De plus chaque couche a deux direction : une direction temporelle et une direction caractéristique.

\includegraphics[]{tdnn.ps}

Fonctionnement

Le but du TDNN est non pas d'apprendre basiquement le signal mais d'extraire les caractéristique de celui-ci. La première couche acquière le signal puis une ou plusieurs couches cachées transforment le signal en des vecteurs de caractéristiques. Un neurone donné détecte une caractéristique locale de la variation de la courbe. Le champ de vision du neurone est restreint à une fenêtre temporelle limitée. Avec la contrainte des poids partagés, le même neurone est dupliqué dans la direction temps (la même matrice de poids dupliquée) pour détecter la présence ou l'absence de la même caractéristique à différentes places le long du signal. En utilisant plusieurs neurones à chaque position temporelle, le réseau de neurone effectue la détection de caractéristiques différentes : les sorties des différents neurones produisent un nouveau vecteur caractéristique pour la couche supérieure.
La composante temporelle du signal d'origine est peu à peu éliminée au fur et à mesure de sa transformation en caractéristique par les couches supérieures, pour compenser cette perte d'information on augmente le nombre de neurones dans la direction caractéristique.

Apprentissage

Pour l'apprentissage du TDNN nous utilisons le classique algorithme de rétro-propagation du gradient mais dans sa version stochastique (c'est à dire que les poids sont mis à jour à chaque exemple). Les phases de calcul sont les mêmes que celle décrites précédemment.

Implémentation

Ayant besoin de faire un certain nombre de test pour déterminer la topologie qui sera la plus adaptée à notre application nous avons eu besoin, comme pour le perceptron multi-couches, de développer non pas un réseau neuronal particulier mais un simulateur neuronal nous permettant de le modeler à loisir. Nous avons suivi le même mode de conception que pour le PMC, mais nous avons rencontré des difficultés pour la rétro-propagation de l'erreur, plus subtile que dans le PMC.
Pour l'implémentation en elle même, nous avons utilisé un pas de $0.01$, initialisé les biais et les poids à des valeurs entre $-0.1$ et $0.1$.

Définition

Le type TDNN se caractérise par : Un neurone est caractérisé par sa couche l, sa caractéristique f, et son emplacement temporel t.

Structure


#define MAXTEMP 12
#define MAXFEAT 8
#define MAXFIELD 5
#define MAXLAYERS 3
#define MAXDELAY 1

typedef struct s_neuron
{
  float x[MAXTEMP][MAXFEAT];
  float d[MAXTEMP][MAXFEAT];
  //  float w[MAXFEAT][MAXFEAT][MAXFIELD];
  float ***w;
  float w_biais[MAXFEAT];
  float sum[MAXTEMP][MAXFEAT];
  float e[MAXTEMP][MAXFEAT];
  //  float delta[MAXFEAT][MAXFEAT][MAXFIELD];
  float ***delta;
} t_neuron;

typedef struct s_layer
{
  int window_t;
  int nb_feat;
  int field_t;
  int delay;
  t_neuron *neurons;
} t_layer;

typedef struct s_tdnn
{
  int nb_layers;
  t_layer **layers;
} t_tdnn;

typedef struct s_patrons
{
  float *donnees;
  int nombre, largeur;
} t_patrons;

typedef struct s_corpus
{
  int nombre_patrons;
  t_patrons entrees, sorties_desirees, test;
} t_corpus;

Tests

La topologie sur laquelle nous nous sommes arrêtés, pour l'instant, est : Nous n'avons fait pour l'instant que quelques tests, mais ceux-ci paraissent encourageant et donnent d'assez bon résultats.

Conclusion

Nous disposons maintenant d'une structure d'apprentissage qui correspond aux critères que nous nous étions fixé dans le cahier des charges. Je profiterais à présent de l'avance que j'ai dans le planning pour mettre au point une méthode permettant d'adapter le pas d'apprentissage au fur et à mesure de celui-ci et pour faire d'autres recherches sous MatLab afin d'optimiser notre post-traitement.

Le TDNN : optimisations

Optimisation du TDNN en CAML

Problématique

L'implémentation du TDNN en C a posé de nombreux problèmes au niveau des tests d'architectures. En effet les erreurs tels que les débordements de tableau ne sont pas gérées en langage C ce qui nous a amené à de nombreuses difficultés tels que des bogues ne se révélant qu'en produisant des résultats incohérents. Afin de pouvoir tester différentes architectures de TDNN en toute ``sécurité'' (nous voulons dire par là que les propagations et rétropropagation sont justes) nous avons recodé le TDNN entièrement en CAML. CAML offre au niveau de la gestion des gros tableau multidimensionnels des fonctions très pratiques, de plus CAML ne laisse passer aucune erreur de débordement.

Implémentation

Nous avons eu de nombreuses difficultées lors de cette traduction/correction du C vers le CAML. En effet nous avons dû mélanger deux styles de programmation : la programmation impérative et récursive. La puissance du langage CAML nous a néanmoins agréablement surpris nous offrant, grâce à la programmation par objet, un environnement de simulation du TDNN très abouti. De plus les possibilités d'interfacage entre C et CAML nous permettrons d'intégrer directement notre code CAML dans notre code C.

Type TDNN

Le TDNN est considéré comme un objet ce qui permet une gestion de celui-ci beaucoup plus rapide.
class tdnn (nb_couches_i,nb_t_i,nb_c_i,fenetre_t_i,delai_i,entrees_i,sorties_i,
            sigmoide_i,derivee_i) = object
    val nb_couches = nb_couches_i
    val nb_t = nb_t_i
    val nb_c = nb_c_i
    val fenetre_t = fenetre_t_i
    val delai = delai_i
    val entrees = entrees_i
    val sorties = sorties_i
    val mutable activation = [|[|[||]|]|]
    val mutable poids = [|[|[|[||]|]|]|]
    val mutable somme = [|[|[||]|]|]
    val mutable erreur = [|[|[||]|]|]
    val mutable gradient = [|[|[|[||]|]|]|]
    val sigmoide = sigmoide_i
    val derivee = derivee_i
    method init = activation <- init_tri (nb_couches,nb_t,nb_c);
      somme <-  init_tri (nb_couches,nb_t,nb_c);
      erreur <-  init_tri (nb_couches,nb_t,nb_c);
      poids <- init_qua (nb_couches,nb_c,fenetre_t);  
      gradient <- init_qua (nb_couches,nb_c,fenetre_t);
    method rand_poids = f_rand_poids(poids)
    method donner_activation n = f_donner_activation (activation,entrees,n,
                                                      List.nth nb_t 0,List.nth nb_c 0)
    method propager_activation = f_propager_activation (activation,somme,poids,nb_c,
                                                        fenetre_t,delai,sigmoide)
    method calculer_erreur_sortie n = f_calculer_erreur_sortie (erreur,activation,
                                                                sorties,n)
    method calculer_gradient eta = f_calculer_gradient (erreur,activation,somme,
                                                        poids,eta,delai,derivee,
                                                        nb_couches,nb_c,fenetre_t)
    method get_erreur_reseau = f_erreur_reseau (erreur)
    method get_nb_couches = nb_couches
    method get_activation = activation
    method get_somme = somme
    method get_poids = poids
    method get_nb_t = nb_t
    method get_nb_c = nb_c
    method get_fenetre_t = fenetre_t
    method get_delai = delai
    method get_sigmoide = sigmoide
    method get_erreur = erreur
    method get_gradient = gradient
    method get_entrees = entrees
   end;;
Ainsi un apprentissage se résume ensuite simplement à la suite de commandes :
network#init;;
network#rand_poids;;
epoque(network,100000,0.01);;

Inter-opérabilité C/CAML

Afin de pouvoir utiliser le TDNN créé en CAML à partir de C (i.e. pouvoir appeller les différentes fonctions d'apprentissage et de reconnaissance) nous avons dû créer une 'interface' entre les deux langages. En effet nous avons été exposé à de nombreuses difficultées dont la plus importante était le passage de paramètres de C à CAML et vice versa. Mais CAML dispose d'énormement de fonctions de conversions entre les deux types de gestion de mémoire.

Conclusion

Finalement cette implémentation du TDNN en CAML, loin d'être une perte de temps, s'est révélée très précieuse à l'évolution de Marvin car en plus d'une belle expérience de la puissance du langage CAML il nous a permis de concevoir un environnement d'expérimentation pour l'apprentissage des empreintes vocales très agréable à utiliser et beaucoup plus sûr.

Modification du pas en temps réel

Introduction

Afin d'améliorer l'apprentissage nous avions exposé dans le dernier rapport la nécessité de mettre au point une méthode permettant de réguler le pas d'apprentissage au fur et à mesure de celui-ci ce qui aurait comme conséquence des résultats encore meilleurs et plus rapidement (en effet nous pouvons commencer avec un pas élevé et le baisser quand le besoin s'en fait ressentir). Dans cet objectif plusieurs méthodes ont été imaginées et mises en oeuvre, nous allons vous les présenter ci-dessous.

Problématique

Dans un premier temps nous avions pensé à faire baisser le pas d'apprentissage à chaque fois que l'erreur en sortie du réseau neuronal remontait. Cependant lors des tests il nous est vite apparut que cette méthode n'était pas appliquable à notre problème. En effet, la phase d'apprentissage d'un réseau neuronal passe souvent par des phases de tâtonnement dans lesquelles le réseau neuronal ``désapprend'' avant de retomber dans une descente de l'erreur meilleure qu'auparavent (on sort en quelques sorte d'un minimum local). En appliquant cette méthode le pas diminuait donc à chaque fois que le réseau neuronal ``tâtonnait'' supprimant ou ralentissant donc cette phase bénéfique pour l'apprentissage. Finalement nous devions trouver une méthode de contrôle du pas qui répondait à plusieurs contraintes :



Nous devions donc concevoir une modification de pas prenant en compte le temps d'apprentissage et l'erreur, permettant de beaucoup tâtonner au début de l'apprentissage et d'affiner au maximum à la fin de celui-ci. Il nous est alors revenu en mémoire un principe que le cour de Micro-informatique de Sup nous avait présenté et qui correspond parfaitement aux contraintes que nous nous sommes posés : le recuit simulé.

Le recuit simulé

Introduction

Le principe du recuit simulé repose sur une analogie avec la thermodynamique qui a été révélée par les travaux simultanés de S. Kirkpatrick, C.D. Gelatt Jr. M.P. Vecchi et d'autre part par V. Cerny vers 1982 à partir de la méthode de Métropolis (1953). Il repose sur le fait qu'une dégradation provisoire peut entraîner une amélioration plus tard. On peut donc autoriser une transformation locale provoquant une augmentation de la fonction d'erreur avec une certaine probabilité.

Une analogie avec la thermodynamique - Le recuit métallurgique

L'état d'un matériau dépend de la température à laquelle il est porté, or la distribution de Gibbs-Boltzmann mesure la probabilité $P(X)$ de visiter l'état $X$ en fonction de son énergie $E(X)$ et de la température $T$ ($k$ est la constante de Boltzmann et $N(t)$ la fonction de partition) :

\begin{displaymath}P(x) = \frac {e ^{-\frac {E(X)}{kT}}} {N(T)}\end{displaymath}

On peut observer que pour $T$ fixé, un état est d'autant plus probable que son énergie est faible. De plus, si $T$ est élevé par rapport à $E(X)$ la probabilité d'observer un état donné sera à peu près la même quel que soit l'état. A l'inverse si $T$ est faible les états de faible énergie deviendront plus probables et à température presque nulle seuls les états à énergie nulle ont une probabilité non négligeable d'être observés. Ces propriétés sont utilisées en métallurgie afin de produire des cristaux qui ont une configuration d'énergie minimum. En effet si on veut obtenir un cristal, i.e. un état d'énergie minimum, il faut faire décroître très lentement la température de telle sorte que l'équilibre thermique ait le temps de s'instaurer entre deux décroissances de température. Comme on ne dispose pas d'un temps infini, on fait décroître la température plus rapidement au risque d'obtenir des structures presque cristallines, mais présentant des défauts. Pour arriver à un état de cristal on réchauffe le matériau ce qui permet aux molécules d'effectuer des mouvements ce qui était interdit lorsque le système était gelé. En particulier ces mouvements entraînent une augmentation de l'énergie qui permet par la suite de passer une barrière énergétique et ainsi trouver une configuration d'énergie minimum (nous passons en quelque sorte un minimum local de l'énergie).
\includegraphics[height=5cm]{recuit.ps}

Application à notre problème

Nous allons donc utiliser le fait qu'une dégradation provisoire peut entraîner une amélioration plus tard, on peut donc autoriser une modification locale du réseau provoquant une augmentation de fonction d'erreur avec une certaine probabilité. Cette probabilité doit tenir compte de l'ampleur de l'augmentation (plus l'augmentation est importante moins la probabilité que celle-ci soit acceptée est grande) et du temps d'apprentissage. En effet, quand le réseau de neurone atteint des résultats acceptables nous devons avoir un paramètre de contrôle qui rendra de moins en moins probable les transformations désavantageuses (c'est le rôle de la température dans le recuit métallurgique). Nous pouvons définir cette probabilité en nous inspirant de la distribution de Gibbs-Boltzmann, on effectue une transformation qui nous permet de passer de l'état $X_{i}$ à l'état $X_{j}$ et on calcule la variation $\Delta f_{ij} = f(X_{j})-f(X_{i})$ de la fonction $f$ représentant l'erreur en sortie du réseau de neurone, et on accepte la transformation avec la probabilité $P(i,j)$ définie par :

\begin{displaymath}P(i,j) = 1\ si\ \Delta f_{ij} <= 0\end{displaymath}


\begin{displaymath}P(i,j) = e^{\frac {\Delta f_{ij}}{t}}\ si\ \Delta f_{ij} > 0\end{displaymath}

$t$ joue ici le rôle de $kT$ que nous avons vu précédemment, nous le désignerons par température.


Quand $t$ sera élevé (ce qui sera le cas initialement) un grand nombre de transformations seront acceptées, le réseau de neurones sera instable. A l'inverse quant $t$ sera proche de $0$ le réseau sera gelé, seules les transformations avantageuses seront acceptées (on espérera alors être proche d'un optimum). Une transformation désavantageuse sera acceptée si en tirant un nombre aléatoire entre $0$ et $1$ celui-ci se révèle inférieur à $P(i,j)$.
Mais ou faisons nous intervenir le pas d'apprentissage dans tout ceci, comment celui-ci sera géré ? Reprenons les contraintes que doit avoir le pas : avoir un pas élevé à l'initialisation nous permettra d'accélérer l'apprentissage puis plus la température baissera plus nous baisserons le pas parallèlement afin d'affiner au fur et à mesure le réseau de neurone.

Algorithme

solution initiale X (réseau de neurone généré avec des poids aléatoires)
Xmeilleur <- X
fmin <- f(Xmeilleur) (f étant l'erreur en sortie du reseau)
initialisation de la température t
initialisation du pas à p = 1
Tant que condition 1 faire
        (dans cette boucle on essaye les transformations pour une température et 
	un pas donné)
  Tant que condition 2 faire
                (à chaque passage dans cette boucle on essaye une transformation 
	         et on détermine si elle est acceptée)
    X' <-  retropropagation du gradient avec X comme tableau de poids et un pas p
    Varf <- f(X') - f(X)
    Si Varf < 0 alors (on adopte toute configuration faisant descendre)
      X <- X'
      Si f(X) < fmin alors (on conserve la meilleure configuration)
        fmin <- f(X)
        Xmeilleur <- X
      Sinon (on ne rejette pas systématiquement une configuration augmentant l'erreur)
        on tire a dans [0;1]
        Si p <= exp(-Varf/t) alors
          X <- X'   (acceptation propre au recuit)
        Fin si
      Fin si
    Fin si
  Fin tant que
  t <- g(t)
  p <- h(t)
FIn tant que
retourner Xmeilleur

Explications

Conclusion

Nous avons donc mis au point un système de mise à jour du pas en temps réel avec l'apprentissage. Celui-ci nous permet, en plus de converger plus vite vers un optimum, de pouvoir ``affiner'' le réseau de neurone en fin d'apprentissage avec des pas très petits. Cependant cette méthode n'ayant, à notre connaissance, jamais été développée auparavant nous avons eu peu de données et il nous faudra encore de nombreux tests expérimentaux afin d'obtenir une solution qui nous convienne.
next up previous contents
suivant: Traitement du signal Création monter: SOUTENANCE FINALE MARVIN (Modest-encoding précédent: Le projet   Table des matières
root 2002-06-18