想解决电力负荷预测中的非线性、非平稳难题?这篇博客介绍了一种创新混合模型,结合变分模态分解(VMD)和CNN-BiLSTM-Attention深度学习架构,实现超高精度预测!
✨ 核心亮点:
✅ VMD分解:有效分离复杂负荷信号,提取关键频率特征
✅ 混合深度学习:CNN捕捉局部模式 + BiLSTM学习长时序依赖 + 注意力机制聚焦关键时段
✅ 端到端优化:从数据预处理(滑动窗口、归一化)到模型训练(MSE损失+Adam优化),完整流程公开!
✅ 工业级评估:提供MSE、MAE、MAPE、R²多指标验证,预测误差显著低于传统方法
📊 适用场景:电网调度、能源管理、工业用电预测等,尤其适合非平稳时序数据!
结尾附完整代码!!
本博客示例代码实现了一个基于VMD(Variational Mode Decomposition,变分模态分解)、CNN(卷积神经网络)、BiLSTM(双向长短期记忆网络)和Attention(注意力机制)的电力负荷预测模型。
目录
导入库 ——
import os
import math
import pandas as pd
import openpyxl
from math import sqrt
from numpy import concatenate
import matplotlib.pyplot as plt
import numpy as np
from sklearn.preprocessing import MinMaxScaler
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import LabelEncoder
from sklearn.metrics import mean_squared_error
from tensorflow.keras.layers import *
from tensorflow.keras.models import *
from sklearn.metrics import mean_squared_error, mean_absolute_error,r2_score
from pandas import DataFrame
from pandas import concat
import keras.backend as K
from scipy.io import savemat, loadmat
from sklearn.neural_network import MLPRegressor
from keras.callbacks import LearningRateScheduler
from tensorflow.keras import Input, Model, Sequential
import mplcyberpunk
from qbstyles import mpl_style
import warnings
from prettytable import PrettyTable #可以优美的打印表格结果
from keras.layers import Dense, Activation, Dropout, LSTM, Bidirectional, LayerNormalization, Input
from tensorflow.keras.models import Model
warnings.filterwarnings("ignore")
1. 基础工具库
库名 | 作用 |
---|---|
os | 操作系统接口(如文件路径管理、环境变量读取) |
math | 提供数学函数(如sqrt ),用于计算平方根等。 |
warnings | 控制警告信息的显示(ignore 表示忽略所有警告)。 |
2. 数据处理与分析
库名 | 作用 |
---|---|
pandas (pd ) | 数据处理核心库,用于读取CSV/Excel(read_csv 、read_excel )、操作DataFrame。 |
numpy (np ) | 数值计算库,提供数组操作(如reshape 、concatenate )和数学函数。 |
scipy.io | 读写MATLAB文件(savemat 、loadmat ) |
3. 机器学习与深度学习
库名/模块 | 作用 |
---|---|
sklearn.preprocessing | 数据预处理: - MinMaxScaler :归一化到[0,1]- StandardScaler :标准化- LabelEncoder :标签编码 |
sklearn.metrics | 评估指标: - mean_squared_error (MSE)- mean_absolute_error (MAE)- r2_score (R²)。 |
sklearn.neural_network | MLPRegressor (多层感知机回归模型) |
tensorflow.keras | 深度学习模型构建: - Input 、Model 、Sequential :模型结构定义- LSTM 、Conv1D 、Dense 等:神经网络层- LearningRateScheduler :动态学习率调整 |
keras.backend (K ) | 提供底层张量操作(如自定义损失函数) |
4. 可视化
库名 | 作用 |
---|---|
matplotlib.pyplot (plt ) | 绘图库,用于绘制预测结果对比图。 |
mplcyberpunk | 为Matplotlib添加赛博朋克风格(代码中未实际生效,因样式被后续rcParams 覆盖)。 |
qbstyles (mpl_style ) | 快速设置Matplotlib样式 |
5. 其他工具
库名 | 作用 |
---|---|
prettytable | 生成美观的表格(用于打印评估指标结果)。 |
openpyxl | 读写Excel文件(被pandas 间接调用,用于读取.xlsx 文件)。 |
关键库的协同作用
-
数据流
-
pandas
读取数据 →numpy
数组转换 →sklearn
预处理 →tensorflow.keras
建模 →matplotlib
可视化。
-
-
模型结构
-
结合
Conv1D
(提取局部特征)、BiLSTM
(捕捉时序依赖)、Attention
(动态权重分配)构建混合模型。
-
-
评估与展示
-
sklearn.metrics
计算误差 →prettytable
格式化输出 →matplotlib
绘制预测曲线。
-
数据准备和预处理 ——
-
数据读取:从CSV文件读取电力负荷数据,从Excel文件读取VMD分解后的数据。
-
数据整合:将原始数据与VMD分解后的各模态分量(IMF)进行组合。
-
数据划分:将数据划分为训练集(85%)和测试集(15%)。
-
归一化处理:使用
MinMaxScaler
对训练集和测试集进行归一化处理。
数据集格式
dataset=pd.read_csv("电力负荷预测数据2.csv",encoding='gb2312')
print(dataset)#显示dataset数据
dataset_vmd = pd.read_excel("VMD.xlsx")
values_vmd = dataset_vmd.values
values_vmd = values_vmd.astype('float32')
values = dataset.values[:,1:] #只取第2列数据,要写成1:2;只取第3列数据,要写成2:3,取第2列之后(包含第二列)的所有数据,写成 1:
values = values.astype('float32')
def data_collation(data, n_in, n_out, or_dim, scroll_window, num_samples):
res = np.zeros((num_samples,n_in*or_dim+n_out))
for i in range(0, num_samples):
h1 = data[scroll_window*i: n_in+scroll_window*i,0:or_dim]
h2 = h1.reshape( 1, n_in*or_dim)
h3 = data[n_in+scroll_window*(i) : n_in+scroll_window*(i)+n_out,-1].T
h4 = h3[np.newaxis, :]
h5 = np.hstack((h2,h4))
res[i,:] = h5
return res
def attention_layer(inputs, time_steps):
# 自定义的注意力机制层
a = Permute((2, 1))(inputs) # 将特征维度和时间步维度互换
a = Dense(time_steps, activation='softmax')(a) # 使用Dense层计算时间步的权重
a_probs = Permute((2, 1), name='attention_vec')(a) # 再次互换维度,使其与原始输入对齐
output_attention_mul = Multiply()([inputs, a_probs]) # 将注意力权重应用到输入上
return output_attention_mul
def cnn_bilstm_attention_model():
# 定义一个包含CNN, LSTM和注意力机制的模型
inputs = Input(shape=(vp_train.shape[1], vp_train.shape[2]))
conv1d = Conv1D(filters=64, kernel_size=2, activation='relu')(inputs)
maxpooling = MaxPooling1D(pool_size=2)(conv1d)
reshaped = Reshape((-1, 64 * maxpooling.shape[1]))(maxpooling) # 重塑形状以适应LSTM层
lstm_out = Bidirectional(LSTM(128, return_sequences=True))(reshaped) # LSTM层
attention_out = attention_layer(lstm_out, time_steps=reshaped.shape[1]) # 注意力机制层
attention_flatten = Flatten()(attention_out) # 展平输出以适应全连接层
outputs = Dense(vt_train.shape[1])(attention_flatten) # 全连接层
model = Model(inputs=inputs, outputs=outputs)
model.compile(loss='mse', optimizer='Adam')
model.summary()
return model
#定义输入和输出
n_in = 5 # 输入前5行的数据
n_out = 1 # 预测未来1步的数据
or_dim = values.shape[1] # 记录特征数据维度
num_samples = 2000 # 可以设定从数据中取出多少个点用于本次网络的训练与测试。
scroll_window = 1 #如果等于1,下一个数据从第二行开始取。如果等于2,下一个数据从第三行开始取
n_train_number = int(num_samples * 0.85) #取出85%作为训练集,剩余的为测试集
n_test_number = num_samples - n_train_number #测试集数量
predicted_data =[]
actual_data = []
# In[7]:
for vmd_num in range (0,len(values_vmd[0])):
imf = values_vmd[:,vmd_num]
imf = imf.reshape(-1,1)
combined_data = np.hstack((values[:,0:-1],imf)) # 把VMD分解的每列数据与原始数据特征列组合到一起
res = data_collation(combined_data, n_in, n_out, or_dim, scroll_window, num_samples)
# 把数据集分为训练集和测试集
combined_data = np.array(res)
# 将前面处理好的DataFrame(combined_data)转换成numpy数组,方便后续的数据操作。
n_train_number = int(num_samples * 0.85)
Xtrain = combined_data[:n_train_number, :n_in*or_dim]
Ytrain = combined_data[:n_train_number, n_in*or_dim:]
Xtest = combined_data[n_train_number:, :n_in*or_dim]
Ytest = combined_data[n_train_number:, n_in*or_dim:]
# 归一化
m_in = MinMaxScaler()
vp_train = m_in.fit_transform(Xtrain) # 注意fit_transform() 和 transform()的区别
vp_test = m_in.transform(Xtest) # 注意fit_transform() 和 transform()的区别
m_out = MinMaxScaler()
vt_train = m_out.fit_transform(Ytrain) # 注意fit_transform() 和 transform()的区别
vt_test = m_out.transform(Ytest) # 注意fit_transform() 和 transform()的区别
vp_train = vp_train.reshape((vp_train.shape[0], n_in, or_dim))
vp_test = vp_test.reshape((vp_test.shape[0], n_in, or_dim))
model = cnn_bilstm_attention_model()
model.fit(vp_train, vt_train, batch_size=32, epochs=50, validation_split=0.25, verbose=2)
yhat = model.predict(vp_test)
yhat = yhat.reshape(num_samples-n_train_number, n_out)
yy = m_out.inverse_transform(yhat) # 反归一化
predicted_data.append(yy)
actual_data.append(Ytest)
1. data_collation(data, n_in, n_out, or_dim, scroll_window, num_samples)
作用
将原始时间序列数据转换为适合监督学习的滑动窗口格式,支持多输入多输出(MIMO)的时序预测任务。
功能流程
-
输入参数:
-
data
: 原始数据(NumPy数组)。 -
n_in
: 输入时间步数(历史窗口大小)。 -
n_out
: 输出时间步数(预测步长)。 -
or_dim
: 原始数据的特征维度(列数)。 -
scroll_window
: 滑动窗口步长(控制样本重叠程度)。 -
num_samples
: 生成的样本总数。
-
-
核心操作:
-
初始化结果数组
res
,形状为(num_samples, n_in*or_dim + n_out)
。 -
循环生成每个样本:
-
历史数据:取
scroll_window*i
到n_in+scroll_window*i
行的前or_dim
列特征,展平为一行。 -
未来数据:取未来
n_out
步的最后一列(目标值),转置后与历史数据拼接。
-
-
返回整理后的数据集。
-
关键库函数
-
numpy.zeros
: 初始化结果数组。 -
numpy.reshape
: 调整数据形状。 -
numpy.hstack
: 水平拼接特征和目标值。
整体作用
将时间序列数据转换为监督学习格式,例如:
输入:[t-4, t-3, t-2, t-1, t]
的特征 → 输出:[t+1]
的目标值。
2. attention_layer(inputs, time_steps)
作用
实现注意力机制层,动态分配不同时间步的权重,增强模型对关键时间步的关注。
功能流程
-
输入参数:
-
inputs
: 输入张量(通常是LSTM层的输出)。 -
time_steps
: 时间步数。
-
-
核心操作:
-
维度置换:通过
Permute
交换特征和时间步维度。 -
权重计算:用
Dense
层和softmax
生成时间步权重。 -
权重应用:将权重与原输入相乘,得到加权后的输出。
-
关键库函数
-
tensorflow.keras.layers.Permute
: 调整张量维度顺序。 -
tensorflow.keras.layers.Dense
: 全连接层计算注意力权重。 -
tensorflow.keras.layers.Multiply
: 对输入应用注意力权重。
整体作用
提升模型对重要时间步的敏感性,尤其在长序列中避免信息稀释。
3. cnn_bilstm_attention_model()
作用
构建结合CNN、双向LSTM和注意力机制的混合神经网络模型。
功能流程
-
输入层:接收形状为
(n_in, or_dim)
的输入。 -
CNN层:
-
Conv1D
: 提取局部时序特征(64个滤波器,核大小2)。 -
MaxPooling1D
: 降维(池化大小2)。
-
-
BiLSTM层:
-
Bidirectional(LSTM)
: 双向LSTM捕捉前后时序依赖(128个单元)。
-
-
注意力层:调用
attention_layer
动态加权时间步。 -
输出层:
Flatten
+Dense
输出预测值。
关键库函数
-
tensorflow.keras.layers.Conv1D
/MaxPooling1D
: CNN特征提取。 -
tensorflow.keras.layers.Bidirectional
: 双向LSTM。 -
tensorflow.keras.Model
: 构建模型。
整体作用
通过混合架构同时捕捉局部特征(CNN)、长期依赖(BiLSTM)和关键时间点(Attention),提升预测精度。
4. 主流程中的关键操作
数据预处理
-
VMD数据整合:
combined_data = np.hstack((values[:,0:-1], imf)) # 合并原始特征与VMD分量
-
使用
numpy.hstack
横向拼接数据。
-
-
归一化:
m_in.fit_transform(Xtrain) # 训练集归一化 m_out.transform(Xtest) # 测试集归一化
-
sklearn.preprocessing.MinMaxScaler
: 将数据缩放到 [0, 1]。
-
模型训练与预测
-
训练:
model.fit(vp_train, vt_train, batch_size=32, epochs=50)
-
tensorflow.keras.Model.fit
: 使用Adam优化器最小化MSE损失。
-
-
预测与反归一化:
yy = m_out.inverse_transform(yhat) # 将预测值还原为原始量纲
-
sklearn.preprocessing.MinMaxScaler.inverse_transform
: 反向缩放数据。
-
总结
函数/操作 | 作用 | 关键库依赖 |
---|---|---|
data_collation | 时间序列转监督学习格式 | numpy (数组操作) |
attention_layer | 动态加权关键时间步 | tensorflow.keras.layers |
cnn_bilstm_attention_model | 构建混合预测模型(CNN+BiLSTM+Attention) | tensorflow.keras |
主流程 | 数据整合、归一化、训练、预测 | sklearn 、numpy 、tensorflow |
通过组合这些函数,代码实现了:
-
数据准备(滑动窗口 + VMD分解)
-
混合建模(CNN局部特征 + BiLSTM时序依赖 + Attention关键点聚焦)
-
端到端预测(归一化→训练→反归一化→评估)。
预测指标计算 ——
pre_test = []
for i in range(0, len(predicted_data[0])):
sum = 0
for j in range(0, len(predicted_data)):
sum = sum + predicted_data[j][i]
pre_test.append(sum)
pre_test = np.array(pre_test)
res = data_collation(values, n_in, n_out, or_dim, scroll_window, num_samples)
values = np.array(res)
actual_test = values[n_train_number:, n_in*or_dim:]
actual_test = actual_test.reshape(num_samples-n_train_number, n_out)
def mape(y_true, y_pred):
# 定义一个计算平均绝对百分比误差(MAPE)的函数。
record = []
for index in range(len(y_true)):
# 遍历
temp_mape = np.abs((y_pred[index] - y_true[index]) / y_true[index])
# 计算MAPE
record.append(temp_mape)
# 将MAPE添加到记录列表中。
return np.mean(record) * 100
# 返回所有记录的平均值,乘以100得到百分比。
def evaluate_forecasts(Ytest, predicted_data, n_out):
mse_dic = []
rmse_dic = []
mae_dic = []
mape_dic = []
r2_dic = []
table = PrettyTable(['测试集指标','MSE', 'RMSE', 'MAE', 'MAPE','R2'])
for i in range(n_out):
actual = [float(row[i]) for row in Ytest] #一列列提取
predicted = [float(row[i]) for row in predicted_data]
mse = mean_squared_error(actual, predicted)
mse_dic.append(mse)
rmse = sqrt(mean_squared_error(actual, predicted))
rmse_dic.append(rmse)
mae = mean_absolute_error(actual, predicted)
mae_dic.append(mae)
MApe = mape(actual, predicted)
mape_dic.append(MApe)
r2 = r2_score(actual, predicted)
r2_dic.append(r2)
if n_out == 1:
strr = '预测结果指标:'
else:
strr = '第'+ str(i + 1)+'步预测结果指标:'
table.add_row([strr, mse, rmse, mae, str(MApe)+'%', str(r2*100)+'%'])
return mse_dic,rmse_dic, mae_dic, mape_dic, r2_dic, table
mse_dic,rmse_dic, mae_dic, mape_dic, r2_dic, table = evaluate_forecasts(actual_test, pre_test, n_out)
print(table)#显示预测指标数值
1. pre_test
数据整合
作用
将多个VMD分量的预测结果(predicted_data
)按样本点求和,得到最终预测值。
方法:
-
遍历每个样本点(
i
),累加所有VMD分量在该点的预测值(predicted_data[j][i]
)。 -
结果存储在
pre_test
中,形状为(n_test_samples, n_out)
。
关键库:
-
numpy
:用于数组操作(np.array
转换)。
2. actual_test
真实值提取
作用
从原始数据中提取测试集的真实目标值。
方法:
-
调用
data_collation
重新整理原始数据(values
)为监督学习格式。 -
取测试集部分(
n_train_number:
)的最后n_out
列作为真实值。 -
调整形状为
(n_test_samples, n_out)
。
关键库:
-
numpy
:数组切片和形状调整(reshape
)。
3. mape(y_true, y_pred)
作用
计算平均绝对百分比误差(MAPE),衡量预测值与真实值的相对偏差。
公式:
MAPE=100%n∑i=1n∣ytrue(i)−ypred(i)ytrue(i)∣MAPE=n100%i=1∑nytrue(i)ytrue(i)−ypred(i)
方法:
-
遍历每个样本点,计算绝对百分比误差并求均值。
-
结果乘以100转换为百分比。
关键库:
-
numpy
:np.abs
(绝对值)、np.mean
(求均值)。
4. evaluate_forecasts(Ytest, predicted_data, n_out)
作用
综合评估预测结果的多个指标,并以表格形式输出。
计算指标:
-
MSE(均方误差):
MSE=1n∑i=1n(ytrue(i)−ypred(i))2MSE=n1i=1∑n(ytrue(i)−ypred(i))2-
使用
sklearn.metrics.mean_squared_error
。
-
-
RMSE(均方根误差):
RMSE=MSERMSE=MSE-
通过
math.sqrt
计算。
-
-
MAE(平均绝对误差):
-
使用
sklearn.metrics.mean_absolute_error
。
-
-
MAPE:调用自定义的
mape
函数。 -
R²(决定系数):
-
使用
sklearn.metrics.r2_score
。
-
输出格式:
-
使用
prettytable.PrettyTable
生成美观的表格,包含各步预测的指标。
关键库:
-
sklearn.metrics
:计算MSE、MAE、R²。 -
math
:计算平方根(RMSE)。 -
prettytable
:格式化输出表格。
5. 模型与方法总结
使用的模型
-
核心模型:
cnn_bilstm_attention_model()
-
结构:
-
CNN层(
Conv1D
+MaxPooling1D
):提取局部时序特征。 -
BiLSTM层(
Bidirectional(LSTM)
):捕捉双向长期依赖。 -
注意力层(
attention_layer
):动态加权关键时间步。 -
全连接层(
Dense
):输出最终预测。
-
-
损失函数:MSE(均方误差)。
-
优化器:Adam。
-
评估方法
-
多指标验证:通过MSE、RMSE、MAE、MAPE、R²全面评估预测性能。
-
反归一化:将预测值从归一化范围还原到原始量纲(
MinMaxScaler.inverse_transform
)。
代码整体流程
-
数据整合:合并VMD分量与原始特征,生成监督学习格式。
-
训练与预测:用CNN-BiLSTM-Attention模型预测各VMD分量结果并求和。
-
评估:计算测试集的多个误差指标,输出表格。
关键库与函数依赖
功能 | 主要库/函数 |
---|---|
数据操作 | numpy (数组操作)、pandas (数据读取) |
机器学习 | sklearn.preprocessing.MinMaxScaler (归一化)、sklearn.metrics (评估指标) |
深度学习模型 | tensorflow.keras.layers (网络层)、tensorflow.keras.Model (模型构建) |
结果可视化 | prettytable (表格打印) |
通过结合信号分解(VMD)、混合深度学习模型和严谨的评估方法,代码实现了高精度的电力负荷预测。
结果图绘制 ——
## 结果图绘制
from matplotlib import rcParams
config = {
"font.family": 'serif',
"font.size": 10,# 相当于小四大小
"mathtext.fontset": 'stix',#matplotlib渲染数学字体时使用的字体,和Times New Roman差别不大
"font.serif": ['Times New Roman'],#Times New Roman
'axes.unicode_minus': False # 处理负号,即-号
}
rcParams.update(config)
plt.ion()
for ii in range(n_out):
plt.rcParams['axes.unicode_minus'] = False
plt.style.use('cyberpunk')
plt.figure(figsize=(10, 2), dpi=300)
x = range(1, len(actual_test) + 1)
plt.xticks(x[::int((len(actual_test)+1))])
plt.tick_params(labelsize=5) # 改变刻度字体大小
plt.plot(x, pre_test[:,ii], linestyle="--",linewidth=0.5, label='predict')
plt.plot(x, actual_test[:,ii], linestyle="-", linewidth=0.5,label='Real')
plt.rcParams.update({'font.size': 5}) # 改变图例里面的字体大小
plt.legend(loc='upper right', frameon=False)
plt.xlabel("Sample points", fontsize=5)
plt.ylabel("value", fontsize=5)
if n_out == 1: #如果是单步预测
plt.title(f"The prediction result of VMD-CNN-BiLSTM-Attention :\nMAPE: {mape(actual_test[:, ii], pre_test[:, ii])} %")
else:
plt.title(f"{ii+1} step of VMD-CNN-BiLSTM-Attention prediction\nMAPE: {mape(actual_test[:,ii], pre_test[:,ii])} %")
plt.ioff()
plt.show()
1. 全局样式设置
作用
配置Matplotlib的全局绘图样式,确保图表风格统一且支持中文/符号显示。
python
复制
rcParams.update({ "font.family": 'serif', # 使用衬线字体(如Times New Roman) "font.size": 10, # 基础字体大小 "mathtext.fontset": 'stix', # 数学字体风格 "font.serif": ['Times New Roman'], # 指定具体字体 'axes.unicode_minus': False # 解决负号显示问题 })
关键方法:
-
matplotlib.rcParams.update()
:覆盖默认绘图参数。
2. 单图绘制逻辑(循环内)
作用
为每个预测步长(n_out
)绘制一幅真实值与预测值的对比图。
分步实现:
-
初始化画布
plt.figure(figsize=(10, 2), dpi=300) # 10英寸宽、2英寸高,300DPI分辨率
-
适合横向展示长时间序列。
-
-
生成x轴坐标
x = range(1, len(actual_test) + 1) # 样本点从1开始编号
-
调整刻度
plt.xticks(x[::int((len(actual_test)+1)]) # 稀疏显示刻度标签 plt.tick_params(labelsize=5) # 刻度字体大小
-
避免x轴标签重叠。
-
-
绘制曲线
# 预测值(虚线) plt.plot(x, pre_test[:,ii], linestyle="--", linewidth=0.5, label='predict') # 真实值(实线) plt.plot(x, actual_test[:,ii], linestyle="-", linewidth=0.5, label='Real')
-
样式区别:虚线(预测)vs 实线(真实),细线宽(0.5)。
-
-
添加图例和标签
plt.legend(loc='upper right', frameon=False) # 无边框图例 plt.xlabel("Sample points", fontsize=5) # x轴标签 plt.ylabel("value", fontsize=5) # y轴标签
-
小字体(5号)避免拥挤。
-
-
动态标题
title = f"Step {ii+1} Prediction (MAPE: {mape(actual_test[:,ii], pre_test[:,ii]):.2f}%)" plt.title(title)
-
调用自定义的
mape()
函数显示误差。
-
3. 交互模式控制
作用
管理绘图窗口的交互行为。
plt.ion() # 开启交互模式(允许动态更新) plt.ioff() # 关闭交互模式(显示后阻塞) plt.show() # 显示所有图像
适用场景:
-
在Jupyter等环境中实时显示图表时使用
ion()
,脚本中可省略。
4. 关键绘图方法总结
功能 | 方法/参数 | 作用 |
---|---|---|
样式配置 | rcParams.update() | 全局设置字体、大小、数学符号等。 |
创建画布 | plt.figure(figsize, dpi) | 控制图像尺寸和分辨率。 |
绘制曲线 | plt.plot(x, y, linestyle, ...) | 绘制折线图,支持样式定制(线型、宽度、标签)。 |
刻度控制 | plt.xticks() , tick_params() | 调整刻度位置和标签字体。 |
图例与标题 | plt.legend() , plt.title() | 添加图例和动态标题(含MAPE值)。 |
交互控制 | plt.ion() , plt.ioff() | 控制是否允许绘图后继续执行代码。 |
5. 可视化效果设计
-
Cyberpunk风格
-
虽然代码中尝试设置
plt.style.use('cyberpunk')
,但实际被rcParams
覆盖,未生效。若需启用,需删除rcParams
中的字体配置。
-
-
学术图表规范
-
字体统一:强制使用Times New Roman,适合论文发表。
-
紧凑布局:小字号和细线宽避免图表元素重叠。
-
误差直观化:直接在标题中显示MAPE,便于快速评估。
-
完整代码 ——
import os
import math
import pandas as pd
import openpyxl
from math import sqrt
from numpy import concatenate
import matplotlib.pyplot as plt
import numpy as np
from sklearn.preprocessing import MinMaxScaler
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import LabelEncoder
from sklearn.metrics import mean_squared_error
from tensorflow.keras.layers import *
from tensorflow.keras.models import *
from sklearn.metrics import mean_squared_error, mean_absolute_error,r2_score
from pandas import DataFrame
from pandas import concat
import keras.backend as K
from scipy.io import savemat, loadmat
from sklearn.neural_network import MLPRegressor
from keras.callbacks import LearningRateScheduler
from tensorflow.keras import Input, Model, Sequential
import mplcyberpunk
from qbstyles import mpl_style
import warnings
from prettytable import PrettyTable #可以优美的打印表格结果
from keras.layers import Dense, Activation, Dropout, LSTM, Bidirectional, LayerNormalization, Input
from tensorflow.keras.models import Model
warnings.filterwarnings("ignore")
dataset=pd.read_csv("电力负荷预测数据2.csv",encoding='gb2312')
print(dataset)#显示dataset数据
dataset_vmd = pd.read_excel("VMD.xlsx")
values_vmd = dataset_vmd.values
values_vmd = values_vmd.astype('float32')
values = dataset.values[:,1:] #只取第2列数据,要写成1:2;只取第3列数据,要写成2:3,取第2列之后(包含第二列)的所有数据,写成 1:
values = values.astype('float32')
def data_collation(data, n_in, n_out, or_dim, scroll_window, num_samples):
res = np.zeros((num_samples,n_in*or_dim+n_out))
for i in range(0, num_samples):
h1 = data[scroll_window*i: n_in+scroll_window*i,0:or_dim]
h2 = h1.reshape( 1, n_in*or_dim)
h3 = data[n_in+scroll_window*(i) : n_in+scroll_window*(i)+n_out,-1].T
h4 = h3[np.newaxis, :]
h5 = np.hstack((h2,h4))
res[i,:] = h5
return res
def attention_layer(inputs, time_steps):
# 自定义的注意力机制层
a = Permute((2, 1))(inputs) # 将特征维度和时间步维度互换
a = Dense(time_steps, activation='softmax')(a) # 使用Dense层计算时间步的权重
a_probs = Permute((2, 1), name='attention_vec')(a) # 再次互换维度,使其与原始输入对齐
output_attention_mul = Multiply()([inputs, a_probs]) # 将注意力权重应用到输入上
return output_attention_mul
def cnn_bilstm_attention_model():
# 定义一个包含CNN, LSTM和注意力机制的模型
inputs = Input(shape=(vp_train.shape[1], vp_train.shape[2]))
conv1d = Conv1D(filters=64, kernel_size=2, activation='relu')(inputs)
maxpooling = MaxPooling1D(pool_size=2)(conv1d)
reshaped = Reshape((-1, 64 * maxpooling.shape[1]))(maxpooling) # 重塑形状以适应LSTM层
lstm_out = Bidirectional(LSTM(128, return_sequences=True))(reshaped) # LSTM层
attention_out = attention_layer(lstm_out, time_steps=reshaped.shape[1]) # 注意力机制层
attention_flatten = Flatten()(attention_out) # 展平输出以适应全连接层
outputs = Dense(vt_train.shape[1])(attention_flatten) # 全连接层
model = Model(inputs=inputs, outputs=outputs)
model.compile(loss='mse', optimizer='Adam')
model.summary()
return model
#定义输入和输出
n_in = 5 # 输入前5行的数据
n_out = 1 # 预测未来1步的数据
or_dim = values.shape[1] # 记录特征数据维度
num_samples = 2000 # 可以设定从数据中取出多少个点用于本次网络的训练与测试。
scroll_window = 1 #如果等于1,下一个数据从第二行开始取。如果等于2,下一个数据从第三行开始取
n_train_number = int(num_samples * 0.85) #取出85%作为训练集,剩余的为测试集
n_test_number = num_samples - n_train_number #测试集数量
predicted_data =[]
actual_data = []
# In[7]:
for vmd_num in range (0,len(values_vmd[0])):
imf = values_vmd[:,vmd_num]
imf = imf.reshape(-1,1)
combined_data = np.hstack((values[:,0:-1],imf)) # 把VMD分解的每列数据与原始数据特征列组合到一起
res = data_collation(combined_data, n_in, n_out, or_dim, scroll_window, num_samples)
# 把数据集分为训练集和测试集
combined_data = np.array(res)
# 将前面处理好的DataFrame(combined_data)转换成numpy数组,方便后续的数据操作。
n_train_number = int(num_samples * 0.85)
Xtrain = combined_data[:n_train_number, :n_in*or_dim]
Ytrain = combined_data[:n_train_number, n_in*or_dim:]
Xtest = combined_data[n_train_number:, :n_in*or_dim]
Ytest = combined_data[n_train_number:, n_in*or_dim:]
# 归一化
m_in = MinMaxScaler()
vp_train = m_in.fit_transform(Xtrain) # 注意fit_transform() 和 transform()的区别
vp_test = m_in.transform(Xtest) # 注意fit_transform() 和 transform()的区别
m_out = MinMaxScaler()
vt_train = m_out.fit_transform(Ytrain) # 注意fit_transform() 和 transform()的区别
vt_test = m_out.transform(Ytest) # 注意fit_transform() 和 transform()的区别
vp_train = vp_train.reshape((vp_train.shape[0], n_in, or_dim))
vp_test = vp_test.reshape((vp_test.shape[0], n_in, or_dim))
model = cnn_bilstm_attention_model()
model.fit(vp_train, vt_train, batch_size=32, epochs=50, validation_split=0.25, verbose=2)
yhat = model.predict(vp_test)
yhat = yhat.reshape(num_samples-n_train_number, n_out)
yy = m_out.inverse_transform(yhat) # 反归一化
predicted_data.append(yy)
actual_data.append(Ytest)
pre_test = []
for i in range(0, len(predicted_data[0])):
sum = 0
for j in range(0, len(predicted_data)):
sum = sum + predicted_data[j][i]
pre_test.append(sum)
pre_test = np.array(pre_test)
res = data_collation(values, n_in, n_out, or_dim, scroll_window, num_samples)
values = np.array(res)
actual_test = values[n_train_number:, n_in*or_dim:]
actual_test = actual_test.reshape(num_samples-n_train_number, n_out)
def mape(y_true, y_pred):
# 定义一个计算平均绝对百分比误差(MAPE)的函数。
record = []
for index in range(len(y_true)):
# 遍历
temp_mape = np.abs((y_pred[index] - y_true[index]) / y_true[index])
# 计算MAPE
record.append(temp_mape)
# 将MAPE添加到记录列表中。
return np.mean(record) * 100
# 返回所有记录的平均值,乘以100得到百分比。
def evaluate_forecasts(Ytest, predicted_data, n_out):
mse_dic = []
rmse_dic = []
mae_dic = []
mape_dic = []
r2_dic = []
table = PrettyTable(['测试集指标','MSE', 'RMSE', 'MAE', 'MAPE','R2'])
for i in range(n_out):
actual = [float(row[i]) for row in Ytest] #一列列提取
predicted = [float(row[i]) for row in predicted_data]
mse = mean_squared_error(actual, predicted)
mse_dic.append(mse)
rmse = sqrt(mean_squared_error(actual, predicted))
rmse_dic.append(rmse)
mae = mean_absolute_error(actual, predicted)
mae_dic.append(mae)
MApe = mape(actual, predicted)
mape_dic.append(MApe)
r2 = r2_score(actual, predicted)
r2_dic.append(r2)
if n_out == 1:
strr = '预测结果指标:'
else:
strr = '第'+ str(i + 1)+'步预测结果指标:'
table.add_row([strr, mse, rmse, mae, str(MApe)+'%', str(r2*100)+'%'])
return mse_dic,rmse_dic, mae_dic, mape_dic, r2_dic, table
mse_dic,rmse_dic, mae_dic, mape_dic, r2_dic, table = evaluate_forecasts(actual_test, pre_test, n_out)
print(table)#显示预测指标数值
## 结果图绘制
from matplotlib import rcParams
config = {
"font.family": 'serif',
"font.size": 10,# 相当于小四大小
"mathtext.fontset": 'stix',#matplotlib渲染数学字体时使用的字体,和Times New Roman差别不大
"font.serif": ['Times New Roman'],#Times New Roman
'axes.unicode_minus': False # 处理负号,即-号
}
rcParams.update(config)
plt.ion()
for ii in range(n_out):
plt.rcParams['axes.unicode_minus'] = False
plt.style.use('cyberpunk')
plt.figure(figsize=(10, 2), dpi=300)
x = range(1, len(actual_test) + 1)
plt.xticks(x[::int((len(actual_test)+1))])
plt.tick_params(labelsize=5) # 改变刻度字体大小
plt.plot(x, pre_test[:,ii], linestyle="--",linewidth=0.5, label='predict')
plt.plot(x, actual_test[:,ii], linestyle="-", linewidth=0.5,label='Real')
plt.rcParams.update({'font.size': 5}) # 改变图例里面的字体大小
plt.legend(loc='upper right', frameon=False)
plt.xlabel("Sample points", fontsize=5)
plt.ylabel("value", fontsize=5)
if n_out == 1: #如果是单步预测
plt.title(f"The prediction result of VMD-CNN-BiLSTM-Attention :\nMAPE: {mape(actual_test[:, ii], pre_test[:, ii])} %")
else:
plt.title(f"{ii+1} step of VMD-CNN-BiLSTM-Attention prediction\nMAPE: {mape(actual_test[:,ii], pre_test[:,ii])} %")
plt.ioff()
plt.show()