架构图
结构组成
输入部分
- Input Embedding: 源文本嵌入层
- Output Embedding:目标文本嵌入层
- Positional Encoding:位置编码器
输出部分
- Linear:线性层
- softmax层
编码器部分
Nx:由N个编码器层堆叠而成
每个编码器层由两个子层连接结构组成
第一个子层连接结构包括一个多头自注意力子层和规范化层以及一个残差连接
第二个子层连接结构包括一个前馈全连接子层和规范化层以及一个残差连接
解码器部分
相比于编码器部分,每层增加一个子层:多头自注意力子层和规范化层以及一个残差连接
作用
基于seq2seq架构的transformer模型可以完成NLP领域研究的典型任务, 如机器翻译, 文本生成等. 同时又可以构建预训练语言模型,用于不同任务的迁移学习.
位置编码器
因为在Transformer的编码器结构中, 并没有针对词汇位置信息的处理,因此需要在Embedding层后加入位置编码器,将词汇位置不同可能会产生不同语义的信息加入到词嵌入张量中, 以弥补位置信息的缺失.
实现
import torch
import torch.nn as nn
import math
from torch.autograd import Variable
class Embeddings(nn.Module):
def __init__(self, vocab, d_model):
super(Embeddings, self).__init__()
self.lut = nn.Embedding(vocab, d_model)
self.d_model = d_model
def forward(self, x):
return self.lut(x) * math.sqrt(self.d_model)
class PositionalEncoding(nn.Module):
def __init__(self, d_model, dropout, max_len=5000):
"""
位置编码器类的初始化函数
:param d_model: 词嵌入维度
:param dropout: 置0比例
:param max_len: 句子最大长度
"""
super(PositionalEncoding, self).__init__()
self.dropout = nn.Dropout(p=dropout)
# 初始化位置编码矩阵
# shape:max_len x d_model.
pe = torch.zeros(max_len, d_model)
# 初始化一个绝对位置矩阵, 在我们这里,词汇的绝对位置就是用它的索引去表示.
# shape:max_len x 1
position = torch.arange(0, max_len).unsqueeze(1)
# 将位置信息存入位置编码矩阵:
# 思路,矩阵变换,shape:max_len x 1 -> max_len x d_model
# 将自然数的绝对位置编码缩放成足够小的数字,有助于在之后的梯度下降过程中更快的收敛
div_term = torch.exp(torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model)) # arrange跳跃2,但后面应用了两次
# 分别填充在位置编码矩阵的偶数和奇数位置上,组成最终的位置编码矩阵.
pe[:, 0::2] = torch.sin(position * div_term) # 将初始化的变换矩阵分布在正弦波
pe[:, 1::2] = torch.cos(position * div_term) # 将初始化的变换矩阵分布在余弦波
# 这样我们就得到了位置编码矩阵pe, pe现在还只是一个二维矩阵,要想和embedding的输出(一个三维张量)相加,
# 就必须拓展一个维度,所以这里使用unsqueeze拓展维度.
pe = pe.unsqueeze(0)
# 最后把pe位置编码矩阵注册成模型的buffer
# buffer:我们把它认为是对模型效果有帮助的,但是却不是模型结构中超参数或者参数,不需要随着优化步骤进行更新的增益对象.
# 注册之后我们就可以在模型保存后重加载时和模型结构与参数一同被加载.
self.register_buffer('pe', pe)
def forward(self, x):
"""forward函数的参数是x, 表示文本序列的词嵌入表示"""
# 在相加之前我们对pe做一些适配工作, 将这个三维张量的第二维也就是句子最大长度的那一维将切片到与输入的x的第二维相同即x.size(1),
# 因为我们默认max_len为5000一般来讲实在太大了,很难有一条句子包含5000个词汇,所以要进行与输入张量的适配.
# 最后使用Variable进行封装,使其与x的样式相同,但是它是不需要进行梯度求解的,因此把requires_grad设置成false.
x = x + Variable(self.pe[:, :x.size(1)], requires_grad=False)
# 最后使用self.dropout对象进行'丢弃'操作, 并返回结果.
return self.dropout(x)
d_model = 512
dropout = 0.1
max_len = 60
# 模拟通过文本嵌入层
x = Variable(torch.LongTensor([[100, 2, 421, 508], [491, 998, 1, 221]])) # 数字表示词典中的索引,相当于2个句子,每个句子4个单词
emb = Embeddings(1000, d_model)
embr = emb(x)
print("embr:", embr)
print("embr shape:", embr.shape)
# 文本嵌入层输出shape:2 x 4 x 512
x = embr
pe = PositionalEncoding(d_model, dropout, max_len)
pe_result = pe(x)
print("pe_result:", pe_result)
输出