1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 【一起入门NLP】中科院自然语言处理作业四:RNN+Attention实现Seq2Seq中英文机器翻译

【一起入门NLP】中科院自然语言处理作业四:RNN+Attention实现Seq2Seq中英文机器翻译

时间:2019-05-19 02:55:00

相关推荐

【一起入门NLP】中科院自然语言处理作业四:RNN+Attention实现Seq2Seq中英文机器翻译

这里是国科大自然语言处理的第四次作业,同样也是从小白的视角解读程序和代码,现在我们开始吧(今天也是花里胡哨的一天呢🤩)

目录

1.程序与实验说明实验要求程序说明2.知识概述2.1 序列生成问题Seq2Seq2.2 RNN+Attention 架构生成模型2.3 机器翻译2.4 GRU2.5 注意力机制3.数据数据来源数据处理4. 模型EncoderAttentionDecoderSeq2Seq5.训练6.测试测试效果为什么效果差demo展示7.疑问与思考🖤序列生成模型评价指标BLEU-精度ROUGE-召回率💛Pytorch中的pack和pad操作💚信息融合❤️mask机制Padding MaskSequence Mask💜beam search

1.程序与实验说明

实验要求

任选一个深度学习框架建立一个机器翻译模型,实现在IWSLT14 En-Zh数据集上进行的机器翻译序列生成任务需要大量的计算资源,公平起见,本作业会按照提交的代码判定成绩而不是模型的训练效果

事实证明老师的考虑是很周到的,我虽然完成了实验内容,但是模型的准确率实在是上不了台面☹️,这一点也会在后面进行说明

程序说明

代码:/download/qq_39328436/69026304

程序目录:

corpus中是IWSLT14 En-Zh数据集的原始语料以及经过预处理后的文件data中存储的是最终参与训练和测试的数据runs文件夹保存每次训练记录model.py中描述模型代码,比如encoder,decoder等translate-best.th:do_train为训练模块,do_test为测试模块,do_translate是一个小demo,输入英文句子会打印出损失最低的5个翻译结果。translate.py中translation_model.log记录训练过程中的损失

2.知识概述

2.1 序列生成问题Seq2Seq

深度学习中建模序列生成问题方法:构建一个联合的神经网络,以端到端的方式将一个序列化数据映射成另一个序列化数据。简称 Sequence-to-Sequence Generation (Seq2Seq)模型。主流的Seq2Seq模型通常基于Encoder-Decoder框架实现。Seq2Seq模型:

Encoder:将输入序列进行编码形成后继处理需要的输入表示形式生成式模型Decoder:根据编码端形成的输入表示和先前时刻产生成的输出tokens,生成当前输出token ( 编码端和解码端有各自词表,二者可相同或不同。解码端需处理集外词OOV,一般用UNK 代替)选择式模型Decoder:根据编码端形成的输入表示和先前时刻产生成的输出tokens,从输入端选择一个token作为输出 token ( 解码端和编码端词表相同选择-生成式模型Decoder:根据编码端形成的输入表示和先前时刻产生成的输出tokens,生成或从输入端选择当前输出token ( 编码端和解码端有各自词表,二者可相同或不同。解码端需处理集外词OOV,一般用UNK 代替,该方法可有效的处理输出端的OOV 问题)

Encoder和Decoder具体使用什么模型都是由研究者自己确定。比如:CNN/RNN/BiRNN/GRU/LSTM/transformer等。很明显本次实验机器翻译任务是生成式decoder。

2.2 RNN+Attention 架构生成模型

纯RNN的生成模型会有什么问题?

输入序列(x1,x2,x3)经过模型得到生成序列(y1,y2,y3),当模型翻译任意一个yi时,所用到的中间语义C都是同一个。而事实上,当我们翻译“杰瑞”时,英文单词“Jerry”应该比其他单词有更重要的影响,比如(Tom,0.3)(Chase,0.2)(Jerry,0.5)。

在RNN模型基础上加入Attention机制就能解决上面提到的这个问题了。

2.3 机器翻译

任务描述:

机器翻译是利用计算机把一种语言(源语言, source language) 翻译成另一种语言(目标语言, target language)的技术。神经机器翻译是序列生成问题,主流神经机器翻译模型有基于RNN的,基于CNN的和基于自注意力机制的。

神经机器翻译系统需要考虑的问题:

词汇表受限问题:考虑到计算的复杂度问题,在神经机器翻译模型中会使用一个受限词表,这样会导致很多单词成了词表外的OOV词。而这种OOV词在翻译时很难处理并且打破了句子结构,增加了语句的歧义性,因此,如何处理罕见词成为NMT领域非常必要的研究问题。翻译覆盖率问题:在**“seq2seq+attention”**框架下机器翻译过程中,翻译当前词汇的“注意力”与翻译在此之前的词汇的“注意力”是独立的,当前的操作不能从之前的翻译中获取alignment相关的信息,这样就导致了“过翻译” (Over-Translation:源端某些词被重复翻译若干次)和“欠翻译” (Under-Translation:源端某些词未被翻译)的问题,coverage机制通过在解码的过程中,保持对attention信号持续关注(利用),可以缓解过翻译和欠翻译问题。系统鲁棒性问题:神经网络能够对全局上下文进行建模,但对于局部变化过于敏感如,提升系统的容错性,一致性(鲁棒性)对用户体验十分重要。可采用对抗学习等训练方法提升系统的鲁棒性。

2.4 GRU

GRU(Gate Recurrent Unit)是循环神经网络(Recurrent Neural Network, RNN)的一种。和LSTM(Long-Short Term Memory)一样,也是为了解决长期记忆和反向传播中的梯度等问题而提出来的。

GRU与LSTM的区别如下:

2.5 注意力机制

注意力机制是神经网络中的一个加权求和的组件。输入是Q,K,输出是Att-V。Attention要回答的问题是:对于Q来说K有多重要?,重要性由输出V描述。Attention机制主要分为三个步骤,对应下图中的三个阶段。

计算F(Q,Ki):F为注意力打分函数,本质上该打分函数描述Q和K之间的关系,它可以是一个小型的神经网络。常见的打分函数有点积模型,缩放点积模型,双线性模型softmax(f(Q,Ki)):经过softmax之后会形成一个概率分布,也就是得到了权重加权求和:Att-V = 𝑎1ⅹK1+𝑎2ⅹK2+𝑎3ⅹK3+𝑎4ⅹK4+𝑎5ⅹK5

3.数据

数据来源

数据来源于小规模数据集:IWSLT14 En-Zh,包含了143920个训练样本,19989个验证样例和15992个测试样例。其中,训练集数据在train_zh.txt/train_en.txt中,验证数据在valid_zh.txt/valid_en.txt中,测试集数据在test_zh.txt/test_en.txt中。X_zh.txt与X_en.txt中的数据每一行是对齐的.

数据处理

在数据处理这一部分,需要将中英文语料按行合并到一起,每一行前部分是英文,通过一个制表符连接英文句子对应的中文。

按行合并两个txt文件本不是一件难事,但是简单的合并会导致出现大量下图中的数据:

这是因为这个数据集IWSLT14 En-Zh是一个人的现场演讲,其中会出现“(众人鼓掌)” 这样的话外音,为保证这些话外音不会影响数据导入需要将他们删除。

with open('train_en.txt', 'r') as fa: # 读取需要拼接的前面那个TXTwith open('train_zh.txt', 'r') as fb: # 读取需要拼接的后面那个TXTwith open('train.txt', 'w') as fc: # 写入新的TXTfor line in fa:fc.write(line.strip('\r\n')) # 用于移除字符串头尾指定的字符fc.write('\t')temp=fb.readline().replace('(鼓掌)', '')temp=temp.replace('(鼓掌声)', '')temp=temp.replace('(众人鼓掌)', '')temp=temp.replace('(热烈鼓掌)', '')temp=temp.replace('(观众鼓掌)', '')temp = temp.replace('(观众掌声)', '')fc.write(temp)

考虑到只能用笔记本cpu来跑代码,最后选取了其中15000条数据来训练。测试数据和验证数据都分别是2000条。

4. 模型

整个模型由Encoder、Attention及Decoder组成,外层用Seq2Seq统一包装。模型结构如下图所示:

编码器采用双向RNN,解码器采用单向RNN,Attention采用双线性Att。

Encoder

Encoder采用BiGRU结构

nn.embedding :将输入的句子映射为词向量。nn.GRU: bidirectional =true表示设置为双向GRU,输入输出需要pack、pad。nn.Dropout:讲输入张量部分元素设置为0,防止模型过拟合nn.Linear:线性Linear层,将GRU最后一个hidden state变换为decoder的初始hidden state输入nn.pad_packed_sequenceh,nn.pack_padded_sequence:实现对文本的填充和相互转化。在RNN网络中,文本的pad操作用于各文本长度的对齐;而pack操作用于实现文本序列数据的压缩。

class Encoder(nn.Module):def __init__(self,vocab_size,embed_size,enc_hidden_size,dec_hidden_size,dropout=0.2):super(Encoder,self).__init__()self.embed = nn.Embedding(vocab_size,embed_size)self.rnn = nn.GRU(embed_size,enc_hidden_size,batch_first=True,bidirectional=True)self.dropout = nn.Dropout(dropout)self.fc = nn.Linear(enc_hidden_size*2, dec_hidden_size)def forward(self,x,lengths):embedded = self.dropout(self.embed(x))packed_embedded = nn.utils.rnn.pack_padded_sequence(embedded,lengths,batch_first=True) packed_out, hid = self.rnn(packed_embedded) out,_ = nn.utils.rnn.pad_packed_sequence(packed_out,batch_first=True,total_length=max(lengths)) hid = torch.cat([hid[-2],hid[-1]],dim=1)# 将hid双向叠加 【batch, 2*enc_hidden_size】hid = torch.tanh(self.fc(hid)).unsqueeze(0) # 转为decoder输入hidden state 【1,batch,dec_hidden_size】return out,hid

Attention

步骤一:计算F(Q,Ki) 打分函数由nn.Linear双线性模型实现(双线性Attention)

步骤二:softmax F.softmax(atten,dim=2) 得到概率 步骤三:加权求和 torch.bmm(atten,context) masked_fill(mask,-1e6):atten做mask,对于source中pad的部分以及target的pad部分用很小的负数代替,以消除后面对softmax的概率影响torch.cat((context,output),dim=2):注意力输出和source state做信息融合concatecontext:encoder的gru hidden stateoutput:decoder的gru hidden statemask:在decoder中创建

class Attention(nn.Module):""" """def __init__(self,enc_hidden_size,dec_hidden_size):super(Attention,self).__init__()self.enc_hidden_size = enc_hidden_sizeself.dec_hidden_size = dec_hidden_sizeself.liner_in = nn.Linear(2*enc_hidden_size,dec_hidden_size)self.liner_out = nn.Linear(2*enc_hidden_size+dec_hidden_size,dec_hidden_size)def forward(self,output,context,mask):batch_size = context.shape[0]enc_seq = context.shape[1]dec_seq = output.shape[1]# score计算公式使用双线性模型 h*w*scontext_in = self.liner_in(context.reshape(batch_size*enc_seq,-1).contiguous())context_in = context_in.view(batch_size,enc_seq,-1).contiguous()atten = torch.bmm(output,context_in.transpose(1,2))atten.data.masked_fill(mask,-1e6) # mask置零atten = F.softmax(atten,dim=2) context = torch.bmm(atten,context) # 将atten与source的hidden state输出做加权求和output = torch.cat((context,output),dim=2) # 将attention + output 堆叠获取融合信息output = torch.tanh(self.liner_out(output.view(batch_size*dec_seq,-1))).view(batch_size,dec_seq,-1) #Linear转换为target的hidden维度,再经tanh激活return output,atten

Decoder

decoder的结构为单向GRU。

nn.Embedding:Embedding层,将target输入查找词向量nn.GRU:单向GRU层,输入输出需要pack、pad,为了保证和source句子对对齐,我们没法保证按句子长度排序,pack_padded_sequence时需要将enforce_sorted置为False。self.atten:进行Attention操作self.create_mask:在Attention之前需要创建mask,具体而言是创建Padding Mask。log_softmax: 将输出转为vocab_size的softmax概率分布并取对数

def __init__(self,vocab_size,embedded_size,enc_hidden_size,dec_hidden_size,dropout=0.2):super(Decoder,self).__init__()self.embed = nn.Embedding(vocab_size,embedded_size)self.atten = Attention(enc_hidden_size,dec_hidden_size)self.rnn = nn.GRU(embedded_size,dec_hidden_size,batch_first=True)self.out = nn.Linear(dec_hidden_size,vocab_size)self.dropout = nn.Dropout(dropout)def create_mask(self,x_len,y_len):# 最长句子的长度max_x_len = x_len.max()max_y_len = y_len.max()# 句子batchbatch_size = len(x_len)# 将超出自身序列长度的元素设为Falsex_mask = (torch.arange(max_x_len.item())[None, :] < x_len[:, None]).float() y_mask = (torch.arange(max_y_len.item())[None, :] < y_len[:, None]).float() # 需要mask的地方设置为truemask = (1 - y_mask[:, :, None] * x_mask[:, None, :]) != 0return maskdef forward(self,ctx,ctx_lengths,y,y_lengths,hid):y_embed = self.dropout(self.embed(y))y_packed = nn.utils.rnn.pack_padded_sequence(y_embed,y_lengths,batch_first=True,enforce_sorted=False)pack_output, hid = self.rnn(y_packed,hid)output_seq,_ = nn.utils.rnn.pad_packed_sequence(pack_output,batch_first=True,total_length=max(y_lengths))mask = self.create_mask(ctx_lengths,y_lengths)# annention处理output,atten = self.atten(output_seq,ctx,mask)output = F.log_softmax(self.out(output),dim=-1)return output,atten,hid

Seq2Seq

将模型整合后,整个完整的模型计算图:

src输入Embedding层src_embedsrc_embed经过双向GRU层,得到src_hidden,src_last_hsrc_last_h经过线性层、tanh激活得到decoder的初始hidden输入tgt_init_htgt输入Embedding层tgt_embedtgt_embed及tgt_init_h经过单向GRU层,得到tgt_hidden根据src及tgt句子batch中的长度,创建masksrc_hidden和tgt_hidden做双线性attention得到输出a_tta_tt做mask后softmax归一化为概率分布src_hidden与a_tt加权求和输出att_valueatt_value与tgt_hidden信息融合concate后输入线性层、tanh激活输出为tgt_outputtgt_output输入线性层、softmax后取对数,得到最终的target vocab size上的对数概率分布。模型的解码过程使用beam search,最大解码长度默认取100,主要是我们的语料数据较少且语句较短。

5.训练

在最开始尝试以10epoch训练15万条数据,一个epoch跑完已经耗时10个小时,无奈最后将数据量减少到1.5万,epoch减少到5,耗时8个小时完成了训练。

即使如此,看上图中的loss也能知道,训练效果很糟糕。

相关参数:

batch_size=16learnning_rate=5e-4dropout=0.2epoch=5optimizer:AdamW

6.测试

测试效果

2000条测试数据最后计算出来的bleu值为2.71,可以说是非常低了😅(对bleu值的说明见最后一小节)

为什么效果差

我认为训练效果差与数据处理有很大的关系,IWSLT14 En-Zh这个数据集有太多的口语内容,比如说:

Ugh. Mini-Me.呃。我太小了--

同时也有太多的话外音,比如说:

1.76 times 0.2 over here is 352 meters per second.(众笑+鼓掌) 1.76乘以0.2得到的是每秒352米。

关于话外音的问题在数据预处理阶段已经尽可能删除了,但是仍然存在一部分嵌入在句子内部的话外音无法删除干净。此外,语料中各个句子的长短很不一致,长的句子将近有100个单词,短的句子就一两个单词,这也会影响训练效果。当然最重要的原因是计算资源不够,有服务器的同学会相对好一点,直接用cpu跑的话根本跑不完所有的语料。在这里我贴一个用其他语料训练完成的模型,准确率会比我训的这个高很多:【审核中…】

demo展示

运行do_translate模块,允许输入任意一个句子,控制台会打印出最优的五个翻译结果:

上图贴出来的都是正确翻译的结果,对于bleu值只有2.71的模型,不出所料绝大部分都是翻译都是错误的😭

7.疑问与思考

🖤序列生成模型评价指标

BLEU-精度

bilingual evaluation understudy :衡量模型生成序列与参考序列之间的N元词组的重合度,最早用来评价机器翻译模的质量,目前也广泛应用在各种序列生成任务中。实现方法:统计同时出现在生成序列和参考序列中 的 n 元词的个数,最后把匹配到的n 元词的数目除以生成序列单词数目,得到评测结果( 𝒏 元组集合的精度)Bleu值只计算精度,不关心召回率(即参考序列中的n元组合是否在生成序列中出现)

ROUGE-召回率

recall-oriented understudy for gisting evaluation :最早应用于文本摘要领域,和bleu值相似,但是rouge计算的是召回率

💛Pytorch中的pack和pad操作

参考:/guofei_fly/article/details/104053532

在RNN网络中,文本的pad操作用于各文本长度的对齐;而pack操作用于实现文本序列数据的压缩。

💚信息融合

参考:/weixin_38646522/article/details/116764227

特征融合目前有两种常用的方式,一种是add操作,一种是Concat操作。

区别:

对于Concat操作而言,通道数的合并,也就是说描述图像本身的特征增加了,而每一特征下的信息是没有增加。对于add层更像是信息之间的叠加。add前后的tensor语义是相似的。

需要将A与B的Tensor进行融合:

如果它们语义不同,则我们可以使用Concat的形式。如果A 与B是相同语义,如A与B是不同分辨率的特征,其语义是相同的,我们可以使用add来进行融合

❤️mask机制

Padding Mask

在训练中每个样本的原始句子的长度是不一样的,在进行 batch训练之前,要先进行长度的统一,过长的句子可以通过truncating 截断到固定的长度,过短的句子可以通过 padding 增加到固定的长度,但是 padding 对应的字符只是为了统一长度,并没有实际的价值,因此希望在之后的计算中屏蔽它们,这时候就需要 Mask。

对于那些补零的数据,为了让attention机制不把注意力放在这些位置上,把这些位置的值加上一个非常大的负数(负无穷),经过softmax后,这些位置的权重就会接近0。Transformer的padding mask实际上是一个张量,每个值都是一个Boolean,值为false的地方就是要遮挡的地方。

Sequence Mask

将输入组成输入矩阵,乘以一个 mask矩阵,屏蔽当前词到最后的词,使当前词只能看到它前面的词。用在decoder端。

💜beam search

参考:/p/36029811?group_id=972420376412762112

在Beam Search中只有一个参数B,叫做beam width(集束宽),用来表示在每一次挑选top B的结果。在集束宽为3时,集束搜索一次只考虑3个可能结果。注意如果集束宽等于1,只考虑1种可能结果,这实际上就变成了贪婪搜索算法,但是如果同时考虑多个,可能的结果比如3个,10个或者其他的个数,集束搜索通常会找到比贪婪搜索更好的输出结果。

好啦,这次的作业也算是勉强顺利完成啦😜

【一起入门NLP】中科院自然语言处理作业四:RNN+Attention实现Seq2Seq中英文机器翻译(Pytorch)【代码+报告】

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。