![]() | ![]() | ![]() | ![]() |
Introducción
Las redes neuronales recurrentes (RNN) son una clase de redes neuronales que es poderosa para modelar datos de secuencia, como series de tiempo o lenguaje natural.
De manera esquemática, una capa RNN usa un bucle for
para iterar sobre los pasos de tiempo de una secuencia, mientras mantiene un estado interno que codifica información sobre los pasos de tiempo que ha visto hasta ahora.
La API de Keras RNN está diseñada con un enfoque en:
Facilidad de uso : las capas
keras.layers.GRU
keras.layers.RNN
,keras.layers.LSTM
,keras.layers.GRU
permiten crear rápidamente modelos recurrentes sin tener que realizar elecciones de configuración difíciles.Facilidad de personalización : también puede definir su propia capa de celda RNN (la parte interna del bucle
for
) con un comportamiento personalizado y utilizarla con la capa genéricakeras.layers.RNN
(el buclefor
sí). Esto le permite crear rápidamente prototipos de diferentes ideas de investigación de una manera 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 integradas: un ejemplo sencillo
Hay tres capas RNN integradas en Keras:
keras.layers.SimpleRNN
, un RNN completamente conectado donde la salida del paso de tiempo anterior se alimenta al siguiente paso de tiempo.keras.layers.GRU
, propuesto por primera vez en Cho et al., 2014 .keras.layers.LSTM
, propuesto por primera vez en Hochreiter & Schmidhuber, 1997 .
A principios de 2015, Keras tuvo las primeras implementaciones de Python de código abierto reutilizables de LSTM y GRU.
Aquí hay un ejemplo simple de un modelo Sequential
que procesa secuencias de números enteros, incrusta cada número entero en un vector de 64 dimensiones y luego procesa la secuencia de vectores usando una capa 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 _________________________________________________________________
Los RNN integrados admiten una serie de funciones útiles:
- Abandono recurrente, a través de los argumentos
dropout
ydropout
recurrent_dropout
- Capacidad para procesar una secuencia de entrada a la inversa, mediante el argumento
go_backwards
- Desenrollado de bucle (que puede conducir a una gran aceleración al procesar secuencias cortas en la CPU), a través del argumento
unroll
- ...y más.
Para obtener más información, consulte la documentación de la API de 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 la secuencia de entrada completa. La forma de esta salida es (batch_size, units)
donde las units
corresponden al argumento de las units
pasado al constructor de la capa.
Una capa RNN también puede devolver la secuencia completa de salidas para cada muestra (un vector por paso de tiempo por muestra), si 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 sus estados internos finales. Los estados devueltos se pueden utilizar para reanudar la ejecución de RNN más tarde o para inicializar otro RNN . Esta configuración se usa comúnmente en el modelo de secuencia a secuencia de codificador-decodificador, donde el estado final del codificador se usa como el estado inicial del decodificador.
Para configurar una capa RNN para que devuelva su estado interno, establezca el parámetro return_state
en True
al crear la capa. Tenga en cuenta que LSTM
tiene 2 tensores de estado, pero GRU
solo 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 unidad de la capa, como en el ejemplo siguiente.
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 células RNN
Además de las capas RNN integradas, la API 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 único paso de tiempo.
La celda es el interior del bucle for
de una capa RNN. Envolver una celda dentro de una capa keras.layers.RNN
le proporciona 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, el uso de las capas GRU
y LSTM
integradas permite el uso de CuDNN y es posible que vea un mejor rendimiento.
Hay tres celdas RNN integradas, cada una de las cuales corresponde a la capa RNN correspondiente.
keras.layers.SimpleRNNCell
corresponde a la capaSimpleRNN
.keras.layers.GRUCell
corresponde a la capaGRU
.keras.layers.LSTMCell
corresponde a la capaLSTM
.
La abstracción de celda, junto con la clase genérica keras.layers.RNN
, facilitan la implementación de arquitecturas RNN personalizadas para su investigación.
Statefulness entre lotes
Al procesar secuencias muy largas (posiblemente infinitas), es posible que desee utilizar el patrón de estado de lotes cruzados .
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 manera, la capa puede retener información sobre la totalidad de la secuencia, aunque solo vea una subsecuencia a la vez.
Puede hacer esto estableciendo stateful=True
en el constructor.
Si tiene una secuencia s = [t0, t1, ... t1546, t1547]
, la dividiría 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 desee borrar el estado, puede usar layer.reset_states()
.
Aquí tienes 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 de estado RNN
Los estados registrados de la capa RNN no se incluyen en layer.weights()
. Si desea reutilizar el estado de una capa RNN, puede recuperar el valor de los estados por layer.states
y usarlo como el estado inicial para una nueva capa a través de la API funcional de Keras como new_layer(inputs, initial_state=layer.states)
o subclases de modelos.
Tenga en cuenta también que es posible que el modelo secuencial no se use en este caso, ya que solo admite capas con una sola entrada y salida, la entrada adicional del estado inicial hace que sea imposible de 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 vienen antes.
Keras proporciona una API fácil para que usted keras.layers.Bidirectional
tales keras.layers.Bidirectional
bidireccionales: la envoltura 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 _________________________________________________________________
Debajo del capó, Bidirectional
copiará la capa RNN pasada y go_backwards
campo go_backwards
de la capa recién copiada, de modo que procesará las entradas en orden inverso.
La salida del RNN Bidirectional
será, por defecto, la concatenación de la salida de la capa directa y la salida de la capa inversa. Si necesita un comportamiento de fusión diferente, por ejemplo, concatenación, cambie el parámetro merge_mode
en el constructor contenedor Bidirectional
. Para obtener más detalles sobre Bidirectional
, consulte los documentos de la API .
Optimización del rendimiento y kernels CuDNN
En TensorFlow 2.0, las capas integradas de LSTM y GRU se actualizaron para aprovechar los kernels CuDNN de forma predeterminada cuando hay una GPU disponible. Con este cambio, las capas keras.layers.CuDNNLSTM/CuDNNGRU
han quedado obsoletas y puede crear su modelo sin preocuparse por el hardware en el que se ejecutará.
Dado que el kernel CuDNN se construye con ciertas suposiciones, esto significa que la capa no podrá usar el kernel CuDNN si cambia los valores predeterminados de las capas integradas LSTM o GRU . P.ej:
- Cambiar la función de
activation
detanh
a otra cosa. - Cambiar la función
recurrent_activation
desigmoid
a otra cosa. - Usando
recurrent_dropout
> 0. - Establecer
unroll
en True, lo que obliga a LSTM / GRU a descomponer eltf.while_loop
interno en un buclefor
desenrollado. - Estableciendo
use_bias
en False. - Usar enmascaramiento cuando los datos de entrada no están estrictamente rellenados a la derecha (si la máscara corresponde a datos estrictamente rellenados a la derecha, CuDNN aún se puede usar. Este es el caso más común).
Para obtener una lista detallada de restricciones, consulte la documentación de las capas LSTM y GRU .
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]
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz 11493376/11490434 [==============================] - 0s 0us/step
Creemos una instancia de modelo y entreneémosla.
Elegimos sparse_categorical_crossentropy
como la función de pérdida para el modelo. La salida del modelo tiene la forma [batch_size, 10]
. El objetivo del modelo es un vector entero, cada uno de los números 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: 1.2900 - accuracy: 0.5779 - val_loss: 0.5115 - val_accuracy: 0.8402 <tensorflow.python.keras.callbacks.History at 0x7f5cf00c6518>
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 [==============================] - 32s 33ms/step - loss: 0.4511 - accuracy: 0.8632 - val_loss: 0.3877 - val_accuracy: 0.8686 <tensorflow.python.keras.callbacks.History at 0x7f5ce00c5748>
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 kernel regular de TensorFlow.
El mismo modelo habilitado para CuDNN también se puede usar para ejecutar inferencias en un entorno solo de CPU. La anotación tf.device
continuación solo fuerza la ubicación del dispositivo. El modelo se ejecutará en la CPU de forma predeterminada si no hay GPU disponible.
Simplemente ya no tiene que preocuparse por el hardware en el que 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 / dictado 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 los 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 xey para la posición actual del lápiz, así como información sobre la presión. Entonces, la representación de datos podría ser:
[batch, timestep, {"location": [x, y], "pressure": [force]}]
El siguiente código proporciona un ejemplo de cómo construir una celda RNN personalizada que acepta tales entradas estructuradas.
Definir una celda personalizada que admita entrada / salida anidada
Consulte Creación de nuevas capas y modelos mediante subclases 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}
Construya un modelo RNN con entrada / salida anidada
Construyamos un modelo de Keras que use una capa keras.layers.RNN
y la celda personalizada 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"])
Entrene el modelo con datos generados aleatoriamente
Dado que no hay un buen conjunto de datos candidato para este modelo, utilizamos datos aleatorios de Numpy 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 25ms/step - loss: 0.9178 - rnn_1_loss: 0.3211 - rnn_1_1_loss: 0.5968 - rnn_1_accuracy: 0.1097 - rnn_1_1_accuracy: 0.0382 <tensorflow.python.keras.callbacks.History at 0x7f5c391e43c8>
Con la capa keras.layers.RNN
Keras, solo se espera que defina la lógica matemática para el paso individual dentro de la secuencia, y la capa keras.layers.RNN
manejará la iteración de la secuencia por usted. Es una forma increíblemente poderosa de crear rápidamente un prototipo de nuevos tipos de RNN (por ejemplo, una variante de LSTM).
Para obtener más detalles, visite los documentos de la API .