7.2 时序数据#
在7.1节内容中,我们详细介绍了RNN模型的原理及使用场景,即对时序特征进行特征提取,因此在本节内容中我们将通过两个实际的案例来介绍RNN的具体使用方式。不过在正式介绍之前我们需要明白一点的是,所谓时序数据并不是一定要具有时间上的概念,只要是包含前后的先后顺序并且打乱这个顺序就改变了样本的属性,那么这样的数据便都可以被称之为时序数据。
7.2.1 时序图片#
虽然对于图像处理来说采用卷积操作是最合理的一种方式,但我们仍旧可以将一张图片看成是时序数据并通过RNN来对其进行特征提取并完成后续的分类任务,而构造成时序数据的方法便是将其按照行或列进行分割。

如图7-5所示,左侧为原始图片,右侧为被垂直分割成4部分后的图片。此时我们可以发现,对于图7-5右侧的图片来说,其分割后的每一列都可以看成是每个时刻对应的状态,并且如果列与列之间的顺序发生了改变,那么将会改变该图片对应的原始属性。因此,在按照这样的分割方式操作后,每张图片均可以看成是一个序列样本。当然,除了以垂直的方式进行分割外也可以按照水平的方式进行分割。
以FashionMNIST数据集为例,其原始形状为$28\times28$,如果我们以垂直方式对其进行分割,那么便可以通过一个包含有28个时刻,每个时刻为一个28维的向量来对其进行表示。
7.2.2 基于RNN的图片分类#
在清楚了时序图片的构造方式后,下面我们再来介绍如何通过RNN来完成FashionMNIST数据集的分类任务。以下完整示例代码可以参见Code/Chapter07/C02_RNNImgCla/FashionMNISTRNN.py文件。
1. 前向传播
由于torchvision中的datasets模块已经将FashionMNIST数据集处理成了[batch_size, 1, width, high]的形式,所以我们只需要压缩掉通道这个维度,然后将width和high分别理解成步长和输入维度即可,并不需要进行特殊处理。因此我们直接定义相应的前向传播过程,示例代码如下所示:
1 class FashionMNISTRNN(nn.Module):
2 def __init__(self, input_size=28, hidden_size=128,
3 num_layers=2, num_classes=10):
4 super(FashionMNISTRNN, self).__init__()
5 self.rnn = nn.RNN(input_size, hidden_size,nonlinearity='relu',
6 num_layers=num_layers, batch_first=True)
7 self.classifier = nn.Sequential(LayerNormalization(hidden_size),
8 nn.Linear(hidden_size, hidden_size),nn.ReLU(inplace=True),
9 nn.Linear(hidden_size, num_classes))
10
11 def forward(self, x, labels=None):
12 x = x.squeeze(1)
13 x, _ = self.rnn(x)
14 logits = self.classifier(x[:, -1].squeeze(1))
15 if labels is not None:
16 loss_fct = nn.CrossEntropyLoss(reduction='mean')
17 loss = loss_fct(logits, labels)
18 return loss, logits
19 else:
20 return logits在上述代码中,第2~3行用于指定相关的模型参数。第5~6行为实例化一个RNN模型,其中nonlinearity用于指定RNN中的激活函数。第7~9行为最后的分类层,其中LayerNormalization为6.4节中介绍到的层归一化方法。第11~20行便是整个前向传播计算过程,其中第12行表示将[batch_size, 1, width, high]压缩成[batch_size, width, high],第13行是RNN的计算结果此时x的形状为[batch_size, time_steps, hidden_size],第14行是取最后一个时刻的输出并压缩成[batch_size, hidden_size]的形状。
此时,我们可以通过如下代码来测试上述模块:
1 if __name__ == '__main__':
2 model = FashionMNISTRNN()
3 x = torch.rand([32, 1, 28, 28])
4 y = model(x)
5 print(y.shape)在上述代码运行结束后便可以得到如下所示结果:
1 torch.Size([32, 10])2. 模型训练
在这里我们将继续使用4.5节中介绍到的FashionMNIST数据集因此不再对相关内容进行赘述。在前面各项工作都准备完毕之后便可以进一步实现模型的训练过程。由于这部分代码在之前也已经多次介绍过程,因此这里也不再赘述,各位读者直接参考源码即可。
最后,在对网络模型进行训练时将会得到类似如下的输出结果:
1 Epochs[1/5]--batch[0/938]--Acc: 0.0156--loss: 2.3238
2 Epochs[1/5]--batch[50/938]--Acc: 0.4688--loss: 1.1348
3 Epochs[1/5]--batch[100/938]--Acc: 0.5625--loss: 1.0453
4 Epochs[1/5]--batch[150/938]--Acc: 0.7188--loss: 0.8871
5 ......
6 Epochs[5/5]--batch[800/938]--Acc: 0.8438--loss: 0.4105
7 Epochs[5/5]--batch[850/938]--Acc: 0.7969--loss: 0.5822
8 Epochs[5/5]--batch[900/938]--Acc: 0.875--loss: 0.3218
9 Epochs[5/5]--Acc on test 0.8622从上述结果可以看出,使用RNN模型来对FashionMNIST数据集进行分类在5轮迭代后在测试集上也能取得不错的效果,但是相较于卷积网络还是稍有差距。
7.2.3 时序文本#
在7.1节内容,我们多次提到文本数据是一种最直观的时序数据,因为同样的字以不同的顺序出现便表示不同的含义,所以在对文本数据进行特征提取时一定需要考虑其时序性。由于文本数据不能直接输入到模型中,因此我们需要一种向量化手段来将文本转换为向量。在深度学习中,一种最简单的文本向量化表示方法就是采用One-hot来进行转换,见3.5.4节内容。