用Python搞定MathorCup C题货量预测:从ARIMA到LSTM的实战对比(附完整代码)
参加数学建模竞赛的同学都知道,货量预测是物流类题目的经典问题。今年的MathorCup C题也不例外,第一问就要求预测分拣中心的货量。与往届不同的是,今年的数据中包含了"双十一"这样的异常波动,这对传统时间序列模型提出了挑战。本文将带你用Python生态中的工具,从基础的ARIMA到最新的LSTM,一步步构建预测模型,并附上可直接运行的完整代码。
1. 数据准备与探索性分析
在开始建模前,我们需要先理解数据。假设我们已经获得了历史货量数据,通常是一个包含日期和货量两列的CSV文件。让我们用pandas加载数据:
import pandas as pd import matplotlib.pyplot as plt # 加载数据 df = pd.read_csv('cargo_data.csv', parse_dates=['date'], index_col='date') df.plot(figsize=(12, 6)) plt.title('Historical Cargo Volume') plt.ylabel('Volume') plt.show()这段代码会生成一个时间序列图,帮助我们直观地观察数据的趋势、季节性和异常点。在今年的题目中,你可能会在11月11日附近看到一个明显的峰值,这就是"双十一"效应。
数据预处理的关键步骤:
- 处理缺失值:用前后值的平均值填充
- 标记异常点:特别是"双十一"这样的特殊日期
- 数据平稳化:通过差分消除趋势
# 标记双十一 df['is_double11'] = df.index.map(lambda x: 1 if x.month == 11 and x.day == 11 else 0) # 一阶差分 diff = df['volume'].diff().dropna()2. ARIMA模型:经典时间序列预测
ARIMA(自回归综合移动平均)是时间序列预测的经典方法。它包含三个参数:(p,d,q),分别代表自回归阶数、差分阶数和移动平均阶数。
构建ARIMA模型的步骤:
- 确定差分阶数d:通过ADF检验判断序列是否平稳
- 确定p和q:通过自相关图(ACF)和偏自相关图(PACF)
- 模型拟合与评估
from statsmodels.tsa.arima.model import ARIMA from statsmodels.graphics.tsaplots import plot_acf, plot_pacf # 绘制ACF和PACF fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8)) plot_acf(diff, ax=ax1) plot_pacf(diff, ax=ax2) plt.show() # 拟合ARIMA(1,1,1)模型 model = ARIMA(df['volume'], order=(1,1,1)) result = model.fit() print(result.summary())处理异常值(双十一)的技巧:
# 创建干预变量 df['intervention'] = (df.index >= '2023-11-01') & (df.index <= '2023-11-15') # 带干预变量的ARIMA model = ARIMA(df['volume'], order=(1,1,1), exog=df[['is_double11', 'intervention']]) result = model.fit()3. Prophet:Facebook的时间序列预测工具
Prophet是Facebook开发的时间序列预测工具,特别适合处理具有强烈季节性模式和节假日效应的数据。
Prophet的优势:
- 自动处理节假日和特殊事件
- 对缺失数据和异常值具有鲁棒性
- 直观的参数调整
from prophet import Prophet # 准备数据格式 prophet_df = df.reset_index()[['date', 'volume']].rename(columns={'date': 'ds', 'volume': 'y'}) # 定义双十一为特殊节日 double11 = pd.DataFrame({ 'holiday': 'double11', 'ds': pd.to_datetime(['2022-11-11', '2023-11-11']), 'lower_window': -2, 'upper_window': 2, }) # 创建并拟合模型 model = Prophet(holidays=double11, yearly_seasonality=True) model.add_seasonality(name='monthly', period=30.5, fourier_order=5) model.fit(prophet_df) # 预测未来30天 future = model.make_future_dataframe(periods=30) forecast = model.predict(future) model.plot(forecast)4. LSTM神经网络:捕捉复杂非线性关系
长短期记忆网络(LSTM)是一种特殊的循环神经网络,特别适合处理时间序列数据中的长期依赖关系。
LSTM建模的关键步骤:
- 数据标准化
- 创建时间序列样本
- 构建网络架构
- 训练与评估
import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense from sklearn.preprocessing import MinMaxScaler # 数据标准化 scaler = MinMaxScaler() scaled_data = scaler.fit_transform(df[['volume']]) # 创建时间序列样本 def create_dataset(data, look_back=1): X, y = [], [] for i in range(len(data)-look_back-1): X.append(data[i:(i+look_back), 0]) y.append(data[i + look_back, 0]) return np.array(X), np.array(y) look_back = 7 X, y = create_dataset(scaled_data, look_back) X = np.reshape(X, (X.shape[0], X.shape[1], 1)) # 构建LSTM模型 model = Sequential() model.add(LSTM(50, return_sequences=True, input_shape=(look_back, 1))) model.add(LSTM(50)) model.add(Dense(1)) model.compile(loss='mean_squared_error', optimizer='adam') # 训练模型 model.fit(X, y, epochs=100, batch_size=32, verbose=1) # 预测 train_predict = model.predict(X) train_predict = scaler.inverse_transform(train_predict)5. 模型对比与选择
现在我们已经实现了三种不同的预测方法,接下来需要评估它们的表现并选择最适合的模型。
评估指标:
- MAE(平均绝对误差)
- RMSE(均方根误差)
- MAPE(平均绝对百分比误差)
from sklearn.metrics import mean_absolute_error, mean_squared_error def evaluate(actual, predicted): mae = mean_absolute_error(actual, predicted) rmse = np.sqrt(mean_squared_error(actual, predicted)) mape = np.mean(np.abs((actual - predicted) / actual)) * 100 return {'MAE': mae, 'RMSE': rmse, 'MAPE': mape} # 划分训练集和测试集 train_size = int(len(df) * 0.8) test = df[train_size:] # 评估ARIMA arima_eval = evaluate(test['volume'], arima_predictions) # 评估Prophet prophet_eval = evaluate(test['volume'], prophet_predictions) # 评估LSTM lstm_eval = evaluate(test['volume'], lstm_predictions) # 结果对比 results = pd.DataFrame({ 'ARIMA': arima_eval, 'Prophet': prophet_eval, 'LSTM': lstm_eval }) print(results)模型选择建议:
| 场景 | 推荐模型 | 理由 |
|---|---|---|
| 数据量小,计算资源有限 | ARIMA | 简单快速,解释性强 |
| 有明显季节性和节假日效应 | Prophet | 自动处理节假日,调参简单 |
| 数据量大,有复杂非线性关系 | LSTM | 捕捉复杂模式,预测精度高 |
6. 应对运输线路变化的策略
题目第二问提到运输线路可能发生变化,这会影响分拣中心的货量。我们需要在模型中考虑这种变化。
处理线路变化的两种方法:
- 引入外部变量:将线路变化作为外生变量加入模型
- 模型集成:当检测到线路变化时,重新训练模型
# 方法1:在ARIMA中加入外生变量 # 假设我们有线路变化的数据 df['route_change'] = 0 # 0表示无变化 df.loc['2023-06-01':'2023-06-30', 'route_change'] = 1 # 6月份有线路变化 model = ARIMA(df['volume'], order=(1,1,1), exog=df[['route_change']]) result = model.fit() # 方法2:检测变化点并重新训练 from changepoint import Pelt algo = Pelt() algo.fit(df['volume'].values) change_points = algo.predict(pen=10) # 在变化点后重新训练模型 for cp in change_points: new_data = df.iloc[cp:] # 重新训练模型...7. 完整代码实现与优化技巧
最后,我们整合所有代码,并提供一些优化技巧。
完整代码结构:
/project /data cargo_data.csv /utils preprocessing.py evaluation.py models.py main.py优化技巧:
- 特征工程:添加星期几、月份等时间特征
- 模型融合:结合统计模型和机器学习的优势
- 超参数调优:使用GridSearch或贝叶斯优化
# 模型融合示例:ARIMA + LSTM combined_pred = 0.5*arima_predictions + 0.5*lstm_predictions # 超参数调优示例 from sklearn.model_selection import GridSearchCV from tensorflow.keras.wrappers.scikit_learn import KerasRegressor def create_model(units=50, learning_rate=0.001): model = Sequential() model.add(LSTM(units, input_shape=(look_back, 1))) model.add(Dense(1)) model.compile(loss='mse', optimizer=Adam(learning_rate=learning_rate)) return model param_grid = { 'units': [30, 50, 70], 'learning_rate': [0.001, 0.01, 0.1] } model = KerasRegressor(build_fn=create_model, epochs=100, batch_size=32, verbose=0) grid = GridSearchCV(estimator=model, param_grid=param_grid, cv=3) grid_result = grid.fit(X, y)在实际比赛中,建议先尝试简单的ARIMA模型建立baseline,然后逐步尝试更复杂的模型。记得保存所有实验结果,这有助于撰写论文时的模型对比部分。