Szkolenie RNN z przykładami różnych długości w Keras

61

Próbuję zacząć uczyć się o RNN i używam Keras. Rozumiem podstawowe założenie waniliowych warstw RNN i LSTM, ale mam problem ze zrozumieniem pewnej technicznej kwestii szkolenia.

W dokumentacji keras napisano, że wejście do warstwy RNN musi mieć kształt (batch_size, timesteps, input_dim). Sugeruje to, że wszystkie przykłady treningu mają ustaloną długość sekwencji, a mianowicie timesteps.

Ale to nie jest szczególnie typowe, prawda? Może chciałbym, aby RNN działał na zdania o różnej długości. Kiedy będę ćwiczyć na jakimś korpusie, nakarmię go partiami zdań o różnej długości.

Przypuszczam, że oczywistą rzeczą byłoby znalezienie maksymalnej długości dowolnej sekwencji w zestawie treningowym i zerowanie jej. Ale czy to oznacza, że ​​nie mogę przewidywać w czasie testu przy większej długości wejściowej?

To pytanie dotyczy konkretnej implementacji Keras, ale sądzę, że pytam również, co ludzie zazwyczaj robią, gdy napotykają tego rodzaju problemy w ogóle.

Taktyka
źródło
@kbrose jest poprawny. Mam jednak jeden problem. W tym przykładzie masz bardzo specjalny generator nieskończonych zbiorów. Co ważniejsze, jest przeznaczony do produkcji partii o wielkości 1000. W praktyce jest to zbyt trudne do spełnienia, jeśli nie niemożliwe. Musisz ponownie uporządkować swoje wpisy, aby te o tej samej długości były ułożone razem, i musisz dokładnie ustawić pozycje podziału partii. Co więcej, nie masz szans na przetasowanie partii. Więc moim zdaniem: nigdy nie używaj danych wejściowych o zmiennej długości w Keras, chyba że dokładnie wiesz, co robisz. Użyj padding i ustaw Maskingwarstwę, aby ignorować
Bs He

Odpowiedzi:

57

Sugeruje to, że wszystkie przykłady treningu mają ustaloną długość sekwencji, a mianowicie timesteps.

Nie jest to do końca poprawne, ponieważ ten wymiar może być None, tzn. O zmiennej długości. W ramach jednej partii musisz mieć taką samą liczbę kroków czasowych (zwykle w tym miejscu widać 0-padding i maskowanie). Ale między partiami nie ma takich ograniczeń. Podczas wnioskowania możesz mieć dowolną długość.

Przykładowy kod, który tworzy losowe partie danych treningowych.

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)

I to właśnie drukuje. Uwaga: kształty wyjściowe (None, None, x)wskazują zmienny rozmiar partii i zmienny rozmiar taktowania.

__________________________________________________________________________________________
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
źródło
Dziękuję Ci za to. Jeśli jednak wstawimy sekwencje 0, wpłynie to na stany ukryte i komórkę pamięci, ponieważ nadal przekazujemy x_t jako 0, gdy w rzeczywistości nic nie powinno zostać przekazane. Normalnie fit()możemy przekazać sequence_lenthparametr, aby określić długość sekwencji, aby go wykluczyć. Wydaje się, że podejście generatora nie pozwala zignorować 0 sekwencji?
GRS
1
@GRS Twój generator może zwrócić 3-krotność (inputs, targets, sample_weights)i możesz ustawić sample_weights0-padów na 0. Jednak nie jestem pewien, czy zadziałałoby to idealnie dla dwukierunkowych numerów RNN.
kbrose
Było to pomocne, ale chciałbym, aby zawierało również przykład użycia model.predict_generatorz zestawem testowym. Kiedy próbuję przewidzieć za pomocą generatora, pojawia się błąd dotyczący konkatenacji (zestaw testowy ma również sekwencje o zmiennej długości). Moim rozwiązaniem było model.predicthackowanie standardu . Być może byłoby to lepiej dostosowane do nowego pytania?
Mickey
@ Mickey, które brzmi jak inne pytanie. To pytanie dotyczy treningu, a nie przewidywania.
kbrose
Jeśli pytanie w komentarzach rzeczywiście zostało zadane jako nowe pytanie, czy możesz do niego link?
Itamar Mushkin
7

@kbrose wydaje się mieć lepsze rozwiązanie

Przypuszczam, że oczywistą rzeczą byłoby znalezienie maksymalnej długości dowolnej sekwencji w zestawie treningowym i zerowanie jej.

Zazwyczaj jest to dobre rozwiązanie. Może wypróbuj maksymalną długość sekwencji + 100. Użyj tego, co najlepiej pasuje do twojej aplikacji.

Ale czy to oznacza, że ​​nie mogę przewidywać w czasie testu przy większej długości wejściowej?

Niekoniecznie. Powodem użycia stałej długości w kamerach jest to, że znacznie poprawia wydajność poprzez tworzenie tensorów o ustalonych kształtach. Ale to tylko na trening. Po treningu nauczysz się odpowiednich ciężarów do swojego zadania.

Załóżmy, że po wielogodzinnych treningach zdajesz sobie sprawę, że maksymalna długość twojego modelu nie była wystarczająco duża / mała i teraz musisz zmienić przedziały czasowe, po prostu wyodrębnij wyuczone ciężary ze starego modelu, zbuduj nowy model z nowymi krokami czasowymi i wstrzykuje do niego wyuczone ciężary.

Prawdopodobnie możesz to zrobić za pomocą:

new_model.set_weights(old_model.get_weights())

Sam tego nie wypróbowałem. Spróbuj i opublikuj swoje wyniki tutaj z korzyścią dla wszystkich. Oto kilka linków: raz dwa

aneesh joshi
źródło
1
Rzeczywiście możesz mieć wejścia o zmiennej długości, bez potrzeby wprowadzania takich hacków max length + 100. Zobacz moją odpowiedź na przykład kod.
kbrose
1
Przeniesienie ciężarów na model z większą liczbą kroków rzeczywiście działa idealnie! Zwiększyłem czasy Bidirectional(LSTM)()i RepeatVector()warstwy, a prognozy są całkowicie wykonalne.
komodovaran_
@kbrose To nie jest włamanie, tak zwykle się to robi. Korzystanie z batch_size jednego jest zbyt wolne, a kamery włączają maskowanie warstw, aby maskowanie nie wpłynęło na utratę.
Ferus