Entrenando a un RNN con ejemplos de diferentes longitudes en Keras


63

Estoy tratando de comenzar a aprender sobre RNN y estoy usando Keras. Entiendo la premisa básica de las capas RNN y LSTM de vainilla, pero tengo problemas para comprender un cierto punto técnico para el entrenamiento.

En la documentación de Keras , dice que la entrada a una capa RNN debe tener forma (batch_size, timesteps, input_dim). Esto sugiere que todos los ejemplos de entrenamiento tienen una longitud de secuencia fija, a saber timesteps.

Pero esto no es especialmente típico, ¿verdad? Es posible que desee que el RNN opere en oraciones de diferentes longitudes. Cuando lo entrene en algún corpus, lo alimentaré con lotes de oraciones, de diferentes longitudes.

Supongo que lo obvio sería encontrar la longitud máxima de cualquier secuencia en el conjunto de entrenamiento y ponerla a cero. Pero entonces, ¿eso significa que no puedo hacer predicciones en el momento de la prueba con una longitud de entrada mayor que eso?

Esta es una pregunta sobre la implementación particular de Keras, supongo, pero también estoy preguntando qué hacen las personas cuando se enfrentan a este tipo de problema en general.


@kbrose es correcto. Sin embargo, tengo una preocupación. En el ejemplo, tiene un generador muy especial de rendimientos infinitos. Más importante aún, está diseñado para producir lotes de tamaño 1000. En la práctica, esto es demasiado difícil de satisfacer, si no imposible. Debe reorganizar sus entradas para que las que tengan la misma longitud se organicen juntas, y debe establecer cuidadosamente las posiciones de división por lotes. Además, no tienes oportunidad de barajar los lotes. Entonces, mi opinión es: nunca use entradas de longitud variable en Keras a menos que sepa exactamente lo que está haciendo. Use relleno y ajuste la Maskingcapa para ignorar
Bs He

Respuestas:


59

Esto sugiere que todos los ejemplos de entrenamiento tienen una longitud de secuencia fija, a saber timesteps.

Eso no es del todo correcto, ya que esa dimensión puede ser None, es decir, longitud variable. Dentro de un solo lote , debe tener el mismo número de pasos de tiempo (esto es típicamente donde se ve el relleno 0 y el enmascaramiento). Pero entre lotes no existe tal restricción. Durante la inferencia, puede tener cualquier longitud.

Código de ejemplo que crea lotes aleatorios de datos de entrenamiento de duración prolongada.

from keras.models import Sequential
from keras.layers import LSTM, Dense, TimeDistributed
from keras.utils import to_categorical
import numpy as np

model = Sequential()

model.add(LSTM(32, return_sequences=True, input_shape=(None, 5)))
model.add(LSTM(8, return_sequences=True))
model.add(TimeDistributed(Dense(2, activation='sigmoid')))

print(model.summary(90))

model.compile(loss='categorical_crossentropy',
              optimizer='adam')

def train_generator():
    while True:
        sequence_length = np.random.randint(10, 100)
        x_train = np.random.random((1000, sequence_length, 5))
        # y_train will depend on past 5 timesteps of x
        y_train = x_train[:, :, 0]
        for i in range(1, 5):
            y_train[:, i:] += x_train[:, :-i, i]
        y_train = to_categorical(y_train > 2.5)
        yield x_train, y_train

model.fit_generator(train_generator(), steps_per_epoch=30, epochs=10, verbose=1)

Y esto es lo que imprime. Tenga en cuenta que las formas de salida (None, None, x)indican un tamaño de lote variable y un tamaño de paso de tiempo variable.

__________________________________________________________________________________________
Layer (type)                            Output Shape                        Param #
==========================================================================================
lstm_1 (LSTM)                           (None, None, 32)                    4864
__________________________________________________________________________________________
lstm_2 (LSTM)                           (None, None, 8)                     1312
__________________________________________________________________________________________
time_distributed_1 (TimeDistributed)    (None, None, 2)                     18
==========================================================================================
Total params: 6,194
Trainable params: 6,194
Non-trainable params: 0
__________________________________________________________________________________________
Epoch 1/10
30/30 [==============================] - 6s 201ms/step - loss: 0.6913
Epoch 2/10
30/30 [==============================] - 4s 137ms/step - loss: 0.6738
...
Epoch 9/10
30/30 [==============================] - 4s 136ms/step - loss: 0.1643
Epoch 10/10
30/30 [==============================] - 4s 142ms/step - loss: 0.1441

Gracias por esto. Sin embargo, si 0 rellenamos las secuencias, afectará los estados ocultos y la celda de memoria porque continuamos pasando x_t como 0s, cuando, de hecho, no debería pasar nada. En condiciones normales fit(), podemos pasar el sequence_lenthparámetro para especificar la longitud de la secuencia para excluirla. Parece que el enfoque del generador no permite ignorar 0 secuencias?
GRS

1
@GRS Su generador puede devolver una tupla de 3 (inputs, targets, sample_weights), y puede establecer sample_weightssus 0 pads en 0. Sin embargo, no estoy seguro de que esto funcione perfectamente para RNN bidireccionales.
kbrose

Esto ha sido útil, pero desearía que también incluyera un ejemplo de uso model.predict_generatorcon un conjunto de prueba. Cuando trato de predecir con un generador me sale un error con respecto a la concatenación (el conjunto de prueba también tiene secuencias de longitud variable). Mi solución ha sido usar el estándar model.predictde una manera hacky. ¿Quizás esto sería mejor para una nueva pregunta?
mickey

@mickey que suena como una pregunta diferente. Esta pregunta es sobre entrenamiento, no predicción.
kbrose

Si la pregunta de los comentarios se hizo como una pregunta nueva, ¿puede vincularla?
Itamar Mushkin

7

@kbrose parece tener una mejor solución

Supongo que lo obvio sería encontrar la longitud máxima de cualquier secuencia en el conjunto de entrenamiento y ponerla a cero.

Esta suele ser una buena solución. Tal vez intente la longitud máxima de secuencia + 100. Use lo que funcione mejor para su aplicación.

Pero entonces, ¿eso significa que no puedo hacer predicciones en el momento de la prueba con una longitud de entrada mayor que eso?

No necesariamente. La razón por la que se usa una longitud fija en keras es porque mejora enormemente el rendimiento al crear tensores de formas fijas. Pero eso es solo para entrenar. Después del entrenamiento, habrás aprendido los pesos correctos para tu tarea.

Supongamos que, después de entrenar durante horas, se da cuenta de que la longitud máxima de su modelo no era lo suficientemente grande / pequeña y ahora necesita cambiar los pasos de tiempo, simplemente extraer los pesos aprendidos del modelo anterior, construir un nuevo modelo con los nuevos pasos de tiempo e inyectarle los pesos aprendidos.

Probablemente puedas hacer esto usando algo como:

new_model.set_weights(old_model.get_weights())

No lo he probado yo mismo. Pruébelo y publique sus resultados aquí para beneficio de todos. Aquí hay algunos enlaces: uno dos


1
De hecho, puede tener entradas de longitud variable, no es necesario introducir hacks como max length + 100. Vea mi respuesta para el código de ejemplo.
kbrose

1
¡Transferir los pesos a un modelo con más pasos de tiempo funciona perfectamente! Aumenté los tiempos Bidirectional(LSTM)()y las RepeatVector()capas, y las predicciones son perfectamente viables.
komodovaran_

@kbrose Esto no es un truco, es cómo lo haces normalmente. Usar un batch_size de uno es demasiado lento y los keras habilitan capas de enmascaramiento para que el enmascaramiento no afecte la pérdida.
Ferus
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.