Neural Machine Translation with RNNs
第四个assignment 搭建了一个NMT,与之前在课程中学到的是一样的,架构如下
完整代码见我的GitHub
环境搭建:
- 由于下载很慢,没有成功,因此我选择了自己已有的环境(python 3.7 torch 1.4.0),根据 local_env.yml 文件,下载之前没有的库:docopt
- 运行过程中,有assert判断torch版本,直接注释即可
- 在nmt_model.py 文件中的 step 函数中,对得到的将pad位置的注意力分数改为负无穷,这句代码中使用了masked_fill_()函数的参数要求有所变化,我使用的torch版本中要求第一个参数为bool类型,将其改为
e_t.data.masked_fill_(enc_masks.bool(), -float('inf'))
注:若检验程序未通过,可以看看输入的报错,然后打印计算过程中矩阵的维度,判断是否出错,大部分bug根据维度就能找到
(a) 识别出batch中最长的句子,并将其他句子pad成相同长度。
只需要遍历求出最长句子的长度,然后使用pad_token将其他句子padding成一样长即可
max_len = max(len(sen) for sen in sents)
for sen in sents:
sents_padded.append(sen + [pad_token] * (max_len-len(sen)))
(b) 实现 model embeddings.py 中的 __init__
使用 nn.Embedding(vocab_len, embedding_size, pad_idx) 即可
self.source = nn.Embedding(len(vocab.src), self.embed_size,src_pad_token_idx)
self.target = nn.Embedding(len(vocab.tgt), self.embed_size,src_pad_token_idx)
下面就正式开始搭建NMT神经网络了
(c) 实现 nmt model.py 中的 __init__
self.encoder = nn.LSTM(embed_size, self.hidden_size,dropout = self.dropout_rate,bidirectional=True)
self.decoder = nn.LSTMCell(embed_size + self.hidden_size, self.hidden_size) #每个时间步的输入为上一时间步得到的o_(t-1)和本时间步的词嵌入输入
self.h_projection = nn.Linear(self.hidden_size * 2, self.hidden_size,bias = False) #将encoder两个hidden拼接转化为decoder的初始hidden (2h,1)->(h,1)
self.c_projection = nn.Linear(self.hidden_size*2, self.hidden_size,bias = False) #同h_projection
self.att_projection = nn.Linear(self.hidden_size*2, self.hidden_size,bias = False) #multiplicative attention,将encoder每个时间步的输出(2h,1),转化为(h,1)
self.combined_output_projection = nn.Linear(self.hidden_size*3, self.hidden_size,bias = False) #将u_t (batch_size,3h) 转化为 v_t(batch_size,h)
self.target_vocab_projection = nn.Linear(self.hidden_size,len(vocab.tgt),bias = False)#将hidden(h,1)转化为(vocab,1)
self.dropout = nn.Dropout(self.dropout_rate)
- 参数看代码中注释,符号与pdf对应
(d) 实现nmt_model.py 中的 encode
X = self.model_embeddings.source(source_padded) #获取源句子词嵌入
X = pack_padded_sequence(X,source_lengths) #相当于压缩
enc_hiddens,(last_hidden,last_cell) = self.encoder(X) #encoder
enc_hiddens = pad_packed_sequence(enc_hiddens,batch_first = True) #(b, src_len, h*2) #相当于解压,注意返回值为一个元组,我们需要第一个元素
init_decoder_hidden = self.h_projection(torch.cat((last_hidden[0],last_hidden[1]),dim = 1)) #拼接后转化
init_decoder_cell = self.c_projection(torch.cat((last_cell[0],last_cell[1]),dim = 1)) #拼接后转化
dec_init_state = (init_decoder_hidden,init_decoder_cell)
### END YOUR CODE
return enc_hiddens[0], dec_init_state
- 注意nn.LSTM的输出,pack_padded_sequence(),pad_packed_sequence()的使用
- 通过检查
(e) 实现nmt_model.py 的 decode
enc_hiddens_proj = self.att_projection(enc_hiddens) #计算W_att * h_encoder,在step函数中用于计算注意力
Y = self.model_embeddings.target(target_padded) #target sentence
for Y_t in torch.split(Y,1):
Y_t = torch.squeeze(Y_t, 0)
Ybar_t = torch.cat((Y_t,o_prev),dim = 1)
dec_state,o_t,_ = self.step(Ybar_t, dec_state, enc_hiddens, enc_hiddens_proj, enc_masks)# see function step
combined_outputs.append(o_t) #保存每个时间步的最终输出(hidden与encoder每个时间步加权 拼接)用于最后预测单词
o_prev = o_t #
combined_outputs = torch.stack(combined_outputs,dim = 0)
- 按照代码中的提示写,需要理解一些函数的用法(split,cat,squeeze)
- step是下一题的任务,这里将step当作api
- 检查通过
(f) 实现step
dec_state = self.decoder(Ybar_t,dec_state) #nn.LSTMcell的用法
e_t = torch.bmm(enc_hiddens_proj,torch.unsqueeze(dec_state[0],dim = 2)) #batch矩阵乘法,计算注意力分数
e_t = torch.squeeze(e_t,dim = 2)
alpha_t = F.softmax(e_t,dim =1) #计算注意力权值
alpha_t = torch.unsqueeze(alpha_t,dim=1)
a_t = torch.bmm(alpha_t, enc_hiddens) #encoder hidden的注意力加权
a_t = torch.squeeze(a_t, dim=1)
U_t = torch.cat((a_t,dec_state[0]),dim = 1)
V_t = self.combined_output_projection(U_t)
O_t = self.dropout(torch.tanh(V_t)) #每个时间步最终的输出
- 需要了解nn.LSTMcell的用法
- 需要知道一些函数的用法:bmm,squeeze,unsqueeze,F.softmax
- 根据代码中的提示一步一步写出即可,检查通过
(g) step 函数中使用了 generate_sent masks() ,解释其构造的masks对注意力计算有什么影响,解释为什么这是必要的。
if enc_masks is not None:
e_t.data.masked_fill_(enc_masks.bool(), -float('inf'))
- 该masks长度与句子长度相同,此段代码是将得到的注意力分数中,相应masks位置为1,即原来是pad的位置置为负无穷。由此可知,在使用softmax计算注意力分布时,pad位置的权重将会变为0。
- 注意力的作用就是根据一个查询向量来计算一组向量的加权值,而pad对问题解决机会没有帮助,因此要降低它们的权重,所以这是必要的
(h) 因为笔者使用的是windows,无法直接运行.sh,所以我这里直接查看run.sh文件,运行相关的命令。
- 运行
python vocab.py --train-src=./en_es_data/train.es --train-tgt=./en_es_data/train.en vocab.json
产生vocab文件 - 运行
python run.py train --train-src=./en_es_data/train.es --train-tgt=./en_es_data/train.en --dev-src=./en_es_data/dev.es --dev-tgt=./en_es_data/dev.en --vocab=vocab.json
进行本地训练
训练部分过程如下
(i) 由于笔者无在线的虚拟机来进行训练,因此这部分先放着。
(j) 三种计算注意力方式的优缺点
- dot product attention:
e
t
,
i
=
s
t
T
h
i
e_{t,i} = s_t^Th_i
et,i=stThi
- 优点:计算简单,参数少,计算量少
- 缺点:不够强大,可能对于注意力的捕获效果不太好
- multiplicative attention:
e
t
,
i
=
s
t
T
W
h
i
e_{t,i} = s_t^TWh_i
et,i=stTWhi
- 优点:对比第一种计算注意力的方式,增加了一个矩阵 W W W,让模型能够捕获更多信息
- 缺点:计算比第一个复杂,在复杂情况下效果没后一个好。
- additive attention: e t , i = v T ( W 1 h i + W 2 s t ) e_{t,i} = v^T(W_1h_i+W_2s_t) et,i=vT(W1hi+W2st)
- 优点:参数多,能够对复杂情况建模,捕获很多有用的信息
- 缺点:计算比前两者复杂
Analyzing NMT Systems
(a) 确定错误类型,提供理由,如何改进
i
.
\mathrm{i.}
i.specific linguistic construct
i
i
.
\mathrm{ii.}
ii. specific linguistic construct
i
i
i
.
\mathrm{iii.}
iii. specific model limitations(We can’t know the unkonw word)
改进:未知词可能是专有名词,我们可以使用复制机制,直接复制过来
i
v
.
\mathrm{iv.}
iv. specific model limitations
v
.
\mathrm{v.}
v. specific model limitations(the model may pay more attetion to the word ‘she’, so the result is “the women’s”)
v
i
.
\mathrm{vi.}
vi. specific model limitations(the model can’t distinguish the difference between French unit and American unit)
转载自:ZacBi’ github
(b)
(c) BLEU计算
i
.
\mathrm{i.}
i.
- c1:
p 1 = 3 5 p1 = \frac{3}{5} p1=53, p 2 = 2 4 p2 = \frac{2}{4} p2=42, c = 5, r ∗ = 5 r^* = 5 r∗=5, B P = 1 BP = 1 BP=1
B L E U = 1 × e x p ( 0.5 × log 0.6 + 0.5 × log 0.5 ) = e x p ( − 0.11092 + − 0.15051 ) = 0.7699 BLEU = 1 × exp(0.5×\log 0.6+0.5×\log 0.5) =exp(-0.11092+-0.15051)=0.7699 BLEU=1×exp(0.5×log0.6+0.5×log0.5)=exp(−0.11092+−0.15051)=0.7699 - c2:
p 1 = 4 5 p1 = \frac{4}{5} p1=54, p 2 = 2 4 p2 = \frac{2}{4} p2=42,c= 5, r ∗ = 5 r^* = 5 r∗=5, B P = 1 BP = 1 BP=1
B L E U = 1 × e x p ( 0.5 × log 0.8 + 0.5 × log 0.5 ) = e x p ( − 0.04845 + − 0.15051 ) = 0.81958 BLEU = 1 × exp(0.5×\log 0.8+0.5×\log 0.5) =exp(-0.04845+-0.15051)=0.81958 BLEU=1×exp(0.5×log0.8+0.5×log0.5)=exp(−0.04845+−0.15051)=0.81958 - 从BLEU来看,c2更好,在我看来也是c2好。
i i . \mathrm{ii.} ii.只基于r1计算,
- c1:
p 1 = 2 5 p1 = \frac{2}{5} p1=52, p 2 = 2 4 p2 = \frac{2}{4} p2=42, c = 5, r ∗ = 5 r^* = 5 r∗=5, B P = 1 BP = 1 BP=1
B L E U = 1 × e x p ( 0.5 × log 0.4 + 0.5 × log 0.5 ) = e x p ( − 0.19897 + − 0.15051 ) = 0.7056 BLEU = 1 × exp(0.5×\log 0.4+0.5×\log 0.5) =exp(-0.19897+-0.15051)=0.7056 BLEU=1×exp(0.5×log0.4+0.5×log0.5)=exp(−0.19897+−0.15051)=0.7056 - c2:
p 1 = 2 5 p1 = \frac{2}{5} p1=52, p 2 = 1 4 p2 = \frac{1}{4} p2=41,c= 5, r ∗ = 5 r^* = 5 r∗=5, B P = 1 BP = 1 BP=1
B L E U = 1 × e x p ( 0.5 × log 0.4 + 0.5 × log 0.25 ) = e x p ( − 0.19897 + − 0.30103 ) = 0.6065 BLEU = 1 × exp(0.5×\log 0.4+0.5×\log 0.25) =exp(-0.19897+-0.30103)=0.6065 BLEU=1×exp(0.5×log0.4+0.5×log0.25)=exp(−0.19897+−0.30103)=0.6065 - c1的分数更高,我认为c1并不是一个好的翻译
i
i
i
.
\mathrm{iii.}
iii.
我觉得只使用一个参考翻译会有很大的不确定性,就比如上面的例子,去掉r2使得c2的分数变低,如果去掉的是r1,那么c1的分数将会降低。因此较少的参考翻译会增加偶然性,我们知道,虽然一个句子有多种不同的说法,但其中有一些词或短语是相同的,一个好的翻译应该尽可能与这些词或短语overlap,因此较多的参考翻译对评价结果更有利。
i v . \mathrm{iv.} iv.
- 优点:
- 自动快速,一个好的自动评价方式对于一个任务有极大的促进作用
- 在大规模的评测中,BLEU与人类评价近似线性相关。
- 缺点:BLEU考察了备选翻译的忠实度和流利度
- 而忠实度方面,没有考虑单词的不同形似,比如上面例子中 make 和 makes(是否可以用字符级别来弥补?),此外还未考虑近义词之间的相似
- 需要较多的参考翻译,不然会出现上面的例子
- 不考虑语义和句法
总结
- 本次实验学习如何使用Pytorch搭建一个神经网络
- 学习了很多常用的函数
- 维度相关:squeeze,unsqueeze,view,cat,split,bmm,stack
- 网络单元:nn.LSTM、nn.LSTMcell、nn.Embedding、pack_padded_sequence、pad_packed_sequence、nn.Linear
- 学习了带有multiplicative attention的seq2seq 机器翻译模型
- seq2seq 的构建方式:encoder,decoder如何写
- attention 如何计算(带有batch)–>使用bmm函数