序列到序列(seq2seq)是nlp任务中重要的一类,它和其他任务的根本区别在于它以序列作为处理对象,而序列由彼此独立又相互联系的单词组成,为了处理这种关系,需要在层与层之间记忆和传递状态,以表达单词之间的联系,深度学习使用rnn及rnn的变种(lstm,gru)解决这一问题。
这篇博客是对https://pytorch.org/tutorials/beginner/nlp/sequence_models_tutorial.html#sphx-glr-beginner-nlp-sequence-models-tutorial-py中一些问题的解惑,可以配合使用,有其他不理解的也欢迎评论讨论。
本实验实现一个单词标注器,即单词序列->词性序列。
("The dog ate the apple".split(), ["DET", "NN", "V", "DET", "NN"]),
The dog ate the apple
冠词,名词,动词,冠词,名词。
可以发现,已知前文有助于推断下文,例如:已知前一个单词是冠词,后一个单词就很可能是名词。
class LSTMTagger(nn.Module):
def __init__(self,VOC,EMB,HID,TAG):
super(LSTMTagger,self).__init__()
self.embedding=nn.Embedding(VOC,EMB)
self.lstm=nn.LSTM(EMB,HID)
self.linear=nn.Linear(HID,TAG)
def forward(self,sentence):
emb=self.embedding(sentence)
out,hidden=self.lstm(emb.view(len(sentence),1,-1))
scores=F.log_softmax(self.linear(hid.view(len(sentence),-1)),dim=1)
return scores
embedding层的参数是<字母表长度,embedding向量长度>,输入是index序列,输出是embedding序列。
lstm层的参数是
有迷惑性的是lstm层的输入,根据lstm的结构,输入有两个:隐层向量和词向量,输出是也有两个:out,hidden。
遵循单次训练原则,即一次实验用多少,size就填多少。
词向量是一个三维tensor,官方文档里定义为
所有的rnn模型都有两种训练方法,一种是一次性填充一个序列,每个单词的填充和状态在不同rnn层之间的传递都是自动的,最终的输出一个二元组(out,hidden),分别表示每一层的输出和最后一层的输出,一般来说,out[0]=hidden。
另一种是每次填充一个单词,手动完成状态在不同rnn层之间的传递过程,如果希望自定义层的功能(例如attention),这种用法就是必不可少的。
本次实验使用第一种用法,那么输入词向量的size就是
lstm有两个隐层:C和H,分别表示细胞长期状态和上一次输出,需要传入一个
Size=(2,hidden)的tensor,在下一个实验中,我们使用gru,它是lstm的变体,没有C层,则只需要传入一个长度为hidden的隐层向量,这是单次训练的hidden输入,无论是每次填充一个序列,还是每次填充一个单词,都只传一个隐层向量。
如果不手动传,则自动使用全0tensor。
out,hidden=self.lstm(emb.view(len(sentence),1,-1))
其中out表示本次训练的所有输出,hidden表示本次训练的最后一个输出。