尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

小批量生产时序预测与排产优化:从Croston方法到LightGBM融合实战

小批量生产时序预测与排产优化:从Croston方法到LightGBM融合实战 1. 赛题回顾与核心挑战解析2022年全国大学生数学建模竞赛的E题聚焦于一个非常贴近工业实际的场景小批量物料的生产安排。这个题目一出来很多队伍的第一反应可能是“这不就是个排产优化问题吗套个遗传算法或者模拟退火不就行了”。但真正上手后才发现远没有那么简单。题目的核心难点恰恰在于“小批量”和“时序预测”这两个关键词的交织。小批量意味着生产数据稀疏、波动大传统基于历史大数据的统计预测方法很容易失灵而生产安排又极度依赖对未来需求的准确判断这就把时序预测模型的精准度推到了至关重要的位置。我当时带队的思路是不能把预测和排产割裂开必须建立一个“预测-排产”联动的闭环分析框架。预测不准排产方案就是空中楼阁排产不合理又会反过来影响对预测误差的评估与修正。本文将深入拆解我们当时解决E题第二问的核心思路重点分享那个被证明非常有效的时序预测模型构建、调参与融合策略并附上可复现的Python代码骨架。无论你是为了备战未来的数模竞赛还是在实际工作中遇到类似的小批量预测难题相信这些从实战中踩坑总结出的经验都能给你带来直接的启发。2. 数据特性分析与小批量预测的特殊性拿到题目数据后千万别急着上模型。第一步也是最重要的一步是静下心来分析数据特征。E题提供的数据通常包括历史订单量、物料种类、生产周期、换线成本等。对于小批量物料其需求时间序列往往呈现以下特征2.1 间歇性与稀疏性这是最显著的特点。某些物料可能长时间无需求然后在某个时间点突然出现一个订单序列中存在大量的零值或接近零的值。直接用ARIMA、Prophet这类经典时序模型会默认数据是连续且相对平滑的结果就是模型会被大量的零值“带偏”预测值趋向于零无法捕捉到突然出现的需求脉冲。2.2 高波动性与无规律性由于订单来自多个客户、多种产品小批量需求往往不具备明显的季节性如月度、季度规律或趋势性。传统的“分解趋势、季节、残差”的思路可能失效。周期图谱分析Periodogram或者直接观察都可能找不到稳定的周期。2.3 外部影响因素多且杂生产安排不仅看历史销量还受促销计划、客户库存策略、甚至供应链突发事件影响。这些因素在题目中可能以文本描述或附加表格的形式给出需要转化为模型可用的特征。我们的应对策略是“分而治之”与“特征工程先行”物料聚类并非所有小批量物料都一样。我们首先根据需求模式如平均需求间隔、需求强度、变异系数对物料进行聚类。对于偶尔有较大波动的“间歇性”物料和长期低量平稳的“平滑性”物料采用不同的预测基模型。创建哑变量与统计特征针对每个预测点如预测未来四周的周需求量我们构造了如下特征滞后特征不仅用前一周、前两周的值更关注“上一次非零需求发生的时间距离现在有多远”、“上一次非零需求的大小是多少”。这对于间歇序列至关重要。滚动统计特征过去N期的均值、标准差、最大值、最小值以及零值占比。用于刻画近期活跃度。时间特征周次、月份、是否季度末等尽管季节性弱但某些宏观节奏可能仍有影响。事件特征从题目描述中提取的关键事件如“客户A计划推出新品”转化为0/1哑变量。注意对于小批量预测简单移动平均SMA或指数平滑ETS常常比复杂模型更稳健。我们初期尝试了LSTM发现极易过拟合在训练集上表现完美在验证集上一塌糊涂。原因就是数据太稀疏神经网络学到的可能是噪声而不是规律。3. 核心时序预测模型选型与融合实战基于以上分析我们放弃了寻找一个“银弹”模型的想法转而采用**多模型融合Ensemble**的策略。核心思想是让不同的模型去捕捉序列不同方面的特性然后加权汇总以降低单一模型的风险。3.1 基模型一Croston方法及其优化变种这是处理间歇性需求Intermittent Demand的经典方法。它分别预测需求发生的间隔间隔时间和需求发生时的规模需求大小。原始Croston方法假设两者均服从随机游走预测较为简单。# 简化的Croston方法实现思路 import pandas as pd import numpy as np def croston_forecast(ts, extra_holidayNone): ts: 需求时间序列Pandas Series索引为日期 extra_holiday: 额外的事件特征如促销期 # 1. 分离需求间隔和需求规模 demand_occurrences (ts 0).astype(int) # 需求发生为1未发生为0 demand_sizes ts[ts 0] # 非零需求值 # 2. 分别对两个序列进行指数平滑 (这里用简单平均示意) # 实际应用应使用SES或更稳健的平滑方法 if len(demand_sizes) 0: avg_interval demand_occurrences.rolling(windowmin(10, len(demand_occurrences)), min_periods1).mean().iloc[-1] avg_size demand_sizes.rolling(windowmin(5, len(demand_sizes)), min_periods1).mean().iloc[-1] # 考虑事件特征影响 if extra_holiday is not None and extra_holiday.iloc[-1] 1: avg_size * 1.2 # 假设促销使需求规模增加20% forecast avg_size / avg_interval if avg_interval 0 else 0 else: forecast 0 return forecast然而原始Croston在有趋势或季节性的序列上表现不佳。我们采用了TSB (Teunter, Syntetos, Babai)方法它直接预测需求概率和需求水平理论上更优。在实际编码中我们重点调整了平滑系数对于非常稀疏的物料我们赋予需求概率预测更高的权重。3.2 基模型二基于LightGBM的时序特征回归模型这是我们将机器学习引入传统时序预测的关键。我们不将数据视为严格的时间序列而是将其转化为一个监督学习回归问题。对于每一个待预测的时点t我们利用t时刻之前的历史信息构造一个特征向量来预测t时刻的需求值。import lightgbm as lgb from sklearn.model_selection import TimeSeriesSplit def create_lgb_features(df, max_lag8): 为LightGBM创建特征 df df.copy() # 滞后特征 for lag in range(1, max_lag1): df[flag_{lag}] df[demand].shift(lag) # 滚动统计特征 df[rolling_mean_4] df[demand].shift(1).rolling(4, min_periods1).mean() df[rolling_std_4] df[demand].shift(1).rolling(4, min_periods1).std() df[days_since_last_demand] (df[demand] 0).astype(int).shift(1).rolling(100, min_periods1).apply(lambda x: len(x) - np.argmax(x[::-1]) - 1 if np.any(x) else len(x)) # 加入外部特征如事件哑变量 # df[is_promotion] ... df.dropna(inplaceTrue) # 滞后操作产生NaN需要丢弃 return df # 使用时序交叉验证训练防止数据泄露 tscv TimeSeriesSplit(n_splits5) lgb_model lgb.LGBMRegressor(objectiveregression_l1, # 使用MAE损失对异常值更稳健 num_leaves31, learning_rate0.05, n_estimators200) for train_idx, val_idx in tscv.split(X): X_train, X_val X.iloc[train_idx], X.iloc[val_idx] y_train, y_val y.iloc[train_idx], y.iloc[val_idx] lgb_model.fit(X_train, y_train, eval_set[(X_val, y_val)], callbacks[lgb.early_stopping(50)])LightGBM的优势在于能自动处理特征交互并且对零值不敏感。我们将Croston方法的预测结果也作为一个特征croston_forecast加入LightGBM让模型自己决定在什么情况下该相信Croston的预测。3.3 基模型三简单但稳健的移动平均与历史同期法作为模型的“压舱石”我们始终保留了两个简单模型加权移动平均WMA给予近期数据更高权重。历史同期均值如果数据有哪怕一丝微弱的周期迹象比如月度就使用去年同期、上月同期的平均值作为参考。3.4 模型融合策略动态加权平均我们不是简单地对三个模型的预测结果取平均而是根据物料近期的预测表现动态分配权重。具体来说我们保留一个滚动评估窗口例如最近8周每周都用各模型预测该周需求并与实际值比较计算每个模型在窗口内的平均绝对误差MAE。预测时权重与MAE成反比误差越小权重越高。def dynamic_weighted_average(predictions_dict, material_id, past_performance): predictions_dict: 字典键为模型名值为该物料的预测值 past_performance: DataFrame记录各物料各模型在最近N期的MAE weights {} for model_name, pred in predictions_dict.items(): # 获取该物料该模型近期的MAE recent_mae past_performance.loc[material_id, model_name].mean() # 权重为MAE的倒数并归一化 weights[model_name] 1.0 / (recent_mae 1e-5) # 加一个小数避免除零 total_weight sum(weights.values()) final_forecast 0 for model_name, pred in predictions_dict.items(): final_forecast pred * (weights[model_name] / total_weight) return final_forecast这套融合机制的优势在于自适应对于近期Croston方法预测很准的物料它会占据主导对于近期LightGBM表现更好的物料机器学习模型的权重会上升。这比固定权重融合更能适应不同物料的需求模式变化。4. 预测结果如何驱动生产排产模型预测不是终点而是排产优化的输入。E题的第二问通常要求基于预测制定具体到每天、每台机器的生产计划。这里的关键是将预测的不确定性纳入排产决策。4.1 从点预测到区间预测我们不仅给出未来每周需求的“最可能值”点预测还利用LightGBM或统计方法如分位数回归给出了80%的预测区间例如预测值是100区间可能是[70, 130]。这对生产安排至关重要。4.2 构建考虑预测误差的排产优化模型我们以最小化总成本生产成本库存持有成本缺货惩罚成本换线成本为目标建立混合整数规划MIP模型。其中需求参数不是一个固定值而是一个范围。我们采用了鲁棒优化Robust Optimization或情景规划Scenario Planning的思想。鲁棒优化假设需求会在预测区间内波动我们优化“最坏情况”下的成本。这会使计划偏保守但能抵御风险。情景规划我们基于预测区间生成几种可能的需求情景如悲观、中性、乐观然后优化这些情景下的期望总成本。这更贴近实际决策。在建模时换线成本Setup Cost是必须重点考虑的。小批量生产换线频繁换线成本可能占总成本很大比例。我们的决策变量包括X[m,t]物料m在t期是否生产0/1变量和Y[m,t]物料m在t期的生产量。目标函数中包含了换线成本项C_setup * X[m,t]。约束条件则包括产能约束、库存平衡约束本期库存 上期库存 本期生产 - 本期需求以及满足服务水平的约束如“缺货概率不超过5%”。4.3 使用Python (PuLP/ortools) 求解排产模型由于是离散优化问题我们使用了PuLP适合中小规模问题或Google OR-Tools功能更强大进行求解。from pulp import LpProblem, LpVariable, lpSum, LpMinimize, LpStatus, value def production_scheduling(forecast_dict, capacity, setup_cost, holding_cost, shortage_penalty): forecast_dict: 物料-[预测值 预测下限 预测上限] prob LpProblem(Production_Scheduling, LpMinimize) materials list(forecast_dict.keys()) periods range(1, 5) # 假设排4周 # 定义变量 X LpVariable.dicts(Produce, (materials, periods), lowBound0, catBinary) # 是否生产 Y LpVariable.dicts(Quantity, (materials, periods), lowBound0, catContinuous) # 生产量 I LpVariable.dicts(Inventory, (materials, periods), lowBound0, catContinuous) # 库存 S LpVariable.dicts(Shortage, (materials, periods), lowBound0, catContinuous) # 缺货量 # 目标函数最小化总成本 prob lpSum([setup_cost[m] * X[m][t] for m in materials for t in periods]) \ lpSum([holding_cost[m] * I[m][t] for m in materials for t in periods]) \ lpSum([shortage_penalty[m] * S[m][t] for m in materials for t in periods]) # 约束条件 for m in materials: for t in periods: # 产能约束简化假设所有物料共享产能 if t 1: prob lpSum([Y[m][t] for m in materials]) capacity[t] # 库存平衡约束 (使用预测区间的中值作为“名义需求”) demand forecast_dict[m][t-1][0] # 取点预测值 if t 1: prob I[m][t] Y[m][t] - demand S[m][t] # 假设期初库存为0 else: prob I[m][t] I[m][t-1] Y[m][t] - demand S[m][t] # 逻辑约束如果生产量大于0则生产标志必须为1 prob Y[m][t] 10000 * X[m][t] # 大M法10000是一个足够大的数 prob.solve() print(fStatus: {LpStatus[prob.status]}) print(fTotal Cost: {value(prob.objective)}) # 提取结果 schedule {} for m in materials: schedule[m] {t: (value(X[m][t]), value(Y[m][t])) for t in periods} return schedule这个模型框架可以根据题目具体条件进行扩展例如考虑多阶段生产、机器专属约束等。5. 模型评估、调参与避坑指南在竞赛和实际应用中模型建好后评估与调参是决定成败的最后一步。5.1 面向小批量预测的评估指标不要只看均方根误差RMSERMSE对大的误差惩罚很重但在小批量场景下一次大的预测失误比如预测0实际100就会让RMSE变得很难看且无法区分是“该预测时没预测到”还是“预测量级不对”。我们主要看三个指标平均绝对百分比误差MAPE虽然它在真实值为零时无穷大不完美但计算时我们通常将零需求周期排除在外用它来衡量非零需求点的预测相对误差。平均绝对误差MAE更稳健直接衡量平均偏差大小。预测区间覆盖率PIC检查实际值落在我们给出的80%预测区间内的比例是否真的接近80%。这衡量了区间预测的校准程度。5.2 调参实战心得Croston/TSB模型核心是平滑系数α需求概率和β需求规模。我们的经验是对于需求间隔非常不稳定的物料α应设小如0.1-0.2让模型更依赖长期平均间隔避免被偶然的短间隔干扰对于需求规模波动大的物料β应设小如0.1-0.3平滑掉极端值的影响。LightGBMnum_leaves这是控制模型复杂度的关键参数。小批量数据容易过拟合不要设置太大我们从31开始尝试如果验证集误差不降反升就降低到15或10。min_data_in_leaf同样为防止过拟合我们设置得相对较大比如10或20确保每个叶子节点有足够多样的样本。feature_fraction/bagging_fraction使用特征采样和数据采样进一步增加模型多样性提升融合效果。最重要的技巧使用时序交叉验证TimeSeriesSplit绝对不能用普通的随机K折交叉验证那会造成严重的未来信息泄露导致评估结果过于乐观。5.3 我们踩过的坑与解决方案坑1忽略零值区间导致库存积压。初期模型对连续零需求预测不准总是给出很小的正数预测。这导致排产模型安排了不必要的生产产生库存。解决方案在特征工程中显式加入“已连续零需求的周数”特征并在训练LightGBM时对零值样本适当降低权重让模型学会识别“真零”和“临时零”。坑2模型融合权重更新太慢。固定权重或基于全部历史计算的权重无法快速响应物料需求模式的突变。解决方案采用前述的滚动窗口动态权重窗口长度根据数据频率调整如8-12周让融合系统具备自适应能力。坑3排产模型求解时间过长。当物料和周期数较多时MIP模型可能求解缓慢影响比赛进程。解决方案先使用线性规划LP松弛求解观察哪些X[m,t]变量接近1或0将其固定再求解剩余的MIP问题可大幅加速。使用启发式算法如遗传算法或模拟退火快速获得一个可行且质量不错的解。在数模竞赛中一个“良好”的可行解比一个“最优”但未求完的解得分更高。利用ortools的CP-SAT求解器它对这类调度问题通常比传统的MIP求解器更快。6. 代码结构组织与复现建议为了让整个解决方案清晰、可复现我们当时的代码结构如下强烈建议你按此组织2022_MCM_E/ ├── data/ │ ├── raw/ # 原始赛题数据 │ └── processed/ # 清洗、特征工程后的数据 ├── src/ │ ├── 01_data_preprocessing.py # 数据清洗、物料聚类 │ ├── 02_feature_engineering.py # 构造滞后、滚动、事件特征 │ ├── 03_croston_model.py # Croston/TSB模型实现 │ ├── 04_lgb_model.py # LightGBM模型训练与预测 │ ├── 05_ensemble_forecast.py # 动态加权融合 │ ├── 06_production_scheduling.py # 排产优化模型 (PuLP/ortools) │ └── 07_evaluation.py # 模型评估与可视化 ├── config.yaml # 所有参数模型参数、路径等 ├── requirements.txt # 依赖包列表 └── main_pipeline.py # 主运行脚本串联整个流程在main_pipeline.py中流程清晰可见# 伪代码示意 import yaml from src import * config load_config(config.yaml) # 1. 数据准备 df preprocess_data(data/raw/input.csv) # 2. 特征工程 df_features create_all_features(df, config[lags], config[windows]) # 3. 模型训练与预测 croston_preds train_croston(df_features) lgb_preds train_lgbm(df_features, config[lgb_params]) # 4. 融合 final_forecasts, prediction_intervals dynamic_ensemble(croston_preds, lgb_preds, df_features) # 5. 排产 schedule, total_cost run_scheduling_optimization(final_forecasts, config[cost_params]) # 6. 评估与输出 evaluate_model(final_forecasts, actuals) export_results(schedule, final_forecasts, results/output.xlsx)这种模块化的设计不仅便于调试也让论文中的“模型”部分有清晰的对应代码增强了解决方案的可信度。最后想说的是数学建模竞赛从来不是追求理论上最完美的模型而是在有限时间内构建一个贴合题目背景、逻辑自洽、稳定可解释、并能得出有价值结论的解决方案。对于E题这类生产调度问题将时序预测的 uncertainty不确定性通过鲁棒优化或情景分析融入到排产决策中是区别于普通排产模型、能体现思考深度的关键。我们的方案未必是最优的但它完整地走通了“数据理解 - 特征构建 - 模型选型与融合 - 优化决策 - 评估反馈”这个闭环并且在每个环节都针对“小批量”这一核心难点做了针对性处理。希望这份详细的复盘能帮助你在遇到类似问题时有一个扎实的起点和清晰的攻关思路。
返回列表