日萌社
人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度学习实战(不定时更新)
Encoder编码器-Decoder解码器框架 + Attention注意力机制
Pytorch:Transformer(Encoder编码器-Decoder解码器、多头注意力机制、多头自注意力机制、掩码张量、前馈全连接层、规范化层、子层连接结构、pyitcast) part1
Pytorch:Transformer(Encoder编码器-Decoder解码器、多头注意力机制、多头自注意力机制、掩码张量、前馈全连接层、规范化层、子层连接结构、pyitcast) part2
Pytorch:解码器端的Attention注意力机制、seq2seq模型架构实现英译法任务
BahdanauAttention注意力机制、LuongAttention注意力机制
BahdanauAttention注意力机制:基于seq2seq的西班牙语到英语的机器翻译任务、解码器端的Attention注意力机制、seq2seq模型架构
图片的描述生成任务、使用迁移学习实现图片的描述生成过程、CNN编码器+RNN解码器(GRU)的模型架构、BahdanauAttention注意力机制、解码器端的Attention注意力机制
机器翻译 MXNet(使用含注意力机制的编码器—解码器,即 Encoder编码器-Decoder解码器框架 + Attention注意力机制)
基于Seq2Seq的中文聊天机器人编程实践(Encoder编码器-Decoder解码器框架 + Attention注意力机制)
基于Transformer的文本情感分析编程实践(Encoder编码器-Decoder解码器框架 + Attention注意力机制 + Positional Encoding位置编码)
注意:这一文章“基于Transformer的文本情感分析编程实践(Encoder编码器-Decoder解码器框架 + Attention注意力机制 + Positional Encoding位置编码)”
该文章实现的Transformer的Model类型模型,实际是改造过的特别版的Transformer,因为Transformer的Model类型模型中只实现了Encoder编码器,
而没有对应实现的Decoder解码器,并且因为当前Transformer的Model类型模型处理的是分类任务,
所以我们此处只用了Encoder编码器来提取特征,最后通过全连接层网络来拟合分类。
1.5 注意力机制
- 学习目标:
- 了解什么是注意力计算规则以及常见的计算规则.
- 了解什么是注意力机制及其作用.
- 掌握注意力机制的实现步骤.
- 什么是注意力:
- 我们观察事物时,之所以能够快速判断一种事物(当然允许判断是错误的), 是因为我们大脑能够很快把注意力放在事物最具有辨识度的部分从而作出判断,而并非是从头到尾的观察一遍事物后,才能有判断结果. 正是基于这样的理论,就产生了注意力机制.
- 什么是注意力计算规则:
- 它需要三个指定的输入Q(query), K(key), V(value), 然后通过计算公式得到注意力的结果, 这个结果代表query在key和value作用下的注意力表示. 当输入的Q=K=V时, 称作自注意力计算规则.
- 常见的注意力计算规则:
1.将Q,K进行纵轴拼接, 做一次线性变化, 再使用softmax处理获得结果最后与V做张量乘法
2.将Q,K进行纵轴拼接, 做一次线性变化后再使用tanh函数激活, 然后再进行内部求和, 最后使用softmax处理获得结果再与V做张量乘法
3.将Q与K的转置做点积运算, 然后除以一个缩放系数, 再使用softmax处理获得结果最后与V做张量乘法
说明:当注意力权重矩阵和V都是三维张量且第一维代表为batch条数时, 则做bmm运算.bmm是一种特殊的张量乘法运算.
bmm运算演示:
# 如果参数1形状是(b × n × m), 参数2形状是(b × m × p), 则输出为(b × n × p)
>>> input = torch.randn(10, 3, 4)
>>> mat2 = torch.randn(10, 4, 5)
>>> res = torch.bmm(input, mat2)
>>> res.size()
torch.Size([10, 3, 5])
- 什么是注意力机制:
- 注意力机制是注意力计算规则能够应用的深度学习网络的载体, 同时包括一些必要的全连接层以及相关张量处理, 使其与应用网络融为一体. 使用自注意力计算规则的注意力机制称为自注意力机制.
- 说明: NLP领域中, 当前的注意力机制大多数应用于seq2seq架构, 即编码器和解码器模型.
- 注意力机制的作用:
- 在解码器端的注意力机制: 能够根据模型目标有效的聚焦编码器的输出结果, 当其作为解码器的输入时提升效果. 改善以往编码器输出是单一定长张量, 无法存储过多信息的情况.
- 在编码器端的注意力机制: 主要解决表征问题, 相当于特征提取过程, 得到输入的注意力表示. 一般使用自注意力(self-attention).
- 注意力机制实现步骤:
- 第一步: 根据注意力计算规则, 对Q,K,V进行相应的计算.
- 第二步: 根据第一步采用的计算方法, 如果第一步采用的是拼接方法,则第二步需要将Q与第一步的计算结果再进行拼接, 如果第一步采用的是转置点积, 一般是自注意力, Q与V相同, 则第二步不需要进行与Q的拼接.
- 第三步: 最后为了使整个attention机制按照指定尺寸输出, 使用线性层作用在第二步的结果上做一个线性变换, 得到最终对Q的注意力表示.
- 常见注意力机制的代码分析:
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):
"""初始化函数中的参数有5个, query_size代表query的最后一维大小
key_size代表key的最后一维大小, value_size1代表value的导数第二维大小,
value = (1, value_size1, value_size2)
value_size2代表value的倒数第一维大小, 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):
"""forward函数的输入参数有三个, 分别是Q, K, V, 根据模型训练常识, 输入给Attion机制的
张量一般情况都是三维张量, 因此这里也假设Q, K, V都是三维张量"""
# 第一步, 按照计算规则进行计算,
# 我们采用常见的第一种计算规则
# 将Q,K进行纵轴拼接, 做一次线性变化, 最后使用softmax处理获得结果
attn_weights = F.softmax(
self.attn(torch.cat((Q[0], K[0]), 1)), dim=1)
# 然后进行第一步的后半部分, 将得到的权重矩阵与V做矩阵乘法计算,
# 当二者都是三维张量且第一维代表为batch条数时, 则做bmm运算
attn_applied = torch.bmm(attn_weights.unsqueeze(0), V)
# 之后进行第二步, 通过取[0]是用来降维, 根据第一步采用的计算方法,
# 需要将Q与第一步的计算结果再进行拼接
output = torch.cat((Q[0], attn_applied[0]), 1)
# 最后是第三步, 使用线性层作用在第二步的结果上做一个线性变换并扩展维度,得到输出
# 因为要保证输出也是三维张量, 因此使用unsqueeze(0)扩展维度
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.4477, -0.0500, -0.2277, -0.3168, -0.4096, -0.5982, 0.1548,
-0.0771, -0.0951, 0.1833, 0.3128, 0.1260, 0.4420, 0.0495,
-0.7774, -0.0995, 0.2629, 0.4957, 1.0922, 0.1428, 0.3024,
-0.2646, -0.0265, 0.0632, 0.3951, 0.1583, 0.1130, 0.5500,
-0.1887, -0.2816, -0.3800, -0.5741, 0.1342, 0.0244, -0.2217,
0.1544, 0.1865, -0.2019, 0.4090, -0.4762, 0.3677, -0.2553,
-0.5199, 0.2290, -0.4407, 0.0663, -0.0182, -0.2168, 0.0913,
-0.2340, 0.1924, -0.3687, 0.1508, 0.3618, -0.0113, 0.2864,
-0.1929, -0.6821, 0.0951, 0.1335, 0.3560, -0.3215, 0.6461,
0.1532]]], grad_fn=<UnsqueezeBackward0>)
tensor([[0.0395, 0.0342, 0.0200, 0.0471, 0.0177, 0.0209, 0.0244, 0.0465, 0.0346,
0.0378, 0.0282, 0.0214, 0.0135, 0.0419, 0.0926, 0.0123, 0.0177, 0.0187,
0.0166, 0.0225, 0.0234, 0.0284, 0.0151, 0.0239, 0.0132, 0.0439, 0.0507,
0.0419, 0.0352, 0.0392, 0.0546, 0.0224]], grad_fn=<SoftmaxBackward>)
- 更多有关注意力机制的应用我们将在案例中进行详尽的理解分析.
-
小节总结:
- 学习了什么是注意力计算规则:
- 它需要三个指定的输入Q(query), K(key), V(value), 然后通过计算公式得到注意力的结果, 这个结果代表query在key和value作用下的注意力表示. 当输入的Q=K=V时, 称作自注意力计算规则.
- 常见的注意力计算规则:
- 将Q,K进行纵轴拼接, 做一次线性变化, 再使用softmax处理获得结果最后与V做张量乘法.
- 将Q,K进行纵轴拼接, 做一次线性变化后再使用tanh函数激活, 然后再进行内部求和, 最后使用softmax处理获得结果再与V做张量乘法.
- 将Q与K的转置做点积运算, 然后除以一个缩放系数, 再使用softmax处理获得结果最后与V做张量乘法.
- 学习了什么是注意力机制:
- 注意力机制是注意力计算规则能够应用的深度学习网络的载体, 同时包括一些必要的全连接层以及相关张量处理, 使其与应用网络融为一体. 使自注意力计算规则的注意力机制称为自注意力机制.
- 注意力机制的作用:
- 在解码器端的注意力机制: 能够根据模型目标有效的聚焦编码器的输出结果, 当其作为解码器的输入时提升效果. 改善以往编码器输出是单一定长张量, 无法存储过多信息的情况.
- 在编码器端的注意力机制: 主要解决表征问题, 相当于特征提取过程, 得到输入的注意力表示. 一般使用自注意力(self-attention).
- 注意力机制实现步骤:
- 第一步: 根据注意力计算规则, 对Q,K,V进行相应的计算.
- 第二步: 根据第一步采用的计算方法, 如果第一步采用的是拼接方法,则第二步需要将Q与第一步的计算结果再进行拼接, 如果第一步采用的是转置点积, 一般是自注意力, Q与V相同, 则第二步不需要进行与Q的拼接
- 第三步: 最后为了使整个attention机制按照指定尺寸输出, 使用线性层作用在第二步的结果上做一个线性变换, 得到最终对Q的注意力表示.
- 学习并实现了一种常见的注意力机制的类Attn.
- 学习了什么是注意力计算规则:
"""
bmm运算
在第一步注意力计算实现中,注意力权重矩阵attn_weights 和 V都是三维张量且两者的第一维代表为batch条数时,
则两者之间做bmm运算。bmm是一种特殊的张量乘法运算,规则是在保持第一个维度batch size不变的情况下,
两者后面的两个维度进行点乘运算,比如 [n,m]乘以[m,p]结果等于[n,p],
那么整个bmm运算的结果就是[batch size,n,m]乘以[batch size,m,p]结果等于[batch size,n,p]
"""
import torch
import torch.nn as nn
# 如果参数1形状是(b × n × m), 参数2形状是(b × m × p), 则输出为(b × n × p)
input = torch.randn(10, 3, 4)
mat2 = torch.randn(10, 4, 5)
res = torch.bmm(input, mat2)
print(res.size()) #torch.Size([10, 3, 5])
"""
注意力计算规则
它需要三个指定的输入Q(query), K(key), V(value), 然后通过计算公式得到注意力的结果,
这个注意力结果代表query在key和value作用下的注意力表示。
自注意力计算规则
1.当输入的Q=K=V时, 称作自注意力计算规则。
2.使用自注意力计算规则的注意力机制称为自注意力机制。
3.当在编码器端的注意力机制:主要解决表征问题, 相当于特征提取过程, 得到输入的注意力表示。一般使用自注意力(self-attention)。
注意力机制
1.注意力机制是注意力计算规则能够应用的深度学习网络的载体, 同时包括一些必要的全连接层以及相关张量处理, 使其与应用网络融为一体。
2.使用自注意力计算规则的注意力机制称为自注意力机制。
3.NLP领域中, 当前的注意力机制大多数应用于seq2seq架构, 即编码器和解码器模型
注意力机制的作用
1.在解码器端的注意力机制:
能够根据模型目标有效的聚焦编码器的输出结果, 当其作为解码器的输入时提升效果。
改善以往编码器输出是单一定长张量, 无法存储过多信息的情况。
2.在编码器端的注意力机制:
主要解决表征问题, 相当于特征提取过程, 得到输入的注意力表示。
一般使用自注意力(self-attention)。
常见的注意力计算规则
1.第一种计算规则:将Q,K进行纵轴列拼接,然后纵轴列拼接的结果做一次线性变化, 再通过softmax处理,最后获得的结果再与V做张量乘法。
2.第二种计算规则:
将Q,K进行纵轴列拼接, 然后纵轴列拼接的结果做一次线性变化后,再通过tanh函数激活, 然后再进行内部求和sum操作,
再通过使用softmax处理,最后获得的结果再与V做张量乘法。
3.第三种计算规则:将Q与K的转置做点积运算, 然后除以一个缩放系数, 再通过softmax处理,最后获得的结果再与V做张量乘法。
注意力机制实现步骤:
1.第一步: 根据注意力计算规则, 对Q,K,V进行相应的计算
2.第二步:
根据第一步采用的计算方法, 如果第一步采用的是纵轴列拼接方法,则第二步中此处需要将Q与第一步的计算结果再进行纵轴列拼接。
如果第一步采用的是转置点积, 一般是自注意力, 那么Q与V相同, 则第二步中此处不需要进行与Q的纵轴列拼接。
3.第三步:
最后为了使整个attention机制按照指定尺寸输出, 使用线性层作用在第二步的计算结果上做一个线性变换, 得到最终对Q的注意力表示.
注意力机制实现步骤简略版:
Q:输入张量进行embedding嵌入化后的张量
K:隐藏状态输入
V:编码器的输出
第一步的前半部分:注意力权重矩阵attn_weights = softmax(Linear(cat((Q[0], K[0]), 1)), dim=1)
第一步的后半部分:attn_applied = bmm(注意力权重矩阵attn_weights.unsqueeze(0), V)
第二步:output = cat((Q[0], attn_applied[0]), 1)
第三步:output = Linear(output).unsqueeze(0)
"""
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):
"""初始化函数中的参数有5个
1.query_size 代表query三维张量中的最后一维大小
2.key_size 代表key的最后一维大小
3.value = (1, value_size1, value_size2)
value_size1代表value三维张量中的倒数第二维大小
value_size2代表value三维张量中的倒数第一维大小
4.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
# 初始化注意力机制实现第一步中需要的线性层
# 输入维度:query_size+key_size 即32+32=64。输出维度:value_size1代表value三维张量中的倒数第二维大小32。
self.attn = nn.Linear(self.query_size + self.key_size, value_size1)
# 初始化注意力机制实现第三步中需要的线性层.
# 输入维度:query_size+value_size2 即32+64=96。输出维度:output_size输出的最后一维大小64。
self.attn_combine = nn.Linear(self.query_size + value_size2, output_size)
def forward(self, Q, K, V):
"""
forward函数的输入参数有三个, 分别是Q, K, V, 根据模型训练常识, 输入给Attion机制的张量一般情况都是三维张量,
因此这里也假设Q, K, V都是三维张量
"""
# 第一步的前半部分:
# 按照第一种的注意力计算规则进行计算,此处案例 采用常见的第一种注意力计算规则。
# 第一种注意力计算规则:即将Q和K进行纵轴列拼接, 然后对拼接的结果做一次线性变化, 最后使用softmax处理后获得的结果再进行与V做张量乘法。
# 注意力权重矩阵attn_weights:Q和K进行纵轴列拼接后再做softmax运算后的的结果作为 注意力权重矩阵attn_weights.
#1.torch.cat((Q[0], K[0]), 1)的结果的shape为:(1, query_size+key_size) 即 (1, 32+32) 得 (1, 64)
#2.attn线性层Linear的输入和输出:输入(1, 64) 输出 (1, 32)
attn_weights = F.softmax(self.attn(torch.cat((Q[0], K[0]), 1)), dim=1)
# print("torch.cat((Q[0], K[0]), 1):",torch.cat((Q[0], K[0]), 1).shape) #torch.Size([1, 64])
# print("attn_weights:",attn_weights.shape) #torch.Size([1, 32])
# 第一步的后半部分:
# 1.将得到的注意力权重矩阵attn_weights与V做矩阵乘法计算,当二者都是三维张量且第一维代表为batch size时, 两者之间则做bmm运算。
# 2.bmm运算例子:保持第一个维度batch size不变的情况下,两者后面的两个维度进行点乘运算 即[n,m]乘以[m,p]结果等于[n,p],
# [batch size,n,m]乘以[batch size,m,p]结果即等于[batch size,n,p]
# 3.注意力权重矩阵attn_weights与V做bmm运算:
# 注意力权重矩阵attn_weights的shape:(1, value_size1) 即 (1, 32)
# V的shape:(1, value_size1, value_size2) 即 (1,32,64)
# 两者做bmm运算即 (1, 1, 32) 乘以 (1,32,64) 得 (1,1, 64),其中(1, 32)乘以(32,64)得(1, 64)
attn_applied = torch.bmm(attn_weights.unsqueeze(0), V)
# print("attn_applied:",attn_applied.shape) #torch.Size([1, 1, 64])
# 第二步:
# 1.根据第一步采用的计算方法, 如果第一步采用的是纵轴列拼接方法,则第二步中此处需要将Q与第一步的计算结果再进行纵轴列拼接。
# 如果第一步采用的是转置点积, 一般是自注意力, 那么Q与V相同, 则第二步中此处不需要进行与Q的纵轴列拼接。
# 2.通过取[0]是用来降维,即仅取出后面两维数据。
# 3.因为第一步采用的计算方法为纵轴列拼接方法,所以第二步中此处需要将Q与第一步的计算结果再进行纵轴列拼接。
# 4.Q的shape:(1,1,32)。attn_applied的shape:(1,1, 64)。
# cat((Q[0], attn_applied[0]), 1) 纵轴列拼接的结果为(1,32+64) 即(1,96)
output = torch.cat((Q[0], attn_applied[0]), 1)
# print("output:",output.shape) #torch.Size([1, 96])
# 第三步:
# 1.使用线性层作用在第二步的结果上做一个线性变换并扩展维度,得到最终输出。
# 因为要保证输出也是三维张量, 因此使用unsqueeze(0)扩展维度。
# 2.output的shape为(1,96),attn_combine线性层输入维度为96,输出维度为64。
output = self.attn_combine(output).unsqueeze(0)
# print("output:",output.shape) #torch.Size([1, 1, 64])
#返回第三步最终输出值output 和 第一步的前半部分计算出的注意力权重矩阵attn_weights
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])
import torch
import torch.nn as nn
import torch.nn.functional as F
# 参数一: 输入x的特征维度, 词嵌入的维度; 参数二: 隐藏层神经元的个数; 参数三: 隐藏层的层数
rnn = nn.RNN(5, 6, 1)
# 1: 代表当前批次的样本个数, 3: 当前样本的sequence_length, 5: 词嵌入的维度
input1 = torch.randn(1, 3, 5)
# 1: 隐藏层的层数, 3: 当前样本的sequence_length, 6: 隐藏层神经元的个数
h0 = torch.randn(1, 3, 6)
output, hn = rnn(input1, h0)
# print(output)
# print(hn)
# -----------------------------------------------
# 参数一: 输入x的特征维度, 词嵌入的维度; 参数二: 隐藏层神经元的个数; 参数三: 隐藏层的层数
lstm = nn.LSTM(5, 6, 2)
# 1: 代表当前批次的样本个数, 3: 当前样本的sequence_length, 5: 词嵌入的维度
input1 = torch.randn(1, 3, 5)
# 2: 隐藏层的层数, 3: 当前样本的sequence_length, 6: 隐藏层神经元的个数
h0 = torch.randn(2, 3, 6)
c0 = torch.randn(2, 3, 6)
output, (hn, cn) = lstm(input1, (h0, c0))
# print(output)
# print(output.shape)
# print(hn)
# print(hn.shape)
# print(cn)
# print(cn.shape)
# -----------------------------------------------
# 参数一: 输入x的特征维度, 词嵌入的维度; 参数二: 隐藏层的神经元个数; 参数三: 两个隐藏层
gru = nn.GRU(5, 6, 2)
# 1: 批次样本数量; 3: 序列的长度sequence_length; 5: 词嵌入的维度
input1 = torch.randn(1, 3, 5)
# 2: 两个隐藏层; 3: 序列的长度sequence_length; 6: 隐藏层的神经元个数
h0 = torch.randn(2, 3, 6)
output, hn = gru(input1, h0)
# print(output)
# print(output.shape)
# print(hn)
# print(hn.shape)
# ------------------------------------------------
mat1 = torch.randn(10, 3, 4)
mat2 = torch.randn(10, 4, 5)
res = torch.bmm(mat1, mat2)
# print(res.size())
# ------------------------------------------------
class Attn(nn.Module):
def __init__(self, query_size, key_size, value_size1, value_size2, output_size):
# query_size代表的是Q的最后一个维度, key_size代表的K的最后一个维度
# V的尺寸表示(1, value_sie1, 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, self.value_size1)
# 初始化注意力机制实现制红第三步的线性层
self.attn_combine = nn.Linear(self.query_size + self.value_size2, self.output_size)
def forward(self, Q, K, V):
# 注意我们假定Q ,K, V都是三维张量
#第一步, 将Q, K进行纵轴的拼接,然后做一次线性变换,最后使用softmax进行处理得到注意力向量
attn_weights = F.softmax(self.attn(torch.cat((Q[0], K[0]), 1)), dim=1)
# 将注意力矩阵和V进行一次bmm运算
attn_applied = torch.bmm(attn_weights.unsqueeze(0), V)
# 再次去Q[0]进行降维,再次和上面的运算结果进行一次拼接
output = torch.cat((Q[0], attn_applied[0]), 1)
# 第三步就是将上面的输出进行一次线性变换,然后再扩展维度成3维张量
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)
output = attn(Q, K, V)
# print(output[0])
print(output[0].size())
# print(output[1])
print(output[1].size())