Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/es-419/guide/keras/rnn.ipynb
25118 views
Kernel: Python 3
#@title Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

Redes neuronales recurrentes (RNN) con Keras

Introducción

Las redes neuronales recurrentes (RNN) son una clase de redes neuronales potentes para modelar datos secuenciales, como series temporales o lenguaje natural.

En términos esquemáticos, una capa RNN utiliza un bucle for para iterar sobre los pasos de tiempo de una secuencia, mientras mantiene un estado interno que codifica la información sobre los pasos de tiempo que ha visto hasta el momento.

La API Keras RNN está diseñada con un enfoque en:

  • Facilidad de uso: las capas incorporadas keras.layers.RNN, keras.layers.LSTM, keras.layers.GRU permiten construir rápidamente modelos recurrentes sin tener que tomar decisiones difíciles sobre la configuración.

  • Facilidad de personalización: También puede definir su propia capa de celdas RNN (la parte interna del bucle for) con un comportamiento personalizado, y utilizarla con la capa genérica keras.layers.RNN (el propio bucle for). Esto le permite crear rápidamente prototipos de diferentes ideas de investigación de una manera flexible con un código mínimo.

Preparación

import numpy as np import tensorflow as tf from tensorflow import keras from tensorflow.keras import layers

Capas RNN integradas: un ejemplo simple

Hay tres capas RNN integradas en Keras:

  1. keras.layers.SimpleRNN, una RNN completamente conectada en la que la salida del paso de tiempo anterior debe alimentar el siguiente paso de tiempo.

  2. keras.layers.GRU, propuesto por primera vez en Cho et al., 2014.

  3. keras.layers.LSTM, propuesto por primera vez en Hochreiter & Schmidhuber, 1997.

A principios del 2015, Keras tuvo las primeras implementaciones reutilizables de código abierto en Python de LSTM y GRU.

Este es un ejemplo sencillo de un modelo de Sequential que procesa secuencias de números enteros, incrusta cada número entero en un vector de 64 dimensiones y, a continuación, procesa la secuencia de vectores utilizando 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()

Las RNN integradas admiten varias funciones útiles:

  • Abandono recurrente, mediante los argumentos dropout y recurrent_dropout.

  • Capacidad para procesar una secuencia de entrada en sentido inverso, mediante el argumento go_backwards.

  • Desenrollar bucles (que puede suponer un gran aumento de velocidad al procesar secuencias cortas en el CPU), mediante el argumento unroll.

  • ...y mucho 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 único vector por muestra. Este vector es la salida de la célula 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 al argumento 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 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()

Además, una capa RNN puede devolver su estados internos finales. Los estados devueltos pueden utilizarse para reanudar la ejecución de la RNN más tarde, o para inicializar otra RNN. Esta configuración se utiliza comúnmente en el modelo secuencia-a-secuencia codificador-decodificador, donde el estado final del codificador se utiliza como 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, basta con llamar a la capa con el argumento 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 siguiente ejemplo.

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()

Capas RNN y celdas RNN

Además de las capas RNN integradas, la API RNN también proporciona APIs a nivel de celda. A diferencia de las capas RNN, que procesan lotes enteros de secuencias de entrada, la celda RNN solo procesa un único paso temporal.

La celda es el interior del bucle for de una capa RNN. Al envolver una celda dentro de una capa keras.layers.RNN se obtiene 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 era simplemente crear la celda RNN correspondiente y envolverla en una capa RNN. Sin embargo, el uso de las capas integradas GRU y LSTM permite el uso de CuDNN y se puede ver 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 capa SimpleRNN.

  • keras.layers.GRUCell corresponde a la capa GRU.

  • keras.layers.LSTMCell corresponde a la capa LSTM.

La abstracción de celdas, junto con la clase genérica keras.layers.RNN, hacen que sea muy fácil implementar arquitecturas RNN personalizadas para su investigación.

Estado de los lotes cruzados

Al procesar secuencias muy largas (posiblemente infinitas), es posible que desee utilizar el patrón de estado de los 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 este modo, la capa puede retener información sobre toda la secuencia, aunque solo vea una subsecuencia cada vez.

Puede hacerlo estableciendo stateful=True en el constructor.

Si tiene una secuencia s = [t0, t1, ... t1546, t1547], la dividiría, por ejemplo, en:

s1 = [t0, t1, ... t100] s2 = [t101, ... t201] ... s16 = [t1501, ... t1547]

Entonces lo procesaría 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 utilizar layer.reset_states().

Nota: En esta configuración, se supone que la muestra i de un lote determinado es la continuación de la muestra i del lote anterior. Esto significa que todos los lotes deben contener el mismo número de muestras (tamaño del lote). Por ejemplo, si un lote contiene [sequence_A_from_t0_to_t100, sequence_B_from_t0_to_t100], el siguiente lote debería contener [sequence_A_from_t101_to_t200, sequence_B_from_t101_to_t200].

Este es 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 RNN no se incluyen en layer.weights(). Si desea reutilizar el estado de una capa RNN, puede recuperar el valor de los estados mediante layer.states y utilizarlo como estado inicial para una nueva capa mediante la API funcional de Keras como new_layer(inputs, initial_state=layer.states), o la subclase de modelos.

Tenga en cuenta también que el modelo sequential no se puede utilizar en este caso, ya que solo admite capas con una entrada y una salida, y la entrada adicional del estado inicial hace que sea imposible utilizarlo 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)

RNNs bidireccionales

Para secuencias que no sean series temporales (por ejemplo, texto), suele ocurrir 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 de una frase, suele ser útil disponer del contexto que rodea a la palabra, no únicamente de las palabras que la preceden.

Keras proporciona una API sencilla para construir RNNs bidireccionales: el envoltorio 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()

En este caso, Bidirectional copiará la capa RNN introducida e invertirá el campo go_backwards de la nueva capa copiada, de forma que procesará las entradas en orden inverso.

La salida de la RNN Bidirectional será, de forma predeterminada, 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, que una concatenación, cambie el parámetro merge_mode en el constructor de la envoltura Bidirectional. Para obtener más información sobre Bidirectional, consulte la documentación de la API.

Optimización del rendimiento y kernels CuDNN

En TensorFlow 2.0, las capas LSTM y GRU incorporadas se actualizaron para aprovechar los núcleos CuDNN de forma predeterminada cuando está disponible una GPU. Con este cambio, las capas anteriores keras.layers.CuDNNLSTM/CuDNNGRU quedaron obsoletas, y podrá construir 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á utilizar el kernel CuDNN si cambia los valores predeterminados de las capas incorporadas LSTM o GRU. Por ejemplo:

  • Cambiar la función activation de tanh a algo distinto.

  • Cambiar la función recurrent_activation de sigmoide a algo diferente.

  • Usar recurrent_dropout > 0.

  • Establecer unroll en True, lo que obligará a LSTM/GRU a descomponer el bucle tf.while_loop interno en un bucle for desenrollado.

  • Establecer use_bias en False.

  • Utilizar el 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 puede seguir utilizándose. Este es el caso más común).

Para obtener la lista detallada de restricciones, consulte la documentación de las capas LSTM y GRU.

Cómo utilizar los kernels CuDNN cuando estén disponibles

Construyamos un modelo LSTM sencillo para demostrar la diferencia de rendimiento.

Utilizaremos como secuencias de entrada la secuencia de filas de dígitos MNIST (tratando cada fila de pixeles 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]

Creemos una instancia del modelo y vamos a entrenarla.

Elegimos sparse_categorical_crossentropy como función de pérdida para el modelo. La salida del modelo tiene forma de [batch_size, 10]. El objetivo para el 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 )

Ahora, comparemos con un modelo que no utiliza 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 )

Cuando se ejecuta en una máquina con una GPU NVIDIA y CuDNN instalada, el modelo construido con CuDNN es mucho más rápido de entrenar en comparación con el modelo que utiliza el kernel TensorFlow normal.

El mismo modelo CuDNN habilitado también se puede utilizar para ejecutar la inferencia en un entorno basado únicamente en el CPU. La anotación tf.device de abajo solo está forzando la colocación del dispositivo. El modelo se ejecutará en la CPU de forma predeterminada si no hay una GPU disponible.

Ya no tendrá que preocuparse por el hardware que utiliza. ¿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"))

RNNs con entradas de lista/dict, o entradas anidadas

Las estructuras anidadas permiten a los programadores incluir más información en un solo paso de tiempo. Por ejemplo, un fotograma 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 manual podrían tener coordenadas x y y para la posición actual del bolígrafo, así como información sobre la presión. Así que la representación de los datos podría ser la siguiente:

[batch, timestep, {"location": [x, y], "pressure": [force]}]

El siguiente código proporciona un ejemplo de cómo construir una celda RNN personalizada que acepte tales entradas estructuradas.

Cómo definir una celda personalizada que admita entradas/salidas anidadas

Consulte Creación de nuevas capas y modelos mediante subclases para obtener más información sobre cómo crear 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}

Cómo construir un modelo RNN con entrada/salida anidada

Vamos a construir un modelo Keras que utilice 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 existe un buen conjunto de datos para este modelo, utilizaremos datos aleatorios de Numpy para realizar 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)

Con la capa keras.layers.RNN de Keras, solo se espera que defina la lógica matemática para un paso individual dentro de la secuencia, y la capa keras.layers.RNN administrará la iteración de la secuencia por usted. Es una forma increíblemente potente de crear rápidamente prototipos de nuevos tipos de RNN (por ejemplo, una variante de LSTM).

Para obtener más información, visite los documentos de la API.