学习资料来源:
GPT-Universal Primer
Transformer_哔哩哔哩_bilibili(李宏毅2020机器学习深度学习)
在讨论自注意力(self-attention)机制之前,需要先对序列数据处理有基本认识
什么是序列数据:
序列数据处理指的是处理一系列有序数据的过程。这类数据通常是时间相关的,如文本、音频或时间序列数据。比如在自然语言处理中,句子就是一个序列,每个词都是序列中的一个元素。处理这些数据时,模型需要考虑元素之间的顺序和上下文关系。
在图像处理中,序列数据处理的概念可以扩展到图像的像素或特征图的序列。虽然图像是二维数据(宽度和高度),但处理图像时,可以将每一行像素视为一个序列,或者将图像的特征图(由卷积层生成)视为多个序列:
-
像素序列:
每个图像由许多像素组成,每个像素可以看作是图像中的一个元素。当我们处理图像时,模型可能需要考虑像素的空间位置关系。 -
特征图序列:
在卷积神经网络(CNN)中,特征图是通过卷积层提取的。这些特征图可以看作是图像中不同特征的表示。例如,边缘、纹理或颜色等。 -
时序图像:
在视频处理中,图像序列(帧)可以看作是一个时间序列,每一帧都与前一帧和后一帧相关联。
自注意力在图像中的应用
自注意力机制允许模型在处理图像时,关注不同位置的像素或特征。例如,在计算某个像素的表示时,可以考虑图像中所有其他像素的信息,从而捕获全局上下文。
这在处理复杂图像时非常有效,因为它能够理解长距离依赖关系,比如在理解一个场景时,前景和背景的关系。
先了解自注意力的简单框架:
-
输入表示:
将输入数据(如词嵌入或特征图)转换为查询(Query)、键(Key)和值(Value)的表示。下面再结合例子分析这三个是什么东东。 -
计算注意力分数:
通过点积计算查询和键的相似度,生成一个注意力分数矩阵。 -
归一化注意力分数:
使用Softmax函数将注意力分数转换为权重,使它们总和为1。 -
加权求和:
将权重应用于值(Value),得到加权求和的输出。
那么假设我们已经写好了 self_attention函数,它的输入和输出分别是:(下面结合例子具体说明)
# 示例输入
inputs = torch.rand(2, 5, 64) # (batch_size, seq_len, d_model)
output, attention_weights = self_attention(inputs)
print("Output shape:", output.shape) #Output形状为 (2, 5, 64)
print("Attention weights shape:", attention_weights.shape) # Attention weights形状为(2, 5, 5)
以"我喜欢吃苹果"为例解释:
输入解析
句子“我喜欢吃苹果”包含四个词:我、喜欢、吃、苹果。
在代码中输入的则是一个张量(这里省去了从数据库读取的步骤,是随机生成的张量),输入的形状为 (2, 5, 64)
,这意味着:
2
:批次大小(batch size),表示同时处理两个独立的序列。(与举的例子无关,是训练参数)5
:序列长度(sequence length),表示每个序列包含5个元素(例如在这里是5个词)。64
:每个元素的特征维度(d_model),表示每个词或特征由64个数值表示。这些数值通常是通过嵌入层(embedding layer)或卷积层等获得的,捕获了该词的语义信息(即从各个不同的语义角度解释这个词的归属)
这个输入进入函数中会得到什么QKV:
1. 查询(Query)
词:我
目的:希望从句子中获取与“我”相关的信息。可以理解为“我”在句子中所代表的主体。
2. 键(Key)
词:喜欢、吃、苹果
作用:每个词作为键,表示其特征。
3. 值(Value)
词:喜欢、吃、苹果的具体含义
作用:值携带了每个词的信息内容。
当“我”作为查询时,注意力计算过程如下
-
相似度计算:
计算“我”与每个键(喜欢、吃、苹果)之间的相似度。(这是通过计算'我'与每个键的64维·64维/根号计算得到的):
- “我”和“苹果”之间的相关性更低,因为“苹果”是行为的对象。
- “我”和“吃”之间的相关性可能较低,因为“吃”是描述行为的动词。
- “我”和“喜欢”之间可能有较高的相关性,因为“我”表达了主体的情感状态。
-
归一化权重:
- 通过Softmax函数将相似度分数转换为权重,决定“我”需要关注哪些信息。假设经过计算,“我”与“喜欢”的权重最高,其次是“吃”,最后是“苹果”。
-
加权求和:
- 根据权重,将每个词的值进行加权求和。最终得到一个综合表示,强调了与“我”相关的重要信息。这个输出可以理解为一个新的表示,表示“我”在这个句子中的状态和意图。
- 这个计算得到的就是b1
- 以句子“我喜欢吃苹果”为例:
- 如果“我”的原始特征向量是
[0.1, 0.2, ...]
(64维),经过自注意力机制的处理后,输出可能变成[0.3, 0.4, ...]
。这里的
[0.3, 0.4, ...]
是基于“我”与“喜欢”、“吃”、“苹果”的关系调整后的结果,可能强调了“我”与“喜欢”的情感关联。
实际上,这些计算都是使用矩阵计算,同步进行,最终每个词的64维向量都会被调整
结果
在代码中的输出(output)依然是一个张量:输出的形状与输入相同,为(2, 5, 64)。
其中每个元素的向量都被调整。
同时还输出了attention_weights
:
-
这是一个注意力权重矩阵,形状为
(2, 5, 5)
,表示每个元素(查询)与所有其他元素(键)之间的相似度(归一化后)。 -
例如,
attention_weights[0]
将展示第一个句子中每个词相对于其他词的注意力权重
补充,self-attention与其他机器学习算法的关系:
1. 与传统注意力机制的关系:
- 传统注意力:通常用于序列到序列模型(如RNN或LSTM),自注意力是其扩展形式,允许模型在输入序列的每个位置直接关注其他位置。
- 优势:自注意力可以并行处理整个序列,而传统注意力往往依赖于序列的顺序处理,效率较低。
2. 与卷积神经网络(CNN):
- 特征捕捉:CNN通常用于处理图像,利用局部特征提取。然而,自注意力可以捕捉全局信息,更适合长序列或需要全局上下文的任务。
- 结合使用:近年来,一些模型结合了自注意力与CNN,以同时获得局部和全局特征。
3. 与循环神经网络(RNN):
- 序列处理:RNN在处理时间序列或序列数据时表现良好,但在长序列中容易出现梯度消失问题。自注意力通过并行计算和全局依赖性解决了这一问题。
- 替代方案:在许多现代架构(如Transformer)中,自注意力逐渐取代了RNN。
4. 与生成模型的关系:
- 生成对抗网络(GAN):自注意力也可以集成到生成模型中,帮助生成更复杂和高质量的样本,例如在图像生成领域。
- 应用于文本生成:在语言模型(如GPT系列)中,自注意力是核心组成部分,用于生成上下文相关的文本。
5. 与强化学习的关系:
- 决策过程:自注意力可以用于表示状态的特征,使得智能体在做决策时能够更好地考虑不同因素之间的关系。
- 增强模型能力:通过自注意力机制,强化学习模型可以更有效地捕捉环境状态之间的依赖。
最终代码:
import torch
import torch.nn.functional as F
class SelfAttention(torch.nn.Module):
def __init__(self, d_model):
super(SelfAttention, self).__init__()
self.d_model = d_model
self.query_linear = torch.nn.Linear(d_model, d_model)
self.key_linear = torch.nn.Linear(d_model, d_model)
self.value_linear = torch.nn.Linear(d_model, d_model)
def forward(self, x):
# 计算查询、键和值
Q = self.query_linear(x) # (batch_size, seq_len, d_model)
K = self.key_linear(x) # (batch_size, seq_len, d_model)
V = self.value_linear(x) # (batch_size, seq_len, d_model)
# 计算相似度
scores = torch.matmul(Q, K.transpose(-2, -1)) / (self.d_model ** 0.5) # (batch_size, seq_len, seq_len)
# 归一化权重
attention_weights = F.softmax(scores, dim=-1) # (batch_size, seq_len, seq_len)
# 加权求和
output = torch.matmul(attention_weights, V) # (batch_size, seq_len, d_model)
return output, attention_weights
# 示例输入
inputs = torch.rand(2, 5, 64) # (batch_size, seq_len, d_model)
# 创建自注意力模型
self_attention = SelfAttention(d_model=64)
# 计算输出和注意力权重
output, attention_weights = self_attention(inputs)
# 输出结果
print("Output shape:", output.shape) # 应该是 (2, 5, 64)
print("Attention weights shape:", attention_weights.shape) # 应该是 (2, 5, 5)