时间序列预测实战:融合SARIMA与LightGBM的汽后配件需求预测方案
2026/8/22 5:45:14 网站建设 项目流程

1. 项目概述与核心价值

最近刚带着团队做完2024年长三角高校数学建模竞赛C题的辅导,这道“汽后配件需求预测”的题目,可以说是把时间序列预测、机器学习以及业务理解这几个坑都踩了一遍。题目给的是某汽后配件公司连续四年的日度需求数据,要求预测未来一段时间内各个配件的需求量。这活儿听起来像是经典的销量预测,但真上手才发现,里头门道不少:数据有明显的季节性波动,还有节假日、促销活动这些外部因素的干扰,单纯套个ARIMA或者LSTM模型,结果肯定好不了。

这道题的价值,远不止是拿个奖。它本质上是一个高度贴近工业界真实场景的预测问题。在汽车后市场,配件需求的准确预测直接关系到库存周转率、资金占用和客户满意度。预测高了,库存积压,现金流紧张;预测低了,缺货导致客户流失,甚至影响维修厂的正常运营。所以,做好这个预测,需要的是一个融合了统计方法、机器学习技术和业务洞察的系统性解决方案。接下来,我就把我们在解题过程中形成的完整思路、核心代码(Matlab和Python双版本)以及那些容易踩坑的细节,毫无保留地拆解一遍。无论你是参赛学生,还是对预测算法感兴趣的从业者,这份实战记录应该都能给你带来直接的参考。

2. 解题核心思路与整体设计

面对一个时间序列预测问题,尤其是带有明显业务背景的赛题,最忌讳的就是拿到数据直接跑模型。我们的整体思路遵循一个从“理解数据”到“模型融合”的递进式 pipeline。

2.1 问题拆解与建模目标定义

首先,我们必须明确题目到底要我们干什么。题目提供了多个配件(SKU)的历史日需求数据,要求预测未来一段时间(例如接下来30天)的日需求量。这本质上是一个多变量时间序列预测问题。但“多变量”在这里有两层含义:

  1. 多个时间序列:我们需要同时对多个独立配件的需求序列进行预测。虽然配件之间可能存在弱关联(例如,刹车片和刹车盘的更换有时会同步),但题目通常默认或暗示它们相互独立,这简化了问题,允许我们采用“单变量模型,批量处理”的策略。
  2. 单序列多特征:对于每一个配件的需求序列,我们不仅要利用其自身的历史值,还要考虑可能影响它的外部特征,如星期几、是否节假日、月份、是否有促销活动等。这要求我们的模型能够融入这些特征。

因此,建模的最终目标是:为每个配件,构建一个能够利用其历史需求数据和已知的未来外部特征信息,来预测未来每日需求量的模型。

2.2 技术路线选型与对比

确定了目标,接下来就是选择技术路线。我们评估了三种主流方向,并最终形成了一个融合方案。

方案一:经典统计时间序列模型(如 ARIMA, SARIMA)

  • 核心思想:认为序列的未来值是其过去值和过去误差的线性组合。SARIMA 特别考虑了季节性。
  • 优点:理论成熟,可解释性强,对于线性、稳定的序列效果很好。Matlab 的arimaregARIMA工具箱,Python 的statsmodels库都能方便实现。
  • 缺点:对数据平稳性要求高,需要人工进行差分、季节差分等预处理。处理外部特征(协变量)相对麻烦(需用regARIMA),且难以捕捉复杂的非线性关系。对于多个序列需要分别建模,自动化批量处理有一定工作量。
  • 我们的判断:适合作为基线模型(Baseline),用于快速验证和捕捉序列的主要线性趋势与季节规律。

方案二:机器学习回归模型(如 XGBoost, LightGBM)

  • 核心思想:将时间序列预测转化为监督学习问题。通过构建特征(如滞后特征lag1,lag7,lag30,滚动统计特征rolling_mean_7,以及外部特征),用树模型进行回归预测。
  • 优点:能够非常方便地融入大量外部特征,对非线性关系捕捉能力强,模型训练速度快。XGBoost/LightGBM 自带正则化,抗过拟合能力较好。
  • 缺点:模型本质上是“静态”的,它学习的是特征与目标值之间的映射关系,对纯粹的时间依赖关系(尤其是长期依赖)的建模能力弱于序列模型。特征工程的质量极大影响结果。
  • 我们的判断:非常适合处理含有丰富外部特征的时间序列预测问题,在本赛题中潜力巨大。

方案三:深度学习序列模型(如 LSTM, GRU, Transformer)

  • 核心思想:利用循环神经网络(RNN)或注意力机制,显式地建模序列中长距离的时序依赖关系。
  • 优点:能够自动学习复杂的时序模式,理论上建模能力最强。对于波动大、模式复杂的序列可能表现出色。
  • 缺点:需要大量的数据,训练时间长,超参数调优复杂,模型可解释性差,容易过拟合。对于本赛题4年的日数据(约1460个点),数据量对于深度模型来说并不算非常充裕。
  • 我们的判断:可以作为进阶尝试,但不应作为首选。在有限的时间和算力下,其稳定性可能不如前两者。

我们的最终融合策略

  1. 第一层:基线模型。使用SARIMA为每个序列建立一个基准,它的结果代表了“纯时序”的预测能力。
  2. 第二层:主力模型。使用LightGBM进行建模。我们会精心构建三类特征:时序滞后特征、滚动窗口统计特征、外部日历特征。这是我们的核心预测引擎。
  3. 第三层:模型融合。将 SARIMA 的预测结果和 LightGBM 的预测结果进行加权平均(如 0.2 * SARIMA + 0.8 * LightGBM),或者将 SARIMA 的预测值也作为一个特征输入给 LightGBM 进行二次训练。融合可以平滑单一模型的误差,提升鲁棒性。
  4. 第四层:后处理。根据业务常识进行修正,例如预测值不应为负数,对于某些配件可能存在最小包装量(如预测12.3个,但实际按整盒13个销售),需要进行取整处理。

这个策略兼顾了可解释性、强大特征处理能力和稳定性,在实际比赛中被证明是有效的。

3. 数据预处理与特征工程详解

数据和特征决定了模型性能的上限。这一步至关重要,且包含大量细节。

3.1 数据清洗与探索性分析(EDA)

拿到数据后,第一件事不是跑代码,而是用眼睛看。

  • 缺失值处理:检查是否有缺失的日期或需求值。对于个别缺失,可以用前向填充、线性插值或该日期的历史均值填充。对于连续大片缺失,需要结合业务判断(如门店停业),可能需要进行标注或使用更复杂的方法。
  • 异常值检测与处理:通过箱线图、3-sigma 法则查看异常值。异常值可能是数据错误(如多打一个0),也可能是真实的促销爆发。不能简单删除!对于疑似错误的,可以用前后数据的均值或中位数修正;对于真实的促销峰值,最好将其单独标记为一个“促销事件”特征,让模型去学习,而不是平滑掉它。
  • 可视化分析:绘制每个配件的时间序列图。观察:
    • 趋势:是平稳、上升还是下降?
    • 季节性:是否有以周、月、年为周期的波动?用statsmodels.tsa.seasonal_decompose可以分解趋势、季节和残差项,一目了然。
    • 节假日效应:在国庆、春节等日期附近,需求是骤降还是激增?
    • 方差稳定性:序列的波动幅度是否随时间变化?如果变化大,可能需要对数变换。

实操心得:EDA阶段花的时间越多,后面建模就越顺。我们曾发现某个配件在每年8月都有一次需求骤降,后来了解到是该配件供应商的固定检修期,这直接催生了一个“供应链检修月”的哑变量特征。

3.2 核心特征工程构建

这是LightGBM模型成功的核心。我们将特征分为以下几类:

1. 时间滞后特征 (Lag Features)这是最直接的特征,告诉模型“昨天卖了多少”、“上周同一天卖了多少”。

# Python 示例 df['lag_1'] = df['demand'].shift(1) # 前1天 df['lag_7'] = df['demand'].shift(7) # 前1周(捕捉周周期) df['lag_30'] = df['demand'].shift(30) # 前1月(捕捉月周期) # 可以构建一个滞后特征列表,如 [1,2,3,7,14,21,30,60,90]

注意:构建滞后特征会导致前N行出现NaN,需要在训练前丢弃或填充。

2. 滚动窗口统计特征 (Rolling Window Statistics)描述近期序列的统计特性,反映趋势和波动。

df['rolling_mean_7'] = df['demand'].rolling(window=7, min_periods=1).mean() df['rolling_std_7'] = df['demand'].rolling(window=7, min_periods=1).std() df['rolling_max_14'] = df['demand'].rolling(window=14, min_periods=1).max() # 还可以计算滚动中位数、分位数等,对异常值更鲁棒

3. 扩展窗口统计特征 (Expanding Window Statistics)描述从序列开始到当前点的全局统计特性。

df['expanding_mean'] = df['demand'].expanding().mean()

4. 日历特征 (Calendar Features)将日期信息转化为模型可理解的维度。

df['year'] = df['date'].dt.year df['month'] = df['date'].dt.month # 1-12 df['day_of_month'] = df['date'].dt.day # 1-31 df['day_of_week'] = df['date'].dt.dayofweek # 周一=0, 周日=6 df['is_weekend'] = df['day_of_week'].apply(lambda x: 1 if x >=5 else 0) df['quarter'] = df['date'].dt.quarter df['week_of_year'] = df['date'].dt.isocalendar().week # 是否为月初/月末/季初/季末 df['is_month_start'] = df['date'].dt.is_month_start.astype(int) df['is_month_end'] = df['date'].dt.is_month_end.astype(int)

5. 节假日与事件特征这是提升预测精度的关键。需要一份节假日日历。

# 假设有一个holiday_list列表 df['is_holiday'] = df['date'].isin(holiday_list).astype(int) # 节假日前后几天也可能受影响 df['days_to_holiday'] = ... # 计算距离最近节假日的天数 # 标记已知的促销活动日 df['is_promotion'] = ... # 根据业务数据标记

6. 目标编码特征 (Target Encoding)对于类别型日历特征,如“星期几”,可以用历史同期(如过去4周同星期几)的平均需求来编码,比单纯的one-hot编码信息量更大。

# 计算“星期几”的历史平均需求(使用滚动窗口,避免数据泄露) df['dow_avg'] = df.groupby('day_of_week')['demand'].transform(lambda x: x.rolling(30, min_periods=1).mean().shift(1))

注意事项:特征工程中最致命的错误是数据泄露(Data Leakage)。任何使用了未来信息(哪怕是统计信息)的特征都会导致模型在训练时“作弊”,从而在真实预测中表现极差。确保所有滚动统计、目标编码等操作都严格使用历史信息(通过.shift().rolling().apply()时小心窗口方向)。

4. 模型实现与核心代码解析

我们将分别展示 Matlab 和 Python 的核心实现代码。这里以 LightGBM 作为主力模型进行示例。

4.1 Python 实现 (基于 LightGBM + Scikit-learn)

import pandas as pd import numpy as np from sklearn.model_selection import TimeSeriesSplit from sklearn.metrics import mean_absolute_error, mean_squared_error import lightgbm as lgb import warnings warnings.filterwarnings('ignore') # 1. 数据加载与基础预处理 def load_and_preprocess_data(filepath): df = pd.read_csv(filepath, parse_dates=['date']) df.sort_values('date', inplace=True) df.reset_index(drop=True, inplace=True) # 假设数据已按配件ID分组,这里以单个配件为例 return df # 2. 特征工程函数 def create_features(df): df = df.copy() # 滞后特征 lags = [1, 2, 3, 7, 14, 30] for lag in lags: df[f'lag_{lag}'] = df['demand'].shift(lag) # 滚动窗口特征 df['rolling_mean_7'] = df['demand'].rolling(window=7, min_periods=1).mean().shift(1) df['rolling_std_7'] = df['demand'].rolling(window=7, min_periods=1).std().shift(1) df['rolling_max_14'] = df['demand'].rolling(window=14, min_periods=1).max().shift(1) # 日历特征 df['year'] = df['date'].dt.year df['month'] = df['date'].dt.month df['day_of_week'] = df['date'].dt.dayofweek df['is_weekend'] = (df['day_of_week'] >= 5).astype(int) df['day_of_month'] = df['date'].dt.day df['week_of_year'] = df['date'].dt.isocalendar().week # 目标编码:星期几的历史平均需求(使用滚动平均,防止泄露) df['dow_avg'] = df.groupby('day_of_week')['demand'].transform( lambda x: x.rolling(30, min_periods=1).mean().shift(1) ) # 节假日特征(需要外部日历,此处用示例) holiday_dates = [] # 这里应填入实际的节假日日期列表 df['is_holiday'] = df['date'].isin(holiday_dates).astype(int) # 丢弃因创建特征产生的NaN行 df.dropna(inplace=True) return df # 3. 训练与预测函数 def train_and_predict_lgb(train_df, forecast_horizon=30, future_calendar_df=None): """ train_df: 包含特征和‘demand’标签的完整训练DataFrame forecast_horizon: 需要预测的未来天数 future_calendar_df: 未来日期的日历DataFrame(用于预测期特征构建) """ # 划分特征和标签 feature_cols = [col for col in train_df.columns if col not in ['date', 'demand', 'sku_id']] X = train_df[feature_cols] y = train_df['demand'] # 使用时序交叉验证 tscv = TimeSeriesSplit(n_splits=5) models = [] scores = [] 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] # 定义LightGBM数据集 train_data = lgb.Dataset(X_train, label=y_train) val_data = lgb.Dataset(X_val, label=y_val, reference=train_data) # 参数设置(需根据实际情况调整) params = { 'objective': 'regression', 'metric': 'mae', 'boosting_type': 'gbdt', 'num_leaves': 31, 'learning_rate': 0.05, 'feature_fraction': 0.9, 'bagging_fraction': 0.8, 'bagging_freq': 5, 'verbose': -1, 'seed': 42 } # 训练模型 gbm = lgb.train(params, train_data, valid_sets=[val_data], num_boost_round=1000, callbacks=[lgb.early_stopping(stopping_rounds=50)]) models.append(gbm) # 验证 val_pred = gbm.predict(X_val, num_iteration=gbm.best_iteration) score = mean_absolute_error(y_val, val_pred) scores.append(score) print(f'Fold MAE: {score:.4f}') print(f'Average CV MAE: {np.mean(scores):.4f}') # 使用全部数据重新训练最终模型 final_model = lgb.train(params, lgb.Dataset(X, label=y), num_boost_round=int(np.mean([m.best_iteration for m in models]))) # --- 预测未来 --- # 这是关键且容易出错的步骤 predictions = [] last_known_data = train_df.iloc[-1:].copy() # 取最后一行已知数据 for i in range(forecast_horizon): # 1. 创建未来一行的基础DataFrame future_row = pd.DataFrame(index=[0]) # 2. 填充已知的未来日历特征(从future_calendar_df获取) # 例如:future_row['year'] = future_calendar_df.iloc[i]['year'] # future_row['month'] = ... # future_row['is_holiday'] = ... # 3. 填充滞后特征:需要使用我们刚刚预测出的值! if i == 0: future_row['lag_1'] = last_known_data['demand'].values[0] future_row['lag_7'] = train_df.iloc[-7]['demand'] if len(train_df) >= 7 else np.nan else: # 对于i>0, lag_1 就是上一步的预测值 future_row['lag_1'] = predictions[-1] # lag_7 需要回溯,如果回溯到历史数据区间就用真实值,否则用预测值 if i >= 7: future_row['lag_7'] = predictions[-7] else: future_row['lag_7'] = train_df.iloc[-7 + i]['demand'] if len(train_df) >= (7-i) else np.nan # 同理填充其他滞后特征... # 4. 填充滚动统计特征:需要基于一个不断扩展的“历史”序列重新计算 # 这是一个递归过程,较为复杂。简化处理:可以用训练集末期的统计量近似,或使用预测值动态构建一个临时序列来计算。 # 简化版:使用训练集最后窗口的统计量(假设短期内外推变化不大) future_row['rolling_mean_7'] = last_known_data['rolling_mean_7'].values[0] # 更严谨的做法需要动态更新,此处代码较长,省略细节。 # 5. 确保特征顺序与训练时一致,并处理可能的NaN future_row = future_row[feature_cols] future_row = future_row.fillna(method='ffill').fillna(0) # 简单填充处理 # 6. 进行单步预测 pred = final_model.predict(future_row)[0] # 7. 业务后处理:非负,可取整 pred = max(0, pred) # pred = round(pred) # 如果需要按整数配件预测 predictions.append(pred) # 8. 更新“最后已知数据”,用于下一步的滞后特征计算(模拟递归过程) # 在实际代码中,可能需要维护一个不断增长的预测序列来更新滚动特征。 return final_model, predictions, np.mean(scores) # 主程序流程示例 if __name__ == '__main__': # 加载数据 df_raw = load_and_preprocess_data('part_demand_data.csv') # 创建特征 df_featured = create_features(df_raw) # 获取未来30天的日历信息(需要提前生成) future_dates = pd.date_range(start=df_raw['date'].max() + pd.Timedelta(days=1), periods=30, freq='D') future_calendar = pd.DataFrame({'date': future_dates}) # 同样需要对future_calendar进行日历特征编码(不含滞后和滚动特征) # ... # 训练与预测 model, preds, avg_mae = train_and_predict_lgb(df_featured, forecast_horizon=30, future_calendar_df=future_calendar) print(f"未来30天预测值: {preds}")

4.2 Matlab 实现 (基于回归模型与时序工具箱)

Matlab 在统计时序建模方面有天然优势,对于机器学习,可以使用 Statistics and Machine Learning Toolbox 中的回归树、集成模型,或第三方工具箱。

%% 主脚本:汽后配件需求预测 - Matlab实现 clear; clc; close all; %% 1. 数据导入与预处理 data = readtable('part_demand_data.csv'); data.date = datetime(data.date, 'InputFormat', 'yyyy-MM-dd'); data = sortrows(data, 'date'); % 假设数据已按配件分组,这里分析第一个配件 sku_id = unique(data.sku_id); target_sku = sku_id(1); idx = data.sku_id == target_sku; ts_data = data(idx, :); %% 2. 探索性数据分析 (EDA) figure; plot(ts_data.date, ts_data.demand); xlabel('Date'); ylabel('Demand'); title(['Demand Time Series for SKU: ', num2str(target_sku)]); grid on; % 季节性分解 (假设周期为7天) T = 7; % 周期长度 demand_ts = ts_data.demand; % 使用移动平均进行简单分解 trend = movmean(demand_ts, [T-1, 0], 'Endpoints', 'discard'); % 趋势项 detrended = demand_ts(1:length(trend)) - trend; % 去趋势后序列 seasonal = zeros(T,1); for i = 1:T seasonal(i) = mean(detrended(i:T:end)); % 计算周期内各点的平均值 end seasonal = repmat(seasonal, ceil(length(detrended)/T), 1); seasonal = seasonal(1:length(detrended)); residual = detrended - seasonal; % 残差项 figure; subplot(4,1,1); plot(ts_data.date(1:length(trend)), demand_ts(1:length(trend))); title('Original'); subplot(4,1,2); plot(ts_data.date(1:length(trend)), trend); title('Trend'); subplot(4,1,3); plot(ts_data.date(1:length(trend)), seasonal); title('Seasonal'); subplot(4,1,4); plot(ts_data.date(1:length(trend)), residual); title('Residual'); %% 3. 构建特征矩阵 (类似Python的Feature Engineering) % 创建滞后特征 lags = [1, 7, 30]; featureTable = ts_data; for i = 1:length(lags) lag = lags(i); varName = sprintf('lag_%d', lag); featureTable.(varName) = [NaN(lag,1); demand_ts(1:end-lag)]; end % 创建日历特征 [Y,M,D,~] = datevec(ts_data.date); featureTable.year = Y; featureTable.month = M; featureTable.day = D; featureTable.day_of_week = weekday(ts_data.date) - 1; % Sunday=1, 调整为周一=0 featureTable.is_weekend = ismember(featureTable.day_of_week, [5,6]); % 创建滚动统计特征 (使用movmean, movstd) featureTable.rolling_mean_7 = movmean(demand_ts, [6 0], 'Endpoints', 'shrink'); featureTable.rolling_std_7 = movstd(demand_ts, [6 0], 'Endpoints', 'shrink'); % 注意:滚动特征在开头会有NaN,需要处理 % 处理NaN值 (简单向前填充) featureTable = fillmissing(featureTable, 'previous'); % 划分特征X和标签y predictorNames = {'lag_1', 'lag_7', 'lag_30', 'month', 'day_of_week', 'is_weekend', 'rolling_mean_7', 'rolling_std_7'}; X = featureTable{:, predictorNames}; y = featureTable.demand; %% 4. 训练回归模型 (使用Fitrensemble - Random Forest或Boosted Trees) % 划分训练集和验证集 (最后20%作为验证) n = size(X,1); splitIdx = floor(0.8 * n); XTrain = X(1:splitIdx, :); yTrain = y(1:splitIdx); XVal = X(splitIdx+1:end, :); yVal = y(splitIdx+1:end); % 训练集成树模型 rng(42); % 设置随机种子保证可重复性 mdl = fitrensemble(XTrain, yTrain, 'Method', 'LSBoost', 'NumLearningCycles', 200, 'LearnRate', 0.1); % 验证 yPred_val = predict(mdl, XVal); mae_val = mean(abs(yPred_val - yVal)); rmse_val = sqrt(mean((yPred_val - yVal).^2)); fprintf('Validation MAE: %.4f, RMSE: %.4f\n', mae_val, rmse_val); figure; plot(yVal, 'b-', 'LineWidth', 1.5); hold on; plot(yPred_val, 'r--', 'LineWidth', 1.5); legend('Actual', 'Predicted'); xlabel('Time Step'); ylabel('Demand'); title('Validation Set Prediction'); %% 5. 使用SARIMA作为基准/对比模型 % 使用Econometrics Toolbox % 首先识别模型阶数 (简化流程,实际需根据ACF/PACF图判断) % 假设我们识别为 SARIMA(1,1,1)(1,1,1,7) Mdl = arima('ARLags', 1, 'D', 1, 'MALags', 1, ... 'Seasonality', 7, 'SARLags', 1, 'SMALags', 1); % 估计参数 EstMdl = estimate(Mdl, demand_ts, 'Display', 'off'); % 进行预测 [forecast_sarima, YMSE] = forecast(EstMdl, 30, 'Y0', demand_ts); lower = forecast_sarima - 1.96*sqrt(YMSE); upper = forecast_sarima + 1.96*sqrt(YMSE); %% 6. 模型融合 (简单加权平均) % 使用全部数据重新训练集成模型 final_mdl = fitrensemble(X, y, 'Method', 'LSBoost', 'NumLearningCycles', 200, 'LearnRate', 0.1); % 生成未来30天的特征进行预测 (递归预测,类似Python部分) % 此处省略详细的递归特征构建代码,逻辑与Python版相同 % future_features = ... % forecast_ml = predict(final_mdl, future_features); % 假设我们已经得到了 forecast_ml (30x1向量) % forecast_combined = 0.2 * forecast_sarima + 0.8 * forecast_ml; %% 7. 结果输出与可视化 % 输出预测结果表格 future_dates = ts_data.date(end) + days(1:30)'; % result_table = table(future_dates, forecast_combined, 'VariableNames', {'date', 'forecast_demand'}); % writetable(result_table, 'forecast_results.csv'); disp('预测流程完成。');

5. 关键难点、避坑指南与效果提升技巧

在实际操作中,我们会遇到一系列教科书上不会讲的难题。

5.1 预测阶段的特征构造“陷阱”

这是最大的难点。训练时,lag_1特征很容易从上一行的真实demand获得。但在预测未来第一天时,我们没有真实的“昨天”的需求。我们的做法是:

  1. 使用训练集最后一天的真实需求作为lag_1
  2. 预测出第一天的值pred_day1
  3. 预测第二天时,用pred_day1作为新的lag_1
  4. 如此递归进行。

问题来了:滚动特征rolling_mean_7怎么算?它依赖于最近7天的值。在预测期,这7天里混入了预测值。我们必须模拟一个“实时更新”的序列:

  • 维护一个扩展的序列extended_series = [真实历史序列, 预测值1, 预测值2, ...]
  • 每预测新的一天,就将预测值追加到这个序列末尾。
  • 计算新一天的滚动特征时,基于这个extended_series的最后7个值(其中可能包含预测值)来计算。
  • 这要求我们的特征工程代码必须是函数化的,能够接收一个序列和当前索引,输出该时间点的所有特征值。

避坑指南:务必编写一个独立的create_features_for_date(today_index, historical_series, calendar_info)函数。在训练时,循环调用它来构建特征矩阵;在预测时,同样调用它,并传入不断增长的“历史+预测”序列。这是保证线上线下逻辑一致、避免数据泄露的关键。

5.2 多序列预测的效率问题

题目有上百个配件,每个都单独跑一遍SARIMA和LightGBM,耗时很长。

  • SARIMA:每个序列需要单独定阶、估计参数,无法批量自动化。比赛中时间有限,可以采用自动定阶工具(如pmdarima库的auto_arima函数),或者为所有序列统一指定一个相对通用的阶数(如(1,1,1)(1,1,1,7)),牺牲一些精度换取速度。
  • LightGBM:这是我们的优势。我们可以将所有配件的训练数据纵向堆叠起来,并增加一个“配件ID”的类别特征。然后训练一个全局模型。模型会自动学习不同配件之间的共性模式,并通过“配件ID”来区分个体差异。这通常比单独训练上百个模型效果更好、更快。
    # 将所有配件数据合并 all_data = pd.concat([df_sku1, df_sku2, ...], ignore_index=True) # 将'sku_id'作为类别特征,进行标签编码或目标编码 from sklearn.preprocessing import LabelEncoder le = LabelEncoder() all_data['sku_id_encoded'] = le.fit_transform(all_data['sku_id']) # 将'sku_id_encoded'加入特征集进行训练

5.3 评估指标与交叉验证

不要只看最后的预测误差,要用对方法评估。

  • 评估指标:常用MAE(平均绝对误差)MAPE(平均绝对百分比误差)。MAE对异常值不敏感,MAPE能反映相对误差,但当真实值很小时MAPE会爆炸。商业场景中更关注WMAPE(加权平均绝对百分比误差),用真实值加权,更稳定。
  • 交叉验证:对于时间序列,绝对不能使用随机划分的K-Fold!必须使用TimeSeriesSplit滚动窗口交叉验证。它的每次验证集都在训练集之后,模拟了真实的预测场景。
  • 划分比例:建议保留最后1-2个月的数据作为严格的测试集(Hold-out Set),完全不参与任何特征构建和模型选择,只用于最终报告性能。用之前的数据进行时序交叉验证来调参。

5.4 模型融合与集成技巧

简单的加权平均有时不够好。

  • Stacking:将SARIMA和LightGBM的预测结果作为新的特征,再加上原始的部分重要特征,训练一个第二层的“元模型”(通常使用简单的线性回归或岭回归)。这能让元模型学习如何权衡两个基模型的输出。
  • 模型多样性:除了SARIMA和LightGBM,可以加入一个简单的历史均值模型上周同期模型作为额外的基模型,增加集成多样性。
  • 动态权重:对于波动大的序列,LightGBM权重可以高一些;对于平稳的序列,SARIMA权重可以高一些。可以尝试根据序列的统计特性(如变异系数)动态调整权重。

6. 比赛策略与论文写作要点

如果是为了参赛,除了算法,策略和表达同样重要。

  1. 结果可视化:论文中一定要有丰富的图表。

    • 序列分解图(趋势、季节、残差)。
    • 历史拟合图(模型在训练集上预测 vs 实际)。
    • 未来预测图(用不同颜色区分历史实际值和未来预测值,并加上置信区间)。
    • 特征重要性图(LightGBM可以输出,能体现你的工作深度)。
    • 残差分析图(检查残差是否随机,有无模式,验证模型假设)。
  2. 模型对比:在测试集上对比SARIMA、LightGBM、融合模型等多个模型的结果,用表格清晰列出MAE、MAPE、RMSE等指标,证明你融合模型的有效性。

  3. 敏感性分析:讨论你的模型对关键参数(如LightGBM的num_leaveslearning_rate)或关键特征(如是否包含节假日特征)的敏感性。这体现了研究的严谨性。

  4. 业务解释:将模型结果翻译成业务语言。例如:“模型预测下个月A配件需求将上升15%,主要驱动因素是劳动节假期前的维修高峰,建议提前增加20%的安全库存。” 这能让评委看到你不仅会建模,更懂业务。

  5. 代码与可复现性:在附录中提供清晰、注释完整的核心代码片段。说明你的运行环境(Python/Matlab版本,主要库版本)。

这道赛题是一个绝佳的练手项目,它覆盖了从数据清洗、特征工程、模型选择与训练、到预测递归和模型融合的完整预测流程。每一个环节都有坑,也都有优化的空间。真正的提升来自于动手实践,不断地调整特征、尝试模型、分析错误。希望这份超详细的拆解能为你提供一个坚实的起点,少走一些我们曾经走过的弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询