【🍊易编橙:一个帮助编程小伙伴少走弯路的终身成长社群🍊】
大家好,我是小森( ﹡ˆoˆ﹡ ) ! 易编橙·终身成长社群创始团队嘉宾,橙似锦计划领衔成员、阿里云专家博主、腾讯云内容共创官、CSDN人工智能领域优质创作者 。
自注意力背景
NLP领域中, 当前的注意力机制大多数应用于seq2seq架构, 即编码器和解码器模型。
- encoder-decoder 结构 : Encoder将输入编码成上下文向量,Decoder进行解码;解码过程顺序进行,每次仅解码出一个单词。
RNN存在一些问题:
- 输 入 输 出 存 在 序 列 关 系 , b 4 的 输 出 需 要 先 依 赖 于 b 3 , … ,
一 次 输 出 , 无 法 进 行 并 行 化 - 不论输入和输出的语句长度是什么,中间的上下文向量长度都是
固定的 - 仅仅利用上下文向量解码,会有信息瓶颈,长度过长时候信息可
能会丢失
可以对对seq2seq结构改进,使 用 C N N 来 进 行 并 行 化。
通过堆叠多层CNN,提高感受野,使上层输出可以捕获长程时序关系。
自注意力
语言的含义是极度依赖上下文的
- 机器人第二法则:机器人必须遵守人类给它的命令,除非该命令违背了第一法则
这句话中高亮表示了三个地方,这三处单词指代的是其它单词。除非我们知道这些词
指代的上下文联系起来,否则根本不可能理解或处理这些词语的意思。当模型处理这
句话的时候,它必须知道:
- 「它」指代机器人
- 「命令」指代前半句话中人类给机器人下的命令,即「人类给它的命令」
- 「第一法则」指机器人第一法则的完整内容
自注意力机制(self-Attention):
3个人工定义的重要概念,查询向量,键向量,值向量
① 查询向量(Query向量):被用来和其它单词的键向量相乘,从而得到其它词相对于当前词的注意力得分。
② 键向量(Key向量):序列中每个单词的标签,是我们搜索相关单词时用来匹配的对象。
③ 值向量(Value向量):单词真正的表征,使用值向量基于注意力得分进行加权求和。
查询向量就像一张便利贴,键向量像是档案柜中文件夹上贴的标签。当找到和便利贴上所写相匹
配的文件夹时,文件夹里的东西便是值向量。
自注意力实现
q u e r y , ke y , va l u e 向 量 的 定 义
使用每一个q对每一个k做attention :
将Query和Key分别计算相似性,然后经过softmax得到相似性概率权重,即注意力,再乘以Value,最后相加即可得到包含注意力的输出 。
常见注意力机制代码
import torch
import torch.nn as nn
import torch.nn.functional as F
class Attn(nn.Module):
def __init__(self, query_size, key_size, value_size1, value_size2, output_size):
super(Attn, self).__init__()
self.query_size = query_size
self.key_size = key_size
self.value_size1 = value_size1
self.value_size2 = value_size2
self.output_size = output_size
# 第一步中需要的线性层
self.attn = nn.Linear(self.query_size + self.key_size, value_size1)
# 第三步中需要的线性层
self.attn_combine = nn.Linear(self.query_size + value_size2, output_size)
def forward(self, Q, K, V):
attn_weights = F.softmax(self.attn(torch.cat((Q[0], K[0]), 1)), dim=1)
attn_applied = torch.bmm(attn_weights.unsqueeze(0), V)
output = torch.cat((Q[0], attn_applied[0]), 1)
# 使用线性层作用在第三步的结果上做一个线性变换并扩展维度
output = self.attn_combine(output).unsqueeze(0)
return output, attn_weights
query_size = 32
key_size = 32
value_size1 = 32
value_size2 = 64
output_size = 64
attn = Attn(query_size, key_size, value_size1, value_size2, output_size)
Q = torch.randn(1,1,32)
K = torch.randn(1,1,32)
V = torch.randn(1,32,64)
out = attn(Q, K ,V)
print(out[0])
print(out[1])
输出:
tensor([[[-0.3390, 0.3021, -0.1952, -0.0400, 0.5597, -0.3745, -0.2216,
-0.3438, -0.2086, -0.1554, -0.2502, 0.0486, 1.0381, -0.1030,
0.7277, 0.0592, -0.9172, -0.3736, -0.2285, -0.0148, -0.3319,
0.0620, -0.6006, 0.1346, -0.1530, 0.0336, 0.3269, -0.2511,
-0.1209, 0.4153, 0.3519, 0.3344, -0.0496, -0.2759, -0.2080,
-0.1669, 0.7263, -0.0893, 0.0298, -0.1326, 0.6898, -0.3864,
-0.0884, -0.2329, -0.2338, 0.1920, 0.2625, 0.0396, -0.3101,
-0.2299, -0.1226, -0.5915, 0.2620, 0.2462, 0.4123, -0.6733,
-0.2091, 0.6727, 0.3754, -0.1620, -0.8333, 0.2066, 0.3082,
-0.5225]]], grad_fn=<UnsqueezeBackward0>)
tensor([[0.0187, 0.0492, 0.0259, 0.0293, 0.0151, 0.0104, 0.0127, 0.0122, 0.0546,
0.0141, 0.0170, 0.0277, 0.0284, 0.0807, 0.0228, 0.0099, 0.0327, 0.0585,
0.0102, 0.0106, 0.0598, 0.0208, 0.0403, 0.0241, 0.0896, 0.0230, 0.0371,
0.0316, 0.0091, 0.0242, 0.0553, 0.0447]], grad_fn=<SoftmaxBackward0>)
Self-attention就本质上是一种特殊的attention。Self-attention向对于attention的变化,就是寻找权重值的𝑤𝑖过程不同。
Self-attention和Attention使用方法
- Attention (AT) 经常被应用在从编码器(encoder)转换到解码器(decoder)。
- SA可以在一个模型当中被多次的、独立的使用(比如说在Transformer中,使用了18次;在Bert当中使用12次)。
- SA比较擅长在一个序列当中,寻找不同部分之间的关系,AT却更擅长寻找两个序列之间的关系
Transformer模型
Encoder由N个相同结构的编码模块堆积而成,每一个编码模块由Multi-Head Attention, Add &
Norm, Feed Forward, Add & Norm 组成的。
编码器结构
第一层的激活函数为 ReLU,第二层不使用激活函数。X是输入,全连接层的输入和输出都是512维,中间隐层维度为2048。
解码器结构
通过输入矩阵X计算得到Q, K, V 矩阵,然后计算 Q 和 KT 的乘积 QKT。
计算注意力分数,在 Softmax 之前需要使用 Mask矩阵遮挡住每一个单词之后的信息。
文本嵌入层:无论是源文本嵌入还是目标文本嵌入,都是为了将文本中词汇的数字表示转变为向量表示, 希望在这样的高维空间捕捉词汇间的关系
import torch
import torch.nn as nn
import math
from torch.autograd import Variable
class Embeddings(nn.Module):
def __init__(self, d_model, vocab):
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)
输出:
embedding = nn.Embedding(10, 3)
input = torch.LongTensor([[1,2,3,4],[6,3,2,9]])
print(embedding(input))
#
tensor([[[ 1.8450, 1.9222, 0.1577],
[-0.7341, 0.3091, 0.7592],
[-0.4300, 0.9030, -0.3533],
[ 1.1873, 0.9349, -1.0567]],
[[ 0.4812, -0.1072, 0.4980],
[-0.4300, 0.9030, -0.3533],
[-0.7341, 0.3091, 0.7592],
[-2.1227, -0.3621, 0.7383]]], grad_fn=<EmbeddingBackward0>)