Rétropropagation

7.2 Rétropropagation

Objectifs d'apprentissage

À la fin de cette section, vous devriez être en mesure de :

  • 7.2.1 Discuter de l'objectif de l'ajustement des poids et des biais pour réduire la perte/l'erreur dans un réseau de neurones.
  • 7.2.2 Utiliser la rétropropagation statique pour entraîner un réseau de neurones.
  • 7.2.3 Définir les réseaux de neurones récurrents et discuter de leurs avantages et inconvénients.

Dans la section précédente, nous avons abordé le perceptron monocouche. L'entraînement du perceptron est simple car il nécessite seulement d'ajuster un ensemble de poids et de biais qui influent directement sur la sortie. En revanche, les réseaux de neurones comportant plus d'une couche, comme les perceptrons multicouches (MLP), doivent être entraînés au moyen de méthodes capables de modifier également les poids et les biais dans les couches cachées.

La rétropropagation (backpropagation) est un algorithme utilisé pour entraîner des réseaux de neurones en déterminant la manière dont les erreurs dépendent des variations des poids et des biais de tous les neurones, en partant de la couche de sortie et en remontant à travers les couches, actualisant récursivement les paramètres en fonction de la variation de l'erreur dans chaque couche. Dans cette section, nous examinerons comment les réseaux de neurones ajustent leurs poids et leurs biais pour minimiser l'erreur (ou perte), améliorant ainsi leur capacité à formuler des prédictions exactes. Fondamentalement, la rétropropagation constitue un vaste problème d'optimisation. Dans ce manuel, nous nous concentrons sur les principes intuitifs de ce problème d'optimisation plutôt que d'entrer dans une analyse approfondie des méthodes et de la théorie requises pour exécuter les étapes d'optimisation de la rétropropagation.

Pour aller plus loin

Rétropropagation

Un traitement complet de la rétropropagation nécessite d'être familiarisé, entre autres, avec les opérations matricielles, le calcul différentiel et intégral ainsi que l'analyse numérique. De tels sujets dépassent largement le cadre de cet ouvrage, mais il existe de nombreuses ressources en ligne plus détaillées, telles que Neural Networks and Deep Learning de Michael Nielsen (2019) et What Is Backpropagation Really Doing? de 3Blue1Brown.

Rétropropagation statique

La rétropropagation est un algorithme d'apprentissage supervisé, ce qui signifie qu'il s'entraîne sur des données préalablement étiquetées (voir Qu'est-ce que l'apprentissage automatique ? pour en savoir plus sur l'apprentissage supervisé en général). L'objectif est d'ajuster itérativement les poids des connexions entre les neurones du réseau et les biais associés à chaque neurone afin de minimiser l'écart entre la sortie prédite et les valeurs cibles réelles. Le terme rétropropagation statique (static backpropagation) fait référence à l'ajustement des paramètres (poids et biais) uniquement, par opposition à la rétropropagation dynamique (dynamic backpropagation), qui peut également modifier la structure sous-jacente (neurones, couches, connexions, etc.).

Voici un aperçu général de l'algorithme de rétropropagation statique :

  1. Passe avant (forward pass). Durant la passe avant, les données d'entrée sont propagées à travers le réseau couche par couche, en utilisant les valeurs actuelles des poids et des biais ainsi que la fonction d'activation choisie, ff. Si le vecteur d'entrée est noté x(0)x(0), et les couches cachées par x(1)x(1), x(2)x(2), etc., jusqu'à atteindre la couche de sortie, x(t)x(t), alors chaque vecteur est obtenu à partir du précédent au moyen d'une formule de la forme x(k+1)=f(wx(k)+b)x(k+1)=f(wx(k)+b). (Remarque : cette formule dissimule un volume important de calculs numériques complexes. Les valeurs de ww et bb sont propres à chaque couche et renferment donc les données de poids et de biais de l'ensemble des neurones de cette couche. En réalité, ww n'est plus seulement un simple vecteur dans ce contexte, mais une matrice, que l'on peut considérer comme un tableau de nombres. Enfin, la fonction d'activation est appliquée à toutes les composantes du vecteur d'entrée, produisant une sortie vectorielle. Ces détails ne sont toutefois pas indispensables à la compréhension fondamentale du processus.)
  2. Calcul de l'erreur. Dès que le réseau génère une sortie, l'erreur entre la sortie prédite et la valeur cible réelle est calculée. L'erreur peut être mesurée de multiples façons. La fonction employée pour mesurer l'erreur dans un réseau de neurones est appelée une fonction de perte ou de coût (loss or cost function).
  3. Passe arrière (rétropropagation). L'erreur à chaque couche est propagée récursivement vers l'arrière à travers le réseau.
  4. Actualisation des paramètres. Les poids et les biais du réseau sont actualisés de manière à réduire l'erreur, généralement à l'aide d'un algorithme d'optimisation tel que la descente de gradient (que nous aborderons plus loin).
  5. Répétition. Les étapes sont répétées sur les données d'entraînement jusqu'à atteindre un niveau d'exactitude suffisant.

Erreur et perte

Error generally refers to the difference between the predicted output of the neural network and the actual target values for a single data point. It is a quantitative measure of the mistakes made by the network in its predictions. The loss function is a mathematical function that takes the predicted output and the actual target values as inputs and outputs a single value representing the error. Learning is essentially minimizing the loss function over all training data. Suppose that the neural network outputs y^=(y^1,y^2,…,y^n)y^=(y^1,y^2,…,y^n) and the target output is y=(y1,y2,…,yn)y=(y1,y2,…,yn). Common loss functions that would measure the error between y^y^ and yy include:

  • Erreur quadratique moyenne (MSE) : 1n∑i=1n(yi−y^i)21n∑i=1n(yi−y^i)2. La formule de la MSE (introduite dans Entraîner et tester un modèle) est importante en raison de son lien avec le concept de variance en statistique. C'est une fonction de perte fréquemment utilisée dans les tâches de régression, où l'objectif consiste à prédire des valeurs continues.
  • Perte d'entropie croisée binaire (binary cross entropy loss) : −1n∑i=1n[yi ln y^i+(1−yi) ln (1−y^i)]−1n∑i=1n[yi ln y^i+(1−yi) ln (1−y^i)]. Cette fonction de perte est couramment utilisée pour les tâches de classification binaire, c'est-à-dire lorsque la sortie vaut soit 0, soit 1. (Remarque : le signe moins devant la formule sert à rendre la valeur globale positive, car les valeurs des logarithmes sont généralement négatives. Le terme entropie est lié aux mesures de l'information définies dans Arbres de décision.)
  • Perte charnière (hinge loss) : 1n∑i=1nmax(0,1−yiy^i)1n∑i=1nmax(0,1−yiy^i). Cette fonction est également couramment utile en classification binaire.
  • Entropie croisée catégorielle creuse (sparse categorical cross entropy) : une généralisation de l'entropie croisée binaire, particulièrement utile lorsque les étiquettes cibles sont des entiers.

La perte moyenne est calculée comme la moyenne de la perte sur l'ensemble des points de données. Plus la perte moyenne est proche de zéro, plus l'erreur de prédiction est faible. Le choix de la fonction de perte dépend de la tâche spécifique, de la nature des entrées et des sorties ainsi que de nombreux autres facteurs, qui seront abordés plus en détail dans Introduction à l'apprentissage profond.

Exemple 7.3

Problème

Les sorties d'un réseau de neurones sur deux points de données sont présentées dans le Tableau 7.1 :

Point de données Prédit y^y^ Réel (Cible) yy
1 (0.135,−0.252,0.873,1.297)(0.135,−0.252,0.873,1.297) (1,0,1,1)(1,0,1,1)
2 (0.729,0.458,0.975,−0.025)(0.729,0.458,0.975,−0.025) (1,1,1,0)(1,1,1,0)
Tableau 7.1 Sortie du réseau de neurones

Calculez la perte moyenne à l'aide des fonctions de perte suivantes.

  1. MSE
  2. Perte d'entropie croisée binaire
  3. Perte charnière

Solution

Ici, le nombre de termes est n=4n=4.

  1. MSE
    1n∑i=1n(yi−y^i)2=14[(y1−y^1)2+(y2−y^2)2+(y3−y^3)2+(y4−y^4)2]1n∑i=1n(yi−y^i)2=14[(y1−y^1)2+(y2−y^2)2+(y3−y^3)2+(y4−y^4)2]
    Point de données 1 :
    Perte=14[(1−0.135)2+(0−(−0.252))2+(1−0.873)2+(1−1.297)2]=0.229Loss=14[(1−0.135)2+(0−(−0.252))2+(1−0.873)2+(1−1.297)2]=0.229
    Point de données 2 :
    Perte=14[(1−0.729)2+(0−(−0.252))2+(1−0.873)2+(1−1.297)2]=0.092Loss=14[(1−0.729)2+(0−(−0.252))2+(1−0.873)2+(1−1.297)2]=0.092
    Perte moyenne:12(0.229+0.092)=0.161Average loss:12(0.229+0.092)=0.161
  2. Perte d'entropie croisée binaire
    −1n∑i=1n[yi ln y^i+(1−yi) ln (1−y^i)]−1n∑i=1n[yi ln y^i+(1−yi) ln (1−y^i)]
    Raccourci : Si yi=0yi=0, le premier terme est nul, yi ln y^i=0yi ln y^i=0, et le second terme se réduit à  ln (1−y^i) ln (1−y^i). Si yi=1yi=1, le second terme est nul, (1−yi) ln (1−y^i)=0(1−yi) ln (1−y^i)=0, et le premier terme se réduit à ln y^iln y^i.
    Point de données 1 :
    Perte=−14[ln(0.135)+ln(1−(−0.252))+ln(0.873)+ln(1.297)]=0.413Loss=−14[ln(0.135)+ln(1−(−0.252))+ln(0.873)+ln(1.297)]=0.413
    Point de données 2 :
    Perte=−14[ln(0.729)+ln(0.458)+ln(0.975)+ln(1−(−0.025))]=0.274Loss=−14[ln(0.729)+ln(0.458)+ln(0.975)+ln(1−(−0.025))]=0.274
    Perte moyenne : 12(0.413+0.274)=0.34412(0.413+0.274)=0.344
  3. Perte charnière
    1n∑i=1nmax(0,1−yiy^i)1n∑i=1nmax(0,1−yiy^i)
    Calculez d'abord les valeurs de 1−yiy^i1−yiy^i. Ensuite, si l'une d'elles est négative, utilisez plutôt 0 dans la somme.
    Point de données 1 : 1−y1y^1=1−(1)(0.135)=0.8651−y1y^1=1−(1)(0.135)=0.865. 1−y2y^2=1−(0)(−0.252)=11−y2y^2=1−(0)(−0.252)=1. 1−y3y^3=1−(1)(0.873)=0.1271−y3y^3=1−(1)(0.873)=0.127. 1−y4y^4=1−(1)(1.297)=−0.2971−y4y^4=1−(1)(1.297)=−0.297.
    Perte=14[0.865+1+0.127+0]=0.498Loss=14[0.865+1+0.127+0]=0.498
    Point de données 2 : 1−y1y^1=1−(1)(0.729)=0.2711−y1y^1=1−(1)(0.729)=0.271. 1−y2y^2=1−(1)(0.458)=0.5421−y2y^2=1−(1)(0.458)=0.542. 1−y3y^3=1−(1)(0.975)=0.0251−y3y^3=1−(1)(0.975)=0.025. 1−y4y^4=1−(0)(−0.025)=11−y4y^4=1−(0)(−0.025)=1.
    Perte=14[0.271+0.542+0.025+1]=0.460Loss=14[0.271+0.542+0.025+1]=0.460
    Perte moyenne : 12(0.498+0.460)=0.4812(0.498+0.460)=0.48

Remarque : le fait que chacune des trois fonctions de perte donne un résultat différent peut traduire la nature des erreurs par rapport à la performance du modèle, mais avec seulement deux points de données dans cet exemple, il n'est pas conseillé d'accorder trop d'importance à ces chiffres.

Descente de gradient et rétropropagation

L'objectif de l'entraînement d'un réseau de neurones étant de réduire la valeur de l'erreur ou de la perte, nous devons essentiellement résoudre un (très vaste) problème d'optimisation. Supposons que vous entraîniez un réseau de neurones. Considérez l'ensemble des poids et des biais — qui peuvent se compter par milliers, par millions, voire davantage ! — comme les paramètres actuels du réseau susceptibles d'être modifiés pour améliorer le modèle. Lors d'une passe avant avec un vecteur d'entrée arbitraire x(0)x(0), la sortie y^y^ est obtenue et comparée à la sortie réelle yy au moyen d'une fonction de perte, CC. Comment exploiter cette information pour modifier les paramètres (poids et biais) afin que, lors de la passe suivante avec le même vecteur d'entrée x(0)x(0), la sortie y^y^ constitue une représentation plus exacte de la sortie réelle yy, au sens où la valeur de CC diminue ? La clé consiste à appréhender l'ensemble du réseau de neurones et le calcul de la perte comme une fonction FF des poids WW et des biais BB, puis à utiliser des techniques comme la descente de gradient (que nous aborderons brièvement dans la section suivante) pour en trouver la valeur minimale. Ici, WW et BB sont en majuscules car ils représentent la multiplicité des poids et des biais à travers tout le réseau, et non ceux d'un seul neurone ou d'une seule couche. La Figure 7.7 présente un schéma illustrant la fonction FF en tant que processus composite.

As a very simple example, suppose that a neural network has only one neuron with weight ww and bias bb. Input xx is fed into the neuron, the weight and bias are applied to xx, and then an activation function is applied to the result to produce an output y^y^. We’ll use the sigmoid, σ(x)=11+e−xσ(x)=11+e−x as activation function for simplicity. Now, the result, y^=σ(wx+b)y^=σ(wx+b), is compared to the true value yy, using a cost function, which for this example will be MSE. The composite function FF looks like this:

z=F(w,b)=MSE(σ(wx+b))=(y−11+e−(wx+b))2z=F(w,b)=MSE(σ(wx+b))=(y−11+e−(wx+b))2

S'il y avait davantage de neurones, de poids et de biais, la formule serait beaucoup plus complexe, car il faudrait prendre en compte un nombre bien supérieur de variables et de paramètres, mais le concept fondamental reste identique. Nous voulons trouver les valeurs de ww et de bb qui minimisent la valeur de F(w,b)F(w,b). Cela exige de nombreux cycles successifs d'ajustement des poids et des biais en se basant sur les valeurs de la fonction de perte. Chaque cycle d'entraînement complet est appelé une époque (epoch).

Un schéma de réseau de neurones illustrant les composantes d'un modèle de réseau de neurones, notamment les données d'entrée, les variables indépendantes, le réseau de neurones, les données de sortie et la fonction de perte. Il montre les données d'entrée (x), les poids (W), les biais (B), le réseau de neurones, la sortie prédite (ŷ), la sortie réelle (y), la fonction de perte (Perte = z), ainsi que les relations entre ces composantes. Les poids et les biais sont des variables indépendantes.
Figure 7.7 Réseau de neurones avec fonction de perte CC, considéré comme une fonction des poids et des biais. Étant donné un ensemble de poids WW et de biais BB, le réseau de neurones est exécuté sur un ensemble de données d'entrée x(0)x(0), la sortie y^y^ est obtenue et comparée à la sortie réelle yy au moyen d'une fonction de perte, CC, fournissant un nombre unique : la perte, zz. L'objectif de l'entraînement consiste à ajuster WW et BB de telle sorte que zz soit minimisé.

Now suppose that w=0.5w=0.5 and b=0.25b=0.25. Consider a single point from the set of training data—for example, (x,y)=(1,0)(x,y)=(1,0). The neural network computes a prediction, y^y^, as follows:

y^=σ(wx+b)=11+e−(0.5(1)+0.25)=0.679y^=σ(wx+b)=11+e−(0.5(1)+0.25)=0.679

La perte par rapport à la sortie réelle donnée (y=0)(y=0) est :

z=(y−y^)2=(0−0.679)2=0.46z=(y−y^)2=(0−0.679)2=0.46

Thus, we have the value F(0.5,0.25)=0.46F(0.5,0.25)=0.46, which indicates that there is some nonzero amount of loss on the prediction just made, and so the values of ww and bb should be adjusted. Instead of trying to adjust ww and bb individually, let’s find the direction from the point (0.5, 0.25) that would result in the fastest decrease of the error zz. If we label the direction of steepest decrease (dx,dy)(dx,dy), then we could move from the point (0.5, 0.25) to a new point, (0.5,0.25)+h·(dw,db)=(0.5,0.25)+h·(dw,db)= (0.5+h·dw,0.25+h·db)(0.5+h·dw,0.25+h·db), where hh is a small number that affects the learning rate of the neural network. This is the main idea behind gradient descent. In our example, we could find the direction of steepest descent by simply looking at how the surface z=F(w,b)z=F(w,b) is slanted near the point (0.5, 0.25). Figure 7.8 indicates the best direction with an arrow. (However, in practice, there will be no way to visualize the graph of loss with respect to weights (W)(W) and biases (BB) because the points (W,B,z)(W,B,z) exist in very high-dimensional spaces. The details of gradient descent and how it is used in neural networks falls outside the scope of this textbook. (See this IBM article for a great introduction to this topic.)

Un tracé en 3D montrant la relation entre le poids, le biais et la perte dans un réseau de neurones. La fonction de perte est visualisée sous la forme d'une surface de hauteurs variables, représentant différentes valeurs de perte pour diverses combinaisons de poids et de biais.
Figure 7.8 Application simple de la descente de gradient. Le point (w,b)=(0.5,0.25)(w,b)=(0.5,0.25) présente une perte de z=0.46z=0.46. La flèche indique la direction de plus grande pente descendante depuis le point (0.5,0.25,0.46)(0.5,0.25,0.46) sur la surface.

Un dernier point à souligner est que la descente de gradient ne fonctionne que lorsque la fonction F est différentiable, ce qui signifie que la courbe de z=F(W,B)z=F(W,B) ne comporte ni angles vifs, ni points de rebroussement, ni points de discontinuité. Dans la mesure où FF est une composition de nombreuses fonctions composantes, chaque composante doit être différentiable. Les fonctions d'activation telles que la fonction en escalier, ReLU et LReLU ne peuvent pas être employées directement avec la descente de gradient. D'autres méthodes sont toutefois disponibles pour rétropropager et entraîner ce type de réseaux. Une autre solution consiste à approximer les fonctions d'activation non lisses par des versions régulières — par exemple, sigmoïde ou tanh à la place de la fonction en escalier, ou softplus à la place de ReLU.

Entraîner un réseau de neurones par rétropropagation en Python avec TensorFlow

La bibliothèque Python TensorFlow, développée à l'origine par l'équipe Google Brain en 2015, permet de concevoir un réseau de neurones doté de la rétropropagation. (Un tenseur (tensor) est un tableau multidimensionnel, généralisant le concept de vecteur.)

Mettons en place un réseau de neurones et entraînons-le à reconnaître des chiffres manuscrits de la base de données MNIST. Ce jeu de données, nommé mnist_784, peut être chargé automatiquement au moyen de la commande fetch_openml. Les étiquettes cibles doivent être converties explicitement en nombres entiers. Les données sont ensuite scindées en ensembles d'entraînement (80 %) et de test (20 %). Les variables explicatives sont renormalisées à l'aide de StandardScaler. Le modèle de réseau de neurones est ensuite créé avec tf.keras.Sequential. L'architecture peut être très complexe, mais nous nous limiterons au cas le plus simple : une couche d'entrée comprenant exactement autant de neurones que de variables (784) et une couche de sortie de 10 neurones, un pour chaque chiffre. Ce modèle ne comprend aucune couche cachée. La commande tf.keras.layers.Dense crée la couche de sortie de 10 neurones entièrement connectée à la couche d'entrée de 784 neurones (la valeur de X_train_scaled.shape[1]). La fonction d'activation softmax est employée, mais d'autres fonctions telles que ReLU sont également envisageables. Le paramètre optimizer='adam' dans la fonction model.compile fait référence à l'algorithme Adaptive Moment Estimation, utilisé pour actualiser les poids et les biais du modèle durant l'apprentissage, dont les détails techniques dépassent le cadre de cet ouvrage.

Remarque : Ce code nécessite un certain temps d'exécution — jusqu'à 5 minutes ! Même sans couche cachée, la rétropropagation prend du temps. Lors de l'exécution, vous devriez observer des barres de progression pour chaque époque (non représentées dans la sortie Python de l'encadré ci-dessous).

Code Python

    # import the libraries
    import tensorflow as tf
    from sklearn.datasets import fetch_openml
    from sklearn.model_selection import train_test_split
    from sklearn.preprocessing import StandardScaler
    
    # Load MNIST Digits dataset
    mnist = fetch_openml('mnist_784', version=1)
    
    # Split the dataset into training and testing sets 80/20
    X_train, X_test, y_train, y_test = train_test_split(mnist.data, mnist.target, test_size=0.2, random_state=42)
    
    # Standardize features by removing the mean and scaling to unit variance
    scaler = StandardScaler()
    X_train_scaled = scaler.fit_transform(X_train)
    X_test_scaled = scaler.transform(X_test)
    
    # Cast the target variable as an integer (int32)
    y_train = y_train.astype('int32')
    y_test = y_test.astype('int32')
    
    # Define the neural network architecture
    model = tf.keras.Sequential([
     tf.keras.layers.Dense(10, activation='softmax', input_shape=(X_train_scaled.shape[1],))
    ])
    
    # Compile the model
    model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
    
    # Train the model
    history = model.fit(X_train_scaled, y_train, epochs=10, batch_size=32, validation_split=0.2)
    
    # Evaluate the model on test data
    test_loss, test_accuracy = model.evaluate(X_test_scaled, y_test)
    print("Test Loss:", test_loss)
    print("Test Accuracy:", test_accuracy)
    

La sortie obtenue se présentera ainsi :

Test Loss: 0.4051963686943054
Test Accuracy: 0.9189285635948181

Réseaux de neurones récurrents

Imaginez que vous puissiez prédire les cours de la bourse ! Et si vous disposiez d'un modèle capable de prendre les cours des jours précédents d'une action pour en prévoir le cours de demain ? Vous pourriez alors maximiser vos profits en achetant au plus bas et en vendant au plus haut, en sachant parfaitement à quel moment se produiraient ces points d'inflexion. Compte tenu de la nature extrêmement imprévisible des marchés boursiers, il est peu probable qu'un modèle de prévision simple soit suffisant ; envisageons plutôt un réseau de neurones. On pourrait le concevoir de sorte que le vecteur d'entrée x(0)x(0) contienne les cours des 14 jours précédents et que la sortie y représente le cours prévu de l'action pour le jour suivant. Après un certain temps, vous décidez de modifier la structure du réseau pour qu'il prenne en compte 30 jours de données. Il vous faudrait alors tout recommencer, en réentraînant et en retestant le nouveau modèle à partir de zéro. En réalité, chaque fois que vous décideriez de modifier le nombre de valeurs d'entrée, vous devriez concevoir un nouveau modèle et recommencer les phases d'entraînement et de test. Il serait bien plus commode de disposer d'un modèle capable d'accepter des volumes variables de données en entrée. De plus, si le modèle s'entraîne d'abord sur 14 jours d'historique, il serait souhaitable que le réseau « se souvienne » de cet apprentissage, afin que lorsque des données supplémentaires sont disponibles (30 jours, 60 jours, un an, etc.), il puisse simplement affiner ses prédictions à partir de la base déjà acquise.

Cette souplesse est offerte par les réseaux de neurones récurrents (RNNs) (recurrent neural networks (RNNs)). Un RNN est un réseau de neurones qui intègre des boucles de rétroaction (feedback loops), c'est-à-dire des connexions internes d'un neurone vers lui-même ou formant des cycles entre plusieurs neurones. Concrètement, la sortie d'un neurone est réinjectée en entrée vers lui-même tout en étant transmise aux neurones de la couche suivante.

Dans le modèle le plus élémentaire, une boucle de rétroaction relie un neurone individuel à lui-même. La sortie zz du neurone est modulée par un poids de connexion (connecting weight) wcwc et le résultat est intégré à la somme constituant l'entrée de ce même neurone. Ainsi, lorsqu'un nouveau signal parvient au neurone, il reçoit en supplément le signal wczwcz. Si le poids de connexion est positif, cela a pour effet général de rendre le neurone de plus en plus actif au fil du temps. En revanche, si le poids de connexion est négatif, il s'établit une boucle de rétroaction négative, qui atténue progressivement l'activité du neurone dans le temps. Les poids de connexion d'un RNN sont entraînés conjointement avec l'ensemble des poids et des biais du réseau à l'aide d'une variante de la rétropropagation appelée rétropropagation à travers le temps (ou BPTT). Voir la Figure 7.9 pour un schéma simple illustrant un modèle de RNN.

Un schéma de réseau de neurones avec une entrée, un rectangle étiqueté W, un rectangle étiqueté b pour le biais, un rectangle étiqueté « fonction d'activation » et une sortie. Une boucle de rétroaction part de « fonction d'activation » vers une boîte étiquetée Wc puis retourne à b.
Figure 7.9 Modèle RNN simplifié. La boucle de rétroaction confère au modèle une mémoire rudimentaire de l'entraînement antérieur.

The RNN model’s feedback loops provide a simple memory, but how does it allow for different amounts of input values? The key is to feed in single (or small batches of) datapoints sequentially. For example, if x(0)=(x1,x2,x3,…,x(n−2),x(n−1))x(0)=(x1,x2,x3,…,x(n−2),x(n−1)) represents the prices of a stock on day 1, 2, 3, up to nn, and the actual value of the stock on day nn is yy, the RNN will take in just one element xn−1xn−1 (typically, the most recent data being used first) and adjust parameters based on the accuracy or loss of the prediction y^1y^1. On the next pass, it takes in x(n−2)x(n−2) and produces a new prediction y^2y^2. Keep in mind, the feedback loops allow the RNN to remember (in some capacity) how it performed on the first input, and so the prediction y^2y^2 is based on the two data points, (xn−2,xn−1)(xn−2,xn−1). Similarly, when xn−3xn−3 is fed into the model on the third pass, the RNN will produce a new prediction y^3y^3 that is based on the three data points, (xn−3,xn−2,xn−1)(xn−3,xn−2,xn−1). Thus, the feedback loops of an RRN provide a mechanism for the input to consist of any number of sequential input data points. This makes RNNs especially useful for time series data (See Time Series and Forecasting for an introduction to time series.)

Dérouler un RNN

The effect of feedback loops may be visualized by “unrolling” the RNN. Consider the simplest case of a single feedback loop from one neuron to itself. The effect of the connecting weight is equivalent to connecting to a copy of the same RNN (with identical weights and biases). Of course, since the second copy of the RNN has the same feedback loop, it can be unrolled to a third, fourth, fifth copy, etc. An “unrolled” RNN with nn copies of the neural net has nn inputs. The effect of feeding the entire vector x(0)x(0) into the unrolled model is equivalent to feeding the data points (x1,x2,x3,…,xn)(x1,x2,x3,…,xn) sequentially into the RNN. Figure 7.10 shows how this works.

Un schéma illustrant la passe avant de plusieurs neurones dans un réseau de neurones. Chaque neurone reçoit une entrée, la multiplie par un poids (w), lui ajoute un biais (b) et transmet le résultat par le biais d'une fonction d'activation pour produire une sortie (z). Ce processus est répété pour plusieurs neurones, les sorties pouvant être transmises à des couches supplémentaires.
Figure 7.10 Dérouler un RNN

Problème de disparition ou d'explosion du gradient

Entraîner un RNN est plus complexe en raison de la grande sensibilité du modèle aux variations des poids de connexion. Au cours de la phase d'apprentissage, la descente de gradient et une variante de la rétropropagation (BPTT, comme indiqué précédemment) sont mises en œuvre pour ajuster l'ensemble des poids et des biais. Cependant, en raison des boucles de rétroaction propres aux RNN, les poids de connexion peuvent se multiplier de manière répétée jusqu'à atteindre des valeurs gigantesques. Dès lors, des algorithmes comme la descente de gradient perdent toute leur efficacité, car des poids élevés induisent des modifications proportionnellement excessives des paramètres, à l'opposé des ajustements infinitésimaux requis pour localiser des minima. C'est ce que l'on appelle le problème d'explosion du gradient (exploding gradient problem). À l'inverse, si les poids de connexion sont initialement trop faibles, l'entraînement peut les faire tendre très vite vers zéro, ce qui constitue le problème de disparition du gradient (vanishing gradient problem). Il est essentiel de prendre en compte ces deux écueils lors de l'utilisation de RNN, faute de quoi l'exactitude de votre modèle risque d'être sérieusement compromise.

Réseaux à mémoire court et long terme

Un réseau à mémoire court et long terme (LSTM) (long short-term memory (LSTM) network) est un type particulier de RNN conçu pour surmonter les problèmes d'explosion ou de disparition du gradient en intégrant des cellules de mémoire (memory cells) capables de capturer les dépendances à long terme bien mieux que de simples boucles de rétroaction. Introduits en 1997, les LSTM sont devenus incontournables dans de nombreuses applications, notamment le traitement du langage naturel, la reconnaissance vocale et la prévision de séries temporelles.

Les réseaux LSTM sont généralement plus stables et plus faciles à entraîner que les RNN classiques. Ils s'avèrent ainsi particulièrement indiqués pour la prévision de séries temporelles comportant des tendances à long terme et des comportements cycliques, ainsi que pour le traitement de données séquentielles d'apparence beaucoup plus imprévisible comme la modélisation du langage naturel, la traduction automatique et l'analyse de sentiments. Les RNN et les LSTM constituent un tremplin vers des modèles d'IA très sophistiqués, que nous examinerons dans la section suivante.

Les RNN en Python

Considérons le jeu de données MonthlyCoalConsumption.csv, que nous avons analysé au moyen des méthodes de séries temporelles de base dans Séries temporelles et prévisions. Ce jeu de données contient des observations s'étendant jusqu'à la fin de l'année 2022. Supposons que vous souhaitiez prévoir la consommation mensuelle de charbon pour chaque mois de l'année suivante à l'aide d'un réseau de neurones récurrent. Nous allons d'abord configurer un RNN simple en Python. La bibliothèque TensorFlow peut être utilisée pour concevoir un RNN. Nous emploierons pandas pour charger les données et numpy pour les structurer sous une forme plus exploitable (np.array) par le modèle. Le modèle RNN est défini à l'aide de la commande tf.keras.layers.SimpleRNN, où units précise le nombre de neurones dans la couche et activation='tanh' définit la fonction d'activation.

Code Python

    import numpy as np
    import pandas as pd
    import tensorflow as tf
    from sklearn.preprocessing import MinMaxScaler
    
    # Load and preprocess the data
    data = pd.read_csv('MonthlyCoalConsumption.csv')
    values = np.array(data['Value'])
    scaler = MinMaxScaler(feature_range=(0, 1))
    scaled_values = scaler.fit_transform(values.reshape(-1, 1))
    
    # Prepare input sequences and target values
    window_size = 12 # Number of months in each input sequence
    X = []
    y = []
    for i in range(len(scaled_values) - window_size):
      X.append(scaled_values[i:i+window_size])
      y.append(scaled_values[i+window_size])
    X = np.array(X)
    y = np.array(y)
    
    # Split data into training and testing sets
    split = int(0.8 * len(X))
    X_train, X_test = X[:split], X[split:]
    y_train, y_test = y[:split], y[split:]
    
    # Define the RNN architecture
    model = tf.keras.Sequential([
      tf.keras.layers.SimpleRNN(units=32, activation='tanh',
                    input_shape=(window_size, 1)),
      tf.keras.layers.Dense(1)
    ])
    
    # Compile the model
    model.compile(optimizer='adam', loss='mean_squared_error')
    
    # Train the model
    model.fit(X_train, y_train, epochs=50, batch_size=16)
    
    # Evaluate the model
    loss = model.evaluate(X_test, y_test)
    print("Test Loss:", loss)
    

La sortie obtenue se présentera ainsi :

Test Loss: 0.018054470419883728

La perte sur les données de test étant extrêmement faible, nous pouvons nous attendre à ce que le modèle soit très précis. Ensuite, le modèle peut être utilisé pour prédire les valeurs inconnues des 12 prochains mois. Remarque : les données d'origine ayant été mises à l'échelle, les prédictions fournies par le RNN doivent être reconverties en données non normalisées grâce à la commande scaler.inverse_transform.

Code Python

    # Predict future values
    future_months = 12
    last_window = scaled_values[-window_size:].reshape(1, window_size, 1)
    predicted_values = []
    for _ in range(future_months):
     prediction = model.predict(last_window)
     predicted_values.append(prediction[0, 0])
     last_window = np.append(last_window[:, 1:, :], prediction.reshape(1, 1, 1), axis=1)
    
    # Inverse transform the predicted births to get actual values
    predicted_values = scaler.inverse_transform(np.array(predicted_values).reshape(-1, 1))
    print("Predicted Values for the Next 12 Months:\n", predicted_values.flatten())
    

La sortie obtenue se présentera ainsi :

Predicted Values for the Next 12 Months:
[49227.05 49190.258 41204.16 30520.389 29591.129 34516.586 42183.016
38737.39 32026.324 23482.027 23381.914 30206.467]

Enfin, voici le tracé superposant les données d'origine et les valeurs prédites. Remarque : de nombreuses lignes de code sont consacrées à la mise en forme des données et aux étiquettes des axes avant de tracer le graphique. C'est malheureusement le propre de ce type de visualisation. Le détail précis de chaque instruction ne fera pas l'objet d'une explication approfondie dans cet ouvrage.

Code Python

    import matplotlib.pyplot as plt
    from matplotlib.ticker import MaxNLocator ## for graph formatting
    from matplotlib.ticker import FuncFormatter ## for formatting y-axis
    
    # Function to format the Y-axis values
    def y_format(value, tick_number):
     return f'{value:,.0f}'
    
    # Plot original time series data and predicted values
    plt.figure(figsize=(10, 6))
    plt.plot(data['Month'], data['Value'], label='Original Data', color='blue')
    plt.plot(range(len(data['Value']), len(data['Value']) + future_months), predicted_values,
        label='Predicted Values', color='red')
    
    # Create a list for the months
    pd_months = pd.date_range(start='2016-01-01', end='2023-07-01', freq='MS')
    
    xticks_positions = range(0, len(pd_months), 6) # Positions to display ticks (e.g., every 12 months)
    xticks_labels = [pd_months[pos].strftime('%Y-%m') for pos in xticks_positions] # Labels corresponding to positions
    
    plt.xlabel('Month')
    plt.ylabel('Value')
    plt.title('Monthly Consumption of Coal from 2016 to 2022 and Predicted Values for 2023')
    plt.legend()
    plt.xticks(ticks=xticks_positions, labels=xticks_labels, rotation=45)
    # Apply the formatter to the Y-axis
    plt.gca().yaxis.set_major_formatter(FuncFormatter(y_format))
    
    plt.show()
    

La sortie obtenue se présentera ainsi :

Un graphique linéaire intitulé « Monthly consumption of coal from 2016 to 2022 and predicted values for 2023 ». L'axe X présente les mois de janvier 2016 à juillet 2023. L'axe Y s'étend de 0 à 80 000. Une ligne bleue dentelée illustre des hausses et des baisses avec une tendance globalement descendante au fil du temps. La valeur prédite pour les 12 derniers mois est représentée par une ligne rouge.