Python/python机器学习回归全家桶/请运行我-各种KAN组合网络.py
2025-07-13 08:55:18 +08:00

319 lines
13 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import warnings
import pandas as pd # 导入pandas模块用于数据处理和分析
import numpy as np
from sklearn.preprocessing import MinMaxScaler # 导入sklearn中的MinMaxScaler用于特征缩放
from prettytable import PrettyTable #可以优美的打印表格结果
import matplotlib.pyplot as plt # 导入matplotlib.pyplot模块用于绘图
from sklearn.metrics import mean_squared_error, mean_absolute_error,r2_score # 导入额外的评估指标
from math import sqrt # 从math模块导入sqrt函数用于计算平方根
import argparse
import time
from src.model import *
warnings.filterwarnings("ignore") #取消警告
'''
## ☆☆☆☆注意 此程序与其他程序不同此程序是基于Pytorch构建的各大网络☆☆☆☆
此程序可以实现单输出和多输出的回归。只需要改66行的n_out参数即可。n_out=1时默认最后一列为输出n_out=2时默认最后2列为输出以此类推。
'''
## 设置每个网络必要的参数这里的parser一般不需要变动。
parser = argparse.ArgumentParser()
parser.add_argument('--vision', type=bool, default=True)
parser.add_argument('--train_test_ratio', type=float, default=0.2)
parser.add_argument('--random_state', type=int, default=34)
# model
parser.add_argument('--model_name', type=str, default='BiLSTM_ekan',help='selection from LSTM,LSTM_ekan,TCN_ekan,TCN,Transformer,Transformer_ekan,BiLSTM,BiLSTM_ekan,GRU,GRU_e_kan')
parser.add_argument('--dropout', type=float, default=0.2)
parser.add_argument('--hidden_dim', type=int, default=32)
parser.add_argument('--n_layers', type=int, default=2)
##kan
parser.add_argument('--grid_size', type=int, default=200,help='grid')
##TCN
parser.add_argument('--num_channels', type=list, default=[25, 50, 25])
parser.add_argument('--kernel_size', type=int, default=3)
##transformer
parser.add_argument('--num_heads', type=int, default=4)
parser.add_argument('--hidden_space', type=int, default=32)
# training
parser.add_argument('--num_epochs', type=int, default=300)
parser.add_argument('--seed', type=int, default=1)
# optimizer
parser.add_argument('--lr', type=float, default=5e-4, help='Adam learning rate')
args = parser.parse_args(args=[])
args.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
## 读取数据,这里替换为你自己的数据即可!
dataset=pd.read_csv("共享单车租赁数据集.csv",encoding='gb2312')
# 使用pandas模块的read_csv函数读取名为"共享单车租赁数据集.csv"的文件。
# 参数'encoding'设置为'gbk',这通常用于读取中文字符,确保文件中的中文字符能够正确读取。
# 读取的数据被存储在名为'dataset'的DataFrame变量中。
print(dataset)#显示dataset数据
values = dataset.values[:,2:] #只取第2列数据要写成1:2只取第3列数据要写成2:3取第2列之后(包含第二列)的所有数据,写成 1
# 把数据集分为训练集和测试集
values = np.array(values)
# 将前面处理好的DataFramedata转换成numpy数组方便后续的数据操作。
num_samples = values.shape[0]
n_out = 1 #选择最后1列作为输出如果等于2则选择最后2列作为输出
per = np.random.permutation(num_samples) #打乱后的行号,为加强回归效果,将数据集打乱
n_train_number = per[:int(num_samples * 0.8)] #选择80%作为训练集
n_test_number = per[int(num_samples * 0.8):] #选择80%作为训练集
# 计算训练集的大小。
# 设置70%作为训练集
# int(...) 确保得到的训练集大小是一个整数。
# 先划分数据集,在进行归一化,这才是正确的做法!
Xtrain = values[n_train_number, :values.shape[1]-n_out] #取特征列
Ytrain = values[n_train_number, values.shape[1]-n_out:] #取最后n_out列为目标列
Ytrain = Ytrain.reshape(-1,n_out)
Xtest = values[n_test_number, :values.shape[1]-n_out]
Ytest = values[n_test_number, values.shape[1]-n_out:] #取最后n_out列为目标列
Ytest = Ytest.reshape(-1,n_out)
# 对训练集和测试集进行归一化
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()的区别
# In[10]:
vp_train = vp_train.reshape((vp_train.shape[0], 1, vp_train.shape[1]))
# 将训练集的输入数据vp_train重塑成三维格式。
# 结果是一个三维数组,其形状为[样本数量, 时间步长, 特征数量]。
vp_test = vp_test.reshape((vp_test.shape[0], 1, vp_test.shape[1]))
# 将训练集的输入数据vp_test重塑成三维格式。
# 结果是一个三维数组,其形状为[样本数量, 时间步长, 特征数量]。
# 设置超参数,一般就变动这里,来提升你数据的准确度
input_dim = vp_train.shape[2] #输入维度,这个一般不需要改
output_dim = n_out #输出维度,这个一般不需要改
hidden_dim = 120 # Feed Forward层Attention后面的全连接网络的隐藏层的神经元数量。该值越大网络参数量越多计算量越大。
num_layers = 2 #层数
# 转换为torch数据
X_TRAIN = torch.from_numpy(vp_train).type(torch.Tensor)
Y_TRAIN = torch.from_numpy(vt_train).type(torch.Tensor)
X_TEST = torch.from_numpy(vp_test).type(torch.Tensor)
Y_TEST = torch.from_numpy(vt_test).type(torch.Tensor)
args.model_name ='TCN_KAN' #修改这里的模型名字,即可修改模型哦!可选:
if args.model_name=='LSTM':
model = LSTM(input_dim=input_dim, hidden_dim=hidden_dim, num_layers=num_layers, output_dim=output_dim)
elif args.model_name=='LSTM_KAN':
model = LSTM_ekan(input_dim=input_dim, hidden_dim=hidden_dim, num_layers=num_layers, output_dim=output_dim)
elif args.model_name=='BiLSTM':
model = BiLSTM(input_dim=input_dim, hidden_dim=hidden_dim, num_layers=num_layers, output_dim=output_dim)
elif args.model_name=='BiLSTM_KAN':
model = BiLSTM_ekan(input_dim=input_dim, hidden_dim=hidden_dim, num_layers=num_layers, output_dim=output_dim)
elif args.model_name=='GRU':
model = GRU(input_dim=input_dim, hidden_dim=hidden_dim, num_layers=num_layers, output_dim=output_dim)
elif args.model_name=='GRU_KAN':
model = GRU_ekan(input_dim=input_dim, hidden_dim=hidden_dim, num_layers=num_layers, output_dim=output_dim)
elif args.model_name=='TCN':
model = TemporalConvNet(num_inputs=input_dim, num_outputs=output_dim ,num_channels=args.num_channels, kernel_size=args.kernel_size, dropout=args.dropout)
elif args.model_name=='TCN_KAN':
model=TemporalConvNet_ekan(num_inputs=input_dim, num_outputs=output_dim ,num_channels=args.num_channels, kernel_size=args.kernel_size, dropout=args.dropout)
elif args.model_name=='Transformer':
model = TimeSeriesTransformer(input_dim=input_dim, num_heads=args.num_heads, num_layers=num_layers, num_outputs=output_dim, hidden_space=args.hidden_space,dropout_rate=args.dropout)
elif args.model_name=='Transformer_KAN':
model = TimeSeriesTransformer_ekan(input_dim=input_dim, num_heads=args.num_heads, num_layers=num_layers, num_outputs=output_dim, hidden_space=args.hidden_space,dropout_rate=args.dropout)
else:
print('please choose correct model name')
## 损失设定
criterion = torch.nn.MSELoss()
optimiser = torch.optim.Adam(model.parameters(), lr=0.01)
## 统计MSE均方误差和R2决定系数
MSE_hist = np.zeros(args.num_epochs)
R2_hist = np.zeros(args.num_epochs)
##开始时间统计和结果保存
start_time = time.time()
result = []
##循环训练
for t in range(args.num_epochs):
y_train_pred = model(X_TRAIN)
##损失计算
loss = criterion(y_train_pred, Y_TRAIN)
##R2计算
R2 = r2_score(y_train_pred.detach().numpy(), Y_TRAIN.detach().numpy())
print("Epoch ", t, "MSE: ", loss.item(), 'R2', R2.item())
# 统计每个时间步的损失和R2指标
MSE_hist[t] = loss.item()
if R2 < 0:
R2 = 0
R2_hist[t] = R2
optimiser.zero_grad()
loss.backward()
optimiser.step()
training_time = time.time() - start_time
print("Training time: {}".format(training_time))
# 测试集测试
y_test_pred = model(X_TEST)
yhat = y_test_pred.detach().numpy()
yhat = yhat.reshape(vp_test.shape[0], n_out)
# 将预测值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)
# 返回所有记录的平均值。
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函数。
# 传递实际值(inv_y)、预测值(inv_yhat)以及预测的步数(n_out)作为参数。
# 此函数将计算每个预测步长的RMSE、MAE、MAPE和R2值。
# In[16]:
print(table)#显示预测指标数值
# In[16]:
#%%
## 画结果图
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的配置用来正常显示负号。
# 创建一个图形对象并设置大小为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.8, label='predict',marker = "o",markersize=2)
# 绘制预测值的折线图线型为虚线线宽为0.5,标签为'predict'。
plt.plot(x, Ytest[:,ii], linestyle="-", linewidth=0.5,label='Real',marker = "x",markersize=2)
# 绘制实际值的折线图线型为直线线宽为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 {args.model_name} :\nMAPE: {mape(Ytest[:, ii], predicted_data[:, ii])} %")
else:
plt.title(f"{ii+1} step of {args.model_name} 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()
# 显示图形。
'''
欢迎关注《淘个代码》公众号。
如代码有问题,请公众号后台留言问题!不要问在吗在吗。
直接截图留言问题!
'''