Architecture d'un perceptron multi-couche
Le perceptron est, essentiellement, un modèle linéaire. Sa capacité de modélisation est donc nécessairement limitée, ce qui a été à l'origine d'un certain désenchantement de la communauté connexionniste au début des années 1970. Dans un célèbre ouvrage de 1969 sur le sujet, le mathématicien et éducateur Seymour Papert et le spécialiste en sciences cognitives Marvin Minsky évoquent même leur intuition de l'inutilité d'étendre le perceptron à des architectures multi-couche... L'histoire leur donnera tort !
Mais comment créer une telle architecture ?
Nous allons créer des couches intermédiaires (ou couches cachées, hidden layers en anglais) entre la couche d'entrée et celle de sortie. Chaque neurone d'une couche est connecté à tous les neurones de la couche au-dessus de lui. Et voilà ce qu'on appelle un perceptron multi-couche (ou multilayer perceptron, souvent abrégé « MLP », en anglais).
Prenons l'exemple d'un perceptron avec une unique couche intermédiaire. La sortie du h-ème neurone de la couche intermédiaire est obtenue en appliquant la fonction d'activation de ce neurone à une combinaison linéaire des entrées : .
La sortie est ensuite obtenue en appliquant la fonction d'activation du neurone de sortie à la combinaison linéaire des sorties de la couche intermédiaire :
Supposons que l'on utilise la fonction logistique comme fonction d'activation pour tous les neurones, alors
Il s'agit donc d'un modèle paramétrique (nous pouvons explicitement écrire la fonction de décision comme une fonction des variables d'entrée), mais il n'est pas linéaire du tout !
Pour des raisons d'optimisation, plutôt que la fonction logistique, on utilise souvent comme fonction d'activation des couches intermédiaires la fonction tangente hyperbolique qui permet de transformer la combinaison linéaire des signaux d'entrée en un nombre entre -1 et 1 plutôt qu'entre 0 et 1.
Cette architecture n'est pas la seule qui soit possible. En particulier, ici les connexions vont toutes des couches basses vers les couches hautes, mais ce n'est pas nécessairement le cas. Ce type d'architecture sans boucle de retour vers les couches basses s'appelle une architecture feed-forward.
Théorème d'approximation universelle
Un résultat dû à Cybenko en 1989 (raffiné par Hornik en 1991) nous dit que toute fonction f continue définie sur peut être approximée, avec une précision arbitraire, par un réseau de neurones formé d'une seule couche intermédiaire (à condition d'utiliser un nombre suffisant de neurones dans cette couche).
C'est un résultat extrêmement encourageant : nous pouvions modéliser uniquement des fonctions linéaires, nous pouvons maintenant modéliser n'importe quelle fonction! Cependant, ce résultat est à modérer par la pratique : le nombre de neurones nécessaire dans cette couche intermédiaire est, dans beaucoup de cas, colossal, ce qui rend les réseaux de neurones à une seule couche cachée peu efficaces.
La solution est alors d'empiler les couches cachées, créant ainsi ce que l'on appelle maintenant un réseau de neurones profond. Voilà le deep learning qui pointe son nez !
On peut interpréter un réseau de neurones multi-couche en considérant que la sortie de chaque couche intermédiaire est une nouvelle représentation des données. La puissance des réseaux de neurones multi-couche vient donc de leur capacité à apprendre une « bonne » représentation, autrement dit une représentation telle que le modèle linéaire (perceptron) entre la dernière couche intermédiaire et la sortie puisse être performant.
Rétropropagation
Comment apprendre les poids d'un MLP ?
Comme pour le perceptron, nous allons utiliser un algorithme itératif basé sur l'algorithme du gradient. Pour faciliter les calculs, on utilise une idée de rétro-propagation des erreurs (ou backpropagation), énoncée pour la première fois (hors du domaine des réseaux de neurones) dans les années 1960 et popularisée chez les connexionnistes par Rumelhart, Hinton et Williams en 1986.
Il s'agit en fait de décomposer l'erreur grâce au théorème de dérivation des fonctions composées : sur le réseau à une couche intermédiaire ci-dessus, la dérivée partielle de l'erreur E par rapport au poids peut s'écrire
Les deux derniers termes sont simples à calculer car la dérivée dela fonction logistique est :
La dérivée partielle de l'erreur par rapport à un poids d'une couche inférieure peut donc se calculer en calculant la dérivée partielle de l'erreur par rapport à la fonction de décision et les dérivées partielles des sorties (f ou z dans notre exemple à une seule couche) par rapport aux couches précédentes (z ou x dans notre exemple).
La mise à jour des poids peut donc se faire en alternant une phase forward (ou montante), dans laquelle les sorties des couches intermédiaires sont mises à jour, et une phase backward (ou descendante), dans laquelle le gradient de l'erreur par rapport aux poids d'une couche peut être calculé à partir du gradient de l'erreur par rapport aux poids d'une couche supérieure. La mise à jour des poids se fait au moment de la phase descendante (après que le gradient nécessaire ait été calculé).
Défis de l'apprentissage d'un réseau de neurones multi-couche
L'algorithme de rétro-propagation permet d'apprendre des réseaux de neurones multi-couche, pour lesquels on ne sait pas optimiser explicitement les paramètres du modèle. Malheureusement, cet algorithme a un certain nombre de limitations et il faut être précautionneux en le manipulant. C'est pour cela que les réseaux de neurones ne sont revenus à la mode qu'en 2006, quand les travaux de Hinton parmi d'autres, combinés à la puissance de calcul des ordinateurs modernes, ont permis de pallier certaines difficultés. C'est à l'heure actuelle encore un domaine en pleine évolution !
Conditionnement et minimums locaux
Malheureusement, les dérivées de la fonction d'erreur utilisée dans un réseau de neurones multi-couche sont généralement mal conditionnées. Cela veut dire que si l'on perturbe un tout petit peu les conditions initiales de notre algorithme, le résultat sera très différent. En pratique, cela se voit en ce que la fonction d'erreur dessine beaucoup de puits et de montagnes.
Ainsi, la fonction d'erreur d'un réseau de neurones multi-couche admet un grand nombre de minimums locaux, c'est-à-dire de points qui sont optimaux dans leur voisinage. Ces points sont situés dans des puits. Ces points ne sont pas des minimums globaux, mais l'algorithme du gradient peut rester « coincé » dans un tel voisinage.
L'initialisation des poids de connexion, la standardisation des variables, le choix de la vitesse d'apprentissage et celui de la fonction d'activation ont tous un impact sur la capacité du réseau de neurones multi-couche à trouver un bon minimum. L'utilisation d'algorithmes d'apprentissage en mini-batch ou d'une vitesse d'apprentissage adaptative peuvent pallier en partie ce problème.
Instabilité du gradient
Dans les architectures multi-couches, le gradient a tendance à être de plus en plus petit quand on redescend les couches cachées depuis la sortie vers l'entrée du réseau. Cela signifie que les neurones des couches basses apprennent beaucoup plus lentement que ceux des couches hautes. C'est ce qu'on appelle en anglais le phénomène de vanishing gradient (« disparition du gradient »).
À l'inverse, il est aussi possible que le gradient prenne des valeurs très grandes dans les couches basses, ce que l'on appelle « l'explosion du gradient » (exploding gradient).
Ce qu'il faut retenir, c'est que le gradient est instable. Cette instabilité est liée au fait que, dans la rétro-propagation, le gradient des couches basses est un produit de celui des couches hautes.
Saturation
La saturation est un phénomène qui apparaît quand la plupart des neurones à activation logistique du réseau ont une sortie à 0 ou 1 (-1 et + 1 pour une activation tangente hyperbolique), autrement dit quand la somme pondérée des signaux qu'ils reçoivent en entrée est trop grande (en valeur absolue). Cela signifie que les poids de connexion sont trop grands. À ce stade, un changement mineur dans les données d'entrée ne va quasiment pas avoir d'impact sur la sortie, et le réseau ne pourra plus apprendre (ou alors très lentement). De plus, un réseau saturé est souvent en situation de sur-apprentissage...
Une des stratégies que l'on peut utiliser pour éviter le sur-apprentissage est la régularisation. Eh oui, comme pour la régression linéaire ! Nous allons même utiliser le même régularisateur que pour la régression ridge : la norme l2 des poids de connexion. Contrôler cette norme permet en effet de contrôler la valeur absolue des poids, évitant ainsi que les sommes pondérées pré-activation soient trop grandes. Dans le monde des réseaux de neurones, cette technique s'appelle la dégradation des pondérations (« weight decay » en anglais). Il s'agit de remplacer l'erreur par .
À vous de jouer !
Contexte
Vous êtes dans le même contexte que l’exercice précédent: vous êtes le Data Scientist dans un nouveau centre de recherche en cardiologie. Vous travaillez avec un cardiologue afin de mettre en place une approche de détection automatique des anomalies grâce aux données des électrocardiogrammes des patients.
Après avoir essayé de construire un modèle performant de détection des anomalies cardiaques grâce aux SVMs, vous avez entendu parler des réseaux de neurones artificiels comme une approche prometteuse.
Vous décidez donc d’utiliser le même jeu de données pour construire votre premier réseau de neurones !
Comme vous le savez, dans ce jeu de données chaque ligne représente les mesures de l’électrocardiogramme d’un patient. La dernière colonne permet de labelliser les patients :
“0” lorsqu’ils sont sains
“1” lorsqu’ils comportent une anomalie.
Les autres colonnes représentent les différents points de mesures de l'électrocardiogramme pour chaque patient.
Consignes
Chargez le dataset “ecg.csv”. Observez la dernière colonne du jeu de données pour vérifier que l’échantillon est bien équilibré (c’est à dire que la proportion des électrocardiogrammes sains et comportant une anomalie sont sensiblement identiques).
Vous pouvez maintenant concevoir votre premier modèle de réseau de neurones et l'entraîner.
Concevoir le modèle
Vous devrez utiliser les bibliothèques Keras et Tensorflow. Votre modèle comportera 2 couches de réseau de neurones avec une fonction d’activation de type sigmoïde pour chacune d’entre elles.
Nous vous conseillons de vous référer à la documentation de Keras sur le lien suivant: Keras Sequential Model
Entraîner son modèle
Une fois que vous avez conçu votre modèle, vous devrez l'entraîner. Deux paramètres sont importants pour l'entraînement de votre réseau de neurones :
le nombre d’itérations d’entraînement (appelé “epoch”)
le nombre d’échantillons qui vont être analysés à chaque itération (appelé “batch size”).
Après avoir identifié les paramètres correspondants à ces deux grandeurs dans la méthode “fit” de Keras, vous lancerez un premier entraînement avec 50 itérations et 4 échantillons pour chaque itération.
Répondre à la question
Quel est le niveau de précision (accuracy) de votre modèle sur votre jeu de données de test ?
Vérifiez votre travail
En résumé
Empiler des perceptrons en un réseau de neurones multi-couches (feed-forward) permet de modéliser des fonctions arbitrairement complexes.
L'entraînement de ces réseaux se fait par rétro-propagation. Attention, cet algorithme ne converge pas nécessairement, et pas nécessairement vers la solution optimale !
Plus il y a de paramètres (i.e. de poids de connexion), plus il faut de données pour pouvoir apprendre les valeurs de ces paramètres sans risquer le sur-apprentissage.
Il existe de nombreuses autres architectures de réseaux de neurones que celle présentée ici et qui permettent de modéliser des types de données particuliers (images, sons, dépendances temporelles...).
Bravo pour l'ensemble de votre travail tout au long du cours. Et je vous dis à tout de suite dans le dernier quizz, pour valider vos connaissances !