【时间序列】Transformer for TimeSeries时序预测算法详解

一、介绍

1.1 背景

2017年,Google的一篇 Attention Is All You Need 为我们带来了Transformer,其在NLP领域的重大成功展示了它对时序数据的强大建模能力,自然有人想要把Transformer应用到时序数据预测上。在Transformer的基础上构建时序预测能力可以突破以往的诸多限制,最明显的一个增益点是,Transformer for TS可以基于Multi-head Attention结构具备同时建模长期和短期时序特征的能力。

本文将要介绍的一个充分利用了Transformer的优势,并在Transformer的基础上改进了Attention的计算方式以适应时序数据,同时提出了一种解决Transformer拓展性差问题的算法: ConvTrans。论文来源为:Shiyang Li, et al. NIPS 2019. Enhancing the Locality and Breaking the Memory Bottleneck of Transformer on Time Series Forecasting并且给出基于PyTorch的具体实现。

1.2 发展历史

一般来说,谈及DL领域时序预测,首先大家会想到RNN类的模型,但RNN在网络加深时存在梯度消失和梯度爆炸问题。即使是后续的LSTM,在捕捉长期依赖上依然力不从心。再后面有了Amazon提出的DeepAR,是一种针对大量相关时间序列统一建模的预测算法,该算法使用递归神经网络 (RNN) 结合自回归(AR) 来预测标量时间序列,在大量时间序列上训练自回归递归网络模型,并通过预测目标在序列每个时间步上取值的概率分布来完成预测任务。

本文对上述三种历史算法不做深入分析,概念有不清楚的童鞋自己搜哈。

1.3 ConvTrans

直接说ConvTrans, 其实它与DeepAR有很多相似的地方,比如它也是一个自回归的概率预测模型,对于下一步预测采用分位数 �10 (分位数就是以概率将一批数据进行分割,比如 �10=� 代表一批数据中小于a的数占总数的10%)、 �50 等;再比如ConvTrans也支持协变量预测,可以接受输入比如气温、事件、个体标识等等其他相关变量来辅助预测。

不同的是ConvTrans具备Transformer架构独有的优势,大致为以下四点:

  1. 支持并行,训练得更快。基于RNN的模型中每一个隐状态都依赖于它前一步的隐状态,因此必须从前向后必须逐个计算,每一次都只能前进一步。而Transformer没有这样的约束,输入的序列被并行处理,由此带来更快的训练速度。
  2. 更强的长期依赖建模能力,在长序列上效果更好。在前面提到过,基于RNN的方法面对长序列时无法完全消除梯度消失和梯度爆炸的问题,而Transformer架构则可以解决这个问题
  3. Transformer可以同时建模长期依赖和短期依赖。Multi-head Attention中不同的head可以关注不同的模式。
  4. Transformer的AttentionScore可以提供一定的可解释性。通过可视化AttentionScore可以看到当前预测对历史值注意力的分布。

当然Transformer for TS架构也有相应的缺点:

  1. 是基于序列的编解码结构(seq2seq),编码器和解码器均采用基于自注意力机制的网络,所以计算空间复杂度大,需要处理序列的编解码。
  2. 原始Transformer的自注意力计算方法对局部信息不敏感,使得模型易受异常点影响,带来了潜在的优化问题。

而2019NIPS的论文[1]针对这些缺点做了相应的2点改进:

  1. Convolutional Self-Attention :针对时序数据预测任务的特点,增强对局部上下文信息的关注,使预测更精准 。
  2. LogSparse :解决了Attention计算空间复杂度太高的问题,使模型能处理更长的时间序列数据。

后面会详细展开说明。

二、模型结构

2.1 Convolutional Self-Attention

原始Transformer中的Self-Attention结构如下,细节也可以参考之前的文章

VoidOc:【深度学习】Transformer详解

而论文中设计的Convolutional Self-Attention更适合时序数据, 因为它能够增强模型对时间序列中局部上下文信息的建模能力,从而降低异常点对预测结果的影响程度,提高预测准确性。这也是ConvTrans(Convolution Transformer)的名称由来。

Convolutional Self Attention

Self-Attention中的计算 Q、K、V 的过程可能导致数据的关注点出现异常,如上图中(a)所示,由于之前的注意力得分仅仅是单时间点之间关联的体现,(a)中中间的红点只关注到与它值相近的另一单时间红点,而没有考虑到自身上下文(即变化趋势)。即希望增强局部上下文的建模能力,得到图(c)中的效果。

作者们提出的改进方法是在计算 Query 和 Key 时采用大小大于1(等于1就是原始Transformer)的卷积核来进行卷积操作,如图中(d)所示,从而实现使注意力关注局部上下文,使得更相关的特征能够得到匹配。

观察下图,对比应用不同大小卷积核的模型实现,Convolutional Self-Attention能够更快地拟合,且取得更低的训练损失。

图中k代表卷积核大小,下角标1d代表预测未来一天

并且文中还给出了基于真实世界数据的具体结果对比,来说明k对模型预测准确率的影响:

注:这里以及之后使用的评价指标都是分位数损失 �0.5/�0.9 ,值越低预测越准确。详细定义请参考论文,代码实现参考章节3。

数据集来源:

太阳能: 137条太阳能产出记录,每1h记录一次。 https://www.nrel.gov/grid/solar-power-data.html
电力:来自370个用户的电力消耗数据,原数据间隔15min,简写e-f表示。e-c代表在原数据上每4个点聚合一次,即数据变为每1h的耗电量。  https://archive.ics.uci.edu/ml/machine-learning-databases/00321/LD2011_2014.txt.zip

由于电力数据集相对简单且协变量提供了丰富的信息,改变k值对模型的提升并不明显。但在更加复杂的交通数据集中,更大的k较明显地提升了模型的预测准确度,进一步验证了增强局部信息的必要性。目前k值的设置需要在实践中权衡。

2.2 LogSparse Transformer

针对Transformer的存储瓶颈问题,文中引入了LogSparse机制,那么具体是个啥呢?

我们先来看一下原始Transformer在交通数据集上训练学习得到的注意力得分分布情况:

Transformer在交通数据集上训练学习得到的注意力得分分布

可以看到该模型共10层,图中蓝色、青色、红色的线分别是第2,6,10层的注意力得分,灰色的线为原始数据。

我们注意到:不同层对不同频率信息的关注度不同

  • 第2层(蓝色)倾向于学习每一天的模式
  • 第6层(青色)则更关注周末的影响
  • 而第10层(红色)对最近的时刻(邻近预测点)关注较高。

论文作者们认为引入某种程度的稀疏性,不会显著影响性能,反而为模型带来了处理具备细粒度和强长期依赖的长时间序列的能力。为了使得最终每个点都能接触到它的所有历史值的信息,所以便提出了LogSparse的设计,通过堆叠多个自注意力层来实现这个目的,如下图所示:

设 ��� 为单元 � 在第k 至 k+1 层计算时要访问的单元的索引的集合。在标准的Transformer中, ���= { �;�≤� }, 这表示每一个单元都要访问所有的历史单元以及它自己(如图a所示),那么这样空间复杂度为 �(�2) ,L是序列长度。

如果采用堆叠多层的方式, ���= { �−2���2�,�−2���2�−1,...,�−20,� }

如图b所示,每一层的空间复杂度就降低到了 �(����2�) , 整体空间复杂度就是 �(�(���2�)2) ,解决了Transformer的可扩展性瓶颈。另外作者还提出了一些其他的稀疏性策略, 具体可以参考原论文。

堆叠theorem证明以及其他稀疏策略:Local Attention & Restart Attention

对比在不同限制条件下的预测效果,可以看出LogSparse在更复杂的交通数据集上对模型提升效果更明显,也说明了长期依赖的重要性。


三、具体实现

3.1 Version(1)

一个比较容易上手的简单实现版本(没有用到稀疏策略)是:

GitHub - mlpotter/Transformer_Time_Series: Enhancing the Locality and Breaking the Memory Bottleneck of Transformer on Time Series Forecasting (NeurIPS 2019)​github.com/mlpotter/Transformer_Time_Series

大致涉及几个类:

  • DataProcessor

    
    
  1. class DataProcessor(Dataset):
  2. "" "synthetic time series dataset from section 5.1" ""
  3. def __init__( self,t 0 = 96,N = 4500,transform =None):
  4. "" "
  5. Args:
  6. t0: previous t0 data points to predict from
  7. N: number of data points
  8. transform: any transformations to be applied to time series
  9. " ""
  10. self.t 0 = t 0
  11. self.N = N
  12. self.transform = None
  13. # time points
  14. self.x = torch.cat(N *[torch.arange( 0,t 0 + 24). type(torch.float).unsqueeze( 0)])
  15. # sinuisoidal signal
  16. # 如果用到自己的数据的话,把下面这块改掉就好
  17. # 注意数据输入格式为(N,Nb of timepoints)
  18. # 其中N为你有多少行ts,以电力系统数据为例,一个客户 10天的数据就构成一行ts
  19. # 而nb of timepoints为一行ts中有几个时间点,比如十天小时粒度的,就是 10 * 24 = 240
  20. A 1,A 2,A 3 = 60 * torch.rand( 3,N)
  21. A 4 = torch.max(A 1,A 2)
  22. self.fx = torch.cat([A 1.unsqueeze( 1) *torch.sin(np.pi * self.x[ 0,0: 12] / 6) + 72 ,
  23. A 2.unsqueeze( 1) *torch.sin(np.pi * self.x[ 0,12: 24] / 6) + 72 ,
  24. A 3.unsqueeze( 1) *torch.sin(np.pi * self.x[ 0,24:t 0] / 6) + 72,
  25. A 4.unsqueeze( 1) *torch.sin(np.pi * self.x[ 0,t 0:t 0 + 24] / 12) + 72], 1)
  26. # add noise
  27. self.fx = self.fx + torch.randn( self.fx.shape)
  28. self.masks = self._ generate_square_subsequent_mask(t 0)
  29. # print out shapes to confirm desired output
  30. print( "x: {}*{}". format( *list( self.x.shape)),
  31. "fx: {}*{}". format( *list( self.fx.shape)))
  32. def __len__( self):
  33. return len( self.fx)
  34. def __getitem__( self,idx):
  35. if torch. is_tensor(idx):
  36. idx = idx.tolist()
  37. sample = ( self.x[idx,:],
  38. self.fx[idx,:],
  39. self.masks)
  40. if self.transform:
  41. sample = self.transform(sample)
  42. return sample
  43. def _ generate_square_subsequent_mask( self,t 0):
  44. mask = torch. zeros(t 0 + 24,t 0 + 24)
  45. for i in range( 0,t 0):
  46. mask[i,t 0:] = 1
  47. for i in range(t 0,t 0 + 24):
  48. mask[i,i + 1:] = 1
  49. mask = mask.float().masked_fill(mask = = 1, float( '-inf'))#.masked_fill(mask = = 1, float( 0.0))
  50. return mask
  • TransformerTimeSeries

    
    
  1. class CausalConv 1d(torch.nn.Conv 1d):
  2. def __init__( self,
  3. in_channels,
  4. out_channels,
  5. kernel_ size,
  6. stride = 1,
  7. dilation = 1,
  8. groups = 1,
  9. bias = True):
  10. super(CausalConv 1d, self).__init__(
  11. in_channels,
  12. out_channels,
  13. kernel_ size =kernel_ size,
  14. stride =stride,
  15. padding = 0,
  16. dilation =dilation,
  17. groups =groups,
  18. bias =bias)
  19. self.__padding = (kernel_ size - 1) * dilation
  20. def forward( self, input):
  21. return super(CausalConv 1d, self).forward(F.pad( input, ( self.__padding, 0)))
  22. class context_embedding(torch.nn.Module):
  23. def __init__( self, in_channels = 1,embedding_ size = 256,k = 5):
  24. super(context_embedding, self).__init__()
  25. self.causal_convolution = CausalConv 1d( in_channels,embedding_ size,kernel_ size =k)
  26. def forward( self,x):
  27. x = self.causal_convolution(x)
  28. return F.tanh(x)
  29. # model class
  30. class TransformerTimeSeries(torch.nn.Module):
  31. "" "
  32. Time Series application of transformers based on paper
  33. causal_convolution_layer parameters:
  34. in_channels: the number of features per time point
  35. out_channels: the number of features outputted per time point
  36. kernel_size: k is the width of the 1-D sliding kernel
  37. nn.Transformer parameters:
  38. d_model: the size of the embedding vector (input)
  39. PositionalEncoding parameters:
  40. d_model: the size of the embedding vector (positional vector)
  41. dropout: the dropout to be used on the sum of positional+embedding vector
  42. " ""
  43. def __init__( self):
  44. super(TransformerTimeSeries, self).__init__()
  45. self. input_embedding = causal_convolution_layer.context_embedding( 2,256,9)
  46. self.positional_embedding = torch.nn.Embedding( 512,256)
  47. self.decode_layer = torch.nn.TransformerEncoderLayer(d_model = 256,nhead = 8)
  48. self.transformer_decoder = torch.nn.TransformerEncoder( self.decode_layer, num_layers = 3)
  49. self.fc 1 = torch.nn.Linear( 256,1)
  50. def forward( self,x,y,attention_masks):
  51. # concatenate observed points and time covariate
  52. # (B *feature_ size *n_ time_points)
  53. z = torch.cat((y.unsqueeze( 1),x.unsqueeze( 1)), 1)
  54. # input_embedding returns shape (Batch size,embedding size, sequence len) - > need ( sequence len,Batch size,embedding_ size)
  55. z_embedding = self. input_embedding(z).permute( 2,0,1)
  56. # get my positional embeddings (Batch size, sequence_len, embedding_ size) - > need ( sequence len,Batch size,embedding_ size)
  57. positional_embeddings = self.positional_embedding(x. type(torch.long)).permute( 1,0,2)
  58. input_embedding = z_embedding +positional_embeddings
  59. transformer_embedding = self.transformer_decoder( input_embedding,attention_masks)
  60. output = self.fc 1(transformer_embedding.permute( 1,0,2))
  61. return output

和具体train函数


    
    
  1. def train(train_dataset, test_dataset, t 0, future):
  2. train_dl = DataLoader(train_dataset, batch_ size = 32, shuffle = True)
  3. test_dl = DataLoader( test_dataset, batch_ size = 128)
  4. model = TransformerTimeSeries()
  5. lr = . 0005 # learning rate
  6. opt = torch.optim.Adam(model.parameters(), lr =lr)
  7. epochs = 50
  8. criterion = torch.nn.MSELoss()
  9. train_epoch_loss = []
  10. eval_epoch_loss = []
  11. Rp_best = 1e 5
  12. model_save_path = 'ConvTransformer_nologsparse.pth'
  13. for e, epoch in enumerate(range(epochs)):
  14. train_loss = []
  15. eval_loss = []
  16. l_t = train_epoch(model, train_dl, opt, criterion, t 0)
  17. train_loss.append(l_t)
  18. Rp = test_epoch(model, test_dl, t 0, future)
  19. if Rp_best > Rp:
  20. Rp_best = Rp
  21. torch.save({
  22. 'epoch': epoch,
  23. 'model_state_dict': model.state_dict(),
  24. 'optimizer_state_dict': opt.state_dict(),
  25. 'loss': Rp,
  26. }, model_save_path)
  27. train_epoch_loss.append(np.mean(train_loss))
  28. eval_epoch_loss.append(np.mean(eval_loss))
  29. print( "Epoch {}: Train loss: {} \t Validation loss: {} \t R_p={}". format(e,
  30. np.mean(train_loss),
  31. np.mean(eval_loss), Rp))
  32. print( "Rp best={}". format(Rp_best))

没啥需要特别说明的地方,就是这个作者只是写了一个论文构造数据复现的代码,所以工程性很差,很多地方都写死或者没有抽象出来,拿来即用的同学们需要注意改一下。另外他也没有prediction相关函数,我这边写了一个,仅供参考:

  • prediction

    
    
  1. def prediction(model, dl, t 0, future):
  2. # 预测前先load model, dl就是待预测数据,t 0就是前n和时间点,future就是要预测的n个时间点
  3. # 比如你要用一周内前五天的数据训练模型,来预测后两天的值 t 0 = 5 * 24 = 120, future = 48
  4. with torch. no_grad():
  5. predictions = []
  6. observations = []
  7. for step, (x, y, attention_masks) in enumerate(dl):
  8. # x: (batch_ size, total_ts_ length)
  9. # y: (batch_ size, total_ts_ length)
  10. # ouput:(batch_ size, total_ts_ length, 1)
  11. output = model(x, y, attention_masks[ 0])
  12. history = y[:, :t 0].cpu().numpy().tolist()
  13. for p, o in zip( output.squeeze()[:, (t 0 - 1):(t 0 + future - 1)].cpu().numpy().tolist(),
  14. y[:, t 0:].cpu().numpy().tolist()): # not missing data
  15. predictions.append(p) # (batch_ size, future)
  16. observations.append(o) # (batch_ size, future)
  17. num = 0
  18. den = 0
  19. for hist, y_preds, y_trues in zip(history, predictions, observations):
  20. plot_result(hist, y_preds, y_trues, t 0)
  21. num_i, den_i = Rp_num_den(y_preds, y_trues, . 5)
  22. num + = num_i
  23. den + = den_i
  24. Rp = ( 2 * num) / den
  25. return Rp
  • plot_result

    
    
  1. def plot_result(history, yhat, ytruth, t 0):
  2. # 带上历史值
  3. yhat = history + yhat
  4. ytruth = history + ytruth
  5. # 画图
  6. x = range(len(ytruth))
  7. yhat = np.round(yhat, 2)
  8. ytruth = np.round(ytruth, 2)
  9. plt.figure(facecolor = 'w')
  10. plt.plot(range(len(x)), ytruth, 'green', linewidth = 1.5, label = 'ground truth')
  11. plt.plot(range(len(x)), yhat, 'blue', alpha = 0.8, linewidth = 1.2, label = 'predict value')
  12. # 画条预测起始线
  13. plt.vlines(t 0, yhat.min() * 0.99, yhat.m ax() * 1.01,
  14. alpha = 0.7, colors = "r", linestyles = "dashed")
  15. # plt.text( 0.15, 0.01, error_message, size = 10, alpha = 0.9, transform =plt.gc a().transAxes) # 相对位置,经验设置值
  16. plt.legend(loc = 'best') # 设置标签的位置
  17. plt.grid( True)
  18. plt.show()

画图效果如下

3.2 Version(2)

Log Sparse策略的实现参考了:ghsama/ConvTransformerTimeSeries

3.2.1 涉及GPU训练的朋友们看这里


    
    
  1. # GPU | CPU
  2. device = 'cpu'
  3. if torch.cuda. is_available():
  4. torch. set_ default_tensor_ type(torch.cuda.FloatTensor)
  5. device = 'cuda'
''' Log Sparse Version '''

ForcastConvTransformer


    
    
  1. # Self Attention Class
  2. class SelfAttentionConv(nn.Module):
  3. def __init__( self, k, headers = 8, kernel_ size = 5, mask_ next = True, mask_diag = False):
  4. super().__init__()
  5. self.k, self.headers, self.kernel_ size = k, headers, kernel_ size
  6. self.mask_ next = mask_ next
  7. self.mask_diag = mask_diag
  8. h = headers
  9. # Query, Key and Value Transformations
  10. padding = (kernel_ size - 1)
  11. self.padding_opertor = nn.ConstantPad 1d((padding, 0), 0)
  12. self.toqueries = nn.Conv 1d(k, k * h, kernel_ size, padding = 0, bias = True)
  13. self.tokeys = nn.Conv 1d(k, k * h, kernel_ size, padding = 0, bias = True)
  14. self.tovalues = nn.Conv 1d(k, k * h, kernel_ size = 1, padding = 0, bias = False) # No convolution operated
  15. # Heads unifier
  16. self.unifyheads = nn.Linear(k * h, k)
  17. def forward( self, x):
  18. # Extraction dimensions
  19. b, t, k = x. size() # batch_ size, number_ of_timesteps, number_ of_ time_series
  20. # Checking Embedding dimension
  21. assert self.k = = k, 'Number of time series ' + str(k) + ' didn t much the number of k ' + str(
  22. self.k) + ' in the initiaalization of the attention layer.'
  23. h = self.headers
  24. # Transpose to see the different time series as different channels
  25. x = x.transpose( 1, 2)
  26. x_padded = self.padding_opertor(x)
  27. # Query, Key and Value Transformations
  28. queries = self.toqueries(x_padded).view(b, k, h, t)
  29. keys = self.tokeys(x_padded).view(b, k, h, t)
  30. values = self.tovalues(x).view(b, k, h, t)
  31. # Transposition to return the canonical format
  32. queries = queries.transpose( 1, 2) # batch, header, time serie, time step (b, h, k, t)
  33. queries = queries.transpose( 2, 3) # batch, header, time step, time serie (b, h, t, k)
  34. values = values.transpose( 1, 2) # batch, header, time serie, time step (b, h, k, t)
  35. values = values.transpose( 2, 3) # batch, header, time step, time serie (b, h, t, k)
  36. keys = keys.transpose( 1, 2) # batch, header, time serie, time step (b, h, k, t)
  37. keys = keys.transpose( 2, 3) # batch, header, time step, time serie (b, h, t, k)
  38. # Weights
  39. queries = queries / (k ** (. 25))
  40. keys = keys / (k ** (. 25))
  41. queries = queries.transpose( 1, 2).contiguous().view(b * h, t, k)
  42. keys = keys.transpose( 1, 2).contiguous().view(b * h, t, k)
  43. values = values.transpose( 1, 2).contiguous().view(b * h, t, k)
  44. weights = torch.bmm(queries, keys.transpose( 1, 2))
  45. ## Mask the upper & diag of the attention matrix
  46. if self.mask_ next:
  47. if self.mask_diag:
  48. indices = torch.triu_indices(t, t, offset = 0)
  49. weights[:, indices[ 0], indices[ 1]] = float( '-inf')
  50. else:
  51. indices = torch.triu_indices(t, t, offset = 1)
  52. weights[:, indices[ 0], indices[ 1]] = float( '-inf')
  53. # Softmax
  54. weights = F.softmax(weights, dim = 2)
  55. # Output
  56. output = torch.bmm(weights, values)
  57. output = output.view(b, h, t, k)
  58. output = output.transpose( 1, 2).contiguous().view(b, t, k * h)
  59. return self.unifyheads( output) # shape (b,t,k)
  60. # Conv Transforme Block
  61. class ConvTransformerBLock(nn.Module):
  62. def __init__( self, k, headers, kernel_ size = 5, mask_ next = True, mask_diag = False, dropout_proba = 0.2):
  63. super().__init__()
  64. # Self attention
  65. self.attention = SelfAttentionConv(k, headers, kernel_ size, mask_ next, mask_diag)
  66. # First & Second Norm
  67. self.norm 1 = nn.LayerNorm(k)
  68. self.norm 2 = nn.LayerNorm(k)
  69. # Feed Forward Network
  70. self.feedforward = nn. Sequential(
  71. nn.Linear(k, 4 * k),
  72. nn.ReLU(),
  73. nn.Linear( 4 * k, k)
  74. )
  75. # Dropout funtcion & Relu:
  76. self.dropout = nn.Dropout(p =dropout_proba)
  77. self.activation = nn.ReLU()
  78. def forward( self, x, train = False):
  79. # Self attention + Residual
  80. x = self.attention(x) + x
  81. # Dropout attention
  82. if train:
  83. x = self.dropout(x)
  84. # First Normalization
  85. x = self.norm 1(x)
  86. # Feed Froward network + residual
  87. x = self.feedforward(x) + x
  88. # Second Normalization
  89. x = self.norm 2(x)
  90. return x
  91. # Forcasting Conv Transformer :
  92. class ForcastConvTransformer(nn.Module):
  93. def __init__( self, k, headers, depth, seq_ length, kernel_ size = 5, mask_ next = True, mask_diag = False, dropout_proba = 0.2,
  94. num_tokens =None):
  95. super().__init__()
  96. # Embedding
  97. self.tokens_ in_ count = False
  98. if num_tokens:
  99. self.tokens_ in_ count = True
  100. self.token_embedding = nn.Embedding(num_tokens, k) # ( 369, 1= (nb_ts, k)
  101. # Embedding the position
  102. self.position_embedding = nn.Embedding(seq_ length, k) # ( 500, 1) = (windows_ size, k)
  103. # Number of kind of time series
  104. self.k = k # 没有协变量的情况下,k = 1
  105. self.seq_ length = seq_ length # seq_ length即窗口大小, 数据准备的时候切割好了
  106. # Transformer blocks
  107. tblocks = []
  108. # log sparse 稀疏策略: 采用多层ConvTrans层堆叠的方式
  109. for t in range(depth):
  110. tblocks.append(ConvTransformerBLock(k, headers, kernel_ size, mask_ next, mask_diag, dropout_proba))
  111. self.TransformerBlocks = nn. Sequential( *tblocks)
  112. # Transformation from k dimension to numClasses
  113. self.topreSigma = nn.Linear(k, 1)
  114. self.tomu = nn.Linear(k, 1)
  115. self. plus = nn.Softplus()
  116. def forward( self, x, tokens =None):
  117. b, t, k = x. size()
  118. # checking that the given batch had same number of time series as the BLock had
  119. assert k = = self.k, 'The k :' + str(
  120. self.k) + ' number of timeseries given in the initialization is different than what given in the x :' + str(
  121. k)
  122. assert t = = self.seq_ length, 'The lenght of the timeseries given t ' + str(
  123. t) + ' miss much with the lenght sequence given in the Tranformers initialisation self.seq_length: ' + str(
  124. self.seq_ length)
  125. # Position embedding
  126. pos = torch.arange(t)
  127. self.pos_emb = self.position_embedding(pos).expand(b, t, k)
  128. # Checking token embedding
  129. assert self.tokens_ in_ count = = ( not (tokens is None)), 'self.tokens_in_count = ' + str(
  130. self.tokens_ in_ count) + ' should be equal to (not (tokens is None)) = ' + str(( not (tokens is None)))
  131. if not (tokens is None):
  132. ## checking that the number of tockens corresponde to the number of batch elements
  133. assert tokens. size( 0) = = b
  134. self.tok_emb = self.token_embedding(tokens)
  135. self.tok_emb = self.tok_emb.expand(t, b, k).transpose( 0, 1)
  136. # Adding Pos Embedding and token Embedding to the variable
  137. if not (tokens is None):
  138. x = self.pos_emb + self.tok_emb + x
  139. else:
  140. x = self.pos_emb + x
  141. # Transformer :
  142. x = self.TransformerBlocks(x)
  143. mu = self.tomu(x)
  144. presigma = self.topreSigma(x)
  145. sigma = self. plus(presigma)
  146. return mu, sigma

四、评估指标

时间序列算法预测效果好与否,依赖于一个好的评估指标。

常见的有基于预测误差的各类指标如RMSE、MSE、MAE、MAPE、SMAPE,这边就不赘述了,公式不清楚的同学见:

预测评价指标RMSE、MSE、MAE、MAPE、SMAPE_藏知阁-CSDN博客​blog.csdn.net/guolindonggld/article/details/87856780正在上传…重新上传取消

也可以基于预测x的概率分布来做,如Quantile Loss、CRPS (Continuous Ranked Probability Score)、这篇paper中提及的 ��(�,�^) 等。

郭奇:需求预测准确率(Forecast Accuracy)中分母是实际值好还是预测值好?13 赞同 · 2 评论文章正在上传…重新上传取消

五、效果对比

原论文在真实数据集上进行了训练评估,并与ARIMA,TRMF以及DeepAR等模型进行了对比实验。

对比的基线模型:

    1. ARIMA:将自回归(AR)的算子加上移动平均(MA),就是 ARIMA 算法。回归能够反映数据的周期性规律,和移动平均形成互补,从统计学的角度可以很好的预测一元与时间强相关场景下的时间序列。
    2. TRMF:矩阵分解方法。
    3. DeepAR:基于LSTM的自回归概率预测方法。
    4. DeepState: 基于RNN的状态空间方法。

实验结果

实验表明其在多个公开数据集上取得state-of-the-art。

六、总结

老生常谈,算法再SOTA,还是要结合你具体的业务场景去选择。

在forecast常见的业务场景,传统方法基于统计、自回归的预测方法,针对单条时间线,虽然需要根据具体数据特征实时计算,但是也轻便快速好上手;

相比之下,深度学习方法能同时考虑多条时间序列之间的相关性,并且可以引入协变量辅助模型判断(例如预测未来销售量时,如果只接受时间和历史销售量数据,则是自回归预测,如果可以接受天气、经济指数、政策事件分类等其他协变量,则称为使用协变量进行预测。)适合业务数据量大、全的场景。


声明:
所有文章都为本人的学习笔记,非商用,
目的只求在工作学习过程中通过记录,梳理清楚自己的知识体系。
 

引用

  1. ghsama/ConvTransformerTimeSeries
  2. 时间序列预测的评估指标补遗
  3. https://zhuanlan.zhihu.com/p/391337035
文章知识点与官方知识档案匹配,可进一步学习相关知识
算法技能树首页概览 55730 人正在系统学习中
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Transformer是一种用于时序预测的模型,在NeurIPS 2019的论文《Enhancing the Locality and Breaking the Memory Bottleneck of Transformer on Time Series Forecasting》中进行了详细介绍。这篇论文提出了一种改进的Attention计算方式,以适应时序数据,并引入了一种名为ConvTrans的算法来解决Transformer的拓展性问题。该论文给出了基于PyTorch的具体实现。 Transformer模型的发展历史可以追溯到其原始论文,该论文详细介绍了Transformer的细节和原理。对于深入理解Transformer及其源码解读,您可以参考一些相关论文和资料,其中包括Transformer论文链接和Informer论文链接。 总结来说,Transformer是一种用于时序预测的模型,在NeurIPS 2019论文中进行了改进,并提出了一种解决拓展性问题的算法。关于Transformer的细节和原理,您可以参考相关的论文和资料。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [【时间序列Transformer for TimeSeries时序预测算法详解](https://blog.csdn.net/weixin_64338372/article/details/129865637)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [学习笔记:基于Transformer时间序列预测模型](https://blog.csdn.net/xxt228/article/details/128754364)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值