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,232 @@
1
+ # Authors: Robin Tibor Schirrmeister
2
+ # Tonio Ball
3
+ #
4
+ # License: BSD-3
5
+
6
+ import numpy as np
7
+
8
+ import torch
9
+ from torch import nn
10
+ from torch.nn import init
11
+ from torch.nn.functional import elu
12
+
13
+ from .functions import transpose_time_to_spat, squeeze_final_output
14
+ from ..util import np_to_var
15
+ from .modules import Expression, AvgPool2dWithConv, Ensure4d
16
+
17
+
18
+ class EEGResNet(nn.Sequential):
19
+ """
20
+ Residual Network for EEG.
21
+ """
22
+ def __init__(self,
23
+ in_chans,
24
+ n_classes,
25
+ input_window_samples,
26
+ final_pool_length,
27
+ n_first_filters,
28
+ n_layers_per_block=2,
29
+ first_filter_length=3,
30
+ nonlinearity=elu,
31
+ split_first_layer=True,
32
+ batch_norm_alpha=0.1,
33
+ batch_norm_epsilon=1e-4,
34
+ conv_weight_init_fn=lambda w: init.kaiming_normal_(w, a=0)):
35
+ super().__init__()
36
+ self.in_chans = in_chans
37
+ self.n_classes = n_classes
38
+ self.input_window_samples = input_window_samples
39
+ if final_pool_length == 'auto':
40
+ assert input_window_samples is not None
41
+ assert first_filter_length % 2 == 1
42
+ self.final_pool_length = final_pool_length
43
+ self.n_first_filters = n_first_filters
44
+ self.n_layers_per_block = n_layers_per_block
45
+ self.first_filter_length = first_filter_length
46
+ self.nonlinearity = nonlinearity
47
+ self.split_first_layer = split_first_layer
48
+ self.batch_norm_alpha = batch_norm_alpha
49
+ self.batch_norm_epsilon = batch_norm_epsilon
50
+ self.conv_weight_init_fn = conv_weight_init_fn
51
+
52
+ self.add_module("ensuredims", Ensure4d())
53
+ if self.split_first_layer:
54
+ self.add_module('dimshuffle', Expression(transpose_time_to_spat))
55
+ self.add_module('conv_time', nn.Conv2d(1, self.n_first_filters,
56
+ (self.first_filter_length, 1),
57
+ stride=1,
58
+ padding=(self.first_filter_length // 2, 0)))
59
+ self.add_module('conv_spat',
60
+ nn.Conv2d(self.n_first_filters, self.n_first_filters,
61
+ (1, self.in_chans),
62
+ stride=(1, 1),
63
+ bias=False))
64
+ else:
65
+ self.add_module('conv_time',
66
+ nn.Conv2d(self.in_chans, self.n_first_filters,
67
+ (self.first_filter_length, 1),
68
+ stride=(1, 1),
69
+ padding=(self.first_filter_length // 2, 0),
70
+ bias=False,))
71
+ n_filters_conv = self.n_first_filters
72
+ self.add_module('bnorm',
73
+ nn.BatchNorm2d(n_filters_conv,
74
+ momentum=self.batch_norm_alpha,
75
+ affine=True,
76
+ eps=1e-5),)
77
+ self.add_module('conv_nonlin', Expression(self.nonlinearity))
78
+ cur_dilation = np.array([1, 1])
79
+ n_cur_filters = n_filters_conv
80
+ i_block = 1
81
+ for i_layer in range(self.n_layers_per_block):
82
+ self.add_module('res_{:d}_{:d}'.format(i_block, i_layer),
83
+ _ResidualBlock(n_cur_filters, n_cur_filters,
84
+ dilation=cur_dilation))
85
+ i_block += 1
86
+ cur_dilation[0] *= 2
87
+ n_out_filters = int(2 * n_cur_filters)
88
+ self.add_module('res_{:d}_{:d}'.format(i_block, 0),
89
+ _ResidualBlock(n_cur_filters, n_out_filters,
90
+ dilation=cur_dilation,))
91
+ n_cur_filters = n_out_filters
92
+ for i_layer in range(1, self.n_layers_per_block):
93
+ self.add_module('res_{:d}_{:d}'.format(i_block, i_layer),
94
+ _ResidualBlock(n_cur_filters, n_cur_filters,
95
+ dilation=cur_dilation))
96
+
97
+ i_block += 1
98
+ cur_dilation[0] *= 2
99
+ n_out_filters = int(1.5 * n_cur_filters)
100
+ self.add_module('res_{:d}_{:d}'.format(i_block, 0),
101
+ _ResidualBlock(n_cur_filters, n_out_filters,
102
+ dilation=cur_dilation,))
103
+ n_cur_filters = n_out_filters
104
+ for i_layer in range(1, self.n_layers_per_block):
105
+ self.add_module('res_{:d}_{:d}'.format(i_block, i_layer),
106
+ _ResidualBlock(n_cur_filters, n_cur_filters,
107
+ dilation=cur_dilation))
108
+
109
+ i_block += 1
110
+ cur_dilation[0] *= 2
111
+ self.add_module('res_{:d}_{:d}'.format(i_block, 0),
112
+ _ResidualBlock(n_cur_filters, n_cur_filters,
113
+ dilation=cur_dilation,))
114
+ for i_layer in range(1, self.n_layers_per_block):
115
+ self.add_module('res_{:d}_{:d}'.format(i_block, i_layer),
116
+ _ResidualBlock(n_cur_filters, n_cur_filters,
117
+ dilation=cur_dilation))
118
+
119
+ i_block += 1
120
+ cur_dilation[0] *= 2
121
+ self.add_module('res_{:d}_{:d}'.format(i_block, 0),
122
+ _ResidualBlock(n_cur_filters, n_cur_filters,
123
+ dilation=cur_dilation,))
124
+ for i_layer in range(1, self.n_layers_per_block):
125
+ self.add_module('res_{:d}_{:d}'.format(i_block, i_layer),
126
+ _ResidualBlock(n_cur_filters, n_cur_filters,
127
+ dilation=cur_dilation))
128
+
129
+ i_block += 1
130
+ cur_dilation[0] *= 2
131
+ self.add_module('res_{:d}_{:d}'.format(i_block, 0),
132
+ _ResidualBlock(n_cur_filters, n_cur_filters,
133
+ dilation=cur_dilation,))
134
+ for i_layer in range(1, self.n_layers_per_block):
135
+ self.add_module('res_{:d}_{:d}'.format(i_block, i_layer),
136
+ _ResidualBlock(n_cur_filters, n_cur_filters,
137
+ dilation=cur_dilation))
138
+ i_block += 1
139
+ cur_dilation[0] *= 2
140
+ self.add_module('res_{:d}_{:d}'.format(i_block, 0),
141
+ _ResidualBlock(n_cur_filters, n_cur_filters,
142
+ dilation=cur_dilation,))
143
+ for i_layer in range(1, self.n_layers_per_block):
144
+ self.add_module('res_{:d}_{:d}'.format(i_block, i_layer),
145
+ _ResidualBlock(n_cur_filters, n_cur_filters,
146
+ dilation=cur_dilation))
147
+
148
+ self.eval()
149
+ if self.final_pool_length == 'auto':
150
+ self.add_module('mean_pool', nn.AdaptiveAvgPool2d((1, 1)))
151
+ else:
152
+ pool_dilation = int(cur_dilation[0]), int(cur_dilation[1])
153
+ self.add_module('mean_pool', AvgPool2dWithConv(
154
+ (self.final_pool_length, 1), (1, 1),
155
+ dilation=pool_dilation))
156
+ self.add_module('conv_classifier',
157
+ nn.Conv2d(n_cur_filters, self.n_classes,
158
+ (1, 1), bias=True))
159
+ self.add_module('softmax', nn.LogSoftmax(dim=1))
160
+ self.add_module('squeeze', Expression(squeeze_final_output))
161
+
162
+ # Initialize all weights
163
+ self.apply(lambda module: _weights_init(module, self.conv_weight_init_fn))
164
+
165
+ # Start in eval mode
166
+ self.eval()
167
+
168
+
169
+ def _weights_init(module, conv_weight_init_fn):
170
+ """
171
+ initialize weights
172
+ """
173
+ classname = module.__class__.__name__
174
+ if 'Conv' in classname and classname != "AvgPool2dWithConv":
175
+ conv_weight_init_fn(module.weight)
176
+ if module.bias is not None:
177
+ init.constant_(module.bias, 0)
178
+ elif 'BatchNorm' in classname:
179
+ init.constant_(module.weight, 1)
180
+ init.constant_(module.bias, 0)
181
+
182
+
183
+ class _ResidualBlock(nn.Module):
184
+ """
185
+ create a residual learning building block with two stacked 3x3 convlayers as in paper
186
+ """
187
+ def __init__(self, in_filters,
188
+ out_num_filters,
189
+ dilation,
190
+ filter_time_length=3,
191
+ nonlinearity=elu,
192
+ batch_norm_alpha=0.1, batch_norm_epsilon=1e-4):
193
+ super(_ResidualBlock, self).__init__()
194
+ time_padding = int((filter_time_length - 1) * dilation[0])
195
+ assert time_padding % 2 == 0
196
+ time_padding = int(time_padding // 2)
197
+ dilation = (int(dilation[0]), int(dilation[1]))
198
+ assert (out_num_filters - in_filters) % 2 == 0, (
199
+ "Need even number of extra channels in order to be able to "
200
+ "pad correctly")
201
+ self.n_pad_chans = out_num_filters - in_filters
202
+
203
+ self.conv_1 = nn.Conv2d(
204
+ in_filters, out_num_filters, (filter_time_length, 1), stride=(1, 1),
205
+ dilation=dilation,
206
+ padding=(time_padding, 0))
207
+ self.bn1 = nn.BatchNorm2d(
208
+ out_num_filters, momentum=batch_norm_alpha, affine=True,
209
+ eps=batch_norm_epsilon)
210
+ self.conv_2 = nn.Conv2d(
211
+ out_num_filters, out_num_filters, (filter_time_length, 1), stride=(1, 1),
212
+ dilation=dilation,
213
+ padding=(time_padding, 0))
214
+ self.bn2 = nn.BatchNorm2d(
215
+ out_num_filters, momentum=batch_norm_alpha,
216
+ affine=True, eps=batch_norm_epsilon)
217
+ # also see https://mail.google.com/mail/u/0/#search/ilya+joos/1576137dd34c3127
218
+ # for resnet options as ilya used them
219
+ self.nonlinearity = nonlinearity
220
+
221
+ def forward(self, x):
222
+ stack_1 = self.nonlinearity(self.bn1(self.conv_1(x)))
223
+ stack_2 = self.bn2(self.conv_2(stack_1)) # next nonlin after sum
224
+ if self.n_pad_chans != 0:
225
+ zeros_for_padding = torch.autograd.Variable(
226
+ torch.zeros(x.size()[0], self.n_pad_chans // 2,
227
+ x.size()[2], x.size()[3]))
228
+ if x.is_cuda:
229
+ zeros_for_padding = zeros_for_padding.cuda()
230
+ x = torch.cat((zeros_for_padding, x, zeros_for_padding), dim=1)
231
+ out = self.nonlinearity(x + stack_2)
232
+ return out
@@ -0,0 +1,43 @@
1
+ import torch
2
+
3
+
4
+ def square(x):
5
+ return x * x
6
+
7
+
8
+ def safe_log(x, eps=1e-6):
9
+ """ Prevents :math:`log(0)` by using :math:`log(max(x, eps))`."""
10
+ return torch.log(torch.clamp(x, min=eps))
11
+
12
+
13
+ def identity(x):
14
+ return x
15
+
16
+
17
+ def squeeze_final_output(x):
18
+ """Removes empty dimension at end and potentially removes empty time
19
+ dimension. It does not just use squeeze as we never want to remove
20
+ first dimension.
21
+
22
+ Returns
23
+ -------
24
+ x: torch.Tensor
25
+ squeezed tensor
26
+ """
27
+
28
+ assert x.size()[3] == 1
29
+ x = x[:, :, :, 0]
30
+ if x.size()[2] == 1:
31
+ x = x[:, :, 0]
32
+ return x
33
+
34
+
35
+ def transpose_time_to_spat(x):
36
+ """Swap time and spatial dimensions.
37
+
38
+ Returns
39
+ -------
40
+ x: torch.Tensor
41
+ tensor in which last and first dimensions are swapped
42
+ """
43
+ return x.permute(0, 3, 2, 1)
@@ -0,0 +1,103 @@
1
+ import torch
2
+ from torch import nn
3
+ from torch.nn import ConstantPad2d
4
+
5
+ from .deep4 import Deep4Net
6
+ from .util import to_dense_prediction_model
7
+ from .shallow_fbcsp import ShallowFBCSPNet
8
+
9
+
10
+ class HybridNet(nn.Module):
11
+ """Hybrid ConvNet model from [3]_.
12
+
13
+ Very hardcoded at the moment.
14
+
15
+ References
16
+ ----------
17
+
18
+ .. [3] Schirrmeister, R. T., Springenberg, J. T., Fiederer, L. D. J.,
19
+ Glasstetter, M., Eggensperger, K., Tangermann, M., Hutter, F. & Ball, T. (2017).
20
+ Deep learning with convolutional neural networks for EEG decoding and
21
+ visualization.
22
+ Human Brain Mapping , Aug. 2017. Online: http://dx.doi.org/10.1002/hbm.23730
23
+ """
24
+
25
+ def __init__(self, in_chans, n_classes, input_window_samples):
26
+ super(HybridNet, self).__init__()
27
+ deep_model = Deep4Net(
28
+ in_chans,
29
+ n_classes,
30
+ n_filters_time=20,
31
+ n_filters_spat=30,
32
+ n_filters_2=40,
33
+ n_filters_3=50,
34
+ n_filters_4=60,
35
+ input_window_samples=input_window_samples,
36
+ final_conv_length=2,
37
+ )
38
+ shallow_model = ShallowFBCSPNet(
39
+ in_chans,
40
+ n_classes,
41
+ input_window_samples=input_window_samples,
42
+ n_filters_time=30,
43
+ n_filters_spat=40,
44
+ filter_time_length=28,
45
+ final_conv_length=29,
46
+ )
47
+
48
+ reduced_deep_model = nn.Sequential()
49
+ for name, module in deep_model.named_children():
50
+ if name == "conv_classifier":
51
+ new_conv_layer = nn.Conv2d(
52
+ module.in_channels,
53
+ 60,
54
+ kernel_size=module.kernel_size,
55
+ stride=module.stride,
56
+ )
57
+ reduced_deep_model.add_module("deep_final_conv", new_conv_layer)
58
+ break
59
+ reduced_deep_model.add_module(name, module)
60
+
61
+ reduced_shallow_model = nn.Sequential()
62
+ for name, module in shallow_model.named_children():
63
+ if name == "conv_classifier":
64
+ new_conv_layer = nn.Conv2d(
65
+ module.in_channels,
66
+ 40,
67
+ kernel_size=module.kernel_size,
68
+ stride=module.stride,
69
+ )
70
+ reduced_shallow_model.add_module(
71
+ "shallow_final_conv", new_conv_layer
72
+ )
73
+ break
74
+ reduced_shallow_model.add_module(name, module)
75
+
76
+ to_dense_prediction_model(reduced_deep_model)
77
+ to_dense_prediction_model(reduced_shallow_model)
78
+ self.reduced_deep_model = reduced_deep_model
79
+ self.reduced_shallow_model = reduced_shallow_model
80
+ self.final_conv = nn.Conv2d(
81
+ 100, n_classes, kernel_size=(1, 1), stride=1
82
+ )
83
+
84
+ def forward(self, x):
85
+ deep_out = self.reduced_deep_model(x)
86
+ shallow_out = self.reduced_shallow_model(x)
87
+
88
+ n_diff_deep_shallow = deep_out.size()[2] - shallow_out.size()[2]
89
+
90
+ if n_diff_deep_shallow < 0:
91
+ deep_out = ConstantPad2d((0, 0, -n_diff_deep_shallow, 0), 0)(
92
+ deep_out
93
+ )
94
+ elif n_diff_deep_shallow > 0:
95
+ shallow_out = ConstantPad2d((0, 0, n_diff_deep_shallow, 0), 0)(
96
+ shallow_out
97
+ )
98
+
99
+ merged_out = torch.cat((deep_out, shallow_out), dim=1)
100
+ linear_out = self.final_conv(merged_out)
101
+ softmaxed = nn.LogSoftmax(dim=1)(linear_out)
102
+ squeezed = softmaxed.squeeze(3)
103
+ return squeezed
@@ -0,0 +1,150 @@
1
+ import numpy as np
2
+
3
+ import torch
4
+ import torch.nn.functional as F
5
+
6
+ from ..util import np_to_var
7
+
8
+
9
+ class Ensure4d(torch.nn.Module):
10
+ def forward(self, x):
11
+ while(len(x.shape) < 4):
12
+ x = x.unsqueeze(-1)
13
+ return x
14
+
15
+
16
+ class Expression(torch.nn.Module):
17
+ """Compute given expression on forward pass.
18
+
19
+ Parameters
20
+ ----------
21
+ expression_fn : callable
22
+ Should accept variable number of objects of type
23
+ `torch.autograd.Variable` to compute its output.
24
+ """
25
+
26
+ def __init__(self, expression_fn):
27
+ super(Expression, self).__init__()
28
+ self.expression_fn = expression_fn
29
+
30
+ def forward(self, *x):
31
+ return self.expression_fn(*x)
32
+
33
+ def __repr__(self):
34
+ if hasattr(self.expression_fn, "func") and hasattr(
35
+ self.expression_fn, "kwargs"
36
+ ):
37
+ expression_str = "{:s} {:s}".format(
38
+ self.expression_fn.func.__name__, str(self.expression_fn.kwargs)
39
+ )
40
+ elif hasattr(self.expression_fn, "__name__"):
41
+ expression_str = self.expression_fn.__name__
42
+ else:
43
+ expression_str = repr(self.expression_fn)
44
+ return (
45
+ self.__class__.__name__ +
46
+ "(expression=%s) " % expression_str
47
+ )
48
+
49
+
50
+ class AvgPool2dWithConv(torch.nn.Module):
51
+ """
52
+ Compute average pooling using a convolution, to have the dilation parameter.
53
+
54
+ Parameters
55
+ ----------
56
+ kernel_size: (int,int)
57
+ Size of the pooling region.
58
+ stride: (int,int)
59
+ Stride of the pooling operation.
60
+ dilation: int or (int,int)
61
+ Dilation applied to the pooling filter.
62
+ padding: int or (int,int)
63
+ Padding applied before the pooling operation.
64
+ """
65
+
66
+ def __init__(self, kernel_size, stride, dilation=1, padding=0):
67
+ super(AvgPool2dWithConv, self).__init__()
68
+ self.kernel_size = kernel_size
69
+ self.stride = stride
70
+ self.dilation = dilation
71
+ self.padding = padding
72
+ # don't name them "weights" to
73
+ # make sure these are not accidentally used by some procedure
74
+ # that initializes parameters or something
75
+ self._pool_weights = None
76
+
77
+ def forward(self, x):
78
+ # Create weights for the convolution on demand:
79
+ # size or type of x changed...
80
+ in_channels = x.size()[1]
81
+ weight_shape = (
82
+ in_channels,
83
+ 1,
84
+ self.kernel_size[0],
85
+ self.kernel_size[1],
86
+ )
87
+ if self._pool_weights is None or (
88
+ (tuple(self._pool_weights.size()) != tuple(weight_shape)) or
89
+ (self._pool_weights.is_cuda != x.is_cuda) or
90
+ (self._pool_weights.data.type() != x.data.type())
91
+ ):
92
+ n_pool = np.prod(self.kernel_size)
93
+ weights = np_to_var(
94
+ np.ones(weight_shape, dtype=np.float32) / float(n_pool)
95
+ )
96
+ weights = weights.type_as(x)
97
+ if x.is_cuda:
98
+ weights = weights.cuda()
99
+ self._pool_weights = weights
100
+
101
+ pooled = F.conv2d(
102
+ x,
103
+ self._pool_weights,
104
+ bias=None,
105
+ stride=self.stride,
106
+ dilation=self.dilation,
107
+ padding=self.padding,
108
+ groups=in_channels,
109
+ )
110
+ return pooled
111
+
112
+
113
+ class IntermediateOutputWrapper(torch.nn.Module):
114
+ """Wraps network model such that outputs of intermediate layers can be returned.
115
+ forward() returns list of intermediate activations in a network during forward pass.
116
+
117
+ Parameters
118
+ ----------
119
+ to_select : list
120
+ list of module names for which activation should be returned
121
+ model : model object
122
+ network model
123
+
124
+ Examples
125
+ --------
126
+ >>> model = Deep4Net()
127
+ >>> select_modules = ['conv_spat','conv_2','conv_3','conv_4'] # Specify intermediate outputs
128
+ >>> model_pert = IntermediateOutputWrapper(select_modules,model) # Wrap model
129
+ """
130
+
131
+ def __init__(self, to_select, model):
132
+ if not len(list(model.children())) == len(list(model.named_children())):
133
+ raise Exception("All modules in model need to have names!")
134
+
135
+ super(IntermediateOutputWrapper, self).__init__()
136
+
137
+ modules_list = model.named_children()
138
+ for key, module in modules_list:
139
+ self.add_module(key, module)
140
+ self._modules[key].load_state_dict(module.state_dict())
141
+ self._to_select = to_select
142
+
143
+ def forward(self, x):
144
+ # Call modules individually and append activation to output if module is in to_select
145
+ o = []
146
+ for name, module in self._modules.items():
147
+ x = module(x)
148
+ if name in self._to_select:
149
+ o.append(x)
150
+ return o
@@ -0,0 +1,158 @@
1
+ import numpy as np
2
+ from torch import nn
3
+ from torch.nn import init
4
+
5
+ from ..util import np_to_var
6
+ from .modules import Expression, Ensure4d
7
+ from .functions import (
8
+ safe_log, square, transpose_time_to_spat, squeeze_final_output
9
+ )
10
+
11
+
12
+ class ShallowFBCSPNet(nn.Sequential):
13
+ """Shallow ConvNet model from [2]_.
14
+
15
+ Parameters
16
+ ----------
17
+ in_chans : int
18
+ XXX
19
+
20
+ References
21
+ ----------
22
+ .. [2] Schirrmeister, R. T., Springenberg, J. T., Fiederer, L. D. J.,
23
+ Glasstetter, M., Eggensperger, K., Tangermann, M., Hutter, F. & Ball, T. (2017).
24
+ Deep learning with convolutional neural networks for EEG decoding and
25
+ visualization.
26
+ Human Brain Mapping , Aug. 2017. Online: http://dx.doi.org/10.1002/hbm.23730
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ in_chans,
32
+ n_classes,
33
+ input_window_samples=None,
34
+ n_filters_time=40,
35
+ filter_time_length=25,
36
+ n_filters_spat=40,
37
+ pool_time_length=75,
38
+ pool_time_stride=15,
39
+ final_conv_length=30,
40
+ conv_nonlin=square,
41
+ pool_mode="mean",
42
+ pool_nonlin=safe_log,
43
+ split_first_layer=True,
44
+ batch_norm=True,
45
+ batch_norm_alpha=0.1,
46
+ drop_prob=0.5,
47
+ ):
48
+ super().__init__()
49
+ if final_conv_length == "auto":
50
+ assert input_window_samples is not None
51
+ self.in_chans = in_chans
52
+ self.n_classes = n_classes
53
+ self.input_window_samples = input_window_samples
54
+ self.n_filters_time = n_filters_time
55
+ self.filter_time_length = filter_time_length
56
+ self.n_filters_spat = n_filters_spat
57
+ self.pool_time_length = pool_time_length
58
+ self.pool_time_stride = pool_time_stride
59
+ self.final_conv_length = final_conv_length
60
+ self.conv_nonlin = conv_nonlin
61
+ self.pool_mode = pool_mode
62
+ self.pool_nonlin = pool_nonlin
63
+ self.split_first_layer = split_first_layer
64
+ self.batch_norm = batch_norm
65
+ self.batch_norm_alpha = batch_norm_alpha
66
+ self.drop_prob = drop_prob
67
+
68
+ self.add_module("ensuredims", Ensure4d())
69
+ pool_class = dict(max=nn.MaxPool2d, mean=nn.AvgPool2d)[self.pool_mode]
70
+ if self.split_first_layer:
71
+ self.add_module("dimshuffle", Expression(transpose_time_to_spat))
72
+ self.add_module(
73
+ "conv_time",
74
+ nn.Conv2d(
75
+ 1,
76
+ self.n_filters_time,
77
+ (self.filter_time_length, 1),
78
+ stride=1,
79
+ ),
80
+ )
81
+ self.add_module(
82
+ "conv_spat",
83
+ nn.Conv2d(
84
+ self.n_filters_time,
85
+ self.n_filters_spat,
86
+ (1, self.in_chans),
87
+ stride=1,
88
+ bias=not self.batch_norm,
89
+ ),
90
+ )
91
+ n_filters_conv = self.n_filters_spat
92
+ else:
93
+ self.add_module(
94
+ "conv_time",
95
+ nn.Conv2d(
96
+ self.in_chans,
97
+ self.n_filters_time,
98
+ (self.filter_time_length, 1),
99
+ stride=1,
100
+ bias=not self.batch_norm,
101
+ ),
102
+ )
103
+ n_filters_conv = self.n_filters_time
104
+ if self.batch_norm:
105
+ self.add_module(
106
+ "bnorm",
107
+ nn.BatchNorm2d(
108
+ n_filters_conv, momentum=self.batch_norm_alpha, affine=True
109
+ ),
110
+ )
111
+ self.add_module("conv_nonlin_exp", Expression(self.conv_nonlin))
112
+ self.add_module(
113
+ "pool",
114
+ pool_class(
115
+ kernel_size=(self.pool_time_length, 1),
116
+ stride=(self.pool_time_stride, 1),
117
+ ),
118
+ )
119
+ self.add_module("pool_nonlin_exp", Expression(self.pool_nonlin))
120
+ self.add_module("drop", nn.Dropout(p=self.drop_prob))
121
+ self.eval()
122
+ if self.final_conv_length == "auto":
123
+ out = self(
124
+ np_to_var(
125
+ np.ones(
126
+ (1, self.in_chans, self.input_window_samples, 1),
127
+ dtype=np.float32,
128
+ )
129
+ )
130
+ )
131
+ n_out_time = out.cpu().data.numpy().shape[2]
132
+ self.final_conv_length = n_out_time
133
+ self.add_module(
134
+ "conv_classifier",
135
+ nn.Conv2d(
136
+ n_filters_conv,
137
+ self.n_classes,
138
+ (self.final_conv_length, 1),
139
+ bias=True,
140
+ ),
141
+ )
142
+ self.add_module("softmax", nn.LogSoftmax(dim=1))
143
+ self.add_module("squeeze", Expression(squeeze_final_output))
144
+
145
+ # Initialization, xavier is same as in paper...
146
+ init.xavier_uniform_(self.conv_time.weight, gain=1)
147
+ # maybe no bias in case of no split layer and batch norm
148
+ if self.split_first_layer or (not self.batch_norm):
149
+ init.constant_(self.conv_time.bias, 0)
150
+ if self.split_first_layer:
151
+ init.xavier_uniform_(self.conv_spat.weight, gain=1)
152
+ if not self.batch_norm:
153
+ init.constant_(self.conv_spat.bias, 0)
154
+ if self.batch_norm:
155
+ init.constant_(self.bnorm.weight, 1)
156
+ init.constant_(self.bnorm.bias, 0)
157
+ init.xavier_uniform_(self.conv_classifier.weight, gain=1)
158
+ init.constant_(self.conv_classifier.bias, 0)