#创作灵感#
时间序列预测模型中的嵌入表示
Autoformer中的嵌入表示代码:共包含值嵌入、位置嵌入和时间维度嵌入
class DataEmbedding_wo_pos(nn.Module):
def __init__(self, c_in, d_model, embed_type='fixed', freq='h', dropout=0.1):
super(DataEmbedding_wo_pos, self).__init__()
self.value_embedding = TokenEmbedding(c_in=c_in, d_model=d_model)
self.position_embedding = PositionalEmbedding(d_model=d_model)
self.temporal_embedding = TemporalEmbedding(d_model=d_model, embed_type=embed_type,
freq=freq) if embed_type != 'timeF' else TimeFeatureEmbedding(
d_model=d_model, embed_type=embed_type, freq=freq)
self.dropout = nn.Dropout(p=dropout)
def forward(self, x, x_mark):
if x_mark is None:
x = self.value_embedding(x)
else:
x = self.value_embedding(x) + self.temporal_embedding(x_mark)
return self.dropout(x)
值嵌入详解:
class TokenEmbedding(nn.Module):
def __init__(self, c_in, d_model):
super(TokenEmbedding, self).__init__()
padding = 1 if torch.__version__ >= '1.5.0' else 2
self.tokenConv = nn.Conv1d(in_channels=c_in, out_channels=d_model,
kernel_size=3, padding=padding, padding_mode='circular', bias=False)
for m in self.modules():
if isinstance(m, nn.Conv1d):
nn.init.kaiming_normal_(
m.weight, mode='fan_in', nonlinearity='leaky_relu')
def forward(self, x):
x = self.tokenConv(x.permute(0, 2, 1)).transpose(1, 2)
return x
值嵌入的本质是定义了一个一维卷积层 self.tokenConv
,其输入通道数为 c_in
,输出通道数为 d_model(嵌入维度)
,卷积核大小为 3。填充方式根据前面设置的 padding
值,并使用循环填充(padding_mode='circular'
)。此卷积层不使用偏置项(bias=False
)。
随后遍历模块中的所有子模块,如果子模块是 nn.Conv1d
类型,则使用 Kaiming 正态初始化方法(He 初始化)初始化其权重。mode='fan_in'
和 nonlinearity='leaky_relu'
指定了初始化的模式和非线性函数。
位置嵌入:
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)]
位置编码在 Transformer 模型中非常重要,因为 Transformer 本身不具备顺序感知能力。位置编码能向输入序列中添加位置信息。
代码中首先创建一个大小为 (max_len, d_model)
的零矩阵 pe
,并将其设置为不需要梯度计算,因为位置编码是固定的,不需要在训练过程中更新。
然后生成一个从 0
到 max_len-1
的序列 position
,并通过 unsqueeze(1)
转换成列向量。
然后计算除数项 div_term
,它是用于位置编码的周期性变化因子,通过指数运算得到。
将 position
和 div_term
相乘的结果分别传给 torch.sin
和 torch.cos
函数,生成位置编码的奇数和偶数列。
将 pe
扩展一个维度,使其形状变为 (1, max_len, d_model)
。register_buffer
方法将 pe
注册为模块的缓冲区,这意味着它不会作为模型参数被更新,但会作为模型的一部分保存和加载。
在前向传播中,输入 x
的形状为 (batch_size, seq_len, d_model)
。这个方法返回位置编码 pe
中对应于输入序列长度 seq_len
的部分。
时间嵌入:
class TemporalEmbedding(nn.Module):
def __init__(self, d_model, embed_type='fixed', freq='h'):
super(TemporalEmbedding, self).__init__()
minute_size = 4
hour_size = 24
weekday_size = 7
day_size = 32
month_size = 13
Embed = FixedEmbedding if embed_type == 'fixed' else nn.Embedding
if freq == 't':
self.minute_embed = Embed(minute_size, d_model)
self.hour_embed = Embed(hour_size, d_model)
self.weekday_embed = Embed(weekday_size, d_model)
self.day_embed = Embed(day_size, d_model)
self.month_embed = Embed(month_size, d_model)
def forward(self, x):
x = x.long()
minute_x = self.minute_embed(x[:, :, 4]) if hasattr(
self, 'minute_embed') else 0.
hour_x = self.hour_embed(x[:, :, 3])
weekday_x = self.weekday_embed(x[:, :, 2])
day_x = self.day_embed(x[:, :, 1])
month_x = self.month_embed(x[:, :, 0])
return hour_x + weekday_x + day_x + month_x + minute_x
首先构造函数里定义了与时间特征相关的大小:
minute_size
:一分钟有 4 个时间段(假设)。hour_size
:一天有 24 小时。weekday_size
:一周有 7 天。day_size
:一个月最多有 31 天(所以用 32 作为大小)。month_size
:一年有 12 个月(所以用 13 作为大小)。根据embed_type
选择嵌入方式。
如果 embed_type
是 fixed
,使用 FixedEmbedding
,否则使用 nn.Embedding
(PyTorch 的嵌入层)。
根据 freq
的值,如果是分钟级别('t'
),则创建 minute_embed
。无论如何,都会创建 hour_embed
、weekday_embed
、day_embed
和 month_embed
。
在前向传播中,输入 x
的形状假定为 (batch_size, seq_len, feature_size)
,其中 feature_size
至少为 5(分别表示月份、日期、星期几、小时和分钟)。
- 将输入转换为长整型。
- 根据是否存在
minute_embed
,提取分钟嵌入。 - 提取小时、星期几、日期和月份的嵌入。
- 返回所有这些嵌入的和。
固定嵌入的代码为:
class FixedEmbedding(nn.Module):
def __init__(self, c_in, d_model):
super(FixedEmbedding, self).__init__()
w = torch.zeros(c_in, d_model).float()
w.require_grad = False
position = torch.arange(0, c_in).float().unsqueeze(1)
div_term = (torch.arange(0, d_model, 2).float()
* -(math.log(10000.0) / d_model)).exp()
w[:, 0::2] = torch.sin(position * div_term)
w[:, 1::2] = torch.cos(position * div_term)
self.emb = nn.Embedding(c_in, d_model)
self.emb.weight = nn.Parameter(w, requires_grad=False)
def forward(self, x):
return self.emb(x).detach()
其类似位置嵌入,但是编码固定,不更新
创建一个大小为 (c_in, d_model)
的零矩阵 w
,并将其设置为不需要梯度计算,因为固定嵌入在训练过程中不会更新。
创建一个嵌入层 self.emb
,大小为 (c_in, d_model)
,然后将其权重设置为之前计算的固定嵌入矩阵 w
,并且设置 requires_grad=False
,以确保这些权重不会在训练过程中更新。
在前向传播中,输入 x
是一个索引序列。通过 self.emb(x)
获取对应的嵌入,并通过 .detach()
确保返回的嵌入在计算图中是独立的,不会反向传播梯度。