使用全新数据构建 SVR 时间序列预测模型:ML-For-Beginners 支撑向量回归扩展任务完整实战指南
【免费下载链接】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》的课后扩展任务(assignment.md,本仓库随附孟加拉语翻译版)展开:在已经掌握 SVR 建模流程的基础上,用一套全新的时间序列数据重新构建 SVR 模型,并在 notebook 中完成数据可视化、模型评估(MAPE)与超参数(kernel、gamma、C、epsilon)和 timesteps 的调优实验。读完本文,你将掌握一套可直接复用的 SVR 时序建模工作流,包括滑动时间窗构造、Min-Max 归一化、模型拟合与反归一化评估,以及如何用网格试探法系统性寻找更优配置。
任务是什么:一节"换个数据集再做一遍"的巩固性作业
这份 assignment 是整个课程第 7 单元第 3 课(SVR,Support Vector Regressor)的收官练习。它位于 7-TimeSeries/3-SVR/assignment.md,原文在文本注解中明确说明其任务模板源于本单元第 2 课 ARIMA 的 assignment。该作业的本质是:
- 前提:你已经跟随 3-SVR 课程正文 用 GEFCom 2014 能源负荷数据构建过一个 SVR 模型;
- 目标:不要复用示例数据,而是挑选一套"新"的时间序列数据集,独立走完 数据加载 → 训练/测试划分 → 归一化 → 时间窗切分 → SVR 建模 → 反归一化 → 可视化 + MAPE 评估 的完整链路;
- 附加实验:分别调整 SVR 的关键超参数(gamma、C、epsilon),并尝试不同的
timesteps取值,观察并记录它们对预测精度的影响; - 产出:一份带注释的 notebook,其中模型、可视化与精度结论并存,作为可评分的交付物。
因此这篇作业检验的不是"会不会调包",而是对 SVR 时序预测整个流程的独立理解:数据为什么要这么切、窗口为什么要这么滑、归一化为什么在预测后要逆回去、MAPE 高低的含义是什么。下面先从课程参考实现入手,把基线流程拆清楚,再给出在新数据集上完成任务的行动清单。
先回顾基线:SVR 为什么适合时序预测
从回归、SVM 到 SVR
课程正文 README.md 用三个递进概念解释 SVR 的定位:
- 回归(Regression):监督学习的一种,从一组输入预测连续值,本质是在特征空间中拟合一条经过最多数据点的曲线(或直线);
- 支持向量机(SVM):用于分类、回归和异常检测的监督模型,在特征空间中表现为一个超平面——分类时它是决策边界,回归时它是最佳拟合线。SVM 通常借助核函数将数据映射到更高维空间,使其更容易被线性分离;
- 支持向量回归(SVR):SVM 家族中用于回归的成员,目标是找到包含最多数据点的最佳拟合线(超平面)。
何时该用 SVR 而不是 ARIMA
同一课程单元中,第 2 课介绍了 ARIMA 这一经典统计线性方法。课程指出:很多时序数据天然存在非线性特征,线性模型无法刻画;而 SVM/SVR 由于能在回归中考虑数据非线性(通过核函数),因此在时序预测场景中往往表现更佳。这就是本节作业假设你已经理解的技术背景——当面对带明显周期与非线性波动的新数据集时,SVR 是一个值得优先尝试的回归器。
基线参考实现全拆解(直接继承课程代码)
新作业要求你独立复现的流程,正是 working/notebook.ipynb 中演示过的内容(同目录 solution/notebook.ipynb 为完整参考答案)。逐段拆解如下。
第 0 步:导入依赖与工具函数
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, mape注意最后一行from common.utils import load_data, mape——课程把公共函数收敛在 7-TimeSeries/common/utils.py。其中:
load_data(data_dir)(utils.py):读取energy.csv,把timestamp列解析为日期并设为索引,再以freq='H'(逐小时)对最小到最大时间戳做reindex,用于暴露缺失时间段;返回以时间为索引、只含load/temp等特征列的数据框。mape(predictions, actuals)(utils.py):实现平均绝对百分比误差,其定义为(|predictions - actuals| / actuals).mean()。后续所有精度表述均出自该函数。
第 1 步:加载并整体可视化数据
示例使用课程数据 7-TimeSeries/data/energy.csv(约 2.6 万行逐小时记录,含timestamp, load, temp三列,load为待预测的能源负荷):
energy = 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()这一步对应作业要求中的"可视化你的数据":在新数据集上,第一件事永远是画出完整序列,观察趋势、季节性、异常点与缺失情况。
第 2 步:按时间先后划分训练集与测试集
课程特别强调了一个易被忽略的原则:测试集在时间上必须晚于训练集。否则模型相当于"偷看"了未来,属于数据泄漏/过拟合。参考实现的划分如下:
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()随后按时间段过滤出各自的'load'列:
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),即训练集含约 59 天逐小时样本,测试集含最后 48 个逐小时样本。
第 3 步:归一化到 (0,1) 区间
SVR 对特征尺度敏感,必须统一量纲。参考实现使用MinMaxScaler(默认将数据映射到 [0,1]):
scaler = MinMaxScaler() train['load'] = scaler.fit_transform(train)test['load'] = scaler.transform(test)关键细节:fit_transform只在训练集上执行一次fit,测试集只用transform。原因是归一化的统计量(min/max)来自训练集分布,若把测试集也纳入fit,等于测试信息提前泄漏进预处理环节——这与时间先后划分遵循的是同一原则。完成评估、做逆变换时,二者共用同一个scaler才能还原出真实量纲的预测值。
第 4 步:用滑动时间窗构造[样本, 时间步]张量
SVR 的输入是二维张量,形式为[batch, timesteps]。为了让模型"看到"历史序列而不是单个点,需把一维序列重排成滑动窗口样本。课程取timesteps = 5,语义是:用前 4 个时刻的值预测第 5 个时刻的值。
# Converting to numpy arrays train_data = train.values test_data = test.valuestimesteps = 5用嵌套列表推导构造窗口:
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] train_data_timesteps.shape # 输出 (1412, 5):1416 - 5 + 1 = 1412 个窗口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] test_data_timesteps.shape # 输出 (44, 5):48 - 5 + 1 = 44 个窗口随后把每个窗口的前timesteps-1列作为特征X、最后一列作为标签y:
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 个点被切成 1412 个长度为 5 的窗口,每个窗口又拆成 4 个输入 + 1 个输出。timesteps就是你让模型回看多长历史的超参数,这正是作业要求你反复调整它的原因——窗口太短看不到周期性,太长则浪费数据并加剧边缘截断。
第 5 步:实例化 SVR 并拟合
课程采用 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)流程上遵循三步:定义模型(传超参数)→fit()拟合训练数据 →predict()预测。
第 6 步:预测并反归一化
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)评估前先把预测值和真实值从 [0,1] 还原回原始量纲:
# Scaling the predictions y_train_pred = scaler.inverse_transform(y_train_pred) y_test_pred = scaler.inverse_transform(y_test_pred) # Scaling the original values y_train = scaler.inverse_transform(y_train) y_test = scaler.inverse_transform(y_test)还原时间戳时注意:第一个输出的时间对应窗口末尾,因此取原始时间轴的第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第 7 步:可视化 + MAPE 评估
训练集上绘制"实际 vs 预测"曲线并计算 MAPE:
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 %参考实现还额外用全量数据回测整体表现:对整个序列构造窗口(26300 个)→ 预测 → 反归一化 → 绘图 → 打印 MAPE(约 2.057%)。基线实验的结论是:训练集 MAPE ≈ 1.72%、测试集 MAPE ≈ 1.26%、全量回测 ≈ 2.06%,说明该配置下 SVR 预测精度相当不错。注意 MAPE 计算发生在反归一化之后,量纲一致才是有效百分比。
在新数据集上独立完成任务:可执行的行动清单
拿到作业后,按下面五步推进即可覆盖全部要求。
1. 选数据:单变量、连续、长度适中的时序
assignment 建议"试试新的数据集"。选型时把握三个可自检的条件:
- 单变量可预测目标:SVR 基线流程只对单一连续列(如负荷、销量、温度、水位)做回归,多列数据需自行扩展特征,不建议作为首次作业;
- 时间等间隔连续:参考流程基于逐小时索引,若新数据按天/月采样,需相应调整重采样口径(
load_data中的freq='H'是 energy 专属设定,新数据要按自身频率处理,见 utils.py); - 样本量充足:时间窗会截掉头尾
timesteps-1个点,数据过短会导致窗口样本稀少,评估不稳定。
2. 写清"数据说明 + 划分理由"
作业评分看重解释性。在 notebook 中至少写清三件事:
- 数据来源与含义、采样频率、时间跨度;
- 训练/测试的时间切点是什么、为什么测试段在时间上晚于训练段(防止未来信息泄漏);
- 为什么要归一化、为什么只对训练集
fit缩放器、为什么要反归一化再算 MAPE。
3. 完成"可视化 + 建模 + 评估"三段式
把上文第 1~7 步完整迁移到新数据:原始序列图 → 训练/测试分割图 → 训练/测试预测对比图(红线实际、蓝线预测),并分别在训练集与测试集上打印mape(...)*100得到的百分比。只有当预测曲线与真实曲线在图上肉眼可对齐、且 MAPE 数值合理时,"模型有效"这一结论才成立。
4. 做超参数实验:kernel / gamma / C / epsilon
课程的 🚀Challenge 与作业的"tweak the hyperparameters"直接对应。推荐用固定控制变量的方式逐组试验,每组都记录测试集 MAPE:
| 超参数 | 含义与影响(标准 SVR 语义) | 试参建议 |
|---|---|---|
kernel | 核函数类型,决定非线性映射方式,可选linear/poly/rbf/sigmoid | 至少对比rbf与linear,观察非线性核是否有收益 |
gamma | 影响单个训练样本的作用半径(对 RBF 核尤为关键);越大拟合越紧、越易过拟合,越小曲线越平缓 | 在0.01 / 0.1 / 0.5 / 1 / 10量级间扫 |
C | 误差惩罚系数;越大越不容忍训练误差(易过拟合),越小越强调模型平滑 | 在1 / 10 / 100 / 1000量级间扫 |
epsilon | ε-不敏感管的宽度;管内的点不计误差,越大对噪声越宽容、预测越平滑 | 在0.01 / 0.05 / 0.1 / 0.5间扫 |
参考实现的起点(gamma=0.5, C=10, epsilon=0.05)在 energy 上表现良好;换数据后最优组合大概率不同,需要重扫。注意 C 与 epsilon 对 SVR 的训练时间也有影响,数据集较大时可用小规模子集先快速探查再全量训练。
5. 做timesteps实验:改变"回看历史"的长度
把窗口长度从 5 换成其他值(例如 3、7、12、24),需要同步注意三点:
- 窗口样本数变化:
n_windows = n_samples - timesteps + 1,timesteps越大可用窗口越少; - 输入/输出维度变化:每个窗口被拆为
timesteps-1个输入 + 1 个输出,x的列数随之改变; - 时间戳对齐变化:输出时间从原时间轴的
timesteps-1处开始(对齐代码见上文第 6 步)。
对日/周周期明显的数据,通常把timesteps设为接近一个周期(如 24 小时或 7 天)能显著提升精度,这正是作业希望你通过实验发现的经验规律。逐组比较时请把结果整理成表,例如"timesteps=12 时测试集 MAPE 从 x% 降到 y%"。
如何自查:对照评分标准验收
作业自带一份 Rubric 表,原文翻译如下,是唯一的验收标准:
| 标准 | 优秀(Exemplary) | 合格(Adequate) | 需改进(Needs Improvement) |
|---|---|---|---|
| 综合 | 提交的 notebook 完成了 SVR 模型的构建、测试与解释,并配有可视化且注明了精度 | 提交的 notebook 没有解释或存在错误(bug) | 提交的 notebook 不完整 |
据此自查清单:
- 构建了:代码能从头跑到尾,
fit与predict无报错; - 测试了:在训练集、测试集(建议再补全量回测)上都算了 MAPE 并给出结论性语句;
- 解释了:每个关键代码块上方有注释/文字说明"为什么这么做",尤其是时间划分与归一化理由;
- 可视化了:数据曲线与"实际 vs 预测"对比曲线齐全;
- 注明精度了:MAPE 结果以百分比形式明确写出,而非只有晦涩的输出。
一句话验收标准:随便拉一个没上过这课的人打开你的 notebook,也能只凭文字与图表复述出整个建模逻辑——能,就是 Exemplary。
小结
本次作业真正的技术要点可浓缩为一条完整链路:
原始序列 → 时间上严格后置的测试集划分 → 只对训练集拟合的 Min-Max 归一化 → 按
timesteps滑窗构造[batch, timesteps]→ SVR(kernel/gamma/C/epsilon) 拟合与预测 → 反归一化 → 对齐时间戳绘图 → 用mape()量化精度 → 以测试集 MAPE 为准则系统性调参。
仓库为你提供了三份可直接对照的参照物:课程正文(含全部代码)、working 手写练习版 notebook、solution 参考答案 notebook;公共实现load_data与mape在 7-TimeSeries/common/utils.py,示例数据在 7-TimeSeries/data/energy.csv。照此把同一套方法论迁移到你的新数据集上,并用上面的自查清单验收,即可高质量完成本作业——同时真正掌握"SVR + 时间窗 + 网格调参"这一可复用到大量单变量预测场景的通用技能。
【免费下载链接】ML-For-Beginners12 weeks, 26 lessons, 52 quizzes, classic Machine Learning for all项目地址: https://gitcode.com/GitHub_Trending/ml/ML-For-Beginners
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考