• 本文是对于Pytorch项目成员:Ben Trevett的教程https://github.com/bentrevett/pytorch-seq2seq中,第6个项目的个人学习整理。原文是实现的“德语-英语”互译。在本博文内,期望在此基础上实现:
  1. 个人对于调参的理解(尚待调整)
  2. 中英互译;
  3. 低资源下的中英互译;(尚待完成)
  • 其它关于Transformer的架构,还可以参考链接:
    http://nlp.seas.harvard.edu/2018/04/03/attention.html、以及李宏毅的ML课程讲解。

  • 代码仓库:https://github.com/zuochao912/NMT_transformer_zh2en

其中,我这里的数据集使用的是IWSLT15Zh-En。这里由于实验设备限制,用若干年前的Titan-XP,在Batch为128的时候,即便EncoderLayer和DecoderLayer都只有3层,单卡在训练集上跑一个Epoch要4min左右。而可怜的是我多GPU训练的loss下降比较慢,单卡的学习率技巧又弄不好,这里就简单的设为0.0005。warmup,cool down或者指数衰减、余弦退火之类的技巧也一下子用不好,batch也设不太好,也希望各位大佬能支个招。

一、数据准备

这里使用Spacy构建两边语料的字典;embedding也是train from scratch的,没有使用预训练的词向量。其中具体内容,请详见另外的Spacy使用指南。

二、模型结构

本文并不介绍Self-attention和Layer_norm,请见CV与NLP中的注意力模块与激活函数模块

2.1 Encoder部分

Encoder由若干个Encoder_layer堆叠组成;

2.1.1 Encoder_layer

Encoder_layer
Multihead Attention
Layer_norm+Residual
FFN(Feed forward network)
Layer_norm+Residual

Transformer并不尝试将一句话中的各word_embedding压缩成一个sentence_embedding,因此对于一句话,若表示为X=(x1,...,xn)X = (x_1, ... ,x_n)X=(x1,...,xn),其有n个词,就有n个Context_vector,如表示为Z=(z1,...,zn)Z = (z_1, ... , z_n)Z=(z1,...,zn),这些向量均参考了句子中所有词语。事实上,用Self-attention机制产生的这些context vector的表达能力比RNN强,因为RNN在时间ttt产生的向量xtx_txt,只能对1:t1−11:t_1-11:t11时刻建模,但是注意力机制可以看到句子中的所有位置。
因此,在Transformer中,句子向量在通过Encoder Layer时,数目并不会减少。

  • 特别提醒,句子长度不同,因此输入时会有pad标签;在计算注意力的时候,我们不需要对这些地方算注意力!
  • 具体实现如下,mask的作用其实就是,把算出来attentionattentionattention分数,对应变为0;那么根据QKQKQK算出来的energyenergyenergy在进行softmaxsoftmaxsoftmax前,将energyenergyenergy选用一个特别小的数字就行,如此处为1e−101e-101e10,如下所示
   energy = torch.matmul(Q, K.permute(0, 1, 3, 2)) / self.scale#energy = [batch size, n heads, query len, key len]if mask is not None:energy = energy.masked_fill(mask == 0, -1e10)attention = torch.softmax(energy, dim = -1)     #attention = [batch size, n heads, query len, key len]x = torch.matmul(self.dropout(attention), V)

2.1.2 Position_Encoding

此外,由于注意力机制的注意力分数,是位置无关,内容相关地;而在句子中,相对位置是十分重要的,因此需要对word加入position_encoding
有的论文中,Position_Encoding是固定的,比如Transformer这里采用的是位置的三角函数;而其实也可以学习得到。

  • 个人觉得只需要体现位置信息就行,是否需要学习并不重要;这就像Condition GAN一样。

2.1.3 FFN

其实就是具有一层hidden_layer的全连接神经网络,先将输入维度,从 hid_dim t映射为 pf_dim, 再映射回hid_dim,在这里pf_dim 通常比hid_dim大,似乎也是常规操作了.
原本的l Transformer 使用 hid_dim 为512 ,使用 pf_dim 为2048.,使用激活函数为RELU,并且也使用了drop_out。
但是在BERT中,使用的是 GELU 激活函数, pytorch实现的时候,只需要把torch.relu替换为 F.gelu. 当然论文中没有解释,为什么这样

  • Encoder与Decoder的FFN都是这样设置的,因此以下不再重复介绍

2.2Decoder部分

本处目标,在时刻ttt时,根据encoder提供的Context vector们,ZZZ,以及时刻ttt之前生成的句子,得到 Yt^\hat{Y_t}Yt^. 在评估时,用生成的Y^\hat{Y}Y^ 与实际答案 YYY计算损失函数,优化。
Decoder也是由若干个Decoder_layer堆叠组成;

2.2.1 Decoder_layer:

Decoder_layer的层次和Encoder_layer其实基本是一样的;唯一不同的就是注意力部分,使用了两个注意力模块:

  1. 因为Decoder需要知到Encoder的信息才能正确输出,因此采用了cross attention,即其中的K和V来源于Encoder,而Q来源于Decoder,这就像我们知道了之前生成结果后,我们需要查看源语言信息,来得到我们的结果一样。不过V是不是也可以来源于Decoder呢…
  2. 因为在翻译的时候,我们不能通过查看答案,来翻译,因此我们需要再翻译的时候,把之后的值给mask掉。因此采用了Masked attention。不过在具体实现中,和Encoder的Multihead attention并没显著差异,因为那里需要把<pad>给mask掉.

当然最后也是FFN,其中也都是使用了Layer_norm和Residual_connection

2.3 Seq2Seq包装

我们将Encoder和Decoder包装为一个Seq2Seq类,就是本模型的完整结构了,可以用于训练(train)和推理(inference)

source mask目标是对句子的 <pad> token进行掩码,因此是 <pad> 的地方设为0,其它地方设为1。注意,一个batch的一个句子,虽然用一个一维向量就可以达到mask目的,但是之后句子经过embedding后变为四维张量,因此还需要升维,以便广播。 energy张量的shape是 [batch size, n heads, seq len, seq len],因此sourch mask的形状是**[batch size, 1, 1, seq len]**

然后target mask 其实就是一个下三角阵,和上面对 <pad> token的mask阵的逻辑和,首先生成下三角阵,

1000011000111001111011111\begin{matrix} 1 & 0 & 0 & 0 & 0\\ 1 & 1 & 0 & 0 & 0\\ 1 & 1 & 1 & 0 & 0\\ 1 & 1 & 1 & 1 & 0\\ 1 & 1 & 1 & 1 & 1\\ \end{matrix}1111101111001110001100001

意思是target token可以看到的src token(其实也是自己所在的这句话),第一行,表示第一个 target token 的mask是 [1, 0, 0, 0, 0] ,只能看自己。 第二个target token的mask为 [1, 1, 0, 0, 0] ,即他能看包括自己的前两个。

可以想到,那么最终的结果,如下示意,其中句子长度为3.

1000011000111001110011100\begin{matrix} 1 & 0 & 0 & 0 & 0\\ 1 & 1 & 0 & 0 & 0\\ 1 & 1 & 1 & 0 & 0\\ 1 & 1 & 1 & 0 & 0\\ 1 & 1 & 1 & 0 & 0\\ \end{matrix}1111101111001110000000000

三、其它implement细节:

3.1 参数初始化:

本文并没有说到参数初始化细节,但是Transformer模型一般采用 Xavier uniform 初始化方法,如下使用

def initialize_weights(m):if hasattr(m, 'weight') and m.weight.dim() > 1:nn.init.xavier_uniform_(m.weight.data)model.apply(initialize_weights)#model就是Seq2Seq模型

3.2 优化器设置:

这里我的炼丹经验明显就很差了,参考原文:

  • Transformer原文中学习率使用了warm-upcool-down的训练技巧,采用Adam优化器
  • BERT和其它一些Transformer事实上就是用固定的学习率和Adam优化器.
  • 注意,使用一个比Adam优化器的默认学习率更小的参数,否则很容易学不好!(尚待实验)
LEARNING_RATE = 0.0005
#三层Encoder_layer的效果不凑,但是6层效果不太好,之后似乎会发生奇怪的loss爆炸现象,而并没有过拟合。
optimizer = torch.optim.Adam(model.parameters(), lr = LEARNING_RATE)

3.3.优化目标

由于BLEU score这种评价方法是不做为代价函数的,一般的,还是采用交叉熵;
但是这里也得特别注意,我们不能看<pad>标签来计算结果,因此要如下设置:

criterion = nn.CrossEntropyLoss(ignore_index = TRG_PAD_IDX)

3.4 注意输入输出的标签

我们在输入模型的时候,并不输入EOS标签,这是要求模型输出的
trg=[sos,y1,y2,y3,eos]trg[:−1]=[sos,x1,x2,x3]trg = [sos,y_1, y_2, y_3, eos]\\ trg[:-1] = [sos,x_1, x_2, x_3] trg=[sos,y1,y2,y3,eos]trg[:1]=[sos,x1,x2,x3]

而在我们用交叉熵损失函数的时候,我们得到的output是没有sos标签的,我们需要注意一下:
output=[y1,y2,y3,eos]trg[1:]=[x1,x2,x3,eos]output = [y_1, y_2, y_3, eos]\\ trg[1:] = [x_1, x_2, x_3, eos] output=[y1,y2,y3,eos]trg[1:]=[x1,x2,x3,eos]

模型训练时的结构应该是如下的:

def train(model, iterator, optimizer, criterion, clip):model.train()epoch_loss = 0for i, batch in enumerate(iterator):     src = batch.srctrg = batch.trg#trg = [batch size, trg len]optimizer.zero_grad()output, _ = model(src, trg[:,:-1]) #output = [batch size, trg len - 1, output dim]output_dim = output.shape[-1]output = output.contiguous().view(-1, output_dim)#output = [batch size * trg len - 1, output dim]trg = trg[:,1:].contiguous().view(-1)  #trg = [batch size * trg len - 1]loss = criterion(output, trg)loss.backward()torch.nn.utils.clip_grad_norm_(model.parameters(), clip)optimizer.step()epoch_loss += loss.item()return epoch_loss / len(iterator)

注意,在训练时使用了梯度剪裁语句,可以试验一下不同的CLIP值,以及不使用会造成什么后果?

nn.utils.clip_grad_norm

3.5 pipeline:

在训练集上训练以及dev集上测试的流程如下。其中,evaluate相比于train,只是不需要计算梯度,也不需要反向传播;这里注意

  • 最好记个时间,这样修改超参数的时候可以感受到对训练时间的改变;
  • 保存模型的时候,在dev集上loss最小的时候保存;其实最好把命名仔细一点,这样可以区分不同时间保存下来的模型;
  • 一定要关注一下train和dev集合上的loss变化,经常可以关注到train集合上损失变小,但dev集合上损失不断增大;这时候往往过拟合了!
best_valid_loss = float('inf')
#这一行是在干什么?for epoch in range(N_EPOCHS):start_time = time.time()train_loss = train(model, train_iterator, optimizer, criterion, CLIP)valid_loss = evaluate(model, valid_iterator, criterion)end_time = time.time()epoch_mins, epoch_secs = epoch_time(start_time, end_time)if valid_loss < best_valid_loss:best_valid_loss = valid_losstorch.save(model.state_dict(), 'transformer_model.pt')

3.6 遗留问题:

1.在我们数据准备中,把出现次数很少的词语转化为了一个奇怪的token,那么我们是不是就永远不可能产生这个词语?
2. 在本指导中所有步骤中,矩阵的行列、维度,以及mask的方向、维度有没有分辨明白啊?好像是没有。。

四、模型推理(Inference)

模型推理似乎可以一句一句输入,也可以将一个矩阵整体输入。

4.1 整体步骤

如下所示:

  • tokenize源语言的句子,把句子从字符串转化为token标记,并且加上 <sos><eos> 的首尾标记;
  • 将token数值化,即embedding化;和训练时一样,先转化为index,再在embedding矩阵中对应位置查找就行
  • 对于单个句子,将矩阵格式变为张量,同时要把batch维度设为1;
  • 生成src_mask矩阵,然后将句子和mask矩阵输入encoder
  • 对于trg部分,我们初始化只有一个<sos> token,同时我们需要进行Auto_Regressive的自回归输入,具体如下,这里我们注意,输出是通过“选取概率最大”的采样而得,因此我们得到的首先都是index,而不是token
  • 当翻译输出没有达到最大限制时
    • 将当前输出转化为batch为1的矩阵
    • 每次都要生成新的trg_mask;
    • 对于decoder,需要输入encoder的context vector,以及“上一时刻”的输入,还有trg_mask矩阵
    • 我们可以得到“本时刻”的注意力分数,以及token(其实是index)
    • 进行自回归,把本时刻的输出,加到decoder输入的最后
    • 当我们看到 <eos> token(其实是index)的时候,停止生成
  • 将句子的index转化为词语,通过查表就行。
    -真正返回句子,这是我们人能看得懂的句子 ( 移除<sos> token) 然后我们可以查看decoder最后一层 attention

代码如下所示意:

def translate_sentence(sentence, src_field, trg_field, model, device, max_len = 50):#这里sentence的输入,是一个句子,不是一个Batchmodel.eval()if isinstance(sentence, str):nlp = spacy.load('de_core_news_sm')tokens = [token.text.lower() for token in nlp(sentence)]else:tokens = [token.lower() for token in sentence]tokens = [src_field.init_token] + tokens + [src_field.eos_token]#src句子前后补齐src_indexes = [src_field.vocab.stoi[token] for token in tokens]#将src句子转化为indexsrc_tensor = torch.LongTensor(src_indexes).unsqueeze(0).to(device)#将src的batch维度补齐src_mask = model.make_src_mask(src_tensor)    #生成src_mask矩阵with torch.no_grad():#此时不需要计算梯度enc_src = model.encoder(src_tensor, src_mask)trg_indexes = [trg_field.vocab.stoi[trg_field.init_token]]for i in range(max_len):#自回归操作trg_tensor = torch.LongTensor(trg_indexes).unsqueeze(0).to(device)#将trg_index转化为embedding,并补齐batchtrg_mask = model.make_trg_mask(trg_tensor)with torch.no_grad():output, attention = model.decoder(trg_tensor, enc_src, trg_mask, src_mask)#生成时刻t的logits概率分布pred_token = output.argmax(2)[:,-1].item()#找到最大词语indextrg_indexes.append(pred_token)#补齐句子if pred_token == trg_field.vocab.stoi[trg_field.eos_token]:breaktrg_tokens = [trg_field.vocab.itos[i] for i in trg_indexes]#将trg_index通过查表,转化为词语#去掉sos首部标签,得到结果return trg_tokens[1:], attention

4.2 Attention可视化:

一般似乎都是用decoder最后一层的attention来可视化内容的。根据cross attention的含义,我们用decoder的q去查询encoder提供的k,v部分,因此,就是目标语言的一个词,对应于encoder的一句话的不同v的分数。

代码如下所示:

def display_attention(sentence, translation, attention, n_heads = 8, n_rows = 4, n_cols = 2):assert n_rows * n_cols == n_headsfig = plt.figure(figsize=(15,25))for i in range(n_heads):ax = fig.add_subplot(n_rows, n_cols, i+1)_attention = attention.squeeze(0)[i].cpu().detach().numpy()cax = ax.matshow(_attention, cmap='bone')ax.tick_params(labelsize=12)ax.set_xticklabels(['']+['<sos>']+[t.lower() for t in sentence]+['<eos>'], rotation=45)ax.set_yticklabels(['']+translation)ax.xaxis.set_major_locator(ticker.MultipleLocator(1))ax.yaxis.set_major_locator(ticker.MultipleLocator(1))plt.show()plt.close()

【Basic model】Transformer-实现中英翻译相关推荐

  1. 大学英语综合教程三 Unit 6 课文内容英译中 中英翻译

    大学英语综合教程三 Unit 6 课文内容英译中 中英翻译   大家好,我叫亓官劼(qí guān jié ),在CSDN中记录学习的点滴历程,时光荏苒,未来可期,加油~博客地址为:亓官劼的博客 本文 ...

  2. 基于有道翻译的中英翻译

    本系列教程是微信公众平台开发的高级篇,以微信公众平台应用天气神(WeatherGod)为例,讲解微信接口开发过程.本文为第三篇,基于有道翻译接口的中英翻译. 要做一个翻译账号,有以下几步: 一. 有翻 ...

  3. 大学英语综合教程二 Unit 1 课文内容英译中 中英翻译

    大学英语综合教程二 Unit 1 课文内容英译中 中英翻译   大家好,我叫亓官劼(qí guān jié ),在CSDN中记录学习的点滴历程,时光荏苒,未来可期,加油~博客地址为:亓官劼的博客 本文 ...

  4. 大学英语综合教程一 Unit1至Unit8 课文内容英译中 中英翻译

    大学英语综合教程一 Unit1至Unit8 课文内容英译中 中英翻译   大家好,我叫亓官劼(qí guān jié ),在CSDN中记录学习的点滴历程,时光荏苒,未来可期,加油~博客地址为:亓官劼的 ...

  5. 微信公众平台消息接口开发(3)中英翻译

    微信公众平台开发模式 微信 平台 消息 接口 英语翻译 互译   作者:http://www.cnblogs.com/txw1958/ 标题http://www.cnblogs.com/txw1958 ...

  6. AI一分钟 | 腾讯将成立机器人实验室;机器翻译重大突破:中英翻译已达人类水平

    整理 | 阿司匹林 一分钟AI 腾讯AI Lab宣布与施普林格·自然集团旗下的自然科研正式达成战略合作,并宣布即将成立机器人实验室"Robotics X". 微软与雷德蒙研究院研发 ...

  7. 中英翻译机c语言实验报告引言,课程设计--C语言关键字中英翻译机.doc

    课程设计--C语言关键字中英翻译机.doc 课 程 设 计 报 告学院.系 吉林大学珠海学院计算机科学与技术系专业名称 计算机科学与技术课程设计科目 C 语言程序课程设计所在班级 4 班学生学号 04 ...

  8. 大学英语综合教程一 Unit 8 课文内容英译中 中英翻译

    大学英语综合教程一 Unit 8 课文内容英译中 中英翻译   大家好,我叫亓官劼(qí guān jié ),在CSDN中记录学习的点滴历程,时光荏苒,未来可期,加油~博客地址为:亓官劼的博客 本文 ...

  9. 大学英语综合教程二 Unit 5 课文内容英译中 中英翻译

    大学英语综合教程二 Unit 5 课文内容英译中 中英翻译   大家好,我叫亓官劼(qí guān jié ),在CSDN中记录学习的点滴历程,时光荏苒,未来可期,加油~博客地址为:亓官劼的博客 本文 ...

  10. 大学英语综合教程一 Unit 2 课文内容英译中 中英翻译

    大学英语综合教程一 Unit 2 课文内容英译中 中英翻译   大家好,我叫亓官劼(qí guān jié ),在CSDN中记录学习的点滴历程,时光荏苒,未来可期,加油~博客地址为:亓官劼的博客 本文 ...

最新文章

  1. 使用DPM2007备份还原Exchange2007邮箱数据库
  2. Python基础 --- 使用 dict 和 set
  3. android d-bus,android EventBus的使用
  4. WSL安装Oracle,折腾记录:WSL(Windows Subsystem for Linux,Windows上的Linux子系统)安装后的环境配置-Go语言中文社区...
  5. 查询mysql临时表空间_查看临时表空间使用情况
  6. 动态加载网上或者本地场景的后续
  7. 显示隐藏-display(HTML、CSS)
  8. 机器学习经典书籍论文
  9. elf section类型_ELF 转二进制(2/4): 允许把 Binary 文件加载到任意位置
  10. macbook运行移动硬盘中windows及bootcamp下载出错问题的解决
  11. android手机修改app名字,手机App名字图标随意改,藏羞羞东西再也不怕被发现了...
  12. springboot+vue公众号页面授权获得微信openId
  13. javac编译带有package的java文件
  14. maven中resource配置详解
  15. word2016 卡顿_office2016打开很慢怎么办?office2016打开卡顿缓慢的解决方法
  16. PTA每日一题-Python-身份证校验
  17. 秀米svg点击显示另一张图_秀米说:SVG和图文排版
  18. Docker——博客收藏
  19. Rockchip HDMI 软件开发指南
  20. 1.1需求调研(一) - 需求调研的目的

热门文章

  1. 微信小程序使用canvas画不规则图形
  2. 从传统运维到AIOps,正确的领跑姿势是什么?
  3. JDK19都出来了~是时候梳理清楚JDK的各个版本的特性了【JDK13特性讲解】
  4. 解决Windows域管理的几个经典问题
  5. 电信光猫配置无线路由器
  6. css如何设置透明度?两种方法
  7. qt 给容器中的控件添加信号槽
  8. Sulfo-Cyanine7.5 azide,Sulfo-Cyanine7.5 N3,磺酸基Cy7.5 叠氮,可用来方便地标记各种生物分子
  9. 显示器输入不支持,黑屏,进入不了系统
  10. 在抽签仪式上,如何通过软件系统的随机抽签功能来决定选手的比赛出场顺序?