不是 TCN 和 RNN 各写各的,而是让它们在一根管子里干活——前面用膨胀卷积抓长周期规律,后面用循环网络补时序依赖。跑了一组电力负荷数据,MAPE 压到了个位数,代码和数据都整理好了。
一、为什么要折腾这个组合
时间序列预测是个老话题了,但电力负荷预测有它的特殊性:周期性明显(每天早高峰、晚高峰)、突发波动多(天气骤变、节假日效应)、多变量耦合(温度、湿度、电价同时影响)。
单独用 RNN 族模型(LSTM/GRU)的问题在于——序列一长,梯度该消失还是消失,靠门控机制只是"缓解"而非"根治"。单独用 TCN(Temporal Convolutional Network)呢?膨胀卷积确实能把感受野拉得很长,计算也比 RNN 快,但它对局部时序的细腻变化不够敏感。
所以思路很直白:串起来用。TCN 先过一遍,把长程特征抽出来;再接一层 SimpleRNN,把相邻时间步的依赖关系补上。不是啥惊天动地的创新,但工程上确实好用。
二、模型长什么样
核心结构就四层,一目了然:
Input(时间步=5, 特征数=多变量) ↓ TCN(nb_filters=128, kernel_size=3, dilations=[1,2,4]) ↓ return_sequences=True,保留时序维度 SimpleRNN(units=50, return_sequences=False) ↓ 压成固定长度向量 Dense(10) + LeakyReLU(α=0.3) ↓ Dense(n_out, activation='linear') ↓ 预测值(反归一化后即为实际负荷)拆开说几个关键点:
2.1 TCN 层:用空洞把视野撑开
CNN 做序列最大的痛点是感受野受限——你要看 100 步之前的信息,普通卷积得叠几十层。TCN 的精髓在于dilated convolution(膨胀卷积):每隔几个点采一个样,卷积核物理大小不变,但"看到"的范围指数级扩大。
本模型里dilations=[1, 2, 4]的含义:
- 第一层 dilation=1:普通卷积,相邻点都看
- 第二层 dilation=2:隔一个点看一个,感受野翻倍
- 第三层 dilation=4:隔三个点看一个,感受野再翻倍
三层下来,有效感受野覆盖1+2×(3−1)×(1+2+4)=291 + 2 \times (3-1) \times (1+2+4) = 291+2×(3−1)×(1+2+4)=29个时间步,而实际卷积核只用了3×3=93 \times 3 = 93×3=9个参数。
公式:膨胀卷积的输出
F(s)=∑i=0k−1f(i)⋅xs−d⋅iF(s) = \sum_{i=0}^{k-1} f(i) \cdot x_{s - d \cdot i}F(s)=i=0∑k−1f(i)⋅xs−d⋅i
其中kkk是卷积核大小,ddd是膨胀系数,sss是当前时间步。
2.2 SimpleRNN 层:补上"相邻"的细腻度
可能有人问:为什么不用 LSTM?因为 TCN 已经用多层膨胀卷积把长程依赖处理得差不多了,后面只需要补充短程时序的连贯性。SimpleRNN 计算快、参数少、不容易过拟合,在这个位置反而是合适的。
SimpleRNN 的前向公式:
ht=tanh(Wxhxt+Whhht−1+bh)h_t = \tanh(W_{xh} x_t + W_{hh} h_{t-1} + b_h)ht=tanh(Wxhxt+Whhht−1+bh)
WxhW_{xh}Wxh是输入权重,WhhW_{hh}Whh是隐状态转移矩阵,bhb_hbh是偏置。每次更新隐状态hth_tht时,都把当前输入xtx_txt和上一时刻状态ht−1h_{t-1}ht−1揉在一起。
2.3 输出层:LeakyReLU 防神经元"死掉"
Dense(10) 后面跟的是LeakyReLU(α=0.3)而非常见的 ReLU。原因是:ReLU 在负数区域梯度为零,一旦某个神经元输出恒为负,它就不再更新(所谓的"dying ReLU")。LeakyReLU 给负数区域一个小斜率(0.3),保证梯度始终能传回去。
LeakyReLU(x)=max(0,x)+α⋅min(0,x)\text{LeakyReLU}(x) = \max(0, x) + \alpha \cdot \min(0, x)LeakyReLU(x)=max(0,x)+α⋅min(0,x)
最后 Dense 层用linear激活(回归任务标配),输出维度由n_out决定——单步预测就是 1,多步预测就多几个。
三、数据怎么处理
3.1 滑窗构造样本
时间序列预测的第一步永远是把连续序列切成监督学习的样本对。代码里的data_collation函数设计得挺灵活:
- 取前n_inn\_inn_in个时间步的所有特征 → 压平成一维向量
- 取接下来n_outn\_outn_out个时间步的目标列→ 作为标签
- 每隔scroll_windowscroll\_windowscroll_window步滑动一次,共取num_samplesnum\_samplesnum_samples个样本
示例:原始数据 7 行×\times×4 列(3 特征 + 1 目标),设n_in=2, n_out=2, scroll_window=1,则生成 3 组样本:
| 输入(2步×4列展平=8维) | 输出(未来2步的目标值) |
|---|---|
| 第1-2行全部特征 | 第3-4行的目标值 |
| 第2-3行全部特征 | 第4-5行的目标值 |
| 第3-4行全部特征 | 第5-6行的目标值 |
支持四种模式:单/多输入 × 单/多步输出,靠改参数就能切换。
3.2 归一化:先切分再缩放
这里有个容易被忽视的细节——必须先划分训练集/测试集,再分别做归一化。如果反过来,先用全部数据 fit 了 MinMaxScaler,测试集的信息就会"泄露"到训练过程中,评估结果会虚高。
# 正确做法Xtrain,Xtest,Ytrain,Ytest=train_test_split(values)# 先切m_in.fit_transform(Xtrain)# 用训练集 fitm_in.transform(Xtest)# 测试集只 transform,不 fitMinMaxScaler 的变换公式:
xscaled=x−xminxmax−xminx_{\text{scaled}} = \frac{x - x_{\min}}{x_{\max} - x_{\min}}xscaled=xmax−xminx−xmin
反归一化就是把公式倒过来用,预测结果映射回原始量纲。
四、训练与评估配置
| 参数 | 取值 | 说明 |
|---|---|---|
输入时间步n_in | 5 | 用前 5 个时刻预测未来 |
输出步数n_out | 1 | 单步预测 |
| 训练/测试比例 | 85% / 15% | 1000 个样本中 850 个用于训练 |
| 归一化方式 | MinMaxScaler | 缩放到 [0,1] 区间 |
| 优化器 | Adam | 默认学习率 0.001 |
| 损失函数 | MSE | 均方误差1n∑(yi−y^i)2\frac{1}{n}\sum(y_i - \hat{y}_i)^2n1∑(yi−y^i)2 |
| 评估指标 | MAE | 平均绝对误差 |
| Batch Size | 32 | |
| Epochs | 60 | |
| 验证集比例 | 25% | 训练集里再划 1/4 做验证 |
| TCN 卷积核数 | 128 | |
| TCN 卷积核大小 | 3 | |
| TCN 膨胀系数 | [1, 2, 4] | |
| SimpleRNN 单元数 | 50 | |
| LeakyReLU 斜率 | 0.3 |
评估用了五个指标,各有所长:
- MSE / RMSE:对大误差敏感(平方放大),适合评估极端偏差
- MAE:对误差一视同仁,直观好理解
- MAPE:百分比形式,跨量纲可比,但不适合真值接近 0 的场景
- R²:衡量模型比"瞎猜均值"强多少,越接近 1 越好
MAPE 的计算:
MAPE=1n∑i=1n∣yi−y^iyi∣×100%\text{MAPE} = \frac{1}{n} \sum_{i=1}^{n} \left| \frac{y_i - \hat{y}_i}{y_i} \right| \times 100\%MAPE=n1i=1∑nyiyi−y^i×100%
五、运行环境与依赖
硬性要求:
Python >= 3.7 TensorFlow >= 2.0 (Keras 已内置) keras-tcn (pip install keras-tcn) scikit-learn pandas / numpy / matplotlib openpyxl (如需读取 .xlsx) prettytable (表格打印) mplcyberpunk (可视化风格,可选) qbstyles (同上,可选)一块普通 GPU 就能跑(CPU 也行,60 个 epoch 几分钟的事)。训练过程中会输出每个 epoch 的 loss 和 val_loss,看着两条曲线收束的走势判断要不要加 epoch 或调学习率。
六、应用场景——不只是电力负荷
虽然示例数据是电力负荷,但凡是多变量时间序列预测的活,这套框架都能直接迁移:
- 新能源功率预测:风电/光伏出力受风速、辐照度、温度等多因素影响,且波动剧烈,TCN 的长程建模能力正好派上用场
- 交通流量预测:路网传感器数据,上下游路段互相影响,多变量输入是天然需求
- 水质/空气质量监测:多种污染物浓度耦合,排放源与扩散延迟带来时序依赖
- 量化金融:价量关系、多品种联动,序列长且信噪比低,膨胀卷积比 RNN 更抗噪
需要调整的地方主要是n_in(看你的数据周期多长)和dilations(感受野要覆盖一到两个完整周期)。
七、一点实操经验
跑这套代码踩过几个坑,列出来省得别人再踩:
- CSV 编码:中文数据文件默认可能不是 UTF-8,代码里用的
gb2312,Windows 上导出的表格尤其注意这一点 - TCN 的
return_sequences:接 RNN 时必须设为True,否则时序维度被压掉,RNN 拿什么来"循环"? - 反归一化别忘了:预测出来的值是 [0,1] 区间的,不反归一化直接算 MAPE 会得到一个离谱的数字
- 滑窗步长:
scroll_window设成 1 会让相邻样本高度重叠,虽然样本数多了但也不是没代价——验证集和训练集可能"长得太像",导致验证 loss 虚低
八、总结
TCN + SimpleRNN 这个组合谈不上多新颖,但它务实:TCN 管全局周期,RNN 管局部细节,两层 Dense 做非线性映射,结构简单、训练快、可解释性不差。对大多数工业场景的时序预测需求,精度和效率的平衡点拿捏得刚好。
有一个 Bonus:因为 TCN 可以并行计算(不像 RNN 必须串行),推理阶段的速度比纯 LSTM/GRU 快不少,这对需要实时预测的上线场景很友好。
📎 获取方式
完整代码 + 示例数据集已打包。
文件包含:TCN-RNN 模型完整代码、电力负荷预测示例数据、一键运行脚本、可视化模块。