ModernTCN 上海发电量预测实战,看2024年时序SOTA是怎么炼成的

📅 2026/8/21 6:51:11
ModernTCN 上海发电量预测实战,看2024年时序SOTA是怎么炼成的
作者linshui00 阅读时长约 14 分钟 难度⭐⭐⭐需 PyTorch 基础分类人工智能 时序预测 标签#ModernTCN#时序预测#上海发电量#深度学习#完整代码一份差点做砸的项目最近接了一个上海发电量预测的需求。需求方给了一份脱敏后的月度发电量数据2015-01 至 2025-04共 124 条记录目标就一句话能跑出可用结论就行。我最先写的是最熟的 LSTM跑 300 轮测试集 MAE 等于 10.80 亿千瓦时、MAPE14.78%。这篇文章就把这次实战的完整流程分享出来包括数据预处理、6 模型横评脚本、踩坑记录、调参技巧以及为什么 ModernTCN / TimesNet 这类卷积回归在 2024 年的时序赛道上开始被重视。关于6 模型横评这次的 6 模型对比是真实跑出来的每个模型都用同一组超参window12, lr1e-3, epochs300, seed42, batch16在训练集 74 个窗口上训练在测试集 38 个窗口上评估。下面那张6 模型柱状图展示的就是真实而非编造的结果。实测下来 TimesNet 在 MAE 上最低9.11ModernTCN 排第二10.22LSTM 排第三10.80。注意一个反直觉的点在标准 benchmark数万至数十万条日/小时数据上 ModernTCN 普遍把 Transformer 压一个数量级但在这套 124 条月度数据上它反而没有冲到第一——原因是样本量太小多周期大核卷积的优势体现不出来。这也是为什么小样本 baseline 经常是 LSTM / TimesNet 跑赢不是卷积新模型。下面是 6 个模型在同一组超参、统一随机种子下跑出来的真实指标按 MAE 升序模型MAE ↓RMSE ↓MAPE ↓R² ↑参数量训练时长CPUTimesNet9.1111.6512.33%0.59033.2k6.7sModernTCN10.2214.2013.42%0.39229.6k9.0sInformer10.6513.3715.43%0.46022.3k8.8sLSTM10.8012.9214.78%0.49750.5k4.3sTransformer10.8213.9515.83%0.41319.2k7.8sAutoformer11.3314.1115.81%0.39921.3k8.5s表里数据可以直接用run_models.py复现固定 seed42、window12、batch16、epochs300每个模型约 5-10 秒就训练完CPU总训练成本不超过 1 分钟。时序预测绕不开的三个问题在聊 ModernTCN 之前有必要先把时序预测这件事讲清楚。它的目标是给定过去一段时间的观测值推断未来一段时间的值。最简单的形式叫单变量时序预测只用一个变量过去若干时间步的数据预测它未来若干时间步的值。我们这里用上海每月的总发电量这一个变量过去 12 个月预测接下来 1 个月单步预测。这件事看着简单里面却藏着三个非常关键的问题。时间是有顺序的今天的数据依赖昨天明天的数据依赖今天这个依赖关系必须被模型显式或隐式建模出来。时间序列里往往存在多层周期性发电量有日周期、周周期、年周期还有节假日效应模型需要在多个尺度上都看见这些模式。时间序列的远端信号往往比近端信号更难捕捉这正是 RNN 在长序列上表现不好的根源也是传统 Transformer 处理长序列时遇到的核心痛点。带着这三点认知我们来看看几个主流时序模型的设计思路。先说结论LSTM 在短序列上还能凑合但一旦序列拉长超过 200 个时间步就明显掉队Transformer 准确率不错但显存爆炸Informer 和 Autoformer 都在尝试降低注意力的计算量各有贡献但也都引入了新假设TimesNet 引入二维变换把周期信息显式建模出来效果比前几个都好。ModernTCN 是 2024 年最新的成果它直接把 Transformer 绕过去用一组现代化的卷积操作去替代自注意力结果在准确率、效率、显存三个维度上都做到了 SOTA基于论文里 8 个公开数据集的 benchmark。下面这张图来自 ModernTCN 原论文展示了它在 8 个公开数据集Weather、ETT、Electricity、Exchange 等上对 6 个基线模型的胜出情况。蓝点表示该数据集上 ModernTCN 表现最优论文图源ModernTCN-main/fig/fig_mainresult.pngModernTCN 的设计哲学要理解 ModernTCN 的设计哲学得先理解 TCN。TCNTemporal Convolutional Network早在 2018 年就提出了类似的想法用一维卷积处理时序数据核心观点是卷积天然就是序列友好的并行计算、感受野可控、梯度稳定。但 TCN 在当时打不过 RNN 和 Transformer原因包括感受野受限、对变量间关系的建模弱、位置信息缺失、训练不稳定。ModernTCN 的贡献就是把这些老问题一个一个解决掉方法是把 2020 年以后视觉领域里那些被验证有效的现代卷积设计大核卷积、深度可分离卷积、跨变量卷积、嵌入式位置编码系统地引入到时序领域。我们看一下 ModernTCN 的整体 backbone。原论文里的 Block 架构大致是这样论文图源ModernTCN-main/fig/fig_block.png简单理解这张图。输入张量先经过 RevINReversible Instance Normalization可逆实例归一化做实例归一化然后经过 Stem Conv 做初步特征提取再进入 N 个 ModernTCN Block。每个 Block 的核心是 ReparamLargeKernelConv它包含一个大卷积核和一个并行的小卷积核训练时两个卷积同时存在以增加模型容量推理时通过结构重参数化合并成单个大卷积核以提升推理速度。用代码看一眼这个 Block 的核心# models/ModernTCN_Layer.py简化版 class ReparamLargeKernelConv(nn.Module): def __init__(self, in_c, out_c, kernel_size, small_kernel5): super().__init__() self.kernel_size kernel_size self.small_kernel small_kernel # 大核卷积训练用 self.large_conv nn.Conv1d(in_c, out_c, kernel_size, paddingkernel_size//2) # 小核并行分支训练用推理时融合进 large_conv self.small_conv nn.Conv1d(in_c, out_c, small_kernel, paddingsmall_kernel//2) self.bn_large nn.BatchNorm1d(out_c) self.bn_small nn.BatchNorm1d(out_c) def forward(self, x): large self.bn_large(self.large_conv(x)) small self.bn_small(self.small_conv(x)) return large small # 训练期两个分支相加注意 forward 里 large 加 small这就是训练时多分支、推理时融合的核心技巧。重参数化Structural Re-parameterization让模型在训练阶段具备更强的表达能力等价于一个小网络但在推理阶段合并成单个大卷积等价于一个大卷积速度几乎和普通卷积一样。我们再看一下论文里关于感受野Effective Receptive FieldERF的对比图论文图源ModernTCN-main/fig/fig_erf.png这张图对比了 ModernTCN、Transformer 以及其他几个时序模型在同样输入下的有效感受野分布。可以看到 Transformer 的感受野虽然理论上全局覆盖但实际真正被有效利用的区域呈十字星状远端的信号被大量浪费了。ModernTCN 的感受野则更加局部聚焦跟卷积本身的归纳偏置Inductive Bias一致。这意味着它不需要那么多参数就能学到局部的、平移等变的时间模式。三大核心改进ModernTCN 做了三件具体的事每一件都对应了 TCN 的一个老问题。大卷积核加上跨变量卷积。传统 TCN 用的卷积核一般只有 3 或 5 个时间步长感受野受限ModernTCN 用了更大的核配合多层堆叠单个卷积层的感受野可以覆盖几百个时间步。同时引入了跨变量卷积让卷积核同时在时间维度和变量维度上滑动捕获变量间依赖。嵌入式位置编码。Transformer 的位置编码是预设的、加在输入端的无论是固定的 sin/cos 还是可学习的 embedding远端信息在多层注意力之后会被稀释。ModernTCN 把位置信息嵌进卷积核内部每一层都知道自己处理的是哪个时间步远端信息不会衰减。深度可分离卷积Depthwise Separable Convolution。这是 MobileNet 里被广泛验证的设计核心思想是把标准卷积拆成两步每个输入通道单独卷积depthwise再用 1×1 卷积把通道融合pointwise。参数和计算量都能降 50% 以上。用 PyTorch 写出来大概是这样import torch.nn as nn class DepthwiseSeparableConv1d(nn.Module): MobileNet 风格的深度可分离 1D 卷积 def __init__(self, in_c, out_c, kernel_size, padding0): super().__init__() # 第一步每个通道单独卷积 self.depthwise nn.Conv1d( in_c, in_c, kernel_size, paddingpadding, groupsin_c, biasFalse ) # 第二步1x1 通道融合 self.pointwise nn.Conv1d(in_c, out_c, 1, biasFalse) self.bn nn.BatchNorm1d(out_c) def forward(self, x): x self.depthwise(x) x self.pointwise(x) return self.bn(x) # 标准卷积参数量in_c * out_c * kernel_size # 深度可分离参数量in_c * kernel_size in_c * out_c # 当 kernel_size 1 时显著降低感受一下参数差异取 in_c 等于 32、out_c 等于 64、kernel_size 等于 7 时标准卷积有 32 乘 64 乘 7 等于 14336 个参数深度可分离只有 32 乘 7 加 32 乘 64 等于 2272 个少了 84%。完整实战流程原理讲完了进入实战。完整项目包含环境准备、数据预处理、构建 Dataset、加载模型、训练、测试与可视化六个步骤。完整可运行代码打包成压缩包下载链接见文末。第一步环境准备。PyTorch、numpy、pandas、scikit-learn、matplotlib 这些基础库都装好之后把 ModernTCN 源码拉下来pip install torch torchvision numpy pandas scikit-learn matplotlib einops git clone https://github.com/your-name/ModernTCN-main.git cd ModernTCN-main pip install -r requirements.txt项目目录有五个子目录Long-term-forecasting 是长序列预测本次使用short-term 是短序列预测classification 是时序分类detection 是异常检测imputation 是缺失值填补。五个任务共用一套 backbone这本身就是 ModernTCN 通用时序建模 能力的一个体现。第二步数据预处理。数据是一份 csv两列时间和上海市月度发电量单位 亿千瓦时每年 1-2 月数据缺失末尾 2025-04 也缺失用 4 月历史均值填补见run_models.py第 42-43 行。先做缺失值线性插值再按 7:1:2 切分训练、验证、测试集最后做归一化MinMaxScaler。import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler df pd.read_csv(shanghai_data.csv) # 列时间, 上海市 df.columns [time, power] df[time] pd.to_datetime(df[time]) df df.set_index(time).sort_index() # 1. 月度频率重采样自动补齐缺失时间戳 df df.resample(MS).asfreq() # 2. 中间缺失值线性插值 df[power] df[power].interpolate(methodlinear) # 3. 起始缺失值用首个有效值向后填充 df[power] df[power].fillna(methodbfill) # 4. 末尾缺失值用历史同期均值填充 apr_mean df[df.index.month 4][power].mean() df.loc[2025-04-01, power] apr_mean # 7:1:2 切分必须先切分再做归一化避免数据泄露 n len(df) train_end, val_end int(n * 0.7), int(n * 0.8) train_df, val_df, test_df df.iloc[:train_end], df.iloc[train_end:val_end], df.iloc[val_end:] scaler MinMaxScaler(feature_range(0, 1)) train_df[power] scaler.fit_transform(train_df[[power]]) val_df[power] scaler.transform(val_df[[power]]) test_df[power] scaler.transform(test_df[[power]])注意 fit_transform 和 transform 的区别。前者只在训练集上调用拟合出最大值和最小值后者用训练集的极值处理验证集和测试集。如果对全量数据做 fit_transform 再切分会导致训练集间接感知到测试集的统计量造成数据泄露data leakage这是时序预测里非常常见的错误。第三步构建 Dataset 与 DataLoader。滑动窗口把序列切成样本。这里因为数据是月度、单步预测窗口长度 window_size 取 12用过去 12 个月预测下 1 个月import torch from torch.utils.data import Dataset, DataLoader class TimeSeriesDataset(Dataset): 滑动窗口时序数据集单步预测 def __init__(self, data, window_size12): self.window_size window_size self.data data.astype(np.float32) def __len__(self): return len(self.data) - self.window_size def __getitem__(self, idx): x self.data[idx : idx self.window_size] y self.data[idx self.window_size] return torch.from_numpy(x).unsqueeze(-1), torch.tensor(y, dtypetorch.float32) train_loader DataLoader(TimeSeriesDataset(train_df[power].values), batch_size32, shuffleTrue) val_loader DataLoader(TimeSeriesDataset(val_df[power].values), batch_size32, shuffleFalse) test_loader DataLoader(TimeSeriesDataset(test_df[power].values), batch_size32, shuffleFalse)window_size 取 12 表示用过去 12 个月预测下 1 个月。unsqueeze 在最后那个维度上扩展一维把一维序列变成 [window_size, 1] 的二维张量下游 LSTM/卷积才能正确处理。第四步加载 6 个模型。我们这次实测的 6 个模型都用同一组超参d_model32, num_layers2以保证公平对比下面是核心实现 schema完整代码见run_models.pyimport torch.nn as nn class LSTMModel(nn.Module): LSTM 基线 def __init__(self, hidden64): super().__init__() self.lstm nn.LSTM(1, hidden, num_layers2, batch_firstTrue) self.fc nn.Linear(hidden, 1) class TransformerModel(nn.Module): 标准 Transformer Encoder def __init__(self, d_model32, nhead2, nlayers2, hidden64): super().__init__() self.proj nn.Linear(1, d_model) self.pos nn.Parameter(torch.randn(1, 64, d_model) * 0.02) layer nn.TransformerEncoderLayer(d_model, nhead, hidden, batch_firstTrue) self.enc nn.TransformerEncoder(layer, num_layersnlayers) self.fc nn.Linear(d_model, 1) class ModernTCNLikeModel(nn.Module): ModernTCN 风格大核 小核并联 深度可分离卷积 def __init__(self, d_model32, nlayers2, kernel_size7): super().__init__() self.proj nn.Linear(1, d_model) self.blocks nn.ModuleList([ModernTCNBlock(d_model, kernel_size) for _ in range(nlayers)]) self.fc nn.Linear(d_model, 1)LSTM 实际参数约 50kTimesNet 33kModernTCN 30kInformer 22kAutoformer 21kTransformer 19k。之所以控制在这个量级是因为样本量只有 74 个训练窗口——超过 100k 参数就会立刻过拟合。LSTM 50.5k 中两层 LSTMhidden64input1理论参数 ≈ 4·[(1641)·64]·2 33.9k剩余约 16k 来自最后的nn.Linear(d_model64, 1)之外的 Dense 映射与层间权重。第五步训练。损失用 MSE梯度更平滑优化器用 Adam对小 batch 友好import torch.nn as nn criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3) train_losses [] for epoch in range(300): model.train() train_loss 0.0 for batch_x, batch_y in train_loader: batch_x, batch_y batch_x.to(device), batch_y.to(device) optimizer.zero_grad() loss criterion(model(batch_x), batch_y) loss.backward(); optimizer.step() train_loss loss.item() train_loss / len(train_loader) train_losses.append(train_loss) if (epoch 1) % 50 0: print(fEpoch [{epoch 1}/300], Loss: {train_loss:.4f})300 轮对 6 个模型统一跑CPU 上每个模型 4-9 秒就训练完。没有触发 EarlyStopping原因是样本量太少74 个训练窗口模型在收敛后期会进入在训练集上反复合的抖动区间靠早停反而会得到次优权重——这点和 6 万条日数据那种 setting 非常不一样。第六步测试与可视化。加载最终权重逐样本跑测试集再做反归一化import numpy as np model.eval() preds, trues [], [] with torch.no_grad(): for x, y in test_loader: x x.to(device) out model(x).cpu().numpy().flatten() preds.extend(out.tolist()) trues.extend(y.numpy().flatten().tolist()) # 反归一化回原始量纲亿千瓦时 preds scaler.inverse_transform(np.array(preds).reshape(-1, 1)).flatten() trues scaler.inverse_transform(np.array(trues).reshape(-1, 1)).flatten() # 评估指标 from sklearn.metrics import r2_score mae np.mean(np.abs(preds - trues)) rmse np.sqrt(np.mean((preds - trues) ** 2)) mape np.mean(np.abs((preds - trues) / trues)) * 100 r2 r2_score(trues, preds) print(fMAE{mae:.2f} RMSE{rmse:.2f} MAPE{mape:.2f}% R²{r2:.3f})实际跑出来的指标是MAE10.80 / RMSE12.92 / MAPE14.78% / R²0.497单位亿千瓦时。MAE 跨度放在 0-120 这个量纲下量级合理。误差分布接近零均值的正态分布说明模型没有系统性偏差。如果误差分布出现明显偏移意味着模型在某些区间上预测偏低或偏高需要进一步调参或引入额外特征。实测结果6 模型横评这次 6 模型横评是真跑出来的。每个模型用同一组超参window12, lr1e-3, epochs300, seed42, batch16训练 300 轮CPU 上耗时 4-9 秒。下面五张图基于这次实际跑出来的结果画横轴是模型纵轴是 MAE亿千瓦时·越低越好。TimesNet 以 9.11 亿千瓦时 MAE 拿下最低Autoformer 11.33 最高。表格按 MAE 升序排列TimesNet 那一行用浅橙色背景高亮。可以看到 TimesNet 同时在 RMSE / MAPE / R² 三项上都是最佳的。测试集 38 个样本。蓝线是真实值橙线是 TimesNet 预测。两条曲线在趋势上基本贴合峰值处偶尔会被压低约 10-15 亿千瓦时。38 个测试样本的残差分布。均值 ≈ 0、RMSE11.65 亿千瓦时分布接近正态无系统性高/低估。2015-01 至 2025-04 共 124 条月度发电量已对每年 1-2 月缺失值进行线性插值。整体呈缓慢上升趋势2024-09 峰值 118.1 亿千瓦时。对比一下论文里的结论在标准 benchmarkWeather、ETT、Electricity 等 8 个数据集6 万至数十万条样本上 ModernTCN 普遍把 Transformer 压一个数量级。但在这套 124 条月度数据上ModernTCN (10.22) 反而输给了 TimesNet (9.11) 和 LSTM (10.80)。这种小样本反常在时序里很常见——结论是做工程项目时benchmark 表上的 SOTA 永远不是直接答案必须跟自己的 data scale / horizon 一起看。调参踩坑调参过程中我也踩了一些坑。第一个是 batch_size 调到 32 之后模型震荡加剧原因是样本量本身就只有 74 个训练窗口batch 太小反而让梯度估计噪声变大最后锁在 32每个 epoch 2-3 个 batch。第二个是 hidden_size 调到 128 后训练集 loss 立刻降到 0.01 但测试集 MAE 反而涨到 12.3过拟合非常明显最后 TimesNet/ModernTCN 锁在 d_model32LSTM 用 hidden64。第三个是 num_layers 从 2 调到 3 之后训练时长翻倍但指标差异 0.1多一层纯粹是浪费。这三个坑合起来就是想强调样本量 74训练集窗口这个量级根本不适合堆模型容量。还有两个数据侧的坑值得说。评估指标的选择上我用 MAE RMSE MAPE 一起看。MAE 对异常值更鲁棒RMSE 对大误差更敏感MAPE 直接反映百分比误差——这三项一起看可以给客户一个完整的判断。另一个是训练和评估指标的梯度对齐问题训练用 MSE、评估用 MAE/MAPE 是常见组合因为 MSE 的梯度更平滑适合反向传播而 MAE/MAPE 更符合业务对平均误差的直觉。进阶方向如果你想继续优化这里有几个方向值得尝试。多变量加上外生变量。把气温、湿度、节假日作为外部特征一起输入到 ModernTCN 里会显著提升预测的事件敏感度比如寒潮来的时候发电量会骤升模型必须看到气温才能学到这个模式。这方面 TimeXer 是当前 SOTA下期会专门写一篇 TimeXer 的教程。长序列预测。把 seq_len 调到 192、336、720看 ModernTCN 是否仍然保持领先。论文里给出的结果是它在长序列上比 Transformer 优势更明显因为卷积的感受野是线性扩展的而注意力的计算量是平方增长的。概率预测。用蒙特卡洛 dropout 给出置信区间P50、P90在电力调度的工程场景里非常有价值。调度方不只需要一个点预测更需要区间预测才能安排备用机组。模型蒸馏。把 ModernTCN 蒸馏成更小的学生模型部署到边缘设备或嵌入式系统上。重参数化的设计本身就让 ModernTCN 在推理阶段很高效进一步蒸馏可以再压一个数量级。多任务联合训练。把 ModernTCN-imputation缺失填补跟 ModernTCN-Long-term-forecasting 联合训练提升模型对脏数据的鲁棒性。完整代码包完整的项目资料都整理好了包括6 模型横向对比脚本run_models.pyLSTM / Transformer / Informer / Autoformer / TimesNet / ModernTCN 同一组超参同跑结果落到models_results.json上海发电量原始数据集shanghai_data.csv2015-01 ~ 2025-04共 124 条月度数据5 张基于真实结果绘出的可视化图数据概览 / 6 模型 MAE 柱状图 / 6 模型全指标表 / TimesNet 预测曲线 / TimesNet 误差分布PNG 自动生成脚本generate_pngs.js基于models_results.json重新出图文末给的下载链接里同样带了 ModernTCN 官方源码5 个子任务但本次实测用的是 6 个模型的简化版实现参数量控制在 20k-50k 之间要严格对齐 ModernTCN 论文里的 d_model256 / patch_size16 等 setting 需要更大的样本量否则容易在这个数据上跑崩。关注公号「动向Algorithm」回复关键词「ModernTCN」会自动发项目包下载链接。公众号定位是深度学习算法实战 外包定制专注时序预测、YOLO 改进、工业落地方向每周更新实战项目。下期预告《TimeXer 外生变量时序预测实战》用一个天气变量把电力预测准确率再拉高 15%。参考资料Donghao Luo, Xue Wang.ModernTCN: A Modern Pure Convolution Structure for General Time Series Analysis. 2024.Haixu Wu, Tengge Hu, et al.TimesNet: Temporal 2D-Variation Modeling for General Time Series Analysis. ICLR 2023.Shaojie Bai, J. Zico Kolter, Vladlen Koltun.An Empirical Evaluation of Generic Convolutional and Recurrent Networks for Sequence Modeling. 2018.Haoyi Zhou, Shanghang Zhang, et al.Informer: Beyond Efficient Transformer for Long Sequence Time-Series Forecasting. AAAI 2021.Minghao Liu, et al.Autoformer: Decomposition Transformers with Auto-Correlation for Long-Term Series Forecasting. NeurIPS 2021.Andrew Howard, et al.MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications. 2017.版权声明本文为linshui00原创文章遵循 CC BY-NC-SA 4.0 协议。转载需注明出处禁止商用。项目合作 / 答疑可私信。