第7.2节 时序数据
各位朋友大家好,欢迎来到月来客栈,我是掌柜空字符。
本期推送内容目录如下,如果本期内容对你有所帮助,欢迎点赞、转发支持掌柜!
7.2 时序数据 7.2.1 时序图片 7.2.2 基于RNN的图片分类 7.2.3 时序文本 7.2.4 基于RNN的文本分类 7.2.5 小结
7.2 时序数据
在第7.1节内容中,笔者详细介绍了RNN模型的原理及使用场景,即对时序特征进行特征提取,因此在本节内容中笔者将通过两个实际的案例来介绍RNN的具体使用方式。不过在正式介绍之前我们需要明白一点的是,所谓时序数据并不是一定要具有时间上的概念,只要是包含前后的先后顺序并且打乱这个顺序就改变了样本的属性,那么这样的数据便都可以被称之为时序数据。
7.2.1 时序图片
虽然对于图像处理来说采用卷积操作是最合理的一种方式,但我我们仍旧可以将一张图片看成是时序数据并通过RNN来对其进行特征提取并完成后续的分类任务,而构造成时序数据的角度便是将其按照行或列进行分割。
如图7-5所示,左侧为原始图片,右侧为被垂直分割成4部分后的图片。此时我们可以发现,对于图7-5右侧的图片来说,其分割后的每一列都可以看成是一个每个时刻对应的状态,并且如果列与列之间的顺序发生了改变,那么将会改变该图片对应的原始属性。因此,在按照这样的分割方式操作后,每张图片均可以看成是一个序列样本。当然,除了以垂直的方式进行分割外也可以按照水平的方式进行分割。
以FashionMNIST数据集为例,其原始形状为,如果我们以垂直方式对其进行分割,那么便可以通过一个包含有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 - INFO: [train.py][75] Epochs[1/5]--batch[0/938]--Acc: 0.0156--loss: 2.3238
2 - INFO: [train.py][75] Epochs[1/5]--batch[50/938]--Acc: 0.4688--loss: 1.1348
3 - INFO: [train.py][75] Epochs[1/5]--batch[100/938]--Acc: 0.5625--loss: 1.0453
4 - INFO: [train.py][75] Epochs[1/5]--batch[150/938]--Acc: 0.7188--loss: 0.8871
5 ......
6 - INFO: [train.py][75] Epochs[5/5]--batch[800/938]--Acc: 0.8438--loss: 0.4105
7 - INFO: [train.py][75] Epochs[5/5]--batch[850/938]--Acc: 0.7969--loss: 0.5822
8 - INFO: [train.py][75] Epochs[5/5]--batch[900/938]--Acc: 0.875--loss: 0.3218
9 - INFO: [train.py][80] Epochs[5/5]--Acc on test 0.8622
从上述结果可以看出,使用RNN模型来对FashionMNIST数据集进行分类在5轮迭代后在测试集上也能取得不错的效果,但是相较于卷积网络还是稍有差距。
7.2.3 时序文本
在第7.1节内容,笔者多次提到文本数据是一种最直观的时序数据,因为同样的字以不同的顺序出现便表示不同的含义,所以在对文本数据进行特征提取时一定需要考虑其时序性。由于文本数据不能直接输入到模型中,因此我们需要一种向量化手段来将文本转换为向量。在深度学习中,一种最简单的文本向量化表示方法就是采用One-hot来进行转换,见第3.5.4节内容。
具体地,对于文本数据来说:①首先将训练集中的所有文本进行切分(Tokenize)处理,切分的粒度可以是词粒度也可以是字粒度,此处我们先以字粒度进行介绍;②然后以每个词在训练集中出现的词频排序,并选择前K个词作为词表;③最后,以每个词在词表中的序号用One-hot的形式来对其进行表示。
例如,现在有两个样本其每个字在词表中出现的顺序分别为[0,6,7]
和[2,5,1]
,且词表的长度为10,即索引为0~9,则其通过One-hot表示后的结果为:
1 if __name__ == '__main__':
2 x = torch.randint(0, 10, [2, 3], dtype=torch.long)
3 x_one_hot = torch.nn.functional.one_hot(x, num_classes=10)
4 print(x, x_one_hot)
5
6 tensor([[0, 6, 7],[2, 5, 1]])
7 tensor([[[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
8 [0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
9 [0, 0, 0, 0, 0, 0, 0, 1, 0, 0]],
10 [[0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
11 [0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
12 [0, 1, 0, 0, 0, 0, 0, 0, 0, 0]]])
在上述代码中,第2行是随机生成两个样本在词表中的索引值;第3行是将每个样本的索引值转换为对应的One-hot表示形式,即最终转换后的结果如第7~12行所示,其中num_classes
表示词表的长度。此时可以看出,原始样本中的每个字都使用了一个长度为10的One-hot向量来对其进行表示。
同时,由于对于文本数据来说每个样本的长度都不尽相同,因此需要进行特殊处理。根据RNN模型的原理可知,我们需要保证在每个小批量内部所有样本长度相同(不同小批量间可以不同),因此一种常见的处理方式便是以其中最长的样本为标准对其它样本进行填充(Padding)处理。当然我们也可以任意指定一个长度,对于超出该长度的部分进行截断处理,小于该长度的部分进行填充处理。
为你认可的知识付费,欢迎订阅本专栏阅读更多优质内容!