find best hidden size and layers

This commit is contained in:
wassname
2020-11-08 10:54:03 +08:00
parent 04229f8bf6
commit 1e69b00fee
2 changed files with 1193 additions and 0 deletions
@@ -0,0 +1,783 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"ExecuteTime": {
"end_time": "2020-10-10T01:25:12.788851Z",
"start_time": "2020-10-10T01:25:12.783398Z"
}
},
"source": [
"# Sequence to Sequence Models for Timeseries Regression\n",
"\n",
"\n",
"In this notebook we are going to find the optimal hidden_size for a model vs a dataset. We will use pytorch lightning and optuna."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"ExecuteTime": {
"end_time": "2020-11-08T02:52:04.993589Z",
"start_time": "2020-11-08T02:52:04.569061Z"
}
},
"outputs": [],
"source": [
"# OPTIONAL: Load the \"autoreload\" extension so that code can change. But blacklist large modules\n",
"%load_ext autoreload\n",
"%autoreload 2\n",
"%aimport -pandas\n",
"%aimport -torch\n",
"%aimport -numpy\n",
"%aimport -matplotlib\n",
"%aimport -dask\n",
"%aimport -tqdm\n",
"%matplotlib inline"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"ExecuteTime": {
"end_time": "2020-11-08T02:52:06.671206Z",
"start_time": "2020-11-08T02:52:04.998087Z"
},
"lines_to_next_cell": 0
},
"outputs": [],
"source": [
"# Imports\n",
"import torch\n",
"from torch import nn, optim\n",
"from torch.nn import functional as F\n",
"from torch.autograd import Variable\n",
"import torch\n",
"import torch.utils.data\n",
"\n",
"import xarray as xr\n",
"import pandas as pd\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"\n",
"from pathlib import Path\n",
"from tqdm.auto import tqdm\n",
"\n",
"import pytorch_lightning as pl"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"ExecuteTime": {
"end_time": "2020-11-08T02:52:06.707927Z",
"start_time": "2020-11-08T02:52:06.674890Z"
}
},
"outputs": [],
"source": [
"from seq2seq_time.data.dataset import Seq2SeqDataSet, Seq2SeqDataSets\n",
"from seq2seq_time.predict import predict, predict_multi\n",
"from seq2seq_time.util import dset_to_nc"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"ExecuteTime": {
"end_time": "2020-11-08T02:52:06.745323Z",
"start_time": "2020-11-08T02:52:06.711604Z"
}
},
"outputs": [],
"source": [
"import logging\n",
"import warnings\n",
"import seq2seq_time.silence \n",
"warnings.simplefilter('once')\n",
"warnings.simplefilter(action='ignore', category=FutureWarning)\n",
"warnings.simplefilter(action='ignore', category=DeprecationWarning)\n",
"warnings.filterwarnings('ignore', 'Consider increasing the value of the `num_workers` argument', UserWarning)\n",
"warnings.filterwarnings('ignore', 'Your val_dataloader has `shuffle=True`', UserWarning)\n",
"\n",
"from pytorch_lightning import _logger as log\n",
"log.setLevel(logging.WARN)"
]
},
{
"cell_type": "markdown",
"metadata": {
"ExecuteTime": {
"end_time": "2020-10-10T01:28:32.492160Z",
"start_time": "2020-10-10T01:28:32.488140Z"
}
},
"source": [
"## Parameters"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"ExecuteTime": {
"end_time": "2020-11-08T02:52:06.843841Z",
"start_time": "2020-11-08T02:52:06.751591Z"
},
"lines_to_next_cell": 0
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"using cuda\n",
"20201108-095004\n"
]
},
{
"data": {
"text/plain": [
"96"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n",
"print(f'using {device}')\n",
"\n",
"timestamp = '20201108-095004'\n",
"print(timestamp)\n",
"window_past = 48*2\n",
"window_future = 48\n",
"batch_size = 64\n",
"num_workers = 5\n",
"datasets_root = Path('../data/processed/')\n",
"window_past"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"ExecuteTime": {
"end_time": "2020-11-01T23:28:09.504323Z",
"start_time": "2020-11-01T23:28:09.453546Z"
},
"lines_to_next_cell": 2
},
"outputs": [],
"source": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Datasets\n",
"\n",
"From easy to hard, these dataset show different challenges, all of them with more than 20k datapoints and with a regression output. See the 00.01 notebook for more details, and the code for more information.\n",
"\n",
"Some such as MetroInterstateTraffic are easier, some are periodic such as BejingPM25, some are conditional on inputs such as GasSensor, and some are noisy and periodic like IMOSCurrentsVel"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"ExecuteTime": {
"end_time": "2020-11-08T02:52:07.298057Z",
"start_time": "2020-11-08T02:52:06.850596Z"
},
"lines_to_next_cell": 0
},
"outputs": [
{
"data": {
"text/plain": [
"[seq2seq_time.data.data.GasSensor,\n",
" seq2seq_time.data.data.IMOSCurrentsVel,\n",
" seq2seq_time.data.data.AppliancesEnergyPrediction,\n",
" seq2seq_time.data.data.BejingPM25,\n",
" seq2seq_time.data.data.MetroInterstateTraffic]"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from seq2seq_time.data.data import IMOSCurrentsVel, AppliancesEnergyPrediction, BejingPM25, GasSensor, MetroInterstateTraffic\n",
"datasets = [GasSensor, IMOSCurrentsVel, AppliancesEnergyPrediction, BejingPM25, MetroInterstateTraffic]\n",
"datasets"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Lightning\n",
"\n",
"We will use pytorch lightning to handle all the training scaffolding. We have a common pytorch lightning class that takes in the model and defines training steps and logging."
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"ExecuteTime": {
"end_time": "2020-11-08T02:52:07.347557Z",
"start_time": "2020-11-08T02:52:07.301918Z"
}
},
"outputs": [],
"source": [
"import pytorch_lightning as pl\n",
"\n",
"class PL_MODEL(pl.LightningModule):\n",
" def __init__(self, model, lr=3e-4, patience=None, weight_decay=0):\n",
" super().__init__()\n",
" self._model = model\n",
" self.lr = lr\n",
" self.patience = patience\n",
" self.weight_decay = weight_decay\n",
"\n",
" def forward(self, x_past, y_past, x_future, y_future=None):\n",
" \"\"\"Eval/Predict\"\"\"\n",
" y_dist, extra = self._model(x_past, y_past, x_future, y_future)\n",
" return y_dist, extra\n",
"\n",
" def training_step(self, batch, batch_idx, phase='train'):\n",
" x_past, y_past, x_future, y_future = batch\n",
" y_dist, extra = self.forward(*batch)\n",
" loss = -y_dist.log_prob(y_future).mean()\n",
" self.log_dict({f'loss/{phase}':loss})\n",
" if ('loss' in extra) and (phase=='train'):\n",
" # some models have a special loss\n",
" loss = extra['loss']\n",
" self.log_dict({f'model_loss/{phase}':loss})\n",
" return loss\n",
"\n",
" def validation_step(self, batch, batch_idx):\n",
" return self.training_step(batch, batch_idx, phase='val')\n",
" \n",
" def test_step(self, batch, batch_idx):\n",
" return self.training_step(batch, batch_idx, phase='test')\n",
" \n",
" def configure_optimizers(self):\n",
" optim = torch.optim.AdamW(self.parameters(), lr=self.lr, weight_decay=self.weight_decay)\n",
" scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(\n",
" optim,\n",
" patience=self.patience,\n",
" verbose=False,\n",
" min_lr=1e-7,\n",
" ) if self.patience else None\n",
" return {'optimizer': optim, 'lr_scheduler': scheduler, 'monitor': 'loss/val'}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"ExecuteTime": {
"start_time": "2020-11-08T02:52:04.592Z"
},
"lines_to_next_cell": 2
},
"outputs": [],
"source": [
"from torch.utils.data import DataLoader\n",
"from pytorch_lightning.loggers import CSVLogger, WandbLogger, TensorBoardLogger, TestTubeLogger\n",
"from pytorch_lightning.callbacks.early_stopping import EarlyStopping\n",
"from pytorch_lightning.callbacks import LearningRateMonitor"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Models"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"ExecuteTime": {
"start_time": "2020-11-08T02:52:04.595Z"
},
"lines_to_end_of_cell_marker": 2,
"lines_to_next_cell": 0
},
"outputs": [],
"source": [
"from seq2seq_time.models.baseline import BaselineLast, BaselineMean\n",
"from seq2seq_time.models.lstm_seq2seq import LSTMSeq2Seq\n",
"from seq2seq_time.models.lstm import LSTM\n",
"from seq2seq_time.models.transformer import Transformer\n",
"from seq2seq_time.models.transformer_seq2seq import TransformerSeq2Seq\n",
"from seq2seq_time.models.neural_process import RANP\n",
"from seq2seq_time.models.transformer_process import TransformerProcess\n",
"from seq2seq_time.models.tcn import TCNSeq\n",
"from seq2seq_time.models.inceptiontime import InceptionTimeSeq\n",
"from seq2seq_time.models.xattention import CrossAttention"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"ExecuteTime": {
"start_time": "2020-11-08T02:52:04.599Z"
},
"lines_to_next_cell": 0
},
"outputs": [],
"source": [
"import gc\n",
"\n",
"def free_mem():\n",
" gc.collect()\n",
" torch.cuda.empty_cache()\n",
" gc.collect()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"ExecuteTime": {
"end_time": "2020-11-02T06:10:41.904480Z",
"start_time": "2020-11-02T06:10:41.848613Z"
},
"lines_to_next_cell": 2
},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"ExecuteTime": {
"start_time": "2020-11-08T02:52:04.605Z"
},
"lines_to_next_cell": 0
},
"outputs": [],
"source": [
"# PARAMS: model\n",
"dropout=0.0\n",
"layers=6\n",
"nhead=4\n",
"\n",
"models = [\n",
"# lambda xs, ys: BaselineLast(),\n",
"# lambda xs, ys, hidden_size: BaselineMean(),\n",
" lambda xs, ys, hidden_size, layers: Transformer(xs,\n",
" ys,\n",
" attention_dropout=dropout,\n",
" nhead=nhead,\n",
" nlayers=layers,\n",
" hidden_size=hidden_size),\n",
"\n",
" lambda xs, ys, hidden_size, layers:TransformerProcess(xs,\n",
" ys, hidden_size=hidden_size, nhead=nhead,\n",
" latent_dim=hidden_size//2, dropout=dropout,\n",
" nlayers=layers),\n",
" lambda xs, ys, hidden_size, layers:TCNSeq(xs, ys, hidden_size=hidden_size, nlayers=layers, dropout=dropout, kernel_size=2),\n",
" lambda xs, ys, hidden_size, layers: RANP(xs,\n",
" ys, hidden_dim=hidden_size, dropout=dropout, \n",
" latent_dim=hidden_size//2, n_decoder_layers=layers, n_latent_encoder_layers=layers, n_det_encoder_layers=layers),\n",
" lambda xs, ys, hidden_size, layers: TransformerSeq2Seq(xs,\n",
" ys,\n",
" hidden_size=hidden_size,\n",
" nhead=nhead,\n",
" nlayers=layers,\n",
" attention_dropout=dropout\n",
" ),\n",
" lambda xs, ys, hidden_size, layers: LSTM(xs,\n",
" ys,\n",
" hidden_size=hidden_size,\n",
" lstm_layers=layers//2,\n",
" lstm_dropout=dropout),\n",
" lambda xs, ys, hidden_size, layers: LSTMSeq2Seq(xs,\n",
" ys,\n",
" hidden_size=hidden_size,\n",
" lstm_layers=layers//2,\n",
" lstm_dropout=dropout),\n",
" lambda xs, ys, hidden_size, layers: CrossAttention(xs,\n",
" ys,\n",
" nlayers=layers,\n",
" hidden_size=hidden_size,),\n",
" lambda xs, ys, hidden_size, layers: InceptionTimeSeq(xs,\n",
" ys,\n",
" kernel_size=96,\n",
" layers=layers//2,\n",
" hidden_size=hidden_size,\n",
" bottleneck=hidden_size//4)\n",
"\n",
"]"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"ExecuteTime": {
"start_time": "2020-11-08T02:52:04.608Z"
}
},
"outputs": [],
"source": [
"# DEBUG: sanity check\n",
"\n",
"for Dataset in datasets:\n",
" dataset_name = Dataset.__name__\n",
" dataset = Dataset(datasets_root)\n",
" ds_train, ds_val, ds_test = dataset.to_datasets(window_past=window_past,\n",
" window_future=window_future)\n",
"\n",
" # Init data\n",
" x_past, y_past, x_future, y_future = ds_train.get_rows(10)\n",
" xs = x_past.shape[-1]\n",
" ys = y_future.shape[-1]\n",
"\n",
" # Loaders\n",
" dl_train = DataLoader(ds_train,\n",
" batch_size=batch_size,\n",
" shuffle=True,\n",
" pin_memory=num_workers == 0,\n",
" num_workers=num_workers)\n",
" dl_val = DataLoader(ds_val,\n",
" shuffle=True,\n",
" batch_size=batch_size,\n",
" num_workers=num_workers)\n",
"\n",
" for m_fn in models:\n",
" free_mem()\n",
" pt_model = m_fn(xs, ys, 8, 4)\n",
" model_name = type(pt_model).__name__\n",
" print(timestamp, dataset_name, model_name)\n",
"\n",
" # Wrap in lightning\n",
" model = PL_MODEL(pt_model,\n",
" lr=3e-4\n",
" ).to(device)\n",
" trainer = pl.Trainer(\n",
" fast_dev_run=True,\n",
" # GPU\n",
" gpus=1,\n",
" amp_level='O1',\n",
" precision=16,\n",
" )"
]
},
{
"cell_type": "markdown",
"metadata": {
"ExecuteTime": {
"end_time": "2020-11-01T07:30:40.569795Z",
"start_time": "2020-11-01T07:29:12.500374Z"
}
},
"source": [
"Lets summarize all models, and make sure they have a similar number of parameters"
]
},
{
"cell_type": "markdown",
"metadata": {
"ExecuteTime": {
"end_time": "2020-10-23T23:36:11.052891Z",
"start_time": "2020-10-23T23:36:11.048874Z"
}
},
"source": [
"## Train"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"ExecuteTime": {
"start_time": "2020-11-08T02:52:04.612Z"
},
"lines_to_next_cell": 2
},
"outputs": [],
"source": [
"from collections import defaultdict\n",
"from seq2seq_time.metrics import rmse, smape"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"ExecuteTime": {
"start_time": "2020-11-08T02:52:04.617Z"
},
"lines_to_next_cell": 2
},
"outputs": [],
"source": [
"max_iters=20000"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"ExecuteTime": {
"start_time": "2020-11-08T02:52:04.620Z"
}
},
"outputs": [],
"source": [
"tensorboard_dir = Path(f\"../outputs/{timestamp}\").resolve()\n",
"print(f'For tensorboard run:\\ntensorboard --logdir=\"{tensorboard_dir}\"')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"ExecuteTime": {
"end_time": "2020-11-08T01:38:37.371764Z",
"start_time": "2020-11-08T01:38:37.315240Z"
}
},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"ExecuteTime": {
"start_time": "2020-11-08T02:52:04.626Z"
}
},
"outputs": [],
"source": [
"class MetricsCallback(pl.Callback):\n",
" \"\"\"PyTorch Lightning metric callback.\"\"\"\n",
"\n",
" def __init__(self):\n",
" super().__init__()\n",
" self.metrics = []\n",
"\n",
" def on_validation_end(self, trainer, pl_module):\n",
" self.metrics.append(trainer.callback_metrics)\n",
"\n",
"def objective(trial):\n",
" # sample\n",
" hidden_size_exp = trial.suggest_int(\"hidden_size_exp\", 2, 8)\n",
" hidden_size = 2**hidden_size_exp\n",
" \n",
" layers = trial.suggest_int(\"layers\", 2, 12)\n",
" \n",
" # Load model\n",
" pt_model = m_fn(xs, ys, hidden_size, layers)\n",
" model_name = type(pt_model).__name__\n",
" \n",
" # Wrap in lightning\n",
" patience = 2\n",
" model = PL_MODEL(pt_model,\n",
" lr=3e-4, patience=patience,\n",
" weight_decay=4e-5\n",
" ).to(device)\n",
"\n",
" \n",
" # The default logger in PyTorch Lightning writes to event files to be consumed by\n",
" # TensorBoard. We don't use any logger here as it requires us to implement several abstract\n",
" # methods. Instead we setup a simple callback, that saves metrics from each validation step.\n",
"# metrics_callback = MetricsCallback()\n",
" \n",
" save_dir = f\"../outputs/{timestamp}/{dataset_name}_{model_name}/{trial.number}\"\n",
" Path(save_dir).mkdir(exist_ok=True, parents=True)\n",
" trainer = pl.Trainer(\n",
" # Training length\n",
" min_epochs=2,\n",
" max_epochs=100,\n",
" limit_train_batches=max_iters//batch_size,\n",
" limit_val_batches=max_iters//batch_size//5,\n",
" # Misc\n",
" gradient_clip_val=20,\n",
" terminate_on_nan=True,\n",
" # GPU\n",
" gpus=1,\n",
" amp_level='O1',\n",
" precision=16,\n",
" # Callbacks\n",
" default_root_dir=save_dir,\n",
" logger=False,\n",
" callbacks=[\n",
"# metrics_callback, \n",
" EarlyStopping(monitor='loss/val', patience=patience * 2),\n",
" PyTorchLightningPruningCallback(trial, monitor=\"loss/val\")],\n",
" )\n",
" trainer.fit(model, dl_train, dl_val)\n",
" \n",
" # Run on all val data, using test mode\n",
" r = trainer.test(model, test_dataloader=dl_val, verbose=False)\n",
" return r[0]['loss/test']"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"ExecuteTime": {
"end_time": "2020-11-08T02:45:44.106583Z",
"start_time": "2020-11-08T02:45:44.050637Z"
}
},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"ExecuteTime": {
"start_time": "2020-11-08T02:52:04.631Z"
}
},
"outputs": [],
"source": [
"import optuna\n",
"from optuna.integration import PyTorchLightningPruningCallback"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"ExecuteTime": {
"start_time": "2020-11-08T02:52:04.634Z"
},
"lines_to_next_cell": 0,
"scrolled": true
},
"outputs": [],
"source": [
"Path(f\"../outputs/{timestamp}\").mkdir(exist_ok=True)\n",
"results = defaultdict(dict)\n",
"for Dataset in tqdm(datasets, desc='datasets'):\n",
" dataset_name = Dataset.__name__\n",
" dataset = Dataset(datasets_root)\n",
" ds_train, ds_val, ds_test = dataset.to_datasets(window_past=window_past,\n",
" window_future=window_future)\n",
"\n",
" # Init data\n",
" x_past, y_past, x_future, y_future = ds_train.get_rows(10)\n",
" xs = x_past.shape[-1]\n",
" ys = y_future.shape[-1]\n",
"\n",
" # Loaders\n",
" dl_train = DataLoader(ds_train,\n",
" batch_size=batch_size,\n",
" shuffle=True,\n",
" pin_memory=num_workers == 0,\n",
" num_workers=num_workers)\n",
" dl_val = DataLoader(ds_val,\n",
" shuffle=False,\n",
" batch_size=batch_size,\n",
" num_workers=num_workers)\n",
"\n",
" for i, m_fn in enumerate(tqdm(models, desc=f'models ({dataset_name})')):\n",
" try:\n",
" model_name = type(m_fn(8, 8, 8, 2)).__name__\n",
" free_mem()\n",
" study_name = f'{timestamp}_{dataset_name}-{model_name}'\n",
" \n",
" storage = f\"sqlite:///../outputs/{timestamp}/optuna.db\"\n",
" pruner = optuna.pruners.MedianPruner()\n",
" study = optuna.create_study(storage=storage, \n",
" study_name=study_name, \n",
" pruner=pruner,\n",
" load_if_exists=True)\n",
" study.optimize(objective, n_trials=100, timeout=60*60)\n",
" print(\"Number of finished trials: {}\".format(len(study.trials)))\n",
"\n",
" print(\"Best trial:\")\n",
" trial = study.best_trial\n",
"\n",
" print(\" Value: {}\".format(trial.value))\n",
"\n",
" print(\" Params: \")\n",
" for key, value in trial.params.items():\n",
" print(\" {}: {}\".format(key, value))\n",
" \n",
" except Exception as e:\n",
" logging.exception('failed to run model')\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"@webio": {
"lastCommId": null,
"lastKernelId": null
},
"jupytext": {
"encoding": "# -*- coding: utf-8 -*-",
"formats": "ipynb,py:light"
},
"kernelspec": {
"display_name": "seq2seq-time",
"language": "python",
"name": "seq2seq-time"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.8"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {
"height": "calc(100% - 180px)",
"left": "10px",
"top": "150px",
"width": "209.162px"
},
"toc_section_display": true,
"toc_window_display": true
}
},
"nbformat": 4,
"nbformat_minor": 2
}
+410
View File
@@ -0,0 +1,410 @@
# -*- coding: utf-8 -*-
# ---
# jupyter:
# jupytext:
# formats: ipynb,py:light
# text_representation:
# extension: .py
# format_name: light
# format_version: '1.5'
# jupytext_version: 1.6.0
# kernelspec:
# display_name: seq2seq-time
# language: python
# name: seq2seq-time
# ---
# # Sequence to Sequence Models for Timeseries Regression
#
#
# In this notebook we are going to find the optimal hidden_size for a model vs a dataset. We will use pytorch lightning and optuna.
# OPTIONAL: Load the "autoreload" extension so that code can change. But blacklist large modules
# %load_ext autoreload
# %autoreload 2
# %aimport -pandas
# %aimport -torch
# %aimport -numpy
# %aimport -matplotlib
# %aimport -dask
# %aimport -tqdm
# %matplotlib inline
# +
# Imports
import torch
from torch import nn, optim
from torch.nn import functional as F
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
from pathlib import Path
from tqdm.auto import tqdm
import pytorch_lightning as pl
# -
from seq2seq_time.data.dataset import Seq2SeqDataSet, Seq2SeqDataSets
from seq2seq_time.predict import predict, predict_multi
from seq2seq_time.util import dset_to_nc
# +
import logging
import warnings
import seq2seq_time.silence
warnings.simplefilter('once')
warnings.simplefilter(action='ignore', category=FutureWarning)
warnings.simplefilter(action='ignore', category=DeprecationWarning)
warnings.filterwarnings('ignore', 'Consider increasing the value of the `num_workers` argument', UserWarning)
warnings.filterwarnings('ignore', 'Your val_dataloader has `shuffle=True`', UserWarning)
from pytorch_lightning import _logger as log
log.setLevel(logging.WARN)
# -
# ## Parameters
# +
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f'using {device}')
timestamp = '20201108-095004'
print(timestamp)
window_past = 48*2
window_future = 48
batch_size = 64
num_workers = 5
datasets_root = Path('../data/processed/')
window_past
# -
# ## Datasets
#
# From easy to hard, these dataset show different challenges, all of them with more than 20k datapoints and with a regression output. See the 00.01 notebook for more details, and the code for more information.
#
# Some such as MetroInterstateTraffic are easier, some are periodic such as BejingPM25, some are conditional on inputs such as GasSensor, and some are noisy and periodic like IMOSCurrentsVel
from seq2seq_time.data.data import IMOSCurrentsVel, AppliancesEnergyPrediction, BejingPM25, GasSensor, MetroInterstateTraffic
datasets = [GasSensor, IMOSCurrentsVel, AppliancesEnergyPrediction, BejingPM25, MetroInterstateTraffic]
datasets
# ## Lightning
#
# We will use pytorch lightning to handle all the training scaffolding. We have a common pytorch lightning class that takes in the model and defines training steps and logging.
# +
import pytorch_lightning as pl
class PL_MODEL(pl.LightningModule):
def __init__(self, model, lr=3e-4, patience=None, weight_decay=0):
super().__init__()
self._model = model
self.lr = lr
self.patience = patience
self.weight_decay = weight_decay
def forward(self, x_past, y_past, x_future, y_future=None):
"""Eval/Predict"""
y_dist, extra = self._model(x_past, y_past, x_future, y_future)
return y_dist, extra
def training_step(self, batch, batch_idx, phase='train'):
x_past, y_past, x_future, y_future = batch
y_dist, extra = self.forward(*batch)
loss = -y_dist.log_prob(y_future).mean()
self.log_dict({f'loss/{phase}':loss})
if ('loss' in extra) and (phase=='train'):
# some models have a special loss
loss = extra['loss']
self.log_dict({f'model_loss/{phase}':loss})
return loss
def validation_step(self, batch, batch_idx):
return self.training_step(batch, batch_idx, phase='val')
def test_step(self, batch, batch_idx):
return self.training_step(batch, batch_idx, phase='test')
def configure_optimizers(self):
optim = torch.optim.AdamW(self.parameters(), lr=self.lr, weight_decay=self.weight_decay)
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
optim,
patience=self.patience,
verbose=False,
min_lr=1e-7,
) if self.patience else None
return {'optimizer': optim, 'lr_scheduler': scheduler, 'monitor': 'loss/val'}
# -
from torch.utils.data import DataLoader
from pytorch_lightning.loggers import CSVLogger, WandbLogger, TensorBoardLogger, TestTubeLogger
from pytorch_lightning.callbacks.early_stopping import EarlyStopping
from pytorch_lightning.callbacks import LearningRateMonitor
# ## Models
from seq2seq_time.models.baseline import BaselineLast, BaselineMean
from seq2seq_time.models.lstm_seq2seq import LSTMSeq2Seq
from seq2seq_time.models.lstm import LSTM
from seq2seq_time.models.transformer import Transformer
from seq2seq_time.models.transformer_seq2seq import TransformerSeq2Seq
from seq2seq_time.models.neural_process import RANP
from seq2seq_time.models.transformer_process import TransformerProcess
from seq2seq_time.models.tcn import TCNSeq
from seq2seq_time.models.inceptiontime import InceptionTimeSeq
from seq2seq_time.models.xattention import CrossAttention
# +
import gc
def free_mem():
gc.collect()
torch.cuda.empty_cache()
gc.collect()
# -
# +
# PARAMS: model
dropout=0.0
layers=6
nhead=4
models = [
# lambda xs, ys: BaselineLast(),
# lambda xs, ys, hidden_size: BaselineMean(),
lambda xs, ys, hidden_size, layers: Transformer(xs,
ys,
attention_dropout=dropout,
nhead=nhead,
nlayers=layers,
hidden_size=hidden_size),
lambda xs, ys, hidden_size, layers:TransformerProcess(xs,
ys, hidden_size=hidden_size, nhead=nhead,
latent_dim=hidden_size//2, dropout=dropout,
nlayers=layers),
lambda xs, ys, hidden_size, layers:TCNSeq(xs, ys, hidden_size=hidden_size, nlayers=layers, dropout=dropout, kernel_size=2),
lambda xs, ys, hidden_size, layers: RANP(xs,
ys, hidden_dim=hidden_size, dropout=dropout,
latent_dim=hidden_size//2, n_decoder_layers=layers, n_latent_encoder_layers=layers, n_det_encoder_layers=layers),
lambda xs, ys, hidden_size, layers: TransformerSeq2Seq(xs,
ys,
hidden_size=hidden_size,
nhead=nhead,
nlayers=layers,
attention_dropout=dropout
),
lambda xs, ys, hidden_size, layers: LSTM(xs,
ys,
hidden_size=hidden_size,
lstm_layers=layers//2,
lstm_dropout=dropout),
lambda xs, ys, hidden_size, layers: LSTMSeq2Seq(xs,
ys,
hidden_size=hidden_size,
lstm_layers=layers//2,
lstm_dropout=dropout),
lambda xs, ys, hidden_size, layers: CrossAttention(xs,
ys,
nlayers=layers,
hidden_size=hidden_size,),
lambda xs, ys, hidden_size, layers: InceptionTimeSeq(xs,
ys,
kernel_size=96,
layers=layers//2,
hidden_size=hidden_size,
bottleneck=hidden_size//4)
]
# +
# DEBUG: sanity check
for Dataset in datasets:
dataset_name = Dataset.__name__
dataset = Dataset(datasets_root)
ds_train, ds_val, 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)
xs = x_past.shape[-1]
ys = y_future.shape[-1]
# Loaders
dl_train = DataLoader(ds_train,
batch_size=batch_size,
shuffle=True,
pin_memory=num_workers == 0,
num_workers=num_workers)
dl_val = DataLoader(ds_val,
shuffle=True,
batch_size=batch_size,
num_workers=num_workers)
for m_fn in models:
free_mem()
pt_model = m_fn(xs, ys, 8, 4)
model_name = type(pt_model).__name__
print(timestamp, dataset_name, model_name)
# Wrap in lightning
model = PL_MODEL(pt_model,
lr=3e-4
).to(device)
trainer = pl.Trainer(
fast_dev_run=True,
# GPU
gpus=1,
amp_level='O1',
precision=16,
)
# -
# Lets summarize all models, and make sure they have a similar number of parameters
# ## Train
from collections import defaultdict
from seq2seq_time.metrics import rmse, smape
max_iters=20000
tensorboard_dir = Path(f"../outputs/{timestamp}").resolve()
print(f'For tensorboard run:\ntensorboard --logdir="{tensorboard_dir}"')
# +
class MetricsCallback(pl.Callback):
"""PyTorch Lightning metric callback."""
def __init__(self):
super().__init__()
self.metrics = []
def on_validation_end(self, trainer, pl_module):
self.metrics.append(trainer.callback_metrics)
def objective(trial):
# sample
hidden_size_exp = trial.suggest_int("hidden_size_exp", 2, 8)
hidden_size = 2**hidden_size_exp
layers = trial.suggest_int("layers", 2, 12)
# Load model
pt_model = m_fn(xs, ys, hidden_size, layers)
model_name = type(pt_model).__name__
# Wrap in lightning
patience = 2
model = PL_MODEL(pt_model,
lr=3e-4, patience=patience,
weight_decay=4e-5
).to(device)
# The default logger in PyTorch Lightning writes to event files to be consumed by
# TensorBoard. We don't use any logger here as it requires us to implement several abstract
# methods. Instead we setup a simple callback, that saves metrics from each validation step.
# metrics_callback = MetricsCallback()
save_dir = f"../outputs/{timestamp}/{dataset_name}_{model_name}/{trial.number}"
Path(save_dir).mkdir(exist_ok=True, parents=True)
trainer = pl.Trainer(
# Training length
min_epochs=2,
max_epochs=100,
limit_train_batches=max_iters//batch_size,
limit_val_batches=max_iters//batch_size//5,
# Misc
gradient_clip_val=20,
terminate_on_nan=True,
# GPU
gpus=1,
amp_level='O1',
precision=16,
# Callbacks
default_root_dir=save_dir,
logger=False,
callbacks=[
# metrics_callback,
EarlyStopping(monitor='loss/val', patience=patience * 2),
PyTorchLightningPruningCallback(trial, monitor="loss/val")],
)
trainer.fit(model, dl_train, dl_val)
# Run on all val data, using test mode
r = trainer.test(model, test_dataloader=dl_val, verbose=False)
return r[0]['loss/test']
# -
import optuna
from optuna.integration import PyTorchLightningPruningCallback
Path(f"../outputs/{timestamp}").mkdir(exist_ok=True)
results = defaultdict(dict)
for Dataset in tqdm(datasets, desc='datasets'):
dataset_name = Dataset.__name__
dataset = Dataset(datasets_root)
ds_train, ds_val, 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)
xs = x_past.shape[-1]
ys = y_future.shape[-1]
# Loaders
dl_train = DataLoader(ds_train,
batch_size=batch_size,
shuffle=True,
pin_memory=num_workers == 0,
num_workers=num_workers)
dl_val = DataLoader(ds_val,
shuffle=False,
batch_size=batch_size,
num_workers=num_workers)
for i, m_fn in enumerate(tqdm(models, desc=f'models ({dataset_name})')):
try:
model_name = type(m_fn(8, 8, 8, 2)).__name__
free_mem()
study_name = f'{timestamp}_{dataset_name}-{model_name}'
storage = f"sqlite:///../outputs/{timestamp}/optuna.db"
pruner = optuna.pruners.MedianPruner()
study = optuna.create_study(storage=storage,
study_name=study_name,
pruner=pruner,
load_if_exists=True)
study.optimize(objective, n_trials=100, timeout=60*60)
print("Number of finished trials: {}".format(len(study.trials)))
print("Best trial:")
trial = study.best_trial
print(" Value: {}".format(trial.value))
print(" Params: ")
for key, value in trial.params.items():
print(" {}: {}".format(key, value))
except Exception as e:
logging.exception('failed to run model')