LSTM и потоковые прогнозы

Я обучил модель LSTM (построенную с Keras и TF) на нескольких партиях по 7 образцов с 3 характеристиками в каждом, с формой, подобной образцу, приведенному ниже (цифры ниже являются просто заполнителями для целей объяснения), каждая партия помечена как 0 или 1:

Данные:

[
   [[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3]]
   [[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3]]
   [[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3]]
   ...
]

то есть: партии из m последовательностей, каждая длиной 7, элементы которых являются 3-мерными векторами (таким образом, партия имеет форму (m * 7 * 3))

Цель:

[
   [1]
   [0]
   [1]
   ...
]

В моей производственной среде данные представляют собой поток образцов с 3 функциями ([1,2,3],[1,2,3]...). Я хотел бы передавать каждый образец по мере его поступления в мою модель и получать промежуточную вероятность, не дожидаясь всего пакета (7) - см. Анимацию ниже.

enter image description here

Одной из моих мыслей было заполнение партии 0 для отсутствующих образцов, [[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[0,0,0],[1,2,3]] но это кажется неэффективным.

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


Обновление, включая код модели:

opt = optimizers.Adam(lr=0.001, beta_1=0.9, beta_2=0.999, epsilon=10e-8, decay=0.001)
model = Sequential()

num_features = data.shape[2]
num_samples = data.shape[1]

first_lstm = LSTM(32, batch_input_shape=(None, num_samples, num_features), return_sequences=True, activation='tanh')
model.add(
    first_lstm)
model.add(LeakyReLU())
model.add(Dropout(0.2))
model.add(LSTM(16, return_sequences=True, activation='tanh'))
model.add(Dropout(0.2))
model.add(LeakyReLU())
model.add(Flatten())
model.add(Dense(1, activation='sigmoid'))

model.compile(loss='binary_crossentropy', optimizer=opt,
              metrics=['accuracy', keras_metrics.precision(), keras_metrics.recall(), f1])

Сводка модели:

_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
lstm_1 (LSTM)                (None, 100, 32)           6272      
_________________________________________________________________
leaky_re_lu_1 (LeakyReLU)    (None, 100, 32)           0         
_________________________________________________________________
dropout_1 (Dropout)          (None, 100, 32)           0         
_________________________________________________________________
lstm_2 (LSTM)                (None, 100, 16)           3136      
_________________________________________________________________
dropout_2 (Dropout)          (None, 100, 16)           0         
_________________________________________________________________
leaky_re_lu_2 (LeakyReLU)    (None, 100, 16)           0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 1600)              0         
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 1601      
=================================================================
Total params: 11,009
Trainable params: 11,009
Non-trainable params: 0
_________________________________________________________________

Ответ 1

Я думаю, что может быть более простое решение.

Если ваша модель не имеет сверточных слоев или каких-либо других слоев, которые влияют на измерение длины/шага, вы можете просто пометить его как stateful=True

Внимание: ваша модель имеет слои, которые влияют на размерность !!

Слой Flatten преобразует измерение длины в измерение объекта. Это полностью помешает вам достичь вашей цели. Если слой Flatten ожидает 7 шагов, вам всегда нужно 7 шагов.

Поэтому, прежде чем применять мой ответ ниже, исправьте вашу модель, чтобы не использовать слой Flatten. Вместо этого он может просто удалить return_sequences=True для последнего уровня LSTM.

Следующий код исправил это, а также подготовил несколько вещей для использования с ответом ниже:

def createModel(forTraining):

    #model for training, stateful=False, any batch size   
    if forTraining == True:
        batchSize = None
        stateful = False

    #model for predicting, stateful=True, fixed batch size
    else:
        batchSize = 1
        stateful = True

    model = Sequential()

    first_lstm = LSTM(32, 
        batch_input_shape=(batchSize, num_samples, num_features), 
        return_sequences=True, activation='tanh', 
        stateful=stateful)   

    model.add(first_lstm)
    model.add(LeakyReLU())
    model.add(Dropout(0.2))

    #this is the last LSTM layer, use return_sequences=False
    model.add(LSTM(16, return_sequences=False, stateful=stateful,  activation='tanh'))

    model.add(Dropout(0.2))
    model.add(LeakyReLU())

    #don't add a Flatten!!!
    #model.add(Flatten())

    model.add(Dense(1, activation='sigmoid'))

    if forTraining == True:
        compileThisModel(model)

При этом вы сможете тренироваться с 7 шагами и прогнозировать с одним шагом. Иначе это будет невозможно.

Использование модели с состоянием в качестве решения вашего вопроса

Во-первых, попробуйте эту новую модель снова, потому что у нее нет слоя Flatten:

trainingModel = createModel(forTraining=True)
trainThisModel(trainingModel)

Теперь, с этой обученной моделью, вы можете просто создать новую модель точно так же, как вы создали обученную модель, но отметив stateful=True во всех ее слоях LSTM. И мы должны скопировать веса из обученной модели.

Поскольку этим новым слоям потребуется фиксированный размер пакета (правила Keras), я предположил, что это будет 1 (один поток идет, а не m потоков), и добавил его к созданию модели выше.

predictingModel = createModel(forTraining=False)
predictingModel.set_weights(trainingModel.get_weights())

И вуаля. Просто спрогнозируйте результаты модели за один шаг:

pseudo for loop as samples arrive to your model:
    prob = predictingModel.predict_on_batch(sample)

    #where sample.shape == (1, 1, 3)

Когда вы решите, что вы достигли конца того, что вы считаете непрерывной последовательностью, вызовите predictingModel.reset_states() чтобы вы могли безопасно начинать новую последовательность, не думая, что ее следует исправить в конце предыдущей.


Сохранение и загрузка состояний

Просто получите и установите их, сохраняя с помощью h5py:

def saveStates(model, saveName):

    f = h5py.File(saveName,'w')

    for l, lay in enumerate(model.layers):
        #if you have nested models, 
            #consider making this recurrent testing for layers in layers
        if isinstance(lay,RNN):
            for s, stat in enumerate(lay.states):
                f.create_dataset('states_' + str(l) + '_' + str(s),
                                 data=K.eval(stat), 
                                 dtype=K.dtype(stat))

    f.close()


def loadStates(model, saveName):

    f = h5py.File(saveName, 'r')
    allStates = list(f.keys())

    for stateKey in allStates:
        name, layer, state = stateKey.split('_')
        layer = int(layer)
        state = int(state)

        K.set_value(model.layers[layer].states[state], f.get(stateKey))

    f.close()

Рабочий тест для сохранения/загрузки состояний

import h5py, numpy as np
from keras.layers import RNN, LSTM, Dense, Input
from keras.models import Model
import keras.backend as K




def createModel():
    inp = Input(batch_shape=(1,None,3))
    out = LSTM(5,return_sequences=True, stateful=True)(inp)
    out = LSTM(2, stateful=True)(out)
    out = Dense(1)(out)
    model = Model(inp,out)
    return model


def saveStates(model, saveName):

    f = h5py.File(saveName,'w')

    for l, lay in enumerate(model.layers):
        #if you have nested models, consider making this recurrent testing for layers in layers
        if isinstance(lay,RNN):
            for s, stat in enumerate(lay.states):
                f.create_dataset('states_' + str(l) + '_' + str(s), data=K.eval(stat), dtype=K.dtype(stat))

    f.close()


def loadStates(model, saveName):

    f = h5py.File(saveName, 'r')
    allStates = list(f.keys())

    for stateKey in allStates:
        name, layer, state = stateKey.split('_')
        layer = int(layer)
        state = int(state)

        K.set_value(model.layers[layer].states[state], f.get(stateKey))

    f.close()

def printStates(model):

    for l in model.layers:
        #if you have nested models, consider making this recurrent testing for layers in layers
        if isinstance(l,RNN):
            for s in l.states:
                print(K.eval(s))   

model1 = createModel()
model2 = createModel()
model1.predict_on_batch(np.ones((1,5,3))) #changes model 1 states

print('model1')
printStates(model1)
print('model2')
printStates(model2)

saveStates(model1,'testStates5')
loadStates(model2,'testStates5')

print('model1')
printStates(model1)
print('model2')
printStates(model2)

Соображения по поводу аспектов данных

В вашей первой модели (если она имеет stateful=False), она считает, что каждая последовательность в m индивидуальна и не связана с другими. Также считается, что каждая партия содержит уникальные последовательности.

Если это не так, вы можете вместо этого обучить модель с сохранением состояния (учитывая, что каждая последовательность на самом деле связана с предыдущей последовательностью). И тогда вам понадобится m партий по 1 последовательности. → mx (1, 7 or None, 3).

Ответ 2

Если я правильно понял, у вас есть партии из m последовательностей, каждая длиной 7, элементы которых являются 3-мерными векторами (поэтому пакет имеет форму (m*7*3)). В любой RNN return_sequences вы можете установить флаг return_sequences в значение True чтобы стать промежуточными состояниями, т. return_sequences Для каждого пакета вместо окончательного прогноза вы получите соответствующие 7 выходов, где выход i представляет прогноз на этапе i учитывая все входные данные от 0 до i.

Но вы получите все сразу в конце. Насколько я знаю, Keras не предоставляет прямого интерфейса для получения пропускной способности во время обработки пакета. Это может быть еще более ограниченным, если вы используете любой из вариантов CUDNN -optimized. Что вы можете сделать, так это в основном рассматривать свою партию как 7 последовательных партий по форме (m*1*3) и постепенно передавать их в LSTM, записывая скрытое состояние и прогноз на каждом шаге. Для этого вы можете либо установить return_state в True и сделать это вручную, либо вы можете просто установить для stateful значение True и позволить объекту отслеживать его.


Следующий пример Python2 + Keras должен точно представлять, что вы хотите. В частности:

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

Для этого он включает в себя пример stateful=True для простейшего обучения и return_state=True для наиболее точного вывода, поэтому вы получите представление об обоих подходах. Это также предполагает, что вы получаете модель, которая была сериализована и о которой вы мало что знаете. Структура тесно связана с тем в курсе Эндрю Нг, который определенно более авторитетен, чем я в этой теме. Так как вы не указываете, как обучали модель, я предполагал, что тренировочная установка "многие к одному", но это может быть легко адаптировано.

from __future__ import print_function
from keras.layers import Input, LSTM, Dense
from keras.models import Model, load_model
from keras.optimizers import Adam
import numpy as np

# globals
SEQ_LEN = 7
HID_DIMS = 32
OUTPUT_DIMS = 3 # outputs are assumed to be scalars


##############################################################################
# define the model to be trained on a fixed batch size:
# assume many-to-one training setup (otherwise set return_sequences=True)
TRAIN_BATCH_SIZE = 20

x_in = Input(batch_shape=[TRAIN_BATCH_SIZE, SEQ_LEN, 3])
lstm = LSTM(HID_DIMS, activation="tanh", return_sequences=False, stateful=True)
dense = Dense(OUTPUT_DIMS, activation='linear')
m_train = Model(inputs=x_in, outputs=dense(lstm(x_in)))
m_train.summary()

# a dummy batch of training data of shape (TRAIN_BATCH_SIZE, SEQ_LEN, 3), with targets of shape (TRAIN_BATCH_SIZE, 3):
batch123 = np.repeat([[1, 2, 3]], SEQ_LEN, axis=0).reshape(1, SEQ_LEN, 3).repeat(TRAIN_BATCH_SIZE, axis=0)
targets = np.repeat([[123,234,345]], TRAIN_BATCH_SIZE, axis=0) # dummy [[1,2,3],,,]-> [123,234,345] mapping to be learned


# train the model on a fixed batch size and save it
print(">> INFERECE BEFORE TRAINING MODEL:", m_train.predict(batch123, batch_size=TRAIN_BATCH_SIZE, verbose=0))
m_train.compile(optimizer=Adam(lr=0.5), loss='mean_squared_error', metrics=['mae'])
m_train.fit(batch123, targets, epochs=100, batch_size=TRAIN_BATCH_SIZE)
m_train.save("trained_lstm.h5")
print(">> INFERECE AFTER TRAINING MODEL:", m_train.predict(batch123, batch_size=TRAIN_BATCH_SIZE, verbose=0))


##############################################################################
# Now, although we aren't training anymore, we want to do step-wise predictions
# that do alter the inner state of the model, and keep track of that.


m_trained = load_model("trained_lstm.h5")
print(">> INFERECE AFTER RELOADING TRAINED MODEL:", m_trained.predict(batch123, batch_size=TRAIN_BATCH_SIZE, verbose=0))

# now define an analogous model that allows a flexible batch size for inference:
x_in = Input(shape=[SEQ_LEN, 3])
h_in = Input(shape=[HID_DIMS])
c_in = Input(shape=[HID_DIMS])
pred_lstm = LSTM(HID_DIMS, activation="tanh", return_sequences=False, return_state=True, name="lstm_infer")
h, cc, c = pred_lstm(x_in, initial_state=[h_in, c_in])
prediction = Dense(OUTPUT_DIMS, activation='linear', name="dense_infer")(h)
m_inference = Model(inputs=[x_in, h_in, c_in], outputs=[prediction, h,cc,c])

#  Let confirm that this model is able to load the trained parameters:
# first, check that the performance from scratch is not good:
print(">> INFERENCE BEFORE SWAPPING MODEL:")
predictions, hs, zs, cs = m_inference.predict([batch123,
                                               np.zeros((TRAIN_BATCH_SIZE, HID_DIMS)),
                                               np.zeros((TRAIN_BATCH_SIZE, HID_DIMS))],
                                              batch_size=1)
print(predictions)


# import state from the trained model state and check that it works:
print(">> INFERENCE AFTER SWAPPING MODEL:")
for layer in m_trained.layers:
    if "lstm" in layer.name:
        m_inference.get_layer("lstm_infer").set_weights(layer.get_weights())
    elif "dense" in layer.name:
        m_inference.get_layer("dense_infer").set_weights(layer.get_weights())

predictions, _, _, _ = m_inference.predict([batch123,
                                            np.zeros((TRAIN_BATCH_SIZE, HID_DIMS)),
                                            np.zeros((TRAIN_BATCH_SIZE, HID_DIMS))],
                                           batch_size=1)
print(predictions)


# finally perform granular predictions while keeping the recurrent activations. Starting the sequence with zeros is a common practice, but depending on how you trained, you might have an <END_OF_SEQUENCE> character that you might want to propagate instead:
h, c = np.zeros((TRAIN_BATCH_SIZE, HID_DIMS)), np.zeros((TRAIN_BATCH_SIZE, HID_DIMS))
for i in range(len(batch123)):
    # about output shape: https://keras.io/layers/recurrent/#rnn
    # h,z,c hold the network throughput: h is the proper LSTM output, c is the accumulator and cc is (probably) the candidate
    current_input = batch123[i:i+1] # the length of this feed is arbitrary, doesn't have to be 1
    pred, h, cc, c = m_inference.predict([current_input, h, c])
    print("input:", current_input)
    print("output:", pred)
    print(h.shape, cc.shape, c.shape)
    raw_input("do something with your prediction and hidden state and press any key to continue")

Дополнительная информация:

Так как у нас есть две формы сохранения состояния:
1. Сохраненные/обученные параметры модели, которые являются одинаковыми для каждой последовательности
2. Состояния a, c которые развиваются во всех последовательностях и могут быть "перезапущены".

Интересно взглянуть на внутренности объекта LSTM. В приведенном мною примере Python веса a и c обрабатываются явно, а обученные параметры - нет, и может быть неясно, как они реализованы внутри или что они означают. Их можно проверить следующим образом:

for w in lstm.weights:
    print(w.name, w.shape)

В нашем случае (32 скрытых состояния) возвращает следующее:

lstm_1/kernel:0 (3, 128)
lstm_1/recurrent_kernel:0 (32, 128)
lstm_1/bias:0 (128,)

Мы наблюдаем размерность 128. Почему это? эта ссылка описывает реализацию Keras LSTM следующим образом:

enter image description here

G - рекуррентная активация, p - активация, Ws - ядра, Us - рекуррентные ядра, h - скрытая переменная, которая также является выходной, а запись * - это поэлементное умножение.

Что объясняет 128=32*4 являющиеся параметрами для аффинного преобразования, происходящего внутри каждого из 4 шлюзов, связанных:

  • Матрица формы (3, 128) (именованное kernel) обрабатывает ввод для данного элемента последовательности
  • Матрица формы (32, 128) (с именем recurrent_kernel) обрабатывает ввод для последнего рекуррентного состояния h.
  • Вектор формы (128,) (называемый bias), как обычно в любой другой настройке NN.

Ответ 3

Примечание. В этом ответе предполагается, что ваша модель на этапе обучения не содержит состояния. Вы должны понимать, что такое уровень RNN с отслеживанием состояния и убедиться, что данные обучения имеют соответствующие свойства сохранения состояния. Вкратце это означает, что существует зависимость между последовательностями, то есть одна последовательность является продолжением другой последовательности, которую вы хотите рассмотреть в своей модели. Если ваша модель и тренировочные данные с состоянием, то я думаю, что другие ответы, которые включают установку stateful=True для уровней RNN с самого начала, проще.

Обновление: независимо от того, является ли модель обучения с состоянием или нет, вы всегда можете скопировать ее веса в модель логического вывода и включить отслеживание состояния. Поэтому я думаю, что решения, основанные на установке stateful=True, короче и лучше, чем мои. Единственный их недостаток заключается в том, что размер партии в этих решениях должен быть фиксированным.


Обратите внимание, что вывод слоя LSTM над одной последовательностью определяется его весовыми матрицами, которые являются фиксированными, и его внутренними состояниями, которые зависят от предыдущего обработанного временного шага. Теперь, чтобы получить выходные данные слоя LSTM для одной последовательности длиной m, одним очевидным способом является подача всей последовательности в слой LSTM за один раз. Однако, как я уже говорил ранее, поскольку его внутренние состояния зависят от предыдущего временного шага, мы можем использовать этот факт и передавать этот фрагмент последовательности за фрагментом, получая состояние уровня LSTM в конце обработки фрагмента и передавая его в LSTM. слой для обработки следующего куска. Чтобы сделать это более понятным, предположим, что длина последовательности равна 7 (т.е. она имеет 7 временных шагов векторов объектов фиксированной длины). В качестве примера, можно обработать эту последовательность следующим образом:

  1. Подайте временные шаги 1 и 2 на слой LSTM; получить конечное состояние (назовите его C1).
  2. Подайте временные шаги 3, 4 и 5 и состояние C1 в качестве начального состояния на уровень LSTM; получить конечное состояние (назовите его C2).
  3. Подайте временные шаги 6 и 7 и состояние C2 в качестве начального состояния на уровень LSTM; получить окончательный вывод.

Этот конечный вывод эквивалентен выводу, производимому слоем LSTM, если бы мы передавали ему все 7 временных шагов за раз.

Таким образом, чтобы реализовать это в Keras, вы можете установить аргумент return_state слоя LSTM в True чтобы вы могли получить промежуточное состояние. Кроме того, не указывайте фиксированную длину временного шага при определении входного слоя. Вместо этого используйте None чтобы иметь возможность снабжать модель последовательностями произвольной длины, что позволяет нам постепенно обрабатывать каждую последовательность (хорошо, если ваши входные данные во время обучения являются последовательностями фиксированной длины).

Поскольку вам нужна эта возможность обработки патрона во время вывода, нам нужно определить новую модель, которая разделяет слой LSTM, используемый в обучающей модели, и может получать начальные состояния в качестве входных данных, а также дает результирующие состояния в качестве выходных данных. Ниже приведен общий эскиз, который можно сделать (обратите внимание, что возвращенное состояние слоя LSTM не используется при обучении модели, оно нам нужно только во время тестирования):

# define training model
train_input = Input(shape=(None, n_feats))   # note that the number of timesteps is None
lstm_layer = LSTM(n_units, return_state=True)
lstm_output, _, _ =  lstm_layer(train_input) # note that we ignore the returned states
classifier = Dense(1, activation='sigmoid')
train_output = classifier(lstm_output)

train_model = Model(train_input, train_output)

# compile and fit the model on training data ...

# ==================================================

# define inference model
inf_input = Input(shape=(None, n_feats))
state_h_input = Input(shape=(n_units,))
state_c_input = Input(shape=(n_units,))

# we use the layers of previous model
lstm_output, state_h, state_c = lstm_layer(inf_input,
                                           initial_state=[state_h_input, state_c_input])
output = classifier(lstm_output)

inf_model = Model([inf_input, state_h_input, state_c_input],
                  [output, state_h, state_c])  # note that we return the states as output

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

state_h = np.zeros((1, n_units,))
state_c = np.zeros((1, n_units))

# three new timesteps are available
outputs = inf_model.predict([timesteps, state_h, state_c])

out = output[0,0]  # you may ignore this output since the entire sequence has not been processed yet
state_h = outputs[0,1]
state_c = outputs[0,2]

# after some time another four new timesteps are available
outputs = inf_model.predict([timesteps, state_h, state_c])

# we have processed 7 timesteps, so the output is valid
out = output[0,0]  # store it, pass it to another thread or do whatever you want to do with it

# reinitialize the state to make them ready for the next sequence chunk
state_h = np.zeros((1, n_units))
state_c = np.zeros((1, n_units))

# to be continued...

Конечно, вам нужно сделать это в каком-то цикле или реализовать структуру потока управления для обработки потока данных, но я думаю, вы получите то, на что похожа общая идея.

Наконец, хотя ваш конкретный пример не является последовательной моделью, но я настоятельно рекомендую прочитать официальный учебник Keras seq2seq, который, я думаю, можно почерпнуть из него множество идей.

Ответ 4

Насколько я знаю, из-за статического графа в Tensorflow нет эффективного способа подачи входных данных, длина которых отличается от длины входных данных обучения.

Заполнение это официальный способ обойти это, но он менее эффективен и требует много памяти. Я предлагаю вам заглянуть в Pytorch, который поможет решить вашу проблему.

Есть много отличных постов для сборки lstm с помощью Pytorch, и вы поймете преимущества динамического графа, как только увидите их.