![]() | ![]() | ![]() | ![]() |
Introducción
Las redes neuronales recurrentes (RNN) son una clase de redes neuronales que son poderosas para modelar datos de secuencia, como series temporales o lenguaje natural.
Esquemáticamente, una capa RNN utiliza un for
bucle para iterar sobre los timesteps de una secuencia, mientras se mantiene un estado interno que codifica información sobre los timesteps se ha visto hasta ahora.
La API Keras RNN está diseñada con un enfoque en:
Facilidad de uso: la incorporada en
keras.layers.RNN
,keras.layers.LSTM
,keras.layers.GRU
capas le permiten crear rápidamente modelos recurrentes sin tener que tomar decisiones difíciles de configuración.Facilidad de personalización: También puede definir su propia capa de células RNN (la parte interna de la
for
bucle) con un comportamiento personalizado, y la usa en el genéricakeras.layers.RNN
capa (lafor
bucle de sí mismo). Esto le permite crear rápidamente prototipos de diferentes ideas de investigación de forma flexible con un código mínimo.
Configuración
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
Capas RNN incorporadas: un ejemplo simple
Hay tres capas RNN integradas en Keras:
keras.layers.SimpleRNN
, un RNN totalmente conectado donde la salida de paso de tiempo anterior es para ser alimentado a la siguiente paso de tiempo.keras.layers.GRU
, propuso por primera vez en Cho et al., 2014 .keras.layers.LSTM
, propuso por primera vez en Hochreiter y Schmidhuber, 1997 .
A principios de 2015, Keras tuvo las primeras implementaciones Python de código abierto reutilizables de LSTM y GRU.
Este es un ejemplo sencillo de un Sequential
modelo que procesos secuencias de números enteros, incrusta cada número entero en un vector de 64 dimensiones, a continuación, procesa la secuencia de vectores utilizando un LSTM
capa.
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 _________________________________________________________________
Los RNN incorporados admiten una serie de características útiles:
- Deserción recurrente, a través de los
dropout
yrecurrent_dropout
argumentos - Capacidad para procesar una secuencia de entrada a la inversa, a través de la
go_backwards
argumento - Desenrollado Loop (que puede conducir a un gran aumento de velocidad al procesar secuencias cortas de CPU), a través de la
unroll
argumento - ...y más.
Para obtener más información, consulte la documentación de la API RNN .
Salidas y estados
De forma predeterminada, la salida de una capa RNN contiene un solo vector por muestra. Este vector es la salida de la celda RNN correspondiente al último paso de tiempo, que contiene información sobre toda la secuencia de entrada. La forma de esta salida es (batch_size, units)
donde units
corresponde a la units
argumento pasado al constructor de la capa.
La capa A RNN también puede devolver toda la secuencia de salidas para cada muestra (un vector por paso de tiempo por muestra), si se establece return_sequences=True
. La forma de esta salida es (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 _________________________________________________________________
Además, una capa RNN puede devolver su(s) estado(s) interno(s) final(es). Los estados devueltos se pueden utilizar para reanudar la ejecución RNN más tarde, o para inicializar otra RNN . Esta configuración se usa comúnmente en el modelo de secuencia a secuencia codificador-decodificador, donde el estado final del codificador se usa como el estado inicial del decodificador.
Para configurar una capa RNN para volver a su estado interno, establecer el return_state
parámetro a True
al crear la capa. Tenga en cuenta que LSTM
tiene 2 tensores del estado, pero GRU
sólo tiene uno.
Para configurar el estado inicial de la capa, simplemente llame a la capa con el argumento de palabra clave adicional initial_state
. Tenga en cuenta que la forma del estado debe coincidir con el tamaño de la unidad de la capa, como en el ejemplo a continuación.
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 __________________________________________________________________________________________________
Capas RNN y celdas RNN
Además de las capas de RNN integradas, la API de RNN también proporciona API a nivel de celda. A diferencia de las capas RNN, que procesan lotes completos de secuencias de entrada, la celda RNN solo procesa un solo paso de tiempo.
La célula es el interior de la for
bucle de una capa RNN. Envolviendo una célula dentro de un keras.layers.RNN
capa que da una capa capaz de procesar lotes de secuencias, por ejemplo RNN(LSTMCell(10))
.
Matemáticamente, RNN(LSTMCell(10))
produce el mismo resultado que LSTM(10)
. De hecho, la implementación de esta capa en TF v1.x fue simplemente crear la celda RNN correspondiente y envolverla en una capa RNN. Sin embargo, utilizando la incorporada en el GRU
y LSTM
capas permitir el uso de CuDNN y es posible que vea un mejor rendimiento.
Hay tres celdas RNN integradas, cada una de ellas correspondiente a la capa RNN correspondiente.
keras.layers.SimpleRNNCell
corresponde a laSimpleRNN
capa.keras.layers.GRUCell
corresponde a laGRU
capa.keras.layers.LSTMCell
corresponde a laLSTM
capa.
La abstracción de células, junto con el genérico keras.layers.RNN
clase, hacen que sea muy fácil de implementar arquitecturas personalizados RNN para su investigación.
Estado de lotes cruzados
Al procesar secuencias muy largas (posiblemente infinito), es posible que desee utilizar el patrón de statefulness transversal lote.
Normalmente, el estado interno de una capa RNN se restablece cada vez que ve un nuevo lote (es decir, se supone que cada muestra vista por la capa es independiente del pasado). La capa solo mantendrá un estado mientras procesa una muestra determinada.
Sin embargo, si tiene secuencias muy largas, es útil dividirlas en secuencias más cortas y alimentar estas secuencias más cortas secuencialmente en una capa RNN sin restablecer el estado de la capa. De esa forma, la capa puede retener información sobre la totalidad de la secuencia, aunque solo vea una subsecuencia a la vez.
Usted puede hacer esto mediante el establecimiento stateful=True
en el constructor.
Si usted tiene una secuencia s = [t0, t1, ... t1546, t1547]
, que sería dividirlo en por ejemplo,
s1 = [t0, t1, ... t100]
s2 = [t101, ... t201]
...
s16 = [t1501, ... t1547]
Entonces lo procesarías a través de:
lstm_layer = layers.LSTM(64, stateful=True)
for s in sub_sequences:
output = lstm_layer(s)
Cuando se quiere borrar el estado, puede utilizar layer.reset_states()
.
Aquí hay un ejemplo completo:
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()
Reutilización del estado RNN
Los estados registrados de la capa de RNN no están incluidos en los layer.weights()
. Si desea volver a utilizar el estado de una capa de RNN, se puede recuperar el valor de los estados por layer.states
y utilizarlo como el estado inicial para una nueva capa a través de la API funcional Keras como new_layer(inputs, initial_state=layer.states)
, o subclases de modelos.
Tenga en cuenta también que es posible que no se use el modelo secuencial en este caso, ya que solo admite capas con una sola entrada y salida, la entrada adicional del estado inicial hace que no se pueda usar aquí.
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 bidireccionales
Para secuencias que no sean series de tiempo (por ejemplo, texto), a menudo ocurre que un modelo RNN puede funcionar mejor si no solo procesa la secuencia de principio a fin, sino también hacia atrás. Por ejemplo, para predecir la siguiente palabra en una oración, a menudo es útil tener el contexto alrededor de la palabra, no solo las palabras que la preceden.
Keras proporciona una API fácil para usted para construir tales RNNs bidireccionales: la keras.layers.Bidirectional
envoltura.
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 _________________________________________________________________
Bajo el capó, Bidirectional
copiará la capa RNN pasado, y darle la vuelta al go_backwards
campo de la capa que acaba de copiar, por lo que va a procesar las entradas en orden inverso.
La salida del Bidirectional
RNN será, por defecto, la concatenación de la salida de la capa hacia adelante y la salida de la capa hacia atrás. Si necesita un comportamiento de fusión diferente, por ejemplo, la concatenación, cambiar el merge_mode
parámetro en el Bidirectional
constructor de envoltura. Para más detalles sobre Bidirectional
, consulte los documentos de la API .
Optimización del rendimiento y núcleos CuDNN
En TensorFlow 2.0, las capas LSTM y GRU integradas se actualizaron para aprovechar los kernels CuDNN de forma predeterminada cuando hay una GPU disponible. Con este cambio, los anteriores keras.layers.CuDNNLSTM/CuDNNGRU
capas han quedado obsoletos, y usted puede construir su modelo sin tener que preocuparse por el hardware que se ejecutará en.
Ya que el núcleo CuDNN está construido con ciertos supuestos, esto significa que la capa no serán capaces de utilizar el núcleo CuDNN si cambia los valores por defecto de las capas LSTM o GRU incorporadas. P.ej:
- Cambio de la
activation
la función detanh
a otra cosa. - Cambio de la
recurrent_activation
función delsigmoid
a otra cosa. - Usando
recurrent_dropout
> 0. - Configuración
unroll
en True, que las fuerzas LSTM / GRU para descomponer el interiortf.while_loop
en un desenrolladofor
bucle. - Configuración
use_bias
en Falso. - Uso de enmascaramiento cuando los datos de entrada no se rellenan estrictamente a la derecha (si la máscara corresponde a datos rellenados estrictamente a la derecha, aún se puede usar CuDNN. Este es el caso más común).
Para una lista detallada de las limitaciones, consulte la documentación de los LSTM y GRU capas.
Usar núcleos CuDNN cuando estén disponibles
Construyamos un modelo LSTM simple para demostrar la diferencia de rendimiento.
Usaremos como secuencias de entrada la secuencia de filas de dígitos MNIST (tratando cada fila de píxeles como un paso de tiempo) y predeciremos la etiqueta del dígito.
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
Carguemos el conjunto de datos 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]
Vamos a crear una instancia de modelo y entrenarla.
Elegimos sparse_categorical_crossentropy
como la función de pérdida para el modelo. La salida del modelo tiene forma de [batch_size, 10]
. El objetivo del modelo es un vector entero, cada uno de los enteros está en el rango de 0 a 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>
Ahora, comparemos con un modelo que no usa el kernel 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>
Cuando se ejecuta en una máquina con una GPU NVIDIA y CuDNN instalados, el modelo creado con CuDNN es mucho más rápido de entrenar en comparación con el modelo que usa el núcleo TensorFlow normal.
El mismo modelo habilitado para CuDNN también se puede usar para ejecutar la inferencia en un entorno solo de CPU. El tf.device
anotación de abajo es sólo obligando a la colocación del dispositivo. El modelo se ejecutará en la CPU de forma predeterminada si no hay una GPU disponible.
Simplemente ya no tiene que preocuparse por el hardware en el que se está ejecutando. ¿No es genial?
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 con entradas de lista/dict o entradas anidadas
Las estructuras anidadas permiten a los implementadores incluir más información en un solo paso de tiempo. Por ejemplo, un cuadro de video podría tener entrada de audio y video al mismo tiempo. La forma de datos en este caso podría ser:
[batch, timestep, {"video": [height, width, channel], "audio": [frequency]}]
En otro ejemplo, los datos de escritura a mano podrían tener las coordenadas x e y para la posición actual del lápiz, así como información sobre la presión. Entonces la representación de los datos podría ser:
[batch, timestep, {"location": [x, y], "pressure": [force]}]
El siguiente código proporciona un ejemplo de cómo crear una celda RNN personalizada que acepte tales entradas estructuradas.
Defina una celda personalizada que admita entrada/salida anidada
Ver Hacer nuevos modelos a través de capas y la subclasificación para obtener detalles sobre cómo escribir sus propias capas.
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}
Cree un modelo RNN con entrada/salida anidada
Vamos a construir un modelo que utiliza un Keras keras.layers.RNN
capa y la célula a medida que acabamos de definir.
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"])
Entrena el modelo con datos generados aleatoriamente
Dado que no hay un buen conjunto de datos candidato para este modelo, usamos datos Numpy aleatorios para la demostración.
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>
Con la Keras keras.layers.RNN
capa, sólo se le espera para definir la lógica matemática para el paso individual dentro de la secuencia, y la keras.layers.RNN
capa va a manejar la iteración secuencia para usted. Es una forma increíblemente poderosa de crear rápidamente prototipos de nuevos tipos de RNN (por ejemplo, una variante de LSTM).
Para más detalles, por favor visite los documentos de la API .