braindecode 0.5.1__py3-none-any.whl

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 (69) hide show
  1. Braindecode-0.5.1.dist-info/LICENSE.txt +31 -0
  2. Braindecode-0.5.1.dist-info/METADATA +133 -0
  3. Braindecode-0.5.1.dist-info/RECORD +69 -0
  4. Braindecode-0.5.1.dist-info/WHEEL +5 -0
  5. Braindecode-0.5.1.dist-info/top_level.txt +2 -0
  6. braindecode/__init__.py +4 -0
  7. braindecode/classifier.py +258 -0
  8. braindecode/datasets/__init__.py +7 -0
  9. braindecode/datasets/base.py +293 -0
  10. braindecode/datasets/bbci.py +679 -0
  11. braindecode/datasets/moabb.py +110 -0
  12. braindecode/datasets/sleep_physionet.py +103 -0
  13. braindecode/datasets/tuh.py +230 -0
  14. braindecode/datautil/__init__.py +11 -0
  15. braindecode/datautil/mne.py +136 -0
  16. braindecode/datautil/preprocess.py +364 -0
  17. braindecode/datautil/serialization.py +111 -0
  18. braindecode/datautil/util.py +36 -0
  19. braindecode/datautil/windowers.py +435 -0
  20. braindecode/datautil/xy.py +77 -0
  21. braindecode/models/__init__.py +12 -0
  22. braindecode/models/deep4.py +252 -0
  23. braindecode/models/eegnet.py +339 -0
  24. braindecode/models/eegresnet.py +232 -0
  25. braindecode/models/functions.py +43 -0
  26. braindecode/models/hybrid.py +103 -0
  27. braindecode/models/modules.py +150 -0
  28. braindecode/models/shallow_fbcsp.py +158 -0
  29. braindecode/models/sleep_stager_chambon_2018.py +113 -0
  30. braindecode/models/tcn.py +143 -0
  31. braindecode/models/util.py +66 -0
  32. braindecode/regressor.py +240 -0
  33. braindecode/samplers/__init__.py +5 -0
  34. braindecode/samplers/base.py +78 -0
  35. braindecode/samplers/ssl.py +118 -0
  36. braindecode/training/__init__.py +8 -0
  37. braindecode/training/callbacks.py +21 -0
  38. braindecode/training/losses.py +17 -0
  39. braindecode/training/scoring.py +296 -0
  40. braindecode/util.py +362 -0
  41. braindecode/version.py +1 -0
  42. braindecode/visualization/__init__.py +5 -0
  43. braindecode/visualization/gradients.py +53 -0
  44. test/__init__.py +0 -0
  45. test/acceptance_tests/__init__.py +0 -0
  46. test/acceptance_tests/test_cropped_decoding.py +171 -0
  47. test/acceptance_tests/test_eeg_classifier.py +255 -0
  48. test/acceptance_tests/test_model_architectures.py +54 -0
  49. test/acceptance_tests/test_trialwise_decoding.py +168 -0
  50. test/unit_tests/__init__.py +0 -0
  51. test/unit_tests/datasets/__init__.py +0 -0
  52. test/unit_tests/datasets/test_dataset.py +227 -0
  53. test/unit_tests/datasets/test_mne.py +145 -0
  54. test/unit_tests/datasets/test_sleep_physionet.py +23 -0
  55. test/unit_tests/datasets/test_tuh.py +97 -0
  56. test/unit_tests/datasets/test_xy.py +31 -0
  57. test/unit_tests/datautil/__init__.py +0 -0
  58. test/unit_tests/datautil/test_preprocess.py +270 -0
  59. test/unit_tests/datautil/test_serialization.py +127 -0
  60. test/unit_tests/datautil/test_windowers.py +519 -0
  61. test/unit_tests/models/__init__.py +0 -0
  62. test/unit_tests/models/test_models.py +136 -0
  63. test/unit_tests/models/test_util.py +22 -0
  64. test/unit_tests/test_eeg_classifier.py +73 -0
  65. test/unit_tests/test_eeg_regressor.py +72 -0
  66. test/unit_tests/test_scoring.py +282 -0
  67. test/unit_tests/test_util.py +30 -0
  68. test/unit_tests/visualization/__init__.py +0 -0
  69. test/unit_tests/visualization/test_gradients.py +38 -0
@@ -0,0 +1,113 @@
1
+ # Authors: Hubert Banville <hubert.jbanville@gmail.com>
2
+ #
3
+ # License: BSD (3-clause)
4
+
5
+
6
+ import torch
7
+ from torch import nn
8
+ import numpy as np
9
+
10
+
11
+ class SleepStagerChambon2018(nn.Module):
12
+ """
13
+ Sleep staging architecture from [1]_.
14
+
15
+ Convolutional neural network for sleep staging described in [1]_.
16
+
17
+ Parameters
18
+ ----------
19
+ n_channels : int
20
+ Number of EEG channels.
21
+ sfreq : float
22
+ EEG sampling frequency.
23
+ n_conv_chs : int
24
+ Number of convolutional channels. Set to 8 in [1]_.
25
+ time_conv_size_s : float
26
+ Size of filters in temporal convolution layers, in seconds. Set to 0.5
27
+ in [1]_ (64 samples at sfreq=128).
28
+ max_pool_size_s : float
29
+ Max pooling size, in seconds. Set to 0.125 in [1]_ (16 samples at
30
+ sfreq=128).
31
+ pad_size_s : float
32
+ Paddind size, in seconds. Set to 0.25 in [1]_ (half the temporal
33
+ convolution kernel size).
34
+ input_size_s : float
35
+ Size of the input, in seconds.
36
+ n_classes : int
37
+ Number of classes.
38
+ dropout : float
39
+ Dropout rate before the output dense layer.
40
+ apply_batch_norm : bool
41
+ If True, apply batch normalization after both temporal convolutional
42
+ layers.
43
+
44
+ References
45
+ ----------
46
+ .. [1] Chambon, S., Galtier, M. N., Arnal, P. J., Wainrib, G., &
47
+ Gramfort, A. (2018). A deep learning architecture for temporal sleep
48
+ stage classification using multivariate and multimodal time series.
49
+ IEEE Transactions on Neural Systems and Rehabilitation Engineering,
50
+ 26(4), 758-769.
51
+ """
52
+ def __init__(self, n_channels, sfreq, n_conv_chs=8, time_conv_size_s=0.5,
53
+ max_pool_size_s=0.125, pad_size_s=0.25, input_size_s=30,
54
+ n_classes=5, dropout=0.25, apply_batch_norm=False):
55
+ super().__init__()
56
+
57
+ time_conv_size = np.ceil(time_conv_size_s * sfreq).astype(int)
58
+ max_pool_size = np.ceil(max_pool_size_s * sfreq).astype(int)
59
+ input_size = np.ceil(input_size_s * sfreq).astype(int)
60
+ pad_size = np.ceil(pad_size_s * sfreq).astype(int)
61
+
62
+ self.n_channels = n_channels
63
+
64
+ if n_channels > 1:
65
+ self.spatial_conv = nn.Conv2d(1, n_channels, (n_channels, 1))
66
+
67
+ batch_norm = nn.BatchNorm2d if apply_batch_norm else nn.Identity
68
+
69
+ self.feature_extractor = nn.Sequential(
70
+ nn.Conv2d(
71
+ 1, n_conv_chs, (1, time_conv_size), padding=(0, pad_size)),
72
+ batch_norm(n_conv_chs),
73
+ nn.ReLU(),
74
+ nn.MaxPool2d((1, max_pool_size)),
75
+ nn.Conv2d(
76
+ n_conv_chs, n_conv_chs, (1, time_conv_size),
77
+ padding=(0, pad_size)),
78
+ batch_norm(n_conv_chs),
79
+ nn.ReLU(),
80
+ nn.MaxPool2d((1, max_pool_size))
81
+ )
82
+ len_last_layer = self._len_last_layer(n_channels, input_size)
83
+ self.fc = nn.Sequential(
84
+ nn.Dropout(dropout),
85
+ nn.Linear(len_last_layer, n_classes)
86
+ )
87
+
88
+ def _len_last_layer(self, n_channels, input_size):
89
+ self.feature_extractor.eval()
90
+ with torch.no_grad():
91
+ out = self.feature_extractor(
92
+ torch.Tensor(1, 1, n_channels, input_size))
93
+ self.feature_extractor.train()
94
+ return len(out.flatten())
95
+
96
+ def forward(self, x):
97
+ """
98
+ Forward pass.
99
+
100
+ Parameters
101
+ ---------
102
+ x: torch.Tensor
103
+ Batch of EEG windows of shape (batch_size, n_channels, n_times).
104
+ """
105
+ if x.ndim == 3:
106
+ x = x.unsqueeze(1)
107
+
108
+ if self.n_channels > 1:
109
+ x = self.spatial_conv(x)
110
+ x = x.transpose(1, 2)
111
+
112
+ x = self.feature_extractor(x)
113
+ return self.fc(x.flatten(start_dim=1))
@@ -0,0 +1,143 @@
1
+ # Authors: Patryk Chrabaszcz
2
+ # Lukas Gemein <l.gemein@gmail.com>
3
+ #
4
+ # License: BSD-3
5
+
6
+ from torch import nn
7
+ from torch.nn import init
8
+ from torch.nn.utils import weight_norm
9
+
10
+ from .modules import Ensure4d, Expression
11
+ from .functions import squeeze_final_output
12
+
13
+
14
+ class TCN(nn.Module):
15
+ """Temporal Convolutional Network (TCN) as described in [1]_.
16
+ Code adapted from https://github.com/locuslab/TCN/blob/master/TCN/tcn.py
17
+ Parameters
18
+ ----------
19
+ n_in_chans: int
20
+ number of input EEG channels
21
+ n_outputs: int
22
+ number of outputs of the decoding task (for example number of classes in
23
+ classification)
24
+ n_filters: int
25
+ number of output filters of each convolution
26
+ n_blocks: int
27
+ number of temporal blocks in the network
28
+ kernel_size: int
29
+ kernel size of the convolutions
30
+ drop_prob: float
31
+ dropout probability
32
+ add_log_softmax: bool
33
+ whether to add a log softmax layer
34
+ References
35
+ ----------
36
+ .. [1] Bai, S., Kolter, J. Z., & Koltun, V. (2018).
37
+ An empirical evaluation of generic convolutional and recurrent networks
38
+ for sequence modeling.
39
+ arXiv preprint arXiv:1803.01271.
40
+ """
41
+ def __init__(self, n_in_chans, n_outputs, n_blocks, n_filters, kernel_size,
42
+ drop_prob, add_log_softmax):
43
+ super().__init__()
44
+ self.ensuredims = Ensure4d()
45
+ t_blocks = nn.Sequential()
46
+ for i in range(n_blocks):
47
+ n_inputs = n_in_chans if i == 0 else n_filters
48
+ dilation_size = 2 ** i
49
+ t_blocks.add_module("temporal_block_{:d}".format(i), TemporalBlock(
50
+ n_inputs=n_inputs,
51
+ n_outputs=n_filters,
52
+ kernel_size=kernel_size,
53
+ stride=1,
54
+ dilation=dilation_size,
55
+ padding=(kernel_size - 1) * dilation_size,
56
+ drop_prob=drop_prob
57
+ ))
58
+ self.temporal_blocks = t_blocks
59
+ self.fc = nn.Linear(in_features=n_filters, out_features=n_outputs)
60
+ if add_log_softmax:
61
+ self.log_softmax = nn.LogSoftmax(dim=1)
62
+ self.squeeze = Expression(squeeze_final_output)
63
+
64
+ self.min_len = 1
65
+ for i in range(n_blocks):
66
+ dilation = 2 ** i
67
+ self.min_len += 2 * (kernel_size - 1) * dilation
68
+
69
+ # start in eval mode
70
+ self.eval()
71
+
72
+ def forward(self, x):
73
+ x = self.ensuredims(x)
74
+ # x is in format: B x C x T x 1
75
+ (batch_size, _, time_size, _) = x.size()
76
+ assert time_size >= self.min_len
77
+ # remove empty trailing dimension
78
+ x = x.squeeze(3)
79
+ x = self.temporal_blocks(x)
80
+ # Convert to: B x T x C
81
+ x = x.transpose(1, 2).contiguous()
82
+
83
+ fc_out = self.fc(x.view(batch_size * time_size, x.size(2)))
84
+ if hasattr(self, "log_softmax"):
85
+ fc_out = self.log_softmax(fc_out)
86
+ fc_out = fc_out.view(batch_size, time_size, fc_out.size(1))
87
+
88
+ out_size = 1 + max(0, time_size - self.min_len)
89
+ out = fc_out[:, -out_size:, :].transpose(1, 2)
90
+ # re-add 4th dimension for compatibility with braindecode
91
+ return self.squeeze(out[:, :, :, None])
92
+
93
+
94
+ class TemporalBlock(nn.Module):
95
+ def __init__(self, n_inputs, n_outputs, kernel_size, stride, dilation,
96
+ padding, drop_prob):
97
+ super().__init__()
98
+ self.conv1 = weight_norm(nn.Conv1d(
99
+ n_inputs, n_outputs, kernel_size,
100
+ stride=stride, padding=padding, dilation=dilation))
101
+ self.chomp1 = Chomp1d(padding)
102
+ self.relu1 = nn.ReLU()
103
+ self.dropout1 = nn.Dropout2d(drop_prob)
104
+
105
+ self.conv2 = weight_norm(nn.Conv1d(
106
+ n_outputs, n_outputs, kernel_size,
107
+ stride=stride, padding=padding, dilation=dilation))
108
+ self.chomp2 = Chomp1d(padding)
109
+ self.relu2 = nn.ReLU()
110
+ self.dropout2 = nn.Dropout2d(drop_prob)
111
+
112
+ self.downsample = (nn.Conv1d(n_inputs, n_outputs, 1)
113
+ if n_inputs != n_outputs else None)
114
+ self.relu = nn.ReLU()
115
+
116
+ init.normal_(self.conv1.weight, 0, 0.01)
117
+ init.normal_(self.conv2.weight, 0, 0.01)
118
+ if self.downsample is not None:
119
+ init.normal_(self.downsample.weight, 0, 0.01)
120
+
121
+ def forward(self, x):
122
+ out = self.conv1(x)
123
+ out = self.chomp1(out)
124
+ out = self.relu1(out)
125
+ out = self.dropout1(out)
126
+ out = self.conv2(out)
127
+ out = self.chomp2(out)
128
+ out = self.relu2(out)
129
+ out = self.dropout2(out)
130
+ res = x if self.downsample is None else self.downsample(x)
131
+ return self.relu(out + res)
132
+
133
+
134
+ class Chomp1d(nn.Module):
135
+ def __init__(self, chomp_size):
136
+ super().__init__()
137
+ self.chomp_size = chomp_size
138
+
139
+ def extra_repr(self):
140
+ return 'chomp_size={}'.format(self.chomp_size)
141
+
142
+ def forward(self, x):
143
+ return x[:, :, :-self.chomp_size].contiguous()
@@ -0,0 +1,66 @@
1
+ import torch
2
+ import numpy as np
3
+
4
+
5
+ def to_dense_prediction_model(model, axis=(2, 3)):
6
+ """
7
+ Transform a sequential model with strides to a model that outputs
8
+ dense predictions by removing the strides and instead inserting dilations.
9
+ Modifies model in-place.
10
+
11
+ Parameters
12
+ ----------
13
+ model: torch.nn.Module
14
+ Model which modules will be modified
15
+ axis: int or (int,int)
16
+ Axis to transform (in terms of intermediate output axes)
17
+ can either be 2, 3, or (2,3).
18
+
19
+ Notes
20
+ -----
21
+ Does not yet work correctly for average pooling.
22
+ Prior to version 0.1.7, there had been a bug that could move strides
23
+ backwards one layer.
24
+
25
+ """
26
+ if not hasattr(axis, "__len__"):
27
+ axis = [axis]
28
+ assert all([ax in [2, 3] for ax in axis]), "Only 2 and 3 allowed for axis"
29
+ axis = np.array(axis) - 2
30
+ stride_so_far = np.array([1, 1])
31
+ for module in model.modules():
32
+ if hasattr(module, "dilation"):
33
+ assert module.dilation == 1 or (module.dilation == (1, 1)), (
34
+ "Dilation should equal 1 before conversion, maybe the model is "
35
+ "already converted?"
36
+ )
37
+ new_dilation = [1, 1]
38
+ for ax in axis:
39
+ new_dilation[ax] = int(stride_so_far[ax])
40
+ module.dilation = tuple(new_dilation)
41
+ if hasattr(module, "stride"):
42
+ if not hasattr(module.stride, "__len__"):
43
+ module.stride = (module.stride, module.stride)
44
+ stride_so_far *= np.array(module.stride)
45
+ new_stride = list(module.stride)
46
+ for ax in axis:
47
+ new_stride[ax] = 1
48
+ module.stride = tuple(new_stride)
49
+
50
+
51
+ def get_output_shape(model, in_chans, input_window_samples):
52
+ """Returns shape of neural network output for batch size equal 1.
53
+
54
+ Returns
55
+ -------
56
+ output_shape: tuple
57
+ shape of the network output for `batch_size==1` (1, ...)
58
+ """
59
+ with torch.no_grad():
60
+ dummy_input = torch.ones(
61
+ 1, in_chans, input_window_samples,
62
+ dtype=next(model.parameters()).dtype,
63
+ device=next(model.parameters()).device,
64
+ )
65
+ output_shape = model(dummy_input).shape
66
+ return output_shape
@@ -0,0 +1,240 @@
1
+ import numpy as np
2
+ from sklearn.metrics import get_scorer
3
+ from skorch.callbacks import EpochTimer, BatchScoring, PrintLog, EpochScoring
4
+ from skorch.classifier import NeuralNet
5
+ from skorch.regressor import NeuralNetRegressor
6
+ from skorch.utils import train_loss_score, valid_loss_score, noop, to_numpy
7
+
8
+ from .training.scoring import PostEpochTrainScoring, CroppedTrialEpochScoring
9
+ from .util import ThrowAwayIndexLoader, update_estimator_docstring
10
+
11
+
12
+ class EEGRegressor(NeuralNetRegressor):
13
+ doc = """Regressor that calls loss function directly.
14
+
15
+ Parameters
16
+ ----------
17
+ cropped: bool (default=False)
18
+ Defines whether torch model passed to this class is cropped or not.
19
+ Currently used for callbacks definition.
20
+
21
+ callbacks: None or list of strings or list of Callback instances (default=None)
22
+ More callbacks, in addition to those returned by
23
+ ``get_default_callbacks``. Each callback should inherit from
24
+ :class:`skorch.callbacks.Callback`. If not ``None``, callbacks can be a
25
+ list of strings specifying `sklearn` scoring functions (for scoring
26
+ functions names see: https://scikit-learn.org/stable/modules/model_evaluation.html#scoring-parameter)
27
+ or a list of callbacks where the callback names are inferred from the
28
+ class name. Name conflicts are resolved by appending a count suffix
29
+ starting with 1, e.g. ``EpochScoring_1``. Alternatively,
30
+ a tuple ``(name, callback)`` can be passed, where ``name``
31
+ should be unique. Callbacks may or may not be instantiated.
32
+ The callback name can be used to set parameters on specific
33
+ callbacks (e.g., for the callback with name ``'print_log'``, use
34
+ ``net.set_params(callbacks__print_log__keys_ignored=['epoch',
35
+ 'train_loss'])``).
36
+
37
+ iterator_train__shuffle: bool (default=True)
38
+ Defines whether train dataset will be shuffled. As skorch does not
39
+ shuffle the train dataset by default this one overwrites this option.
40
+
41
+ """
42
+ __doc__ = update_estimator_docstring(NeuralNetRegressor, doc)
43
+
44
+ def __init__(self, *args, cropped=False, callbacks=None,
45
+ iterator_train__shuffle=True, **kwargs):
46
+ self.cropped = cropped
47
+ callbacks = self._parse_callbacks(callbacks)
48
+
49
+ super().__init__(*args,
50
+ callbacks=callbacks,
51
+ iterator_train__shuffle=iterator_train__shuffle,
52
+ **kwargs)
53
+
54
+ def _parse_callbacks(self, callbacks):
55
+ callbacks_list = []
56
+ if callbacks is not None:
57
+ for callback in callbacks:
58
+ if isinstance(callback, tuple):
59
+ callbacks_list.append(callback)
60
+ else:
61
+ assert isinstance(callback, str)
62
+ scoring = get_scorer(callback)
63
+ scoring_name = scoring._score_func.__name__
64
+ assert scoring_name.endswith(
65
+ ('_score', '_error', '_deviance', '_loss'))
66
+ if (scoring_name.endswith('_score') or
67
+ callback.startswith('neg_')):
68
+ lower_is_better = False
69
+ else:
70
+ lower_is_better = True
71
+ train_name = f'train_{callback}'
72
+ valid_name = f'valid_{callback}'
73
+ if self.cropped:
74
+ # In case of cropped decoding we are using braindecode
75
+ # specific scoring created for cropped decoding
76
+ train_scoring = CroppedTrialEpochScoring(
77
+ callback, lower_is_better, on_train=True, name=train_name
78
+ )
79
+ valid_scoring = CroppedTrialEpochScoring(
80
+ callback, lower_is_better, on_train=False, name=valid_name
81
+ )
82
+ else:
83
+ train_scoring = PostEpochTrainScoring(
84
+ callback, lower_is_better, name=train_name
85
+ )
86
+ valid_scoring = EpochScoring(
87
+ callback, lower_is_better, on_train=False, name=valid_name
88
+ )
89
+ callbacks_list.extend([
90
+ (train_name, train_scoring),
91
+ (valid_name, valid_scoring)
92
+ ])
93
+
94
+ return callbacks_list
95
+
96
+ # pylint: disable=arguments-differ
97
+ def get_loss(self, y_pred, y_true, *args, **kwargs):
98
+ """Return the loss for this batch by calling NeuralNet get_loss.
99
+ Parameters
100
+ ----------
101
+ y_pred : torch tensor
102
+ Predicted target values
103
+ y_true : torch tensor
104
+ True target values.
105
+ X : input data, compatible with skorch.dataset.Dataset
106
+ By default, you should be able to pass:
107
+ * numpy arrays
108
+ * torch tensors
109
+ * pandas DataFrame or Series
110
+ * scipy sparse CSR matrices
111
+ * a dictionary of the former three
112
+ * a list/tuple of the former three
113
+ * a Dataset
114
+ If this doesn't work with your data, you have to pass a
115
+ ``Dataset`` that can deal with the data.
116
+ training : bool (default=False)
117
+ Whether train mode should be used or not.
118
+
119
+ """
120
+ return NeuralNet.get_loss(self, y_pred, y_true, *args, **kwargs)
121
+
122
+ def get_iterator(self, dataset, training=False, drop_index=True):
123
+ iterator = super().get_iterator(dataset, training=training)
124
+ if drop_index:
125
+ return ThrowAwayIndexLoader(self, iterator, is_regression=True)
126
+ else:
127
+ return iterator
128
+
129
+ def on_batch_end(self, net, X, y, training=False, **kwargs):
130
+ # If training is false, assume that our loader has indices for this
131
+ # batch
132
+ if not training:
133
+ cbs = self._default_callbacks + self.callbacks
134
+ epoch_cbs = []
135
+ for name, cb in cbs:
136
+ if (cb.__class__.__name__ == 'CroppedTrialEpochScoring') and (
137
+ hasattr(cb, 'window_inds_')) and (cb.on_train == False):
138
+ epoch_cbs.append(cb)
139
+ # for trialwise decoding stuffs it might also be we don't have
140
+ # cropped loader, so no indices there
141
+ if len(epoch_cbs) > 0:
142
+ assert hasattr(self, '_last_window_inds')
143
+ for cb in epoch_cbs:
144
+ cb.window_inds_.append(self._last_window_inds)
145
+ del self._last_window_inds
146
+
147
+ def predict_with_window_inds_and_ys(self, dataset):
148
+ preds = []
149
+ i_window_in_trials = []
150
+ i_window_stops = []
151
+ window_ys = []
152
+ for X, y, i in self.get_iterator(dataset, drop_index=False):
153
+ i_window_in_trials.append(i[0].cpu().numpy())
154
+ i_window_stops.append(i[2].cpu().numpy())
155
+ preds.append(to_numpy(self.forward(X)))
156
+ window_ys.append(y.cpu().numpy())
157
+ preds = np.concatenate(preds)
158
+ i_window_in_trials = np.concatenate(i_window_in_trials)
159
+ i_window_stops = np.concatenate(i_window_stops)
160
+ window_ys = np.concatenate(window_ys)
161
+ return dict(
162
+ preds=preds, i_window_in_trials=i_window_in_trials,
163
+ i_window_stops=i_window_stops, window_ys=window_ys)
164
+
165
+ # Removes default EpochScoring callback computing 'accuracy' to work properly
166
+ # with cropped decoding.
167
+ @property
168
+ def _default_callbacks(self):
169
+ return [
170
+ ("epoch_timer", EpochTimer()),
171
+ (
172
+ "train_loss",
173
+ BatchScoring(
174
+ train_loss_score,
175
+ name="train_loss",
176
+ on_train=True,
177
+ target_extractor=noop,
178
+ ),
179
+ ),
180
+ (
181
+ "valid_loss",
182
+ BatchScoring(
183
+ valid_loss_score, name="valid_loss", target_extractor=noop,
184
+ ),
185
+ ),
186
+ ("print_log", PrintLog()),
187
+ ]
188
+
189
+ # Method added to fix `predict_proba` behavior to return proper values
190
+ # in cropped mode even if regressor does not return probabilities.
191
+ # We implement `predict_proba` because it exists in skorch.NeuralNetRegressor.
192
+ def predict_proba(self, X):
193
+ """Return the output of the module's forward method as a numpy
194
+ array. In case of cropped decoding returns averaged values for
195
+ each trial.
196
+
197
+ If the module's forward method returns multiple outputs as a
198
+ tuple, it is assumed that the first output contains the
199
+ relevant information and the other values are ignored.
200
+ If all values are relevant or module's output for each crop
201
+ is needed, consider using :func:`~skorch.NeuralNet.forward`
202
+ instead.
203
+
204
+ Parameters
205
+ ----------
206
+ X : input data, compatible with skorch.dataset.Dataset
207
+ By default, you should be able to pass:
208
+
209
+ * numpy arrays
210
+ * torch tensors
211
+ * pandas DataFrame or Series
212
+ * scipy sparse CSR matrices
213
+ * a dictionary of the former three
214
+ * a list/tuple of the former three
215
+ * a Dataset
216
+
217
+ If this doesn't work with your data, you have to pass a
218
+ ``Dataset`` that can deal with the data.
219
+
220
+ Warnings
221
+ --------
222
+ Regressors predict regression targets, so output of this method
223
+ can't be interpreted as probabilities. We advise you to use
224
+ `predict` method instead of `predict_proba`.
225
+
226
+ Returns
227
+ -------
228
+ y_proba : numpy ndarray
229
+
230
+ """
231
+ y_pred = super().predict_proba(X)
232
+ # Normally, we have to average the predictions across crops/timesteps
233
+ # to get one prediction per window/trial
234
+ # Predictions may be already averaged in CroppedTrialEpochScoring (y_pred.shape==2).
235
+ # However, when predictions are computed outside of CroppedTrialEpochScoring
236
+ # we have to average predictions, hence the check if len(y_pred.shape) == 3
237
+ if self.cropped and len(y_pred.shape) == 3:
238
+ return y_pred.mean(-1)
239
+ else:
240
+ return y_pred
@@ -0,0 +1,5 @@
1
+ """Classes to sample examples.
2
+ """
3
+
4
+ from .base import RecordingSampler
5
+ from .ssl import RelativePositioningSampler
@@ -0,0 +1,78 @@
1
+ """
2
+ Sampler classes.
3
+ """
4
+
5
+ # Authors: Hubert Banville <hubert.jbanville@gmail.com>
6
+ #
7
+ # License: BSD (3-clause)
8
+
9
+ from torch.utils.data.sampler import Sampler
10
+ from sklearn.utils import check_random_state
11
+
12
+
13
+ class RecordingSampler(Sampler):
14
+ """Base sampler simplifying sampling from recordings.
15
+
16
+ Parameters
17
+ ----------
18
+ metadata : pd.DataFrame
19
+ DataFrame with at least one of {subject, session, run} columns for each
20
+ window in the BaseConcatDataset to sample examples from. Normally
21
+ obtained with `BaseConcatDataset.get_metadata()`. For instance,
22
+ `metadata.head()` might look like this:
23
+
24
+ i_window_in_trial i_start_in_trial i_stop_in_trial target subject session run
25
+ 0 0 0 500 -1 4 session_T run_0
26
+ 1 1 500 1000 -1 4 session_T run_0
27
+ 2 2 1000 1500 -1 4 session_T run_0
28
+ 3 3 1500 2000 -1 4 session_T run_0
29
+ 4 4 2000 2500 -1 4 session_T run_0
30
+
31
+ random_state : np.RandomState | int | None
32
+ Random state.
33
+
34
+ Attributes
35
+ ----------
36
+ info : pd.DataFrame
37
+ Series with MultiIndex index which contains the subject, session, run
38
+ and window indices information in an easily accessible structure for
39
+ quick sampling of windows.
40
+ """
41
+ def __init__(self, metadata, random_state=None):
42
+ self.metadata = metadata
43
+ self._init_info()
44
+ self.rng = check_random_state(random_state)
45
+
46
+ def _init_info(self):
47
+ keys = [k for k in ['subject', 'session', 'run']
48
+ if k in self.metadata.columns]
49
+ if not keys:
50
+ raise ValueError(
51
+ 'metadata must contain at least one of the following columns: '
52
+ 'subject, session or run.')
53
+
54
+ self.metadata = self.metadata.reset_index().rename(
55
+ columns={'index': 'window_index'})
56
+ self.info = self.metadata.reset_index().groupby(keys)[
57
+ ['index', 'i_start_in_trial']].agg(['unique'])
58
+ self.info.columns = self.info.columns.get_level_values(0)
59
+
60
+ def sample_recording(self):
61
+ """Return a random recording index.
62
+ """
63
+ return self.rng.choice(self.n_recordings)
64
+
65
+ def sample_window(self, rec_ind=None):
66
+ """Return a specific window.
67
+ """
68
+ if rec_ind is None:
69
+ rec_ind = self.sample_recording()
70
+ win_ind = self.rng.choice(self.info.iloc[rec_ind]['index'])
71
+ return win_ind, rec_ind
72
+
73
+ def __iter__(self):
74
+ raise NotImplementedError
75
+
76
+ @property
77
+ def n_recordings(self):
78
+ return self.info.shape[0]