GRU / LSTM в Keras с входной последовательностью различной длины - PullRequest
5 голосов
/ 02 апреля 2019

Я работаю над меньшим проектом, чтобы лучше понять RNN, в частности LSTM и GRU. Я совсем не эксперт, поэтому, пожалуйста, имейте это в виду.

Проблема, с которой я сталкиваюсь, указывается в виде:

>>> import numpy as np
>>> import pandas as pd
>>> pd.DataFrame([[1, 2, 3],[1, 2, 1], [1, 3, 2],[2, 3, 1],[3, 1, 1],[3, 3, 2],[4, 3, 3]], columns=['person', 'interaction', 'group'])
   person  interaction  group
0       1            2      3
1       1            2      1
2       1            3      2
3       2            3      1
4       3            1      1
5       3            3      2
6       4            3      3

это только для объяснения. У нас разные люди взаимодействуют с разными группами по-разному. Я уже закодировал различные функции. Последнее взаимодействие пользователя - это всегда 3, что означает выбор определенной группы. В приведенном выше коротком примере person 1 выбирает группу 2, person 2 выбирает группу 1 и так далее.

Весь мой набор данных намного больше, но я хотел бы сначала понять концептуальную часть, прежде чем бросать в нее модели. Задача, которую я хотел бы выучить, - это последовательность взаимодействия, какую группу выбирает человек. Немного конкретнее, я хотел бы получить список со всеми группами (есть 3 группы, 1, 2, 3), отсортированными по наиболее вероятному варианту, за которым следуют вторая и третья группы. Поэтому функция потерь является средним взаимным рангом.

Я знаю, что в Keras Grus / LSTM можно обрабатывать ввод различной длины. Итак, мои три вопроса:

Ввод имеет формат:

(samples, timesteps, features)

написание кода высокого уровня:

import keras.layers as L
import keras.models as M
model_input = L.Input(shape=(?, None, 2))

timestep=None должно означать переменный размер, а 2 - для функции interaction и group. Но как насчет образцов? Как определить партии?

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

model_output = L.LSTM(3, return_sequences=False)

Затем я хочу его скомпилировать. Есть ли способ использовать среднее взаимное звание?

model.compile('adam', '?')

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

1 Ответ

6 голосов
/ 06 апреля 2019

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

  • Вы можете указать LSTM(n_hidden, input_shape=(None, 2)) напрямую, вместо вставки дополнительного слоя Input;размерность пакета должна быть опущена для определения.
  • Поскольку ваша модель будет выполнять какую-то классификацию (на основе данных временного ряда), последний уровень - это то, что мы ожидаем от "нормальной" классификации какну, а Dense(num_classes, action='softmax').Объединение в цепочку слоя LSTM и слоя Dense сначала передаст входные временные ряды через слой LSTM, а затем передаст его выходные данные (определяемые количеством скрытых единиц) в слой Dense.activation='softmax' позволяет вычислить оценку класса для каждого класса (мы собираемся использовать горячее кодирование на этапе предварительной обработки данных, см. Пример кода ниже).Это означает, что оценки классов не упорядочены, но вы всегда можете сделать это с помощью np.argsort или np.argmax.
  • Категориальная потеря кроссентропии подходит для сравнения классификационной оценки, поэтому мы будем использовать ее: model.compile(loss='categorical_crossentropy', optimizer='adam').
  • Так как количество взаимодействий.т. е. длина входных данных модели варьируется от образца к образцу. Мы будем использовать размер партии 1 и подачу по одному образцу за раз.

Ниже приведен пример реализации по вышеупомянутым соображениям.,Обратите внимание, что я немного изменил ваши образцы данных, чтобы обеспечить более «обоснование» выбора групп.Также каждый человек должен выполнить хотя бы одно взаимодействие перед выбором группы (т.е. последовательность ввода не может быть пустой);если это не относится к вашим данным, то может помочь введение дополнительного неактивного взаимодействия (например, 0).

import pandas as pd
import tensorflow as tf

model = tf.keras.models.Sequential()
model.add(tf.keras.layers.LSTM(10, input_shape=(None, 2)))  # LSTM for arbitrary length series.
model.add(tf.keras.layers.Dense(3, activation='softmax'))   # Softmax for class probabilities.
model.compile(loss='categorical_crossentropy', optimizer='adam')

# Example interactions:
#   * 1: Likes the group,
#   * 2: Dislikes the group,
#   * 3: Chooses the group.
df = pd.DataFrame([
    [1, 1, 3],
    [1, 1, 3],
    [1, 2, 2],
    [1, 3, 3],
    [2, 2, 1],
    [2, 2, 3],
    [2, 1, 2],
    [2, 3, 2],
    [3, 1, 1],
    [3, 1, 1],
    [3, 1, 1],
    [3, 2, 3],
    [3, 2, 2],
    [3, 3, 1]],
    columns=['person', 'interaction', 'group']
)
data = [person[1][['interaction', 'group']].values for person in df.groupby('person')]
x_train = [x[:-1] for x in data]
y_train = tf.keras.utils.to_categorical([x[-1, 1]-1 for x in data])  # Expects class labels from 0 to n (-> subtract 1).
print(x_train)
print(y_train)

class TrainGenerator(tf.keras.utils.Sequence):
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __len__(self):
        return len(self.x)

    def __getitem__(self, index):
        # Need to expand arrays to have batch size 1.
        return self.x[index][None, :, :], self.y[index][None, :]

model.fit_generator(TrainGenerator(x_train, y_train), epochs=1000)
pred = [model.predict(x[None, :, :]).ravel() for x in x_train]
for p, y in zip(pred, y_train):
    print(p, y)

И соответствующий пример вывода:

[...]
Epoch 1000/1000
3/3 [==============================] - 0s 40ms/step - loss: 0.0037
[0.00213619 0.00241093 0.9954529 ] [0. 0. 1.]
[0.00123938 0.99718493 0.00157572] [0. 1. 0.]
[9.9632275e-01 7.5039308e-04 2.9268670e-03] [1. 0. 0.]

Использование пользовательских выражений генератора: Согласно документации мы можем использовать любой генератор для получения данных.Ожидается, что генератор будет выдавать пакеты данных и обходить весь набор данных бесконечно.При использовании tf.keras.utils.Sequence нам не нужно указывать параметр steps_per_epoch, так как по умолчанию он будет len(train_generator).Следовательно, при использовании пользовательского генератора мы также предоставляем этот параметр:

import itertools as it

model.fit_generator(((x_train[i % len(x_train)][None, :, :],
                      y_train[i % len(y_train)][None, :]) for i in it.count()),
                    epochs=1000,
                    steps_per_epoch=len(x_train))
...