资讯动态

贝叶斯LSTM实战:给时间序列预测戴上概率眼镜

发布时间:2026/10/20 21:45:55 来源:尧图企业网站定制
去年有段时间我帮某团队做销量预测模型用的是LSTM训练集和验证集上的RMSE都漂亮得可以挂墙上。结果到了下个季度仓库该断货还是断货该积压还是积压。后来复盘时我突然意识到一个问题我一直在给业务方一个最可能的数字但他们真正需要的是这个数字到底靠不靠谱。换句话说模型只摘下了苹果却没说这棵树上的苹果有多大、有多少。从那次之后我开始认真琢磨怎么给LSTM戴上概率眼镜用贝叶斯的视角重新看待时间序列预测把单一的预测曲线变成一条带置信区间的概率带。这篇内容就是我在这条路上攒下来的经验适合已经会跑LSTM、但想进一步量化预测不确定性的人参考。1. 为什么点预测会骗人从一次库存预测翻车说起1.1 RMSE好看仓库照样断货先还原一下当时的场景。我们按天预测某SKU的销量输入过去90天的数据输出未来7天的值模型结构就是很常规的两层LSTM加一个全连接输出层。训练完成后测试集RMSE大概是日均销量的7%左右这个数在同类项目里已经算不错了。可实际跑起来每周都有那么两三个SKU在补货周期内卖断。断货的原因并不神秘。销量波动比较大的SKU真实值经常偏离预测值20%甚至30%特别是遇到促销、天气突变、平台流量变动这些情况时误差会更大。RMSE被大量普通日子的平均表现拉低了它根本没有告诉我哪些日子预测是稳的哪些日子预测随时会崩。这个现象背后有个容易被忽略的事实最小化均方误差MSE的模型学到的是条件均值E[y|x]。均值在误差平方意义下是最优的但它不能反映分布的宽度。如果某天的销量有70%概率是100件、30%概率是400件均值是190件。按190件备货大概率不够——因为70%的情况下真实值只有100件而30%的情况下需要400件。点预测把两种完全不同的风险场景揉成了一个数揉完之后信息就丢了。1.2 点预测到底缺了什么点预测缺的东西专业说法叫预测不确定性predictive uncertainty。它大体可以拆成两块偶然不确定性aleatoric uncertainty数据本身带有的噪声比如天气导致的销量波动这种不确定性即使模型再强也无法消除只能评估它的规模。认知不确定性epistemic uncertainty模型对参数掌握得不够好带来的不确定性比如训练数据太少、分布外样本、模型结构选错。这种不确定性可以通过更多数据、更强模型来降低。把这两类不确定性综合起来才是对一个预测结果的完整描述。你给业务方的不再是下周卖12345件而是下周销量大概率落在9500到15500件之间这句话的信息量完全不同。业务方可以根据区间的宽度提前做备货、调价、排产能而不是对着一个孤零零的数字赌运气。2. 贝叶斯LSTM的底层物理权重不再是定值而是分布2.1 从一组最优权重到权重分布传统LSTM的训练方式是梯度下降目标是把损失函数压到最低最后得到一组固定的权重。你可以把这组权重理解成对数据的最佳解释。但问题在于能够解释同一批训练数据的权重其实有无数种组合梯度下降只是选了其中一组。换一组同样能解释数据的权重下次预测的结果可能就会偏移。偏移的程度就是认知不确定性。贝叶斯方法换了个思路。它不追求找唯一一组最优权重而是维护一个关于权重的概率分布P(w|D)也就是在看过数据D之后每个候选权重有多大的可能是真实的。这个分布集中说明数据对权重压得很死模型很有信心这个分布宽说明数据不足以锁定唯一解模型的判断模糊。预测时也不再用单组权重算一个数而是把每一组可能的权重都考虑进来加权平均得到完整的预测分布P(y*|x*, D) ∫ P(y*|x*, w) · P(w|D) dw这个积分把无数个可能的世界都跑了一遍输出的是一整条分布。分布窄说明预测稳定分布宽说明情势不明朗。这是贝叶斯视角和常规训练最本质的区别。2.2 后验分布为什么会难到要逃跑公式写起来简单求起来却很痛苦。原因有三参数空间太大。LSTM里包含输入门、遗忘门、输出门、记忆单元的权重参数少则几万多则几百万上千万。在这么高维的空间上去猜一个分布不是靠画图能解决的。后验分布没有解析解。神经网络是非线性结构P(w|D)的形状异常复杂根本不是高斯分布。我们用高斯去近似它本身就是一种妥协。积分算不动。∫P(w|D)dw这种高维积分没有任何闭式解必须靠采样或者变分推断。正是因为这些困难实际工程里很少有人真去跑MCMC采样。大家都是用各种近似手段把贝叶斯这座大山搬成一座小土丘。好在近几年的研究把门槛降得很低下面要讲的两种方案几乎是开箱即用的状态。3. 不写复杂采样代码也能让LSTM戴上概率眼镜3.1 方案一MC Dropout把丢弃层当采样器MC Dropout的原理可以用一句话概括训练时开Dropout预测时也开Dropout多次前向传播得到一组预测值这组值的统计特征就是不确定性估计。2016年那篇经典论文证明了一件事在神经网络里用Dropout做训练等价于对后验分布做了一种特殊的高斯混合近似。按照这个思路预测时随机丢弃不同的神经元相当于从近似的后验分布里抽取了不同的权重组合。跑10次前向得到10个预测值均值就是最终点预测标准差就是不确定性。代码实现非常轻量。以PyTorch为例训练时正常开Dropout预测时不要用model.eval()而是手动把Dropout层置为训练模式。下面这段代码是典型的做法import torch def predict_with_mc_dropout(model, x, num_samples100): # 把Dropout层保持开启 model.train() with torch.no_grad(): predictions torch.stack([model(x) for _ in range(num_samples)]) # predictions形状: (num_samples, batch_size, output_dim) mean predictions.mean(dim0) std predictions.std(dim0) return mean, std注意这里有个容易踩的细节model.train()会把BatchNorm的行为也切换掉。如果你的LSTM之后接了BatchNorm层需要手动只打开Dropout否则统计量会出问题。我习惯把模型里所有Dropout层单独拿出来处理或者干脆在LSTM场景下不用BatchNorm。MC Dropout最大的优点是无痛接入模型结构不用动损失函数不用动只改推理方式就行。缺点是它主要捕捉认知不确定性对数据自带噪声的建模比较粗糙。另外它的采样效率不高Dropout率的设置也会影响结果需要小调一下。3.2 方案二双头输出让网络自己学会报方差第二种方案是让网络直接输出分布的参数。具体做法是把LSTM最后的全连接层从1个输出改成2个输出一个预测均值μ一个预测方差σ²。训练时的损失函数不再是MSE而是高斯分布的负对数似然NLL 0.5·log(2πσ²) (y - μ)² / (2σ²)这里有个非常有意思的地方网络在训练中会自动调节σ²。当某个时间点它预测不准时梯度会促使它把σ²调大避免损失爆炸当它预测很准时σ²可以压得很低获得更小的损失。这就相当于让模型学会了什么时候该说我不确定。[\begin{aligned} \mathcal{L}{\mathrm{NLL}}\frac{1}{T}\sum{t1}^{T}\left(\frac{\left(y_t-\mu_t\right)^2}{2\sigma_t^2}\frac{1}{2}\log(2\pi,\sigma_t^2)\right) \end{aligned}]实现上需要做一步数值保护的细节网络输出的方差要经过softplus或exp激活函数保证σ²始终为正。直接用线性输出可能导致训练初期出现负方差损失直接变NaN。import torch import torch.nn as nn class UncertaintyLSTM(nn.Module): def __init__(self, input_dim, hidden_dim): super().__init__() self.lstm nn.LSTM(input_dim, hidden_dim, batch_firstTrue) self.fc nn.Linear(hidden_dim, 2) # 输出均值和方差 def forward(self, x): out, _ self.lstm(x) out self.fc(out[:, -1, :]) mu out[:, 0] log_var out[:, 1] sigma torch.sqrt(torch.exp(log_var) 1e-6) return mu, sigma def nll_loss(mu, sigma, y): return (torch.log(sigma) 0.5 * ((y - mu) / sigma) ** 2).mean()不过这里要特别提醒一句直接把整段序列的方差当成σ²它建模的是偶然不确定性也就是数据噪声。如果你还想把认知不确定性也量化出来可以把方案二和MC Dropout组合起来用——既开Dropout采样又用高斯输出头输出方差。两种不确定性最终用方差相加的方式融合这是很多生产级系统的标准做法。3.3 两种方案怎么选我把这两种方案在几个维度上做了对比方便你按场景选择维度MC Dropout高斯输出头改动成本极低不改模型结构低改最后一层和损失函数不确定性类型以认知不确定性为主以偶然不确定性为主推理计算量需要数十次前向传播一次前向即可融合能力可与其他方法叠加可与Dropout叠加适合场景训练数据少、需要判断模型懂不懂数据噪声明显、业务上要区间实际我的经验是如果目标是快速给现有模型加一个信心指标首选MC Dropout。如果是要做正经的预测区间、并且要影响库存或资金决策那就用高斯输出头最好再叠加MC Dropout。4. 手把手搭一个带不确定性的LSTM时序模型4.1 准备数据与构造模型我这里用一段模拟数据来演示完整流程方便你直接跑通后迁移到自己的数据上。假设我们要预测某个指标的未来值指标本身带有明显的周期性和噪声import numpy as np import matplotlib.pyplot as plt import torch from torch.utils.data import DataLoader, TensorDataset # 生成模拟时间序列正弦波 时变噪声 np.random.seed(42) T 3000 t np.arange(T) signal 10 * np.sin(2 * np.pi * t / 96) # 周期96个单位 noise_scale 1 1.5 * np.sin(2 * np.pi * t / 480) # 噪声幅度缓慢变化 data signal np.random.normal(0, noise_scale, sizeT)这里噪声的幅度是变化的意味着不同时间段的数据不确定性不同正好能体现概率模型的优势。然后构造滑窗样本每个样本用过去48天的序列预测未来第1天的值def make_windows(data, input_len48): X, y [], [] for i in range(len(data) - input_len): X.append(data[i:iinput_len]) y.append(data[iinput_len]) return np.array(X), np.array(y) X, y make_windows(data) # 归一化 mean, std X.mean(), X.std() X (X - mean) / std y (y - mean) / std # 划分训练/验证/测试集 split1, split2 int(len(X)*0.7), int(len(X)*0.9) dataset TensorDataset(torch.FloatTensor(X), torch.FloatTensor(y)) train_loader DataLoader(dataset[:split1], batch_size256, shuffleTrue) val_loader DataLoader(dataset[split1:split2], batch_size256) test_loader DataLoader(dataset[split2:], batch_size256)模型的搭建沿用前面UncertaintyLSTM的结构输入维度是1隐藏维度设64输出2个数。如果你想让它输出长度为K的预测序列而不是单步预测需要把输出维度改成2 * K这一步留作你自己的扩展。提示时间序列建模前一定要确认数据平稳性。如果原始序列有趋势建议先做差分或者加一个趋势项作为外部输入否则LSTM很容易学出一个滞后的预测。4.2 训练负对数似然训练时使用NLL损失。相比MSENLL收敛速度通常会慢一点因为模型同时要学均值和方差两样东西。我的做法是前5个epoch先用MSE热身让均值部分先落到位之后再切换成NLL这样方差头不会在初始阶段被噪声带跑optimizer torch.optim.Adam(model.parameters(), lr1e-3) model.train() for epoch in range(30): epoch_loss 0 for x_batch, y_batch in train_loader: optimizer.zero_grad() mu, sigma model(x_batch.unsqueeze(-1)) if epoch 5: loss ((mu - y_batch) ** 2).mean() else: loss nll_loss(mu.squeeze(), sigma.squeeze(), y_batch) loss.backward() optimizer.step() epoch_loss loss.item() print(fepoch {epoch1}: loss {epoch_loss/len(train_loader):.4f})训练过程中建议同时监测验证集上的覆盖率这个指标我们后面第5节会重点分析。用验证集而不是训练集来调参是为了判断模型是真学到了规律还是仅仅背下了训练数据。4.3 推理从方差到95%预测区间训练完成之后给定一段新的历史序列x*模型输出μ和σ。在高斯假设下真实值y*的95%预测区间是μ ± 1.96σ。如果你叠加了MC Dropout还需要把多次前向得到的均值方差和高斯头的输出方差加起来σ_total² σ_gaussian² σ_mc²然后把σ_total带进区间公式。这里有个容易被误解的点这个区间叫预测区间prediction interval不是置信区间confidence interval。预测区间包含的是真实观测值的不确定性既包含均值估计不准的部分也包含数据噪声本身。业务上做决策看预测区间才合理。下面这段代码把预测结果画成带置信带的曲线model.eval() with torch.no_grad(): mu, sigma model(test_tensor.unsqueeze(-1)) mu mu.squeeze() * std mean sigma sigma.squeeze() * std lower mu - 1.96 * sigma upper mu 1.96 * sigma plt.figure(figsize(12, 5)) plt.plot(actual, label真实值, colorblack) plt.plot(mu, label预测均值, colorblue) plt.fill_between(range(len(mu)), lower, upper, alpha0.3, label95%预测区间, colororange) plt.legend() plt.show()图上你会发现一个很有意思的现象在噪声幅度大的时间段区间会明显变宽噪声平静的时间段区间收窄。这就是高斯输出头学到的不确定性随环境变化也是概率预测相对点预测最直观的优势。5. 校准是概率模型的生死线三次踩坑记录5.1 踩坑一区间太窄95%的区间实际只覆盖了70%的数据第一次用高斯输出头时我信心满满地把预测区间发给业务方结果对方一周后就反馈真实值经常跑出区间之外。我统计了一下号称95%的区间实际覆盖率只有73%。为什么会这样原因是模型学到的σ偏小。神经网络在训练时倾向于把σ压小因为这样能让高概率区域的似然变大。尤其是当训练数据噪声分布不是严格高斯、或者存在离群点的时候σ会被低估。这个问题在文献里叫miscalibration解决途径主要有两种温度缩放在验证集上调一个缩放系数s把σ变成s·σ使覆盖率正好落在目标水平。改用分位数回归直接用pinball loss预测0.025、0.5、0.975三个分位点不假设高斯分布覆盖目标区间的比例更容易控制。温度缩放是最快的做法。在验证集上二分搜索s找到让覆盖率最接近95%的值。这个方法逻辑上不太优雅但非常有效工程上可以先用它兜底。5.2 踩坑二过拟合把不确定性压没了第二个坑出现在训练轮数过多的时候。训练集上的NLL持续下降但验证集上的覆盖率却在恶化。原因是网络开始记住训练集的具体细节连噪声的精确位置都背下来了所以输出σ越来越小仿佛模型觉得世界没有噪声。这个现象的本质是过拟合的不确定性表现。我们在做点预测时通常用验证损失早停就能防住。但做概率预测时损失下降不等于不确定性估计合理。我后来给自己定了个规矩每隔几个epoch就在验证集上计算一次覆盖率或者至少盯着验证集NLL。如果验证集NLL开始上升而训练集还在下降立刻早停。5.3 踩坑三我差点用RMSE当唯一指标最隐蔽的坑是用点预测的指标来评价概率模型。模型A的RMSE可能比模型B低但模型A对不确定性完全没概念区间覆盖率只有60%模型B的RMSE稍高但区间覆盖率稳定在94%到96%。如果你的KPI只有RMSE你永远会选中模型A然后业务方继续断货。正确的评估应该至少包含三个指标RMSE或MAE衡量均值预测的准确性。区间覆盖率PICP真实值落在预测区间内的比例应接近名义置信水平。区间宽度MPIW在覆盖率达标的前提下区间越窄越好。这三个指标要放在一起看。覆盖率达标而区间太宽说明预测过于保守业务上没办法用区间窄但覆盖率太低说明模型自欺欺人。我在项目汇报里直接放这三张图比单看RMSE的说服力强得多。6. 哪些业务场景真的需要概率预测6.1 库存与供应链安全库存的数学依据库存计划里有一个经典概念叫安全库存它是为了吸收需求波动而额外储备的部分。安全库存的准确计算依赖于需求的分布而不是需求均值。如果你只知道平均销量100件安全库存只能拍脑袋如果你知道销量95%概率在75到130件之间那么安全库存就等于订货提前期内的需求波动上限减去均值这件事被转化成了有数学依据的计算。把概率预测用在库存上的收益是实打实的。某项目在引入预测区间之后把安全库存从固定3天销量改成了动态按区间宽度决定同一服务水平下库存金额下降了大约12%。区间宽的日子多备一点区间窄的日子少备一点整体库存效率明显提升。6.2 设备剩余寿命预测把不确定变成维护计划设备预测性维护同样高度依赖不确定性。剩余寿命预测如果只给一个还有30天维护团队没法排期——因为30天是个点估计提前坏和滞后坏都会造成损失。但如果模型给出剩余寿命为20到40天峰值在30天维护团队就可以在第18天开始准备第26天安排检修窗口。这种场景里概率区间的价值可能比点估计本身还大。LSTM在这里的用法和销量预测没有本质区别只是输出从一个数值变成了失效概率曲线。把σ做成随时间变化的曲线不同设备的健康状态在不确定性这个维度上会表现出明显差异——健康设备曲线平滑故障前不确定性指标通常先急剧放大这是点预测很难觉察的迹象。6.3 金融场景先想清楚你要预测什么金融领域对不确定性量化尤其狂热但入局之前必须想清楚你要预测的到底是什么如果预测的是波动率那方差本身就是目标可以直接用高斯输出头输出σ。如果预测的是价格方向那区间无法覆盖价格跳跃模型很可能给你一个超宽的区间业务上并不好用。金融数据的信噪比极低概率模型提供的区间往往宽到看起来毫无信息量这不是模型废物而是市场本身就很难预测——知道这一点你就不会对着一个宽区间骂模型了。7. 给入坑者的实用建议最后整理几条我在实际项目中反复验证过的经验。先确认你的场景真的需要不确定性。有些项目只需要一个排序比如给用户推哪个商品排名顺序比置信区间重要得多。不确定性只在涉及储备、调度、排期、风控这类资源分配决策时才真正有价值。从MC Dropout起步再决定是否上高斯输出头。改动最小能让你快速感受模型信心长什么样。如果发现业务方确实在用区间做事再切换到更完整的方案。校准要放在评估流程里。无论是PICP还是PINB分位数损失的覆盖率都需要一个量化指标持续跟踪。没有校准的概率输出只是一个看起来漂亮的装饰。不要迷信复杂的贝叶斯方法。变分推理、正态流、MCMC这些方法在其他场景有优势但在LSTM时序预测这个场景里MC Dropout和高斯输出头能覆盖绝大多数需求先把手头的模型榨干再追求更重的方法论。我自己的体会是给LSTM戴概率眼镜这件事真正改变的不是模型结构而是你对预测这件事的认知。点预测让你相信未来可以精确预知概率预测则让你尊重世界的随机性、并用一种系统化的方式去度量随机性的大小。单是完成这个思维转变就已经值回票价了——因为后面所有模型层面的修改都是在为这个转变服务。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑