Vous êtes sur la page 1sur 10

L’architecture encoder-décodeur (Encoder-Decoder)

I. Introduction aux problèmes de modélisation des séquences


(sequence-to-sequence modeling)
Les problèmes de modélisation de séquences font référence aux problèmes où l'entrée et/ou la
sortie est une séquence de données (mots, lettres, etc.).
Considérons un problème très simple : prédire si une critique de film est positive ou négative.
Ici, notre entrée est une séquence de mots et la sortie est un nombre unique entre 0 et 1. Ici,
notre entrée est une séquence de mots et la sortie est un nombre unique entre 0 et 1. Si nous
utilisions des réseaux de neurones traditionnels, nous devrions typiquement encoder notre
texte d'entrée dans un vecteur de longueur fixe en utilisant des techniques comme BOW,
Word2Vec, etc. Mais notez qu'ici, la séquence des mots n'est pas préservée et donc, lorsque
nous introduisons notre vecteur d'entrée dans le modèle, celui-ci n'a aucune idée de l'ordre des
mots et il lui manque donc un élément d'information très important sur l'entrée.
C'est donc pour résoudre ce problème que les RNN sont apparus. En substance, pour toute
entrée X = (x₀, x₁, x₂, ... xn) avec un nombre variable de caractéristiques, à chaque pas de
temps, une cellule RNN prend un élément/jeton xt en entrée et produit une sortie ht tout en
transmettant certaines informations au pas de temps suivant. Ces sorties peuvent être utilisées
en fonction du problème à résoudre.

Le problème de prédiction de critiques de films est un exemple de problème de séquence très


basique appelé prédiction "many to one". Il existe différents types de problèmes de séquence
pour lesquels des versions modifiées de cette architecture RNN sont utilisées.
Les problèmes de séquence peuvent être classés dans les catégories suivantes :

Les problèmes de séquence à séquence (Seq2Seq) constituent une classe spéciale de


problèmes de modélisation de séquences dans lesquels l'entrée et la sortie sont toutes deux des
séquences. Les modèles encodeur-décodeur ont été conçus à l'origine pour résoudre ces
problèmes Seq2Seq. Dans ce cours, nous travaillons sur un problème de type « many-to-
many » de traduction automatique neuronale (NMT) comme exemple courant.

II. Le problème de la traduction automatique neuronale


Pour vous aider à mieux comprendre, nous allons prendre la traduction automatique neuronale
comme exemple tout au long de ce cours. Dans la traduction automatique neuronale, l'entrée
est une série de mots, traités les uns après les autres. La sortie est, elle aussi, une série de
mots.
Tâche : Prédire la traduction française pour chaque phrase anglaise utilisée comme entrée.
Pour simplifier, considérons qu'il n'y a qu'une seule phrase dans notre corpus de données. Soit
nos données :

• Entrée : Phrase anglaise : “nice to meet you”


• Sortie : Traduction en français: “ravi de vous rencontrer”
Pour éviter toute confusion :

• Nous allons nous référer à la phrase d'entrée “nice to meet you” par X/input-sequence
• La phrase de sortie “ravi de vous rencontrer” par Y_true/target-sequence → C'est ce que
nous voulons que notre modèle prédise (la vérité de base).
• La phrase de sortie prédite du modèle en tant que Y_pred/predicted-sequence
• Les mots individuels de la phrase anglaise et française sont désignés sous le nom de
« tokens »
Ainsi, étant donné la séquence d'entrée "ravi de vous rencontrer", nous voulons que notre
modèle prédise la séquence cible/Y_true, c'est-à-dire "ravi de vous rencontrer".
III. The Architecture of Encoder-Decoder models
1. Vue d'ensemble de haut niveau
À un niveau très élevé, un modèle d'encodeur-décodeur peut être considéré comme deux
blocs, l'encodeur et le décodeur, reliés par un vecteur que nous appellerons le "vecteur de
contexte".

• Encodeur : L'encodeur traite chaque élément de la séquence d'entrée. Il essaie de


rassembler toutes les informations sur la séquence d'entrée dans un vecteur de
longueur fixe, c'est-à-dire le "vecteur de contexte". Après avoir parcouru tous les
tokens, le codeur transmet ce vecteur au décodeur.
• Vecteur de contexte : Le vecteur est construit de telle manière qu'il est censé
encapsuler toute la signification de la séquence d'entrée et aider le décodeur à faire des
prédictions précises. Nous verrons plus tard qu'il s'agit des états internes finaux de
notre bloc encodeur.
• Décodeur : Le décodeur lit le vecteur de contexte et essaie de prédire la séquence
cible, mot à mot.
2. Vue d’ensemble détaillée
La structure interne des deux blocs ressemblerait à ceci :

En ce qui concerne l'architecture, c'est assez simple. Le modèle peut être considéré comme
deux cellules LSTM avec une connexion entre elles. L'essentiel ici est de savoir comment
nous traitons les entrées et les sorties. Nous allons expliquer chaque partie une par une.
2.1 Le bloc encodeur
La partie encodeur est une cellule LSTM. Elle est alimentée en séquence d'entrée au fil du
temps et elle essaie d'encapsuler toutes ses informations et de les stocker dans ses états
internes finaux ht (état caché) et ct (état de la cellule). Les états internes sont ensuite transmis
à la partie décodeur, qu'elle utilisera pour essayer de produire la séquence cible. C'est le "
vecteur de contexte " auquel nous faisions référence précédemment. Les sorties à chaque pas
de temps de la partie codeur sont toutes rejetées.
2.2 Le bloc décodeur
Ainsi, après avoir lu toute la séquence d'entrée, le codeur transmet les états internes au
décodeur et c'est là que commence la prédiction de la séquence de sortie.

Le bloc décodeur est également une cellule LSTM. La principale chose à noter ici est que les
états initiaux (h₀, c₀) du décodeur sont fixés aux états finaux (ht, ct) de l'encodeur. Ceux-ci
agissent comme le vecteur " contexte " et aident le décodeur à produire la séquence cible
souhaitée.
Maintenant, la façon dont le décodeur fonctionne, est que sa sortie à n'importe quel pas de
temps t est supposée être le tᵗʰ mot dans la séquence cible/Y_true ("ravi de vous rencontrer").
Pour expliquer cela, voyons ce qui se passe à chaque pas de temps.
Au pas de temps 1
L'entrée fournie au décodeur au premier pas de temps est un symbole spécial "<START>". Il
est utilisé pour signifier le début de la séquence de sortie. Maintenant, le décodeur utilise cette
entrée et les états internes (ht, ct) pour produire la sortie dans le 1er pas de temps qui est censé
être le 1er mot/token dans la séquence cible, c'est-à-dire 'ravi'.
A l'étape 2
À la deuxième étape temporelle, la sortie de la première étape temporelle "ravi" est utilisée
comme entrée de la deuxième étape temporelle. La sortie de la deuxième étape temporelle est
censée être le deuxième mot de la séquence cible, c'est-à-dire "de".
De la même manière, la sortie de chaque étape temporelle sert d'entrée à l'étape temporelle
suivante. Cela continue jusqu'à ce que l'on obtienne le symbole "<END>" qui est à nouveau
un symbole spécial utilisé pour marquer la fin de la séquence de sortie. Les états internes
finaux du décodeur sont rejetés.

IV. Entrainement et test de l'encodeur


Le fonctionnement de l'encodeur est le même dans la phase de formation et dans la phase de
test. Il accepte chaque jeton/mot de la séquence d'entrée un par un et envoie les états finaux au
décodeur. Ses paramètres sont mis à jour par rétro-propagation au fil du temps.

1. Le Décodeur en phase d’entrainement : enseignant forcé


Le fonctionnement du décodeur est différent pendant la phase d’entrainement et de test,
contrairement à la partie encodeur. Nous verrons donc les deux séparément.
Pour entraîner notre modèle de décodeur, nous utilisons une technique appelée "Teacher
Forcing" dans laquelle nous introduisons la vraie sortie/le vrai jeton (et non la sortie/le jeton
prédit) de l'étape temporelle précédente comme entrée de l'étape temporelle actuelle.
Pour expliquer, regardons la première itération d’entrainement. Ici, nous avons transmis notre
séquence d'entrée à l'encodeur, qui la traite et transmet ses états internes finaux au décodeur.
Maintenant, pour la partie décodeur, référez-vous au diagramme ci-dessous.
Avant de poursuivre, notons que dans le décodeur, à n'importe quel pas de temps t, la sortie
yt_pred est la distribution de probabilité sur l'ensemble du vocabulaire de l'ensemble de
données de sortie qui est générée en utilisant la fonction d'activation Softmax. Le token avec
la probabilité maximale est choisi pour être le mot prédit.

2. Le décodeur en phase de test


Dans une application réelle, nous n'aurons pas Y_vrai mais seulement X. Nous ne pouvons
donc pas utiliser ce que nous avons fait dans la phase de formation car nous n'avons pas la
séquence cible/Y_vrai. Ainsi, lorsque nous testons notre modèle, la sortie prédite (et non la
vraie sortie, contrairement à la phase d'apprentissage) de l'étape précédente est utilisée comme
entrée de l'étape actuelle. Le reste est identique à la phase d’entrainement.
Disons que nous avons formé notre modèle et que nous le testons maintenant sur la seule
phrase sur laquelle nous l'avons formé. Maintenant, si nous avons bien formé le modèle et que
nous ne l'avons formé que sur une seule phrase, il devrait fonctionner presque parfaitement,
mais pour les besoins de l'explication, disons que notre modèle n'est pas bien formé ou qu'il
est partiellement formé et que nous le testons maintenant. Le scénario est représenté par le
diagramme ci-dessous.
Une meilleure visualisation pour la même chose serait :

Ainsi, selon notre modèle entraîné, la séquence prédite au moment du test est "ravi de
rencontrer rencontrer". Par conséquent, bien que le modèle ait été incorrect lors de la
troisième prédiction, nous l'avons quand même utilisé comme entrée pour l'étape temporelle
suivante. L'exactitude du modèle dépend de la quantité de données disponibles et de la qualité
de son apprentissage. Le modèle peut prédire une sortie erronée, mais néanmoins, la même
sortie est utilisée pour l'étape suivante de la phase de test.

3. La couche d'incorporation (Embedding)


Un petit détail à rajouter est que la séquence d'entrée dans le décodeur et l'encodeur passe par
une couche d'incorporation (embedding) pour réduire les dimensions des vecteurs de mots
d'entrée. En effet, dans la pratique, les vecteurs codés peuvent être très grands et les vecteurs
incorporés (embedded vectors) représentent une meilleure représentation des mots. Pour la
partie encodeur, cela peut être illustré ci-dessous où la couche d'incorporation réduit les
dimensions des vecteurs de mots de quatre à trois.
La visualisation finale au moment du test

Implémentation de l’architecture encodeur-décodeur avec Keras :


# Define an input sequence and process it.
encoder_inputs = keras.Input(shape=(None, num_encoder_tokens))
encoder = keras.layers.LSTM(latent_dim, return_state=True)
encoder_outputs, state_h, state_c = encoder(encoder_inputs)

# We discard `encoder_outputs` and only keep the states.


encoder_states = [state_h, state_c]

# Set up the decoder, using `encoder_states` as initial state.


decoder_inputs = keras.Input(shape=(None, num_decoder_tokens))

# We set up our decoder to return full output sequences,


# and to return internal states as well. We don't use the
# return states in the training model, but we will use them in inference.
decoder_lstm = keras.layers.LSTM(latent_dim, return_sequences=True,
return_state=True)
decoder_outputs, _, _ = decoder_lstm(decoder_inputs,
initial_state=encoder_states)
decoder_dense = keras.layers.Dense(num_decoder_tokens,
activation="softmax")
decoder_outputs = decoder_dense(decoder_outputs)

# Define the model that will turn


# `encoder_input_data` & `decoder_input_data` into `decoder_target_data`
model = keras.Model([encoder_inputs, decoder_inputs], decoder_outputs)

Un exemple complet est fourni avec la documentation officielle de Keras sur


https://keras.io/examples/nlp/lstm_seq2seq/

Vous aimerez peut-être aussi