transformer的pytorch实现

attention 应用于RNN

注意力应用于文本:Bahdanau等2014发布的Neural Machine Translation by Jointly Learning to Align and Translate 和 Luong等2015年发布的Effective Approaches to Attention-based Neural Machine Translation 两篇论文。

解码器:

上下文向量:RNN 加上注意力机制之后,编码器把所有时间步的 hidden state(隐藏层状态)传递给解码器,而不是只传递最后一个 hidden state(隐藏层状态),解码器对编码器传输的所有隐藏层状态给分数,经过softmax,变成注意力分数,注意力分数对隐藏层状态加权和得到上下文向量。

  1. 注意力模型的解码器 RNN 的输入包括:一个embedding 向量,和一个初始化好的解码器 hidden state(隐藏层状态)。
  2. RNN 处理上述的 2 个输入,产生一个输出和一个新的 hidden state(隐藏层状态 h4 向量),其中输出会被忽略
  3. 注意力的步骤:我们使用编码器的 hidden state(隐藏层状态)和 h4 向量来计算这个时间步的上下文向量(相似度加权和)(C4)。
  4. 我们把 h4 和 C4 拼接起来,得到一个向量。
  5. 我们把这个向量输入一个前馈神经网络(这个网络是和整个模型一起训练的)。
  6. 前馈神经网络的输出的输出表示这个时间步输出的单词
  7. 在下一个时间步重复这个步骤。

[基于 TensorFlow 的 神经机器翻译 (seq2seq) 指南。]

transformer 基于self-attention 结构

自注意力的优点:可以并行计算。

编码器:

编码器(Encoder)接收的输入都是一个向量列表,输出也是大小同样的向量列表,然后接着输入下一个编码器。(第一 个/层 编码器的输入是词向量而后面的编码器的输入是上一个编码器的输出。)

注意力分数:
image-20210816233408064

自注意力:Query=Key=Value (是词向量分别和 3 个矩阵相乘得到的,而这个矩阵是我们要学习的参数。)

score:计算 “Thinking” 对应的 Query 向量和其他位置的每个词的 Key 向量的点积,而得到的。如果我们计算句子中第一个位置单词的 Attention Score(注意力分数),那么第一个分数就是 q1 和 k1 的内积,第二个分数就是 q1 和 k2 的点积,再除以 ( d k e y ) \sqrt(d_{key}) ( dkey)(除以一个数是为了在反向传播时,求取梯度更加稳定:导数不会梯度爆炸)。在经过一个softmax层。将每个分数分别与每个 Value 向量相乘,就是对不同的词施加注意力的过程。

将上面的向量输入前馈神经网络。

**tips:**用矩阵可以加速计算所有词的位置的向量。

多头注意力:

  1. 扩展了模型关注不同位置的能力。
  2. 多头注意力机制赋予 attention 层多个“子表示空间”。多组 W Q , W K W V W^Q, W^K W^V WQ,WKWV 的权重矩阵(在 Transformer 的论文中,使用了 8 组注意力(attention heads)。

然后将8组矩阵拼接成一个矩阵,和权重矩阵 W Q W^Q WQ相乘,再输入前馈神经网络(每行代表一个词)。

image-20210816233954392
编码实现 transformer
torch.nn.MultiheadAttention(embed_dim, num_heads, dropout=0.0, bias=True, add_bias_kv=False, add_zero_attn=False, kdim=None, vdim=None)
# embed_dim:最终输出的 K、Q、V 矩阵的维度,这个维度需要和词向量的维度一样
# num_heads:设置多头注意力的数量。如果设置为 1,那么只使用一组注意力。如果设置为其他数值,那么 - - num_heads 的值需要能够被 embed_dim 整除
# dropout:这个 dropout 加在 attention score 后面

解码器

QUERY是下层输入,K、V是编码器的输出。

编码器的multi-head 函数:

torch.nn.MultiheadAttention(embed_dim, num_heads, dropout=0.0, bias=True, add_bias_kv=False, add_zero_attn=False, kdim=None, vdim=None)
#传入参数
forward(query, key, value, key_padding_mask=None, need_weights=True, attn_mask=None)
####例子

## nn.MultiheadAttention 输入第0维为length
# batch_size 为 64,有 12 个词,每个词的 Query 向量是 300 维
query = torch.rand(12,64,300)
# batch_size 为 64,有 10 个词,每个词的 Key 向量是 300 维
key = torch.rand(10,64,300)
# batch_size 为 64,有 10 个词,每个词的 Value 向量是 300 维
value= torch.rand(10,64,300)

embed_dim = 300
num_heads = 1
# 输出是 (attn_output, attn_output_weights)
multihead_attn = nn.MultiheadAttention(embed_dim, num_heads)
attn_output = multihead_attn(query, key, value)[0]
# output: torch.Size([12, 64, 300])
# batch_size 为 64,有 12 个词,每个词的向量是 300 维
print(attn_output.shape)

torch.nn.linear
y = x A T + b y = xA^T + b y=xAT+b

import torch

x = torch.randn(128, 20)  # 输入的维度是(128,20)
m = torch.nn.Linear(20, 30)  # 20,30是指维度
output = m(x)
print('m.weight.shape:\n ', m.weight.shape)
print('m.bias.shape:\n', m.bias.shape)
print('output.shape:\n', output.shape)

# ans = torch.mm(input,torch.t(m.weight))+m.bias 等价于下面的
ans = torch.mm(x, m.weight.t()) + m.bias   
print('ans.shape:\n', ans.shape)

print(torch.equal(ans, output))
#输出   
m.weight.shape:
  torch.Size([30, 20])
m.bias.shape:
 torch.Size([30])
output.shape:
 torch.Size([128, 30])
ans.shape:
 torch.Size([128, 30])    
True
位置编码的可视化
image-20210817183557873

左半部分的值是由 sine 函数产生的,而右半部分的值是由 cosine 函数产生的,然后将他们拼接起来,得到每个位置编码向量。由欧拉公式,这可以唯一指定位置。

论文中的方法和上面图中的稍有不同,它不是直接拼接两个向量,而是将两个向量交织在一起

在解码器里,Self Attention 层只允许关注到输出序列中早于当前位置之前的单词。具体做法是:在 Self Attention 分数经过 Softmax 层之前,屏蔽当前位置之后的那些位置。

transformer 前向过程

两层编码器,一层解码器。在这里插入图片描述

编码器一般有多层,第一个编码器的输入是一个序列,最后一个编码器输出是一组注意力向量 K 和 V。这些注意力向量将会输入到每个解码器的Encoder-Decoder Attention层,这有助于解码器把注意力集中中输入序列的合适位置。Encoder-Decoder Attention层使用前一层的输出作为当前解码器的额输入,加上位置编码来构造 Query 矩阵,而 Key 矩阵和 Value 矩阵来自于解码器最终的输出。

和编码器中的 Self Attention 层不太一样:在解码器里,Self Attention 层只允许关注到输出序列中早于当前位置之前的单词。具体做法是:在 Self Attention 分数经过 Softmax 层之前,屏蔽当前位置之后的那些位置。

解码器最终的输出怎么转换为单词:
在这里插入图片描述

在softmax后面连接一个线性层,把解码器输出的向量,映射到一个更长的向量,这个向量称为 logits 向量(logits向量的长度是词汇表的长度)。

transformer 训练过程

由于模型每个时间步只产生一个输出,我们可以认为:模型是从概率分布中选择概率最大的词,并且丢弃其他词。这种方法叫做贪婪解码(greedy decoding)。另一种方法是每个时间步保留两个最高概率的输出词,然后在下一个时间步,重复执行这个过程:假设第一个位置概率最高的两个输出的词是”I“和”a“,这两个词都保留,然后根据第一个词计算第二个位置的词的概率分布,再取出 2 个概率最高的词,对于第二个位置和第三个位置,我们也重复这个过程。这种方法称为集束搜索(beam search),在我们的例子中,beam_size 的值是 2(含义是:在所有时间步,我们保留两个最高概率),top_beams 的值也是 2(表示我们最终会返回两个翻译的结果)。beam_size 和 top_beams 都是你可以在实验中尝试的超参数。

transformer 训练

1. 词表,文本切割

如果有100哥句子,那么就有100个切割的列表。指定最大长度 假设=128。

2. 词嵌入处理

在torch里基于torch.nn.Embedding实现,实例化时需要设置的参数为词表的大小和被映射的向量的维度比如embed = nn.Embedding(10,8)。向量的维度通俗来说就是向量里面有多少个数。注意,第一个参数是词表的大小,如果你目前最多有8个词,通常填写10(多一个位置留给unk和pad),你后面万一进入与这8个词不同的词就映射到unk上,序列padding的部分就映射到pad上。num_features或者embed_dim是向量的维数。

(batch_size, seq_length, embed_dim) 一般深度学习只改变embed_dim

Transformer 代码

# 示例  平均分布的赋值
w = torch.empty(3,5)
print(nn.init.xavier_uniform_(w))

KaTeX parse error: Expected '}', got '_' at position 57: …{6}{\text { fan_̲in }+\text { fa…

fan_in, fan_out = _calculate_fan_in_and_fan_out(tensor)

fan_in = tensor.size(1) * receptive_field_size

fan_out = tensor.size(0) * receptive_field_size

所以上面的是 sqrt*(6/8)

# 位置编码
Tensor = torch.Tensor
def positional_encoding(X, num_features, dropout_p=0.1, max_len=512) -> Tensor:
    r'''
        给输入加入位置编码
    参数:
        - num_features: 输入进来的维度
        - dropout_p: dropout的概率,当其为非零时执行dropout
        - max_len: 句子的最大长度,默认512
    
    形状:输出的是加上PE的E
        - 输入: [batch_size, seq_length, num_features]
        - 输出: [batch_size, seq_length, num_features]

    例子:
        >>> X = torch.randn((2,4,10))
        >>> X = positional_encoding(X, 10)
        >>> print(X.shape)
        >>> torch.Size([2, 4, 10])
    '''

    dropout = nn.Dropout(dropout_p)
    P = torch.zeros((1,max_len,num_features))
    X_ = torch.arange(max_len,dtype=torch.float32).reshape(-1,1) / torch.pow(
        10000,torch.arange(0,num_features,2,dtype=torch.float32) /num_features)
    P[:,:,0::2] = torch.sin(X_)
    P[:,:,1::2] = torch.cos(X_)
    X = X + P[:,:X.shape[1],:].to(X.device)  # X + 位置编码(按照max_length 给定编码)
    return dropout(X)

# 位置编码例子
X = torch.randn((2,4,10))  
X = positional_encoding(X, 10)
  • 格式化写法简化
  • 时刻用 reshape确认维数、数值类型
  • 切片 [:,:,0::2] ,广播:/num_features
  • torch.pow 幂指数
位置编码原理 link

transformer位置编码对token进行,衡量token的位置差异(相对位置编码)。结合方式是每个token输入E与位置编码PE相加,所以PE的维数要与E一致。

位置编码的要求:

  • 它能为每个时间步 t 输出一个独一无二的编码;
  • 不同长度的句子之间,任何两个时间步之间的距离应该保持一致(线性);
  • 模型应该能毫不费力地泛化到更长的句子(根据d给定)。它的值应该是有界的;
  • 它必须是确定性的。

位置编码分类:函数型、表格型。transformer的位置编码:函数型相对位置编码。

位置编码过程

[n,d] .给定一个长度为[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AuwzsVyK-1629276734855)(https://www.zhihu.com/equation?tex=n)]的输入序列,让[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iXTJgAhJ-1629276734856)(https://www.zhihu.com/equation?tex=t)]表示词在序列中的位置,[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oiamGuQW-1629276734857)(https://www.zhihu.com/equation?tex=%5Coverrightarrow%7Bp_t%7D+%5Cin+%5Cmathbb%7BR%7D%5Ed)]表示[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-387BbYTv-1629276734858)(https://www.zhihu.com/equation?tex=t)]位置对应的向量,[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nw8DKX4D-1629276734859)(https://www.zhihu.com/equation?tex=d)]是token词向量的维度。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AbS2bzXc-1629276734860)(https://www.zhihu.com/equation?tex=f%3A+%5Cmathbb%7BN%7D+%5Crightarrow+%5Cmathbb%7BR%7D%5Ed)]是生成位置向量[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ny95Ght2-1629276734860)(https://www.zhihu.com/equation?tex=%5Coverrightarrow%7Bp_t%7D)]的函数,定义如下:
p t → ( i ) = f ( t ) ( i ) : = { sin ⁡ ( ω k ⋅ t ) ,  if  i = 2 k )

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值