-
Notifications
You must be signed in to change notification settings - Fork 3
/
forecast_POP.py
128 lines (99 loc) · 4.81 KB
/
forecast_POP.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
import argparse
import torch
import pandas as pd
import numpy as np
import pytorch_lightning as pl
from tqdm import tqdm
from models.GTM import GTM
from utils.data import POPDataset
from pathlib import Path
from sklearn.metrics import mean_absolute_error
from pathlib import Path
def cal_error_metrics(gt, forecasts):
# Absolute errors
mae = mean_absolute_error(gt, forecasts)
wape = 100 * np.sum(np.sum(np.abs(gt - forecasts), axis=-1)) / np.sum(gt)
return round(mae, 3), round(wape, 3)
def print_error_metrics(y_test, y_hat, rescaled_y_test, rescaled_y_hat):
mae, wape = cal_error_metrics(y_test, y_hat)
rescaled_mae, rescaled_wape = cal_error_metrics(rescaled_y_test, rescaled_y_hat)
print(mae, wape, rescaled_mae, rescaled_wape)
def run(args):
print(args)
# Set up CUDA
device = torch.device(f'cuda:{args.gpu_num}' if torch.cuda.is_available() else 'cpu')
# Seeds for reproducibility
pl.seed_everything(args.seed)
# Load sales data
test_df = pd.read_csv(Path(args.data_folder + 'test.csv'), parse_dates=['release_date'])
item_codes = test_df['external_code'].values
# Load category and color encodings
cat_dict = torch.load(Path(args.data_folder + 'category_labels.pt'))
col_dict = torch.load(Path(args.data_folder + 'color_labels.pt'))
fab_dict = torch.load(Path(args.data_folder + 'fabric_labels.pt'))
pop_signal = torch.load(args.pop_path)
test_loader = POPDataset(test_df, args.img_root, pop_signal, cat_dict, col_dict, \
fab_dict, args.trend_len).get_loader(batch_size=1, train=False)
model_savename = f'{args.wandb_run}_{args.output_dim}'
# Create model
model = GTM(
embedding_dim=args.embedding_dim,
hidden_dim=args.hidden_dim,
output_dim=12,
num_heads=args.num_attn_heads,
num_layers=args.num_hidden_layers,
cat_dict=cat_dict,
col_dict=col_dict,
fab_dict=fab_dict,
trend_len=args.trend_len,
num_trends= args.num_trends,
decoder_input_type=args.decoder_input_type,
use_encoder_mask=args.use_encoder_mask,
autoregressive=args.autoregressive,
gpu_num=args.gpu_num
)
model.load_state_dict(torch.load(args.ckpt_path)['state_dict'], strict=False)
# Forecast the testing set
model.to(device)
model.eval()
gt, forecasts, attns = [], [],[]
for test_data in tqdm(test_loader, total=len(test_loader), ascii=True):
with torch.no_grad():
test_data = [tensor.to(device) for tensor in test_data]
item_sales, attrs, temporal_features, pop_signal, images = test_data
y_pred, att = model(attrs, temporal_features, pop_signal, images)
forecasts.append(y_pred.detach().cpu().numpy().flatten()[:args.output_dim])
gt.append(item_sales.detach().cpu().numpy().flatten()[:args.output_dim])
attns.append(att.detach().cpu().numpy())
attns = np.stack(attns)
forecasts = np.array(forecasts)
gt = np.array(gt)
rescale_vals = np.load(args.data_folder + 'normalization_scale.npy')
rescaled_forecasts = forecasts * rescale_vals
rescaled_gt = gt * rescale_vals
print_error_metrics(gt, forecasts, rescaled_gt, rescaled_forecasts)
torch.save({'results': forecasts* rescale_vals, 'gts': gt* rescale_vals, 'codes': item_codes.tolist()}, Path('results/' + model_savename+'.pth'))
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Zero-shot sales forecasting')
# General arguments
parser.add_argument('--data_folder', type=str, default='dataset/')
parser.add_argument('--img_root', type=str, default='dataset/images/')
parser.add_argument('--pop_path', type=str, default='signals/pop.pt')
parser.add_argument('--ckpt_path', type=str, default='ckpt/path-to-model.ckpt')
parser.add_argument('--gpu_num', type=int, default=0)
parser.add_argument('--seed', type=int, default=21)
# Model specific arguments
parser.add_argument('--use_trends', type=int, default=1)
parser.add_argument('--num_trends', type=int, default=1)
parser.add_argument('--trend_len', type=int, default=52)
parser.add_argument('--decoder_input_type', type=int, default=3)
parser.add_argument('--embedding_dim', type=int, default=32)
parser.add_argument('--hidden_dim', type=int, default=64)
parser.add_argument('--output_dim', type=int, default=12)
parser.add_argument('--use_encoder_mask', type=int, default=1)
parser.add_argument('--autoregressive', type=int, default=0)
parser.add_argument('--num_attn_heads', type=int, default=4)
parser.add_argument('--num_hidden_layers', type=int, default=1)
parser.add_argument('--wandb_run', type=str, default='Run1')
args = parser.parse_args()
run(args)