Les réseaux de neurones

Warren McCulloch (à gauche) et Walter Pitts (à droite)

\bullet Historique :

\begin{tabular}{|l|c|c|} \hline Bloc & 17/01/2024-V1 \\ \hline Historique & Cr\'eation \\ \hline Sommaire & Cr\'eation \\ \hline Pr\'esentation & Cr\'eation \\ \hline Les r\'eseaux de neurones & Cr\'eation \\ \hline Annexe th\'eorique & Cr\'eation \\ \hline Exemple & Cr\'eation \\ \hline Application sous R & Cr\'eation \\ \hline Application sous SAS & Cr\'eation \\ \hline Bibliographie & Cr\'eation \\ \hline \end{tabular}

\bullet Sommaire :

  • Présentation
  • Les réseaux de neurones
    • Vocabulaire
    • L’algorithme
    • Autres particularités
    • Les réseaux à base radiale
  • Annexe théorique
  • Exemple
  • Application sous R
  • Application sous SAS
  • Bibliographie

\bullet Présentation :

Les réseaux de neurones ou neuronale, outil d’analyse supervisée qui prend ses racines dans les années 1940 et connait un essor constant de développement depuis, permet de discriminer une matrice de Q variables réponses \mathbf{Y} = (Y ^1, \cdots, Y ^Q) de n’importe quel format (continue, binaire, etc.) à partir d’une matrice de P variables explicatives, également de n’importe quel format, \mathbf{X} = (X ^1, \cdots, X ^P).

Les réseaux de neurones se sont construits une solide réputation au fil du temps, marquée par des hauts et des bas, dépendante de l’engouement mais aussi de la lassitude du moment. Relégués au statut de boîte noire, ils demeurent aussi riches que leur histoire. Initialement conceptualisés par Warren McCulloch et Walter Pitts en 1943, introduisant la notion de neurone formel, leur idée était de s’inspirer des neurones et des synapses du cerveau humain et de les mathématiser. Le concept a progressé, et en 1950, cette idée a pris forme avec la création du Perceptron par Frank Rosenblatt. Malheureusement, en 1969, Marvin Lee Minsky et Seymour Papert ont souligné d’importantes limites théoriques, entraînant une chute vertigineuse de l’enthousiasme généré, jusqu’au début des années 1980.

En 1982, John Joseph Hopfield introduit une version portant son propre nom et généralise la notion de base des neurones connectés de McCulloch et Pitts. Puis, en 1986, David Rumelhart, Geoffrey Hinton et Ronald J. Williams développent l’algorithme de rétropropagation du gradient pour l’apprentissage, faisant tomber un à un les détracteurs des réseaux de neurones. Cela ressuscite tout l’engouement qu’ils avaient généré autrefois et les propulse jusqu’à une utilisation courante en industrie à partir des années 1990. Leur essor reprend ainsi de plus belle et, dans les années 2000 avec l’augmentation exponentielle de la puissance de calcul des ordinateurs, valide l’arrivée des réseaux de neurones profonds (DNN), convolutionnels (CNN) et récurrents (RNN). Les désagréments théoriques balayés, les réseaux de neurones peuvent s’épanouir, permettant des avancées significatives dans le domaine du traitement du langage naturel. Depuis les années 2010, ils accumulent de nombreux succès remarquables dans divers domaines tels que le traitement de l’image, la traduction automatisée et la santé.

Malgré les éloges envers les réseaux de neurones, il faut rappeler qu’il s’agit d’un outil d’une haute complexité, manquant de lisibilité, capable de reproduire n’importe quelle fonction, notamment en raison de leur paramétrage et du risque accru de sur-apprentissage du phénomène que l’on cherche à modéliser ou encore de la convergence vers une solution globale et non locale. D’une certaine manière, ils sont déjà limités à une utilisation sur de grandes bases de données pour justement minimiser ce risque.

Enfin, bien que présentés ici comme un outil de classification et de prédiction, il existe également une version pour mener des analyses non supervisées, comme les cartes auto-organisatrices de Kohonen.

\bullet Les réseaux de neurones :

Hypothèse préliminaire : Tout format de variable X et Y.

Vocabulaire

Avant de se lancer dans une vision plus approfondie des réseaux de neurones, il est important de rappeler le vocabulaire associé.

– Neurones, ou nœuds, et connexions synaptiques : unités de traitement, ou calculateur, interconnectées par des poids synaptiques. Chaque connexion synaptique a un poids qui détermine l’importance de la contribution d’un neurone à l’activation d’un autre ;

– Architecture ou topologie : structure du réseau distinguée généralement selon trois types. La couche d’entrée qui recevra les données initiales, les couches cachées qui effectueront les transformations intermédiaires, la couche de sortie qui retournera la modélisation finale. Dès lors où l’on aura plusieurs couches cachées on parlera de réseaux de neurones profonds ;

– Fonction d’activation ou de transfert : introduit la non-linéarité dans le réseau et ainsi la capacité de cet outil à modéliser des relations complexes. Cette dernière interviendra lors de la propagation avant alors que sa dérivée interviendra lors de la rétropropagation ;

– Propagation avant (feedforward) : les données circulent à travers le réseau de la couche d’entrée à la couche de sortie sans rétroaction. Chaque neurone transmet son activation aux neurones de la couche suivante. Il s’agit de la partie non supervisée de l’algorithme car ne faisant pas intervenir la réponse ;

– Rétropropagation (backpropagation) : ajustement des poids synaptiques du réseau en fonction de l’erreur calculée entre la sortie prédite et la sortie réelle. Son objectif est de minimiser cette erreur en temps réel, en ajustant les poids de manière itérative. il s’agit de la partie supervisée de l’algorithme ;

– Fonction de coût : mesure de l’écart entre la sortie prédite et la sortie réelle que la rétropropagation va chercher à chaque itération de minimiser ;

– Taux d’apprentissage : servant à déterminer la taille du pas que l’algorithme de rétropropagation prend pour ajuster les poids du réseau. Plus il est haut et plus la convergence est rapide au risque de s’arrêter sur une solution locale et non globale. Alors que plus il est faible, plus l’on devra utiliser d’itérations pour obtenir la convergence, favorisant ainsi les chances de trouver la solution globale.

Enfin, afin d’avoir une vision simple de toutes ces notions, on conclura avec la graphique suivant :

L’algorithme

On part d’une matrice (\mathbf{X}, \mathbf{Y}) = (X^1, X^2, \cdots, X^P, Y ^1, Y ^2, \cdots, Y ^Q), avec  \mathbf{X} variables explicatives et \mathbf{Y} variables à prédire.

L’algorithme que l’on proposera n’intègre que les concepts de rétropropagation et d’apprentissage profond. Il s’approche plus particulièrement d’un type de réseau de neurones qui porte le nom de Perceptron multicouche. En voici les différentes étapes :

Etape 1 : Choix du paramétrage du réseau de neurones,

  • On définit le nombre C de couches cachées. Pour chaque couche c \in \lbrace 0, C \rbrace, on désigne un nombre de neurones T_c. On a volontairement mis la paramétrage possible à 0 couche cachée afin de rappeler qu’il est possible, revenant à faire une simple régression linéaire ou logistique en fonction de la fonction d’activation choisie. Plus l’on ajoute de couches cachées et plus le pouvoir prédictif du modèle s’en voit amélioré, mais c’est aussi là que réside l’un des plus gros risques de surapprentissage. De nombreux auteurs proposent des méthodes différentes pour le choix du nombre de couches cachées. S. Tufféry, par exemple, indique qu’il faut compter 5 à 10 individus pour ajuster chaque poids et qu’il vaut mieux se concentrer sur l’utilisation au maximum de deux couches cachées puis jouer sur les autres paramètres pour améliorer son réseau de neurones. 
  • On définit la fonction d’activation. La plus communément utilisée est la fonction logistique ou sigmoïde f(x) = \frac{1}{1 + e^{-x}} et de dérivée : f'(x) = f(x) \cdot (1 - f(x)). Mais on en retrouve d’autres qui le sont également dans une moindre mesure,

– ReLU (Rectified Linear Unit) : f(x) = max(0,x), appréciée par sa simplicité et sa capacité à résourdre le problème du gradient pour certaines types de réseau. Sa dérivée est 1 si x > 0 et 0 si x \leq 0 ;

– Leaky ReLU (Unité Linéaire Paramétrique) : f(x) = max(\beta \cdot x, x), variation de ReLU permettant l’utilisation d’un « petit » gradient lorsque l’entrée est négative. Sa dérivée est 1 si x > 0 et \beta si x \leq 0. On peut également parler de la version Parametric ReLU (PreLU) avec \beta qui n’est plus fixé comme un paramètre mais va bénéficier du processus d’apprentissage de l’algorithme pour s’optimiser ;

– Tangente hyperbolique : f(x) = \frac{e ^x - e ^{-x}}{e ^x + e ^{-x}} = \frac{e^{2x} - 1}{e^{2x}+1}, similaire à la fonction sigmoïde mais bornée sur [-1, 1], utile pour certains types de réseau nécessitant que les données soient normalisées. Sa dérivée est f'(x) =  1 - f^2 (x) ;

– ELU (Unité Linéaire Exponentielle) : f(x) = x si x > 0, \beta \cdot (e ^x - 1) si x \leq 0, introduisant le concept de saturation pour les valeurs négatives du fait de la convergence vers 0 de l’exponentielle dans ces cas-là. Sa dérivée est 1 si x > 0, f(x) + \beta si x \leq 0 ;

– Softmax : f_p(x) = \frac{e^{x_p}}{\sum_{p_b = P} ^n e ^{x_{p_b}}}, à privilégier dans le cadre où \mathbf{Y} est composée d’une ou plusieurs variable(s) multiclasse(s), il convient alors en amont d’en produire le tableau disjonctif complet. D’ailleurs, la remarque s’applique également si c’est le cas de \mathbf{X}. Sa dérivée est f'_p(x) = f-p \cdot (1 - f_p) si p = p_b et -f_p \cdot f_{p_b} si p \neq p_b ;

– Swish : f(x) = x \cdot \sigma(\beta \cdot x), avec \sigma(.) la fonction sigmoïde, permettant d’améliorer les performances dans certains cas de l’utilisation de la fonction ReLU. Sa dérivée est f'(x) = f(x) + \sigma(\beta \cdot x) \cdot (1 - f(x)) \cdot \beta.

Le choix de la fonction d’activation se fait en lien avec la nécessité d’avoir une fonction linéaire au voisinage de 0 et non linéaire aux extrémités. Enfin, la standardisation ou la normalisation des données est souvent conseillée en privilégiant des variables variant dans [0, 1] ou [-1,1] afin de contrôler le calcul des poids, favorisant alors certaines variables au détriment de la qualité prédictive du modèle final.

  • On définit les poids (synaptiques) initiaux associés aux différentes couches. De préférence, aléatoirement. En effet, démarrer avec des poids identiques risque fortement d’entrainer la non-différenciation des neurones. Ce phénomène est connu sous le terme de « problème des neurones morts », diminuant leur capacité d’apprentissage. Xavier Glorot propose de délimiter le périmètre de tirage en divisant chacun des poids par : \frac{1}{P+Q}. Pour chacune des couches cachées c \in [1, C], on notera \mathbf{W} ^c la matrice des poids de taille (P + 1) \times T_c si c = 1, (T_{c-1} + 1) \times T_c si c \in [2, C]. Enfin, il faut également attribuer à la couche de sortie ses propres pondérations \mathbf{W} ^s, tirées de la même manière et de taille (T_C + 1) \times Q. A noter que l’ajout de +1 systématiquement est lié au besoin d’avoir, sans que cela soit obligatoire mais simplement conseillé, une ligne supplémentaire de poids associés aux termes constants.
  • On définit le taux d’apprentissage \alpha, qui servira à fixer la vitesse de correction des matrices de pondérations. Ce dernier étant un pourcentage, il doit être compris dans [0, 1] ;
  • On définit un nombre d’itérations it à appliquer afin d’arrêter l’algorithme qu’il converge ou non.

Etape 2 : Propagation avant (apprentissage non supervisé), en notant A \cdot B le produit matricielle, on applique l’algorithme suivant,

  • Pour la première couche cachée : \mathbf{H} ^1 = f((1,\mathbf{X}) \cdot \mathbf{W} ^1) ;
  • Dès la seconde couche cachée : \forall c \in [2, C], \mathbf{H} ^c = f((1,\mathbf{H} ^{c-1}) \cdot \mathbf{W} ^c) ;
  • Pour la couche de sortie : \mathbf{S} = f((1,\mathbf{H} ^C) \cdot \mathbf{W} ^s)

En fait, cette étape de l’algorithme remet un peu en question la vision traditionnelle du réseau de neurones que l’on veut à tout prix opposer à celle du cerveau, ce qui est normal puisqu’il s’agit du concept de base à l’origine de l’outil. Mais on comprend mieux certaines présentations qui consistent à le représenter comme une succession de « plaques » contre lesquels l’on va jeter notre jeu de données, l’éparpillant matriciellement pour à nouveau le rejeter comme une nouvelle plaque qui va l’éparpiller à son tour. Et ainsi de suite jusqu’à ce qu’à force d’exploser nos données, elles finissent par se remettre dans le bon ordre. On retrouve ici l’approche non supervisée puisque l’on n’utilise pas notre réponse \mathbf{Y} dans la propagation.

Enfin, à noter que cette partie de l’algorithme fait également aussi office de règle décisionnelle. Munis des poids et d’un nouvel individu décrit selon les caractéristiques utilisées pour le paramétrage, il suffit d’appliquer les itérations indiquées précédemment pour obtenir sa prédiction.

Etape 3 : Calcul de l’erreur, on compare la sortie prédite \mathbf{S} avec la réponse \mathbf{Y} en utilisant une fonction de coût. Cette fonction mesure la différence entre la prédiction du réseau et les classes réelles. On a l’embarras du choix à nouveau :

– L’erreur quadratique moyenne (MSE) : Erreur = \frac{1}{2} \sum_{i = 1} ^n \sum_{q = 1} ^Q (Y_{i,q} - S_{i,q}) ^2, le classique du genre. On peut précéder à sa variante consistant à appliquer le facteur \frac{1}{2 N} au lieu de \frac{1}{2} ou encore celle s’affranchissant de l’élévation au carré pour les valeurs absolues (MAE), moins sensible aux valeurs aberrantes que la MSE. Elles sont alors réservée à une ou plusieurs réponse(s) continue(s) ;

– L’entropie croisée binaire (Binary Cross-Entropy) : Erreur = - \frac{1}{N} \sum_{i = 1} ^n (Y_i \cdot log(S_i) + (1 - Y_i) \cdot log (1 - S_i)), réservée à une réponse univariée binaire et donc souvent associée à la fonction d’activation sigmoïde ;

– L’entropie croisée catégorielle (Categorical Cross-Entropy) : Erreur = - \frac{1}{N} \sum_{i = 1} ^n \sum_{m = 1} ^M Y_{i,m} \cdot log(S_{i,m}), réservée à une réponse univariée à plusieurs classes M et donc souvent associée à la fonction d’activation softmax.

Etape 4 : Rétropropagation (apprentissage supervisé), l’erreur calculée est propagée en sens inverse à travers le réseau. Les poids synaptiques sont ajustés de manière itérative pour minimiser cette erreur. Cela se fait en utilisant la descente de gradient, où la dérivée partielle de la fonction de coût. en notant A \odot B le produit terme à terme,

– On démarre sur la couche de sortie,

  • Initialiser \partial \mathbf{H} \leftarrow \mathbf{S} - \mathbf{Y}, sachant que certains réseaux programmés font intervenir la dérivée de la fonction coût à la place de la formule proposée ;
  • Calculer les corrections des poids de la couche de sortie \partial \mathbf{W} ^s = (1,\mathbf{H} ^C)^t \cdot \partial \mathbf{H}

– Puis sur la dernière couche cachée,

  • Mettre à jour la dérivation \partial \mathbf{H} \leftarrow \partial \mathbf{H} (\mathbf{W}_{-1} ^S)^t, la matrice des poids de la couche de sortie est utilisée cette fois-ci en lui retirant la première ligne ;
  • Calculer les corrections des poids de la dernière couche cachée en faisant intervenir la dérivée de la fonction d’activation f'(.), et en notant \odot le produit terme à terme,  \partial \mathbf{W} ^C = (1,\mathbf{H} ^{C-1})^t \cdot f'(\mathbf{H}^C) \odot \partial \mathbf{H}

– Puis en poursuivant couche par couche en sens inverse, \forall c \in [C-1, \cdots, 2],

  • Itérativement mettre à jour la dérivation \partial \mathbf{H} = \partial \mathbf{H} \cdot (\mathbf{W}_{-1,.} ^c) ^t, la matrice des poids de la couche c est utilisée en lui ôtant la première ligne ;
  • Calculer les corrections des poids de la couche c, \partial \mathbf{W} ^c = (1,\mathbf{H} ^{c-1})^t \cdot f'(\mathbf{H} ^c) \odot \partial  \mathbf{H}

– Enfin, une fois arrivé à la première couche,

  • On met à jour la dérivation \partial \mathbf{H} \leftarrow \partial \mathbf{H} \cdot (\mathbf{W}_{-1} ^2)^t, la matrice des poids de la seconde couche est utilisée en lui supprimant la première ligne ;
  • Calculer les corrections des poids de la première couche, \partial \mathbf{W} ^1 = (1,\mathbf{X})^t \cdot f'(\mathbf{H} ^1) \odot \partial \mathbf{H}

Etape 5 : On dispose désormais des matrices de correction des différentes couches \partial \mathbf{W} ^1, \partial \mathbf{W} ^2, \cdots, \partial \mathbf{W} ^C, \partial \mathbf{W} ^s. Et on applique ces corrections en une seule fois : 

\forall c \in [1, \cdots, C, S], \mathbf{W} ^c \leftarrow \mathbf{W} ^c - \alpha \cdot \partial \mathbf{W} ^c

L’algorithme de la descente du gradient, présenté ici, est le plus répandu mais demeure cependant sensible aux minimums locaux. Parmi les autres méthodes existantes : l’algorithme de Levenberg-Marquardt optimal pour les petits réseaux, quasi-Newton, gradient conjugué meilleur compromis pour les réseaux complexes, propagation rapide ou encore les algorithmes génétiques. Concernant celui présenté ici, on peut retrouver dans la littérature plusieurs amélioration comme l’optimisation stochastique, la régularisation (pour éviter le surapprentissage) et des techniques avancées telles que les algorithmes Adam, RMSprop, etc.

Etape 6 : Sur le nombre d’itérations it défini en guise de paramétrage, on répète les étapes 2 à 5 jusqu’à ce que le modèle atteigne une performance satisfaisante sur les données d’entraînement.

Enfin, et comme tout bon outil de type data mining, il convient de valider le modèle construit par précédés empiriques. On lance alors ces étapes sur un échantillon d’apprentissage obtenu soit en scindant l’échantillon de base (cross-validation, LOOCV, tirage aléatoire de deux tiers de l’échantillon) et de valider la modèle « tuné » obtenu sur un échantillon test. L’objectif étant d’obtenir les mêmes performances sur échantillon test et échantillon de validation. Si c’est le cas on peut le valider, sinon il faut en reconstruire un nouveau.

Autres particularités

Comme indiqué, l’algorithme présenté ici est assez incomplet vis à vis de ceux des véritables réseaux de neurones implémentés sur bon nombre de langage de programmation de nos jours. Parmi les nombreux concepts non évoqués précédemment on a :

– La convolution, donnant leur nom aux réseaux CNN, consiste à appliquer un filtre (ou noyau) directement sur \mathbf{X}. Son but est de détecter les motifs spatiaux dans les données d’entrée. Si l’on avait voulu intégrer ce concept dans l’algorithme présenté ci-dessus, grosso-modo il aurait fallu définir de deux nouveaux jeux de pondérations dédiés (filtres), appliquer la convolution des données d’origine avec le premier filtre translaté par le second. Cela définit l’étape en amont, ensuite on applique les étapes suivantes pour la propagation. Pour la rétropropagation, l’application de la convolution se fait cette fois-ci à la toute fin de l’algorithme, via processus inversé.

– La normalisation par lots (Batch Normalization) est un concept introduit pour résoudre les problèmes de convergence « lente », d’explosions du gradients et ainsi gagner en stabilité du modèle. Initialement développé pour les CNN, elle est désormais utilisée pour tout type de réseau. Il s’agit de composer un certain nombres de sous-échantillon des données d’origine, appelés mini-batch, et pour chaque sortie de neurone t, on va appliquer la Batch Normalisation au travers de la formule suivante :

BN(t) = \gamma(\frac{t - \overline{t}}{\sqrt{\sigma_t ^2 + \epsilon}}) + \beta

, avec \gamma paramètre d’échelle, \beta paramètre de translation et \epsilon constante ajoutée pour évitée la division par 0. A noter qu’il y a autant de paramétrages que de couches cachées considérées dans la structure du réseau.

Concrètement et si l’on reprenait l’algorithme décrit, cela reviendrait à appliquer la fonction d’activation après Batch Normalisation du produit scalaire pour la propagation. Et lors de la rétropropagation, appliquer le processus de calcul inversé sur \partial H et à chaque couche. Une fois de plus, le paramètre \alpha, taux d’apprentissage, servira à fixer à quelle vitesse on rectifie les paramètres \gamma et \beta.

Un mot sur le réseau résiduel (Residual Networks ou ResNets) dont le concept vise à introduire des sauts de connexion pour faciliter le flux d’information à travers le réseau, permettant l’entrainement de réseaux beaucoup plus profonds. Souvent couplé à la Batch Normalisation, il s’agit alors de l’appliquer sur certaines couches et pas systématiquement à toutes.

– Et encore de nombreux et autres nombreux concepts, tels que les fonctions d’erreur ou coût adaptatives, les méthodes de régularisation (régression Ridge, Lasso, Elasticnet, etc.), les méthodes des moments permettant de contrôler le risque que les pondérations partent dans un unique sens et aillent s’enterrer vers une solution locale, etc., que l’on ne définira pas ici. L’on invitera alors le lecteur à se fournir en ouvrage spécialisé sur le sujet tant il y en a pléthore.

Les réseaux à base radiale :

On dénombre énormément de type de réseaux différents, adaptés selon le format des données en entrée comme par exemple celles appariées ou temporelles. Une fois de plus, il est conseillé de s’orienter ensuite vers des ouvrages spécialisés. Toutefois, on pourra laisser un mot sur l’une des alternatives les plus connues au Perceptron Multicouche, les réseaux à base radiale (RBF).

Ils se distinguent par une architecture composée d’une couche d’entrée, d’une unique couche cachée utilisant des fonctions radiales comme la fonction gaussienne f(r) = e ^{-\frac{r ^2}{2 \phi ^2}} avec r distance euclidienne entre le point d’entrée et le centre et \phi le paramètre de largeur gaussienne ou écart-type, et d’une couche de sortie. La notion de centre dans un RBF consiste à en définir un pour chaque neurone de la couche cachée, soit un point dans l’espace d’entrée du réseau. 

Les RBF présentent les avantages d’une capacité à apprendre à modéliser des relations non linéaires grâce aux fonctions radiales et sont moins sensibles au surajustement par rapport au Perceptron Multicouche. Quant aux inconvénients majeurs, ils nécessitent la sélection des centres et des paramètres radiaux parfois difficile ainsi que des performances potentiellement inférieures sur des tâches plus complexes comparées aux Perceptron Multicouche.

\bullet Annexe théorique :

On présentera ici une esquisse de la démonstration de l’apport de l’algorithme de rétropropagation sur la qualité de modélisation des réseaux de neurones.
En notant \alpha le taux d’apprentissage, \mathbf{W} ^c poids de la couche cachée c, l’algorithme de la descente de gradient lors de l’étape de rétropropagation s’exprime ainsi :

\mathbf{W} ^c = \mathbf{W} ^c - \alpha \cdot \partial \mathbf{W} ^c

\partial \mathbf{W} ^c = (1,\mathbf{H} ^{c-1})^t \cdot \delta ^c \odot \partial \mathbf{H}

L’apport de la rétropropagation sur l’estimation des paramètres provient du terme \delta ^c qui est l’erreur de prédiction et servira à ajuster et réajuster les matrices des poids de chaque couche. En notant L la fonction coût qui mesure l’écart entre valeurs prédites et valeurs réelles, On peut plus précisément écrire :

\delta ^c = \frac{\partial L}{\partial z ^c} = \frac{\partial L}{\partial a ^c} \cdot \frac{\partial a ^c}{\partial z ^c}

, avec,

\frac{\partial L}{\partial a ^c} mesure comment le coût L varie par rapport à la sortie de la couche de sortie. Cela donne une indication de la sensibilité de la fonction de coût aux changements dans la prédiction ;

\frac{\partial a ^c}{\partial z ^c} représente la dérivée de la fonction d’activation par rapport à son entrée. Cette dérivée mesure comment de petits changements dans l’entrée de la couche de sortie affectent la sortie elle-même.

Maintenant, pour le taux d’apprentissage \alpha, on applique la fonction coût à la formule d’actualisation des poids, et selon le développement de Taylor on a :

L(\mathbf{W} ^c) \approx L(\mathbf{W} ^c) - \alpha \cdot \frac{\partial L}{\partial \mathbf{W} ^c} \cdot \frac{\partial L}{\partial \mathbf{W} ^c}

Si \frac{\partial L}{\partial \mathbf{W}} est faible, alors la mise à jour des poids est stable, tandis que si elle est importante, la mise à jour devient significative. Dès lors, le taux d’apprentissage va permettre un équilinre entre converge rapide et stabilité à l’entraînement.

\bullet Exemple :

Soit l’échantillon (Y, X ^1, X ^2) suivant,

\begin{tabular}{|c|c|c|} \hline Y & X1 & X2 \\ \hline A & 8.1472 & 3.1101 \\ \hline A & 9.0579 & 4.1008 \\ \hline A & 1.2699 & 4.7876 \\ \hline A & 9.1338 & 7.0677 \\ \hline A & 6.3236 & 6.0858 \\ \hline A & 0.9754 & 4.9309 \\ \hline A & 2.7850 & 4.0449 \\ \hline A & 5.4688 & 3.0101 \\ \hline A & 9.5751 & 5.9496 \\ \hline A & 9.6489 & 6.8729 \\ \hline B & 1.5761 & 1.0898 \\ \hline B & 9.7059 & 1.9868 \\ \hline B & 9.5717 & 2.9853 \\ \hline B & 4.8538 & 10.0080 \\ \hline B & 8.0028 & 8.9052 \\ \hline B & 1.4189 & 8.0411 \\ \hline B & 4.2176 & 2.0826 \\ \hline B & 9.1574 & 1.0536 \\ \hline B & 7.9221 & 9.0649 \\ \hline B & 9.5949 & 10.0826 \\ \hline \end{tabular}

Ci-dessous le nuage de point basé sur ces données,

En vert la classe « A » et en rouge la classe « B »

On cherche donc à déterminer une fonction permettant de prédire les deux classes de Y en fonction des valeurs de (X ^1, X ^2). Comme il s’agit d’un exemple, on s’affranchira de la construction par apprentissage statistique. De plus, on ne procèdera pas à la standardisation des variables en amont.

On opte pour le paramétrage suivant :

– Une architecture formée d’une couche d’entrée à 2 neurones (en lien avec le nombre de variables explicatives). 2 couches cachées, l’une à 3 neurones et la seconde à 6 neurones, et une couche de sortie à un neurone (en lien avec le nombre de variable à prédire et son nombre de classes) ;

– La fonction d’activation choisie sera la fonction sigmoïde : f(x) = \frac{1}{1 + e ^{-x}}, de dérivée f'(x) = f(x) \cdot (1 - f(x)) ;

– La fonction coût : \frac{1}{2} \sum_{i = 1} ^{n} (Y_i - H^s) ^2 ;

– Le taux d’apprentissage : \alpha = 0.1 ;

– Et pour les poids, on tire aléatoirement ceux associés à la première couche cachée de taille (2 +1) \times 3 = 3 \times 3, nombre de neurones de la couche d’entrée, soit deux car deux variables explicatives et nombre de neurones paramétrée sur cette couche cachée :

\mathbf{W ^1} = \begin{pmatrix} -0.5166363 & 0.9554752 & -0.6368997 \\ 0.1703469 & -0.8107111 & -1.0079267 \\ 0.3509102 & 0.6576622 & 0.6678786 \\ \end{pmatrix}

, à la seconde couche cachée de taille (3 + 1) \times 6 = 4 \times 6 (nombre de neurones de la première couche cachée puis de la seconde couche) :

\mathbf{W ^2} = \begin{pmatrix} 1.076244 & 1.2653173 & 1.6323954 & 0.26325727 & 0.1376167 & 1.0766192 \\ -1.586651 & 0.3370436 & 0.5188823 & 0.65283448 & -0.9157413 & -0.6095473 \\ -1.382789 & 0.7540253 & -0.8235877 & 0.01111528 & 0.3684991 & 1.4985495 \\ 0.712537 & -1.8088945 & -1.2941944 & -0.44789165 & -0.1230735 & -0.3934913 \\ \end{pmatrix}

, et à la couche de sortie de taille (6 + 1) \times 1 = 7 \times 1 (nombre de neurones de la seconde et dernière couche et nombre de réponse de Y, ici une seule) : 

W ^S = \begin{pmatrix} -0.1755870 \\ 1.2174536 \\ 0.8050443 \\ 0.4084043 \\ 1.3316199 \\ 1.2032622 \\ -0.3737364 \\ \end{pmatrix}

L’étape d’initialisation étant réglée, on passe à la suite avec l’utilisation de l’algorithme de propagation avant. Donc, pour l’itération N° 1,

– La phase de propagation, en débutant par celle dans la première couche se calcul via :

(1,X^1,X^2) \cdot \mathbf{W} ^1 = \begin{pmatrix} 1 & 8.1472 & 3.1101 \\ 1 & 9.0579 & 4.1008 \\ 1 & 1.2699 & 4.7876 \\ 1 & 9.1338 & 7.0677 \\ 1 & 6.3236 & 6.0858 \\ 1 & 0.9754 & 4.9309 \\ 1 & 2.7850 & 4.0449 \\ 1 & 5.4688 & 3.0101 \\ 1 & 9.5751 & 5.9495 \\ 1 & 9.6489 & 6.8729 \\ 1 & 1.5761 & 1.0898 \\ 1 & 9.7059 & 1.9868 \\ 1 & 9.5717 & 2.9853 \\ 1 & 4.8538 & 10.0080 \\ 1 & 8.0028 & 8.9052 \\ 1 & 1.4189 & 8.0411 \\ 1 & 4.2176 & 2.0826 \\ 1 & 9.1574 & 1.0536 \\ 1 & 7.9221 & 9.0649 \\1 & 9.5949 & 10.0826 \\ \end{pmatrix} \cdot \begin{pmatrix} -0.5166363 & 0.9554752 & -0.6368997 \\ 0.1703469 & -0.8107111 & -1.0079267 \\ 0.3509102 & 0.6576622 & 0.6678786 \\ \end{pmatrix}

= \begin{pmatrix} 1.9625799 & -3.6041549 & -6.7715113 \\ 2.4653615 & -3.6909235 & -7.0277629 \\ 1.3797047 & 3.0745768 & 1.2806696 \\ 3.5194062 & -1.8012384 & -5.1227355 \\ 2.6961386 & -0.1687367 & -2.9460498 \\ 1.3798230 & 3.4075742 & 1.6732111 \\ 1.3771764 & 1.3578227 & -0.7424736 \\ 1.4712317 & -1.4985125 & -4.1386682 \\ 3.2021926 & -2.8944031 & -6.3143555 \\ 3.5387946 & -2.3469483 & -5.7720214 \\ 0.1342694 & 0.3944337 & -1.4976390 \\ 1.8339223 & -5.6065622 & -9.0927948 \\ 2.1614455 & -4.8410891 & -8.2906542 \\ 3.8221025 & 3.6023292 & 1.1549544 \\ 3.9715412 & 0.3241301 & -2.7555435 \\ 2.5467726 & 5.0934849 & 3.3034315 \\ 0.9326244 & -1.0941325 & -3.4970076 \\ 1.4130176 & -5.7756176 & -9.1632112 \\ 4.0138346 & 0.4945831 & -2.5675436 \\ 4.6559122 & -0.1922715 & -3.5739034 \\ \end{pmatrix}

On obtient alors, \mathbf{H} ^1 = f((1,X^1,X^2) \cdot \mathbf{W} ^1)

= \frac{1}{1 + e ^{-(1,X^1,X^2)} \cdot \mathbf{W} ^1}

=\begin{pmatrix} 0.8768119 & 0.026489637 & 0.0011446497 \\ 0.9216776 & 0.024341652 & 0.0008861278 \\ 0.7989436 & 0.955831796 & 0.7825637407 \\ 0.9712349 & 0.141700377 & 0.0059243900 \\ 0.9367984 & 0.457915631 & 0.0499235420 \\ 0.7989626 & 0.967940411 & 0.8420034693 \\ 0.7985371 & 0.795405604 & 0.3224634694 \\ 0.8132445 & 0.182647478 & 0.0156938481 \\ 0.9609167 & 0.052430931 & 0.0018068614 \\ 0.9717717 & 0.087308642 & 0.0031037942 \\ 0.5335170 & 0.597349571 & 0.1827779240 \\ 0.8622283 & 0.003660231 & 0.0001124606 \\ 0.8967335 & 0.007836551 & 0.0002507874 \\ 0.9785868 & 0.973463242 & 0.7604146857 \\ 0.9815042 & 0.580330460 & 0.0597743373 \\ 0.9273564 & 0.993900831 & 0.9645463452 \\ 0.7176074 & 0.250840892 & 0.0293974927 \\ 0.8042415 & 0.003092687 & 0.0001048147 \\ 0.9822565 & 0.621185509 & 0.0712566964 \\ 0.9905843 & 0.452079660 & 0.0272810357 \\ \end{pmatrix}

– Pour celle de la seconde couche : \mathbf{H} ^2 = f((1,\mathbf{H} ^1) \cdot \mathbf{W} ^2)

= \begin{pmatrix} 0.4131971 & 0.8290266 & 0.8873606 & 0.6975065 & 0.3417072 & 0.6413910 \\ 0.3967167 & 0.8309976 & 0.8898729 & 0.7036696 & 0.3323577 & 0.6343540 \\ 0.2777784 & 0.6984109 & 0.5614262 & 0.6094393 & 0.4162714 & 0.8473614 \\ 0.3415292 & 0.8440656 & 0.8820494 & 0.7101751 & 0.3317466 & 0.6669938 \\ 0.2674192 & 0.8624788 & 0.8424723 & 0.7021508 & 0.3640911 & 0.7635354 \\ 0.2829391 & 0.6772929 & 0.5399305 & 0.6031195 & 0.4155740 & 0.8466805 \\ 0.2571439 & 0.8250455 & 0.7259808 & 0.6567811 & 0.4157567 & 0.8395577 \\ 0.3880607 & 0.8387218 & 0.8680283 & 0.6876536 & 0.3677850 & 0.7002396 \\ 0.3729437 & 0.8355459 & 0.8894837 & 0.7089625 & 0.3266808 & 0.6384757 \\ 0.3579814 & 0.8393049 & 0.8870251 & 0.7103827 & 0.3272865 & 0.6488284 \\ 0.3855555 & 0.8270591 & 0.7650751 & 0.6309584 & 0.4617451 & 0.8284457 \\ 0.4263418 & 0.8261355 & 0.8886109 & 0.6955381 & 0.3428484 & 0.6356402 \\ 0.4116335 & 0.8282124 & 0.8900171 & 0.7002840 & 0.3361046 & 0.6321991 \\ 0.2174307 & 0.7218486 & 0.5876624 & 0.6393051 & 0.3790971 & 0.8374928 \\ 0.2242718 & 0.8727600 & 0.8301045 & 0.7075874 & 0.3647764 & 0.7899260 \\ 0.2530848 & 0.6416518 & 0.5116752 & 0.6100929 & 0.3860230 & 0.8349634 \\ 0.4041359 & 0.8379625 & 0.8532239 & 0.6729059 & 0.3939569 & 0.7317527 \\ 0.4491741 & 0.8232501 & 0.8856461 & 0.6874622 & 0.3548624 & 0.6435921 \\ 0.2157767 & 0.8738982 & 0.8232102 & 0.7067181 & 0.3677830 & 0.7991075 \\ 0.2495227 & 0.8688374 & 0.8505292 & 0.7115145 & 0.3529108 & 0.7575952 \\ \end{pmatrix}

– Et enfin, la couche de sortie : \hat{Y} = S = f((1,\mathbf{H} ^2) \cdot \mathbf{W} ^s) = \begin{pmatrix} 0.9211105 \\ 0.9198026 \\ 0.8754326 \\ 0.9149425 \\ 0.9068800 \\ 0.8722916 \\ 0.8965290 \\ 0.9185722 \\ 0.9178061 \\ 0.9164806 \\ 0.9135889 \\ 0.9221999 \\ 0.9210319 \\ 0.8704239 \\ 0.9024737 \\ 0.8609141 \\ 0.9195385 \\ 0.9239743 \\ 0.9013045 \\ 0.9058494 \\ \end{pmatrix}

Avant de passer à l’étape de rétropropagation, on calcul le taux d’erreur global, en créant un vecteur numérique associé à la réponse Y, le fixant à 0 pour la classe « A » et à 1 pour la classe « B ». Selon la fonction coût retenue :

E_1 = \frac{1}{2} \sum_{i = 1} ^{20} (Y_i - \hat{Y}) ^2

= \frac{(0 - 0.9211105) ^2 + (0 - 0.9198026) ^2 + (0 - 0.8754326) ^2 + \cdots + (1 - 0.9058494) ^2}{2}

= \frac{0.848444587 + 0.846036792 + 0.766382237 + 0.837119754 + \cdots + 0.008864327}{2}

= \frac{8.307412}{2}

= 4.153706

– La phase de rétropropagation. Avant, on va calculer et incrémenter le terme : 

\partial \mathbf{H} \leftarrow S - Y = \begin{pmatrix} 0.92111052 \\ 0.91980258 \\ 0.87543260 \\ 0.91494249 \\ 0.90687996 \\ 0.87229158 \\ 0.89652903 \\ 0.91857219 \\ 0.91780608 \\ 0.91648058 \\ -0.08641106 \\ -0.07780008 \\ -0.07896811 \\ -0.12957611 \\ -0.09752632 \\ -0.13908589 \\ -0.08046153 \\ -0.07602568 \\ -0.09869546 \\ -0.09415056 \\ \end{pmatrix}

, et maintenant la matrice correctrice des poids associées à la couche de sortie :

\partial W ^s = (1, \mathbf{H} ^2) \cdot \partial \mathbf{H}

= \begin{pmatrix} 1 & 1 & \cdots & 1 & 1 \\ 0.4131971 & 0.3967167 & \cdots & 0.2157767 & 0.2495227 \\ 0.8290266 & 0.8309976 & \cdots & 0.8738982 & 0.8688374 \\ 0.8873606 & 0.8898729 & \cdots & 0.8232102 & 0.8505292 \\ 0.6975065 & 0.7036696 & \cdots & 0.7067181 & 0.7115145 \\ 0.3417072 & 0.3323577 & \cdots & 0.3677830 & 0.3529108 \\ 0.6413910 & 0.6343540 & \cdots & 0.7991075 & 0.7575952 \\ \end{pmatrix} \cdot \begin{pmatrix} 0.92111052 \\ 0.91980258 \\ 0.87543260 \\ 0.91494249 \\ 0.90687996 \\ 0.87229158 \\ 0.89652903 \\ 0.91857219 \\ 0.91780608 \\ 0.91648058 \\ -0.08641106 \\ -0.07780008 \\ -0.07896811 \\ -0.12957611 \\ -0.09752632 \\ -0.13908589 \\ -0.08046153 \\ -0.07602568 \\ -0.09869546 \\ -0.09415056 \end{pmatrix}

= \begin{pmatrix} 8.101147 \\ 2.752158 \\ 6.563701 \\ 6.515795 \\ 5.514313 \\ 2.931740 \\ 5.803216 \\ \end{pmatrix}

Avant de procéder à la rétropropagation sur la seconde couche cachée, on met à jour :

\partial \mathbf{H} \leftarrow \partial \mathbf{H} \cdot (W_{-1} ^s) ^t

= \begin{pmatrix} 0.92111052 \\ 0.91980258 \\ 0.87543260 \\ 0.91494249 \\ 0.90687996 \\ 0.87229158 \\ 0.89652903 \\ 0.91857219 \\ 0.91780608 \\ 0.91648058 \\ -0.08641106 \\ -0.07780008 \\ -0.07896811 \\ -0.12957611 \\ -0.09752632 \\ -0.13908589 \\ -0.08046153 \\ -0.07602568 \\ -0.09869546 \\ -0.09415056 \\ \end{pmatrix} \cdot \begin{pmatrix} 1.2174536 & 0.8050443 & 0.4084043 & 1.3316199 & 1.2032622 & -0.3737364 \\ \end{pmatrix}

= \begin{pmatrix} 1.12140930 & 0.74153476 & 0.37618554 & 1.2265691 & 1.10833749 & -0.34425252 \\ 1.11981695 & 0.74048181 & 0.37565137 & 1.2248275 & 1.10676370 & -0.34376369 \\ 1.06579856 & 0.70476201 & 0.35753048 & 1.1657435 & 1.05337497 & -0.32718102 \\ 1.11390001 & 0.73656922 & 0.37366649 & 1.2183557 & 1.10091573 & -0.34194730 \\ 1.10408425 & 0.73007853 & 0.37037372 & 1.2076194 & 1.09121439 & -0.33893404 \\ 1.06197452 & 0.70223336 & 0.35624767 & 1.1615609 & 1.04959551 & -0.32600710 \\ 1.09148248 & 0.72174557 & 0.36614635 & 1.1938359 & 1.07875951 & -0.33506552 \\ 1.11831900 & 0.73949129 & 0.37514887 & 1.2231890 & 1.10528321 & -0.34330385 \\ 1.11738630 & 0.73887454 & 0.37483599 & 1.2221689 & 1.10436138 & -0.34301753 \\ 1.11577257 & 0.73780746 & 0.37429465 & 1.2204038 & 1.10276646 & -0.34252214 \\ -0.10520145 & -0.06956473 & -0.03529065 & -0.1150667 & -0.10397516 & 0.03229496 \\ -0.09471799 & -0.06263251 & -0.03177389 & -0.1036001 & -0.09361390 & 0.02907672 \\ -0.09614001 & -0.06357283 & -0.03225092 & -0.1051555 & -0.09501935 & 0.02951326 \\ -0.15775290 & -0.10431451 & -0.05291945 & -0.1725461 & -0.15591404 & 0.04842731 \\ -0.11873377 & -0.07851301 & -0.03983017 & -0.1298680 & -0.11734974 & 0.03644914 \\ -0.16933061 & -0.11197030 & -0.05680328 & -0.1852095 & -0.16735679 & 0.05198146 \\ -0.09795818 & -0.06477510 & -0.03286084 & -0.1071442 & -0.09681632 & 0.03007140 \\ -0.09255774 & -0.06120404 & -0.03104922 & -0.1012373 & -0.09147883 & 0.02841356 \\ -0.12015714 & -0.07945422 & -0.04030766 & -0.1314248 & -0.11875652 & 0.03688609 \\ -0.11462393 & -0.07579537 & -0.03845150 & -0.1253728 & -0.11328781 & 0.03518749 \\ \end{pmatrix}

Et puis on travaille sur la seconde couche, en faisant intervenir la dérivée de la fonction d’activation, d’où l’utilisation du terme \mathbf{H} ^c \odot (1 - \mathbf{H} ^c) :

\partial \mathbf{W} ^2 = (1, \mathbf{H} ^1)^t \cdot (\mathbf{H} ^2 \odot (1 - \mathbf{H} ^2) \odot \partial \mathbf{H})

= \begin{pmatrix} 1 & \cdots & 1 \\ 0.87681189 & \cdots & 0.99058426 \\ 0.02648964 & \cdots & 0.45207966 \\ 0.00114465 & \cdots & 0.02728104 \\ \end{pmatrix} \cdot (\begin{pmatrix} 0.4131971 & \cdots & 06413910 \\ 0.3967167 & \cdots & 0.6343540 \\ \cdots & \cdots & \cdots \\ 0.2157767 & \cdots & 0.7991075 \\ 0.2495227 & \cdots & 0.7575952 \\ \end{pmatrix} \odot (1 - \begin{pmatrix} 0.4131971 & \cdots & 0.6413910 \\ 0.3967167 & \cdots & 0.6343540 \\ \cdots & \cdots & \cdots \\ 0.2157767 & \cdots & 0.7991075 \\ 0.2495227 & \cdots & 0.7575952 \\ \end{pmatrix}) \odot \begin{pmatrix} 1.12140930 & \cdots & -0.34425252 \\ \cdots & \cdots & \cdots \\ -0.11462393 & \cdots & 0.03518749 \\ \end{pmatrix})

= \begin{pmatrix} 1 & \cdots & 1 \\ 0.87681189 & \cdots & 0.99058426 \\ 0.02648964 & \cdots & 0.45207966 \\ 0.00114465 & \cdots & 0.02728104 \\ \end{pmatrix} \cdot (\begin{pmatrix} 0.4131971 & \cdots & 0.6413910 \\ 0.3967167 & \cdots & 0.6343540 \\ \cdots & \cdots & \cdots \\ 0.2157767 & \cdots & 0.7991075 \\ 0.2495227 & \cdots & 0.7575952 \\ \end{pmatrix} \odot \begin{pmatrix} 0.5868029 & \cdots & 0.3586090 \\ 0.6032833 & \cdots & 0.3656460 \\ \cdots & \cdots & \cdots \\ 0.7842233 & \cdots & 0.2008925 \\ 0.7504773 & \cdots & 0.2424048 \\ \end{pmatrix} \odot \begin{pmatrix} 1.12140930 & \cdots & -0.34425252 \\ \cdots & \cdots & \cdots \\ -0.11462393 & \cdots & 0.03518749 \\ \end{pmatrix})

= \begin{pmatrix} 1 & \cdots & 1 \\ 0.87681189 & \cdots & 0.99058426 \\ 0.02648964 & \cdots & 0.45207966 \\ 0.00114465 & \cdots & 0.02728104 \\ \end{pmatrix} \cdot \begin{pmatrix} 0.27190280 & \cdots & -0.079181036 \\ \cdots & \cdots & \cdots \\ -0.02146461 & \cdots & 0.006461996 \end{pmatrix}

= \begin{pmatrix} 2.1886287 & 0.9813242 & 0.4658472 & 2.3283033 & 2.2379984 & -0.59232150 \\ 1.9501212 & 0.8585796 & 0.4004733 & 2.0553278 & 1.9778999 & -0.53306538 \\ 0.6982186 & 0.3956445 & 0.2301328 & 0.8468720 & 0.8022339 & -0.15629433 \\ 0.3744277 & 0.2419484 & 0.1448994 & 0.4754036 & 0.4433529 & -0.07458981 \\ \end{pmatrix}

Avant de boucler la rétropropagation, on met à nouveau à jour : \partial \mathbf{H} \leftarrow \partial \mathbf{H} \cdot (\mathbf{W}_{-1} ^2) ^t

= \begin{pmatrix} -1.3385252 & -1.3951825 & -1.5794864 \\ -1.3366245 & -1.3932014 & -1.5772436 \\ -1.2721477 & -1.3259954 & -1.5011596 \\ -1.3295620 & -1.3858400 & -1.5689097 \\ -1.3178458 & -1.3736279 & -1.5550844 \\ -1.2675832 & -1.3212377 & -1.4957735 \\ -1.3028042 & -1.3579496 & -1.5373350 \\ -1.3348366 & -1.3913378 & -1.5751338 \\ -1.3337233 & -1.3901774 & -1.5738201 \\ -1.3317971 & -1.3881697 & -1.5715472 \\ 0.1255695 & 0.1308846 & 0.1481745 \\ 0.1130563 & 0.1178418 & 0.1334087 \\ 0.1147537 & 0.1196110 & 0.1354116 \\ 0.1882954 & 0.1962656 & 0.2221924 \\ 0.1417218 & 0.1477206 & 0.1672346 \\ 0.2021147 & 0.2106698 & 0.2384994 \\ 0.1169238 & 0.1218730 & 0.1379725 \\ 0.1104778 & 0.1151542 & 0.1303660 \\ 0.1434208 & 0.1494915 & 0.1692393 \\ 0.1368163 & 0.1426074 & 0.1614459 \\ \end{pmatrix}

Et enfin, on travaille sur la première couche cachée, en faisant à nouveau intervenir la dérivée de la fonction d’activation :

\partial \mathbf{W} ^1 = (1, \mathbf{X}) \cdot (\mathbf{H} ^1 \odot (1 - \mathbf{H} ^1) \odot \partial \mathbf{H})

= \begin{pmatrix} -1.142825  & -1.115025 & -0.8088836 \\ -5.235543 & -6.785117 & -1.7740459 \\ -5.204545 & -5.531934 & -3.4801889 \\ \end{pmatrix}

Les termes d’ajustement sont désormais tous déterminés, il ne reste plus qu’à les appliquer :

\mathbf{W} ^1 \leftarrow \mathbf{W} ^1 - \alpha \cdot \partial \mathbf{W} ^1

= \begin{pmatrix} -0.5166363 & 0.9554752 & -0.6368997 \\ 0.1703469 & -0.8107111 & -1.0079267 \\ 0.3509102 & 0.6576622 & 0.6678786 \\ \end{pmatrix} - 0.01 \cdot \begin{pmatrix} -1.142825 & -1.115025 & -0.8088836 \\ -5.235543 & -6.785117 & -1.7740459 \\ -5.204545 & -5.531934 & -3.4801889 \\ \end{pmatrix}

= \begin{pmatrix} -0.4023538 & 1.0669776 & -0.5560114 \\ 0.6939012 & -0.1321994 & -0.8305222 \\ 0.8713647 & 1.2108556 & 1.0158975 \\ \end{pmatrix}

\mathbf{W} ^2 \leftarrow \mathbf{W} ^2 - \alpha \cdot \partial \mathbf{W} ^2

= \begin{pmatrix} 0.8573814 & 1.1671849 & 1.585811 & 0.03042694 & -0.08618317 & 1.1358513 \\ -1.7816631 & 0.2511856 & 0.478835 & 0.44730170 & -1.11353124 & -0.5562407 \\ -1.4526108 & 0.7144609 & -0.846601 & -0.07357192 & 0.28827575 & 1.5141790 \\ 0.6750942 & -1.8330894 & -1.308684 & -0.49543201 & -0.16740878 & -0.3860323 \\ \end{pmatrix}

\mathbf{W} ^s \leftarrow \mathbf{W} ^s - \alpha \cdot \partial \mathbf{W} ^s

= \begin{pmatrix} -0.9857017 \\ 0.9422378 \\ 0.1486742 \\ -0.2431751 \\ 0.7801886 \\ 0.9100882 \\ -0.9540580 \end{pmatrix}

Les trois jeux de pondérations étant désormais mis à jour, on peut lancer la seconde itération. Sans reprendre les calculs, car il s’agit exactement des mêmes et seul \mathbf{W} ^1, \mathbf{W} ^2, W ^s vont changer, on obtiendra pour 20000 itérations, la courbe de l’évolution de l’erreur de prédiction suivante :

Au bout de 10000 itérations, l’algorithme converge. On a ainsi les pondérations finales suivantes :

\mathbf{W} ^1 = \begin{pmatrix} -27.913541 & -1.692546 & -25.192062 \\ -2.256813 & -5.271274 & -8.311072 \\ 16.047583 & 16.263879 & 11.522344 \\ \end{pmatrix}

\mathbf{W} ^2 = \begin{pmatrix} 3.1876627 & 1.9608594 & 2.714583 & 1.1968219 & 1.462413844 & 1.1547669 \\ -4.6957099 & -5.4131982 & -4.467734 & -3.0069745 & -4.644235307 & -0.8838743 \\ -1.5607231 & -0.1115301 & -1.406312 & -0.8445986 & -0.009784852 & 3.5465520 \\ -0.7995404 & -3.1382452 & -1.881752 & -1.0259792 & -1.699814696 & -7.2018946 \\ \end{pmatrix}

W ^s = \begin{pmatrix} 1.018232 \\ 5.555755 \\ 5.637654 \\ 4.665471 \\ 3.050049 \\ 4.611297 \\ -11.054914 \end{pmatrix}

Les performances obtenues peuvent être décrites au travers de la matrice de confusion suivante :

\begin{tabular}{|l|c|c|} \hline & Prediction = A & Pr\'ediction = B \\ \hline Observ\'e = A & 8 & 2 \\ \hline Observ\'e = B & 0 & 10 \\ \hline \end{tabular}

, soit un taux de bonne classification globale de 90 \%. Enfin, et plus concrètement, on peut afficher le découpage du plan comparé aux classes réelles.

Les bandes rouges correspondent à la région de la classe « A » construite par le modèle, en bleu celle de la classe « B ». En rouge les données réelles de la classe « A » et en vert celles de la classe « B »

\bullet Application sous R :

Soit l’exemple suivant :

BDD = data.frame(Y = c(rep(0,10),rep(1,10)),
X1 = c(8.1472, 9.0579, 1.2699, 9.1338, 6.3236, 0.9754, 2.7850, 5.4688, 9.5751, 9.6489, 1.5761, 9.7059, 9.5717, 4.8538, 8.0028, 1.4189, 4.2176, 9.1574, 7.9221, 9.5949),
X2 = c(3.1101, 4.1008, 4.7876, 7.0677, 6.0858, 4.9309, 4.0449, 3.0101, 5.9496, 6.8729, 1.0898, 1.9868, 2.9853, 10.0080, 8.9052, 8.0411, 2.0826, 1.0536, 9.0649, 10.0826))

Package et fonction R: neuralnet: Training of Neural Networks (r-project.org)

La fonction neuralnet du package du même nom permet de réaliser des réseaux de neurones. Après chargement du package, on lance sa conception de la manière suivante :

neuralnet(Y ~ x1 + x2, data = BDD, hidden = c(3,6), algorithm = « backprop », learningrate = 0.1, act.fct = « logistic », linear.output = FALSE, lifesign = « full », constant = TRUE)

Parmi les éléments à insérer les plus importants il faut relever :

–  La formule définissant variable réponse (à gauche) et variables explicatives (à droite) : Y ~ X1 + X2 ;

–  La base de données sur laquelle on souhaite travailler : data = BDD ;

– Le nombre de couches cachées et de neurones par couche : hidden = c(3, 6) ;

– L’utilisation de l’algorithme de rétropropagation : algorithm = "backprop" ;

– Le taux d’apprentissage nécessaire pour définir la vitesse à laquelle on rectifie les pondérations : learningrate = 0.1 ;

– La fonction d’activation, ici la fonction sigmoïde : act.fct = "logistic" ;

– L’affichage de tous les résultats obtenus : lifesign = "full"

– Le choix d’un modèle non linéraire : linear.output = FALSE.

On obtient alors les résultats suivants :

, avec, à gauche, les poids finaux par couche et, à droite, les scores de prédiction.

Les différences obtenues avec les calculs manuels (cf partie « Exemple ») sont explicables du fait que la fonction neuralnet est bien plus complète et permet de lancer des réseaux de neurones bien plus élaborés que celui présenté dans cet article.

\bullet Application sous SAS :

Soit l’exemple suivant :

data BDD;
input x1 x2 Y;
cards;
8.1472 3.1101 0
9.0579 4.1008 0
1.2699 4.7876 0
9.1338 7.0677 0
6.3236 6.0858 0
0.9754 4.9309 0
2.7850 4.0449 0
5.4688 3.0101 0
9.5751 5.9495 0
9.6489 6.8729 0
1.5761 1.0898 1
9.7059 1.9868 1
9.5717 2.9853 1
4.8538 10.0080 1
8.0028 8.9052 1
1.4189 8.0411 1
4.2176 2.0826 1
9.1574 1.0536 1
7.9221 9.0649 1
9.5949 10.0826 1
;
run;

Le logiciel SAS offre un module complet pour la production de réseaux de neurones : SAS Visual Data Mining and Machine Learning. Cependant, ce dernier n’est pas disponible dans le package de base, tout comme le module IML pour les calculs matriciels. Par conséquent, on proposera la macro suivante :

%macro neuralNetwork(DATA =, REPONSE =, W =, LEARN_RATE =, ITERATION =, TYPE =);
/* La macro neuralNetwork est disponible pour concevoir son propre réseau de neurones. Sur cette première version, il n’est pas possible de traiter plusieurs réponses Y à la fois. Concernant l’architecture, elle se déploie automatiquement en fonction des matrices de pondérations insérées dans le paramétrage. Par défaut, le programme n’a été implémenté que pour la fonction sigmoïde, des indications dans le code permettent de voir où porter les modifications pour l’ajout d’autres fonctions d’activation.
Paramètres : La base de données DATA et le nom de la variable à discriminer REPONSE, la liste des matrices/vecteurs de pondération W, le taux d’apprentissage LEARN_RATE, le nombre d’itérations à exécuter ITERATION. Si l’on veut utiliser la macro pour classer un nouvelle individu TYPE = C ou pour tuner ses paramètres TYPE = M.
Le programme n’ayant été testé que pour l’exemple qui suit, il est important de conserver les mêmes noms de variables et structure des données. Des mises à jour seront réalisées dans le temps, ajoutant d’autres fonctionnalités */
/* Paramétrage des fonctions pour n’afficher que les erreurs et les warnings */ 
options nonotes spool;
/* Si on veut classer un nouvelle individu, alors on se débarrasse du paramètre sur les itérations en le fixant à 1 puisqu’il s’agit d’utiliser uniquement que la propagation sur les paramètres entrés */
%if &TYPE. = C %then %do;
%let ITERATION = 1;
%put Classification;
%let Obj = Classification;
%end;
/* On renvoi les paramètres du réseau en fonction des matrices des poids utilisées */
%let nbc = %eval(%sysfunc(countw(&W.)));
%do couche = 1 %to &nbc.;
%if &couche. < &nbc. %then %do;
proc contents data = W&couche. out = biblio noprint;
run;
proc sql noprint;
select count(*) into: nb_neurones from biblio;
quit;
%if &couche. = 1 %then %do;
%let neurones = &nb_neurones.;
%end;
%if &couche. > 1 %then %do;
%let neurones = &neurones. &nb_neurones.;
%end;
%end;
%end;
%put Au regard du paramétrage, le réseau programmé aura &nbc. couches cachées, de nombre de neurones respectifs : &neurones.;
/* Pour chaque itération, on applique les étapes de propagation et rétropropagation */
%do it = 1 %to &ITERATION.;
%if &TYPE. = M %then %do;
%put Modélisation;
%put Itération N° &it. sur &ITERATION.;
%let Obj = Modélisation;
%end;
%put Application de la propagation dans un objectif de &Obj.;
/* On lance la propagation pour chaque couche indirectement paramétrée via les poids insérés */
%do couche = 1 %to &nbc.;
%put Sur couche &couche.;
%let cmu = %eval(&couche. – 1);
/* Constitution de la matrice de gauche pour le produit scalaire (1,X) %*% W */
data I_data;
/* Si première couche alors X est la matrice de données d’origine */
%if &couche. = 1 %then %do;
set &DATA.;
drop &REPONSE.;
%end;
/* Pour les couches suivantes, X est H de la couche moins un */
%if &couche. > 1 %then %do;
set H&cmu.;
%end;
/* On créé le colonne associée à la constante de régression */
Cst = 1;
run;
/* L’ordre étant primordial pour le bon déroulement de l’algorithme, on s’assure que la colonne Cst est bien en première */
proc contents data = I_data out = biblio noprint;
run;
proc sql noprint;
select(name) into: list_var separated by ‘,’ from biblio where name ne « Cst »;
quit;
proc sql noprint;
create table I_dataf as select Cst, &list_var. from I_data;
quit;
/* Application du produit sclaire (1,X) %*% W en prenant les bons X et W en lien avec la couche en cours, on créé alors nos matrices H */
%produit_scalaire(A = I_dataf, B = W&couche., Activation = 1);
data H&couche.;
set ResultF;
run;
%end;
/* Si on cherche juste à classifier, alors l’algorithme s’arrête, dans le cas où l’on veut tuner nos paramètres/modéliser, on applique la rétropropagation */
%if &TYPE. = M %then %do;
/* On calcul l’erreur de prédiction afin de constituer le vecteur que l’on pourrait ploter pour voir sa convergence, mais également l’erreur par simple différence nécessaire aux calculs à venir */
data Erreur;
merge &DATA. H&nbc.;
/* Choix de la fonction erreur et sa dérivée */
Erreur = (col1 – &REPONSE.);
Erreur2 = Erreur**2;
keep Erreur Erreur2;
run;
proc sql noprint;
select sum(Erreur2) into: Erreur from Erreur;
quit;
/* Ajout de la nouvelle Erreur calculée aux autres */
data Courbe_erreur_add;
Erreur = 0.5*&Erreur.;
run;
%if &it. = 1 %then %do;
data Courbe_erreur;
set Courbe_erreur_add;
run;
%end;
%if &it. > 1 %then %do;
data Courbe_erreur;
set Courbe_erreur Courbe_erreur_add;
run;
%end;
%put Application de la rétropropagation pour la Modélisation;
/* Algorithme de rétropropagation */
%do couche = 1 %to &nbc.;
/* On inverse l’incrémentation pour retrouver la logique backward */
%let couche_retro = %eval(-(&couche. – &nbc. – 1));
%let cmu = %eval(&couche_retro. – 1);
%put Sur couche &couche_retro.;
/* On crée la matrice t(1,H), matrice à gauche du produit scalaire */
data I_data;
/* En bout de rétropropagation, H est les données d’origine */
%if &couche_retro. = 1 %then %do;
set &DATA.;
drop &REPONSE.;
%end;
/* Sur les couches en sens inverse, on prend le H calculé lors de la propagation */
%if &couche_retro. > 1 %then %do;
set H&cmu.;
%end;
/* On rajoute la Constante */
Cst = 1;
run;
/* On s’assure que la Constante est bien en première colonne */
proc contents data = I_data out = biblio noprint;
run;
proc sql noprint;
select(name) into: list_var separated by ‘,’ from biblio where name ne « Cst »;
quit;
proc sql noprint;
create table I_dataf as select Cst, &list_var. from I_data;
quit;
proc transpose data = I_dataf out = T_I_dataG;
run;
data T_I_dataG;
set T_I_dataG;
drop _name_;
run;
/* Sur la première couche rétropropagée, à savoir la dernière du réseau, on applique la formule t(1,H) %*% fonction coût ou sa dérivée */
%if &couche_retro. = &nbc. %then %do;
/* Matrice à droite du produit scalaire */
data H;
set Erreur;
keep Erreur;
run;
/* Application du produit scalaire et création de la matrice correctice des poids de la dernière couche */
%produit_scalaire(A = T_I_dataG, B = H, Activation = 0);
data dW&couche_retro.;
set ResultF;
run;
%end;
%let nbc_mu = %eval(&nbc. – 1);
%let nbc_md = %eval(&nbc. – 2);
%if &couche_retro. < &nbc. %then %do;
/* Pour les couches, en sens inverse, suivantes, on créé la matrice à droite */
proc contents data = H&couche_retro. out = biblio noprint;
run;
proc sql noprint;
select(name) into: list_mu separated by ‘ ‘ from biblio;
select count(*) into: nb_mu from biblio;
quit;
data H;
merge H&couche_retro.;
%do vmu = 1 %to &nb_mu.;
/* Application de la dérivée de la fonction d’activation, ici la fonction sigmoïde. Afin d’étendre le programme à d’autres fonctions d’activation, il convient de réaliser les ajouts ici de leur dérivée */
h_%scan(&list_mu.,&vmu.,’ ‘) = %scan(&list_mu.,&vmu.,’ ‘) * (1 – %scan(&list_mu.,&vmu.,’ ‘));
drop %scan(&list_mu.,&vmu.,’ ‘);
%end;
run;
/* On récupére la bonne matrice des poids associés à la couche en cours de traitement */
%let cr_pu = %eval(&couche_retro. + 1);
data T_W;
set W&cr_pu.;
/* A laquelle on supprime la première ligne, que l’on transpose ensuite */
if _n_ ne 1;
run;
proc transpose data = T_W out = T_W;
run;
data T_W;
set T_W;
drop _NAME_;
run;
/* Si on est à l’avant-dernière couche, alors exceptionnellement, on prend l’erreur ou sa dérivée directement */
%if &couche_retro. = &nbc_mu. %then %do;
data Erreur;
set Erreur;
drop Erreur2;
run;
%produit_scalaire(A = Erreur, B = T_W, Activation = 0);
/* Et comme pour les couches qui vont suivre on incrémente la dérivation, on l’enregistre afin de l’ajustement couche par couche */
data dh;
set ResultF;
run;
%end;
/* A partir de l’avant-avant-dernière couche, on peut plus facilement automatiser en prenant la dérivation dh incrémentée et l’appliquer directement */
%if &couche_retro. <= &nbc_md. %then %do;
%produit_scalaire(A = dh, B = T_W, Activation = 0);
data dh;
set ResultF;
run;
%end;
/* Reste un dernier produit scalaire à exécuter et sur les matrices réassemblées à gauche et à droite */
data T_I_dataD;
merge H dh;
%do vmu = 1 %to &nb_mu.;
hh_%scan(&list_mu.,&vmu.,’ ‘) = h_%scan(&list_mu.,&vmu.,’ ‘) * %scan(&list_mu.,&vmu.,’ ‘);
drop h_%scan(&list_mu.,&vmu.,’ ‘) %scan(&list_mu.,&vmu.,’ ‘);
%end;
run;
%produit_scalaire(A = T_I_dataG, B = T_I_dataD, Activation = 0);
/* On obtient alors les autres matrices correctices des poids */
data dW&couche_retro.;
set ResultF;
run;
%end;
%end;
/* On applique les corrections dW à nos poids W et ensuite on peut passer à l’itération suivante avec les nouveaux W mis à jour */
%put Mise à jour des poids;
%do couche = 1 %to &nbc.;
proc contents data = W&couche. out = biblio noprint;
run;
proc sql noprint;
select (name) into: list_w separated by ‘ ‘ from biblio;
select count(*) into: nb_w from biblio;
quit;
proc contents data = dW&couche. out = biblio noprint;
run;
proc sql noprint;
select (name) into: list_dw separated by ‘ ‘ from biblio;
quit;
data W&couche.;
merge W&couche. dW&couche.;
%do w = 1 %to &nb_w.;
%scan(&list_w.,&w.,’ ‘) = %scan(&list_w.,&w.,’ ‘) – &LEARN_RATE.*%scan(&list_dw.,&w.,’ ‘);
drop %scan(&list_dw.,&w.,’ ‘);
%end;
run;
%end;
%end;
%end;
/* Que ce soit pour du tuning/modélisation de paramètres, ou de la classification directement, on renvoie la matrice d’origine avec le résultats de ces prévisions que l’on peut traiter à part */
data Classification;
merge &DATA. H&nbc.;
run;
/* Suppression des différentes tables annexes utilisées et sans intérêt */
proc datasets lib = work nolist;
delete varA varB PA PB prod_scal result_add resultF result I_data biblio I_dataF erreur T_I_dataG T_I_dataD dH H T_w courbe_erreur_add;
%do d = 1 %to &nbc.;
delete H&d. Dw&d.;
%end;
run;
/* Réinitialisation des paramètres de la log */
options notes nospool;
%mend;

, qu’il faudra coupler à la macro suivante permettant de calculer les produits scalaires nécessaires :

%macro produit_scalaire(A=, B=, Activation=);
/* Macro pour réaliser un produit scalaire entre deux matrices/vecteurs de données 
   Paramètres : la matrice de gauche A, la matrice de droite B, et si oui (1) ou non (0) on applique au résultat du produit scalaire la fonction d’activation pour chaque cellule/terme. Sur cette première version, seule la fonction sigmoïde a été codée */
/* Nombre de lignes de la matrice de gauche */ 
proc sql noprint;
select count(*) into: nbA from &A.;
quit;
/* Nombre de colonnes de la matrice de droite */
proc contents data = &B. out = varB noprint;
run;
proc sql noprint;
select count(*) into: varB from varB;
select (name) into: listB separated by ‘ ‘ from varB;
quit;
/* Application du produit scolaire */
%do l = 1 %to &nbA.;
%do c = 1 %to &varB.;
/* On récupère les éléments de la ligne l de la matrice de gauche */
data PA;
set &A.;
if _n_ = &l.;
run;
proc transpose data = PA out = PA;
run;
/* On récupère les éléments de la colonne c de la matrice de droite */
data PB;
set &B.;
rename %scan(&listB.,&c.,’ ‘) = T_%scan(&listB.,&c.,’ ‘);
keep %scan(&listB.,&c.,’ ‘);
run;
/* On les superpose, afin de directement multiplier les deux vecteurs en cours de traitement */
data prod_scal;
merge PA PB;
prod = col1*T_%scan(&listB.,&c.,’ ‘);
run;
/* On somme les produits réalisés un à un */
proc sql noprint;
select sum(prod) into: prod_scal from prod_scal;
quit;
/* Adaptée au réseau de neurone, on propose ici de mettre directement l’application de la fonction d’activation ou non, on peut modifier le paramètre Activation si l’on souhaite rajouter d’autres fonctions */
data Result_add;
%if &Activation. = 1 %then %do;
/* Application de la fonction d’activation */
col&c. = 1/(1 + exp(-&prod_scal.));
%end;
%if &Activation. = 0 %then %do;
col&c. = &prod_scal.;
%end;
run;
/* On reconstitue la première ligne des résultats des différents produits scalaires */
%if &c. = 1 %then %do;
data Result;
set Result_add;
run;
%end;
%if &c. > 1 %then %do;
data Result;
merge Result Result_add;
run;
%end;
%end;
/* On rajoute ensuite les nouvelles lignes des résultats des différents produits scalaires */
%if &l. = 1 %then %do;
data ResultF;
set Result;
run;
%end;
%if &l. > 1 %then %do;
data ResultF;
set ResultF Result;
run;
%end;
%end;
%mend;

On lance le réseau de neurones dans un objectif de déterminer le modèle prédictif. On doit alors produire dans un premier temps les différents poids \mathbf{W} ^1, \mathbf{W} ^2, W ^3 = W ^s. Etant donné que l’on veut 2 couches cachées à, respectivement, 3 et 6 neurones,

data w1;
input w1_1 w1_2 w1_3;
cards;
-0.5166363 0.9554752 -0.6368997
0.1703469 -0.8107111 -1.0079267
0.3509102 0.6576622 0.6678786
;
run;

data w2;
input w2_1 w2_2 w2_3 w2_4 w2_5 w2_6;
cards;
1.076244 1.2653173 1.6323954 0.26325727 0.1376167 1.0766192
-1.586651 0.3370436 0.5188823 0.65283448 -0.9157413 -0.6095473
-1.382789 0.7540253 -0.8235877 0.01111528 0.3684991 1.4985495
0.712537 -1.8088945 -1.2941944 -0.44789165 -0.1230735 -0.3934913
;
run;

data w3;
input w;
cards;
-0.1755870
1.2174536
0.8050443
0.4084043
1.3316199
1.2032622
-0.3737364
;
run;

On applique la macro de la manière suivante :

%neuralNetwork(DATA = BDD, REPONSE = Y, W = W1 W2 W3, LEARN_RATE = 0.1, ITERATION = 50, TYPE = M);

Parmi les éléments à insérer, il faut relever :

– Le tableau sur lequel on veut travailler : DATA = BDD ;

– Le nom de la réponse à discriminer : REPONSE = Y ;

– La liste des matrices des poids : W = W1 W2 W3 ;

– Le taux d’apprentissage, qui servira à définir à quel vitesse l’on applique les matrices correctrices des poids : LEARN\_RATE = 0.1 ;

– Le nombre d’itérations à réaliser : ITERATION = 50 ;

– Le mode d’utilisation de la macro : TYPE = M pour tuner les paramètres, TYPE = C pour classer un nouvel individu.

On obtient alors les résultats suivants parmi les tableaux générés en fin de procédure :

, puis :

, et :

La première série de tableaux présente les pondérations finales : \mathbf{W} ^1, \mathbf{W} ^2, W ^s. Le quatrième tableau, le taux d’erreur à chaque itération et le dernier la base de données d’origine à laquelle on a inséré les prédictions finales.

L’algorithme implémenté tel quel est particulièrement long en calcul, aussi il n’a été testé que pour 50 itérations, réalisées en 44 minutes, très fortement dépendant de la taille de la base de données du fait de l’application du produit scalaire.

\bullet Bibliographie :

– Probabilité, analyse des données et statistiques de Gilbert Saporta ;

– The Elements of Statsticial Learning de Trevor Hastie, Robert Tibshirani et Jerome Friedman ;

– Data mining et statistique décisionnelle, l’intelligence des données de Stéphane Tufféry ;

– Comprendre le deep learning, une introduction aux réseaux de neurones de Jean-Claude Heudin ;

– Le site web : https://datafuture.fr/post/fabrique-ton-premier-reseau-de-neurones/

Laisser un commentaire