![]() | ![]() | ![]() | ![]() |
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 les séries chronologiques ou le langage naturel.
Schématiquement, une couche RNN utilise un for
boucle pour itérer sur les pas de temps d'une séquence, tout en maintenant un état interne qui code pour des informations sur les pas de temps qu'il a vu jusqu'à présent.
L'API Keras RNN est conçue en mettant l'accent sur :
Facilité d'utilisation: le haut-
keras.layers.RNN
,keras.layers.LSTM
,keras.layers.GRU
couches vous permettent de construire 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 intérieure de la
for
la boucle) avec un comportement personnalisé, et l' utiliser avec le génériquekeras.layers.RNN
couche (lafor
boucle elle - même). Cela vous permet de prototyper rapidement différentes idées de recherche de manière flexible avec un minimum de code.
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ù le signal de sortie timestep précédente doit être alimentée au prochain pas de temps.keras.layers.GRU
, d' abord proposé dans Cho et al., 2014 .keras.layers.LSTM
, d' abord proposé dans Hochreiter & Schmidhuber, 1997 .
Au début de 2015, Keras avait les premières implémentations Python open source réutilisables de LSTM et GRU.
Voici un exemple d'un simple Sequential
modèle qui traite les séquences de nombres entiers, chaque entier incorpore dans un vecteur 64 dimensions, puis traite la séquence de vecteurs en utilisant une LSTM
couche.
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 :
- Abandon récurrent, par les
dropout
etrecurrent_dropout
arguments - Capacité à traiter une séquence d'entrée dans le sens inverse, par l'intermédiaire du
go_backwards
thèse - 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 la cellule RNN correspondant au dernier pas de temps, contenant des informations sur l'ensemble de la séquence d'entrée. La forme de cette sortie est (batch_size, units)
où des units
correspond à l' units
argument passé au constructeur de la couche.
Une couche RNN peut également renvoyer toute la séquence de sorties pour chaque échantillon (par un vecteur timestep par échantillon), si vous avez défini return_sequences=True
. La forme de cette sortie est (batch_size, timesteps, units)
pas de (batch_size, timesteps, units)
les (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 ou ses états internes finaux. Les états retournés peuvent être utilisés pour reprendre l'exécution RNN plus tard, 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 pour retourner son état interne, réglez le return_state
paramètre à True
lors de la création de la couche. Notez que LSTM
a 2 tenseurs de l' État, mais GRU
n'a qu'un seul.
Pour configurer l'état initial de la couche, il suffit d' appeler la couche avec l' argument 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 for
boucle d'une couche RNN. Enrouler une cellule à l' intérieur d' une keras.layers.RNN
couche 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, l'implémentation de cette couche dans TF v1.x ne faisait que créer la cellule RNN correspondante et l'envelopper dans une couche RNN. Cependant , l' utilisation du haut- GRU
et LSTM
couches permettre l'utilisation de CuDNN et vous pouvez voir une meilleure performance.
Il existe trois cellules RNN intégrées, chacune correspondant à la couche RNN correspondante.
keras.layers.SimpleRNNCell
correspond à laSimpleRNN
couche.keras.layers.GRUCell
correspond à laGRU
couche.keras.layers.LSTMCell
correspond à laLSTM
couche.
L'abstraction cellulaire, ainsi que le générique keras.layers.RNN
classe, il est très facile à mettre en œuvre des architectures RNN personnalisées pour votre recherche.
État d'état de lots croisés
Lors du traitement des séquences très longues (éventuellement infini), vous pouvez utiliser le modèle de statefulness croisée par lots.
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 d'alimenter séquentiellement ces séquences plus courtes dans une couche RNN sans réinitialiser l'état de la couche. De cette façon, le calque peut conserver des informations sur l'intégralité de la séquence, même s'il ne voit qu'une sous-séquence à la fois.
Vous pouvez le faire en mettant stateful=True
dans le constructeur.
Si vous avez une séquence s = [t0, t1, ... t1546, t1547]
, vous ne le diviser en 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 voulez 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()
RNN État Réutilisation
Les états enregistrés de la couche RNN ne sont pas inclus dans les 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 l'état initial d'une nouvelle couche via l'API fonctionnelle Keras comme new_layer(inputs, initial_state=layer.states)
, ou sous-classement 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 le rend impossible à utiliser 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 chronologiques (par exemple le texte), il arrive souvent qu'un modèle RNN soit plus performant 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 facile pour vous de construire de tels RNNs bidirectionnel: l' keras.layers.Bidirectional
emballage.
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
copie la couche RNN passé dans et retourner le go_backwards
champ de la couche nouvellement copié, de sorte qu'il traitera les entrées dans l' ordre inverse.
La sortie de la Bidirectional
RNN sera, par défaut, la concaténation de la sortie de la couche avant et la couche de sortie vers l' arrière. Si vous avez besoin d' un comportement de fusion différent, par exemple concaténation, modifiez le merge_mode
paramètre dans le Bidirectional
constructeur d'emballage. Pour plus de détails sur Bidirectional
, s'il vous plaît vérifier l'API docs .
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 avant keras.layers.CuDNNLSTM/CuDNNGRU
couches ont été désapprouvés, et vous pouvez construire votre modèle sans se soucier du matériel , il fonctionnera sur.
Comme le noyau CuDNN est construit avec certaines hypothèses, cela signifie que la couche ne sera pas en mesure d'utiliser le noyau CuDNN si vous modifiez les paramètres par défaut des couches LSMC ou GRU intégrées. Par exemple:
- Modification de l'
activation
fonction detanh
à autre chose. - Modification de la
recurrent_activation
fonction desigmoid
à autre chose. - L' utilisation
recurrent_dropout
> 0. - Réglage de
unroll
True, qui forces LSTM / GRU pour décomposer l'intérieurtf.while_loop
dans un dérouléfor
boucle. - Réglage
use_bias
sur False. - Utilisation du masquage lorsque les données d'entrée ne sont pas remplies strictement à droite (si le masque correspond à des données remplies strictement à droite, CuDNN peut toujours être utilisé. C'est le cas le plus courant).
Pour la liste détaillée des contraintes, s'il vous plaît voir la documentation pour les LSMC et GRU couches.
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 (en 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 l'ensemble 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 formons-la.
Nous choisissons sparse_categorical_crossentropy
que la fonction de perte pour le modèle. La sortie du modèle a une forme de [batch_size, 10]
. La cible du modèle est un vecteur entier, chacun des entiers est 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 [==============================] - 6s 5ms/step - loss: 0.9510 - accuracy: 0.7029 - val_loss: 0.5633 - val_accuracy: 0.8209 <keras.callbacks.History at 0x7fc9942efad0>
Comparons maintenant à 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 [==============================] - 34s 35ms/step - loss: 0.3894 - accuracy: 0.8846 - val_loss: 0.5677 - val_accuracy: 0.8045 <keras.callbacks.History at 0x7fc945fa2650>
Lors de l'exécution sur une machine avec un GPU NVIDIA et CuDNN installés, le modèle construit avec CuDNN est beaucoup plus rapide à former 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. La tf.device
annotation ci - dessous est juste le placement oblige l' appareil. Le modèle fonctionnera sur le CPU par défaut si aucun GPU n'est disponible.
Vous n'avez tout 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 list/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 à la fois les coordonnées x et y pour la position actuelle du stylet, ainsi que des informations sur la 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 de telles entrées structurées.
Définir une cellule personnalisée qui prend en charge les entrées/sorties imbriquées
Voir Faire de nouvelles couches et modèles via le sous - classement pour plus de détails sur l' écriture de vos propres couches.
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}
Construire un modèle RNN avec entrée/sortie imbriquée
La construction de laisser un modèle Keras qui utilise une keras.layers.RNN
couche 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îner le modèle avec des données générées aléatoirement
Puisqu'il n'y a pas de 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 26ms/step - loss: 0.7316 - rnn_1_loss: 0.2590 - rnn_1_1_loss: 0.4725 - rnn_1_accuracy: 0.1016 - rnn_1_1_accuracy: 0.0328 <keras.callbacks.History at 0x7fc5686e6f50>
Avec la Keras keras.layers.RNN
couche, vous ne prévu de définir la logique mathématique pour l' étape individuelle dans la séquence et la keras.layers.RNN
couche gérerez 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, s'il vous plaît visitez les API docs .