-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathutils.py
240 lines (184 loc) · 7.57 KB
/
utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
import os
import torch
import random
import numpy as np
import pandas as pd
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
def log_string(log, string):
"""Print log"""
log.write(string + '\n')
log.flush()
print(string)
def count_parameters(model):
"""Statistical Model Parameters"""
return sum(p.numel() for p in model.parameters() if p.requires_grad)
def init_seed(seed):
"""Disable cudnn to maximize reproducibility"""
torch.cuda.cudnn_enabled = False
"""
cuDNN uses a non-deterministic algorithm and can be disabled using torch.backends.cudnn.enabled = False
If it is set to torch.backends.cudnn.enabled=True, it means it is set to use a non-deterministic
algorithm
Then set: torch.backends.cudnn.benchmark = True, when this flag is True,
it will make the program spend a little extra time at the beginning,
Search for the most suitable convolution implementation algorithm for each convolutional layer of the
entire network, thereby achieving network acceleration
But because it uses a non-deterministic algorithm, this will make the network feedforward
results slightly different each time. If you want to avoid this kind of result fluctuation,
you can set the following flag to True
"""
# torch.backends.cudnn.benchmark = False
torch.backends.cudnn.deterministic = True
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
"""Read and Construct Localized Spatio-Temporal Adjacency Matrix"""
def get_adjacency_matrix(distance_df_filename):
"""
distance_df_filename: str, adj csv file path
"""
df = pd.read_csv(distance_df_filename, header=None)
adj_mx = df.values
# Scale the adjacency matrix
adj_mx_final = adj_mx / np.max(adj_mx)
# Threshold for the 51 States or 83 counties
thresh = 0.3
adj_mx_final[adj_mx_final > thresh] = 0.
return adj_mx_final
def construct_adj(A, steps):
"""
Build a spatial-temporal graph
A: np.ndarray, adjacency matrix, shape is (N, N)
steps: select a few time steps to build the graph
return: new adjacency matrix: csr_matrix, shape is (N * steps, N * steps)
"""
N = len(A) # Get the number of rows
adj = np.zeros((N * steps, N * steps))
for i in range(steps):
"""The diagonal represents the space map of each time step, which is A"""
adj[i * N: (i + 1) * N, i * N: (i + 1) * N] = A
for i in range(N):
for k in range(steps - 1):
"""Each node will only connect to itself in adjacent time steps"""
adj[k * N + i, (k + 1) * N + i] = 1.
adj[(k + 1) * N + i, k * N + i] = 1.
return adj
class DataLoader(object):
def __init__(self, xs, ys, batch_size, pad_with_last_sample=False):
"""
Data loader
:param xs: training data
:param ys: label data
:param batch_size:batch size
:param pad_with_last_sample: When the remaining data is not enough,
whether to copy the last sample to reach the batch size
"""
self.batch_size = batch_size
self.current_ind = 0
if pad_with_last_sample:
num_padding = (batch_size - (len(xs) % batch_size)) % batch_size
x_padding = np.repeat(xs[-1:], num_padding, axis=0)
y_padding = np.repeat(ys[-1:], num_padding, axis=0)
xs = np.concatenate([xs, x_padding], axis=0)
ys = np.concatenate([ys, y_padding], axis=0)
self.size = len(xs)
self.num_batch = int(self.size // self.batch_size)
self.xs = xs
self.ys = ys
def shuffle(self):
"""Shuffle Dataset"""
permutation = np.random.permutation(self.size)
xs, ys = self.xs[permutation], self.ys[permutation]
self.xs = xs
self.ys = ys
def get_iterator(self):
self.current_ind = 0
def _wrapper():
while self.current_ind < self.num_batch:
start_ind = self.batch_size * self.current_ind
end_ind = min(self.size, self.batch_size * (self.current_ind + 1))
x_i = self.xs[start_ind:end_ind, ...]
y_i = self.ys[start_ind:end_ind, ...]
yield x_i, y_i
self.current_ind += 1
return _wrapper()
class StandardScaler:
"""Standardize the input using standard mean sub and std div"""
def __init__(self, mean, std, fill_zeros=False):
self.mean = mean
self.std = std
self.fill_zeros = fill_zeros
def transform(self, data):
if self.fill_zeros:
mask = (data == 0)
data[mask] = self.mean
return (data - self.mean) / self.std
def inverse_transform(self, data):
return (data * self.std) + self.mean
def load_dataset(dataset_dir, batch_size, valid_batch_size, test_batch_size, fill_zeros=False):
"""
Load data set
dataset_dir: dataset directory
batch_size: Training batch size
valid_batch_size: validation set batch size
test_batch_size: test set batch size
fill_zeros: (bool) whether to fill zeros in data with average
"""
data = {}
for category in ['train', 'val', 'test']:
cat_data = np.load(os.path.join(dataset_dir, category + '.npz'))
data['x_' + category] = cat_data['x']
data['y_' + category] = cat_data['y']
scaler = StandardScaler(mean=data['x_train'][..., 0].mean(),
std=data['x_train'][..., 0].std(),
fill_zeros=fill_zeros)
for category in ['train', 'val', 'test']:
data['x_' + category][..., 0] = scaler.transform(data['x_' + category][..., 0])
data['train_loader'] = DataLoader(data['x_train'], data['y_train'], batch_size)
data['val_loader'] = DataLoader(data['x_val'], data['y_val'], valid_batch_size)
data['test_loader'] = DataLoader(data['x_test'], data['y_test'], test_batch_size)
data['scaler'] = scaler
return data
def masked_mse(preds, labels, null_val=np.nan):
if np.isnan(null_val):
mask = ~torch.isnan(labels)
else:
mask = (labels != null_val)
# Calculate Mask
mask = mask.float()
mask /= torch.mean(mask)
mask = torch.where(torch.isnan(mask), torch.zeros_like(mask), mask)
# Masked MSE Loss
loss = (preds-labels)**2
loss = loss * mask
loss = torch.where(torch.isnan(loss), torch.zeros_like(loss), loss)
return torch.mean(loss)
def masked_rmse(preds, labels, null_val=np.nan):
# Masked RMSE Loss
return torch.sqrt(masked_mse(preds=preds, labels=labels, null_val=null_val))
def masked_rmsle(preds, labels, null_val=np.nan):
# Masked RMSLE Loss
# loss = (torch.log(torch.abs(preds) + 1) - torch.log(torch.abs(labels) + 1)) ** 2
return torch.sqrt(masked_mse(preds=torch.log(torch.abs(preds) + 1),
labels=torch.log(torch.abs(labels) + 1),
null_val=null_val))
def masked_mae(preds, labels, null_val=np.nan):
if np.isnan(null_val):
mask = ~torch.isnan(labels)
else:
mask = (labels != null_val)
# Calculate Mask
mask = mask.float()
mask /= torch.mean(mask)
mask = torch.where(torch.isnan(mask), torch.zeros_like(mask), mask)
# Masked MAE Loss
loss = torch.abs(preds-labels)
loss = loss * mask
loss = torch.where(torch.isnan(loss), torch.zeros_like(loss), loss)
return torch.mean(loss)
def metric(pred, real):
mae = masked_mae(pred, real, 0.0).item()
rmse = masked_rmse(pred, real, 0.0).item()
rmsle = masked_rmsle(pred, real, 0.0).item()
return mae, rmse, rmsle