Розуміння Керасу LSTM


311

Я намагаюся узгодити своє розуміння LSTM і наголосив тут, на цій посаді Крістофер Олах, реалізований у Керасі. Я стежу за блогом, написаним Джейсоном Браунлі для підручника «Керас». Що я в основному плутаю, це

  1. Перестановка рядів даних у [samples, time steps, features]та,
  2. Державні LSTM

Давайте зосередимось на двох вищезазначених питаннях з посиланням на код, вставлений нижче:

# 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()

Примітка: create_dataset приймає послідовність довжиною N і повертає N-look_backмасив, з якого кожен елемент є look_backпослідовністю довжини.

Що таке часові кроки та особливості?

Як видно, TrainX - це тривимірний масив, що має два останніх розміри відповідно Time_steps і Feature (3 і 1 у цьому конкретному коді). Що стосується зображення нижче, чи означає це, що ми розглядаємо many to oneвипадок, коли кількість рожевих коробок становить 3? Або це буквально означає, що довжина ланцюга дорівнює 3 (тобто вважаються лише 3 зелені коробки).введіть тут опис зображення

Чи стає аргумент функції важливим, коли ми розглядаємо багатовимірні серії? наприклад, моделювання двох фінансових акцій одночасно?

Державні LSTM

Чи означають стані LSTM, що ми зберігаємо значення пам’яті комірок між тирами пакетів? Якщо це так, batch_sizeце одне, і пам'ять скидається між тренінговими запусками, так що було сенсом сказати, що це було репутацією. Я здогадуюсь, що це пов'язано з тим, що дані про навчання не змішуються, але я не знаю як.

Будь-які думки? Посилання на зображення: http://karpathy.github.io/2015/05/21/rnn-effectiveness/

Редагувати 1:

Трохи розгублений коментар @ van про те, що червоні та зелені поля є рівними. Отже, лише для підтвердження, чи відповідають наступні дзвінки API нерозкритим діаграм? Особливо відзначаючи другу діаграму ( batch_sizeбула обрана довільно.): введіть тут опис зображення введіть тут опис зображення

Редагувати 2:

Люди, які пройшли курс глибокого навчання Udacity і все ще плутають аргумент time_step, дивіться наступну дискусію: https://discussions.udacity.com/t/rnn-lstm-use-implementation/163169

Оновлення:

Виявляється, model.add(TimeDistributed(Dense(vocab_len)))було те, що я шукав. Ось приклад: https://github.com/sachinruk/ShakespeareBot

Оновлення2:

Я узагальнив більшу частину свого розуміння LSTM тут: https://www.youtube.com/watch?v=ywinX5wgdEU


7
Перше фото має бути (batch_size, 5, 1); друге фото має бути (batch_size, 4, 3) (якщо немає наступних послідовностей). І чому вихід все ще "X"? Чи повинно бути "Y"?
Ван

1
Тут я припускаю, що X_1, X_2 ... X_6 - це єдине число. І три числа (X_1, X_2, X_3) утворюють вектор форми (3,). Одне число (X_1) складає вектор форми (1,).
Ван

2
@ Ван, ваше припущення правильне. Це цікаво, тому в основному модель не вивчає шаблони, що перевищують кількість часових кроків. Отже, якщо у мене часовий ряд довжиною 1000, і я можу візуально бачити шаблон кожні 100 днів, я повинен зробити параметр time_steps не менше 100. Це правильне спостереження?
sachinruk

3
Так. І якщо ви можете збирати 3 відповідні функції на день, то ви можете встановити розмір функції до 3, як це було зроблено на другій фотографії. За цієї обставини форма введення буде (batch_size, 100, 3).
Ван

1
і щоб відповісти на ваше перше питання, це було тому, що я робив єдиний часовий ряд. Наприклад, ціни на акції, тому X і Y - з тієї ж серії.
sachinruk

Відповіді:


173

Перш за все, для початку ви вибираєте чудові підручники ( 1 , 2 ).

Що означає крок часу : Time-steps==3у X.shape (опис форми даних) означає, що є три рожеві поля. Оскільки в Керасі кожен крок вимагає введення, тому кількість зелених полів зазвичай повинна дорівнювати кількості червоних коробок. Якщо ви не зламаєте структуру.

від багатьох до багатьох проти багатьох до одного : у керах є return_sequencesпараметр, коли ваша ініціалізація LSTMчи GRUабо SimpleRNN. Коли return_sequencesє False(за замовчуванням), це багато на один, як показано на малюнку. Його повертається форма (batch_size, hidden_unit_length), яка представляє останній стан. Коли return_sequencesє True, то це багато-багато . Його повернена форма така(batch_size, time_step, hidden_unit_length)

Чи стає релевантним аргумент функції: Аргумент функції означає "Наскільки великий ваш червоний ящик" або який вхідний вимір кожного кроку. Якщо ви хочете передбачити, скажімо, 8 видів ринкової інформації, то ви можете генерувати свої дані за допомогою feature==8.

Stateful : Ви можете шукати вихідний код . При ініціалізації стану, якщо stateful==True, тоді стан з останнього навчання буде використовуватися як початковий стан, інакше він буде генерувати новий стан. Я ще не ввімкнув stateful. Однак я не згоден з тим, що batch_sizeможе бути 1, коли stateful==True.

Наразі ви генеруєте свої дані зібраними даними. Зображуйте, що інформація про ваші запаси надходить як потік, а не чекати дня, щоб зібрати всі послідовні, ви хочете генерувати вхідні дані в Інтернеті під час навчання / прогнозування в мережі. Якщо у вас є 400 акцій, які діляться однією і тією ж мережею, ви можете встановити batch_size==400.


Трохи розгублений, чому червоні та зелені поля повинні бути однаковими. Не могли б ви подивитись на зроблені мною зміни (в основному нові фотографії) та прокоментувати?
sachinruk

1
Справді. Перевірте документ: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.
Ван

1
@Van Якщо я маю багатоваріантний часовий ряд, чи варто все-таки використовувати lookback = 1?
заїзд

1
Чому розмірність LSTM вихідного простору (32) відрізняється від кількості нейронів (клітин LSTM)?
Клейкий

1
Додаток до stateful=True: Розмір партії може бути будь-яким, що вам подобається, але ви повинні дотримуватися цього. Якщо ви будуєте свою модель з партією розміром 5, то все fit(), predict()і пов'язана з ними методу зажадає партії 5. Зверніть увагу , однак , що цей стан не буде збережено з model.save(), що може здатися небажаним. Однак ви можете вручну додати стан до файлу hdf5, якщо він вам потрібен. Але це ефективно дозволяє змінити розмір партії шляхом простого збереження та перезавантаження моделі.
jlh

191

Як доповнення до прийнятої відповіді, ця відповідь показує поведінку кераса та способи досягнення кожної картини.

Загальна поведінка Кераса

Стандартна внутрішня обробка керас - це завжди багато-багато, як на наступному малюнку (де я використовувався features=2, тиск і температура, як приклад):

ManyToMany

У цьому зображенні я збільшив кількість кроків до 5, щоб уникнути плутанини з іншими вимірами.

Для цього прикладу:

  • У нас N нафтових резервуарів
  • Ми витратили 5 годин на прийняття заходів щогодини (часові кроки)
  • Ми виміряли дві особливості:
    • Тиск P
    • Температура 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]],
        ]

Входи для розсувних вікон

Часто шари LSTM повинні обробляти цілі послідовності. Розділення вікон може бути не найкращою ідеєю. У шарі є внутрішні стани про те, як розвивається послідовність під час кроку вперед. Windows виключає можливість вивчення довгих послідовностей, обмежуючи всі послідовності розміром вікна.

У вікнах кожне вікно є частиною тривалої оригінальної послідовності, але Керас їх бачить як незалежну послідовність:

        [     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]],
  ....
        ]

Зауважте, що в цьому випадку ви спочатку маєте лише одну послідовність, але ви ділите її на багато послідовностей, щоб створити вікна.

Поняття "що таке послідовність" є абстрактним. Важливі частини:

  • Ви можете мати партії з безліччю окремих послідовностей
  • що робить послідовності послідовностями, це те, що вони розвиваються поетапно (як правило, часові кроки)

Досягнення кожного випадку за допомогою "окремих шарів"

Досягнення стандартів багато-багато:

StandardManyToMany

Ви можете досягти багатьох до багатьох за допомогою простого шару LSTM, використовуючи return_sequences=True:

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

#output_shape -> (batch_size, steps, units)

Досягнення багатьох до одного:

Використовуючи той самий шар, керас виконає таку ж внутрішню попередню обробку, але коли ви будете використовувати return_sequences=False(або просто ігнорувати цей аргумент), керас автоматично відкидає етапи, попередні до останнього:

ManyToOne

outputs = LSTM(units)(inputs)

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

Досягнення одного до багатьох

Тепер це не підтримується лише шарами LSTM keras. Вам потрібно буде створити власну стратегію для множення кроків. Є два хороших підходи:

  • Створіть постійний багатоступінчастий вхід, повторивши тензор
  • Використовуйте a, stateful=Trueщоб періодично брати результат одного кроку і слугувати ним як вхід наступного кроку (потреби output_features == input_features)

Один до багатьох з повторним вектором

Для того, щоб підходити до стандартної поведінки кераса, нам потрібні введення поетапно, тому ми просто повторюємо входи на потрібну нам довжину:

OneToManyRepeat

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

#output_shape -> (batch_size, steps, units)

Розуміння наголошений = Правда

Зараз з'являється одне з можливих застосувань stateful=True (окрім уникнення завантаження даних, які не можуть одразу поміститись у пам'ять комп'ютера)

Stateful дозволяє нам вводити "частини" послідовностей поетапно. Різниця полягає в:

  • У stateful=Falseдругій партії містяться цілі нові послідовності, незалежні від першої партії
  • У stateful=True, друга партія продовжує першу партію, розширюючи ті ж послідовності.

Це як поділ послідовностей у вікнах теж із цими двома основними відмінностями:

  • ці вікна не накладають !!
  • stateful=True побачать ці вікна підключені як одну довгу послідовність

У stateful=Trueкожен нова партія буде інтерпретуватися як продовження попередньої партії (до виклику model.reset_states()).

  • Послідовність 1 у партії 2 продовжить послідовність 1 у партії 1.
  • Послідовність 2 у партії 2 продовжить послідовність 2 у партії 1.
  • Послідовність n у партії 2 продовжить послідовність n у партії 1.

Приклад входів, партія 1 містить етапи 1 і 2, партія 2 містить кроки 3 до 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]],
        ]                                  ]

Помітьте вирівнювання резервуарів у партії 1 та партії 2! Ось чому нам це потрібно shuffle=False(якщо, звичайно, ми не використовуємо лише одну послідовність).

Ви можете мати будь-яку кількість партій, нескінченно. (Для змінної довжини в кожній партії використовуйте input_shape=(None,features).

Один до багатьох із заявляючими = True

У нашому випадку тут ми будемо використовувати лише 1 крок за партію, оскільки ми хочемо отримати один вихідний крок і зробити його вхідним.

Зверніть увагу, що поведінка на малюнку не "викликана" stateful=True. Ми змусимо цю поведінку в ручному циклі внизу. У цьому прикладі stateful=Trueє те, що "дозволяє" нам зупинити послідовність, маніпулювати тим, що ми хочемо, і продовжувати туди, де ми зупинилися.

OneToManyStateful

Чесно кажучи, повторний підхід, мабуть, кращий вибір для цієї справи. Але оскільки ми розглядаємо stateful=True, це хороший приклад. Найкращий спосіб скористатися цією справою - наступний випадок "багато-багато".

Шар:

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) 

Тепер нам знадобиться ручний цикл для прогнозів:

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()

Багато до багатьох із заявою = Істинно

Тепер ми отримуємо дуже приємне додаток: задавши послідовність введення, спробуйте передбачити її майбутні невідомі кроки.

Ми використовуємо той самий метод, що і в "один до багатьох" вище, з тією різницею, що:

  • ми будемо використовувати саму послідовність, щоб бути цільовими даними, на крок попереду
  • ми знаємо частину послідовності (тому ми відкидаємо цю частину результатів).

ManyToManyStateful

Шар (такий же, як і вище):

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) 

Навчання:

Ми будемо навчати нашу модель, щоб передбачити наступний крок послідовностей:

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)

Прогнозування:

Перший етап нашого прогнозування передбачає "коригування штатів". Ось чому ми збираємось передбачити всю послідовність ще раз, навіть якщо ми вже знаємо цю частину:

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

Тепер ми переходимо до циклу, як у випадку з багатьма. Але не скидайте стани тут! . Ми хочемо, щоб модель знала, на якому кроці послідовності вона знаходиться (і вона знає, що це на першому новому кроці через прогноз, який ми тільки що зробили вище)

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()

Цей підхід був використаний у цих відповідях та файлах:

Досягнення складних конфігурацій

У всіх прикладах вище я показав поведінку "одного шару".

Звичайно, ви можете складати багато шарів один на одного, не обов'язково всі дотримуючись одного шаблону, і створювати власні моделі.

Один цікавий приклад, який з'являється, - це "автокодер", який має кодер "багато на один", за яким слідує декодер "один на багато":

Кодер:

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)

Декодер:

Використання методу «повторити»;

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)

Автокодер:

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

autoencoder = Model(inputs,outputs)

Поїзд с fit(X,X)

Додаткові пояснення

Якщо ви хочете отримати детальну інформацію про те, як обчислюються кроки в LSTM, або детальніше про stateful=Trueвипадки, описані вище, ви можете прочитати більше у цій відповіді: Сумніви щодо "Розуміння Керса LSTMs"


1
Дуже цікаве використання стану із використанням виходів у якості входів. Як додаткова примітка, ще одним способом зробити це було б використовувати функціональний API Keras (як ви це робили тут, хоча я вважаю, що ви могли використовувати послідовний) і просто повторно використовувати ту саму клітинку LSTM для кожного кроку часу , передаючи одночасно отриманий стан і вихід з комірки до себе. Тобто my_cell = LSTM(num_output_features_per_timestep, return_state=True), слідом за цим цикломa, _, c = my_cell(output_of_previous_time_step, initial_states=[a, c])
Jacob R

1
Клітини і довжина - цілком незалежні значення. Жодна з картинок не представляє кількість "комірок". Вони всі на "довжину".
Даніель Мьоллер

1
@ DanielMöller Я знаю, що трохи пізно, але ваша відповідь дійсно привертає мою увагу. Одне з ваших пунктів розбило все про моє розуміння того, що таке партія для LSTM. Ви наводите приклад з N цистерн, п’яти ступенів та двох функцій. Я вважав, що якщо партія буде, наприклад, двома, це означає, що два зразки (цистерни з 5 кроками 2 функцій) будуть подані в мережу, а після цього будуть адаптовані ваги. Але якщо я правильно розумію, ви заявляєте, що партія 2 означає, що часові кроки зразків будуть розділені на 2, а перша половина всіх зразків буде подаватися на оновлення ваги LSTM-> і ніж на секунду.
вікеріел

1
Так. У стані = True, пакетна 1 = група зразків, оновлення. Потім пакетне 2 = більше кроків для тієї ж групи зразків, оновлення.
Даніель Мьоллер

2
Я б хотів, щоб я міг підкреслити це 100 разів. Супер корисна відповідь.
adamconkey

4

Якщо у вашому останньому шарі RNN є return_sequences, ви не можете використовувати простий шар щільного, а не використовувати TimeDistributed.

Ось приклад фрагмента коду, який може допомогти іншим.

слова = keras.layers.Input (batch_shape = (Немає, self.maxSequenceLength), name = "введення")

    # 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))
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.