Понимание LSTM Keras

311

Я пытаюсь согласовать свое понимание LSTM и указал здесь в этом посте Кристофером Олахом, реализованным в Керасе. Я слежу за блогом, написанным Джейсоном Браунли для учебника Keras. То, что я в основном смущен о том,

  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 зеленых поля).введите описание изображения здесь

Становится ли аргумент функций актуальным, когда мы рассматриваем многомерный ряд? например, моделирование двух финансовых акций одновременно?

Statest 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

Update2:

Я кратко изложил большую часть моего понимания LSTM здесь: https://www.youtube.com/watch?v=ywinX5wgdEU

sachinruk
источник
7
Первое фото должно быть (batch_size, 5, 1); второе фото должно быть (batch_size, 4, 3) (если нет следующих последовательностей). И почему на выходе все еще "Х"? Должно ли это быть "Y"?
Ван
1
Здесь я предполагаю, что X_1, X_2 ... X_6 - это одно число. И три числа (X_1, X_2, X_3) составляют вектор формы (3,). Одно число (X_1) образует вектор формы (1,).
Ван
2
@ Ван, твои предположения верны. Это интересно, так что в основном модель не изучает шаблоны за пределами количества time_steps. Поэтому, если у меня есть временной ряд длиной 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 (описание формы данных) есть три розовых прямоугольника. Так как в Keras каждый шаг требует ввода, поэтому количество зеленых прямоугольников должно обычно равняться количеству красных прямоугольников. Если только вы не взломаете структуру.

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

Соответствует ли аргумент feature: Аргумент Feature означает «насколько велик ваш красный прямоугольник» или каково входное измерение на каждом шаге Если вы хотите прогнозировать, скажем, 8 видов рыночной информации, то вы можете сгенерировать свои данные с помощью feature==8.

С состоянием : вы можете посмотреть исходный код . При инициализации состояния, если 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?
17
1
Почему размерность LSTM выходного пространства (32) отличается от числа нейронов (клеток LSTM)?
Липкий
1
Дополнение к stateful=True: Размер партии может быть любым, но вы должны придерживаться его. Если вы строите свою модель с партией размером 5, то все fit(), predict()и связанная с ними метода потребует партии 5. Обратите внимание , однако , что это состояние не будет сохранено с model.save(), что может показаться нежелательным. Однако вы можете вручную добавить состояние в файл hdf5, если вам это нужно. Но по сути это позволяет изменить размер партии, просто сохранив и перезагрузив модель.
июля
192

В качестве дополнения к принятому ответу, этот ответ показывает поведение keras и способы достижения каждой картины.

Поведение генерала Кераса

Стандартная внутренняя обработка keras всегда много-много, как показано на следующем рисунке (где я использовал features=2давление и температуру, просто в качестве примера):

ManyToMany

На этом изображении я увеличил количество шагов до 5, чтобы избежать путаницы с другими измерениями.

Для этого примера:

  • У нас есть N нефтяных резервуаров
  • Мы потратили 5 часов, принимая меры каждый час (временные шаги)
  • Мы измерили две особенности:
    • Давление Р
    • Температура Т

Наш входной массив должен быть в форме (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 исключает возможность изучения длинных последовательностей, ограничивая все последовательности размером окна.

В окнах каждое окно является частью длинной оригинальной последовательности, но для Keras они будут рассматриваться как независимая последовательность:

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

Достижение многих к одному:

Используя точно такой же слой, keras будет выполнять ту же самую внутреннюю предварительную обработку, но когда вы используете return_sequences=False(или просто игнорируете этот аргумент), keras автоматически отбрасывает шаги, предшествующие последнему:

ManyToOne

outputs = LSTM(units)(inputs)

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

Достижение один ко многим

Теперь это поддерживается не только слоями keras LSTM. Вам нужно будет создать свою собственную стратегию, чтобы умножить шаги. Есть два хороших подхода:

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

Один ко многим с повторным вектором

Чтобы соответствовать стандартному поведению keras, нам нужны входные данные поэтапно, поэтому мы просто повторяем входные данные для нужной длины:

OneToManyRepeat

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

#output_shape -> (batch_size, steps, units)

Понимание состояния = True

Теперь прибывает одно из возможных применений 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()

Многие ко многим с Stateful = True

Теперь, здесь, мы получаем очень хорошее приложение: учитывая входную последовательность, попытайтесь предсказать ее будущие неизвестные шаги.

Мы используем тот же метод, что и в примере «один ко многим», с той разницей, что:

  • мы будем использовать саму последовательность, чтобы быть целевыми данными, на шаг впереди
  • мы знаем часть последовательности (поэтому мы отбрасываем эту часть результатов).

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вышеупомянутых случаях, вы можете прочитать больше в этом ответе: Сомнения относительно `Понимания LSTM Keras`

Даниэль Мёллер
источник
1
Очень интересное использование Stateful с использованием выходов в качестве входных данных. В качестве дополнительного замечания, другой способ сделать это - использовать функциональный 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])
Джейкоб Р
1
Клетки и длина являются полностью независимыми значениями. Ни одна из картинок не представляет количество «ячеек». Они все для "длины".
Даниэль Мёллер
1
@ DanielMöller Я знаю, немного поздно, но твой ответ действительно привлек мое внимание. Одна ваша точка зрения разбила все о моем понимании того, что такое партия для LSTM. Вы приводите пример с N танками, пятью ступенями и двумя функциями. Я полагал, что, если партия, например, равна двум, это означает, что два образца (резервуары с 5-ю ступенями 2) будут поступать в сеть и после этого будут адаптироваться веса. Но если я правильно понимаю, вы утверждаете, что партия 2 означает, что временные шаги образцов будут разделены на 2, и первая половина всех образцов будет передана для обновления LSTM-> weight, а затем second.
наместник
1
Да. В состоянии = True, пакет 1 = группа образцов, обновление. Затем пакет 2 = больше шагов для той же группы образцов, обновите.
Даниэль Мёллер
2
Хотел бы я поднять это 100 раз. Супер полезный ответ.
adamconkey
4

Если у вас есть return_sequence в вашем последнем слое RNN, вы не можете использовать простой плотный слой, вместо этого используйте TimeDistributed.

Вот пример кода, который может помочь другим.

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))
Санджай Кришна
источник