Table Of Contents

Previous topic

Introduction à l’apprentissage par descente de gradient

Next topic

Le défi d’entraîner des réseaux de neurones profonds

This Page

English version

Introduction aux perceptrons multi-couches (réseaux de neurones à propagation avant)

On appelle réseau de neurone “à propagation avant” (feedforward neural network) un réseau de neurone sans connection récurrente.

Réseaux de neurones multi-couches

Un perceptron multi-couches (PMC, ou multi-layer perceptron, MLP), ou réseau de neurones multi-couches, définit une famille de fonctions. Considérons d’abord le cas le plus classique, celui d’un réseau de neurones à une couche cachée, qui associe à un vecteur de dimension d un vecteur de longueur m (par exemple, pour effectuer une régression) :

g(x) = b + W \tanh(c + V x)

où :

  • x est un vecteur de longueur d (l’entrée) ;
  • V est une matrice k \times d (les poids de la couche d’entrée vers la couche cachée) ;
  • c est un vecteur de longueur k (les biais de la couche cachée) ;
  • b est un vecteur de longueur m (les biais de la couche de sortie) ;
  • W est une matrice m \times h (les poids de la couche cachée vers la couche de sortie).

La fonction h(x) = \tanh(x + V x), qui retourne un vecteur, est appelée la sortie de la couche cachée. Notons que dans le réseau ci-dessus, la sortie est une transformation affine de la couche cachée. Une non-linéarité peut être appliquée par dessus dans certaines architectures. Les éléments de la couche cachée sont appelés unités cachées.

Il est possible d’appliquer à nouveau le même genre de transformation à h(x) elle-même, avec des paramètres (biais et poids) différents. Cela donnerait un réseau de neurones multi-couches (à propagation avant) avec deux couches cachées. De manière plus générale, on peut construire un réseau de neurones profond en empilant plusieurs de ces couches. Chacune des couches peut avoir une taille différente (le k utilisé ci-dessus). Une variante commune est l’ajout de connexions directes, c’est-à-dire qu’une couche peut prendre comme entrée à la fois la couche précédente et d’autres couches plus basses (plus proches de l’entrée).

Critères d’entraînements communs et non-linéarités de sortie

Soit f(x) = r(g(x)), où r représente la fonction non-linéaire de sortie. En apprentissage supervisé, la sortie f(x) peut être comparée avec une valeur cible, y, par une fonction d’erreur, L(f,(x,y)). Voici quelques fonctions d’erreur utilisées communément, avec la non-linéarité de sortie correspondante :

  • Pour une régression (L2) ordinaire : pas de non-linéarité (r(a)=a), erreur quadratique L(f,(x,y))=\left\|f(x)-y\right\|^2 = \sum_i (f_i(x) - y_i)^2.
  • Pour une régression médiane (L1) : pas de non-linéarité (r(a)=a), erreur absolue : L(f,(x,y))=|f(x)-y|_1 = \sum_i |f_i(x) - y_i|.
  • Pour la classification probabiliste à deux classes : la non-linéarité est la sigmoïde logistique (r(a)={\rm sigmoid}(a)=1/(1+e^{-a}), appliquée élément par élément), et l’erreur est l’entropie croisée (cross-entropy) L(f,(x,y))= -y \log f(x) -(1-y)\log(1-f(x)), pour y binaire. Notons que la sortie de la sigmoide f(x) est dans l’intervalle (0,1), et correspond à un estimateur de P(y=1|x). La classe 1 est prédite si f(x) > \frac{1}{2}.
  • Pour la classification binaire multiple : chaque élément du vecteur de sortie est traité comme ci-dessus.
  • Pour la classification dure à deux classes, avec l’erreur charnière (hinge loss) : pas de non-linéarité, et l’erreur charnière est L(f,(x,y))=\max(0,1 - (2y-1) f(x)) (ici aussi, pour y binaire). C’est l’erreur utilisée par les classifieurs comme les machines à vecteurs de support (support vector machines, SVM).
  • Le cas ci-dessus peut être généralisé à des classes multiples, en considérant séparément la classification binaire de chaque classe contre toutes les autres.
  • Pour la classification probabilistique multi-classes : la non-linéarité est le softmax (r_i(a) = e^{a_i}/\sum_j e^{a_j}, avec une sortie par classe), l’erreur est la log-vraisemblance négative, L(f,(x,y)) = - \log f_y(x). Notons que \sum_i f_i(x)=1 et que 0<f_i(x)<1. Notons aussi que c’est équivalent à l’entropie croisée dans le cas de 2 classes (la sortie pour la deuxième classe est en faite redondante).

L’algorithme de rétropropagation

Il s’agit simplement d’appliquer l’algorithme récursif de calcul du gradient, vu précédemment, au graphe formé par le PMC, avec un nœud par unité d’entrée, par unité cachée, et par unité de sortie. Notons que chaque paramètre (biais ou poids) correspond aussi à un nœud.

Voici quelques conventions de notation pour les PMC avec plus d’une couche cachée. On note h_i le vecteur de sortie de la couche i, en commençant par h_0(x) = x (l’entrée), jusqu’à la couche de sortie, h_K, qui produit la prédiction, ou sortie, du réseau.

Avec des unités tanh (dont la fonction d’activation est la tangente hyperbolique) dans les couches cachées, nous avons (en utilisant la notation matricielle) :

  • Pour k = 1 à K-1:

    • h_k = {\rm tanh}(b_k + W_k h_{k-1})

      b_k est un vecteur de biais, et W_k est une matrice de poids, connectant la couche k-1 à la couche k. Le calcul scalaire associé à une unité donnée i de la couche k est h_{k,i} = {\rm tanh}(b_{k,i} + \sum_j W_{k,i,j} h_{k-1,j}).

Dans le cas d’un classifieur probabiliste, nous avons un softmax sur la couche de sortie, c’est-à-dire :

p = h_K = {\rm softmax}(b_K + W_K h_{K-1})

où nous notons la sortie p parce que c’est un vecteur indiquant une distribution de probabilité sur les classes. L’erreur est

L = - \log p_y

y est la classe cible, c’est-à-dire que l’on veut maximiser p_y=P(Y=y|x), qui est un estimateur de la probabilité conditionnelle de la classe y étant donnée l’entrée x.

Voyons maintenant comment se fait, dans cette structure, l’application récursive de la règle de dérivation en chaîne dans un graphe de flot. Soit

a_k=b_k + W_k h_{k-1}

l’argument de la non-linéarité à chaque couche, que nous appellerons le potentiel, notons que (par une dérivation rapide)

\frac{\partial(- \log p_y)}{\partial a_{K,i}} = (p_i - 1_{y=i})

et que

\frac{\partial \tanh(u)}{\partial u} = (1-\tanh(u)^2).

Maintenant, appliquons la méthode de la rétropropagation dans le graphe de flot correspondant. Chaque paramètre (chaque poids et chaque biais) est un nœud, le potentiel a_{k,i} et la sortie h_{k,i} de chaque neurone est aussi un nœud.

  • On part du nœud de sortie:

    \frac{\partial L}{\partial L} = 1

  • Ensuite, on calcule le gradient de L par rapport à chacune des sommes précédant le softmax, a_{K,i}:

    \frac{\partial L}{\partial a_{K,i}}
= \frac{\partial L}{\partial L}
  \frac{\partial L}{\partial a_{K,i}}
= (p_i - 1_{y=i}).

  • Maintenant, on peut répéter la procédure pour chaque couche. Pour k=K descendant jusqu’à 1 :

    • On obtient facilement le gradient de L par rapport aux biais :

      \frac{\partial L}{\partial b_{k,i}}
= \frac{\partial L}{\partial a_{k,i}}
  \frac{\partial a_{k,i}}{\partial b_{k,i}}
= \frac{\partial L}{\partial a_{k,i}}.

    • On calcule le gradient par rapport aux poids :

      \frac{\partial L}{\partial W_{k,i,j}}
= \frac{\partial L}{\partial a_{k,i}}
  \frac{\partial a_{k,i}}{\partial W_{k,i,j}}
= \frac{\partial L}{\partial a_{k,i}} h_{k-1,j}.

    • On rétropropage le gradient vers la couche précédente, si k>1 :

      \frac{\partial L}{\partial h_{k-1,j}}
= \sum_i \frac{\partial L}{\partial a_{k,i}}
         \frac{\partial a_{k,i}}{\partial h_{k-1,j}}
= \sum_i \frac{\partial L}{\partial a_{k,i}} W_{k,i,j}

\frac{\partial L}{\partial a_{k-1,j}}
= \frac{\partial L}{\partial h_{k-1,j}}
  \frac{\partial h_{k-1,j}}{\partial a_{k-1,j}}
= \frac{\partial L}{\partial h_{k-1,j}} (1 - h_{k-1,j}^2)

Régression logistique

La régression logistique est un cas particulier de PMC, qui n’a pas de couche cachée (l’entrée est directement connectée à la sortie), dont l’erreur est l’entropie croisée (sortie sigmoïde), ou la log-vraisemblance négative (sortie softmax). C’est un classifieur linéaire probabiliste, et le critère d’entraînement est convexe selon les paramètres (ce qui garantit qu’il existe un seul minimum, global).

L’entraînement de réseaux de neurones multi-couches

De nombreux algorithmes pour entraîner des réseaux de neurones multi-couches ont été proposés, mais les plus couramment utilisés sont à base de gradient.

Deux principes fondamentaux guident les différentes stratégies employées pour entraîner des PMC :

  • Entraîner aussi efficacement que possible, c’est-à-dire faire baisser l’erreur d’entraînement aussi vite que possible, éviter d’être bloqué dans une vallée étroite ou un minimum local de la fonction de coût ;
  • Contrôler la capacité, de manière à éviter le sur-apprentissage, afin de minimiser l’erreur de généralisation.

Problème fondamentalement difficile d’optimisation

L’optimisation du critère d’apprentissage dans les réseaux de neurones multi-couches est difficile car il y a de nombreux minima locaux. On peut même démontrer que de trouver les poids optimaux est NP-dur. Cependant on se contente de trouver un bon minimum local, ou même simplement une valeur suffisamment basse du critère. Comme ce qui nous intéresse est la généralisation et non pas l’erreur d’apprentissage (ce qu’on minimise n’est pas ce qu’on voudrait vraiment minimiser), la différence entre “près d’un minimum” et “au minimum” est souvent sans importance. Par ailleurs, comme il n’y a pas de solution analytique au problème de minimisation, on est forcé de faire cette optimisation de manière itérative.

Choix de l’architecture

En principe, une manière d’accélérer la descente de gradient est de faire des choix qui rendent la matrice Hessienne \frac{\partial^2 C}{\partial \theta_i \partial \theta_j} mieux conditionnée. La dérivée seconde dans une certaine direction indique la courbure de la fonction de coût dans cette direction. Plus la courbure est grande (vallée étroite) et plus petites doivent être les mises à jour des paramètres si on éviter que l’erreur augmente. Plus précisement, le pas de gradient optimal est 1 sur la courbure. On peut voir cela par une simple expansion de Taylor du coût et est derrière le fameux algorithme de Newton pour l’optimisation. Mettons que l’on soit à \theta^k et que l’on veuille choisir \theta^{k+1} pour qu’il soit un minimum:

C(\theta^{k+1}) = C(\theta^k) + (\theta^{k+1} - \theta^k) C'(\theta^k) + 0.5 (\theta^{k+1} - \theta^k)^2 C''(\theta^k)

0 = \frac{\partial C(\theta^{k+1})}{\partial \theta^{k+1}} = C'(\theta^k) + (\theta^{k+1} - \theta^k) C''(\theta^k)

\theta^{k+1} = \theta^k - \frac{C'(\theta^k)}{C''(\theta^k)}

Donc on veut un pas de gradient égal à l’inverse de la dérivée seconde. On peut montrer que le nombre d’itération d’un algorithme de descente de gradient sera proportionnel au ratio de la plus grande à la plus petite valeur propre de la matrice Hessienne (avec une approximation quadratique de la fonction de coût). La raison de base en est que la plus grande valeur propre limite le pas de gradient maximum (on ne peut aller plus vite que la courbure la plus forte parmi toutes les directions possibles, sinon l’erreur remonte), mais qu’en utilisant le même pas de gradient dans toutes les directions, la convergence sera la plus longue dans la direction la plus “plate” (valeur propre la plus petite).

  • En théorie une couche cachée suffit, mais cette théorie ne dit pas que cette représentation de la fonction sera efficace. En pratique cela a été le choix le plus commun avant 2006, sauf pour les réseaux de neurones à convolution (qui peuvent avoir 5 ou 6 couches par exemple). Parfois on obtient de bien meilleurs résultats avec 2 couches cachées. En fait on peut obtenir une bien meilleure généralisation avec encore plus de couches, mais une initialisation aléatoire ne fonctionne pas bien avec plus de deux couches (mais voir les travaux depuis 2006 sur l’initialisation non-supervisée vorace pour les architectures profondes).
  • Pour la régression ou avec des cibles réelles et non-bornées en général, il vaut généralement mieux utiliser des neurones linéaires à la couche de sortie. Pour la classification, il vaut généralement mieux utiliser des neurones avec non-linéralité (sigmoide ou softmax) à la couche de sortie.
  • Dans certains cas une connexion directe entre l’entrée et la sortie peut être utile. Dans le cas de la régression, elle peut aussi être initialisée directement par le résultat d’une régression linéaire des sorties sur les entrées. Les neurones cachées servent alors seulement à apprendre la partie non-linéaire manquante.
  • Une architecture avec poids partagés, ou bien le partage de certains éléments de l’architecture (e.g., la première couche) entre les réseaux associés à plusieurs tâches connexes, peuvent significativement améliorer la généralisation. Voir aussi la discussion à venir sur les réseaux à convolution.
  • Il vaut mieux utiliser une non-linéarité symétrique dans les couches cachées (comme la tanh, et non pas la sigmoide), afin d’améliorer le conditionnement du Hessien et éviter la saturation des couches cachées.

Normalisation des entrées

Il est impératif que les entrées soient de moyenne pas trop loin de zéro et de variance pas trop loin de 1. Les valeurs en entrées devraient aussi ne pas avoir une magnitude trop grande. On peut faire certaines transformations monotones non-linéaires qui réduisent les grandes valeurs. Si on a une entrée très grande, elle fait saturer plusieurs neurones et bloque l’apprentissage pour cet exemple. Les magnitudes (variances) des entrées de chaque couche devraient aussi être du même ordre quand on utilise un pas de gradient commun pour toute les couches, pour éviter que l’une des couches devienne le goulot d’étranglement (plus lent à entraîner). En fait, dans le cas linéaire, le conditionnement du Hessien est optimal quand les entrées sont normalisées (donc avec matrice de covariance = identité), ce qui peut se faire en les projetant dans l’espace des vecteurs propres de la matrice X' X, où X est la matrice de dimension nombre d’exemples par nombre d’entrées.

Traitement des sorties désirées

Dans le cas d’apprentissage par minimisation d’un coût quadratique, on doit s’assurer que les sorties désirées

  • sont toujours dans l’intervalle des valeurs que la non-linéarité de la couche de sortie peut produire (et sont à peu près normales N(0,1) dans le cas linéaire),

  • ne sont pas trop proches des valeurs limites de la non-linéarité de la couche de sortie: pour la classification, une valeur optimale est près d’un des deux points d’inflexion (i.e., les points de courbure (dérivée seconde) maximale, environ -0.6 et 0.6 pour tanh, 0.2 et 0.8 pour la sigmoide).

  • Il vaut mieux utiliser le critère d’entropie croisée (ou la vraisemblance conditionnelle) pour la classification probabiliste, ou bien le critère de marge “hinge” (comme pour le perceptron et les SVMs, mais en pénalisant les écarts à la surface de décision au-delà d’une marge). Dans le cas multiclasse, ça donne

    \sum_i (-1^{1_{y=i}}f_i(x) + 1)_+,

    x_+ = x 1_{x>0} est la partie positive et f_i(x) est la sortie (sans non-linéarité) pour la classe i.

Codage des sorties désirées et des entrées discrètes

En entrée comme en sortie, on va généralement représenter les variables discrètes par des groupes d’unités (un groupe de k unités par variable discrète pouvant prendre k valeurs). L’exception pratique est le cas d’une variable binaire, qu’on encode généralement avec une seule unité. Dans le cas des sorties on va associer à chaque groupe une distribution discrète (binomiale pour un seul bit, multinomiale pour une variable discrète générale).

Algorithme d’optimisation

Quand le nombre d’exemples est grand (plusieurs milliers) la descente de gradient stochastique est souvent le meilleur choix (surtout pour la classification), en terme de vitesse et en terme de contrôle de la capacité: il est plus difficile d’avoir de l’overfitting avec la descente de gradient stochastique. En effet, la descente de gradient stochastique ne tombe pas facilement dans les minima très pointus (qui ne généralisent pas bien, car une légére perturbation des données déplaçant la surface d’erreur donnerait une très mauvaise performance), à cause du bruit induit par le pas de gradient et le gradient “bruité”. Ce gradient bruité aide aussi à sortir de certains minima locaux, pour la même raison.

Quand la descente de gradient stochastique est utilisée, il est IMPÉRATIF que les exemples soient bien mélangés: par exemple si on a beaucoup d’exemples consécutifs de la même classe, la convergence sera très lente. Il suffit de permuter aléatoirement les exemples une fois pour toute, pour éliminer toute dépendence entre les exemples successifs. Avec certaines architectures qui captent des dépendences temporelles (signaux, musique, parole, séries chrono, vidéo) on a pas le choix de présenter des séquences dont les éléments sont fortement dépendents, mais on peut mélanger les séquences (l’ensemble d’apprentissage est une suite de séquences).

En principe le pas de gradient devrait être graduellement réduit pendant l’apprentissage pour garantir la convergence asymptotique. Pour certains problèmes (surtout de classification) cela ne semble pas nécessaire, et peut même nuire. Une cédule de descente raisonnable est par exemple \epsilon_t = \frac{\epsilon_0 \tau}{t + \tau} comme discuté ici. Si on pouvait le calculer, le pas de gradient optimal serait \frac{1}{\lambda_{\rm max}}, i.e., l’inverse de la valeur propre la plus grande de la matrice Hessienne, et le pas de gradient maximal (avant divergence) est deux fois plus grand. Le Cun propose une méthode pour estimer efficacemenet \lambda_{\rm max} (voir son tutorial sur le sujet), mais cette technique ne semble pas courramment utilisée.

Quand le nombre d’exemples (et donc de paramètres) est plus petit, et surtout pour la régression, les techniques du second degré (surtout la technique des gradients conjugués) permettent une convergence beaucoup plus rapide. Ces techniques sont batch (modification des paramètres après calcul de l’erreur et gradient sur tous les exemples). Ces techniques sont généralement plus facile à ajuster que la descente de gradient stochastique (moins d’hyper-paramètres ou moins nécessaire de les ajuster par rapport à une valeur par défaut), mais la généralisation est parfois moins bonne à cause de la facilité de tomber dans des minima pointus.

Jusqu’à quelques dizaines de milliers d’exemples, la descente de gradient conjugués reste une des meilleures techniques pour l’optimisation des réseaux de neurones. Au-delà il vaut généralement mieux s’en tenir au gradient stochastique ou à sa version minibatch.

Initialisation des paramètres

On ne peut initialiser tous les poids à zéro sans quoi tous les neurones cachés sont condamnés à toujours faire la même chose (qu’on peut voir par un simple argument de symétrie). On veut aussi éviter la saturation des neurones (sortie près des limites de la non-linéarité, donc gradient presque 0), mais ne pas être trop près initialement d’une fonction linéaire. Quand les paramètres sont tous près de 0, le réseau multicouche calcule une transformation affine (linéaire), donc sa capacité effective par sortie est égale au nombre d’entrées plus 1. En se basant sur ces considérations, le point idéal d’opération du neurone devrait être proche du point d’inflexion de la non-linéarité (entre la partie linéaire près de l’origine et la partie saturation). Par ailleurs, on aimerait que la variance moyenne des valeurs des unités cachées soit préservée quand on propage les “activations” de l’entrée vers la sortie, et de la même manière on aimerait que la variance des gradients le soit aussi quand on les propage de la sortie vers l’entrée. Pour atteindre cet objectif, on peut argumenter que les poids initiaux devraient être initialisés de manière uniforme dans un intervalle [-\sqrt{6/(n_i + n_o)}, \sqrt{6/(n_i+n_o)}], où n_i est le fan-in, i.e., le nombre d’entrées du neurone, le nombre de neurones de la couche précédente, et n_o est le fan-out, i.e., le nombre de neurones de la couche visée. Cela suppose que les entrées sont approximativement uniformes dans l’intervalle (-1,1) (et remarquez comme les sorties des unités cachées tanh sont aussi dans le même intervalle).

Contrôle de la saturation

Un des problèmes fréquents pendant l’apprentissage est la saturation des neurones, souvent dûe à une mauvaise normalisation des entrées ou des sorties désirées ou une mauvaise initialisation des poids, ou bien à l’utilisation de la sigmoide plutôt qu’une fonction de non-linéarité symétrique comme la tanh. On peut contrôler cela on observant la distribution des sorties des neurones (en particulier, la moyenne des valeurs absolues de la somme pondérée est un bon indice). Quand les neurones saturent fréquemment, l’apprentissage est bloqué sur un plateau de la fonction de coût dû à de très petits gradients sur certains paramètres (donc un très mauvais conditionnement du Hessien).

Contrôle de la capacité effective

La théorie du structural risk minimization de Vapnik nous dit qu’il existe une capacité optimale autour de laquelle l’erreur de généralisation augmente (c’est un minimum global et unique). Les techniques de contrôle de la capacité effective visent donc à chercher ce minimum (evidemment de façon approximative).

  • early stopping: il s’agit d’une des techniques les plus populaires et les plus efficaces, mais elle ne marche pas bien quand le nombre d’exemples disponibles est très petit. L’idée est très simple: on utilise un ensemble d’exemples de validation non-utilisés pour l’apprentissage par descente de gradient pour estimer l’erreur de généralisation au fur et à mesure que l’apprentissage itératif progresse (normalement, après chaque époque on mesure l’erreur sur l’ensemble de validation). On garde les paramètres correspondant au minimum de cette courbe d’erreur de généralisation estimée (et on peut s’arrêter quand cette erreur commence à remonter sérieusement ou qu’un minimum a été atteint depuis un certain nombre d’époques). Cela a l’avantage de répondre à une des questions difficile de l’optimisation, qui est: quand arrêter? De plus on remarque qu’on a ainsi choisi pour pas cher (en temps de calcul) un hyper-paramètre important (le nombre d’itérations d’entraînement) qui touche à la fois l’optimisation et la généralisation.

  • contrôle du nombre d’unités cachées: ce nombre influence directement la capacité. Dans ce cas il faut malheureusement faire plusieurs d’expériences d’apprentissage, à moins d’utiliser un algorithme d’apprentissage constructif (qui rajoute des ressources au fur et à mesure), voir l’algorithme de cascade-correlation (Fahlman, 1990). On peut utiliser un ensemble de validation ou la validation croisée pour estimer l’erreur de généralisation. Il faut faire attention au fait que cet estimé est bruité (d’autant plus qu’il y a peu d’exemples de validation). Quand on a plusieurs couches cachées, choisir le même nombre d’unités par couche semble bien fonctionner. Le prix à payer pour un nombre d’unités trop grand est surtout que les calculs sont plus longs, car le nombre accru de paramètre est généralement compensé par le early stopping. Par contre quand le nombre d’unités cachées est trop petit, l’effet sur l’erreur de généralisation et sur l’erreur d’apprentissage peut être beaucoup plus grand. On va généralement choisir la taille des réseau de façon empirique, en gardant ces considérations à l’esprit pour éviter d’avoir à essayer trop de valeurs de la taille.

  • weight decay: c’est une méthode de régularisation (pour contrôler la capacité, empêcher l’overfitting) dont le but est de pénaliser les poids forts. En effet, on peut montrer que la capacité est bornée par la magnitude des poids du réseau de neurones. On rajoute la pénalité

    \lambda \sum_i \theta_i^2

    à la fonction de coût. On l’appelle régularisation L2 car on minimise la norme 2 des paramètres. Certains l’appliquent uniquement aux poids et non pas au biais.

    Comme dans le cas précédent, il faut faire plusieurs expériences d’apprentissage et choisir le facteur de pénalité \lambda (un hyper-paramètre) qui minimise l’erreur de généralisation estimée. On l’estime avec un ensemble de validation ou bien par validation croisée.

    Une forme de régularisation de plus en plus utilisée comme alternative à la régularisation L2 est la régularisation L1, qui a comme avantage que les petits paramètres seront carrément amenés à 0, donnant lieu à un vecteur de paramètres qui est sparse. On va donc minimiser la somme des valeurs absolues des paramètres.