braindecode 0.3.2__tar.gz → 0.4.0__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.3.2 → Braindecode-0.4.0}/Braindecode.egg-info/PKG-INFO +1 -1
- {Braindecode-0.3.2 → Braindecode-0.4.0}/Braindecode.egg-info/SOURCES.txt +10 -2
- {Braindecode-0.3.2 → Braindecode-0.4.0}/PKG-INFO +1 -1
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/datautil/iterators.py +18 -10
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/experiments/experiment.py +65 -39
- Braindecode-0.4.0/braindecode/experiments/loggers.py +48 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/experiments/monitors.py +69 -11
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/experiments/stopcriteria.py +41 -4
- Braindecode-0.4.0/braindecode/models/base.py +276 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/models/deep4.py +16 -14
- Braindecode-0.4.0/braindecode/models/eegnet.py +236 -0
- Braindecode-0.4.0/braindecode/models/hybrid.py +77 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/models/shallow_fbcsp.py +11 -10
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/torch_ext/init.py +2 -2
- Braindecode-0.4.0/braindecode/torch_ext/optimizers.py +77 -0
- Braindecode-0.4.0/braindecode/torch_ext/schedulers.py +65 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/torch_ext/util.py +11 -11
- Braindecode-0.4.0/braindecode/version.py +1 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/conf.py +1 -1
- {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/index.rst +3 -2
- Braindecode-0.4.0/docs/notebooks/Cropped_Decoding.ipynb +980 -0
- Braindecode-0.3.2/docs/notebooks/Cropped_Decoding.ipynb → Braindecode-0.4.0/docs/notebooks/Cropped_Manual_Training_Loop.ipynb +199 -114
- Braindecode-0.4.0/docs/notebooks/Cropped_Trialwise_Explanation_Figures.ipynb +382 -0
- Braindecode-0.4.0/docs/notebooks/Trialwise_Decoding.ipynb +1034 -0
- Braindecode-0.3.2/docs/notebooks/TrialWise_Decoding.ipynb → Braindecode-0.4.0/docs/notebooks/Trialwise_Manual_Training_Loop.ipynb +244 -164
- Braindecode-0.4.0/docs/notebooks/visualization/Perturbation.ipynb +855 -0
- Braindecode-0.3.2/docs/notebooks/visualization/Perturbation.ipynb → Braindecode-0.4.0/docs/notebooks/visualization/Perturbation_Old.ipynb +63 -25
- {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/source/braindecode.experiments.rst +8 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/source/braindecode.models.rst +16 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/source/braindecode.torch_ext.rst +16 -0
- Braindecode-0.3.2/braindecode/models/eegnet.py +0 -115
- Braindecode-0.3.2/braindecode/version.py +0 -1
- Braindecode-0.3.2/docs/notebooks/Experiment_Class.ipynb +0 -804
- {Braindecode-0.3.2 → Braindecode-0.4.0}/Braindecode.egg-info/dependency_links.txt +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/Braindecode.egg-info/not-zip-safe +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/Braindecode.egg-info/requires.txt +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/Braindecode.egg-info/top_level.txt +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/LICENSE.txt +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/MANIFEST.in +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/README.rst +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/__init__.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/datasets/__init__.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/datasets/bbci.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/datasets/bcic_iv_2a.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/datasets/multiple.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/datasets/sensor_positions.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/datautil/__init__.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/datautil/signal_target.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/datautil/signalproc.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/datautil/splitters.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/datautil/trial_segment.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/datautil/util.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/experiments/__init__.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/mne_ext/__init__.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/mne_ext/signalproc.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/models/__init__.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/models/util.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/torch_ext/__init__.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/torch_ext/constraints.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/torch_ext/functions.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/torch_ext/losses.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/torch_ext/modules.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/util.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/visualization/__init__.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/visualization/input_windows.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/visualization/perturbation.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/visualization/plot.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/visualization/sinfit.py +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/Makefile +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/notebooks/BBCI_Data.ipynb +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/notebooks/BBCI_Data_Start_Stop.ipynb +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/notebooks/BBCI_Data_Trialwise.ipynb +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/source/braindecode.datasets.rst +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/source/braindecode.datautil.rst +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/source/braindecode.mne_ext.rst +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/source/braindecode.rst +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/source/braindecode.visualization.rst +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/source/modules.rst +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/setup.cfg +0 -0
- {Braindecode-0.3.2 → Braindecode-0.4.0}/setup.py +0 -0
|
@@ -25,13 +25,16 @@ braindecode/datautil/trial_segment.py
|
|
|
25
25
|
braindecode/datautil/util.py
|
|
26
26
|
braindecode/experiments/__init__.py
|
|
27
27
|
braindecode/experiments/experiment.py
|
|
28
|
+
braindecode/experiments/loggers.py
|
|
28
29
|
braindecode/experiments/monitors.py
|
|
29
30
|
braindecode/experiments/stopcriteria.py
|
|
30
31
|
braindecode/mne_ext/__init__.py
|
|
31
32
|
braindecode/mne_ext/signalproc.py
|
|
32
33
|
braindecode/models/__init__.py
|
|
34
|
+
braindecode/models/base.py
|
|
33
35
|
braindecode/models/deep4.py
|
|
34
36
|
braindecode/models/eegnet.py
|
|
37
|
+
braindecode/models/hybrid.py
|
|
35
38
|
braindecode/models/shallow_fbcsp.py
|
|
36
39
|
braindecode/models/util.py
|
|
37
40
|
braindecode/torch_ext/__init__.py
|
|
@@ -40,6 +43,8 @@ braindecode/torch_ext/functions.py
|
|
|
40
43
|
braindecode/torch_ext/init.py
|
|
41
44
|
braindecode/torch_ext/losses.py
|
|
42
45
|
braindecode/torch_ext/modules.py
|
|
46
|
+
braindecode/torch_ext/optimizers.py
|
|
47
|
+
braindecode/torch_ext/schedulers.py
|
|
43
48
|
braindecode/torch_ext/util.py
|
|
44
49
|
braindecode/visualization/__init__.py
|
|
45
50
|
braindecode/visualization/input_windows.py
|
|
@@ -53,9 +58,12 @@ docs/notebooks/BBCI_Data.ipynb
|
|
|
53
58
|
docs/notebooks/BBCI_Data_Start_Stop.ipynb
|
|
54
59
|
docs/notebooks/BBCI_Data_Trialwise.ipynb
|
|
55
60
|
docs/notebooks/Cropped_Decoding.ipynb
|
|
56
|
-
docs/notebooks/
|
|
57
|
-
docs/notebooks/
|
|
61
|
+
docs/notebooks/Cropped_Manual_Training_Loop.ipynb
|
|
62
|
+
docs/notebooks/Cropped_Trialwise_Explanation_Figures.ipynb
|
|
63
|
+
docs/notebooks/Trialwise_Decoding.ipynb
|
|
64
|
+
docs/notebooks/Trialwise_Manual_Training_Loop.ipynb
|
|
58
65
|
docs/notebooks/visualization/Perturbation.ipynb
|
|
66
|
+
docs/notebooks/visualization/Perturbation_Old.ipynb
|
|
59
67
|
docs/source/braindecode.datasets.rst
|
|
60
68
|
docs/source/braindecode.datautil.rst
|
|
61
69
|
docs/source/braindecode.experiments.rst
|
|
@@ -15,7 +15,6 @@ def get_balanced_batches(n_trials, rng, shuffle, n_batches=None,
|
|
|
15
15
|
n_trials : int
|
|
16
16
|
Size of set.
|
|
17
17
|
rng : RandomState
|
|
18
|
-
|
|
19
18
|
shuffle : bool
|
|
20
19
|
Whether to shuffle indices before splitting set.
|
|
21
20
|
n_batches : int, optional
|
|
@@ -64,10 +63,14 @@ class BalancedBatchSizeIterator(object):
|
|
|
64
63
|
Resulting batches will not necessarily have the given batch size
|
|
65
64
|
but rather the next largest batch size that allows to split the set into
|
|
66
65
|
balanced batches (maximum size difference 1).
|
|
66
|
+
seed: int
|
|
67
|
+
Random seed for initialization of `numpy.RandomState` random generator
|
|
68
|
+
that shuffles the batches.
|
|
67
69
|
"""
|
|
68
|
-
def __init__(self, batch_size):
|
|
70
|
+
def __init__(self, batch_size, seed=328774):
|
|
69
71
|
self.batch_size = batch_size
|
|
70
|
-
self.
|
|
72
|
+
self.seed = seed
|
|
73
|
+
self.rng = RandomState(self.seed)
|
|
71
74
|
|
|
72
75
|
def get_batches(self, dataset, shuffle):
|
|
73
76
|
n_trials = dataset.X.shape[0]
|
|
@@ -85,7 +88,7 @@ class BalancedBatchSizeIterator(object):
|
|
|
85
88
|
yield (batch_X, batch_y)
|
|
86
89
|
|
|
87
90
|
def reset_rng(self):
|
|
88
|
-
self.rng = RandomState(
|
|
91
|
+
self.rng = RandomState(self.seed)
|
|
89
92
|
|
|
90
93
|
|
|
91
94
|
class ClassBalancedBatchSizeIterator(object):
|
|
@@ -100,11 +103,15 @@ class ClassBalancedBatchSizeIterator(object):
|
|
|
100
103
|
Resulting batches will not necessarily have the given batch size
|
|
101
104
|
but rather the next largest batch size that allows to split the set into
|
|
102
105
|
balanced batches (maximum size difference 1).
|
|
106
|
+
seed: int
|
|
107
|
+
Random seed for initialization of `numpy.RandomState` random generator
|
|
108
|
+
that shuffles the batches.
|
|
103
109
|
"""
|
|
104
110
|
|
|
105
|
-
def __init__(self, batch_size):
|
|
111
|
+
def __init__(self, batch_size, seed=328774):
|
|
106
112
|
self.batch_size = batch_size
|
|
107
|
-
self.
|
|
113
|
+
self.seed = seed
|
|
114
|
+
self.rng = RandomState(self.seed)
|
|
108
115
|
|
|
109
116
|
def get_batches(self, dataset, shuffle):
|
|
110
117
|
n_trials = dataset.X.shape[0]
|
|
@@ -138,7 +145,7 @@ class ClassBalancedBatchSizeIterator(object):
|
|
|
138
145
|
yield (batch_X, batch_y)
|
|
139
146
|
|
|
140
147
|
def reset_rng(self):
|
|
141
|
-
self.rng = RandomState(
|
|
148
|
+
self.rng = RandomState(self.seed)
|
|
142
149
|
|
|
143
150
|
|
|
144
151
|
class CropsFromTrialsIterator(object):
|
|
@@ -149,7 +156,7 @@ class CropsFromTrialsIterator(object):
|
|
|
149
156
|
Predicting the given input batches can lead to some samples
|
|
150
157
|
being predicted multiple times, if the receptive field size
|
|
151
158
|
(input_time_length - n_preds_per_input + 1) is not a divisor
|
|
152
|
-
of the trial length. :func:`
|
|
159
|
+
of the trial length. :func:`compute_preds_per_trial_from_crops`
|
|
153
160
|
can help with removing the overlapped predictions again for evaluation.
|
|
154
161
|
|
|
155
162
|
Parameters
|
|
@@ -163,11 +170,12 @@ class CropsFromTrialsIterator(object):
|
|
|
163
170
|
by making a forward pass with the given input time length, the
|
|
164
171
|
output length in 3rd dimension is n_preds_per_input.
|
|
165
172
|
seed: int
|
|
166
|
-
Random seed for initialization of `numpy.RandomState
|
|
173
|
+
Random seed for initialization of `numpy.RandomState` random generator
|
|
174
|
+
that shuffles the batches.
|
|
167
175
|
|
|
168
176
|
See Also
|
|
169
177
|
--------
|
|
170
|
-
braindecode.experiments.monitors.
|
|
178
|
+
braindecode.experiments.monitors.compute_preds_per_trial_from_crops : Assigns predictions to trials, removes overlaps.
|
|
171
179
|
"""
|
|
172
180
|
def __init__(self, batch_size, input_time_length, n_preds_per_input,
|
|
173
181
|
seed=(2017, 6, 28)):
|
|
@@ -7,6 +7,7 @@ import pandas as pd
|
|
|
7
7
|
import torch as th
|
|
8
8
|
|
|
9
9
|
from braindecode.datautil.splitters import concatenate_sets
|
|
10
|
+
from braindecode.experiments.loggers import Printer
|
|
10
11
|
from braindecode.experiments.stopcriteria import MaxEpochs, ColumnBelow, Or
|
|
11
12
|
from braindecode.torch_ext.util import np_to_var, set_random_seeds
|
|
12
13
|
|
|
@@ -109,7 +110,7 @@ class Experiment(object):
|
|
|
109
110
|
(predictions: `torch.autograd.Variable`,
|
|
110
111
|
targets:`torch.autograd.Variable`)
|
|
111
112
|
-> loss: `torch.autograd.Variable`
|
|
112
|
-
optimizer: torch.optim.Optimizer`
|
|
113
|
+
optimizer: `torch.optim.Optimizer`
|
|
113
114
|
model_constraint: object
|
|
114
115
|
Object with apply function that takes model and constraints its
|
|
115
116
|
parameters. `None` for no constraint.
|
|
@@ -137,6 +138,16 @@ class Experiment(object):
|
|
|
137
138
|
do_early_stop: bool
|
|
138
139
|
Whether to do an early stop at all. If true, reset to best model
|
|
139
140
|
even in case experiment does not run after early stop.
|
|
141
|
+
reset_after_second_run: bool
|
|
142
|
+
If true, reset to best model when second run did not find a valid loss
|
|
143
|
+
below or equal to the best train loss of first run.
|
|
144
|
+
log_0_epoch: bool
|
|
145
|
+
Whether to compute monitor values and log them before the
|
|
146
|
+
start of training.
|
|
147
|
+
loggers: list of :class:`.Logger`
|
|
148
|
+
How to show computed metrics.
|
|
149
|
+
seed: int
|
|
150
|
+
Random seed for python random module numpy.random and torch.
|
|
140
151
|
|
|
141
152
|
Attributes
|
|
142
153
|
----------
|
|
@@ -149,10 +160,14 @@ class Experiment(object):
|
|
|
149
160
|
run_after_early_stop,
|
|
150
161
|
model_loss_function=None,
|
|
151
162
|
batch_modifier=None, cuda=True, pin_memory=False,
|
|
152
|
-
do_early_stop=True
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
163
|
+
do_early_stop=True,
|
|
164
|
+
reset_after_second_run=False,
|
|
165
|
+
log_0_epoch=True,
|
|
166
|
+
loggers=('print',),
|
|
167
|
+
seed=2382938):
|
|
168
|
+
if run_after_early_stop or reset_after_second_run:
|
|
169
|
+
assert do_early_stop == True, ("Can only run after early stop or "
|
|
170
|
+
"reset after second run if doing an early stop")
|
|
156
171
|
if do_early_stop:
|
|
157
172
|
assert valid_set is not None
|
|
158
173
|
assert remember_best_column is not None
|
|
@@ -163,6 +178,9 @@ class Experiment(object):
|
|
|
163
178
|
self.datasets.pop('valid')
|
|
164
179
|
assert run_after_early_stop == False
|
|
165
180
|
assert do_early_stop == False
|
|
181
|
+
if test_set is None:
|
|
182
|
+
self.datasets.pop('test')
|
|
183
|
+
|
|
166
184
|
self.iterator = iterator
|
|
167
185
|
self.loss_function = loss_function
|
|
168
186
|
self.optimizer = optimizer
|
|
@@ -179,7 +197,10 @@ class Experiment(object):
|
|
|
179
197
|
self.rememberer = None
|
|
180
198
|
self.pin_memory = pin_memory
|
|
181
199
|
self.do_early_stop = do_early_stop
|
|
182
|
-
|
|
200
|
+
self.reset_after_second_run = reset_after_second_run
|
|
201
|
+
self.log_0_epoch = log_0_epoch
|
|
202
|
+
self.seed = seed
|
|
203
|
+
self.loggers = loggers
|
|
183
204
|
|
|
184
205
|
def run(self):
|
|
185
206
|
"""
|
|
@@ -195,7 +216,17 @@ class Experiment(object):
|
|
|
195
216
|
self.setup_after_stop_training()
|
|
196
217
|
if self.run_after_early_stop:
|
|
197
218
|
log.info("Run until second stop...")
|
|
219
|
+
loss_to_reach = float(self.epochs_df['train_loss'].iloc[-1])
|
|
198
220
|
self.run_until_second_stop()
|
|
221
|
+
if self.reset_after_second_run:
|
|
222
|
+
# if no valid loss was found below the best train loss on 1st
|
|
223
|
+
# run, reset model to the epoch with lowest valid_misclass
|
|
224
|
+
if float(self.epochs_df['valid_loss'].iloc[-1]) > loss_to_reach:
|
|
225
|
+
log.info("Resetting to best epoch {:d}".format(
|
|
226
|
+
self.rememberer.best_epoch))
|
|
227
|
+
self.rememberer.reset_to_best_model(self.epochs_df,
|
|
228
|
+
self.model,
|
|
229
|
+
self.optimizer)
|
|
199
230
|
|
|
200
231
|
def setup_training(self):
|
|
201
232
|
"""
|
|
@@ -205,8 +236,10 @@ class Experiment(object):
|
|
|
205
236
|
# reset remember best extension in case you rerun some experiment
|
|
206
237
|
if self.do_early_stop:
|
|
207
238
|
self.rememberer = RememberBest(self.remember_best_column)
|
|
239
|
+
if self.loggers == ('print',):
|
|
240
|
+
self.loggers = [Printer()]
|
|
208
241
|
self.epochs_df = pd.DataFrame()
|
|
209
|
-
set_random_seeds(seed=
|
|
242
|
+
set_random_seeds(seed=self.seed, cuda=self.cuda)
|
|
210
243
|
if self.cuda:
|
|
211
244
|
assert th.cuda.is_available(), "Cuda not available"
|
|
212
245
|
self.model.cuda()
|
|
@@ -231,9 +264,7 @@ class Experiment(object):
|
|
|
231
264
|
datasets['train'] = concatenate_sets([datasets['train'],
|
|
232
265
|
datasets['valid']])
|
|
233
266
|
|
|
234
|
-
|
|
235
|
-
# reset to best model again (check if valid loss not below train loss)
|
|
236
|
-
self.run_until_stop(datasets, remember_best=False)
|
|
267
|
+
self.run_until_stop(datasets, remember_best=True)
|
|
237
268
|
|
|
238
269
|
def run_until_stop(self, datasets, remember_best):
|
|
239
270
|
"""
|
|
@@ -248,11 +279,12 @@ class Experiment(object):
|
|
|
248
279
|
remember_best: bool
|
|
249
280
|
Whether to remember parameters at best epoch.
|
|
250
281
|
"""
|
|
251
|
-
self.
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
282
|
+
if self.log_0_epoch:
|
|
283
|
+
self.monitor_epoch(datasets)
|
|
284
|
+
self.log_epoch()
|
|
285
|
+
if remember_best:
|
|
286
|
+
self.rememberer.remember_epoch(self.epochs_df, self.model,
|
|
287
|
+
self.optimizer)
|
|
256
288
|
|
|
257
289
|
self.iterator.reset_rng()
|
|
258
290
|
while not self.stop_criterion.should_stop(self.epochs_df):
|
|
@@ -285,7 +317,7 @@ class Experiment(object):
|
|
|
285
317
|
end_train_epoch_time - start_train_epoch_time))
|
|
286
318
|
|
|
287
319
|
self.monitor_epoch(datasets)
|
|
288
|
-
self.
|
|
320
|
+
self.log_epoch()
|
|
289
321
|
if remember_best:
|
|
290
322
|
self.rememberer.remember_epoch(self.epochs_df, self.model,
|
|
291
323
|
self.optimizer)
|
|
@@ -331,21 +363,20 @@ class Experiment(object):
|
|
|
331
363
|
|
|
332
364
|
"""
|
|
333
365
|
self.model.eval()
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
loss = loss.cpu().data.numpy()
|
|
366
|
+
with th.no_grad():
|
|
367
|
+
input_vars = np_to_var(inputs, pin_memory=self.pin_memory)
|
|
368
|
+
target_vars = np_to_var(targets, pin_memory=self.pin_memory)
|
|
369
|
+
if self.cuda:
|
|
370
|
+
input_vars = input_vars.cuda()
|
|
371
|
+
target_vars = target_vars.cuda()
|
|
372
|
+
outputs = self.model(input_vars)
|
|
373
|
+
loss = self.loss_function(outputs, target_vars)
|
|
374
|
+
if hasattr(outputs, 'cpu'):
|
|
375
|
+
outputs = outputs.cpu().data.numpy()
|
|
376
|
+
else:
|
|
377
|
+
# assume it is iterable
|
|
378
|
+
outputs = [o.cpu().data.numpy() for o in outputs]
|
|
379
|
+
loss = loss.cpu().data.numpy()
|
|
349
380
|
return outputs, loss
|
|
350
381
|
|
|
351
382
|
def monitor_epoch(self, datasets):
|
|
@@ -395,17 +426,12 @@ class Experiment(object):
|
|
|
395
426
|
assert set(self.epochs_df.columns) == set(row_dict.keys())
|
|
396
427
|
self.epochs_df = self.epochs_df[list(row_dict.keys())]
|
|
397
428
|
|
|
398
|
-
def
|
|
429
|
+
def log_epoch(self):
|
|
399
430
|
"""
|
|
400
431
|
Print monitoring values for this epoch.
|
|
401
432
|
"""
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
log.info("Epoch {:d}".format(i_epoch))
|
|
405
|
-
last_row = self.epochs_df.iloc[-1]
|
|
406
|
-
for key, val in last_row.iteritems():
|
|
407
|
-
log.info("{:25s} {:.5f}".format(key, val))
|
|
408
|
-
log.info("")
|
|
433
|
+
for logger in self.loggers:
|
|
434
|
+
logger.log_epoch(self.epochs_df)
|
|
409
435
|
|
|
410
436
|
def setup_after_stop_training(self):
|
|
411
437
|
"""
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
log = logging.getLogger(__name__)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Logger(ABC):
|
|
8
|
+
@abstractmethod
|
|
9
|
+
def log_epoch(self, epochs_df):
|
|
10
|
+
raise NotImplementedError("Need to implement the log_epoch function!")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Printer(Logger):
|
|
14
|
+
"""
|
|
15
|
+
Prints output to the terminal using Python's logging module.
|
|
16
|
+
"""
|
|
17
|
+
def log_epoch(self, epochs_df):
|
|
18
|
+
# -1 due to doing one monitor at start of training
|
|
19
|
+
i_epoch = len(epochs_df) - 1
|
|
20
|
+
log.info("Epoch {:d}".format(i_epoch))
|
|
21
|
+
last_row = epochs_df.iloc[-1]
|
|
22
|
+
for key, val in last_row.iteritems():
|
|
23
|
+
log.info("{:25s} {:.5f}".format(key, val))
|
|
24
|
+
log.info("")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TensorboardWriter(Logger):
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
Logs all values for tensorboard visualiuzation using tensorboardX.
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
log_dir: string
|
|
35
|
+
Directory path to log the output to
|
|
36
|
+
"""
|
|
37
|
+
def __init__(self, log_dir):
|
|
38
|
+
# import inside to prevent dependency of braindecode onto tensorboardX
|
|
39
|
+
from tensorboardX import SummaryWriter
|
|
40
|
+
self.writer = SummaryWriter(log_dir)
|
|
41
|
+
|
|
42
|
+
def log_epoch(self, epochs_df):
|
|
43
|
+
# -1 due to doing one monitor at start of training
|
|
44
|
+
i_epoch = len(epochs_df) - 1
|
|
45
|
+
last_row = epochs_df.iloc[-1]
|
|
46
|
+
for key, val in last_row.iteritems():
|
|
47
|
+
val = last_row[key]
|
|
48
|
+
self.writer.add_scalar(key, val, i_epoch)
|
|
@@ -70,6 +70,34 @@ class MisclassMonitor(object):
|
|
|
70
70
|
return {column_name: float(misclass)}
|
|
71
71
|
|
|
72
72
|
|
|
73
|
+
def compute_pred_labels_from_trial_preds(
|
|
74
|
+
all_preds, threshold_for_binary_case=None):
|
|
75
|
+
all_pred_labels = []
|
|
76
|
+
for i_batch in range(len(all_preds)):
|
|
77
|
+
preds = all_preds[i_batch]
|
|
78
|
+
# preds could be examples x classes x time
|
|
79
|
+
# or just
|
|
80
|
+
# examples x classes
|
|
81
|
+
# make sure not to remove first dimension if it only has size one
|
|
82
|
+
if preds.ndim > 1:
|
|
83
|
+
only_one_row = preds.shape[0] == 1
|
|
84
|
+
|
|
85
|
+
pred_labels = np.argmax(preds, axis=1).squeeze()
|
|
86
|
+
# add first dimension again if needed
|
|
87
|
+
if only_one_row:
|
|
88
|
+
pred_labels = pred_labels[None]
|
|
89
|
+
else:
|
|
90
|
+
assert threshold_for_binary_case is not None, (
|
|
91
|
+
"In case of only one output, please supply the "
|
|
92
|
+
"threshold_for_binary_case parameter")
|
|
93
|
+
# binary classification case... assume logits
|
|
94
|
+
pred_labels = np.int32(preds > threshold_for_binary_case)
|
|
95
|
+
# now examples x time or examples
|
|
96
|
+
all_pred_labels.extend(pred_labels)
|
|
97
|
+
all_pred_labels = np.array(all_pred_labels)
|
|
98
|
+
return all_pred_labels
|
|
99
|
+
|
|
100
|
+
|
|
73
101
|
class AveragePerClassMisclassMonitor(object):
|
|
74
102
|
"""
|
|
75
103
|
Compute average of misclasses per class,
|
|
@@ -148,6 +176,7 @@ class LossMonitor(object):
|
|
|
148
176
|
return {column_name: mean_loss}
|
|
149
177
|
|
|
150
178
|
|
|
179
|
+
|
|
151
180
|
class CroppedTrialMisclassMonitor(object):
|
|
152
181
|
"""
|
|
153
182
|
Compute trialwise misclasses from predictions for crops.
|
|
@@ -169,9 +198,10 @@ class CroppedTrialMisclassMonitor(object):
|
|
|
169
198
|
assert self.input_time_length is not None, "Need to know input time length..."
|
|
170
199
|
# First case that each trial only has a single label
|
|
171
200
|
if not hasattr(dataset.y[0], '__len__'):
|
|
172
|
-
all_pred_labels =
|
|
173
|
-
|
|
201
|
+
all_pred_labels = compute_trial_labels_from_crop_preds(
|
|
202
|
+
all_preds, self.input_time_length, dataset.X)
|
|
174
203
|
assert all_pred_labels.shape == dataset.y.shape
|
|
204
|
+
all_trial_labels = dataset.y
|
|
175
205
|
else:
|
|
176
206
|
all_trial_labels, all_pred_labels = (
|
|
177
207
|
self._compute_trial_pred_labels_from_cnt_y(dataset, all_preds))
|
|
@@ -181,8 +211,8 @@ class CroppedTrialMisclassMonitor(object):
|
|
|
181
211
|
return {column_name: float(misclass)}
|
|
182
212
|
|
|
183
213
|
def _compute_pred_labels(self, dataset, all_preds, ):
|
|
184
|
-
preds_per_trial =
|
|
185
|
-
all_preds, self.input_time_length, dataset)
|
|
214
|
+
preds_per_trial = compute_preds_per_trial_from_crops(
|
|
215
|
+
all_preds, self.input_time_length, dataset.X)
|
|
186
216
|
all_pred_labels = [np.argmax(np.mean(p, axis=1))
|
|
187
217
|
for p in preds_per_trial]
|
|
188
218
|
all_pred_labels = np.array(all_pred_labels)
|
|
@@ -194,8 +224,8 @@ class CroppedTrialMisclassMonitor(object):
|
|
|
194
224
|
# we only want the preds that are for the same labels as the last label in y
|
|
195
225
|
# (there might be parts of other class-data at start, for trialwise misclass we assume
|
|
196
226
|
# they are contained in other trials at the end...)
|
|
197
|
-
preds_per_trial =
|
|
198
|
-
all_preds, self.input_time_length, dataset)
|
|
227
|
+
preds_per_trial = compute_preds_per_trial_from_crops(
|
|
228
|
+
all_preds, self.input_time_length, dataset.X)
|
|
199
229
|
trial_labels = []
|
|
200
230
|
trial_pred_labels = []
|
|
201
231
|
for trial_pred, trial_y in zip(preds_per_trial, dataset.y):
|
|
@@ -217,8 +247,36 @@ class CroppedTrialMisclassMonitor(object):
|
|
|
217
247
|
return trial_labels, trial_pred_labels
|
|
218
248
|
|
|
219
249
|
|
|
220
|
-
def
|
|
221
|
-
|
|
250
|
+
def compute_trial_labels_from_crop_preds(all_preds, input_time_length, X):
|
|
251
|
+
"""
|
|
252
|
+
Compute predicted trial labels from arrays of crop predictions
|
|
253
|
+
|
|
254
|
+
Parameters
|
|
255
|
+
----------
|
|
256
|
+
all_preds: list of 2darrays (classes x time)
|
|
257
|
+
All predictions for the crops.
|
|
258
|
+
input_time_length: int
|
|
259
|
+
Temporal length of one input to the model.
|
|
260
|
+
X: ndarray
|
|
261
|
+
Input tensor the crops were taken from.
|
|
262
|
+
|
|
263
|
+
Returns
|
|
264
|
+
-------
|
|
265
|
+
pred_labels_per_trial: 1darray
|
|
266
|
+
Predicted label for each trial.
|
|
267
|
+
|
|
268
|
+
"""
|
|
269
|
+
|
|
270
|
+
preds_per_trial = compute_preds_per_trial_from_crops(
|
|
271
|
+
all_preds, input_time_length, X)
|
|
272
|
+
pred_labels_per_trial = [np.argmax(np.mean(p, axis=1))
|
|
273
|
+
for p in preds_per_trial]
|
|
274
|
+
pred_labels_per_trial = np.array(pred_labels_per_trial)
|
|
275
|
+
return pred_labels_per_trial
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def compute_preds_per_trial_from_crops(all_preds, input_time_length,
|
|
279
|
+
X, ):
|
|
222
280
|
"""
|
|
223
281
|
Compute predictions per trial from predictions for crops.
|
|
224
282
|
|
|
@@ -228,8 +286,8 @@ def compute_preds_per_trial_for_set(all_preds, input_time_length,
|
|
|
228
286
|
All predictions for the crops.
|
|
229
287
|
input_time_length: int
|
|
230
288
|
Temporal length of one input to the model.
|
|
231
|
-
|
|
232
|
-
|
|
289
|
+
X: ndarray
|
|
290
|
+
Input tensor the crops were taken from.
|
|
233
291
|
|
|
234
292
|
Returns
|
|
235
293
|
-------
|
|
@@ -240,7 +298,7 @@ def compute_preds_per_trial_for_set(all_preds, input_time_length,
|
|
|
240
298
|
n_preds_per_input = all_preds[0].shape[2]
|
|
241
299
|
n_receptive_field = input_time_length - n_preds_per_input + 1
|
|
242
300
|
n_preds_per_trial = [trial.shape[1] - n_receptive_field + 1
|
|
243
|
-
for trial in
|
|
301
|
+
for trial in X]
|
|
244
302
|
preds_per_trial = compute_preds_per_trial_from_n_preds_per_trial(
|
|
245
303
|
all_preds, n_preds_per_trial)
|
|
246
304
|
return preds_per_trial
|
|
@@ -27,10 +27,29 @@ class Or(object):
|
|
|
27
27
|
"""
|
|
28
28
|
def __init__(self, stop_criteria):
|
|
29
29
|
self.stop_criteria = stop_criteria
|
|
30
|
+
self.triggered = dict([(s, False) for s in stop_criteria])
|
|
30
31
|
|
|
31
32
|
def should_stop(self, epochs_df):
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
# Update dictionary of which criterion was triggered ...
|
|
34
|
+
for s in self.stop_criteria:
|
|
35
|
+
self.triggered[s] = s.should_stop(epochs_df)
|
|
36
|
+
# Then check if any of them was triggered.
|
|
37
|
+
return np.any(list(self.triggered.values()))
|
|
38
|
+
|
|
39
|
+
def was_triggered(self, criterion):
|
|
40
|
+
"""
|
|
41
|
+
Return if given criterion was triggered in the last call to should stop.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
criterion: stop criterion
|
|
46
|
+
|
|
47
|
+
Returns
|
|
48
|
+
-------
|
|
49
|
+
triggered: bool
|
|
50
|
+
|
|
51
|
+
"""
|
|
52
|
+
return self.triggered[criterion]
|
|
34
53
|
|
|
35
54
|
|
|
36
55
|
class And(object):
|
|
@@ -45,8 +64,26 @@ class And(object):
|
|
|
45
64
|
self.stop_criteria = stop_criteria
|
|
46
65
|
|
|
47
66
|
def should_stop(self, epochs_df):
|
|
48
|
-
|
|
49
|
-
|
|
67
|
+
# Update dictionary of which criterion was triggered ...
|
|
68
|
+
for s in self.stop_criteria:
|
|
69
|
+
self.triggered[s] = s.should_stop(epochs_df)
|
|
70
|
+
# Then check if all of them were triggered.
|
|
71
|
+
return np.all(list(self.triggered.values()))
|
|
72
|
+
|
|
73
|
+
def was_triggered(self, criterion):
|
|
74
|
+
"""
|
|
75
|
+
Return if given criterion was triggered in the last call to should stop.
|
|
76
|
+
|
|
77
|
+
Parameters
|
|
78
|
+
----------
|
|
79
|
+
criterion: stop criterion
|
|
80
|
+
|
|
81
|
+
Returns
|
|
82
|
+
-------
|
|
83
|
+
triggered: bool
|
|
84
|
+
|
|
85
|
+
"""
|
|
86
|
+
return self.triggered[criterion]
|
|
50
87
|
|
|
51
88
|
|
|
52
89
|
class NoDecrease(object):
|