时序Transformer代码复现心得
Vaswani A. Attention is all you need[J]. Advances in Neural Information Processing Systems, 2017.
代码部分
def forecast(self, x_enc, x_mark_enc, x_dec, x_mark_dec):
进预测模块,此时他四种信息都用到了
# Embedding
enc_out = self.enc_embedding(x_enc, x_mark_enc)#b,seq,dmodel
#encout: b, seq, dmodel
- 先进embedding
x = self.value_embedding(
x) + self.temporal_embedding(x_mark) + self.position_embedding(x)
return self.dropout(x)
三种嵌入方法,这是第一种TokenEmbedding
class TokenEmbedding(nn.Module):
self.tokenConv = nn.Conv1d(in_channels=c_in, out_channels=d_model,
kernel_size=3, padding=padding, padding_mode='circular', bias=False)##循环填充方式对时序数据比较有用
#B,seq,N
x = self.tokenConv(x.permute(0, 2, 1)).transpose(1, 2)
#B,seq,dmodel
#通过卷积N-dmodel,有维度变换,N必须在中间
return x
用的1D卷积做的,把特征N映射到demodel,并且用到了循环填充方式
class TimeFeatureEmbedding(nn.Module):
第二种时间信息嵌入,根据时间步长,弄一个线性层
freq_map = {'h': 4, 't': 5, 's': 6,
'm': 1, 'a': 1, 'w': 2, 'd': 3, 'b': 3}
d_inp = freq_map[freq]
self.embed = nn.Linear(d_inp, d_model, bias=False)
def forward(self, x):
return self.embed(x)#也是一个线性层
第三种pos位置嵌入
class PositionalEmbedding(nn.Module):
def __init__(self, d_model, max_len=5000):
super(PositionalEmbedding, self).__init__()
# Compute the positional encodings once in log space.
pe = torch.zeros(max_len, d_model).float()#不会传最大长度
pe.require_grad = False#训练过程中,位置编码的参数不会更新。这是因为位置编码是固定的,不需要学习
position = torch.arange(0, max_len).float().unsqueeze(1)
div_term = (torch.arange(0, d_model, 2).float()
* -(math.log(10000.0) / d_model)).exp()
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
pe = pe.unsqueeze(0)
self.register_buffer('pe', pe)#它将被模型保留,但不会被优化器更新。
def forward(self, x):
return self.pe[:, :x.size(1)]#传过去batch和seq
在训练过程中,位置编码的参数不会更新。这是因为位置编码是固定的,不需要学习。
到此嵌入部分就完了,此时维度#encout: b, seq, dmodel、接下来进encoder
enc_out, attns = self.encoder(enc_out, attn_mask=None)#attns:none,none
# b, seq, dmodel
class EncoderLayer(nn.Module):
new_x, attn = self.attention(
x, x, x,#自注意力qkv
attn_mask=attn_mask,
tau=tau, delta=delta
)
x = x + self.dropout(new_x)#残差连接
先进注意力
B, L, _ = queries.shape#B,seq,dmodel
_, S, _ = keys.shape
H = self.n_heads
#其实L和S是一个数
queries = self.query_projection(queries).view(B, L, H, -1)#B, L, H, dmodel/h
keys = self.key_projection(keys).view(B, S, H, -1)#一样的计算方法
values = self.value_projection(values).view(B, S, H, -1)#H 表示头的数量-1 表示自动计算该维度
normal
def forward(self, queries, keys, values, attn_mask, tau=None, delta=None):
B, L, H, E = queries.shape#B,seq,head ,64
_, S, _, D = values.shape
scale = self.scale or 1. / sqrt(E)#注意力权重的缩放因子
scores = torch.einsum("blhe,bshe->bhls", queries, keys)#张量乘法
#blhe 和 bshe 分别代表 queries 和 keys 张量的维度标签
用的fullattention
dec_out = self.dec_embedding(x_dec, x_mark_dec)
# b,label+pred,dmodel
来到dec_embedding,维度会变成b,label+pred,dmodel
和encoder一样的嵌入流程,区别只是输入数据不一样
这里省略直接到decoder
# b,label+pred,dmodel
dec_out = self.decoder(dec_out, enc_out, x_mask=None, cross_mask=None)
# b,label+ored,N
class DecoderLayer(nn.Module):
def forward(self, x, cross, x_mask=None, cross_mask=None, tau=None, delta=None):
x是dec_out,cross是enc_out要在decoder做交叉注意力
x = x + self.dropout(self.self_attention(#残差连接
x, x, x,
attn_mask=x_mask,
tau=tau, delta=None
)[0])#自注意力
x = self.norm1(x)
不过要先做一次自注意力,也是全连接
if self.mask_flag:
if attn_mask is None:
attn_mask = TriangularCausalMask(B, L, device=queries.device)
scores.masked_fill_(attn_mask.mask, -np.inf)
和encoder不同的是,decoder的注意力计算创建了一个下三角的掩码,确保在序列中的位置只能关注它之前的位置,而不能关注它之后的位置
x = x + self.dropout(self.cross_attention(
x, cross, cross,
attn_mask=cross_mask,
tau=tau, delta=delta
)[0])#必不可少,decoder提供Q
之后再执行一次交叉注意力,也是fullattention,不过没用到掩码
y = x = self.norm2(x)
y = self.dropout(self.activation(self.conv1(y.transpose(-1, 1))))
y = self.dropout(self.conv2(y).transpose(-1, 1))
return self.norm3(x + y)
自注意力和交叉注意力完了之后就是很多个线性层
直接跟着归一化和预测头
if self.norm is not None:
x = self.norm(x)
if self.projection is not None:
x = self.projection(x)
return x
总结
和encoder不同的是,decoder的注意力计算创建了一个下三角的掩码,确保在序列中的位置只能关注它之前的位置,而不能关注它之后的位置
只有decoder的自注意力用到了掩码