【Basic model】Transformer-实现中英翻译
- 本文是对于Pytorch项目成员:Ben Trevett的教程https://github.com/bentrevett/pytorch-seq2seq中,第6个项目的个人学习整理。原文是实现的“德语-英语”互译。在本博文内,期望在此基础上实现:
- 个人对于调参的理解(尚待调整)
- 中英互译;
- 低资源下的中英互译;(尚待完成)
其它关于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:t1−1时刻建模,但是注意力机制可以看到句子中的所有位置。
因此,在Transformer中,句子向量在通过Encoder Layer时,数目并不会减少。
- 特别提醒,句子长度不同,因此输入时会有
pad
标签;在计算注意力的时候,我们不需要对这些地方算注意力! - 具体实现如下,mask的作用其实就是,把算出来attentionattentionattention分数,对应变为0;那么根据QKQKQK算出来的energyenergyenergy在进行softmaxsoftmaxsoftmax前,将energyenergyenergy选用一个特别小的数字就行,如此处为1e−101e-101e−10,如下所示
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其实基本是一样的;唯一不同的就是注意力部分,使用了两个注意力模块:
- 因为Decoder需要知到Encoder的信息才能正确输出,因此采用了cross attention,即其中的K和V来源于Encoder,而Q来源于Decoder,这就像我们知道了之前生成结果后,我们需要查看源语言信息,来得到我们的结果一样。不过V是不是也可以来源于Decoder呢…
- 因为在翻译的时候,我们不能通过查看答案,来翻译,因此我们需要再翻译的时候,把之后的值给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-up和cool-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-实现中英翻译相关推荐
- 大学英语综合教程三 Unit 6 课文内容英译中 中英翻译
大学英语综合教程三 Unit 6 课文内容英译中 中英翻译 大家好,我叫亓官劼(qí guān jié ),在CSDN中记录学习的点滴历程,时光荏苒,未来可期,加油~博客地址为:亓官劼的博客 本文 ...
- 基于有道翻译的中英翻译
本系列教程是微信公众平台开发的高级篇,以微信公众平台应用天气神(WeatherGod)为例,讲解微信接口开发过程.本文为第三篇,基于有道翻译接口的中英翻译. 要做一个翻译账号,有以下几步: 一. 有翻 ...
- 大学英语综合教程二 Unit 1 课文内容英译中 中英翻译
大学英语综合教程二 Unit 1 课文内容英译中 中英翻译 大家好,我叫亓官劼(qí guān jié ),在CSDN中记录学习的点滴历程,时光荏苒,未来可期,加油~博客地址为:亓官劼的博客 本文 ...
- 大学英语综合教程一 Unit1至Unit8 课文内容英译中 中英翻译
大学英语综合教程一 Unit1至Unit8 课文内容英译中 中英翻译 大家好,我叫亓官劼(qí guān jié ),在CSDN中记录学习的点滴历程,时光荏苒,未来可期,加油~博客地址为:亓官劼的 ...
- 微信公众平台消息接口开发(3)中英翻译
微信公众平台开发模式 微信 平台 消息 接口 英语翻译 互译 作者:http://www.cnblogs.com/txw1958/ 标题http://www.cnblogs.com/txw1958 ...
- AI一分钟 | 腾讯将成立机器人实验室;机器翻译重大突破:中英翻译已达人类水平
整理 | 阿司匹林 一分钟AI 腾讯AI Lab宣布与施普林格·自然集团旗下的自然科研正式达成战略合作,并宣布即将成立机器人实验室"Robotics X". 微软与雷德蒙研究院研发 ...
- 中英翻译机c语言实验报告引言,课程设计--C语言关键字中英翻译机.doc
课程设计--C语言关键字中英翻译机.doc 课 程 设 计 报 告学院.系 吉林大学珠海学院计算机科学与技术系专业名称 计算机科学与技术课程设计科目 C 语言程序课程设计所在班级 4 班学生学号 04 ...
- 大学英语综合教程一 Unit 8 课文内容英译中 中英翻译
大学英语综合教程一 Unit 8 课文内容英译中 中英翻译 大家好,我叫亓官劼(qí guān jié ),在CSDN中记录学习的点滴历程,时光荏苒,未来可期,加油~博客地址为:亓官劼的博客 本文 ...
- 大学英语综合教程二 Unit 5 课文内容英译中 中英翻译
大学英语综合教程二 Unit 5 课文内容英译中 中英翻译 大家好,我叫亓官劼(qí guān jié ),在CSDN中记录学习的点滴历程,时光荏苒,未来可期,加油~博客地址为:亓官劼的博客 本文 ...
- 大学英语综合教程一 Unit 2 课文内容英译中 中英翻译
大学英语综合教程一 Unit 2 课文内容英译中 中英翻译 大家好,我叫亓官劼(qí guān jié ),在CSDN中记录学习的点滴历程,时光荏苒,未来可期,加油~博客地址为:亓官劼的博客 本文 ...
最新文章
- 使用DPM2007备份还原Exchange2007邮箱数据库
- Python基础 --- 使用 dict 和 set
- android d-bus,android EventBus的使用
- WSL安装Oracle,折腾记录:WSL(Windows Subsystem for Linux,Windows上的Linux子系统)安装后的环境配置-Go语言中文社区...
- 查询mysql临时表空间_查看临时表空间使用情况
- 动态加载网上或者本地场景的后续
- 显示隐藏-display(HTML、CSS)
- 机器学习经典书籍论文
- elf section类型_ELF 转二进制(2/4): 允许把 Binary 文件加载到任意位置
- macbook运行移动硬盘中windows及bootcamp下载出错问题的解决
- android手机修改app名字,手机App名字图标随意改,藏羞羞东西再也不怕被发现了...
- springboot+vue公众号页面授权获得微信openId
- javac编译带有package的java文件
- maven中resource配置详解
- word2016 卡顿_office2016打开很慢怎么办?office2016打开卡顿缓慢的解决方法
- PTA每日一题-Python-身份证校验
- 秀米svg点击显示另一张图_秀米说:SVG和图文排版
- Docker——博客收藏
- Rockchip HDMI 软件开发指南
- 1.1需求调研(一) - 需求调研的目的
热门文章
- 微信小程序使用canvas画不规则图形
- 从传统运维到AIOps,正确的领跑姿势是什么?
- JDK19都出来了~是时候梳理清楚JDK的各个版本的特性了【JDK13特性讲解】
- 解决Windows域管理的几个经典问题
- 电信光猫配置无线路由器
- css如何设置透明度?两种方法
- qt 给容器中的控件添加信号槽
- Sulfo-Cyanine7.5 azide,Sulfo-Cyanine7.5 N3,磺酸基Cy7.5 叠氮,可用来方便地标记各种生物分子
- 显示器输入不支持,黑屏,进入不了系统
- 在抽签仪式上,如何通过软件系统的随机抽签功能来决定选手的比赛出场顺序?