Training eines RNN mit Beispielen unterschiedlicher Länge in Keras

60

Ich versuche, etwas über RNNs zu lernen und verwende Keras. Ich verstehe die Grundvoraussetzung von Vanille-RNN- und LSTM-Schichten, habe jedoch Probleme, einen bestimmten technischen Punkt für das Training zu verstehen.

In der Keras-Dokumentation heißt es, dass die Eingabe in eine RNN-Ebene eine Form haben muss (batch_size, timesteps, input_dim). Dies legt nahe, dass alle Trainingsbeispiele eine feste Sequenzlänge haben, nämlich timesteps.

Aber das ist nicht besonders typisch, oder? Ich möchte vielleicht, dass die RNN Sätze unterschiedlicher Länge bearbeitet. Wenn ich es auf einem Korpus trainiere, füttere ich es mit Sätzen unterschiedlicher Länge.

Ich nehme an, es wäre naheliegend, die maximale Länge einer Sequenz im Trainingsset zu finden und sie mit einem Null-Pad zu versehen. Aber heißt das dann, dass ich zum Zeitpunkt des Tests keine Vorhersagen mit einer längeren Eingabe machen kann?

Ich nehme an, dies ist eine Frage zur speziellen Implementierung von Keras, aber ich frage mich auch, was die Leute normalerweise tun, wenn sie mit solchen Problemen im Allgemeinen konfrontiert werden.

Taktik
quelle
@ kbrose ist richtig. Ich habe jedoch eine Sorge. Im Beispiel haben Sie einen ganz besonderen Generator mit unendlich vielen Erträgen. Noch wichtiger ist, dass es Chargen der Größe 1000 liefert. In der Praxis ist dies zu schwer zu befriedigen, wenn nicht sogar unmöglich. Sie müssen Ihre Einträge neu organisieren, damit die Einträge mit der gleichen Länge zusammen angeordnet werden, und Sie müssen die Positionen für die Stapelaufteilung sorgfältig festlegen. Darüber hinaus haben Sie keine Möglichkeit, die Stapel zu mischen. Meine Meinung ist also: Verwenden Sie in Keras niemals Eingaben unterschiedlicher Länge, es sei denn, Sie wissen genau, was Sie tun. Verwende Maskingpadding und setze die Ebene auf ignor
Bs He

Antworten:

56

Dies legt nahe, dass alle Trainingsbeispiele eine feste Sequenzlänge haben, nämlich timesteps.

Das ist nicht ganz richtig, da diese Dimension Nonevariabel sein kann. Innerhalb einer einzelnen Charge , müssen Sie die gleiche Anzahl von Zeitschritten haben (dies in der Regel ist , wo Sie sehen 0-Polster und Maskierung). Zwischen den Chargen gibt es jedoch keine solche Einschränkung. Während der Inferenz können Sie eine beliebige Länge haben.

Beispielcode, der zufällige zeitlange Stapel von Trainingsdaten erstellt.

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)

Und das druckt es. Beachten Sie, dass die Ausgabeformen eine (None, None, x)variable Stapelgröße und eine variable Zeitschrittgröße anzeigen.

__________________________________________________________________________________________
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
quelle
Danke dafür. Wenn wir jedoch die Sequenzen mit 0 auffüllen, wirkt sich dies auf die verborgenen Zustände und die Speicherzelle aus, da wir weiterhin x_t als 0 übergeben, wenn tatsächlich nichts übergeben werden sollte. Normalerweise fit()können wir den sequence_lenthParameter übergeben, um die Länge der Sequenz anzugeben, die ausgeschlossen werden soll. Es scheint, dass der Generator-Ansatz das Ignorieren von 0-Sequenzen nicht zulässt?
GRS
1
@GRS Ihr Generator kann ein 3-Tupel von zurückgeben (inputs, targets, sample_weights), und Sie können sample_weightsIhre 0-Pads auf 0 setzen. Ich bin jedoch nicht sicher, ob dies für bidirektionale RNNs perfekt funktionieren würde.
Kbrose
Dies war hilfreich, aber ich wünschte, es würde auch ein Beispiel für die Verwendung model.predict_generatormit einem Test-Set enthalten. Wenn ich versuche, mit einem Generator eine Vorhersage zu treffen, erhalte ich eine Fehlermeldung bezüglich der Verkettung (der Testsatz enthält auch Sequenzen mit variabler Länge). Meine Lösung bestand darin, den Standard model.predictauf hackige Weise zu verwenden. Wäre das vielleicht besser für eine neue Frage?
Mickey
@ Mickey das klingt nach einer anderen Frage. Bei dieser Frage geht es um Training, nicht um Vorhersage.
kbrose
Wenn die Frage in den Kommentaren tatsächlich als neue Frage gestellt wurde, können Sie einen Link dazu erstellen?
Itamar Mushkin vor
7

@kbrose scheint eine bessere Lösung zu haben

Ich nehme an, es wäre naheliegend, die maximale Länge einer Sequenz im Trainingsset zu finden und sie mit einem Null-Pad zu versehen.

Dies ist normalerweise eine gute Lösung. Versuchen Sie es mit einer maximalen Sequenzlänge von + 100. Verwenden Sie das, was für Ihre Anwendung am besten geeignet ist.

Aber heißt das dann, dass ich zum Zeitpunkt des Tests keine Vorhersagen mit einer längeren Eingabe machen kann?

Nicht unbedingt. Der Grund, warum in Keras eine feste Länge verwendet wird, ist, dass dies die Leistung erheblich verbessert, indem Tensoren mit festen Formen erzeugt werden. Aber das ist nur für das Training. Nach dem Training haben Sie die richtigen Gewichte für Ihre Aufgabe gelernt.

Nehmen wir an, dass Sie nach stundenlangem Training feststellen, dass die maximale Länge Ihres Modells nicht groß / klein genug ist und Sie nun die Zeitschritte ändern müssen. Extrahieren Sie einfach die erlernten Gewichte aus dem alten Modell und erstellen Sie ein neues Modell mit den neuen Zeitschritten und spritze die erlernten Gewichte hinein.

Sie können dies wahrscheinlich mit etwas tun, wie:

new_model.set_weights(old_model.get_weights())

Ich habe es nicht selbst ausprobiert. Bitte probieren Sie es aus und veröffentlichen Sie Ihre Ergebnisse hier zum Vorteil aller. Hier sind einige Links: eins zwei

Aneesh Joshi
quelle
1
Sie können in der Tat Eingänge mit variabler Länge haben, ohne Hacks wie einführen zu müssen max length + 100. Siehe meine Antwort zum Beispiel Code.
Kbrose
1
Das Übertragen der Gewichte auf ein Modell mit mehr Zeitschritten funktioniert in der Tat einwandfrei! Ich habe die Zeitschritte für Bidirectional(LSTM)()und RepeatVector()Ebenen erhöht , und die Vorhersagen sind absolut realisierbar.
Komodovaran_
@kbrose Dies ist kein Hack, es ist, wie Sie es normalerweise tun. Die Verwendung einer batch_size von eins ist zu langsam und Keras aktivieren Maskierungsebenen, damit die Maskierung den Verlust nicht beeinträchtigt.
Ferus