Files
2026-07-13 13:35:51 +08:00

291 lines
8.4 KiB
Python
Executable File

import argparse
from functools import partial
import dgl
import numpy as np
import torch
import torch.nn as nn
from dataloading import (
METR_LAGraphDataset,
METR_LATestDataset,
METR_LATrainDataset,
METR_LAValidDataset,
PEMS_BAYGraphDataset,
PEMS_BAYTestDataset,
PEMS_BAYTrainDataset,
PEMS_BAYValidDataset,
)
from dcrnn import DiffConv
from gaan import GatedGAT
from model import GraphRNN
from torch.utils.data import DataLoader
from utils import get_learning_rate, masked_mae_loss, NormalizationLayer
batch_cnt = [0]
def train(
model,
graph,
dataloader,
optimizer,
scheduler,
normalizer,
loss_fn,
device,
args,
):
total_loss = []
graph = graph.to(device)
model.train()
batch_size = args.batch_size
for i, (x, y) in enumerate(dataloader):
optimizer.zero_grad()
# Padding: Since the diffusion graph is precmputed we need to pad the batch so that
# each batch have same batch size
if x.shape[0] != batch_size:
x_buff = torch.zeros(batch_size, x.shape[1], x.shape[2], x.shape[3])
y_buff = torch.zeros(batch_size, x.shape[1], x.shape[2], x.shape[3])
x_buff[: x.shape[0], :, :, :] = x
x_buff[x.shape[0] :, :, :, :] = x[-1].repeat(
batch_size - x.shape[0], 1, 1, 1
)
y_buff[: x.shape[0], :, :, :] = y
y_buff[x.shape[0] :, :, :, :] = y[-1].repeat(
batch_size - x.shape[0], 1, 1, 1
)
x = x_buff
y = y_buff
# Permute the dimension for shaping
x = x.permute(1, 0, 2, 3)
y = y.permute(1, 0, 2, 3)
x_norm = (
normalizer.normalize(x)
.reshape(x.shape[0], -1, x.shape[3])
.float()
.to(device)
)
y_norm = (
normalizer.normalize(y)
.reshape(x.shape[0], -1, x.shape[3])
.float()
.to(device)
)
y = y.reshape(y.shape[0], -1, y.shape[3]).float().to(device)
batch_graph = dgl.batch([graph] * batch_size)
output = model(batch_graph, x_norm, y_norm, batch_cnt[0], device)
# Denormalization for loss compute
y_pred = normalizer.denormalize(output)
loss = loss_fn(y_pred, y)
loss.backward()
nn.utils.clip_grad_norm_(model.parameters(), args.max_grad_norm)
optimizer.step()
if get_learning_rate(optimizer) > args.minimum_lr:
scheduler.step()
total_loss.append(float(loss))
batch_cnt[0] += 1
print("\rBatch: ", i, end="")
return np.mean(total_loss)
def eval(model, graph, dataloader, normalizer, loss_fn, device, args):
total_loss = []
graph = graph.to(device)
model.eval()
batch_size = args.batch_size
for i, (x, y) in enumerate(dataloader):
# Padding: Since the diffusion graph is precmputed we need to pad the batch so that
# each batch have same batch size
if x.shape[0] != batch_size:
x_buff = torch.zeros(batch_size, x.shape[1], x.shape[2], x.shape[3])
y_buff = torch.zeros(batch_size, x.shape[1], x.shape[2], x.shape[3])
x_buff[: x.shape[0], :, :, :] = x
x_buff[x.shape[0] :, :, :, :] = x[-1].repeat(
batch_size - x.shape[0], 1, 1, 1
)
y_buff[: x.shape[0], :, :, :] = y
y_buff[x.shape[0] :, :, :, :] = y[-1].repeat(
batch_size - x.shape[0], 1, 1, 1
)
x = x_buff
y = y_buff
# Permute the order of dimension
x = x.permute(1, 0, 2, 3)
y = y.permute(1, 0, 2, 3)
x_norm = (
normalizer.normalize(x)
.reshape(x.shape[0], -1, x.shape[3])
.float()
.to(device)
)
y_norm = (
normalizer.normalize(y)
.reshape(x.shape[0], -1, x.shape[3])
.float()
.to(device)
)
y = y.reshape(x.shape[0], -1, x.shape[3]).to(device)
batch_graph = dgl.batch([graph] * batch_size)
output = model(batch_graph, x_norm, y_norm, i, device)
y_pred = normalizer.denormalize(output)
loss = loss_fn(y_pred, y)
total_loss.append(float(loss))
return np.mean(total_loss)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
# Define the arguments
parser.add_argument(
"--batch_size",
type=int,
default=64,
help="Size of batch for minibatch Training",
)
parser.add_argument(
"--num_workers",
type=int,
default=0,
help="Number of workers for parallel dataloading",
)
parser.add_argument(
"--model",
type=str,
default="dcrnn",
help="WHich model to use DCRNN vs GaAN",
)
parser.add_argument(
"--gpu", type=int, default=-1, help="GPU indexm -1 for CPU training"
)
parser.add_argument(
"--diffsteps",
type=int,
default=2,
help="Step of constructing the diffusiob matrix",
)
parser.add_argument(
"--num_heads", type=int, default=2, help="Number of multiattention head"
)
parser.add_argument(
"--decay_steps",
type=int,
default=2000,
help="Teacher forcing probability decay ratio",
)
parser.add_argument(
"--lr", type=float, default=0.01, help="Initial learning rate"
)
parser.add_argument(
"--minimum_lr",
type=float,
default=2e-6,
help="Lower bound of learning rate",
)
parser.add_argument(
"--dataset",
type=str,
default="LA",
help="dataset LA for METR_LA; BAY for PEMS_BAY",
)
parser.add_argument(
"--epochs", type=int, default=100, help="Number of epoches for training"
)
parser.add_argument(
"--max_grad_norm",
type=float,
default=5.0,
help="Maximum gradient norm for update parameters",
)
args = parser.parse_args()
# Load the datasets
if args.dataset == "LA":
g = METR_LAGraphDataset()
train_data = METR_LATrainDataset()
test_data = METR_LATestDataset()
valid_data = METR_LAValidDataset()
elif args.dataset == "BAY":
g = PEMS_BAYGraphDataset()
train_data = PEMS_BAYTrainDataset()
test_data = PEMS_BAYTestDataset()
valid_data = PEMS_BAYValidDataset()
if args.gpu == -1:
device = torch.device("cpu")
else:
device = torch.device("cuda:{}".format(args.gpu))
train_loader = DataLoader(
train_data,
batch_size=args.batch_size,
num_workers=args.num_workers,
shuffle=True,
)
valid_loader = DataLoader(
valid_data,
batch_size=args.batch_size,
num_workers=args.num_workers,
shuffle=True,
)
test_loader = DataLoader(
test_data,
batch_size=args.batch_size,
num_workers=args.num_workers,
shuffle=True,
)
normalizer = NormalizationLayer(train_data.mean, train_data.std)
if args.model == "dcrnn":
batch_g = dgl.batch([g] * args.batch_size).to(device)
out_gs, in_gs = DiffConv.attach_graph(batch_g, args.diffsteps)
net = partial(
DiffConv,
k=args.diffsteps,
in_graph_list=in_gs,
out_graph_list=out_gs,
)
elif args.model == "gaan":
net = partial(GatedGAT, map_feats=64, num_heads=args.num_heads)
dcrnn = GraphRNN(
in_feats=2,
out_feats=64,
seq_len=12,
num_layers=2,
net=net,
decay_steps=args.decay_steps,
).to(device)
optimizer = torch.optim.Adam(dcrnn.parameters(), lr=args.lr)
scheduler = torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma=0.99)
loss_fn = masked_mae_loss
for e in range(args.epochs):
train_loss = train(
dcrnn,
g,
train_loader,
optimizer,
scheduler,
normalizer,
loss_fn,
device,
args,
)
valid_loss = eval(
dcrnn, g, valid_loader, normalizer, loss_fn, device, args
)
test_loss = eval(
dcrnn, g, test_loader, normalizer, loss_fn, device, args
)
print(
"\rEpoch: {} Train Loss: {} Valid Loss: {} Test Loss: {}".format(
e, train_loss, valid_loss, test_loss
)
)