前文回顾
在前两篇文章新手的第一个 NLP 任务:文本分类(1)和新手的第一个 NLP 项目:文本分类(2)中,我们读取了数据、对数据进行了预处理和封装,并搭建了一个 CNN 模型。本文中,我们将 CNN 模型换为 RNN 模型。
数据的准备
同新手的第一个 NLP 任务:文本分类(1)一样,不再赘述。
基础 RNN 模型
有关 RNN 的知识可以参考我以前写的文章 PyTorch 折桂 11:CNN & RNN。
1 | from torch import nn, optim |
我们首先使用一层单向 RNN。RNN 网络生成两个张量:输出层与保存了历史信息的隐藏层。使用哪一个呢?这要具体问题具体分析。对于文本摘要类任务,一般使用保存了历史信息的隐藏层。
这里要注意隐藏层的维度:当 batch_first=True
时,隐藏层的维度为((num_layers * directions, BATCH_SIZE, HIDDEN_DIM)
);当 batch_first=False
时,隐藏层的维度为((num_layers * directions, SEQ_LENGTH, HIDDEN_DIM)
)。
因为这是一个单词单向的 RNN,所以第 0 维为 1;在将隐藏层进行全连接处理以前,先去除无用的第 0 维。
实例化 RNN 网络:
1 | EMBED_DIM = 128 |
损失函数、优化器、训练过程与前文一致,不再赘述。训练 10 个 epoch 后的结果如下:
1 | Epoch: 10 | Epoch Time: 1m 8s |
可以看到,模型过拟合了。下面我们改进一下这个 RNN 模型。
改进 RNN 模型
我们主要从以下两个方面进行改进:
- 改进词嵌入;
- 增加模型的复杂度(使用两层双向 LSTM);
- 增加正则化。首先,填充
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29class LSTM(nn.Module):
def __init__(self, vocab_size, embedding_dim, hidden_dim, n_layers,
bidirectional, dropout):
super(LSTM, self).__init__()
self.embed = nn.Embedding(vocab_size, embedding_dim, padding_idx=0)
self.lstm = nn.LSTM(embedding_dim,
hidden_dim,
num_layers=n_layers,
bidirectional=bidirectional,
dropout=dropout,
batch_first=True)
self.dropout = nn.Dropout(dropout)
self.num_directions = 2 if bidirectional else 1
self.fc = nn.Linear(hidden_dim * self.num_directions, 1)
def forward(self, x):
embedded = self.dropout(self.embed(x)) # (BATCH_SIZE, SEQ_LEN, EMBED_DIM)
output, (hidden, cell) = self.lstm(embedded)
# output: (BATCH_SIZE, SEQ_LENGTH, HIDDEN_DIM)
# hidden: (n_layers * num_directions, BATCH_SIZE, HIDDEN_DIM)
# cell: (n_layers * num_directions, BATCH_SIZE, HIDDEN_DIM)
hidden = self.dropout(torch.cat((hidden[-2, :, :], hidden[-1, :, :]), dim=1))
# hidden: (BATCH_SIZE, HIDDEN_DIM * 2)
return self.fc(hidden)<PAD>
应该恒为 0,所以我们在词嵌入层中加入padding_idx=0
条件。这里padding_idx
为 0 是因为我们在数据准备过程中将填充占位设为 0。
其次,将 RNN 层变成 LSTM 层。LSTM 模型的输出有三个,output, (hidden, cell)
,隐藏层与细胞状态在一个元组内。当 batch_first=True
时,隐藏层与细胞状态的维度为((num_layers * directions, BATCH_SIZE, HIDDEN_DIM)
);当 batch_first=False
时,隐藏层与细胞状态的维度为((num_layers * directions, SEQ_LENGTH, HIDDEN_DIM)
)。当方向为双向且层数多于 1 时,隐藏层与细胞状态的堆叠层次为:$[第一层正向,第一层反向,…,最后一层正向,最后一层反向]$。这里使用了两层双向 LSTM。我们需要最后一层的正向与反向隐藏层,并把它们拼接在一起。
最后,还加入了 dropout 正则化。LSTM 内部的 dropout 可以使用 dropout
声明,LSTM 与全连接层之间的 dropout 可以使用 nn.Dropout
层。
实例化这个 LSTM 模型。
1 | EMBED_DIM = 128 |
损失函数、优化器、训练过程与前面相同。最终的训练效果为:
1 | Epoch: 10 | Epoch Time: 7m 34s |
比前面的 CNN 效果稍好。下文中我们将使用 SOTA 的预训练模型 - BERT。
本文的代码可以在 https://github.com/vincent507cpu/nlp\_project/blob/master/text%20classification/02%20RNN.ipynb 查看。