braindecode 0.1.0__tar.gz → 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {Braindecode-0.1.0 → Braindecode-0.1.2}/Braindecode.egg-info/PKG-INFO +16 -3
- Braindecode-0.1.2/Braindecode.egg-info/SOURCES.txt +39 -0
- Braindecode-0.1.2/Braindecode.egg-info/not-zip-safe +1 -0
- Braindecode-0.1.2/LICENSE.txt +31 -0
- Braindecode-0.1.2/MANIFEST.in +5 -0
- {Braindecode-0.1.0 → Braindecode-0.1.2}/PKG-INFO +16 -3
- Braindecode-0.1.2/README.md +17 -0
- Braindecode-0.1.2/braindecode/datasets/__init__.py +0 -0
- Braindecode-0.1.2/braindecode/datasets/signal_target.py +20 -0
- Braindecode-0.1.2/braindecode/experiments/__init__.py +0 -0
- Braindecode-0.1.2/braindecode/experiments/experiment.py +207 -0
- Braindecode-0.1.2/braindecode/experiments/monitors.py +142 -0
- Braindecode-0.1.2/braindecode/experiments/stopcriteria.py +53 -0
- Braindecode-0.1.2/braindecode/models/__init__.py +0 -0
- Braindecode-0.1.2/braindecode/models/shallow_fbcsp.py +90 -0
- Braindecode-0.1.2/braindecode/torchext/__init__.py +0 -0
- Braindecode-0.1.2/braindecode/torchext/constraints.py +13 -0
- Braindecode-0.1.2/braindecode/torchext/functions.py +9 -0
- Braindecode-0.1.2/braindecode/torchext/losses.py +11 -0
- Braindecode-0.1.2/braindecode/torchext/modules.py +20 -0
- Braindecode-0.1.2/braindecode/torchext/util.py +33 -0
- Braindecode-0.1.2/docs/Makefile +26 -0
- Braindecode-0.1.2/docs/conf.py +179 -0
- Braindecode-0.1.2/docs/index.rst +34 -0
- Braindecode-0.1.2/docs/notebooks/TrialWise_Decoding.ipynb +511 -0
- Braindecode-0.1.2/docs/source/braindecode.datasets.rst +22 -0
- Braindecode-0.1.2/docs/source/braindecode.experiments.rst +38 -0
- Braindecode-0.1.2/docs/source/braindecode.models.rst +22 -0
- Braindecode-0.1.2/docs/source/braindecode.rst +56 -0
- Braindecode-0.1.2/docs/source/braindecode.torchext.rst +54 -0
- Braindecode-0.1.2/docs/source/modules.rst +7 -0
- {Braindecode-0.1.0 → Braindecode-0.1.2}/setup.py +4 -4
- Braindecode-0.1.0/Braindecode.egg-info/SOURCES.txt +0 -11
- {Braindecode-0.1.0 → Braindecode-0.1.2}/Braindecode.egg-info/dependency_links.txt +0 -0
- {Braindecode-0.1.0 → Braindecode-0.1.2}/Braindecode.egg-info/requires.txt +0 -0
- {Braindecode-0.1.0 → Braindecode-0.1.2}/Braindecode.egg-info/top_level.txt +0 -0
- {Braindecode-0.1.0 → Braindecode-0.1.2}/braindecode/__init__.py +0 -0
- {Braindecode-0.1.0 → Braindecode-0.1.2}/braindecode/iterators.py +0 -0
- {Braindecode-0.1.0 → Braindecode-0.1.2}/braindecode/splitters.py +0 -0
- {Braindecode-0.1.0 → Braindecode-0.1.2}/braindecode/trial_segment.py +0 -0
- {Braindecode-0.1.0 → Braindecode-0.1.2}/braindecode/util.py +0 -0
- {Braindecode-0.1.0 → Braindecode-0.1.2}/setup.cfg +0 -0
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
Metadata-Version: 1.1
|
|
2
2
|
Name: Braindecode
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: A deep learning for raw time-domain EEG decoding toolbox.
|
|
5
5
|
Home-page: https://github.com/robintibor/braindecode
|
|
6
6
|
Author: Robin Tibor Schirrmeister
|
|
7
7
|
Author-email: robintibor@gmail.com
|
|
8
8
|
License: BSD 3-Clause
|
|
9
|
-
Description: Note: The old braindecode repository has been moved to
|
|
9
|
+
Description: Note: The old braindecode repository has been moved to
|
|
10
|
+
https://github.com/robintibor/braindevel.
|
|
11
|
+
|
|
12
|
+
Braindecode
|
|
13
|
+
===========
|
|
10
14
|
|
|
11
|
-
# Braindecode
|
|
12
15
|
A deep learning for raw time-domain EEG decoding toolbox.
|
|
13
16
|
|
|
17
|
+
Installation
|
|
18
|
+
============
|
|
19
|
+
|
|
20
|
+
Install pytorch from http://pytorch.org/.
|
|
21
|
+
|
|
22
|
+
Documentation
|
|
23
|
+
=============
|
|
24
|
+
|
|
25
|
+
Documentation is online under http://braindecode.readthedocs.io/
|
|
26
|
+
|
|
14
27
|
Keywords: eeg deep-learning brain-state-decoding
|
|
15
28
|
Platform: UNKNOWN
|
|
16
29
|
Classifier: Development Status :: 3 - Alpha
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
LICENSE.txt
|
|
2
|
+
MANIFEST.in
|
|
3
|
+
README.md
|
|
4
|
+
setup.py
|
|
5
|
+
Braindecode.egg-info/PKG-INFO
|
|
6
|
+
Braindecode.egg-info/SOURCES.txt
|
|
7
|
+
Braindecode.egg-info/dependency_links.txt
|
|
8
|
+
Braindecode.egg-info/not-zip-safe
|
|
9
|
+
Braindecode.egg-info/requires.txt
|
|
10
|
+
Braindecode.egg-info/top_level.txt
|
|
11
|
+
braindecode/__init__.py
|
|
12
|
+
braindecode/iterators.py
|
|
13
|
+
braindecode/splitters.py
|
|
14
|
+
braindecode/trial_segment.py
|
|
15
|
+
braindecode/util.py
|
|
16
|
+
braindecode/datasets/__init__.py
|
|
17
|
+
braindecode/datasets/signal_target.py
|
|
18
|
+
braindecode/experiments/__init__.py
|
|
19
|
+
braindecode/experiments/experiment.py
|
|
20
|
+
braindecode/experiments/monitors.py
|
|
21
|
+
braindecode/experiments/stopcriteria.py
|
|
22
|
+
braindecode/models/__init__.py
|
|
23
|
+
braindecode/models/shallow_fbcsp.py
|
|
24
|
+
braindecode/torchext/__init__.py
|
|
25
|
+
braindecode/torchext/constraints.py
|
|
26
|
+
braindecode/torchext/functions.py
|
|
27
|
+
braindecode/torchext/losses.py
|
|
28
|
+
braindecode/torchext/modules.py
|
|
29
|
+
braindecode/torchext/util.py
|
|
30
|
+
docs/Makefile
|
|
31
|
+
docs/conf.py
|
|
32
|
+
docs/index.rst
|
|
33
|
+
docs/notebooks/TrialWise_Decoding.ipynb
|
|
34
|
+
docs/source/braindecode.datasets.rst
|
|
35
|
+
docs/source/braindecode.experiments.rst
|
|
36
|
+
docs/source/braindecode.models.rst
|
|
37
|
+
docs/source/braindecode.rst
|
|
38
|
+
docs/source/braindecode.torchext.rst
|
|
39
|
+
docs/source/modules.rst
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017 Robin Tibor Schirrmeister
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without modification,
|
|
7
|
+
are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
10
|
+
this list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
|
17
|
+
may be used to endorse or promote products derived from this software
|
|
18
|
+
without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
22
|
+
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A P
|
|
23
|
+
ARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
|
|
24
|
+
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
25
|
+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
26
|
+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
27
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
28
|
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
29
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
|
|
30
|
+
WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
31
|
+
POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
Metadata-Version: 1.1
|
|
2
2
|
Name: Braindecode
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: A deep learning for raw time-domain EEG decoding toolbox.
|
|
5
5
|
Home-page: https://github.com/robintibor/braindecode
|
|
6
6
|
Author: Robin Tibor Schirrmeister
|
|
7
7
|
Author-email: robintibor@gmail.com
|
|
8
8
|
License: BSD 3-Clause
|
|
9
|
-
Description: Note: The old braindecode repository has been moved to
|
|
9
|
+
Description: Note: The old braindecode repository has been moved to
|
|
10
|
+
https://github.com/robintibor/braindevel.
|
|
11
|
+
|
|
12
|
+
Braindecode
|
|
13
|
+
===========
|
|
10
14
|
|
|
11
|
-
# Braindecode
|
|
12
15
|
A deep learning for raw time-domain EEG decoding toolbox.
|
|
13
16
|
|
|
17
|
+
Installation
|
|
18
|
+
============
|
|
19
|
+
|
|
20
|
+
Install pytorch from http://pytorch.org/.
|
|
21
|
+
|
|
22
|
+
Documentation
|
|
23
|
+
=============
|
|
24
|
+
|
|
25
|
+
Documentation is online under http://braindecode.readthedocs.io/
|
|
26
|
+
|
|
14
27
|
Keywords: eeg deep-learning brain-state-decoding
|
|
15
28
|
Platform: UNKNOWN
|
|
16
29
|
Classifier: Development Status :: 3 - Alpha
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Note: The old braindecode repository has been moved to
|
|
2
|
+
https://github.com/robintibor/braindevel.
|
|
3
|
+
|
|
4
|
+
Braindecode
|
|
5
|
+
===========
|
|
6
|
+
|
|
7
|
+
A deep learning for raw time-domain EEG decoding toolbox.
|
|
8
|
+
|
|
9
|
+
Installation
|
|
10
|
+
============
|
|
11
|
+
|
|
12
|
+
Install pytorch from http://pytorch.org/.
|
|
13
|
+
|
|
14
|
+
Documentation
|
|
15
|
+
=============
|
|
16
|
+
|
|
17
|
+
Documentation is online under http://braindecode.readthedocs.io/
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
class SignalAndTarget(object):
|
|
2
|
+
def __init__(self, X, y):
|
|
3
|
+
assert len(X) == len(y)
|
|
4
|
+
self.X = X
|
|
5
|
+
self.y = y
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def apply_to_X_y(fn, *sets):
|
|
9
|
+
X = fn(*[s.X for s in sets])
|
|
10
|
+
y = fn(*[s.y for s in sets])
|
|
11
|
+
return SignalAndTarget(X,y)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def split_into_two_sets(dataset, fraction=None, n_boundary=None):
|
|
15
|
+
assert fraction is not None or n_boundary is not None
|
|
16
|
+
if n_boundary is None:
|
|
17
|
+
n_boundary = int(round(len(dataset.X) * fraction))
|
|
18
|
+
first_set = apply_to_X_y(lambda a: a[:n_boundary], dataset)
|
|
19
|
+
second_set = apply_to_X_y(lambda a: a[n_boundary:], dataset)
|
|
20
|
+
return first_set, second_set
|
|
File without changes
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
from collections import OrderedDict
|
|
2
|
+
import logging
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from braindecode.splitters import concatenate_sets
|
|
8
|
+
from braindecode.experiments.stopcriteria import MaxEpochs, ColumnBelow, Or
|
|
9
|
+
from braindecode.torchext.util import np_to_var, set_random_seeds
|
|
10
|
+
|
|
11
|
+
log = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RememberBest(object):
|
|
15
|
+
def __init__(self, column_name):
|
|
16
|
+
self.column_name = column_name
|
|
17
|
+
self.best_epoch = 0
|
|
18
|
+
self.lowest_val = float('inf')
|
|
19
|
+
self.model_state_dict = None
|
|
20
|
+
self.optimizer_state_dict = None
|
|
21
|
+
|
|
22
|
+
def remember_epoch(self, epochs_df, model, optimizer):
|
|
23
|
+
i_epoch = len(epochs_df) - 1
|
|
24
|
+
current_val = float(epochs_df[self.column_name].iloc[-1])
|
|
25
|
+
if current_val <= self.lowest_val:
|
|
26
|
+
self.best_epoch = i_epoch
|
|
27
|
+
self.lowest_val = current_val
|
|
28
|
+
self.model_state_dict = deepcopy(model.state_dict())
|
|
29
|
+
self.optimizer_state_dict = deepcopy(optimizer.state_dict())
|
|
30
|
+
log.info("New best {:s}: {:5f}".format(self.column_name,
|
|
31
|
+
current_val))
|
|
32
|
+
log.info("")
|
|
33
|
+
|
|
34
|
+
def reset_to_best_model(self, epochs_df, model, optimizer):
|
|
35
|
+
# Remove epochs past the best one from epochs dataframe
|
|
36
|
+
epochs_df.drop(range(self.best_epoch+1, len(epochs_df)), inplace=True)
|
|
37
|
+
model.load_state_dict(self.model_state_dict)
|
|
38
|
+
optimizer.load_state_dict(self.optimizer_state_dict)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Experiment(object):
|
|
42
|
+
def __init__(self, model, train_set, valid_set, test_set,
|
|
43
|
+
iterator, loss_function, optimizer, model_constraint,
|
|
44
|
+
monitors, stop_criterion, remember_best_column,
|
|
45
|
+
run_after_early_stop,
|
|
46
|
+
batch_modifier=None, cuda=True):
|
|
47
|
+
self.model = model
|
|
48
|
+
self.datasets = OrderedDict(
|
|
49
|
+
(('train', train_set), ('valid', valid_set), ('test', test_set)))
|
|
50
|
+
self.iterator = iterator
|
|
51
|
+
self.loss_function = loss_function
|
|
52
|
+
self.optimizer = optimizer
|
|
53
|
+
self.model_constraint = model_constraint
|
|
54
|
+
self.monitors = monitors
|
|
55
|
+
self.stop_criterion = stop_criterion
|
|
56
|
+
self.remember_best_column = remember_best_column
|
|
57
|
+
self.run_after_early_stop = run_after_early_stop
|
|
58
|
+
self.batch_modifier = batch_modifier
|
|
59
|
+
self.cuda = cuda
|
|
60
|
+
self.epochs_df = pd.DataFrame()
|
|
61
|
+
self.before_stop_df = None
|
|
62
|
+
self.rememberer = None
|
|
63
|
+
|
|
64
|
+
def run(self):
|
|
65
|
+
self.setup_training()
|
|
66
|
+
log.info("Run until first stop...")
|
|
67
|
+
self.run_until_early_stop()
|
|
68
|
+
# always setup for second stop, in order to get best model
|
|
69
|
+
# even if not running after early stop...
|
|
70
|
+
log.info("Setup for second stop...")
|
|
71
|
+
self.setup_after_stop_training()
|
|
72
|
+
if self.run_after_early_stop:
|
|
73
|
+
log.info("Run until second stop...")
|
|
74
|
+
self.run_until_second_stop()
|
|
75
|
+
|
|
76
|
+
def setup_training(self):
|
|
77
|
+
# reset remember best extension in case you rerun some experiment
|
|
78
|
+
self.rememberer = RememberBest(self.remember_best_column)
|
|
79
|
+
self.epochs_df = pd.DataFrame()
|
|
80
|
+
set_random_seeds(seed=2382938, cuda=self.cuda)
|
|
81
|
+
if self.cuda:
|
|
82
|
+
self.model.cuda()
|
|
83
|
+
|
|
84
|
+
def run_until_early_stop(self):
|
|
85
|
+
self.run_until_stop(self.datasets, remember_best=True)
|
|
86
|
+
|
|
87
|
+
def run_until_second_stop(self):
|
|
88
|
+
datasets = self.datasets
|
|
89
|
+
datasets['train'] = concatenate_sets(datasets['train'],
|
|
90
|
+
datasets['valid'])
|
|
91
|
+
|
|
92
|
+
# Todo: actually keep remembering and in case of twice number of epochs
|
|
93
|
+
# reset to best model again (check if valid loss not below train loss)
|
|
94
|
+
self.run_until_stop(datasets, remember_best=False)
|
|
95
|
+
|
|
96
|
+
def run_until_stop(self, datasets, remember_best):
|
|
97
|
+
self.monitor_epoch(datasets)
|
|
98
|
+
self.print_epoch()
|
|
99
|
+
if remember_best:
|
|
100
|
+
self.rememberer.remember_epoch(self.epochs_df, self.model,
|
|
101
|
+
self.optimizer)
|
|
102
|
+
|
|
103
|
+
self.iterator.reset_rng()
|
|
104
|
+
while not self.stop_criterion.should_stop(self.epochs_df):
|
|
105
|
+
self.run_one_epoch(datasets, remember_best)
|
|
106
|
+
|
|
107
|
+
def run_one_epoch(self, datasets, remember_best):
|
|
108
|
+
batch_generator = self.iterator.get_batches(datasets['train'],
|
|
109
|
+
shuffle=True)
|
|
110
|
+
# TODO, add timing again?
|
|
111
|
+
for inputs, targets in batch_generator:
|
|
112
|
+
if self.batch_modifier is not None:
|
|
113
|
+
inputs, targets = self.batch_modifier.process(inputs,
|
|
114
|
+
targets)
|
|
115
|
+
# could happen that batch modifier has removed all inputs...
|
|
116
|
+
if len(inputs) > 0:
|
|
117
|
+
self.train_batch(inputs, targets)
|
|
118
|
+
|
|
119
|
+
self.monitor_epoch(datasets)
|
|
120
|
+
self.print_epoch()
|
|
121
|
+
if remember_best:
|
|
122
|
+
self.rememberer.remember_epoch(self.epochs_df, self.model,
|
|
123
|
+
self.optimizer)
|
|
124
|
+
|
|
125
|
+
def monitor_epoch(self, datasets):
|
|
126
|
+
result_dicts_per_monitor = OrderedDict()
|
|
127
|
+
for m in self.monitors:
|
|
128
|
+
result_dicts_per_monitor[m] = OrderedDict()
|
|
129
|
+
for m in self.monitors:
|
|
130
|
+
result_dict = m.monitor_epoch()
|
|
131
|
+
if result_dict is not None:
|
|
132
|
+
result_dicts_per_monitor[m].update(result_dict)
|
|
133
|
+
for setname in datasets:
|
|
134
|
+
assert setname in ['train', 'valid', 'test']
|
|
135
|
+
dataset = datasets[setname]
|
|
136
|
+
all_preds = []
|
|
137
|
+
all_losses = []
|
|
138
|
+
all_batch_sizes = []
|
|
139
|
+
all_targets = []
|
|
140
|
+
for batch in self.iterator.get_batches(dataset, shuffle=False):
|
|
141
|
+
preds, loss = self.eval_on_batch(batch[0], batch[1])
|
|
142
|
+
all_preds.append(preds)
|
|
143
|
+
all_losses.append(loss)
|
|
144
|
+
all_batch_sizes.append(len(batch[0]))
|
|
145
|
+
all_targets.append(batch[1])
|
|
146
|
+
|
|
147
|
+
for m in self.monitors:
|
|
148
|
+
result_dict = m.monitor_set(setname, all_preds, all_losses,
|
|
149
|
+
all_batch_sizes, all_targets,
|
|
150
|
+
dataset)
|
|
151
|
+
if result_dict is not None:
|
|
152
|
+
result_dicts_per_monitor[m].update(result_dict)
|
|
153
|
+
row_dict = OrderedDict()
|
|
154
|
+
for m in self.monitors:
|
|
155
|
+
row_dict.update(result_dicts_per_monitor[m])
|
|
156
|
+
self.epochs_df = self.epochs_df.append(row_dict, ignore_index=True)
|
|
157
|
+
assert set(self.epochs_df.columns) == set(row_dict.keys())
|
|
158
|
+
self.epochs_df = self.epochs_df[list(row_dict.keys())]
|
|
159
|
+
|
|
160
|
+
def print_epoch(self):
|
|
161
|
+
# -1 due to doing one monitor at start of training
|
|
162
|
+
i_epoch = len(self.epochs_df) - 1
|
|
163
|
+
log.info("Epoch {:d}".format(i_epoch))
|
|
164
|
+
last_row = self.epochs_df.iloc[-1]
|
|
165
|
+
for key, val in last_row.iteritems():
|
|
166
|
+
log.info("{:25s} {:.5f}".format(key, val))
|
|
167
|
+
log.info("")
|
|
168
|
+
|
|
169
|
+
def eval_on_batch(self, inputs, targets):
|
|
170
|
+
self.model.eval()
|
|
171
|
+
input_vars = np_to_var(inputs)
|
|
172
|
+
target_vars = np_to_var(targets)
|
|
173
|
+
if self.cuda:
|
|
174
|
+
input_vars = input_vars.cuda()
|
|
175
|
+
target_vars = target_vars.cuda()
|
|
176
|
+
outputs = self.model(input_vars)
|
|
177
|
+
loss = self.loss_function(outputs, target_vars)
|
|
178
|
+
outputs = outputs.cpu().data.numpy()
|
|
179
|
+
loss = loss.cpu().data.numpy()
|
|
180
|
+
return outputs, loss
|
|
181
|
+
|
|
182
|
+
def train_batch(self, inputs, targets):
|
|
183
|
+
self.model.train()
|
|
184
|
+
input_vars = np_to_var(inputs)
|
|
185
|
+
target_vars = np_to_var(targets)
|
|
186
|
+
if self.cuda:
|
|
187
|
+
input_vars = input_vars.cuda()
|
|
188
|
+
target_vars = target_vars.cuda()
|
|
189
|
+
self.optimizer.zero_grad()
|
|
190
|
+
outputs = self.model(input_vars)
|
|
191
|
+
loss = self.loss_function(outputs, target_vars)
|
|
192
|
+
loss.backward()
|
|
193
|
+
self.optimizer.step()
|
|
194
|
+
if self.model_constraint is not None:
|
|
195
|
+
self.model_constraint.apply(self.model)
|
|
196
|
+
|
|
197
|
+
def setup_after_stop_training(self):
|
|
198
|
+
# also remember old monitor chans, will be put back into
|
|
199
|
+
# monitor chans after experiment finished
|
|
200
|
+
self.before_stop_df = deepcopy(self.epochs_df)
|
|
201
|
+
self.rememberer.reset_to_best_model(self.epochs_df, self.model,
|
|
202
|
+
self.optimizer)
|
|
203
|
+
loss_to_reach = float(self.epochs_df['train_loss'].iloc[-1])
|
|
204
|
+
self.stop_criterion = Or(stop_criteria=[
|
|
205
|
+
MaxEpochs(max_epochs=self.rememberer.best_epoch * 2),
|
|
206
|
+
ColumnBelow(column_name='valid_loss', target_value=loss_to_reach)])
|
|
207
|
+
log.info("Train loss to reach {:.5f}".format(loss_to_reach))
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
from braindecode.trial_segment import compute_trial_start_stop_samples
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MisclassMonitor(object):
|
|
8
|
+
def __init__(self, exponentiate_preds=False, col_suffix='misclass'):
|
|
9
|
+
self.col_suffix = col_suffix
|
|
10
|
+
self.exponentiate_preds = exponentiate_preds
|
|
11
|
+
|
|
12
|
+
def monitor_epoch(self, ):
|
|
13
|
+
return
|
|
14
|
+
|
|
15
|
+
def monitor_set(self, setname, all_preds, all_losses,
|
|
16
|
+
all_batch_sizes, all_targets, dataset):
|
|
17
|
+
all_pred_labels = []
|
|
18
|
+
all_target_labels = []
|
|
19
|
+
for i_batch in range(len(all_batch_sizes)):
|
|
20
|
+
preds = all_preds[i_batch]
|
|
21
|
+
if True:
|
|
22
|
+
preds = np.exp(preds)
|
|
23
|
+
pred_labels = np.argmax(preds, axis=1)
|
|
24
|
+
all_pred_labels.extend(pred_labels)
|
|
25
|
+
targets = all_targets[i_batch]
|
|
26
|
+
# targets may be one-hot-encoded or not
|
|
27
|
+
if targets.ndim >= pred_labels.ndim:
|
|
28
|
+
targets = np.argmax(targets, axis=1)
|
|
29
|
+
assert targets.shape == pred_labels.shape
|
|
30
|
+
all_target_labels.extend(targets)
|
|
31
|
+
all_pred_labels = np.array(all_pred_labels)
|
|
32
|
+
all_target_labels = np.array(all_target_labels)
|
|
33
|
+
assert all_pred_labels.shape == all_target_labels.shape
|
|
34
|
+
|
|
35
|
+
misclass = 1 - np.mean(all_target_labels == all_pred_labels)
|
|
36
|
+
column_name = "{:s}_{:s}".format(setname, self.col_suffix)
|
|
37
|
+
return {column_name: float(misclass)}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class LossMonitor(object):
|
|
41
|
+
def monitor_epoch(self,):
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
def monitor_set(self, setname, all_preds, all_losses,
|
|
45
|
+
all_batch_sizes, all_targets, dataset):
|
|
46
|
+
batch_weights = np.array(all_batch_sizes)/ np.sum(all_batch_sizes)
|
|
47
|
+
loss_per_batch = [np.mean(loss) for loss in all_losses]
|
|
48
|
+
mean_loss = np.sum(batch_weights * loss_per_batch)
|
|
49
|
+
column_name = "{:s}_loss".format(setname)
|
|
50
|
+
return {column_name: mean_loss}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class CroppedTrialMisclassMonitor(object):
|
|
54
|
+
def __init__(self, input_time_length=None):
|
|
55
|
+
self.input_time_length = input_time_length
|
|
56
|
+
|
|
57
|
+
def monitor_epoch(self,):
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
def monitor_set(self, setname, all_preds, all_losses,
|
|
61
|
+
all_batch_sizes, all_targets, dataset):
|
|
62
|
+
"""Assuming one hot encoding for now"""
|
|
63
|
+
assert self.input_time_length is not None, "Need to know input time length..."
|
|
64
|
+
all_pred_labels = self.compute_pred_labels(dataset, all_preds)
|
|
65
|
+
assert all_pred_labels.shape == dataset.y.shape
|
|
66
|
+
misclass = 1 - np.mean(all_pred_labels == dataset.y)
|
|
67
|
+
column_name = "{:s}_misclass".format(setname)
|
|
68
|
+
return {column_name: float(misclass)}
|
|
69
|
+
|
|
70
|
+
def compute_pred_labels(self, dataset, all_preds,):
|
|
71
|
+
n_preds_per_input = all_preds[0].shape[2]
|
|
72
|
+
n_receptive_field = self.input_time_length - n_preds_per_input + 1
|
|
73
|
+
|
|
74
|
+
n_preds_per_trial = [trial.shape[1] - n_receptive_field + 1
|
|
75
|
+
for trial in dataset.X]
|
|
76
|
+
preds_per_trial = compute_preds_per_trial_from_n_preds_per_trial(
|
|
77
|
+
all_preds, n_preds_per_trial)
|
|
78
|
+
all_pred_labels = [np.argmax(np.mean(p, axis=1))
|
|
79
|
+
for p in preds_per_trial]
|
|
80
|
+
|
|
81
|
+
all_pred_labels = np.array(all_pred_labels)
|
|
82
|
+
assert all_pred_labels.shape == dataset.y.shape
|
|
83
|
+
return all_pred_labels
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def compute_preds_per_trial_for_set(all_preds, input_time_length,
|
|
87
|
+
dataset, ):
|
|
88
|
+
n_preds_per_input = all_preds[0].shape[2]
|
|
89
|
+
n_receptive_field = input_time_length - n_preds_per_input + 1
|
|
90
|
+
n_preds_per_trial = [trial.shape[1] - n_receptive_field + 1
|
|
91
|
+
for trial in dataset.X]
|
|
92
|
+
preds_per_trial = compute_preds_per_trial_from_n_preds_per_trial(
|
|
93
|
+
all_preds, n_preds_per_trial)
|
|
94
|
+
return preds_per_trial
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def compute_preds_per_trial_from_n_preds_per_trial(
|
|
98
|
+
all_preds, n_preds_per_trial):
|
|
99
|
+
# all_preds_arr has shape forward_passes x classes x time
|
|
100
|
+
all_preds_arr = np.concatenate(all_preds, axis=0)
|
|
101
|
+
preds_per_trial = []
|
|
102
|
+
i_pred_block = 0
|
|
103
|
+
for i_trial in range(len(n_preds_per_trial)):
|
|
104
|
+
n_needed_preds = n_preds_per_trial[i_trial]
|
|
105
|
+
preds_this_trial = []
|
|
106
|
+
while n_needed_preds > 0:
|
|
107
|
+
# - n_needed_preds: only has an effect
|
|
108
|
+
# in case there are more samples than we actually still need
|
|
109
|
+
# in the block.
|
|
110
|
+
# That can happen since final block of a trial can overlap
|
|
111
|
+
# with block before so we can have some redundant preds.
|
|
112
|
+
pred_samples = all_preds_arr[i_pred_block,:,
|
|
113
|
+
-n_needed_preds:]
|
|
114
|
+
preds_this_trial.append(pred_samples)
|
|
115
|
+
n_needed_preds -= pred_samples.shape[1]
|
|
116
|
+
i_pred_block += 1
|
|
117
|
+
|
|
118
|
+
preds_this_trial = np.concatenate(preds_this_trial, axis=1)
|
|
119
|
+
preds_per_trial.append(preds_this_trial)
|
|
120
|
+
assert i_pred_block == len(all_preds_arr), (
|
|
121
|
+
"Expect that all prediction forward passes are needed, "
|
|
122
|
+
"used {:d}, existing {:d}".format(
|
|
123
|
+
i_pred_block, len(all_preds_arr)))
|
|
124
|
+
return preds_per_trial
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class RuntimeMonitor(object):
|
|
128
|
+
def __init__(self):
|
|
129
|
+
self.last_call_time = None
|
|
130
|
+
|
|
131
|
+
def monitor_epoch(self,):
|
|
132
|
+
cur_time = time.time()
|
|
133
|
+
if self.last_call_time is None:
|
|
134
|
+
# just in case of first call
|
|
135
|
+
self.last_call_time = cur_time
|
|
136
|
+
epoch_runtime = cur_time - self.last_call_time
|
|
137
|
+
self.last_call_time = cur_time
|
|
138
|
+
return {'runtime': epoch_runtime}
|
|
139
|
+
|
|
140
|
+
def monitor_set(self, setname, all_preds, all_losses,
|
|
141
|
+
all_batch_sizes, all_targets, dataset):
|
|
142
|
+
return {}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MaxEpochs(object):
|
|
5
|
+
def __init__(self, max_epochs):
|
|
6
|
+
self.max_epochs = max_epochs
|
|
7
|
+
|
|
8
|
+
def should_stop(self, epochs_df):
|
|
9
|
+
# Keep in mind epoch 0 without training is also part of dataframe
|
|
10
|
+
return len(epochs_df) - 1 >= self.max_epochs
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Or(object):
|
|
14
|
+
def __init__(self, stop_criteria):
|
|
15
|
+
self.stop_criteria = stop_criteria
|
|
16
|
+
|
|
17
|
+
def should_stop(self, epochs_df):
|
|
18
|
+
return np.any([s.should_stop(epochs_df)
|
|
19
|
+
for s in self.stop_criteria])
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class NoDecrease(object):
|
|
23
|
+
""" Stops if there is no decrease on a given monitor channel
|
|
24
|
+
for given number of epochs."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, column_name, num_epochs, min_decrease=1e-6):
|
|
27
|
+
self.column_name = column_name
|
|
28
|
+
self.num_epochs = num_epochs
|
|
29
|
+
self.min_decrease = min_decrease
|
|
30
|
+
self.best_epoch = 0
|
|
31
|
+
self.lowest_val = float('inf')
|
|
32
|
+
|
|
33
|
+
def should_stop(self, epochs_df):
|
|
34
|
+
# -1 due to doing one monitor at start of training
|
|
35
|
+
i_epoch = len(epochs_df) - 1
|
|
36
|
+
current_val = float(epochs_df[self.column_name].iloc[-1])
|
|
37
|
+
if current_val < ((1 - self.min_decrease) * self.lowest_val):
|
|
38
|
+
self.best_epoch = i_epoch
|
|
39
|
+
self.lowest_val = current_val
|
|
40
|
+
|
|
41
|
+
return (i_epoch - self.best_epoch) >= self.num_epochs
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ColumnBelow():
|
|
45
|
+
""" Stops if the given column is below the given value."""
|
|
46
|
+
def __init__(self, column_name, target_value):
|
|
47
|
+
self.column_name = column_name
|
|
48
|
+
self.target_value = target_value
|
|
49
|
+
|
|
50
|
+
def should_stop(self, epochs_df):
|
|
51
|
+
# -1 due to doing one monitor at start of training
|
|
52
|
+
current_val = float(epochs_df[self.column_name].iloc[-1])
|
|
53
|
+
return current_val < self.target_value
|
|
File without changes
|