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.
Files changed (80) hide show
  1. {Braindecode-0.3.2 → Braindecode-0.4.0}/Braindecode.egg-info/PKG-INFO +1 -1
  2. {Braindecode-0.3.2 → Braindecode-0.4.0}/Braindecode.egg-info/SOURCES.txt +10 -2
  3. {Braindecode-0.3.2 → Braindecode-0.4.0}/PKG-INFO +1 -1
  4. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/datautil/iterators.py +18 -10
  5. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/experiments/experiment.py +65 -39
  6. Braindecode-0.4.0/braindecode/experiments/loggers.py +48 -0
  7. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/experiments/monitors.py +69 -11
  8. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/experiments/stopcriteria.py +41 -4
  9. Braindecode-0.4.0/braindecode/models/base.py +276 -0
  10. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/models/deep4.py +16 -14
  11. Braindecode-0.4.0/braindecode/models/eegnet.py +236 -0
  12. Braindecode-0.4.0/braindecode/models/hybrid.py +77 -0
  13. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/models/shallow_fbcsp.py +11 -10
  14. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/torch_ext/init.py +2 -2
  15. Braindecode-0.4.0/braindecode/torch_ext/optimizers.py +77 -0
  16. Braindecode-0.4.0/braindecode/torch_ext/schedulers.py +65 -0
  17. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/torch_ext/util.py +11 -11
  18. Braindecode-0.4.0/braindecode/version.py +1 -0
  19. {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/conf.py +1 -1
  20. {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/index.rst +3 -2
  21. Braindecode-0.4.0/docs/notebooks/Cropped_Decoding.ipynb +980 -0
  22. Braindecode-0.3.2/docs/notebooks/Cropped_Decoding.ipynb → Braindecode-0.4.0/docs/notebooks/Cropped_Manual_Training_Loop.ipynb +199 -114
  23. Braindecode-0.4.0/docs/notebooks/Cropped_Trialwise_Explanation_Figures.ipynb +382 -0
  24. Braindecode-0.4.0/docs/notebooks/Trialwise_Decoding.ipynb +1034 -0
  25. Braindecode-0.3.2/docs/notebooks/TrialWise_Decoding.ipynb → Braindecode-0.4.0/docs/notebooks/Trialwise_Manual_Training_Loop.ipynb +244 -164
  26. Braindecode-0.4.0/docs/notebooks/visualization/Perturbation.ipynb +855 -0
  27. Braindecode-0.3.2/docs/notebooks/visualization/Perturbation.ipynb → Braindecode-0.4.0/docs/notebooks/visualization/Perturbation_Old.ipynb +63 -25
  28. {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/source/braindecode.experiments.rst +8 -0
  29. {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/source/braindecode.models.rst +16 -0
  30. {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/source/braindecode.torch_ext.rst +16 -0
  31. Braindecode-0.3.2/braindecode/models/eegnet.py +0 -115
  32. Braindecode-0.3.2/braindecode/version.py +0 -1
  33. Braindecode-0.3.2/docs/notebooks/Experiment_Class.ipynb +0 -804
  34. {Braindecode-0.3.2 → Braindecode-0.4.0}/Braindecode.egg-info/dependency_links.txt +0 -0
  35. {Braindecode-0.3.2 → Braindecode-0.4.0}/Braindecode.egg-info/not-zip-safe +0 -0
  36. {Braindecode-0.3.2 → Braindecode-0.4.0}/Braindecode.egg-info/requires.txt +0 -0
  37. {Braindecode-0.3.2 → Braindecode-0.4.0}/Braindecode.egg-info/top_level.txt +0 -0
  38. {Braindecode-0.3.2 → Braindecode-0.4.0}/LICENSE.txt +0 -0
  39. {Braindecode-0.3.2 → Braindecode-0.4.0}/MANIFEST.in +0 -0
  40. {Braindecode-0.3.2 → Braindecode-0.4.0}/README.rst +0 -0
  41. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/__init__.py +0 -0
  42. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/datasets/__init__.py +0 -0
  43. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/datasets/bbci.py +0 -0
  44. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/datasets/bcic_iv_2a.py +0 -0
  45. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/datasets/multiple.py +0 -0
  46. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/datasets/sensor_positions.py +0 -0
  47. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/datautil/__init__.py +0 -0
  48. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/datautil/signal_target.py +0 -0
  49. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/datautil/signalproc.py +0 -0
  50. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/datautil/splitters.py +0 -0
  51. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/datautil/trial_segment.py +0 -0
  52. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/datautil/util.py +0 -0
  53. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/experiments/__init__.py +0 -0
  54. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/mne_ext/__init__.py +0 -0
  55. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/mne_ext/signalproc.py +0 -0
  56. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/models/__init__.py +0 -0
  57. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/models/util.py +0 -0
  58. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/torch_ext/__init__.py +0 -0
  59. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/torch_ext/constraints.py +0 -0
  60. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/torch_ext/functions.py +0 -0
  61. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/torch_ext/losses.py +0 -0
  62. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/torch_ext/modules.py +0 -0
  63. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/util.py +0 -0
  64. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/visualization/__init__.py +0 -0
  65. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/visualization/input_windows.py +0 -0
  66. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/visualization/perturbation.py +0 -0
  67. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/visualization/plot.py +0 -0
  68. {Braindecode-0.3.2 → Braindecode-0.4.0}/braindecode/visualization/sinfit.py +0 -0
  69. {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/Makefile +0 -0
  70. {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/notebooks/BBCI_Data.ipynb +0 -0
  71. {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/notebooks/BBCI_Data_Start_Stop.ipynb +0 -0
  72. {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/notebooks/BBCI_Data_Trialwise.ipynb +0 -0
  73. {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/source/braindecode.datasets.rst +0 -0
  74. {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/source/braindecode.datautil.rst +0 -0
  75. {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/source/braindecode.mne_ext.rst +0 -0
  76. {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/source/braindecode.rst +0 -0
  77. {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/source/braindecode.visualization.rst +0 -0
  78. {Braindecode-0.3.2 → Braindecode-0.4.0}/docs/source/modules.rst +0 -0
  79. {Braindecode-0.3.2 → Braindecode-0.4.0}/setup.cfg +0 -0
  80. {Braindecode-0.3.2 → Braindecode-0.4.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.1
2
2
  Name: Braindecode
3
- Version: 0.3.2
3
+ Version: 0.4.0
4
4
  Summary: A deep learning toolbox to decode raw time-domain EEG.
5
5
  Home-page: https://github.com/robintibor/braindecode
6
6
  Author: Robin Tibor Schirrmeister
@@ -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/Experiment_Class.ipynb
57
- docs/notebooks/TrialWise_Decoding.ipynb
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.1
2
2
  Name: Braindecode
3
- Version: 0.3.2
3
+ Version: 0.4.0
4
4
  Summary: A deep learning toolbox to decode raw time-domain EEG.
5
5
  Home-page: https://github.com/robintibor/braindecode
6
6
  Author: Robin Tibor Schirrmeister
@@ -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.rng = RandomState(328774)
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(328774)
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.rng = RandomState(328774)
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((4, 7, 2017))
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:`compute_preds_per_trial_for_set`
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.compute_preds_per_trial_for_set : Assigns predictions to trials, removes overlaps.
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
- if run_after_early_stop:
154
- assert do_early_stop == True, ("Can only run after early stop if "
155
- "doing an early stop")
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=2382938, cuda=self.cuda)
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
- # Todo: actually keep remembering and in case of twice number of epochs
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.monitor_epoch(datasets)
252
- self.print_epoch()
253
- if remember_best:
254
- self.rememberer.remember_epoch(self.epochs_df, self.model,
255
- self.optimizer)
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.print_epoch()
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
- input_vars = np_to_var(inputs, pin_memory=self.pin_memory,
335
- volatile=True)
336
- target_vars = np_to_var(targets, pin_memory=self.pin_memory,
337
- volatile=True)
338
- if self.cuda:
339
- input_vars = input_vars.cuda()
340
- target_vars = target_vars.cuda()
341
- outputs = self.model(input_vars)
342
- loss = self.loss_function(outputs, target_vars)
343
- if hasattr(outputs, 'cpu'):
344
- outputs = outputs.cpu().data.numpy()
345
- else:
346
- # assume it is iterable
347
- outputs = [o.cpu().data.numpy() for o in outputs]
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 print_epoch(self):
429
+ def log_epoch(self):
399
430
  """
400
431
  Print monitoring values for this epoch.
401
432
  """
402
- # -1 due to doing one monitor at start of training
403
- i_epoch = len(self.epochs_df) - 1
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 = self._compute_pred_labels(dataset, all_preds)
173
- all_trial_labels = dataset.y
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 = compute_preds_per_trial_for_set(
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 = compute_preds_per_trial_for_set(
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 compute_preds_per_trial_for_set(all_preds, input_time_length,
221
- dataset, ):
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
- dataset: :class:`.SignalAndTarget`
232
- Dataset the crops were taken from.
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 dataset.X]
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
- return np.any([s.should_stop(epochs_df)
33
- for s in self.stop_criteria])
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
- return np.all([s.should_stop(epochs_df)
49
- for s in self.stop_criteria])
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):