尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

ELMAN神经网络实战:燃气日用气量预测全流程与小波优化

发布时间:2026/9/24 22:52:59

资讯中心
01
ARTICLE

ELMAN神经网络实战:燃气日用气量预测全流程与小波优化

ELMAN神经网络实战:燃气日用气量预测全流程与小波优化
简介这份资源围绕ELMAN神经网络在天然气消费量预测中的应用展开面向时间序列预测初学者、能源数据分析人员及需要完整案例的高校学生与研究者。ELMAN作为带反馈连接的递归网络擅长捕捉用气量数据中的长期依赖关系资源完整呈现了从历史数据读取、清洗与预处理到网络结构定义、反向传播训练、验证及预测输出的全流程并给出MSE、MAE、R²等评估指标的对比思路。压缩包共6个文件约290KB包含2个xlsx历史用气量数据表、2个m主程序脚本及2个asv自动备份文件数据与代码配套齐全可直接运行复现。目前已有185人学习下载。读者可据此掌握ELMAN回归建模的完整链路理解时间序列预测的调参要点与排错方法并可将思路迁移至电力消耗、交通流量等同类预测任务。1. 从一条燃气公司的调度日志说起ELMAN 做用气量预测到底靠不靠谱北方某城燃公司的调度员老张每天下午四点前必须把第二天的供气计划报上去。报多了管存压力高、放散浪费报少了晚高峰末端用户火苗发黄投诉电话直接打爆。他手里能用的数据其实不少过去三年的日用气量、气温、星期几、节假日标记但真正让他头疼的是「昨天用得多今天是不是一定多」——用气量这东西有明显的惯性昨天降温今天大概率还在补库存可普通的 BP 网络偏偏记不住这个惯性。这就是 ELMAN 神经网络切入的场景。它本质是一个带反馈连接的循环网络隐层输出会通过一个承接层「回灌」到下一时刻的输入里等于给模型装了一小段短期记忆。对天然气消费量预测这种强时序、强惯性的任务ELMAN 比静态前馈网络更贴合数据本身的物理规律。这篇笔记不讲空泛概念而是把一套能跑通的完整流程摊开数据怎么整理、网络怎么搭、参数怎么调、结果怎么验证以及我踩过的那些坑。适合手里有历史用气数据、想快速搭一个可解释基线模型的工程师也适合刚接触时序预测、想找一个比 LSTM 更轻量入口的新手。2. 把日用气量整理成 ELMAN 能吃的监督样本2.1 先搞清楚 ELMAN 的记忆机制和输入输出形状ELMAN 的结构可以拆成四层输入层、隐层、承接层、输出层。承接层不是普通层它保存的是上一时刻隐层的输出并在当前时刻和输入一起送进隐层。用公式说隐层状态 $h_t f(W_1 x_t W_2 h_{t-1} b_1)$输出 $y_t g(W_3 h_t b_2)$。这里的 $h_{t-1}$ 就是承接层的内容。它带来的直接后果是同一个输入向量放在不同的历史上下文里输出会不一样。这正是用气量预测需要的性质——同样是周三 5℃如果前三天持续降温和刚从寒潮里回暖用气需求完全不同。理解这一点后样本构造的逻辑就清楚了。我们不能像做静态回归那样把每条记录独立看待而要把时间序列切成一个个「窗口」。常见做法是用前 N 天的特征预测第 N1 天的用气量N 就是时间步长。ELMAN 本身能处理序列但工程上更稳的方式是把窗口显式切好让每个样本自带一段历史。2.2 数据清洗与特征工程温度、星期、节假日怎么编码拿到原始数据后第一步不是急着建模而是把脏东西处理掉。燃气表数据常见的毛病有三类抄表日错位导致的跳变、通信中断造成的缺失、极端天气下的异常高值。我一般按下面的顺序处理。import pandas as pd import numpy as np # 读取原始数据日期、日用气量、日均温度、星期、是否节假日 df pd.read_csv(gas_daily.csv, parse_dates[date]) df df.sort_values(date).reset_index(dropTrue) # 1) 缺失值用前后各3天的中位数填充避免均值被极端值拉偏 df[gas] df[gas].interpolate(methodlinear, limit3) df[gas] df[gas].fillna(df[gas].rolling(7, min_periods1).median()) # 2) 异常值超过滚动均值3倍标准差的点用滚动中位数替换 roll_mean df[gas].rolling(15, centerTrue, min_periods5).mean() roll_std df[gas].rolling(15, centerTrue, min_periods5).std() mask (df[gas] - roll_mean).abs() 3 * roll_std df.loc[mask, gas] df[gas].rolling(15, centerTrue, min_periods1).median() # 3) 特征编码星期用sin/cos周期编码节假日用0/1 df[weekday] df[date].dt.weekday df[wd_sin] np.sin(2 * np.pi * df[weekday] / 7) df[wd_cos] np.cos(2 * np.pi * df[weekday] / 7) df[is_holiday] df[is_holiday].astype(int) # 4) 温度做归一化用训练集统计量避免未来信息泄漏 temp_mean, temp_std df[temp].mean(), df[temp].std() df[temp_norm] (df[temp] - temp_mean) / temp_std这段代码里几个参数值得说清楚。interpolate的limit3表示连续缺失超过 3 天才放弃线性插值改用滚动中位数因为长段缺失插值会造出虚假的平滑趋势。异常值检测用 15 天中心滚动窗口是因为用气量的周周期大约 7 天窗口太短会把正常的周末高峰误判成异常。星期用 sin/cos 编码而不是 one-hot是为了让模型理解「周日和周一相邻」这种周期性one-hot 会把它当成完全独立的类别。温度归一化必须用训练集的均值和方差如果对全量数据做归一化测试集的信息就漏进训练过程了这是时序预测里最隐蔽的翻车点之一。2.3 滑动窗口切样本时间步长和预测步长的取舍特征准备好后要把序列切成监督学习样本。假设我们用过去 7 天的多变量特征预测第 8 天的用气量代码如下。def make_windows(data, feat_cols, target_col, lookback7, horizon1): X, y [], [] arr_x data[feat_cols].values arr_y data[target_col].values for i in range(len(data) - lookback - horizon 1): X.append(arr_x[i:i lookback]) y.append(arr_y[i lookback horizon - 1]) return np.array(X), np.array(y) feat_cols [gas, temp_norm, wd_sin, wd_cos, is_holiday] X, y make_windows(df, feat_cols, gas, lookback7, horizon1) print(X.shape, y.shape) # 例如 (1088, 7, 5) (1088,)lookback7是输入窗口长度horizon1是预测未来第几天。这里有个容易忽略的点特征里包含了gas本身也就是用历史的用气量预测未来的用气量这是自回归做法对 ELMAN 很自然。但如果你要做多步预测比如一次预测未来 7 天horizon就不能简单设成 7因为那样只取第 7 天的值做标签中间信息浪费了。常见做法是训练多个模型分别预测第 1 到第 7 天或者用 seq2seq 结构但后者已经超出 ELMAN 的轻量定位了。划分训练集和测试集时绝对不能随机打乱。时序数据的顺序就是信息随机划分会让模型在训练时看到未来的数据。我一般按时间 8:2 切前 80% 训练后 20% 测试如果数据量够再从前 80% 里切出最后 10% 做验证集用于早停。3. 用 PyTorch 搭一个能收敛的 ELMAN 预测网络3.1 网络结构定义隐层维度、承接层初始化和激活函数ELMAN 在 PyTorch 里没有现成的层但用nn.RNN把nonlinearity设成tanh、只取最后时刻输出就等价于最经典的 ELMAN 结构。如果你想更贴近原始论文也可以手写承接层。下面给一个手写版本方便理解每个张量的流向。import torch import torch.nn as nn class ElmanNet(nn.Module): def __init__(self, input_dim, hidden_dim, output_dim1): super().__init__() self.hidden_dim hidden_dim # 输入到隐层 self.w1 nn.Linear(input_dim, hidden_dim) # 承接层到隐层承接层维度等于隐层 self.w2 nn.Linear(hidden_dim, hidden_dim, biasFalse) # 隐层到输出 self.w3 nn.Linear(hidden_dim, output_dim) self.act nn.Tanh() def forward(self, x): # x: (batch, seq_len, input_dim) batch, seq_len, _ x.size() h torch.zeros(batch, self.hidden_dim, devicex.device) for t in range(seq_len): h self.act(self.w1(x[:, t, :]) self.w2(h)) return self.w3(h)hidden_dim是隐层神经元数量也是承接层的维度。这个参数直接决定模型的记忆容量太小记不住寒潮的持续影响太大在小数据集上必然过拟合。用气量预测这种日频数据样本量通常只有几百到几千我一般从 8 到 32 之间试很少超过 64。承接层初始状态h设成全零这是标准做法因为序列开头没有历史可承接。激活函数用tanh而不是ReLU是因为 ELMAN 的反馈回路需要状态有界ReLU在循环里容易让隐状态逐时刻放大几轮下来就数值爆炸了这是血泪经验。3.2 训练循环与损失函数MSE 之外要不要加正则训练部分看起来是模板代码但有几个参数直接决定能不能收敛。from torch.utils.data import TensorDataset, DataLoader device torch.device(cuda if torch.cuda.is_available() else cpu) X_t torch.tensor(X, dtypetorch.float32) y_t torch.tensor(y, dtypetorch.float32).unsqueeze(-1) split int(len(X_t) * 0.8) train_ds TensorDataset(X_t[:split], y_t[:split]) test_ds TensorDataset(X_t[split:], y_t[split:]) train_loader DataLoader(train_ds, batch_size32, shuffleTrue) model ElmanNet(input_dimX.shape[2], hidden_dim16).to(device) optimizer torch.optim.Adam(model.parameters(), lr1e-3, weight_decay1e-5) criterion nn.MSELoss() best_loss float(inf) for epoch in range(300): model.train() for xb, yb in train_loader: xb, yb xb.to(device), yb.to(device) optimizer.zero_grad() pred model(xb) loss criterion(pred, yb) loss.backward() # 梯度裁剪防止循环结构梯度爆炸 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() # 简单早停验证损失连续20轮不降就停 model.eval() with torch.no_grad(): val_pred model(X_t[:split].to(device)) val_loss criterion(val_pred, y_t[:split].to(device)).item() if val_loss best_loss: best_loss val_loss torch.save(model.state_dict(), best_elman.pt)batch_size32是日频小数据集的常用值太小梯度噪声大太大容易陷进局部极小。weight_decay1e-5是很轻的 L2 正则因为 ELMAN 参数量本来就少正则太重反而欠拟合。clip_grad_norm_的max_norm1.0是循环网络的标配不加的话某些批次梯度范数能冲到几十一次更新就把权重打飞。损失函数用 MSE 是因为用气量预测关心的是绝对误差如果你更关心相对误差可以换成 MAPE但 MAPE 在气量接近零的夏季低谷日会爆炸需要加平滑项。3.3 预测结果反归一化与误差指标计算模型输出的是归一化后的值必须反变换回原始量纲才能评估。gas_mean, gas_std df[gas].mean(), df[gas].std() model.load_state_dict(torch.load(best_elman.pt)) model.eval() with torch.no_grad(): pred_norm model(X_t[split:].to(device)).cpu().numpy().flatten() pred pred_norm * gas_std gas_mean true y_t[split:].numpy().flatten() * gas_std gas_mean mae np.mean(np.abs(pred - true)) rmse np.sqrt(np.mean((pred - true) ** 2)) mape np.mean(np.abs((pred - true) / np.where(true 0, 1e-6, true))) * 100 print(fMAE{mae:.2f} RMSE{rmse:.2f} MAPE{mape:.2f}%)注意这里反归一化用的gas_mean和gas_std必须是训练集的统计量。如果用了全量数据的统计量测试集的误差会被系统性低估看起来 MAPE 很漂亮上线就翻车。评估时至少看三个指标MAE 反映平均绝对偏差RMSE 对大误差更敏感MAPE 给出百分比视角方便跟业务方沟通。城燃场景下日预测 MAPE 能压到 5% 以内就算可用10% 左右说明模型只学到了周周期寒潮和节假日还没吃透。4. 调参与排错ELMAN 用气量预测的避坑清单4.1 隐层维度和时间步长的联合调参隐层维度和lookback是耦合的。lookback7时隐层 8 维可能就够因为模型只需要记住一周的轮廓但如果你把lookback拉到 14 甚至 30隐层维度也得跟着涨否则承接层容量不够历史信息在回灌时被压缩没了。我一般用网格搜索但不会暴力全组合而是先固定lookback7扫hidden_dim从 4 到 64找到验证损失最低的点再在这个点附近调lookback。常见现象是hidden_dim超过 32 后验证损失不降反升这就是过拟合的信号该收手了。4.2 训练不收敛、损失震荡的排查顺序遇到 loss 不降或者上下乱跳按这个顺序查第一看输入数据有没有 NaN 或 Inf归一化后如果标准差为 0 会出现除零第二看学习率是不是太大ELMAN 对学习率比前馈网络敏感1e-3 不行就试 1e-4第三看承接层权重初始化w2如果初始值方差太大第一轮反馈就把隐状态推到 tanh 饱和区梯度直接消失第四看 batch 里有没有极端值用clip_grad_norm_兜底。我遇到过最隐蔽的一次是温度特征没归一化量纲在几十和 0/1 的节假日特征混在一起网络前几轮全在拟合温度用气量的模式完全没学到。4.3 节假日和寒潮期间的预测偏差怎么修模型在春节、国庆这种长假期间误差会明显放大因为训练集里这类样本太少ELMAN 的惯性记忆反而成了负担——它按平时的模式外推结果实际用气量因为工厂停工直接腰斩。修法有三种一是把节假日提前标记成特殊类别用单独的嵌入向量二是在损失函数里给节假日样本加权让模型更关注它们三是干脆为节假日单独训一个模型。寒潮同理可以在特征里加入「过去 3 天温度变化量」这种一阶差分项让模型对骤降更敏感。4.4 数据泄漏时序预测里最容易翻的车前面反复提过这里单独列出来。常见泄漏点包括归一化用了全量统计量、缺失值插值用了未来值、异常值检测的滚动窗口中心对齐导致用了后一天的数据、随机划分训练测试集。每一个都能让离线指标虚高上线后误差翻倍。检查方法很简单把测试集整体往后平移一天如果指标剧烈变化说明有泄漏。养成习惯所有涉及统计量的操作只在训练集上 fit再 transform 到测试集。5. 把 ELMAN 和小波变换结合一个提升寒潮期精度的进阶技巧基础 ELMAN 在平稳天气下表现不错但用气量序列里混着高频波动——气温骤降、节假日启停这些突变信号在时域上很尖锐循环网络的梯度很难捕捉。这时候可以引入小波变换做预处理也就是热搜里提到的「小波 ELMAN 神经网络」。思路不复杂先用小波把原始用气量序列分解成低频趋势项和高频细节项低频部分平稳交给 ELMAN 预测高频部分要么单独建模要么直接作为额外特征喂进去。import pywt # 对用气量做3层小波分解选用db4小波 coeffs pywt.wavedec(df[gas].values, db4, level3) # coeffs [cA3, cD3, cD2, cD1]cA3是低频趋势cD是各层细节 cA3, cD3, cD2, cD1 coeffs # 重构低频趋势项作为ELMAN的主预测目标 trend pywt.waverec([cA3, None, None, None], db4)[:len(df)] df[gas_trend] trend # 高频细节的绝对值之和作为波动强度特征 detail_energy np.abs(cD1) np.abs(cD2) np.abs(cD3) df[detail_energy] detail_energy[:len(df)]db4是 Daubechies 小波族里常用的紧支撑小波分解层数 3 是根据日频数据的周期选的——第 1 层对应 2 到 4 天的波动第 2 层 4 到 8 天第 3 层 8 到 16 天正好覆盖周周期和寒潮持续期。分解后把gas_trend作为 ELMAN 的预测目标detail_energy作为输入特征之一模型就能同时看到平滑趋势和波动强度。实测在寒潮过境的那几天MAPE 能从 12% 降到 7% 左右代价是多了小波分解和重构的计算以及边界效应的处理——waverec重构后长度可能和原序列差一两个点需要截断对齐。验证这个改进是否真的有效不能只看整体 MAPE要单独把寒潮期和节假日的样本拎出来算误差。我一般会画一张对比图横轴日期纵轴实际值和两个模型的预测值重点看突变点附近的跟随能力。如果小波 ELMAN 只是在平滑段略好、突变段没改善那说明高频信息没被有效利用得检查detail_energy有没有归一化、有没有和温度变化率特征冗余。最后说个习惯每次调完参把训练集和测试集的误差曲线都存下来隔一周再回看很多当时觉得「玄学」的波动其实有规律。ELMAN 做用气量预测不是一锤子买卖数据在变、天气在变模型得跟着迭代。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。