菜鸟学NLP(二)

关于torchtext

之前介绍过torchtext,在Pytorch-tutorial-学习(四)中,本以为可以省很多事,但用户体验太差….(也可能是我太菜).

它主要的流程是,先定义一个Field,在定义的同时可以包括各种文本预处理操作,比如分词、padding、初始化,固定句长等.然后是将每一条文本实例化(torchtext.data.example),实例化的同时会作用上之前定义好的Field.此时,就算是得到了一个datasets.对之前的Field进行操作,它可以生成关于这些语句的一个vocab,通过Field.build_vocab(datasets).常规的pytorch处理最后会处理成trainloader,这里也会有一步操作,torchtext.data.BucketIterator(dataset,batch_size,shuffle)生成迭代器,可以设置batch_size,也可以设置shuffle.差不多了.

反正我下来体验挺不好的.
所以放弃了,还是老老实实子集处理吧,还能夯实基础

Seq2Seq实验

文本预处理

手上的是中英文文本存在一个text文件中,一行一句话,先是英文,然后用’\t’隔开,然后是中文.
很显然,现在我们这里的源语言是英文,目标语言是中文.
列举我们要做的操作

  1. 分词
  2. 去符号
  3. 构建词表
  4. 数字化

实操记录

实操了一把seq2seq,首先看了一下EncoderRNN的输出,两个变量一个是output一个是hidden.
output的shape是[batch,sequence,hidden_dim],而hidden的形状是[batch,1,hidden_dim]
也就是说,每个时间点的输出都会被记录下来,而hidden表示的最后一个时间点的hidden_state.

还有就是实操验证了一下,hidden等于最后一个output,这也是在之前有提到过的.hidden实际上是与output相等的,只不过每一次我们只记录下了output,而将hidden覆盖了.

1
2
3
4
5
6
7
8
a=torch.ones((1,10)).long()
length=[10]
e=EncoderRNN(10,20)
o,h=e(a,length)
print(o.shape)#[1,10,20]
print(h.shape)#[1,1,20]
print(o)
print(h)

pic1
这张图里包含了o里最后一个时间点的输出和h的值。可以看到两者是相等的.


P.S.一个好玩的现象是,这里的a的形状是(1,10),而不是我本来认为的(1,10,1).当我采用(1,10,1)输入到embedding层中会报错,也就是说,embedding层接受的输入就是二维的形式,且只要把每个batch里的每个单词视为一个标量就行了.(是我多此一举了)


SeqAttention

今天终于把attention的代码实现了,奈何数据量小,而且没有用预训练,效果一般…
总结一下这次遇到的问题.

Embedding层

之前写过embedding的代码,但是还是第一次调用torch里的embedding layer.embedding layer接收的数据形式为(batch_size,sequence_length),本来我以为后面要多一维,后来发现其实这是多此一举.输入时(batch_size,Sequence_length),输出是(batch_size,Sequence_length,hidden_size).可以看到在embedding layer的时候这个维度还是正常的.

Tensor形式

一般我们见到的(CV中)tensor的形式是(batch,channel,w,h),但在pytorch的NLP中并非如此,一些RNN组件默认接受的的tensor形式是(Sequence_length×Batch×hidden_side),也就是说Batch不是第一维度的.当然pytorch也很人性,在RNN组件中增加了Batch_first的设置.所以在使用Pytorch的时候,要注意许多换维度的情况.这里就会用到transpose,transpose(0,1)能将第0维与第1维互换.

Pack_Padded_Sequence和Pad_Packed_Sequence

之前的许多RNN的训练形式是都是用for循环的方式,将数据序列中的数据反复输入倒RNN中.而用Pack_Padded_Sequence和Pad_Packed_Sequence可以简化这个过程.因为我们在预处理的时候,往往会处理一句话将其Pad为等长的序列.所以我们在模型的输入端拿到的就是Padded_Sequence.当要输入到RNN的时候,我们只要将有效长度的数据输入到RNN中即可,而后续的Pad是无效的可以不输入到RNN中.所以我们在输入到RNN之前需要Pack操作,它是对Padded_sequence做压缩操作,压缩到有效长度.而这个有效长度是多少,我们往往会在输入的时候就告诉这个长度是多少(在dataloader的时候会统计).Pack_padded_Sequence希望接收到的一个batch的句子,是经过长度由大到小排序过的,在Pack_padded_sequence中会默认进行排序.在经过RNN之后,我们得到的输出(output)还是Pack_Padded_Sequence形式的数据,此时需要变成tensor形式或者说是带pad的tensor数据通过Pad_Packed_Sequence.(好像发现从RNN中输出的hidden的形式仍是tensor数据,可能是因为hidden一个batch里的数据的sequence_length都是1.)

Attention layer

如果用一般的RNN或许可以用Pack_Padded_Sequence和Pad_Packed_Sequence操作.但是我们要用到Attention,只能用for循环.在Encoder的RNN部分,我们可以采用上述操作.但是在Decoder的RNN部分,由于要计算attention,即将待求状态$h_t$的前一个状态$h_{t-1}$与Encoder的输出output里的每个时间点隐状态进行点积或者其他的一些操作,计算出$h_{t-1}$与Encoder中各个时间点的hidden state的关联程度(权重),然后将其作为一种权重,对Encoder_output进行加权求和参与到求$h_t$的过程中.
在Attention的计算过程中,我们需要输入Decoder_Pre_hidden(1×batch_size×hidden_size),和Encoder_output(sequence_length×batch_size×hidden_size).这里有时会需要加入矩阵变换,但是输入的数据是3维的,我们不能用Linear,因为Linear的输入形式是(batch_size,hidden_size).所以这里我才用的是矩阵运算.直接用matmul(W)


区分bmm,matmul和mm,mul
bmm是在batch上进行的矩阵相乘.(batch_wise).这个操作是batch_first,而且要求两个数据在batch维度上的大小相同.

1
2
3
4
5
>>> batch1 = torch.randn(10, 3, 4)
>>> batch2 = torch.randn(10, 4, 5)
>>> res = torch.bmm(batch1, batch2)
>>> res.size()
torch.Size([10, 3, 5])

matmul则是可以进行自动补全(广播),当A(C×Q×P×M),B(M×N),则A.matmul(B)得到的形式为(C×Q×P×N).

mm则只能进行2维的矩阵乘法操作,A(m×n),B(n×p), A.mm(B)得到形式为m×p

mul则是对应位数字相乘,A(C×Q×P×M),B(C×Q×P×M),则A.mul(B)得到的形式为(C×Q×P×M)


其他

首先是Tanh激活函数
pytorch中这tanh激活函数的输入为(N, ),这里意味着后面的维度是任意的.其输出也是(N, * ).
其次是损失函数,这里用的是log_softmax+NLLLoss,事实上log_softmax+NLLLoss等价于cross_entropy.

-------------本文结束感谢您的阅读-------------

本文标题:菜鸟学NLP(二)

文章作者:Yif Du

发布时间:2019年04月05日 - 00:04

最后更新:2019年04月10日 - 12:04

原始链接:http://yifdu.github.io/2019/04/05/菜鸟学NLP(二)/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。