Понимание LSTM Keras
Я пытаюсь согласовать свое понимание LSTM и указал здесь в этом посте Кристофером Олахом, реализованным в Керасе. Я слежу за блогом, написанным Джейсоном Браунли для учебника Keras. То, что я в основном смущен о том,
- Преобразование ряда данных в
[samples, time steps, features]
а также, - Состояния LSTMs
Давайте сосредоточимся на двух вышеупомянутых вопросах со ссылкой на код, вставленный ниже:
# 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
4 ответа
Прежде всего, вы выбираете отличные учебники ( 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)
Актуален ли аргумент feature: Аргумент Feature означает "насколько велик ваш красный прямоугольник" или каково входное измерение на каждом шаге. Если вы хотите прогнозировать, скажем, 8 видов рыночной информации, то вы можете генерировать свои данные с feature==8
,
С состоянием: вы можете посмотреть исходный код. При инициализации состояния, если stateful==True
, то состояние из последней тренировки будет использоваться в качестве начального состояния, в противном случае оно будет генерировать новое состояние. Я не включаю stateful
еще. Тем не менее, я не согласен с этим batch_size
может быть только 1, когда stateful==True
,
В настоящее время вы генерируете свои данные с собранными данными. Представьте, что ваша фондовая информация поступает в виде потока, вместо того, чтобы ждать целый день, чтобы собрать все последовательные данные, вы хотели бы генерировать входные данные в режиме онлайн во время обучения / прогнозирования по сети. Если у вас есть 400 акций в одной сети, вы можете установить batch_size==400
,
В качестве дополнения к принятому ответу, этот ответ показывает поведение keras и способы достижения каждой картины.
Поведение генерала Кераса
Стандартная внутренняя обработка keras всегда много-много, как на следующем рисунке (где я использовал features=2
, давление и температура, просто в качестве примера):
На этом изображении я увеличил количество шагов до 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]],
....
]
Обратите внимание, что в этом случае у вас изначально есть только одна последовательность, но вы делите ее на несколько последовательностей для создания окон.
Понятие "что такое последовательность" абстрактно. Важными частями являются:
- Вы можете иметь партии со многими отдельными последовательностями
- что делает последовательности последовательностями, так это то, что они развиваются поэтапно (обычно временные шаги)
Достижение каждого случая с "отдельными слоями"
Достижение стандарта много ко многим:
Вы можете достигнуть многих ко многим с помощью простого слоя LSTM, используя return_sequences=True
:
outputs = LSTM(units, return_sequences=True)(inputs)
#output_shape -> (batch_size, steps, units)
Достижение многих к одному:
Используя точно такой же слой, keras будет выполнять ту же самую внутреннюю предварительную обработку, но когда вы используете return_sequences=False
(или просто игнорируйте этот аргумент), keras автоматически откажется от шагов, предшествующих последнему:
outputs = LSTM(units)(inputs)
#output_shape -> (batch_size, units) --> steps were discarded, only the last was returned
Достижение один ко многим
Теперь это не поддерживается только слоями keras LSTM. Вам нужно будет создать свою собственную стратегию, чтобы умножить шаги. Есть два хороших подхода:
- Создайте постоянный многошаговый ввод, повторяя тензор
- Использовать
stateful=True
периодически принимать выходные данные одного шага и использовать их в качестве входных данных следующего шага (необходимоoutput_features == input_features
)
Один ко многим с повторным вектором
Чтобы соответствовать стандартному поведению keras, нам нужно вводить шаги поэтапно, поэтому мы просто повторяем вводы для желаемой длины:
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
это то, что "позволяет" нам остановить последовательность, манипулировать тем, что мы хотим, и продолжать с того места, где мы остановились.
Честно говоря, повторный подход, вероятно, является лучшим выбором для этого случая. Но так как мы смотрим в 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
Теперь, здесь, мы получаем очень хорошее приложение: учитывая входную последовательность, попытайтесь предсказать ее будущие неизвестные шаги.
Мы используем тот же метод, что и в примере "один ко многим", с той разницей, что:
- мы будем использовать саму последовательность, чтобы быть целевыми данными, на шаг впереди
- мы знаем часть последовательности (поэтому мы отбрасываем эту часть результатов).
Слой (такой же, как выше):
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()
Этот подход был использован в этих ответах и файле:
- Прогнозирование множественного временного шага вперед временного ряда с использованием LSTM
- Как использовать модель Keras для прогнозирования будущих дат или событий?
- https://github.com/danmoller/TestRepo/blob/master/TestBookLSTM.ipynb
Достижение сложных конфигураций
Во всех приведенных выше примерах я показал поведение "одного слоя".
Конечно, вы можете размещать много слоев друг над другом, не обязательно все по одной схеме, и создавать свои собственные модели.
Один интересный пример, который появился, - это "автоматический кодер", который имеет кодер "многие к одному", за которым следует декодер "один ко многим":
Кодер:
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)
Если у вас есть 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))
Обратитесь к этому блогу для получения более подробной информации Анимированные RNN, LSTM и GRU .
Это изображение дает вам лучшее представление о LSTM. Это ячейка LSTM.
Как видите, X имеет 3
features
(зеленые кружки), поэтому вход этой ячейки является вектором размерности 3, а скрытое состояние имеет 2
units
(красные кружки), поэтому выходные данные этой ячейки (а также состояние ячейки) представляют собой вектор размерности 2.
Это изображение является примером одного слоя LSTM с 3 временными шагами (3 ячейки LSTM).
Модель может иметь несколько слоев LSTM.
Теперь я снова использую пример Даниэля Мёллера для лучшего понимания: у нас есть 10 нефтяных резервуаров. Для каждого из них мы измеряем 2 характеристики: температуру, давление каждый час 5 раз. теперь параметры:
- batch_size = количество образцов, используемых в одном проходе вперед / назад (по умолчанию =32) -> например, если у вас есть 1000 образцов и вы установили batch_size равным 100, тогда модели потребуется 10 итераций для однократной передачи всех образцов сеть (1 эпоха). Чем выше размер пакета, тем больше места в памяти вам понадобится. Поскольку количество выборок в этом примере невелико, мы считаем batch_size равным всем выборкам = 10.
- временные шаги = 5
- features = 2
- units = Это положительное целое число, определяющее размер скрытого состояния и состояния ячейки или, другими словами, количество параметров, переданных в следующую ячейку LSTM. Его можно выбрать произвольно или эмпирически в зависимости от характеристик и временных шагов. Использование большего количества единиц приведет к большей точности, а также к увеличению времени вычислений. Но это может привести к переделке.
- input_shape = (размер_пакета, временные шаги, характеристики) = (10,5,2)
- output_shape:
- (размер_пакета, временные шаги, единицы), если return_sequences=True
- (размер_пакета, единицы), если return_sequences=False