时间序列模型(四)—— Attention机制
Chen Kai Architect

本文讨论了时间序列模型中的Attention机制,深入探讨了其数学原理和代码实现。Attention机制通过计算输入序列中各位置之间的相似性来生成新的表示,是近年来自然语言处理和时间序列分析中的一项重要技术。它允许模型在处理序列数据时关注不同的输入部分,从而更有效地捕捉序列中的长距离依赖关系。

数学原理

自注意力机制通过计算输入序列中每个位置与其他位置之间的相似度来生成新的表示。具体步骤如下:

输入表示:假设输入序列为 ,每个 是一个向量。

线性变换:通过学习的权重矩阵 将输入序列 转换为查询(Query)、键(Key)和值(Value)向量:

计算注意力得分:通过点积计算查询和键之间的相似度,并使用缩放因子 进行缩放:

归一化注意力得分:使用softmax函数对注意力得分进行归一化,得到注意力权重:

加权求和:将注意力权重应用于值向量,得到最终的注意力输出:

代码实现

以下是一个简单的自注意力机制的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import numpy as np

def scaled_dot_product_attention(Q, K, V, mask=None):
"""
计算注意力权重并应用于值向量。
Q, K, V: 输入的查询、键和值矩阵。
mask: 可选的掩码矩阵,用于遮挡某些位置。
"""
# 获取键向量的最后一个维度大小d_k
d_k = Q.shape[-1]
# 计算查询向量与键向量的点积,并除以sqrt(d_k)进行缩放
scores = np.matmul(Q, K.transpose(-2, -1)) / np.sqrt(d_k)

# 如果提供了掩码矩阵,将掩码为0的地方设置为非常大的负数,避免其对注意力权重的影响
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9)

# 对得分矩阵应用softmax函数,得到注意力权重
attention_weights = np.softmax(scores, axis=-1)
# 使用注意力权重对值向量进行加权求和,得到最终的注意力输出
output = np.matmul(attention_weights, V)
return output, attention_weights

# 示例输入
Q = np.random.rand(1, 10, 64) # (batch_size, seq_len, d_k)
K = np.random.rand(1, 10, 64)
V = np.random.rand(1, 10, 64)

# 计算自注意力
output, attention_weights = scaled_dot_product_attention(Q, K, V)

Seq2Seq with Attention

数学原理

带有注意力机制的Seq2Seq模型通过动态调整解码器对编码器隐藏状态的关注来提高模型性能。以下是其核心原理:

编码器:将输入序列 通过RNN(如LSTM或GRU)处理,生成隐藏状态序列

注意力权重:在解码器的每个时间步 ,计算解码器隐藏状态 与编码器隐藏状态 之间的相似度,得到注意力权重

其中,,通常采用点积、双线性或MLP作为得分函数。

上下文向量:根据注意力权重对编码器隐藏状态加权求和,得到上下文向量

解码器:将上下文向量 与解码器的输入和隐藏状态结合,生成当前时间步的输出。

代码实现

以下是一个带有注意力机制的Seq2Seq模型的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import torch
import torch.nn as nn
import torch.optim as optim

# 定义注意力机制的类
class Attention(nn.Module):
def __init__(self, hidden_dim):
super(Attention, self).__init__()
# 定义线性变换,用于将输入的hidden和encoder_outputs连接起来
self.attn = nn.Linear(hidden_dim * 2, hidden_dim)
# 定义一个可训练的参数向量v
self.v = nn.Parameter(torch.rand(hidden_dim))

def forward(self, hidden, encoder_outputs):
# 获取encoder输出的时间步长度
timestep = encoder_outputs.size(1)
# 重复hidden状态,使其与encoder_outputs的时间步数相同
h = hidden.repeat(timestep, 1, 1).transpose(0, 1)
# 将hidden状态和encoder_outputs连接起来,经过线性变换和tanh激活函数
energy = torch.tanh(self.attn(torch.cat((h, encoder_outputs), 2)))
# 转置energy,使其维度与v向量匹配
energy = energy.transpose(2, 1)
# 重复v向量,使其维度与encoder_outputs的batch大小匹配
v = self.v.repeat(encoder_outputs.size(0), 1).unsqueeze(1)
# 计算注意力权重,使用batch矩阵乘法
attention_weights = torch.bmm(v, energy).squeeze(1)
# 使用softmax函数对注意力权重进行归一化
return torch.softmax(attention_weights, dim=1)

# 定义带有注意力机制的序列到序列模型
class Seq2SeqWithAttention(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super(Seq2SeqWithAttention, self).__init__()
# 定义编码器,使用LSTM
self.encoder = nn.LSTM(input_dim, hidden_dim, batch_first=True)
# 定义解码器,输入是编码器输出的hidden状态和上下文向量的拼接
self.decoder = nn.LSTM(hidden_dim + output_dim, hidden_dim, batch_first=True)
# 定义注意力机制
self.attention = Attention(hidden_dim)
# 定义线性变换,用于将解码器的输出映射到最终的输出维度
self.fc = nn.Linear(hidden_dim * 2, output_dim)

def forward(self, src, trg):
# 通过编码器处理输入序列,得到编码器输出和最后的hidden状态
encoder_outputs, (hidden, cell) = self.encoder(src)
# 初始化输出张量
outputs = torch.zeros(trg.size(0), trg.size(1), trg.size(2)).to(trg.device)
# 将解码器的初始输入设为目标序列的第一个时间步
input = trg[:, 0, :]

for t in range(1, trg.size(1)):
# 计算注意力权重
attention_weights = self.attention(hidden, encoder_outputs)
# 根据注意力权重计算上下文向量
context = attention_weights.unsqueeze(1).bmm(encoder_outputs).squeeze(1)
# 将当前的输入和上下文向量拼接,作为解码器的输入
rnn_input = torch.cat((input, context), dim=1).unsqueeze(1)
# 通过解码器计算输出
output, (hidden, cell) = self.decoder(rnn_input, (hidden, cell))
# 通过线性层映射到最终输出
output = self.fc(torch.cat((output.squeeze(1), context), dim=1))
# 将当前时间步的输出存储到outputs张量中
outputs[:, t, :] = output
# 将当前时间步的输出作为下一个时间步的输入
input = output

return outputs

# 示例输入
input_dim = 10
hidden_dim = 20
output_dim = 10
src = torch.rand(32, 15, input_dim) # (batch_size, src_seq_len, input_dim)
trg = torch.rand(32, 20, output_dim) # (batch_size, trg_seq_len, output_dim)

# 模型实例化
model = Seq2SeqWithAttention(input_dim, hidden_dim, output_dim)
# 通过模型进行前向传播,得到输出
outputs = model(src, trg)

一些小问题

问题1:什么是位置编码(Positional Encoding),为什么需要它?

位置编码是Transformer模型中的一种机制,用于提供输入序列中的位置信息。由于自注意力机制不包含序列的顺序信息,为了让模型知道每个输入的位置,我们需要引入位置编码。位置编码通常通过正弦和余弦函数生成,或者使用可学习的参数。公式如下: 其中, 是位置, 是维度索引, 是嵌入维度。位置编码通过与输入嵌入相加来传递位置信息。

问题2:在多头注意力机制(Multi-Head Attention)中,每个头(head)是如何独立工作的?

在多头注意力机制中,输入序列通过多个独立的注意力头进行处理,每个头有自己独立的查询(Query)、键(Key)和值(Value)权重矩阵。每个头独立计算注意力,得到一组注意力输出。最后,将所有头的输出连接起来,通过线性变换得到最终的输出。这样可以让模型在不同的子空间中并行关注不同的特征,从而增强模型的表达能力。

问题3:如何使用掩码(Mask)来处理变长序列?

掩码用于遮挡序列中的某些部分,使得这些部分不会影响注意力计算。在处理变长序列时,可以使用填充掩码(padding mask)来遮挡填充位置,防止它们对注意力权重产生影响。具体来说,可以将填充位置的注意力得分设置为一个非常大的负数,这样经过softmax函数后,其注意力权重接近于零。

问题4:解释Transformer中的编码器层和解码器层的结构。

Transformer中的编码器层包括一个多头自注意力子层和一个前馈神经网络子层,每个子层后面都跟着一个层归一化(Layer Normalization)和残差连接(Residual Connection)。解码器层除了包含上述两个子层外,还包含一个编码器-解码器多头注意力子层,用于关注编码器的输出。解码器中的自注意力子层会使用掩码来防止未来信息泄露。

问题5:什么是多头注意力机制中的“头”(Head)?

多头注意力机制中的“头”指的是多个独立的注意力计算模块。每个头有独立的查询、键和值权重矩阵,通过不同的视角来计算注意力,从而捕捉输入序列的不同特征。将多个头的输出连接起来可以丰富模型的表示能力。

问题6:如何训练包含注意力机制的Seq2Seq模型?

训练包含注意力机制的Seq2Seq模型与普通Seq2Seq模型类似。首先需要定义损失函数(如交叉熵损失),然后通过前向传播计算损失,通过反向传播更新模型参数。注意力机制会在前向传播中计算注意力权重,并通过梯度传播更新注意力权重的相关参数。

问题7:如何初始化注意力机制中的参数?

注意力机制中的线性变换权重矩阵通常使用标准的权重初始化方法,如Xavier初始化或He初始化。参数向量可以用均匀分布或正态分布进行初始化。Xavier初始化旨在保持前向传播过程中信号的方差不变,其公式为:

其中, 分别是权重矩阵的输入和输出维度。He初始化旨在保持反向传播过程中梯度的方差不变,

具体初始化方法可以根据模型的需要进行调整。

问题8:什么是前馈神经网络(Feed-Forward Neural Network,FFN)在Transformer中的作用?

前馈神经网络(FFN)是Transformer中的一个子层,位于每个编码器和解码器层的多头注意力子层之后。FFN包括两个线性变换和一个激活函数(如ReLU),用于进一步处理注意力子层的输出,增强模型的非线性表示能力。

问题9:Transformer模型相比传统RNN模型有哪些优势?

Transformer模型相比传统RNN模型具有以下优势:

  1. 并行计算:自注意力机制允许对序列中的所有位置同时进行计算,从而提高计算效率。
  2. 长距离依赖:Transformer能够更好地捕捉序列中的长距离依赖关系,而RNN容易出现梯度消失问题。
  3. 表达能力强:多头注意力机制和位置编码增强了模型的表达能力,使其能够处理更加复杂的任务。

问题10:如何在模型训练中使用掩码(mask)来处理自回归任务中的未来信息泄露问题?

在自回归任务中,为了防止解码器在生成下一个时间步时访问未来的输出,需要使用掩码来遮挡未来的信息。具体来说,可以使用下三角掩码(triangular mask)将未来时间步的注意力得分设置为负无穷大,从而确保解码器只能访问当前时间步及之前的输出。这样可以避免未来信息泄露,保证模型的生成过程符合实际情况。

  • Post title:时间序列模型(四)—— Attention机制
  • Post author:Chen Kai
  • Create time:2022-06-15 12:00:00
  • Post link:https://www.chenk.top/时间序列模型(四)—— Attention机制/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.
 Comments