几个月前,一个做供应链的朋友拿着一张销量表来找我,说想用人工智能做预测。数据有三年、按天记录,但中间有促销、缺货、节假日。他先试了 LSTM,说不够稳定;又听人说卡尔曼滤波是经典方案,但不知道这两个模型到底该选谁。我当时给了一个听起来不太像结论的回答:这两个模型根本不在同一个赛道上。LSTM 是拿数据训练出来的非线性序列模型,卡尔曼滤波是拿状态空间建模、再用观测数据在线修正的估计器。它们之所以总被放在一起比较,不是因为长得像,而是因为“时间序列预测”这个任务让它们成了常被并列的答案。后来我帮他把流程拆成两步,才解决了问题。这篇文章就用论文精读加最小代码复现的方式,讲清楚这两条路各自能做什么、不能做什么,以及什么时候该组合。
1. 先想清楚:LSTM 和卡尔曼滤波,帮我们解决的是同一件事吗?
1.1 一个真实场景:拿到销量数据后我该先跑哪个模型
那个朋友一开始的做法很有代表性:把三年销量丢给 LSTM,隐藏层加到 8 层,训练了几十个 epoch,验证集波动很大。然后他又在知乎上看到“卡尔曼滤波也能做时间序列预测”,于是开始怀疑自己是不是用错了模型。
这种情况在时间序列预测里非常普遍。大家习惯把问题简化为“选一个模型跑一下”,但实际落地时,第一个要回答的问题不是“用哪个模型”,而是“我们到底在预测什么”。
如果问题是“未来 7 天销量大概落在什么范围”,这是一种预测。如果问题是“当前真实库存水平是多少,如何从带噪声的销售记录里估计出来”,这是另一种任务,更接近状态估计。前者可以用 LSTM,也可以用 Transformer、XGBoost;后者则是卡尔曼滤波非常自然的场景,因为它本身就是在递归估计一个不可直接观测的状态。
那个朋友真正需要的其实是两件事:先用卡尔曼滤波对销量序列做平滑,把促销和缺货带来的异常观测降到合理范围;再拿处理后的序列去训练 LSTM,预测未来趋势。把两件事分开以后,效果才明显改善。
1.2 两种模型的核心差异:学习模式 vs 估计模式
LSTM 和卡尔曼滤波的差异,不是“神经网络 vs 传统算法”这种表层区别,而是建模哲学不同。
LSTM 属于学习模式。它通过大量历史样本,学习一个从过去窗口到未来值的映射函数。这个函数可以非常复杂,能捕捉非线性、周期性和交互效应。它不需要你先写出系统的物理方程,但它需要数据,并且需要足够多的、让模式可以被学到的数据。
卡尔曼滤波属于估计模式。它先要求你建立状态转移方程和观测方程,然后利用贝叶斯思想,把模型预测和观测数据以一种带权的方式融合,得到对系统状态的最优估计。它不需要海量样本,但需要你对系统机制有一定先验认识,比如状态怎么演化、噪声大概有多大。
所以从模型能力上看,LSTM 更像“数据驱动的函数拟合器”,卡尔曼滤波更像“带约束的在线估计器”。
| 维度 | LSTM | 卡尔曼滤波 |
|---|---|---|
| 建模范式 | 数据驱动的非线性映射 | 状态空间模型 + 递归估计 |
| 训练方式 | 大量样本 + 梯度下降 | 无需训练,需要初始化参数 |
| 输入要求 | 需要构造历史窗口 | 需要状态转移方程和观测方程 |
| 可解释性 | 低,内部状态难直接业务解释 | 较高,状态变量往往对应业务含义 |
| 在线适应性 | 需要重新训练或在线微调 | 天然适合在线递归更新 |
| 非线性能力 | 强 | 原始形式较弱,扩展后有 EKF/UKF |
| 典型场景 | 销量、流量、股价等复杂趋势预测 | 目标跟踪、导航、传感器融合 |
这个对比告诉我们一件事:遇到时间序列任务,先不要急着选模型,先搞清楚问题更像“学习规律”还是“估计状态”。这是整个选型流程的第一步。
2. 从 LSTM 那篇经典论文讲起:门控机制为什么能记住长期依赖
2.1 循环网络的尴尬:短时记忆不是靠容量解决,而是靠路径
LSTM 最早的核心贡献,不是“增加了网络层数”,而是解决了循环神经网络在长序列上的梯度传播问题。论文里的思想放到今天看依然成立:普通 RNN 在时间维度上不断乘同一个权重矩阵,序列一长,梯度很容易指数级缩小或放大,导致前面时间步的信息根本传不到后面。
这个问题的本质是路径太深。一个长度为 100 的序列,在反向传播时实际上等价于一个 100 层的网络。如果中间没有任何“捷径”,输入到输出的信息链就会断裂。LSTM 的解决方案不是增大模型容量,而是引入一条贯穿所有时间步的细胞状态线,让信息有机会不走非线性变换,直接传下去。
2.2 输入门、遗忘门、输出门到底在做什么
LSTM 内部有三个门,名字听起来玄,但作用很具体。
遗忘门决定细胞状态里哪些历史信息应该丢掉。它把当前输入 (x_t) 和上一个隐状态 (h_{t-1}) 拼接后过一层 sigmoid,输出一个 0 到 1 之间的向量。这个向量和细胞状态逐元素相乘,0 表示“忘掉”,1 表示“保留”。
输入门决定哪些新信息要写进细胞状态。它同样通过 sigmoid 计算一个候选权重,同时用 tanh 生成候选值,叠加到旧的细胞状态上。
输出门决定当前这个时间步要把哪些信息输出到隐状态。细胞状态已经完成了长期信息的保存和更新,但并不是所有信息都对当前输出有用,所以输出门再做一次筛选。
如果只记住一句话:LSTM 的核心不是“储存更多”,而是“决定什么时候记住、什么时候遗忘”。这让网络在很长的序列里也能保留真正重要的信息,同时避免梯度路径完全消失。
从论文精读的角度看,LSTM 的数学形式并不复杂,但工程设计非常巧妙。它没有把记忆问题交给网络容量去硬扛,而是用门控机制把信息流动变成可学习的。这也是为什么它从 1997 年提出到现在,仍然在序列建模里被大量使用。
2.3 论文之外:LSTM 在时间序列里的适用边界
但经典模型的优点,往往也是它的局限。
LSTM 很适合非线性模式明显、数据量充足、不要求强解释性的任务。比如电商销量预测、服务器负载预测、异常检测,这些场景里特征交互复杂,很难用简单方程描述。
它不适合的场景也很清晰:小样本数据。如果你只有几百条数据,LSTM 很容易过拟合,表现反而不如简单的统计模型。它也不适合对不确定性要求高的场景,因为标准 LSTM 输出的是点预测,虽然可以加贝叶斯层或 quantile loss,但模型本身并不是为一个明确的噪声模型设计的。
还有一点很多人忽略:LSTM 对输入尺度非常敏感。如果不做归一化、不统一时间步长,训练会非常不稳定。这个问题后面会重点讲。
3. 卡尔曼滤波不是“滤波”,而是一个递归估计器
3.1 从状态空间模型说起:系统状态才是主角
很多人第一次接触卡尔曼滤波,会被“滤波”两个字带偏,以为它像低通滤波一样,把高频噪声去掉。实际上,卡尔曼滤波做的是“估计”。
它假设系统存在一个真实状态 (x_k),这个状态我们观测不到,只能通过观测值 (z_k) 间接感知。状态会随时间按照状态转移方程演化,同时受到过程噪声影响。观测方程描述状态如何映射到观测值,并叠加观测噪声。
整个过程是概率化的:任何一步都有不确定性,卡尔曼滤波的目标是,在已知所有历史观测的条件下,给出状态的最优估计及其不确定性。
3.2 预测和更新两步:为什么每次只保留一个高斯分布
卡尔曼滤波的核心只有两步。
第一步是预测。根据上一时刻的最优状态估计和状态转移方程,推算出当前时刻状态的先验估计,同时更新协方差矩阵,表示这段时间里不确定性变大了多少。
第二步是更新。拿到当前观测后,把观测值、观测噪声和先验估计融合起来,计算后验估计。由于在线性高斯假设下,先验和似然都是高斯分布,融合后的后验仍然是一个高斯分布。所以算法不需要维护一个完整的概率分布,只需要维护均值和协方差矩阵两个量。
这个设计非常优雅。它能在线递归运行,每次只需要记住当前状态,不需要回放所有历史数据。
3.3 卡尔曼增益的意义:信模型还是信数据
卡尔曼滤波里最关键的参数是卡尔曼增益 (K)。它的作用是决定状态更新时,模型预测和观测数据各占多大权重。
如果观测噪声很大,也就是说 (R) 很大,卡尔曼增益会变小,系统更相信状态转移模型的结果。如果过程噪声很大,也就是说 (Q) 很大,说明模型本身不可靠,系统会更关注观测数据。
所以卡尔曼滤波看起来是在“滤波”,实际上是在做一个动态平衡:一边是模型预测,一边是数据观测,两边都不完全可信,于是用方差来加权。这就是它为什么在实际工程里非常好用,尤其适合导航、目标跟踪、传感器融合这些“有模型但模型不完美,有观测但观测带噪声”的场景。
卡尔曼滤波用于时间序列预测时,最常见的错误是直接把 Q 和 R 任意设成 0.1 或 1。Q 和 R 的大小不是调参游戏,它们描述的是你对模型和观测的真实置信程度。设得太离谱,滤波结果会变成“无脑跟随原始数据”或“完全忽略新观测”。
4. 代码复现:先让最小示例跑起来
4.1 环境准备与数据集构造
在动手复现之前,先确认环境里有 Python、NumPy、PyTorch。如果你更习惯 MATLAB,也可以做同样实验,但这里用 Python 的好处是更容易和后续数据工程、模型服务衔接。
我建议不要一上来就用很复杂的商业数据。先把流程跑通,再切到真实业务。下面用一个带噪的正弦波序列作为示例数据,目的不是模拟真实场景,而是验证两个模型的代码链路是否正常。
import numpy as np # 生成一段带噪声的正弦波,作为最小验证数据 np.random.seed(42) t = np.arange(0, 1000, 0.1) data = np.sin(t) + 0.05 * np.random.randn(len(t))真实项目里,这一步大概率是读数据库或 CSV,但验证思路是一样的:先确认数据没有缺失、没有异常跳变,再决定后续处理。
4.2 LSTM 的 PyTorch 最小实现
LSTM 做时间序列预测,常见做法是把过去 (seq_len) 个时间步作为输入,预测下一个时间步的值。下面是一个最小实现示例,结构很标准:一个LSTM层加一个线性输出层。
import torch import torch.nn as nn import torch.optim as optim class LSTMPredictor(nn.Module): def __init__(self, input_size=1, hidden_size=32, num_layers=1): super().__init__() self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True) self.linear = nn.Linear(hidden_size, 1) def forward(self, x): out, _ = self.lstm(x) # 只取最后一个时间步的隐状态做预测 return self.linear(out[:, -1, :])训练之前需要把数据切成固定长度的窗口。这里的关键点是:训练集、验证集的划分要在时间轴上顺序切割,不能随机打乱,否则会造成未来信息泄漏。
def make_sequences(data, seq_len=12): xs, ys = [], [] for i in range(len(data) - seq_len): xs.append(data[i:i+seq_len]) ys.append(data[i+seq_len]) return np.array(xs, dtype=np.float32), np.array(ys, dtype=np.float32) seq_len = 12 x_data, y_data = make_sequences(data, seq_len)然后把数据归一化到 0 附近。LSTM 对输入尺度很敏感,这个步骤不是可选项,而是必选项。归一化时要注意:只能用训练集的均值和标准差去转换验证集和测试集,避免验证集信息混入训练阶段。
之后的训练循环比较常规:用 MSE 损失、Adam 优化器、固定随机种子、每轮记录训练和验证 loss。训练 30 个 epoch 左右,通常就能看到验证 loss 明显下降。预测时,可以先用最后一段已知输入做滚动预测,逐步生成未来多个时间步。
需要提醒的是,上面的代码是教学示例结构,真实业务里还要考虑 batch 构造、模型保存、早停、多步预测评估等。但“最小可运行”这个目标,它已经够了。
4.3 卡尔曼滤波的 Python 最小实现
卡尔曼滤波实现起来比 LSTM 更直接,因为它不需要训练过程。下面是一个一维随机游走模型的例子,状态转移矩阵和观测矩阵都取 1,表示真实状态在时间上缓慢变化,观测值是真实状态加噪声。
def kalman_filter_1d(measurements, process_noise=0.01, measurement_noise=0.1): # 初始状态和初始协方差 x = 0.0 p = 1.0 # 状态转移矩阵和观测矩阵,这里简化为一维 F = 1.0 H = 1.0 Q = process_noise R = measurement_noise filtered = [] for z in measurements: # 预测 x_pred = F * x p_pred = F * p * F + Q # 更新 K = p_pred * H / (H * p_pred * H + R) x = x_pred + K * (z - H * x_pred) p = (1 - K * H) * p_pred filtered.append(x) return np.array(filtered) filtered_data = kalman_filter_1d(data, process_noise=0.01, measurement_noise=0.1)这段代码的结果是对原始观测的平滑估计。如果想做未来预测,在随机游走模型下,预测值会等于最后一步的状态估计,但不确定性会随预测步长增加而增大。真实项目里,状态转移方程通常更丰富,比如加入速度分量、季节性分量,但核心逻辑不变。
初学者很容易把 Q 和 R 看成“两个超参数”,然后对着 loss 调。但在卡尔曼滤波里,Q 和 R 描述的是“过程噪声”和“观测噪声”,它们的比例决定了滤波器的动态特性。Q 越大,滤波结果越贴近观测;R 越大,滤波结果越平滑、滞后越明显。调参前先理解这个语义,比单纯 grid search 重要得多。
4.4 怎么判断复现成功:先看 loss,再看预测曲线,再看误差分布
代码能跑起来,不代表复现成功。我一般会按这个顺序验证:
- Loss 曲线:LSTM 的训练集和验证集 loss 都应该稳定下降。如果 loss 震荡剧烈,先看学习率是否太大,再看数据归一化是否做好。
- 预测曲线:画出验证集上的预测值和真实值。LSTM 如果滞后明显,往往和序列长度或模型容量不足有关;卡尔曼滤波如果过度平滑,往往是 R 设置过大。
- 误差分布:计算残差序列,看它是否围绕 0 波动、是否还有明显自相关。如果残差里仍然有周期模式,说明模型没有把数据中的主要结构学完。
- 实际业务指标:不要只看 MSE,还要看这个误差在业务上不可接受。比如销量预测误差平均 20% 在库存管理里能不能接受,不同场景标准完全不同。
复现实验的核心目的不是把 loss 压到无限低,而是确认整个链路是通的、稳定的、可解释的。
5. 从“跑通”到“能用”:参数、评估和踩坑链路
5.1 单次跑通只是起点,批量实验才是常态
很多人在本地 notebook 里跑通一次,就以为模型可以上线了。实际上,单次跑通只能说明流程没有断,不能说明结果稳定。
更稳妥的做法是:固定随机种子,跑 3 到 5 次,观察指标方差。如果每次结果差异都很大,那你选中的可能不是模型,而是运气。时间序列实验尤其要小心,因为不同初始种子、不同训练/验证切分点,可能导致完全不同的结论。
建议把实验过程记录成一张配置表:数据起点、训练集比例、序列长度、隐藏层大小、学习率、归一化方式、随机种子、训练轮数。没有配置记录,任何复现结果都是不可信的。
5.2 六个容易踩坑的环节
时间序列复现里最常见的坑,往往不在模型结构里,而在数据处理和评估设计里。我归纳成六个点:
- 数据泄漏:用全量数据计算归一化的均值和标准差,是新手最容易犯的错误。必须先对训练集 fit,再 transform 验证集和测试集。
- 随机打乱数据:时间序列要求按时间顺序划分,打乱后等于提前看到了未来,验证结果会虚高。
- 序列长度选择:seq_len 太长,训练困难;太短,模型看不到周期。先用自相关分析或简单实验确定一个合理范围。
- 卡尔曼滤波参数初始化:初始状态和初始协方差不是随便设的,它们会影响前几步估计。如果初始值离真实状态太远,滤波结果一开始会明显偏离。
- 多步预测的误差累计:拿上一步预测值作为下一步输入,误差会逐步放大。如果业务需要 7 天预测,评估时就不要只测 1 步预测。
- 只看 RMSE,不看残差结构:RMSE 很低,但残差里仍有明显自相关,说明模型漏掉了模式,需要进行残差分析。
5.3 一套可复用的排查顺序
当预测结果不符合预期时,建议按这个顺序排查,而不是直接换模型。
- 先看数据:有没有缺失、异常值、量纲差异。
- 再看来数据处理:划分、归一化、滑窗是否泄漏。
- 再看训练过程:loss 是否下降、梯度是否稳定、模型是否过拟合。
- 再看预测输出:滞后程度、幅度、趋势是否合理。
- 最后看模型假设:对 LSTM 是序列长度和数据量问题,对卡尔曼滤波是 Q/R 和状态方程问题。
这条链路可以用在很多时间序列任务里。它的核心逻辑是:先排除数据层的问题,再检查训练层,最后才怀疑模型层。很多人一上来就调模型结构,结果真正问题出在验证集上混入了未来数据。
6. 选型判断:什么时候用 LSTM,什么时候用卡尔曼滤波,什么时候组合
6.1 按问题特征选:非线性、在线性、可解释性、数据量
到了最后决策环节,我建议用下面这个“三步选型框架”。
第一步,确认任务类型。如果是预测一个未来值,并且历史模式比较复杂,优先考虑 LSTM 这类学习模型。如果是估计一个不可直接观测的状态,比如设备健康度、目标位置、真实信号,优先考虑卡尔曼滤波。
第二步,看是否有可用的先验模型。如果你知道系统大概怎么演化,能写出状态转移方程,那卡尔曼滤波会非常稳,泛化能力也强。如果你完全不知道系统机制,只有一个原始序列,那先让神经网络去学习更合理。
第三步,看业务约束。需要在线实时更新,且算力有限,卡尔曼滤波占优。需要处理复杂非线性,且有一定的数据积累,LSTM 占优。需要向业务方解释模型为什么这么预测,卡尔曼滤波的状态变量更好讲,LSTM 则更像黑盒。
| 场景 | 更推荐起点 | 理由 |
|---|---|---|
| 小样本 + 有简单物理模型 | 卡尔曼滤波 | 不需要大量数据,可解释性强 |
| 大样本 + 强非线性 | LSTM | 能自动学习复杂交互关系 |
| 在线实时传感器融合 | 卡尔曼滤波 | 递归更新,延迟低 |
| 多步销量/流量预测 | LSTM 或树模型 | 需要大量特征和模式学习 |
| 需要不确定性估计 | 卡尔曼滤波自带协方差 | LSTM 需额外改造 |
| 有状态方程但模型不准 | 卡尔曼滤波 + 神经网络残差 | 用网络补偿模型误差 |
6.2 组合思路:状态空间为骨架,LSTM 学习动态残差
如果你已经分别跑通了两个模型,想更进一步,可以尝试组合思路。常见做法是把卡尔曼滤波看作状态估计的骨架,用 LSTM 去学习状态转移方程里的残差,或者用 LSTM 预测状态转移参数。
这种组合在文献里有多种变体,也常被称为深度学习与状态空间模型的结合。它能得到的好处是:保留卡尔曼滤波的不确定性表达,同时引入神经网络的非线性拟合能力。但从工程经验看,我不建议新手一上来就做组合。组合方案的问题在于:调试链路更长、参数更多、可解释性更难维护。先分别跑通两种基础模型,理解各自的失败模式,再组合才有意义。
这个方向也解释了为什么到了 2026 年,LSTM 和卡尔曼滤波在 AI 和时间序列预测相关讨论里仍然是高频词。它们并不像一些新模型那样需要很重的训练基础设施,而是以很稳健的方式嵌入到真实系统里。
另外,Transformer 在长序列建模里确实越来越火,但它和 LSTM 不是简单的替代关系。Transformer 更擅长跨位置的特征交互,但参数量大、训练成本高,在小样本或在线低延迟场景里未必划算。LSTM 和卡尔曼滤波的组合,在不少工业场景里反而更容易落地。
6.3 不要神化模型,时间序列预测没有万能解
最后必须泼一盆冷水。无论是 LSTM、卡尔曼滤波,还是更复杂的深度状态空间模型,都不可能做到“准确预测未来”。时间序列预测的价值,更多在于提供一个比拍脑袋更可靠的参考区间,并帮助业务方理解不确定性。
我见过不少项目,模型结构很先进,但预测结果并不好用。原因往往不是模型不够强,而是业务问题没有定义清楚:预测目标是什么、误差代价是什么、模型输出怎么接入决策流程。如果你只追求验证集上的 RMSE,模型做得越复杂,越容易过拟合历史。
所以我的建议始终是:先做一个能解释、能维护、能验证的最小方案。等它稳定之后,再根据业务反馈,一步一步加入更复杂的模型。LSTM 和卡尔曼滤波的代码都不复杂,真正需要花时间的,是理解它们在什么假设下有效,在什么场景下失效。
那天帮朋友处理完销量数据,我发现项目最花时间的不是调 LSTM 的隐藏单元,而是和他确认业务链路里哪一段适合用模型替代,哪一段不适合。模型只是工具箱,不是水晶球。如果你现在正准备做时间序列预测,我的建议很简单:先拿两个模型在最小数据集上跑通,感受它们各自在什么时候失效。之后你就会明白,选型不是选一个更高级的算法,而是选择一个能对业务解释、能上线维护、能持续验证的方案。