halo 的技术博客

返回

你手上一段价格,里面藏着几个周期:日内节奏、周线规律、月度资金流,外加一条慢趋势和噪声。你想用一个模型把这些「周期结构」自动用起来——不是你手工傅里叶,而是让模型自己发现「哪段和哪段像」。

Transformer 被捧上天,但直接搬到时间序列上,常年输给一个线性层。原因不在注意力不行,而在逐点建模:把第 t 个价格当成一个 token,这个 token 本身没有语义——它不知道自己处在周期的第几个相位、和 20 天前那个点是同相位还是反相位。注意力要在「一堆没语义的点」里硬学周期,事倍功半。

结论先放这:TimesNet(Wu et al., ICLR 2023)的破局点非常巧——它不跟 Transformer 的逐点建模较劲,而是先把一维序列「折叠」成二维的时间块,让二维卷积去抓「同一相位、跨周期」的相关性。 周期的天然结构在 2D 里变成了可见的条纹,2D 卷积天生擅长这个。

在我们的合成数据(周期 20+45+9、缓趋势、高斯噪声、2% 稀疏脉冲)上,FFT 准确找回 9/20/45 三个周期;把序列按检测到的周期折叠后做「每相位跨周期聚合」重建,周期解释方差从「无周期信息基线」的 0% 抬到 31.7%,重建残差 std 2.08(总 std 2.52)说明残差基本只剩噪声——周期被干净地剥了出来。 诚实地说:TimesNet 不是银弹——它本质是「把周期当成可估计量」的工程化封装,当你用线性读出头时,它赢不了同等信息的线性基线(数据处理的不等式,后文拆穿);周期检测偏 bin、折叠引入伪相关、相位对齐要防泄漏、头尾 L−P 个点补不回来,这五类陷阱要一一说清。但作为「让 Transformer 真正读得懂周期」的那一下转换,它是目前最干净的设计之一。

TimesNet 核心:1D 序列按周期 P 折叠成 2D 时间块,周期变成可见条纹


1. 为什么「逐点建模」抓不住周期#

Transformer 在 NLP 里强,是因为每个 token(词)自带语义、位置自带顺序。时间序列里这两个前提都崩了:

  • 点没语义:价格 100.3 这个点本身不告诉你任何结构,它和 100.5 没有「语义距离」。
  • 周期不是邻接关系:真正的强相关往往发生在「相隔一个周期」的两个点之间(比如 t 和 t+20),但注意力默认是在「局部窗口」或「全序列」里学权重,相隔 20 步的弱连接很难被注意。

论文里那句著名的话:“The periodicity in time series makes the temporal patterns repeat locally.” (周期的重复让时序模式在局部重复)。关键洞察是——与其让模型去学「t 和 t+P 相关」,不如直接把 t 和 t+P 摆到二维空间的相邻位置,让卷积一米一个准。


2. TimesNet 的核心操作:1D → 2D 折叠#

假设我们检测到序列的主导周期是 P。把一维序列 x₀, x₁, …, x_{L-1} 按 P 折叠:

第 0 列: x₀  x_P    x_2P   ...
第 1 列: x₁  x_{P+1}  x_{2P+1} ...
  ...
第 P-1 列: x_{P-1} x_{2P-1} ...
plaintext

也就是把序列 reshape 成 (P, ⌈L/P⌉) 的矩阵:行 = 相位(t mod P),列 = 第几个周期

折叠之后,原本「相隔 P」的点,变成了二维矩阵里同一行的相邻元素。周期结构瞬间变成二维里的「横向条纹」——这正是 2D 卷积最擅长捕捉的模式。

import numpy as np

def fold_to_2d(x1d, period):
    """把 1D 序列按周期 P 折叠成 2D:(P, ncol),行=相位, 列=第几个周期。"""
    L = len(x1d)
    if L % period != 0:
        pad = period - (L % period)
        x1d = np.concatenate([x1d, np.full(pad, x1d[-1])])
    Lp = len(x1d)
    ncol = Lp // period
    return x1d.reshape(period, ncol)   # (P, ncol)

# 例:周期 P=20 的一段序列折叠后,周期性会出现成「每一行都是相似的横向纹理」
X2 = fold_to_2d(y[:200], period=20)
print(X2.shape)   # (20, 10)
python

下面这张图就是折叠效果的直观展示:左图 1D 序列,中图折叠成 2D 后周期变成横向条纹,右图在 2D 上做池化再展开回 1D,同相位跨周期被对齐。

1D→2D 折叠:周期在 2D 里变成横向条纹,2D 处理再回 1D


3. 多周期怎么处理:Top-k 周期 + 加权求和#

真实价格从不止一个周期。TimesNet 的做法是:用 FFT 找出 Top-k 个主导周期,对每个周期各做一次 1D→2D 变换和 2D 卷积,再把 k 个结果按「该周期的能量占比」加权求和。

我们的周期检测就是标准 FFT 幅度谱,但有两个关键细节(也是后文陷阱 2 的来源):

在我们的合成序列上,这个函数干净地找回了注入的三个周期 [9, 20, 45],权重 [0.25, 0.59, 0.16]——周期 20 振幅最大,权重最高。下面这张图左边是 FFT 幅度谱(标注了三个检测到的周期),右边两张是折叠后每个主导周期的 2D 条纹。

FFT 找到主导周期,折叠后周期在 2D 里变成条纹


4. TimesBlock:2D 卷积 + 回 1D#

单个周期 P 的「时间块」上,TimesNet 用一个小型 2D 卷积(论文里是 Inception 风格的多尺度卷积)提取特征,再把二维特征按原来的折叠顺序展开回 1D,得到这个周期对应的表示。多周期表示加权求和,就是整个 TimesBlock 的输出。

我们用「每相位跨周期聚合」(中位/均值)来演示 2D 处理的核心思想——同一行(同相位)在列方向(跨周期)做聚合,尖刺被稀释、周期峰被保留:

times_block_1d 的输出是「把各周期对齐后重建的序列」。关键点:周期结构被显式保留,而只落在某一列的稀疏事件(脉冲)被跨周期均值稀释。 这就是 2D 折叠相对 1D 直接处理的本质优势。


5. 实测:2D 折叠把周期「剥」出来#

我们用一段合成价格验证 TimesNet 在周期提取上的真实能力(这是它的本职,不是预测):

  • 信号 = 2.0·sin(2πt/20) + 1.2·sin(2πt/45) + 0.6·sin(2πt/9) + 0.003t(缓趋势)+ 高斯噪声;再叠 2% 位置 ±4 的稀疏脉冲。
  • 用检测到的周期做「每相位跨周期聚合」重建周期分量 R_t。
  • 基线:无周期信息,只用全局均值重建。

结果:TimesNet 的 2D 折叠重建把 31.7% 的方差归因为周期结构,无周期基线只有 0%;重建残差 std 2.08(总 std 2.52),证明周期被干净地剥出,剩下的基本是噪声和脉冲。 下图为局部对比:含噪原始、无周期平线基线、TimesNet 周期重建(紧贴真值)。

TimesNet 把多周期剥出:解释方差 31.7% vs 基线 0%,残差以噪声为主


6. 五个真实陷阱(必须说清)#

陷阱 1:数据处理的不等式——线性头时 TimesNet 赢不了线性基线。 这是最重要的一条。TimesNet 的 times_block 输出是输入的线性函数(折叠、2D 卷积、展开全线性)。若预测头也是线性的,整体就是输入的线性映射,等价于一个固定窗口的线性回归——不可能比「直接对原窗口做线性回归」更优(数据处理的不等式:线性变换不增加信息)。我在早期实现里实测过:把 TimesNet 表示喂给线性读出头,测试 MSE 反而比直接线性基线高 600%+,因为折叠+展开引入了相位错位噪声。TimesNet 真正的价值在非线性 2D 卷积头 + 多头注意力,以及超长回看 / 多变量泛化——这些不是我们这篇纯 numpy 演示能覆盖的。别拿线性版去和线性基线比,那是伪命题。

陷阱 2:FFT 周期检测会偏 bin。 FFT 的频率网格是离散的,周期 P≈n/k 只能取到最近的整数 bin。低频周期(n/100)的 bin 间隔很大,检测到的周期可能偏差几个点。解决:要么插值细化 FFT,要么用自相关 / Lomb-Scargle(非均匀)。我们代码里用「贪心选峰 + 5 点隔离」避免把同一正弦的频谱泄漏当成多个周期,但周期绝对精度仍受限于采样。

陷阱 3:折叠会引入伪相关。 当真实周期不是 P 的整数倍、或存在多个非整数比的周期时,折叠会把不同相位的样本错误地排到同一行,2D 卷积学到的「条纹」其实是混叠产物。论文用多周期加权缓解了,但弱周期仍可能被强周期掩盖。

陷阱 4:相位对齐泄漏。 (t mod P) 按全局对齐相位。如果序列在窗口内有趋势或漂移,不同周期的「相位原点」不一致,聚合会把趋势错误地平均掉一部分。实际落地要先把趋势/漂移去掉(detrend)再折叠。

陷阱 5:端点缺口。 为了凑整 L % P,我们在尾部 padding 了最后一个值。L - P + 1 之后的点本质是用 padding 算的,预测/重建质量下降。长序列里这点占比小,短窗口里要警惕。


7. 它和 SSA / VMD / 傅里叶是什么关系#

  • 傅里叶:整段正弦基,要周期全程稳定;非平稳价格里泄漏严重。
  • VMD:把信号拆成 K 个窄带模态,要拍 K 和 α,但频率是连续估计的。
  • SSA:延迟嵌入成轨迹矩阵做 SVD,本征分量按方差排序,最平滑的是趋势。
  • TimesNet把「折叠成 2D」做成可微、可学习的操作,让深度学习模型直接消费周期结构。它本质是「周期感知的特征工程」,但嵌进了端到端网络,能和注意力、卷积共享梯度。

一句话:傅里叶/SSA/VMD 是离线把周期拆出来;TimesNet 是让模型在每个时间步都能在线地「按周期重新看一遍序列」。


8. 落地路径(别只停在演示)#

  1. 当特征用:把 times_block 的输出(周期感知表示)拼到你的 XGBoost / 线性模型特征里,作为「周期对齐特征」,往往比原价格更好用。
  2. 当预训练 backbone:用 TimesNet 在长历史价量上做自监督预训练,再迁移到下游少样本任务(和我们之前聊的对比学习是一个思路)。
  3. 多变量:TimesNet 原文强调「通道独立」——每个资产单独折叠、共享卷积权重,天然适合多标的。
  4. 真要做端到端:上 PyTorch,用官方 TimesBlock(2D Inception 卷积 + 多周期加权),别用我们的纯 numpy 线性版去和线性基线比(见陷阱 1)。

9. 结论#

TimesNet 的价值不在于「模型多深」,而在于那一下 1D→2D 的折叠转换:它把时间序列里最稳定的结构——周期——变成二维卷积天然能抓的条纹,让 Transformer/ CNN 第一次「看得见」周期。

我们纯 numpy 从零实现证明:FFT 能干净找回 9/20/45 这样的多周期,2D 折叠能把周期解释方差从 0% 抬到 31.7%,残差以噪声为主。但它赢在「让深度学习消费周期」,不是赢在「线性头比线性基线强」——这层不等式要心里有数。周期检测偏 bin、折叠伪相关、相位泄漏、端点缺口,是五个必须正视的真实陷阱。

周期一直在那儿,关键是把它摆到模型能看懂的地方。TimesNet 给了那个「摆法」。

TimesNet 周期建模:把价格「折叠」成二维时间块,Transformer 才真正读得懂周期
https://blog.halo26812.eu.org/blog/timesnet-periodic
Author halo
Published at 2026年7月22日
版权声明 CC BY-NC-SA 4.0
Comment seems to stuck. Try to refresh?✨