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) # 将前面处理好的DataFrame(data)转换成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() # 显示图形。 ''' 欢迎关注《淘个代码》公众号。 如代码有问题,请公众号后台留言问题!不要问在吗在吗。 直接截图留言问题! '''