1. 磐创AI-开放猫官方网站首页
  2. 系列教程
  3. PyTorch

PyTorch专栏(十九):序列模型和长短句记忆(LSTM)模型 | 文末开奖

本文是全系列中第23 / 24篇:Pytorch 专栏

PyTorch专栏(十九):序列模型和长短句记忆(LSTM)模型 | 文末开奖

作者 | News

编辑 | 奇予纪

出品 | 磐创AI团队出品

【磐创AI 导读】查看关于本专栏历史文章,请点击文末[阅读全文]查看本章历史文章,请点击下方蓝色字体进入相应链接阅读。


专栏目录:

第五章:PyTorch之文本篇
  • 使用Sequence2Sequence网络和注意力进行翻译


序列模型和长短句记忆(LSTM)模型


  • 前馈网络

之前我们已经学过了许多的前馈网络。所谓前馈网络, 就是网络中不会保存状态。然而有时这并不是我们想要的效果。在自然语言处理(NLP, Natural Language Processing) 中, 序列模型是一个核心的概念。
  • 序列模型

所谓序列模型, 即输入依赖于时间信息的模型。一个典型的序列模型是隐马尔科夫模型 (HMM, Hidden Markov Model)。另一个序列模型的例子是条件随机场 (CRF, Conditional Random Field)。
  • 循环神经网络
循环神经网络是指可以保存某种状态的神经网络。比如说, 神经网络中上个时刻的输出可以作为下个 时刻的输入的一部分, 以此信息就可以通过序列在网络中一直往后传递。对于LSTM (Long-Short Term Memory) 来说, 序列中的每个元素都有一个相应的隐状态PyTorch专栏(十九):序列模型和长短句记忆(LSTM)模型 | 文末开奖,该隐状态原则上可以包含序列当前结点之前的任一节点的信息。我们可以使用隐藏状态来预测语言模型中的单词, 词性标签以及其他。

1.Pytorch中的LSTM

在正式学习之前,有几个点要说明一下,Pytorch中 LSTM 的输入形式是一个 3D 的Tensor,每一个维度都有重要的意义,第一个维度就是序列本身,第二个维度是mini-batch中实例的索引,第三个维度是输入元素的索引,我们之前没有接触过mini-batch,所以我们就先忽略它并假设第二维的维度是1。如果要用”The cow jumped”这个句子来运行一个序列模型,那么就应该把它整理成如下的形式:

PyTorch专栏(十九):序列模型和长短句记忆(LSTM)模型 | 文末开奖

除了有一个额外的大小为1的第二维度。

此外, 你还可以向网络逐个输入序列, 在这种情况下, 第一个轴的大小也是1。

来看一个简单的例子。

  1. # Author: Robert Guthrie


  2. import torch

  3. import torch.nn as nn

  4. import torch.nn.functional as F

  5. import torch.optim as optim


  6. torch.manual_seed(1)

  1. lstm = nn.LSTM(3, 3) # 输入维度为3维,输出维度为3维

  2. inputs = [torch.randn(1, 3) for _ in range(5)] # 生成一个长度为5的序列


  3. # 初始化隐藏状态.

  4. hidden = (torch.randn(1, 1, 3),

  5. torch.randn(1, 1, 3))

  6. for i in inputs:

  7. # 将序列中的元素逐个输入到LSTM.

  8. # 经过每步操作,hidden 的值包含了隐藏状态的信息.

  9. out, hidden = lstm(i.view(1, 1, -1), hidden)


  10. # 另外我们可以对一整个序列进行训练.

  11. # LSTM第一个返回的第一个值是所有时刻的隐藏状态

  12. # 第二个返回值是最后一个时刻的隐藏状态

  13. #(所以"out"的最后一个和"hidden"是一样的)

  14. # 之所以这样设计:

  15. # 通过"out"你能取得任何一个时刻的隐藏状态,而"hidden"的值是用来进行序列的反向传播运算, 具体方式就是将它作为参数传入后面的 LSTM 网络.


  16. # 增加额外的第二个维度.

  17. inputs = torch.cat(inputs).view(len(inputs), 1, -1)

  18. hidden = (torch.randn(1, 1, 3), torch.randn(1, 1, 3)) # 清空隐藏状态.

  19. out, hidden = lstm(inputs, hidden)

  20. print(out)

  21. print(hidden)

  • 输出结果:

  1. tensor([[[-0.0187, 0.1713, -0.2944]],


  2. [[-0.3521, 0.1026, -0.2971]],


  3. [[-0.3191, 0.0781, -0.1957]],


  4. [[-0.1634, 0.0941, -0.1637]],


  5. [[-0.3368, 0.0959, -0.0538]]], grad_fn=<StackBackward>)

  6. (tensor([[[-0.3368, 0.0959, -0.0538]]], grad_fn=<StackBackward>), tensor([[[-0.9825, 0.4715, -0.0633]]], grad_fn=<StackBackward>))

2.例子:用LSTM来进行词性标注

在这部分, 我们将会使用一个 LSTM 网络来进行词性标注。在这里我们不会用到维特比算法, 前向-后向算法或者任何类似的算法,而是将这部分内容作为一个 (有挑战) 的练习留给读者, 希望读者在了解了这部分的内容后能够实现如何将维特比算法应用到 LSTM 网络中来。

该模型如下:输入的句子是PyTorch专栏(十九):序列模型和长短句记忆(LSTM)模型 | 文末开奖,其中PyTorch专栏(十九):序列模型和长短句记忆(LSTM)模型 | 文末开奖,标签的集合定义为 T,PyTorch专栏(十九):序列模型和长短句记忆(LSTM)模型 | 文末开奖为单词PyTorch专栏(十九):序列模型和长短句记忆(LSTM)模型 | 文末开奖的标签,用PyTorch专栏(十九):序列模型和长短句记忆(LSTM)模型 | 文末开奖表示对单词PyTorch专栏(十九):序列模型和长短句记忆(LSTM)模型 | 文末开奖词性的预测。
这是一个结构预测模型, 我们的输出是一个序列PyTorch专栏(十九):序列模型和长短句记忆(LSTM)模型 | 文末开奖, 其中PyTorch专栏(十九):序列模型和长短句记忆(LSTM)模型 | 文末开奖
在进行预测时, 需将句子每个词输入到一个 LSTM 网络中。将时刻 i 的隐藏状态标记为PyTorch专栏(十九):序列模型和长短句记忆(LSTM)模型 | 文末开奖,同样地, 对每个标签赋一个独一无二的索引 (类似 word embeddings 部分 word_to_ix 的设置). 然后就得到了PyTorch专栏(十九):序列模型和长短句记忆(LSTM)模型 | 文末开奖的预测规则:
PyTorch专栏(十九):序列模型和长短句记忆(LSTM)模型 | 文末开奖
即先对隐状态进行一个仿射变换, 然后计算一个对数 softmax, 最后得到的预测标签即为对数 softmax 中最大的值对应的标签. 注意, 这也意味着 A 空间的维度是|T|。

2.1 准备数据

  1. def prepare_sequence(seq, to_ix):

  2. idxs = [to_ix[w] for w in seq]

  3. return torch.tensor(idxs, dtype=torch.long)


  4. training_data = [

  5. ("The dog ate the apple".split(), ["DET", "NN", "V", "DET", "NN"]),

  6. ("Everybody read that book".split(), ["NN", "V", "DET", "NN"])

  7. ]

  8. word_to_ix = {}

  9. for sent, tags in training_data:

  10. for word in sent:

  11. if word not in word_to_ix:

  12. word_to_ix[word] = len(word_to_ix)

  13. print(word_to_ix)

  14. tag_to_ix = {"DET": 0, "NN": 1, "V": 2}


  15. # 实际中通常使用更大的维度如32维, 64维.

  16. # 这里我们使用小的维度, 为了方便查看训练过程中权重的变化.

  17. EMBEDDING_DIM = 6

  18. HIDDEN_DIM = 6

  • 输出结果:

  1. {'The': 0, 'dog': 1, 'ate': 2, 'the': 3, 'apple': 4, 'Everybody': 5, 'read': 6, 'that': 7, 'book': 8}

2.2 创建模型

  1. class LSTMTagger(nn.Module):


  2. def __init__(self, embedding_dim, hidden_dim, vocab_size, tagset_size):

  3. super(LSTMTagger, self).__init__()

  4. self.hidden_dim = hidden_dim


  5. self.word_embeddings = nn.Embedding(vocab_size, embedding_dim)


  6. # LSTM以word_embeddings作为输入, 输出维度为 hidden_dim 的隐藏状态值

  7. self.lstm = nn.LSTM(embedding_dim, hidden_dim)


  8. # 线性层将隐藏状态空间映射到标注空间

  9. self.hidden2tag = nn.Linear(hidden_dim, tagset_size)

  10. self.hidden = self.init_hidden()


  11. def init_hidden(self):

  12. # 一开始并没有隐藏状态所以我们要先初始化一个

  13. # 关于维度为什么这么设计请参考Pytoch相关文档

  14. # 各个维度的含义是 (num_layers, minibatch_size, hidden_dim)

  15. return (torch.zeros(1, 1, self.hidden_dim),

  16. torch.zeros(1, 1, self.hidden_dim))


  17. def forward(self, sentence):

  18. embeds = self.word_embeddings(sentence)

  19. lstm_out, self.hidden = self.lstm(

  20. embeds.view(len(sentence), 1, -1), self.hidden)

  21. tag_space = self.hidden2tag(lstm_out.view(len(sentence), -1))

  22. tag_scores = F.log_softmax(tag_space, dim=1)

  23. return tag_scores

2.3 训练模型

  1. model = LSTMTagger(EMBEDDING_DIM, HIDDEN_DIM, len(word_to_ix), len(tag_to_ix))

  2. loss_function = nn.NLLLoss()

  3. optimizer = optim.SGD(model.parameters(), lr=0.1)


  4. # 查看训练前的分数

  5. # 注意: 输出的 i,j 元素的值表示单词 i 的 j 标签的得分

  6. # 这里我们不需要训练不需要求导,所以使用torch.no_grad()

  7. with torch.no_grad():

  8. inputs = prepare_sequence(training_data[0][0], word_to_ix)

  9. tag_scores = model(inputs)

  10. print(tag_scores)


  11. for epoch in range(300): # 实际情况下你不会训练300个周期, 此例中我们只是随便设了一个值

  12. for sentence, tags in training_data:

  13. # 第一步: 请记住Pytorch会累加梯度.

  14. # 我们需要在训练每个实例前清空梯度

  15. model.zero_grad()


  16. # 此外还需要清空 LSTM 的隐状态,

  17. # 将其从上个实例的历史中分离出来.

  18. model.hidden = model.init_hidden()


  19. # 准备网络输入, 将其变为词索引的 Tensor 类型数据

  20. sentence_in = prepare_sequence(sentence, word_to_ix)

  21. targets = prepare_sequence(tags, tag_to_ix)


  22. # 第三步: 前向传播.

  23. tag_scores = model(sentence_in)


  24. # 第四步: 计算损失和梯度值, 通过调用 optimizer.step() 来更新梯度

  25. loss