状态空间模型 Mamba 金融时序:把「历史」压缩成一个状态向量,选择性地记住该记的
Transformer 的注意力是 O(N²) 且训练时看不到未来、推理时还得把整段重新过一遍;RNN 虽是线性复杂度却有梯度消失、难以并行。状态空间模型(SSM,Gu et al. 2021)走中间路线:用连续系统的离散化,把历史压成一个状态向量 h_t 沿时间递推。Mamba(Gu & Dao 2023)再加一招「选择性」——让扫描步长 Δ 由输入决定,模型自己决定「记住什么、忘掉什么」。本文用纯 numpy+scipy 从零实现零阶保持离散化、递归扫描与卷积视角,在「目标被 130 步前事件驱动」的长程依赖合成序列上实测:带持久循环状态的选择性 SSM 预测 MSE=0.50,相对 120 窗口线性基线(2.93)降 82.9%、相对 AR(1)(1.34)降 62.7%——因为循环状态携带了窗口外的回声;并诚实拆穿线性读出下 SSM 不赢线性基线、Δ 选择性是演示近似、状态维度 N 受限、训练不稳、长程衰减五类真实陷阱(中阶)。
你有一段很长的时间序列——几年 tick 级或日线价量。你想预测下一步。Transformer 一把梭:把所有历史塞进注意力,算一遍全对的权重。问题是:注意力矩阵是 O(N²),序列一长显存就爆;而且它每次都得「从头看一遍」全序列,没法像人一样边走边记。
RNN 相反:它有个状态 h_t,每来一个新点就更新一下,复杂度 O(N),还能边走边记。但 RNN 有老毛病——梯度消失,记不了太久;而且必须严格串行,没法并行训练,慢。
结论先放这:状态空间模型 SSM 走中间路线——它用「连续系统的离散化」把历史压成一个固定维度的状态向量 h_t,h_t 沿时间递推(线性复杂度、可并行训练),又天然携带长程记忆。Mamba(Gu & Dao, 2023)的关键创新是「选择性」:让离散化步长 Δ 由输入 x_t 决定,模型自己决定「这一步该认真记还是快速忘」。
在我们的长程依赖合成序列(目标 = 130 步前的事件回声,τ 远大于 120 的滚动窗口)上:带持久循环状态的选择性 SSM 预测 MSE=0.50,相对固定 120 窗口线性基线(2.93)降 82.9%、相对 AR(1)(1.34)降 62.7%。 诚实地讲:这个优势来自 SSM 的循环状态跨窗口延续、能看到窗口外的 τ 步回声,而固定窗口线性读出的感受野被窗口锁死;但如果你把 SSM 也切成固定窗口、用线性读出头,它一样赢不了同等信息的线性基线(数据处理的不等式,后文陷阱 1)。SSM 的 Δ 选择性、状态维度 N、训练稳定性、长程衰减,是四个必须正视的真实约束。但作为「线性复杂度 + 长程记忆 + 可并行」三者兼得的结构,它是目前最接近理想的那一个。

1. 连续状态空间:从微分方程到递推#
经典 SSM 描述一个连续系统:输入 u(t) 通过状态 h(t) 产生输出 y(t)。
h'(t) = A·h(t) + B·u(t)
y(t) = C·h(t)plaintext- A(N×N):状态转移矩阵,决定记忆怎么衰减(对角线为负 → 指数遗忘)。
- B(N×1):输入怎么注入状态。
- C(1×N):状态怎么读出输出。
- N:状态维度,固定大小,这就是「把无限历史压成有限向量」的关键。
关键一步是离散化:模型在离散时间步 t 上跑,要用零阶保持(ZOH)把连续系统变成递推式。给定步长 Δ_t:
Ā = exp(Δ_t · A)
B̄ = (Δ_t·A)⁻¹ (exp(Δ_t·A) − I) · Δ_t·B
h_t = Ā·h_{t-1} + B̄·x_t
y_t = C·h_tplaintextexp(Δ_t·A) 是矩阵指数(我们用 scipy.linalg.expm)。这样 h_t 就递归地携带了所有过去——它是历史的压缩,而不是重看一遍。
2. 从零实现:离散化 + 递归扫描#
import numpy as np
from scipy.linalg import expm
def discretize(A, B, dt):
"""零阶保持离散化。A:(N,N) B:(N,1) dt:(T,) -> Abar:(T,N,N) Bbar:(T,N,1)"""
T = len(dt); N = A.shape[0]
Abar = np.zeros((T, N, N)); Bbar = np.zeros((T, N, 1))
for t in range(T):
dA = dt[t] * A
M = expm(dA)
Abar[t] = M
inv = np.linalg.inv(dA)
Bbar[t] = inv @ (M - np.eye(N)) @ (B * dt[t])
return Abar, Bbar
def ssm_scan(x, A, B, C, dt, h0=None):
"""x:(T,) -> (T,) 输出。选择性 SSM 递归。"""
T = len(x); N = A.shape[0]
Abar, Bbar = discretize(A, B, dt)
h = np.zeros(N) if h0 is None else h0.copy()
ys = np.zeros(T); CB = C.ravel()
for t in range(T):
h = Abar[t] @ h + Bbar[t].ravel() * x[t]
ys[t] = CB @ h
return ys, hpython注意 dt 是逐时间步的——这就是 Mamba 的入口。传统 SSM(如 S4)用固定 Δ;Mamba 让 Δ 由输入 x_t 经过一层线性投影得到,于是「每一步记得多细」随数据变化。
3. 卷积视角:SSM 等价于一个卷积#
SSM 的递推可以展开成卷积:y_t = Σ_{s≥0} k_s · x_{t-s},其中核 k_s = C·Ā^{s}·B̄。也就是说,SSM 既不神秘也不必须串行——它能写成卷积,因此训练时可并行(像 CNN 一样一次性算完);推理时又能递归(像 RNN 一样 O(1) 每步)。这是它比 Transformer 省、比 RNN 快的根本原因。
def ssm_kernel(A, B, C, dt, L):
"""展开成卷积核 k(h),h 越大=越久之前。"""
N = A.shape[0]
Abar, Bbar = discretize(A, B, dt)
CB = C.ravel()
k = np.zeros(L); h = np.zeros(N)
for t in range(L):
h = Abar[t] @ h + Bbar[t].ravel()
k[t] = CB @ h
return kpython在我们的参数下,卷积核在回溯 130 步处仍有明显权重(-1.86,非零),说明状态确实能感知窗口外的历史——这正是它能在长程依赖上赢过固定窗口的关键。下图为核的衰减形状和状态的递推轨迹。

4. Mamba 的「选择性」:Δ 由输入决定#
普通 SSM 的 Δ 是常数,等于「对所有输入一视同仁地遗忘」。Mamba 让 Δ_t = softplus(W_Δ · x_t + b_Δ),于是:
- 遇到「重要的、信息量大」的点(|x_t| 大)→ Δ 大 → Ā 更接近单位阵 → 认真记住;
- 遇到「噪声、冗余」的点 → Δ 小 → Ā 更接近零 → 快速遗忘。
这就是「选择性」:模型对历史做内容相关的压缩,而不是无差别地遗忘。
def selective_delta(x, dt_min=0.05, dt_max=0.9):
"""演示用:Δ 随输入局部幅度变化(真实 Mamba 是学出来的线性投影+softplus)。"""
loc = np.abs(x)
return dt_min + (dt_max - dt_min) / (1.0 + np.exp(-(loc - 0.8) * 3.0))python为了直观演示「选择性 = 任务相关」,我们让同一个序列通过两个不同关注点的 Δ 函数:任务 A 随幅度上升、任务 B 与幅度反相关。下图左是原始序列,中右是两个任务各自学到的输入相关 Δ(t)——同一段数据,不同任务聚焦的区域不同。这正是选择性相对「固定 Δ」的本质区别。

5. 实测:长程依赖上,持久状态赢在「看得见窗口外」#
我们造一段长程依赖序列:目标 y_t 里有 0.6·evt_{t-130}——130 步前的事件回声,再加短周期和噪声。预测窗 H=12。
- 固定窗口线性基线:用最近 120 点线性读出未来 12 步。但 120 < 130,窗口里根本看不到那次事件的回声,感受野被锁死。
- AR(1):只用上一时刻值外推,更看不到长程。
- 选择性 SSM(持久状态):维护一个跨窗口延续的循环状态 h,每个时刻读 y_t=C·h_t 再刷新 h。因为 h 里一直带着 130 步前的回声,预测头只用当前状态就能用上它。
# 训练一个从「状态」到「未来 H 步」的读出头(滑动构造 (h_t, y_{t+H}))
Hs, Ys = [], []
for t in range(W, len(y) - H):
dt = selective_delta(y[:t + 1])
_, hf = ssm_scan(y[:t + 1], A_ssm, B_ssm, C_ssm, dt)
Hs.append(hf); Ys.append(y[t + 1:t + 1 + H])
Hs = np.array(Hs); Ys = np.array(Ys)
Ws, *_ = np.linalg.lstsq(Hs[:ntr], Ys[:ntr], rcond=None)
pred_ssm = Hs @ Wspython结果:
窗口线性基线 MSE (H=12): 2.9250
AR(1) MSE: 1.3425
SSM 持久状态 MSE: 0.5004
SSM 相对窗口线性改进: 82.9%
SSM 相对 AR 改进: 62.7%plaintextSSM 的持久循环状态把 MSE 砍到 0.50,比锁死在 120 窗口的线性基线低 82.9%。 根因很干净:τ=130 的回声在 120 窗口外,固定窗口线性读出永远看不到它;而 SSM 的状态 h 跨窗口延续,把那次事件一直带到了预测时刻。下图是测试段一条样本的预测对比(SSM 紧贴真实)和各步平均误差曲线(SSM 全程最低)。

6. 五个真实陷阱(必须说清)#
陷阱 1:线性读出下,SSM 赢不了同等信息的线性基线。 和 TimesNet 完全同构的「数据处理的不等式」:如果你把 SSM 也切成固定窗口、用线性读出头,那么 SSM 的输出是输入的线性函数,整体等价于对窗口的线性回归,不可能比「直接对原窗口线性回归」更优。我实测的对比其实有个关键不对称——SSM 用的是「跨窗口延续的持久状态」(感受野是整个历史),而线性基线用的是「固定 120 窗口」(感受野锁死)。这个不对称才是 SSM 赢的原因,不是 SSM 本身比线性强。 若把 SSM 也切成 120 窗口、丢弃窗口外状态,它立刻回到和线性基线同一水平。所以:SSM 的威力来自「持久状态 / 长程感受野」,不是来自「变换本身」。 要在短程、窗口内就能解决的任务上,SSM 没有结构性优势。
陷阱 2:Δ 选择性是演示近似。
我们 selective_delta 用「幅度 → sigmoid」做演示,真实 Mamba 是学出来的线性投影 + softplus,Δ 是端到端从任务里涌现的。演示函数只为了说明「选择性 = 输入相关」,不能当真模型用。真要做,得上 PyTorch + 因果卷积扫 Δ。
陷阱 3:状态维度 N 是硬上限。 记忆被压进 N 维向量,N 太小记不下复杂历史,N 太大计算量和显存又回来。金融多资产、长历史场景里 N 的选取是实打实的 trade-off,不是越大越好(过拟合 + 训练不稳)。
陷阱 4:训练稳定性。
expm、矩阵求逆、Δ 的极端值都会让训练抖动。真实实现用 A = exp(A_log) 参数化(保证稳定)、Δ = softplus 保证正、并配合归一化。我们的 numpy 演示固定了参数,没暴露这层。
陷阱 5:长程记忆会衰减。 A 的对角线为负 → Ā 是收缩矩阵 → 越久之前的影响按指数衰减。τ 太大(超过 exp(ΔA) 的有效记忆长度)时,回声也会被忘掉。我们的 τ=130 还能被感知,是因为 N 维状态 + 适中 Δ 给了够长的有效记忆;换成 τ=500 可能就失效了。
7. 它和 RNN / Transformer / TimesNet 怎么选#
| 结构 | 复杂度 | 长程记忆 | 可并行 | 选择性 |
|---|---|---|---|---|
| RNN/LSTM | O(N) | 有但梯度消失 | 否(串行) | 隐式 |
| Transformer | O(N²) | 完美(全看) | 是 | 注意力即选择性 |
| SSM (S4) | O(N) | 有,固定 Δ | 是(卷积视角) | 否 |
| Mamba | O(N) | 有 | 是 | Δ 输入相关 |
- Transformer:数据够、序列不太长、要最强拟合 → 用注意力。
- TimesNet:周期结构主导、想让模型「看得见周期」→ 折叠成 2D。
- Mamba/SSM:序列极长、要线性复杂度 + 长程记忆 + 可并行 → 状态空间。
一句话:Mamba 是「把 RNN 的记忆、Transformer 的选择性、CNN 的并行」揉到一起的尝试,代价是状态维度有限、训练更娇气。
8. 落地路径#
- 长序列特征提取:用预训练 Mamba 当 backbone 处理 tick / 分钟级长序列,吐出状态给下游模型。
- 在线推断省显存:生产环境用 SSM 的递归形式,每步 O(1) 状态更新,不重算全序列。
- 多资产联合:状态维度 N 同时编码多标的动态,比逐标的 RNN 共享记忆。
- 真要做端到端:上官方 Mamba 的
selective_scan(CUDA 实现),别用 numpy 递归(慢且不稳)。
9. 结论#
状态空间模型的精髓一句话:把无限长的历史,压成一个 N 维状态向量 h_t,让它沿时间递推。 它既不像 Transformer 那样每次从头看一遍全序列(省复杂度),也不像 RNN 那样记不住、训不动(可并行、长程记忆)。Mamba 再加的「选择性」——让 Δ 由输入决定——把「模型自己决定记什么、忘什么」变成了可微的操作。
我们纯 numpy+scipy 从零实现证明:在 τ=130 的长程依赖上,带持久循环状态的选择性 SSM 把预测 MSE 压到 0.50,比锁死窗口的线性基线低 82.9%——因为循环状态带着窗口外的回声。但这个优势来自「持久状态 / 长程感受野」,不是 SSM 变换本身比线性强;切成固定窗口、线性读出,它立刻回到基线水平(陷阱 1)。Δ 选择性、状态维度 N、训练稳定性、长程衰减,是四个必须正视的约束。
历史不用每次重看,压缩成一个状态就好——前提是,你让它选择性地压缩。