Former un RNN avec des exemples de différentes longueurs à Keras

64

J'essaie de commencer à apprendre sur les RNN et j'utilise Keras. Je comprends le principe de base des couches RNN et LSTM à la vanille, mais j’ai du mal à comprendre un certain point technique pour la formation.

Dans la documentation de keras , il est indiqué que l’entrée dans une couche RNN doit avoir une forme (batch_size, timesteps, input_dim). Cela suggère que tous les exemples d'apprentissage ont une longueur de séquence fixe, à savoir timesteps.

Mais ce n'est pas particulièrement typique, n'est-ce pas? Je souhaiterais peut-être que le RNN opère avec des phrases de durée variable. Lorsque je l’entraînerai sur un corpus, je le nourrirai de lots de phrases de longueurs différentes.

Je suppose que la chose la plus évidente à faire serait de trouver la longueur maximale d’une séquence dans l’ensemble d’entraînement et de la mettre à zéro. Mais alors cela signifie-t-il que je ne peux pas faire de prédiction au moment du test avec une longueur d'entrée supérieure à celle?

Je suppose que cette question concerne la mise en œuvre particulière de Keras, mais je demande aussi ce que les gens font généralement lorsqu'ils sont confrontés à ce type de problème en général.

Tactique
la source
@kbrose est correct. Cependant, j'ai une préoccupation. Dans l'exemple, vous avez un générateur très spécial de rendements infinis. Plus important encore, il est conçu pour produire des lots de taille 1000. En pratique, cela est trop difficile à satisfaire, voire impossible. Vous devez réorganiser vos entrées pour que celles de même longueur soient organisées ensemble et vous devez définir avec soin les positions de fractionnement par lots. De plus, vous n’avez aucune chance de mélanger les lots. Donc, mon opinion est la suivante: n'utilisez jamais de longueur variable dans Keras à moins de savoir exactement ce que vous faites. Utilisez padding et set Maskinglayer to ignor
Bs He

Réponses:

59

Cela suggère que tous les exemples d'apprentissage ont une longueur de séquence fixe, à savoir timesteps.

Ce n’est pas tout à fait correct, puisque cette dimension peut être None, c’est-à-dire une longueur variable. Dans un même lot , vous devez avoir le même nombre de pas de temps (c'est généralement l'endroit où vous voyez le remplissage et le masquage 0). Mais entre les lots, il n'y a pas de telle restriction. Pendant l'inférence, vous pouvez avoir n'importe quelle longueur.

Exemple de code qui crée des lots de données d’entraînement aléatoires dans le temps.

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)

Et c'est ce que ça imprime. Notez que les formes en sortie (None, None, x)indiquent une taille de lot variable et une taille de pas 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
kbrose
la source
Merci pour ça. Cependant, si nous 0 complétons les séquences, cela affectera les états cachés et la cellule de mémoire car nous continuons à passer x_t à 0, alors que si cela était fait, rien ne devrait être passé. En mode normal fit(), on peut passer le sequence_lenthparamètre pour spécifier la longueur de la séquence à exclure. Il semble que l'approche du générateur ne permet pas d'ignorer les séquences 0?
GRS
1
@GRS Votre générateur peut renvoyer 3 tuple de (inputs, targets, sample_weights)et vous pouvez régler sample_weightsvos 0-pads à 0. Cependant, je ne suis pas sûr que cela fonctionnerait parfaitement pour les RNN bidirectionnels.
kbrose
Cela a été utile, mais j'aimerais aussi inclure un exemple d'utilisation model.predict_generatoravec un ensemble de test. Lorsque j'essaie de prédire avec un générateur, une erreur de concaténation se produit (l'ensemble de test comporte également des séquences de longueur variable). Ma solution a été d’utiliser la norme model.predictde manière simpliste. Peut-être que cela conviendrait mieux à une nouvelle question?
Mickey
@ mickey cela ressemble à une question différente. Cette question concerne la formation, pas la prédiction.
Kbrose
Si la question dans les commentaires a bien été posée en tant que nouvelle question, pouvez-vous y accéder?
Itamar Mushkin
7

@kbrose semble avoir une meilleure solution

Je suppose que la chose la plus évidente à faire serait de trouver la longueur maximale d’une séquence dans l’ensemble d’entraînement et de la mettre à zéro.

C'est généralement une bonne solution. Essayez peut-être longueur maximale de séquence + 100. Utilisez ce qui convient le mieux à votre application.

Mais alors cela signifie-t-il que je ne peux pas faire de prédiction au moment du test avec une longueur d'entrée supérieure à celle?

Pas nécessairement. La raison pour laquelle une longueur fixe est utilisée dans les keras est qu’elle améliore considérablement les performances en créant des tenseurs de formes fixes. Mais ce n'est que pour la formation. Après la formation, vous aurez appris le poids qui convient à votre tâche.

Supposons qu'après des heures d'entraînement, vous réalisiez que la longueur maximale de votre modèle n'était pas assez grande / petite et que vous deviez maintenant modifier les pas de temps, extraire simplement les poids appris de l'ancien modèle, construire un nouveau modèle avec les nouveaux pas de temps. et injecter les poids appris en elle.

Vous pouvez probablement le faire en utilisant quelque chose comme:

new_model.set_weights(old_model.get_weights())

Je n'ai pas essayé moi-même. Essayez-le et publiez vos résultats ici pour le bénéfice de tous. Voici quelques liens: un deux

Aneesh Joshi
la source
1
Vous pouvez en effet avoir des entrées de longueur variable, pas besoin d'introduire des hacks comme max length + 100. Voir ma réponse pour un exemple de code.
kbrose
1
Transférer les poids sur un modèle avec plus de pas de temps fonctionne parfaitement! J'ai heurté les timesteps Bidirectional(LSTM)()et les RepeatVector()couches, et les prédictions sont parfaitement viables.
komodovaran_
@kbrose Ce n'est pas un hack, c'est comment vous le faites normalement. L'utilisation d'une taille_batch de l'une est trop lente et keras permet le masquage des calques afin que le masquage n'affecte pas la perte.
Ferus