1. 时间序列分解
采用 移动平均 来平滑周期波动 和 强调 长期趋势。
输入长度L的时间序列:
2. 实验分析
表2:利用MSE度量,在ETT数据集上进行分解的消融研究。 其中 Ours 是本文提出的分解架构引入其他模型。 Seq 采用两个模型分别来预测 预处理分解的 seasonal 和 trend-cyclical 部分。 Promotion(提升) 是 和 Origin 比, 预处理分解(Sep)和本文的分解架构(Ours)的 MSE 提升了多少。说明了 本文的方法提升的MSE 更多。
上表表面:上面的分解方法引入到其他模型 是有效的,预测的长度越长,效果越明显。作者说,这验证了 该分解方法可以泛化到其他模型,释放其它依赖学习机制的能力(?不太懂啥意思),缓解复杂模式 带来的 干扰( distraction)。此外,该分解方法超出了 预处理分解,尽管预处理分解使用了更大的模型和更多的参数。特别是,预处理分解 甚至可能带来负面影响,因为它忽略了组件(components)在长期未来的相互作用,如Transformer[36] predict-720,Informer[42] predict-336。
3. 序列分解分析
如图4所示,如果没有我们的序列分解块,预测模型将无法捕捉 seasonal 部分的增长趋势和峰值。通过增加序列分解块, Autoformer 可以逐步地聚合和细化序列中的 trend-cyclical 部分。这种设计还便于学习 seasonal 部分,特别是高峰和低谷部分。这验证了我们提出的渐进分解架构的必要性。
图4:最后的解码器层学习到的 seasonal 和 trend-cyclical 。我们在解码器中从左到右,逐渐增加了 分解模块。此例来自 ETT 数据集 在输入 96 预测720设置下。为了清晰起见,我们在原始数据中附加了线性增长。
3. pytorch 实现:
class moving_avg(nn.Module):
"""
Moving average block to highlight the trend of time series
"""
def __init__(self, kernel_size, stride):
super(moving_avg, self).__init__()
self.kernel_size = kernel_size
self.avg = nn.AvgPool1d(kernel_size=kernel_size, stride=stride, padding=0)
def forward(self, x):
# padding on the both ends of time series 假设输入x shape: 32, 96, 21 kernel_size = 25
front = x[:, 0:1, :].repeat(1, (self.kernel_size - 1) // 2, 1) # 32, 1, 21 -> 32, 12, 21 取的x 的第一个值进行填充
end = x[:, -1:, :].repeat(1, (self.kernel_size - 1) // 2, 1) # 32, 1, 21 -> 32, 12, 21 取的x 的最后一个值进行填充
x = torch.cat([front, x, end], dim=1) # 24 + 96 = 120, (32, 120, 21)
x = self.avg(x.permute(0, 2, 1)) # (32, 21, 120) # 换位置是因为 AvgPool1d 是在最后一维操作,我们要pool 的是 序列那个维度,故需要把该维度放到最后
x = x.permute(0, 2, 1) # pool 完毕换回来
return x
class series_decomp(nn.Module):
"""
Series decomposition block
"""
def __init__(self, kernel_size):
super(series_decomp, self).__init__()
self.moving_avg = moving_avg(kernel_size, stride=1)
def forward(self, x):
moving_mean = self.moving_avg(x)
res = x - moving_mean
return res, moving_mean
上面公式中所谓的 Padding 实现是代码中的 x = torch.cat([front, x, end], dim=1),front 是把原来输入的第一个复制多次(根据移动平均的长度),end 是把序列的最后一个进行复制多次(同front 次),然后把 它们cat 到一起,然后使用 AvgPool1d 后,结果的序列长度也就同X 一样,不会发生变化。
上图,假设 x 输入长度为 4, kernel_size = 25, 上图就是 Padding 操作,为了保持AvgPool1d 后长度不变。
然后在上面使用 1D 平均pool ,一次移动一步。
感觉这种只取最后一个元素和第一个元素的 Padding 方式并不是一个好办法,应该可以优化该部分。
如果 x 的长度很长,这种方式或许还好,但是如果x 较短,可能问题就比较大了。
4. 移动平均解释
时间邻近的情况下,观测值也很可能接近。由此,平均值消除了数据中的一些随机性,从而我们可以得到较为平滑的趋势周期项。
年份 | 电力销售量 (亿瓦时) | 5-MA |
---|---|---|
1989 | 2354.34 | |
1990 | 2379.71 | |
1991 | 2318.52 | 2381.53 |
1992 | 2468.99 | 2424.56 |
1993 | 2386.09 | 2463.76 |
1994 | 2569.47 | 2552.60 |
1995 | 2575.72 | 2627.70 |
1996 | 2762.72 | 2750.62 |
1997 | 2844.50 | 2858.35 |
1998 | 3000.70 | 3014.70 |
1999 | 3108.10 | 3077.30 |
2000 | 3357.50 | 3144.52 |
2001 | 3075.70 | 3188.70 |
2002 | 3180.60 | 3202.32 |
2003 | 3221.60 | 3216.94 |
2004 | 3176.20 | 3307.30 |
2005 | 3430.60 | 3398.75 |
2006 | 3527.48 | 3485.43 |
2007 | 3637.89 | |
2008 | 3655.00 |
表中的最前面 2 年和最后面 2 年都没有值,因为我们在两端都没有观测值。之后我们会运用更复杂的方法来估计趋势-周期项,它可以求出端点的估计值。
显然,最简单的移动平均法前后数据的结果是有缺失的。
趋势-周期项(红色)比原始数据更平滑,捕捉了时间序列去除了微小波动后的主要变化。移动平均的阶数决定了趋势-周期项的平滑程度。一般情况下,阶数越大曲线越平滑。
其他知识,请参考:预测: 方法与实践
计划更新:[3]模型整体架构分析,[4]模型部件之自相关层