ML-For-Beginners 时间序列预测实战:基于支持向量回归器(SVR)构建能源负荷预测模型
【免费下载链接】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/3-SVR/README.md 的完整 SVR 建模教程,围绕课程作业"构建一个新的 SVR 模型"展开。你将掌握:如何用 scikit-learn 的SVR对连续值时间序列(GEFCom 2014 电力负荷数据)进行预测,如何通过时间步张量重塑输入、用MinMaxScaler归一化、以 MAPE 评估精度,并完成"换新数据 + 调超参数 + 改时间步长"的进阶练习。阅读后可独立复现整个 SVR 时间序列预测管线,并知道如何把该方法迁移到其他数据集。
为什么用 SVR 做时间序列预测
在上一课 ARIMA 中,你已经了解 ARIMA 是预测时间序列的经典统计线性方法。然而很多真实时间序列具有非线性特征,线性模型难以刻画。SVM 能够利用核函数将数据映射到高维空间以捕捉非线性关系,这让它的回归版本SVR(Support Vector Regressor)在时间序列预测中表现良好。
三个关键概念(来自 SVR 教程 的术语表):
- 回归(Regression):监督学习技术,根据给定输入预测连续值,核心思想是在特征空间中拟合一条经过最多数据点的曲线(或直线)。
- 支持向量机(SVM):用于分类、回归和异常值检测的监督学习模型。模型是特征空间中的一个超平面——分类时作为决策边界,回归时作为最佳拟合线。通常用核函数把数据集变换到更高维空间,使其更易分离。
- 支持向量回归器(SVR):SVM 的一种,用于寻找包含最多数据点的最佳拟合线(即 SVM 语境下的超平面)。
数据集与辅助工具
本课使用的数据是 GEFCom 2014 电力负荷数据集,位于 7-TimeSeries/data/energy.csv,时间跨度从 2012 年 1 月到 2014 年 12 月,按小时记录。仓库在 7-TimeSeries/common/utils.py 中提供了两个与本课直接相关的工具函数:
load_data(data_dir):读取energy.csv,把timestamp列解析为日期后设为索引,并用pd.date_range(..., freq='H')重索引,确保时间序列每个小时都有记录(该数据集没有缺失时间段)。mape(predictions, actuals):计算平均绝对百分比误差,公式为(|预测值 - 真实值| / 真实值).mean(),本课用它量化模型精度。
# 来自 7-TimeSeries/common/utils.py 的核心实现 energy = pd.read_csv(os.path.join(data_dir, 'energy.csv'), parse_dates=['timestamp']) energy.index = energy['timestamp'] energy = energy.reindex(pd.date_range(min(energy['timestamp']), max(energy['timestamp']), freq='H')) energy = energy.drop('timestamp', axis=1)完整建模流程:从数据到 SVR 预测
完整可运行的代码在 7-TimeSeries/3-SVR/working/notebook.ipynb(注意:工作目录中的 notebook 是留给学习者填写的练习版本,超参数与张量构造处留有空位;下文代码为教程给出的完整实现,可直接对照填写)。本课的数据准备前几步与 ARIMA 课 相同。
第一步:导入库并加载数据
import sys sys.path.append('../../')import os import warnings import matplotlib.pyplot as plt import numpy as np import pandas as pd import datetime as dt import math from sklearn.svm import SVR from sklearn.preprocessing import MinMaxScaler from common.utils import load_data, mapeenergy = load_data('../../data')[['load']]第二步:可视化全部数据
energy.plot(y='load', subplots=True, figsize=(15, 8), fontsize=12) plt.xlabel('timestamp', fontsize=12) plt.ylabel('load', fontsize=12) plt.show()第三步:划分训练集与测试集
划分的原则是测试集在时间上必须晚于训练集,避免模型从未来时间段获取信息(即防止"过拟合式"的数据泄露)。教程将训练集设为 2014-11-01 至 2014-12-29,测试集为 2014-12-30 起至数据末尾。
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()第四步:过滤与缩放数据
按时间段过滤出训练/测试子集,并用MinMaxScaler把数据缩放到 (0, 1) 区间:
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)scaler = MinMaxScaler() train['load'] = scaler.fit_transform(train) test['load'] = scaler.transform(test)注意缩放细节:训练集用fit_transform拟合缩放器参数(最小值、最大值),测试集只用transform复用同一组参数,这保证训练与测试在相同尺度上,也是避免数据泄露的关键一步。
第五步:构造时间步张量
SVR 的输入形式为[batch, timesteps],需要把一维序列重构成"用前 N 个时间步预测第 N+1 个"的滑动窗口样本。教程取timesteps = 5,即用前 4 个时间步的数据作为输入,第 5 个时间步作为输出。
# 转换为 numpy 数组 train_data = train.values test_data = test.values # 选择时间步数 timesteps = 5 # 训练数据转为 2D 张量(嵌套列表推导) train_data_timesteps = np.array([[j for j in train_data[i:i+timesteps]] for i in range(0, len(train_data)-timesteps+1)])[:,:,0] print(train_data_timesteps.shape) # 输出 (1412, 5) # 测试数据转为 2D 张量 test_data_timesteps = np.array([[j for j in test_data[i:i+timesteps]] for i in range(0, len(test_data)-timesteps+1)])[:,:,0] print(test_data_timesteps.shape) # 输出 (44, 5) # 选取输入与输出 x_train, y_train = train_data_timesteps[:,:timesteps-1], train_data_timesteps[:,[timesteps-1]] x_test, y_test = test_data_timesteps[:,:timesteps-1], test_data_timesteps[:,[timesteps-1]] print(x_train.shape, y_train.shape) # (1412, 4) (1412, 1) print(x_test.shape, y_test.shape) # (44, 4) (44, 1)窗口滑动原理:1416 个训练样本生成 1416 - 5 + 1 = 1412 个窗口;每个窗口取前 4 步作为x、第 5 步作为y。滑动步长为 1,即窗口逐小时前移。
第六步:实现 SVR 模型
实现分三步:调用SVR()定义模型并传入超参数 → 调用fit()在训练数据上拟合 → 调用predict()做预测。
# 使用 RBF 核,gamma=0.5, C=10, epsilon=0.05 model = SVR(kernel='rbf', gamma=0.5, C=10, epsilon=0.05)# 拟合训练数据 model.fit(x_train, y_train[:, 0])SVR(C=10, cache_size=200, coef0=0.0, degree=3, epsilon=0.05, gamma=0.5, kernel='rbf', max_iter=-1, shrinking=True, tol=0.001, verbose=False)# 预测 y_train_pred = model.predict(x_train).reshape(-1, 1) y_test_pred = model.predict(x_test).reshape(-1, 1) print(y_train_pred.shape, y_test_pred.shape) # (1412, 1) (44, 1)超参数含义解析(对应 sklearn 的 RBF 核参数)
kernel='rbf':径向基核函数,把数据隐式映射到高维空间以捕捉非线性,是时间序列 SVR 的默认首选。gamma(取值 0.5):RBF 核的带宽系数,控制单个训练样本的影响半径。gamma 越大,决策边界越"弯曲"、越容易过拟合;越小则越平滑。C(取值 10):正则化参数,衡量误差容忍度与模型复杂度之间的权衡。C 越大越倾向于最小化训练误差(可能过拟合),越小则允许更多误差换取更平滑的模型。epsilon(取值 0.05):epsilon 不敏感损失函数的管径,落在该误差带内的样本不计算损失,直接决定回归管道的宽度。
fit()后打印出的完整参数列表还揭示了其余默认值:cache_size=200(核缓存大小,单位 MB)、coef0=0.0与degree=3(仅对 poly/sigmoid 核生效)、shrinking=True(启发式裁剪)、tol=0.001(停止迭代的容差)、verbose=False。调参时通常只需关注kernel/gamma/C/epsilon四者。
第七步:评估模型
评估前先要把预测结果和真实值反缩放回原始量纲:
# 反缩放预测值 y_train_pred = scaler.inverse_transform(y_train_pred) y_test_pred = scaler.inverse_transform(y_test_pred) # 反缩放真实值 y_train = scaler.inverse_transform(y_train) y_test = scaler.inverse_transform(y_test)由于第一个输出的输入是前timesteps-1个时间步,输出对应的时间戳要从第timesteps-1个索引之后开始取:
train_timestamps = energy[(energy.index < test_start_dt) & (energy.index >= train_start_dt)].index[timesteps-1:] test_timestamps = energy[test_start_dt:].index[timesteps-1:] print(len(train_timestamps), len(test_timestamps)) # 1412 44训练集评估
plt.figure(figsize=(25, 6)) plt.plot(train_timestamps, y_train, color='red', linewidth=2.0, alpha=0.6) plt.plot(train_timestamps, y_train_pred, color='blue', linewidth=0.8) plt.legend(['Actual', 'Predicted']) plt.xlabel('Timestamp') plt.title("Training data prediction") plt.show()print('MAPE for training data: ', mape(y_train_pred, y_train)*100, '%')MAPE for training data: 1.7195710200875551 %测试集评估
plt.figure(figsize=(10, 3)) plt.plot(test_timestamps, y_test, color='red', linewidth=2.0, alpha=0.6) plt.plot(test_timestamps, y_test_pred, color='blue', linewidth=0.8) plt.legend(['Actual', 'Predicted']) plt.xlabel('Timestamp') plt.show()print('MAPE for testing data: ', mape(y_test_pred, y_test)*100, '%')MAPE for testing data: 1.2623790187854018 %测试集 MAPE 约 1.26%,说明模型在未见过的未来数据上表现很好(教程原文评价"You have a very good result on the testing dataset!")。
全量数据集评估
# 提取 load 值为 numpy 数组 data = energy.copy().values # 缩放 data = scaler.transform(data) # 转为模型输入要求的 2D 张量 data_timesteps = np.array([[j for j in data[i:i+timesteps]] for i in range(0, len(data)-timesteps+1)])[:,:,0] print("Tensor shape: ", data_timesteps.shape) # (26300, 5) # 选取输入与输出 X, Y = data_timesteps[:,:timesteps-1], data_timesteps[:,[timesteps-1]] print("X shape: ", X.shape, "\nY shape: ", Y.shape) # (26300, 4) (26300, 1) # 预测并反缩放 Y_pred = model.predict(X).reshape(-1, 1) Y_pred = scaler.inverse_transform(Y_pred) Y = scaler.inverse_transform(Y) plt.figure(figsize=(30, 8)) plt.plot(Y, color='red', linewidth=2.0, alpha=0.6) plt.plot(Y_pred, color='blue', linewidth=0.8) plt.legend(['Actual', 'Predicted']) plt.xlabel('Timestamp') plt.show() print('MAPE: ', mape(Y_pred, Y)*100, '%')MAPE: 2.0572089029888656 %全量 26300 个窗口的 MAPE 约 2.06%,红色真实曲线与蓝色预测曲线高度贴合(教程原文评价"Very nice plots, showing a model with good accuracy")。注意全量评估时同样只对数据做transform(不做fit_transform),复用训练阶段学到的缩放参数。
作业实践:构建你自己的新 SVR 模型
课程作业(德语版,英文原版)要求:在完成上述 SVR 模型之后,用一份全新数据再构建一个 SVR 模型,并完成四件事:
- 换新数据:作业建议使用 Duke 大学维护的时间序列数据集(原文给出了数据集站点链接)。
- Notebook 注释:在 Jupyter Notebook 中记录全部工作,对每个步骤给出文字说明——这与本课练习版 notebook 7-TimeSeries/3-SVR/working/notebook.ipynb 中留空待填的代码位(
timesteps=None、model=None、y_train_pred=None等)形成呼应:作业要求的就是把"填空"升级为"从零搭建"。 - 可视化:绘制原始数据、训练/测试划分、训练集预测、测试集预测和全量预测图(可复用本课的五张结果图类型)。
- MAPE 精度评估:分别报告训练集、测试集、全量数据集的 MAPE。
同时作业明确要求做两组实验:
- 调整不同超参数:修改
gamma、C、epsilon(可尝试网格搜索式组合,如gamma在 0.1~1.0 之间、C在 1~100 之间、epsilon在 0.01~0.1 之间),观察测试集 MAPE 的变化规律——一般而言,epsilon过大模型过于粗糙、C过大容易过拟合,需要结合训练/测试 MAPE 的差距判断。 - 使用不同的时间步长值:把
timesteps从 5 改为其他值(如 1、10、24),体会"回看窗口"长度对预测精度的影响;窗口过短丢失历史模式,过长则可能引入噪声并显著减少样本数(样本数 = 序列长度 - timesteps + 1)。
评分标准解读
作业提供了三档评分表(translations/de/7-TimeSeries/3-SVR/assignment.md):
| 标准 | 优秀 | 合格 | 待改进 |
|---|---|---|---|
| 交付物 | 提交一个 Notebook:SVR 模型完成构建、测试,并通过可视化与明确的精度指标(MAPE)解释结果 | Notebook 无注释或存在 Bug | 提交的是不完整 Notebook |
对照打分,交付时请重点自查:notebook 是否对每个代码块有 markdown 注释、是否同时给出了预测图与 MAPE 数值、是否真的换用了新数据集(而不是照抄 energy.csv)。该作业文本基于 ARIMA 课作业 改写,区别在于:ARIMA 作业只要求 MAPE 评估,而 SVR 作业额外要求可视化数据与模型并调整超参数与时间步长,实践维度更深。
常见误区与注意事项
- 数据泄露:
MinMaxScaler必须只在训练集上fit,测试集与全量数据只transform;否则缩放器"见过"未来数据,MAPE 会虚高。 - 时间顺序:测试集必须晚于训练集。本课训练集覆盖 11 月、测试集覆盖 12 月末,正是为了保证模型不接触未来信息。
- 张量形状:SVR 输入必须是二维
[样本数, 时间步数],窗口构造后样本数会减少timesteps - 1个,时间戳轴也要相应错位,否则绘图会错位或报维度错误。 - 超参数组合:RBF 核的
gamma、C、epsilon三者相互制约,调参应整体观察训练/测试 MAPE 而非只看测试集,防止过拟合被掩盖。
总结
从数据加载、训练/测试划分、MinMaxScaler归一化、时间步张量构造,到SVR(kernel='rbf', gamma=0.5, C=10, epsilon=0.05)的拟合预测与 MAPE 评估,本课完整展示了一条可迁移的 SVR 时间序列预测管线。测试集 1.26% 与全量 2.06% 的 MAPE 表明:当数据存在非线性时,SVR 是 ARIMA 之外的高精度替代方案。完成作业时,记得换新数据、写清注释、画全图、算 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),仅供参考