假设你是一家餐厅的主厨,每天打烊后都要对着明天的采购单发愁:鸡肉进多少、青菜进多少?订多了,食材在冷库里蔫掉;订少了,晚市高峰只能对客人说“抱歉,卖完了”。更麻烦的是,需求背后藏着无数看不见的节奏——周末总比工作日旺、节日前总会翻台、阴雨天外卖反而暴增。这些隐藏规律,就是时间序列预测想要捕捉的东西。 传统的“老师傅经验法”(统计模型)擅长对付平稳的周期,但一旦数据里同时混着趋势、季节、噪声和突发事件,就力不从心。这时深度学习进场了,而其中最特别的一对组合,是 N-BEATS 与它的继承者 N-HiTS。N-BEATS 用最朴素的“全连接层堆叠”实现了惊艳的预测效果,还自带拆解趋势与季节的可解释“账本”;N-HiTS 则在它之上加了“多速率采样”和“分层插值”两大绝活,专治超长预测窗口的“计算爆炸”和“误差累积”。两兄弟一前一后,像后厨里一群各司其职的老师傅,通过“接力找茬”把预测误差一步步缩小。这篇文章,就带你看懂这对“双雄”的整套手艺。

岗位关联

对 AI 从业者而言,N-BEATS/N-HiTS 是时序预测领域“简单而高效”的典范:掌握它们,不仅能应对需求预测、负荷预测等落地场景,还能理解“可解释性”与“长序列建模”两种核心设计哲学,在算法选型时多一套攻守兼备的武器。

目录
  1. 可视化演示
  2. 模型背景:主厨的采购难题
  3. 基础块结构:找茬接力赛
  4. 可解释模块:专人专班的账本
  5. 堆叠架构:后厨的分组排班
  6. 多速率采样:不同粗细的筛子
  7. 分层插值:把粗结论抹回细刻度
  8. 实战调参:后厨排班表
  9. Transformer对比:圆桌会议与接力修正
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示N-BEATS双残差堆叠架构的前向/反向残差流和N-HiTS多速率分层插值机制。

本节配有交互动效(进入视口后自动加载,约 3KB)。

N-BEATS双残差堆叠 前向残差:每层输出叠加到预测,逐步精化预测结果 反向残差:每层从输入中减去已解释部分,传递未解释信号 基模块:可解释分支输出趋势和季节性分量 y = Sum(theta_i * basis_i(x)) 纯MLP堆叠实现强预测能力

模型背景:主厨的采购难题

假想你是一家热门餐厅的主厨。每天打烊后,你都要对着明天的采购单发愁:鸡肉进多少、牛腩进多少、青菜进多少?订多了,食材在冷库里蔫掉;订少了,晚市高峰只能对客人说“抱歉,卖完了”。这个决定依赖的不只是昨天的销量,还有一周的天气、上个月的节假日、甚至隔壁商场搞活动的规律。这些历史数据里藏着无数隐藏的节奏——这就是时序预测问题:从过去观测值推断未来的值。

传统上,餐厅靠“老师傅经验法”。像 ARIMA、指数平滑这类统计模型,擅长抓住平稳的周期和趋势,可一旦数据里同时混着长期趋势、短周期、节假日脉冲和随机噪声,它们就顾此失彼。深度学习提供的思路完全不同:不靠人手工设计特征,而是让网络自己在历史里“找规律”。RNN、LSTM 逐个时间步递推,训练慢且长程信息容易走丢;Transformer 虽然强,却需要海量数据和昂贵的算力。

N-BEATS 在 2019 年横空出世,核心信念只有一个:把时序预测做到极致简单——只用全连接层(MLP)堆叠,没有循环、没有注意力。它像一群各有所长的师傅接力修补一份预测,每个人都只修正前一个人留下的误差,最终误差越补越小。N-HiTS 则是它的继承者,2022 年加入“多速率采样 + 分层插值”两件新武器,专门解决预测窗口变长时计算爆炸和精度下降的问题。两兄弟一起,被称为时序预测界的“双雄”。

对主厨来说,$f_\theta$ 就是整个后厨团队学到的“预测默契”——他们不需要知道复杂的数学公式,只需在一次次试菜中把 $\theta$ 调得越来越准。

y^T+1:T+H=fθ(x1:T)\displaystyle \hat{y}_{T+1:T+H} = f_\theta(x_{1:T})
这个公式说的是:模型 $f_\theta$ 把过去 $T$ 个时刻的观测值,映射成未来 $H$ 个时刻的预测值。$\theta$ 是所有可学习参数。
  1. 历史窗口$x_{1:T}$ 表示过去 $T$ 个时刻的观测值,比如过去 14 天的销量:
    x1:T=(x1,x2,…,xT)x_{1:T} = (x_1, x_2, \dots, x_T)
  2. 预测目标$\hat{y}_{T+1:T+H}$ 是未来 $H$ 个时刻的预测值,比如未来 7 天的采购量:
    y^T+1:T+H=(y^T+1,…,y^T+H)\hat{y}_{T+1:T+H} = (\hat{y}_{T+1}, \dots, \hat{y}_{T+H})
  3. 映射函数$f_\theta$ 是带参数 $\theta$ 的模型。N-BEATS 中 $\theta$ 就是神经网络里所有连接的权重:
    fθ:RT→RHf_\theta: \mathbb{R}^{T} \rightarrow \mathbb{R}^{H}
  4. 训练目标训练就是不断调整 $\theta$,让预测与真实值之间的损失 $\mathcal{L}$ 最小:
    θ∗=arg⁡min⁡θL(y,y^)\theta^{*} = \arg\min_{\theta} \mathcal{L}(y, \hat{y})

基础块结构:找茬接力赛

N-BEATS 的最小单元叫基础块(Block),你可以把它想象成后厨里的一位师傅。每位师傅的工作方式完全一样:看一眼当前桌上一份“遗漏清单”(输入序列),然后写出两样东西——第一样是backcast(回首):“我刚才看走眼的那部分有多少,我现在把它从清单上划掉”;第二样是forecast(前瞻):“据此推测,未来该补做多少菜”。下一位师傅不看原始数据,只看前一位师傅“划掉之后还剩下的遗漏”,继续修补。这就是接力找茬的精髓:每一棒的错漏范围都更小。

技术实现上,一个基础块内部非常简单:先把输入序列展平,依次通过 4 层带 ReLU 激活的全连接层,然后分叉成两个线性输出头。backcast 头的输出长度等于输入长度,forecast 头的输出长度等于预测长度。关键设计是双残差结构:每个块的 backcast 被从当前输入残差中减去,传递给下一块;所有块的 forecast 则被逐项相加,构成最终预测。

主厨看到这一幕会心一笑:每个人只操心“上一棒还剩什么问题”,团队里没有人需要背整套菜谱,但合起来却能把预测误差一点一点啃下来。

x^ℓ=xℓ−1−backcastℓ,y^=∑ℓ=1Lforecastℓ\displaystyle \hat{\mathbf{x}}_{\ell} = \mathbf{x}_{\ell-1} - \mathbf{backcast}_{\ell}, \qquad \hat{\mathbf{y}} = \sum_{\ell=1}^{L} \mathbf{forecast}_{\ell}
第一个式子描述“回首修正”:第 $\ell$ 个块把输入残差减去自己发现的偏差,传给下一个块;第二个式子描述“前瞻累加”:所有块的预测相加得到最终结果。
  1. 块编号$\ell$ 表示第几个基础块,$\mathbf{x}_{\ell-1}$ 是第 $\ell$ 个块拿到的输入:
    xℓ−1∈RT\mathbf{x}_{\ell-1} \in \mathbb{R}^{T}
  2. backcast 修正$\mathbf{backcast}_{\ell}$ 是当前块“看走眼”的估计,输入减掉它得到下一块的输入:
    xℓ=xℓ−1−backcastℓ\mathbf{x}_{\ell} = \mathbf{x}_{\ell-1} - \mathbf{backcast}_{\ell}
  3. forecast 输出每个块都产生一段长度为 $H$ 的前瞻向量,$\mathbf{forecast}_{\ell} \in \mathbb{R}^{H}$:
    y^=∑ℓ=1Lforecastℓ\hat{\mathbf{y}} = \sum_{\ell=1}^{L} \mathbf{forecast}_{\ell}
  4. 堆叠总数$L$ 是基础块个数。块越多,理论上的修正粒度越细,但参数量和过拟合风险也上升:
    L∈{1,2,…,10}L \in \{1, 2, \dots, 10\}

可解释模块:专人专班的账本

普通基础块像个“通才”,什么规律都学,但学完你也说不清它到底懂了什么。N-BEATS 的亮点在于提供可解释基模块:让某些师傅只负责一类信号。就像餐厅后厨的“专人专班”——大厨看大局,负责趋势:比如年底公司聚餐变多、夏天夜宵增长,这对应数据里缓慢变化的大方向;面点师傅摸准周期,负责季节:比如工作日包子卖得少、周末翻倍,这对应固定周期的节律。

趋势模块用多项式基:一次项表示线性上升或下降,二次项表示加速或减速,更高次项表达更复杂的弯曲。季节模块用傅里叶基:不同频率的正弦和余弦波叠加,可以拟合任意周期性形状。因为基函数是固定的,训练完成后每个堆叠的输出系数就直接对应“趋势涨了多少”“每周周期贡献了多少”——预测账本被拆成了一目了然的条目。

月底老板问主厨“为什么下周采购预算要涨 20%?”主厨翻开账本:趋势项显示“入秋后火锅类需求稳步爬升”,季节项显示“周末本来就会脉冲式上涨”。两条清清楚楚,这就是可解释性的价值。

T(θT)=∑i=0pθT,i ti,S(θS)=∑k=1K(akcos⁡2πktTs+bksin⁡2πktTs)\displaystyle \mathbf{T}(\theta_T) = \sum_{i=0}^{p} \theta_{T,i}\, t^{i}, \qquad \mathbf{S}(\theta_S) = \sum_{k=1}^{K} \left( a_k \cos\frac{2\pi k t}{T_s} + b_k \sin\frac{2\pi k t}{T_s} \right)
第一个公式是趋势的“多项式语言”:用 $t$ 的幂次模拟缓慢变化的大方向;第二个公式是季节的“波浪语言”:用不同频率的正余弦叠加模拟周期节律。
  1. 趋势多项式的阶数$p$ 是多项式最高次数。$p=1$ 是直线,$p=2$ 是抛物线,$p$ 越大弯曲能力越强:
    θT,iti,i=0,1,…,p\theta_{T,i} t^{i}, \quad i = 0, 1, \dots, p
  2. 趋势系数$\theta_{T,i}$ 是每个幂次项的权重,训练后它的大小直接表示“这个方向涨了多少”:
    T(θT)=θT,0+θT,1t+θT,2t2+⋯\mathbf{T}(\theta_T) = \theta_{T,0} + \theta_{T,1} t + \theta_{T,2} t^{2} + \cdots
  3. 季节频率$k$ 是谐波序号,$T_s$ 是周期长度(如一周 168 小时)。频率 $\frac{2\pi k}{T_s}$ 决定了波浪的快慢:
    cos⁡(2πktTs),sin⁡(2πktTs)\cos\left(\frac{2\pi k t}{T_s}\right), \quad \sin\left(\frac{2\pi k t}{T_s}\right)
  4. 季节系数$a_k, b_k$ 是每个频率分量的大小,决定该频率的季节强度。$K$ 个谐波叠加后就能逼近任意周期曲线:
    S(θS)=∑k=1K(akcos⁡2πktTs+bksin⁡2πktTs)\mathbf{S}(\theta_S) = \sum_{k=1}^{K} \left( a_k \cos\frac{2\pi k t}{T_s} + b_k \sin\frac{2\pi k t}{T_s} \right)

堆叠架构:后厨的分组排班

单个师傅的修正能力有限,N-BEATS 于是把多个基础块串成一个堆叠(Stack),再把多个堆叠串联成整个网络,就像后厨分成“趋势组”“季节组”“自由发挥组”三个班组,每个班组内部又有好几棒接力。输入数据先进第一个堆叠,它吃下能解释的部分,输出 backcast 残差交给下一个堆叠;每个堆叠的 forecast 则直接相加,形成最终预测。

在原始 N-BEATS 论文中,典型配置是 3 个堆叠:趋势堆叠 + 季节堆叠 + 泛化堆叠。趋势堆叠和季节堆叠内部使用上一节说的基函数输出,所以天然可解释;泛化堆叠用普通线性头,负责捕捉前两组没覆盖的复杂模式。每组里的块可以共享权重,以此减少参数量。整体前向过程就是一个“分组接力”:每一组只处理上一组剩下的残差。

主厨看一眼排班表就懂了这个逻辑:先让趋势组和季节组把最有把握的部分领走,剩下的“疑难杂症”交给自由发挥组兜底,各组预测最后合在一起就是完整采购方案。

xs+1=xs−backcasts,y^=∑s=1Sforecasts\displaystyle \mathbf{x}^{s+1} = \mathbf{x}^{s} - \mathbf{backcast}^{s}, \qquad \hat{\mathbf{y}} = \sum_{s=1}^{S} \mathbf{forecast}^{s}
第一个式子表示堆叠间的残差级联:下一个堆叠的输入 = 上一个堆叠的输入减它的 backcast;第二个式子表示最终预测是所有堆叠 forecast 的和。
  1. 堆叠编号$s$ 表示第几个堆叠,$\mathbf{x}^{s}$ 是该堆叠接收到的残差输入:
    xs∈RT\mathbf{x}^{s} \in \mathbb{R}^{T}
  2. 堆叠内修正每个堆叠输出一段 backcast,从输入残差中扣除后才能传给下一组:
    xs+1=xs−backcasts\mathbf{x}^{s+1} = \mathbf{x}^{s} - \mathbf{backcast}^{s}
  3. 堆叠内预测每个堆叠输出一段 forecast,它们互不丢弃、全部保留:
    y^=forecast1+forecast2+⋯+forecastS\hat{\mathbf{y}} = \mathbf{forecast}^{1} + \mathbf{forecast}^{2} + \cdots + \mathbf{forecast}^{S}
  4. 堆叠数量$S$ 是堆叠总数。$S=3$ 是最常见的配置,太多堆叠会增加训练难度:
    S=3 (趋势+季节+泛化)S = 3 \ (\text{趋势} + \text{季节} + \text{泛化})

多速率采样:不同粗细的筛子

N-BEATS 虽然效果好,但预测窗口一长,每个块都要处理高分辨率的长序列,计算量迅速膨胀,而且模型容易“只见树木不见森林”。N-HiTS 的解决方案很直观:让不同师傅用不同粗细的筛子看同一段历史。细筛子的师傅盯着每分钟客流,把握细节;粗筛子的师傅先看每小时汇总,抓住大方向。这样既有人管细节,又有人管全局,还大幅降低了计算量。

技术实现上,N-HiTS 在部分基础块的输入处引入多速率采样:用一个 MaxPool 层对输入序列做下采样。具体做法是把序列按 $r$ 个时刻一组,每组取最大值,把长度为 $T$ 的序列压缩为 $\lceil T/r \rceil$。$r$ 被称为池化因子,不同的块使用不同的 $r$(如 2、4、8),就形成了从细到粗的多粒度视野。粗粒度的块相当于“站在山顶看森林”,天然对噪声更鲁棒。

主厨发现,过去让所有师傅都盯着每分钟流水,既累又慢;现在有人看 10 分钟汇总、有人看 1 小时汇总,大方向一眼就清楚了,高峰期细节也没漏掉。

xℓ(r)=MaxPoolr(xℓ−1),xℓ(r)∈R⌈T/r⌉\displaystyle \mathbf{x}_{\ell}^{(r)} = \mathrm{MaxPool}_{r}\left( \mathbf{x}_{\ell-1} \right), \qquad \mathbf{x}_{\ell}^{(r)} \in \mathbb{R}^{\lceil T / r \rceil}
这个公式说明:第 $\ell$ 个块的输入先经过窗口大小为 $r$ 的最大池化,序列长度从 $T$ 变成 $T/r$,信息密度降低但视野变大。
  1. 池化窗口$r$ 是池化因子,表示每 $r$ 个连续时刻合成一个代表点:
    r∈{2,3,4,…,8}r \in \{2, 3, 4, \dots, 8\}
  2. 最大值代表$\mathrm{MaxPool}$ 从每组 $r$ 个值里取最大者,保留“最显著信号”:
    MaxPoolr(x1:T)=(max⁡i∈[1,r]xi,max⁡i∈[r+1,2r]xi,… )\mathrm{MaxPool}_{r}(x_{1:T}) = \left( \max_{i \in [1,r]} x_i, \max_{i \in [r+1, 2r]} x_i, \dots \right)
  3. 输出长度$\lceil T / r \rceil$ 是压缩后的序列长度。$r$ 越大,序列越短,计算越省:
    xℓ(r)∈R⌈T/r⌉\mathbf{x}_{\ell}^{(r)} \in \mathbb{R}^{\lceil T / r \rceil}
  4. 多粒度体系不同块采用不同 $r$,使整个网络同时拥有细粒度与粗粒度视野:
    rℓ,1≠rℓ,2≠rℓ,3≠…r_{\ell,1} \neq r_{\ell,2} \neq r_{\ell,3} \neq \dots

分层插值:把粗结论抹回细刻度

多速率采样解决了“怎么看历史”的问题,但紧接着还有一个问题:粗筛子师傅得出的结论是一段低分辨率预测(比如“每小时卖 60 份”),可采购单要求精确到每天、每个时段。N-HiTS 的第二件武器——分层插值——就是负责把低分辨率结论平滑地铺回原来的细粒度刻度,像大厨把一大块面团按比例均匀切回小份,保证总量不变、细节平滑。

技术实现上,N-HiTS 在基础块的输出端接一个插值层:对低分辨率的 forecast 向量做线性上采样(upsample),将其长度放大回原预测长度 $H$。backcast 同样需要插值回原始输入长度。这样整个块可以在低分辨率空间做“粗规划”,再由插值补足细节,既大幅减少 MLP 的计算量,又天然带有平滑先验,非常适合长 horizon 预测。

主厨把“每小时 60 份”的结论按营业节奏均匀摊回每个 10 分钟:清早少、午市多、晚市最多,总量一分不少,细节也刚刚好。这就是插值的妙处。

qℓ=Interp(forecastℓ(r)),qℓ∈RH\displaystyle \mathbf{q}_{\ell} = \mathrm{Interp}\left( \mathbf{forecast}_{\ell}^{(r)} \right), \qquad \mathbf{q}_{\ell} \in \mathbb{R}^{H}
这个公式说的是:当前块输出的低分辨率预测经过插值函数,恢复到完整预测长度 $H$。插值本身没有可学习参数,却让模型的计算成本大幅下降。
  1. 低分辨率预测$\mathbf{forecast}_{\ell}^{(r)}$ 是池化后短序列上的预测结果,长度只有 $H/r$:
    forecastℓ(r)∈R⌈H/r⌉\mathbf{forecast}_{\ell}^{(r)} \in \mathbb{R}^{\lceil H / r \rceil}
  2. 插值操作$\mathrm{Interp}$ 用线性插值把每两个相邻点之间补出等间隔的值:
    Interp(a)i=a⌊i/r⌋+(i mod r)⋅a⌊i/r⌋+1−a⌊i/r⌋r\mathrm{Interp}(\mathbf{a})_i = \mathbf{a}_{\lfloor i / r \rfloor} + (i \bmod r) \cdot \frac{\mathbf{a}_{\lfloor i / r \rfloor + 1} - \mathbf{a}_{\lfloor i / r \rfloor}}{r}
  3. 恢复分辨率插值后的输出长度回到 $H$,与目标预测长度一致:
    qℓ=Interp(forecastℓ(r))∈RH\mathbf{q}_{\ell} = \mathrm{Interp}\left( \mathbf{forecast}_{\ell}^{(r)} \right) \in \mathbb{R}^{H}
  4. 平滑先验相邻时刻的输出在插值下是连续的,天然抑制了逐点预测的抖动:
    qℓ,i≈qℓ,i+1(当 r 较大时)\mathbf{q}_{\ell,i} \approx \mathbf{q}_{\ell,i+1} \quad (\text{当}\ r\ \text{较大时})

实战调参:后厨排班表

模型选好了,真正上了战场,考验的是排兵布阵。就像主厨要给后厨定几个关键旋钮:几组人(堆叠数)、每组几棒(块数)、每棒盯多长历史(输入窗口)、筛子多粗(池化因子)。调参没有魔法,但有经过验证的默认值和方向。

实践经验总结:输入窗口长度建议至少是预测长度的 2 倍,给模型“看更久”的底气;每个堆叠放 3~5 个块,每块 4 层 MLP、宽度 256~512 起步;N-HiTS 的池化因子通常取 2~8,预测窗口越长越适合用大值;训练时先对数据做标准化,用 Adam 优化器配合学习率衰减,并加少量 dropout(0~0.5)和 weight decay 防过拟合。这些参数就像后厨的“默认排班表”,在自家数据上再微调才高效。

主厨排班时也一样:先定人数和分工(架构),再统一计量单位(标准化),最后按客流高峰期微调(调参)。有了这份“排班表”,新店开业也能快速跑起来。

z=x−μσ\displaystyle \mathbf{z} = \frac{\mathbf{x} - \mu}{\sigma}
这是数据标准化的公式:把每个值减去均值、除以标准差,让不同量纲的序列都变成均值为 0、方差为 1 的分布,训练更稳定。
  1. 均值中心化$\mu$ 是训练窗口内所有值的平均,减去它让序列围绕 0 波动:
    μ=1T∑i=1Txi\mu = \frac{1}{T} \sum_{i=1}^{T} x_i
  2. 方差归一$\sigma$ 是标准差,除以它把波动幅度压到单位尺度:
    σ=1T∑i=1T(xi−μ)2\sigma = \sqrt{\frac{1}{T} \sum_{i=1}^{T} (x_i - \mu)^2}
  3. 标准化后输入$\mathbf{z}$ 是模型真正看到的输入,消除了量纲和幅值差异:
    z∈RT,E[z]=0,Var[z]=1\mathbf{z} \in \mathbb{R}^{T}, \quad \mathbb{E}[\mathbf{z}] = 0, \quad \mathrm{Var}[\mathbf{z}] = 1

Transformer对比:圆桌会议与接力修正

很多人会问:既然 Transformer 类模型在时序预测里那么火,为什么还要用 N-BEATS 和 N-HiTS?想象一下两种团队:Transformer 像开圆桌会议,每个时刻都能直接给其他任何时刻“传小纸条”(注意力机制),信息交互充分但纸片满天飞,开销大;N-BEATS 像阶梯式接力找茬,依次修正、责任明确、成本可控。两种哲学没有绝对优劣,只有适合的场景。

从结构复杂度看,Transformer 由多头自注意力和前馈网络堆叠,参数多、调参敏感;N-BEATS 纯 MLP,结构极简且稳定。从长序列性价比看,标准注意力的计算量随序列长度平方增长,Informer 等模型用稀疏注意力缓解,而 N-HiTS 用多速率采样 + 插值把计算成本压到线性级别,在超长 horizon 上优势明显。可解释性上,N-BEATS 自带趋势/季节分解,Transformer 则接近黑箱。小数据场景下,简化结构往往比复杂结构更可靠。

主厨对比完两种团队后得出结论:如果门店小、数据不多、要快速上线,就选接力修正的 N-BEATS;如果要做超长周期的大型预测,N-HiTS 的“粗筛子 + 插值”组合性价比更高;圆桌会议式的大模型,留给数据量海量、算力充裕的巨头场景更合适。

Attention(Q,K,V)=softmax(QK⊤d)V\displaystyle \mathrm{Attention}(Q, K, V) = \mathrm{softmax}\left( \frac{Q K^{\top}}{\sqrt{d}} \right) V
这是自注意力的核心公式:每个位置通过查询 $Q$ 与所有位置的键 $K$ 计算相似度,再用相似度加权求和值 $V$。强大,但 $QK^\top$ 的计算量随序列长度平方增长。
  1. 查询与键$Q$ 是当前时刻的“问题”,$K$ 是其他时刻的“标签”,点积衡量两者相关性:
    QK⊤=∑jQjKjQ K^{\top} = \sum_{j} Q_j K_j
  2. 缩放因子$\sqrt{d}$ 是向量维度,除以它防止点积过大导致 softmax 饱和:
    QK⊤d\frac{Q K^{\top}}{\sqrt{d}}
  3. 注意力权重$\mathrm{softmax}$ 把相关性分数转成和为 1 的权重:
    softmax(QK⊤d)∈[0,1]T×T\mathrm{softmax}\left( \frac{Q K^{\top}}{\sqrt{d}} \right) \in [0,1]^{T \times T}
  4. 加权求和用权重对 $V$ 加权,得到每个位置融合全局信息后的表示:
    softmax(⋅)V\mathrm{softmax}(\cdot) V

AI 应用场景

商超餐饮需求预测

阶段:数据准备与特征工程、模型训练与部署

解决的问题:传统采购靠经验的“拍脑袋”加上简单移动平均,无法拆解节日增量、周末周期等结构性因素。N-BEATS 的可解释堆叠输出趋势与季节分解,采购团队能直接看到“夏季旅游季日均销量 +20%”“周六是工作日的 1.8 倍”等可行动洞察。

具体案例:一家连锁烘焙品牌用 N-BEATS 预测全国 300 家门店的每日可颂销量。每个门店单独训练一个模型,输入过去 60 天销量,预测未来 14 天需求。趋势堆叠捕捉到三家门店所在商圈人流的慢变化,季节堆叠识别出周五和节假日的周期性脉冲。上线后,报废率下降 18%,缺货率下降 12%,店员每周的补货手工调整从 3 小时减到 20 分钟。

电力系统负荷预测

阶段:模型训练与优化、在线推理

解决的问题:电网调度需要未来 24~72 小时、每 15 分钟采样的负荷曲线。传统模型长 horizon 预测时误差逐点累积,而 Transformer 模型计算量过大、部署成本高。N-HiTS 用多速率采样让部分块在小时粒度上把握用电大趋势,再通过分层插值把预测铺回 15 分钟刻度,兼顾精度与速度。

具体案例:某省级电网用 N-HiTS 替代原先的 XGBoost 基线,将 72 小时负荷预测的 MAPE 从 4.2% 降至 3.1%,训练时间比同规模的 N-BEATS 快约 3 倍,单次推理在 CPU 上不到 50 毫秒,可部署在调度中心的边缘服务器上。

金融交易量与波动率分析

阶段:数据预处理、模型部署与监控

解决的问题:金融时序噪声大、序列长、非平稳性强,且对模型的可解释性有合规要求。N-BEATS 的季节分解能识别出“开盘、收盘时段交易量聚集”等日内规律,趋势分解则帮助分析师区分长期资金流向与短期情绪扰动。

具体案例:某量化私募用 N-BEATS 预测全市场 2000 只股票的分时成交量,模型结构轻量,可批量并行推理。研究团队利用趋势+季节分解,将“流动性事件”从日常波动中剥离,作为下单执行的调度信号,冲击成本降低约 8%。

Python 演示

python86 行
import mathimport torchimport torch.nn as nn  class NBeatsBlock(nn.Module):    def __init__(self, input_size, output_size, hidden=64):        super().__init__()        self.input_size = input_size        self.output_size = output_size        self.stack = nn.Sequential(            nn.Linear(input_size, hidden),            nn.ReLU(),            nn.Linear(hidden, hidden),            nn.ReLU(),            nn.Linear(hidden, hidden),            nn.ReLU(),        )        self.backcast_head = nn.Linear(hidden, input_size)        self.forecast_head = nn.Linear(hidden, output_size)     def forward(self, x):        h = self.stack(x)        bc = self.backcast_head(h)        fc = self.forecast_head(h)        return x - bc, fc  # 双残差: 回首修正 + 前瞻预测  class NHitsBlock(nn.Module):    def __init__(self, input_size, output_size, pool=2, hidden=64):        super().__init__()        self.pool = pool        self.pooled_in = input_size // pool        self.pooled_out = output_size // pool        self.block = NBeatsBlock(self.pooled_in, self.pooled_out, hidden)        self.resample = nn.Upsample(scale_factor=pool, mode="linear", align_corners=False)     def forward(self, x):        # 多速率采样: MaxPool 降维, 每组取最大值作代表        xp = x.view(x.size(0), self.pool, -1).max(dim=1).values        bc_pooled, fc_pooled = self.block(xp)        # 分层插值: 线性上采样回原分辨率        bc = self.resample(bc_pooled.unsqueeze(1)).squeeze(1)        fc = self.resample(fc_pooled.unsqueeze(1)).squeeze(1)        return x - bc, fc  def synthetic_series(length=96, period=32):    t = torch.arange(length).float()    trend = 0.5 * t / length    signal = 3.0 * torch.sin(2 * math.pi * t / period)    noise = torch.randn(length) * 0.1    return signal + trend + noise  if __name__ == "__main__":    torch.manual_seed(42)    W, H = 64, 32    data = [synthetic_series(W + H) for _ in range(100)]    X = torch.stack([d[:W] for d in data])    Y = torch.stack([d[W:] for d in data])     model = nn.Sequential(        NBeatsBlock(W, H, hidden=64),        NHitsBlock(W, H, pool=2, hidden=64),    )    opt = torch.optim.Adam(model.parameters(), lr=1e-3)    loss_fn = nn.MSELoss()    batch = 16     for epoch in range(20):        total = 0.0        for i in range(0, len(X), batch):            xb, yb = X[i:i + batch], Y[i:i + batch]            cur_x = xb            pred = torch.zeros_like(yb)            for layer in model:          # 逐层接力: 双残差串接                cur_x, fc = layer(cur_x)                pred = pred + fc         # forecast 累加            loss = loss_fn(pred, yb)     # 预测与真实比较            opt.zero_grad()            loss.backward()            opt.step()            total += loss.item()        if epoch % 5 == 0:            print(f"epoch {epoch:2d} loss {total / (len(X) / batch):.4f}")
目的:演示 N-BEATS 的双残差前向流程,以及 N-HiTS 如何用 MaxPool 多速率采样 + 插值上采样搭建块结构,在合成数据上端到端训练。
  1. return x - bc, fc双残差核心:把“回首修正”从输入中减去,把“前瞻预测”单独输出,下一个块只看残差。
  2. xp = x.view(x.size(0), self.pool, -1).max(dim=1).values多速率采样:把每 pool 个时刻分成一组并取最大值,序列长度缩为原来的 1/pool。
  3. self.resample = nn.Upsample(scale_factor=pool, mode="linear", align_corners=False)插值层:用线性插值把低分辨率预测铺回原始长度。
  4. for layer in model:逐层接力:按顺序遍历 N-BEATS 块和 N-HiTS 块,前一块的残差输出就是后一块的输入。
  5. pred = pred + fc预测累加:每个块的 forecast 都加入最终预测,对应“所有人前瞻建议相加”。
  6. cur_x, fc = layer(cur_x)残差传递:把“看走眼的部分”传递给下一棒,模拟餐厅后厨的接力找茬。

常见误区

误区N-BEATS 只有全连接层,听起来就是个线性模型的组合,能拿竞赛冠军肯定是数据集挑选过或者运气好。

正解全连接层带 ReLU 激活,本身是强非线性映射;4 层 MLP 加宽到 256~512 维后,拟合能力远超线性模型。更重要的是双残差结构让每一层专注修正上层的误差,这种“逐层细化”的归纳偏置比盲目的深层网络更高效。就像后厨老师傅不是一个人记住所有菜谱,而是每人只修正前一个人的疏漏,合起来反而更准。

为何常见:因为很多人把“没有用 RNN/Attention”等同于“能力弱”,忽略了结构设计本身带来的学习效率优势。

误区预测未来时,backcast 输出的是“对过去的复现”,和最终预测没关系,删掉也不影响效果。

正解backcast 是双残差结构的一半。没有它,模型就只能看到原始输入,无法逐层剥离已被解释的信号;有了它,每一层都在“剩余误差”上建模,使得后面的块能专注更细的规律。这就像接力赛中只有人在终点冲刺、没人传递接力棒,团队优势就消失了。

为何常见:初学者习惯“输入→输出”的单通道思维,忽略了残差学习这种让网络由粗到细逼近的经典范式。

误区插值不就是把预测结果放大几倍吗?用 `nn.Upsample` 谁都会,这算什么创新。

正解插值单独看确实简单,但它的关键在于与多速率采样协同:先降采样让块在低分辨率上“粗规划”,再插值回细粒度“补细节”。这一降一升把长序列建模的计算量从 O(T) 的每个点变成 O(T/r) 的粗粒度,同时平滑先验抑制了长 horizon 的抖动。两件武器组合起来才是 N-HiTS 的完整设计。

为何常见:只看到“插值”这一个操作,忽略了它在整个架构中承担的角色。

误区既然有趋势和季节分解,那模型输出的任何一个预测值都应该能手工拆解出来,验证每一步数学运算。

正解可解释性指的是“宏观语义可读”——趋势堆叠告诉你大方向是涨还是跌,季节堆叠告诉你周期贡献多少。但 MLP 内部的非线性变换和残差交互仍然是不可逐点展开的。这就像主厨能告诉你“这周销量上涨是因为节日 + 周末效应”,但没办法把每一份菜品的销量追溯到某一勺调料。要的是业务可解释,不是数学证明。

为何常见:把“可解释”过度理解为“白箱”,忽略了实际业务决策只需要结构性归因。

误区N-HiTS 是 N-BEATS 的升级版,任何场景下用 `pool` 参数一开就完事,效果必然更好。

正解N-HiTS 在超长 horizon、序列长且平滑的场景优势明显;但如果数据量小、周期不明显,池化会丢失关键细节,插值引入的平滑也会掩盖真实突变。短预测窗口下,N-BEATS 的完整分辨率处理反而更精细。选型应像主厨排班:新店客流小,一个全能老师傅就够;大型宴会才需要多组粗细分工人手。

为何常见:把“新版本”等同于“全面超越”,忽略了算法取舍依赖数据特性和任务目标。

面试题

Q1. N-BEATS 的基础网络单元是什么?

单选题 · easy

  • A.循环神经网络单元
  • B.全连接层堆叠块← 正确答案
  • C.多头自注意力层
  • D.一维卷积网络层

答案:B。N-BEATS 的核心是纯 MLP(全连接层)堆叠,没有 RNN 的循环、没有注意力、没有卷积。它用最朴素的组块证明了结构设计(双残差)比单元复杂程度更重要。A 是传统时序模型的选择,C 是 Transformer 的选择,D 是 TCN 类模型的选择。

Q2. 双残差结构中,backcast 头的主要作用是什么?

单选题 · easy

  • A.修正当前输入残差← 正确答案
  • B.直接输出最终预测
  • C.生成注意力权重
  • D.降低特征维度

答案:A。backcast 输出的是当前块对输入中“已解释成分”的估计,从输入残差中减去后传给下一个块,让后续块专注修正残余误差。B 是 forecast 的职责;C 是注意力机制;D 与残差学习无关。

Q3. N-BEATS 可解释基模块包含哪些类型?

多选题 · mid

  • A.趋势多项式基← 正确答案
  • B.季节傅里叶基← 正确答案
  • C.注意力权重基
  • D.随机森林回归基

答案:AB。可解释堆叠分为趋势堆叠(用多项式基拟合缓慢变化)和季节堆叠(用傅里叶正余弦基拟合周期)。C 是 Transformer 的组件,D 是传统机器学习模型,都不属于 N-BEATS 的基函数设计。

Q4. N-HiTS 相对于 N-BEATS 的两个核心改进是什么?

多选题 · mid

  • A.多速率采样← 正确答案
  • B.分层插值← 正确答案
  • C.引入注意力机制
  • D.增加深度残差网络

答案:AB。N-HiTS 的核心贡献是“多速率采样 + 分层插值”组合:先降采样让块在低分辨率上粗规划,再插值回原分辨率补细节。C 是 Transformer 系模型的改进方向,D 是 ResNet 的思想,均非 N-HiTS 的主要工作。

Q5. 多速率采样中,MaxPool 层的作用是什么?

单选题 · mid

  • A.用局部最大值代表一段子序列← 正确答案
  • B.求整段序列的平均值
  • C.随机丢弃一段数据
  • D.把序列元素两两交换

答案:A。MaxPool 按窗口大小 r 分组,每组取最大值,压缩序列长度,保留局部最显著信号。B 是 AvgPool;C 是 Dropout 的思想;D 是乱序操作,均不符合多速率采样的定义。

Q6. N-HiTS 的插值层主要负责什么?

单选题 · mid

  • A.低分辨率预测上采样回原尺度← 正确答案
  • B.高分辨率特征降维压缩
  • C.用 LSTM 编码全部序列
  • D.计算训练时的梯度裁剪

答案:A。插值层把降采样后生成的短预测向量线性上采样到原始预测长度,完成“粗规划→细粒度”的还原。B 是池化层的工作;C 是循环网络的编码方式;D 是优化器层面的操作。

Q7. 以下哪个模型在超长 horizon 预测中通常训练更快、更省显存?

单选题 · mid

  • A.N-HiTS← 正确答案
  • B.N-BEATS
  • C.原始 Transformer
  • D.双向 LSTM

答案:A。N-HiTS 通过在低分辨率空间完成大部分计算,参数和 FLOPs 都比 N-BEATS 更少,训练速度可达 2~3 倍。Transformer 的注意力计算量随序列长度平方增长,双向 LSTM 也不适合超长序列。

Q8. N-BEATS 相比 Transformer 在时序预测中的优势有哪些?

多选题 · hard

  • A.结构轻量容易部署← 正确答案
  • B.自带趋势季节解释← 正确答案
  • C.小数据下比较稳定← 正确答案
  • D.语义交互更加充分

答案:ABC。N-BEATS 纯 MLP 结构轻量,可解释堆叠能直接输出趋势/季节分解,且在小数据场景下比参数庞大的 Transformer 更稳定。D 描述的“语义交互充分”正是 Transformer 注意力机制的优势,而非 N-BEATS 的特点。

Q9. 趋势堆叠使用哪种基函数进行输出约束?

单选题 · easy

  • A.多项式时间基← 正确答案
  • B.傅里叶周期基
  • C.小波多尺度基
  • D.径向距离基

答案:A。趋势堆叠用 $1, t, t^2, \dots$ 多项式基拟合缓慢变化的大方向,系数直观对应“涨跌幅度”。B 是季节堆叠的基函数;C、D 是其他领域的分解工具。

Q10. 在 N-HiTS 中,如果 pooling factor 设置得过大,最可能出现什么问题?

单选题 · hard

  • A.丢失短程细节信息← 正确答案
  • B.训练过程直接崩溃
  • C.模型退化成线性模型
  • D.预测输出全部为零

答案:A。pool 因子越大,序列压缩越狠,块看到的粒度越粗,短程突变和局部细节容易被最大池化或插值抹平。它不会导致崩溃、线性化或全零输出,但会让预测变得过度平滑,错失关键拐点。