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,252 @@
1
+ import numpy as np
2
+ from torch import nn
3
+ from torch.nn import init
4
+ from torch.nn.functional import elu
5
+
6
+ from .modules import Expression, AvgPool2dWithConv, Ensure4d
7
+ from .functions import identity, transpose_time_to_spat, squeeze_final_output
8
+ from ..util import np_to_var
9
+
10
+
11
+ class Deep4Net(nn.Sequential):
12
+ """
13
+ Deep ConvNet model from [1]_.
14
+
15
+ References
16
+ ----------
17
+
18
+ .. [1] 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__(
26
+ self,
27
+ in_chans,
28
+ n_classes,
29
+ input_window_samples,
30
+ final_conv_length,
31
+ n_filters_time=25,
32
+ n_filters_spat=25,
33
+ filter_time_length=10,
34
+ pool_time_length=3,
35
+ pool_time_stride=3,
36
+ n_filters_2=50,
37
+ filter_length_2=10,
38
+ n_filters_3=100,
39
+ filter_length_3=10,
40
+ n_filters_4=200,
41
+ filter_length_4=10,
42
+ first_nonlin=elu,
43
+ first_pool_mode="max",
44
+ first_pool_nonlin=identity,
45
+ later_nonlin=elu,
46
+ later_pool_mode="max",
47
+ later_pool_nonlin=identity,
48
+ drop_prob=0.5,
49
+ double_time_convs=False,
50
+ split_first_layer=True,
51
+ batch_norm=True,
52
+ batch_norm_alpha=0.1,
53
+ stride_before_pool=False,
54
+ ):
55
+ super().__init__()
56
+ if final_conv_length == "auto":
57
+ assert input_window_samples is not None
58
+ self.in_chans = in_chans
59
+ self.n_classes = n_classes
60
+ self.input_window_samples = input_window_samples
61
+ self.final_conv_length = final_conv_length
62
+ self.n_filters_time = n_filters_time
63
+ self.n_filters_spat = n_filters_spat
64
+ self.filter_time_length = filter_time_length
65
+ self.pool_time_length = pool_time_length
66
+ self.pool_time_stride = pool_time_stride
67
+ self.n_filters_2 = n_filters_2
68
+ self.filter_length_2 = filter_length_2
69
+ self.n_filters_3 = n_filters_3
70
+ self.filter_length_3 = filter_length_3
71
+ self.n_filters_4 = n_filters_4
72
+ self.filter_length_4 = filter_length_4
73
+ self.first_nonlin = first_nonlin
74
+ self.first_pool_mode = first_pool_mode
75
+ self.first_pool_nonlin = first_pool_nonlin
76
+ self.later_nonlin = later_nonlin
77
+ self.later_pool_mode = later_pool_mode
78
+ self.later_pool_nonlin = later_pool_nonlin
79
+ self.drop_prob = drop_prob
80
+ self.double_time_convs = double_time_convs
81
+ self.split_first_layer = split_first_layer
82
+ self.batch_norm = batch_norm
83
+ self.batch_norm_alpha = batch_norm_alpha
84
+ self.stride_before_pool = stride_before_pool
85
+
86
+ if self.stride_before_pool:
87
+ conv_stride = self.pool_time_stride
88
+ pool_stride = 1
89
+ else:
90
+ conv_stride = 1
91
+ pool_stride = self.pool_time_stride
92
+ self.add_module("ensuredims", Ensure4d())
93
+ pool_class_dict = dict(max=nn.MaxPool2d, mean=AvgPool2dWithConv)
94
+ first_pool_class = pool_class_dict[self.first_pool_mode]
95
+ later_pool_class = pool_class_dict[self.later_pool_mode]
96
+ if self.split_first_layer:
97
+ self.add_module("dimshuffle", Expression(transpose_time_to_spat))
98
+ self.add_module(
99
+ "conv_time",
100
+ nn.Conv2d(
101
+ 1,
102
+ self.n_filters_time,
103
+ (self.filter_time_length, 1),
104
+ stride=1,
105
+ ),
106
+ )
107
+ self.add_module(
108
+ "conv_spat",
109
+ nn.Conv2d(
110
+ self.n_filters_time,
111
+ self.n_filters_spat,
112
+ (1, self.in_chans),
113
+ stride=(conv_stride, 1),
114
+ bias=not self.batch_norm,
115
+ ),
116
+ )
117
+ n_filters_conv = self.n_filters_spat
118
+ else:
119
+ self.add_module(
120
+ "conv_time",
121
+ nn.Conv2d(
122
+ self.in_chans,
123
+ self.n_filters_time,
124
+ (self.filter_time_length, 1),
125
+ stride=(conv_stride, 1),
126
+ bias=not self.batch_norm,
127
+ ),
128
+ )
129
+ n_filters_conv = self.n_filters_time
130
+ if self.batch_norm:
131
+ self.add_module(
132
+ "bnorm",
133
+ nn.BatchNorm2d(
134
+ n_filters_conv,
135
+ momentum=self.batch_norm_alpha,
136
+ affine=True,
137
+ eps=1e-5,
138
+ ),
139
+ )
140
+ self.add_module("conv_nonlin", Expression(self.first_nonlin))
141
+ self.add_module(
142
+ "pool",
143
+ first_pool_class(
144
+ kernel_size=(self.pool_time_length, 1), stride=(pool_stride, 1)
145
+ ),
146
+ )
147
+ self.add_module("pool_nonlin", Expression(self.first_pool_nonlin))
148
+
149
+ def add_conv_pool_block(
150
+ model, n_filters_before, n_filters, filter_length, block_nr
151
+ ):
152
+ suffix = "_{:d}".format(block_nr)
153
+ self.add_module("drop" + suffix, nn.Dropout(p=self.drop_prob))
154
+ self.add_module(
155
+ "conv" + suffix,
156
+ nn.Conv2d(
157
+ n_filters_before,
158
+ n_filters,
159
+ (filter_length, 1),
160
+ stride=(conv_stride, 1),
161
+ bias=not self.batch_norm,
162
+ ),
163
+ )
164
+ if self.batch_norm:
165
+ self.add_module(
166
+ "bnorm" + suffix,
167
+ nn.BatchNorm2d(
168
+ n_filters,
169
+ momentum=self.batch_norm_alpha,
170
+ affine=True,
171
+ eps=1e-5,
172
+ ),
173
+ )
174
+ self.add_module("nonlin" + suffix, Expression(self.later_nonlin))
175
+
176
+ self.add_module(
177
+ "pool" + suffix,
178
+ later_pool_class(
179
+ kernel_size=(self.pool_time_length, 1),
180
+ stride=(pool_stride, 1),
181
+ ),
182
+ )
183
+ self.add_module(
184
+ "pool_nonlin" + suffix, Expression(self.later_pool_nonlin)
185
+ )
186
+
187
+ add_conv_pool_block(
188
+ self, n_filters_conv, self.n_filters_2, self.filter_length_2, 2
189
+ )
190
+ add_conv_pool_block(
191
+ self, self.n_filters_2, self.n_filters_3, self.filter_length_3, 3
192
+ )
193
+ add_conv_pool_block(
194
+ self, self.n_filters_3, self.n_filters_4, self.filter_length_4, 4
195
+ )
196
+
197
+ # self.add_module('drop_classifier', nn.Dropout(p=self.drop_prob))
198
+ self.eval()
199
+ if self.final_conv_length == "auto":
200
+ out = self(
201
+ np_to_var(
202
+ np.ones(
203
+ (1, self.in_chans, self.input_window_samples, 1),
204
+ dtype=np.float32,
205
+ )
206
+ )
207
+ )
208
+ n_out_time = out.cpu().data.numpy().shape[2]
209
+ self.final_conv_length = n_out_time
210
+ self.add_module(
211
+ "conv_classifier",
212
+ nn.Conv2d(
213
+ self.n_filters_4,
214
+ self.n_classes,
215
+ (self.final_conv_length, 1),
216
+ bias=True,
217
+ ),
218
+ )
219
+ self.add_module("softmax", nn.LogSoftmax(dim=1))
220
+ self.add_module("squeeze", Expression(squeeze_final_output))
221
+
222
+ # Initialization, xavier is same as in our paper...
223
+ # was default from lasagne
224
+ init.xavier_uniform_(self.conv_time.weight, gain=1)
225
+ # maybe no bias in case of no split layer and batch norm
226
+ if self.split_first_layer or (not self.batch_norm):
227
+ init.constant_(self.conv_time.bias, 0)
228
+ if self.split_first_layer:
229
+ init.xavier_uniform_(self.conv_spat.weight, gain=1)
230
+ if not self.batch_norm:
231
+ init.constant_(self.conv_spat.bias, 0)
232
+ if self.batch_norm:
233
+ init.constant_(self.bnorm.weight, 1)
234
+ init.constant_(self.bnorm.bias, 0)
235
+ param_dict = dict(list(self.named_parameters()))
236
+ for block_nr in range(2, 5):
237
+ conv_weight = param_dict["conv_{:d}.weight".format(block_nr)]
238
+ init.xavier_uniform_(conv_weight, gain=1)
239
+ if not self.batch_norm:
240
+ conv_bias = param_dict["conv_{:d}.bias".format(block_nr)]
241
+ init.constant_(conv_bias, 0)
242
+ else:
243
+ bnorm_weight = param_dict["bnorm_{:d}.weight".format(block_nr)]
244
+ bnorm_bias = param_dict["bnorm_{:d}.bias".format(block_nr)]
245
+ init.constant_(bnorm_weight, 1)
246
+ init.constant_(bnorm_bias, 0)
247
+
248
+ init.xavier_uniform_(self.conv_classifier.weight, gain=1)
249
+ init.constant_(self.conv_classifier.bias, 0)
250
+
251
+ # Start in eval mode
252
+ self.eval()
@@ -0,0 +1,339 @@
1
+ import torch
2
+ from torch import nn
3
+ from torch.nn.functional import elu
4
+
5
+ from .modules import Expression, Ensure4d
6
+ from .functions import squeeze_final_output
7
+
8
+ class Conv2dWithConstraint(nn.Conv2d):
9
+ def __init__(self, *args, max_norm=1, **kwargs):
10
+ self.max_norm = max_norm
11
+ super(Conv2dWithConstraint, self).__init__(*args, **kwargs)
12
+
13
+ def forward(self, x):
14
+ self.weight.data = torch.renorm(
15
+ self.weight.data, p=2, dim=0, maxnorm=self.max_norm
16
+ )
17
+ return super(Conv2dWithConstraint, self).forward(x)
18
+
19
+
20
+ class EEGNetv4(nn.Sequential):
21
+ """
22
+ EEGNet v4 model from [EEGNet4]_.
23
+
24
+ Notes
25
+ -----
26
+ This implementation is not guaranteed to be correct, has not been checked
27
+ by original authors, only reimplemented from the paper description.
28
+
29
+ References
30
+ ----------
31
+
32
+ .. [EEGNet4] Lawhern, V. J., Solon, A. J., Waytowich, N. R., Gordon,
33
+ S. M., Hung, C. P., & Lance, B. J. (2018).
34
+ EEGNet: A Compact Convolutional Network for EEG-based
35
+ Brain-Computer Interfaces.
36
+ arXiv preprint arXiv:1611.08024.
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ in_chans,
42
+ n_classes,
43
+ input_window_samples=None,
44
+ final_conv_length="auto",
45
+ pool_mode="mean",
46
+ F1=8,
47
+ D=2,
48
+ F2=16, # usually set to F1*D (?)
49
+ kernel_length=64,
50
+ third_kernel_size=(8, 4),
51
+ drop_prob=0.25,
52
+ ):
53
+ super().__init__()
54
+ if final_conv_length == "auto":
55
+ assert input_window_samples is not None
56
+ self.in_chans = in_chans
57
+ self.n_classes = n_classes
58
+ self.input_window_samples = input_window_samples
59
+ self.final_conv_length = final_conv_length
60
+ self.pool_mode = pool_mode
61
+ self.F1 = F1
62
+ self.D = D
63
+ self.F2 = F2
64
+ self.kernel_length = kernel_length
65
+ self.third_kernel_size = third_kernel_size
66
+ self.drop_prob = drop_prob
67
+
68
+ pool_class = dict(max=nn.MaxPool2d, mean=nn.AvgPool2d)[self.pool_mode]
69
+ self.add_module("ensuredims", Ensure4d())
70
+ # b c 0 1
71
+ # now to b 1 0 c
72
+ self.add_module("dimshuffle", Expression(_transpose_to_b_1_c_0))
73
+
74
+ self.add_module(
75
+ "conv_temporal",
76
+ nn.Conv2d(
77
+ 1,
78
+ self.F1,
79
+ (1, self.kernel_length),
80
+ stride=1,
81
+ bias=False,
82
+ padding=(0, self.kernel_length // 2),
83
+ ),
84
+ )
85
+ self.add_module(
86
+ "bnorm_temporal",
87
+ nn.BatchNorm2d(self.F1, momentum=0.01, affine=True, eps=1e-3),
88
+ )
89
+ self.add_module(
90
+ "conv_spatial",
91
+ Conv2dWithConstraint(
92
+ self.F1,
93
+ self.F1 * self.D,
94
+ (self.in_chans, 1),
95
+ max_norm=1,
96
+ stride=1,
97
+ bias=False,
98
+ groups=self.F1,
99
+ padding=(0, 0),
100
+ ),
101
+ )
102
+
103
+ self.add_module(
104
+ "bnorm_1",
105
+ nn.BatchNorm2d(
106
+ self.F1 * self.D, momentum=0.01, affine=True, eps=1e-3
107
+ ),
108
+ )
109
+ self.add_module("elu_1", Expression(elu))
110
+
111
+ self.add_module("pool_1", pool_class(kernel_size=(1, 4), stride=(1, 4)))
112
+ self.add_module("drop_1", nn.Dropout(p=self.drop_prob))
113
+
114
+ # https://discuss.pytorch.org/t/how-to-modify-a-conv2d-to-depthwise-separable-convolution/15843/7
115
+ self.add_module(
116
+ "conv_separable_depth",
117
+ nn.Conv2d(
118
+ self.F1 * self.D,
119
+ self.F1 * self.D,
120
+ (1, 16),
121
+ stride=1,
122
+ bias=False,
123
+ groups=self.F1 * self.D,
124
+ padding=(0, 16 // 2),
125
+ ),
126
+ )
127
+ self.add_module(
128
+ "conv_separable_point",
129
+ nn.Conv2d(
130
+ self.F1 * self.D,
131
+ self.F2,
132
+ (1, 1),
133
+ stride=1,
134
+ bias=False,
135
+ padding=(0, 0),
136
+ ),
137
+ )
138
+
139
+ self.add_module(
140
+ "bnorm_2",
141
+ nn.BatchNorm2d(self.F2, momentum=0.01, affine=True, eps=1e-3),
142
+ )
143
+ self.add_module("elu_2", Expression(elu))
144
+ self.add_module("pool_2", pool_class(kernel_size=(1, 8), stride=(1, 8)))
145
+ self.add_module("drop_2", nn.Dropout(p=self.drop_prob))
146
+
147
+ out = self(
148
+ torch.ones(
149
+ (1, self.in_chans, self.input_window_samples, 1),
150
+ dtype=torch.float32
151
+ )
152
+ )
153
+ n_out_virtual_chans = out.cpu().data.numpy().shape[2]
154
+
155
+ if self.final_conv_length == "auto":
156
+ n_out_time = out.cpu().data.numpy().shape[3]
157
+ self.final_conv_length = n_out_time
158
+
159
+ self.add_module(
160
+ "conv_classifier",
161
+ nn.Conv2d(
162
+ self.F2,
163
+ self.n_classes,
164
+ (n_out_virtual_chans, self.final_conv_length),
165
+ bias=True,
166
+ ),
167
+ )
168
+ self.add_module("softmax", nn.LogSoftmax(dim=1))
169
+ # Transpose back to the the logic of braindecode,
170
+ # so time in third dimension (axis=2)
171
+ self.add_module("permute_back", Expression(_transpose_1_0))
172
+ self.add_module("squeeze", Expression(squeeze_final_output))
173
+
174
+ _glorot_weight_zero_bias(self)
175
+
176
+
177
+ def _transpose_to_b_1_c_0(x):
178
+ return x.permute(0, 3, 1, 2)
179
+
180
+
181
+ def _transpose_1_0(x):
182
+ return x.permute(0, 1, 3, 2)
183
+
184
+
185
+ class EEGNetv1(nn.Sequential):
186
+ """
187
+ EEGNet model from [EEGNet]_.
188
+
189
+ Notes
190
+ -----
191
+ This implementation is not guaranteed to be correct, has not been checked
192
+ by original authors, only reimplemented from the paper description.
193
+
194
+ References
195
+ ----------
196
+
197
+ .. [EEGNet] Lawhern, V. J., Solon, A. J., Waytowich, N. R., Gordon,
198
+ S. M., Hung, C. P., & Lance, B. J. (2016).
199
+ EEGNet: A Compact Convolutional Network for EEG-based
200
+ Brain-Computer Interfaces.
201
+ arXiv preprint arXiv:1611.08024.
202
+ """
203
+
204
+ def __init__(
205
+ self,
206
+ in_chans,
207
+ n_classes,
208
+ input_window_samples=None,
209
+ final_conv_length="auto",
210
+ pool_mode="max",
211
+ second_kernel_size=(2, 32),
212
+ third_kernel_size=(8, 4),
213
+ drop_prob=0.25,
214
+ ):
215
+ super().__init__()
216
+ if final_conv_length == "auto":
217
+ assert input_window_samples is not None
218
+ self.in_chans = in_chans
219
+ self.n_classes = n_classes
220
+ self.input_window_samples = input_window_samples
221
+ self.final_conv_length = final_conv_length
222
+ self.pool_mode = pool_mode
223
+ self.second_kernel_size = second_kernel_size
224
+ self.third_kernel_size = third_kernel_size
225
+ self.drop_prob = drop_prob
226
+
227
+ pool_class = dict(max=nn.MaxPool2d, mean=nn.AvgPool2d)[self.pool_mode]
228
+ self.add_module("ensuredims", Ensure4d())
229
+ n_filters_1 = 16
230
+ self.add_module(
231
+ "conv_1",
232
+ nn.Conv2d(self.in_chans, n_filters_1, (1, 1), stride=1, bias=True),
233
+ )
234
+ self.add_module(
235
+ "bnorm_1",
236
+ nn.BatchNorm2d(n_filters_1, momentum=0.01, affine=True, eps=1e-3),
237
+ )
238
+ self.add_module("elu_1", Expression(elu))
239
+ # transpose to examples x 1 x (virtual, not EEG) channels x time
240
+ self.add_module(
241
+ "permute_1", Expression(lambda x: x.permute(0, 3, 1, 2))
242
+ )
243
+
244
+ self.add_module("drop_1", nn.Dropout(p=self.drop_prob))
245
+
246
+ n_filters_2 = 4
247
+ # keras padds unequal padding more in front, so padding
248
+ # too large should be ok.
249
+ # Not padding in time so that cropped training makes sense
250
+ # https://stackoverflow.com/questions/43994604/padding-with-even-kernel-size-in-a-convolutional-layer-in-keras-theano
251
+
252
+ self.add_module(
253
+ "conv_2",
254
+ nn.Conv2d(
255
+ 1,
256
+ n_filters_2,
257
+ self.second_kernel_size,
258
+ stride=1,
259
+ padding=(self.second_kernel_size[0] // 2, 0),
260
+ bias=True,
261
+ ),
262
+ )
263
+ self.add_module(
264
+ "bnorm_2",
265
+ nn.BatchNorm2d(n_filters_2, momentum=0.01, affine=True, eps=1e-3),
266
+ )
267
+ self.add_module("elu_2", Expression(elu))
268
+ self.add_module("pool_2", pool_class(kernel_size=(2, 4), stride=(2, 4)))
269
+ self.add_module("drop_2", nn.Dropout(p=self.drop_prob))
270
+
271
+ n_filters_3 = 4
272
+ self.add_module(
273
+ "conv_3",
274
+ nn.Conv2d(
275
+ n_filters_2,
276
+ n_filters_3,
277
+ self.third_kernel_size,
278
+ stride=1,
279
+ padding=(self.third_kernel_size[0] // 2, 0),
280
+ bias=True,
281
+ ),
282
+ )
283
+ self.add_module(
284
+ "bnorm_3",
285
+ nn.BatchNorm2d(n_filters_3, momentum=0.01, affine=True, eps=1e-3),
286
+ )
287
+ self.add_module("elu_3", Expression(elu))
288
+ self.add_module("pool_3", pool_class(kernel_size=(2, 4), stride=(2, 4)))
289
+ self.add_module("drop_3", nn.Dropout(p=self.drop_prob))
290
+
291
+ out = self(
292
+ torch.ones(
293
+ (1, self.in_chans, self.input_window_samples, 1),
294
+ dtype=torch.float32,
295
+ )
296
+ )
297
+ n_out_virtual_chans = out.cpu().data.numpy().shape[2]
298
+
299
+ if self.final_conv_length == "auto":
300
+ n_out_time = out.cpu().data.numpy().shape[3]
301
+ self.final_conv_length = n_out_time
302
+
303
+ self.add_module(
304
+ "conv_classifier",
305
+ nn.Conv2d(
306
+ n_filters_3,
307
+ self.n_classes,
308
+ (n_out_virtual_chans, self.final_conv_length),
309
+ bias=True,
310
+ ),
311
+ )
312
+ self.add_module("softmax", nn.LogSoftmax(dim=1))
313
+ # Transpose back to the the logic of braindecode,
314
+ # so time in third dimension (axis=2)
315
+ self.add_module(
316
+ "permute_2", Expression(lambda x: x.permute(0, 1, 3, 2))
317
+ )
318
+ self.add_module("squeeze", Expression(squeeze_final_output))
319
+ _glorot_weight_zero_bias(self)
320
+
321
+ def _glorot_weight_zero_bias(model):
322
+ """Initalize parameters of all modules by initializing weights with
323
+ glorot
324
+ uniform/xavier initialization, and setting biases to zero. Weights from
325
+ batch norm layers are set to 1.
326
+
327
+ Parameters
328
+ ----------
329
+ model: Module
330
+ """
331
+ for module in model.modules():
332
+ if hasattr(module, "weight"):
333
+ if not ("BatchNorm" in module.__class__.__name__):
334
+ nn.init.xavier_uniform_(module.weight, gain=1)
335
+ else:
336
+ nn.init.constant_(module.weight, 1)
337
+ if hasattr(module, "bias"):
338
+ if module.bias is not None:
339
+ nn.init.constant_(module.bias, 0)