完整代码:GitHub - nnzhan/Graph-WaveNet: graph wavenet
DCRNN代码:GitHub - liyaguang/DCRNN: Implementation of Diffusion Convolutional Recurrent Neural Network in Tensorflow(需要从DCRNN项目代码中获得数据)
运行配置: Colab T4 GPU
运行示例(colab)
import os
path= '/content/drive/MyDrive/Graph-WaveNet-master'
os.chdir(path)
os.listdir(path)
generate data:
!python generate_training_data.py --output_dir=data/METR-LA --traffic_df_filename=metr-la.h5
train command
!python /content/drive/MyDrive/Graph-WaveNet-master/train.py --adjdata '/content/drive/MyDrive/DCRNN-master/data/sensor_graph/adj_mx.pkl' --device 'cuda:0'
实际操作中,如果直接运行上述代码会报错‘Expected 2D (unbatched) or 3D (batched) input to conv1d, but got input of size: [64, 32, 207, 13]’
解决方案:将配置改成 python3.6,torch1.10.2
具体操作:参考用colab跑项目代码但是需要的环境是python3.6时,需要配置python3.6的环境_colab怎么配环境-CSDN博客
%%bash
MINICONDA_INSTALLER_SCRIPT=Miniconda3-4.5.4-Linux-x86_64.sh
MINICONDA_PREFIX=/usr/local
wget https://repo.continuum.io/miniconda/$MINICONDA_INSTALLER_SCRIPT
chmod +x $MINICONDA_INSTALLER_SCRIPT
./$MINICONDA_INSTALLER_SCRIPT -b -f -p $MINICONDA_PREFIX
!which conda # 返回/usr/local/bin/conda
!conda --version # 返回4.5.4
!which python # 依旧是/usr/local/bin/python
!python --version # 返回 Python 3.6.5 :: Anaconda, Inc.
%%bash
conda install --channel defaults conda python=3.6 --yes
conda update --channel defaults --all --yes
!conda --version # now returns 4.10.3
!python --version # now returns Python 3.6.13 :: Anaconda, Inc.
import sys
sys.path
_ = (sys.path.append("/usr/local/lib/python3.6/site-packages"))
_ = (sys.path.append("/usr/local/lib/python3.6/dist-packages"))
!sudo apt-get install python3-pip
!python -m pip install --upgrade pip
!pip install -q ipykernel numpy pandas torch argparse matplotlib time sys seaborn
!pip install -q tables scipy matplotlib ipykernel
!pip install torch==1.10.2
再运行
!python /content/drive/MyDrive/Graph-WaveNet-master/train.py --adjdata '/content/drive/MyDrive/DCRNN-master/data/sensor_graph/adj_mx.pkl' --device 'cuda:0'
就可以啦~
主函数 train.py
命令行获取运行参数
parser = argparse.ArgumentParser()
parser.add_argument('--device',type=str,default='cuda:3',help='')
parser.add_argument('--data',type=str,default='data/METR-LA',help='data path')
parser.add_argument('--adjdata',type=str,default='data/sensor_graph/adj_mx.pkl',help='adj data path')
parser.add_argument('--adjtype',type=str,default='doubletransition',help='adj type')
parser.add_argument('--gcn_bool',action='store_true',help='whether to add graph convolution layer')
parser.add_argument('--aptonly',action='store_true',help='whether only adaptive adj')
parser.add_argument('--addaptadj',action='store_true',help='whether add adaptive adj')
parser.add_argument('--randomadj',action='store_true',help='whether random initialize adaptive adj')
parser.add_argument('--seq_length',type=int,default=12,help='')
parser.add_argument('--nhid',type=int,default=32,help='')
parser.add_argument('--in_dim',type=int,default=2,help='inputs dimension')
parser.add_argument('--num_nodes',type=int,default=207,help='number of nodes')
parser.add_argument('--batch_size',type=int,default=64,help='batch size')
parser.add_argument('--learning_rate',type=float,default=0.001,help='learning rate')
parser.add_argument('--dropout',type=float,default=0.3,help='dropout rate')
parser.add_argument('--weight_decay',type=float,default=0.0001,help='weight decay rate')
parser.add_argument('--epochs',type=int,default=100,help='')
parser.add_argument('--print_every',type=int,default=50,help='')
#parser.add_argument('--seed',type=int,default=99,help='random seed')
parser.add_argument('--save',type=str,default='./garage/metr',help='save path')
parser.add_argument('--expid',type=int,default=1,help='experiment id')
args = parser.parse_args()
这一部分的解析可以参考STGCN 代码解读(Pytorch)-CSDN博客
加载图信息
sensor_ids, sensor_id_to_ind, adj_mx = util.load_adj(args.adjdata,args.adjtype)
其中util.load_adj()主要引用utils文件中两个函数(除了Laplacian矩阵定义计算的几个函数之外):
def load_pickle(pickle_file):
try:
with open(pickle_file, 'rb') as f:
pickle_data = pickle.load(f)
except UnicodeDecodeError as e:
with open(pickle_file, 'rb') as f:
pickle_data = pickle.load(f, encoding='latin1')
except Exception as e:
print('Unable to load data ', pickle_file, ':', e)
raise
return pickle_data
def load_adj(pkl_filename, adjtype):
sensor_ids, sensor_id_to_ind, adj_mx = load_pickle(pkl_filename)
if adjtype == "scalap":
adj = [calculate_scaled_laplacian(adj_mx)]
elif adjtype == "normlap":
adj = [calculate_normalized_laplacian(adj_mx).astype(np.float32).todense()]
elif adjtype == "symnadj":
adj = [sym_adj(adj_mx)]
elif adjtype == "transition":
adj = [asym_adj(adj_mx)]
elif adjtype == "doubletransition":
adj = [asym_adj(adj_mx), asym_adj(np.transpose(adj_mx))]
elif adjtype == "identity":
adj = [np.diag(np.ones(adj_mx.shape[0])).astype(np.float32)]
else:
error = 0
assert error, "adj type not defined"
return sensor_ids, sensor_id_to_ind, adj
load_pickle干了一件事:解析pkl文件
什么是pkl文件?
.pkl文件通常是使用Python中的pickle
模块保存的二进制文件,它用于序列化(将对象转换为字节流)和反序列化(将字节流还原为对象)Python对象。这允许将Python对象保存到文件中以便后续使用,或者从文件中加载已保存的对象。
当使用pickle.load(f)
加载一个.pkl文件时,它会返回文件f
中存储的Python对象。例如,如果将一个列表对象序列化并保存到.pkl文件中,然后使用pickle.load(f)
加载该文件,它将返回原始的列表对象。这使得在不丢失对象的结构或数据的情况下将对象保存到磁盘并重新加载它们成为可能。
将Python对象保存为.pkl文件相比于其他形式的文件保存有一些明显的好处:
-
完整性和结构保持:使用.pkl文件保存对象会保持对象的完整结构,包括嵌套对象、自定义类、函数等。这使得您可以轻松地保存和加载复杂的数据结构,而无需手动编写解析和序列化代码。
-
跨平台兼容性:.pkl文件是Python的标准二进制序列化格式,因此它在不同平台和Python版本之间具有很好的兼容性。您可以在不同计算环境中轻松地加载.pkl文件,而无需担心不同系统的字节顺序或其他兼容性问题。
-
快速和高效:pickle是一个非常高效的序列化方法,通常比使用文本格式(如JSON或XML)更快。这对于大型数据结构或需要频繁读写的数据非常有用。
-
支持自定义对象:您可以使用pickle保存自定义的Python对象,包括用户定义的类实例,而不需要额外的转换工作。这为保存包含用户自定义类的复杂数据结构提供了方便。
文件中data -> sensor_graph -> adj_mx.pkl获得
划分数据
dataloader = util.load_dataset(args.data, args.batch_size, args.batch_size, args.batch_size)
其函数定义在utils文件中load_dataset()函数给出
def load_dataset(dataset_dir, batch_size, valid_batch_size= None, test_batch_size=None):
data = {}
for category in ['train', 'val', 'test']:
cat_data = np.load(os.path.join(dataset_dir, category + '.npz'))
data['x_' + category] = cat_data['x']
data['y_' + category] = cat_data['y']
scaler = StandardScaler(mean=data['x_train'][..., 0].mean(), std=data['x_train'][..., 0].std())
# Data format
for category in ['train', 'val', 'test']:
data['x_' + category][..., 0] = scaler.transform(data['x_' + category][..., 0])
data['train_loader'] = DataLoader(data['x_train'], data['y_train'], batch_size)
data['val_loader'] = DataLoader(data['x_val'], data['y_val'], valid_batch_size)
data['test_loader'] = DataLoader(data['x_test'], data['y_test'], test_batch_size)
data['scaler'] = scaler
return data
接受的输入参数:数据集目录、批量大小以及验证集和测试集的批量大小。
-
函数接受三个参数:def load_dataset(dataset_dir, batch_size, valid_batch_size=None, test_batch_size=None):
test_batch_size
: 测试数据的批量大小,可选参数。valid_batch_size
: 验证数据的批量大小,可选参数。batch_size
: 训练数据的批量大小。dataset_dir
: 数据集目录的路径,其中包含训练、验证和测试数据文件。
-
data = {}
: 创建一个空的字典,用于存储加载的数据和数据加载器。 -
遍历包含三个字符串的列表:'train'、'val' 和 'test'。这对应数据集中的三个部分:训练集、验证集和测试集。for category in ['train', 'val', 'test']:
-
cat_data = np.load(os.path.join(dataset_dir, category + '.npz'))
:- 加载名为
category
的数据部分,npz
文件是NumPy的一种文件格式,通常包含多个数组。 os.path.join
用于构建完整的文件路径,dataset_dir
是数据集目录的路径,category + '.npz'
构建了特定部分的文件名。- 加载的数据存储在
cat_data
中。
- 加载名为
-
将加载的数据分为特征('x')和目标('y')部分,并将它们存储在data['x_' + category] = cat_data['x']
和data['y_' + category] = cat_data['y']
:data
字典中,使用键'x_' + category
和'y_' + category
进行区分。这会创建六个不同的数组:x_train
、y_train
、x_val
、y_val
、x_test
和y_test
。 -
scaler = StandardScaler(mean=data['x_train'][..., 0].mean(), std=data['x_train'][..., 0].std())
:- 创建一个数据标准化(scaling)器,使用
StandardScaler
类。 - 这个标准化器用于对数据进行标准化,以确保均值为0,标准差为1。它是根据训练集的特征('x_train'的第一个维度)的均值和标准差来初始化的。
- 创建一个数据标准化(scaling)器,使用
-
再次遍历数据集的三个部分。for category in ['train', 'val', 'test']:
-
对每个数据部分('x_train'、'x_val' 和 'x_test')的特征的第一个维度进行标准化,使用先前创建的标准化器data['x_' + category][..., 0] = scaler.transform(data['x_' + category][..., 0])
:scaler
。 -
创建一个 PyTorch 数据加载器(data['train_loader'] = DataLoader(data['x_train'], data['y_train'], batch_size)
:DataLoader
),用于训练数据部分。它接受训练特征data['x_train']
和对应的标签data['y_train']
,以及批量大小batch_size
。 -
data['val_loader'] = DataLoader(data['x_val'], data['y_val'], valid_batch_size)
和data['test_loader'] = DataLoader(data['x_test'], data['y_test'], test_batch_size)
:
- 创建验证数据和测试数据的数据加载器,使用相应的批量大小。
data['scaler'] = scaler
:
- 将数据标准化器存储在
data
字典中,以便以后使用。
return data
:
- 返回包含加载的数据和数据加载器的
data
字典。
这个函数的主要功能是加载数据,将数据分为训练、验证和测试部分,对数据进行标准化,然后创建用于训练、验证和测试的数据加载器,最后将这些数据和加载器打包到一个字典中返回。这是一个常见的数据预处理流程,特别是在深度学习任务中,以确保数据准备好用于训练和评估模型。
DataLoader类的定义:
class DataLoader(object):
def __init__(self, xs, ys, batch_size, pad_with_last_sample=True):
"""
:param xs:
:param ys:
:param batch_size:
:param pad_with_last_sample: pad with the last sample to make number of samples divisible to batch_size.
"""
self.batch_size = batch_size
self.current_ind = 0
if pad_with_last_sample:
num_padding = (batch_size - (len(xs) % batch_size)) % batch_size
x_padding = np.repeat(xs[-1:], num_padding, axis=0)
y_padding = np.repeat(ys[-1:], num_padding, axis=0)
xs = np.concatenate([xs, x_padding], axis=0)
ys = np.concatenate([ys, y_padding], axis=0)
self.size = len(xs)
self.num_batch = int(self.size // self.batch_size)
self.xs = xs
self.ys = ys
def shuffle(self):
permutation = np.random.permutation(self.size)
xs, ys = self.xs[permutation], self.ys[permutation]
self.xs = xs
self.ys = ys
def get_iterator(self):
self.current_ind = 0
def _wrapper():
while self.current_ind < self.num_batch:
start_ind = self.batch_size * self.current_ind
end_ind = min(self.size, self.batch_size * (self.current_ind + 1))
x_i = self.xs[start_ind: end_ind, ...]
y_i = self.ys[start_ind: end_ind, ...]
yield (x_i, y_i)
self.current_ind += 1
return _wrapper()
张量转移到device
device = torch.device(args.device)
supports = [torch.tensor(i).to(device) for i in adj_mx]
这段代码是为了将一个包含邻接矩阵的列表 adj_mx
中的每个张量转移到指定的 PyTorch 设备 device
上,以便与模型一起使用。
-
device = torch.device(args.device)
: 这一行代码创建了一个 PyTorch 设备对象,以确定模型和张量运行的设备,根据args.device
的值。args.device
可能是 'cuda:0'(表示使用第一块 GPU)或 'cpu'(表示使用 CPU)等。 -
supports = [torch.tensor(i).to(device) for i in adj_mx]
:- 这一行代码使用列表推导式对
adj_mx
列表中的每个张量进行处理,并将其移到指定的device
上。 - 对于列表
adj_mx
中的每个张量i
,torch.tensor(i)
用于创建一个 PyTorch 张量,然后to(device)
方法将这个张量移到指定的设备。 - 最后,处理后的张量列表被赋值给变量
supports
。
- 这一行代码使用列表推导式对
随机初始化和是否有graph prior knowledge
parser.add_argument('--randomadj',action='store_true',help='whether random initialize adaptive adj')
parser.add_argument('--aptonly',action='store_true',help='whether only adaptive adj')
if args.randomadj:
adjinit = None
else:
adjinit = supports[0]
if args.aptonly:
supports = None
--randomadj
:布尔类型的命令行参数,用于标志是否要进行随机初始化的操作。
--aptonly
:这同样是一个布尔类型的命令行参数,通常用于标志是否只使用自适应邻接矩阵
由论文,对于是否拥有prior knowledge of graph对应两种空间卷积形式:
训练模型
engine = trainer(scaler, args.in_dim, args.seq_length, args.num_nodes, args.nhid, args.dropout,
args.learning_rate, args.weight_decay, device, supports, args.gcn_bool, args.addaptadj,
adjinit)
print("start training...",flush=True)
trainer是engine.py中定义的一个类:
class trainer():
def __init__(self, scaler, in_dim, seq_length, num_nodes, nhid , dropout, lrate, wdecay, device, supports, gcn_bool, addaptadj, aptinit):
self.model = gwnet(device, num_nodes, dropout, supports=supports, gcn_bool=gcn_bool, addaptadj=addaptadj, aptinit=aptinit, in_dim=in_dim, out_dim=seq_length, residual_channels=nhid, dilation_channels=nhid, skip_channels=nhid * 8, end_channels=nhid * 16)
self.model.to(device)
self.optimizer = optim.Adam(self.model.parameters(), lr=lrate, weight_decay=wdecay)
self.loss = util.masked_mae
self.scaler = scaler
self.clip = 5
def train(self, input, real_val):
self.model.train()
self.optimizer.zero_grad()
input = nn.functional.pad(input,(1,0,0,0))
output = self.model(input)
output = output.transpose(1,3)
#output = [batch_size,12,num_nodes,1]
real = torch.unsqueeze(real_val,dim=1)
predict = self.scaler.inverse_transform(output)
loss = self.loss(predict, real, 0.0)
loss.backward()
if self.clip is not None:
torch.nn.utils.clip_grad_norm_(self.model.parameters(), self.clip)
self.optimizer.step()
mape = util.masked_mape(predict,real,0.0).item()
rmse = util.masked_rmse(predict,real,0.0).item()
return loss.item(),mape,rmse
def eval(self, input, real_val):
self.model.eval()
input = nn.functional.pad(input,(1,0,0,0))
output = self.model(input)
output = output.transpose(1,3)
#output = [batch_size,12,num_nodes,1]
real = torch.unsqueeze(real_val,dim=1)
predict = self.scaler.inverse_transform(output)
loss = self.loss(predict, real, 0.0)
mape = util.masked_mape(predict,real,0.0).item()
rmse = util.masked_rmse(predict,real,0.0).item()
return loss.item(),mape,rmse
这段代码主要是为了训练和评估一个Graph WaveNet模型,其中engine
是 trainer
类的一个实例,trainer
类用于管理模型的训练和评估过程。下面是这段代码的详细解释:
首先,创建了一个 trainer
类的实例 engine
,并且传递了多个参数给该实例:
scaler
:数据标准化器,用于将数据标准化为均值为0、标准差为1的形式。in_dim
:输入数据的特征维度。seq_length
:时间序列数据的长度。num_nodes
:时间序列中的节点数目。nhid
:模型中的隐藏层维度。dropout
:模型中的丢弃率。lrate
:学习率。wdecay
:权重衰减。device
:模型运行的设备(CPU 或 GPU)。supports
:邻接矩阵,用于模型中的图卷积操作。gcn_bool
:一个布尔值,指示是否使用图卷积层。addaptadj
:一个布尔值,指示是否使用自适应邻接矩阵。aptinit
:邻接矩阵的初始化。
然后,以下代码段执行模型的训练和评估:
-
self.model.train()
和self.optimizer.zero_grad()
: 这两行代码将模型设置为训练模式,并将优化器的梯度清零。 -
input = nn.functional.pad(input, (1, 0, 0, 0))
: 这一行代码在输入数据的前面添加了一个维度,通常用于数据处理。 -
output = self.model(input)
: 这行代码使用模型进行前向传播,生成模型的输出。 -
self.loss(predict, real, 0.0)
: 这行代码计算模型的损失,其中predict
是模型的预测值,real
是真实的目标值。 -
loss.backward()
: 这行代码执行反向传播,计算梯度。 -
torch.nn.utils.clip_grad_norm_(self.model.parameters(), self.clip)
: 如果self.clip
不为None
,则执行梯度裁剪,以避免梯度爆炸。 -
self.optimizer.step()
: 这行代码执行梯度下降步骤,更新模型的参数。 -
util.masked_mape(predict, real, 0.0).item()
和util.masked_rmse(predict, real, 0.0).item()
: 这两行代码计算模型的评估指标,包括 MAPE(平均绝对百分比误差)和 RMSE(均方根误差)。
整个 trainer
类用于管理模型的训练和评估过程,包括前向传播、损失计算、反向传播和梯度更新。它还包括了一些评估指标的计算,用于评估模型性能。这是一个典型的深度学习训练和评估过程的实现。
his_loss =[]
val_time = []
train_time = []
for i in range(1,args.epochs+1):
#if i % 10 == 0:
#lr = max(0.000002,args.learning_rate * (0.1 ** (i // 10)))
#for g in engine.optimizer.param_groups:
#g['lr'] = lr
train_loss = []
train_mape = []
train_rmse = []
t1 = time.time()
dataloader['train_loader'].shuffle()
for iter, (x, y) in enumerate(dataloader['train_loader'].get_iterator()):
trainx = torch.Tensor(x).to(device)
trainx= trainx.transpose(1, 3)
trainy = torch.Tensor(y).to(device)
trainy = trainy.transpose(1, 3)
metrics = engine.train(trainx, trainy[:,0,:,:])
train_loss.append(metrics[0])
train_mape.append(metrics[1])
train_rmse.append(metrics[2])
if iter % args.print_every == 0 :
log = 'Iter: {:03d}, Train Loss: {:.4f}, Train MAPE: {:.4f}, Train RMSE: {:.4f}'
print(log.format(iter, train_loss[-1], train_mape[-1], train_rmse[-1]),flush=True)
t2 = time.time()
train_time.append(t2-t1)
#validation
valid_loss = []
valid_mape = []
valid_rmse = []
s1 = time.time()
for iter, (x, y) in enumerate(dataloader['val_loader'].get_iterator()):
testx = torch.Tensor(x).to(device)
testx = testx.transpose(1, 3)
testy = torch.Tensor(y).to(device)
testy = testy.transpose(1, 3)
metrics = engine.eval(testx, testy[:,0,:,:])
valid_loss.append(metrics[0])
valid_mape.append(metrics[1])
valid_rmse.append(metrics[2])
s2 = time.time()
log = 'Epoch: {:03d}, Inference Time: {:.4f} secs'
print(log.format(i,(s2-s1)))
val_time.append(s2-s1)
mtrain_loss = np.mean(train_loss)
mtrain_mape = np.mean(train_mape)
mtrain_rmse = np.mean(train_rmse)
mvalid_loss = np.mean(valid_loss)
mvalid_mape = np.mean(valid_mape)
mvalid_rmse = np.mean(valid_rmse)
his_loss.append(mvalid_loss)
log = 'Epoch: {:03d}, Train Loss: {:.4f}, Train MAPE: {:.4f}, Train RMSE: {:.4f}, Valid Loss: {:.4f}, Valid MAPE: {:.4f}, Valid RMSE: {:.4f}, Training Time: {:.4f}/epoch'
print(log.format(i, mtrain_loss, mtrain_mape, mtrain_rmse, mvalid_loss, mvalid_mape, mvalid_rmse, (t2 - t1)),flush=True)
torch.save(engine.model.state_dict(), args.save+"_epoch_"+str(i)+"_"+str(round(mvalid_loss,2))+".pth")
print("Average Training Time: {:.4f} secs/epoch".format(np.mean(train_time)))
print("Average Inference Time: {:.4f} secs".format(np.mean(val_time)))
#testing
bestid = np.argmin(his_loss)
engine.model.load_state_dict(torch.load(args.save+"_epoch_"+str(bestid+1)+"_"+str(round(his_loss[bestid],2))+".pth"))
outputs = []
realy = torch.Tensor(dataloader['y_test']).to(device)
realy = realy.transpose(1,3)[:,0,:,:]
for iter, (x, y) in enumerate(dataloader['test_loader'].get_iterator()):
testx = torch.Tensor(x).to(device)
testx = testx.transpose(1,3)
with torch.no_grad():
preds = engine.model(testx).transpose(1,3)
outputs.append(preds.squeeze())
yhat = torch.cat(outputs,dim=0)
yhat = yhat[:realy.size(0),...]
print("Training finished")
print("The valid loss on best model is", str(round(his_loss[bestid],4)))
amae = []
amape = []
armse = []
for i in range(12):
pred = scaler.inverse_transform(yhat[:,:,i])
real = realy[:,:,i]
metrics = util.metric(pred,real)
log = 'Evaluate best model on test data for horizon {:d}, Test MAE: {:.4f}, Test MAPE: {:.4f}, Test RMSE: {:.4f}'
print(log.format(i+1, metrics[0], metrics[1], metrics[2]))
amae.append(metrics[0])
amape.append(metrics[1])
armse.append(metrics[2])
log = 'On average over 12 horizons, Test MAE: {:.4f}, Test MAPE: {:.4f}, Test RMSE: {:.4f}'
print(log.format(np.mean(amae),np.mean(amape),np.mean(armse)))
torch.save(engine.model.state_dict(), args.save+"_exp"+str(args.expid)+"_best_"+str(round(his_loss[bestid],2))+".pth")
-
his_loss
,val_time
,train_time
:这些是用于存储历史损失、验证时间和训练时间的列表。 -
主循环
for i in range(1, args.epochs + 1)
:这个循环迭代训练模型多个周期(epochs)。- 内部包含以下步骤:
- 训练集上的循环:
- 每个周期开始时,初始化
train_loss
、train_mape
和train_rmse
用于存储每个批次的损失和评估指标。 - 数据加载器
dataloader['train_loader']
被洗牌(shuffle),以确保随机访问训练数据。 - 对于数据加载器中的每个批次,执行以下操作:
- 将输入数据
x
和目标数据y
转换为 PyTorch 张量,并移动到指定的设备device
上。 - 调用
engine.train
方法执行模型的训练,并获取损失和评估指标。 - 将每个批次的损失、MAPE 和 RMSE 存储到相应的列表中。
- 将输入数据
- 如果当前批次的迭代次数是
args.print_every
的倍数,将当前训练损失、MAPE 和 RMSE 打印到控制台。
- 每个周期开始时,初始化
- 记录训练时间,并将其添加到
train_time
列表中。 - 验证集上的循环:
- 记录验证时间,并将其添加到
val_time
列表中。 - 计算训练集和验证集的平均损失、MAPE 和 RMSE。
- 将验证损失添加到
his_loss
列表中。
- 记录验证时间,并将其添加到
- 训练集上的循环:
- 内部包含以下步骤:
-
输出训练的性能指标和损失,同时保存训练模型的参数文件。
-
找到在验证集上表现最好的模型(损失最小的模型)。
-
使用最佳模型对测试数据进行推断。对于每个预测时刻,计算 MAE、MAPE 和 RMSE,并打印它们。
-
打印模型的性能指标:在每个预测时刻的平均 MAE、MAPE 和 RMSE。
-
最后,保存最佳模型的参数文件,并输出训练结束的信息。