简介:面向新能源汽车市场销量预测的混合建模项目,融合长短期记忆网络与高斯过程回归两种模型,并引入贝叶斯优化算法进行超参数自动寻优,形成一套高精度、高稳定性的预测方案。资源面向数据科学、机器学习及智能交通领域的开发者和研究人员,解决时序数据长期依赖与不确定性量化两大难题。压缩包共五个文件,包括两个脚本文件、一个说明文档、一个文本说明及一个附加文档,包体仅42KB,体量轻便。其中,脚本分别承担混合模型构建与数据抓取处理,说明文档提供项目结构与环境配置指引,文本与附加文档补充项目极简说明、建模原理和调参思路;目前已有四十八人学习,适合快速搭建预测基线或作为后续研究模板。通过该资源可掌握融合深度学习和概率模型的完整流程,直接套用销量预测、负荷预测等类似时序任务,节省大量算法调试时间。
1. 贝叶斯优化算法遇上销量预测:LSTM 与 GPR 为什么非得结合
把长短期记忆网络、高斯过程回归和贝叶斯优化算法放进同一个预测框架,乍一听像是为了凑技术关键词。但做过新能源汽车销量预测的人都知道,纯深度学习的销量模型在真实业务里经常翻车:训练集上 MAPE 做到 3%,一遇到补贴政策调整或车企降价潮,单月偏差直接飙到 20% 以上。原因不在模型不够深,而在销量序列本身是「非线性趋势 + 政策脉冲 + 小样本」的混合体,单靠神经网络的点预测根本给不出风险边界。这套混合框架做的事情很简单:LSTM 负责抓长期趋势和周期,高斯过程回归兜住残差并提供预测区间,贝叶斯优化算法替你把 LSTM 的隐层数、学习率、序列长度这些超参一次调到位。适合手里有周度或月度销量数据、需要做产能规划和库存备货的从业者,也适合正在做销量预测课题、想找一个能落地的混合模型的同学。
2. 混合框架为什么能打:LSTM 抓趋势,GPR 兜残差
2.1 新能源销量序列的统计特征:非线性趋势、周期和突变脉冲
新能源汽车月度销量和传统燃油车最大的区别,是它叠加了政策和价格两个强外部变量。产量爬坡、电池成本下降、补贴退坡、购置税减免延长、车企降价促销,这些事件在销量曲线上留下的不是平滑波动,而是突然的台阶或尖峰。比如某个月的销量环比暴涨 40%,下个月又回落,这种序列对纯时序模型极不友好。
从数据形态上看,新能源销量序列有三个特征值得留意。第一个是趋势项随时间非线性增长,但增速受到渗透率基数的限制,不会无限上涨。第二个是周期性,月度销量有明显的季末冲量现象,季度末最后一个月通常偏高。第三个是外部脉冲,政策截止日期前的抢购效应会制造局部离群点。这三个特征叠加,导致整个序列既不满足平稳性假设,又存在大量结构突变。传统时间序列模型如 ARIMA 对第二个和第三个特征处理起来很吃力,而纯 LSTM 虽然能拟合非线性趋势,却容易在突变点附近产生系统性滞后。
这里就引出了混合框架的核心判断:没有一个单一模型能同时处理好趋势、周期和突变三个维度。LSTM 擅长从历史序列中抽象出趋势和周期性,但对小样本场景下的突变点,它缺少显式的不确定性表达。高斯过程回归正好补上这一环:它以概率分布的形式输出预测值,天然适合小样本回归,且能给出方差估计。两者结合,本质上是让 LSTM 解决“能预测多少”的问题,让 GPR 解决“预测不准时风险多大”的问题。
2.2 长短期记忆网络和传统机器学习模型的分工边界
在具体分工上,我一般把 LSTM 作为主预测器,输入是过去多个时间步的销量及相关特征,输出是下一期的点预测值。LSTM 内部的门控结构让它能记住较长时间跨度的依赖,比如去年同期的高基数效应,或者三个季度前的补贴政策对当前月度销量的滞后影响。这是传统机器学习模型很难用手工特征复现的部分,因为滞后阶数太多,特征工程做不过来。
高斯过程回归则被放在残差层。所谓残差,就是真实销量减去 LSTM 预测值后的剩余部分。这些残差里往往还包含着 LSTM 没有吃透的局部关联,比如相邻月份的政策应对波动,或者某些归因不明的季节性抖动。GPR 对这些残差建模时,输入维度不需要太高,通常只取滞后 1 到 3 个月的残差差值和少量日期特征,就能捕捉到剩余的相关结构。GPR 的输出是一个分布,均值作为残差的点修正,方差用来构造最终预测的置信区间。
这种分层的思路比直接训练一个端到端的深度学习模型更有工程价值。深度学习模型的问题在于低层特征和高层特征都挤在一个黑匣子里,你很难判断预测偏差来自哪里。而混合框架把黑匣子拆成两块:LSTM 解释不了的那部分,显式地扔给 GPR 去概率化。一旦模型在新月份上预测偏差变大,你可以先看 GPR 的方差是否急剧扩大,再判断是输入特征缺少了关键信息,还是 LSTM 本身已经过拟合。
2.3 两种融合方式的选型理由:残差融合比状态融合更稳
LSTM 和 GPR 的融合在工程实现上主要有两种方案。第一种是状态融合,把 GPR 直接接在 LSTM 的隐层输出上,让 GPR 对高维特征做概率映射。这个方案的思路很优雅,但实际操作难度大:LSTM 的隐层维度通常在 32 到 128 之间,GPR 的协方差矩阵复杂度是样本数的三次方级,特征维度过高会让协方差矩阵病态,训练极不稳定。第二种是残差融合,LSTM 先独立预测,GPR 对残差单独建模,最终预测等于两个模型输出之和。这个方案的优势是每个模型只需要处理自己擅长的部分,GPR 面对的是低维残差序列,协方差矩阵依然可控。
我在实际项目里强烈推荐残差融合。原因有三条。其一,训练过程解耦,LSTM 可以先调到满意状态,再单独训练 GPR,出现问题时定位更快。其二,GPR 的输入特征可以灵活设计,你可以只喂最近三个月的残差差值和是否节假日标记,不必强行从 LSTM 隐层里抽取特征。其三,当政策突变导致输出波动时,GPR 的方差会显著变大,这个信号能直接作为业务层面的预警指标,点预测加区间预测的组合比单点输出更有决策意义。
2.4 贝叶斯优化算法的工作机制:为什么不用网格搜索
LSTM 的可调超参太多了:隐层神经元数、层数、学习率、批大小、序列长度、dropout 比率。用网格搜索组合爆炸,随便排列一下就是几百组实验,每组 LSTM 都要完整训练几十个 epoch,时间成本完全不可接受。贝叶斯优化算法在这里的价值是用尽可能少的实验次数逼近最优超参组合,它通过维护一个代理模型来学习「超参组合 → 验证损失」的映射关系,然后在候选空间里选择最有可能降低损失的点做下一次实验。
贝叶斯优化算法内部有两种常见的代理模型选择:高斯过程代理模型和树结构概率密度估计(TPE)。如果采用高斯过程作为代理模型,新实验点的采集可以通过期望改进(EI)或置信上界(UCB)来筛选。TPE 则用非参数密度估计替代高斯过程,在离散和连续超参混合的场景下表现更稳。实际工程里用 TPE 比较多,因为它不需要手动设置核函数,搜索空间里同时存在整型和浮点型参数时处理更自然。需要注意一点:标题里的「高斯过程回归」和贝叶斯优化内部的高斯过程代理模型是两个不同层次的概念,前者是销量预测模型的一部分,后者只是调参时的搜索策略,别把两者混淆了。
贝叶斯优化的另一个关键点是目标函数的设计。你不要只用验证集的 MAPE 作为目标,最好把连续三个月预测误差的标准差也纳入考量,否则优化算法会选出那些均值误差低但单月波动很大的超参组合。我习惯把目标函数写成验证集上 MAPE 的滚动均值加上滚动标准差,乘一个权重系数,这样选出来的模型在业务上更可用。
3. 数据切分的正确姿势:别让归一化和验证集泄露未来
3.1 特征集构造:把政策、节假日和价格波动写进特征矩阵
在做特征工程之前,先把数据粒度定下来。月度销量预测的特征粒度相对粗糙,每条样本代表一个自然月;周度预测则要处理节假日对齐问题,农历春节前后两周的数据需要特殊标记。我建议从月度数据起步,把模型跑通后再降粒度。
核心特征至少包括三组。第一组是销量历史特征,包括滞后 1 到 12 个月的销量值、最近三个月的销量差分、去年同月销量占比。第二组是时间特征,月份序号、季度末标记、是否春节月、是否双十一促销月。第三组是外部变量,动力电池价格指数、当月平均优惠幅度、充电桩保有量增速、政策事件哑变量。其中政策事件哑变量很关键,新能源销量受补贴退坡和购置税政策影响显著,必须把政策变化点显式编码成 0/1 特征,否则模型会把政策冲击当成随机噪声处理。
这一步骤最容易被低估的是外部变量的对齐问题。销量数据是月度截止值,而电池价格指数可能是月度均值,充电桩保有量是月末存量,它们的统计口径不完全一致。我在项目里一般统一按月内最后一天或月内均值做对齐,并且把所有外部特征都滞后一期,因为当月的电池价格指数要到月末才知道,而你要预测的是这个月整月的销量,只能用上个月已知的信息。这个细节直接决定了模型能否落地到真实预测场景,用当月数据做特征属于典型的数据泄露。
3.2 时间序列的合法切分:滚动窗口而不是随机打散
分类任务里常见的train_test_split随机打散在时间序列预测里完全不能用。销量预测最怕的就是把未来数据混进训练集,模型会「记住」未来几个月的销量均值,验证集上表现极好,一到线上预测就崩盘。正确的做法是滚动时间窗口切分,训练集始终在验证集之前。
| 切分方式 | 训练集范围 | 验证集范围 | 适用场景 |
|---|---|---|---|
| 留出法 | 前 80% 时间 | 后 20% 时间 | 样本量 60 个月以上 |
| 滚动前推 | 第 1-36 月 | 第 37-40 月 | 样本量中等,需要多次验证 |
| 扩展窗口 | 第 1-n 月随迭代扩展 | 紧接其后的 3 个月 | 样本量小,充分利用历史 |
我一般用扩展窗口交叉验证:第一次用前 36 个月训练,预测第 37 到 40 个月,第二次把训练集扩展到第 40 个月,预测第 41 到 44 个月,以此类推。这样每轮验证集都是模型没见过的连续时间段,能更真实地反映线上预测表现。
切分之后还有一个容易被忽略的操作:归一化必须在训练集内部计算统计量,然后用训练集的均值和标准差去转换验证集。如果你在全部数据上先做标准化再切分,验证集的分布信息已经泄露到训练过程里。GitHub 上很多开源销量预测项目都存在这个问题,新手最容易踩。
3.3 标签与归一化:先压缩再进行反变换
销量数据的数值范围变化很大,早期一个月销量可能只有几千台,后期可以达到几万台,如果直接对原始值做标准化,小数值时期的误差会被大数值时期稀释,模型训练会偏向后期的大数值样本。我的处理方式是先做log1p压缩,即对y取自然对数加一,再对压缩后的序列做标准化。这样既保持了序列的相对变化幅度,又避免了极端高销量月份主导梯度。
反变换的顺序也需要小心。预测完成后先反标准化,再执行expm1还原销量数值。残差 GPR 部分训练时用的是真实销量减去 LSTM 预测值的残差,这个残差不需要做 log 变换,因为残差的数值范围通常稳定在几千台以内,标准化即可。最终预测结果是反变换后的 LSTM 预测值与残差修正值相加。
标签设计上还有两个进阶选项。如果业务需要预测未来三个月的销量,可以设计多步预测标签,即用第 t 月及之前的数据预测第 t+1、t+2、t+3 月。但多步预测会放大误差,残差 GPR 在处理多步残差时复杂度上升,我建议先把单步预测做扎实,再用递归方式生成多步预测:预测出 t+1 后,把 t+1 的预测值拼接进历史序列,预测 t+2,依此类推。递归预测会累积误差,但配合 GPR 的方差估计,你能看清误差是怎么一步步扩大的。
4. 从零跑通模型:贝叶斯优化调参下的 LSTM 加残差 GPR
4.1 搭建 LSTM 主模型:先固定结构再谈调参
下面这套代码是我在月度销量预测项目里打磨过的最小实现,直接能跑通。数据形态用模拟数据演示,把它替换成你自己手头的销量表和外部变量表即可。代码里只保留核心结构,实际项目的边界条件处理放在注释里说明。
import numpy as np import pandas as pd from sklearn.preprocessing import StandardScaler from sklearn.gaussian_process import GaussianProcessRegressor from sklearn.gaussian_process.kernels import RBF, Matern, ConstantKernel from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from optuna import trial import optuna # 模拟月度销量数据,实际使用时替换为你的真实数据集 np.random.seed(42) dates = pd.date_range("2018-01-01", periods=72, freq="ME") trend = np.linspace(0.5, 5, 72) ** 1.5 seasonal = 0.3 * np.sin(np.arange(72) * 2 * np.pi / 12) pulse = np.where(np.isin(dates.month, [1, 12]), 0.8, 0.0) # 年末抢购效应 sales = np.exp(trend + seasonal + pulse + np.random.normal(0, 0.2, 72)) df = pd.DataFrame({"date": dates, "sales": sales}) df["month"] = dates.month df["quarter_end"] = (dates.month % 3 == 0).astype(int) df["policy"] = np.where(dates >= "2023-01-01", 1, 0) # 政策哑变量示例这段代码里trend用指数形式模拟非线性增长,pulse标记年末冲量。真实数据里你不需要生成这些,而是把销量表和特征表直接读入 DataFrame。关键是要保证每行代表一个自然月,date列按升序排列。模拟数据只是为了让你能完整跑通流程,理解每一步的输入输出形态。
4.2 LSTM 训练与预测:序列构造函数是关键
def make_lstm_data(df, lookback=12, feature_cols=["sales", "month", "quarter_end", "policy"]): data = df[feature_cols].values.astype(float) X, y = [], [] for i in range(lookback, len(data)): X.append(data[i - lookback:i]) y.append(data[i, 0]) # 第一列是销量 return np.array(X), np.array(y) # 切分:训练集用前 48 个月,验证集用后 12 个月 df_train = df.iloc[:48].copy() df_val = df.iloc[48:60].copy() # 训练集归一化 scalers = {} for col in ["sales", "month", "quarter_end", "policy"]: scaler = StandardScaler() df_train[col] = scaler.fit_transform(df_train[[col]]) df_val[col] = scaler.transform(df_val[[col]]) scalers[col] = scaler归一化这里用的是逐列 StandardScaler,注意month列是周期性变量,标准化后 1 月和 12 月之间失去了邻近性,我在实际项目里会额外加一组sin(month * 2 * pi / 12)和cos(month * 2 * pi / 12)特征来保留周期关系。上述代码中暂时省略,但强烈建议加上,尤其是你要预测春节月份的销量时。
4.3 混合模型训练:残差 GPR 接管 LSTM 解释不了的部分
def train_lstm_and_gpr(train_df, val_df, lookback=12, epochs=50): # 构造训练数据 X_train, y_train = make_lstm_data(train_df, lookback) X_val, y_val = make_lstm_data(val_df, lookback) # LSTM 结构:两层隐藏层加 Dropout model = Sequential() model.add(LSTM(units=64, return_sequences=True, input_shape=(lookback, X_train.shape[2]))) model.add(Dropout(0.2)) model.add(LSTM(units=32, return_sequences=False)) model.add(Dropout(0.2)) model.add(Dense(1)) model.compile(optimizer="adam", loss="mse") history = model.fit(X_train, y_train, epochs=epochs, batch_size=16, verbose=0, validation_data=(X_val, y_val)) # LSTM 对训练集和验证集做预测(原始归一化空间) lstm_train_pred = model.predict(X_train).flatten() lstm_val_pred = model.predict(X_val).flatten() # 残差计算:真实销量减去 LSTM 预测 residual_train = y_train - lstm_train_pred residual_val = y_val - lstm_val_pred # GPR 输入:只用滞后 1-3 的残差作为低维特征,防止协方差矩阵病态 def make_gpr_input(residuals, lag=3): X, y = [], [] for i in range(lag, len(residuals)): X.append(residuals[i - lag:i]) y.append(residuals[i]) return np.array(X), np.array(y) gpr_X_train, gpr_y_train = make_gpr_input(residual_train) gpr_X_val, gpr_y_val = make_gpr_input(residual_val) # 核函数:Matern 核在带噪声的销量残差上比 RBF 更稳 kernel = ConstantKernel(1.0) * Matern(length_scale=1.0, nu=1.5) gpr = GaussianProcessRegressor(kernel=kernel, alpha=1e-2, normalize_y=True, n_restarts_optimizer=4) gpr.fit(gpr_X_train, gpr_y_train) # 最终预测:LSTM 预测 + GPR 残差修正 final_train_pred = lstm_train_pred[lag:] + gpr.predict(gpr_X_train) final_val_pred = lstm_val_pred[lag:] + gpr.predict(gpr_X_val) return model, gpr, final_train_pred, final_val_pred, scalers这段代码是整个方案的核心,有四个设计点需要解释。第一,LSTM 训练好之后,LSTM 预测和真实值之间的残差才是 GPR 的学习目标,这比让 GPR 直接预测销量更合理,因为残差的数值范围小且相对平稳。第二,GPR 的输入特征只取了前三个残差值,故意不加入外部变量,因为外部变量信息已经通过 LSTM 间接编码,残差序列自带的滞后相关才是 GPR 该捕捉的内容。第三,核函数选择了 Matern 核而非 RBF,销量残差往往带有一定的平滑但不完全光滑的特征,Matern 核的nu参数控制平滑度,取 1.5 表示二阶可导,对中等噪声水平的数据适应性更好。第四,n_restarts_optimizer=4表示核函数超参数优化时随机重启 4 次,降低陷入局部最优的概率。如果验证集残差仍然存在明显趋势,说明 LSTM 欠拟合,需要先把 LSTM 的容量调大或增加序列长度,而不是急着调 GPR。
4.4 贝叶斯优化接管超参搜索:Optuna 目标函数写法
def objective(trial): lookback = trial.suggest_int("lookback", 6, 18) units = trial.suggest_int("units", 32, 128) dropout = trial.suggest_float("dropout", 0.1, 0.4) lr = trial.suggest_float("lr", 1e-4, 1e-2, log=True) epochs = trial.suggest_int("epochs", 30, 80) # 每次实验用扩展窗口交叉验证 val_errors = [] for start in range(24, 48, 6): train_df = df.iloc[:start] val_df = df.iloc[start:start + 6] # 这里复用 train_lstm_and_gpr 函数,传入 lr 和 units 等超参 # 为了节省篇幅,省略了完整传参,实际需要在函数内部生效 val_errors.append(compute_mape_with_lstm_gpr(train_df, val_df, lookback, units, dropout, lr, epochs)) return np.mean(val_errors) + np.std(val_errors) study = optuna.create_study(direction="minimize", sampler=optuna.samplers.TPESampler(seed=42)) study.optimize(objective, n_trials=50)贝叶斯优化目标函数的写法有两点要特别注意。第一点是搜索空间不要过大,lookback在 6 到 18 之间足够了,超过 18 个月会让 LSTM 输入维度膨胀,训练时间翻倍但精度提升有限。第二点是lr用对数均匀分布采样,学习率在时间序列模型里通常是数量级敏感的,从 0.001 到 0.01 之间按线性均匀分布采样的效果远不如对数分布。n_trials=50是经验值,样本量较小且序列长度不长的场景下,50 组实验足够收敛;如果数据量很大,可以提高到 100 组,但不要无限增加,每组 LSTM 训练时间会线性累积。
TPE 采样器设置固定seed很重要。贝叶斯优化的启动点来自随机采样,不固定种子的话,同一次优化流程两次运行结果不同,复现和排查问题都会很麻烦。另外建议在目标函数里把每次实验的验证误差存到 list 里,实验结束后画出误差收敛曲线,如果 50 组实验误差还在持续下降,说明搜索空间边界设小了,或者实验次数不够。
4.5 反归一化和预测区间输出:让结果落到业务口径
def inverse_transform_predictions(lstm_pred, gpr_mean, gpr_std, scaler): # 反标准化回到 log 空间 lstm_log = scaler.inverse_transform(lstm_pred.reshape(-1, 1)).flatten() # log1p 的反变换 lstm_original = np.expm1(lstm_log) # 残差 GPR 输出本身在原始销量量纲,直接相加 final_mean = lstm_original + gpr_mean final_lower = lstm_original + (gpr_mean - 1.645 * gpr_std) final_upper = lstm_original + (gpr_mean + 1.645 * gpr_std) return final_mean, final_lower, final_upper这里最烦人也最容易出错的是量纲切换。LSTM 的预测值在 log 标准化空间里,inverse_transform只恢复标准化,之后还要expm1才能还原成销量;而 GPR 的均值和方差在训练时就是针对原始残差算的,不需要任何转换。很多人在这一步直接把 LSTM 的输出反标准化相加,导致最终预测值整体偏移,MAPE 高得离谱却找不到原因。预测区间用的是残差 GPR 给的标准差,乘 1.645 得到 90% 置信区间。残差 GPR 的方差只涵盖残差的不确定性,LSTM 参数本身的不确定性没有纳入,所以实际覆盖率会略低于名义水平,这个在第 6 章单独说。
5. 避坑:混合预测框架的 5 个真实翻车现场
5.1 随机打散训练集导致数据泄露
现象:训练集 MAPE 只有 1.5%,验证集却高达 18%,而且误差随着预测月份往后推移越来越大。排查时发现代码里用了train_test_split默认的随机模式,未来月份的销量数据混进了训练集,模型直接记住了验证期的销量均值。
原因:时间序列数据存在时间自相关,随机切分破坏了序列的时间结构,训练时会用到未来信息。
解决:统一改用时间顺序切分,训练集只能是验证集之前的历史数据。推荐扩展窗口交叉验证,代码里训练区间严格在验证区间之前,边界用日期做硬校验,防止索引错位。
5.2 GPR 输入特征维度太高导致协方差矩阵病态
现象:GPR 训练时出现LinAlgError: Matrix is not positive definite,或者训练完成后预测方差全部接近 0。
原因:把 LSTM 的隐层特征或者十几个原始特征直接喂给 GPR,特征维度过高,协方差矩阵在小样本下数值不稳定,Cholesky 分解失败。
解决:GPR 输入严格控制在 2 到 5 个特征,我使用残差序列的滞后 3 阶,必要时加一个月份正弦编码。如果业务上确实需要更多特征,改用稀疏高斯过程近似,比如 SGPR 或 VGP,但复杂度会明显上升,小规模月度预测没必要。
5.3 政策突变月份预测整体滞后
现象:补贴退坡或大范围降价后的第一个月,预测值明显低于实际值,误差方向是系统性低估。LSTM 学到的是历史均值回归,遇到从未出现过的政策力度时,状态更新滞后。
原因:LSTM 本质上是基于历史模式的映射,政策脉冲在训练集中没有足够样本,模型无法外推突变幅度。
解决:在特征矩阵中加入政策哑变量,并让它参与 LSTM 的输入。如果政策发生在预测期之前且训练集中有相似事件,模型能学到一定模式;如果预测期本身就有已知政策变化,直接在特征里把政策哑变量预设为 1,让模型把政策影响纳入预测。这个操作本质上是把结构化先验写进模型,比让模型自己从数据里发现要可靠得多。预测区间在政策月的宽度会变大,这也是 GPR 给出的正常信号,不要把区间收窄到常规水平。
5.4 反变换顺序错误导致量纲漂移
现象:预测结果整体比真实销量低 15% 左右,但趋势形状完全一致。检查代码发现只做了inverse_transform,没有做expm1还原 log 压缩。
原因:训练前对销量做了log1p,预测时只恢复了标准化,log 空间的值被当成原始销量输出。
解决:严格按 4.5 节顺序反变换,先反标准化再expm1。另外归一化的统计量要从训练集计算,验证集和测试集只能用训练集的 scaler 变换,这个顺序写反也会导致量纲漂移。
5.5 贝叶斯优化结果无法复现
现象:同一个搜索空间和数据集跑两次优化,得到的最优超参完全不同,最终 MAPE 差异达到 1.5 个百分点。
原因:数据加载时没有固定排序、模型内部随机种子没有固定、Optuna 的 TPE 采样器没有设种子。销量数据如果按品牌聚合或按时间插入的顺序不同,喂给 LSTM 的序列就不同,优化结果自然漂移。
解决:在脚本开头统一设置np.random.seed、tensorflow.random.set_seed,Optuna 的TPESampler也设seed。数据加载后按日期列显式排序再构造样本。多次实验取最优超参的平均值或按验证误差的稳健性挑选,而不是只挑一次实验的最优点。超参对验证集的误差波动越不敏感,模型上线后的表现越稳定。
6. 进阶:把预测区间当成业务决策信号
单点预测值只能告诉你下个月大概卖多少,业务上真正需要的是下个月销量的下限和上限。库存备货、产能安排、零部件采购,关注的都是“最坏情况能不能兜住”。残差 GPR 给的不确定性区间,是这个混合框架最值钱的部分,但前提是你得先验证区间的可靠性。
区间可靠性用三个指标衡量。第一个是覆盖率,统计验证期内真实销量落在预测区间内的比例,90% 区间理论覆盖率应该在 85% 到 95% 之间;第二个是区间宽度,覆盖率达到目标但区间过宽,意味着模型预测精度不足,业务会被无效信息干扰;第三个是偏差率,检查真实值落在区间上侧和下侧的比例是否接近。如果上下侧偏差率明显不对称,比如真实值长期贴着区间下沿,说明 LSTM 存在系统性高估,需要检查训练集是不是有某些时期的销量虚高。
计算覆盖率可以直接用第 4 章的final_lower和final_upper,逐月判断真实销量是否在区间内,然后取平均。覆盖率低于 80% 时,建议把 1.645 倍标准差扩大到 1.96 倍,也就是 95% 区间,同时要诊断 LSTM 是否欠拟合。区间宽度过大而覆盖率已经达标,说明 GPR 的核函数长度尺度偏大,可以考虑把 Matern 核的长度尺度搜索范围收紧,或者适当增大alpha噪声项,让 GPR 更相信训练数据。
最后一个进阶操作是把点预测和区间输出接到滚动预测流程里。我习惯在每月月初用截止到上月末的历史数据重新训练一次完整模型,输出未来一个月的点预测、区间预测和历史 12 个月的滚动回测误差。如果本月实际销量出来后,落在区间外部,我会记录这个案例,按季度统计哪类月份最容易翻车。这套框架跑下来最大的收获不是 MAPE 从 8% 降到 5%,而是模型在什么场景下靠得住、什么场景下必须人为干预变得可以预判。
我最早做新能源销量预测时也迷信过纯深度学习模型,训练了很大的 LSTM 网络,验证集表现很漂亮,但一到补贴政策变化和价格战就连续踩坑。后来把高斯过程回归接在残差层,用贝叶斯优化算法重新调参,模型的稳定性才真正提上来。混合框架的工程价值并不在于某个单点模型有多强,而是让深度学习的拟合能力和概率模型的可靠性各归其位。希望帮到你。
本文还有配套的精品资源,点击获取