Zrozumienie LSTM Keras

311

Próbuję pogodzić moje rozumienie LSTM i wskazałem tutaj w tym poście Christophera Olaha zaimplementowanego w Keras. Śledzę ten blog napisany przez Jason Brownlee za tutorial Keras. Głównie jestem zdezorientowany:

  1. Przekształcenie serii danych w [samples, time steps, features]i,
  2. Stanowe LSTM

Skoncentrujmy się na dwóch powyższych pytaniach w odniesieniu do kodu wklejonego poniżej:

# reshape into X=t and Y=t+1
look_back = 3
trainX, trainY = create_dataset(train, look_back)
testX, testY = create_dataset(test, look_back)

# reshape input to be [samples, time steps, features]
trainX = numpy.reshape(trainX, (trainX.shape[0], look_back, 1))
testX = numpy.reshape(testX, (testX.shape[0], look_back, 1))
########################
# The IMPORTANT BIT
##########################
# create and fit the LSTM network
batch_size = 1
model = Sequential()
model.add(LSTM(4, batch_input_shape=(batch_size, look_back, 1), stateful=True))
model.add(Dense(1))
model.compile(loss='mean_squared_error', optimizer='adam')
for i in range(100):
    model.fit(trainX, trainY, nb_epoch=1, batch_size=batch_size, verbose=2, shuffle=False)
    model.reset_states()

Uwaga: create_dataset przyjmuje sekwencję długości N i zwraca N-look_backtablicę, której każdy element jest look_backsekwencją długości.

Co to są kroki czasowe i funkcje?

Jak można zobaczyć TrainX jest trójwymiarową tablicą, w której Time_steps i Feature są odpowiednio dwoma ostatnimi wymiarami (3 i 1 w tym konkretnym kodzie). Czy w odniesieniu do poniższego obrazu oznacza to, że rozważamy many to oneprzypadek, w którym liczba różowych pudełek wynosi 3? Czy oznacza to dosłownie, że długość łańcucha wynosi 3 (tzn. Uwzględniono tylko 3 zielone pola).wprowadź opis zdjęcia tutaj

Czy argument funkcji staje się istotny, gdy weźmiemy pod uwagę serie wielowymiarowe? np. modelowanie dwóch zasobów finansowych jednocześnie?

Stanowe LSTM

Czy stanowe LSTM oznaczają, że zapisujemy wartości pamięci komórkowej między seriami partii? Jeśli tak jest, batch_sizeto jest jeden, a pamięć jest resetowana między przebiegami treningowymi, więc po co było mówić, że była stanowa. Zgaduję, że jest to związane z faktem, że dane treningowe nie są tasowane, ale nie jestem pewien, jak to zrobić.

jakieś pomysły? Referencje obrazu: http://karpathy.github.io/2015/05/21/rnn-effectiveness/

Edycja 1:

Trochę zdezorientowany komentarzem van na temat równości czerwonych i zielonych pól. Tak więc, aby potwierdzić, czy poniższe wywołania API odpowiadają rozwiniętym diagramom? Szczególnie zwracając uwagę na drugi schemat ( batch_sizezostał wybrany arbitralnie): wprowadź opis zdjęcia tutaj wprowadź opis zdjęcia tutaj

Edycja 2:

Dla osób, które ukończyły kurs głębokiego uczenia się Udacity i nadal są zdezorientowane argumentem time_step, zapoznaj się z następującą dyskusją: https://discussions.udacity.com/t/rnn-lstm-use-implementation/163169

Aktualizacja:

Okazuje się, model.add(TimeDistributed(Dense(vocab_len)))że tego szukałem. Oto przykład: https://github.com/sachinruk/ShakespeareBot

Aktualizacja 2:

Podsumowałem większość mojego zrozumienia LSTM tutaj: https://www.youtube.com/watch?v=ywinX5wgdEU

sachinruk
źródło
7
Pierwszym zdjęciem powinno być (batch_size, 5, 1); drugie zdjęcie powinno być (batch_size, 4, 3) (jeśli nie ma następujących sekwencji). I dlaczego wynik nadal jest „X”? Czy powinno to być „Y”?
Van
1
Tutaj zakładam, że X_1, X_2 ... X_6 jest pojedynczą liczbą. A trzy cyfry (X_1, X_2, X_3) tworzą wektor kształtu (3,). Jedna liczba (X_1) tworzy wektor kształtu (1,).
Van
2
@ Van, twoje założenie jest prawidłowe. To ciekawe, więc w zasadzie model nie uczy się wzorców poza liczbą kroków czasowych. Więc jeśli mam szereg czasowy o długości 1000 i mogę wizualnie zobaczyć wzór co 100 dni, powinienem ustawić parametr time_steps co najmniej na 100. Czy to poprawna obserwacja?
sachinruk,
3
Tak. A jeśli możesz zebrać 3 odpowiednie funkcje dziennie, możesz ustawić rozmiar funkcji na 3, tak jak na drugim zdjęciu. W tych okolicznościach kształtem wejściowym będzie (batch_size, 100, 3).
Van
1
i żeby odpowiedzieć na twoje pierwsze pytanie, to dlatego, że brałem jedną serię czasową. Na przykład ceny akcji, więc X i Y pochodzą z tej samej serii.
sachinruk,

Odpowiedzi:

173

Na początek wybierasz świetne samouczki ( 1 , 2 ), aby rozpocząć.

Co oznacza krok czasu : Time-steps==3w X. kształt (Opis kształtu danych) oznacza, że ​​są trzy różowe pola. Ponieważ w Keras każdy krok wymaga danych wejściowych, dlatego liczba zielonych pól powinna zwykle być równa liczbie czerwonych pól. Chyba że zhakujesz strukturę.

wiele do wielu kontra wiele do jednego : w keras istnieje return_sequencesparametr podczas inicjowania LSTMlub GRUlub SimpleRNN. Kiedy return_sequencesjest False(domyślnie), to jest wiele do jednego, jak pokazano na rysunku. Zwracany jest kształt (batch_size, hidden_unit_length), który reprezentuje ostatni stan. Kiedy return_sequencesjest True, to jest wiele do wielu . Jego kształt zwrotny to(batch_size, time_step, hidden_unit_length)

Czy argument funkcji staje się odpowiedni : Argument funkcji oznacza „Jak duże jest twoje czerwone pole” lub jaki jest wymiar wejściowy na każdym kroku. Jeśli chcesz przewidzieć, powiedzmy, 8 rodzajów informacji rynkowych, możesz wygenerować swoje dane feature==8.

Stanowy : możesz wyszukać kod źródłowy . Podczas inicjowania stanu, jeśli stateful==True, to stan z ostatniego szkolenia zostanie użyty jako stan początkowy, w przeciwnym razie wygeneruje nowy stan. Nie włączyłem się statefuljeszcze. Jednak nie zgadzam się z tym, że batch_sizemoże być tylko 1, gdy stateful==True.

Obecnie generujesz dane na podstawie zgromadzonych danych. Wyobraź sobie, że informacje o akcjach nadchodzą w formie strumienia, zamiast czekać na jeden dzień, aby zebrać wszystkie sekwencyjne, chciałbyś generować dane wejściowe online podczas szkolenia / prognozowania w sieci. Jeśli masz 400 akcji posiadających tę samą sieć, możesz to ustawić batch_size==400.

Awangarda
źródło
Nieco zdezorientowany, dlaczego czerwone i zielone pola muszą być takie same. Czy możesz spojrzeć na dokonaną przeze mnie edycję (głównie nowe zdjęcia) i skomentować?
sachinruk,
1
W rzeczy samej. Sprawdź dokument:stateful: Boolean (default False). If True, the last state for each sample at index i in a batch will be used as initial state for the sample of index i in the following batch.
Van
1
@ Van Jeśli mam wielowymiarowy szereg czasowy, czy powinienem go nadal używać lookback = 1?
innm
1
Dlaczego wymiarowość LSTM przestrzeni wyjściowej (32) różni się od liczby neuronów (komórek LSTM)?
Przyklejony
1
Dodatek do stateful=True: Wielkość partii może być dowolna, ale musisz się jej trzymać. Jeżeli budujesz swój model o wielkości wsadu 5, następnie wszystko fit(), predict()i metody pokrewne będzie wymagało partii 5. Należy jednak pamiętać, że ten stan nie zostaną zachowane model.save(), co może wydawać się pożądane. Możesz jednak ręcznie dodać stan do pliku hdf5, jeśli go potrzebujesz. Ale skutecznie pozwala to zmienić rozmiar partii po prostu zapisując i ponownie ładując model.
jlh
191

Jako uzupełnienie przyjętej odpowiedzi, ta odpowiedź pokazuje zachowania keras i sposób uzyskania każdego zdjęcia.

Ogólne zachowanie Keras

Standardowe przetwarzanie wewnętrzne keras jest zawsze wiele do wielu, jak na poniższym obrazku (gdzie użyłem features=2, ciśnienie i temperatura, tylko jako przykład):

ManyToMany

Na tym obrazie zwiększyłem liczbę kroków do 5, aby uniknąć pomyłek z innymi wymiarami.

W tym przykładzie:

  • Mamy N zbiorników oleju
  • Spędziliśmy 5 godzin, podejmując działania co godzinę (kroki czasowe)
  • Zmierzyliśmy dwie cechy:
    • Ciśnienie P
    • Temperatura T.

Nasza tablica wejściowa powinna zatem mieć kształt (N,5,2):

        [     Step1      Step2      Step3      Step4      Step5
Tank A:    [[Pa1,Ta1], [Pa2,Ta2], [Pa3,Ta3], [Pa4,Ta4], [Pa5,Ta5]],
Tank B:    [[Pb1,Tb1], [Pb2,Tb2], [Pb3,Tb3], [Pb4,Tb4], [Pb5,Tb5]],
  ....
Tank N:    [[Pn1,Tn1], [Pn2,Tn2], [Pn3,Tn3], [Pn4,Tn4], [Pn5,Tn5]],
        ]

Wejścia do przesuwnych okien

Często warstwy LSTM mają przetwarzać całe sekwencje. Dzielenie okien może nie być najlepszym pomysłem. Warstwa ma wewnętrzne stany dotyczące ewolucji sekwencji w miarę postępów. Windows eliminuje możliwość uczenia się długich sekwencji, ograniczając wszystkie sekwencje do rozmiaru okna.

W oknach każde okno jest częścią długiej oryginalnej sekwencji, ale według Keras będą one postrzegane jako niezależna sekwencja:

        [     Step1    Step2    Step3    Step4    Step5
Window  A:  [[P1,T1], [P2,T2], [P3,T3], [P4,T4], [P5,T5]],
Window  B:  [[P2,T2], [P3,T3], [P4,T4], [P5,T5], [P6,T6]],
Window  C:  [[P3,T3], [P4,T4], [P5,T5], [P6,T6], [P7,T7]],
  ....
        ]

Zauważ, że w tym przypadku początkowo masz tylko jedną sekwencję, ale dzielisz ją na wiele sekwencji, aby utworzyć okna.

Pojęcie „czym jest sekwencja” jest abstrakcyjne. Ważne części to:

  • możesz mieć partie z wieloma pojedynczymi sekwencjami
  • to, co sprawia, że ​​sekwencje są sekwencjami, polega na tym, że ewoluują one etapami (zwykle etapami czasowymi)

Osiągnięcie każdego przypadku dzięki „pojedynczym warstwom”

Osiągnięcie standardu wielu do wielu:

StandardManyToMany

Możesz osiągnąć wiele do wielu za pomocą prostej warstwy LSTM, używając return_sequences=True:

outputs = LSTM(units, return_sequences=True)(inputs)

#output_shape -> (batch_size, steps, units)

Osiągnięcie wielu w jednym:

Używając dokładnie tej samej warstwy, keras wykona dokładnie takie samo wewnętrzne przetwarzanie wstępne, ale kiedy użyjesz return_sequences=False(lub po prostu zignorujesz ten argument), keras automatycznie odrzuci kroki poprzedzające ostatnie:

ManyToOne

outputs = LSTM(units)(inputs)

#output_shape -> (batch_size, units) --> steps were discarded, only the last was returned

Osiągnięcie jednego do wielu

Teraz nie jest to obsługiwane tylko przez same warstwy LSTM keras. Będziesz musiał stworzyć własną strategię, aby pomnożyć kroki. Istnieją dwa dobre podejścia:

  • Stwórz stały, wieloetapowy wkład, powtarzając tensor
  • Użyj a, stateful=Trueaby cyklicznie pobierać dane wyjściowe z jednego kroku i służyć jako dane wejściowe do następnego kroku (potrzeby output_features == input_features)

Jeden do wielu z powtarzalnym wektorem

Aby dopasować się do standardowego zachowania keras, potrzebujemy danych wejściowych w krokach, więc po prostu powtarzamy dane wejściowe dla pożądanej długości:

OneToManyRepeat

outputs = RepeatVector(steps)(inputs) #where inputs is (batch,features)
outputs = LSTM(units,return_sequences=True)(outputs)

#output_shape -> (batch_size, steps, units)

Zrozumienie stanowe = Prawda

Teraz pojawia się jedno z możliwych zastosowań stateful=True(oprócz unikania ładowania danych, które nie mogą jednocześnie zmieścić się w pamięci komputera)

Stateful pozwala nam wprowadzać „części” sekwencji etapami. Różnica polega na:

  • W stateful=False, druga partia zawiera zupełnie nowe sekwencje, niezależne od pierwszej partii
  • W stateful=Truedrugiej partii kontynuuje pierwszą partię, przedłużając te same sekwencje.

To tak, jakby podzielić sekwencje również w oknach, z tymi dwiema głównymi różnicami:

  • te okna nie nakładają się !!
  • stateful=True zobaczą te okna połączone jako jedną długą sekwencję

W stateful=True, każda nowa partia będzie interpretowana jako kontynuacja poprzedniej partii (aż do wywołania model.reset_states()).

  • Sekwencja 1 w partii 2 będzie kontynuować sekwencję 1 w partii 1.
  • Sekwencja 2 w partii 2 będzie kontynuować sekwencję 2 w partii 1.
  • Sekwencja nw partii 2 będzie kontynuować sekwencję nw partii 1.

Przykład danych wejściowych, partia 1 zawiera kroki 1 i 2, partia 2 zawiera kroki od 3 do 5:

                   BATCH 1                           BATCH 2
        [     Step1      Step2        |    [    Step3      Step4      Step5
Tank A:    [[Pa1,Ta1], [Pa2,Ta2],     |       [Pa3,Ta3], [Pa4,Ta4], [Pa5,Ta5]],
Tank B:    [[Pb1,Tb1], [Pb2,Tb2],     |       [Pb3,Tb3], [Pb4,Tb4], [Pb5,Tb5]],
  ....                                |
Tank N:    [[Pn1,Tn1], [Pn2,Tn2],     |       [Pn3,Tn3], [Pn4,Tn4], [Pn5,Tn5]],
        ]                                  ]

Zwróć uwagę na wyrównanie zbiorników w partii 1 i 2! Właśnie dlatego potrzebujemy shuffle=False(chyba że używamy tylko jednej sekwencji).

Możesz mieć dowolną liczbę partii w nieskończoność. (Aby mieć zmienne długości w każdej partii, użyj input_shape=(None,features).

Jeden do wielu z stateful = True

W naszym przypadku zastosujemy tylko 1 krok na partię, ponieważ chcemy uzyskać jeden krok wyjściowy i ustawić go jako dane wejściowe.

Zwróć uwagę, że zachowanie na zdjęciu nie jest „spowodowane przez” stateful=True. Zmusimy to zachowanie do ręcznej pętli poniżej. W tym przykładzie stateful=True„pozwala” nam zatrzymać sekwencję, manipulować tym, co chcemy i kontynuować od miejsca, w którym się zatrzymaliśmy.

OneToManyStateful

Szczerze mówiąc, powtórzenie podejścia jest prawdopodobnie lepszym wyborem w tym przypadku. Ale skoro się przyglądamy stateful=True, to dobry przykład. Najlepszym sposobem na użycie tego jest następna sprawa „wielu do wielu”.

Warstwa:

outputs = LSTM(units=features, 
               stateful=True, 
               return_sequences=True, #just to keep a nice output shape even with length 1
               input_shape=(None,features))(inputs) 
    #units = features because we want to use the outputs as inputs
    #None because we want variable length

#output_shape -> (batch_size, steps, units) 

Teraz potrzebujemy ręcznej pętli do prognoz:

input_data = someDataWithShape((batch, 1, features))

#important, we're starting new sequences, not continuing old ones:
model.reset_states()

output_sequence = []
last_step = input_data
for i in steps_to_predict:

    new_step = model.predict(last_step)
    output_sequence.append(new_step)
    last_step = new_step

 #end of the sequences
 model.reset_states()

Wiele do wielu z stateful = True

Teraz mamy bardzo ładną aplikację: biorąc pod uwagę sekwencję wejściową, spróbuj przewidzieć jej przyszłe nieznane kroki.

Używamy tej samej metody, co w powyższej sekcji „jeden do wielu”, z tą różnicą, że:

  • wykorzystamy samą sekwencję jako dane docelowe, o krok do przodu
  • znamy część sekwencji (więc odrzucamy tę część wyników).

ManyToManyStateful

Warstwa (taka sama jak powyżej):

outputs = LSTM(units=features, 
               stateful=True, 
               return_sequences=True, 
               input_shape=(None,features))(inputs) 
    #units = features because we want to use the outputs as inputs
    #None because we want variable length

#output_shape -> (batch_size, steps, units) 

Trening:

Będziemy trenować nasz model, aby przewidzieć następny krok sekwencji:

totalSequences = someSequencesShaped((batch, steps, features))
    #batch size is usually 1 in these cases (often you have only one Tank in the example)

X = totalSequences[:,:-1] #the entire known sequence, except the last step
Y = totalSequences[:,1:] #one step ahead of X

#loop for resetting states at the start/end of the sequences:
for epoch in range(epochs):
    model.reset_states()
    model.train_on_batch(X,Y)

Prognozowanie:

Pierwszy etap naszego przewidywania polega na „unicestwieniu stanów”. Dlatego ponownie przewidzieliśmy całą sekwencję, nawet jeśli znamy już tę jej część:

model.reset_states() #starting a new sequence
predicted = model.predict(totalSequences)
firstNewStep = predicted[:,-1:] #the last step of the predictions is the first future step

Teraz przechodzimy do pętli, jak w przypadku jednego do wielu. Ale nie resetuj stanów tutaj! . Chcemy, aby model wiedział, w którym etapie sekwencji jest (i wie, że jest to na pierwszym nowym etapie z powodu prognozy, którą właśnie stworzyliśmy powyżej)

output_sequence = [firstNewStep]
last_step = firstNewStep
for i in steps_to_predict:

    new_step = model.predict(last_step)
    output_sequence.append(new_step)
    last_step = new_step

 #end of the sequences
 model.reset_states()

Takie podejście zastosowano w tych odpowiedziach i pliku:

Osiąganie złożonych konfiguracji

We wszystkich powyższych przykładach pokazałem zachowanie „jednej warstwy”.

Możesz oczywiście układać wiele warstw jeden na drugim, niekoniecznie wszystkie według tego samego wzoru i tworzyć własne modele.

Jednym z interesujących przykładów, które się pojawiły, jest „autoencoder” z koderem „jeden do wielu”, a następnie dekoder „jeden do wielu”:

Enkoder:

inputs = Input((steps,features))

#a few many to many layers:
outputs = LSTM(hidden1,return_sequences=True)(inputs)
outputs = LSTM(hidden2,return_sequences=True)(outputs)    

#many to one layer:
outputs = LSTM(hidden3)(outputs)

encoder = Model(inputs,outputs)

Dekoder:

Korzystanie z metody „powtarzania”;

inputs = Input((hidden3,))

#repeat to make one to many:
outputs = RepeatVector(steps)(inputs)

#a few many to many layers:
outputs = LSTM(hidden4,return_sequences=True)(outputs)

#last layer
outputs = LSTM(features,return_sequences=True)(outputs)

decoder = Model(inputs,outputs)

Autoencoder:

inputs = Input((steps,features))
outputs = encoder(inputs)
outputs = decoder(outputs)

autoencoder = Model(inputs,outputs)

Trenuj z fit(X,X)

Dodatkowe wyjaśnienia

Jeśli chcesz uzyskać szczegółowe informacje na temat obliczania kroków w LSTM lub szczegóły dotyczące stateful=Truepowyższych przypadków, możesz przeczytać więcej w tej odpowiedzi: Wątpliwości dotyczące `Zrozumienia LSTM Keras`

Daniel Möller
źródło
1
Bardzo interesujące użycie stanu z wykorzystaniem wyjść jako danych wejściowych. Jako dodatkową uwagę, innym sposobem na to byłoby użycie funkcjonalnego API Keras (tak jak tutaj zrobiłeś, chociaż uważam, że mógłbyś użyć tego sekwencyjnego) i po prostu ponownie użyć tej samej komórki LSTM dla każdego kroku , przekazując jednocześnie wynikowy stan i wynik z komórki do siebie. To znaczy my_cell = LSTM(num_output_features_per_timestep, return_state=True), po którym następuje pętlaa, _, c = my_cell(output_of_previous_time_step, initial_states=[a, c])
Jacob R
1
Komórki i długość są całkowicie niezależnymi wartościami. Żadne ze zdjęć nie reprezentuje liczby „komórek”. Wszystkie dotyczą „długości”.
Daniel Möller,
1
@ DanielMöller Wiem, że trochę się spóźniłem, ale twoja odpowiedź naprawdę przykuła moją uwagę. Jedna twoja uwaga zniszczyła wszystko w moim zrozumieniu, czym jest partia dla LSTM. Podajesz przykład z N czołgami, pięcioma krokami i dwiema funkcjami. Uważałem, że jeśli partia to na przykład dwie, oznacza to, że dwie próbki (zbiorniki o 5 krokach 2 cechy) zostaną wprowadzone do sieci, a następnie zostaną dostosowane wagi. Ale jeśli dobrze rozumiem, stwierdzasz, że partia 2 oznacza, że ​​czasy próbek zostaną podzielone na 2, a pierwsza połowa wszystkich próbek zostanie przekazana do aktualizacji LSTM-> wagi, a następnie druga.
namiestnik
1
Tak. W przypadku stateful = True, partia 1 = grupa próbek, aktualizacja. Następnie partia 2 = więcej kroków dla tej samej grupy próbek, aktualizacja.
Daniel Möller,
2
Chciałbym móc to głosować 100 razy. Super przydatna odpowiedź.
adamconkey
4

Gdy masz sekwencje zwrotne w ostatniej warstwie RNN, nie możesz użyć prostej warstwy zwartej, zamiast tego użyj funkcji TimeDistribution.

Oto przykładowy kod, który może pomóc innym.

words = keras.layers.Input (batch_shape = (None, self.maxSequenceLength), name = "input")

    # Build a matrix of size vocabularySize x EmbeddingDimension 
    # where each row corresponds to a "word embedding" vector.
    # This layer will convert replace each word-id with a word-vector of size Embedding Dimension.
    embeddings = keras.layers.embeddings.Embedding(self.vocabularySize, self.EmbeddingDimension,
        name = "embeddings")(words)
    # Pass the word-vectors to the LSTM layer.
    # We are setting the hidden-state size to 512.
    # The output will be batchSize x maxSequenceLength x hiddenStateSize
    hiddenStates = keras.layers.GRU(512, return_sequences = True, 
                                        input_shape=(self.maxSequenceLength,
                                        self.EmbeddingDimension),
                                        name = "rnn")(embeddings)
    hiddenStates2 = keras.layers.GRU(128, return_sequences = True, 
                                        input_shape=(self.maxSequenceLength, self.EmbeddingDimension),
                                        name = "rnn2")(hiddenStates)

    denseOutput = TimeDistributed(keras.layers.Dense(self.vocabularySize), 
        name = "linear")(hiddenStates2)
    predictions = TimeDistributed(keras.layers.Activation("softmax"), 
        name = "softmax")(denseOutput)  

    # Build the computational graph by specifying the input, and output of the network.
    model = keras.models.Model(input = words, output = predictions)
    # model.compile(loss='kullback_leibler_divergence', \
    model.compile(loss='sparse_categorical_crossentropy', \
        optimizer = keras.optimizers.Adam(lr=0.009, \
            beta_1=0.9,\
            beta_2=0.999, \
            epsilon=None, \
            decay=0.01, \
            amsgrad=False))
Sanjay Krishna
źródło