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,变成注意力分数,注意力分数对隐藏层状态加权和得到上下文向量。
- 注意力模型的解码器 RNN 的输入包括:一个embedding 向量,和一个初始化好的解码器 hidden state(隐藏层状态)。
- RNN 处理上述的 2 个输入,产生一个输出和一个新的 hidden state(隐藏层状态 h4 向量),其中输出会被忽略。
- 注意力的步骤:我们使用编码器的 hidden state(隐藏层状态)和 h4 向量来计算这个时间步的上下文向量(相似度加权和)(C4)。
- 我们把 h4 和 C4 拼接起来,得到一个向量。
- 我们把这个向量输入一个前馈神经网络(这个网络是和整个模型一起训练的)。
- 前馈神经网络的输出的输出表示这个时间步输出的单词。
- 在下一个时间步重复这个步骤。
[基于 TensorFlow 的 神经机器翻译 (seq2seq) 指南。]
transformer 基于self-attention 结构
自注意力的优点:可以并行计算。
编码器:
编码器(Encoder)接收的输入都是一个向量列表,输出也是大小同样的向量列表,然后接着输入下一个编码器。(第一 个/层 编码器的输入是词向量,而后面的编码器的输入是上一个编码器的输出。)
注意力分数:
自注意力: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:**用矩阵可以加速计算所有词的位置的向量。
多头注意力:
- 扩展了模型关注不同位置的能力。
- 多头注意力机制赋予 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相乘,再输入前馈神经网络(每行代表一个词)。
编码实现 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
位置编码的可视化
左半部分的值是由 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 )