Compare commits
3 Commits
c6e5e904ea
...
241274e60f
| Author | SHA1 | Date |
|---|---|---|
|
|
241274e60f | |
|
|
2685d049d7 | |
|
|
162bc8dc53 |
|
|
@ -4,6 +4,7 @@
|
|||
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
"name": "EXP_PEMSD8",
|
||||
"type": "debugpy",
|
||||
|
|
@ -19,6 +20,14 @@
|
|||
"program": "run.py",
|
||||
"console": "integratedTerminal",
|
||||
"args": "--config ./config/REPST/PEMSD8.yaml"
|
||||
},
|
||||
{
|
||||
"name": "REPST-PEMSBAY",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "run.py",
|
||||
"console": "integratedTerminal",
|
||||
"args": "--config ./config/REPST/PEMS-BAY.yaml"
|
||||
}
|
||||
]
|
||||
}
|
||||
9762
baseline.ipynb
9762
baseline.ipynb
File diff suppressed because it is too large
Load Diff
16544
baseline1.ipynb
16544
baseline1.ipynb
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,58 @@
|
|||
basic:
|
||||
dataset: "PEMS-BAY"
|
||||
mode : "train"
|
||||
device : "cuda:0"
|
||||
model: "REPST"
|
||||
|
||||
data:
|
||||
add_day_in_week: true
|
||||
add_time_in_day: true
|
||||
column_wise: false
|
||||
days_per_week: 7
|
||||
default_graph: true
|
||||
horizon: 12
|
||||
lag: 12
|
||||
normalizer: std
|
||||
num_nodes: 325
|
||||
steps_per_day: 288
|
||||
test_ratio: 0.2
|
||||
tod: false
|
||||
val_ratio: 0.2
|
||||
sample: 1
|
||||
input_dim: 1
|
||||
batch_size: 16
|
||||
|
||||
model:
|
||||
pred_len: 12
|
||||
seq_len: 12
|
||||
patch_len: 6
|
||||
stride: 7
|
||||
dropout: 0.2
|
||||
gpt_layers: 9
|
||||
d_ff: 128
|
||||
gpt_path: ./GPT-2
|
||||
d_model: 64
|
||||
n_heads: 1
|
||||
|
||||
train:
|
||||
batch_size: 16
|
||||
early_stop: true
|
||||
early_stop_patience: 15
|
||||
epochs: 100
|
||||
grad_norm: false
|
||||
loss_func: mae
|
||||
lr_decay: true
|
||||
lr_decay_rate: 0.3
|
||||
lr_decay_step: "5,20,40,70"
|
||||
lr_init: 0.003
|
||||
max_grad_norm: 5
|
||||
real_value: true
|
||||
seed: 12
|
||||
weight_decay: 0
|
||||
debug: false
|
||||
output_dim: 1
|
||||
log_step: 100
|
||||
plot: false
|
||||
mae_thresh: None
|
||||
mape_thresh: 0.001
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
from lib.normalization import normalize_dataset
|
||||
from utils.normalization import normalize_dataset
|
||||
|
||||
import numpy as np
|
||||
import gc
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
import numpy as np
|
||||
import gc
|
||||
import os
|
||||
import torch
|
||||
import h5py
|
||||
from lib.normalization import normalize_dataset
|
||||
|
||||
from utils.normalization import normalize_dataset
|
||||
from dataloader.data_selector import load_st_dataset
|
||||
|
||||
def get_dataloader(args, normalizer="std", single=True):
|
||||
# args should now include 'cycle'
|
||||
|
|
@ -121,70 +118,6 @@ def data_loader_with_cycle(X, Y, C, batch_size, shuffle=True, drop_last=True):
|
|||
return loader
|
||||
|
||||
|
||||
# Rest of the helper functions (load_st_dataset, split_data..., add_window_x/y) unchanged
|
||||
|
||||
|
||||
def load_st_dataset(dataset, sample):
|
||||
# output B, N, D
|
||||
match dataset:
|
||||
case "PEMSD3":
|
||||
data_path = os.path.join("./data/PEMS03/PEMS03.npz")
|
||||
data = np.load(data_path)["data"][
|
||||
:, :, 0
|
||||
] # only the first dimension, traffic flow data
|
||||
case "PEMSD4":
|
||||
data_path = os.path.join("./data/PEMS04/PEMS04.npz")
|
||||
data = np.load(data_path)["data"][
|
||||
:, :, 0
|
||||
] # only the first dimension, traffic flow data
|
||||
case "PEMSD7":
|
||||
data_path = os.path.join("./data/PEMS07/PEMS07.npz")
|
||||
data = np.load(data_path)["data"][
|
||||
:, :, 0
|
||||
] # only the first dimension, traffic flow data
|
||||
case "PEMSD8":
|
||||
data_path = os.path.join("./data/PEMS08/PEMS08.npz")
|
||||
data = np.load(data_path)["data"][
|
||||
:, :, 0
|
||||
] # only the first dimension, traffic flow data
|
||||
case "PEMSD7(L)":
|
||||
data_path = os.path.join("./data/PEMS07(L)/PEMS07L.npz")
|
||||
data = np.load(data_path)["data"][
|
||||
:, :, 0
|
||||
] # only the first dimension, traffic flow data
|
||||
case "PEMSD7(M)":
|
||||
data_path = os.path.join("./data/PEMS07(M)/V_228.csv")
|
||||
data = np.genfromtxt(
|
||||
data_path, delimiter=","
|
||||
) # Read CSV directly with numpy
|
||||
case "METR-LA":
|
||||
data_path = os.path.join("./data/METR-LA/METR.h5")
|
||||
with h5py.File(
|
||||
data_path, "r"
|
||||
) as f: # Use h5py to handle HDF5 files without pandas
|
||||
data = np.array(f["data"])
|
||||
case "BJ":
|
||||
data_path = os.path.join("./data/BJ/BJ500.csv")
|
||||
data = np.genfromtxt(
|
||||
data_path, delimiter=",", skip_header=1
|
||||
) # Skip header if present
|
||||
case "Hainan":
|
||||
data_path = os.path.join("./data/Hainan/Hainan.npz")
|
||||
data = np.load(data_path)["data"][:, :, 0]
|
||||
case "SD":
|
||||
data_path = os.path.join("./data/SD/data.npz")
|
||||
data = np.load(data_path)["data"][:, :, 0].astype(np.float32)
|
||||
case _:
|
||||
raise ValueError(f"Unsupported dataset: {dataset}")
|
||||
|
||||
# Ensure data shape compatibility
|
||||
if len(data.shape) == 2:
|
||||
data = np.expand_dims(data, axis=-1)
|
||||
|
||||
print("加载 %s 数据集中... " % dataset)
|
||||
return data[::sample]
|
||||
|
||||
|
||||
def split_data_by_days(data, val_days, test_days, interval=30):
|
||||
t = int((24 * 60) / interval)
|
||||
test_data = data[-t * int(test_days) :]
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
from lib.normalization import normalize_dataset
|
||||
from utils.normalization import normalize_dataset
|
||||
from dataloader.data_selector import load_st_dataset
|
||||
|
||||
import numpy as np
|
||||
import gc
|
||||
import os
|
||||
import torch
|
||||
import h5py
|
||||
|
||||
|
||||
|
||||
def get_dataloader(args, normalizer="std", single=True):
|
||||
|
|
@ -113,69 +113,6 @@ def get_dataloader(args, normalizer="std", single=True):
|
|||
return train_dataloader, val_dataloader, test_dataloader, scaler
|
||||
|
||||
|
||||
def load_st_dataset(config):
|
||||
dataset = config["basic"]["dataset"]
|
||||
sample = config["data"]["sample"]
|
||||
# output B, N, D
|
||||
match dataset:
|
||||
case "PEMSD3":
|
||||
data_path = os.path.join("./data/PEMS03/PEMS03.npz")
|
||||
data = np.load(data_path)["data"][
|
||||
:, :, 0
|
||||
] # only the first dimension, traffic flow data
|
||||
case "PEMSD4":
|
||||
data_path = os.path.join("./data/PEMS04/PEMS04.npz")
|
||||
data = np.load(data_path)["data"][
|
||||
:, :, 0
|
||||
] # only the first dimension, traffic flow data
|
||||
case "PEMSD7":
|
||||
data_path = os.path.join("./data/PEMS07/PEMS07.npz")
|
||||
data = np.load(data_path)["data"][
|
||||
:, :, 0
|
||||
] # only the first dimension, traffic flow data
|
||||
case "PEMSD8":
|
||||
data_path = os.path.join("./data/PEMS08/PEMS08.npz")
|
||||
data = np.load(data_path)["data"][
|
||||
:, :, 0
|
||||
] # only the first dimension, traffic flow data
|
||||
case "PEMSD7(L)":
|
||||
data_path = os.path.join("./data/PEMS07(L)/PEMS07L.npz")
|
||||
data = np.load(data_path)["data"][
|
||||
:, :, 0
|
||||
] # only the first dimension, traffic flow data
|
||||
case "PEMSD7(M)":
|
||||
data_path = os.path.join("./data/PEMS07(M)/V_228.csv")
|
||||
data = np.genfromtxt(
|
||||
data_path, delimiter=","
|
||||
) # Read CSV directly with numpy
|
||||
case "METR-LA":
|
||||
data_path = os.path.join("./data/METR-LA/METR.h5")
|
||||
with h5py.File(
|
||||
data_path, "r"
|
||||
) as f: # Use h5py to handle HDF5 files without pandas
|
||||
data = np.array(f["data"])
|
||||
case "BJ":
|
||||
data_path = os.path.join("./data/BJ/BJ500.csv")
|
||||
data = np.genfromtxt(
|
||||
data_path, delimiter=",", skip_header=1
|
||||
) # Skip header if present
|
||||
case "Hainan":
|
||||
data_path = os.path.join("./data/Hainan/Hainan.npz")
|
||||
data = np.load(data_path)["data"][:, :, 0]
|
||||
case "SD":
|
||||
data_path = os.path.join("./data/SD/data.npz")
|
||||
data = np.load(data_path)["data"][:, :, 0].astype(np.float32)
|
||||
case _:
|
||||
raise ValueError(f"Unsupported dataset: {dataset}")
|
||||
|
||||
# Ensure data shape compatibility
|
||||
if len(data.shape) == 2:
|
||||
data = np.expand_dims(data, axis=-1)
|
||||
|
||||
print("加载 %s 数据集中... " % dataset)
|
||||
return data[::sample]
|
||||
|
||||
|
||||
def split_data_by_days(data, val_days, test_days, interval=30):
|
||||
t = int((24 * 60) / interval)
|
||||
test_data = data[-t * int(test_days) :]
|
||||
|
|
|
|||
|
|
@ -1,253 +0,0 @@
|
|||
from lib.normalization import normalize_dataset
|
||||
|
||||
import numpy as np
|
||||
import gc
|
||||
import os
|
||||
import torch
|
||||
import h5py
|
||||
|
||||
|
||||
def get_dataloader(args, normalizer="std", single=True):
|
||||
data = load_st_dataset(args["type"]) # 加载数据
|
||||
L, N, F = data.shape # 数据形状
|
||||
|
||||
# Step 1: data -> x,y
|
||||
x = add_window_x(data, args["lag"], args["horizon"], single)
|
||||
y = add_window_y(data, args["lag"], args["horizon"], single)
|
||||
|
||||
del data
|
||||
gc.collect()
|
||||
|
||||
# Step 2: time_in_day, day_in_week -> day, week
|
||||
time_in_day = [i % args["steps_per_day"] / args["steps_per_day"] for i in range(L)]
|
||||
time_in_day = np.tile(np.array(time_in_day), [1, N, 1]).transpose((2, 1, 0))
|
||||
day_in_week = [
|
||||
(i // args["steps_per_day"]) % args["days_per_week"] for i in range(L)
|
||||
]
|
||||
day_in_week = np.tile(np.array(day_in_week), [1, N, 1]).transpose((2, 1, 0))
|
||||
|
||||
x_day = add_window_x(time_in_day, args["lag"], args["horizon"], single)
|
||||
x_week = add_window_x(day_in_week, args["lag"], args["horizon"], single)
|
||||
|
||||
# Step 3 day, week, x, y --> x, y
|
||||
x = np.concatenate([x, x_day, x_week], axis=-1)
|
||||
|
||||
del x_day, x_week
|
||||
gc.collect()
|
||||
|
||||
# Step 4 x,y --> x_train, x_val, x_test, y_train, y_val, y_test
|
||||
if args["test_ratio"] > 1:
|
||||
x_train, x_val, x_test = split_data_by_days(
|
||||
x, args["val_ratio"], args["test_ratio"]
|
||||
)
|
||||
else:
|
||||
x_train, x_val, x_test = split_data_by_ratio(
|
||||
x, args["val_ratio"], args["test_ratio"]
|
||||
)
|
||||
|
||||
del x
|
||||
gc.collect()
|
||||
|
||||
# Normalization
|
||||
scaler = normalize_dataset(
|
||||
x_train[..., : args["input_dim"]], normalizer, args["column_wise"]
|
||||
)
|
||||
x_train[..., : args["input_dim"]] = scaler.transform(
|
||||
x_train[..., : args["input_dim"]]
|
||||
)
|
||||
x_val[..., : args["input_dim"]] = scaler.transform(x_val[..., : args["input_dim"]])
|
||||
x_test[..., : args["input_dim"]] = scaler.transform(
|
||||
x_test[..., : args["input_dim"]]
|
||||
)
|
||||
|
||||
y_day = add_window_y(time_in_day, args["lag"], args["horizon"], single)
|
||||
y_week = add_window_y(day_in_week, args["lag"], args["horizon"], single)
|
||||
|
||||
del time_in_day, day_in_week
|
||||
gc.collect()
|
||||
|
||||
y = np.concatenate([y, y_day, y_week], axis=-1)
|
||||
|
||||
del y_day, y_week
|
||||
gc.collect()
|
||||
|
||||
# Split Y
|
||||
if args["test_ratio"] > 1:
|
||||
y_train, y_val, y_test = split_data_by_days(
|
||||
y, args["val_ratio"], args["test_ratio"]
|
||||
)
|
||||
else:
|
||||
y_train, y_val, y_test = split_data_by_ratio(
|
||||
y, args["val_ratio"], args["test_ratio"]
|
||||
)
|
||||
|
||||
del y
|
||||
gc.collect()
|
||||
|
||||
# Step 5: x_train y_train x_val y_val x_test y_test --> train val test
|
||||
# train_dataloader = data_loader(x_train[..., :args['input_dim']], y_train[..., :args['input_dim']], args['batch_size'], shuffle=True, drop_last=True)
|
||||
train_dataloader = data_loader(
|
||||
x_train, y_train, args["batch_size"], shuffle=True, drop_last=True
|
||||
)
|
||||
|
||||
del x_train, y_train
|
||||
gc.collect()
|
||||
|
||||
# val_dataloader = data_loader(x_val[..., :args['input_dim']], y_val[..., :args['input_dim']], args['batch_size'], shuffle=False, drop_last=True)
|
||||
val_dataloader = data_loader(
|
||||
x_val, y_val, args["batch_size"], shuffle=False, drop_last=True
|
||||
)
|
||||
|
||||
del x_val, y_val
|
||||
gc.collect()
|
||||
|
||||
# test_dataloader = data_loader(x_test[..., :args['input_dim']], y_test[..., :args['input_dim']], args['batch_size'], shuffle=False, drop_last=False)
|
||||
test_dataloader = data_loader(
|
||||
x_test, y_test, args["batch_size"], shuffle=False, drop_last=False
|
||||
)
|
||||
|
||||
del x_test, y_test
|
||||
gc.collect()
|
||||
|
||||
return train_dataloader, val_dataloader, test_dataloader, scaler
|
||||
|
||||
|
||||
def load_st_dataset(dataset):
|
||||
# output B, N, D
|
||||
match dataset:
|
||||
case "PEMSD3":
|
||||
data_path = os.path.join("./data/PEMS03/PEMS03.npz")
|
||||
data = np.load(data_path)["data"][
|
||||
:, :, 0
|
||||
] # only the first dimension, traffic flow data
|
||||
case "PEMSD4":
|
||||
data_path = os.path.join("./data/PEMS04/PEMS04.npz")
|
||||
data = np.load(data_path)["data"][
|
||||
:, :, 0
|
||||
] # only the first dimension, traffic flow data
|
||||
case "PEMSD7":
|
||||
data_path = os.path.join("./data/PEMS07/PEMS07.npz")
|
||||
data = np.load(data_path)["data"][
|
||||
:, :, 0
|
||||
] # only the first dimension, traffic flow data
|
||||
case "PEMSD8":
|
||||
data_path = os.path.join("./data/PEMS08/PEMS08.npz")
|
||||
data = np.load(data_path)["data"][
|
||||
:, :, 0
|
||||
] # only the first dimension, traffic flow data
|
||||
case "PEMSD7(L)":
|
||||
data_path = os.path.join("./data/PEMS07(L)/PEMS07L.npz")
|
||||
data = np.load(data_path)["data"][
|
||||
:, :, 0
|
||||
] # only the first dimension, traffic flow data
|
||||
case "PEMSD7(M)":
|
||||
data_path = os.path.join("./data/PEMS07(M)/V_228.csv")
|
||||
data = np.genfromtxt(
|
||||
data_path, delimiter=","
|
||||
) # Read CSV directly with numpy
|
||||
case "METR-LA":
|
||||
data_path = os.path.join("./data/METR-LA/METR.h5")
|
||||
with h5py.File(
|
||||
data_path, "r"
|
||||
) as f: # Use h5py to handle HDF5 files without pandas
|
||||
data = np.array(f["data"])
|
||||
case "BJ":
|
||||
data_path = os.path.join("./data/BJ/BJ500.csv")
|
||||
data = np.genfromtxt(
|
||||
data_path, delimiter=",", skip_header=1
|
||||
) # Skip header if present
|
||||
case "Hainan":
|
||||
data_path = os.path.join("./data/Hainan/Hainan.npz")
|
||||
data = np.load(data_path)["data"][:, :, 0]
|
||||
case _:
|
||||
raise ValueError(f"Unsupported dataset: {dataset}")
|
||||
|
||||
# Ensure data shape compatibility
|
||||
if len(data.shape) == 2:
|
||||
data = np.expand_dims(data, axis=-1)
|
||||
|
||||
print(
|
||||
"Load %s Dataset shaped: " % dataset,
|
||||
data.shape,
|
||||
data.max(),
|
||||
data.min(),
|
||||
data.mean(),
|
||||
np.median(data),
|
||||
)
|
||||
return data
|
||||
|
||||
|
||||
def split_data_by_days(data, val_days, test_days, interval=30):
|
||||
t = int((24 * 60) / interval)
|
||||
test_data = data[-t * int(test_days) :]
|
||||
val_data = data[-t * int(test_days + val_days) : -t * int(test_days)]
|
||||
train_data = data[: -t * int(test_days + val_days)]
|
||||
return train_data, val_data, test_data
|
||||
|
||||
|
||||
def split_data_by_ratio(data, val_ratio, test_ratio):
|
||||
data_len = data.shape[0]
|
||||
test_data = data[-int(data_len * test_ratio) :]
|
||||
val_data = data[
|
||||
-int(data_len * (test_ratio + val_ratio)) : -int(data_len * test_ratio)
|
||||
]
|
||||
train_data = data[: -int(data_len * (test_ratio + val_ratio))]
|
||||
return train_data, val_data, test_data
|
||||
|
||||
|
||||
def data_loader(X, Y, batch_size, shuffle=True, drop_last=True):
|
||||
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
||||
X = torch.tensor(X, dtype=torch.float32, device=device)
|
||||
Y = torch.tensor(Y, dtype=torch.float32, device=device)
|
||||
data = torch.utils.data.TensorDataset(X, Y)
|
||||
dataloader = torch.utils.data.DataLoader(
|
||||
data, batch_size=batch_size, shuffle=shuffle, drop_last=drop_last
|
||||
)
|
||||
return dataloader
|
||||
|
||||
|
||||
def add_window_x(data, window=3, horizon=1, single=False):
|
||||
"""
|
||||
Generate windowed X values from the input data.
|
||||
|
||||
:param data: Input data, shape [B, ...]
|
||||
:param window: Size of the sliding window
|
||||
:param horizon: Horizon size
|
||||
:param single: If True, generate single-step windows, else multi-step
|
||||
:return: X with shape [B, W, ...]
|
||||
"""
|
||||
length = len(data)
|
||||
end_index = length - horizon - window + 1
|
||||
x = [] # Sliding windows
|
||||
index = 0
|
||||
|
||||
while index < end_index:
|
||||
x.append(data[index : index + window])
|
||||
index += 1
|
||||
|
||||
return np.array(x)
|
||||
|
||||
|
||||
def add_window_y(data, window=3, horizon=1, single=False):
|
||||
"""
|
||||
Generate windowed Y values from the input data.
|
||||
|
||||
:param data: Input data, shape [B, ...]
|
||||
:param window: Size of the sliding window
|
||||
:param horizon: Horizon size
|
||||
:param single: If True, generate single-step windows, else multi-step
|
||||
:return: Y with shape [B, H, ...]
|
||||
"""
|
||||
length = len(data)
|
||||
end_index = length - horizon - window + 1
|
||||
y = [] # Horizon values
|
||||
index = 0
|
||||
|
||||
while index < end_index:
|
||||
if single:
|
||||
y.append(data[index + window + horizon - 1 : index + window + horizon])
|
||||
else:
|
||||
y.append(data[index + window : index + window + horizon])
|
||||
index += 1
|
||||
|
||||
return np.array(y)
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
from lib.normalization import normalize_dataset
|
||||
from utils.normalization import normalize_dataset
|
||||
import numpy as np
|
||||
import gc
|
||||
import os
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
import os
|
||||
import numpy as np
|
||||
import h5py
|
||||
|
||||
def load_st_dataset(config):
|
||||
dataset = config["basic"]["dataset"]
|
||||
sample = config["data"]["sample"]
|
||||
# output B, N, D
|
||||
match dataset:
|
||||
case "PEMS-BAY":
|
||||
data_path = os.path.join("./data/PEMS-BAY/pems-bay.h5")
|
||||
with h5py.File(data_path, 'r') as f:
|
||||
data = f['speed']['block0_values'][:]
|
||||
case "PEMSD3":
|
||||
data_path = os.path.join("./data/PEMS03/PEMS03.npz")
|
||||
data = np.load(data_path)["data"][
|
||||
:, :, 0
|
||||
]
|
||||
case "PEMSD4":
|
||||
data_path = os.path.join("./data/PEMS04/PEMS04.npz")
|
||||
data = np.load(data_path)["data"][
|
||||
:, :, 0
|
||||
]
|
||||
case "PEMSD7":
|
||||
data_path = os.path.join("./data/PEMS07/PEMS07.npz")
|
||||
data = np.load(data_path)["data"][
|
||||
:, :, 0
|
||||
]
|
||||
case "PEMSD8":
|
||||
data_path = os.path.join("./data/PEMS08/PEMS08.npz")
|
||||
data = np.load(data_path)["data"][
|
||||
:, :, 0
|
||||
]
|
||||
case "PEMSD7(L)":
|
||||
data_path = os.path.join("./data/PEMS07(L)/PEMS07L.npz")
|
||||
data = np.load(data_path)["data"][
|
||||
:, :, 0
|
||||
]
|
||||
case "PEMSD7(M)":
|
||||
data_path = os.path.join("./data/PEMS07(M)/V_228.csv")
|
||||
data = np.genfromtxt(
|
||||
data_path, delimiter=","
|
||||
)
|
||||
case "METR-LA":
|
||||
data_path = os.path.join("./data/METR-LA/METR.h5")
|
||||
with h5py.File(
|
||||
data_path, "r"
|
||||
) as f:
|
||||
data = np.array(f["data"])
|
||||
case "BJ":
|
||||
data_path = os.path.join("./data/BJ/BJ500.csv")
|
||||
data = np.genfromtxt(
|
||||
data_path, delimiter=",", skip_header=1
|
||||
)
|
||||
case "Hainan":
|
||||
data_path = os.path.join("./data/Hainan/Hainan.npz")
|
||||
data = np.load(data_path)["data"][:, :, 0]
|
||||
case "SD":
|
||||
data_path = os.path.join("./data/SD/data.npz")
|
||||
data = np.load(data_path)["data"][:, :, 0].astype(np.float32)
|
||||
case _:
|
||||
raise ValueError(f"Unsupported dataset: {dataset}")
|
||||
|
||||
# Ensure data shape compatibility
|
||||
if len(data.shape) == 2:
|
||||
data = np.expand_dims(data, axis=-1)
|
||||
|
||||
print("加载 %s 数据集中... " % dataset)
|
||||
return data[::sample]
|
||||
271
lib/LargeST.py
271
lib/LargeST.py
|
|
@ -1,271 +0,0 @@
|
|||
import pickle
|
||||
import torch
|
||||
import numpy as np
|
||||
import os
|
||||
import gc
|
||||
# ! X shape: (B, T, N, C)
|
||||
|
||||
|
||||
def load_pkl(pickle_file: str) -> object:
|
||||
"""
|
||||
Load data from a pickle file.
|
||||
|
||||
Args:
|
||||
pickle_file (str): Path to the pickle file.
|
||||
|
||||
Returns:
|
||||
object: Loaded object from the pickle file.
|
||||
"""
|
||||
|
||||
try:
|
||||
with open(pickle_file, "rb") as f:
|
||||
pickle_data = pickle.load(f)
|
||||
except UnicodeDecodeError:
|
||||
with open(pickle_file, "rb") as f:
|
||||
pickle_data = pickle.load(f, encoding="latin1")
|
||||
except Exception as e:
|
||||
print(f"Unable to load data from {pickle_file}: {e}")
|
||||
raise
|
||||
return pickle_data
|
||||
|
||||
|
||||
def get_dataloaders_from_index_data(
|
||||
data_dir, tod=False, dow=False, batch_size=64, log=None, train_size=0.6
|
||||
):
|
||||
data = np.load(os.path.join(data_dir, "data.npz"))["data"].astype(np.float32)
|
||||
|
||||
features = [0]
|
||||
if tod:
|
||||
features.append(1)
|
||||
if dow:
|
||||
features.append(2)
|
||||
# if dom:
|
||||
# features.append(3)
|
||||
data = data[..., features]
|
||||
|
||||
index = np.load(os.path.join(data_dir, "index.npz"))
|
||||
|
||||
train_index = index["train"] # (num_samples, 3)
|
||||
val_index = index["val"]
|
||||
test_index = index["test"]
|
||||
|
||||
x_train_index = vrange(train_index[:, 0], train_index[:, 1])
|
||||
y_train_index = vrange(train_index[:, 1], train_index[:, 2])
|
||||
x_val_index = vrange(val_index[:, 0], val_index[:, 1])
|
||||
y_val_index = vrange(val_index[:, 1], val_index[:, 2])
|
||||
x_test_index = vrange(test_index[:, 0], test_index[:, 1])
|
||||
y_test_index = vrange(test_index[:, 1], test_index[:, 2])
|
||||
|
||||
x_train = data[x_train_index]
|
||||
y_train = data[y_train_index][..., :1]
|
||||
x_val = data[x_val_index]
|
||||
y_val = data[y_val_index][..., :1]
|
||||
x_test = data[x_test_index]
|
||||
y_test = data[y_test_index][..., :1]
|
||||
|
||||
scaler = StandardScaler(mean=x_train[..., 0].mean(), std=x_train[..., 0].std())
|
||||
|
||||
x_train[..., 0] = scaler.transform(x_train[..., 0])
|
||||
x_val[..., 0] = scaler.transform(x_val[..., 0])
|
||||
x_test[..., 0] = scaler.transform(x_test[..., 0])
|
||||
|
||||
print_log(f"Trainset:\tx-{x_train.shape}\ty-{y_train.shape}", log=log)
|
||||
print_log(f"Valset: \tx-{x_val.shape} \ty-{y_val.shape}", log=log)
|
||||
print_log(f"Testset:\tx-{x_test.shape}\ty-{y_test.shape}", log=log)
|
||||
|
||||
trainset = torch.utils.data.TensorDataset(
|
||||
torch.FloatTensor(x_train), torch.FloatTensor(y_train)
|
||||
)
|
||||
valset = torch.utils.data.TensorDataset(
|
||||
torch.FloatTensor(x_val), torch.FloatTensor(y_val)
|
||||
)
|
||||
testset = torch.utils.data.TensorDataset(
|
||||
torch.FloatTensor(x_test), torch.FloatTensor(y_test)
|
||||
)
|
||||
if train_size != 0.6:
|
||||
drop_last = True
|
||||
else:
|
||||
drop_last = False
|
||||
trainset_loader = torch.utils.data.DataLoader(
|
||||
trainset, batch_size=batch_size, shuffle=True, drop_last=drop_last
|
||||
)
|
||||
valset_loader = torch.utils.data.DataLoader(
|
||||
valset, batch_size=batch_size, shuffle=False, drop_last=drop_last
|
||||
)
|
||||
testset_loader = torch.utils.data.DataLoader(
|
||||
testset, batch_size=batch_size, shuffle=False, drop_last=drop_last
|
||||
)
|
||||
|
||||
return trainset_loader, valset_loader, testset_loader, scaler
|
||||
|
||||
|
||||
def get_dataloaders_from_index_data_MTS(
|
||||
data_dir,
|
||||
in_steps=12,
|
||||
out_steps=12,
|
||||
tod=False,
|
||||
dow=False,
|
||||
y_tod=False,
|
||||
y_dow=False,
|
||||
batch_size=64,
|
||||
log=None,
|
||||
):
|
||||
data = np.load(os.path.join(data_dir, f"data.npz"))["data"].astype(np.float32)
|
||||
index = np.load(os.path.join(data_dir, f"index_{in_steps}_{out_steps}.npz"))
|
||||
|
||||
x_features = [0]
|
||||
if tod:
|
||||
x_features.append(1)
|
||||
if dow:
|
||||
x_features.append(2)
|
||||
|
||||
y_features = [0]
|
||||
if y_tod:
|
||||
y_features.append(1)
|
||||
if y_dow:
|
||||
y_features.append(2)
|
||||
|
||||
train_index = index["train"] # (num_samples, 3)
|
||||
val_index = index["val"]
|
||||
test_index = index["test"]
|
||||
|
||||
# Parallel
|
||||
# x_train_index = vrange(train_index[:, 0], train_index[:, 1])
|
||||
# y_train_index = vrange(train_index[:, 1], train_index[:, 2])
|
||||
# x_val_index = vrange(val_index[:, 0], val_index[:, 1])
|
||||
# y_val_index = vrange(val_index[:, 1], val_index[:, 2])
|
||||
# x_test_index = vrange(test_index[:, 0], test_index[:, 1])
|
||||
# y_test_index = vrange(test_index[:, 1], test_index[:, 2])
|
||||
|
||||
# x_train = data[x_train_index][..., x_features]
|
||||
# y_train = data[y_train_index][..., y_features]
|
||||
# x_val = data[x_val_index][..., x_features]
|
||||
# y_val = data[y_val_index][..., y_features]
|
||||
# x_test = data[x_test_index][..., x_features]
|
||||
# y_test = data[y_test_index][..., y_features]
|
||||
|
||||
# Iterative
|
||||
x_train = np.stack([data[idx[0] : idx[1]] for idx in train_index])[..., x_features]
|
||||
y_train = np.stack([data[idx[1] : idx[2]] for idx in train_index])[..., y_features]
|
||||
x_val = np.stack([data[idx[0] : idx[1]] for idx in val_index])[..., x_features]
|
||||
y_val = np.stack([data[idx[1] : idx[2]] for idx in val_index])[..., y_features]
|
||||
x_test = np.stack([data[idx[0] : idx[1]] for idx in test_index])[..., x_features]
|
||||
y_test = np.stack([data[idx[1] : idx[2]] for idx in test_index])[..., y_features]
|
||||
|
||||
scaler = StandardScaler(mean=x_train[..., 0].mean(), std=x_train[..., 0].std())
|
||||
|
||||
x_train[..., 0] = scaler.transform(x_train[..., 0])
|
||||
x_val[..., 0] = scaler.transform(x_val[..., 0])
|
||||
x_test[..., 0] = scaler.transform(x_test[..., 0])
|
||||
|
||||
print_log(f"Trainset:\tx-{x_train.shape}\ty-{y_train.shape}", log=log)
|
||||
print_log(f"Valset: \tx-{x_val.shape} \ty-{y_val.shape}", log=log)
|
||||
print_log(f"Testset:\tx-{x_test.shape}\ty-{y_test.shape}", log=log)
|
||||
|
||||
trainset = torch.utils.data.TensorDataset(
|
||||
torch.FloatTensor(x_train), torch.FloatTensor(y_train)
|
||||
)
|
||||
valset = torch.utils.data.TensorDataset(
|
||||
torch.FloatTensor(x_val), torch.FloatTensor(y_val)
|
||||
)
|
||||
testset = torch.utils.data.TensorDataset(
|
||||
torch.FloatTensor(x_test), torch.FloatTensor(y_test)
|
||||
)
|
||||
|
||||
trainset_loader = torch.utils.data.DataLoader(
|
||||
trainset, batch_size=batch_size, shuffle=True
|
||||
)
|
||||
valset_loader = torch.utils.data.DataLoader(
|
||||
valset, batch_size=batch_size, shuffle=False
|
||||
)
|
||||
testset_loader = torch.utils.data.DataLoader(
|
||||
testset, batch_size=batch_size, shuffle=False
|
||||
)
|
||||
|
||||
return trainset_loader, valset_loader, testset_loader, scaler
|
||||
|
||||
|
||||
def get_dataloaders_from_index_data_Test(
|
||||
data_dir,
|
||||
in_steps=12,
|
||||
out_steps=12,
|
||||
tod=False,
|
||||
dow=False,
|
||||
y_tod=False,
|
||||
y_dow=False,
|
||||
batch_size=64,
|
||||
log=None,
|
||||
):
|
||||
data = np.load(os.path.join(data_dir, f"data.npz"))["data"].astype(np.float32)
|
||||
index = np.load(os.path.join(data_dir, f"index_{in_steps}_{out_steps}.npz"))
|
||||
|
||||
x_features = [0]
|
||||
if tod:
|
||||
x_features.append(1)
|
||||
if dow:
|
||||
x_features.append(2)
|
||||
|
||||
y_features = [0]
|
||||
if y_tod:
|
||||
y_features.append(1)
|
||||
if y_dow:
|
||||
y_features.append(2)
|
||||
|
||||
train_index = index["train"] # (num_samples, 3)
|
||||
# val_index = index["val"]
|
||||
test_index = index["test"]
|
||||
|
||||
# Parallel
|
||||
# x_train_index = vrange(train_index[:, 0], train_index[:, 1])
|
||||
# y_train_index = vrange(train_index[:, 1], train_index[:, 2])
|
||||
# x_val_index = vrange(val_index[:, 0], val_index[:, 1])
|
||||
# y_val_index = vrange(val_index[:, 1], val_index[:, 2])
|
||||
# x_test_index = vrange(test_index[:, 0], test_index[:, 1])
|
||||
# y_test_index = vrange(test_index[:, 1], test_index[:, 2])
|
||||
|
||||
# x_train = data[x_train_index][..., x_features]
|
||||
# y_train = data[y_train_index][..., y_features]
|
||||
# x_val = data[x_val_index][..., x_features]
|
||||
# y_val = data[y_val_index][..., y_features]
|
||||
# x_test = data[x_test_index][..., x_features]
|
||||
# y_test = data[y_test_index][..., y_features]
|
||||
|
||||
# Iterative
|
||||
x_train = np.stack([data[idx[0] : idx[1]] for idx in train_index])[..., x_features]
|
||||
# y_train = np.stack([data[idx[1] : idx[2]] for idx in train_index])[..., y_features]
|
||||
# x_val = np.stack([data[idx[0] : idx[1]] for idx in val_index])[..., x_features]
|
||||
# y_val = np.stack([data[idx[1] : idx[2]] for idx in val_index])[..., y_features]
|
||||
x_test = np.stack([data[idx[0] : idx[1]] for idx in test_index])[..., x_features]
|
||||
y_test = np.stack([data[idx[1] : idx[2]] for idx in test_index])[..., y_features]
|
||||
|
||||
scaler = StandardScaler(mean=x_train[..., 0].mean(), std=x_train[..., 0].std())
|
||||
|
||||
# x_train[..., 0] = scaler.transform(x_train[..., 0])
|
||||
# x_val[..., 0] = scaler.transform(x_val[..., 0])
|
||||
x_test[..., 0] = scaler.transform(x_test[..., 0])
|
||||
|
||||
# print_log(f"Trainset:\tx-{x_train.shape}\ty-{y_train.shape}", log=log)
|
||||
# print_log(f"Valset: \tx-{x_val.shape} \ty-{y_val.shape}", log=log)
|
||||
print_log(f"Testset:\tx-{x_test.shape}\ty-{y_test.shape}", log=log)
|
||||
|
||||
# trainset = torch.utils.data.TensorDataset(
|
||||
# torch.FloatTensor(x_train), torch.FloatTensor(y_train)
|
||||
# )
|
||||
# valset = torch.utils.data.TensorDataset(
|
||||
# torch.FloatTensor(x_val), torch.FloatTensor(y_val)
|
||||
# )
|
||||
testset = torch.utils.data.TensorDataset(
|
||||
torch.FloatTensor(x_test), torch.FloatTensor(y_test)
|
||||
)
|
||||
|
||||
# trainset_loader = torch.utils.data.DataLoader(
|
||||
# trainset, batch_size=batch_size, shuffle=True
|
||||
# )
|
||||
# valset_loader = torch.utils.data.DataLoader(
|
||||
# valset, batch_size=batch_size, shuffle=False
|
||||
# )
|
||||
testset_loader = torch.utils.data.DataLoader(
|
||||
testset, batch_size=batch_size, shuffle=False
|
||||
)
|
||||
|
||||
return testset_loader, scaler
|
||||
|
|
@ -1,261 +0,0 @@
|
|||
import torch
|
||||
import math
|
||||
import os
|
||||
import time
|
||||
import copy
|
||||
import numpy as np
|
||||
|
||||
# import pynvml
|
||||
from lib.logger import get_logger
|
||||
from lib.loss_function import all_metrics
|
||||
|
||||
|
||||
class Trainer(object):
|
||||
def __init__(
|
||||
self,
|
||||
model,
|
||||
loss,
|
||||
optimizer,
|
||||
train_loader,
|
||||
val_loader,
|
||||
test_loader,
|
||||
scaler,
|
||||
args,
|
||||
lr_scheduler=None,
|
||||
):
|
||||
super(Trainer, self).__init__()
|
||||
self.model = model
|
||||
self.loss = loss
|
||||
self.optimizer = optimizer
|
||||
self.train_loader = train_loader
|
||||
self.val_loader = val_loader
|
||||
self.test_loader = test_loader
|
||||
self.scaler = scaler
|
||||
self.args = args
|
||||
self.lr_scheduler = lr_scheduler
|
||||
self.train_per_epoch = len(train_loader)
|
||||
if val_loader != None:
|
||||
self.val_per_epoch = len(val_loader)
|
||||
self.best_path = os.path.join(self.args["log_dir"], "best_model.pth")
|
||||
self.best_test_path = os.path.join(self.args["log_dir"], "best_test_model.pth")
|
||||
self.loss_figure_path = os.path.join(self.args["log_dir"], "loss.png")
|
||||
# log
|
||||
if os.path.isdir(args["log_dir"]) == False and not args["debug"]:
|
||||
os.makedirs(args["log_dir"], exist_ok=True)
|
||||
self.logger = get_logger(
|
||||
args["log_dir"], name=self.model.__class__.__name__, debug=args["debug"]
|
||||
)
|
||||
self.logger.info("Experiment log path in: {}".format(args["log_dir"]))
|
||||
|
||||
def val_epoch(self, epoch, val_dataloader):
|
||||
self.model.eval()
|
||||
total_val_loss = 0
|
||||
epoch_time = time.time()
|
||||
with torch.no_grad():
|
||||
for batch_idx, (data, target) in enumerate(val_dataloader):
|
||||
data = data
|
||||
label = target[..., : self.args["output_dim"]]
|
||||
output = self.model(data)
|
||||
if self.args["real_value"]:
|
||||
output = self.scaler.inverse_transform(output)
|
||||
loss = self.loss(output.cuda(), label)
|
||||
if not torch.isnan(loss):
|
||||
total_val_loss += loss.item()
|
||||
val_loss = total_val_loss / len(val_dataloader)
|
||||
self.logger.info(
|
||||
"Val Epoch {}: average Loss: {:.6f}, train time: {:.2f} s".format(
|
||||
epoch, val_loss, time.time() - epoch_time
|
||||
)
|
||||
)
|
||||
return val_loss
|
||||
|
||||
def test_epoch(self, epoch, test_dataloader):
|
||||
self.model.eval()
|
||||
total_test_loss = 0
|
||||
epoch_time = time.time()
|
||||
with torch.no_grad():
|
||||
for batch_idx, (data, target) in enumerate(test_dataloader):
|
||||
data = data
|
||||
label = target[..., : self.args["output_dim"]]
|
||||
output = self.model(data)
|
||||
if self.args["real_value"]:
|
||||
output = self.scaler.inverse_transform(output)
|
||||
loss = self.loss(output.cuda(), label)
|
||||
if not torch.isnan(loss):
|
||||
total_test_loss += loss.item()
|
||||
test_loss = total_test_loss / len(test_dataloader)
|
||||
self.logger.info(
|
||||
"test Epoch {}: average Loss: {:.6f}, train time: {:.2f} s".format(
|
||||
epoch, test_loss, time.time() - epoch_time
|
||||
)
|
||||
)
|
||||
return test_loss
|
||||
|
||||
def train_epoch(self, epoch):
|
||||
self.model.train()
|
||||
total_loss = 0
|
||||
epoch_time = time.time()
|
||||
for batch_idx, (data, target) in enumerate(self.train_loader):
|
||||
data = data
|
||||
label = target[..., : self.args["output_dim"]]
|
||||
self.optimizer.zero_grad()
|
||||
|
||||
output = self.model(data)
|
||||
if self.args["real_value"]:
|
||||
output = self.scaler.inverse_transform(output)
|
||||
|
||||
loss = self.loss(output.cuda(), label)
|
||||
loss.backward()
|
||||
|
||||
# add max grad clipping
|
||||
if self.args["grad_norm"]:
|
||||
torch.nn.utils.clip_grad_norm_(
|
||||
self.model.parameters(), self.args["max_grad_norm"]
|
||||
)
|
||||
self.optimizer.step()
|
||||
total_loss += loss.item()
|
||||
|
||||
# log information
|
||||
if (batch_idx + 1) % self.args["log_step"] == 0:
|
||||
self.logger.info(
|
||||
"Train Epoch {}: {}/{} Loss: {:.6f}".format(
|
||||
epoch, batch_idx + 1, self.train_per_epoch, loss.item()
|
||||
)
|
||||
)
|
||||
train_epoch_loss = total_loss / self.train_per_epoch
|
||||
self.logger.info(
|
||||
"Train Epoch {}: averaged Loss: {:.6f}, train time: {:.2f} s".format(
|
||||
epoch, train_epoch_loss, time.time() - epoch_time
|
||||
)
|
||||
)
|
||||
|
||||
# learning rate decay
|
||||
if self.args["lr_decay"]:
|
||||
self.lr_scheduler.step()
|
||||
return train_epoch_loss
|
||||
|
||||
def train(self):
|
||||
best_model = None
|
||||
best_test_model = None
|
||||
not_improved_count = 0
|
||||
best_loss = float("inf")
|
||||
best_test_loss = float("inf")
|
||||
vaild_loss = []
|
||||
for epoch in range(0, self.args["epochs"]):
|
||||
train_epoch_loss = self.train_epoch(epoch)
|
||||
if self.val_loader == None:
|
||||
val_dataloader = self.test_loader
|
||||
else:
|
||||
val_dataloader = self.val_loader
|
||||
test_dataloader = self.test_loader
|
||||
|
||||
val_epoch_loss = self.val_epoch(epoch, val_dataloader)
|
||||
vaild_loss.append(val_epoch_loss)
|
||||
|
||||
test_epoch_loss = self.test_epoch(epoch, test_dataloader)
|
||||
if train_epoch_loss > 1e6:
|
||||
self.logger.warning("Gradient explosion detected. Ending...")
|
||||
break
|
||||
|
||||
if val_epoch_loss < best_loss:
|
||||
best_loss = val_epoch_loss
|
||||
not_improved_count = 0
|
||||
best_state = True
|
||||
else:
|
||||
not_improved_count += 1
|
||||
best_state = False
|
||||
# early stop
|
||||
if self.args["early_stop"]:
|
||||
if not_improved_count == self.args["early_stop_patience"]:
|
||||
self.logger.info(
|
||||
"Validation performance didn't improve for {} epochs. "
|
||||
"Training stops.".format(self.args["early_stop_patience"])
|
||||
)
|
||||
break
|
||||
# save the best state
|
||||
if best_state == True:
|
||||
self.logger.info("Current best model saved!")
|
||||
best_model = copy.deepcopy(self.model.state_dict())
|
||||
|
||||
if test_epoch_loss < best_test_loss:
|
||||
best_test_loss = test_epoch_loss
|
||||
best_test_model = copy.deepcopy(self.model.state_dict())
|
||||
|
||||
# save the best model to file
|
||||
if not self.args["debug"]:
|
||||
torch.save(best_model, self.best_path)
|
||||
self.logger.info("Saving current best model to " + self.best_path)
|
||||
torch.save(best_test_model, self.best_test_path)
|
||||
self.logger.info("Saving current best model to " + self.best_test_path)
|
||||
|
||||
# test
|
||||
self.model.load_state_dict(best_model)
|
||||
self.test(self.model, self.args, self.test_loader, self.scaler, self.logger)
|
||||
|
||||
self.logger.info("This is best_test_model")
|
||||
self.model.load_state_dict(best_test_model)
|
||||
self.test(self.model, self.args, self.test_loader, self.scaler, self.logger)
|
||||
|
||||
def save_checkpoint(self):
|
||||
state = {
|
||||
"state_dict": self.model.state_dict(),
|
||||
"optimizer": self.optimizer.state_dict(),
|
||||
"config": self.args,
|
||||
}
|
||||
torch.save(state, self.best_path)
|
||||
self.logger.info("Saving current best model to " + self.best_path)
|
||||
|
||||
@staticmethod
|
||||
def test(model, args, data_loader, scaler, logger, path=None):
|
||||
if path != None:
|
||||
check_point = torch.load(path)
|
||||
state_dict = check_point["state_dict"]
|
||||
args = check_point["config"]
|
||||
model.load_state_dict(state_dict)
|
||||
model.to(args["device"])
|
||||
model.eval()
|
||||
y_pred = []
|
||||
y_true = []
|
||||
with torch.no_grad():
|
||||
for batch_idx, (data, target) in enumerate(data_loader):
|
||||
data = data
|
||||
label = target[..., : args["output_dim"]]
|
||||
output = model(data)
|
||||
y_true.append(label)
|
||||
y_pred.append(output)
|
||||
if args["real_value"]:
|
||||
y_pred = scaler.inverse_transform(torch.cat(y_pred, dim=0))
|
||||
y_true = torch.cat(y_true, dim=0)
|
||||
else:
|
||||
y_pred = torch.cat(y_pred, dim=0)
|
||||
y_true = torch.cat(y_true, dim=0)
|
||||
for t in range(y_true.shape[1]):
|
||||
mae, rmse, mape = all_metrics(
|
||||
y_pred[:, t, ...],
|
||||
y_true[:, t, ...],
|
||||
args["mae_thresh"],
|
||||
args["mape_thresh"],
|
||||
)
|
||||
logger.info(
|
||||
"Horizon {:02d}, MAE: {:.4f}, RMSE: {:.4f}, MAPE: {:.4f}".format(
|
||||
t + 1, mae, rmse, mape
|
||||
)
|
||||
)
|
||||
mae, rmse, mape = all_metrics(
|
||||
y_pred, y_true, args["mae_thresh"], args["mape_thresh"]
|
||||
)
|
||||
logger.info(
|
||||
"Average Horizon, MAE: {:.4f}, RMSE: {:.4f}, MAPE: {:.4f}".format(
|
||||
mae, rmse, mape
|
||||
)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _compute_sampling_threshold(global_step, k):
|
||||
"""
|
||||
Computes the sampling probability for scheduled sampling using inverse sigmoid.
|
||||
:param global_step:
|
||||
:param k:
|
||||
:return:
|
||||
"""
|
||||
return k / (k + math.exp(global_step / k))
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
if __name__ == "__main__":
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
# Kaggle 数据集列表
|
||||
datasets = {
|
||||
"hangzhou_taxi": "changyuheng/hz-taxi",
|
||||
"nyc_taxi": "new-york-city/nyc-taxi-trip-duration",
|
||||
"hangzhou_bike": "changyuheng/hz-bike",
|
||||
}
|
||||
|
||||
# 下载保存目录
|
||||
save_dir = "./datasets"
|
||||
os.makedirs(save_dir, exist_ok=True)
|
||||
|
||||
# 检查 Kaggle API 配置
|
||||
kaggle_json = os.path.expanduser("~/.kaggle/kaggle.json")
|
||||
if not os.path.exists(kaggle_json):
|
||||
raise FileNotFoundError(
|
||||
f"未找到 {kaggle_json},请先在 Kaggle 设置中下载并放置 API Key。"
|
||||
)
|
||||
|
||||
# 循环下载
|
||||
for name, kaggle_id in datasets.items():
|
||||
print(f"📥 正在下载 {name} ({kaggle_id}) ...")
|
||||
cmd = [
|
||||
"kaggle",
|
||||
"datasets",
|
||||
"download",
|
||||
"-d",
|
||||
kaggle_id,
|
||||
"-p",
|
||||
os.path.join(save_dir, name),
|
||||
"--unzip",
|
||||
]
|
||||
subprocess.run(cmd, check=True)
|
||||
print(f"✅ {name} 下载完成\n")
|
||||
|
||||
print("🎉 所有数据集已下载到", save_dir)
|
||||
|
|
@ -4,10 +4,10 @@ import os
|
|||
import time
|
||||
import copy
|
||||
import numpy as np
|
||||
from lib.logger import get_logger
|
||||
from utils.logger import get_logger
|
||||
from lib.metrics import All_Metrics
|
||||
from lib.TrainInits import print_model_parameters
|
||||
from lib.training_stats import TrainingStats
|
||||
from utils.training_stats import TrainingStats
|
||||
|
||||
|
||||
class Trainer(object):
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ import os
|
|||
import time
|
||||
import copy
|
||||
import numpy as np
|
||||
from lib.logger import get_logger
|
||||
from utils.logger import get_logger
|
||||
from lib.metrics import All_Metrics
|
||||
from lib.TrainInits import print_model_parameters
|
||||
from lib.training_stats import TrainingStats
|
||||
from utils.training_stats import TrainingStats
|
||||
|
||||
|
||||
class Trainer(object):
|
||||
|
|
|
|||
4
run.py
4
run.py
|
|
@ -3,7 +3,7 @@ import torch
|
|||
|
||||
# import time
|
||||
from config.args_parser import parse_args
|
||||
import lib.initializer as init
|
||||
import utils.initializer as init
|
||||
from dataloader.loader_selector import get_dataloader
|
||||
from trainer.trainer_selector import select_trainer
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ def main():
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from lib.Download_data import check_and_download_data
|
||||
from utils.Download_data import check_and_download_data
|
||||
|
||||
data_complete = check_and_download_data()
|
||||
assert data_complete is not None, "数据集下载失败,请重试!"
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import copy
|
|||
from tqdm import tqdm
|
||||
|
||||
import torch
|
||||
from lib.logger import get_logger
|
||||
from lib.loss_function import all_metrics
|
||||
from lib.training_stats import TrainingStats
|
||||
from utils.logger import get_logger
|
||||
from utils.loss_function import all_metrics
|
||||
from utils.training_stats import TrainingStats
|
||||
|
||||
|
||||
class Trainer:
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import copy
|
|||
from tqdm import tqdm
|
||||
|
||||
import torch
|
||||
from lib.logger import get_logger
|
||||
from lib.loss_function import all_metrics
|
||||
from lib.training_stats import TrainingStats
|
||||
from utils.logger import get_logger
|
||||
from utils.loss_function import all_metrics
|
||||
from utils.training_stats import TrainingStats
|
||||
|
||||
|
||||
class Trainer:
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import copy
|
|||
from tqdm import tqdm
|
||||
|
||||
import torch
|
||||
from lib.logger import get_logger
|
||||
from lib.loss_function import all_metrics
|
||||
from lib.training_stats import TrainingStats
|
||||
from utils.logger import get_logger
|
||||
from utils.loss_function import all_metrics
|
||||
from utils.training_stats import TrainingStats
|
||||
|
||||
|
||||
class Trainer:
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import copy
|
|||
from tqdm import tqdm
|
||||
|
||||
import torch
|
||||
from lib.logger import get_logger
|
||||
from lib.loss_function import all_metrics
|
||||
from lib.training_stats import TrainingStats
|
||||
from utils.logger import get_logger
|
||||
from utils.loss_function import all_metrics
|
||||
from utils.training_stats import TrainingStats
|
||||
|
||||
|
||||
class Trainer:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import math
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import copy
|
||||
import torch.nn.functional as F
|
||||
|
|
@ -8,10 +7,10 @@ import torch
|
|||
from torch import nn
|
||||
|
||||
from tqdm import tqdm
|
||||
from lib.logger import get_logger
|
||||
from lib.loss_function import all_metrics
|
||||
from utils.logger import get_logger
|
||||
from utils.loss_function import all_metrics
|
||||
from model.STMLP.STMLP import STMLP
|
||||
from lib.training_stats import TrainingStats
|
||||
from utils.training_stats import TrainingStats
|
||||
|
||||
|
||||
class Trainer:
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import time
|
|||
import copy
|
||||
import psutil
|
||||
import torch
|
||||
from lib.logger import get_logger
|
||||
from lib.loss_function import all_metrics
|
||||
from utils.logger import get_logger
|
||||
from utils.loss_function import all_metrics
|
||||
|
||||
|
||||
class TrainingStats:
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import copy
|
|||
from tqdm import tqdm
|
||||
|
||||
import torch
|
||||
from lib.logger import get_logger
|
||||
from lib.loss_function import all_metrics
|
||||
from lib.training_stats import TrainingStats
|
||||
from utils.logger import get_logger
|
||||
from utils.loss_function import all_metrics
|
||||
from utils.training_stats import TrainingStats
|
||||
|
||||
|
||||
class Trainer:
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ import copy
|
|||
from tqdm import tqdm
|
||||
|
||||
import torch
|
||||
from lib.logger import get_logger
|
||||
from lib.loss_function import all_metrics
|
||||
from lib.training_stats import TrainingStats
|
||||
from utils.logger import get_logger
|
||||
from utils.loss_function import all_metrics
|
||||
from utils.training_stats import TrainingStats
|
||||
|
||||
|
||||
class Trainer:
|
||||
|
|
|
|||
|
|
@ -1,80 +0,0 @@
|
|||
import pandas as pd
|
||||
import numpy as np
|
||||
import os
|
||||
from statsmodels.tsa.stattools import adfuller
|
||||
from arch.unitroot import ADF
|
||||
|
||||
|
||||
def calculate_ADF(root_path, data_path):
|
||||
df_raw = pd.read_csv(os.path.join(root_path, data_path))
|
||||
cols = list(df_raw.columns)
|
||||
cols.remove("date")
|
||||
df_raw = df_raw[cols]
|
||||
adf_list = []
|
||||
for i in cols:
|
||||
df_data = df_raw[i]
|
||||
adf = adfuller(df_data, maxlag=1)
|
||||
print(adf)
|
||||
adf_list.append(adf)
|
||||
return np.array(adf_list)
|
||||
|
||||
|
||||
def calculate_target_ADF(root_path, data_path, target="OT"):
|
||||
df_raw = pd.read_csv(os.path.join(root_path, data_path))
|
||||
target_cols = target.split(",")
|
||||
# df_data = df_raw[target]
|
||||
df_raw = df_raw[target_cols]
|
||||
adf_list = []
|
||||
for i in target_cols:
|
||||
df_data = df_raw[i]
|
||||
adf = adfuller(df_data, maxlag=1)
|
||||
# print(adf)
|
||||
adf_list.append(adf)
|
||||
return np.array(adf_list)
|
||||
|
||||
|
||||
def archADF(root_path, data_path):
|
||||
df = pd.read_csv(os.path.join(root_path, data_path))
|
||||
cols = df.columns[1:]
|
||||
stats = 0
|
||||
for target_col in cols:
|
||||
series = df[target_col].values
|
||||
adf = ADF(series)
|
||||
stat = adf.stat
|
||||
stats += stat
|
||||
return stats / len(cols)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# * Exchange - result: -1.902402344564288 | report: -1.889
|
||||
ADFmetric = archADF(
|
||||
root_path="./dataset/exchange_rate/", data_path="exchange_rate.csv"
|
||||
)
|
||||
print("Exchange ADF metric", ADFmetric)
|
||||
|
||||
# * Illness - result: -5.33416661870624 | report: -5.406
|
||||
ADFmetric = archADF(
|
||||
root_path="./dataset/illness/", data_path="national_illness.csv"
|
||||
)
|
||||
print("Illness ADF metric", ADFmetric)
|
||||
|
||||
# * ETTm2 - result: -5.663628743471695 | report: -6.225
|
||||
ADFmetric = archADF(root_path="./dataset/ETT-small/", data_path="ETTm2.csv")
|
||||
print("ETTm2 ADF metric", ADFmetric)
|
||||
|
||||
# * Electricity - result: -8.44485821939281 | report: -8.483
|
||||
ADFmetric = archADF(root_path="./dataset/electricity/", data_path="electricity.csv")
|
||||
print("Electricity ADF metric", ADFmetric)
|
||||
|
||||
# * Traffic - result: -15.020978067839014 | report: -15.046
|
||||
ADFmetric = archADF(root_path="./dataset/traffic/", data_path="traffic.csv")
|
||||
print("Traffic ADF metric", ADFmetric)
|
||||
|
||||
# * Weather - result: -26.681433085204866 | report: -26.661
|
||||
ADFmetric = archADF(root_path="./dataset/weather/", data_path="weather.csv")
|
||||
print("Weather ADF metric", ADFmetric)
|
||||
|
||||
# print(ADFmetric)
|
||||
|
||||
# mean_ADFmetric = ADFmetric[:,0].mean()
|
||||
# print(mean_ADFmetric)
|
||||
|
|
@ -43,6 +43,11 @@ def check_and_download_data():
|
|||
"PEMS08_dtw_distance.npy",
|
||||
"PEMS08_spatial_distance.npy",
|
||||
],
|
||||
"PEMS-BAY": [
|
||||
"adj_mx_bay.pkl",
|
||||
"pems-bay-meta.h5",
|
||||
"pems-bay.h5"
|
||||
]
|
||||
}
|
||||
|
||||
current_dir = os.getcwd() # 获取当前工作目录
|
||||
|
|
@ -90,7 +95,12 @@ def check_and_download_data():
|
|||
if missing_adj:
|
||||
download_adj_data(current_dir)
|
||||
if missing_main_files:
|
||||
download_kaggle_data(current_dir)
|
||||
download_kaggle_data(current_dir, 'elmahy/pems-dataset')
|
||||
download_kaggle_data(current_dir, 'scchuy/pemsbay')
|
||||
|
||||
rearrange_dir()
|
||||
|
||||
|
||||
|
||||
return True
|
||||
|
||||
|
|
@ -143,32 +153,59 @@ def download_adj_data(current_dir, max_retries=3):
|
|||
)
|
||||
|
||||
|
||||
def download_kaggle_data(current_dir):
|
||||
def download_kaggle_data(current_dir, kaggle_path):
|
||||
"""
|
||||
下载 KaggleHub 数据集,并将 data 文件夹合并到当前工作目录。
|
||||
下载 KaggleHub 数据集,并将数据直接移动到当前工作目录的 data 文件夹。
|
||||
如果目标文件夹已存在,会覆盖冲突的文件。
|
||||
"""
|
||||
try:
|
||||
print("正在下载 PEMS 数据集...")
|
||||
path = kagglehub.dataset_download("elmahy/pems-dataset")
|
||||
print(f"正在下载 {kaggle_path} 数据集...")
|
||||
path = kagglehub.dataset_download(kaggle_path)
|
||||
# print("Path to KaggleHub dataset files:", path)
|
||||
|
||||
if os.path.exists(path):
|
||||
data_folder_path = os.path.join(path, "data")
|
||||
if os.path.exists(data_folder_path):
|
||||
destination_path = os.path.join(current_dir, "data")
|
||||
|
||||
# 使用 shutil.copytree 合并文件夹,覆盖冲突的文件
|
||||
shutil.copytree(data_folder_path, destination_path, dirs_exist_ok=True)
|
||||
# print(f"data 文件夹已合并到: {destination_path}")
|
||||
# else:
|
||||
# print("未找到 data 文件夹,跳过合并操作。")
|
||||
# else:
|
||||
# print("未找到 KaggleHub 数据集路径,跳过处理。")
|
||||
destination_path = os.path.join(current_dir, "data")
|
||||
# 使用 shutil.copytree 将文件夹内容直接放在 data 文件夹下,覆盖冲突的文件
|
||||
shutil.copytree(path, destination_path, dirs_exist_ok=True)
|
||||
except Exception as e:
|
||||
print(f"下载或处理 KaggleHub 数据集时出错: {e}")
|
||||
|
||||
|
||||
|
||||
def rearrange_dir():
|
||||
"""
|
||||
将 data/data 中的文件合并到上级目录,并删除 data/data 目录。
|
||||
"""
|
||||
data_dir = os.path.join(os.getcwd(), "data")
|
||||
nested_data_dir = os.path.join(data_dir, "data")
|
||||
|
||||
if os.path.exists(nested_data_dir) and os.path.isdir(nested_data_dir):
|
||||
for item in os.listdir(nested_data_dir):
|
||||
source_path = os.path.join(nested_data_dir, item)
|
||||
destination_path = os.path.join(data_dir, item)
|
||||
|
||||
if os.path.isdir(source_path):
|
||||
shutil.copytree(source_path, destination_path, dirs_exist_ok=True)
|
||||
else:
|
||||
shutil.copy2(source_path, destination_path)
|
||||
|
||||
shutil.rmtree(nested_data_dir)
|
||||
# print(f"已合并 {nested_data_dir} 到 {data_dir},并删除嵌套目录。")
|
||||
|
||||
# 将带有 "bay" 的文件移动到 PEMS-BAY 文件夹
|
||||
pems_bay_dir = os.path.join(data_dir, "PEMS-BAY")
|
||||
os.makedirs(pems_bay_dir, exist_ok=True)
|
||||
|
||||
for item in os.listdir(data_dir):
|
||||
if "bay" in item.lower() and (item.endswith(".pkl") or item.endswith(".h5")):
|
||||
source_path = os.path.join(data_dir, item)
|
||||
destination_path = os.path.join(pems_bay_dir, item)
|
||||
shutil.move(source_path, destination_path)
|
||||
|
||||
# print(f"已将带有 'bay' 的文件移动到 {pems_bay_dir}。")
|
||||
|
||||
|
||||
# 主程序
|
||||
if __name__ == "__main__":
|
||||
check_and_download_data()
|
||||
# rearrange_dir()
|
||||
|
|
@ -1,613 +0,0 @@
|
|||
import numpy as np
|
||||
from tqdm import tqdm
|
||||
|
||||
|
||||
def jitter(x, sigma=0.03):
|
||||
# https://arxiv.org/pdf/1706.00527.pdf
|
||||
return x + np.random.normal(loc=0.0, scale=sigma, size=x.shape)
|
||||
|
||||
|
||||
def scaling(x, sigma=0.1):
|
||||
# https://arxiv.org/pdf/1706.00527.pdf
|
||||
factor = np.random.normal(loc=1.0, scale=sigma, size=(x.shape[0], x.shape[2]))
|
||||
return np.multiply(x, factor[:, np.newaxis, :])
|
||||
|
||||
|
||||
def rotation(x):
|
||||
x = np.array(x)
|
||||
flip = np.random.choice([-1, 1], size=(x.shape[0], x.shape[2]))
|
||||
rotate_axis = np.arange(x.shape[2])
|
||||
np.random.shuffle(rotate_axis)
|
||||
return flip[:, np.newaxis, :] * x[:, :, rotate_axis]
|
||||
|
||||
|
||||
def permutation(x, max_segments=5, seg_mode="equal"):
|
||||
orig_steps = np.arange(x.shape[1])
|
||||
|
||||
num_segs = np.random.randint(1, max_segments, size=(x.shape[0]))
|
||||
|
||||
ret = np.zeros_like(x)
|
||||
for i, pat in enumerate(x):
|
||||
if num_segs[i] > 1:
|
||||
if seg_mode == "random":
|
||||
split_points = np.random.choice(
|
||||
x.shape[1] - 2, num_segs[i] - 1, replace=False
|
||||
)
|
||||
split_points.sort()
|
||||
splits = np.split(orig_steps, split_points)
|
||||
else:
|
||||
splits = np.array_split(orig_steps, num_segs[i])
|
||||
warp = np.concatenate(np.random.permutation(splits)).ravel()
|
||||
# ? Question: What is the point of making segments?
|
||||
# for i in range(len(splits)):
|
||||
# permute = np.random.permutation(splits[i])
|
||||
|
||||
ret[i] = pat[warp]
|
||||
else:
|
||||
ret[i] = pat
|
||||
return ret
|
||||
|
||||
|
||||
def magnitude_warp(x, sigma=0.2, knot=4):
|
||||
from scipy.interpolate import CubicSpline
|
||||
|
||||
orig_steps = np.arange(x.shape[1])
|
||||
|
||||
random_warps = np.random.normal(
|
||||
loc=1.0, scale=sigma, size=(x.shape[0], knot + 2, x.shape[2])
|
||||
)
|
||||
warp_steps = (
|
||||
np.ones((x.shape[2], 1)) * (np.linspace(0, x.shape[1] - 1.0, num=knot + 2))
|
||||
).T
|
||||
ret = np.zeros_like(x)
|
||||
for i, pat in enumerate(x):
|
||||
warper = np.array(
|
||||
[
|
||||
CubicSpline(warp_steps[:, dim], random_warps[i, :, dim])(orig_steps)
|
||||
for dim in range(x.shape[2])
|
||||
]
|
||||
).T
|
||||
ret[i] = pat * warper
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def time_warp(x, sigma=0.2, knot=4):
|
||||
from scipy.interpolate import CubicSpline
|
||||
|
||||
orig_steps = np.arange(x.shape[1])
|
||||
|
||||
random_warps = np.random.normal(
|
||||
loc=1.0, scale=sigma, size=(x.shape[0], knot + 2, x.shape[2])
|
||||
)
|
||||
warp_steps = (
|
||||
np.ones((x.shape[2], 1)) * (np.linspace(0, x.shape[1] - 1.0, num=knot + 2))
|
||||
).T
|
||||
|
||||
ret = np.zeros_like(x)
|
||||
for i, pat in enumerate(x):
|
||||
for dim in range(x.shape[2]):
|
||||
time_warp = CubicSpline(
|
||||
warp_steps[:, dim], warp_steps[:, dim] * random_warps[i, :, dim]
|
||||
)(orig_steps)
|
||||
scale = (x.shape[1] - 1) / time_warp[-1]
|
||||
ret[i, :, dim] = np.interp(
|
||||
orig_steps, np.clip(scale * time_warp, 0, x.shape[1] - 1), pat[:, dim]
|
||||
).T
|
||||
return ret
|
||||
|
||||
|
||||
def window_slice(x, reduce_ratio=0.9):
|
||||
# https://halshs.archives-ouvertes.fr/halshs-01357973/document
|
||||
target_len = np.ceil(reduce_ratio * x.shape[1]).astype(int)
|
||||
if target_len >= x.shape[1]:
|
||||
return x
|
||||
starts = np.random.randint(
|
||||
low=0, high=x.shape[1] - target_len, size=(x.shape[0])
|
||||
).astype(int)
|
||||
ends = (target_len + starts).astype(int)
|
||||
|
||||
ret = np.zeros_like(x)
|
||||
for i, pat in enumerate(x):
|
||||
for dim in range(x.shape[2]):
|
||||
ret[i, :, dim] = np.interp(
|
||||
np.linspace(0, target_len, num=x.shape[1]),
|
||||
np.arange(target_len),
|
||||
pat[starts[i] : ends[i], dim],
|
||||
).T
|
||||
return ret
|
||||
|
||||
|
||||
def window_warp(x, window_ratio=0.1, scales=[0.5, 2.0]):
|
||||
# https://halshs.archives-ouvertes.fr/halshs-01357973/document
|
||||
warp_scales = np.random.choice(scales, x.shape[0])
|
||||
warp_size = np.ceil(window_ratio * x.shape[1]).astype(int)
|
||||
window_steps = np.arange(warp_size)
|
||||
|
||||
window_starts = np.random.randint(
|
||||
low=1, high=x.shape[1] - warp_size - 1, size=(x.shape[0])
|
||||
).astype(int)
|
||||
window_ends = (window_starts + warp_size).astype(int)
|
||||
|
||||
ret = np.zeros_like(x)
|
||||
for i, pat in enumerate(x):
|
||||
for dim in range(x.shape[2]):
|
||||
start_seg = pat[: window_starts[i], dim]
|
||||
window_seg = np.interp(
|
||||
np.linspace(0, warp_size - 1, num=int(warp_size * warp_scales[i])),
|
||||
window_steps,
|
||||
pat[window_starts[i] : window_ends[i], dim],
|
||||
)
|
||||
end_seg = pat[window_ends[i] :, dim]
|
||||
warped = np.concatenate((start_seg, window_seg, end_seg))
|
||||
ret[i, :, dim] = np.interp(
|
||||
np.arange(x.shape[1]),
|
||||
np.linspace(0, x.shape[1] - 1.0, num=warped.size),
|
||||
warped,
|
||||
).T
|
||||
return ret
|
||||
|
||||
|
||||
def spawner(x, labels, sigma=0.05, verbose=0):
|
||||
# https://www.ncbi.nlm.nih.gov/pmc/articles/PMC6983028/
|
||||
# use verbose=-1 to turn off warnings
|
||||
# use verbose=1 to print out figures
|
||||
|
||||
import utils.dtw as dtw
|
||||
|
||||
random_points = np.random.randint(low=1, high=x.shape[1] - 1, size=x.shape[0])
|
||||
window = np.ceil(x.shape[1] / 10.0).astype(int)
|
||||
orig_steps = np.arange(x.shape[1])
|
||||
l = np.argmax(labels, axis=1) if labels.ndim > 1 else labels
|
||||
|
||||
ret = np.zeros_like(x)
|
||||
# for i, pat in enumerate(tqdm(x)):
|
||||
for i, pat in enumerate(x):
|
||||
# guarentees that same one isnt selected
|
||||
choices = np.delete(np.arange(x.shape[0]), i)
|
||||
# remove ones of different classes
|
||||
choices = np.where(l[choices] == l[i])[0]
|
||||
if choices.size > 0:
|
||||
random_sample = x[np.random.choice(choices)]
|
||||
# SPAWNER splits the path into two randomly
|
||||
path1 = dtw.dtw(
|
||||
pat[: random_points[i]],
|
||||
random_sample[: random_points[i]],
|
||||
dtw.RETURN_PATH,
|
||||
slope_constraint="symmetric",
|
||||
window=window,
|
||||
)
|
||||
path2 = dtw.dtw(
|
||||
pat[random_points[i] :],
|
||||
random_sample[random_points[i] :],
|
||||
dtw.RETURN_PATH,
|
||||
slope_constraint="symmetric",
|
||||
window=window,
|
||||
)
|
||||
combined = np.concatenate(
|
||||
(np.vstack(path1), np.vstack(path2 + random_points[i])), axis=1
|
||||
)
|
||||
if verbose:
|
||||
# print(random_points[i])
|
||||
dtw_value, cost, DTW_map, path = dtw.dtw(
|
||||
pat,
|
||||
random_sample,
|
||||
return_flag=dtw.RETURN_ALL,
|
||||
slope_constraint=slope_constraint,
|
||||
window=window,
|
||||
)
|
||||
dtw.draw_graph1d(cost, DTW_map, path, pat, random_sample)
|
||||
dtw.draw_graph1d(cost, DTW_map, combined, pat, random_sample)
|
||||
mean = np.mean([pat[combined[0]], random_sample[combined[1]]], axis=0)
|
||||
for dim in range(x.shape[2]):
|
||||
ret[i, :, dim] = np.interp(
|
||||
orig_steps,
|
||||
np.linspace(0, x.shape[1] - 1.0, num=mean.shape[0]),
|
||||
mean[:, dim],
|
||||
).T
|
||||
else:
|
||||
# if verbose > -1:
|
||||
# print("There is only one pattern of class {}, skipping pattern average".format(l[i]))
|
||||
ret[i, :] = pat
|
||||
return jitter(ret, sigma=sigma)
|
||||
|
||||
|
||||
def wdba(
|
||||
x, labels, batch_size=6, slope_constraint="symmetric", use_window=True, verbose=0
|
||||
):
|
||||
# https://ieeexplore.ieee.org/document/8215569
|
||||
# use verbose = -1 to turn off warnings
|
||||
# slope_constraint is for DTW. "symmetric" or "asymmetric"
|
||||
x = np.array(x)
|
||||
import utils.dtw as dtw
|
||||
|
||||
if use_window:
|
||||
window = np.ceil(x.shape[1] / 10.0).astype(int)
|
||||
else:
|
||||
window = None
|
||||
orig_steps = np.arange(x.shape[1])
|
||||
l = np.argmax(labels, axis=1) if labels.ndim > 1 else labels
|
||||
|
||||
ret = np.zeros_like(x)
|
||||
# for i in tqdm(range(ret.shape[0])):
|
||||
for i in range(ret.shape[0]):
|
||||
# get the same class as i
|
||||
choices = np.where(l == l[i])[0]
|
||||
if choices.size > 0:
|
||||
# pick random intra-class pattern
|
||||
k = min(choices.size, batch_size)
|
||||
random_prototypes = x[np.random.choice(choices, k, replace=False)]
|
||||
|
||||
# calculate dtw between all
|
||||
dtw_matrix = np.zeros((k, k))
|
||||
for p, prototype in enumerate(random_prototypes):
|
||||
for s, sample in enumerate(random_prototypes):
|
||||
if p == s:
|
||||
dtw_matrix[p, s] = 0.0
|
||||
else:
|
||||
dtw_matrix[p, s] = dtw.dtw(
|
||||
prototype,
|
||||
sample,
|
||||
dtw.RETURN_VALUE,
|
||||
slope_constraint=slope_constraint,
|
||||
window=window,
|
||||
)
|
||||
|
||||
# get medoid
|
||||
medoid_id = np.argsort(np.sum(dtw_matrix, axis=1))[0]
|
||||
nearest_order = np.argsort(dtw_matrix[medoid_id])
|
||||
medoid_pattern = random_prototypes[medoid_id]
|
||||
|
||||
# start weighted DBA
|
||||
average_pattern = np.zeros_like(medoid_pattern)
|
||||
weighted_sums = np.zeros((medoid_pattern.shape[0]))
|
||||
for nid in nearest_order:
|
||||
if nid == medoid_id or dtw_matrix[medoid_id, nearest_order[1]] == 0.0:
|
||||
average_pattern += medoid_pattern
|
||||
weighted_sums += np.ones_like(weighted_sums)
|
||||
else:
|
||||
path = dtw.dtw(
|
||||
medoid_pattern,
|
||||
random_prototypes[nid],
|
||||
dtw.RETURN_PATH,
|
||||
slope_constraint=slope_constraint,
|
||||
window=window,
|
||||
)
|
||||
dtw_value = dtw_matrix[medoid_id, nid]
|
||||
warped = random_prototypes[nid, path[1]]
|
||||
weight = np.exp(
|
||||
np.log(0.5)
|
||||
* dtw_value
|
||||
/ dtw_matrix[medoid_id, nearest_order[1]]
|
||||
)
|
||||
average_pattern[path[0]] += weight * warped
|
||||
weighted_sums[path[0]] += weight
|
||||
|
||||
ret[i, :] = average_pattern / weighted_sums[:, np.newaxis]
|
||||
else:
|
||||
# if verbose > -1:
|
||||
# print("There is only one pattern of class {}, skipping pattern average".format(l[i]))
|
||||
ret[i, :] = x[i]
|
||||
return ret
|
||||
|
||||
|
||||
# Proposed
|
||||
|
||||
|
||||
def random_guided_warp(
|
||||
x,
|
||||
labels,
|
||||
slope_constraint="symmetric",
|
||||
use_window=True,
|
||||
dtw_type="normal",
|
||||
verbose=0,
|
||||
):
|
||||
# use verbose = -1 to turn off warnings
|
||||
# slope_constraint is for DTW. "symmetric" or "asymmetric"
|
||||
# dtw_type is for shapeDTW or DTW. "normal" or "shape"
|
||||
|
||||
import utils.dtw as dtw
|
||||
|
||||
if use_window:
|
||||
window = np.ceil(x.shape[1] / 10.0).astype(int)
|
||||
else:
|
||||
window = None
|
||||
orig_steps = np.arange(x.shape[1])
|
||||
l = np.argmax(labels, axis=1) if labels.ndim > 1 else labels
|
||||
|
||||
ret = np.zeros_like(x)
|
||||
# for i, pat in enumerate(tqdm(x)):
|
||||
for i, pat in enumerate(x):
|
||||
# guarentees that same one isnt selected
|
||||
choices = np.delete(np.arange(x.shape[0]), i)
|
||||
# remove ones of different classes
|
||||
choices = np.where(l[choices] == l[i])[0]
|
||||
if choices.size > 0:
|
||||
# pick random intra-class pattern
|
||||
random_prototype = x[np.random.choice(choices)]
|
||||
|
||||
if dtw_type == "shape":
|
||||
path = dtw.shape_dtw(
|
||||
random_prototype,
|
||||
pat,
|
||||
dtw.RETURN_PATH,
|
||||
slope_constraint=slope_constraint,
|
||||
window=window,
|
||||
)
|
||||
else:
|
||||
path = dtw.dtw(
|
||||
random_prototype,
|
||||
pat,
|
||||
dtw.RETURN_PATH,
|
||||
slope_constraint=slope_constraint,
|
||||
window=window,
|
||||
)
|
||||
|
||||
# Time warp
|
||||
warped = pat[path[1]]
|
||||
for dim in range(x.shape[2]):
|
||||
ret[i, :, dim] = np.interp(
|
||||
orig_steps,
|
||||
np.linspace(0, x.shape[1] - 1.0, num=warped.shape[0]),
|
||||
warped[:, dim],
|
||||
).T
|
||||
else:
|
||||
# if verbose > -1:
|
||||
# print("There is only one pattern of class {}, skipping timewarping".format(l[i]))
|
||||
ret[i, :] = pat
|
||||
return ret
|
||||
|
||||
|
||||
def random_guided_warp_shape(x, labels, slope_constraint="symmetric", use_window=True):
|
||||
return random_guided_warp(x, labels, slope_constraint, use_window, dtw_type="shape")
|
||||
|
||||
|
||||
def discriminative_guided_warp(
|
||||
x,
|
||||
labels,
|
||||
batch_size=6,
|
||||
slope_constraint="symmetric",
|
||||
use_window=True,
|
||||
dtw_type="normal",
|
||||
use_variable_slice=True,
|
||||
verbose=0,
|
||||
):
|
||||
# use verbose = -1 to turn off warnings
|
||||
# slope_constraint is for DTW. "symmetric" or "asymmetric"
|
||||
# dtw_type is for shapeDTW or DTW. "normal" or "shape"
|
||||
|
||||
import utils.dtw as dtw
|
||||
|
||||
if use_window:
|
||||
window = np.ceil(x.shape[1] / 10.0).astype(int)
|
||||
else:
|
||||
window = None
|
||||
orig_steps = np.arange(x.shape[1])
|
||||
l = np.argmax(labels, axis=1) if labels.ndim > 1 else labels
|
||||
|
||||
positive_batch = np.ceil(batch_size / 2).astype(int)
|
||||
negative_batch = np.floor(batch_size / 2).astype(int)
|
||||
|
||||
ret = np.zeros_like(x)
|
||||
warp_amount = np.zeros(x.shape[0])
|
||||
# for i, pat in enumerate(tqdm(x)):
|
||||
for i, pat in enumerate(x):
|
||||
# guarentees that same one isnt selected
|
||||
choices = np.delete(np.arange(x.shape[0]), i)
|
||||
|
||||
# remove ones of different classes
|
||||
positive = np.where(l[choices] == l[i])[0]
|
||||
negative = np.where(l[choices] != l[i])[0]
|
||||
|
||||
if positive.size > 0 and negative.size > 0:
|
||||
pos_k = min(positive.size, positive_batch)
|
||||
neg_k = min(negative.size, negative_batch)
|
||||
positive_prototypes = x[np.random.choice(positive, pos_k, replace=False)]
|
||||
negative_prototypes = x[np.random.choice(negative, neg_k, replace=False)]
|
||||
|
||||
# vector embedding and nearest prototype in one
|
||||
pos_aves = np.zeros((pos_k))
|
||||
neg_aves = np.zeros((pos_k))
|
||||
if dtw_type == "shape":
|
||||
for p, pos_prot in enumerate(positive_prototypes):
|
||||
for ps, pos_samp in enumerate(positive_prototypes):
|
||||
if p != ps:
|
||||
pos_aves[p] += (1.0 / (pos_k - 1.0)) * dtw.shape_dtw(
|
||||
pos_prot,
|
||||
pos_samp,
|
||||
dtw.RETURN_VALUE,
|
||||
slope_constraint=slope_constraint,
|
||||
window=window,
|
||||
)
|
||||
for ns, neg_samp in enumerate(negative_prototypes):
|
||||
neg_aves[p] += (1.0 / neg_k) * dtw.shape_dtw(
|
||||
pos_prot,
|
||||
neg_samp,
|
||||
dtw.RETURN_VALUE,
|
||||
slope_constraint=slope_constraint,
|
||||
window=window,
|
||||
)
|
||||
selected_id = np.argmax(neg_aves - pos_aves)
|
||||
path = dtw.shape_dtw(
|
||||
positive_prototypes[selected_id],
|
||||
pat,
|
||||
dtw.RETURN_PATH,
|
||||
slope_constraint=slope_constraint,
|
||||
window=window,
|
||||
)
|
||||
else:
|
||||
for p, pos_prot in enumerate(positive_prototypes):
|
||||
for ps, pos_samp in enumerate(positive_prototypes):
|
||||
if p != ps:
|
||||
pos_aves[p] += (1.0 / (pos_k - 1.0)) * dtw.dtw(
|
||||
pos_prot,
|
||||
pos_samp,
|
||||
dtw.RETURN_VALUE,
|
||||
slope_constraint=slope_constraint,
|
||||
window=window,
|
||||
)
|
||||
for ns, neg_samp in enumerate(negative_prototypes):
|
||||
neg_aves[p] += (1.0 / neg_k) * dtw.dtw(
|
||||
pos_prot,
|
||||
neg_samp,
|
||||
dtw.RETURN_VALUE,
|
||||
slope_constraint=slope_constraint,
|
||||
window=window,
|
||||
)
|
||||
selected_id = np.argmax(neg_aves - pos_aves)
|
||||
path = dtw.dtw(
|
||||
positive_prototypes[selected_id],
|
||||
pat,
|
||||
dtw.RETURN_PATH,
|
||||
slope_constraint=slope_constraint,
|
||||
window=window,
|
||||
)
|
||||
|
||||
# Time warp
|
||||
warped = pat[path[1]]
|
||||
warp_path_interp = np.interp(
|
||||
orig_steps,
|
||||
np.linspace(0, x.shape[1] - 1.0, num=warped.shape[0]),
|
||||
path[1],
|
||||
)
|
||||
warp_amount[i] = np.sum(np.abs(orig_steps - warp_path_interp))
|
||||
for dim in range(x.shape[2]):
|
||||
ret[i, :, dim] = np.interp(
|
||||
orig_steps,
|
||||
np.linspace(0, x.shape[1] - 1.0, num=warped.shape[0]),
|
||||
warped[:, dim],
|
||||
).T
|
||||
else:
|
||||
# if verbose > -1:
|
||||
# print("There is only one pattern of class {}".format(l[i]))
|
||||
ret[i, :] = pat
|
||||
warp_amount[i] = 0.0
|
||||
if use_variable_slice:
|
||||
max_warp = np.max(warp_amount)
|
||||
if max_warp == 0:
|
||||
# unchanged
|
||||
ret = window_slice(ret, reduce_ratio=0.9)
|
||||
else:
|
||||
for i, pat in enumerate(ret):
|
||||
# Variable Sllicing
|
||||
ret[i] = window_slice(
|
||||
pat[np.newaxis, :, :],
|
||||
reduce_ratio=0.9 + 0.1 * warp_amount[i] / max_warp,
|
||||
)[0]
|
||||
return ret
|
||||
|
||||
|
||||
def discriminative_guided_warp_shape(
|
||||
x, labels, batch_size=6, slope_constraint="symmetric", use_window=True
|
||||
):
|
||||
return discriminative_guided_warp(
|
||||
x, labels, batch_size, slope_constraint, use_window, dtw_type="shape"
|
||||
)
|
||||
|
||||
|
||||
def run_augmentation(x, y, args):
|
||||
print("Augmenting %s" % args.data)
|
||||
np.random.seed(args.seed)
|
||||
x_aug = x
|
||||
y_aug = y
|
||||
if args.augmentation_ratio > 0:
|
||||
augmentation_tags = "%d" % args.augmentation_ratio
|
||||
for n in range(args.augmentation_ratio):
|
||||
x_temp, augmentation_tags = augment(x, y, args)
|
||||
x_aug = np.append(x_aug, x_temp, axis=0)
|
||||
y_aug = np.append(y_aug, y, axis=0)
|
||||
print("Round %d: %s done" % (n, augmentation_tags))
|
||||
if args.extra_tag:
|
||||
augmentation_tags += "_" + args.extra_tag
|
||||
else:
|
||||
augmentation_tags = args.extra_tag
|
||||
return x_aug, y_aug, augmentation_tags
|
||||
|
||||
|
||||
def run_augmentation_single(x, y, args):
|
||||
# print("Augmenting %s"%args.data)
|
||||
np.random.seed(args.seed)
|
||||
|
||||
x_aug = x
|
||||
y_aug = y
|
||||
|
||||
if len(x.shape) < 3:
|
||||
# Augmenting on the entire series: using the input data as "One Big Batch"
|
||||
# Before - (sequence_length, num_channels)
|
||||
# After - (1, sequence_length, num_channels)
|
||||
# Note: the 'sequence_length' here is actually the length of the entire series
|
||||
x_input = x[np.newaxis, :]
|
||||
elif len(x.shape) == 3:
|
||||
# Augmenting on the batch series: keep current dimension (batch_size, sequence_length, num_channels)
|
||||
x_input = x
|
||||
else:
|
||||
raise ValueError(
|
||||
"Input must be (batch_size, sequence_length, num_channels) dimensional"
|
||||
)
|
||||
|
||||
if args.augmentation_ratio > 0:
|
||||
augmentation_tags = "%d" % args.augmentation_ratio
|
||||
for n in range(args.augmentation_ratio):
|
||||
x_aug, augmentation_tags = augment(x_input, y, args)
|
||||
# print("Round %d: %s done"%(n, augmentation_tags))
|
||||
if args.extra_tag:
|
||||
augmentation_tags += "_" + args.extra_tag
|
||||
else:
|
||||
augmentation_tags = args.extra_tag
|
||||
|
||||
if len(x.shape) < 3:
|
||||
# Reverse to two-dimensional in whole series augmentation scenario
|
||||
x_aug = x_aug.squeeze(0)
|
||||
return x_aug, y_aug, augmentation_tags
|
||||
|
||||
|
||||
def augment(x, y, args):
|
||||
import utils.augmentation as aug
|
||||
|
||||
augmentation_tags = ""
|
||||
if args.jitter:
|
||||
x = aug.jitter(x)
|
||||
augmentation_tags += "_jitter"
|
||||
if args.scaling:
|
||||
x = aug.scaling(x)
|
||||
augmentation_tags += "_scaling"
|
||||
if args.rotation:
|
||||
x = aug.rotation(x)
|
||||
augmentation_tags += "_rotation"
|
||||
if args.permutation:
|
||||
x = aug.permutation(x)
|
||||
augmentation_tags += "_permutation"
|
||||
if args.randompermutation:
|
||||
x = aug.permutation(x, seg_mode="random")
|
||||
augmentation_tags += "_randomperm"
|
||||
if args.magwarp:
|
||||
x = aug.magnitude_warp(x)
|
||||
augmentation_tags += "_magwarp"
|
||||
if args.timewarp:
|
||||
x = aug.time_warp(x)
|
||||
augmentation_tags += "_timewarp"
|
||||
if args.windowslice:
|
||||
x = aug.window_slice(x)
|
||||
augmentation_tags += "_windowslice"
|
||||
if args.windowwarp:
|
||||
x = aug.window_warp(x)
|
||||
augmentation_tags += "_windowwarp"
|
||||
if args.spawner:
|
||||
x = aug.spawner(x, y)
|
||||
augmentation_tags += "_spawner"
|
||||
if args.dtwwarp:
|
||||
x = aug.random_guided_warp(x, y)
|
||||
augmentation_tags += "_rgw"
|
||||
if args.shapedtwwarp:
|
||||
x = aug.random_guided_warp_shape(x, y)
|
||||
augmentation_tags += "_rgws"
|
||||
if args.wdba:
|
||||
x = aug.wdba(x, y)
|
||||
augmentation_tags += "_wdba"
|
||||
if args.discdtw:
|
||||
x = aug.discriminative_guided_warp(x, y)
|
||||
augmentation_tags += "_dgw"
|
||||
if args.discsdtw:
|
||||
x = aug.discriminative_guided_warp_shape(x, y)
|
||||
augmentation_tags += "_dgws"
|
||||
return x, augmentation_tags
|
||||
258
utils/dtw.py
258
utils/dtw.py
|
|
@ -1,258 +0,0 @@
|
|||
__author__ = "Brian Iwana"
|
||||
|
||||
import numpy as np
|
||||
import math
|
||||
import sys
|
||||
|
||||
RETURN_VALUE = 0
|
||||
RETURN_PATH = 1
|
||||
RETURN_ALL = -1
|
||||
|
||||
|
||||
# Core DTW
|
||||
def _traceback(DTW, slope_constraint):
|
||||
i, j = np.array(DTW.shape) - 1
|
||||
p, q = [i - 1], [j - 1]
|
||||
|
||||
if slope_constraint == "asymmetric":
|
||||
while i > 1:
|
||||
tb = np.argmin((DTW[i - 1, j], DTW[i - 1, j - 1], DTW[i - 1, j - 2]))
|
||||
|
||||
if tb == 0:
|
||||
i = i - 1
|
||||
elif tb == 1:
|
||||
i = i - 1
|
||||
j = j - 1
|
||||
elif tb == 2:
|
||||
i = i - 1
|
||||
j = j - 2
|
||||
|
||||
p.insert(0, i - 1)
|
||||
q.insert(0, j - 1)
|
||||
elif slope_constraint == "symmetric":
|
||||
while i > 1 or j > 1:
|
||||
tb = np.argmin((DTW[i - 1, j - 1], DTW[i - 1, j], DTW[i, j - 1]))
|
||||
|
||||
if tb == 0:
|
||||
i = i - 1
|
||||
j = j - 1
|
||||
elif tb == 1:
|
||||
i = i - 1
|
||||
elif tb == 2:
|
||||
j = j - 1
|
||||
|
||||
p.insert(0, i - 1)
|
||||
q.insert(0, j - 1)
|
||||
else:
|
||||
sys.exit("Unknown slope constraint %s" % slope_constraint)
|
||||
|
||||
return (np.array(p), np.array(q))
|
||||
|
||||
|
||||
def dtw(
|
||||
prototype,
|
||||
sample,
|
||||
return_flag=RETURN_VALUE,
|
||||
slope_constraint="asymmetric",
|
||||
window=None,
|
||||
):
|
||||
"""Computes the DTW of two sequences.
|
||||
:param prototype: np array [0..b]
|
||||
:param sample: np array [0..t]
|
||||
:param extended: bool
|
||||
"""
|
||||
p = prototype.shape[0]
|
||||
assert p != 0, "Prototype empty!"
|
||||
s = sample.shape[0]
|
||||
assert s != 0, "Sample empty!"
|
||||
|
||||
if window is None:
|
||||
window = s
|
||||
|
||||
cost = np.full((p, s), np.inf)
|
||||
for i in range(p):
|
||||
start = max(0, i - window)
|
||||
end = min(s, i + window) + 1
|
||||
cost[i, start:end] = np.linalg.norm(sample[start:end] - prototype[i], axis=1)
|
||||
|
||||
DTW = _cummulative_matrix(cost, slope_constraint, window)
|
||||
|
||||
if return_flag == RETURN_ALL:
|
||||
return DTW[-1, -1], cost, DTW[1:, 1:], _traceback(DTW, slope_constraint)
|
||||
elif return_flag == RETURN_PATH:
|
||||
return _traceback(DTW, slope_constraint)
|
||||
else:
|
||||
return DTW[-1, -1]
|
||||
|
||||
|
||||
def _cummulative_matrix(cost, slope_constraint, window):
|
||||
p = cost.shape[0]
|
||||
s = cost.shape[1]
|
||||
|
||||
# Note: DTW is one larger than cost and the original patterns
|
||||
DTW = np.full((p + 1, s + 1), np.inf)
|
||||
|
||||
DTW[0, 0] = 0.0
|
||||
|
||||
if slope_constraint == "asymmetric":
|
||||
for i in range(1, p + 1):
|
||||
if i <= window + 1:
|
||||
DTW[i, 1] = cost[i - 1, 0] + min(DTW[i - 1, 0], DTW[i - 1, 1])
|
||||
for j in range(max(2, i - window), min(s, i + window) + 1):
|
||||
DTW[i, j] = cost[i - 1, j - 1] + min(
|
||||
DTW[i - 1, j - 2], DTW[i - 1, j - 1], DTW[i - 1, j]
|
||||
)
|
||||
elif slope_constraint == "symmetric":
|
||||
for i in range(1, p + 1):
|
||||
for j in range(max(1, i - window), min(s, i + window) + 1):
|
||||
DTW[i, j] = cost[i - 1, j - 1] + min(
|
||||
DTW[i - 1, j - 1], DTW[i, j - 1], DTW[i - 1, j]
|
||||
)
|
||||
else:
|
||||
sys.exit("Unknown slope constraint %s" % slope_constraint)
|
||||
|
||||
return DTW
|
||||
|
||||
|
||||
def shape_dtw(
|
||||
prototype,
|
||||
sample,
|
||||
return_flag=RETURN_VALUE,
|
||||
slope_constraint="asymmetric",
|
||||
window=None,
|
||||
descr_ratio=0.05,
|
||||
):
|
||||
"""Computes the shapeDTW of two sequences.
|
||||
:param prototype: np array [0..b]
|
||||
:param sample: np array [0..t]
|
||||
:param extended: bool
|
||||
"""
|
||||
# shapeDTW
|
||||
# https://www.sciencedirect.com/science/article/pii/S0031320317303710
|
||||
|
||||
p = prototype.shape[0]
|
||||
assert p != 0, "Prototype empty!"
|
||||
s = sample.shape[0]
|
||||
assert s != 0, "Sample empty!"
|
||||
|
||||
if window is None:
|
||||
window = s
|
||||
|
||||
p_feature_len = np.clip(np.round(p * descr_ratio), 5, 100).astype(int)
|
||||
s_feature_len = np.clip(np.round(s * descr_ratio), 5, 100).astype(int)
|
||||
|
||||
# padding
|
||||
p_pad_front = (np.ceil(p_feature_len / 2.0)).astype(int)
|
||||
p_pad_back = (np.floor(p_feature_len / 2.0)).astype(int)
|
||||
s_pad_front = (np.ceil(s_feature_len / 2.0)).astype(int)
|
||||
s_pad_back = (np.floor(s_feature_len / 2.0)).astype(int)
|
||||
|
||||
prototype_pad = np.pad(prototype, ((p_pad_front, p_pad_back), (0, 0)), mode="edge")
|
||||
sample_pad = np.pad(sample, ((s_pad_front, s_pad_back), (0, 0)), mode="edge")
|
||||
p_p = prototype_pad.shape[0]
|
||||
s_p = sample_pad.shape[0]
|
||||
|
||||
cost = np.full((p, s), np.inf)
|
||||
for i in range(p):
|
||||
for j in range(max(0, i - window), min(s, i + window)):
|
||||
cost[i, j] = np.linalg.norm(
|
||||
sample_pad[j : j + s_feature_len] - prototype_pad[i : i + p_feature_len]
|
||||
)
|
||||
|
||||
DTW = _cummulative_matrix(cost, slope_constraint=slope_constraint, window=window)
|
||||
|
||||
if return_flag == RETURN_ALL:
|
||||
return DTW[-1, -1], cost, DTW[1:, 1:], _traceback(DTW, slope_constraint)
|
||||
elif return_flag == RETURN_PATH:
|
||||
return _traceback(DTW, slope_constraint)
|
||||
else:
|
||||
return DTW[-1, -1]
|
||||
|
||||
|
||||
# Draw helpers
|
||||
def draw_graph2d(cost, DTW, path, prototype, sample):
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
plt.figure(figsize=(12, 8))
|
||||
# plt.subplots_adjust(left=.02, right=.98, bottom=.001, top=.96, wspace=.05, hspace=.01)
|
||||
|
||||
# cost
|
||||
plt.subplot(2, 3, 1)
|
||||
plt.imshow(cost.T, cmap=plt.cm.gray, interpolation="none", origin="lower")
|
||||
plt.plot(path[0], path[1], "y")
|
||||
plt.xlim((-0.5, cost.shape[0] - 0.5))
|
||||
plt.ylim((-0.5, cost.shape[0] - 0.5))
|
||||
|
||||
# dtw
|
||||
plt.subplot(2, 3, 2)
|
||||
plt.imshow(DTW.T, cmap=plt.cm.gray, interpolation="none", origin="lower")
|
||||
plt.plot(path[0] + 1, path[1] + 1, "y")
|
||||
plt.xlim((-0.5, DTW.shape[0] - 0.5))
|
||||
plt.ylim((-0.5, DTW.shape[0] - 0.5))
|
||||
|
||||
# prototype
|
||||
plt.subplot(2, 3, 4)
|
||||
plt.plot(prototype[:, 0], prototype[:, 1], "b-o")
|
||||
|
||||
# connection
|
||||
plt.subplot(2, 3, 5)
|
||||
for i in range(0, path[0].shape[0]):
|
||||
plt.plot(
|
||||
[prototype[path[0][i], 0], sample[path[1][i], 0]],
|
||||
[prototype[path[0][i], 1], sample[path[1][i], 1]],
|
||||
"y-",
|
||||
)
|
||||
plt.plot(sample[:, 0], sample[:, 1], "g-o")
|
||||
plt.plot(prototype[:, 0], prototype[:, 1], "b-o")
|
||||
|
||||
# sample
|
||||
plt.subplot(2, 3, 6)
|
||||
plt.plot(sample[:, 0], sample[:, 1], "g-o")
|
||||
|
||||
plt.tight_layout()
|
||||
plt.show()
|
||||
|
||||
|
||||
def draw_graph1d(cost, DTW, path, prototype, sample):
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
plt.figure(figsize=(12, 8))
|
||||
# plt.subplots_adjust(left=.02, right=.98, bottom=.001, top=.96, wspace=.05, hspace=.01)
|
||||
p_steps = np.arange(prototype.shape[0])
|
||||
s_steps = np.arange(sample.shape[0])
|
||||
|
||||
# cost
|
||||
plt.subplot(2, 3, 1)
|
||||
plt.imshow(cost.T, cmap=plt.cm.gray, interpolation="none", origin="lower")
|
||||
plt.plot(path[0], path[1], "y")
|
||||
plt.xlim((-0.5, cost.shape[0] - 0.5))
|
||||
plt.ylim((-0.5, cost.shape[0] - 0.5))
|
||||
|
||||
# dtw
|
||||
plt.subplot(2, 3, 2)
|
||||
plt.imshow(DTW.T, cmap=plt.cm.gray, interpolation="none", origin="lower")
|
||||
plt.plot(path[0] + 1, path[1] + 1, "y")
|
||||
plt.xlim((-0.5, DTW.shape[0] - 0.5))
|
||||
plt.ylim((-0.5, DTW.shape[0] - 0.5))
|
||||
|
||||
# prototype
|
||||
plt.subplot(2, 3, 4)
|
||||
plt.plot(p_steps, prototype[:, 0], "b-o")
|
||||
|
||||
# connection
|
||||
plt.subplot(2, 3, 5)
|
||||
for i in range(0, path[0].shape[0]):
|
||||
plt.plot(
|
||||
[path[0][i], path[1][i]],
|
||||
[prototype[path[0][i], 0], sample[path[1][i], 0]],
|
||||
"y-",
|
||||
)
|
||||
plt.plot(p_steps, sample[:, 0], "g-o")
|
||||
plt.plot(s_steps, prototype[:, 0], "b-o")
|
||||
|
||||
# sample
|
||||
plt.subplot(2, 3, 6)
|
||||
plt.plot(s_steps, sample[:, 0], "g-o")
|
||||
|
||||
plt.tight_layout()
|
||||
plt.show()
|
||||
|
|
@ -1,185 +0,0 @@
|
|||
from numpy import array, zeros, full, argmin, inf, ndim
|
||||
from scipy.spatial.distance import cdist
|
||||
from math import isinf
|
||||
|
||||
|
||||
def dtw(x, y, dist, warp=1, w=inf, s=1.0):
|
||||
"""
|
||||
Computes Dynamic Time Warping (DTW) of two sequences.
|
||||
|
||||
:param array x: N1*M array
|
||||
:param array y: N2*M array
|
||||
:param func dist: distance used as cost measure
|
||||
:param int warp: how many shifts are computed.
|
||||
:param int w: window size limiting the maximal distance between indices of matched entries |i,j|.
|
||||
:param float s: weight applied on off-diagonal moves of the path. As s gets larger, the warping path is increasingly biased towards the diagonal
|
||||
Returns the minimum distance, the cost matrix, the accumulated cost matrix, and the wrap path.
|
||||
"""
|
||||
assert len(x)
|
||||
assert len(y)
|
||||
assert isinf(w) or (w >= abs(len(x) - len(y)))
|
||||
assert s > 0
|
||||
r, c = len(x), len(y)
|
||||
if not isinf(w):
|
||||
D0 = full((r + 1, c + 1), inf)
|
||||
for i in range(1, r + 1):
|
||||
D0[i, max(1, i - w) : min(c + 1, i + w + 1)] = 0
|
||||
D0[0, 0] = 0
|
||||
else:
|
||||
D0 = zeros((r + 1, c + 1))
|
||||
D0[0, 1:] = inf
|
||||
D0[1:, 0] = inf
|
||||
D1 = D0[1:, 1:] # view
|
||||
for i in range(r):
|
||||
for j in range(c):
|
||||
if isinf(w) or (max(0, i - w) <= j <= min(c, i + w)):
|
||||
D1[i, j] = dist(x[i], y[j])
|
||||
C = D1.copy()
|
||||
jrange = range(c)
|
||||
for i in range(r):
|
||||
if not isinf(w):
|
||||
jrange = range(max(0, i - w), min(c, i + w + 1))
|
||||
for j in jrange:
|
||||
min_list = [D0[i, j]]
|
||||
for k in range(1, warp + 1):
|
||||
i_k = min(i + k, r)
|
||||
j_k = min(j + k, c)
|
||||
min_list += [D0[i_k, j] * s, D0[i, j_k] * s]
|
||||
D1[i, j] += min(min_list)
|
||||
if len(x) == 1:
|
||||
path = zeros(len(y)), range(len(y))
|
||||
elif len(y) == 1:
|
||||
path = range(len(x)), zeros(len(x))
|
||||
else:
|
||||
path = _traceback(D0)
|
||||
return D1[-1, -1], C, D1, path
|
||||
|
||||
|
||||
def accelerated_dtw(x, y, dist, warp=1):
|
||||
"""
|
||||
Computes Dynamic Time Warping (DTW) of two sequences in a faster way.
|
||||
Instead of iterating through each element and calculating each distance,
|
||||
this uses the cdist function from scipy (https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html)
|
||||
|
||||
:param array x: N1*M array
|
||||
:param array y: N2*M array
|
||||
:param string or func dist: distance parameter for cdist. When string is given, cdist uses optimized functions for the distance metrics.
|
||||
If a string is passed, the distance function can be 'braycurtis', 'canberra', 'chebyshev', 'cityblock', 'correlation', 'cosine', 'dice', 'euclidean', 'hamming', 'jaccard', 'kulsinski', 'mahalanobis', 'matching', 'minkowski', 'rogerstanimoto', 'russellrao', 'seuclidean', 'sokalmichener', 'sokalsneath', 'sqeuclidean', 'wminkowski', 'yule'.
|
||||
:param int warp: how many shifts are computed.
|
||||
Returns the minimum distance, the cost matrix, the accumulated cost matrix, and the wrap path.
|
||||
"""
|
||||
assert len(x)
|
||||
assert len(y)
|
||||
if ndim(x) == 1:
|
||||
x = x.reshape(-1, 1)
|
||||
if ndim(y) == 1:
|
||||
y = y.reshape(-1, 1)
|
||||
r, c = len(x), len(y)
|
||||
D0 = zeros((r + 1, c + 1))
|
||||
D0[0, 1:] = inf
|
||||
D0[1:, 0] = inf
|
||||
D1 = D0[1:, 1:]
|
||||
D0[1:, 1:] = cdist(x, y, dist)
|
||||
C = D1.copy()
|
||||
for i in range(r):
|
||||
for j in range(c):
|
||||
min_list = [D0[i, j]]
|
||||
for k in range(1, warp + 1):
|
||||
min_list += [D0[min(i + k, r), j], D0[i, min(j + k, c)]]
|
||||
D1[i, j] += min(min_list)
|
||||
if len(x) == 1:
|
||||
path = zeros(len(y)), range(len(y))
|
||||
elif len(y) == 1:
|
||||
path = range(len(x)), zeros(len(x))
|
||||
else:
|
||||
path = _traceback(D0)
|
||||
return D1[-1, -1], C, D1, path
|
||||
|
||||
|
||||
def _traceback(D):
|
||||
i, j = array(D.shape) - 2
|
||||
p, q = [i], [j]
|
||||
while (i > 0) or (j > 0):
|
||||
tb = argmin((D[i, j], D[i, j + 1], D[i + 1, j]))
|
||||
if tb == 0:
|
||||
i -= 1
|
||||
j -= 1
|
||||
elif tb == 1:
|
||||
i -= 1
|
||||
else: # (tb == 2):
|
||||
j -= 1
|
||||
p.insert(0, i)
|
||||
q.insert(0, j)
|
||||
return array(p), array(q)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
w = inf
|
||||
s = 1.0
|
||||
if 1: # 1-D numeric
|
||||
from sklearn.metrics.pairwise import manhattan_distances
|
||||
|
||||
x = [0, 0, 1, 1, 2, 4, 2, 1, 2, 0]
|
||||
y = [1, 1, 1, 2, 2, 2, 2, 3, 2, 0]
|
||||
dist_fun = manhattan_distances
|
||||
w = 1
|
||||
# s = 1.2
|
||||
elif 0: # 2-D numeric
|
||||
from sklearn.metrics.pairwise import euclidean_distances
|
||||
|
||||
x = [
|
||||
[0, 0],
|
||||
[0, 1],
|
||||
[1, 1],
|
||||
[1, 2],
|
||||
[2, 2],
|
||||
[4, 3],
|
||||
[2, 3],
|
||||
[1, 1],
|
||||
[2, 2],
|
||||
[0, 1],
|
||||
]
|
||||
y = [
|
||||
[1, 0],
|
||||
[1, 1],
|
||||
[1, 1],
|
||||
[2, 1],
|
||||
[4, 3],
|
||||
[4, 3],
|
||||
[2, 3],
|
||||
[3, 1],
|
||||
[1, 2],
|
||||
[1, 0],
|
||||
]
|
||||
dist_fun = euclidean_distances
|
||||
else: # 1-D list of strings
|
||||
from nltk.metrics.distance import edit_distance
|
||||
|
||||
# x = ['we', 'shelled', 'clams', 'for', 'the', 'chowder']
|
||||
# y = ['class', 'too']
|
||||
x = ["i", "soon", "found", "myself", "muttering", "to", "the", "walls"]
|
||||
y = ["see", "drown", "himself"]
|
||||
# x = 'we talked about the situation'.split()
|
||||
# y = 'we talked about the situation'.split()
|
||||
dist_fun = edit_distance
|
||||
dist, cost, acc, path = dtw(x, y, dist_fun, w=w, s=s)
|
||||
|
||||
# Vizualize
|
||||
from matplotlib import pyplot as plt
|
||||
|
||||
plt.imshow(cost.T, origin="lower", cmap=plt.cm.Reds, interpolation="nearest")
|
||||
plt.plot(path[0], path[1], "-o") # relation
|
||||
plt.xticks(range(len(x)), x)
|
||||
plt.yticks(range(len(y)), y)
|
||||
plt.xlabel("x")
|
||||
plt.ylabel("y")
|
||||
plt.axis("tight")
|
||||
if isinf(w):
|
||||
plt.title("Minimum distance: {}, slope weight: {}".format(dist, s))
|
||||
else:
|
||||
plt.title(
|
||||
"Minimum distance: {}, window widht: {}, slope weight: {}".format(
|
||||
dist, w, s
|
||||
)
|
||||
)
|
||||
plt.show()
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import torch
|
||||
import torch.nn as nn
|
||||
from model.model_selector import model_selector
|
||||
from lib.loss_function import masked_mae_loss
|
||||
from utils.loss_function import masked_mae_loss
|
||||
import random
|
||||
import numpy as np
|
||||
from datetime import datetime
|
||||
111
utils/losses.py
111
utils/losses.py
|
|
@ -1,111 +0,0 @@
|
|||
# This source code is provided for the purposes of scientific reproducibility
|
||||
# under the following limited license from Element AI Inc. The code is an
|
||||
# implementation of the N-BEATS model (Oreshkin et al., N-BEATS: Neural basis
|
||||
# expansion analysis for interpretable time series forecasting,
|
||||
# https://arxiv.org/abs/1905.10437). The copyright to the source code is
|
||||
# licensed under the Creative Commons - Attribution-NonCommercial 4.0
|
||||
# International license (CC BY-NC 4.0):
|
||||
# https://creativecommons.org/licenses/by-nc/4.0/. Any commercial use (whether
|
||||
# for the benefit of third parties or internally in production) requires an
|
||||
# explicit license. The subject-matter of the N-BEATS model and associated
|
||||
# materials are the property of Element AI Inc. and may be subject to patent
|
||||
# protection. No license to patents is granted hereunder (whether express or
|
||||
# implied). Copyright © 2020 Element AI Inc. All rights reserved.
|
||||
|
||||
"""
|
||||
Loss functions for PyTorch.
|
||||
"""
|
||||
|
||||
import torch as t
|
||||
import torch.nn as nn
|
||||
import numpy as np
|
||||
import pdb
|
||||
|
||||
|
||||
def divide_no_nan(a, b):
|
||||
"""
|
||||
a/b where the resulted NaN or Inf are replaced by 0.
|
||||
"""
|
||||
result = a / b
|
||||
result[result != result] = 0.0
|
||||
result[result == np.inf] = 0.0
|
||||
return result
|
||||
|
||||
|
||||
class mape_loss(nn.Module):
|
||||
def __init__(self):
|
||||
super(mape_loss, self).__init__()
|
||||
|
||||
def forward(
|
||||
self,
|
||||
insample: t.Tensor,
|
||||
freq: int,
|
||||
forecast: t.Tensor,
|
||||
target: t.Tensor,
|
||||
mask: t.Tensor,
|
||||
) -> t.float:
|
||||
"""
|
||||
MAPE loss as defined in: https://en.wikipedia.org/wiki/Mean_absolute_percentage_error
|
||||
|
||||
:param forecast: Forecast values. Shape: batch, time
|
||||
:param target: Target values. Shape: batch, time
|
||||
:param mask: 0/1 mask. Shape: batch, time
|
||||
:return: Loss value
|
||||
"""
|
||||
weights = divide_no_nan(mask, target)
|
||||
return t.mean(t.abs((forecast - target) * weights))
|
||||
|
||||
|
||||
class smape_loss(nn.Module):
|
||||
def __init__(self):
|
||||
super(smape_loss, self).__init__()
|
||||
|
||||
def forward(
|
||||
self,
|
||||
insample: t.Tensor,
|
||||
freq: int,
|
||||
forecast: t.Tensor,
|
||||
target: t.Tensor,
|
||||
mask: t.Tensor,
|
||||
) -> t.float:
|
||||
"""
|
||||
sMAPE loss as defined in https://robjhyndman.com/hyndsight/smape/ (Makridakis 1993)
|
||||
|
||||
:param forecast: Forecast values. Shape: batch, time
|
||||
:param target: Target values. Shape: batch, time
|
||||
:param mask: 0/1 mask. Shape: batch, time
|
||||
:return: Loss value
|
||||
"""
|
||||
return 200 * t.mean(
|
||||
divide_no_nan(
|
||||
t.abs(forecast - target), t.abs(forecast.data) + t.abs(target.data)
|
||||
)
|
||||
* mask
|
||||
)
|
||||
|
||||
|
||||
class mase_loss(nn.Module):
|
||||
def __init__(self):
|
||||
super(mase_loss, self).__init__()
|
||||
|
||||
def forward(
|
||||
self,
|
||||
insample: t.Tensor,
|
||||
freq: int,
|
||||
forecast: t.Tensor,
|
||||
target: t.Tensor,
|
||||
mask: t.Tensor,
|
||||
) -> t.float:
|
||||
"""
|
||||
MASE loss as defined in "Scaled Errors" https://robjhyndman.com/papers/mase.pdf
|
||||
|
||||
:param insample: Insample values. Shape: batch, time_i
|
||||
:param freq: Frequency value
|
||||
:param forecast: Forecast values. Shape: batch, time_o
|
||||
:param target: Target values. Shape: batch, time_o
|
||||
:param mask: 0/1 mask. Shape: batch, time_o
|
||||
:return: Loss value
|
||||
"""
|
||||
masep = t.mean(t.abs(insample[:, freq:] - insample[:, :-freq]), dim=1)
|
||||
masked_masep_inv = divide_no_nan(mask, masep[:, None])
|
||||
return t.mean(t.abs(target - forecast) * masked_masep_inv)
|
||||
|
|
@ -1,175 +0,0 @@
|
|||
# This source code is provided for the purposes of scientific reproducibility
|
||||
# under the following limited license from Element AI Inc. The code is an
|
||||
# implementation of the N-BEATS model (Oreshkin et al., N-BEATS: Neural basis
|
||||
# expansion analysis for interpretable time series forecasting,
|
||||
# https://arxiv.org/abs/1905.10437). The copyright to the source code is
|
||||
# licensed under the Creative Commons - Attribution-NonCommercial 4.0
|
||||
# International license (CC BY-NC 4.0):
|
||||
# https://creativecommons.org/licenses/by-nc/4.0/. Any commercial use (whether
|
||||
# for the benefit of third parties or internally in production) requires an
|
||||
# explicit license. The subject-matter of the N-BEATS model and associated
|
||||
# materials are the property of Element AI Inc. and may be subject to patent
|
||||
# protection. No license to patents is granted hereunder (whether express or
|
||||
# implied). Copyright 2020 Element AI Inc. All rights reserved.
|
||||
|
||||
"""
|
||||
M4 Summary
|
||||
"""
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from data_provider.m4 import M4Dataset
|
||||
from data_provider.m4 import M4Meta
|
||||
import os
|
||||
|
||||
|
||||
def group_values(values, groups, group_name):
|
||||
return np.array([v[~np.isnan(v)] for v in values[groups == group_name]])
|
||||
|
||||
|
||||
def mase(forecast, insample, outsample, frequency):
|
||||
return np.mean(np.abs(forecast - outsample)) / np.mean(
|
||||
np.abs(insample[:-frequency] - insample[frequency:])
|
||||
)
|
||||
|
||||
|
||||
def smape_2(forecast, target):
|
||||
denom = np.abs(target) + np.abs(forecast)
|
||||
# divide by 1.0 instead of 0.0, in case when denom is zero the enumerator will be 0.0 anyway.
|
||||
denom[denom == 0.0] = 1.0
|
||||
return 200 * np.abs(forecast - target) / denom
|
||||
|
||||
|
||||
def mape(forecast, target):
|
||||
denom = np.abs(target)
|
||||
# divide by 1.0 instead of 0.0, in case when denom is zero the enumerator will be 0.0 anyway.
|
||||
denom[denom == 0.0] = 1.0
|
||||
return 100 * np.abs(forecast - target) / denom
|
||||
|
||||
|
||||
class M4Summary:
|
||||
def __init__(self, file_path, root_path):
|
||||
self.file_path = file_path
|
||||
self.training_set = M4Dataset.load(training=True, dataset_file=root_path)
|
||||
self.test_set = M4Dataset.load(training=False, dataset_file=root_path)
|
||||
self.naive_path = os.path.join(root_path, "submission-Naive2.csv")
|
||||
|
||||
def evaluate(self):
|
||||
"""
|
||||
Evaluate forecasts using M4 test dataset.
|
||||
|
||||
:param forecast: Forecasts. Shape: timeseries, time.
|
||||
:return: sMAPE and OWA grouped by seasonal patterns.
|
||||
"""
|
||||
grouped_owa = OrderedDict()
|
||||
|
||||
naive2_forecasts = pd.read_csv(self.naive_path).values[:, 1:].astype(np.float32)
|
||||
naive2_forecasts = np.array([v[~np.isnan(v)] for v in naive2_forecasts])
|
||||
|
||||
model_mases = {}
|
||||
naive2_smapes = {}
|
||||
naive2_mases = {}
|
||||
grouped_smapes = {}
|
||||
grouped_mapes = {}
|
||||
for group_name in M4Meta.seasonal_patterns:
|
||||
file_name = self.file_path + group_name + "_forecast.csv"
|
||||
if os.path.exists(file_name):
|
||||
model_forecast = pd.read_csv(file_name).values
|
||||
|
||||
naive2_forecast = group_values(
|
||||
naive2_forecasts, self.test_set.groups, group_name
|
||||
)
|
||||
target = group_values(
|
||||
self.test_set.values, self.test_set.groups, group_name
|
||||
)
|
||||
# all timeseries within group have same frequency
|
||||
frequency = self.training_set.frequencies[
|
||||
self.test_set.groups == group_name
|
||||
][0]
|
||||
insample = group_values(
|
||||
self.training_set.values, self.test_set.groups, group_name
|
||||
)
|
||||
|
||||
model_mases[group_name] = np.mean(
|
||||
[
|
||||
mase(
|
||||
forecast=model_forecast[i],
|
||||
insample=insample[i],
|
||||
outsample=target[i],
|
||||
frequency=frequency,
|
||||
)
|
||||
for i in range(len(model_forecast))
|
||||
]
|
||||
)
|
||||
naive2_mases[group_name] = np.mean(
|
||||
[
|
||||
mase(
|
||||
forecast=naive2_forecast[i],
|
||||
insample=insample[i],
|
||||
outsample=target[i],
|
||||
frequency=frequency,
|
||||
)
|
||||
for i in range(len(model_forecast))
|
||||
]
|
||||
)
|
||||
|
||||
naive2_smapes[group_name] = np.mean(smape_2(naive2_forecast, target))
|
||||
grouped_smapes[group_name] = np.mean(
|
||||
smape_2(forecast=model_forecast, target=target)
|
||||
)
|
||||
grouped_mapes[group_name] = np.mean(
|
||||
mape(forecast=model_forecast, target=target)
|
||||
)
|
||||
|
||||
grouped_smapes = self.summarize_groups(grouped_smapes)
|
||||
grouped_mapes = self.summarize_groups(grouped_mapes)
|
||||
grouped_model_mases = self.summarize_groups(model_mases)
|
||||
grouped_naive2_smapes = self.summarize_groups(naive2_smapes)
|
||||
grouped_naive2_mases = self.summarize_groups(naive2_mases)
|
||||
for k in grouped_model_mases.keys():
|
||||
grouped_owa[k] = (
|
||||
grouped_model_mases[k] / grouped_naive2_mases[k]
|
||||
+ grouped_smapes[k] / grouped_naive2_smapes[k]
|
||||
) / 2
|
||||
|
||||
def round_all(d):
|
||||
return dict(map(lambda kv: (kv[0], np.round(kv[1], 3)), d.items()))
|
||||
|
||||
return (
|
||||
round_all(grouped_smapes),
|
||||
round_all(grouped_owa),
|
||||
round_all(grouped_mapes),
|
||||
round_all(grouped_model_mases),
|
||||
)
|
||||
|
||||
def summarize_groups(self, scores):
|
||||
"""
|
||||
Re-group scores respecting M4 rules.
|
||||
:param scores: Scores per group.
|
||||
:return: Grouped scores.
|
||||
"""
|
||||
scores_summary = OrderedDict()
|
||||
|
||||
def group_count(group_name):
|
||||
return len(np.where(self.test_set.groups == group_name)[0])
|
||||
|
||||
weighted_score = {}
|
||||
for g in ["Yearly", "Quarterly", "Monthly"]:
|
||||
weighted_score[g] = scores[g] * group_count(g)
|
||||
scores_summary[g] = scores[g]
|
||||
|
||||
others_score = 0
|
||||
others_count = 0
|
||||
for g in ["Weekly", "Daily", "Hourly"]:
|
||||
others_score += scores[g] * group_count(g)
|
||||
others_count += group_count(g)
|
||||
weighted_score["Others"] = others_score
|
||||
scores_summary["Others"] = others_score / others_count
|
||||
|
||||
average = np.sum(list(weighted_score.values())) / len(self.test_set.groups)
|
||||
scores_summary["Average"] = average
|
||||
|
||||
return scores_summary
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
import torch
|
||||
|
||||
|
||||
class TriangularCausalMask:
|
||||
def __init__(self, B, L, device="cpu"):
|
||||
mask_shape = [B, 1, L, L]
|
||||
with torch.no_grad():
|
||||
self._mask = torch.triu(
|
||||
torch.ones(mask_shape, dtype=torch.bool), diagonal=1
|
||||
).to(device)
|
||||
|
||||
@property
|
||||
def mask(self):
|
||||
return self._mask
|
||||
|
||||
|
||||
class ProbMask:
|
||||
def __init__(self, B, H, L, index, scores, device="cpu"):
|
||||
_mask = torch.ones(L, scores.shape[-1], dtype=torch.bool).to(device).triu(1)
|
||||
_mask_ex = _mask[None, None, :].expand(B, H, L, scores.shape[-1])
|
||||
indicator = _mask_ex[
|
||||
torch.arange(B)[:, None, None], torch.arange(H)[None, :, None], index, :
|
||||
].to(device)
|
||||
self._mask = indicator.view(scores.shape).to(device)
|
||||
|
||||
@property
|
||||
def mask(self):
|
||||
return self._mask
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import numpy as np
|
||||
|
||||
|
||||
def RSE(pred, true):
|
||||
return np.sqrt(np.sum((true - pred) ** 2)) / np.sqrt(
|
||||
np.sum((true - true.mean()) ** 2)
|
||||
)
|
||||
|
||||
|
||||
def CORR(pred, true):
|
||||
u = ((true - true.mean(0)) * (pred - pred.mean(0))).sum(0)
|
||||
d = np.sqrt(((true - true.mean(0)) ** 2 * (pred - pred.mean(0)) ** 2).sum(0))
|
||||
return (u / d).mean(-1)
|
||||
|
||||
|
||||
def MAE(pred, true):
|
||||
return np.mean(np.abs(true - pred))
|
||||
|
||||
|
||||
def MSE(pred, true):
|
||||
return np.mean((true - pred) ** 2)
|
||||
|
||||
|
||||
def RMSE(pred, true):
|
||||
return np.sqrt(MSE(pred, true))
|
||||
|
||||
|
||||
def MAPE(pred, true):
|
||||
return np.mean(np.abs((true - pred) / true))
|
||||
|
||||
|
||||
def MSPE(pred, true):
|
||||
return np.mean(np.square((true - pred) / true))
|
||||
|
||||
|
||||
def metric(pred, true):
|
||||
mae = MAE(pred, true)
|
||||
mse = MSE(pred, true)
|
||||
rmse = RMSE(pred, true)
|
||||
mape = MAPE(pred, true)
|
||||
mspe = MSPE(pred, true)
|
||||
|
||||
return mae, mse, rmse, mape, mspe
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
def print_args(args):
|
||||
print("\033[1m" + "Basic Config" + "\033[0m")
|
||||
print(
|
||||
f" {'Task Name:':<20}{args.task_name:<20}{'Is Training:':<20}{args.is_training:<20}"
|
||||
)
|
||||
print(f" {'Model ID:':<20}{args.model_id:<20}{'Model:':<20}{args.model:<20}")
|
||||
print()
|
||||
|
||||
print("\033[1m" + "Data Loader" + "\033[0m")
|
||||
print(f" {'Data:':<20}{args.data:<20}{'Root Path:':<20}{args.root_path:<20}")
|
||||
print(
|
||||
f" {'Data Path:':<20}{args.data_path:<20}{'Features:':<20}{args.features:<20}"
|
||||
)
|
||||
print(f" {'Target:':<20}{args.target:<20}{'Freq:':<20}{args.freq:<20}")
|
||||
print(f" {'Checkpoints:':<20}{args.checkpoints:<20}")
|
||||
print()
|
||||
|
||||
if args.task_name in ["long_term_forecast", "short_term_forecast"]:
|
||||
print("\033[1m" + "Forecasting Task" + "\033[0m")
|
||||
print(
|
||||
f" {'Seq Len:':<20}{args.seq_len:<20}{'Label Len:':<20}{args.label_len:<20}"
|
||||
)
|
||||
print(
|
||||
f" {'Pred Len:':<20}{args.pred_len:<20}{'Seasonal Patterns:':<20}{args.seasonal_patterns:<20}"
|
||||
)
|
||||
print(f" {'Inverse:':<20}{args.inverse:<20}")
|
||||
print()
|
||||
|
||||
if args.task_name == "imputation":
|
||||
print("\033[1m" + "Imputation Task" + "\033[0m")
|
||||
print(f" {'Mask Rate:':<20}{args.mask_rate:<20}")
|
||||
print()
|
||||
|
||||
if args.task_name == "anomaly_detection":
|
||||
print("\033[1m" + "Anomaly Detection Task" + "\033[0m")
|
||||
print(f" {'Anomaly Ratio:':<20}{args.anomaly_ratio:<20}")
|
||||
print()
|
||||
|
||||
print("\033[1m" + "Model Parameters" + "\033[0m")
|
||||
print(f" {'Top k:':<20}{args.top_k:<20}{'Num Kernels:':<20}{args.num_kernels:<20}")
|
||||
print(f" {'Enc In:':<20}{args.enc_in:<20}{'Dec In:':<20}{args.dec_in:<20}")
|
||||
print(f" {'C Out:':<20}{args.c_out:<20}{'d model:':<20}{args.d_model:<20}")
|
||||
print(f" {'n heads:':<20}{args.n_heads:<20}{'e layers:':<20}{args.e_layers:<20}")
|
||||
print(f" {'d layers:':<20}{args.d_layers:<20}{'d FF:':<20}{args.d_ff:<20}")
|
||||
print(f" {'Moving Avg:':<20}{args.moving_avg:<20}{'Factor:':<20}{args.factor:<20}")
|
||||
print(f" {'Distil:':<20}{args.distil:<20}{'Dropout:':<20}{args.dropout:<20}")
|
||||
print(f" {'Embed:':<20}{args.embed:<20}{'Activation:':<20}{args.activation:<20}")
|
||||
print()
|
||||
|
||||
print("\033[1m" + "Run Parameters" + "\033[0m")
|
||||
print(f" {'Num Workers:':<20}{args.num_workers:<20}{'Itr:':<20}{args.itr:<20}")
|
||||
print(
|
||||
f" {'Train Epochs:':<20}{args.train_epochs:<20}{'Batch Size:':<20}{args.batch_size:<20}"
|
||||
)
|
||||
print(
|
||||
f" {'Patience:':<20}{args.patience:<20}{'Learning Rate:':<20}{args.learning_rate:<20}"
|
||||
)
|
||||
print(f" {'Des:':<20}{args.des:<20}{'Loss:':<20}{args.loss:<20}")
|
||||
print(f" {'Lradj:':<20}{args.lradj:<20}{'Use Amp:':<20}{args.use_amp:<20}")
|
||||
print()
|
||||
|
||||
print("\033[1m" + "GPU" + "\033[0m")
|
||||
print(f" {'Use GPU:':<20}{args.use_gpu:<20}{'GPU:':<20}{args.gpu:<20}")
|
||||
print(
|
||||
f" {'Use Multi GPU:':<20}{args.use_multi_gpu:<20}{'Devices:':<20}{args.devices:<20}"
|
||||
)
|
||||
print()
|
||||
|
||||
print("\033[1m" + "De-stationary Projector Params" + "\033[0m")
|
||||
p_hidden_dims_str = ", ".join(map(str, args.p_hidden_dims))
|
||||
print(
|
||||
f" {'P Hidden Dims:':<20}{p_hidden_dims_str:<20}{'P Hidden Layers:':<20}{args.p_hidden_layers:<20}"
|
||||
)
|
||||
print()
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
# From: gluonts/src/gluonts/time_feature/_base.py
|
||||
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License").
|
||||
# You may not use this file except in compliance with the License.
|
||||
# A copy of the License is located at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# or in the "license" file accompanying this file. This file is distributed
|
||||
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
# express or implied. See the License for the specific language governing
|
||||
# permissions and limitations under the License.
|
||||
|
||||
from typing import List
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from pandas.tseries import offsets
|
||||
from pandas.tseries.frequencies import to_offset
|
||||
|
||||
|
||||
class TimeFeature:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __call__(self, index: pd.DatetimeIndex) -> np.ndarray:
|
||||
pass
|
||||
|
||||
def __repr__(self):
|
||||
return self.__class__.__name__ + "()"
|
||||
|
||||
|
||||
class SecondOfMinute(TimeFeature):
|
||||
"""Minute of hour encoded as value between [-0.5, 0.5]"""
|
||||
|
||||
def __call__(self, index: pd.DatetimeIndex) -> np.ndarray:
|
||||
return index.second / 59.0 - 0.5
|
||||
|
||||
|
||||
class MinuteOfHour(TimeFeature):
|
||||
"""Minute of hour encoded as value between [-0.5, 0.5]"""
|
||||
|
||||
def __call__(self, index: pd.DatetimeIndex) -> np.ndarray:
|
||||
return index.minute / 59.0 - 0.5
|
||||
|
||||
|
||||
class HourOfDay(TimeFeature):
|
||||
"""Hour of day encoded as value between [-0.5, 0.5]"""
|
||||
|
||||
def __call__(self, index: pd.DatetimeIndex) -> np.ndarray:
|
||||
return index.hour / 23.0 - 0.5
|
||||
|
||||
|
||||
class DayOfWeek(TimeFeature):
|
||||
"""Hour of day encoded as value between [-0.5, 0.5]"""
|
||||
|
||||
def __call__(self, index: pd.DatetimeIndex) -> np.ndarray:
|
||||
return index.dayofweek / 6.0 - 0.5
|
||||
|
||||
|
||||
class DayOfMonth(TimeFeature):
|
||||
"""Day of month encoded as value between [-0.5, 0.5]"""
|
||||
|
||||
def __call__(self, index: pd.DatetimeIndex) -> np.ndarray:
|
||||
return (index.day - 1) / 30.0 - 0.5
|
||||
|
||||
|
||||
class DayOfYear(TimeFeature):
|
||||
"""Day of year encoded as value between [-0.5, 0.5]"""
|
||||
|
||||
def __call__(self, index: pd.DatetimeIndex) -> np.ndarray:
|
||||
return (index.dayofyear - 1) / 365.0 - 0.5
|
||||
|
||||
|
||||
class MonthOfYear(TimeFeature):
|
||||
"""Month of year encoded as value between [-0.5, 0.5]"""
|
||||
|
||||
def __call__(self, index: pd.DatetimeIndex) -> np.ndarray:
|
||||
return (index.month - 1) / 11.0 - 0.5
|
||||
|
||||
|
||||
class WeekOfYear(TimeFeature):
|
||||
"""Week of year encoded as value between [-0.5, 0.5]"""
|
||||
|
||||
def __call__(self, index: pd.DatetimeIndex) -> np.ndarray:
|
||||
return (index.isocalendar().week - 1) / 52.0 - 0.5
|
||||
|
||||
|
||||
def time_features_from_frequency_str(freq_str: str) -> List[TimeFeature]:
|
||||
"""
|
||||
Returns a list of time features that will be appropriate for the given frequency string.
|
||||
Parameters
|
||||
----------
|
||||
freq_str
|
||||
Frequency string of the form [multiple][granularity] such as "12H", "5min", "1D" etc.
|
||||
"""
|
||||
|
||||
features_by_offsets = {
|
||||
offsets.YearEnd: [],
|
||||
offsets.QuarterEnd: [MonthOfYear],
|
||||
offsets.MonthEnd: [MonthOfYear],
|
||||
offsets.Week: [DayOfMonth, WeekOfYear],
|
||||
offsets.Day: [DayOfWeek, DayOfMonth, DayOfYear],
|
||||
offsets.BusinessDay: [DayOfWeek, DayOfMonth, DayOfYear],
|
||||
offsets.Hour: [HourOfDay, DayOfWeek, DayOfMonth, DayOfYear],
|
||||
offsets.Minute: [
|
||||
MinuteOfHour,
|
||||
HourOfDay,
|
||||
DayOfWeek,
|
||||
DayOfMonth,
|
||||
DayOfYear,
|
||||
],
|
||||
offsets.Second: [
|
||||
SecondOfMinute,
|
||||
MinuteOfHour,
|
||||
HourOfDay,
|
||||
DayOfWeek,
|
||||
DayOfMonth,
|
||||
DayOfYear,
|
||||
],
|
||||
}
|
||||
|
||||
offset = to_offset(freq_str)
|
||||
|
||||
for offset_type, feature_classes in features_by_offsets.items():
|
||||
if isinstance(offset, offset_type):
|
||||
return [cls() for cls in feature_classes]
|
||||
|
||||
supported_freq_msg = f"""
|
||||
Unsupported frequency {freq_str}
|
||||
The following frequencies are supported:
|
||||
Y - yearly
|
||||
alias: A
|
||||
M - monthly
|
||||
W - weekly
|
||||
D - daily
|
||||
B - business days
|
||||
H - hourly
|
||||
T - minutely
|
||||
alias: min
|
||||
S - secondly
|
||||
"""
|
||||
raise RuntimeError(supported_freq_msg)
|
||||
|
||||
|
||||
def time_features(dates, freq="h"):
|
||||
return np.vstack([feat(dates) for feat in time_features_from_frequency_str(freq)])
|
||||
128
utils/tools.py
128
utils/tools.py
|
|
@ -1,128 +0,0 @@
|
|||
import os
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import matplotlib.pyplot as plt
|
||||
import pandas as pd
|
||||
import math
|
||||
|
||||
plt.switch_backend("agg")
|
||||
|
||||
|
||||
def adjust_learning_rate(optimizer, epoch, args):
|
||||
# lr = args.learning_rate * (0.2 ** (epoch // 2))
|
||||
if args.lradj == "type1":
|
||||
lr_adjust = {epoch: args.learning_rate * (0.5 ** ((epoch - 1) // 1))}
|
||||
elif args.lradj == "type2":
|
||||
lr_adjust = {2: 5e-5, 4: 1e-5, 6: 5e-6, 8: 1e-6, 10: 5e-7, 15: 1e-7, 20: 5e-8}
|
||||
elif args.lradj == "type3":
|
||||
lr_adjust = {
|
||||
epoch: args.learning_rate
|
||||
if epoch < 3
|
||||
else args.learning_rate * (0.9 ** ((epoch - 3) // 1))
|
||||
}
|
||||
elif args.lradj == "cosine":
|
||||
lr_adjust = {
|
||||
epoch: args.learning_rate
|
||||
/ 2
|
||||
* (1 + math.cos(epoch / args.train_epochs * math.pi))
|
||||
}
|
||||
if epoch in lr_adjust.keys():
|
||||
lr = lr_adjust[epoch]
|
||||
for param_group in optimizer.param_groups:
|
||||
param_group["lr"] = lr
|
||||
print("Updating learning rate to {}".format(lr))
|
||||
|
||||
|
||||
class EarlyStopping:
|
||||
def __init__(self, patience=7, verbose=False, delta=0):
|
||||
self.patience = patience
|
||||
self.verbose = verbose
|
||||
self.counter = 0
|
||||
self.best_score = None
|
||||
self.early_stop = False
|
||||
self.val_loss_min = np.Inf
|
||||
self.delta = delta
|
||||
|
||||
def __call__(self, val_loss, model, path):
|
||||
score = -val_loss
|
||||
if self.best_score is None:
|
||||
self.best_score = score
|
||||
self.save_checkpoint(val_loss, model, path)
|
||||
elif score < self.best_score + self.delta:
|
||||
self.counter += 1
|
||||
print(f"EarlyStopping counter: {self.counter} out of {self.patience}")
|
||||
if self.counter >= self.patience:
|
||||
self.early_stop = True
|
||||
else:
|
||||
self.best_score = score
|
||||
self.save_checkpoint(val_loss, model, path)
|
||||
self.counter = 0
|
||||
|
||||
def save_checkpoint(self, val_loss, model, path):
|
||||
if self.verbose:
|
||||
print(
|
||||
f"Validation loss decreased ({self.val_loss_min:.6f} --> {val_loss:.6f}). Saving model ..."
|
||||
)
|
||||
torch.save(model.state_dict(), path + "/" + "checkpoint.pth")
|
||||
self.val_loss_min = val_loss
|
||||
|
||||
|
||||
class dotdict(dict):
|
||||
"""dot.notation access to dictionary attributes"""
|
||||
|
||||
__getattr__ = dict.get
|
||||
__setattr__ = dict.__setitem__
|
||||
__delattr__ = dict.__delitem__
|
||||
|
||||
|
||||
class StandardScaler:
|
||||
def __init__(self, mean, std):
|
||||
self.mean = mean
|
||||
self.std = std
|
||||
|
||||
def transform(self, data):
|
||||
return (data - self.mean) / self.std
|
||||
|
||||
def inverse_transform(self, data):
|
||||
return (data * self.std) + self.mean
|
||||
|
||||
|
||||
def visual(true, preds=None, name="./pic/test.pdf"):
|
||||
"""
|
||||
Results visualization
|
||||
"""
|
||||
plt.figure()
|
||||
if preds is not None:
|
||||
plt.plot(preds, label="Prediction", linewidth=2)
|
||||
plt.plot(true, label="GroundTruth", linewidth=2)
|
||||
plt.legend()
|
||||
plt.savefig(name, bbox_inches="tight")
|
||||
|
||||
|
||||
def adjustment(gt, pred):
|
||||
anomaly_state = False
|
||||
for i in range(len(gt)):
|
||||
if gt[i] == 1 and pred[i] == 1 and not anomaly_state:
|
||||
anomaly_state = True
|
||||
for j in range(i, 0, -1):
|
||||
if gt[j] == 0:
|
||||
break
|
||||
else:
|
||||
if pred[j] == 0:
|
||||
pred[j] = 1
|
||||
for j in range(i, len(gt)):
|
||||
if gt[j] == 0:
|
||||
break
|
||||
else:
|
||||
if pred[j] == 0:
|
||||
pred[j] = 1
|
||||
elif gt[i] == 0:
|
||||
anomaly_state = False
|
||||
if anomaly_state:
|
||||
pred[i] = 1
|
||||
return gt, pred
|
||||
|
||||
|
||||
def cal_accuracy(y_pred, y_true):
|
||||
return np.mean(y_pred == y_true)
|
||||
Loading…
Reference in New Issue