ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

CNN-LSTM光伏发电量预测实战:从数据预处理到模型对比避坑指南

CNN-LSTM光伏发电量预测实战:从数据预处理到模型对比避坑指南 简介面向毕业设计场景的深度学习光伏发电量预测项目整合CNN-LSTM、CNN-GRU、GRU、LSTM、LSTM_transform五种模型既有完整Python源码与可直接训练的数据集也存有训练好的h5权重文件适合自动化、电气及人工智能方向学生完成时间序列预测课题或研究生快速搭建基线模型。资源压缩包共70个文件约10.08MB核心包括8个py脚本数据建表、入库、预测、评估等、8个csv测试集预测结果、5个pkl归一化参数、5个h5模型权重以及大量png训练/预测对比图、xlsx原始功率数据和模型说明文档目录按模型分别组织便于对照复现。目前已有89人学习除源代码和数据集外还给出各模型在测试集上的预测与真实结果对比CSV、训练损失曲线、结果对比图和eval_results评估文件可直观比较CNN-LSTM与GRU等结构在光伏功率预测上的差异。对于需要撰写毕设或课程设计的学生这套资料提供从数据预处理、模型训练到评估展示的完整链路能够帮助快速理解深度学习在光伏功率预测中的落地流程。1. 毕业设计做光伏发电量预测CNN-LSTM 能跑通的不少能讲清楚的不多拿到“基于深度学习CNN LSTM网络的光伏发电量预测”这个题目意味着你的任务很明确交出一套能跑通、能出对比图、能写进论文的源代码同时把数据集、模型权重和结果图表整理成完整交付物。光伏发电量预测是典型的时间序列回归问题输入历史功率和气象数据输出未来一段时间的功率值。别被标题里“多个模型”吓住CNN-LSTM、CNN-GRU、GRU 在 Keras 里都是三五层的事。真正让答辩现场被问住的往往是数据泄漏、预测曲线右移、反归一化错位这类“看着对、细想错”的细节。这篇笔记适合想用深度学习完成毕设的学生也适合刚接触时间序列预测的工程师拿来练手。2. CNN-LSTM 的选型逻辑定指标、定输入再谈模型结构2.1 先定义“预测什么”单步预测还是多步预测光伏发电量预测的“量”通常指某电站未来一段时间的有功功率输出单位是 kW 或 MW。动手写模型之前要先和导师确认输出口径是预测未来 15 分钟一个点还是预测未来 24 小时每 15 分钟一个点前者是单步预测后者是多步预测模型尾部的结构完全不同。单步预测的最后一层 Dense 输出 1 个值多步预测可以输出一个序列长度也可以用自回归方式一步步滚动预测。评价指标在项目一开始就定下来不要训练完再挑“哪个好看用哪个”。光伏领域常用三个RMSE、MAE、R²。RMSE 对峰值误差敏感光伏出力中午高、早晚低RMSE 能体现极端偏差MAE 稳定但容易被夜间零功率样本稀释R² 在答辩时一定会被问“为什么不是越高越好”因为光伏受天气影响本身方差大一年数据里有阴天有晴天R² 能到 0.85 以上就算可用。指标公式在光伏预测里的特点RMSEsqrt(mean((y - y_hat)^2))对大偏差惩罚重突出中午峰值误差MAEmean(abs(y - y_hat))对异常值不敏感但夜间零值会拉低它R²1 - SS_res / SS_tot与数据方差相关不同天气组合下不可直接比较我一般建议训练时用 MSE 做 loss测试集上汇报 RMSE 和 R²。训练用 MSE 的原因很简单MSE 和 RMSE 单调同向梯度比 MAE 稳定。不要在训练时用 MAE、汇报时又换成 RMSE两者最优解不完全一致最后你会说不清模型到底在优化什么。2.2 CNN 提局部特征LSTM 记时序依赖组合的意义在哪里单看结构CNN-LSTM 就是把一维卷积接在 LSTM 前面。为什么要加这一层卷积纯 LSTM 是把每个时刻的所有特征直接塞进循环单元特征之间的局部相关性需要模型自己从零学而光伏功率曲线有很强的局部形态比如一片云飘过来辐照度在 15 分钟内骤降功率跟着下跌这属于“相邻时间步之间的相关性”。卷积核做的就是这件事把相邻 3 到 5 个时间步的特征融合成一个局部特征向量LSTM 再在这个特征序列上建模长程依赖。另一个常被忽略的点是组件温度的迟滞效应。高辐照度不一定对应高发电量因为组件温度升高会降低光电转换效率功率曲线和辐照度曲线之间存在明显的相位差。这种“延迟响应”是标准循环网络能学会的但纯 LSTM 需要更长的训练时间才能学到CNN 先做了局部平滑等于是把输入做了一次特征工程循环层的负担小很多。实际工程里在天气突变日CNN-LSTM 的 RMSE 比纯 LSTM 低 5% 到 10% 是很常见的结论这也是毕设里把它当主模型的理由。2.3 模型族谱GRU、LSTM-transform 和 LSTM 各站什么位置标题里列了五个模型第一反应是“我得写五个不同结构的网络”。其实它们可以归成三类LSTM 和 GRU 是基础循环结构CNN-LSTM 和 CNN-GRU 是卷积加循环的组合LSTM-transform 是循环加注意力机制的变体。GRU 比 LSTM 少一个门参数量约为 LSTM 的 3/4在小数据集上更不容易过拟合训练也更快LSTM-transform 常见做法是在 LSTM 后面接一个多头自注意力层来对时间步做变换效果看天气突变场景有提升但毕设级数据量下收益有限更适合当作“探索性对比”放在论文里。模型参数量适合场景训练速度GRU较小数据量小、基线模型最快LSTM较大长序列、复杂时间依赖中等CNN-GRU中小局部特征加时序求快较快CNN-LSTM较大局部特征加时序求稳中等LSTM-transform最大想加注意力、序列长最慢在一年 15 分钟粒度的数据量下LSTM 和 GRU 的精度差距很小GRU 反而更省时间。写论文时把 GRU 定位为“轻量基线”把 CNN-LSTM 定位为“主模型”把 LSTM-transform 定位为“注意力机制尝试”这个逻辑比“我把五个模型都跑了一遍”更有说服力。3. 数据准备与滑窗构造归一化拟合在前时间切分在后3.1 光伏数据集长什么样怎么清洗常见做法是用某光伏电站的公开实测数据CSV 里一般包含时间戳、总辐照度、环境温度、组件温度、湿度、风速、风向、实际有功功率这 8 个字段。采集间隔通常为 15 分钟一天 96 个点一年大约 3.5 万条。注意区分辐照度和发电量辐照度是 W/m²功率是 kW发电量是功率对时间的积分三者不是一回事论文里写错了会被问住。清洗要做三件事。第一索引按时间排序很多公开数据的时间戳不是严格有序的先 sort_values 再 reset_index。第二辐照度小于等于 0 且功率大于 0 的样本把功率归零这是夜里或运维状态的错误记录。第三功率出现负值的片段直接删掉或用邻近值插值光伏板不会反向发电负功率通常是仪表异常。清洗这块没有太多玄学做扎实就行。3.2 特征列与滑窗样本生成滑窗是时间序列预测的基本操作。用过去 24 个时间点也就是 6 小时预测未来 1 个时间点窗口按步长 1 滑动得到 X 的形状是 (样本数, 24, 特征数)y 的形状是 (样本数, 1)。窗口里放哪些特征直接决定模型能学到的信息上限。公共做法是把辐照度、温度、湿度、风速、历史功率都放进去风向和组件温度看情况。import pandas as pd import numpy as np df pd.read_csv(pv_data.csv, parse_dates[time]) df df.sort_values(time).reset_index(dropTrue) # 简单清洗无辐照时功率应接近 0 df.loc[df[irradiance] 0, power] 0 features [ irradiance, ambient_temp, module_temp, humidity, wind_speed, power ] data df[features].values.astype(float32) def make_sequences(data, input_len24, output_len1, step1): X, y [], [] for i in range(0, len(data) - input_len - output_len 1, step): X.append(data[i:i input_len]) # 输入窗口 y.append(data[i input_len:i input_len output_len, -1]) return np.array(X), np.array(y) X, y make_sequences(data, input_len24, output_len1) print(X.shape, y.shape)把 power 列放在 features 最后一位取 y 时直接用 -1 索引这是代码里最顺手但最容易改错的细节。滑窗会生成大量重复样本一年数据没压力如果换成三年数据建议用 tf.keras.utils.Sequence 写生成器避免一次性把全量数组加载进内存。3.3 MinMax 归一化的先后顺序训练段拟合再全量变换归一化要用 MinMaxScaler 把每列压到 [0, 1]但顺序必须对先把数据集按时间切成训练、验证、测试三段再用训练段拟合 scaler最后用这个 scaler 变换三段。如果先归一化再切分测试段的最大辐照度会参与缩放边界计算等效于把未来信息带回了训练验证指标会虚高这就是典型的数据泄漏。from sklearn.preprocessing import MinMaxScaler # 按时序切分7:2:1 n len(X) train_len int(n * 0.7) val_len int(n * 0.2) X_train, y_train X[:train_len], y[:train_len] X_val, y_val X[train_len:train_len val_len], y[train_len:train_len val_len] X_test, y_test X[train_len val_len:], y[train_len val_len:] # X 按特征维度缩放 scaler_X MinMaxScaler(feature_range(0, 1)) scaler_y MinMaxScaler(feature_range(0, 1)) train_flat X_train.reshape(-1, X_train.shape[-1]) scaler_X.fit(train_flat) # 只 fit 训练段 X_train scaler_X.transform(X_train.reshape(-1, X_train.shape[-1])).reshape(X_train.shape) X_val scaler_X.transform(X_val.reshape(-1, X_val.shape[-1])).reshape(X_val.shape) X_test scaler_X.transform(X_test.reshape(-1, X_test.shape[-1])).reshape(X_test.shape) # y 单独缩放预测后单独反归一化 scaler_y.fit(y_train.reshape(-1, 1)) y_train scaler_y.transform(y_train.reshape(-1, 1)) y_val scaler_y.transform(y_val.reshape(-1, 1)) y_test scaler_y.transform(y_test.reshape(-1, 1))X 和 y 分开缩放这一点很多人忽略。如果你只用一个 MinMaxScaler 去 fit 整个特征矩阵预测完之后对一列 y 做 inverse_transform 会因列数不匹配直接报错就算你 reshape 成完整特征数功率列的位置也可能错位。单独为 y 建一个 scaler后面预测结果还原时才不会乱。4. 用 Keras 一次跑通 CNN-LSTM、CNN-GRU、GRU 和 LSTM-transform4.1 公共参数与输入形状模型的输入形状是 (batch, input_len, n_features)input_len 就是滑窗的 24n_features 是特征数 6output_len 是预测步长 1。写四个模型之前先约定公共变量避免每个模型里改来改去改出不一致。input_len X_train.shape[1] # 24 n_features X_train.shape[-1] # 6 output_len 1 from tensorflow.keras.models import Sequential, Model from tensorflow.keras.layers import ( Conv1D, MaxPooling1D, LSTM, GRU, Dense, Dropout, LayerNormalization, MultiHeadAttention, Input )四个模型共用同一份训练好的 X_train、X_val、X_test对比才有意义。我见过有人给不同模型用了不同的归一化方式最后对比表格里的 RMSE 差出几个量级那不是模型差异是数据处理差异。4.2 CNN-LSTM 主模型定义CNN-LSTM 作为主模型第一个 Conv1D 在 24 步窗口内做局部特征提取后面接两层 LSTM 建模时间依赖最后用全连接层输出预测功率。model_cnn_lstm Sequential([ Conv1D(filters64, kernel_size3, paddingsame, activationrelu, input_shape(input_len, n_features)), MaxPooling1D(pool_size2), LSTM(units64, return_sequencesTrue), Dropout(0.2), LSTM(units32, return_sequencesFalse), Dense(units16, activationrelu), Dense(unitsoutput_len) ]) model_cnn_lstm.compile(optimizeradam, lossmse, metrics[mae]) model_cnn_lstm.summary()Conv1D 的 filters 取 64相当于用 64 个卷积核分别从“辐照度、温度、历史功率”的组合中提取局部模式kernel_size3 表示看相邻 3 个时间步也就是 45 分钟。第一层 LSTM 必须设置 return_sequencesTrue因为后面还要接第二层 LSTM如果只有一层 LSTM这里就设 False。Dropout(0.2) 加在两层 LSTM 之间目的是抑制时序层的过拟合不要放在 Conv1D 前面。4.3 GRU 与 CNN-GRU轻量基线GRU 参数更少收敛更快适合当基线。CNN-GRU 的结构和 CNN-LSTM 几乎一样只是把循环单元换成 GRU可以看训练时间能省多少。model_gru Sequential([ GRU(units64, input_shape(input_len, n_features), return_sequencesFalse), Dense(units16, activationrelu), Dense(unitsoutput_len) ]) model_cnn_gru Sequential([ Conv1D(filters64, kernel_size3, paddingsame, activationrelu, input_shape(input_len, n_features)), MaxPooling1D(pool_size2), GRU(units64, return_sequencesFalse), Dropout(0.2), Dense(units16, activationrelu), Dense(unitsoutput_len) ])GRU 这里只用了单层原因是在一年的光伏数据上单层 GRU 的拟合能力已经够了再加一层收益很小。CNN-GRU 的精度和 CNN-LSTM 相近但训练时间能减少 20% 上下。如果你的电脑没独立显卡跑四个模型时建议按 GRU、CNN-GRU、CNN-LSTM、LSTM-transform 的顺序前两个调通流程后两个直接复用数据处理代码。4.4 LSTM-transform循环加自注意力的变体LSTM-transform 这个叫法在不同源码里并不统一。常见做法是先让 LSTM 编码序列再经过一个多头自注意力层对时间步做加权变换最后接全局特征输出。用函数式 API 写比 Sequential 灵活因为要构建残差连接。inputs Input(shape(input_len, n_features)) x LSTM(units64, return_sequencesTrue)(inputs) # 自注意力让模型自己决定哪几个时间步更重要 attn MultiHeadAttention(num_heads4, key_dim64)(x, x) x LayerNormalization()(x attn) x LSTM(units32, return_sequencesFalse)(x) x Dense(units16, activationrelu)(x) outputs Dense(unitsoutput_len)(x) model_lstm_tf Model(inputs, outputs) model_lstm_tf.compile(optimizeradam, lossmse, metrics[mae])MultiHeadAttention 的两个输入都传 x意思是用序列自身做注意力权重分配也就是自注意力。残差连接要求 x 和 attn 形状一致所以第一个 LSTM 必须 return_sequencesTrue如果你把 LSTM 的 units 改了残差连接会因形状不匹配直接报错。key_dim64 能被 num_heads4 整除这是多头注意力计算的基本约束不想记规则就把 key_dim 设成 num_heads 的整数倍。在本科毕设的数据量上这个模型不容易比 CNN-LSTM 更好反而更慢适合写进“对比实验”而不是当主角。4.5 训练与早停不要手动盯 epoch四个模型共用一套训练逻辑。EarlyStopping 监听验证集 loss连续 10 个 epoch 没下降就停并恢复到验证集最优的权重。光伏数据一般 20 到 50 个 epoch 就收敛设 epochs100 是上界。from tensorflow.keras.callbacks import EarlyStopping callbacks [ EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue) ] def train_model(model, X_tr, y_tr, X_va, y_va, epochs100): history model.fit( X_tr, y_tr, validation_data(X_va, y_va), epochsepochs, batch_size128, callbackscallbacks, verbose1 ) return history history_cnn_lstm train_model(model_cnn_lstm, X_train, y_train, X_val, y_val) history_gru train_model(model_gru, X_train, y_train, X_val, y_val) history_cnn_gru train_model(model_cnn_gru, X_train, y_train, X_val, y_val) history_lstm_tf train_model(model_lstm_tf, X_train, y_train, X_val, y_val)batch_size128 在 3.5 万条样本上是稳妥选择显存不够就降到 64。如果训练 loss 一直震荡不下降先看学习率是不是默认的 0.001 对当前数据太大改成 0.0005 再试。不要一上来就调网络层数先把数据处理和训练流程稳定下来。5. 避坑清单五个把“测试集预测”做假好看的常见错误5.1 数据泄漏scaler 用全量数据拟合验证集指标虚高现象是测试集 RMSE 非常好但把预测曲线和真实曲线叠在一起看峰值段偏移明显。原因是归一化在切分之前做MinMaxScaler 记录了全量数据的最大值和最小值测试段的最大辐照度参与了缩放边界计算。模型在训练时等效地“见过”未来数据的统计范围验证指标自然好看。解决方法是严格按第 3.3 节顺序先切分再只对训练段 fit最后统一 transform。这一步是毕设里最常见的作弊位置不是故意的但答辩老师一眼就能看出来。5.2 时间序列随机切分把未来数据混进了训练集现象是训练和验证阶段的 R² 都很高换到测试段误差忽然变大而且没有规律。原因是用 sklearn 的 train_test_split 切分时间序列默认 shuffleTrue把打乱的样本送进训练集使得同一段天气过程同时出现在训练集和验证集里。解决方法是按时间顺序做切片训练占 70%、验证占 20%、测试占 10%并且切分时不打乱顺序。这不算高深技巧但每年都有翻车案例。5.3 预测曲线整体右移一个时间点现象是预测曲线和真实曲线形状几乎完全重叠但滞后了一个时间步RMSE 看着还挺小。原因是滑窗索引写错了常见错误是 y 取了 i 时刻的功率而 X 也包含 i 时刻的功率模型学到的不是预测而是“复制上一时刻的值”。解决方法是检查 make_sequences 里的索引X 取 i 到 iinput_leny 必须从 iinput_len 开始取。验证方法是把预测序列整体平移一个点后再算 RMSE如果平移后误差显著更小说明模型学的就是滞后复制。5.4 把夜间零功率样本算进评价指标现象是 RMSE 数字很漂亮但白天 10 点到 14 点的峰值段误差很大。原因是夜间辐照度为 0、功率也为 0 的样本占了一半以上这些零值把均方误差稀释掉了。解决方法是把评价指标分成两套一套是全时段指标用于和文献对比另一套只统计白天样本也就是辐照度大于 0 的时段。论文里写清楚你用哪一套并解释夜间时段在光伏预测里本来就没有难度。5.5 反归一化错位预测功率和真实功率不在一个量级现象是预测曲线被压缩在 0 到 1 之间或者整体比真实功率低一个数量级。原因是直接拿整个特征矩阵的 scaler 对单列 y 做 inverse_transform列数对不上或列位置错位。解决方法是训练前单独建 scaler_y预测后单独还原y_pred model_cnn_lstm.predict(X_test) # 反归一化回真实功率单位 y_pred_inv scaler_y.inverse_transform(y_pred) y_test_inv scaler_y.inverse_transform(y_test) print(f预测功率范围: {y_pred_inv.min():.2f} ~ {y_pred_inv.max():.2f} kW) print(f真实功率范围: {y_test_inv.min():.2f} ~ {y_test_inv.max():.2f} kW)注意 inverse_transform 接收的输入必须是二维列向量形状为 (样本数, 1)不要传一维数组。这个错位问题在模型跑通后特别常见也是结果对比阶段最容易让人怀疑“数据造假”的地方。6. 测试集结果对比与调优先理清单位、反归一化和验证顺序测试集模型对比是这个项目的最后一步也是最容易出成果图的地方。先把三件事做对第一单位统一训练时如果用 kW图和表里就都是 kW不要描述写 kW、坐标轴写 MW。第二把反归一化后的预测值和真实值放进同一个 DataFrame按时间戳对齐方便逐段检查。第三先抽 500 个测试样本把对比图画通再画全量数据否则一整年的预测曲线会在 matplotlib 里压成一团黑线什么都看不出来。对比表格建议用 sklearn.metrics 直接算不要手写公式算错。每个模型记录三列RMSE、MAE、R²。R² 用 r2_score 计算它返回的是 0 到 1 之间的拟合优度。调参顺序上我一般先固定学习率 0.001 和 batch_size 128把四个模型跑通再逐个调 CNN 的 filters、LSTM 的 units最后才碰 LSTM-transform 里的 num_heads。调参这件事很看缘分但有个血泪经验不要同时改两个变量否则出问题了根本不知道是哪一个导致的。最终交付时把源代码、数据集、模型权重和结果对比图分目录放好。我的个人习惯是固定随机种子把每个模型的 seed、训练轮数、验证集 loss 写进一个 CSV预测曲线单独存一张 png这样答辩前一晚只需要检查一件事测试集预测曲线有没有滞后。这个破绽是最容易被老师一眼看穿的。希望帮到你。本文还有配套的精品资源点击获取
返回列表