ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python时序预测建模实战:从SARIMA到Prophet的数学建模竞赛解题指南

Python时序预测建模实战:从SARIMA到Prophet的数学建模竞赛解题指南 1. 项目概述与核心问题拆解看到“2022年全国大学生数学建模竞赛E题”这个标题很多参加过数模或者正在准备参赛的同学应该会心一笑或者眉头一紧。这道关于“小批量物料生产安排”的题目可以说是当年让不少队伍“又爱又恨”的一道典型赛题。爱的是它场景非常具体就是工厂里实实在在的生产调度问题不像一些纯理论题那么抽象恨的是它把“生产安排”和“需求预测”这两个硬骨头绑在了一起要求你不仅要会排产还得能预测对综合能力是个不小的考验。我当年带学生队伍时就花了不少时间研究这道题今天就把我们当时的解题思路、核心模型尤其是用Python构建时序预测模型这部分掰开揉碎了讲清楚。无论你是想复盘学习还是为未来的比赛做准备这篇文章都能给你提供一个可直接参考、甚至能“抄作业”的完整框架。简单来说这道题的核心就两件事预测和安排。题目会给你一段时间内工厂对各种小批量物料的历史需求数据。你的第一个任务就是根据这些历史数据预测未来一段时间比如接下来几周每种物料的需求量。光预测出来还不行第二个任务更关键工厂的生产资源机器、人力、时间是有限的你怎么根据预测出的需求以及每种物料的生产成本、库存成本、生产准备时间Setup Time等约束来安排未来一段时间内的生产计划目标通常是使得总成本生产成本库存成本可能的生产切换成本最低或者满足需求的前提下生产效率最高。这本质上是一个需求预测叠加生产调度优化的复合问题。今天这篇我们先集中火力攻克第一个也是最基础的堡垒如何构建一个靠谱的时序预测模型用Python把它实现出来。2. 解题核心思路与模型选型考量面对一道数模题最忌讳的就是拿到数据直接开始敲代码。磨刀不误砍柴工先把思路理清楚后续能省掉大量返工的时间。对于E题这种预测优化的结构我们采用的是经典的“分而治之先预测后优化”的两阶段策略。第一阶段需求预测模型。这是所有后续优化的基础。预测不准后面的生产安排就成了“空中楼阁”计划得再精美也白搭。题目给的需求数据通常是按时间序列排列的比如每日或每周需求量因此我们自然要使用时序预测模型。模型选型是第一个关键决策。为什么不直接用简单的移动平均或者指数平滑因为题目数据往往具有更复杂的模式比如趋势性需求整体在上升或下降、季节性每周、每月的周期性波动以及可能的节假日效应。我们需要一个能捕捉这些特征的模型。当时我们主要评估了以下几种方案经典统计模型ARIMA/SARIMA。这是处理线性时序问题的“老炮”理论扎实解释性强。对于有明显趋势和季节性的数据季节性ARIMA即SARIMA是个不错的选择。它的优势在于参数有明确的统计意义结果稳定。但缺点是对数据平稳性要求高需要提前进行差分等预处理且对于非线性模式捕捉能力有限。机器学习模型Prophet。由Facebook开源的Prophet在处理商业时序数据上非常友好。它内置了趋势、季节性和节假日效应组件几乎可以“开箱即用”对缺失值和异常值也比较稳健。对于数模竞赛这种时间紧、任务重的场景Prophet能快速提供一个不错的基线预测节省大量特征工程时间。机器学习/深度学习模型LSTM。长短期记忆网络是处理时序预测的“网红”模型理论上能捕捉非常复杂的非线性依赖关系。如果数据量足够大模式非常复杂LSTM可能给出最优结果。但它的缺点也很明显需要大量的数据、训练时间长、超参数调优复杂并且像一个“黑盒”结果不易解释。我们的选型思路是在保证预测精度的前提下优先选择稳健、可解释、易实现的模型。考虑到数模竞赛的数据量通常不会特别巨大几百到几千条记录且赛题更看重模型应用的合理性和解决问题的完整性我们最终选择了SARIMA模型为主Prophet模型作为对比和补充验证的方案。这样既能体现我们对传统时序模型的掌握又能展示我们运用新工具的能力形成方法上的对比让论文内容更丰满。LSTM则作为备选如果前两者效果不佳或时间允许可以尝试。第二阶段生产调度优化模型。在获得预测需求后我们需要建立一个优化模型来安排生产。这通常是一个带约束的优化问题决策变量是“在哪个时间点、生产哪种物料、生产多少”。目标函数是最小化总成本约束条件包括生产能力约束机器工时、人力、库存平衡约束本期库存上期库存本期产量-本期需求、物料非负约束等。这部分常用的建模工具是线性规划LP或混合整数线性规划MILP如果涉及生产启动的固定成本可以用Python的PuLP、ortools等库求解。这部分我们留到下一篇详解。明确了“先SARIMA预测后优化调度”的两阶段思路后我们就可以撸起袖子开始第一阶段的实战了。3. 数据预处理与探索性分析任何模型建立之前数据预处理和探索性数据分析都是必不可少的步骤。这一步做得好能帮你避开很多坑甚至发现一些意想不到的规律。假设我们拿到的数据是一个CSV文件demand_data.csv包含以下字段date日期material_id物料编号demand需求量。我们的目标是为每一种物料建立预测模型。3.1 数据加载与初步检查import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from statsmodels.tsa.stattools import adfuller from statsmodels.graphics.tsaplots import plot_acf, plot_pacf import warnings warnings.filterwarnings(ignore) # 加载数据 df pd.read_csv(demand_data.csv) df[date] pd.to_datetime(df[date]) print(df.head()) print(df.info()) print(f数据时间范围{df[date].min()} 到 {df[date].max()}) print(f物料种类数量{df[material_id].nunique()})首先检查数据基本信息有没有缺失值时间范围是否连续物料种类有多少这决定了我们是需要为每一种物料单独建模还是可以聚类后对类别建模。通常对于小批量物料种类多但每种的历史数据可能不多需要谨慎处理。3.2 数据重采样与序列化题目要求可能是周预测或月预测。如果原始数据是日度的我们需要先聚合到周级别。# 假设按周聚合计算每周每种物料的总需求 df.set_index(date, inplaceTrue) # 使用重采样确保时间序列频率一致 weekly_df df.groupby(material_id).resample(W-MON).sum().reset_index() # 注意这里‘demand’被求和了。如果原始就是周数据则无需此步。 # 检查重采样后的数据 print(weekly_df.head())接下来为了便于分析我们选取一种需求量较大或较有代表性的物料将其数据提取成一个单独的时间序列。# 选取一个示例物料比如‘MAT001’ sample_material MAT001 ts_data weekly_df[weekly_df[material_id] sample_material][[date, demand]] ts_data.set_index(date, inplaceTrue) ts_data ts_data.asfreq(W-MON) # 确保频率为每周一 # 处理可能因重采样产生的NaN值某些周可能没有需求 ts_data[demand].fillna(0, inplaceTrue) print(ts_data.head())3.3 时序可视化与模式识别这是关键一步直接决定我们后续选择什么样的模型参数。plt.figure(figsize(14, 6)) plt.plot(ts_data.index, ts_data[demand], markero, linestyle-) plt.title(f物料 {sample_material} 周需求时序图) plt.xlabel(日期) plt.ylabel(需求量) plt.grid(True, whichboth, linestyle--, linewidth0.5) plt.tight_layout() plt.show()通过看图我们需要回答几个问题趋势性整体需求是在上升、下降还是基本平稳季节性是否存在以“年”、“季度”或“月”为周期的规律波动对于周数据可以观察是否存在“一年52周”或“季度13周”的周期。平稳性序列的均值、方差是否随时间变化这是使用ARIMA类模型的重要前提。为了更客观地判断季节性可以绘制年度子图或计算自相关函数。# 绘制年度子图如果数据跨越多年度 ts_data[year] ts_data.index.year ts_data[week_of_year] ts_data.index.isocalendar().week pivot_table ts_data.pivot_table(indexweek_of_year, columnsyear, valuesdemand) plt.figure(figsize(12, 8)) sns.heatmap(pivot_table, cmapYlOrRd, annotFalse) plt.title(f物料 {sample_material} 需求热力图周 vs 年) plt.xlabel(年份) plt.ylabel(一年中的第几周) plt.tight_layout() plt.show()热力图可以直观展示每年相同周次的需求是否相似从而判断年度季节性。3.4 平稳性检验使用Augmented Dickey-Fuller (ADF)检验来判断序列是否平稳。原假设是序列非平稳。result adfuller(ts_data[demand]) print(ADF Statistic: %f % result[0]) print(p-value: %f % result[1]) print(Critical Values:) for key, value in result[4].items(): print(\t%s: %.3f % (key, value)) if result[1] 0.05: print(p值小于0.05拒绝原假设序列是平稳的。) else: print(p值大于0.05无法拒绝原假设序列可能是非平稳的需要进行差分。)如果序列非平稳通常需要进行差分处理。一阶差分是消除趋势的常用方法。# 一阶差分 ts_data[demand_diff1] ts_data[demand].diff().dropna() # 再次检验平稳性 result_diff adfuller(ts_data[demand_diff1].dropna()) print(差分后序列 p-value: %f % result_diff[1])3.5 自相关与偏自相关分析ACF和PACF图是确定ARIMA模型阶数(p, d, q)的重要工具。fig, axes plt.subplots(2, 1, figsize(12, 8)) plot_acf(ts_data[demand].dropna(), lags40, axaxes[0]) axes[0].set_title(f物料 {sample_material} 需求序列自相关函数 (ACF)) plot_pacf(ts_data[demand].dropna(), lags40, axaxes[1]) axes[1].set_title(f物料 {sample_material} 需求序列偏自相关函数 (PACF)) plt.tight_layout() plt.show()解读ACF/PACF图的经验法则确定差分阶数d如果原序列ACF衰减很慢说明非平稳需要差分。差分后序列ACF快速衰减通常在滞后1或2后落入置信区间则d1。确定自回归阶数p观察PACF图。PACF在滞后p阶后“截尾”突然降到置信区间内则p可以取该值。确定移动平均阶数q观察ACF图。ACF在滞后q阶后“截尾”则q可以取该值。季节性模式如果ACF/PACF在滞后周期倍数处如滞后52周处出现显著峰值则表明存在季节性需要考虑季节性ARIMASARIMA模型。注意在实际操作中尤其是数模竞赛中我们往往没有足够的时间对几十上百种物料逐一进行如此细致的分析。一个实用的策略是选取3-5种具有代表性的物料如高需求、中需求、低需求、波动大、波动小进行上述完整分析总结出共性和模型阶数的大致范围。对于其他物料可以采用自动定阶函数如pmdarima.auto_arima来高效确定参数但必须在论文中说明你这样做的理由和代表性物料分析的过程。4. SARIMA模型构建、训练与评估经过EDA我们对数据特征有了了解。假设我们的示例物料MAT001的需求序列存在趋势和年度季节性周期s52周并且通过一阶差分d1后变得平稳。接下来我们构建SARIMA模型。SARIMA模型的阶数表示为(p, d, q) x (P, D, Q, s)其中(p, d, q)是非季节性部分的阶数。(P, D, Q, s)是季节性部分的阶数s是周期长度周数据s52。4.1 手动定阶与模型拟合根据ACF/PACF图我们初步判断非季节性部分可能是低阶的。例如PACF在滞后1阶后截尾可能p1ACF在滞后1阶后截尾可能q1。季节性部分ACF在滞后52处有显著峰值表明需要季节性差分D1和季节性MA项Q可能为1。我们可以从一个简单的模型开始尝试比如SARIMA(1,1,1)(0,1,1,52)。from statsmodels.tsa.statespace.sarimax import SARIMAX import itertools # 划分训练集和测试集例如用最后8周作为测试集 train_size len(ts_data) - 8 train, test ts_data[demand].iloc[:train_size], ts_data[demand].iloc[train_size:] # 定义初始模型参数 order (1, 1, 1) # (p, d, q) seasonal_order (0, 1, 1, 52) # (P, D, Q, s) # 拟合模型 model SARIMAX(train, orderorder, seasonal_orderseasonal_order, enforce_stationarityFalse, enforce_invertibilityFalse) model_fit model.fit(dispFalse) # dispFalse 不显示迭代信息 print(model_fit.summary())查看模型摘要重点关注系数显著性P|z|列值小于0.05通常认为该系数显著。如果不显著可以考虑从模型中移除该成分。模型诊断残差应该近似为白噪声均值为0方差恒定无自相关。可以通过残差图、ACF图以及Ljung-Box检验来诊断。信息准则AICAkaike Information Criterion和BICBayesian Information Criterion用于模型比较值越小越好。4.2 自动定阶与模型选择手动定阶依赖经验且效率低。在竞赛中更常用的方法是使用pmdarima库的auto_arima函数进行自动网格搜索寻找最优参数。它能自动进行差分阶数判断和季节性检测。# 首先安装 pmdarima: pip install pmdarima import pmdarima as pm # 使用 auto_arima 自动寻找最优参数 # 设置 seasonalTrue, m52 (周周期) # 设置 stepwiseTrue 可以加快搜索速度 # traceTrue 打印搜索过程 auto_model pm.auto_arima(train, start_p0, max_p3, start_q0, max_q3, dNone, # 让函数自动检测最优d start_P0, max_P2, start_Q0, max_Q2, DNone, # 让函数自动检测季节性差分阶数 max_D1, m52, # 季节性周期 seasonalTrue, traceTrue, error_actionignore, suppress_warningsTrue, stepwiseTrue) print(auto_model.summary())auto_arima会输出它找到的最优模型参数。通常这个结果比我们手动猜测的更可靠。记下这个最优参数组合(p,d,q)(P,D,Q,s)。4.3 模型诊断与残差分析无论手动还是自动选择的模型都必须进行严格的诊断确保模型是合适的。# 使用auto_arima找到的最优模型进行拟合和诊断 best_order auto_model.order best_seasonal_order auto_model.seasonal_order print(f最优参数: order{best_order}, seasonal_order{best_seasonal_order}) # 用最优参数重新拟合模型为了获取更完整的诊断工具 final_model SARIMAX(train, orderbest_order, seasonal_orderbest_seasonal_order, enforce_stationarityFalse, enforce_invertibilityFalse) final_model_fit final_model.fit(dispFalse) # 绘制诊断图 final_model_fit.plot_diagnostics(figsize(15, 10)) plt.suptitle(f物料 {sample_material} SARIMA{best_order}x{best_seasonal_order} 模型诊断图, y1.02) plt.tight_layout() plt.show()诊断图包含四个子图标准化残差图残差应该围绕0随机波动无明显趋势或周期性。残差直方图核密度估计理想情况下应接近正态分布红线。正态Q-Q图点应大致分布在45度对角线上表示残差符合正态分布。残差自相关图所有滞后阶数的自相关系数都应落在置信区间内蓝色区域表明残差是白噪声没有信息未被模型提取。如果诊断图显示残差非白噪声如ACF图有显著超出置信区间的柱说明模型还有改进空间可能需要增加阶数或考虑其他模型。4.4 模型预测与性能评估用训练好的模型对测试集进行预测并与真实值比较。# 进行预测预测起点为训练集结束预测长度为测试集长度 forecast_steps len(test) # 使用 get_prediction 方法可以获得预测区间 pred_obj final_model_fit.get_prediction(startlen(train), endlen(train)forecast_steps-1, dynamicFalse) pred_mean pred_obj.predicted_mean pred_ci pred_obj.conf_int(alpha0.05) # 95%置信区间 # 将预测结果与测试集对比 plt.figure(figsize(12, 6)) plt.plot(train.index, train, label训练集) plt.plot(test.index, test, label真实值, colororange, markero) plt.plot(pred_mean.index, pred_mean, label预测值, colorgreen, marker^) plt.fill_between(pred_ci.index, pred_ci.iloc[:, 0], pred_ci.iloc[:, 1], colorgreen, alpha0.2, label95%置信区间) plt.title(f物料 {sample_material} 需求预测 vs 真实值) plt.xlabel(日期) plt.ylabel(需求量) plt.legend() plt.grid(True) plt.tight_layout() plt.show() # 计算评估指标 from sklearn.metrics import mean_absolute_error, mean_squared_error, mean_absolute_percentage_error mae mean_absolute_error(test, pred_mean) rmse np.sqrt(mean_squared_error(test, pred_mean)) mape mean_absolute_percentage_error(test, pred_mean) * 100 # 百分比 print(f模型在测试集上的表现) print(f MAE (平均绝对误差): {mae:.2f}) print(f RMSE (均方根误差): {rmse:.2f}) print(f MAPE (平均绝对百分比误差): {mape:.2f}%)评估指标解读MAE直观反映预测误差的平均大小单位与原始数据一致。RMSE对较大误差惩罚更重也是常用指标。MAPE百分比误差便于比较不同量级序列的预测精度。一般来说MAPE 10% 可以认为预测精度较高 20% 可以接受但具体标准因行业而异。实操心得在数模论文中不要只展示一个物料的预测结果。应该展示多个代表性物料的预测效果图和评估指标表以证明你的建模方法具有普适性。对于预测效果特别差的物料要在论文中分析原因如需求过于稀疏、有异常点等并提出可能的改进方案如使用更简单的模型、进行数据平滑等这体现了你分析的全面性和批判性思维。5. 面向全量物料的自动化预测流程竞赛中物料种类繁多我们不可能手动为每一个跑一遍auto_arima。需要构建一个自动化的预测管道。5.1 设计稳健的预测函数这个函数需要能处理各种可能出现的异常情况比如数据量太少、序列全为零、模型拟合不收敛等。def forecast_material(df, material_id, forecast_horizon8, freqW-MON): 为单一物料拟合SARIMA模型并预测未来需求。 参数: df: 包含date, material_id, demand列的DataFrame material_id: 物料ID forecast_horizon: 预测步长例如未来8周 freq: 时间序列频率 返回: forecast_series: 预测值的pandas Series (索引为未来日期) model_aic: 模型的AIC值 error_msg: 错误信息如果成功则为None try: # 1. 提取该物料数据并处理 material_data df[df[material_id] material_id].copy() if len(material_data) 20: # 数据量太少无法有效建模 return None, None, f数据量不足 ({len(material_data)} 条记录) material_data.set_index(date, inplaceTrue) material_data material_data.asfreq(freq) ts material_data[demand].fillna(0) # 填充缺失周为0需求 # 检查序列是否全为零或几乎无变化 if ts.nunique() 1 or ts.std() 1e-5: # 对于恒定序列直接返回恒定值作为预测 future_dates pd.date_range(startts.index[-1] pd.Timedelta(weeks1), periodsforecast_horizon, freqfreq) forecast pd.Series([ts.iloc[-1]] * forecast_horizon, indexfuture_dates) return forecast, None, 恒定序列使用最后值预测 # 2. 划分训练集全部历史数据用于最终建模 train ts # 3. 自动寻找最优SARIMA参数 (使用 stepwise 加快速度) # 设置合理的搜索范围避免过拟合和过长时间 auto_model pm.auto_arima(train, start_p0, max_p2, start_q0, max_q2, dNone, max_d1, start_P0, max_P1, start_Q0, max_Q1, DNone, max_D1, m52, seasonalTrue, traceFalse, error_actionignore, suppress_warningsTrue, stepwiseTrue, n_jobs-1) # 使用所有CPU核心加速 # 4. 用最优参数拟合最终模型 final_model SARIMAX(train, orderauto_model.order, seasonal_orderauto_model.seasonal_order, enforce_stationarityFalse, enforce_invertibilityFalse) final_model_fit final_model.fit(dispFalse) # 5. 进行未来预测 forecast_obj final_model_fit.get_forecast(stepsforecast_horizon) forecast_mean forecast_obj.predicted_mean # forecast_ci forecast_obj.conf_int() # 如果需要置信区间 return forecast_mean, final_model_fit.aic, None except Exception as e: # 捕获任何异常返回错误信息 return None, None, f建模失败: {str(e)}5.2 批量预测与结果汇总循环调用上述函数处理所有物料。# 获取所有物料ID列表 all_materials weekly_df[material_id].unique() print(f开始处理 {len(all_materials)} 种物料...) results [] failed_materials [] for i, mat_id in enumerate(all_materials): if i % 10 0: print(f正在处理第 {i1}/{len(all_materials)} 个物料: {mat_id}) forecast_series, aic, error_msg forecast_material(weekly_df, mat_id, forecast_horizon8) if forecast_series is not None: # 将预测结果转换为字典列表便于后续转换为DataFrame for date, value in forecast_series.items(): results.append({ material_id: mat_id, forecast_date: date, forecast_demand: round(value, 2), # 保留两位小数 model_aic: aic }) else: failed_materials.append({material_id: mat_id, reason: error_msg}) # 将成功预测的结果转换为DataFrame forecast_df pd.DataFrame(results) # 透视表便于查看行是物料列是预测周值是预测需求 forecast_pivot forecast_df.pivot_table(indexmaterial_id, columnsforecast_date, valuesforecast_demand) print(\n预测结果预览前5种物料) print(forecast_pivot.head()) print(f\n成功预测物料数{forecast_pivot.shape[0]}) print(f预测失败物料数{len(failed_materials)}) if failed_materials: print(失败物料及原因) for fm in failed_materials[:5]: # 只显示前5个 print(f {fm[material_id]}: {fm[reason]})5.3 结果后处理与输出对于预测失败的物料我们需要制定后备策略。常见的策略包括简单移动平均使用最近N周的平均值作为未来预测。最后观测值推移直接用最近一周的需求作为未来各周的预测适用于需求稳定的物料。同类物料均值如果物料可以分类使用同类物料预测值的均值。# 处理预测失败的物料 - 示例使用4周移动平均 for fm in failed_materials: mat_id fm[material_id] material_data weekly_df[weekly_df[material_id] mat_id].copy() material_data.set_index(date, inplaceTrue) ts material_data[demand].fillna(0) if len(ts) 4: last_4_weeks_avg ts.iloc[-4:].mean() else: last_4_weeks_avg ts.mean() if len(ts) 0 else 0 # 生成未来8周的预测日期 last_date ts.index[-1] if len(ts) 0 else weekly_df[date].max() future_dates pd.date_range(startlast_date pd.Timedelta(weeks1), periods8, freqW-MON) for date in future_dates: results.append({ material_id: mat_id, forecast_date: date, forecast_demand: round(last_4_weeks_avg, 2), model_aic: None, note: backup_MA4 }) # 最终整合所有预测结果 final_forecast_df pd.DataFrame(results) final_forecast_pivot final_forecast_df.pivot_table(indexmaterial_id, columnsforecast_date, valuesforecast_demand) print(f\n最终预测结果覆盖物料数{final_forecast_pivot.shape[0]}) # 保存预测结果到CSV文件用于下一阶段的生产安排优化 final_forecast_df.to_csv(material_demand_forecasts.csv, indexFalse) final_forecast_pivot.to_csv(material_demand_forecasts_wide.csv) print(预测结果已保存至 CSV 文件。)6. 常见问题、避坑指南与模型优化在实际操作中你肯定会遇到各种各样的问题。下面是我和学生们踩过的一些坑以及对应的解决方案。6.1 数据量不足与稀疏需求问题很多小批量物料的需求历史数据很少或者长期为零偶尔有 spikes尖峰。auto_arima可能无法收敛或者拟合出荒谬的模型。解决方案数据聚合如果物料可以按照产品族、类型进行归类先对类别的总需求进行预测再按历史比例分摊到具体物料。这能有效增加训练数据量。使用更简单的模型对于数据量少于20-30个点的物料放弃复杂的SARIMA。采用简单指数平滑SES、移动平均MA甚至朴素预测法用上一期值作为预测。在我们的自动化函数中已经加入了对此类情况的判断和后备方案。需求转换对于间歇性需求Intermittent Demand可以考虑使用专门的方法如Croston方法但数模竞赛中不常见。一个实用的技巧是将周数据聚合到月数据以减少零值出现的频率。6.2 季节性周期m的选择问题对于周数据季节性周期m应该设多少52一年周数4月度近似还是13季度周数分析与选择m52这是最严格的年度季节性。要求数据至少覆盖2年104周以上否则季节性参数无法可靠估计。如果数据只有一年强制设置m52会导致模型自由度不足可能过拟合或报错。m4或m5近似月度周期一个月约4.33周。适用于月度规律强于年度规律的数据或者数据长度不足一年的情况。m13季度周期。一个季度约13周。最佳实践在auto_arima中可以设置seasonalFalse先不检测季节性观察非季节性模型的效果。如果残差中仍有明显的周期性再尝试不同的m值。也可以通过观察序列的自相关图看在滞后多少期时出现周期性峰值来判断。在数模论文中你应该说明你选择m值的依据比如“通过观察自相关图在滞后52期处发现显著峰值故设定周期m52”。6.3 模型过拟合与评估陷阱问题auto_arima找到了一个AIC很低的复杂模型但在测试集上预测效果很差。原因与对策样本外测试至关重要永远不要只依赖训练集上的拟合优度或AIC来评价模型。必须保留一部分数据作为测试集Hold-out Set用测试集上的MAE、RMSE、MAPE来评估模型泛化能力。我们之前代码中划分了最后8周作为测试集。交叉验证对于时间序列不能使用随机交叉验证而要使用时序交叉验证Time Series Split。pmdarima提供了model_selection.TimeSeriesSplit。这能更稳健地评估模型。from pmdarima.model_selection import TimeSeriesSplit # 使用时序交叉验证评估auto_arima找到的模型参数 cv TimeSeriesSplit(n_splits3) cv_scores [] for train_idx, test_idx in cv.split(train): # 注意这里只用训练集做CV cv_train train.iloc[train_idx] cv_test train.iloc[test_idx] # 用最优参数在cv_train上拟合预测cv_test并计算误差 # ... (代码略)简化模型如果auto_arima返回的模型阶数很高如p, q, P, Q 都大于2可以手动尝试更简单的模型如SARIMA(0,1,1)(0,1,1,52)并在测试集上比较。往往简单的模型更稳健。6.4 预测结果为负数或异常值问题需求预测值出现了负数这在实际中不可能。解决方案模型层面SARIMA模型本身不保证预测值非负。可以对原始数据取对数变换np.log1p在模型预测后再通过指数变换np.expm1还原。这适用于需求值都为正数的情况。如果数据包含零使用np.log1p。ts_log np.log1p(train) # log(1x) 变换 # ... 在ts_log上建模 ... forecast_log model_fit.forecast(steps8) forecast_original np.expm1(forecast_log) # 还原结果后处理更简单粗暴但有效的方法是将负预测直接截断为0。forecast_series[forecast_series 0] 06.5 计算效率与大规模物料处理问题物料有上千种auto_arima逐个运行非常耗时可能无法在比赛时间内完成。优化策略并行计算利用pmdarima的n_jobs-1参数以及Python的multiprocessing或joblib库对物料列表进行并行处理。我们的批量预测示例是串行的在实际竞赛中应改为并行。参数预热不要对每种物料都从零开始搜索全部参数空间。通过对少数代表性物料的分析确定一个大致合理的参数范围(p,d,q)和(P,D,Q)然后在auto_arima中缩小max_p,max_q等范围大幅减少搜索空间。聚类后建模使用聚类算法如K-Means基于需求模式将物料分成几大类。对每一类物料的聚合需求序列建立一个“母模型”然后将这个模型的预测结果按照每个物料在类内的历史需求比例进行分配。这能将模型数量从上千个减少到几个。使用更快的模型对于非重点物料直接使用指数平滑ETS或Prophet。Prophet虽然也有拟合过程但其速度通常比网格搜索的SARIMA要快且效果有保障。7. 备选方案使用Prophet进行快速预测作为对比和补充特别是当SARIMA遇到困难时Prophet是一个非常好的备选工具。它对于缺失值、异常值和季节性变化处理得非常鲁棒且API极其简单。from prophet import Prophet def forecast_with_prophet(df, material_id, forecast_horizon8, freqW): 使用Prophet进行预测 try: material_data df[df[material_id] material_id][[date, demand]].copy() material_data.columns [ds, y] # Prophet要求列名为ds和y # 创建并拟合模型 model Prophet( yearly_seasonalityTrue, # 打开年度季节性 weekly_seasonalityFalse, # 周数据关闭周季节性 daily_seasonalityFalse, seasonality_modeadditive # 或 multiplicative ) model.fit(material_data) # 构建未来时间数据框 future model.make_future_dataframe(periodsforecast_horizon, freqfreq) # 预测 forecast model.predict(future) # 提取预测值yhat列 forecast_values forecast.tail(forecast_horizon)[[ds, yhat]].set_index(ds)[yhat] return forecast_values, None except Exception as e: return None, str(e) # 对示例物料使用Prophet prophet_forecast, error forecast_with_prophet(weekly_df, MAT001, 8, W) if prophet_forecast is not None: print(Prophet 预测结果:) print(prophet_forecast) # 可以同样计算评估指标与SARIMA结果对比在论文中你可以展示SARIMA和Prophet对关键物料的预测结果对比分析各自的优劣这能大大增加论文的深度和广度。至此我们已经完成了2022年数模国赛E题第一大部分——需求预测模型的完整构建。从数据预处理、探索分析到SARIMA模型的原理、实现、自动化批量预测再到常见问题的排查与解决以及备选的Prophet方案形成了一个闭环。拿到预测结果material_demand_forecasts.csv后我们就可以信心满满地进入下一阶段如何利用这些预测需求在复杂的生产约束下制定出成本最低或效率最高的生产计划。那将是另一个充满挑战和趣味的优化世界我们下一篇再续。
返回列表