├── requirements.txt ├── AR-Net_paper.pdf ├── .gitattributes ├── scripts ├── pre_commit.bash ├── pre_push.bash ├── install_hooks.bash └── arnet_dev_setup ├── .gitignore ├── pyproject.toml ├── setup.py ├── LICENSE ├── arnet ├── __init__.py ├── utils.py ├── plotting.py ├── utils_data.py ├── ar_net_legacy.py ├── fastai_mods.py ├── create_ar_data.py └── ar_net.py ├── v0_1 ├── model.py ├── example.py ├── data_loader.py ├── utils.py └── training.py ├── README.md ├── tests ├── test_unit.py ├── test_integration.py └── test_legacy.py └── example_notebooks ├── create_ar_data.ipynb ├── make_dataset.ipynb ├── 01_fit_arnet.ipynb └── legacy_run_experiments.ipynb /requirements.txt: -------------------------------------------------------------------------------- 1 | fastai>=2.1.4 2 | statsmodels>=0.12.1 3 | seaborn>=0.11.0 -------------------------------------------------------------------------------- /AR-Net_paper.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ourownstory/AR-Net/HEAD/AR-Net_paper.pdf -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Jupyter notebook language stats 2 | 3 | # For text count 4 | # *.ipynb text 5 | 6 | # To ignore it use below 7 | *.ipynb linguist-documentation 8 | -------------------------------------------------------------------------------- /scripts/pre_commit.bash: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | files=$(git diff --staged --name-only --diff-filter=d -- "*.py") 6 | for file in $files; do 7 | black "$file" 8 | git add "$file" 9 | done -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __py* 2 | *.egg* 3 | *.idea* 4 | *.log 5 | **/*.pyc 6 | **/__pycache__/ 7 | **/*.gz 8 | **/*.whl 9 | **/*egg-info/ 10 | site/ 11 | *.ipynb_checkpoints* 12 | ar_data/ 13 | results/ 14 | */models/* 15 | -------------------------------------------------------------------------------- /scripts/pre_push.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "Running pre-push hook: unittests" 4 | if ! python3 -m unittest discover -s tests; 5 | then 6 | echo "Failed tests. Unittests must pass before push!" 7 | exit 1 8 | fi -------------------------------------------------------------------------------- /scripts/install_hooks.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "Installing hooks..." 4 | GIT_DIR=$(git rev-parse --git-dir) 5 | # create symlink to our pre-commit and pre-push scripts 6 | ln -s ../../scripts/pre_commit.bash "$GIT_DIR"/hooks/pre-commit 7 | ln -s ../../scripts/pre_push.bash "$GIT_DIR"/hooks/pre-push 8 | # make the symlinks executable 9 | chmod a+rwx "$GIT_DIR"/hooks/pre-commit 10 | chmod a+rwx "$GIT_DIR"/hooks/pre-push 11 | echo "Done!" 12 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 120 3 | target-version = ['py38'] 4 | include = '\.pyi?$' 5 | exclude = ''' 6 | 7 | ( 8 | /( 9 | \.eggs # exclude a few common directories in the 10 | | \.git # root of the project 11 | | \.hg 12 | | \.mypy_cache 13 | | \.tox 14 | | \.venv 15 | | _build 16 | | buck-out 17 | | build 18 | | dist 19 | | site 20 | | ar_data # exclude our project directories that are not current source code. 21 | | v0_1 22 | | v1_0 23 | | results 24 | )/ 25 | | .gitignore 26 | ) 27 | ''' -------------------------------------------------------------------------------- /scripts/arnet_dev_setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import subprocess 5 | 6 | 7 | def install_hooks(): 8 | dir_scripts = os.path.abspath(os.path.dirname(__file__)) 9 | script_files = [ 10 | "install_hooks.bash", 11 | "pre_commit.bash", 12 | "pre_push.bash", 13 | ] 14 | for script_f in script_files: 15 | file = os.path.join(dir_scripts, script_f) 16 | subprocess.check_call(["chmod", "a+rwx", file]) 17 | subprocess.call(os.path.join(dir_scripts, "install_hooks.bash"), shell=True) 18 | 19 | 20 | if __name__ == "__main__": 21 | install_hooks() 22 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import setuptools 3 | 4 | dir_repo = os.path.abspath(os.path.dirname(__file__)) 5 | # read the contents of REQUIREMENTS file 6 | with open(os.path.join(dir_repo, "requirements.txt"), "r") as f: 7 | requirements = f.read().splitlines() 8 | # read the contents of README file 9 | with open(os.path.join(dir_repo, "README.md"), encoding="utf-8") as f: 10 | readme = f.read() 11 | 12 | setuptools.setup( 13 | name="arnet", 14 | version="1.2.0", 15 | description="A simple auto-regressive Neural Network for time-series", 16 | author="Oskar Triebe", 17 | url="https://github.com/ourownstory/AR-Net", 18 | packages=setuptools.find_packages(), 19 | python_requires=">=3.8", 20 | install_requires=requirements, 21 | extras_require={ 22 | "dev": ["black"], 23 | }, 24 | # setup_requires=[""], 25 | scripts=["scripts/arnet_dev_setup"], 26 | long_description=readme, 27 | long_description_content_type="text/markdown", 28 | ) 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Oskar Triebe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /arnet/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | log = logging.getLogger("ARNet") 4 | log.setLevel("INFO") 5 | # Create handlers 6 | c_handler = logging.StreamHandler() 7 | f_handler = logging.FileHandler("logs.log", "w+") 8 | # c_handler.setLevel("WARNING") 9 | # f_handler.setLevel("INFO") 10 | # Create formatters and add it to handlers 11 | c_format = logging.Formatter("%(levelname)s: %(name)s - %(funcName)s: %(message)s") 12 | c_handler.setFormatter(c_format) 13 | log.addHandler(c_handler) 14 | 15 | # uncomment for a log file 16 | # f_format = logging.Formatter("%(asctime)s; %(levelname)s; %(name)s; %(funcName)s; %(message)s") 17 | # f_handler.setFormatter(f_format) 18 | # log.addHandler(f_handler) 19 | 20 | # lazy imports ala fastai2 style (for nice print functionality) 21 | from fastai.basics import * 22 | from fastai.tabular.all import * 23 | 24 | from .ar_net import ARNet 25 | 26 | # from .ar_net_legacy import init_ar_learner 27 | from .utils_data import load_from_file, tabularize_univariate, estimate_noise, split_by_p_valid 28 | from .utils import pad_ar_params, nice_print_list, compute_sTPE, coeff_from_model 29 | from .plotting import plot_weights, plot_prediction_sample, plot_error_scatter 30 | -------------------------------------------------------------------------------- /v0_1/model.py: -------------------------------------------------------------------------------- 1 | import torch.nn as nn 2 | import torch.nn.functional as F 3 | 4 | 5 | class DAR(nn.Module): 6 | ''' 7 | A simple, general purpose, fully connected network 8 | ''' 9 | 10 | def __init__(self, ar, num_layers=1, d_hidden=None): 11 | # Perform initialization of the pytorch superclass 12 | super().__init__() 13 | # Define network layer dimensions 14 | d_in, d_out = [ar, 1] 15 | self.ar = ar 16 | self.num_layers = num_layers 17 | if d_hidden is None and num_layers > 1: 18 | d_hidden = d_in 19 | if self.num_layers == 1: 20 | self.layer_1 = nn.Linear(d_in, d_out, bias=True) 21 | else: 22 | self.layer_1 = nn.Linear(d_in, d_hidden, bias=True) 23 | self.mid_layers = [] 24 | for i in range(self.num_layers - 2): 25 | self.mid_layers.append(nn.Linear(d_hidden, d_hidden, bias=True)) 26 | self.layer_out = nn.Linear(d_hidden, d_out, bias=True) 27 | 28 | def forward(self, x): 29 | ''' 30 | This method defines the network layering and activation functions 31 | ''' 32 | activation = F.relu 33 | x = self.layer_1(x) 34 | if self.num_layers > 1: 35 | x = activation(x) 36 | for layer in self.mid_layers: 37 | x = layer(x) 38 | x = activation(x) 39 | x = self.layer_out(x) 40 | return x 41 | 42 | 43 | def main(): 44 | pass 45 | 46 | 47 | if __name__ == "__main__": 48 | main() 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) 2 | 3 | # AR-Net 4 | A simple auto-regressive Neural Network for time-series ([link to paper](https://arxiv.org/abs/1911.12436)). 5 | 6 | ## Install 7 | After downloading the code repository (via `git clone`), change to the repository directory (`cd AR-Net`) 8 | and install arnet as python package with `pip install .` 9 | 10 | ## Use 11 | View the notebook [`example_notebooks/arnet.ipynb`](example_notebooks/01_fit_arnet.ipynb) for an example of how to use the model. 12 | 13 | ## Versions 14 | ### Current (1.2) 15 | The version 1.0 made the model easier to use with your own datasets and requires less hyperparameters 16 | for a simpler training procedure. It is built on the fastai library. 17 | 18 | Changes (1.1 -> 1.2): 19 | * simplified UI with ARNet as object 20 | * GPU support 21 | * robustified training 22 | * added test cases 23 | * updated example notebooks 24 | 25 | Changes (1.0 -> 1.1): 26 | * port [beta fastai2](https://github.com/fastai/fastai2) to it's current [stable release](https://github.com/fastai/fastai) 27 | * make install as pip package possible 28 | * add black code formatter (and git pre-commit hook) 29 | * add unittests (and git pre-push hook) 30 | * fix issues with new fastai api 31 | * remove old code fragments 32 | 33 | ### Pure PyTorch (0.1) 34 | Version 0.1 was based on Pytorch and you can still use it if you do not want to use fastai. 35 | 36 | See file [`v0_1/example.py`](v0_1/example.py) for how to use the v0.1 model. 37 | 38 | ### Now also part of NeuralProphet 39 | AR-Net is now part of a more comprehensive package [NeuralProphet](https://github.com/ourownstory/neural_prophet). 40 | 41 | I strongly recommend using it instead of the standalone version, unless you specifically want to use AR-Net, 42 | which may make sense if you need to model a highly-autoregressive time-series with sparse long-range dependencies. 43 | -------------------------------------------------------------------------------- /arnet/utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import torch 3 | 4 | 5 | def pad_ar_params(ar_params, n_lags, n_forecasts=1): 6 | """ " 7 | pads ar_parameter lists to the length of n_lags 8 | ar_params: list of length n_forecasts with elements: lists of ar coeffs 9 | n_lags: length to which pad each of the ar coeffs 10 | """ 11 | assert n_forecasts == len(ar_params) 12 | if n_forecasts != 1: 13 | if all([isinstance(ar_params[i], list) for i in range(n_forecasts)]): 14 | return [pad_ar_params([ar_params[i]], n_lags, 1)[0] for i in range(n_forecasts)] 15 | else: 16 | raise NotImplementedError("AR Coeff for each of the forecast targets are needed") 17 | return [ar_params[0] + [0.0] * (n_lags - len(ar_params[0]))] 18 | 19 | 20 | def nice_print_list(data): 21 | if all([isinstance(data[i], list) for i in range(len(data))]): 22 | return [nice_print_list(data[i]) for i in range(len(data))] 23 | return ["{:.3f}".format(x) for x in data] 24 | # return [["{:.2f}".format(x) for x in sublist] for sublist in data] 25 | 26 | 27 | def compute_sTPE(est, real): 28 | est, real = np.array(est), np.array(real) 29 | sum_abs_diff = np.sum(np.abs(est - real)) 30 | sum_abs = np.sum(np.abs(est) + np.abs(real)) 31 | return 100.0 * sum_abs_diff / (10e-9 + sum_abs) 32 | 33 | 34 | def coeff_from_model(model, reversed_weights=True): 35 | for layer in model.modules(): 36 | if isinstance(layer, torch.nn.Linear): 37 | weights = [list(x[::-1] if reversed_weights else x) for x in layer.weight.detach().cpu().numpy()] 38 | return weights # note: preliminary exit of loop is a feature. 39 | 40 | 41 | def set_logger_level(logger, log_level=None, include_handlers=False): 42 | if log_level is None: 43 | logger.warning("Failed to set log_level to None.") 44 | elif log_level not in ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", 10, 20, 30, 40, 50): 45 | logger.error( 46 | "Failed to set log_level to {}." 47 | "Please specify a valid log level from: " 48 | "'DEBUG', 'INFO', 'WARNING', 'ERROR' or 'CRITICAL'" 49 | "".format(log_level) 50 | ) 51 | else: 52 | logger.setLevel(log_level) 53 | if include_handlers: 54 | for h in logger.handlers: 55 | h.setLevel(log_level) 56 | logger.debug("Set log level to {}".format(log_level)) 57 | -------------------------------------------------------------------------------- /arnet/plotting.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import matplotlib.pyplot as plt 3 | import seaborn as sns 4 | import os 5 | 6 | 7 | def plot_weights(ar_val, weights, ar=None, model_name="AR-Net", save=False, savedir="results", figsize=(10, 4)): 8 | if ar is not None: 9 | df = pd.DataFrame( 10 | zip( 11 | list(range(1, ar_val + 1)) * 2, 12 | ["AR-Process (True)"] * ar_val + [model_name] * ar_val, 13 | list(ar) + list(weights), 14 | ), 15 | columns=["AR-coefficient (lag number)", "model", "value (weight)"], 16 | ) 17 | plt.figure(figsize=figsize) 18 | palette = {"Classic-AR": "C0", "AR-Net": "C1", "AR-Process (True)": "k"} 19 | sns.barplot(data=df, palette=palette, x="AR-coefficient (lag number)", hue="model", y="value (weight)") 20 | else: 21 | df = pd.DataFrame( 22 | zip( 23 | list(range(1, ar_val + 1)), 24 | [model_name] * ar_val, 25 | list(weights), 26 | ), 27 | columns=["AR-coefficient (lag number)", "model", "value (weight)"], 28 | ) 29 | plt.figure(figsize=figsize) 30 | sns.barplot(data=df, x="AR-coefficient (lag number)", hue="model", y="value (weight)") 31 | if save: 32 | if not os.path.exists(savedir): 33 | os.makedirs(savedir) 34 | figname = "weights_{}_{}.png".format(ar_val, model_name) 35 | plt.savefig(os.path.join(savedir, figname), dpi=300, bbox_inches="tight") 36 | else: 37 | plt.show() 38 | 39 | 40 | def plot_prediction_sample(predicted, actual, model_name="AR-Net", save=False, savedir="results"): 41 | fig2 = plt.figure() 42 | fig2.set_size_inches(10, 6) 43 | plt.plot(actual) 44 | plt.plot(predicted) 45 | plt.legend(["Actual Time-Series", "{}-Prediction".format(model_name)]) 46 | if save: 47 | if not os.path.exists(savedir): 48 | os.makedirs(savedir) 49 | figname = "prediction_{}.png".format(model_name) 50 | plt.savefig(os.path.join(savedir, figname), dpi=600, bbox_inches="tight") 51 | else: 52 | plt.show() 53 | 54 | 55 | def plot_error_scatter(predicted, actual, model_name="AR-Net", save=False, savedir="results"): 56 | # error = predicted - actual 57 | fig3 = plt.figure() 58 | fig3.set_size_inches(6, 6) 59 | plt.scatter(actual, predicted - actual, marker="o", s=10, alpha=0.3) 60 | plt.legend(["{}-Error".format(model_name)]) 61 | if save: 62 | if not os.path.exists(savedir): 63 | os.makedirs(savedir) 64 | figname = "scatter_{}.png".format(model_name) 65 | plt.savefig(os.path.join(savedir, figname), dpi=600, bbox_inches="tight") 66 | else: 67 | plt.show() 68 | -------------------------------------------------------------------------------- /tests/test_unit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import unittest 4 | import os 5 | import pathlib 6 | import shutil 7 | import logging 8 | import random 9 | import pandas as pd 10 | import numpy as np 11 | import warnings 12 | 13 | warnings.filterwarnings("ignore", message=".*nonzero.*", category=UserWarning) 14 | 15 | import arnet 16 | from arnet.create_ar_data import generate_armaprocess_data 17 | from arnet.create_ar_data import save_to_file, load_from_file 18 | 19 | log = logging.getLogger("ARNet.test") 20 | log.setLevel("DEBUG") 21 | log.parent.setLevel("WARNING") 22 | 23 | DIR = pathlib.Path(__file__).parent.absolute() 24 | data_path = os.path.join(DIR, "ar_data") 25 | results_path = os.path.join(DIR, "results") 26 | EPOCHS = 2 27 | 28 | 29 | class UnitTests(unittest.TestCase): 30 | save = True 31 | 32 | def test_create_data_random(self): 33 | # option 1: Randomly generated AR parameters 34 | data_config = { 35 | "samples": 1000, 36 | "noise_std": 0.1, 37 | "ar_order": 3, 38 | "ma_order": 0, 39 | "params": None, # for randomly generated AR params 40 | } 41 | log.debug("{}".format(data_config)) 42 | 43 | # Generate data 44 | series, data_config["ar_params"], data_config["ma_params"] = generate_armaprocess_data(**data_config) 45 | 46 | if self.save: 47 | del data_config["params"] 48 | data_name = save_to_file(data_path, series, data_config) 49 | # just to test: 50 | df, data_config2 = load_from_file(data_path, data_name, load_config=True) 51 | log.debug("loaded from saved files:") 52 | log.debug("{}".format(data_config2)) 53 | log.debug("{}".format(df.head())) 54 | 55 | def test_create_data_manual(self): 56 | # option 1: Randomly generated AR parameters 57 | # option 2: Manually define AR parameters 58 | data_config = { 59 | "samples": 1000, 60 | "noise_std": 0.1, 61 | "params": ([0.2, 0.3, -0.5], []), 62 | # "params": ([0.2, 0, 0.3, 0, 0, 0, 0, 0, 0, -0.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], []), 63 | } 64 | data_config["ar_order"] = int(sum(np.array(data_config["params"][0]) != 0.0)) 65 | data_config["ma_order"] = int(sum(np.array(data_config["params"][1]) != 0.0)) 66 | log.debug("{}".format(data_config)) 67 | 68 | # Generate data 69 | series, data_config["ar_params"], data_config["ma_params"] = generate_armaprocess_data(**data_config) 70 | 71 | if self.save: 72 | del data_config["params"] 73 | data_name = save_to_file(data_path, series, data_config) 74 | # just to test: 75 | df, data_config2 = load_from_file(data_path, data_name, load_config=True) 76 | log.debug("loaded from saved files:") 77 | log.debug("{}".format(data_config2)) 78 | log.debug("{}".format(df.head())) 79 | -------------------------------------------------------------------------------- /arnet/utils_data.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import logging 4 | 5 | from arnet.create_ar_data import load_from_file 6 | 7 | log = logging.getLogger("ARNet") 8 | 9 | 10 | def estimate_noise(series): 11 | return float(np.mean(np.abs(series.iloc[:-1].values - series.iloc[1:].values))) 12 | 13 | 14 | def split_by_p_valid(valid_p, n_sample, verbose=False): 15 | split_idx = int(n_sample * (1 - valid_p)) 16 | splits = [list(range(split_idx)), list(range(split_idx, n_sample))] 17 | if verbose: 18 | print("split on idx: ", split_idx) 19 | print("split sizes: ", [len(x) for x in splits]) 20 | return splits 21 | 22 | 23 | def tabularize_univariate(series, n_lags, n_forecasts=1, nested_list=False): 24 | """ 25 | Create a tabular dataset with ar_order lags for supervised forecasting 26 | Arguments: 27 | series: Sequence of observations as a Pandas DataFrame 28 | n_lags: Number of lag observations as input (X). 29 | n_forecasts: Number of observations as output (y). 30 | Returns: 31 | df: Pandas DataFrame of input lags and forecast values (as nested lists) 32 | shape (n_samples, 2). 33 | Cols: "x": list(n_lags) 34 | Cols: "y": list(n_forecasts) 35 | """ 36 | n_samples = len(series) - n_lags + 1 - n_forecasts 37 | 38 | x = pd.DataFrame([series.iloc[i : i + n_lags, 0].values for i in range(n_samples)]) 39 | y = pd.DataFrame([series.iloc[i + n_lags : i + n_lags + n_forecasts, 0].values for i in range(n_samples)]) 40 | if nested_list: 41 | df = pd.concat([x.apply(list, axis=1), y.apply(list, axis=1)], axis=1) 42 | df.columns = ["x", "y"] 43 | else: 44 | df = pd.concat([x, y], axis=1) 45 | df.columns = ["x_{}".format(num) for num in list(range(len(x.columns)))] + [ 46 | "y_{}".format(num) for num in list(range(len(y.columns))) 47 | ] 48 | return df 49 | 50 | 51 | def main(): 52 | verbose = True 53 | data_path = "ar_data" 54 | data_name = "ar_3_ma_0_noise_0.100_len_10000" 55 | 56 | ## if created AR data with create_ar_data, we can use the helper function: 57 | df, data_config = load_from_file(data_path, data_name, load_config=True) 58 | n_lags = data_config["ar_order"] 59 | 60 | ## else we can manually load any file that stores a time series, for example: 61 | # df = pd.read_csv(os.path.join(data_path, data_name + '.csv'), header=None, index_col=False) 62 | # n_lags = 3 63 | 64 | if verbose: 65 | print(data_config) 66 | print(df.shape) 67 | 68 | ## create a tabularized dataset from time series 69 | df_tab = tabularize_univariate( 70 | df, 71 | n_lags=n_lags, 72 | n_forecasts=1, 73 | nested_list=False, 74 | ) 75 | 76 | if verbose: 77 | print("tabularized df") 78 | print(df_tab.shape) 79 | # print(df_tab.columns) 80 | # if nested_list: 81 | # print("x_dim:", len(df_tab['x'][0]), "y_dim:", len(df_tab['y'][0])) 82 | print(df_tab.head()) 83 | 84 | 85 | if __name__ == "__main__": 86 | main() 87 | -------------------------------------------------------------------------------- /arnet/ar_net_legacy.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | import logging 3 | 4 | import fastai 5 | 6 | # lazy imports ala fastai2 style (for nice print functionality) 7 | from fastai.basics import * 8 | from fastai.tabular.all import * 9 | 10 | # explicit imports (for reference) 11 | # from fastai.data.core import DataLoaders 12 | # from fastai.metrics import mse, mae 13 | # from fastai.tabular.core import TabularPandas, TabDataLoader 14 | # from fastai.tabular.learner import tabular_learner 15 | # from fastai.data.transforms import Normalize 16 | 17 | 18 | ## import arnet 19 | from arnet.utils_data import load_from_file, tabularize_univariate, estimate_noise, split_by_p_valid 20 | from arnet.utils import pad_ar_params, nice_print_list, compute_sTPE, coeff_from_model 21 | from arnet import utils, plotting 22 | from arnet.fastai_mods import SparsifyAR, sTPE 23 | 24 | log = logging.getLogger("ARNet") 25 | 26 | 27 | def init_ar_learner( 28 | series, 29 | ar_order, 30 | n_forecasts=1, 31 | valid_p=0.1, 32 | sparsity=None, 33 | ar_params=None, 34 | train_bs=32, 35 | valid_bs=128, 36 | verbose=False, 37 | ): 38 | if sparsity is not None and sparsity == 1.0: 39 | sparsity = None 40 | df_all = tabularize_univariate(series, ar_order, n_forecasts) 41 | est_noise = estimate_noise(series) 42 | 43 | if verbose: 44 | print("tabularized df") 45 | print("df columns", list(df_all.columns)) 46 | print("df shape", df_all.shape) 47 | # if nested_list: print("x_dim:", len(df_all['x'][0]), "y_dim:", len(df_all['y'][0])) 48 | # print("df head(3)", df_all.head(3)) 49 | print("estimated noise of series", est_noise) 50 | 51 | ## split 52 | splits = split_by_p_valid(valid_p, len(df_all), verbose) 53 | 54 | cont_names = [col for col in list(df_all.columns) if "x_" == col[:2]] 55 | target_names = [col for col in list(df_all.columns) if "y_" == col[:2]] 56 | 57 | ## preprocess? 58 | # procs = [Normalize] 59 | procs = [] 60 | 61 | tp = TabularPandas(df_all, procs=procs, cat_names=None, cont_names=cont_names, y_names=target_names, splits=splits) 62 | if verbose: 63 | print("cont var num", len(tp.cont_names), tp.cont_names) 64 | # print(tp.iloc[0:5]) 65 | 66 | ### next: data loader, learner 67 | trn_dl = TabDataLoader(tp.train, bs=train_bs, shuffle=True, drop_last=True) 68 | val_dl = TabDataLoader(tp.valid, bs=valid_bs) 69 | dls = DataLoaders(trn_dl, val_dl) 70 | 71 | # if verbose: 72 | # print("showing batch") 73 | # print(dls.show_batch(show=False)) 74 | 75 | callbacks = [] 76 | if sparsity is not None: 77 | callbacks.append(SparsifyAR(sparsity, est_noise)) 78 | if verbose: 79 | print("reg lam: ", callbacks[0].lam) 80 | 81 | metrics = [mae] 82 | if ar_params is not None: 83 | metrics.append(sTPE(ar_params, at_epoch_end=False)) 84 | 85 | tm_config = {"use_bn": False, "bn_final": False, "bn_cont": False} 86 | learn = tabular_learner( 87 | dls, 88 | layers=[], # Note: None defaults to [200, 100] 89 | config=tm_config, # None calls tabular_config() 90 | n_out=len(target_names), # None calls get_c(dls) 91 | train_bn=False, # passed to Learner 92 | metrics=metrics, # passed on to TabularLearner, to parent Learner 93 | loss_func=mse, 94 | cbs=callbacks, 95 | ) 96 | if verbose: 97 | print(learn.model) 98 | return learn 99 | -------------------------------------------------------------------------------- /tests/test_integration.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import unittest 4 | import os 5 | import pathlib 6 | import shutil 7 | import logging 8 | import random 9 | import pandas as pd 10 | import warnings 11 | 12 | warnings.filterwarnings("ignore", message=".*nonzero.*", category=UserWarning) 13 | import arnet 14 | 15 | log = logging.getLogger("ARNet.test") 16 | log.setLevel("WARNING") 17 | log.parent.setLevel("WARNING") 18 | 19 | DIR = pathlib.Path(__file__).parent.absolute() 20 | data_path = os.path.join(DIR, "ar_data") 21 | results_path = os.path.join(DIR, "results_test") 22 | AR_FILE = "ar_3_ma_0_noise_0.100_len_1000" 23 | EPOCHS = 2 24 | 25 | 26 | class IntegrationTests(unittest.TestCase): 27 | plot = False 28 | save = False 29 | 30 | def test_random_data(self): 31 | df = pd.DataFrame({"x": [random.gauss(0.0, 1.0) for i in range(1000)]}) 32 | m = arnet.ARNet(ar_order=3, n_epoch=3) 33 | m.tabularize(df) 34 | m.make_datasets() 35 | m.create_regularizer(sparsity=0.3) 36 | m.create_learner() 37 | m.find_lr(plot=False) 38 | m.fit_one_cycle(cycles=2, plot=False) 39 | log.info("coeff of random data: {}".format(m.coeff)) 40 | 41 | def test_plot(self): 42 | if not os.path.exists(results_path): 43 | os.makedirs(results_path) 44 | 45 | df = pd.DataFrame({"x": [random.gauss(0.0, 1.0) for i in range(1000)]}) 46 | m = arnet.ARNet(ar_order=3, n_epoch=3) 47 | m.fit(series=df) 48 | if self.plot: 49 | m.learn.recorder.plot_loss() 50 | m.plot_weights(save=True, savedir=results_path) 51 | m.plot_fitted_obs(num_obs=100, save=True, savedir=results_path) 52 | m.plot_errors(save=True, savedir=results_path) 53 | 54 | shutil.rmtree(results_path) 55 | 56 | def test_save_load(self): 57 | if not os.path.exists(results_path): 58 | os.makedirs(results_path) 59 | 60 | df = pd.DataFrame({"x": [random.gauss(0.0, 1.0) for i in range(1000)]}) 61 | m = arnet.ARNet(ar_order=3, n_epoch=3) 62 | m.fit(series=df) 63 | 64 | # Optional:save and create inference learner 65 | sparsity = 1.0 if m.sparsity is None else m.sparsity 66 | model_name = "ar{}_sparse_{:.3f}_ahead_{}_epoch_{}.pkl".format(m.ar_order, sparsity, m.n_forecasts, m.n_epoch) 67 | m.save_model(results_path=results_path, model_name=model_name) 68 | # can be loaded like this 69 | m.load_model(results_path, model_name) 70 | # can unfreeze the model and fine_tune 71 | log.info("loaded coeff: {}".format(m.coeff)) 72 | 73 | shutil.rmtree(results_path) 74 | 75 | def test_ar_data(self): 76 | df, data_config = arnet.load_from_file(data_path, AR_FILE, load_config=True, verbose=False) 77 | df = df[:1000] 78 | 79 | # Hyperparameters 80 | sparsity = 0.3 81 | ar_order = int(1 / sparsity * data_config["ar_order"]) # sparse AR: (for non-sparse, set sparsity to 1.0) 82 | ar_params = arnet.pad_ar_params([data_config["ar_params"]], ar_order, 1) # to compute stats 83 | 84 | # run 85 | m = arnet.ARNet( 86 | ar_order=ar_order, 87 | n_epoch=EPOCHS, 88 | sparsity=sparsity, 89 | ar_params=ar_params, 90 | ) 91 | m.fit(series=df) 92 | 93 | # Look at Coeff 94 | log.info("ar params: {}".format(arnet.nice_print_list(ar_params))) 95 | log.info("model weights: {}".format(arnet.nice_print_list(m.coeff))) 96 | -------------------------------------------------------------------------------- /v0_1/example.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from data_loader import load_data 3 | from training import run as run_training 4 | 5 | from v0_1_pure_pytorch import utils 6 | 7 | 8 | def load_config(verbose=False, random=True): 9 | # load specified settings 10 | 11 | #### Data settings #### 12 | data_config = { 13 | "type": 'AR', 14 | "ar_val": 3, 15 | "pad_to": 10, # set to >ar_val for sparse AR estimation 16 | "ar_params": None, # for randomly generated AR params 17 | "noise_std": 1.0, 18 | "test": 0.2, 19 | "n_samples": int(1.25e5), # for 1e5 train size 20 | } 21 | 22 | # OR manually define AR params: 23 | if not random: 24 | # data_config["ar_params"] = [0.2, 0.3, -0.5] 25 | # Alternative: sparse AR params: 26 | data_config["ar_params"] = [0.2, 0, 0.3, 0, 0, 0, 0, 0, 0, -0.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 27 | 28 | # correct settings if manually set 29 | data_config["ar_val"] = sum(np.array(data_config["ar_params"]) != 0.0) 30 | data_config["pad_to"] = int(len(data_config["ar_params"])) 31 | 32 | #### Model settings #### 33 | model_config = { 34 | "ar": data_config["ar_val"], # for normal AR 35 | "ma": 0, 36 | "num_layers": 1, 37 | "d_hidden": None 38 | } 39 | if data_config["pad_to"] is not None and data_config["pad_to"] > data_config["ar_val"]: 40 | model_config["ar"] = data_config["pad_to"] # for sparse AR 41 | 42 | #### Train settings #### 43 | train_config = { 44 | "lr": 2e-4, 45 | "lr_decay": 0.9, 46 | "epochs": 10, 47 | "batch": 128, 48 | "est_sparsity": 1, # 0 = fully sparse, 1 = not sparse 49 | "lambda_delay": 10, # delays start of regularization by lambda_delay epochs 50 | } 51 | # For auto-regularization based on sparsity estimation: 52 | if data_config["pad_to"] is not None and data_config["pad_to"] > data_config["ar_val"]: 53 | train_config["est_sparsity"] = data_config["ar_val"] / (1.0 * data_config["pad_to"]) 54 | 55 | # Note: find the right learning rate range with a learning rate range test 56 | # e.g. a LR range test on random AR data (with 5e5 data, batch 64, pad_to 100) led to 57 | # ---> min 5e-7, max 5e-4 58 | 59 | if verbose: 60 | print("data_config\n", data_config) 61 | print("model_config\n", model_config) 62 | print("train_config\n", train_config) 63 | 64 | return data_config, model_config, train_config 65 | 66 | 67 | def main(verbose=False, plot=False, save=False, random_ar_param=True): 68 | # load configuration dicts. Could be implemented to load from JSON instead. 69 | data_config, model_config, train_config = load_config(verbose, random_ar_param) 70 | # loads randomly generated data. Could be implemented to load a specific dataset instead. 71 | data = load_data(data_config, verbose, plot) 72 | # runs training and testing. 73 | results_dar, stats_dar = run_training(data, model_config, train_config, verbose) 74 | 75 | # optional printing 76 | if verbose: 77 | print(stats_dar) 78 | 79 | # optional plotting 80 | if plot: 81 | utils.plot_loss_curve( 82 | losses=results_dar["losses"], 83 | test_loss=results_dar["test_mse"], 84 | epoch_losses=results_dar["epoch_losses"], 85 | show=False, 86 | save=save 87 | ) 88 | utils.plot_weights( 89 | model_config["ar"], 90 | results_dar["weights"], 91 | data["ar"], 92 | model_name="AR-Net", 93 | save=save 94 | ) 95 | utils.plot_results( 96 | results_dar, 97 | model_name="AR-Net", 98 | save=save 99 | ) 100 | 101 | 102 | if __name__ == "__main__": 103 | main(verbose=True, plot=True, save=True, random_ar_param=False) 104 | -------------------------------------------------------------------------------- /arnet/fastai_mods.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import torch 3 | import torch.nn.functional as F 4 | import fastai 5 | from fastai.torch_core import flatten_check 6 | from fastai.metrics import mse, mae 7 | from fastai.basics import Callback 8 | from fastai.learner import Metric 9 | 10 | from arnet import utils 11 | 12 | log = logging.getLogger("ARNet.fastai_mods") 13 | 14 | 15 | def huber(inp, targ): 16 | """Huber error between `inp` and `targ`.""" 17 | return F.smooth_l1_loss(*flatten_check(inp, targ)) 18 | 19 | 20 | class SparsifyAR(Callback): 21 | """Callback that adds regularization of first linear layer according to AR-Net paper""" 22 | 23 | def __init__( 24 | self, 25 | est_sparsity, 26 | est_noise=1.0, 27 | reg_strength=0.01, 28 | start_pct=0.0, 29 | full_pct=0.5, 30 | c1=2.0, 31 | c2=2.0, 32 | **kwargs, 33 | ): 34 | super().__init__(**kwargs) 35 | self.lam_max = 0.0 36 | est_noise = 1.0 if est_noise is None else est_noise 37 | if est_sparsity is not None: 38 | assert 1 >= est_sparsity > 0 39 | self.lam_max = reg_strength * est_noise * ((1.0 + 1e-9) / (est_sparsity + 1e-9) - 1.0) 40 | self.start_pct = start_pct 41 | self.full_pct = full_pct 42 | # note: implementation in paper used c1 = 3.0, c2 = 3.0 43 | self.c1 = c1 44 | self.c2 = c2 45 | self.lam = None 46 | 47 | def after_loss(self): 48 | if not self.training: 49 | return 50 | if self.lam_max == 0.0 or self.lam == 0.0: 51 | return 52 | abs_weights = None 53 | for layer in self.learn.model.modules(): 54 | if isinstance(layer, torch.nn.Linear): 55 | abs_weights = torch.abs(layer.weight) 56 | break 57 | if abs_weights is None: 58 | raise NotImplementedError("weight regualarization only implemented for model with Linear layer") 59 | reg = torch.div(2.0, 1.0 + torch.exp(-self.c1 * abs_weights.pow(1.0 / self.c2))) - 1.0 60 | 61 | progress_iter = (1.0 + self.learn.iter) / (1.0 * self.learn.n_iter) 62 | progress = (progress_iter + self.learn.epoch) / (1.0 * self.learn.n_epoch) 63 | progress = (progress - self.start_pct) / (self.full_pct - self.start_pct) 64 | if progress <= 0: 65 | self.lam = 0.0 66 | elif progress < 1: 67 | self.lam = self.lam_max * progress ** 2 68 | else: 69 | self.lam = self.lam_max 70 | 71 | self.learn.loss += self.lam * torch.mean(reg) 72 | 73 | _docs = dict(after_loss="Add regularization of first linear layer") 74 | 75 | 76 | class sTPE(Metric): 77 | """ " 78 | Symmetrical Total Percentage Error of learned weights compared to underlying AR coefficients. 79 | Computed as the average over snapshots at each batch. 80 | """ 81 | 82 | def __init__(self, ar_params, at_epoch_end=False): 83 | self.ar_params = ar_params 84 | self.at_epoch_end = at_epoch_end 85 | self.total = 0.0 86 | self.count = 0 87 | self.sTPE = None 88 | 89 | def reset(self): 90 | self.total = 0.0 91 | self.count = 0 92 | self.sTPE = None 93 | 94 | def accumulate(self, learn): 95 | self.sTPE = fastai.torch_core.to_detach( 96 | utils.compute_sTPE( 97 | est=utils.coeff_from_model(model=learn.model, reversed_weights=True), 98 | real=self.ar_params, 99 | ) 100 | ) 101 | self.total += self.sTPE 102 | self.count += 1 103 | 104 | @property 105 | def value(self): 106 | if self.at_epoch_end: 107 | return self.sTPE 108 | return self.total / self.count if self.count != 0 else None 109 | 110 | @property 111 | def name(self): 112 | return "sTPE of AR coeff" 113 | 114 | 115 | def get_loss_func(loss_func): 116 | if type(loss_func) == str: 117 | if loss_func.lower() == "mse": 118 | loss_func = mse 119 | elif loss_func.lower() in ["huber", "smooth_l1", "smoothl1"]: 120 | loss_func = huber 121 | elif loss_func.lower() in ["mae", "l1"]: 122 | loss_func = mae 123 | else: 124 | log.error("loss {} not defined".format(loss_func)) 125 | loss_func = None 126 | return loss_func 127 | -------------------------------------------------------------------------------- /example_notebooks/create_ar_data.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import numpy as np\n", 10 | "from statsmodels.tsa.arima_process import ArmaProcess" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 2, 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "from arnet.create_ar_data import generate_armaprocess_data\n", 20 | "from arnet.create_ar_data import save_to_file, load_from_file" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": 3, 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [ 29 | "#### Notebook settings ####\n", 30 | "random_ar_params = False\n", 31 | "save = True\n", 32 | "save_path = '../ar_data'" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": 4, 38 | "metadata": {}, 39 | "outputs": [], 40 | "source": [ 41 | "#### Data settings ####\n", 42 | "# option 1: Randomly generated AR parameters\n", 43 | "data_config_random = {\n", 44 | " \"samples\": 10000,\n", 45 | " \"noise_std\": 0.1,\n", 46 | " \"ar_order\": 3,\n", 47 | " \"ma_order\": 0,\n", 48 | " \"params\": None, # for randomly generated AR params\n", 49 | "}\n", 50 | "\n", 51 | "# option 2: Manually define AR parameters\n", 52 | "data_config_manual = {\n", 53 | " \"samples\": 10000,\n", 54 | " \"noise_std\": 0.1,\n", 55 | " \"params\": ([0.2, 0.3, -0.5], []), \n", 56 | "# \"params\": ([0.2, 0, 0.3, 0, 0, 0, 0, 0, 0, -0.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], []), \n", 57 | "}\n", 58 | "data_config_manual[\"ar_order\"] = int(sum(np.array(data_config_manual[\"params\"][0]) != 0.0))\n", 59 | "data_config_manual[\"ma_order\"] = int(sum(np.array(data_config_manual[\"params\"][1]) != 0.0))" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": 5, 65 | "metadata": {}, 66 | "outputs": [ 67 | { 68 | "name": "stdout", 69 | "output_type": "stream", 70 | "text": [ 71 | "{'samples': 10000, 'noise_std': 0.1, 'params': ([0.2, 0.3, -0.5], []), 'ar_order': 3, 'ma_order': 0}\n" 72 | ] 73 | } 74 | ], 75 | "source": [ 76 | "## Select config\n", 77 | "data_config = data_config_random if random_ar_params else data_config_manual\n", 78 | "print(data_config)" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": 6, 84 | "metadata": {}, 85 | "outputs": [], 86 | "source": [ 87 | "## Generate data\n", 88 | "series, data_config[\"ar_params\"], data_config[\"ma_params\"] = generate_armaprocess_data(**data_config)\n", 89 | "del data_config[\"params\"]" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": 7, 95 | "metadata": {}, 96 | "outputs": [ 97 | { 98 | "name": "stdout", 99 | "output_type": "stream", 100 | "text": [ 101 | "loaded from saved files:\n", 102 | "{'samples': 10000, 'noise_std': 0.1, 'ar_order': 3, 'ma_order': 0, 'ar_params': [0.2, 0.3, -0.5], 'ma_params': []}\n", 103 | " 0\n", 104 | "0 0.114610\n", 105 | "1 0.027092\n", 106 | "2 -0.015263\n", 107 | "3 -0.125719\n", 108 | "4 -0.279195\n" 109 | ] 110 | } 111 | ], 112 | "source": [ 113 | "if save:\n", 114 | " data_name = save_to_file(save_path, series, data_config)\n", 115 | " \n", 116 | " # just to test:\n", 117 | " df, data_config2 = load_from_file(save_path, data_name, load_config=True)\n", 118 | " print(\"loaded from saved files:\")\n", 119 | " print(data_config2)\n", 120 | " print(df.head())" 121 | ] 122 | }, 123 | { 124 | "cell_type": "code", 125 | "execution_count": null, 126 | "metadata": {}, 127 | "outputs": [], 128 | "source": [] 129 | } 130 | ], 131 | "metadata": { 132 | "kernelspec": { 133 | "display_name": "Python 3", 134 | "language": "python", 135 | "name": "python3" 136 | }, 137 | "language_info": { 138 | "codemirror_mode": { 139 | "name": "ipython", 140 | "version": 3 141 | }, 142 | "file_extension": ".py", 143 | "mimetype": "text/x-python", 144 | "name": "python", 145 | "nbconvert_exporter": "python", 146 | "pygments_lexer": "ipython3", 147 | "version": "3.8.6" 148 | } 149 | }, 150 | "nbformat": 4, 151 | "nbformat_minor": 4 152 | } 153 | -------------------------------------------------------------------------------- /arnet/create_ar_data.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import numpy as np 4 | import pandas as pd 5 | from statsmodels.tsa.arima_process import ArmaProcess 6 | 7 | 8 | def _get_config(noise_std=0.1, n_samples=10000, random_ar_params=True): 9 | # option 1: Randomly generated AR parameters 10 | data_config_random = {"noise_std": noise_std, "ar_order": 3, "ma_order": 0, "params": None, "samples": n_samples} 11 | # option 2: Manually define AR parameters 12 | data_config_manual = {"noise_std": noise_std, "params": ([0.2, 0.3, -0.5], [])} 13 | data_config_manual["ar_order"] = int(sum(np.array(data_config_manual["params"][0]) != 0.0)) 14 | data_config_manual["ma_order"] = int(sum(np.array(data_config_manual["params"][1]) != 0.0)) 15 | data_config_manual["samples"] = n_samples # + int(data_config_manual["ar_order"]) 16 | return data_config_random if random_ar_params else data_config_manual 17 | 18 | 19 | def _generate_random_arparams(ar_order, ma_order, limit_abs_sum=True, maxiter=100): 20 | is_stationary = False 21 | iteration = 0 22 | while not is_stationary: 23 | iteration += 1 24 | # print("Iteration", iteration) 25 | if iteration > maxiter: 26 | raise RuntimeError("failed to find stationary coefficients") 27 | # Generate random parameters 28 | arparams = [] 29 | maparams = [] 30 | for i in range(ar_order): 31 | arparams.append(2 * np.random.random() - 1) 32 | for i in range(ma_order): 33 | maparams.append(2 * np.random.random() - 1) 34 | # print(arparams) 35 | arparams = np.array(arparams) 36 | maparams = np.array(maparams) 37 | if limit_abs_sum: 38 | ar_abssum = sum(np.abs(arparams)) 39 | ma_abssum = sum(np.abs(maparams)) 40 | if ar_abssum > 1: 41 | arparams = arparams / (ar_abssum + 10e-6) 42 | arparams = arparams * (0.5 + 0.5 * np.random.random()) 43 | if ma_abssum > 1: 44 | maparams = maparams / (ma_abssum + 10e-6) 45 | maparams = maparams * (0.5 + 0.5 * np.random.random()) 46 | 47 | arparams = arparams - np.mean(arparams) 48 | maparams = maparams - np.mean(maparams) 49 | arma_process = ArmaProcess.from_coeffs(arparams, maparams, nobs=100) 50 | is_stationary = arma_process.isstationary 51 | return arparams, maparams 52 | 53 | 54 | def generate_armaprocess_data(samples, ar_order, ma_order, noise_std, params=None): 55 | if params is not None: 56 | # use specified params (make sure to sum up to 1 or less) 57 | arparams, maparams = params 58 | else: 59 | # iterate to find random arparams that are stationary 60 | arparams, maparams = _generate_random_arparams(ar_order, ma_order) 61 | arma_process = ArmaProcess.from_coeffs(arparams, maparams, nobs=samples) 62 | # sample output from ARMA Process 63 | series = arma_process.generate_sample(samples, scale=noise_std) 64 | # make zero-mean: 65 | series = series - np.mean(series) 66 | return series, list(arparams), list(maparams) 67 | 68 | 69 | def save_to_file(save_path, series, data_config): 70 | if not os.path.exists(save_path): 71 | os.makedirs(save_path) 72 | file_data = "ar_{}_ma_{}_noise_{:.3f}_len_{}".format( 73 | data_config["ar_order"], data_config["ma_order"], data_config["noise_std"], data_config["samples"] 74 | ) 75 | # data_config["ar_params"] = list(data_config["ar_params"]) 76 | # data_config["ma_params"] = list(data_config["ma_params"]) 77 | np.savetxt(os.path.join(save_path, file_data + ".csv"), series, delimiter=",") 78 | with open(os.path.join(save_path, "info_" + file_data + ".json"), "w") as f: 79 | json.dump(data_config, f) 80 | return file_data 81 | 82 | 83 | def load_from_file(data_path, data_name, load_config=True, verbose=False): 84 | df = pd.read_csv(os.path.join(data_path, data_name + ".csv"), header=None, index_col=False) 85 | if load_config: 86 | with open(os.path.join(data_path, "info_" + data_name + ".json"), "r") as f: 87 | data_config = json.load(f) 88 | else: 89 | data_config = None 90 | if verbose: 91 | print("loaded series from file") 92 | print("data_config", data_config) 93 | print(df.shape) 94 | print(df.head()) 95 | return df, data_config 96 | 97 | 98 | def main(): 99 | verbose = True 100 | random = False 101 | save = True 102 | save_path = "ar_data" 103 | 104 | data_config = _get_config(random_ar_params=random) 105 | if verbose: 106 | print(data_config) 107 | 108 | series, data_config["ar_params"], data_config["ma_params"] = generate_armaprocess_data(**data_config) 109 | del data_config["params"] 110 | 111 | if save: 112 | data_name = save_to_file(save_path, series, data_config) 113 | 114 | # just to test: 115 | df, data_config2 = load_from_file(save_path, data_name, load_config=True) 116 | if verbose: 117 | print("loaded from saved files:") 118 | print(data_config2) 119 | print(df.head()) 120 | 121 | 122 | if __name__ == "__main__": 123 | main() 124 | -------------------------------------------------------------------------------- /tests/test_legacy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import unittest 4 | import os 5 | import pathlib 6 | import shutil 7 | import logging 8 | import warnings 9 | 10 | warnings.filterwarnings("ignore", message=".*nonzero.*", category=UserWarning) 11 | 12 | import fastai 13 | 14 | ## lazy imports ala fastai2 style (needed for nice print functionality) 15 | from fastai.basics import * 16 | from fastai.tabular.all import * 17 | 18 | import arnet 19 | from arnet.ar_net_legacy import init_ar_learner 20 | 21 | log = logging.getLogger("ARNet.test_legacy") 22 | log.setLevel("WARNING") 23 | log.parent.setLevel("WARNING") 24 | 25 | DIR = pathlib.Path(__file__).parent.parent.absolute() 26 | data_path = os.path.join(DIR, "ar_data") 27 | results_path = os.path.join(data_path, "results_test") 28 | EPOCHS = 2 29 | 30 | 31 | class LegacyTests(unittest.TestCase): 32 | verbose = False 33 | plot = False 34 | save = False 35 | 36 | def test_legacy_ar(self): 37 | self.save = True 38 | if self.save: 39 | if not os.path.exists(results_path): 40 | os.makedirs(results_path) 41 | # Hyperparameters 42 | n_epoch = 3 43 | valid_p = 0.2 44 | n_forecasts = 1 # Note: if more than one, must have a list of ar_param for each forecast target. 45 | sparsity = 0.3 # guesstimate 46 | data_name = "ar_3_ma_0_noise_0.100_len_10000" 47 | df, data_config = arnet.load_from_file(data_path, data_name, load_config=True, verbose=self.verbose) 48 | df = df[:1000] 49 | 50 | # sparse AR: (for non-sparse, set sparsity to 1.0) 51 | ar_order = int(1 / sparsity * data_config["ar_order"]) 52 | # to compute stats 53 | ar_params = arnet.pad_ar_params([data_config["ar_params"]], ar_order, n_forecasts) 54 | 55 | learn = init_ar_learner( 56 | series=df, 57 | ar_order=ar_order, 58 | n_forecasts=n_forecasts, 59 | valid_p=valid_p, 60 | sparsity=sparsity, 61 | ar_params=ar_params, 62 | verbose=self.verbose, 63 | ) 64 | 65 | lr_at_min, _ = learn.lr_find(start_lr=1e-6, end_lr=1e2, num_it=400) 66 | log.info("lr at minimum: {}".format(lr_at_min)) 67 | 68 | # Run Model 69 | # if you know the best learning rate: 70 | # learn.fit(n_epoch, 1e-2) 71 | # else use onecycle 72 | learn.fit_one_cycle(n_epoch=EPOCHS, lr_max=lr_at_min / 10) 73 | 74 | # Look at Coeff 75 | coeff = arnet.coeff_from_model(learn.model) 76 | log.info("ar params: {}".format(arnet.nice_print_list(ar_params))) 77 | log.info("model weights: {}".format(arnet.nice_print_list(coeff))) 78 | # should be [0.20, 0.30, -0.50, ...] 79 | 80 | preds, y = learn.get_preds() 81 | if self.plot or self.save: 82 | if self.plot: 83 | learn.recorder.plot_loss() 84 | arnet.plot_weights( 85 | ar_val=len(ar_params[0]), weights=coeff[0], ar=ar_params[0], save=not self.plot, savedir=results_path 86 | ) 87 | arnet.plot_prediction_sample(preds[:100], y[:100], save=not self.plot, savedir=results_path) 88 | arnet.plot_error_scatter(preds, y, save=not self.plot, savedir=results_path) 89 | 90 | if self.save: 91 | # Optional:save and create inference learner 92 | learn.freeze() 93 | model_name = "ar{}_sparse_{:.3f}_ahead_{}_epoch_{}.pkl".format(ar_order, sparsity, n_forecasts, n_epoch) 94 | learn.export(fname=os.path.join(results_path, model_name)) 95 | # can be loaded like this 96 | infer = load_learner(fname=os.path.join(results_path, model_name), cpu=True) 97 | # can unfreeze the model and fine_tune 98 | learn.unfreeze() 99 | learn.fit_one_cycle(1, lr_at_min / 100) 100 | 101 | coeff2 = arnet.coeff_from_model(learn.model) 102 | log.info("ar params: {}".format(arnet.nice_print_list(ar_params))) 103 | log.info("model weights: {}".format(arnet.nice_print_list(coeff))) 104 | log.info("model weights2: {}".format(arnet.nice_print_list(coeff2))) 105 | 106 | if self.plot or self.save: 107 | if self.plot: 108 | learn.recorder.plot_loss() 109 | arnet.plot_weights( 110 | ar_val=len(ar_params[0]), weights=coeff2[0], ar=ar_params[0], save=not self.plot, savedir=results_path 111 | ) 112 | if self.save: 113 | shutil.rmtree(results_path) 114 | 115 | def test_legacy_random(self): 116 | df = pd.DataFrame({"x": [random.gauss(0.0, 1.0) for i in range(1000)]}) 117 | learn = init_ar_learner( 118 | series=df, 119 | ar_order=3, 120 | n_forecasts=1, 121 | valid_p=0.1, 122 | sparsity=0.3, 123 | train_bs=32, 124 | valid_bs=1024, 125 | verbose=False, 126 | ) 127 | # find Learning Rate 128 | lr_at_min, lr_steep = learn.lr_find(start_lr=1e-6, end_lr=1, num_it=1000, show_plot=self.plot) 129 | if self.plot: 130 | plt.show() 131 | print("lr at minimum: {}; steeptes lr: {}".format(lr_at_min, lr_steep)) 132 | learn.fit_one_cycle(n_epoch=EPOCHS, lr_max=lr_at_min / 10) 133 | if self.plot: 134 | learn.recorder.plot_loss() 135 | plt.show() 136 | # record Coeff 137 | coeff = arnet.coeff_from_model(learn.model) 138 | -------------------------------------------------------------------------------- /v0_1/data_loader.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | from statsmodels.tsa.arima_process import ArmaProcess 4 | from torch.utils.data.dataset import Dataset 5 | import torch 6 | import copy 7 | 8 | 9 | def sample(y, offset, sample_inp_size, sample_out_size): 10 | Xin = np.arange(offset, offset + sample_inp_size) 11 | Xout = np.arange(sample_inp_size + offset, offset + sample_inp_size + sample_out_size) 12 | out = y[Xout] 13 | inp = y[Xin] 14 | return inp, out 15 | 16 | 17 | def create_dataset(series, n_samples=None, sample_inp_size=51, sample_out_size=1, test=None, verbose=False, plot=False): 18 | if n_samples is None: 19 | n_samples = len(series) 20 | data_inp = np.zeros((n_samples, sample_inp_size)) 21 | data_out = np.zeros((n_samples, sample_out_size)) 22 | 23 | for i in range(n_samples): 24 | sample_inp, sample_out = sample(series, i, sample_inp_size, sample_out_size) 25 | data_inp[i, :] = sample_inp 26 | data_out[i, :] = sample_out 27 | if test is not None: 28 | assert 0 < test < 1 29 | split = int(n_samples * (1 - test)) 30 | train_inp, train_out = data_inp[:split], data_out[:split] 31 | test_inp, test_out = data_inp[split:], data_out[split:] 32 | series_train = series[:split] 33 | series_test = series[split:] 34 | else: 35 | train_inp, train_out = data_inp, data_out 36 | test_inp, test_out = data_inp, data_out 37 | series_train = series 38 | series_test = series 39 | 40 | dataset_train = LocalDataset(x=train_inp, y=train_out) 41 | dataset_test = LocalDataset(x=test_inp, y=test_out) 42 | 43 | if verbose: 44 | print("Train set size: ", dataset_train.length) 45 | print("Test set size: ", dataset_test.length) 46 | 47 | if plot: 48 | # Plot generated process. 49 | plt.plot(np.array(series)[:200]) 50 | plt.show() 51 | return dataset_train, dataset_test, series_train, series_test 52 | 53 | 54 | class LocalDataset(Dataset): 55 | def __init__(self, x, y): 56 | x_dtype = torch.FloatTensor 57 | y_dtype = torch.FloatTensor # for MSE or L1 Loss 58 | 59 | self.length = x.shape[0] 60 | 61 | self.x_data = torch.from_numpy(x).type(x_dtype) 62 | self.y_data = torch.from_numpy(y).type(y_dtype) 63 | 64 | def __getitem__(self, index): 65 | return self.x_data[index], self.y_data[index] 66 | 67 | def __len__(self): 68 | return self.length 69 | 70 | 71 | def generate_armaprocess_data(samples, noise_std, random_order=None, params=None, limit_abs_sum=True): 72 | if params is not None: 73 | # use specified params, make sure to sum up to 1 or less 74 | arparams, maparams = params 75 | arma_process = ArmaProcess.from_coeffs(arparams, maparams, nobs=samples) 76 | else: 77 | is_stationary = False 78 | iteration = 0 79 | while not is_stationary: 80 | iteration += 1 81 | # print("Iteration", iteration) 82 | if iteration > 100: 83 | raise RuntimeError("failed to find stationary coefficients") 84 | # Generate random parameters 85 | arparams = [] 86 | maparams = [] 87 | ar_order, ma_order = random_order 88 | for i in range(ar_order): 89 | arparams.append(2 * np.random.random() - 1) 90 | for i in range(ma_order): 91 | maparams.append(2 * np.random.random() - 1) 92 | 93 | # print(arparams) 94 | arparams = np.array(arparams) 95 | maparams = np.array(maparams) 96 | if limit_abs_sum: 97 | ar_abssum = sum(np.abs(arparams)) 98 | ma_abssum = sum(np.abs(maparams)) 99 | if ar_abssum > 1: 100 | arparams = arparams / (ar_abssum + 10e-6) 101 | arparams = arparams * (0.5 + 0.5*np.random.random()) 102 | if ma_abssum > 1: 103 | maparams = maparams / (ma_abssum + 10e-6) 104 | maparams = maparams * (0.5 + 0.5*np.random.random()) 105 | 106 | arparams = arparams - np.mean(arparams) 107 | maparams = maparams - np.mean(maparams) 108 | arma_process = ArmaProcess.from_coeffs(arparams, maparams, nobs=samples) 109 | is_stationary = arma_process.isstationary 110 | 111 | # sample output from ARMA Process 112 | series = arma_process.generate_sample(samples, scale=noise_std) 113 | # make zero-mean: 114 | series = series - np.mean(series) 115 | return series, arparams, maparams 116 | 117 | 118 | def init_ar_dataset(n_samples, ar_val, ar_params=None, noise_std=1.0, plot=False, verbose=False, test=None, pad_to=None): 119 | # AR-Process 120 | if ar_params is not None: 121 | ar_val = len(ar_params) 122 | params = (ar_params, []) 123 | else: 124 | params = None 125 | 126 | if pad_to is None: 127 | inp_size = ar_val 128 | else: 129 | inp_size = pad_to 130 | 131 | series, ar, ma = generate_armaprocess_data( 132 | samples=n_samples+inp_size, 133 | noise_std=noise_std, 134 | random_order=(ar_val, 0), 135 | params=params, 136 | ) 137 | # print("series mean", np.mean(series)) 138 | 139 | if pad_to is not None: 140 | ar_pad = [0.0] * max(0, pad_to - ar_val) 141 | ar = list(ar) + ar_pad 142 | 143 | if verbose: 144 | print("AR params: ") 145 | print(ar) 146 | 147 | # Initialize data for DAR 148 | dataset_train, dataset_test, series_train, series_test = create_dataset( 149 | series=series, 150 | n_samples=n_samples, 151 | sample_inp_size=inp_size, 152 | sample_out_size=1, 153 | verbose=verbose, 154 | plot=plot, 155 | test=test, 156 | ) 157 | 158 | return dataset_train, dataset_test, series_train, series_test, ar 159 | 160 | 161 | def load_data(data_config_in, verbose=False, plot=False): 162 | data_config = copy.deepcopy(data_config_in) 163 | data_type = data_config.pop("type") 164 | data = { 165 | "type": data_type 166 | } 167 | if data_type == 'AR': 168 | data["train"], data["test"], data["series_train"], data["series_test"], data["ar"] = init_ar_dataset( 169 | **data_config, 170 | verbose=verbose, 171 | plot=plot, 172 | ) 173 | data["pad_to"] = data_config_in["pad_to"] 174 | else: 175 | raise NotImplementedError 176 | return data 177 | -------------------------------------------------------------------------------- /v0_1/utils.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import matplotlib.pyplot as plt 3 | import numpy as np 4 | import seaborn as sns 5 | import os 6 | 7 | 8 | def compute_stats_ar(results, ar_params, verbose=False): 9 | weights = results["weights"] 10 | error = results["predicted"] - results["actual"] 11 | stats = {} 12 | 13 | abs_error = np.abs(weights - ar_params) 14 | 15 | symmetric_abs_coeff = np.abs(weights) + np.abs(ar_params) 16 | stats["sMAPE (AR-coefficients)"] = 100 * np.mean(abs_error / (10e-9 + symmetric_abs_coeff)) 17 | 18 | sTPE = 100 * np.sum(abs_error) / (10e-9 + np.sum(symmetric_abs_coeff)) 19 | stats["sTPE (AR-coefficients)"] = sTPE 20 | 21 | # predictions error 22 | stats["MSE"] = np.mean(error ** 2) 23 | 24 | if verbose: 25 | print("MSE: {}".format(stats["MSE"])) 26 | print("sMAPE (AR-coefficients): {:6.3f}".format(stats["sMAPE (AR-coefficients)"])) 27 | print("sTPE (AR-coefficients): {:6.3f}".format(stats["sTPE (AR-coefficients)"])) 28 | # print("Relative error: {:6.3f}".format(stats["TP (AR-coefficients)"])) 29 | # print("Mean relative error: {:6.3f}".format(mean_rel_error)) 30 | 31 | print("AR params: ") 32 | print(ar_params) 33 | 34 | print("Weights: ") 35 | print(weights) 36 | return stats 37 | 38 | 39 | def plot_loss_curve(losses, test_loss=None, epoch_losses=None, show=False, save=False): 40 | fig = plt.figure() 41 | fig.set_size_inches(12, 6) 42 | ax = plt.axes() 43 | ax.set_xlabel("Iteration") 44 | ax.set_ylabel("Loss") 45 | x_loss = list(range(len(losses))) 46 | plt.plot(x_loss, losses, 'b', alpha=0.3) 47 | if epoch_losses is not None: 48 | iter_per_epoch = int(len(losses) / len(epoch_losses)) 49 | epoch_ends = int(iter_per_epoch/2) + iter_per_epoch*np.arange(len(epoch_losses)) 50 | plt.plot(epoch_ends, epoch_losses, 'b') 51 | if test_loss is not None: 52 | plt.hlines(test_loss, xmin=x_loss[0], xmax=x_loss[-1]) 53 | if save: 54 | if not os.path.exists('results'): 55 | os.makedirs('results') 56 | figname = 'results/loss_DAR.png' 57 | plt.savefig(figname, dpi=600, bbox_inches='tight') 58 | plt.show() 59 | # plt.close() 60 | 61 | 62 | def plot_prediction_sample(predicted, actual, num_obs=100, model_name="AR-Net", save=False): 63 | fig2 = plt.figure() 64 | fig2.set_size_inches(10, 6) 65 | plt.plot(actual[0:num_obs]) 66 | plt.plot(predicted[0:num_obs]) 67 | plt.legend(["Actual Time-Series", "{}-Prediction".format(model_name)]) 68 | if save: 69 | if not os.path.exists('results'): 70 | os.makedirs('results') 71 | figname = 'results/prediction_{}.png'.format(model_name) 72 | plt.savefig(figname, dpi=600, bbox_inches='tight') 73 | plt.show() 74 | 75 | 76 | def plot_error_scatter(predicted, actual, model_name="AR-Net", save=False): 77 | # error = predicted - actual 78 | fig3 = plt.figure() 79 | fig3.set_size_inches(6, 6) 80 | plt.scatter(actual, predicted - actual, marker='o', s=10, alpha=0.3) 81 | plt.legend(["{}-Error".format(model_name)]) 82 | if save: 83 | if not os.path.exists('results'): 84 | os.makedirs('results') 85 | figname = 'results/scatter_{}.png'.format(model_name) 86 | plt.savefig(figname, dpi=600, bbox_inches='tight') 87 | plt.show() 88 | 89 | 90 | def plot_weights(ar_val, weights, ar, model_name="AR-Net", save=False): 91 | df = pd.DataFrame( 92 | zip( 93 | list(range(1, ar_val + 1)) * 2, 94 | ["AR-Process (True)"] * ar_val + [model_name] * ar_val, 95 | list(ar) + list(weights) 96 | ), 97 | columns=["AR-coefficient (lag number)", "model", "value (weight)"] 98 | ) 99 | plt.figure(figsize=(10, 6)) 100 | palette = {"Classic-AR": "C0", "AR-Net": "C1", "AR-Process (True)": "k"} 101 | sns.barplot(x="AR-coefficient (lag number)", hue="model", y="value (weight)", data=df) 102 | if save: 103 | if not os.path.exists('results'): 104 | os.makedirs('results') 105 | figname = 'results/weights_{}_{}.png'.format(ar_val, model_name, palette=palette) 106 | plt.savefig(figname, dpi=600, bbox_inches='tight') 107 | 108 | plt.show() 109 | 110 | 111 | def plot_results(results, model_name="MODEL", save=False): 112 | plot_prediction_sample(results["predicted"], results["actual"], num_obs=100, model_name=model_name, save=save) 113 | plot_error_scatter(results["predicted"], results["actual"], model_name=model_name, save=save) 114 | 115 | 116 | def jsonize(results): 117 | for key, value in results.items(): 118 | if type(value) is list: 119 | if type(value[0]) is list: 120 | results[key] = [["{:8.5f}".format(xy) for xy in x] for x in value] 121 | else: 122 | results[key] = ["{:8.5f}".format(x) for x in value] 123 | else: 124 | results[key] = "{:8.5f}".format(value) 125 | return results 126 | 127 | 128 | def list_of_dicts_2_dict_of_lists(sources): 129 | keys = sources[0].keys() 130 | res = {} 131 | for key in keys: 132 | res[key] = [d[key] for d in sources] 133 | return res 134 | 135 | 136 | def list_of_dicts_2_dict_of_means(sources): 137 | keys = sources[0].keys() 138 | res = {} 139 | for key in keys: 140 | res[key] = np.mean([d[key] for d in sources]) 141 | return res 142 | 143 | 144 | def list_of_dicts_2_dict_of_means_minmax(sources): 145 | keys = sources[0].keys() 146 | res = {} 147 | for key in keys: 148 | values = [d[key] for d in sources] 149 | res[key] = (np.mean(values), min(values), max(values)) 150 | return res 151 | 152 | 153 | def get_json_filenames(values, subdir=None): 154 | ar_filename = get_json_filenames_type("AR", values, subdir) 155 | dar_filename = get_json_filenames_type("DAR", values, subdir) 156 | return ar_filename, dar_filename 157 | 158 | 159 | def get_json_filenames_type(model_type, values, subdir=None): 160 | filename = 'results/{}{}_{}.json'.format( 161 | subdir + "/" if subdir is not None else "", 162 | model_type, 163 | "-".join([str(x) for x in values]) 164 | ) 165 | return filename 166 | 167 | 168 | def intelligent_regularization(sparsity): 169 | if sparsity is not None: 170 | # best: 171 | # lam = 0.01 * (1.0 / sparsity - 1.0) 172 | lam = 0.02 * (1.0 / sparsity - 1.0) 173 | # lam = 0.05 * (1.0 / sparsity - 1.0) 174 | 175 | # alternatives 176 | # l1 = 0.02 * (np.log(2) / np.log(1 + sparsity) - 1.0) 177 | # l1 = 0.1 * (1.0 / np.sqrt(sparsity) - 1.0) 178 | else: 179 | lam = 0.0 180 | return lam 181 | 182 | 183 | -------------------------------------------------------------------------------- /v0_1/training.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import numpy as np 4 | import torch 5 | import torch.nn as nn 6 | from model import DAR 7 | from torch import optim 8 | from torch.utils.data import DataLoader 9 | 10 | from v0_1_pure_pytorch import utils 11 | 12 | 13 | # Architecture, batching etc of DARMA 14 | def train_batch(model, x, y, optimizer, loss_fn, lambda_value=None): 15 | # Run forward calculation 16 | y_predict = model.forward(x) 17 | 18 | # Compute loss. 19 | loss = loss_fn(y_predict, y) 20 | 21 | # regularize 22 | if lambda_value is not None: 23 | reg_loss = torch.zeros(1, dtype=torch.float, requires_grad=True) 24 | if model.num_layers == 1: 25 | abs_weights = torch.abs(model.layer_1.weight) 26 | # classic L1 27 | # reg = torch.mean(abs_weights) 28 | 29 | # sqrt - helps to protect some weights and bring others to zero, 30 | # but is still tough on larger weights 31 | # reg = torch.mean(torch.sqrt(abs_weights)) 32 | 33 | # new, less hard on larger weights: (protects ~0.1-1.0) 34 | # reg = torch.div(2.0, 1.0 + torch.exp(-5.0*abs_weights.pow(0.4))) - 1.0 35 | 36 | # mid-way, more stable 37 | reg = torch.div(2.0, 1.0 + torch.exp(-3.0*abs_weights.pow(1.0/3.0))) - 1.0 38 | 39 | reg_loss = reg_loss + torch.mean(reg) 40 | else: 41 | # for weights in model.parameters(): 42 | raise NotImplementedError("L1 Norm for deeper models not implemented") 43 | 44 | loss = loss + lambda_value * reg_loss 45 | 46 | optimizer.zero_grad() 47 | 48 | loss.backward(retain_graph=True) 49 | 50 | optimizer.step() 51 | 52 | return loss.data.item() 53 | 54 | 55 | def train(model, loader, loss_fn, lr, epochs, lr_decay, est_sparsity, lambda_delay=None, verbose=False): 56 | 57 | # Initialize the optimizer with above parameters 58 | optimizer = optim.Adam(model.parameters(), lr=lr) 59 | scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=1, gamma=lr_decay) 60 | 61 | losses = list() 62 | batch_index = 0 63 | epoch_losses = [] 64 | avg_losses = [] 65 | lambda_value = utils.intelligent_regularization(est_sparsity) 66 | 67 | for e in range(epochs): 68 | # slowly increase regularization until lambda_delay epoch 69 | if lambda_delay is not None and e < lambda_delay: 70 | l_factor = e / (1.0 * lambda_delay) 71 | # l_factor = (e / (1.0 * lambda_delay))**2 72 | else: 73 | l_factor = 1.0 74 | 75 | for x, y in loader: 76 | loss = train_batch(model=model, x=x, y=y, optimizer=optimizer, 77 | loss_fn=loss_fn, lambda_value=l_factor*lambda_value) 78 | epoch_losses.append(loss) 79 | batch_index += 1 80 | scheduler.step() 81 | losses.extend(epoch_losses) 82 | avg_loss = np.mean(epoch_losses) 83 | avg_losses.append(avg_loss) 84 | epoch_losses = [] 85 | if verbose: 86 | print("{}. Epoch Avg Loss: {:10.2f}".format(e + 1, avg_loss)) 87 | if verbose: 88 | print("Total Batches: ", batch_index) 89 | 90 | return losses, avg_losses 91 | 92 | 93 | def test_batch(model, x, y, loss_fn): 94 | # run forward calculation 95 | y_predict = model.forward(x) 96 | loss = loss_fn(y_predict, y) 97 | 98 | return y_predict, loss 99 | 100 | 101 | def test(model, loader, loss_fn): 102 | losses = list() 103 | y_vectors = list() 104 | y_predict_vectors = list() 105 | 106 | batch_index = 0 107 | for x, y in loader: 108 | y_predict, loss = test_batch(model=model, x=x, y=y, loss_fn=loss_fn) 109 | 110 | losses.append(loss.data.numpy()) 111 | y_vectors.append(y.data.numpy()) 112 | y_predict_vectors.append(y_predict.data.numpy()) 113 | 114 | batch_index += 1 115 | 116 | losses = np.array(losses) 117 | y_predict_vector = np.concatenate(y_predict_vectors) 118 | mse = np.mean((y_predict_vector - np.concatenate(y_vectors)) ** 2) 119 | 120 | return y_predict_vector, losses, mse 121 | 122 | 123 | def run_train_test(dataset_train, dataset_test, model_config, train_config, verbose=False): 124 | data_loader_train = DataLoader(dataset=dataset_train, batch_size=train_config["batch"], shuffle=True) 125 | data_loader_test = DataLoader(dataset=dataset_test, batch_size=len(dataset_test), shuffle=False) 126 | 127 | if model_config["ma"] > 0: 128 | # TODO: implement DARMA 129 | raise NotImplementedError 130 | else: 131 | del model_config["ma"] 132 | model = DAR( 133 | **model_config 134 | ) 135 | 136 | # Define the loss function 137 | loss_fn = nn.MSELoss() # mean squared error 138 | 139 | # Train and get the resulting loss per iteration 140 | del train_config["batch"] 141 | losses, avg_losses = train( 142 | model=model, 143 | loader=data_loader_train, 144 | loss_fn=loss_fn, 145 | **train_config, 146 | verbose=verbose, 147 | ) 148 | 149 | # Test and get the resulting predicted y values 150 | y_predict, test_losses, test_mse = test(model=model, loader=data_loader_test, loss_fn=loss_fn) 151 | 152 | actual = np.concatenate(np.array(dataset_test.y_data)) 153 | predicted = np.concatenate(y_predict) 154 | # weights_rereversed = np.array(model.layer_1.data)[0, ::-1] 155 | weights_rereversed = model.layer_1.weight.detach().numpy()[0, ::-1] 156 | 157 | return predicted, actual, np.array(losses), weights_rereversed, test_mse, avg_losses 158 | 159 | 160 | def run(data, model_config, train_config, verbose=False): 161 | if verbose: 162 | print("################ Model: AR-Net ################") 163 | start = time.time() 164 | predicted, actual, losses, weights, test_mse, epoch_losses = run_train_test( 165 | dataset_train=data["train"], 166 | dataset_test=data["test"], 167 | model_config=model_config, 168 | train_config=train_config, 169 | verbose=verbose, 170 | ) 171 | end = time.time() 172 | duration = end - start 173 | 174 | if verbose: 175 | print("Time: {:8.4f}".format(duration)) 176 | print("Final train epoch loss: {:10.2f}".format(epoch_losses[-1])) 177 | print("Test MSEs: {:10.2f}".format(test_mse)) 178 | 179 | results = {} 180 | results["weights"] = weights 181 | results["predicted"] = predicted 182 | results["actual"] = actual 183 | results["test_mse"] = test_mse 184 | results["losses"] = losses 185 | results["epoch_losses"] = epoch_losses 186 | if data["type"] == 'AR': 187 | stats = utils.compute_stats_ar(results, ar_params=data["ar"], verbose=verbose) 188 | else: 189 | raise NotImplementedError 190 | stats["Time (s)"] = duration 191 | 192 | return results, stats 193 | 194 | 195 | def main(): 196 | print("deprecated") 197 | 198 | 199 | if __name__ == "__main__": 200 | main() 201 | -------------------------------------------------------------------------------- /example_notebooks/make_dataset.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import os\n", 10 | "import json\n", 11 | "import pandas as pd\n", 12 | "import numpy as np" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": 2, 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "from arnet.create_ar_data import load_from_file\n", 22 | "from arnet.make_dataset import tabularize_univariate" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": 3, 28 | "metadata": {}, 29 | "outputs": [], 30 | "source": [ 31 | "## Notebook settings\n", 32 | "data_path = '../ar_data'\n", 33 | "data_name = 'ar_3_ma_0_noise_0.100_len_10000'" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 4, 39 | "metadata": {}, 40 | "outputs": [ 41 | { 42 | "name": "stdout", 43 | "output_type": "stream", 44 | "text": [ 45 | "{'samples': 10000, 'noise_std': 0.1, 'ar_order': 3, 'ma_order': 0, 'ar_params': [0.2, 0.3, -0.5], 'ma_params': []}\n" 46 | ] 47 | } 48 | ], 49 | "source": [ 50 | "## Load data\n", 51 | "## if created AR data with create_ar_data, we can use the helper function:\n", 52 | "df, data_config = load_from_file(data_path, data_name, load_config=True)\n", 53 | "print(data_config)\n", 54 | "n_lags = data_config[\"ar_order\"]" 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": 5, 60 | "metadata": {}, 61 | "outputs": [], 62 | "source": [ 63 | "## Load data\n", 64 | "## else we can manually load any file that stores a time series, for example:\n", 65 | "# df = pd.read_csv(os.path.join(data_path, data_name + '.csv'), header=None, index_col=False)\n", 66 | "# n_lags = 3" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": 6, 72 | "metadata": {}, 73 | "outputs": [ 74 | { 75 | "name": "stdout", 76 | "output_type": "stream", 77 | "text": [ 78 | "(10000, 1)\n" 79 | ] 80 | }, 81 | { 82 | "data": { 83 | "text/html": [ 84 | "
\n", 85 | "\n", 98 | "\n", 99 | " \n", 100 | " \n", 101 | " \n", 102 | " \n", 103 | " \n", 104 | " \n", 105 | " \n", 106 | " \n", 107 | " \n", 108 | " \n", 109 | " \n", 110 | " \n", 111 | " \n", 112 | " \n", 113 | " \n", 114 | " \n", 115 | " \n", 116 | " \n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | "
0
00.114610
10.027092
2-0.015263
3-0.125719
4-0.279195
\n", 128 | "
" 129 | ], 130 | "text/plain": [ 131 | " 0\n", 132 | "0 0.114610\n", 133 | "1 0.027092\n", 134 | "2 -0.015263\n", 135 | "3 -0.125719\n", 136 | "4 -0.279195" 137 | ] 138 | }, 139 | "execution_count": 6, 140 | "metadata": {}, 141 | "output_type": "execute_result" 142 | } 143 | ], 144 | "source": [ 145 | "print(df.shape)\n", 146 | "df.head()" 147 | ] 148 | }, 149 | { 150 | "cell_type": "code", 151 | "execution_count": 7, 152 | "metadata": {}, 153 | "outputs": [], 154 | "source": [ 155 | "## create a tabularized dataset from time series\n", 156 | "df_tab = tabularize_univariate(df, n_lags, n_forecasts=1)" 157 | ] 158 | }, 159 | { 160 | "cell_type": "code", 161 | "execution_count": 8, 162 | "metadata": {}, 163 | "outputs": [ 164 | { 165 | "name": "stdout", 166 | "output_type": "stream", 167 | "text": [ 168 | "(9997, 4)\n" 169 | ] 170 | }, 171 | { 172 | "data": { 173 | "text/html": [ 174 | "
\n", 175 | "\n", 188 | "\n", 189 | " \n", 190 | " \n", 191 | " \n", 192 | " \n", 193 | " \n", 194 | " \n", 195 | " \n", 196 | " \n", 197 | " \n", 198 | " \n", 199 | " \n", 200 | " \n", 201 | " \n", 202 | " \n", 203 | " \n", 204 | " \n", 205 | " \n", 206 | " \n", 207 | " \n", 208 | " \n", 209 | " \n", 210 | " \n", 211 | " \n", 212 | " \n", 213 | " \n", 214 | " \n", 215 | " \n", 216 | " \n", 217 | " \n", 218 | " \n", 219 | " \n", 220 | " \n", 221 | " \n", 222 | " \n", 223 | " \n", 224 | " \n", 225 | " \n", 226 | " \n", 227 | " \n", 228 | " \n", 229 | " \n", 230 | " \n", 231 | " \n", 232 | " \n", 233 | " \n", 234 | " \n", 235 | "
x_0x_1x_2y_0
00.1146100.027092-0.015263-0.125719
10.027092-0.015263-0.125719-0.279195
2-0.015263-0.125719-0.279195-0.133535
3-0.125719-0.279195-0.133535-0.254239
4-0.279195-0.133535-0.2542390.122992
\n", 236 | "
" 237 | ], 238 | "text/plain": [ 239 | " x_0 x_1 x_2 y_0\n", 240 | "0 0.114610 0.027092 -0.015263 -0.125719\n", 241 | "1 0.027092 -0.015263 -0.125719 -0.279195\n", 242 | "2 -0.015263 -0.125719 -0.279195 -0.133535\n", 243 | "3 -0.125719 -0.279195 -0.133535 -0.254239\n", 244 | "4 -0.279195 -0.133535 -0.254239 0.122992" 245 | ] 246 | }, 247 | "execution_count": 8, 248 | "metadata": {}, 249 | "output_type": "execute_result" 250 | } 251 | ], 252 | "source": [ 253 | "print(df_tab.shape)\n", 254 | "df_tab.head()" 255 | ] 256 | }, 257 | { 258 | "cell_type": "code", 259 | "execution_count": null, 260 | "metadata": {}, 261 | "outputs": [], 262 | "source": [] 263 | } 264 | ], 265 | "metadata": { 266 | "kernelspec": { 267 | "display_name": "Python 3", 268 | "language": "python", 269 | "name": "python3" 270 | }, 271 | "language_info": { 272 | "codemirror_mode": { 273 | "name": "ipython", 274 | "version": 3 275 | }, 276 | "file_extension": ".py", 277 | "mimetype": "text/x-python", 278 | "name": "python", 279 | "nbconvert_exporter": "python", 280 | "pygments_lexer": "ipython3", 281 | "version": "3.8.6" 282 | } 283 | }, 284 | "nbformat": 4, 285 | "nbformat_minor": 4 286 | } 287 | -------------------------------------------------------------------------------- /arnet/ar_net.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | import logging 3 | import os 4 | import pandas as pd 5 | import matplotlib.pyplot as plt 6 | 7 | import torch 8 | from fastai.data.core import DataLoaders 9 | from fastai.tabular.core import TabularPandas, TabDataLoader 10 | from fastai.tabular.learner import tabular_learner, TabularLearner 11 | from fastai.data.transforms import Normalize 12 | from fastai.learner import load_learner 13 | from fastai.distributed import ParallelTrainer 14 | 15 | from arnet import utils, utils_data, plotting 16 | from arnet.fastai_mods import SparsifyAR, huber, sTPE, get_loss_func 17 | 18 | log = logging.getLogger("ARNet") 19 | 20 | 21 | @dataclass 22 | class ARNet: 23 | ar_order: int 24 | sparsity: float = None 25 | n_forecasts: int = 1 26 | n_epoch: int = 20 27 | lr: float = None 28 | est_noise: float = None 29 | start_reg_pct: float = 0.0 30 | full_reg_pct: float = 0.5 31 | use_reg_noise: bool = False 32 | reg_c1: float = 2.0 33 | reg_c2: float = 2.0 34 | loss_func: str = "huber" 35 | train_bs: int = 32 36 | valid_bs: int = 1024 37 | valid_p: float = 0.1 38 | normalize: bool = False 39 | ar_params: list = None 40 | log_level: str = None 41 | callbacks: list = None 42 | metrics: list = None 43 | use_gpu: bool = False 44 | dls: DataLoaders = field(init=False, default=None) 45 | learn: TabularLearner = field(init=False, default=None) 46 | coeff: list = field(init=False, default=None) 47 | df: pd.DataFrame = field(init=False, default=None) 48 | regularizer: SparsifyAR = field(init=False, default=None) 49 | 50 | def __post_init__(self): 51 | if self.log_level is not None: 52 | utils.set_logger_level(log, self.log_level) 53 | self.loss_func = get_loss_func(self.loss_func) 54 | if self.use_gpu: 55 | if torch.cuda.is_available(): 56 | self.device = torch.device("cuda") 57 | # torch.cuda.set_device(0) 58 | else: 59 | log.error("CUDA is not available. defaulting to CPU") 60 | self.device = torch.device("cpu") 61 | else: 62 | self.device = torch.device("cpu") 63 | 64 | def tabularize(self, series): 65 | if self.est_noise is None: 66 | self.est_noise = utils_data.estimate_noise(series) 67 | log.info("estimated noise of series: {}".format(self.est_noise)) 68 | df_all = utils_data.tabularize_univariate(series, self.ar_order, self.n_forecasts) 69 | log.debug("tabularized df") 70 | log.debug("df columns: {}".format(list(df_all.columns))) 71 | log.debug("df shape: {}".format(df_all.shape)) 72 | # log.debug("df head(3): {}".format(df_all.head(3))) 73 | self.df = df_all 74 | return self 75 | 76 | def make_datasets( 77 | self, 78 | series=None, 79 | valid_p=None, 80 | train_bs=None, 81 | valid_bs=None, 82 | normalize=None, 83 | ): 84 | if series is None: 85 | if self.df is None: 86 | raise ValueError("must pass a series.") 87 | else: 88 | self.tabularize(series) 89 | valid_p = self.valid_p if valid_p is None else valid_p 90 | train_bs = self.train_bs if train_bs is None else train_bs 91 | valid_bs = self.valid_bs if valid_bs is None else valid_bs 92 | normalize = self.normalize if normalize is None else normalize 93 | 94 | procs = [] 95 | if normalize: 96 | procs.append(Normalize) 97 | 98 | df_all = self.df 99 | splits = utils_data.split_by_p_valid(valid_p, len(df_all)) 100 | cont_names = [col for col in list(df_all.columns) if "x_" == col[:2]] 101 | target_names = [col for col in list(df_all.columns) if "y_" == col[:2]] 102 | tp = TabularPandas( 103 | df_all, 104 | procs=procs, 105 | cat_names=None, 106 | cont_names=cont_names, 107 | y_names=target_names, 108 | splits=splits, 109 | ) 110 | log.debug("cont var num: {}, names: {}".format(len(tp.cont_names), tp.cont_names)) 111 | 112 | trn_dl = TabDataLoader(tp.train, bs=train_bs, shuffle=True, drop_last=True, device=self.device) 113 | val_dl = TabDataLoader(tp.valid, bs=valid_bs, device=self.device) 114 | self.dls = DataLoaders(trn_dl, val_dl, device=self.device) 115 | log.debug("showing batch") 116 | log.debug("{}".format(self.dls.show_batch(show=False))) 117 | return self 118 | 119 | def create_regularizer( 120 | self, 121 | sparsity=None, 122 | start_reg_pct=None, 123 | full_reg_pct=None, 124 | est_noise=None, 125 | use_reg_noise=None, 126 | reg_c1=None, 127 | reg_c2=None, 128 | ): 129 | sparsity = self.sparsity if sparsity is None else sparsity 130 | start_reg_pct = self.start_reg_pct if start_reg_pct is None else start_reg_pct 131 | full_reg_pct = self.full_reg_pct if full_reg_pct is None else full_reg_pct 132 | est_noise = self.est_noise if est_noise is None else est_noise 133 | use_reg_noise = self.use_reg_noise if use_reg_noise is None else use_reg_noise 134 | reg_c1 = self.reg_c1 if reg_c1 is None else reg_c1 135 | reg_c2 = self.reg_c2 if reg_c2 is None else reg_c2 136 | 137 | self.regularizer = SparsifyAR( 138 | sparsity, 139 | est_noise=est_noise if use_reg_noise else None, 140 | start_pct=start_reg_pct, 141 | full_pct=full_reg_pct, 142 | c1=reg_c1, 143 | c2=reg_c2, 144 | ) 145 | log.info("reg lam (max): {}".format(self.regularizer.lam_max)) 146 | return self 147 | 148 | def create_learner( 149 | self, 150 | loss_func=None, 151 | metrics=None, 152 | ar_params=None, 153 | callbacks=None, 154 | ): 155 | loss_func = self.loss_func if loss_func is None else get_loss_func(loss_func) 156 | metrics = self.metrics if metrics is None else metrics 157 | ar_params = self.ar_params if ar_params is None else ar_params 158 | callbacks = self.callbacks if callbacks is None else callbacks 159 | 160 | if metrics is None: 161 | metrics = ["MSE", "MAE"] 162 | metrics = [get_loss_func(m) for m in metrics] 163 | if ar_params is not None: 164 | metrics.append(sTPE(ar_params, at_epoch_end=False)) 165 | 166 | if callbacks is None: 167 | callbacks = [] 168 | if self.sparsity is not None and self.regularizer is None: 169 | self.create_regularizer() 170 | if self.regularizer is not None: 171 | callbacks.append(self.regularizer) 172 | 173 | self.learn = tabular_learner( 174 | self.dls, 175 | layers=[], # Note: None defaults to [200, 100] 176 | config={"use_bn": False, "bn_final": False, "bn_cont": False}, 177 | n_out=self.n_forecasts, # None calls get_c(dls) 178 | train_bn=False, # passed to Learner 179 | metrics=metrics, # passed to Learner 180 | loss_func=loss_func, 181 | cbs=callbacks, 182 | ) 183 | log.debug("{}".format(self.learn.model)) 184 | return self 185 | 186 | def find_lr(self, plot=True): 187 | if self.learn is None: 188 | raise ValueError("create learner first.") 189 | lr_at_min, lr_steep = self.learn.lr_find(start_lr=1e-6, end_lr=10, num_it=300, show_plot=plot) 190 | if plot: 191 | plt.show() 192 | log.debug("lr at minimum: {}; (steepest lr: {})".format(lr_at_min, lr_steep)) 193 | lr = lr_at_min 194 | log.info("Optimal learning rate: {}".format(lr)) 195 | self.lr = lr 196 | return self 197 | 198 | def fit_one_cycle(self, n_epoch=None, lr=None, cycles=1, plot=True): 199 | n_epoch = self.n_epoch if n_epoch is None else n_epoch 200 | lr = self.lr if lr is None else lr 201 | 202 | if lr is None: 203 | self.find_lr(plot=plot) 204 | lr = self.lr 205 | for i in range(0, cycles): 206 | self.learn.fit_one_cycle(n_epoch=n_epoch, lr_max=lr, div=25.0, div_final=10000.0, pct_start=0.25) 207 | lr = lr / 10 208 | if plot: 209 | self.learn.recorder.plot_loss(skip_start=20) 210 | if plot: 211 | plt.show() 212 | # record Coeff 213 | self.coeff = utils.coeff_from_model(self.learn.model) 214 | return self 215 | 216 | def fit(self, series, plot=False): 217 | self.make_datasets(series) 218 | self.create_learner() 219 | self.fit_one_cycle(plot=plot) 220 | return self 221 | 222 | def plot_weights(self, **kwargs): 223 | plotting.plot_weights( 224 | ar_val=self.ar_order, 225 | weights=self.coeff[0], 226 | ar=self.ar_params, 227 | **kwargs, 228 | ) 229 | 230 | def plot_fitted_obs(self, num_obs=100, **kwargs): 231 | preds, y = self.learn.get_preds() 232 | if num_obs is not None: 233 | y = y[0:num_obs] 234 | preds = preds[0:num_obs] 235 | plotting.plot_prediction_sample(preds, y, **kwargs) 236 | 237 | def plot_errors(self, **kwargs): 238 | preds, y = self.learn.get_preds() 239 | plotting.plot_error_scatter(preds, y, **kwargs) 240 | 241 | def save_model(self, results_path="results", model_name=None): 242 | # self.learn.freeze() 243 | sparsity = 1.0 if self.sparsity is None else self.sparsity 244 | if model_name is None: 245 | model_name = "ar{}_sparse_{:.3f}_ahead_{}_epoch_{}.pkl".format( 246 | self.ar_order, sparsity, self.n_forecasts, self.n_epoch 247 | ) 248 | self.learn.export(fname=os.path.join(results_path, model_name)) 249 | return self 250 | 251 | def load_model(self, results_path="results", model_name=None, cpu=True): 252 | self.learn = load_learner(fname=os.path.join(results_path, model_name), cpu=cpu) 253 | # can unfreeze the model and fine_tune 254 | self.learn.unfreeze() 255 | return self 256 | -------------------------------------------------------------------------------- /example_notebooks/01_fit_arnet.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import numpy as np\n", 10 | "import pandas as pd\n", 11 | "import os\n", 12 | "import torch\n", 13 | "import arnet\n", 14 | "from arnet import ARNet\n", 15 | "import matplotlib.pyplot as plt\n", 16 | "%matplotlib inline" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": 2, 22 | "metadata": {}, 23 | "outputs": [], 24 | "source": [ 25 | "DIR = os.path.dirname(os.path.abspath(''))\n", 26 | "data_path = os.path.join(DIR, 'ar_data')\n", 27 | "name = 'ar_3_ma_0_noise_0.100_len_10000'\n", 28 | "df = pd.read_csv(os.path.join(data_path, name + '.csv'), header=None, index_col=False)" 29 | ] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": 3, 34 | "metadata": {}, 35 | "outputs": [ 36 | { 37 | "name": "stderr", 38 | "output_type": "stream", 39 | "text": [ 40 | "INFO: ARNet - tabularize: estimated noise of series: 0.12853080551811422\n", 41 | "INFO: ARNet - create_regularizer: reg lam (max): 0.009999999980000004\n" 42 | ] 43 | }, 44 | { 45 | "data": { 46 | "text/html": [], 47 | "text/plain": [ 48 | "" 49 | ] 50 | }, 51 | "metadata": {}, 52 | "output_type": "display_data" 53 | }, 54 | { 55 | "data": { 56 | "image/png": "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\n", 57 | "text/plain": [ 58 | "
" 59 | ] 60 | }, 61 | "metadata": { 62 | "needs_background": "light" 63 | }, 64 | "output_type": "display_data" 65 | }, 66 | { 67 | "name": "stderr", 68 | "output_type": "stream", 69 | "text": [ 70 | "INFO: ARNet - find_lr: Optimal learning rate: 0.1369830012321472\n" 71 | ] 72 | }, 73 | { 74 | "data": { 75 | "text/html": [ 76 | "\n", 77 | " \n", 78 | " \n", 79 | " \n", 80 | " \n", 81 | " \n", 82 | " \n", 83 | " \n", 84 | " \n", 85 | " \n", 86 | " \n", 87 | " \n", 88 | " \n", 89 | " \n", 90 | " \n", 91 | " \n", 92 | " \n", 93 | " \n", 94 | " \n", 95 | " \n", 96 | " \n", 97 | " \n", 98 | " \n", 99 | " \n", 100 | " \n", 101 | " \n", 102 | " \n", 103 | " \n", 104 | " \n", 105 | " \n", 106 | " \n", 107 | " \n", 108 | " \n", 109 | " \n", 110 | " \n", 111 | " \n", 112 | " \n", 113 | " \n", 114 | " \n", 115 | " \n", 116 | " \n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | " \n", 167 | " \n", 168 | " \n", 169 | "
epochtrain_lossvalid_lossmsemaetime
00.0109550.0109120.0109120.08409900:01
10.0129360.0108500.0108500.08300300:01
20.0134330.0107410.0107410.08213700:01
30.0146200.0109330.0109330.08277300:01
40.0152000.0108460.0108460.08353900:01
50.0143740.0112870.0112870.08352200:01
60.0136780.0104330.0104330.08183900:01
70.0131770.0102530.0102530.08079800:01
80.0120350.0101050.0101050.08013400:01
90.0118870.0101020.0101020.08002000:01
" 170 | ], 171 | "text/plain": [ 172 | "" 173 | ] 174 | }, 175 | "metadata": {}, 176 | "output_type": "display_data" 177 | }, 178 | { 179 | "data": { 180 | "image/png": "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\n", 181 | "text/plain": [ 182 | "
" 183 | ] 184 | }, 185 | "metadata": { 186 | "needs_background": "light" 187 | }, 188 | "output_type": "display_data" 189 | } 190 | ], 191 | "source": [ 192 | "m = arnet.ARNet(\n", 193 | " ar_order=10,\n", 194 | " sparsity=0.5,\n", 195 | " n_epoch=10,\n", 196 | " loss_func=\"MSE\",\n", 197 | " valid_p=0.1,\n", 198 | " use_gpu=False,\n", 199 | ")\n", 200 | "m = m.fit(df, plot=True)" 201 | ] 202 | }, 203 | { 204 | "cell_type": "code", 205 | "execution_count": 4, 206 | "metadata": {}, 207 | "outputs": [ 208 | { 209 | "data": { 210 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAmkAAAEKCAYAAABJ+cK7AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8vihELAAAACXBIWXMAAAsTAAALEwEAmpwYAAAhwUlEQVR4nO3de5xeZX33+8+XcIgotJAgIiEmj6IcSgw6IHLSchI3j4BbqlDloNLIFiyI1tLtsxVbt9BKaxHpQ9ngIx4IKlKJTz1BEDlUkATDMSioAUIRQlAOVdTAb/9xryTDOJMMmbln3TP5vF+ved3rdK/rtyaafFnrWteVqkKSJEm9ZYO2C5AkSdIfMqRJkiT1IEOaJElSDzKkSZIk9SBDmiRJUg8ypEmSJPWgVkNakoOT/DjJPUlOG2T/CUluS7IoyXVJdmqjTkmSpLGWtsZJSzIJ+AlwILAUuAk4qqru7HfM5lX1eLN8KPDeqjq4jXolSZLGUpt30nYH7qmqn1XV74BLgMP6H7AyoDWeDzjyriRJWi9s2GLb2wL391tfCrxm4EFJTgROBTYG9lvbSadOnVozZswYpRIlSZK6Z+HChY9U1VaD7WszpA1LVZ0LnJvkz4H/ARw78Jgkc4A5ANOnT2fBggVjW6QkSdI6SHLvUPvafNz5ALBdv/VpzbahXAIcPtiOqjq/qvqqqm+rrQYNo5IkSeNKmyHtJmD7JDOTbAwcCczrf0CS7futHgLcPYb1SZIktaa1x51VtSLJScB3gEnAZ6vqjiR/CyyoqnnASUkOAH4P/JJBHnVKkiRNRK32SauqbwLfHLDtI/2WTx7zoiRJ0oj8/ve/Z+nSpTz11FNtl9IzJk+ezLRp09hoo42G/Z2ef3FAkiSNL0uXLmWzzTZjxowZJGm7nNZVFcuXL2fp0qXMnDlz2N9zWihJkjSqnnrqKaZMmWJAayRhypQpz/nOoiFNkiSNOgPas63L78OQJkmS1IPskzZBvPqvPt92Cc/Jwk8e03YJkqT11IwZM1iwYAFTp04d0THd5p00SZKkHmRIkyRJPW/JkiXssMMOHHfccbz85S/n7W9/O1deeSV77bUX22+/PT/84Q959NFHOfzww5k1axZ77LEHt956KwDLly/noIMOYuedd+b444+nqlad94tf/CK77747s2fP5j3veQ9PP/10W5f4BwxpkiRpXLjnnnv4wAc+wF133cVdd93FxRdfzHXXXcdZZ53FJz7xCT760Y+y6667cuutt/KJT3yCY47pdK352Mc+xt57780dd9zBm9/8Zu677z4AFi9ezJe//GWuv/56Fi1axKRJk/jSl77U5iU+i33SJEnSuDBz5kx22WUXAHbeeWf2339/krDLLruwZMkS7r33Xr72ta8BsN9++7F8+XIef/xxrrnmGi677DIADjnkELbYYgsA5s+fz8KFC9ltt90A+M1vfsMLX/jCFq5scIY0SZI0LmyyySarljfYYINV6xtssAErVqx4TqP5Q2eQ2WOPPZYzzjhjVOscLT7ulCRJE8I+++yz6nHl1VdfzdSpU9l8883Zd999ufjiiwH41re+xS9/+UsA9t9/fy699FIefvhhAB599FHuvffedoofhHfSJEnShHD66afzrne9i1mzZrHpppty0UUXAfDRj36Uo446ip133pk999yT6dOnA7DTTjvx8Y9/nIMOOohnnnmGjTbaiHPPPZeXvOQlbV7GKun/hsNE0NfXVwsWLGi7jDHnOGmSpF6xePFidtxxx7bL6DmD/V6SLKyqvsGO93GnJElSDzKkSZIk9SBDmiRJUg8ypEmSJPUgQ5okSVIPMqRJkiT1oFbHSUtyMHA2MAm4oKrOHLD/VOB4YAWwDHhXVfXOKHOSJGmtRnuYqOEO4/T1r3+dN7/5zSxevJgddtiBJUuWsOOOO/KKV7yC3/3ud/T19XHhhRcOOlNBEk499VT+8R//EYCzzjqLJ598ktNPP33I9q6++mo23nhj9txzz3W6roFau5OWZBJwLvBGYCfgqCQ7DTjsR0BfVc0CLgX+YWyrlCRJ49XcuXPZe++9mTt37qptL33pS1m0aBG33XYbS5cu5Stf+cqg391kk0247LLLeOSRR4bd3tVXX81//Md/jLjuldp83Lk7cE9V/ayqfgdcAhzW/4Cq+l5V/bpZvQGYNsY1SpKkcejJJ5/kuuuu48ILL+SSSy75g/2TJk1i991354EHHhj0+xtuuCFz5szhU5/61B/sW7ZsGW95y1vYbbfd2G233bj++utZsmQJ5513Hp/61KeYPXs211577Yivoc3HndsC9/dbXwq8Zg3Hvxv4VlcrkiRJE8Lll1/OwQcfzMtf/nKmTJnCwoULmTJlyqr9Tz31FDfeeCNnn332kOc48cQT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\n", 211 | "text/plain": [ 212 | "
" 213 | ] 214 | }, 215 | "metadata": { 216 | "needs_background": "light" 217 | }, 218 | "output_type": "display_data" 219 | } 220 | ], 221 | "source": [ 222 | "m.plot_weights()" 223 | ] 224 | } 225 | ], 226 | "metadata": { 227 | "kernelspec": { 228 | "display_name": "light-matter", 229 | "language": "python", 230 | "name": "light-matter" 231 | }, 232 | "language_info": { 233 | "codemirror_mode": { 234 | "name": "ipython", 235 | "version": 3 236 | }, 237 | "file_extension": ".py", 238 | "mimetype": "text/x-python", 239 | "name": "python", 240 | "nbconvert_exporter": "python", 241 | "pygments_lexer": "ipython3", 242 | "version": "3.8.6" 243 | } 244 | }, 245 | "nbformat": 4, 246 | "nbformat_minor": 4 247 | } 248 | -------------------------------------------------------------------------------- /example_notebooks/legacy_run_experiments.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import numpy as np\n", 10 | "import pandas as pd\n", 11 | "import os\n", 12 | "import matplotlib.pyplot as plt\n", 13 | "\n", 14 | "import arnet\n", 15 | "from arnet.ar_net_legacy import init_ar_learner\n", 16 | "\n", 17 | "%matplotlib inline" 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": 3, 23 | "metadata": {}, 24 | "outputs": [], 25 | "source": [ 26 | "# hyperparameters\n", 27 | "save = True\n", 28 | "sparsity = 0.5 # guesstimate\n", 29 | "ar_order = 10 # guesstimate\n", 30 | "\n", 31 | "n_epoch = 10\n", 32 | "valid_p = 0.1" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": 2, 38 | "metadata": {}, 39 | "outputs": [], 40 | "source": [ 41 | "# data\n", 42 | "DIR = os.path.dirname(os.path.abspath(''))\n", 43 | "data_path = os.path.join(DIR, 'ar_data')\n", 44 | "results_path = os.path.join(DIR, 'results')\n", 45 | "models_path = os.path.join(DIR, 'models')\n", 46 | "if not os.path.exists(results_path): \n", 47 | " os.makedirs(results_path) \n", 48 | "if not os.path.exists(models_path): \n", 49 | " os.makedirs(models_path)\n", 50 | " \n", 51 | "data_names = ['ar_3_ma_0_noise_0.100_len_10000'] " 52 | ] 53 | }, 54 | { 55 | "cell_type": "code", 56 | "execution_count": 4, 57 | "metadata": {}, 58 | "outputs": [ 59 | { 60 | "name": "stdout", 61 | "output_type": "stream", 62 | "text": [ 63 | "fitting: ar_3_ma_0_noise_0.100_len_10000\n" 64 | ] 65 | }, 66 | { 67 | "data": { 68 | "text/html": [], 69 | "text/plain": [ 70 | "" 71 | ] 72 | }, 73 | "metadata": {}, 74 | "output_type": "display_data" 75 | }, 76 | { 77 | "data": { 78 | "image/png": "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\n", 79 | "text/plain": [ 80 | "
" 81 | ] 82 | }, 83 | "metadata": { 84 | "needs_background": "light" 85 | }, 86 | "output_type": "display_data" 87 | }, 88 | { 89 | "name": "stdout", 90 | "output_type": "stream", 91 | "text": [ 92 | "lr at minimum: 0.05623413324356079; steeptes lr: 0.020417379215359688\n" 93 | ] 94 | }, 95 | { 96 | "data": { 97 | "text/html": [ 98 | "\n", 99 | " \n", 100 | " \n", 101 | " \n", 102 | " \n", 103 | " \n", 104 | " \n", 105 | " \n", 106 | " \n", 107 | " \n", 108 | " \n", 109 | " \n", 110 | " \n", 111 | " \n", 112 | " \n", 113 | " \n", 114 | " \n", 115 | " \n", 116 | " \n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | " \n", 167 | " \n", 168 | " \n", 169 | " \n", 170 | " \n", 171 | " \n", 172 | " \n", 173 | " \n", 174 | " \n", 175 | " \n", 176 | " \n", 177 | " \n", 178 | " \n", 179 | " \n", 180 | "
epochtrain_lossvalid_lossmaetime
00.0202480.0133060.09141800:01
10.0103280.0102450.08072200:01
20.0102580.0100970.08010400:01
30.0105520.0102400.08094500:01
40.0101900.0102570.08097500:01
50.0106040.0101000.08025200:01
60.0103740.0100580.07983100:01
70.0103700.0100760.07997000:01
80.0102770.0100790.07997200:01
90.0104680.0100700.07992400:01
" 181 | ], 182 | "text/plain": [ 183 | "" 184 | ] 185 | }, 186 | "metadata": {}, 187 | "output_type": "display_data" 188 | }, 189 | { 190 | "data": { 191 | "image/png": "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\n", 192 | "text/plain": [ 193 | "
" 194 | ] 195 | }, 196 | "metadata": { 197 | "needs_background": "light" 198 | }, 199 | "output_type": "display_data" 200 | } 201 | ], 202 | "source": [ 203 | "# fit and collect AR coefficients\n", 204 | "coeff_list = []\n", 205 | "for name in data_names:\n", 206 | " print(\"fitting: {}\".format(name))\n", 207 | " df = pd.read_csv(os.path.join(data_path, name + '.csv'), header=None, index_col=False)\n", 208 | " # Init Model\n", 209 | " learn = init_ar_learner(\n", 210 | " series=df,\n", 211 | " ar_order=ar_order,\n", 212 | " n_forecasts=1,\n", 213 | " valid_p=valid_p,\n", 214 | " sparsity=sparsity,\n", 215 | " train_bs=32,\n", 216 | " valid_bs=1024,\n", 217 | " verbose=False,\n", 218 | " )\n", 219 | " # find Learning Rate\n", 220 | " lr_at_min, lr_steep = learn.lr_find(start_lr=1e-7, end_lr=100, num_it=100, show_plot=True)\n", 221 | " plt.show()\n", 222 | " print(\"lr at minimum: {}; steeptes lr: {}\".format(lr_at_min, lr_steep))\n", 223 | " lr_max = lr_at_min/10\n", 224 | "\n", 225 | " # Run Model\n", 226 | " learn.fit_one_cycle(n_epoch=n_epoch, lr_max=lr_max)\n", 227 | " learn.recorder.plot_loss()\n", 228 | " plt.show()\n", 229 | " # record Coeff\n", 230 | " coeff = arnet.coeff_from_model(learn.model) \n", 231 | " coeff_list.append({\"name\": name, \"coeff\": coeff[0]})\n", 232 | "df_coeff = pd.DataFrame(coeff_list)\n" 233 | ] 234 | }, 235 | { 236 | "cell_type": "code", 237 | "execution_count": 5, 238 | "metadata": {}, 239 | "outputs": [ 240 | { 241 | "data": { 242 | "text/html": [ 243 | "
\n", 244 | "\n", 257 | "\n", 258 | " \n", 259 | " \n", 260 | " \n", 261 | " \n", 262 | " \n", 263 | " \n", 264 | " \n", 265 | " \n", 266 | " \n", 267 | " \n", 268 | " \n", 269 | " \n", 270 | " \n", 271 | " \n", 272 | "
namecoeff
0ar_3_ma_0_noise_0.100_len_10000[0.19604042, 0.2887145, -0.4777012, 0.0003959917, 1.7987082e-05, 3.091372e-05, 0.00032146755, -1.1136071e-05, 2.8788853e-07, -2.827955e-06]
\n", 273 | "
" 274 | ], 275 | "text/plain": [ 276 | " name \\\n", 277 | "0 ar_3_ma_0_noise_0.100_len_10000 \n", 278 | "\n", 279 | " coeff \n", 280 | "0 [0.19604042, 0.2887145, -0.4777012, 0.0003959917, 1.7987082e-05, 3.091372e-05, 0.00032146755, -1.1136071e-05, 2.8788853e-07, -2.827955e-06] " 281 | ] 282 | }, 283 | "execution_count": 5, 284 | "metadata": {}, 285 | "output_type": "execute_result" 286 | } 287 | ], 288 | "source": [ 289 | "df_coeff" 290 | ] 291 | }, 292 | { 293 | "cell_type": "code", 294 | "execution_count": 6, 295 | "metadata": {}, 296 | "outputs": [ 297 | { 298 | "data": { 299 | "image/png": "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LVu2pLS0lH33rWsiA7YqM6LGZ+Fq9J3vfIdDDz2UP/zhD5x00kn8+te/JiIYO3Ys11133Tbv33fffSktLa3cTyfW6ve//faWSRsq7t+R7wBg8+bN7LrrrpWtl9Xt6PdUoa64KupSWFjIpk2bWLt2LZ06daq1jkCtx+vDy0KZmVneW7t2LR07dqRdu3YsWbKEuXPnpnVfRLBs2bLK7ccee4yDDjqoXrGsW7eOnXbaiV122YX33nuPJ56ocXYoACTx1a9+tXLM1NSpUxkxYkSt19dmyJAhzJgxg/LyctasWcPzzz/PoEGDtrrmzTffpHv37lx00UWMGDGCBQsWMHToUGbOnMnq1alh3R988AErV66ssYy9996bDh06MHfuXCKCu+++e7tiHThwIEuXLmX58uX861//Yvr06QwfPnyHv4MOHTrQrVs3HnzwQSD185s/f37a8WxLXXENHz6cqVOnAjBz5kyOPfZYJDF8+HCmT5/Oxo0bWb58OUuXLmXQoEG11r2+3JJmZmbbJRfTAw0bNozbb7+dgw8+mK985SsMHjw4rfsqWpLWrVtHRNC3b19+9atf1SuWvn370r9/fw466CD2228/jjjiiDqvv/766xk1ahQ/+clP6N+/P+PHj9/uMk8++WTmzJlD3759kcQNN9zAXnvtxYoVKyqveeCBB5g2bRqtWrVir7324sc//jG77bYb11xzDccffzybN2+mVatW3Hbbbey/f82rLP7yl7+snILjxBNP5MQTTwRS490Avve97/Huu+9SVFTEunXraNGiBTfddBOLFi2iQ4cO3HrrrZxwwgmUl5dz1lln0atXr3p9B/feey/nnXce11xzDZ9//jmjRo2ib9++2/39VTx08Mknn1BYWMiUKVM44YQTao1r/PjxnHHGGfTo0YPddtuN6dOnA6ku79NOO42ePXvSsmVLbrvttsqW3NrqXh/akSbCfFZUVBTFxcVfON7U50lrbPXzHHBmjcfixYs5+OCDcx2GWaNX078lSSURUVTT9e7uNDMzM8tD7u40M7Mm4eSTT2b58uVbHbv++usrZ4SvasKECZVjnSqceuqpXHnllY22/O1x6KGHbvWkJMC0adMqn2RtCJn+Dl577TXOOOOMrY61adOGl156aYdjzDV3d+Yxd3eaWT5wd6dZZri708zMMq6p/Q+9WUPbkX9DTtLMzKxO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\n", 300 | "text/plain": [ 301 | "
" 302 | ] 303 | }, 304 | "metadata": { 305 | "needs_background": "light" 306 | }, 307 | "output_type": "display_data" 308 | } 309 | ], 310 | "source": [ 311 | "if save:\n", 312 | " df_coeff.to_csv(\n", 313 | " os.path.join(results_path, \"coeff_ar-{}_spar-{}.csv\".format(ar_order, sparsity)),\n", 314 | " index=False,\n", 315 | " )\n", 316 | " for index, row in df_coeff.iterrows():\n", 317 | " arnet.plot_weights(\n", 318 | " ar_val=ar_order,\n", 319 | " weights=row[\"coeff\"],\n", 320 | " model_name=row[\"name\"],\n", 321 | " save=True,\n", 322 | " savedir=results_path,\n", 323 | " figsize=(10,3),\n", 324 | " )" 325 | ] 326 | }, 327 | { 328 | "cell_type": "code", 329 | "execution_count": null, 330 | "metadata": {}, 331 | "outputs": [], 332 | "source": [] 333 | }, 334 | { 335 | "cell_type": "code", 336 | "execution_count": null, 337 | "metadata": {}, 338 | "outputs": [], 339 | "source": [] 340 | }, 341 | { 342 | "cell_type": "code", 343 | "execution_count": null, 344 | "metadata": {}, 345 | "outputs": [], 346 | "source": [] 347 | }, 348 | { 349 | "cell_type": "code", 350 | "execution_count": null, 351 | "metadata": {}, 352 | "outputs": [], 353 | "source": [] 354 | } 355 | ], 356 | "metadata": { 357 | "kernelspec": { 358 | "display_name": "light-matter", 359 | "language": "python", 360 | "name": "light-matter" 361 | }, 362 | "language_info": { 363 | "codemirror_mode": { 364 | "name": "ipython", 365 | "version": 3 366 | }, 367 | "file_extension": ".py", 368 | "mimetype": "text/x-python", 369 | "name": "python", 370 | "nbconvert_exporter": "python", 371 | "pygments_lexer": "ipython3", 372 | "version": "3.8.6" 373 | } 374 | }, 375 | "nbformat": 4, 376 | "nbformat_minor": 4 377 | } 378 | --------------------------------------------------------------------------------