资讯详情

基于LSTM的风机风速功率预测:Python深度学习实战与权重推理

📅 2026/10/11 19:22:52 | 华诺云谱 👁 阅读
基于LSTM的风机风速功率预测:Python深度学习实战与权重推理
简介本资源面向具备一定Python与深度学习基础、希望上手风电时序预测的开发者与研究人员提供一套基于深度时空网络的风机发电风速及功率预测项目源码与训练权重。包内共12个文件以6个py脚本为核心涵盖数据加载、模型定义、训练器与工具函数等模块另含2个csv指标文件、2个pt权重文件及说明文档压缩包约178KB结构紧凑便于快速复现。使用前需从公开数据集下载风电数据并放入data目录重命名为wind_power.csv随后通过train.py指定名称、轮数、批大小、学习率、折数与风机数量即可启动训练。项目已产出风速与功率两组评估指标及对应权重读者可据此理解时空建模思路、对照指标结果并在此基础上迁移到其他时序预测任务。目前已有380人学习下载适合作为课程设计、科研入门或工程原型的参考方案。1. 风机风速功率预测为什么深度学习方案值得你花时间跑通风电场上最让运维头疼的不是风机故障而是功率预测偏差带来的考核罚款。一个 50MW 的风电场如果日前预测误差超过 15%单月考核费用可能吃掉相当一部分利润。传统方法用测风塔数据配合物理模型或统计回归在复杂地形和突变天气下经常翻车——风速从 3m/s 跳到 12m/s 可能只需要十几分钟线性模型根本追不上这种非线性突变。这个项目标题指向的方案核心是用 Python 搭建深度学习模型输入历史风速、风向、温度、气压等时序数据输出未来一段时间的风速和对应功率预测值并且附带训练好的权重文件让你不用从零训练就能直接推理验证。适合两类人一是风电行业的数据分析或运维工程师想快速验证深度学习在自己场站数据上的效果二是做时序预测方向的学生或开发者需要一个有明确物理背景、数据特征清晰的实战项目来练手。热搜里“动手深度学习”“深度学习入门”这些词频繁出现说明很多人卡在“看了理论不知道怎么落地”这一步而这个项目恰好补上了从代码到权重的完整链路。2. 风速功率预测的数据管线与模型选型从 SCADA 原始数据到可训练张量2.1 为什么选 LSTM 而不是 Transformer 或纯 CNN风机 SCADA 系统采集的数据是典型的多变量时间序列采样间隔常见为 1 分钟、5 分钟或 10 分钟。风速本身有很强的时序依赖——当前时刻的风速和前 1 小时的风速高度相关这种依赖跨度可能达到几十甚至上百个时间步。LSTM 的门控机制天然适合捕捉这种长程依赖而且参数量比 Transformer 小得多在几千到几万条样本的量级上不容易过拟合。Transformer 在长序列建模上确实更强但它需要更大的数据量和更精细的位置编码设计。对于单个风电场、几个月到一年的 SCADA 数据LSTM 或 GRU 往往是性价比最高的选择。CNN 擅长提取局部模式但风速的突变更多是全局趋势变化单纯用 CNN 容易丢失长程信息。常见做法是 CNNLSTM 混合先用一维卷积提取局部特征再送入 LSTM 捕捉时序依赖。这个项目如果权重文件是基于 LSTM 的直接加载推理即可如果想换模型结构数据管线部分可以复用。选型时还要考虑预测步长。单步预测预测下一个 10 分钟用 LSTM 就够了如果要预测未来 4 小时、24 小时可能需要 Seq2Seq 结构或引入注意力机制。标题里“风速及功率预测”没有明确步长我一般会先做单步验证效果再扩展到多步。2.2 从 SCADA 导出到模型输入完整预处理流程拿到原始数据后第一步是确认字段。典型 SCADA 导出文件包含时间戳、风速、风向、温度、气压、机舱角度、功率等列。不同厂商的列名可能不一样需要先做字段映射。import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler # 读取原始 SCADA 数据假设是 CSV 格式 df pd.read_csv(scada_raw.csv, parse_dates[timestamp]) df df.sort_values(timestamp).reset_index(dropTrue) # 字段映射把不同厂商的列名统一 col_map { wind_speed: wind_speed, wind_direction: wind_dir, temperature: temp, pressure: pressure, active_power: power } df df.rename(columnscol_map) # 处理缺失值风速和功率用线性插值风向用前后填充 df[wind_speed] df[wind_speed].interpolate(methodlinear, limit6) df[power] df[power].interpolate(methodlinear, limit6) df[wind_dir] df[wind_dir].ffill().bfill() # 异常值处理功率为负或超过额定值的置为 NaN 再插值 rated_power df[power].quantile(0.99) df.loc[df[power] 0, power] np.nan df.loc[df[power] rated_power * 1.1, power] np.nan df[power] df[power].interpolate(methodlinear, limit6) # 风向三角函数编码避免 0 度和 360 度的跳变 df[wind_dir_sin] np.sin(np.radians(df[wind_dir])) df[wind_dir_cos] np.cos(np.radians(df[wind_dir])) # 选择特征列 feature_cols [wind_speed, wind_dir_sin, wind_dir_cos, temp, pressure] target_col wind_speed # 先预测风速再用功率曲线转功率 # 归一化用训练集拟合 scaler避免数据泄漏 scaler MinMaxScaler() scaled scaler.fit_transform(df[feature_cols])这段代码的关键点有三个。第一缺失值处理不能简单 dropna否则会丢掉大量连续时间段线性插值配合 limit 参数可以防止在长时间断采时插出虚假数据。第二风向必须做三角函数编码直接拿 0-360 的数值喂给模型模型会认为 359 度和 1 度差距巨大实际上它们几乎一样。第三归一化要用训练集拟合如果全量数据一起 fit测试集的信息会泄漏到训练过程导致评估结果虚高。接下来是构造监督学习样本。假设用过去 60 个时间步预测下一个时间步def create_sequences(data, target_idx, seq_len60, pred_len1): X, y [], [] for i in range(len(data) - seq_len - pred_len 1): X.append(data[i:iseq_len]) y.append(data[iseq_len:iseq_lenpred_len, target_idx]) return np.array(X), np.array(y) # target_idx 是 wind_speed 在 feature_cols 中的位置 target_idx feature_cols.index(wind_speed) X, y create_sequences(scaled, target_idx, seq_len60, pred_len1) # 按时间顺序划分训练集和测试集不能随机打乱 split int(len(X) * 0.8) X_train, X_test X[:split], X[split:] y_train, y_test y[:split], y[split:]这里最容易踩的坑是随机划分。时间序列必须按时间顺序切分否则未来数据会“穿越”到训练集。seq_len 的选择要看数据采样间隔10 分钟采样的话60 步相当于过去 10 小时对风速预测来说偏长可以试 18 到 36 步1 分钟采样的话 60 步只有 1 小时可能不够。我一般会试 3 到 4 个 seq_len 值看验证集损失再定。2.3 功率转换从预测风速到预测功率标题里“风速及功率预测”意味着最终要输出功率值。常见做法有两种一是直接让模型输出功率把功率作为目标变量二是先预测风速再用功率曲线转换。直接预测功率的好处是端到端坏处是功率受限电、停机影响大数据噪声更重。先预测风速再转功率的好处是风速数据更干净模型更容易学到物理规律但需要准确的功率曲线。如果权重文件是直接输出功率的那就按功率做目标变量。如果是输出风速的转换时用分段函数def wind_to_power(wind_speed, cut_in3.0, rated12.0, cut_out25.0, rated_power2000): 简化功率曲线单位风速 m/s功率 kW power np.zeros_like(wind_speed) mask1 (wind_speed cut_in) (wind_speed rated) mask2 (wind_speed rated) (wind_speed cut_out) # 立方段 power[mask1] rated_power * ((wind_speed[mask1] - cut_in) / (rated - cut_in)) ** 3 # 额定段 power[mask2] rated_power return power实际功率曲线应该从场站历史数据拟合而不是用理论曲线。拟合方法可以用分箱平均把风速按 0.5m/s 分箱每箱取功率均值再做平滑。这样得到的曲线更贴近实际机组特性。3. 加载权重文件做推理最小可运行代码与参数核对3.1 权重文件格式识别与模型结构对齐拿到权重文件后第一件事是确认它保存的是什么。常见格式有 PyTorch 的 .pt/.pth、Keras 的 .h5、TensorFlow 的 SavedModel 目录。如果只有权重没有模型定义代码需要根据权重文件的键名反推网络结构。import torch # 加载权重文件先看 state_dict 的键名 state_dict torch.load(wind_lstm.pth, map_locationcpu) for k, v in state_dict.items(): print(k, v.shape)输出可能类似lstm.weight_ih_l0 torch.Size([256, 5])说明 LSTM 输入维度是 5隐藏层维度是 256。fc.weight torch.Size([1, 256])说明全连接层输出维度是 1。根据这些信息可以重建模型import torch.nn as nn class WindLSTM(nn.Module): def __init__(self, input_dim5, hidden_dim256, num_layers2, output_dim1): super().__init__() self.lstm nn.LSTM(input_dim, hidden_dim, num_layers, batch_firstTrue) self.fc nn.Linear(hidden_dim, output_dim) def forward(self, x): out, _ self.lstm(x) out self.fc(out[:, -1, :]) # 取最后一个时间步 return out model WindLSTM(input_dim5, hidden_dim256, num_layers2) model.load_state_dict(torch.load(wind_lstm.pth, map_locationcpu)) model.eval()如果键名对不上比如权重里有lstm.weight_ih_l0_reverse说明是双向 LSTM需要设置bidirectionalTrue。如果有多层且键名带_l1说明 num_layers 至少是 2。这一步没有捷径只能对着键名逐个核对。3.2 推理时的归一化一致性最容易翻车的地方训练时用了 MinMaxScaler推理时必须用同一个 scaler 的参数。如果 scaler 没有保存需要从训练数据重新计算但这样可能和训练时不一致。正确做法是训练完把 scaler 的 min_ 和 scale_ 存下来import joblib # 训练时保存 joblib.dump(scaler, scaler.pkl) # 推理时加载 scaler joblib.load(scaler.pkl) # 对新数据做同样的变换 new_scaled scaler.transform(new_data[feature_cols])如果权重文件里没有附带 scaler只能从训练数据统计量反推。常见做法是看权重文件同目录下有没有scaler.pkl或norm_params.json。没有的话需要问原作者要或者用自己的数据重新拟合——但这样预测结果会有偏差因为模型是在特定归一化空间下训练的。推理完整流程# 构造输入序列seq_len 必须和训练时一致 seq_len 60 input_seq new_scaled[-seq_len:] # 取最近 seq_len 个时间步 input_tensor torch.FloatTensor(input_seq).unsqueeze(0) # 增加 batch 维度 with torch.no_grad(): pred_wind model(input_tensor).item() # 反归一化风速在 feature_cols 中的位置是 0 pred_wind_original pred_wind * (scaler.data_max_[0] - scaler.data_min_[0]) scaler.data_min_[0] pred_power wind_to_power(np.array([pred_wind_original]))[0] print(f预测风速: {pred_wind_original:.2f} m/s, 预测功率: {pred_power:.1f} kW)反归一化时要注意如果目标变量在归一化矩阵中的位置不是 0需要用对应的索引。另外如果训练时对目标变量单独做了归一化反归一化也要用对应的参数。3.3 批量推理与结果保存实际使用中往往需要批量预测比如对测试集所有样本做推理并保存结果model.eval() predictions [] with torch.no_grad(): for i in range(0, len(X_test), 256): batch torch.FloatTensor(X_test[i:i256]) pred model(batch).numpy().flatten() predictions.extend(pred) predictions np.array(predictions) # 反归一化 predictions_original predictions * (scaler.data_max_[0] - scaler.data_min_[0]) scaler.data_min_[0] y_test_original y_test.flatten() * (scaler.data_max_[0] - scaler.data_min_[0]) scaler.data_min_[0] # 计算评估指标 from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score mae mean_absolute_error(y_test_original, predictions_original) rmse np.sqrt(mean_squared_error(y_test_original, predictions_original)) r2 r2_score(y_test_original, predictions_original) print(fMAE: {mae:.3f} m/s, RMSE: {rmse:.3f} m/s, R2: {r2:.3f})风速预测的 MAE 能到 0.5-1.0 m/s 就算不错R2 在 0.85 以上说明模型捕捉到了主要趋势。如果 R2 低于 0.7需要检查数据质量、seq_len 是否合理、模型是否欠拟合。4. 训练自己的权重从冻结层微调到全量训练4.1 什么时候需要重新训练如果权重文件是在公开数据集或别的场站上训练的直接用到你的场站数据上效果可能不好。风速受地形影响极大两个相距 50 公里的风电场风速分布可能完全不同。判断是否需要重训的方法很简单用你的测试集跑一遍推理如果 MAE 比原报告高 50% 以上就需要微调或重训。微调策略先冻结 LSTM 层只训练全连接层学习率设小一点1e-4跑 10-20 个 epoch。如果效果还不够再解冻所有层用更小的学习率1e-5做全量微调。这样比从头训练收敛快也不容易过拟合。# 冻结 LSTM 参数 for param in model.lstm.parameters(): param.requires_grad False # 只优化全连接层 optimizer torch.optim.Adam(model.fc.parameters(), lr1e-4) criterion nn.MSELoss() # 微调循环 for epoch in range(20): model.train() for i in range(0, len(X_train), 64): batch_x torch.FloatTensor(X_train[i:i64]) batch_y torch.FloatTensor(y_train[i:i64]) optimizer.zero_grad() output model(batch_x) loss criterion(output, batch_y) loss.backward() optimizer.step() print(fEpoch {epoch1}, Loss: {loss.item():.6f})4.2 学习率与 batch size 的实操经验LSTM 训练对学习率敏感。我一般从 1e-3 开始试如果 loss 震荡就降到 1e-4。batch size 在 32 到 128 之间显存够就大一点训练更稳。如果数据量少于 5000 条batch size 用 32 甚至 16避免每个 batch 里样本太少导致梯度噪声大。还有一个容易忽略的点梯度裁剪。LSTM 在长序列上容易出现梯度爆炸加一行torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)能明显提升训练稳定性。# 在 loss.backward() 之后、optimizer.step() 之前加 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)4.3 验证集划分与早停时间序列不能随机划分验证集。正确做法是按时间顺序切前 70% 训练中间 15% 验证最后 15% 测试。早停的 patience 设 5 到 10如果验证集 loss 连续这么多轮不下降就停。best_val_loss float(inf) patience 7 counter 0 for epoch in range(100): # 训练... # 验证... val_loss evaluate(model, X_val, y_val) if val_loss best_val_loss: best_val_loss val_loss torch.save(model.state_dict(), best_model.pth) counter 0 else: counter 1 if counter patience: print(fEarly stop at epoch {epoch1}) break5. 避坑与排查风速功率预测项目里最常见的 5 个翻车点5.1 预测曲线整体偏移MAE 正常但 R2 为负现象推理结果和真实值画在一起形状很像但整体高了一截或低了一截MAE 看起来不大但 R2 是负数。原因归一化和反归一化不一致。训练时用了 MinMaxScaler推理时可能用了 StandardScaler或者反归一化时索引取错了列。解决打印 scaler 的 data_min_ 和 data_max_确认目标变量的范围。反归一化公式是pred * (max - min) min如果目标变量在特征矩阵中的位置不是 0索引要对应。另外检查训练时是否对目标变量单独做了归一化如果是反归一化也要用单独的参数。5.2 模型在训练集上表现很好测试集一塌糊涂现象训练 loss 降到很低测试集 MAE 是训练集的 3 倍以上。原因过拟合或者数据泄漏。过拟合常见于数据量少、模型参数量大数据泄漏常见于归一化时用了全量数据 fit或者随机划分了时间序列。解决先检查划分方式确保按时间顺序切分。然后看模型参数量如果 LSTM hidden_dim 是 512 而样本只有几千条降到 128 或 64。加 Dropout0.2-0.3和 L2 正则化也有帮助。数据量实在少的话考虑用 GRU 替代 LSTM参数量更小。5.3 风速预测还行转成功率后误差巨大现象风速 MAE 只有 0.6 m/s但功率 MAE 超过额定功率的 20%。原因功率曲线不准确或者风速在额定风速附近时小的风速误差被立方放大。解决不要用理论功率曲线从历史数据拟合。拟合时注意剔除限电和停机数据。另外在额定风速附近风速误差 0.5 m/s 可能导致功率误差 10% 以上这是物理规律决定的只能通过提高风速预测精度来缓解。如果功率预测要求高建议直接以功率为目标变量训练模型而不是先预测风速再转换。5.4 推理速度慢单条预测要几秒钟现象批量预测时速度还行但单条实时预测延迟很高。原因每次推理都重新加载模型或者没有用torch.no_grad()或者 batch size 为 1 时 GPU 利用率低。解决模型只加载一次放在全局变量里。推理时用with torch.no_grad():关闭梯度计算。如果部署在 CPU 上考虑用 ONNX Runtime 或 TorchScript 加速。单条预测延迟在 CPU 上应该控制在 50ms 以内超过的话检查是不是 seq_len 设得太长。5.5 换了台机器加载权重报错键名不匹配现象在训练机器上能加载换一台机器或换 PyTorch 版本后报Missing key(s) in state_dict或Unexpected key(s)。原因PyTorch 版本差异导致键名前缀变化或者模型定义代码和权重保存时的结构不一致。解决先用torch.load打印键名和模型定义的state_dict键名逐个对比。如果是module.前缀问题多卡训练保存的权重加载时去掉前缀state_dict torch.load(wind_lstm.pth, map_locationcpu) new_state_dict {k.replace(module., ): v for k, v in state_dict.items()} model.load_state_dict(new_state_dict)如果是键名完全对不上说明模型结构定义错了需要根据权重形状反推正确的层数和维度。6. 把预测误差再降 10%特征工程与后处理的两个实用技巧6.1 加入滞后特征和滑动窗口统计量原始特征只有当前时刻的风速、风向、温度、气压模型能学到的信息有限。加入滞后特征和滑动统计量相当于给模型提供了更丰富的上下文。# 滞后特征过去 1、2、3、6 个时间步的风速 for lag in [1, 2, 3, 6]: df[fwind_speed_lag{lag}] df[wind_speed].shift(lag) # 滑动统计量过去 6、12、24 步的均值和标准差 for window in [6, 12, 24]: df[fwind_speed_roll{window}_mean] df[wind_speed].rolling(window).mean() df[fwind_speed_roll{window}_std] df[wind_speed].rolling(window).std() # 去掉因 shift 和 rolling 产生的 NaN df df.dropna().reset_index(dropTrue)这些特征在风速突变前往往有征兆滑动标准差突然增大说明风速波动加剧接下来可能有大风或风速骤降。模型学到这些模式后对突变天气的预测会明显改善。注意滞后和滑动窗口的计算必须在归一化之前做否则统计量会被归一化扭曲。6.2 预测结果的后处理滑动平均与偏差校正模型输出往往有高频噪声直接使用会导致功率预测在相邻时间步之间跳变。简单滑动平均可以平滑曲线def smooth_predictions(preds, window3): 对预测序列做滑动平均window 为奇数 smoothed np.convolve(preds, np.ones(window)/window, modesame) # 处理边界两端用原始值 half window // 2 smoothed[:half] preds[:half] smoothed[-half:] preds[-half:] return smoothed偏差校正则是针对系统性误差。如果模型在某个风速区间 consistently 高估或低估可以按区间做线性校正# 按风速分箱计算平均偏差 bins [0, 3, 6, 9, 12, 15, 25] df_test[bin] pd.cut(y_test_original, bins) bias df_test.groupby(bin).apply(lambda x: (x[pred] - x[true]).mean()) # 校正 def correct_bias(pred, wind_speed, bias_dict): for interval, b in bias_dict.items(): if wind_speed in interval: return pred - b return pred偏差校正要在验证集上计算不能直接在测试集上算否则又是数据泄漏。我一般会在验证集上跑一遍把每个风速区间的平均偏差存下来推理时查表校正。这个操作通常能把 MAE 再降 5% 到 10%尤其是模型在低风速段和高风速段表现差异大的时候。最后说一个我自己的习惯每次跑完推理先把预测值和真实值按时间画出来肉眼扫一遍。如果发现某段时间预测曲线明显滞后于真实曲线说明 seq_len 可能太短模型看不到足够的历史信息如果预测曲线过于平滑真实曲线的尖峰都没跟上说明模型容量不够或者正则化太强。这些直觉判断比单看 MAE 数字更有用。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑