预测类创新算法 TCN-GRU/BiGRU
TCN-GRU和TCN-BiGRU在时序预测中至关重要,因为它们结合了TCN的长依赖建模能力与GRU/BiGRU的动态序列处理优势。TCN能够通过膨胀卷积捕捉长时间跨度的信息,而GRU和BiGRU则能更有效地处理时间序列中的短期和长期依赖关系。特别是BiGRU还能同时考虑前向和后向的信息流。这种组合不仅提高了模型的预测精度,还增强了处理复杂时序数据的能力,使其在实际应用中表现优异。接下来我们将详细学习这两种算法案例,文末附注代码和案例数据!
目录
一、TCN算法
(1)算法原理
TCN(Temporal Convolutional Network,时序卷积神经网络) 模型在一维因果卷积的基础上,结合了膨胀卷积和残差连接,通过并行卷积处理数据,从而在时间步上提取特征。其完整结构如图 1 所示。
图1:时序卷积神经网络基本结构
因果膨胀卷积由因果卷积和膨胀卷积组成,结构如图 2 所示。
图2:因果膨胀卷积
因果卷积由一维卷积组成,其输出基于上一层当前时刻和更早时刻的输入联合生成,是一种严格遵循时间约束的模型,适用于挖掘时序数据的潜在特征。膨胀卷积最初为解决图像领域在采样过程中信息丢失的问题而提出。与传统只关注局部邻域的卷积层不同,膨胀卷积能够在更大的感受野内获取信息,从而更好地捕捉序列中的长期依赖关系。
(2)算法特点
-
并行计算能力:相比传统的循环神经网络(RNN),TCN通过卷积操作实现高效的并行计算。这使得TCN在大规模数据的训练和推理中更具优势,能够更好地利用现代硬件的并行计算能力。
-
长依赖关系建模:传统卷积操作在局部感受野内工作,原始TCN因此难以捕捉长序列数据中的长期依赖关系。然而,通过采用深层TCN和膨胀卷积(dilated convolutions)等技术,TCN能够扩大感受野并增加信息传递的距离,从而更有效地捕捉和建模长期依赖关系。
-
多尺度信息提取:TCN能够通过堆叠多个卷积层提取不同尺度的特征。每个卷积层使用不同大小的卷积核处理输入,从而捕捉序列数据中不同时间范围的特征。这种多尺度信息提取能力使得TCN对序列数据中的局部依赖关系更加敏感。
-
输入长度灵活性:与传统的RNN相比,TCN在处理变长输入序列时更加灵活。TCN可以处理固定长度和可变长度的序列数据,这种灵活性使得TCN在处理不同长度的序列数据时更加便捷。
-
适用于各种序列建模任务:得益于TCN的并行计算能力和长依赖关系建模能力,它适用于语音识别、自然语言处理、动作识别、时间序列预测等多种序列建模任务。在某些任务中,TCN的性能可能与传统RNN相当,甚至更优。
二、TCN-GRU算法
(1)GRU算法
GRU(Gated Recurrent Unit)网络是一种循环神经网络(Recurrent Neural Network, RNN)的变体,采用门控机制(gated mechanism)来控制信息流动和记忆。与传统的RNN相比,GRU能够更有效地处理长期依赖(long-term dependencies)问题;同时,相较于LSTM(Long Short-Term Memory),GRU结构更为简单,参数更少,计算也更高效。
(2)TCN-GRU
-
TCN层:输入时间序列数据,首先通过一系列的因果卷积和扩张卷积操作,提取多尺度的时间特征。由于TCN可以并行计算且具有较大的感受野,因此在处理长时间依赖时,能够比传统的RNN结构更高效。
-
GRU层:TCN层的输出被传递到GRU层,GRU通过其门机制进一步捕捉数据中的序列依赖性,尤其是处理长程依赖的情况下非常有效。GRU层会根据前面的时间步的信息动态更新其隐藏状态,从而生成最终的时间序列预测结果。
-
输出层:GRU层的输出经过一个或多个全连接层(可能包含激活函数)后,产生最终的预测结果。
(3)python案例
1)导入库和读取数据
import os # 导入os模块,用于操作系统功能,比如环境变量
import math # 导入math模块,提供基本的数学功能
import pandas as pd # 导入pandas模块,用于数据处理和分析
import openpyxl
import numpy as np # 导入numpy模块,用于数值计算
import matplotlib.pyplot as plt # 导入matplotlib.pyplot模块,用于绘图
from math import sqrt # 从math模块导入sqrt函数,用于计算平方根
from sklearn.preprocessing import MinMaxScaler # 导入sklearn中的MinMaxScaler,用于特征缩放
from sklearn.preprocessing import StandardScaler # 导入sklearn中的StandardScaler,用于特征标准化
from sklearn.preprocessing import LabelEncoder # 导入sklearn中的LabelEncoder,用于标签编码
from sklearn.metrics import mean_squared_error # 导入sklearn中的mean_squared_error,用于计算均方误差
from tensorflow.keras.layers import * # 从tensorflow.keras.layers导入所有层,用于构建神经网络
from tensorflow.keras.models import * # 从tensorflow.keras.models导入所有模型,用于构建和管理模型
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score # 导入额外的评估指标
from pandas import DataFrame # 从pandas导入DataFrame,用于创建和操作数据表
from pandas import concat # 从pandas导入concat函数,用于DataFrame的拼接
import keras.backend as K # 导入keras的后端接口
from scipy.io import savemat, loadmat # 从scipy.io导入savemat和loadmat,用于MATLAB文件的读写
from sklearn.neural_network import MLPRegressor # 从sklearn.neural_network导入MLPRegressor,用于创建多层感知器回归模型
from keras.callbacks import LearningRateScheduler # 从keras.callbacks导入LearningRateScheduler,用于调整学习率
from tensorflow.keras import Input, Model, Sequential # 从tensorflow.keras导入Input, Model和Sequential,用于模型构建
import mplcyberpunk
from qbstyles import mpl_style
from tensorflow import keras # 从tensorflow.keras导入所有层,用于构建神经网络
from tcn.tcn import TCN
from prettytable import PrettyTable # 可以优美的打印表格结果
# 读取数据集
dataset=pd.read_csv("股票预测.csv",encoding='gb2312')
print(dataset) # 显示dataset数据
运行结果
发布日期 开盘指数 最高指数 最低指数 成交量(亿股) 成交额(亿元) 涨跌幅(%) \
0 2022/5/20 0:00 3223.16 3267.41 3220.70 25.14 243.93 0.77
1 2022/5/19 0:00 3211.60 3265.00 3208.24 25.61 254.88 -0.86
2 2022/5/18 0:00 3255.59 3280.32 3245.07 21.21 204.77 -0.17
3 2022/5/17 0:00 3301.25 3301.25 3255.96 26.54 268.17 -1.14
4 2022/5/16 0:00 3241.51 3311.60 3221.92 30.75 301.98 3.09
... ... ... ... ... ... ... ...
5401 2000/1/7 0:00 1076.06 1117.71 1058.83 0.65 8.75 3.80
5402 2000/1/6 0:00 1025.38 1070.79 1011.00 0.30 4.21 3.58
5403 2000/1/5 0:00 1035.47 1057.85 1013.07 0.25 2.90 0.12
5404 2000/1/4 0:00 1001.98 1035.36 985.47 0.14 1.83 2.77
5405 1999/12/30 0:00 1000.00 1000.00 1000.00 0.10 1.24 0.00
收盘指数
0 3259.34
1 3234.45
2 3262.52
3 3267.97
4 3305.64
... ...
5401 1106.19
5402 1065.67
5403 1028.87
5404 1027.66
5405 1000.00
[5406 rows x 8 columns]
2)数据预处理与特征工程
# 单输入单步预测,就让values等于某一列数据,n_out = 1,n_in, num_samples, scroll_window 根据自己情况来
# 单输入多步预测,就让values等于某一列数据,n_out > 1,n_in, num_samples, scroll_window 根据自己情况来
# 多输入单步预测,就让values等于多列数据,n_out = 1,n_in, num_samples, scroll_window 根据自己情况来
# 多输入多步预测,就让values等于多列数据,n_out > 1,n_in, num_samples, scroll_window 根据自己情况来
values = dataset.values[:, 1:] # 只取第2列数据,要写成1:2;只取第3列数据,要写成2:3,取第2列之后(包含第二列)的所有数据,写成 1:
# 从dataset DataFrame中提取数据。
# dataset.values将DataFrame转换为numpy数组。
# [:,1:]表示选择所有行(:)和从第二列到最后一列(1:)的数据。
# 这样做通常是为了去除第一列,这在第一列是索引或不需要的数据时很常见。
# 确保所有数据是浮动的
values = values.astype('float32')
# 将values数组中的数据类型转换为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 = values[scroll_window * i: n_in + scroll_window * i, 0: or_dim]
h2 = h1.reshape(1, n_in * or_dim)
h3 = values[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
# 举例设置参数
n_in = 5 # 输入前5行的数据
n_out = 1 # 预测未来1个时刻的数据
or_dim = values.shape[1] # 记录特征数据维度
num_samples = 2000 # 可以设定从数据中取出多少个点用于本次网络的训练与测试。
scroll_window = 1 # 如果等于1,下一个数据从第二行开始取。如果等于2,下一个数据从第三行开始取
res = data_collation(values, n_in, n_out, or_dim, scroll_window, num_samples)
3)数据集划分与归一化
# 把数据集分为训练集和测试集
values = np.array(res)
# 将前面处理好的DataFrame(data)转换成numpy数组,方便后续的数据操作。
n_train_number = int(num_samples * 0.85)
# 计算训练集的大小。
# 设置85%作为训练集
# int(...) 确保得到的训练集大小是一个整数。
# 先划分数据集,在进行归一化,这才是正确的做法!
Xtrain = values[:n_train_number, :n_in * or_dim]
Ytrain = values[:n_train_number, n_in * or_dim:]
Xtest = values[n_train_number:, :n_in * or_dim]
Ytest = values[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_train重塑成三维格式。
# 结果是一个三维数组,其形状为[样本数量, 时间步长, 特征数量]。
vp_test = vp_test.reshape((vp_test.shape[0], n_in, or_dim))
# 将训练集的输入数据vp_test重塑成三维格式。
# 结果是一个三维数组,其形状为[样本数量, 时间步长, 特征数量]。
4)构建TCN-GRU模型
def tcn_gru_model():
# 定义一个函数来创建TCN-GRU模型
inputs = Input(shape=(vp_train.shape[1], vp_train.shape[2]))
# 创建模型的输入层
model = Sequential()
model.add(inputs)
model.add(TCN(nb_filters=10, kernel_size=2, dilations=[1, 2, 4], return_sequences=True))
model.add(GRU(units=128, return_sequences=False))
model.add(Dense(10))
model.add(LeakyReLU(alpha=0.3))
model.add(Dense(vt_train.shape[1], activation='linear'))
# 配置和训练
model.compile(optimizer='Adam', loss='mse', metrics='mae')
model.summary()
return model
# 返回构建的模型。
model = tcn_gru_model()
# 调用tcn_gru_model函数来建立TCN-GRU模型。
history = model.fit(vp_train, vt_train, batch_size=32, epochs=60, validation_split=0.25, verbose=2)
# 训练模型。指定批处理大小为32,训练轮数为60,将25%的数据用作验证集。
# verbose=2表示在训练过程中会输出详细信息。
# 绘制历史数据
plt.plot(history.history['loss'], label='train')
# 绘制训练过程中的损失曲线。
# history.history['loss']获取训练集上每个epoch的损失值。
# 'label='train''设置该曲线的标签为'train'。
plt.plot(history.history['val_loss'], label='test')
# 绘制验证过程中的损失曲线。
# history.history['val_loss']获取验证集上每个epoch的损失值。
# 'label='test''设置该曲线的标签为'test'。
plt.legend()
# 显示图例,方便识别每条曲线代表的数据集。
plt.show()
# 展示绘制的图像。
图:TCN-GRU损失曲线图
5)结果预测与评估
# 作出预测
yhat = model.predict(vp_test)
# 使用模型对测试集的输入特征(vp_test)进行预测。
# yhat是模型预测的输出值。
predicted_data = m_out.inverse_transform(yhat) # 反归一化
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)。
mse_dic.append(mse)
rmse = sqrt(mean_squared_error(actual, predicted))
# 计算均方根误差(RMSE)。
rmse_dic.append(rmse)
mae = mean_absolute_error(actual, predicted)
# 计算平均绝对误差(MAE)。
mae_dic.append(mae)
MApe = mape(actual, predicted)
# 计算平均绝对百分比误差(MAPE)。
mape_dic.append(MApe)
r2 = r2_score(actual, predicted)
# 计算R平方值(R2)。
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(Ytest, predicted_data, n_out)
# 调用evaluate_forecasts函数。
# 传递实际值(Ytest)、预测值(predicted_data)以及预测的步数(n_out)作为参数。
# 此函数将计算每个预测步长的MSE、RMSE、MAE、MAPE和R2值。
print(table) # 显示预测指标数值
测试集指标 | MSE | RMSE | MAE | MAPE | R2 |
---|---|---|---|---|---|
预测结果指标: | 26948.54 | 164.160 | 117.798 | 6.607% | 85.07% |
6)结果可视化
# %%
## 画结果图
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
# 设置matplotlib的配置,用来正常显示负号。
# 使用赛博朋克风样式
plt.style.use('cyberpunk')
# 创建一个图形对象,并设置大小为10x2英寸,分辨率为300dpi。
plt.figure(figsize=(10, 2), dpi=300)
x = range(1, len(predicted_data) + 1)
# 创建x轴的值,从1到实际值列表的长度。
plt.xticks(x[::int((len(predicted_data)+1))])
# 设置x轴的刻度,每几个点显示一个刻度。
plt.tick_params(labelsize=5) # 改变刻度字体大小
# 设置刻度标签的字体大小。
plt.plot(x, predicted_data[:, ii], linestyle="--", linewidth=0.5, label='predict')
# 绘制预测值的折线图,线型为虚线,线宽为0.5,标签为'predict'。
plt.plot(x, Ytest[:, ii], linestyle="-", linewidth=0.5, label='Real')
# 绘制实际值的折线图,线型为直线,线宽为0.5,标签为'Real'。
plt.rcParams.update({'font.size': 5}) # 改变图例里面的字体大小
# 更新图例的字体大小。
plt.legend(loc='upper right', frameon=False)
# 显示图例,位置在图形的右上角,没有边框。
plt.xlabel("Sample points", fontsize=5)
# 设置x轴标签为"样本点",字体大小为5。
plt.ylabel("value", fontsize=5)
# 设置y轴标签为"值",字体大小为5。
if n_out == 1: # 如果是单步预测
plt.title(f"The prediction result of TCN-GRU :\nMAPE: {mape(Ytest[:, ii], predicted_data[:, ii])} %")
else:
plt.title(f"{ii+1} step of TCN-GRU prediction\nMAPE: {mape(Ytest[:,ii], predicted_data[:,ii])} %")
# plt.xlim(xmin=600, xmax=700) # 显示600-1000的值 局部放大有利于观察
# 如果需要,可以取消注释这行代码,以局部放大显示600到700之间的值。
# plt.savefig('figure/预测结果图.png')
# 如果需要,可以取消注释这行代码,以将图形保存为PNG文件。
plt.ioff() # 关闭交互模式
plt.show()
# 显示图形。
图:TCN-GRU预测结果图
三、TCN-BiGRU算法
(1)BiGRU算法
GRU 是沿单一方向传输信息的,它仅获取当前时间之前的历史信息,从而忽略了未来信息,可能导致丢失重要信息。为了解决这一问题,提出了 BiGRU 网络,其结构如图 3 所示。BiGRU 由一个前向 GRU 隐藏层和一个后向 GRU 隐藏层组成,能够同时捕获先前和未来的信息特征,充分利用序列信息以便更好地进行特征提取。
这两个隐藏层都与输出层相连接,分别在时间维度上对输入序列进行前向和后向计算,得到两个不同的隐藏层状态。随后,这两个状态向量相加,生成最终的输出。同时,每个时间步的输出也连接到最终的输出层。
(2)TCN-BiGRU
该模型充分利用了 TCN 在局部特征提取和捕捉时序数据中远距离依赖关系的能力,以及 BiGRU 在全局上下文理解和双向信息流处理上的优势,从而更全面地捕捉时序数据中的相关信息,同时更好地建模时序数据中的长期依赖关系,最终实现提高预测精度的目标。
(3)Python案例
1)导入库和读取数据
# 调用相关库
import os # 导入os模块,用于操作系统功能,比如环境变量
import math # 导入math模块,提供基本的数学功能
import pandas as pd # 导入pandas模块,用于数据处理和分析
import openpyxl
from math import sqrt # 从math模块导入sqrt函数,用于计算平方根
from numpy import concatenate # 从numpy模块导入concatenate函数,用于数组拼接
import matplotlib.pyplot as plt # 导入matplotlib.pyplot模块,用于绘图
import numpy as np # 导入numpy模块,用于数值计算
import tensorflow as tf # 导入tensorflow模块,用于深度学习
from sklearn.preprocessing import MinMaxScaler # 导入sklearn中的MinMaxScaler,用于特征缩放
from sklearn.preprocessing import StandardScaler # 导入sklearn中的StandardScaler,用于特征标准化
from sklearn.preprocessing import LabelEncoder # 导入sklearn中的LabelEncoder,用于标签编码
from sklearn.metrics import mean_squared_error # 导入sklearn中的mean_squared_error,用于计算均方误差
from tensorflow.keras.layers import * # 从tensorflow.keras.layers导入所有层,用于构建神经网络
from tensorflow.keras.models import * # 从tensorflow.keras.layers导入所有层,用于构建神经网络
from tensorflow.keras.models import * # 从tensorflow.keras.models导入所有模型,用于构建和管理模型
from sklearn.metrics import mean_squared_error, mean_absolute_error,r2_score # 导入额外的评估指标
from pandas import DataFrame # 从pandas导入DataFrame,用于创建和操作数据表
from pandas import concat # 从pandas导入concat函数,用于DataFrame的拼接
import keras.backend as K # 导入keras的后端接口
from scipy.io import savemat, loadmat # 从scipy.io导入savemat和loadmat,用于MATLAB文件的读写
from sklearn.neural_network import MLPRegressor # 从sklearn.neural_network导入MLPRegressor,用于创建多层感知器回归模型
from keras.callbacks import LearningRateScheduler # 从keras.callbacks导入LearningRateScheduler,用于调整学习率
from tensorflow.keras import Input, Model, Sequential # 从tensorflow.keras导入Input, Model和Sequential,用于模型构建
import mplcyberpunk
from qbstyles import mpl_style
from tensorflow import keras # 从tensorflow.keras导入所有层,用于构建神经网络
from tcn.tcn import TCN
from prettytable import PrettyTable #可以优美的打印表格结果
# dataset=pd.read_excel("风电场功率预测.xlsx")
dataset=pd.read_csv("股票预测.csv",encoding='gb2312')
# 参数'encoding'设置为'gbk',这通常用于读取中文字符,确保文件中的中文字符能够正确读取。
# 读取的数据被存储在名为'dataset'的DataFrame变量中。
print(dataset)#显示dataset数据
数据示例:
发布日期 开盘指数 最高指数 最低指数 成交量(亿股) 成交额(亿元) 涨跌幅(%) \
0 2022/5/20 0:00 3223.16 3267.41 3220.70 25.14 243.93 0.77
1 2022/5/19 0:00 3211.60 3265.00 3208.24 25.61 254.88 -0.86
2 2022/5/18 0:00 3255.59 3280.32 3245.07 21.21 204.77 -0.17
3 2022/5/17 0:00 3301.25 3301.25 3255.96 26.54 268.17 -1.14
4 2022/5/16 0:00 3241.51 3311.60 3221.92 30.75 301.98 3.09
... ... ... ... ... ... ... ...
5401 2000/1/7 0:00 1076.06 1117.71 1058.83 0.65 8.75 3.80
5402 2000/1/6 0:00 1025.38 1070.79 1011.00 0.30 4.21 3.58
5403 2000/1/5 0:00 1035.47 1057.85 1013.07 0.25 2.90 0.12
5404 2000/1/4 0:00 1001.98 1035.36 985.47 0.14 1.83 2.77
5405 1999/12/30 0:00 1000.00 1000.00 1000.00 0.10 1.24 0.00
收盘指数
0 3259.34
1 3234.45
2 3262.52
3 3267.97
4 3305.64
... ...
5401 1106.19
5402 1065.67
5403 1028.87
5404 1027.66
5405 1000.00
[5406 rows x 8 columns]
2) 数据预处理与特征工程
# 单输入单步预测,就让values等于某一列数据,n_out = 1,n_in, num_samples, scroll_window 根据自己情况来
# 单输入多步预测,就让values等于某一列数据,n_out > 1,n_in, num_samples, scroll_window 根据自己情况来
# 多输入单步预测,就让values等于多列数据,n_out = 1,n_in, num_samples, scroll_window 根据自己情况来
# 多输入多步预测,就让values等于多列数据,n_out > 1,n_in, num_samples, scroll_window 根据自己情况来
values = dataset.values[:,1:] # 只取第2列数据,要写成1:2;只取第3列数据,要写成2:3,取第2列之后(包含第二列)的所有数据,写成 1:
# 从dataset DataFrame中提取数据。
# dataset.values将DataFrame转换为numpy数组。
# [:,1:]表示选择所有行(:)和从第二列到最后一列(1:)的数据。
# 这样做通常是为了去除第一列,这在第一列是索引或不需要的数据时很常见。
# 确保所有数据是浮动的
values = values.astype('float32')
# 将values数组中的数据类型转换为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 = values[scroll_window*i: n_in+scroll_window*i,0:or_dim]
h2 = h1.reshape( 1, n_in*or_dim)
h3 = values[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
# 举例设置参数
n_in = 5 # 输入前5行的数据
n_out = 1 # 预测未来1个时刻的数据
or_dim = values.shape[1] # 记录特征数据维度
num_samples = 2000 # 可以设定从数据中取出多少个点用于本次网络的训练与测试。
scroll_window = 1 # 如果等于1,下一个数据从第二行开始取。如果等于2,下一个数据从第三行开始取
res = data_collation(values, n_in, n_out, or_dim, scroll_window, num_samples)
3) 数据集划分与归一化
# 把数据集分为训练集和测试集
values = np.array(res)
# 将前面处理好的DataFrame(data)转换成numpy数组,方便后续的数据操作。
n_train_number = int(num_samples * 0.85)
# 计算训练集的大小。
# 设置80%作为训练集
# int(...) 确保得到的训练集大小是一个整数。
# 先划分数据集,在进行归一化,这才是正确的做法!
Xtrain = values[:n_train_number, :n_in*or_dim]
Ytrain = values[:n_train_number, n_in*or_dim:]
Xtest = values[n_train_number:, :n_in*or_dim]
Ytest = values[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()的区别
4)调整训练数据格式
vp_train = vp_train.reshape((vp_train.shape[0], n_in, or_dim))
# 将训练集的输入数据vp_train重塑成三维格式。
# 结果是一个三维数组,其形状为[样本数量, 时间步长, 特征数量]。
vp_test = vp_test.reshape((vp_test.shape[0], n_in, or_dim))
# 将训练集的输入数据vp_test重塑成三维格式。
# 结果是一个三维数组,其形状为[样本数量, 时间步长, 特征数量]。
5)构建TCN-BiGRU模型
from keras.layers import Dense, Activation, Dropout, LSTM, Bidirectional, LayerNormalization, Input
# 从keras.layers模块导入多种层类型。
# Dense是用于创建全连接层的类。
# Activation是用于添加激活函数的层。
# Dropout是用于减少过拟合的丢弃层。
# LSTM是长短时记忆网络层,用于处理序列数据。
# Bidirectional是用于创建双向LSTM层的包装器。
# LayerNormalization是用于层级归一化的类。
# Input是用于模型输入层的函数。
from tensorflow.keras.models import Model
# 从tensorflow.keras.models模块导入Model类。
# Model是用于创建Keras函数式API模型的类。
from sklearn.model_selection import KFold
# 从sklearn.model_selection模块导入KFold类。
# KFold是一种交叉验证方法,用于评估模型的泛化能力。
def tcn_bigru_model():
# 定义一个函数来创建TCN-BiGRU模型
inputs = Input(shape=(vp_train.shape[1], vp_train.shape[2]))
# 创建模型的输入层
model = Sequential()
model.add(inputs)
model.add(TCN(nb_filters=32, kernel_size=2, dilations=[1, 2, 4], return_sequences=True))
model.add(Bidirectional((GRU(35, activation='selu', return_sequences=False))))
model.add(Dense(10))
model.add(LeakyReLU(alpha=0.3))
model.add(Dense(vt_train.shape[1], activation='linear'))
# 配置和训练
model.compile(optimizer='Adam', loss='mse', metrics='mae')
model.summary()
return model
# 返回构建的模型。
model = tcn_bigru_model()
# 调用tcn_bigru_model函数来建立TCN-BiGRU模型。
history = model.fit(vp_train, vt_train, batch_size=32, epochs=60, validation_split=0.25, verbose=2)
# 训练模型。指定批处理大小为32,训练轮数为60,将25%的数据用作验证集。
# verbose=2表示在训练过程中会输出详细信息。
6)绘制训练与验证损失曲线
# 绘制历史数据
plt.plot(history.history['loss'], label='train')
# 绘制训练过程中的损失曲线。
# history.history['loss']获取训练集上每个epoch的损失值。
# 'label='train''设置该曲线的标签为'train'。
plt.plot(history.history['val_loss'], label='test')
# 绘制验证过程中的损失曲线。
# history.history['val_loss']获取验证集上每个epoch的损失值。
# 'label='test''设置该曲线的标签为'test'。
plt.legend()
# 显示图例,方便识别每条曲线代表的数据集。
plt.show()
# 展示绘制的图像。
图:TCN-BiGRU损失曲线
7)结果预测与评估
# 作出预测
yhat = model.predict(vp_test)
# 使用模型对测试集的输入特征(vp_test)进行预测。
# yhat是模型预测的输出值。
predicted_data = m_out.inverse_transform(yhat) # 反归一化
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)。
mse_dic.append(mse)
rmse = sqrt(mean_squared_error(actual, predicted))
# 计算均方根误差(RMSE)。
rmse_dic.append(rmse)
mae = mean_absolute_error(actual, predicted)
# 计算平均绝对误差(MAE)。
mae_dic.append(mae)
MApe = mape(actual, predicted)
# 计算平均绝对百分比误差(MAPE)。
mape_dic.append(MApe)
r2 = r2_score(actual, predicted)
# 计算R平方值(R2)。
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(Ytest, predicted_data, n_out)
# 调用evaluate_forecasts函数。
# 传递实际值(Ytest)、预测值(predicted_data)以及预测的步数(n_out)作为参数。
# 此函数将计算每个预测步长的MSE、RMSE、MAE、MAPE和R2值。
print(table) # 显示预测指标数值
测试集指标 | MSE | RMSE | MAE | MAPE | R2 |
---|---|---|---|---|---|
预测结果指标: | 10542.140 | 102.67 | 73.42 | 4.10% | 94.16% |
8)结果可视化
# 画结果图
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
# 设置matplotlib的配置,用来正常显示负号。
# 使用赛博朋克风样式
plt.style.use('cyberpunk')
# 创建一个图形对象,并设置大小为10x2英寸,分辨率为300dpi。
plt.figure(figsize=(10, 2), dpi=300)
x = range(1, len(predicted_data) + 1)
# 创建x轴的值,从1到实际值列表的长度。
plt.xticks(x[::int((len(predicted_data)+1))])
# 设置x轴的刻度,每几个点显示一个刻度。
plt.tick_params(labelsize=5) # 改变刻度字体大小
# 设置刻度标签的字体大小。
plt.plot(x, predicted_data[:,ii], linestyle="--",linewidth=0.5, label='predict')
# 绘制预测值的折线图,线型为虚线,线宽为0.5,标签为'predict'。
plt.plot(x, Ytest[:,ii], linestyle="-", linewidth=0.5,label='Real')
# 绘制实际值的折线图,线型为直线,线宽为0.5,标签为'Real'。
plt.rcParams.update({'font.size': 5}) # 改变图例里面的字体大小
# 更新图例的字体大小。
plt.legend(loc='upper right', frameon=False)
# 显示图例,位置在图形的右上角,没有边框。
plt.xlabel("Sample points", fontsize=5)
# 设置x轴标签为"样本点",字体大小为5。
plt.ylabel("value", fontsize=5)
# 设置y轴标签为"值",字体大小为5。
if n_out == 1: # 如果是单步预测
plt.title(f"The prediction result of TCN-BiGRU :\nMAPE: {mape(Ytest[:, ii], predicted_data[:, ii])} %")
else:
plt.title(f"{ii+1} step of TCN-BiGRU prediction\nMAPE: {mape(Ytest[:,ii], predicted_data[:,ii])} %")
# plt.xlim(xmin=600, xmax=700) # 显示600-1000的值 局部放大有利于观察
# 如果需要,可以取消注释这行代码,以局部放大显示600到700之间的值。
# plt.savefig('figure/预测结果图.png')
# 如果需要,可以取消注释这行代码,以将图形保存为PNG文件。
plt.ioff() # 关闭交互模式
plt.show()
# 显示图形。
图:TCN-BiGRU预测结果图
有关预测问题的并基于TCN的创新算法还有TCN-LSTM、TCN-RNN以及结合注意力机制的组合优化算法,关注Easy数模,我们将持续输出紧跟学术前沿的数模方法论!
关注公众号,后台回复“TCN”即可获得本文TCN-GRU和TCN-BiGRU算法的python代码和案例数据获取方法!
参考文献
Li L, Li Y, Mao R, et al. Remaining useful life prediction for lithium-ion batteries with a hybrid model based on TCN-GRU-DNN and dual attention mechanism[J]. IEEE Transactions on Transportation Electrification, 2023, 9(3): 4726-4740.
Song Y, Luktarhan N, Shi Z, et al. TGA: a novel network intrusion detection method based on TCN, BiGRU and attention mechanism[J]. Electronics, 2023, 12(13): 2849.