如果我喜欢编写一个LSTM
网络并通过不同的输入数组大小来提供它,这怎么可能?
例如,我想获取不同语言的语音消息或文本消息并翻译它们。所以第一个输入可能是“你好”,但第二个输入是“你好吗”。如何设计一个LSTM
可以处理不同输入数组大小的?
我正在使用Keras
.LSTM
如果我喜欢编写一个LSTM
网络并通过不同的输入数组大小来提供它,这怎么可能?
例如,我想获取不同语言的语音消息或文本消息并翻译它们。所以第一个输入可能是“你好”,但第二个输入是“你好吗”。如何设计一个LSTM
可以处理不同输入数组大小的?
我正在使用Keras
.LSTM
最简单的方法是使用Padding 和 Masking。
处理可变长度序列的一般方法有以下三种:
填充和遮罩
在这种方法中,我们用一个特殊的值填充较短的序列,以便稍后屏蔽(跳过)。例如,假设每个时间戳的维度为 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_len
LSTM 设置为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_shape
inMasking
的第一个维度再次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)
额外说明
[20, 21, 22, -10, -10]
将与传感器报告相同,最后有两个噪声(错误)测量值。模型可能会学习完全或至少部分忽略这种噪声,但首先清理数据是合理的,即使用掩码。我们使用具有多种输入大小的 LSTM 层。但是,您需要在将它们馈送到 LSTM 之前对其进行处理。
填充序列:
您需要将不同长度的序列填充到固定长度。对于此预处理,您需要确定数据集中序列的最大长度。
这些值主要由值 0 填充。您可以在 Keras 中执行此操作:
y = keras.preprocessing.sequence.pad_sequences( x , maxlen=10 )
如果序列短于最大长度,则将附加零,直到其长度等于最大长度。
如果序列长于最大长度,则序列将被修剪到最大长度。