基于 ML-For-Beginners 的 ARIMA 实战任务:使用新数据集从零构建 ARIMA 模型并完成 MAPE 精度评估
【免费下载链接】ML-For-Beginners12 weeks, 26 lessons, 52 quizzes, classic Machine Learning for all项目地址: https://gitcode.com/GitHub_Trending/ml/ML-For-Beginners
本篇技术指南围绕 ML-For-Beginners 时间序列课程(7-TimeSeries)第 2 课时《使用 ARIMA 进行时间序列预测》的课后任务展开,任务要求读者在掌握现有 ARIMA 建模流程后,自行挑选一份全新数据集、在 notebook 中构建并评估一个新的 ARIMA 模型。读完本文,你将掌握一套可复制的"数据选择—数据切分—缩放—SARIMAX 建模—滚动验证—MAPE 精度评估"完整流程,并能对照官方评分标准自查作品是否达到优秀(Exemplary)水平。
一、任务在课程中的位置与前后置知识
本任务对应的原始作业文件是课程根目录下的 7-TimeSeries/2-ARIMA/assignment.md,本文讨论的关联文档是它的阿拉伯语翻译版 translations/ar/7-TimeSeries/2-ARIMA/assignment.md(该页面系通过 Co-op Translator 机器翻译,内容如有出入以仓库内英文原文为准)。
该任务位于"时间序列预测"课程单元的第二个课时之后:
- 前置课时:7-TimeSeries/1-Introduction 讲解了时间序列预测的基本概念,并加载了展示电力负荷波动的
energy.csv数据集; - 本课时:7-TimeSeries/2-ARIMA/README.md 系统讲解了ARIMA(自回归积分滑动平均,AutoRegressive Integrated Moving Average)模型的理论与完整建模代码,其中还包含一段用 R 演示但概念通用的 ARIMA 入门视频;
- 本任务(assignment):在学完上述课时后,脱离课程自带的能源数据,用一份"全新数据"独立完成一次 ARIMA 建模全流程。
因此,要高质量完成本任务,读者必须先熟悉课时 README 中三个核心统计学概念:
| 概念 | 含义 |
|---|---|
| 平稳性(Stationarity) | 数据的分布在时间平移后不发生改变。非平稳数据会因趋势产生波动,需经变换后才能分析;例如季节性会给数据引入波动,可通过"季节性差分"消除 |
| 差分(Differencing) | 通过移除数据中非常数趋势,把非平稳数据转换为平稳数据的过程,能够消除趋势与季节性的影响,从而稳定时间序列的均值 |
| 滞后(Lag) | 时间序列中某个观测值之前的历史取值,是 ARIMA 各组成部分建模的基础 |
二、任务拆解:assignment 到底要求交付什么
翻译版任务原文的"Instructions"部分内容凝练,可拆解为四类交付物:
- 新模型 + 新数据:在已经构建过一个 ARIMA 模型的基础上,用一份全新数据构建另一个 ARIMA 模型(任务原文建议从 Duke 大学公开的时间序列数据集集合中挑选——原任务以外部链接形式给出,此处不再展开网址);
- notebook 文档化:将全部工作写入 notebook,并做好逐格注释(Annotate your work in a notebook);
- 可视化:对数据本身和模型结果都要进行可视化;
- 精度评估:使用MAPE(平均绝对百分比误差)检验模型精度。
任务的"Rubric(评分标准)"表格定义了三个等级,是判断作品质量最直接的标尺,原表翻译对照如下:
| 标准 | 优秀(Exemplary) | 合格(Adequate) | 需改进(Needs Improvement) |
|---|---|---|---|
| 交付物 | 交付的 notebook 中构建、测试并讲解了一个全新的 ARIMA 模型,包含可视化并明确写出精度 | 交付的 notebook 未经注释或含有 bug | 交付的 notebook 不完整 |
可以看出,"优秀"的门槛是模型全新、经过测试、有解释、有可视化、精度被明确陈述这五者缺一不可;而任务明确允许"合格"档出现未注释或含 bug 的作品——说明注释完整性和可运行性是拉开档次的关键分水岭。
三、动手前的准备:环境、依赖与公共工具函数
课程把运行环境固化为 conda 配置文件,位于 7-TimeSeries/2-ARIMA/solution/common/environment.yaml,文件头部注释给出了三条标准命令:
# 创建环境 conda env create -f environment.yaml # 更新环境 conda env update -f environment.yaml # 激活并注册为 Jupyter kernel conda activate dlts python -m ipykernel install --user --name dlts --display-name "Python (dlts)"该文件锁定的核心依赖包括(注意:这是仓库教学时期固定的版本,若在现代系统上重建环境遇兼容问题,需在保持 API 一致性的前提下自行升级相应版本):
python==3.6.6、pandas==0.23.4、numpy==1.16.2matplotlib==3.0.0(绘图)scikit-learn==0.20.3(提供MinMaxScaler)statsmodels==0.9.0(提供SARIMAX,构建 ARIMA/SARIMA 模型的核心库)- pip 侧额外安装
pyramid-arima==0.8.1(提供auto_arima,用于自动搜索最优模型参数)
课时代码还依赖两个公共工具函数,它们在 7-TimeSeries/2-ARIMA/solution/common/utils.py 与根级 7-TimeSeries/common/utils.py 中各有实现:
load_data(data_dir):读取energy.csv,把timestamp列设为索引,并按小时频率重新索引为完整连续时间轴,用于识别缺失时间片(该数据集中无缺失),对应实现位于 solution/common/utils.py;mape(predictions, actuals):计算平均绝对百分比误差,即对每个预测点求|预测值−真实值| / 真实值后取平均,对应实现位于 solution/common/utils.py。
课时 README 的完整 import 块如下,新任务 notebook 应保持类似的库加载结构:
import os import warnings import matplotlib.pyplot as plt import numpy as np import pandas as pd import datetime as dt import math from pandas.plotting import autocorrelation_plot from statsmodels.tsa.statespace.sarimax import SARIMAX from sklearn.preprocessing import MinMaxScaler from common.utils import load_data, mape from IPython.display import Image %matplotlib inline pd.options.display.float_format = '{:,.2f}'.format np.set_printoptions(precision=2) warnings.filterwarnings("ignore") # 忽略告警,保持输出整洁四、选数据:一份"合格的新数据"应满足什么条件
任务的核心约束是"新数据",即不能重复使用课程自带的energy.csv。可参考的候选来源是任务原文链接中 Duke 大学的时间序列数据集集合。结合课时示范数据(GEFCom 2014 逐小时电力负荷数据,见 7-TimeSeries/2-ARIMA/solution/data/energy.csv)的特征,挑选新数据时应重点考察以下五点:
- 时间可解析:数据必须包含可解析为时间戳的列(如日期、小时),能被
pd.read_csv(..., parse_dates=[...])正确读取; - 采样频率规整:按固定频率(小时/日/月)采样为佳,便于像课时那样用
pd.date_range(..., freq='H')重索引补齐缺失;若为不规则采样需先做重采样; - 历史长度充足:至少要有能够支撑"训练窗口 + 测试集"切分的长度。课时示范取约 2 个月做测试、用 720 小时(30 天)做滚动训练窗口,数据过短将无法复现该流程;
- 具备可建模结构:肉眼观察应有趋势、季节性等可被 ARIMA/SARIMA 捕捉的模式(课时数据呈现出明显的逐日季节性,即用电量在一天 24 小时内的周期性波动);
- 无泄漏风险:选择任务时点之前就存在的公开历史数据,确保训练集时间段早于测试集时间段(详见下文第五节的时序切分原则)。
五、参考工作流:把课时的完整代码迁移到新数据上
课程 README 与两份 notebook(练习版 working/notebook.ipynb、参考实现版 solution/notebook.ipynb)已经给出了一套端到端流程。构建新模型的任务本质上是把这套流程中的数据源替换为你的新数据,其余环节保持方法论一致。下面逐步展开。
5.1 加载并观察全量数据
参照课时做法,先加载数据并观察头部记录:
energy = load_data('./data')[['load']] energy.head(10)再绘制 2012 年 1 月至 2014 年 12 月的全部电力负荷曲线,确认时间序列的整体形态:
energy.plot(y='load', subplots=True, figsize=(15, 8), fontsize=12) plt.xlabel('timestamp', fontsize=12) plt.ylabel('load', fontsize=12) plt.show()注意:换成你的新数据后,
energy.csv、列名'load'、时间段等都需要同步替换为与新数据集对应的名称与取值。
5.2 按时间顺序切分训练集与测试集
时序建模的铁律是:测试集必须覆盖比训练集更晚的时间段,避免模型从"未来"学到信息。课时把 2014-09-01 至 2014-10-31 划给训练、11 月至 12 月留给测试,示范代码如下:
train_start_dt = '2014-11-01 00:00:00' test_start_dt = '2014-12-30 00:00:00' energy[(energy.index < test_start_dt) & (energy.index >= train_start_dt)][['load']].rename(columns={'load':'train'}) \ .join(energy[test_start_dt:][['load']].rename(columns={'load':'test'}), how='outer') \ .plot(y=['train', 'test'], figsize=(15, 8), fontsize=12) plt.xlabel('timestamp', fontsize=12) plt.ylabel('load', fontsize=12) plt.show()由于电力消耗存在强季节性、且近期日期的负荷形态最接近当前模式,课时指出用一个相对较窄的时间窗口训练即可——这一点在你自己挑选的新数据上同样适用,前提是你的数据也存在类似的近期相关结构。下方是课时对训练/测试切分进行可视化的参考结果(即你在新数据上应产出的可视化样式):
注:由于拟合 ARIMA 模型所用的函数在拟合过程中采用样本内验证,课时明确省略了单独的验证集。
随后过滤出两个集合:
train = energy.copy()[(energy.index >= train_start_dt) & (energy.index < test_start_dt)][['load']] test = energy.copy()[energy.index >= test_start_dt][['load']] print('Training data shape: ', train.shape) print('Test data shape: ', test.shape)示范数据的输出形状为Training data shape: (1416, 1)、Test data shape: (48, 1),即训练集 1416 个逐小时样本、测试集 48 个样本。
5.3 用 MinMaxScaler 把数据缩放到 (0, 1)
统计模型的数值稳定性对量纲敏感,因此需把数据缩放到 (0, 1) 区间。关键操作是:先用训练集fit_transform,再用同一套缩放器transform测试集,避免测试集信息泄漏进缩放参数:
scaler = MinMaxScaler() train['load'] = scaler.fit_transform(train) train.head(10)课时还示范了用直方图对比原始数据与缩放后数据的分布形态:
energy[(energy.index >= train_start_dt) & (energy.index < test_start_dt)][['load']].rename(columns={'load':'original load'}).plot.hist(bins=100, fontsize=12) train.rename(columns={'load':'scaled load'}).plot.hist(bins=100, fontsize=12) plt.show()完成校准后缩放测试集:
test['load'] = scaler.transform(test) test.head()5.4 定义并拟合 SARIMAX 模型
由于示范数据带有季节性(24 小时周期),课程使用的是SARIMA(季节性 ARIMA)模型,通过statsmodels的SARIMAX()类实现。在 solution/notebook.ipynb 中可以看到实际调用形式:order = (4, 1, 0)、seasonal_order = (1, 1, 0, 24)。
建模前必须理解两组参数的语义:
p:自回归部分的阶数,刻画对*过去值(滞后值)*的回归;d:积分部分的差分阶数,决定对序列施加多少差分以消除非平稳性;q:移动平均部分的阶数;P、D、Q:与p、d、q描述同一类关联,但对应模型的季节性分量;SARIMAX的seasonal_order元组第四个元素(示范中为24)表示季节周期长度(这里是每日 24 小时)。
定义一个基础模型并查看拟合摘要:
order = (4, 1, 0) seasonal_order = (1, 1, 0, 24) model = SARIMAX(endog=train, order=order, seasonal_order=seasonal_order) results = model.fit() print(results.summary())results.summary()会输出一张包含对数似然(示范中 Log Likelihood 为 3477.239)、各系数及其显著性等内容的表格。课时提醒:手工挑选最优p/d/q有一定主观性且耗时,可考虑用pyramid-arima/pmdarima库的auto_arima()自动搜索(见环境配置中的pyramid-arima==0.8.1)。
5.5 用 Walk-Forward(滚动)验证评估模型
评估时序模型不应只用单次切分测试。课时的做法是walk-forward 验证:从序列起点开始,在训练集上拟合模型、预测下一个时间步、与真实值对比,随后把该真实值并入训练集继续重复。实践中时序模型通常在每个新数据点到来时重新训练,以在每个时刻给出最优预测。
效率提示:课时建议保持训练窗口长度固定——每当向训练集尾部加入一个新观测,就同步从开头移除一个最旧观测,避免窗口无限膨胀。
在开始滚动前,需要先为HORIZON(预测步数/预测期长度)内的每一步构造测试目标列。设HORIZON = 3(预测未来 3 小时):
# Specify the number of steps to forecast ahead HORIZON = 3 print('Forecasting horizon:', HORIZON, 'hours')随后把测试集按各步移位(load+1、load+2分别为前 1、2 小时的目标值):
test_shifted = test.copy() for t in range(1, HORIZON+1): test_shifted['load+'+str(t)] = test_shifted['load'].shift(-t, freq='H') test_shifted = test_shifted.dropna(how='any') test_shifted.head(5)示范数据移位后形如:
| timestamp | load | load+1 | load+2 |
|---|---|---|---|
| 2014-12-30 00:00:00 | 0.33 | 0.29 | 0.27 |
| 2014-12-30 01:00:00 | 0.29 | 0.27 | 0.27 |
| 2014-12-30 02:00:00 | 0.27 | 0.27 | 0.30 |
接下来用滑窗在测试集长度内循环训练与预测——这是本任务 notebook 中最核心的代码块,课时 README 中的完整实现为:
%%time training_window = 720 # dedicate 30 days (720 hours) for training train_ts = train['load'] test_ts = test_shifted history = [x for x in train_ts] history = history[(-training_window):] predictions = list() order = (2, 1, 0) seasonal_order = (1, 1, 0, 24) for t in range(test_ts.shape[0]): model = SARIMAX(endog=history, order=order, seasonal_order=seasonal_order) model_fit = model.fit() yhat = model_fit.forecast(steps = HORIZON) predictions.append(yhat) obs = list(test_ts.iloc[t]) # move the training window history.append(obs[0]) history.pop(0) print(test_ts.index[t]) print(t+1, ': predicted =', yhat, 'expected =', obs)循环中反复调用SARIMAX(...).fit()再forecast(steps=HORIZON),同时维护固定长度 720 的history窗口。该方法的优点是能给出模型在实践中表现的最稳健估计;代价是训练了非常多个模型,示范数据量小可接受,但扩展到大规模数据时要权衡计算成本。滚动预测的中间输出形如:
2014-12-30 00:00:00 1 : predicted = [0.32 0.29 0.28] expected = [0.32945389435989236, 0.2900626678603402, 0.2739480752014323] 2014-12-30 01:00:00 2 : predicted = [0.3 0.29 0.3 ] expected = [0.2900626678603402, 0.2739480752014323, 0.26812891674127126]把全部预测结果组装成便于评估的eval_df,并把缩放后的预测与真实值反变换回原始量纲(以便与真实负荷直接对比):
eval_df = pd.DataFrame(predictions, columns=['t+'+str(t) for t in range(1, HORIZON+1)]) eval_df['timestamp'] = test.index[0:len(test.index)-HORIZON+1] eval_df = pd.melt(eval_df, id_vars='timestamp', value_name='prediction', var_name='h') eval_df['actual'] = np.array(np.transpose(test_ts)).ravel() eval_df[['prediction', 'actual']] = scaler.inverse_transform(eval_df[['prediction', 'actual']]) eval_df.head()(7-TimeSeries/common/utils.py中的create_evaluation_df是这段逻辑的封装实现。)
5.6 用 MAPE 检验模型精度
MAPE(平均绝对百分比误差)把预测误差表达为真实值的比例,公式为:对每个预测点求|actual_t − predicted_t| / actual_t(绝对差除以真实值),再把全部预测点的取值求和后除以拟合点数 n,最终以百分比呈现。数学式插图见 7-TimeSeries/2-ARIMA/images/mape.png。
逐步计算多步预测中每个 horizon 的平均绝对误差:
if(HORIZON > 1): eval_df['APE'] = (eval_df['prediction'] - eval_df['actual']).abs() / eval_df['actual'] print(eval_df.groupby('h')['APE'].mean())计算单步预测(t+1)的 MAPE:
print('One step forecast MAPE: ', (mape(eval_df[eval_df['h'] == 't+1']['prediction'], eval_df[eval_df['h'] == 't+1']['actual']))*100, '%')计算全部步长的整体 MAPE:
print('Multi-step forecast MAPE: ', mape(eval_df['prediction'], eval_df['actual'])*100, '%')重要提醒:课时示范数据得到的"单步 MAPE 约 0.557%、多步 MAPE 约 1.146%"是针对课程自带
energy.csv的结果,不应被当作你新数据模型的预期或达标数值。MAPE 越低越好(例如 MAPE 为 10 意味着平均偏离 10%),但不同数据集的可达精度差异很大,任务评分关注的是流程完备性而非某个绝对分数。请在 notebook 中明确写出你自己模型实际测得的 MAPE。
最后把预测与真实值画在同一张图上,直观检查拟合质量。多步预测时,课时按"预测越远线条越浅越细"的方式分层绘制(线宽按4*pow(0.9,t)递减、透明度按pow(0.8,t)递减):
if(HORIZON == 1): ## Plotting single step forecast eval_df.plot(x='timestamp', y=['actual', 'prediction'], style=['r', 'b'], figsize=(15, 8)) else: ## Plotting multi step forecast plot_df = eval_df[(eval_df.h=='t+1')][['timestamp', 'actual']] for t in range(1, HORIZON+1): plot_df['t+'+str(t)] = eval_df[(eval_df.h=='t+'+str(t))]['prediction'].values fig = plt.figure(figsize=(15, 8)) ax = plt.plot(plot_df['timestamp'], plot_df['actual'], color='red', linewidth=4.0) ax = fig.add_subplot(111) for t in range(1, HORIZON+1): x = plot_df['timestamp'][(t-1):] y = plot_df['t+'+str(t)][0:len(x)] ax.plot(x, y, color='blue', linewidth=4*math.pow(.9,t), alpha=math.pow(0.8,t)) ax.legend(loc='best') plt.xlabel('timestamp', fontsize=12) plt.ylabel('load', fontsize=12) plt.show()下图是课时在示范数据上得到的最终模型精度图——红粗线为真实负荷、蓝色渐细线为各步预测,可作为你在新数据集上应产出的可视化基准:
六、对照 Rubric 的交付自查清单
任务只有三档评分,向"优秀(Exemplary)"冲刺时可逐条自查以下清单:
模型与数据维度
- 使用了一份课程中未出现过的全新时间序列数据集;
- 模型确实为 ARIMA/SARIMA 族模型(
statsmodels的SARIMAX或pmdarima的ARIMA均可),且不是简单复制课时参数的"换数据跑一遍"——若沿用课时参数请说明理由,若能结合新数据的季节性周期(如每日、每周周期长度)设置seasonal_order会更出彩。
notebook 注释与可复现性
- 每个代码格均有 Markdown/注释说明意图(对应"Annotate"要求);
- 数据集来源、加载方式、切分边界(起止时间)被明确交代;
- notebook 可从头到尾无报错执行(对应规避"contains bugs"档)。
可视化维度
- 至少包含全量原始序列图(观察趋势/季节性);
- 包含训练/测试切分可视化;
- 包含预测 vs 真实值的对比图(形如上文精度图)。
精度维度
- 使用 MAPE 计算精度,并在 notebook 中以文字明确写出数值(对应"accuracy stated"要求);
- 最好同时给出单步(
t+1)与多步 MAPE,并可加一句对数值的解读("误差约 X% 意味着平均偏离 X%")。
若上述项目全部完成,即满足"构建、测试、讲解、可视化、精度陈述"五要素,可判定为 Exemplary。
七、延伸思考:其他精度指标与自动化调参
任务的核心评估指标是 MAPE,但课时末尾的 Challenge 也提示:时间序列模型的精度度量不止 MAPE 一种。作为锦上添花,可在 notebook 中补充研究并记录诸如 MAE、MSE/RMSE、MAD 等指标与 MAPE 的适用场景差异——这将让你的"讲解"部分更有深度。
另外,若手动挑选p/d/q/P/D/Q让你感到耗时,可在 notebook 中尝试环境里已配置的pyramid-arima提供的auto_arima()函数自动搜索参数,再与手工参数的结果做对比,以支撑你对自己参数选择的解释。整条工作流的参考实现可直接对照 7-TimeSeries/2-ARIMA/solution/notebook.ipynb,练习起点则为 7-TimeSeries/2-ARIMA/working/notebook.ipynb;课程同一目录下还提供了 R 与 Julia 两个语言的参考说明(见 7-TimeSeries/2-ARIMA/solution/R 与 7-TimeSeries/2-ARIMA/solution/Julia),可供跨语言对照学习。总而言之,本任务考察的不是背出 ARIMA 的公式,而是把课时方法论迁移到陌生数据上的完整工程能力——数据是否选得合理、notebook 是否自洽可复现、结论是否用 MAPE 和数据可视化支撑,这三者缺一不可。
【免费下载链接】ML-For-Beginners12 weeks, 26 lessons, 52 quizzes, classic Machine Learning for all项目地址: https://gitcode.com/GitHub_Trending/ml/ML-For-Beginners
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考