对于某些序列处理问题,这种一维卷积神经网络的效果可以媲美 RNN,而且计算代价通常要小很多。 最近,一维卷积神经网络[通常与空洞卷积核( dilated kernel)一起使用]已经在音频生成和机器翻译领域取得了巨大成功。

理解序列数据的一维卷积

前面介绍的卷积层都是二维卷积,从图像张量中提取二维图块并对每个图块应用相同的变换。按照同样的方法,你也可以使用一维卷积,从序列中提取局部一维序列段(即子序列)。
image.png
这种一维卷积层可以识别序列中的局部模式。因为对每个序列段执行相同的输入变换,所以在句子中某个位置学到的模式稍后可以在其他位置被识别,这使得一维卷积神经网络具有平移不变性(对于时间平移而言)。

序列数据的一维池化

你已经学过二维池化运算,比如二维平均池化和二维最大池化,在卷积神经网络中用于对图像张量进行空间下采样。一维也可以做相同的池化运算:从输入中提取一维序列段(即子序列),然后输出其最大值(最大池化)或平均值(平均池化)。与二维卷积神经网络一样,该运算也是用于降低一维输入的长度( 子采样)。

实现一维卷积神经网络

Keras 中的一维卷积神经网络是 Conv1D 层,其接口类似于 Conv2D。它接收的输入是形状为 (samples, time, features) 的三维张量,并返回类似形状的三维张量。卷积窗口是时间轴上的一维窗口(时间轴是输入张量的第二个轴)。

获取数据

准备 IMDB 数据

  1. from keras.datasets import imdb
  2. from keras.preprocessing import sequence
  3. max_features = 10000
  4. max_len = 500
  5. print('Loading data...')
  6. (x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features)
  7. print(len(x_train), 'train sequences')
  8. print(len(x_test), 'test sequences')
  9. print('Pad sequences (samples x time)')
  10. x_train = sequence.pad_sequences(x_train, maxlen=max_len)
  11. x_test = sequence.pad_sequences(x_test, maxlen=max_len)
  12. print('x_train shape:', x_train.shape)
  13. print('x_test shape:', x_test.shape)

搭建网络

一维卷积神经网络的架构与第 5 章的二维卷积神经网络相同,它是 Conv1D 层和 MaxPooling1D 层的堆叠,最后是一个全局池化层或 Flatten 层,将三维输出转换为二维输出,让你可以向模型中添加一个或多个 Dense 层,用于分类或回归。

不过二者有一点不同:一维卷积神经网络可以使用更大的卷积窗口。对于二维卷积层,3× 3 的卷积窗口包含 3× 3=9 个特征向量;但对于一位卷积层,大小为 3 的卷积窗口只包含 3 个卷积向量。因此,你可以轻松使用大小等于 7 或 9 的一维卷积窗口。

在 IMDB 数据上训练并评估一个简单的一维卷积神经网络:

  1. from keras.models import Sequential
  2. from keras import layers
  3. from keras.optimizers import RMSprop
  4. model = Sequential()
  5. model.add(layers.Embedding(max_features, 128, input_length=max_len))
  6. model.add(layers.Conv1D(32, 7, activation='relu'))
  7. model.add(layers.MaxPooling1D(5))
  8. model.add(layers.Conv1D(32, 7, activation='relu'))
  9. model.add(layers.GlobalMaxPooling1D())
  10. model.add(layers.Dense(1))
  11. model.summary()
  12. model.compile(optimizer=RMSprop(lr=1e-4),
  13. loss='binary_crossentropy',
  14. metrics=['acc'])
  15. history = model.fit(x_train, y_train,
  16. epochs=10,
  17. batch_size=128,
  18. validation_split=0.2)

下面的图给出了模型的训练结果和验证结果。验证精度略低于 LSTM,但在 CPU 和 GPU 上的运行速度都要更快(速度提高多少取决于具体配置,会有很大差异)。现在,你可以使用正确的轮数( 4 轮)重新训练这个模型,然后在测试集上运行。这个结果可以让我们确信,在单词级的情感分类任务上,一维卷积神经网络可以替代循环网络,并且速度更快、计算代价更低。
image.pngimage.png

结合 CNN 和 RNN 来处理长序列

一维卷积神经网络分别处理每个输入序列段,所以它对时间步的顺序不敏感(这里所说顺序的范围要大于局部尺度,即大于卷积窗口的大小),这一点与 RNN 不同。
当然,为了识别更长期的模式,你可以将许多卷积层和池化层堆叠在一起,这样上面的层能够观察到原始输入中
更长的序列段,但这仍然不是一种引入顺序敏感性的好方法。 想要证明这种方法的不足,一种方法是在温度预测问题上使用一维卷积神经网络,在这个问题中顺序敏感性对良好的预测结果非常关键。以下示例复用了前面定义的这些变量: float_data、 train_gen、 val_gen 和 val_steps:

  1. from keras.models import Sequential
  2. from keras import layers
  3. from keras.optimizers import RMSprop
  4. model = Sequential()
  5. model.add(layers.Conv1D(32, 5, activation='relu',
  6. input_shape=(None, float_data.shape[-1])))
  7. model.add(layers.MaxPooling1D(3))
  8. model.add(layers.Conv1D(32, 5, activation='relu'))
  9. model.add(layers.MaxPooling1D(3))
  10. model.add(layers.Conv1D(32, 5, activation='relu'))
  11. model.add(layers.GlobalMaxPooling1D())
  12. model.add(layers.Dense(1))
  13. model.compile(optimizer=RMSprop(), loss='mae')
  14. history = model.fit_generator(train_gen,
  15. steps_per_epoch=500,
  16. epochs=20,
  17. validation_data=val_gen,
  18. validation_steps=val_steps)

image.png
验证 MAE 停留在 0.4~0.5,使用小型卷积神经网络甚至无法击败基于常识的基准方法。同样,这是因为卷积神经网络在输入时间序列的所有位置寻找模式,它并不知道所看到某个模式的时间位置(距开始多长时间,距结束多长时间等)。
对于这个具体的预测问题,对最新数据点的解释与对较早数据点的解释应该并不相同,所以卷积神经网络无法得到有意义的结果。卷积神经网络的这种限制对于 IMDB 数据来说并不是问题,因为对于与正面情绪或负面情绪相关联的关键词模式,无论出现在输入句子中的什么位置,它所包含的信息量是一样的。

将一维 CNN 作为预处理步骤

要想结合卷积神经网络的速度和轻量与 RNN 的顺序敏感性,一种方法是在 RNN 前面使用一维卷积神经网络作为预处理步骤(见图 6-30)。对于那些非常长,以至于 RNN 无法处理的序列(比如包含上千个时间步的序列),这种方法尤其有用。卷积神经网络可以将长的输入序列转换为高级特征组成的更短序列(下采样)。然后,提取的特征组成的这些序列成为网络中 **RNN **的输入
image.png
这种方法在研究论文和实际应用中并不多见,可能是因为很多人并不知道。这种方法非常有效,应该被更多人使用。

我们尝试将其应用于温度预测数据集。因为这种方法允许操作更长的序列,所以我们可以查看更早的数据(通过增大数据生成器的 lookback 参数)或查看分辨率更高的时间序列(通过减小生成器的 step 参数)。这里我们任意地将 step 减半,得到时间序列的长度变为之前的两倍,温度数据的采样频率变为每 30 分钟一个数据点。 本示例复用了之前定义的 generator 函数。

  1. model = Sequential()
  2. model.add(layers.Conv1D(32, 5, activation='relu',
  3. input_shape=(None, float_data.shape[-1])))
  4. model.add(layers.MaxPooling1D(3))
  5. model.add(layers.Conv1D(32, 5, activation='relu'))
  6. model.add(layers.GRU(32, dropout=0.1, recurrent_dropout=0.5))
  7. model.add(layers.Dense(1))
  8. model.summary()
  9. model.compile(optimizer=RMSprop(), loss='mae')
  10. history = model.fit_generator(train_gen,
  11. steps_per_epoch=500,
  12. epochs=20,
  13. validation_data=val_gen,
  14. validation_steps=val_steps)

image.png
从验证损失来看,这种架构的效果不如只用正则化 GRU,但速度要快很多。它查看了两倍的数据量,在本例中可能不是非常有用,但对于其他数据集可能非常重要。

小结

  • 二维卷积神经网络在二维空间中处理视觉模式时表现很好,与此相同,一维卷积神经网络在处理时间模式时表现也很好。对于某些问题,特别是自然语言处理任务,它可以替代 RNN,并且速度更快。
  • 通常情况下,一维卷积神经网络的架构与计算机视觉领域的二维卷积神经网络很相似,它将 Conv1D 层和 MaxPooling1D 层堆叠在一起,最后是一个全局池化运算或展平操作。
  • 因为 RNN 在处理非常长的序列时计算代价很大,但一维卷积神经网络的计算代价很小,所以在 RNN 之前使用一维卷积神经网络作为预处理步骤是一个好主意,这样可以使序列变短,并提取出有用的表示交给 RNN 来处理。