![]() | ![]() | ![]() | ![]() |
introduction
Les réseaux de neurones récurrents (RNN) sont une classe de réseaux de neurones puissants pour modéliser des données de séquence telles que des séries chronologiques ou un langage naturel.
Schématiquement, une couche RNN utilise une boucle for
pour itérer sur les pas de temps d'une séquence, tout en conservant un état interne qui code les informations sur les pas de temps qu'elle a vus jusqu'à présent.
L'API Keras RNN est conçue avec un accent sur:
Facilité d'utilisation : les couches
keras.layers.GRU
keras.layers.RNN
,keras.layers.LSTM
,keras.layers.GRU
vous permettent de créer rapidement des modèles récurrents sans avoir à faire des choix de configuration difficiles.Facilité de personnalisation : vous pouvez également définir votre propre couche de cellules RNN (la partie interne de la boucle
for
) avec un comportement personnalisé et l'utiliser avec la couche génériquekeras.layers.RNN
(la bouclefor
elle-même). Cela vous permet de prototyper rapidement différentes idées de recherche de manière flexible avec un code minimal.
Installer
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
Couches RNN intégrées: un exemple simple
Il existe trois couches RNN intégrées dans Keras:
keras.layers.SimpleRNN
, un RNN entièrement connecté où la sortie du pas de temps précédent doit être transmise au prochain pas de temps.keras.layers.GRU
, proposé pour la première fois dans Cho et al., 2014 .keras.layers.LSTM
, proposé pour la première fois dans Hochreiter & Schmidhuber, 1997 .
Début 2015, Keras avait les premières implémentations Python open-source réutilisables de LSTM et GRU.
Voici un exemple simple de modèle Sequential
qui traite des séquences d'entiers, incorpore chaque entier dans un vecteur à 64 dimensions, puis traite la séquence de vecteurs à l'aide d'une couche LSTM
.
model = keras.Sequential()
# Add an Embedding layer expecting input vocab of size 1000, and
# output embedding dimension of size 64.
model.add(layers.Embedding(input_dim=1000, output_dim=64))
# Add a LSTM layer with 128 internal units.
model.add(layers.LSTM(128))
# Add a Dense layer with 10 units.
model.add(layers.Dense(10))
model.summary()
Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= embedding (Embedding) (None, None, 64) 64000 _________________________________________________________________ lstm (LSTM) (None, 128) 98816 _________________________________________________________________ dense (Dense) (None, 10) 1290 ================================================================= Total params: 164,106 Trainable params: 164,106 Non-trainable params: 0 _________________________________________________________________
Les RNN intégrés prennent en charge un certain nombre de fonctionnalités utiles:
- Décrochage récurrent, via les arguments
dropout
etrecurrent_dropout
- Possibilité de traiter une séquence d'entrée à l'envers, via l'argument
go_backwards
- Boucle de déroulement (qui peut conduire à une grande accélération lors du traitement des séquences courtes sur CPU), par l'intermédiaire du
unroll
thèse - ...et plus.
Pour plus d'informations, consultez la documentation de l'API RNN .
Sorties et états
Par défaut, la sortie d'une couche RNN contient un seul vecteur par échantillon. Ce vecteur est la sortie de cellule RNN correspondant au dernier pas de temps, contenant des informations sur la séquence d'entrée entière. La forme de cette sortie est (batch_size, units)
où les units
correspondent à l'argument units
passé au constructeur de la couche.
Une couche RNN peut également renvoyer la séquence entière de sorties pour chaque échantillon (un vecteur par pas de temps par échantillon), si vous définissez return_sequences=True
. La forme de cette sortie est (batch_size, timesteps, units)
.
model = keras.Sequential()
model.add(layers.Embedding(input_dim=1000, output_dim=64))
# The output of GRU will be a 3D tensor of shape (batch_size, timesteps, 256)
model.add(layers.GRU(256, return_sequences=True))
# The output of SimpleRNN will be a 2D tensor of shape (batch_size, 128)
model.add(layers.SimpleRNN(128))
model.add(layers.Dense(10))
model.summary()
Model: "sequential_1" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= embedding_1 (Embedding) (None, None, 64) 64000 _________________________________________________________________ gru (GRU) (None, None, 256) 247296 _________________________________________________________________ simple_rnn (SimpleRNN) (None, 128) 49280 _________________________________________________________________ dense_1 (Dense) (None, 10) 1290 ================================================================= Total params: 361,866 Trainable params: 361,866 Non-trainable params: 0 _________________________________________________________________
De plus, une couche RNN peut renvoyer son (ses) état (s) interne (s) final (s). Les états renvoyés peuvent être utilisés pour reprendre l'exécution du RNN ultérieurement, ou pour initialiser un autre RNN . Ce paramètre est couramment utilisé dans le modèle séquence à séquence codeur-décodeur, où l'état final du codeur est utilisé comme état initial du décodeur.
Pour configurer une couche RNN afin qu'elle renvoie son état interne, définissez le paramètre return_state
sur True
lors de la création de la couche. Notez que LSTM
a 2 tenseurs d'état, mais GRU
n'en a qu'un.
Pour configurer l'état initial de la couche, appelez simplement la couche avec l'argument de mot clé supplémentaire initial_state
. Notez que la forme de l'état doit correspondre à la taille unitaire du calque, comme dans l'exemple ci-dessous.
encoder_vocab = 1000
decoder_vocab = 2000
encoder_input = layers.Input(shape=(None,))
encoder_embedded = layers.Embedding(input_dim=encoder_vocab, output_dim=64)(
encoder_input
)
# Return states in addition to output
output, state_h, state_c = layers.LSTM(64, return_state=True, name="encoder")(
encoder_embedded
)
encoder_state = [state_h, state_c]
decoder_input = layers.Input(shape=(None,))
decoder_embedded = layers.Embedding(input_dim=decoder_vocab, output_dim=64)(
decoder_input
)
# Pass the 2 states to a new LSTM layer, as initial state
decoder_output = layers.LSTM(64, name="decoder")(
decoder_embedded, initial_state=encoder_state
)
output = layers.Dense(10)(decoder_output)
model = keras.Model([encoder_input, decoder_input], output)
model.summary()
Model: "model" __________________________________________________________________________________________________ Layer (type) Output Shape Param # Connected to ================================================================================================== input_1 (InputLayer) [(None, None)] 0 __________________________________________________________________________________________________ input_2 (InputLayer) [(None, None)] 0 __________________________________________________________________________________________________ embedding_2 (Embedding) (None, None, 64) 64000 input_1[0][0] __________________________________________________________________________________________________ embedding_3 (Embedding) (None, None, 64) 128000 input_2[0][0] __________________________________________________________________________________________________ encoder (LSTM) [(None, 64), (None, 33024 embedding_2[0][0] __________________________________________________________________________________________________ decoder (LSTM) (None, 64) 33024 embedding_3[0][0] encoder[0][1] encoder[0][2] __________________________________________________________________________________________________ dense_2 (Dense) (None, 10) 650 decoder[0][0] ================================================================================================== Total params: 258,698 Trainable params: 258,698 Non-trainable params: 0 __________________________________________________________________________________________________
Couches RNN et cellules RNN
En plus des couches RNN intégrées, l'API RNN fournit également des API au niveau de la cellule. Contrairement aux couches RNN, qui traitent des lots entiers de séquences d'entrée, la cellule RNN ne traite qu'un seul pas de temps.
La cellule est l'intérieur de la boucle for
d'une couche RNN. Enveloppant une cellule dans une couche keras.layers.RNN
vous donne une couche capable de traiter des lots de séquences, par exemple RNN(LSTMCell(10))
.
Mathématiquement, RNN(LSTMCell(10))
produit le même résultat que LSTM(10)
. En fait, la mise en œuvre de cette couche dans TF v1.x créait simplement la cellule RNN correspondante et l'enveloppait dans une couche RNN. Cependant, l'utilisation des couches GRU
et LSTM
permet l'utilisation de CuDNN et vous pouvez voir de meilleures performances.
Il existe trois cellules RNN intégrées, chacune d'elles correspondant à la couche RNN correspondante.
keras.layers.SimpleRNNCell
correspond à la coucheSimpleRNN
.keras.layers.GRUCell
correspond à la coucheGRU
.keras.layers.LSTMCell
correspond à la coucheLSTM
.
L'abstraction de cellule, associée à la classe générique keras.layers.RNN
, facilite la mise en œuvre d'architectures RNN personnalisées pour vos recherches.
État inter-lots
Lors du traitement de très longues séquences (éventuellement infinies), vous souhaiterez peut-être utiliser le modèle d'état inter-batch .
Normalement, l'état interne d'une couche RNN est réinitialisé chaque fois qu'elle voit un nouveau lot (c'est-à-dire que chaque échantillon vu par la couche est supposé être indépendant du passé). La couche ne conservera un état que pendant le traitement d'un échantillon donné.
Cependant, si vous avez des séquences très longues, il est utile de les diviser en séquences plus courtes, et de nourrir ces séquences plus courtes séquentiellement dans une couche RNN sans réinitialiser l'état de la couche. De cette façon, la couche peut conserver des informations sur l'intégralité de la séquence, même si elle ne voit qu'une sous-séquence à la fois.
Vous pouvez le faire en définissant stateful=True
dans le constructeur.
Si vous avez une séquence s = [t0, t1, ... t1546, t1547]
, vous la s = [t0, t1, ... t1546, t1547]
par exemple
s1 = [t0, t1, ... t100]
s2 = [t101, ... t201]
...
s16 = [t1501, ... t1547]
Ensuite, vous le traiteriez via:
lstm_layer = layers.LSTM(64, stateful=True)
for s in sub_sequences:
output = lstm_layer(s)
Lorsque vous souhaitez effacer l'état, vous pouvez utiliser layer.reset_states()
.
Voici un exemple complet:
paragraph1 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph2 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph3 = np.random.random((20, 10, 50)).astype(np.float32)
lstm_layer = layers.LSTM(64, stateful=True)
output = lstm_layer(paragraph1)
output = lstm_layer(paragraph2)
output = lstm_layer(paragraph3)
# reset_states() will reset the cached state to the original initial_state.
# If no initial_state was provided, zero-states will be used by default.
lstm_layer.reset_states()
Réutilisation de l'état RNN
Les états enregistrés de la couche RNN ne sont pas inclus dans le layer.weights()
. Si vous souhaitez réutiliser l'état d'une couche RNN, vous pouvez récupérer la valeur des états par layer.states
et l'utiliser comme état initial pour une nouvelle couche via l'API fonctionnelle Keras comme new_layer(inputs, initial_state=layer.states)
, ou sous-classification de modèle.
Veuillez également noter que le modèle séquentiel peut ne pas être utilisé dans ce cas car il ne prend en charge que les couches avec une seule entrée et sortie, l'entrée supplémentaire de l'état initial rend son utilisation impossible ici.
paragraph1 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph2 = np.random.random((20, 10, 50)).astype(np.float32)
paragraph3 = np.random.random((20, 10, 50)).astype(np.float32)
lstm_layer = layers.LSTM(64, stateful=True)
output = lstm_layer(paragraph1)
output = lstm_layer(paragraph2)
existing_state = lstm_layer.states
new_lstm_layer = layers.LSTM(64)
new_output = new_lstm_layer(paragraph3, initial_state=existing_state)
RNN bidirectionnels
Pour les séquences autres que les séries temporelles (par exemple le texte), il arrive souvent qu'un modèle RNN puisse mieux fonctionner s'il traite non seulement la séquence du début à la fin, mais aussi en arrière. Par exemple, pour prédire le mot suivant dans une phrase, il est souvent utile d'avoir le contexte autour du mot, pas seulement les mots qui le précèdent.
Keras fournit une API simple pour vous permettre de créer de tels RNN bidirectionnels: le wrapper keras.layers.Bidirectional
.
model = keras.Sequential()
model.add(
layers.Bidirectional(layers.LSTM(64, return_sequences=True), input_shape=(5, 10))
)
model.add(layers.Bidirectional(layers.LSTM(32)))
model.add(layers.Dense(10))
model.summary()
Model: "sequential_2" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= bidirectional (Bidirectional (None, 5, 128) 38400 _________________________________________________________________ bidirectional_1 (Bidirection (None, 64) 41216 _________________________________________________________________ dense_3 (Dense) (None, 10) 650 ================================================================= Total params: 80,266 Trainable params: 80,266 Non-trainable params: 0 _________________________________________________________________
Sous le capot, Bidirectional
copiera la couche RNN transmise et retournera le champ go_backwards
de la couche nouvellement copiée, afin de traiter les entrées dans l'ordre inverse.
La sortie du RNN Bidirectional
sera, par défaut, la somme de la sortie de la couche avant et de la sortie de la couche arrière. Si vous avez besoin d'un comportement de fusion différent, par exemple la concaténation, modifiez le paramètre merge_mode
dans le constructeur d'encapsuleur Bidirectional
. Pour plus de détails sur Bidirectional
, veuillez consulter la documentation de l'API .
Optimisation des performances et noyaux CuDNN
Dans TensorFlow 2.0, les couches LSTM et GRU intégrées ont été mises à jour pour tirer parti des noyaux CuDNN par défaut lorsqu'un GPU est disponible. Avec ce changement, les keras.layers.CuDNNLSTM/CuDNNGRU
précédentes keras.layers.CuDNNLSTM/CuDNNGRU
sont keras.layers.CuDNNLSTM/CuDNNGRU
obsolètes et vous pouvez créer votre modèle sans vous soucier du matériel sur lequel il fonctionnera.
Puisque le noyau CuDNN est construit avec certaines hypothèses, cela signifie que la couche ne pourra pas utiliser le noyau CuDNN si vous modifiez les valeurs par défaut des couches LSTM ou GRU intégrées . Par exemple:
- Changer la fonction d'
activation
detanh
à autre chose. - Changer la fonction
recurrent_activation
desigmoid
à autre chose. - Utilisation de
recurrent_dropout
> 0. - Définir
unroll
sur True, ce qui force LSTM / GRU à décomposer letf.while_loop
interne en une bouclefor
déroulée. - Définition de
use_bias
sur False. - Utilisation du masquage lorsque les données d'entrée ne sont pas strictement remplies à droite (si le masque correspond à des données strictement remplies à droite, CuDNN peut toujours être utilisé. C'est le cas le plus courant).
Pour la liste détaillée des contraintes, veuillez consulter la documentation des couches LSTM et GRU .
Utilisation des noyaux CuDNN lorsqu'ils sont disponibles
Construisons un modèle LSTM simple pour démontrer la différence de performances.
Nous utiliserons comme séquences d'entrée la séquence de lignes de chiffres MNIST (traitant chaque ligne de pixels comme un pas de temps), et nous prédirons l'étiquette du chiffre.
batch_size = 64
# Each MNIST image batch is a tensor of shape (batch_size, 28, 28).
# Each input sequence will be of size (28, 28) (height is treated like time).
input_dim = 28
units = 64
output_size = 10 # labels are from 0 to 9
# Build the RNN model
def build_model(allow_cudnn_kernel=True):
# CuDNN is only available at the layer level, and not at the cell level.
# This means `LSTM(units)` will use the CuDNN kernel,
# while RNN(LSTMCell(units)) will run on non-CuDNN kernel.
if allow_cudnn_kernel:
# The LSTM layer with default options uses CuDNN.
lstm_layer = keras.layers.LSTM(units, input_shape=(None, input_dim))
else:
# Wrapping a LSTMCell in a RNN layer will not use CuDNN.
lstm_layer = keras.layers.RNN(
keras.layers.LSTMCell(units), input_shape=(None, input_dim)
)
model = keras.models.Sequential(
[
lstm_layer,
keras.layers.BatchNormalization(),
keras.layers.Dense(output_size),
]
)
return model
Chargeons le jeu de données MNIST:
mnist = keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
sample, sample_label = x_train[0], y_train[0]
Créons une instance de modèle et entraînons-la.
Nous choisissons sparse_categorical_crossentropy
comme fonction de perte pour le modèle. La sortie du modèle a la forme de [batch_size, 10]
. La cible du modèle est un vecteur entier, chacun des nombres entiers étant compris entre 0 et 9.
model = build_model(allow_cudnn_kernel=True)
model.compile(
loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
optimizer="sgd",
metrics=["accuracy"],
)
model.fit(
x_train, y_train, validation_data=(x_test, y_test), batch_size=batch_size, epochs=1
)
938/938 [==============================] - 7s 6ms/step - loss: 1.2775 - accuracy: 0.5890 - val_loss: 0.4391 - val_accuracy: 0.8663 <tensorflow.python.keras.callbacks.History at 0x7f769430c630>
Maintenant, comparons à un modèle qui n'utilise pas le noyau CuDNN:
noncudnn_model = build_model(allow_cudnn_kernel=False)
noncudnn_model.set_weights(model.get_weights())
noncudnn_model.compile(
loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
optimizer="sgd",
metrics=["accuracy"],
)
noncudnn_model.fit(
x_train, y_train, validation_data=(x_test, y_test), batch_size=batch_size, epochs=1
)
938/938 [==============================] - 36s 37ms/step - loss: 0.3995 - accuracy: 0.8823 - val_loss: 0.3322 - val_accuracy: 0.8953 <tensorflow.python.keras.callbacks.History at 0x7f76941cde10>
Lorsqu'il est exécuté sur une machine avec un GPU NVIDIA et CuDNN installés, le modèle construit avec CuDNN est beaucoup plus rapide à entraîner par rapport au modèle qui utilise le noyau TensorFlow standard.
Le même modèle compatible CuDNN peut également être utilisé pour exécuter l'inférence dans un environnement CPU uniquement. L'annotation tf.device
ci-dessous ne fait que forcer le placement de l'appareil. Le modèle fonctionnera par défaut sur le processeur si aucun GPU n'est disponible.
Vous n'avez simplement plus à vous soucier du matériel sur lequel vous utilisez. N'est-ce pas plutôt cool?
import matplotlib.pyplot as plt
with tf.device("CPU:0"):
cpu_model = build_model(allow_cudnn_kernel=True)
cpu_model.set_weights(model.get_weights())
result = tf.argmax(cpu_model.predict_on_batch(tf.expand_dims(sample, 0)), axis=1)
print(
"Predicted result is: %s, target result is: %s" % (result.numpy(), sample_label)
)
plt.imshow(sample, cmap=plt.get_cmap("gray"))
Predicted result is: [3], target result is: 5
RNN avec entrées liste / dict ou entrées imbriquées
Les structures imbriquées permettent aux implémenteurs d'inclure plus d'informations dans un seul pas de temps. Par exemple, une image vidéo peut avoir une entrée audio et vidéo en même temps. La forme des données dans ce cas pourrait être:
[batch, timestep, {"video": [height, width, channel], "audio": [frequency]}]
Dans un autre exemple, les données d'écriture manuscrite pourraient avoir les coordonnées x et y pour la position actuelle du stylet, ainsi que des informations de pression. Ainsi, la représentation des données pourrait être:
[batch, timestep, {"location": [x, y], "pressure": [force]}]
Le code suivant fournit un exemple de création d'une cellule RNN personnalisée qui accepte ces entrées structurées.
Définir une cellule personnalisée prenant en charge les entrées / sorties imbriquées
Voir Créer de nouveaux calques et modèles via la sous-classification pour plus de détails sur l'écriture de vos propres calques.
class NestedCell(keras.layers.Layer):
def __init__(self, unit_1, unit_2, unit_3, **kwargs):
self.unit_1 = unit_1
self.unit_2 = unit_2
self.unit_3 = unit_3
self.state_size = [tf.TensorShape([unit_1]), tf.TensorShape([unit_2, unit_3])]
self.output_size = [tf.TensorShape([unit_1]), tf.TensorShape([unit_2, unit_3])]
super(NestedCell, self).__init__(**kwargs)
def build(self, input_shapes):
# expect input_shape to contain 2 items, [(batch, i1), (batch, i2, i3)]
i1 = input_shapes[0][1]
i2 = input_shapes[1][1]
i3 = input_shapes[1][2]
self.kernel_1 = self.add_weight(
shape=(i1, self.unit_1), initializer="uniform", name="kernel_1"
)
self.kernel_2_3 = self.add_weight(
shape=(i2, i3, self.unit_2, self.unit_3),
initializer="uniform",
name="kernel_2_3",
)
def call(self, inputs, states):
# inputs should be in [(batch, input_1), (batch, input_2, input_3)]
# state should be in shape [(batch, unit_1), (batch, unit_2, unit_3)]
input_1, input_2 = tf.nest.flatten(inputs)
s1, s2 = states
output_1 = tf.matmul(input_1, self.kernel_1)
output_2_3 = tf.einsum("bij,ijkl->bkl", input_2, self.kernel_2_3)
state_1 = s1 + output_1
state_2_3 = s2 + output_2_3
output = (output_1, output_2_3)
new_states = (state_1, state_2_3)
return output, new_states
def get_config(self):
return {"unit_1": self.unit_1, "unit_2": unit_2, "unit_3": self.unit_3}
Créer un modèle RNN avec des entrées / sorties imbriquées
Construisons un modèle Keras qui utilise une couche keras.layers.RNN
et la cellule personnalisée que nous venons de définir.
unit_1 = 10
unit_2 = 20
unit_3 = 30
i1 = 32
i2 = 64
i3 = 32
batch_size = 64
num_batches = 10
timestep = 50
cell = NestedCell(unit_1, unit_2, unit_3)
rnn = keras.layers.RNN(cell)
input_1 = keras.Input((None, i1))
input_2 = keras.Input((None, i2, i3))
outputs = rnn((input_1, input_2))
model = keras.models.Model([input_1, input_2], outputs)
model.compile(optimizer="adam", loss="mse", metrics=["accuracy"])
Entraînez le modèle avec des données générées aléatoirement
Puisqu'il n'y a pas un bon ensemble de données candidat pour ce modèle, nous utilisons des données Numpy aléatoires pour la démonstration.
input_1_data = np.random.random((batch_size * num_batches, timestep, i1))
input_2_data = np.random.random((batch_size * num_batches, timestep, i2, i3))
target_1_data = np.random.random((batch_size * num_batches, unit_1))
target_2_data = np.random.random((batch_size * num_batches, unit_2, unit_3))
input_data = [input_1_data, input_2_data]
target_data = [target_1_data, target_2_data]
model.fit(input_data, target_data, batch_size=batch_size)
10/10 [==============================] - 1s 27ms/step - loss: 0.8448 - rnn_1_loss: 0.2678 - rnn_1_1_loss: 0.5770 - rnn_1_accuracy: 0.1061 - rnn_1_1_accuracy: 0.0344 <tensorflow.python.keras.callbacks.History at 0x7f7795504208>
Avec la couche Keras keras.layers.RNN
, vous devez uniquement définir la logique mathématique pour chaque étape de la séquence, et la couche keras.layers.RNN
gérera l'itération de la séquence pour vous. C'est un moyen incroyablement puissant de prototyper rapidement de nouveaux types de RNN (par exemple une variante LSTM).
Pour plus de détails, veuillez consulter la documentation sur l' API .