【长时间序列预测】Autoformer 代码详解之[2]模型部件之时间序列分解

1. 时间序列分解

        采用 移动平均 来平滑周期波动 和 强调 长期趋势。

        输入长度L的时间序列:X\in \mathbb{R}^{L\times d}

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 X_{de}^{M} 和  trend-cyclical \tau_{de}^{M}。我们在解码器中从左到右,逐渐增加了 分解模块。此例来自 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. 移动平均解释

6.2 移动平均 | 预测: 方法与实践

时间邻近的情况下,观测值也很可能接近。由此,平均值消除了数据中的一些随机性,从而我们可以得到较为平滑的趋势周期项

年份电力销售量 (亿瓦时)5-MA
19892354.34
19902379.71
19912318.522381.53
19922468.992424.56
19932386.092463.76
19942569.472552.60
19952575.722627.70
19962762.722750.62
19972844.502858.35
19983000.703014.70
19993108.103077.30
20003357.503144.52
20013075.703188.70
20023180.603202.32
20033221.603216.94
20043176.203307.30
20053430.603398.75
20063527.483485.43
20073637.89
20083655.00

        表中的最前面 2 年和最后面 2 年都没有值,因为我们在两端都没有观测值。之后我们会运用更复杂的方法来估计趋势-周期项,它可以求出端点的估计值。

        显然,最简单的移动平均法前后数据的结果是有缺失的。

趋势-周期项(红色)比原始数据更平滑,捕捉了时间序列去除了微小波动后的主要变化。移动平均的阶数决定了趋势-周期项的平滑程度。一般情况下,阶数越大曲线越平滑。

其他知识,请参考:预测: 方法与实践

计划更新:[3]模型整体架构分析,[4]模型部件之自相关层

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

理心炼丹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值