Обучение RNN с примерами различной длины в Керасе

64

Я пытаюсь начать изучать RNN и использую Keras. Я понимаю основную предпосылку ванильных слоев RNN и LSTM, но у меня возникают проблемы с пониманием определенного технического аспекта обучения.

В документации keras говорится, что входные данные для слоя RNN должны иметь форму (batch_size, timesteps, input_dim). Это говорит о том, что все обучающие примеры имеют фиксированную длину последовательности, а именно timesteps.

Но это не особенно типично, не так ли? Я мог бы хотеть, чтобы RNN оперировал предложениями различной длины. Когда я тренирую его в каком-то корпусе, я буду кормить его пакетами предложений различной длины.

Я полагаю, что очевидным было бы найти максимальную длину любой последовательности в обучающем наборе и заполнить ее нулями. Но значит ли это, что я не могу делать прогнозы во время теста с длиной ввода больше, чем это?

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

Tac-Тики
источник
@kbrose правильно. Однако у меня есть одна проблема. В этом примере у вас есть очень особый генератор бесконечной доходности. Что еще более важно, он предназначен для производства партий размером 1000. На практике это слишком трудно удовлетворить, если не невозможно. Вы должны реорганизовать свои записи так, чтобы те, у кого одинаковая длина, были расположены вместе, и вам нужно тщательно установить позиции разделения партии. Более того, у вас нет шансов переставить партии. Поэтому мое мнение таково: никогда не используйте ввод различной длины в Keras, если вы точно не знаете, что делаете. Используйте отступы и установите Maskingслой, чтобы игнорировать
Bs Он

Ответы:

59

Это говорит о том, что все обучающие примеры имеют фиксированную длину последовательности, а именно timesteps.

Это не совсем правильно, так как это измерение может быть None, то есть переменной длины. В пределах одного пакета у вас должно быть одинаковое количество временных шагов (как правило, именно там вы видите 0-заполнение и маскирование). Но между партиями такого ограничения нет. Во время вывода вы можете иметь любую длину.

Пример кода, который создает случайные пакеты продолжительности обучения.

from keras.models import Sequential
from keras.layers import LSTM, Dense, TimeDistributed
from keras.utils import to_categorical
import numpy as np

model = Sequential()

model.add(LSTM(32, return_sequences=True, input_shape=(None, 5)))
model.add(LSTM(8, return_sequences=True))
model.add(TimeDistributed(Dense(2, activation='sigmoid')))

print(model.summary(90))

model.compile(loss='categorical_crossentropy',
              optimizer='adam')

def train_generator():
    while True:
        sequence_length = np.random.randint(10, 100)
        x_train = np.random.random((1000, sequence_length, 5))
        # y_train will depend on past 5 timesteps of x
        y_train = x_train[:, :, 0]
        for i in range(1, 5):
            y_train[:, i:] += x_train[:, :-i, i]
        y_train = to_categorical(y_train > 2.5)
        yield x_train, y_train

model.fit_generator(train_generator(), steps_per_epoch=30, epochs=10, verbose=1)

И это то, что он печатает. Обратите внимание, что выходные формы (None, None, x)указывают на переменный размер пакета и переменный размер временного шага.

__________________________________________________________________________________________
Layer (type)                            Output Shape                        Param #
==========================================================================================
lstm_1 (LSTM)                           (None, None, 32)                    4864
__________________________________________________________________________________________
lstm_2 (LSTM)                           (None, None, 8)                     1312
__________________________________________________________________________________________
time_distributed_1 (TimeDistributed)    (None, None, 2)                     18
==========================================================================================
Total params: 6,194
Trainable params: 6,194
Non-trainable params: 0
__________________________________________________________________________________________
Epoch 1/10
30/30 [==============================] - 6s 201ms/step - loss: 0.6913
Epoch 2/10
30/30 [==============================] - 4s 137ms/step - loss: 0.6738
...
Epoch 9/10
30/30 [==============================] - 4s 136ms/step - loss: 0.1643
Epoch 10/10
30/30 [==============================] - 4s 142ms/step - loss: 0.1441
kbrose
источник
Спасибо тебе за это. Однако, если мы заполняем 0 последовательностями, это повлияет на скрытые состояния и ячейку памяти, потому что мы продолжаем передавать x_t как 0, когда, если факт, ничего не должно быть передано. В норме fit()мы можем передать sequence_lenthпараметр, чтобы указать длину последовательности, чтобы исключить ее. Кажется, что генераторный подход не позволяет игнорировать 0 последовательностей?
GRS
1
@GRS Ваш генератор может вернуть 3-х кортеж (inputs, targets, sample_weights), и вы можете установить sample_weightsсвои 0-пэды на 0. Однако я не уверен, что это будет работать идеально для двунаправленных RNN.
kbrose
Это было полезно, но я бы хотел, чтобы в нем также был пример использования model.predict_generatorс тестовым набором. Когда я пытаюсь предсказать с помощью генератора, я получаю ошибку, касающуюся конкатенации (тестовый набор также имеет последовательности переменной длины). Мое решение было использовать стандарт model.predictв хакерской манере. Возможно, это было бы лучше для нового вопроса?
Микки
@ микки, это звучит как другой вопрос. Этот вопрос касается обучения, а не предсказания.
kbrose
Если вопрос в комментариях действительно был задан как новый вопрос, можете ли вы дать ссылку на него?
Итамар Мушкин
7

У @kbrose есть лучшее решение

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

Обычно это хорошее решение. Может быть, попробуйте максимальную длину последовательности + 100. Используйте все, что лучше всего подходит для вашего приложения.

Но значит ли это, что я не могу делать прогнозы во время теста с длиной ввода больше, чем это?

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

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

Вы, вероятно, можете сделать это, используя что-то вроде:

new_model.set_weights(old_model.get_weights())

Я не пробовал это сам. Пожалуйста, попробуйте и опубликуйте свои результаты здесь для всеобщего блага. Вот несколько ссылок: один два

Аниш Джоши
источник
1
Вы действительно можете иметь входные данные переменной длины, нет необходимости вводить подобные хаки max length + 100. Смотрите мой ответ для примера кода.
kbrose
1
Перенос весов на модель с большим количеством временных шагов действительно работает отлично! Я увеличил временные шаги Bidirectional(LSTM)()и RepeatVector()слои, и прогнозы вполне жизнеспособны.
Комодоваран_
@kbrose Это не хак, а то, как вы обычно это делаете. Использование batch_size of one слишком медленное, и кераты позволяют маскировать слои, чтобы маскирование не влияло на потери.
Ферус