Skip to content
Closed
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
8d87d82
:art: Lint, format, remove not used imports, sort imports.
May 3, 2020
1d9f542
:bug: Fix search.py import bug.
May 3, 2020
4ece89a
:shirt: Clear training.ipynb output.
May 3, 2020
71c9edb
:shirt: lint make_npz.py
May 3, 2020
bce27ba
:shirt: lint datasets/README.md
May 3, 2020
f1fd606
:shirt: lint test/multiHeadAttention.py
May 3, 2020
8ab9035
:construction: Update labels.json to reflect the challenge.
May 3, 2020
de9274c
:construction: Add model.py.
May 3, 2020
6c35365
:construction: Add dataset2.py.
May 3, 2020
b1fb712
:construction: Improve pull request accordingly to feedback.
May 3, 2020
c780755
:shirt: dopout should actually be dropout.
May 3, 2020
2a8ba6d
Restore training.ipynb
May 3, 2020
de92c1a
:heavy_minus_sign: Remove csv2npz and make_npz.py
May 3, 2020
5a7fd4e
:heavy_plus_sign: Add pylint to requirements.txt.
May 4, 2020
6359785
:shirt: lint src/utils/search.py
May 4, 2020
6e86025
:shirt: lint src/utils/search.py
May 4, 2020
6de0f16
:construction: Add to ignore list *.csv
May 4, 2020
2fe0b18
:construction: Development path
May 6, 2020
d4fe40c
:construction: Development path.
May 28, 2020
5285fd4
:construction: Development path.
May 28, 2020
0af294d
:art: Resolution of conflicts.
May 28, 2020
31e8d4a
:shirt: lint code.
May 29, 2020
906d883
:rocket: Speed up transformer pytest.
May 29, 2020
3304457
:rocket: Speed up pytest with seaborn passsengers dataset.
May 30, 2020
d7c1be3
:bug: Fix FlightsDataset shape convention bug.
May 30, 2020
d1019b3
:heavy_plus_sign: Add FlightsDataset labels.
May 30, 2020
ad80d93
:heavy_plus_sign: Add FlightsDataset labels.
May 30, 2020
1384707
:art: Improve flights_dataset.
May 30, 2020
1329e82
:art: Start using the specialized MinMaxScaler object to scale the da…
Jun 1, 2020
ad0479c
:heavy_plus_sign: Implement forecast functionality.
Jun 2, 2020
e032fbb
:bug: Fix make_future_dataframe method of TransformerTimeSeriesPredic…
Jun 2, 2020
2d42ffe
:construction: Development path.
Dec 20, 2020
e096b00
:fire: Clean code base.
Dec 21, 2020
2a6d5aa
:fire: Clean code base.
Dec 21, 2020
0aac45e
:construction: Update version number in bumpversion.cfg
Dec 21, 2020
339fe18
:construction: Update version number in deploy.ps1
Dec 21, 2020
d3417a1
:fire: Remove doc conf leftover in bumpversion.cfg
Dec 21, 2020
8e88a81
Bump version: 0.3.0 → 0.4.0
Dec 21, 2020
7db1943
Add to git ignore list build and dist folders.
Dec 21, 2020
f6188f7
Bump version: 0.4.0 → 0.4.1
Dec 21, 2020
5cd8133
:heavy_plus_sign: Add travis.
Dec 21, 2020
4e6f58e
:memo: Add badges to README.md.
Dec 21, 2020
a3d4795
:bug: Should fix codecov.
Dec 21, 2020
c8de445
Bump version: 0.4.1 → 0.4.2
Dec 21, 2020
78e00c4
:white_check_mark: Update main_test.
Dec 21, 2020
9e7f016
:construction: Development path.
Jan 4, 2021
9837bbc
:rocket: Speed up fitting.
Jan 4, 2021
50e7ca9
Bump version: 0.4.2 → 0.4.3
Jan 4, 2021
2df8280
:construction: Development path in order to progressively improve all…
Jan 5, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ build

# Dataset
*.npz
datasets/*.csv

# Models
*.pth
Expand All @@ -24,3 +25,6 @@ _build
# Figures
*.png
*.jpg

# Outputs
*.csv
9 changes: 9 additions & 0 deletions .pylintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[MASTER]
extension-pkg-whitelist=numpy,torch

[TYPECHECK]

# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=numpy.*,torch.*
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,33 @@
[![Documentation Status](https://readthedocs.org/projects/timeseriestransformer/badge/?version=latest)](https://timeseriestransformer.readthedocs.io/en/latest/?badge=latest) [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Latest release](https://img.shields.io/github/release/maxjcohen/transformer.svg)](https://github.com/maxjcohen/transformer/releases/latest)
# Transformers for Time Series
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we removing such big chunks of the Readme ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The purpose was to simplify the package documentation so that only essential info is there for SW developers.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes but this content is important, it show how and why I developed that repo. The README is not the documentation, I think we can get away with having a detailed file here.


Transformers for Time Series
============================
[![Documentation Status](https://readthedocs.org/projects/timeseriestransformer/badge/?version=latest)](https://timeseriestransformer.readthedocs.io/en/latest/?badge=latest) [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![Latest release](https://img.shields.io/github/release/maxjcohen/transformer.svg)](https://github.com/maxjcohen/transformer/releases/latest)

Implementation of Transformer model (originally from [Attention is All You Need](https://arxiv.org/abs/1706.03762)) applied to Time Series (Powered by [PyTorch](https://pytorch.org/)).

## Transformer model

Transformer are attention based neural networks designed to solve NLP tasks. Their key features are:

- linear complexity in the dimension of the feature vector ;
- paralellisation of computing of a sequence, as opposed to sequential computing ;
- long term memory, as we can look at any input time sequence step directly.

This repo will focus on their application to times series.

## Dataset and application as metamodel

Our use-case is modeling a numerical simulator for building consumption prediction. To this end, we created a dataset by sampling random inputs (building characteristics and usage, weather, ...) and got simulated outputs. We then convert these variables in time series format, and feed it to the transformer.

## Adaptations for time series

In order to perform well on time series, a few adjustments had to be made:

- The embedding layer is replaced by a generic linear layer ;
- Original positional encoding are removed. A "regular" version, better matching the input sequence day/night patterns, can be used instead ;
- A window is applied on the attention map to limit backward attention, and focus on short term patterns.

## Installation

All required packages can be found in `requirements.txt`, and expect to be run with `python3.7`. Note that you may have to install pytorch manually if you are not using pip with a Debian distribution : head on to [PyTorch installation page](https://pytorch.org/get-started/locally/). Here are a few lines to get started with pip and virtualenv:

```bash
Expand All @@ -36,12 +41,15 @@ $ . .env/bin/activate
## Usage

### Downloading the dataset

The dataset is not included in this repo, and must be downloaded manually. It is comprised of two files, `dataset.npz` contains all input and outputs value, `labels.json` is a detailed list of the variables.

### Running training script
Using jupyter, run the default `training.ipynb` notebook. All adjustable parameters can be found in the second cell. Careful with the `BATCH_SIZE`, as we are using it to parallelize head and time chunk calculations.

Using jupyter, run the default `training.ipynb` notebook. All adjustable parameters can be found in the second cell. Careful with the `BATCH_SIZE`, as we are using it to parallelize head and time chunk calculations.

### Outside usage

The `Transformer` class can be used out of the box, [docs](https://timeseriestransformer.readthedocs.io/en/latest/Transformer.html) for more info.

```python
Expand All @@ -51,7 +59,9 @@ net = Transformer(d_input, d_model, d_output, q, v, h, N, TIME_CHUNK, pe)
```

### Building the docs

To build the doc:

```bash
(.env) $ cd docs && make html
```
4 changes: 2 additions & 2 deletions benchmark.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"from tqdm import tqdm\n",
"import seaborn as sns\n",
"\n",
"from tst.loss import OZELoss\n",
"from test.loss import OZELoss\n",
"\n",
"from src.benchmark import BiGRU, ConvGru\n",
"from src.dataset import OzeDataset\n",
Expand Down Expand Up @@ -395,4 +395,4 @@
},
"nbformat": 4,
"nbformat_minor": 4
}
}
19 changes: 11 additions & 8 deletions cross_validation.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import numpy as np
"""
Cross validation
"""
# import numpy as np
import torch
import torch.nn as nn
# import torch.nn as nn
import torch.optim as optim
from tqdm import tqdm

from tst import Transformer
from tst.loss import OZELoss

from src.benchmark import BiGRU # , LSTM
from src.dataset import OzeDataset
from src.utils import compute_loss, fit, Logger, kfold
from src.benchmark import LSTM, BiGRU
from src.utils import Logger, fit, kfold # compute_loss
# from test import Transformer
from tst.loss import OZELoss

# Search parameters
CHUNKS = 5
Expand Down Expand Up @@ -58,7 +60,8 @@
# Load transformer with Adam optimizer and MSE loss function
# net = Transformer(d_input, d_model, d_output, q, v, h, N, attention_size=attention_size,
# dropout=dropout, chunk_mode=chunk_mode, pe=pe).to(device)
net = BiGRU(d_input, d_model, d_output, num_layers=N, dropout=dropout, bidirectional=True).to(device)
net = BiGRU(d_input, d_model, d_output, num_layers=N, dropout=dropout, \
bidirectional=True).to(device)

optimizer = optim.Adam(net.parameters(), lr=LR)

Expand Down
9 changes: 9 additions & 0 deletions datasets/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Datasets

## Description

This is the folder where datasets should be downloaded to.

## Installation

After downloading the .csv files from the [challenge](https://challengedata.ens.fr/challenges/28), install by running `make_npz.py`
6 changes: 5 additions & 1 deletion export_doc.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
"""
Export doc
"""
import json
import datetime
from pathlib import Path
import argparse


# NOTE Redefining name 'notebook_path' from outer scope (line 39)pylint(redefined-outer-name)
def export_notebook(notebook_path: Path, export_dir: Path):
# Load notebook
with open(notebook_path, "r") as stream_json:
Expand All @@ -18,6 +21,7 @@ def export_notebook(notebook_path: Path, export_dir: Path):

# Add date to export path
export_name = f'training_{export_time.strftime("%Y_%m_%d__%H%M%S")}.ipynb'
# NOTE Redefining name 'export_path' from outer scope (line 40)pylint(redefined-outer-name)
export_path = export_dir.joinpath(export_name)

# Export
Expand Down
22 changes: 10 additions & 12 deletions learning_curve.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import numpy as np
from matplotlib import pyplot as plt
"""
Learning curve
"""
import seaborn as sns
import torch
import torch.nn as nn
import torch.optim as optim
from tqdm import tqdm
import seaborn as sns

from src.dataset import OzeDataset
from src.utils import Logger, fit, learning_curve
from tst import Transformer
from tst.loss import OZELoss

from src.dataset import OzeDataset
from src.utils import visual_sample, compute_loss
from src.utils import compute_loss, fit, Logger, kfold, leargnin_curve

# Search parameters
PARTS = 8
VALIDATION_SPLIT = 0.3
Expand Down Expand Up @@ -53,11 +51,11 @@

logger = Logger('learningcurve_log.csv')

learningcurveIterator = leargnin_curve(ozeDataset, n_part=PARTS, validation_split=VALIDATION_SPLIT,
batch_size=BATCH_SIZE, num_workers=NUM_WORKERS)
learning_curve_iterator = learning_curve(ozeDataset, n_part=PARTS, \
validation_split=VALIDATION_SPLIT, batch_size=BATCH_SIZE, num_workers=NUM_WORKERS)

with tqdm(total=PARTS*EPOCHS) as pbar:
for dataloader_train, dataloader_val in learningcurveIterator:
for dataloader_train, dataloader_val in learning_curve_iterator:

# Load transformer with Adam optimizer and MSE loss function
net = Transformer(d_input, d_model, d_output, q, v, h, N, attention_size=attention_size,
Expand All @@ -70,4 +68,4 @@
dataloader_val, epochs=EPOCHS, pbar=pbar, device=device)

# Log
logger.log(loss=loss)
logger.log(loss=loss)
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ torch
jupyterlab
matplotlib
seaborn
tqdm
tqdm
pylint
11 changes: 5 additions & 6 deletions search.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
"""
Search
"""
import itertools
import datetime
import json
from collections import OrderedDict

import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, random_split
from tqdm import tqdm

from src.dataset import OzeDataset
from src.utils import Logger, fit
from tst import Transformer
from tst.loss import OZELoss

from src.dataset import OzeDataset
from src.utils import compute_loss, fit, Logger

# ===== user set params ====
search_params = OrderedDict({
"d_model": [32],
Expand Down
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
"""
Setup
"""
import setuptools

with open("README.md", "r") as fh:
Expand Down
42 changes: 33 additions & 9 deletions src/benchmark.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
"""
Benchmark
"""
import torch
import torch.nn as nn

Expand All @@ -21,6 +24,7 @@ class LSTM(nn.Module):
If ``True``, becomes a bidirectional LSTM. Default: ``False``.
"""

# NOTE Too many arguments (7/5)pylint(too-many-arguments)
def __init__(self,
input_dim: int,
hidden_dim: int,
Expand All @@ -31,12 +35,14 @@ def __init__(self,
**kwargs):
super().__init__(**kwargs)

self.rnn = nn.LSTM(input_dim, hidden_dim, num_layers=num_layers, dropout=dropout, batch_first=True, bidirectional=bidirectional)
self.rnn = nn.LSTM(input_dim, hidden_dim, num_layers=num_layers, dropout=dropout, \
batch_first=True, bidirectional=bidirectional)

if bidirectional:
hidden_dim *= 2
self.linear = nn.Linear(hidden_dim, output_dim)

# NOTE Parameters differ from overridden 'forward' methodpylint(arguments-differ)
def forward(self, x: torch.Tensor) -> torch.Tensor:
"""Propagate input through the network.

Expand Down Expand Up @@ -81,12 +87,18 @@ def __init__(self,
dropout: float = 0,
bidirectional: bool = False,
**kwargs):
super().__init__(input_dim, hidden_dim, output_dim, num_layers, dropout, bidirectional, **kwargs)
super().__init__(input_dim, hidden_dim, output_dim, num_layers, dropout, bidirectional, \
**kwargs)

self.rnn = nn.GRU(input_dim, hidden_dim, num_layers=num_layers, dropout=dropout, batch_first=True, bidirectional=bidirectional)
self.rnn = nn.GRU(input_dim, hidden_dim, num_layers=num_layers, dropout=dropout, \
batch_first=True, bidirectional=bidirectional)


class ConvGru(nn.Module):
"""
ConvGru
"""
# NOTE Too many arguments (7/5)pylint(too-many-arguments)
def __init__(self,
input_dim: int,
hidden_dim: int,
Expand All @@ -97,9 +109,12 @@ def __init__(self,
**kwargs):
super().__init__(**kwargs)

self.conv1 = nn.Conv1d(in_channels=input_dim, out_channels=hidden_dim, kernel_size=11, stride=1, padding=11//2)
self.conv2 = nn.Conv1d(in_channels=hidden_dim, out_channels=hidden_dim, kernel_size=11, stride=1, padding=11//2)
self.conv3 = nn.Conv1d(in_channels=hidden_dim, out_channels=hidden_dim, kernel_size=11, stride=1, padding=11//2)
self.conv1 = nn.Conv1d(in_channels=input_dim, out_channels=hidden_dim, \
kernel_size=11, stride=1, padding=11//2)
self.conv2 = nn.Conv1d(in_channels=hidden_dim, out_channels=hidden_dim, \
kernel_size=11, stride=1, padding=11//2)
self.conv3 = nn.Conv1d(in_channels=hidden_dim, out_channels=hidden_dim, \
kernel_size=11, stride=1, padding=11//2)

self.activation = nn.LeakyReLU(0.1)

Expand All @@ -110,6 +125,7 @@ def __init__(self,
dropout=dropout,
bidirectional=bidirectional)

# NOTE Parameters differ from overridden 'forward' methodpylint(arguments-differ)
def forward(self, x):
x = x.transpose(1, 2)
x = self.conv1(x)
Expand All @@ -126,20 +142,28 @@ def forward(self, x):


class FullyConv(nn.Module):
"""
FullyConv
"""
def __init__(self,
input_dim: int,
hidden_dim: int,
output_dim: int,
# NOTE Unused argument 'dropout'pylint(unused-argument)
dropout: float = 0,
**kwargs):
super().__init__(**kwargs)

self.conv1 = nn.Conv1d(in_channels=input_dim, out_channels=hidden_dim, kernel_size=11, stride=1, padding=11//2)
self.conv2 = nn.Conv1d(in_channels=hidden_dim, out_channels=hidden_dim, kernel_size=11, stride=1, padding=11//2)
self.conv3 = nn.Conv1d(in_channels=hidden_dim, out_channels=output_dim, kernel_size=11, stride=1, padding=11//2)
self.conv1 = nn.Conv1d(in_channels=input_dim, out_channels=hidden_dim, \
kernel_size=11, stride=1, padding=11//2)
self.conv2 = nn.Conv1d(in_channels=hidden_dim, out_channels=hidden_dim, \
kernel_size=11, stride=1, padding=11//2)
self.conv3 = nn.Conv1d(in_channels=hidden_dim, out_channels=output_dim, \
kernel_size=11, stride=1, padding=11//2)

self.activation = nn.LeakyReLU(0.1)

# NOTE Parameters differ from overridden 'forward' methodpylint(arguments-differ)
def forward(self, x):
x = x.transpose(1, 2)
x = self.conv1(x)
Expand Down
6 changes: 5 additions & 1 deletion src/dataset.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
"""
Dataset
"""
import json
from typing import Optional

Expand Down Expand Up @@ -103,7 +106,8 @@ def rescale(self,
Index of the output label.
"""
if self._normalize == "max":
return y * (self._M[idx_label] - self._m[idx_label] + np.finfo(float).eps) + self._m[idx_label]
return y * (self._M[idx_label] - self._m[idx_label] + np.finfo(float).eps) + \
self._m[idx_label]
elif self._normalize == "mean":
return y * (self._std[idx_label] + np.finfo(float).eps) + self._mean[idx_label]
else:
Expand Down
7 changes: 5 additions & 2 deletions src/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
from .search import Logger, fit, kfold, leargnin_curve
from .utils import compute_loss
"""
__init__.py
"""
from .search import Logger, fit, kfold, learning_curve
from .utils import compute_loss
Loading