在序列模型中,是否可以使用不同时间步长的训练批次来减少每个输入序列所需的填充?

数据挖掘 喀拉斯 张量流 nlp 顺序 小批量梯度下降
2021-09-22 23:54:14

我想训练一个具有可变长度输入的 LSTM 模型。具体来说,我想在仍然使用小批量的同时尽可能少地使用填充。

据我了解,每个批次都需要所有输入的固定数量的时间步长,因此需要填充。但是不同批次的输入可能有不同数量的时间步长,因此在每个批次中,输入只需填充到同一批次中最长输入序列的长度。这就是我想要实现的。

我需要做什么:

  1. 在训练期间动态创建给定大小的批次,每个批次中的输入被填充到同一批次中的最长序列。
  2. 训练数据在每个 epoch 之后被打乱,因此输入在不同的批次中出现在不同的批次中,并以不同的方式填充。

可悲的是,我的谷歌搜索技能完全让我失望了。我只能找到有关如何将整个输入集填充为固定长度的示例和资源,这是我已经在做的并且想要摆脱的。一些线索将我指向 tensorflow 的 Dataset API,但我找不到它如何以及为什么适用于我所面临的问题的示例。

我会很感激任何指向资源的指针,以及关于我想要完成的工作的理想示例和教程。

2个回答

满足您需求的答案称为“分桶”。它包括创建具有相似长度的批量序列,以最小化所需的填充。

在 tensorflow 中,您可以使用tf.data.experimental.bucket_by_sequence_length. 考虑到以前它位于不同的 python 包中(tf.contrib.data.bucket_by_sequence_length),因此在线示例可能包含过时的名称。

要查看一些使用示例,您可以查看此 jupyter notebookstackoverflow本教程中的其他答案

找到了一个解决方案,将 keras.utils.Sequence 类型的自定义批处理生成器传递给 model.fit 函数(可以编写任何逻辑来构造批处理和修改/增强训练数据),而不是将整个数据集传入一口气。相关代码供参考:

# Must implement the __len__ function returning the number
# of batches in this dataset, and the __getitem__ function
# that returns a tuple (inputs, labels).
# Optionally, on_epoch_end() can be implemented which as the
# name suggest is called at the end of each epoch. Here one
# can e.g. shuffle the input data for the next epoch.

class BatchGenerator(keras.utils.Sequence):
    
    def __init__(self, inputs, labels, padding, batch_size):
        self.inputs = inputs
        self.labels = labels
        self.padding = padding
        self.batch_size = batch_size
        

    def __len__(self):
      return int(np.floor(len(self.inputs) / self.batch_size))

    def __getitem__(self, index):
      max_length = 0
      start_index = index*batch_size
      end_index = start_index+batch_size
      for i in range(start_index, end_index):
        l = len(self.inputs[i])
        if l>max_length:
          max_length = l
      
      out_x = np.empty([self.batch_size, max_length], dtype='int32')
      out_y = np.empty([self.batch_size, 1], dtype='float32')
      for i in range(self.batch_size):
        out_y[i] = self.labels[start_index+i]
        tweet = self.inputs[start_index+i]
        l = len(tweet)
        for j in range(l):
          out_x[i][j] = tweet[j]
        for j in range(l, max_length):
          out_x[i][j] = self.padding
      return out_x, out_y


# The model.fit function can then be called like this:

training_generator = BatchGenerator(tokens_train, y_train, pad, batch_size)
model.fit(training_generator, epochs=epochs)