如何为 LSTM 提供不同的输入数组大小?

数据挖掘 喀拉斯 lstm
2021-09-22 22:19:44

如果我喜欢编写一个LSTM网络并通过不同的输入数组大小来提供它,这怎么可能?

例如,我想获取不同语言的语音消息或文本消息并翻译它们。所以第一个输入可能是“你好”,但第二个输入是“你好吗”。如何设计一个LSTM可以处理不同输入数组大小的?

我正在使用Keras.LSTM

2个回答

最简单的方法是使用Padding 和 Masking

处理可变长度序列的一般方法有以下三种:

  1. 填充和遮罩(可用于(3)),
  2. 批量大小 = 1,并且
  3. 批次大小 > 1,每批次的样本长度相等。

填充和遮罩

在这种方法中,我们用一个特殊的值填充较短的序列,以便稍后屏蔽(跳过)。例如,假设每个时间戳的维度为 2,并且-10是特殊值,那么

X = [

  [[1,    1.1],
   [0.9, 0.95]],  # sequence 1 (2 timestamps)

  [[2,    2.2],
   [1.9, 1.95],
   [1.8, 1.85]],  # sequence 2 (3 timestamps)

]

将转换为

X2 = [

  [[1,    1.1],
   [0.9, 0.95],
   [-10, -10]], # padded sequence 1 (3 timestamps)

  [[2,    2.2],
   [1.9, 1.95],
   [1.8, 1.85]], # sequence 2 (3 timestamps)
]

这样,所有序列将具有相同的长度。然后,我们使用一个Masking层来跳过那些不存在的特殊时间戳。最后给出了一个完整的例子。

对于情况 (2) 和 (3),您需要将seq_lenLSTM 设置为None,例如

model.add(LSTM(units, input_shape=(None, dimension)))

这样 LSTM 接受不同长度的批次;尽管每批内的样本长度必须相同。然后,您需要将自定义批处理生成器提供给model.fit_generator(而不是model.fit)。

最后,我提供了简单案例 (2) (batch size = 1) 的完整示例。根据此示例和链接,您应该能够为案例 (3)(批量大小 > 1)构建一个生成器。具体来说,我们要么(a)返回batch_size长度相同的序列,要么(b)选择长度几乎相同的序列,并像案例(1)一样填充较短的序列,并Masking在 LSTM 层之前使用一层忽略填充的时间戳,例如

model.add(Masking(mask_value=special_value, input_shape=(None, dimension)))
model.add(LSTM(lstm_units))

其中input_shapeinMasking的第一个维度再次None允许具有不同长度的批次。

以下是案例(1)和(2)的代码:

from keras import Sequential
from keras.utils import Sequence
from keras.layers import LSTM, Dense, Masking
import numpy as np


class MyBatchGenerator(Sequence):
    'Generates data for Keras'
    def __init__(self, X, y, batch_size=1, shuffle=True):
        'Initialization'
        self.X = X
        self.y = y
        self.batch_size = batch_size
        self.shuffle = shuffle
        self.on_epoch_end()

    def __len__(self):
        'Denotes the number of batches per epoch'
        return int(np.floor(len(self.y)/self.batch_size))

    def __getitem__(self, index):
        return self.__data_generation(index)

    def on_epoch_end(self):
        'Shuffles indexes after each epoch'
        self.indexes = np.arange(len(self.y))
        if self.shuffle == True:
            np.random.shuffle(self.indexes)

    def __data_generation(self, index):
        Xb = np.empty((self.batch_size, *X[index].shape))
        yb = np.empty((self.batch_size, *y[index].shape))
        # naively use the same sample over and over again
        for s in range(0, self.batch_size):
            Xb[s] = X[index]
            yb[s] = y[index]
        return Xb, yb


# Parameters
N = 1000
halfN = int(N/2)
dimension = 2
lstm_units = 3

# Data
np.random.seed(123)  # to generate the same numbers
# create sequence lengths between 1 to 10
seq_lens = np.random.randint(1, 10, halfN)
X_zero = np.array([np.random.normal(0, 1, size=(seq_len, dimension)) for seq_len in seq_lens])
y_zero = np.zeros((halfN, 1))
X_one = np.array([np.random.normal(1, 1, size=(seq_len, dimension)) for seq_len in seq_lens])
y_one = np.ones((halfN, 1))
p = np.random.permutation(N)  # to shuffle zero and one classes
X = np.concatenate((X_zero, X_one))[p]
y = np.concatenate((y_zero, y_one))[p]

# Batch = 1
model = Sequential()
model.add(LSTM(lstm_units, input_shape=(None, dimension)))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='rmsprop', metrics=['accuracy'])
print(model.summary())
model.fit_generator(MyBatchGenerator(X, y, batch_size=1), epochs=2)

# Padding and Masking
special_value = -10.0
max_seq_len = max(seq_lens)
Xpad = np.full((N, max_seq_len, dimension), fill_value=special_value)
for s, x in enumerate(X):
    seq_len = x.shape[0]
    Xpad[s, 0:seq_len, :] = x
model2 = Sequential()
model2.add(Masking(mask_value=special_value, input_shape=(max_seq_len, dimension)))
model2.add(LSTM(lstm_units))
model2.add(Dense(1, activation='sigmoid'))
model2.compile(loss='binary_crossentropy', optimizer='rmsprop', metrics=['accuracy'])
print(model2.summary())
model2.fit(Xpad, y, epochs=50, batch_size=32)

额外说明

  1. 请注意,如果我们不加掩码填充,填充的值将被视为实际值,因此它成为数据中的噪声。例如,填充温度序列[20, 21, 22, -10, -10]将与传感器报告相同,最后有两个噪声(错误)测量值。模型可能会学习完全或至少部分忽略这种噪声,但首先清理数据是合理的,即使用掩码。

我们使用具有多种输入大小的 LSTM 层。但是,您需要在将它们馈送到 LSTM 之前对其进行处理。

填充序列:

您需要将不同长度的序列填充到固定长度。对于此预处理,您需要确定数据集中序列的最大长度。

这些值主要由值 0 填充。您可以在 Keras 中执行此操作:

y = keras.preprocessing.sequence.pad_sequences( x , maxlen=10 )
  • 如果序列短于最大长度,则将附加零,直到其长度等于最大长度。

  • 如果序列长于最大长度,则序列将被修剪到最大长度。