misc, fix transformer, more plots

This commit is contained in:
wassname
2020-10-30 14:46:31 +08:00
parent b7a80f187c
commit e1c86f9654
7 changed files with 1114 additions and 11017 deletions
-1
View File
@@ -55,7 +55,6 @@ Using sequence to sequence interfaces for timeseries regression
<td>1.08</td>
</tr>
<tr>
<th>MetroInterstateTraffic</th>
<td>1.76</td>
<td>-0.27</td>
File diff suppressed because one or more lines are too long
+186 -75
View File
@@ -19,15 +19,22 @@
#
# In this notebook we are going to tackle a harder problem:
# - predicting the future on a timeseries
# - using an LSTM
# - by outputing sequence of predictions
# - with rough uncertainty (uncalibrated)
# - outputing sequence of predictions
# - using forecasted information (like weather report, week, or cycle of the moon)
#
# Not many papers benchmark movels for multivariate regression, much less seq prediction with uncertainty. So this notebook will try a range of models on a range of dataset.
#
# We do this using a sequence to sqequence interface
#
# <img src="../reports/figures/Seq2Seq for regression.png" />
#
#
# https://medium.com/@boitemailjeanmid/smart-meters-in-london-part1-description-and-first-insights-jean-michel-d-db97af2de71b
#
# - [ ] tensorboard / wandb
# - [ ] show test train
# - [ ] val
# - [ ] don't overfit
# - [ ] TCN
# OPTIONAL: Load the "autoreload" extension so that code can change. But blacklist large modules
# %load_ext autoreload
@@ -49,10 +56,11 @@ from torch.autograd import Variable
import torch
import torch.utils.data
import xarray as xr
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['figure.figsize'] = (12.0, 3.0)
plt.rcParams['figure.figsize'] = (10.0, 2.0)
plt.style.use('ggplot')
from pathlib import Path
@@ -71,11 +79,19 @@ from seq2seq_time.util import dset_to_nc
import logging, sys
# logging.basicConfig(stream=sys.stdout, level=logging.INFO)
# +
import holoviews as hv
from holoviews import opts
from holoviews.operation.datashader import datashade, dynspread
hv.extension('bokeh')
# holoview datashader timeseries options
# %opts RGB [width=800 height=200 active_tools=["xwheel_zoom"] default_tools=["xpan","xwheel_zoom", "reset"] toolbar="right"]
# -
import warnings
warnings.filterwarnings("ignore")
# ## Parameters
@@ -91,15 +107,16 @@ num_workers = 5
freq = '30T'
max_rows = 5e5
datasets_root = Path('../data/processed/')
window_past
# -
# ## Plot helpers
# +
def plot_prediction(ds_preds, i):
"""Plot a prediction into the future, at a single point in time."""
def plot_prediction(ds_preds, i, ax=None, title='', std=False, label='pred', legend=False):
"""Plot a prediction into the future, at a single point in time."""
d = ds_preds.isel(t_source=i)
# Get arrays
@@ -109,18 +126,16 @@ def plot_prediction(ds_preds, i):
yt = d.y_true
now = d.t_source.squeeze()
plt.figure(figsize=(12, 4))
plt.scatter(xf, yt, label='true', c='k', s=6)
plt.scatter(xf, yt, c='k', s=6, label='true' if legend else None)
ylim = plt.ylim()
# plot prediction
plt.fill_between(xf, yp-2*s, yp+2*s, alpha=0.25,
facecolor="b",
interpolate=True,
label="2 std",)
plt.plot(xf, yp, label='pred', c='b')
if std:
plt.fill_between(xf, yp-2*s, yp+2*s, alpha=0.25,
facecolor="b",
interpolate=True,
label="2 std" if legend else None,)
plt.plot(xf, yp, label=label)
# plot true
plt.scatter(
@@ -131,24 +146,26 @@ def plot_prediction(ds_preds, i):
)
# plot a red line for now
plt.vlines(x=now, ymin=0, ymax=1, label='now', color='r')
plt.vlines(x=now, ymin=ylim[0], ymax=ylim[1], color='grey', ls='--')
plt.ylim(*ylim)
now=pd.Timestamp(now.values)
plt.title(f'Prediction NLL={d.nll.mean().item():2.2g}')
plt.xlabel(f'{now.date()}')
plt.ylabel('energy(kWh/hh)')
plt.legend()
plt.xticks(rotation=45)
plt.show()
plt.title(title or f'Prediction NLL={d.nll.mean().item():2.2g}')
plt.xticks(rotation=0)
if legend:
plt.legend()
plt.xlabel(f'{now}')
plt.ylabel(ds_preds.attrs['targets'])
return now
def plot_performance(ds_preds, full=False):
"""Multiple plots using xr_preds"""
plot_prediction(ds_preds, 24)
plot_prediction(ds_preds, 24, std=True, legend=True)
plt.show()
ds_preds.mean('t_source').plot.scatter('t_ahead_hours', 'nll') # Mean over all predictions
n = len(ds_preds.t_source)
plt.ylabel('Negative Log Likelihood (lower is better)')
plt.ylabel('NLL (lower is better)')
plt.xlabel('Hours ahead')
plt.title(f'NLL vs time ahead (no. samples={n})')
plt.show()
@@ -175,10 +192,39 @@ def plot_hist(trainer):
df_histe = df_hist.set_index('epoch').groupby('epoch').mean()
if len(df_histe)>1:
df_histe[['loss/train', 'loss/val']].plot(title='history')
plt.show()
return df_histe
except Exception:
pass
# ## Datasets
# +
from seq2seq_time.data.data import IMOSCurrentsVel, AppliancesEnergyPrediction, BejingPM25, GasSensor, MetroInterstateTraffic
datasets = [BejingPM25, GasSensor, AppliancesEnergyPrediction, MetroInterstateTraffic, IMOSCurrentsVel]
datasets
# -
# View train, test, val splits
l = hv.Layout()
for dataset in datasets:
d = dataset(datasets_root)
p = dynspread(
datashade(hv.Scatter(d.df_train[d.columns_target[0]]),
cmap='red'))
p *= dynspread(
datashade(hv.Scatter(d.df_val[d.columns_target[0]]),
cmap='green'))
p *= dynspread(
datashade(hv.Scatter(d.df_test[d.columns_target[0]]),
cmap='blue'))
p = p.opts(title=f"{dataset}")
l += p
l.cols(1)
# ## Lightning
@@ -262,30 +308,30 @@ models = [
# lambda: TransformerAutoR(input_size,
# output_size, hidden_out_size=32),
lambda: RANP(input_size,
output_size, hidden_dim=32,
latent_dim=64, n_decoder_layers=4),
output_size, hidden_dim=64, dropout=0.5,
latent_dim=32, n_decoder_layers=4),
lambda: LSTM(input_size,
output_size,
hidden_size=80,
hidden_size=32,
lstm_layers=3,
lstm_dropout=0.3),
lstm_dropout=0.4),
lambda: LSTMSeq2Seq(input_size,
output_size,
hidden_size=64,
lstm_layers=2,
lstm_dropout=0.25),
lstm_dropout=0.4),
lambda: TransformerSeq2Seq(input_size,
output_size,
hidden_size=128,
hidden_size=64,
nhead=8,
nlayers=4,
attention_dropout=0.2),
attention_dropout=0.4),
lambda: Transformer(input_size,
output_size,
attention_dropout=0.2,
attention_dropout=0.4,
nhead=8,
nlayers=8,
hidden_size=128),
nlayers=6,
hidden_size=64),
lambda :TransformerProcess(input_size,
output_size, hidden_size=16,
latent_dim=8, dropout=0.5,
@@ -294,11 +340,7 @@ models = [
]
# models
# +
from seq2seq_time.data.data import IMOSCurrentsVel, AppliancesEnergyPrediction, BejingPM25, GasSensor, MetroInterstateTraffic
datasets = [IMOSCurrentsVel, BejingPM25, GasSensor, AppliancesEnergyPrediction, MetroInterstateTraffic]
datasets
# +
# GasSensor(datasets_root)
@@ -309,25 +351,26 @@ datasets
from collections import defaultdict
results = defaultdict(dict)
# +
# tmp
model = Transformer(input_size,
output_size,
attention_dropout=0.4,
nhead=2,
nlayers=4,
hidden_size=16)
x_past, y_past, x_future, y_future = next(iter(dl_val))
model(x_past, y_past, x_future, y_future)
# -
from seq2seq_time.metrics import rmse, smape
for Dataset in datasets:
dataset_name = Dataset.__name__
dataset = Dataset(datasets_root)
ds_train, ds_test = dataset.to_datasets(window_past=window_past,
window_future=window_future)
# Init data
x_past, y_past, x_future, y_future = ds_train.get_rows(10)
input_size = x_past.shape[-1]
output_size = y_future.shape[-1]
# +
for Dataset in datasets:
dataset_name = Dataset.__name__
dataset = Dataset(datasets_root)
ds_train, ds_test = dataset.to_datasets(window_past=window_past,
ds_train, ds_val, ds_test = dataset.to_datasets(window_past=window_past,
window_future=window_future)
# Init data
@@ -341,7 +384,7 @@ for Dataset in datasets:
shuffle=True,
pin_memory=num_workers == 0,
num_workers=num_workers)
dl_test = DataLoader(ds_test,
dl_val = DataLoader(ds_val,
batch_size=batch_size,
num_workers=num_workers)
@@ -353,21 +396,21 @@ for Dataset in datasets:
print(dataset_name, model_name)
# Wrap in lightning
patience = 2
patience = 3
model = PL_MODEL(pt_model,
lr=3e-4, patience=patience,
lr=3e-3, patience=patience,
weight_decay=1e-5).to(device)
# Trainer
trainer = pl.Trainer(
gpus=1,
min_epochs=2,
max_epochs=20,
max_epochs=300,
amp_level='O1',
precision=16,
limit_train_batches=1000,
limit_val_batches=100,
limit_train_batches=300,
limit_val_batches=30,
logger=CSVLogger("../outputs", name=f'{dataset_name}_{model_name}'),
callbacks=[
EarlyStopping(monitor='loss/val', patience=patience * 2, verbose=True),
@@ -375,7 +418,7 @@ for Dataset in datasets:
)
# Train
trainer.fit(model, dl_train, dl_test)
trainer.fit(model, dl_train, dl_val)
ds_preds = predict(model.to(device),
ds_test,
@@ -387,9 +430,9 @@ for Dataset in datasets:
print(f'mean_NLL {ds_preds.nll.mean().item():2.2f}')
loss = ds_preds.nll.mean().item()
# Performance
# print(plot_hist(trainer))
# plot_performance(ds_preds)
# Performance TODO tensorboard, wandb
print(plot_hist(trainer))
plot_performance(ds_preds)
metrics = dict(
rmse=rmse(ds_preds.y_true, ds_preds.y_pred).item(),
@@ -408,20 +451,88 @@ for Dataset in datasets:
df_results = pd.concat({k:pd.DataFrame(v) for k,v in results.items()})
display(df_results)
# +
# File "/media/wassname/Storage5/projects2/3ST/seq2seq-time/seq2seq_time/models/transformer.py", line 54, in forward
# outputs = self.encoder(x, mask=mask#, src_key_padding_mask=x_key_padding_mask
# File "/media/wassname/Storage5/projects2/3ST/seq2seq-time/seq2seq_time/models/transformer.py", line 54, in forward
# outputs = self.encoder(x, mask=mask#, src_key_padding_mask=x_key_padding_mask
# -
df_results.xs('nll', level=1).round(2)
# # Leaderboard
# +
# ds_preds.to_netcdf(trainer.logger.experiment.log_dir+'/ds_preds2.nc')
# -
def bold_min(data):
'''
highlight the maximum in a Series or DataFrame
'''
attr = 'font-weight: bold'
#remove % and cast to float
data = data.replace('%','', regex=True).astype(float)
if data.ndim == 1: # Series from .apply(axis=0) or axis=1
is_min = data == data.min()
return [attr if v else '' for v in is_min]
else: # from .apply(axis=None)
is_min = data == data.min().min()
return pd.DataFrame(np.where(is_min, attr, ''),
index=data.index, columns=data.columns)
print(f'Negative Log-Likelihood (NLL).\nover {window_future} steps')
d=df_results.xs('nll', level=1).T.round(2)
d.style.apply(bold_min)
print(f'Symmetric mean absolute percentage error (SMAPE)\nover {window_future} steps')
d=df_results.xs('smape', level=1).T.round(2)
d.style.apply(bold_min)
# # Plots
# # plots
# Load saved preds
results = defaultdict(dict)
for Dataset in datasets:
dataset_name = Dataset.__name__
for m_fn in models:
pt_model = m_fn()
model_name = type(pt_model).__name__
checkpoint_name = f"{dataset_name}_{model_name}"
save_dir = Path(f"../outputs")/checkpoint_name
fs = sorted(save_dir.glob("**/ds_preds.nc"))
if len(fs)>0:
ds_preds = xr.open_dataset(fs[-1])
results[dataset_name][model_name] = ds_preds
data_i = 100
# Plot mean of predictions
for dataset in results.keys():
for model in results[dataset].keys():
ds_preds = results[dataset][model]
plot_prediction(ds_preds, data_i, label=f"{model}")
plt.title(dataset)
plt.legend()
plt.show()
# +
dataset='BejingPM25'
n = len(results[dataset].keys())
plt.figure(figsize=(8, 1.5*n))
plt.suptitle(f'Plots with confidence for {dataset} ')
for i, model in enumerate(results[dataset].keys()):
plt.subplot(n, 1, i+1)
ds_preds = results[dataset][model]
if i==n-1:
# The last one has the legend
plot_prediction(ds_preds, data_i, title=f"{model}", std=True, legend=True)
else:
plot_prediction(ds_preds, data_i, title=f"{model}", std=True, )
# share the x axis
locs, _ = plt.xticks()
plt.xticks(locs, labels=[])
plt.xlabel(None)
plt.subplots_adjust()
# -
+25 -18
View File
@@ -26,7 +26,7 @@ class RegressionForecastData:
self.df = self.download()
self.df_norm, self.scaler = self.normalize(self.df)
self.output_scaler = next(filter(lambda r:r[0][0] in self.columns_target, self.scaler.features))[-1]
self.df_train, self.df_test = self.split(self.df_norm)
self.df_train, self.df_val, self.df_test = self.split(self.df_norm)
# Check processing
self.check()
@@ -46,7 +46,8 @@ class RegressionForecastData:
def split(self, df_norm: pd.DataFrame) -> Tuple[pd.DataFrame, pd.DataFrame]:
df_train, df_test = timeseries_split(df_norm)
return df_train, df_test
df_test, df_val = timeseries_split(df_test, 0.5)
return df_train, df_val, df_test
def check(self) -> None:
"""Check the resulting dataframe"""
@@ -61,8 +62,9 @@ class RegressionForecastData:
def to_datasets(self, window_past: int, window_future: int, valid:bool=False) -> Tuple[Seq2SeqDataSet, Seq2SeqDataSet]:
"""Convert to torch datasets"""
ds_train = Seq2SeqDataSet(self.df_train, window_past=window_past, window_future=window_future, columns_target=self.columns_target, columns_past=self.columns_past)
ds_val = Seq2SeqDataSet(self.df_val, window_past=window_past, window_future=window_future, columns_target=self.columns_target, columns_past=self.columns_past)
ds_test = Seq2SeqDataSet(self.df_test, window_past=window_past, window_future=window_future, columns_target=self.columns_target, columns_past=self.columns_past)
return ds_train, ds_test
return ds_train, ds_val, ds_test
def __repr__(self):
return f'<{type(self).__name__} {self.df.shape if (self.df is not None) else None}>'
@@ -76,27 +78,32 @@ class GasSensor(RegressionForecastData):
columns_forecast = ['Flow rate (mL/min)', 'Heater voltage (V)']
def download(self):
# TODO cache in faster format
url = 'http://archive.ics.uci.edu/ml/machine-learning-databases/00487/gas-sensor-array-temperature-modulation.zip'
# download if needed
# extract_path = self.datasets_root/'gas-sensor-array-temperature-modulation.zip'
download_url(url, self.datasets_root)
outfile = self.datasets_root / 'gas-sensor-array-temperature-modulation.pk'
if not outfile.exists():
# Load csv's from inside zip
zf = zipfile.ZipFile(self.datasets_root / 'gas-sensor-array-temperature-modulation.zip')
dfs=[]
for f in zf.namelist():
if f.endswith('.csv'):
now = pd.to_datetime(Path(f).stem, format='%Y%m%d_%H%M%S')
df = pd.read_csv(zf.open(f))
df.index = pd.to_timedelta(df['Time (s)'], unit='s') + now
dfs.append(df)
self.df = pd.concat(dfs).dropna(subset=self.columns_target)
# Load csv's from inside zip
zf = zipfile.ZipFile(self.datasets_root / 'gas-sensor-array-temperature-modulation.zip')
dfs=[]
for f in zf.namelist():
if f.endswith('.csv'):
now = pd.to_datetime(Path(f).stem, format='%Y%m%d_%H%M%S')
df = pd.read_csv(zf.open(f))
df.index = pd.to_timedelta(df['Time (s)'], unit='s') + now
dfs.append(df)
self.df = pd.concat(dfs).dropna(subset=self.columns_target)
df = df[[ 'CO (ppm)', 'Humidity (%r.h.)', 'Temperature (C)',
'Flow rate (mL/min)', 'Heater voltage (V)', 'R1 (MOhm)']]
df = df.resample('0.3S').first()
df = df[[ 'CO (ppm)', 'Humidity (%r.h.)', 'Temperature (C)',
'Flow rate (mL/min)', 'Heater voltage (V)', 'R1 (MOhm)']]
df = df.resample('0.3S').first()
df.to_pickle(outfile)
df = pd.read_pickle(outfile)
return df
@@ -304,6 +311,6 @@ class IMOSCurrentsVel(RegressionForecastData):
columns=['HEIGHT_ABOVE_SENSOR', 'NOMINAL_DEPTH'])
df['SPD'] = np.sqrt(df.VCUR**2 + df.UCUR**2)
df.dropna(subset=self.columns_target, inplace=True)
df = df.resample('30T').first()
df = df.resample('30T').first()[:'2015']
return df
+1 -1
View File
@@ -31,7 +31,7 @@ class Seq2SeqDataSet(torch.utils.data.Dataset):
assert df.index.freq is not None, 'should have freq'
assert_no_objects(df)
self.freq = self.df.index.freq
self.freq = df.index.freq
self.df = df.dropna(subset=columns_target).ffill()
self.window_past = window_past
+1 -1
View File
@@ -48,7 +48,7 @@ class Transformer(nn.Module):
x = self.enc_emb(x).permute(1, 0, 2)
B, S, _ = x.shape
S, B, _ = x.shape
mask = mask_upper_triangular(S, device)
outputs = self.encoder(x, mask=mask#, src_key_padding_mask=x_key_padding_mask
+1 -1
View File
@@ -49,7 +49,7 @@ def predict(model, ds_test, batch_size, device='cpu', scaler=None):
"y_true": (["t_source", "t_ahead",], y_future),
},
coords={"t_source": t_source, "t_ahead": t_ahead, "t_behind": t_behind},
attrs={'freq': ds_test.freq, "model": str(model), "targets": ds_test.columns_target}
attrs={'freq': str(ds_test.freq), "model": str(type(model)), "targets": ds_test.columns_target}
)
xrs.append(xr_out)