LSTM и потоковые прогнозы - PullRequest
0 голосов
/ 07 ноября 2018

Я обучил модель 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
_________________________________________________________________

Ответы [ 4 ]

0 голосов
/ 16 ноября 2018

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

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

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

0 голосов
/ 13 ноября 2018

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

0 голосов
/ 14 ноября 2018

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

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

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

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

Итак, прежде чем применять мой ответ ниже, исправьте вашу модель, чтобы не использовать слой Flatten. Вместо этого он может просто удалить return_sequences=True для слоя last 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. И мы должны скопировать веса с обученной модели.

Поскольку этим новым слоям понадобится фиксированный размер пакета (правила Кераса), я предположил, что это будет 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 последовательности. -> m x (1, 7 or None, 3).

0 голосов
/ 07 ноября 2018

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

Но вы получите все сразу в конце. Насколько я знаю, Keras не предоставляет прямой интерфейс для получения пропускной способности во время обработки пакета . Это может быть еще более ограничено, если вы используете любой из CUDNN -оптимизированных вариантов. Что вы можете сделать, так это, в основном, считать вашу партию 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's 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's 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.
...