介绍
在其开创性工作(“Attention is All You Need”)中,Vaswani等人介绍了用于机器翻译的Transformer架构。所提出的模型以监督方式训练,训练数据由输入序列X和输出序列Y组成。这些序列是两种语言中的对应句子。在推理过程中,模型以生成方式执行:
ŷ0 = Transformer(X, SOS),
ŷ1 = Transformer(X, SOS, ŷ0),
ŷ2 = Transformer(X, SOS, ŷ0, ŷ1),
…
ŷN = Transformer(X, SOS, ŷ0, ŷ1, ..., ŷN−1) = EOS
以生成与输入序列X对应的输出序列Y = [SOS, ŷ0, ..., ŷN](翻译),其中SOS和EOS是特殊符号(标记)“序列开始”和“序列结束”。在后续工作中,Transformer模型已扩展到其他模态和任务。毫无疑问,Transformer是处理序列数据的最新模型。
时间序列应用于统计学、信号处理、计量经济学、控制工程以及任何涉及时间测量的应用科学和工程领域。时间序列预测是使用时间序列模型根据先前观察到的值来预测未来值。预测问题可以转化为序列到序列(Seq2Seq)模型,并采用(1)中定义的生成推理。因此,将Transformer应用于时间序列预测非常引人关注。
已经提出了几种用于时间序列预测的适应方法。例如,LogSparse Transformer、MTS Transformer、Informer和ContiFormer(见[1])。这些工作解决了时间序列预测的各种挑战,如变化的采样率和长期依赖关系。然而,尚不清楚对“基础”Transformer(Vaswani等人提出)进行哪些最小更改才能使其适应连续数据序列。由于基础Transformer是为离散输入和输出、标记设计的,并不适用于连续数据,因此需要进行一些适应。先前工作中的适应方法相当复杂,引发了疑问,因为它们没有与任何“时间序列Transformer基线”进行比较。此外,由于Zeng等人通过实验证明了一个“令人尴尬地简单”的单层线性回归器在多个基准测试中优于最先进的时间序列Transformer,因此有理由定义一个简单的时间序列Transformer基线——即最小时间序列Transformer。
方法
基础Transformer
该类使用流行的PyTorch库中的torch.nn.Transformer类作为主要构建块。
class Seq2SeqTransformer(nn.Module):
# Constructor
def __init__(
self,
num_tokens,
d_model,
nhead,
num_encoder_layers,
num_decoder_layers,
dim_feedforward,
dropout_p,
layer_norm_eps,
padding_idx = None
):
super().__init__()
self.d_model = d_model
self.padding_idx = padding_idx
if padding_idx != None:
# Token embedding layer - this takes care of converting integer to vectors
self.embedding = nn.Embedding(num_tokens+1, d_model, padding_idx = self.padding_idx)
else:
# Token embedding layer - this takes care of converting integer to vectors
self.embedding = nn.Embedding(num_tokens, d_model)
# Token "unembedding" to one-hot token vector
self.unembedding = nn.Linear(d_model, num_tokens)
# Positional encoding
self.positional_encoder = PositionalEncoding(d_model=d_model, dropout=dropout_p)
# nn.Transformer that does the magic
self.transformer = nn.Transformer(
d_model = d_model,
nhead = nhead,
num_encoder_layers = num_encoder_layers,
num_decoder_layers = num_decoder_layers,
dim_feedforward = dim_feedforward,
dropout = dropout_p,
layer_norm_eps = layer_norm_eps,
norm_first = True
)
def forward(
self,
src,
tgt,
tgt_mask = None,
src_key_padding_mask = None,
tgt_key_padding_mask = None
):
# Note: src & tgt default size is (seq_length, batch_num, feat_dim)
# Token embedding
src = self.embedding(src) * math.sqrt(self.d_model)
tgt = self.embedding(tgt) * math.sqrt(self.d_model)
# Positional encoding - this is sensitive that data _must_ be seq len x batch num x feat dim
# Inference often misses the batch num
if src.dim() == 2: # seq len x feat dim
src = torch.unsqueeze(src,1)
src = self.positional_encoder(src)
if tgt.dim() == 2: # seq len x feat dim
tgt = torch.unsqueeze(tgt,1)
tgt = self.positional_encoder(tgt)
# Transformer output
out = self.transformer(src, tgt, tgt_mask=tgt_mask, src_key_padding_mask = src_key_padding_mask,
tgt_key_padding_mask=tgt_key_padding_mask, memory_key_padding_mask=src_key_padding_mask)
out = self.unembedding(out)
return out
主要处理步骤包括:1) 使用PyTorch的torch.nn.Embedding类对源序列和目标序列进行嵌入,该类将标记ID(整数值)映射为独热编码向量,并进一步映射为嵌入向量;2) 使用遵循原始“Attention is All You Need”工作的编码类实现位置编码;3) 线性“反嵌入”层,将Transformer输出转换为类别概率。请注意,PyTorch实现的交叉熵损失中包含了softmax非线性函数,因此它并未在forward函数中显式出现。PyTorch的Transformer类负责屏蔽未来输出和填充键,并且参考代码中提供了屏蔽构造方法。一个通用且常令人困惑的要求是,源变量和目标变量必须是三维的:序列长度 × 批次数量 × 特征向量维度。在类中,有一些繁琐的步骤用于保持正确的形状,这些步骤往往是导致编程错误的原因。
最小时间序列Transformer —— MiTS-Transformer
对离散标记Seq2SeqTransformer的最小适应是将“整数到向量”嵌入层(torch.nn.Embedding)更改为将连续值向量转换为模型维度向量的层——“向量到向量”。实现这一点的神经网络技巧是用线性层替换嵌入层。这可以通过对原始代码进行小的更改来实现。原始嵌入代码如下:
# Token embedding layer - this takes care of converting integer to vectors
self.embedding = nn.Embedding(num_tokens, d_model)
# Token "unembedding" to one-hot token vector
self.unembedding = nn.Linear(d_model, num_tokens)
# Positional encoding
self.positional_encoder = PositionalEncoding(d_model=d_model, dropout=dropout_p)
被替换为
# For continuous embedding & unembedding
self.embedding = nn.Linear(d_input, d_model)
self.unembedding = nn.Linear(d_model, d_input)
在连续情况下,嵌入将d_input维的样本映射到d_model维的模型向量。在反嵌入步骤中,转换是反向进行的。
位置编码扩展——PoTS-Transformer
在时间序列预测中,Transformer面临三个潜在挑战:
解决这些挑战的直接方法存在冲突。长序列需要高维模型,以便有“空间”来容纳位置信息。数据量小使得训练大型模型因过拟合而变得困难。相邻样本之间的强相关性使得可以使用小型模型,因为每个新样本提供的新信息量微乎其微。为了满足这些相互冲突的要求,需要采用特殊技巧。
在现有文献中,特别是长序列问题受到了关注。例如,对序列进行对数稀疏采样,使得位置编码变为对数形式,从而减少了对高维模型的需求。Wav2vec 2.0是一种用于音频特征提取的最先进音频骨干网络。它首先应用卷积滤波器,然后使用最近邻乘积量化器,将长音频序列压缩为更紧凑的表示——“音频标记”。
针对这些挑战,最简单的解决方案是什么?在PoTS-Transformer中,保持模型规模较小以避免过拟合。同时,位置编码在更高维的空间中进行,以确保其对长序列的有效性。这是通过在两个线性层之间包裹位置编码器来实现的,这两个线性层首先执行位置编码扩展,然后在编码步骤后进行逆位置扩展。
.init()init()
# Positional encoding expansion
self.pos_expansion = nn.Linear(d_model, pos_expansion_dim)
self.pos_unexpansion = nn.Linear(pos_expansion_dim, d_model)
.forward()
src = self.pos_expansion(src)
src = self.positional_encoder(src)
src = self.pos_unexpansion(src)
在PoTS-Transformer中,使用8维向量进行Transformer模型计算,并使用128维向量进行位置编码,这使得可学习参数的数量从1,433增加到3,473(2.4倍)。相比之下,将MiTS-Transformer的模型大小从8维空间增加到128维空间,参数数量从1,289增加到204,689(158倍)。差异接近两个数量级。
训练细节
所有Transformer模型均使用标准PyTorch Adam优化器(torch.optim.Adam)进行训练,初始学习率设为0.023。使用多步调度器,一旦达到里程碑之一,就将每个参数组的学习率按gamma衰减。gamma固定为0.1,里程碑针对每种情况单独手动优化,并记录目标损失,以便复现所有实验。
然而,使用固定的学习率0.023、不进行调度并训练2000个周期,也可以获得相似的结果。在我们实验中,最大模型的训练在没有GPU的标准笔记本电脑上仅需几分钟。
数据
实验所用的Seq2Seq数据是通过采样正弦函数生成的:
y(t) = sin 2πf t
其中f是正弦波的频率,t定义时间瞬间。对于离散信号,将频率定义为每样本数的波形离散频率较为方便。例如,f = 1/31表示每31个样本有1个波形。
def gen_sinusoidal(seq_len_, disc_freq_):
#seq_len: Anything
#disc_freq: waves per signal lenght, e.g., 2/seq_len
t = np.arange(seq_len_)
y = np.sin(2*np.pi*disc_freq_*t)
return t,y
sin_len = 31
max_f = 1/sin_len # To make sure no sampling effects occur too much
t,y = gen_sinusoidal(sin_len, max_f)
print(f'Sequence length is {sin_len} and frequency {max_f:.4f} (meaning {max_f*sin_len:.4f} waves per seg) ')
plt.plot(t,y,'b-')
plt.plot(t,y,'bx')
#plt.savefig('sin_example_4.png', bbox_inches='tight')
plt.show()
当频率增加时,采样误差开始出现,但在我们的情况下,这并不重要(见下面的波形峰值)。
数据类型
生成了三种类型的数据:
U()表示均匀随机分布。类型1用于对模型进行基本检查。类型2是一个简单情况,其中只有少数不同的序列。类型3是最困难的情况,因为两个频率相近的正弦波之间的差异可能非常微妙。在实验中,信号长度设置为31,其中19个作为输入(源)样本X,12个作为输出(目标)样本Y。
实验
使用单个固定序列进行代码基本检查(类型1序列)
基本检查是使用长度为L = 31、频率为f = 1/31的单个正弦波进行的。在训练集和测试集中,该信号被重复了100次,以模拟标准训练过程。
MiTS-Transformer — 该模型在使用固定学习率0.023的情况下,经过200个周期后收敛,并获得了最终的均方误差(MSE)0.23。
多个序列(类型2)
MiTS-Transformer — 结果如下所示。单个序列和多个(四个)序列的准确度之间几乎没有差异。
任意序列(类型3)
MiTS-Transformer — 以下是使用相同训练和测试数据进行三次不同运行的结果和样本示例。
结果证明,最小时间序列Transformer能够处理序列中任意微小变化的数据。结果验证了模型具有一定的插值能力,因为许多测试序列并未出现在训练数据中。对于任意序列的结果几乎比固定类型1和类型2序列的结果差一个数量级。通过增加模型维度(d_model),可以提高MiTS-Transformer的学习能力。以下是模型维度设置为16和32时的结果。
大小为16的模型系统地优于大小为8的模型,但大小为32的模型开始偶尔出现过拟合训练数据的情况。8、16和32模型中的可学习参数总数分别为1289、4097和14321。
PoTS-Transformer — 实验使用位置扩展维度64和2385个可学习参数进行。以下结果显示,其性能系统地优于MiTS-Transformer。
结论
本工作及其附带的Jupyter笔记本的目的是研究如何将针对离散标记的“Attention is All You Need”(原生)Transformer适应于时间序列(连续)数据。最小的适应性改变是将标记嵌入层改为线性层。这一改变在我们的最小时间序列Transformer(MiTS-Transformer)中得到了实现,该模型能够很好地学习正弦波。MiTS-Transformer的学习能力可以通过改变模型维度来调整。然而,这很快就会导致可学习模型参数方面的模型规模爆炸,并且模型容易过拟合。针对这一问题,我们提出了一个简单的模型,称为位置编码扩展时间序列Transformer(PoTS-Transformer)。它结合了扩展空间中长序列的位置编码和低维模型,以避免过拟合。我们实验中的结果令人信服,并为基于Transformer的时间序列预测提供了类似的最小且简单的技巧。