上一篇确定了预测时刻(Issue Time)的信息边界:站在时刻 tt,只用当时可获得的信息预测未来。本篇要把一条连续的序列,变成一组有明确输入和标签的训练样本。

滑动窗口看起来只是切片,但窗口长度、移动步长和时间边界会同时影响样本数量、计算成本与评估结论。

1. 问题:一条序列如何变成训练样本?

假设我们有一条逐小时负荷序列。在本系列的固定窗口任务中,每个样本都应对应一次真实可执行的预测,输入也不能随意包含未来数据:

过去 L 小时的负荷  |  未来 H 小时的负荷
       输入         |       标签
                    ↑
                Issue Time

对于本系列的建筑负荷案例,目标是预测未来 24 小时,因此 H=24H=24。回看多久(LL)需要结合日周期、周周期和验证集表现决定。例如 L=168L=168 表示回看过去 7 天,它是一个候选值,而不是预先认定的最优值。

2. 直觉:沿时间轴移动同一个观察框

先看一个只有 10 个观测值的例子。取 Lookback L=4L=4、Horizon H=2H=2,每次向前移动两格:

时间位置       0  1  2  3  4  5  6  7  8  9

第 1 个窗口   [0  1  2  3] [4  5]
第 2 个窗口         [2  3  4  5] [6  7]
第 3 个窗口               [4  5  6  7] [8  9]
                输入 L=4     标签 H=2

方括号中的数字是时间位置,不是负荷值。第二个窗口站在位置 5 之后发起预测;此时位置 4、5 的真实值已经成为历史,可以作为输入。第一个窗口的标签与第二个窗口的输入使用了同一段观测,这本身不构成泄漏;前提是每个窗口都只使用其 Issue Time 已知的信息,并且训练与评估的划分正确。

3. 方法与数学定义

3.1 Lookback、Horizon 与 Stride

设序列为 y0,y1,…,yT−1y_0,y_1,\ldots,y_{T-1},共 TT 个观测值。用零起始索引 ii 表示窗口起点,定义:

Xi=(yi,…,yi+L−1),Yi=(yi+L,…,yi+L+H−1). X_i = (y_i,\ldots,y_{i+L-1}), \qquad Y_i = (y_{i+L},\ldots,y_{i+L+H-1}).

这个样本的 Issue Time 是 i+L−1i+L-1:输入最晚到此为止,标签从下一个时间点开始。三个参数分别控制:

参数含义建筑负荷案例中的问题
LL:Lookback每个样本包含多少个历史观测看过去 1 天、1 周,还是更久?
HH:Horizon每次预测多少个未来观测本系列固定为未来 24 小时
ss:Stride相邻窗口的起点相隔多少个观测每小时建一个样本,还是每天一个?

当起点依次为 0,s,2s,…0,s,2s,\ldots 时,最后一个窗口必须满足 i+L+H≤Ti+L+H\leq T。因此,若 T≥L+HT\geq L+H,样本数为:

N=⌊T−L−Hs⌋+1. N=\left\lfloor\frac{T-L-H}{s}\right\rfloor+1.

若 T<L+HT<L+H,一个完整样本也构造不出来,N=0N=0。上面的 10 个观测值在 L=4,H=2,s=2L=4,H=2,s=2 时产生 ⌊(10−4−2)/2⌋+1=3\lfloor(10-4-2)/2\rfloor+1=3 个样本。

3.2 Stride 改变了哪些重叠?

当 s=1s=1 时,相邻窗口的输入大量重叠;若 H>1H>1,标签也会重叠。这样能从有限历史中构造更多训练样本,但样本之间高度相关,不能把窗口个数直接当成独立观测的个数。

当 s=Hs=H 时,相邻窗口的标签区间不重叠。如果 L>HL>H,输入区间仍然重叠。因此,“Stride 等于 Horizon 就得到完全独立的样本”是错误的。

Stride 还有两个使用场景,不能混为一谈:

  • 训练步长决定从训练期生成多少学习样本;
  • 评估或线上发起频率决定隔多久真正发布一次预测。

例如,训练时每小时取一个窗口(s=1s=1),线上每天 00:00 预测未来 24 小时,是完全可行的。评估时则应模拟每天 00:00 的发起频率,而不是把训练窗口的步长自动当成线上调度频率。如果线上确实每小时发布一次 24 小时预测,评估也可以使用每小时一个 Issue Time;此时不同预测会覆盖相同的未来时刻,需要说明指标的汇总口径。

3.3 多变量输入和未来已知特征

窗口不一定只包含目标历史。对建筑 bb 在时刻 tt 发起的预测,输入还可以包含:

历史窗口:负荷、截至 t 的天气观测
未来窗口:t+1 到 t+H 的日历特征、当时可获得的天气预报
静态特征:建筑用途、面积
标签窗口:t+1 到 t+H 的真实负荷

这几类数据的时间切片不同。尤其要避免把未来实测天气切入输入窗口:离线文件虽然保存了这些值,但 Issue Time 并不知道它们。天气预报的来源与误差将在 Part 6 讨论。

4. 一个最小实现

先用单变量列表验证索引和样本数量。数据清洗、时间戳对齐和多变量拼接会在后续 Part 实现。

def make_windows(values, lookback, horizon, stride=1):
    if min(lookback, horizon, stride) < 1:
        raise ValueError("lookback, horizon and stride must be positive")

    windows = []
    last_start = len(values) - lookback - horizon
    for start in range(0, last_start + 1, stride):
        x = values[start : start + lookback]
        y = values[start + lookback : start + lookback + horizon]
        windows.append((x, y))
    return windows


examples = make_windows(list(range(10)), lookback=4, horizon=2, stride=2)
for x, y in examples:
    print(x, "->", y)

输出应为:

[0, 1, 2, 3] -> [4, 5]
[2, 3, 4, 5] -> [6, 7]
[4, 5, 6, 7] -> [8, 9]

如果输入不足 L+HL+H 个观测,函数返回空列表。真实数据还需要检查时间戳是否连续、缺失值如何处理,以及每个窗口是否属于同一栋建筑;否则“相邻两行”未必真是相邻两个小时。

5. 如何选择参数?

先由任务决定 Horizon:本系列需要一次给出未来一天逐小时负荷,因此 H=24H=24。Lookback 再作为可比较的设计选择,例如 24、168、336 小时,分别覆盖一天、一周、两周历史。

选择 LL 时,需要同时考虑:

  • 周期信息:若负荷有明显日周期或周周期,窗口至少要能提供相应历史,或通过滞后特征显式补足;
  • 数据质量:更长窗口更容易碰到缺失段和异常值;
  • 模型成本:输入长度增加会提高存储、训练或推理成本,具体程度因模型而异;
  • 陈旧信息:旧的运行模式在节假日、换季或设备调整后可能不再有用。

一个可执行的比较方法是:先固定 H=24H=24 和同一套时间划分,在验证集比较几个 LL 候选值,并同时报告整体误差、各预测步长误差与计算成本。测试集只用于报告最终方案,不用于反复选择窗口长度。关于时间划分和 Walk-Forward Validation,Part 3 会正式定义。

Stride 同样需要记录在实验配置中。缩小训练 Stride 会增加样本数量,但不会凭空增加独立信息;增大 Stride 可以减少训练成本,也可能漏掉重要的发起时刻,例如只在午夜取样会看不到白天发起预测时的表现。它是否合适,应由实际预测频率和验证结果共同决定。

6. 常见错误

先对全量序列开窗,再随机分训练和测试

相邻窗口共享大量观测。随机拆分容易让训练样本的标签与测试样本的输入、甚至标签出现在相邻时段,无法模拟真正的向前预测。应先确定时间边界,再约束每个窗口的 Issue Time 和标签所属区间。

只检查输入落在哪个区间

一个训练窗口即使输入全部落在训练期,只要它的预测标签伸入验证期,就不能再当成训练样本。边界检查应覆盖整个标签区间。验证或测试窗口是否允许使用边界前的历史作为上下文,需要按真实上线时可获得的信息明确规定,而不是无意间截断。

把重叠当成泄漏,或把不重叠当成独立

训练期内的重叠窗口是正常的数据构造方式。泄漏发生在使用了 Issue Time 不可获得的信息,或用未来数据影响了训练和选择。反过来,标签不重叠也不意味着输入和误差独立。

只按数组位置切片

缺失小时、夏令时、重复时间戳、跨建筑拼接都会让位置间隔与真实时间间隔不同。Part 2 完成时间戳和实体校验后,才能把这个最小函数扩展为真实数据集构造器。

7. 本文产出与下一篇

现在我们有了明确的窗口契约:每条样本包含 LL 个历史观测和 HH 个未来标签,起点按 Stride 移动,Issue Time 是输入的最后一个时刻。我们还知道训练步长和线上发起频率应分别定义。

这些规则将在后续实现为数据集构造与边界检查代码。本篇的列表函数只是索引示例,尚未产生 BDG2 的实验结果,也未确定最优 Lookback。

下一篇讨论:拿到同样的历史窗口后,模型应如何预测未来 24 个值?是逐步递推、分别预测每一步,还是一次输出整条曲线?

参考资料