AI-For-Beginners/translations/fr/lessons/5-NLP/16-RNN/RNNTF.ipynb

460 lines
25 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Réseaux neuronaux récurrents\n",
"\n",
"Dans le module précédent, nous avons abordé les représentations sémantiques riches du texte. L'architecture que nous avons utilisée capture le sens global des mots dans une phrase, mais elle ne prend pas en compte l'**ordre** des mots, car l'opération d'agrégation qui suit les embeddings élimine cette information du texte original. Étant donné que ces modèles ne peuvent pas représenter l'ordre des mots, ils ne peuvent pas résoudre des tâches plus complexes ou ambiguës comme la génération de texte ou la réponse à des questions.\n",
"\n",
"Pour capturer le sens d'une séquence de texte, nous utiliserons une architecture de réseau neuronal appelée **réseau neuronal récurrent**, ou RNN. Lorsqu'on utilise un RNN, on fait passer notre phrase à travers le réseau un jeton à la fois, et le réseau produit un certain **état**, que l'on transmet ensuite au réseau avec le jeton suivant.\n",
"\n",
"![Image montrant un exemple de génération par réseau neuronal récurrent.](../../../../../lessons/5-NLP/16-RNN/images/rnn.png)\n",
"\n",
"Étant donné la séquence d'entrée de jetons $X_0,\\dots,X_n$, le RNN crée une séquence de blocs de réseau neuronal et entraîne cette séquence de bout en bout en utilisant la rétropropagation. Chaque bloc de réseau prend une paire $(X_i,S_i)$ en entrée et produit $S_{i+1}$ en résultat. L'état final $S_n$ ou la sortie $Y_n$ est ensuite transmis à un classificateur linéaire pour produire le résultat. Tous les blocs de réseau partagent les mêmes poids et sont entraînés de bout en bout en une seule passe de rétropropagation.\n",
"\n",
"> La figure ci-dessus montre un réseau neuronal récurrent sous forme déroulée (à gauche) et sous une représentation récurrente plus compacte (à droite). Il est important de comprendre que toutes les cellules RNN partagent les mêmes **poids partageables**.\n",
"\n",
"Comme les vecteurs d'état $S_0,\\dots,S_n$ sont transmis à travers le réseau, le RNN est capable d'apprendre les dépendances séquentielles entre les mots. Par exemple, lorsque le mot *pas* apparaît quelque part dans la séquence, il peut apprendre à négativer certains éléments dans le vecteur d'état.\n",
"\n",
"À l'intérieur, chaque cellule RNN contient deux matrices de poids : $W_H$ et $W_I$, ainsi qu'un biais $b$. À chaque étape du RNN, étant donné l'entrée $X_i$ et l'état d'entrée $S_i$, l'état de sortie est calculé comme $S_{i+1} = f(W_H\\times S_i + W_I\\times X_i+b)$, où $f$ est une fonction d'activation (souvent $\\tanh$).\n",
"\n",
"> Pour des problèmes comme la génération de texte (que nous aborderons dans la prochaine unité) ou la traduction automatique, nous souhaitons également obtenir une valeur de sortie à chaque étape du RNN. Dans ce cas, il y a une autre matrice $W_O$, et la sortie est calculée comme $Y_i=f(W_O\\times S_i+b_O)$.\n",
"\n",
"Voyons comment les réseaux neuronaux récurrents peuvent nous aider à classifier notre ensemble de données de nouvelles.\n",
"\n",
"> Pour l'environnement sandbox, nous devons exécuter la cellule suivante pour nous assurer que la bibliothèque requise est installée et que les données sont préchargées. Si vous travaillez en local, vous pouvez ignorer la cellule suivante.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import sys\n",
"!{sys.executable} -m pip install --quiet tensorflow_datasets==4.4.0\n",
"!cd ~ && wget -q -O - https://mslearntensorflowlp.blob.core.windows.net/data/tfds-ag-news.tgz | tar xz"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"import tensorflow as tf\n",
"from tensorflow import keras\n",
"import tensorflow_datasets as tfds\n",
"import numpy as np\n",
"\n",
"# We are going to be training pretty large models. In order not to face errors, we need\n",
"# to set tensorflow option to grow GPU memory allocation when required\n",
"physical_devices = tf.config.list_physical_devices('GPU') \n",
"if len(physical_devices)>0:\n",
" tf.config.experimental.set_memory_growth(physical_devices[0], True)\n",
"\n",
"ds_train, ds_test = tfds.load('ag_news_subset').values()"
]
},
{
"cell_type": "markdown",
"metadata": {
"nteract": {
"transient": {
"deleting": false
}
}
},
"source": [
"Lors de l'entraînement de modèles de grande taille, l'allocation de mémoire GPU peut poser problème. Nous pourrions également avoir besoin d'expérimenter avec différentes tailles de minibatch, afin que les données tiennent dans la mémoire GPU tout en garantissant un entraînement suffisamment rapide. Si vous exécutez ce code sur votre propre machine équipée d'un GPU, vous pouvez essayer d'ajuster la taille des minibatchs pour accélérer l'entraînement.\n",
"\n",
"> **Note** : Certaines versions des pilotes NVidia sont connues pour ne pas libérer la mémoire après l'entraînement du modèle. Nous exécutons plusieurs exemples dans ce notebook, ce qui pourrait entraîner une saturation de la mémoire dans certains cas, en particulier si vous réalisez vos propres expériences dans le même notebook. Si vous rencontrez des erreurs étranges au moment de commencer l'entraînement du modèle, il peut être utile de redémarrer le noyau du notebook.\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": true,
"jupyter": {
"outputs_hidden": false,
"source_hidden": false
},
"nteract": {
"transient": {
"deleting": false
}
}
},
"outputs": [],
"source": [
"batch_size = 16\n",
"embed_size = 64"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Classificateur RNN simple\n",
"\n",
"Dans le cas d'un RNN simple, chaque unité récurrente est un réseau linéaire simple, qui prend en entrée un vecteur d'entrée et un vecteur d'état, et produit un nouveau vecteur d'état. Dans Keras, cela peut être représenté par la couche `SimpleRNN`.\n",
"\n",
"Bien que nous puissions transmettre directement des tokens encodés en one-hot à la couche RNN, ce n'est pas une bonne idée en raison de leur haute dimensionnalité. Par conséquent, nous utiliserons une couche d'embedding pour réduire la dimensionnalité des vecteurs de mots, suivie d'une couche RNN, et enfin d'un classificateur `Dense`.\n",
"\n",
"> **Note** : Dans les cas où la dimensionnalité n'est pas si élevée, par exemple lors de l'utilisation de la tokenisation au niveau des caractères, il peut être pertinent de transmettre directement les tokens encodés en one-hot dans la cellule RNN.\n"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Model: \"sequential\"\n",
"_________________________________________________________________\n",
"Layer (type) Output Shape Param # \n",
"=================================================================\n",
"text_vectorization (TextVect (None, None) 0 \n",
"_________________________________________________________________\n",
"embedding (Embedding) (None, None, 64) 1280000 \n",
"_________________________________________________________________\n",
"simple_rnn (SimpleRNN) (None, 16) 1296 \n",
"_________________________________________________________________\n",
"dense (Dense) (None, 4) 68 \n",
"=================================================================\n",
"Total params: 1,281,364\n",
"Trainable params: 1,281,364\n",
"Non-trainable params: 0\n",
"_________________________________________________________________\n"
]
}
],
"source": [
"vocab_size = 20000\n",
"\n",
"vectorizer = keras.layers.experimental.preprocessing.TextVectorization(\n",
" max_tokens=vocab_size,\n",
" input_shape=(1,))\n",
"\n",
"model = keras.models.Sequential([\n",
" vectorizer,\n",
" keras.layers.Embedding(vocab_size, embed_size),\n",
" keras.layers.SimpleRNN(16),\n",
" keras.layers.Dense(4,activation='softmax')\n",
"])\n",
"\n",
"model.summary()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> **Note:** Nous utilisons ici une couche d'embedding non entraînée pour simplifier, mais pour obtenir de meilleurs résultats, nous pouvons utiliser une couche d'embedding préentraînée avec Word2Vec, comme décrit dans l'unité précédente. Ce serait un bon exercice pour vous d'adapter ce code afin de fonctionner avec des embeddings préentraînés.\n",
"\n",
"Passons maintenant à l'entraînement de notre RNN. Les RNNs sont généralement assez difficiles à entraîner, car une fois que les cellules RNN sont déroulées sur la longueur de la séquence, le nombre de couches impliquées dans la rétropropagation devient très important. Par conséquent, nous devons sélectionner un taux d'apprentissage plus faible et entraîner le réseau sur un ensemble de données plus large pour obtenir de bons résultats. Cela peut prendre beaucoup de temps, donc l'utilisation d'un GPU est préférable.\n",
"\n",
"Pour accélérer les choses, nous allons entraîner le modèle RNN uniquement sur les titres des actualités, en omettant la description. Vous pouvez essayer d'entraîner avec la description et voir si vous parvenez à faire fonctionner le modèle.\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Training vectorizer\n"
]
}
],
"source": [
"def extract_title(x):\n",
" return x['title']\n",
"\n",
"def tupelize_title(x):\n",
" return (extract_title(x),x['label'])\n",
"\n",
"print('Training vectorizer')\n",
"vectorizer.adapt(ds_train.take(2000).map(extract_title))"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"7500/7500 [==============================] - 82s 11ms/step - loss: 0.6629 - acc: 0.7623 - val_loss: 0.5559 - val_acc: 0.7995\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
]
},
{
"data": {
"text/plain": [
"<tensorflow.python.keras.callbacks.History at 0x7f3e0030d350>"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
"model.fit(ds_train.map(tupelize_title).batch(batch_size),validation_data=ds_test.map(tupelize_title).batch(batch_size))"
]
},
{
"cell_type": "markdown",
"metadata": {
"nteract": {
"transient": {
"deleting": false
}
}
},
"source": [
"> **Note** que la précision est probablement plus faible ici, car nous nous entraînons uniquement sur les titres des actualités.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Revoir les séquences de variables\n",
"\n",
"Rappelez-vous que la couche `TextVectorization` ajoutera automatiquement des tokens de remplissage aux séquences de longueur variable dans un minibatch. Il s'avère que ces tokens participent également à l'entraînement, ce qui peut compliquer la convergence du modèle.\n",
"\n",
"Il existe plusieurs approches pour minimiser la quantité de remplissage. L'une d'elles consiste à réorganiser le dataset par longueur de séquence et à regrouper toutes les séquences par taille. Cela peut être réalisé en utilisant la fonction `tf.data.experimental.bucket_by_sequence_length` (voir [documentation](https://www.tensorflow.org/api_docs/python/tf/data/experimental/bucket_by_sequence_length)).\n",
"\n",
"Une autre approche consiste à utiliser **le masquage**. Dans Keras, certaines couches prennent en charge des entrées supplémentaires qui indiquent quels tokens doivent être pris en compte lors de l'entraînement. Pour intégrer le masquage dans notre modèle, nous pouvons soit inclure une couche `Masking` séparée ([docs](https://keras.io/api/layers/core_layers/masking/)), soit spécifier le paramètre `mask_zero=True` dans notre couche `Embedding`.\n",
"\n",
"> **Note** : Cet entraînement prendra environ 5 minutes pour compléter une époque sur l'ensemble du dataset. N'hésitez pas à interrompre l'entraînement à tout moment si vous manquez de patience. Ce que vous pouvez également faire, c'est limiter la quantité de données utilisées pour l'entraînement, en ajoutant une clause `.take(...)` après les datasets `ds_train` et `ds_test`.\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"7500/7500 [==============================] - 371s 49ms/step - loss: 0.5401 - acc: 0.8079 - val_loss: 0.3780 - val_acc: 0.8822\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
]
},
{
"data": {
"text/plain": [
"<tensorflow.python.keras.callbacks.History at 0x7f3dec118850>"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"def extract_text(x):\n",
" return x['title']+' '+x['description']\n",
"\n",
"def tupelize(x):\n",
" return (extract_text(x),x['label'])\n",
"\n",
"model = keras.models.Sequential([\n",
" vectorizer,\n",
" keras.layers.Embedding(vocab_size,embed_size,mask_zero=True),\n",
" keras.layers.SimpleRNN(16),\n",
" keras.layers.Dense(4,activation='softmax')\n",
"])\n",
"\n",
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
"model.fit(ds_train.map(tupelize).batch(batch_size),validation_data=ds_test.map(tupelize).batch(batch_size))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Maintenant que nous utilisons le masquage, nous pouvons entraîner le modèle sur l'ensemble du jeu de données des titres et descriptions.\n",
"\n",
"> **Note** : Avez-vous remarqué que nous avons utilisé un vectoriseur entraîné sur les titres des actualités, et non sur l'intégralité du corps de l'article ? Cela peut potentiellement entraîner l'ignorance de certains tokens, il serait donc préférable de réentraîner le vectoriseur. Cependant, l'impact pourrait être très minime, donc nous continuerons à utiliser le vectoriseur pré-entraîné précédent pour des raisons de simplicité.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## LSTM : Mémoire à long et court terme\n",
"\n",
"L'un des principaux problèmes des RNN est le phénomène de **gradients évanescents**. Les RNN peuvent être assez longs et peuvent avoir du mal à propager les gradients jusqu'à la première couche du réseau lors de la rétropropagation. Lorsque cela se produit, le réseau ne peut pas apprendre les relations entre des tokens éloignés. Une façon d'éviter ce problème est d'introduire une **gestion explicite de l'état** en utilisant des **portes**. Les deux architectures les plus courantes qui introduisent des portes sont la **mémoire à long et court terme** (LSTM) et l'**unité de relais à portes** (GRU). Nous allons nous concentrer ici sur les LSTM.\n",
"\n",
"![Image montrant un exemple de cellule de mémoire à long et court terme](../../../../../lessons/5-NLP/16-RNN/images/long-short-term-memory-cell.svg)\n",
"\n",
"Un réseau LSTM est organisé de manière similaire à un RNN, mais il y a deux états qui sont transmis de couche en couche : l'état réel $c$ et le vecteur caché $h$. À chaque unité, le vecteur caché $h_{t-1}$ est combiné avec l'entrée $x_t$, et ensemble, ils contrôlent ce qui arrive à l'état $c_t$ et à la sortie $h_{t}$ via des **portes**. Chaque porte utilise une activation sigmoïde (avec une sortie dans l'intervalle $[0,1]$), que l'on peut considérer comme un masque binaire lorsqu'elle est multipliée par le vecteur d'état. Les LSTM possèdent les portes suivantes (de gauche à droite sur l'image ci-dessus) :\n",
"* **Porte d'oubli**, qui détermine quelles composantes du vecteur $c_{t-1}$ doivent être oubliées et lesquelles doivent être conservées.\n",
"* **Porte d'entrée**, qui détermine la quantité d'informations provenant du vecteur d'entrée et du vecteur caché précédent à incorporer dans le vecteur d'état.\n",
"* **Porte de sortie**, qui prend le nouveau vecteur d'état et décide quelles de ses composantes seront utilisées pour produire le nouveau vecteur caché $h_t$.\n",
"\n",
"Les composantes de l'état $c$ peuvent être considérées comme des indicateurs que l'on peut activer ou désactiver. Par exemple, lorsque nous rencontrons le nom *Alice* dans une séquence, nous supposons qu'il s'agit d'une femme et activons l'indicateur dans l'état qui signale la présence d'un nom féminin dans la phrase. Lorsque nous rencontrons ensuite les mots *et Tom*, nous activons l'indicateur signalant la présence d'un nom pluriel. Ainsi, en manipulant l'état, nous pouvons suivre les propriétés grammaticales de la phrase.\n",
"\n",
"> **Note** : Voici une excellente ressource pour comprendre les mécanismes internes des LSTM : [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) par Christopher Olah.\n",
"\n",
"Bien que la structure interne d'une cellule LSTM puisse sembler complexe, Keras masque cette implémentation dans la couche `LSTM`, donc la seule chose que nous devons faire dans l'exemple ci-dessus est de remplacer la couche récurrente :\n"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"15000/15000 [==============================] - 188s 13ms/step - loss: 0.5692 - acc: 0.7916 - val_loss: 0.3441 - val_acc: 0.8870\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\n"
]
},
{
"data": {
"text/plain": [
"<tensorflow.python.keras.callbacks.History at 0x7f3d6af5c350>"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"model = keras.models.Sequential([\n",
" vectorizer,\n",
" keras.layers.Embedding(vocab_size, embed_size),\n",
" keras.layers.LSTM(8),\n",
" keras.layers.Dense(4,activation='softmax')\n",
"])\n",
"\n",
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
"model.fit(ds_train.map(tupelize).batch(8),validation_data=ds_test.map(tupelize).batch(8))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## RNN bidirectionnels et multicouches\n",
"\n",
"Dans nos exemples jusqu'à présent, les réseaux récurrents fonctionnent du début d'une séquence jusqu'à la fin. Cela nous semble naturel car cela suit la même direction que celle dans laquelle nous lisons ou écoutons un discours. Cependant, pour des scénarios nécessitant un accès aléatoire à la séquence d'entrée, il est plus logique d'exécuter le calcul récurrent dans les deux directions. Les RNN qui permettent des calculs dans les deux directions sont appelés **RNN bidirectionnels**, et ils peuvent être créés en enveloppant la couche récurrente avec une couche spéciale `Bidirectional`.\n",
"\n",
"> **Note** : La couche `Bidirectional` crée deux copies de la couche qu'elle contient et définit la propriété `go_backwards` de l'une de ces copies sur `True`, ce qui lui permet de parcourir la séquence dans la direction opposée.\n",
"\n",
"Les réseaux récurrents, qu'ils soient unidirectionnels ou bidirectionnels, capturent des motifs au sein d'une séquence et les stockent dans des vecteurs d'état ou les renvoient comme sortie. Comme pour les réseaux convolutionnels, nous pouvons construire une autre couche récurrente après la première pour capturer des motifs de niveau supérieur, construits à partir des motifs de niveau inférieur extraits par la première couche. Cela nous amène à la notion de **RNN multicouche**, qui consiste en deux réseaux récurrents ou plus, où la sortie de la couche précédente est transmise à la couche suivante comme entrée.\n",
"\n",
"![Image montrant un RNN multicouche à mémoire longue et courte](../../../../../lessons/5-NLP/16-RNN/images/multi-layer-lstm.jpg)\n",
"\n",
"*Image tirée de [cet excellent article](https://towardsdatascience.com/from-a-lstm-cell-to-a-multilayer-lstm-network-with-pytorch-2899eb5696f3) par Fernando López.*\n",
"\n",
"Keras facilite la construction de ces réseaux, car il suffit d'ajouter davantage de couches récurrentes au modèle. Pour toutes les couches sauf la dernière, nous devons spécifier le paramètre `return_sequences=True`, car nous avons besoin que la couche renvoie tous les états intermédiaires, et non seulement l'état final du calcul récurrent.\n",
"\n",
"Construisons un LSTM bidirectionnel à deux couches pour notre problème de classification.\n",
"\n",
"> **Note** : Ce code prend encore beaucoup de temps à s'exécuter, mais il nous donne la meilleure précision que nous ayons vue jusqu'à présent. Cela vaut peut-être la peine d'attendre pour voir le résultat.\n"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"5044/7500 [===================>..........] - ETA: 2:33 - loss: 0.3709 - acc: 0.8706\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\r5045/7500 [===================>..........] - ETA: 2:33 - loss: 0.3709 - acc: 0.8706"
]
}
],
"source": [
"model = keras.models.Sequential([\n",
" vectorizer,\n",
" keras.layers.Embedding(vocab_size, 128, mask_zero=True),\n",
" keras.layers.Bidirectional(keras.layers.LSTM(64,return_sequences=True)),\n",
" keras.layers.Bidirectional(keras.layers.LSTM(64)), \n",
" keras.layers.Dense(4,activation='softmax')\n",
"])\n",
"\n",
"model.compile(loss='sparse_categorical_crossentropy',metrics=['acc'], optimizer='adam')\n",
"model.fit(ds_train.map(tupelize).batch(batch_size),\n",
" validation_data=ds_test.map(tupelize).batch(batch_size))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## RNNs pour d'autres tâches\n",
"\n",
"Jusqu'à présent, nous nous sommes concentrés sur l'utilisation des RNNs pour classifier des séquences de texte. Mais ils peuvent gérer bien d'autres tâches, comme la génération de texte et la traduction automatique — nous aborderons ces tâches dans la prochaine unité.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n---\n\n**Avertissement** : \nCe document a été traduit à l'aide du service de traduction automatique [Co-op Translator](https://github.com/Azure/co-op-translator). Bien que nous nous efforcions d'assurer l'exactitude, veuillez noter que les traductions automatisées peuvent contenir des erreurs ou des inexactitudes. Le document original dans sa langue d'origine doit être considéré comme la source faisant autorité. Pour des informations critiques, il est recommandé de recourir à une traduction professionnelle réalisée par un humain. Nous déclinons toute responsabilité en cas de malentendus ou d'interprétations erronées résultant de l'utilisation de cette traduction.\n"
]
}
],
"metadata": {
"kernel_info": {
"name": "conda-env-py37_tensorflow-py"
},
"kernelspec": {
"display_name": "py37_tensorflow",
"language": "python",
"name": "conda-env-py37_tensorflow-py"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.9"
},
"nteract": {
"version": "nteract-front-end@1.0.0"
},
"coopTranslator": {
"original_hash": "81351e61f619b432ff51010a4f993194",
"translation_date": "2025-08-31T15:21:42+00:00",
"source_file": "lessons/5-NLP/16-RNN/RNNTF.ipynb",
"language_code": "fr"
}
},
"nbformat": 4,
"nbformat_minor": 4
}