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
braindecode/util.py ADDED
@@ -0,0 +1,362 @@
1
+ # Authors: Robin Schirrmeister <robintibor@gmail.com>
2
+ #
3
+ # License: BSD (3-clause)
4
+
5
+
6
+ import os
7
+ import glob
8
+ import random
9
+
10
+ import numpy as np
11
+ import mne
12
+ import h5py
13
+ import torch
14
+ from sklearn.utils import check_random_state
15
+
16
+
17
+ def set_random_seeds(seed, cuda):
18
+ """Set seeds for python random module numpy.random and torch.
19
+
20
+ Parameters
21
+ ----------
22
+ seed: int
23
+ Random seed.
24
+ cuda: bool
25
+ Whether to set cuda seed with torch.
26
+ """
27
+ random.seed(seed)
28
+ torch.manual_seed(seed)
29
+ if cuda:
30
+ torch.cuda.manual_seed_all(seed)
31
+ np.random.seed(seed)
32
+
33
+
34
+ def np_to_var(
35
+ X, requires_grad=False, dtype=None, pin_memory=False, **tensor_kwargs
36
+ ):
37
+ """
38
+ Convenience function to transform numpy array to `torch.Tensor`.
39
+
40
+ Converts `X` to ndarray using asarray if necessary.
41
+
42
+ Parameters
43
+ ----------
44
+ X: ndarray or list or number
45
+ Input arrays
46
+ requires_grad: bool
47
+ passed on to Variable constructor
48
+ dtype: numpy dtype, optional
49
+ var_kwargs:
50
+ passed on to Variable constructor
51
+
52
+ Returns
53
+ -------
54
+ var: `torch.Tensor`
55
+ """
56
+ if not hasattr(X, "__len__"):
57
+ X = [X]
58
+ X = np.asarray(X)
59
+ if dtype is not None:
60
+ X = X.astype(dtype)
61
+ X_tensor = torch.tensor(X, requires_grad=requires_grad, **tensor_kwargs)
62
+ if pin_memory:
63
+ X_tensor = X_tensor.pin_memory()
64
+ return X_tensor
65
+
66
+
67
+ def var_to_np(var):
68
+ """Convenience function to transform `torch.Tensor` to numpy
69
+ array.
70
+
71
+ Should work both for CPU and GPU."""
72
+ return var.cpu().data.numpy()
73
+
74
+
75
+ def corr(a, b):
76
+ """
77
+ Computes correlation only between terms of a and terms of b, not within
78
+ a and b.
79
+
80
+ Parameters
81
+ ----------
82
+ a, b: 2darray, features x samples
83
+
84
+ Returns
85
+ -------
86
+ Correlation between features in x and features in y
87
+ """
88
+ # Difference to numpy:
89
+ # Correlation only between terms of x and y
90
+ # not between x and x or y and y
91
+ this_cov = cov(a, b)
92
+ return _cov_to_corr(this_cov, a, b)
93
+
94
+
95
+ def cov(a, b):
96
+ """
97
+ Computes covariance only between terms of a and terms of b, not within
98
+ a and b.
99
+
100
+ Parameters
101
+ ----------
102
+ a, b: 2darray, features x samples
103
+
104
+ Returns
105
+ -------
106
+ Covariance between features in x and features in y
107
+ """
108
+ demeaned_a = a - np.mean(a, axis=1, keepdims=True)
109
+ demeaned_b = b - np.mean(b, axis=1, keepdims=True)
110
+ this_cov = np.dot(demeaned_a, demeaned_b.T) / (b.shape[1] - 1)
111
+ return this_cov
112
+
113
+
114
+ def _cov_to_corr(this_cov, a, b):
115
+ # computing "unbiased" corr
116
+ # ddof=1 for unbiased..
117
+ var_a = np.var(a, axis=1, ddof=1)
118
+ var_b = np.var(b, axis=1, ddof=1)
119
+ return _cov_and_var_to_corr(this_cov, var_a, var_b)
120
+
121
+
122
+ def _cov_and_var_to_corr(this_cov, var_a, var_b):
123
+ divisor = np.outer(np.sqrt(var_a), np.sqrt(var_b))
124
+ return this_cov / divisor
125
+
126
+
127
+ def wrap_reshape_apply_fn(stat_fn, a, b, axis_a, axis_b):
128
+ """
129
+ Reshape two nd-arrays into 2d-arrays, apply function and reshape
130
+ result back.
131
+
132
+ Parameters
133
+ ----------
134
+ stat_fn: function
135
+ Function to apply to 2d-arrays
136
+ a: nd-array: nd-array
137
+ b: nd-array
138
+ axis_a: int or list of int
139
+ sample axis
140
+ axis_b: int or list of int
141
+ sample axis
142
+
143
+ Returns
144
+ -------
145
+ result: nd-array
146
+ The result reshaped to remaining_dims_a + remaining_dims_b
147
+ """
148
+ if not hasattr(axis_a, "__len__"):
149
+ axis_a = [axis_a]
150
+ if not hasattr(axis_b, "__len__"):
151
+ axis_b = [axis_b]
152
+ other_axis_a = [i for i in range(a.ndim) if i not in axis_a]
153
+ other_axis_b = [i for i in range(b.ndim) if i not in axis_b]
154
+ transposed_topo_a = a.transpose(tuple(other_axis_a) + tuple(axis_a))
155
+ n_stat_axis_a = [a.shape[i] for i in axis_a]
156
+ n_other_axis_a = [a.shape[i] for i in other_axis_a]
157
+ flat_topo_a = transposed_topo_a.reshape(
158
+ np.prod(n_other_axis_a), np.prod(n_stat_axis_a)
159
+ )
160
+ transposed_topo_b = b.transpose(tuple(other_axis_b) + tuple(axis_b))
161
+ n_stat_axis_b = [b.shape[i] for i in axis_b]
162
+ n_other_axis_b = [b.shape[i] for i in other_axis_b]
163
+ flat_topo_b = transposed_topo_b.reshape(
164
+ np.prod(n_other_axis_b), np.prod(n_stat_axis_b)
165
+ )
166
+ assert np.array_equal(n_stat_axis_a, n_stat_axis_b)
167
+ stat_result = stat_fn(flat_topo_a, flat_topo_b)
168
+ topo_result = stat_result.reshape(
169
+ tuple(n_other_axis_a) + tuple(n_other_axis_b)
170
+ )
171
+ return topo_result
172
+
173
+
174
+ def get_balanced_batches(
175
+ n_trials, rng, shuffle, n_batches=None, batch_size=None
176
+ ):
177
+ """Create indices for batches balanced in size
178
+ (batches will have maximum size difference of 1).
179
+ Supply either batch size or number of batches. Resulting batches
180
+ will not have the given batch size but rather the next largest batch size
181
+ that allows to split the set into balanced batches (maximum size difference 1).
182
+
183
+ Parameters
184
+ ----------
185
+ n_trials : int
186
+ Size of set.
187
+ rng : RandomState
188
+ shuffle : bool
189
+ Whether to shuffle indices before splitting set.
190
+ n_batches : int, optional
191
+ batch_size : int, optional
192
+
193
+ Returns
194
+ -------
195
+ batches: list of list of int
196
+ Indices for each batch.
197
+ """
198
+ assert batch_size is not None or n_batches is not None
199
+ if n_batches is None:
200
+ n_batches = int(np.round(n_trials / float(batch_size)))
201
+
202
+ if n_batches > 0:
203
+ min_batch_size = n_trials // n_batches
204
+ n_batches_with_extra_trial = n_trials % n_batches
205
+ else:
206
+ n_batches = 1
207
+ min_batch_size = n_trials
208
+ n_batches_with_extra_trial = 0
209
+ assert n_batches_with_extra_trial < n_batches
210
+ all_inds = np.array(range(n_trials))
211
+ if shuffle:
212
+ rng.shuffle(all_inds)
213
+ i_start_trial = 0
214
+ i_stop_trial = 0
215
+ batches = []
216
+ for i_batch in range(n_batches):
217
+ i_stop_trial += min_batch_size
218
+ if i_batch < n_batches_with_extra_trial:
219
+ i_stop_trial += 1
220
+ batch_inds = all_inds[range(i_start_trial, i_stop_trial)]
221
+ batches.append(batch_inds)
222
+ i_start_trial = i_stop_trial
223
+ assert i_start_trial == n_trials
224
+ return batches
225
+
226
+
227
+ def create_mne_dummy_raw(n_channels, n_times, sfreq, include_anns=True,
228
+ description=None, savedir=None, save_format='fif',
229
+ overwrite=True, random_state=None):
230
+ """Create an mne.io.RawArray with fake data, and optionally save it.
231
+
232
+ This will overwrite already existing files.
233
+
234
+ Parameters
235
+ ----------
236
+ n_channels : int
237
+ Number of channels.
238
+ n_times : int
239
+ Number of samples.
240
+ sfreq : float
241
+ Sampling frequency.
242
+ include_anns : bool
243
+ If True, also create annotations.
244
+ description : list | None
245
+ List of descriptions used for creating annotations. It should contain
246
+ 10 elements.
247
+ savedir : str | None
248
+ If provided as a string, the file will be saved under that directory.
249
+ save_format : str | list
250
+ If `savedir` is provided, this specifies the file format the data should
251
+ be saved to. Can be 'raw' or 'hdf5', or a list containing both.
252
+ random_state : int | RandomState
253
+ Random state for the generation of random data.
254
+
255
+ Returns
256
+ -------
257
+ raw : mne.io.Raw
258
+ The created Raw object.
259
+ save_fname : dict | None
260
+ Dictionary containing the name the raw data was saved to.
261
+ """
262
+ random_state = check_random_state(random_state)
263
+ data = random_state.rand(n_channels, n_times)
264
+ ch_names = [f'ch{i}' for i in range(n_channels)]
265
+ ch_types = ['eeg'] * n_channels
266
+ info = mne.create_info(ch_names=ch_names, sfreq=sfreq, ch_types=ch_types)
267
+
268
+ raw = mne.io.RawArray(data, info)
269
+
270
+ if include_anns:
271
+ n_anns = 10
272
+ inds = np.linspace(
273
+ int(sfreq * 2), int(n_times - sfreq * 2), num=n_anns).astype(int)
274
+ onset = raw.times[inds]
275
+ duration = [1] * n_anns
276
+ if description is None:
277
+ description = ['test'] * n_anns
278
+ anns = mne.Annotations(onset, duration, description)
279
+ raw = raw.set_annotations(anns)
280
+
281
+ save_fname = dict()
282
+ if savedir is not None:
283
+ if not isinstance(save_format, list):
284
+ save_format = [save_format]
285
+ fname = os.path.join(savedir, 'fake_eeg_raw')
286
+
287
+ if 'fif' in save_format:
288
+ fif_fname = fname + '.fif'
289
+ raw.save(fif_fname, overwrite=overwrite)
290
+ save_fname['fif'] = fif_fname
291
+ if 'hdf5' in save_format:
292
+ h5_fname = fname + '.h5'
293
+ with h5py.File(h5_fname, 'w') as f:
294
+ f.create_dataset(
295
+ 'fake_raw', dtype='f8', data=raw.get_data())
296
+ save_fname['hdf5'] = h5_fname
297
+
298
+ return raw, save_fname
299
+
300
+
301
+ class ThrowAwayIndexLoader(object):
302
+ def __init__(self, net, loader, is_regression):
303
+ self.net = net
304
+ self.loader = loader
305
+ self.last_i = None
306
+ self.is_regression = is_regression
307
+
308
+ def __iter__(self, ):
309
+ normal_iter = self.loader.__iter__()
310
+ for batch in normal_iter:
311
+ if len(batch) == 3:
312
+ x, y, i = batch
313
+ # Store for scoring callbacks
314
+ self.net._last_window_inds = i
315
+ else:
316
+ x, y = batch
317
+
318
+ # TODO: should be on dataset side
319
+ if hasattr(x, 'type'):
320
+ x = x.type(torch.float32)
321
+ if self.is_regression:
322
+ y = y.type(torch.float32)
323
+ else:
324
+ y = y.type(torch.int64)
325
+ yield x, y
326
+
327
+
328
+ def update_estimator_docstring(base_class, docstring):
329
+ base_doc = base_class.__doc__.replace(' : ', ': ')
330
+ idx = base_doc.find('callbacks:')
331
+ idx_end = idx + base_doc[idx:].find('\n\n')
332
+ # remove callback descripiton already included in braindecode docstring
333
+ filtered_doc = base_doc[:idx] + base_doc[idx_end+6:]
334
+ splitted = docstring.split('Parameters\n ----------\n ')
335
+ out_docstring = splitted[0] + \
336
+ filtered_doc[filtered_doc.find('Parameters'):filtered_doc.find('Attributes')] + \
337
+ splitted[1] + \
338
+ filtered_doc[filtered_doc.find('Attributes'):]
339
+ return out_docstring
340
+
341
+
342
+ def read_all_file_names(directory, extension):
343
+ """Read all files with specified extension from given path and sorts them
344
+ based on a given sorting key.
345
+
346
+ Parameters
347
+ ----------
348
+ directory: str
349
+ Parent directory to be searched for files of the specified type.
350
+ extension: str
351
+ File extension, i.e. ".edf" or ".txt".
352
+
353
+ Returns
354
+ -------
355
+ file_paths: list(str)
356
+ List of all files found in (sub)directories of path.
357
+ """
358
+ assert extension.startswith('.')
359
+ file_paths = glob.glob(directory + '**/*' + extension, recursive=True)
360
+ assert len(file_paths) > 0, (
361
+ f'something went wrong. Found no {extension} files in {directory}')
362
+ return file_paths
braindecode/version.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.5.1"
@@ -0,0 +1,5 @@
1
+ """
2
+ Functions for visualisations, especially of the ConvNets.
3
+ """
4
+
5
+ from .gradients import compute_amplitude_gradients
@@ -0,0 +1,53 @@
1
+ # Authors: Robin Schirrmeister <robintibor@gmail.com>
2
+ #
3
+ # License: BSD (3-clause)
4
+
5
+ import numpy as np
6
+ from skorch.utils import to_numpy, to_tensor
7
+ import torch
8
+
9
+
10
+ def compute_amplitude_gradients(model, dataset, batch_size):
11
+ loader = torch.utils.data.DataLoader(dataset, batch_size=batch_size,
12
+ drop_last=False, shuffle=False)
13
+ all_amp_grads = []
14
+ for batch_X, _, _ in loader:
15
+ this_amp_grads = compute_amplitude_gradients_for_X(model, batch_X, )
16
+ all_amp_grads.append(this_amp_grads)
17
+ all_amp_grads = np.concatenate(all_amp_grads, axis=1)
18
+ return all_amp_grads
19
+
20
+
21
+ def compute_amplitude_gradients_for_X(model, X):
22
+ device = next(model.parameters()).device
23
+ ffted = np.fft.rfft(X, axis=2)
24
+ amps = np.abs(ffted)
25
+ phases = np.angle(ffted)
26
+ amps_th = to_tensor(amps.astype(np.float32), device=device).requires_grad_(True)
27
+ phases_th = to_tensor(phases.astype(np.float32), device=device).requires_grad_(True)
28
+
29
+ fft_coefs = amps_th.unsqueeze(-1) * torch.stack(
30
+ (torch.cos(phases_th), torch.sin(phases_th)), dim=-1)
31
+ fft_coefs = fft_coefs.squeeze(3)
32
+
33
+ try:
34
+ complex_fft_coefs = torch.view_as_complex(fft_coefs)
35
+ iffted = torch.fft.irfft(
36
+ complex_fft_coefs, n=X.shape[2], dim=2)
37
+ except AttributeError:
38
+ iffted = torch.irfft( # Deprecated since 1.7
39
+ fft_coefs, signal_ndim=1, signal_sizes=(X.shape[2],))
40
+
41
+ outs = model(iffted)
42
+
43
+ n_filters = outs.shape[1]
44
+ amp_grads_per_filter = np.full((n_filters,) + ffted.shape,
45
+ np.nan, dtype=np.float32)
46
+ for i_filter in range(n_filters):
47
+ mean_out = torch.mean(outs[:, i_filter])
48
+ mean_out.backward(retain_graph=True)
49
+ amp_grads = to_numpy(amps_th.grad.clone())
50
+ amp_grads_per_filter[i_filter] = amp_grads
51
+ amps_th.grad.zero_()
52
+ assert not np.any(np.isnan(amp_grads_per_filter))
53
+ return amp_grads_per_filter
test/__init__.py ADDED
File without changes
File without changes
@@ -0,0 +1,171 @@
1
+ # Authors: Maciej Sliwowski
2
+ # Robin Tibor Schirrmeister
3
+ #
4
+ # License: BSD-3
5
+
6
+ import mne
7
+ import numpy as np
8
+ import torch
9
+ from mne.io import concatenate_raws
10
+ from skorch.helper import predefined_split
11
+ from torch import optim
12
+
13
+ from braindecode import EEGClassifier
14
+ from braindecode.datautil.xy import create_from_X_y
15
+ from braindecode.training.losses import CroppedLoss
16
+ from braindecode.models import ShallowFBCSPNet
17
+ from braindecode.models.util import to_dense_prediction_model, get_output_shape
18
+ from braindecode.util import set_random_seeds
19
+
20
+
21
+ def test_cropped_decoding():
22
+ # 5,6,7,10,13,14 are codes for executed and imagined hands/feet
23
+ subject_id = 1
24
+ event_codes = [5, 6, 9, 10, 13, 14]
25
+
26
+ # This will download the files if you don't have them yet,
27
+ # and then return the paths to the files.
28
+ physionet_paths = mne.datasets.eegbci.load_data(
29
+ subject_id, event_codes, update_path=False
30
+ )
31
+
32
+ # Load each of the files
33
+ parts = [
34
+ mne.io.read_raw_edf(
35
+ path, preload=True, stim_channel="auto", verbose="WARNING"
36
+ )
37
+ for path in physionet_paths
38
+ ]
39
+
40
+ # Concatenate them
41
+ raw = concatenate_raws(parts)
42
+
43
+ # Find the events in this dataset
44
+ events, _ = mne.events_from_annotations(raw)
45
+ # Use only EEG channels
46
+ eeg_channel_inds = mne.pick_types(
47
+ raw.info, meg=False, eeg=True, stim=False, eog=False, exclude="bads"
48
+ )
49
+
50
+ # Extract trials, only using EEG channels
51
+ epoched = mne.Epochs(
52
+ raw,
53
+ events,
54
+ dict(hands=2, feet=3),
55
+ tmin=1,
56
+ tmax=4.1,
57
+ proj=False,
58
+ picks=eeg_channel_inds,
59
+ baseline=None,
60
+ preload=True,
61
+ )
62
+ # Convert data from volt to millivolt
63
+ # Pytorch expects float32 for input and int64 for labels.
64
+ X = (epoched.get_data() * 1e6).astype(np.float32)
65
+ y = (epoched.events[:, 2] - 2).astype(np.int64) # 2,3 -> 0,1
66
+
67
+ # Set if you want to use GPU
68
+ # You can also use torch.cuda.is_available() to determine if cuda is available on your machine.
69
+ cuda = False
70
+ set_random_seeds(seed=20170629, cuda=cuda)
71
+
72
+ # This will determine how many crops are processed in parallel
73
+ input_window_samples = 450
74
+ n_classes = 2
75
+ in_chans = X.shape[1]
76
+ # final_conv_length determines the size of the receptive field of the ConvNet
77
+ model = ShallowFBCSPNet(
78
+ in_chans=in_chans,
79
+ n_classes=n_classes,
80
+ input_window_samples=input_window_samples,
81
+ final_conv_length=12,
82
+ )
83
+ to_dense_prediction_model(model)
84
+
85
+ if cuda:
86
+ model.cuda()
87
+
88
+ # Perform forward pass to determine how many outputs per input
89
+ n_preds_per_input = get_output_shape(model, in_chans, input_window_samples)[2]
90
+
91
+ train_set = create_from_X_y(X[:60], y[:60],
92
+ drop_last_window=False,
93
+ window_size_samples=input_window_samples,
94
+ window_stride_samples=n_preds_per_input)
95
+
96
+ valid_set = create_from_X_y(X[60:], y[60:],
97
+ drop_last_window=False,
98
+ window_size_samples=input_window_samples,
99
+ window_stride_samples=n_preds_per_input)
100
+
101
+
102
+ train_split = predefined_split(valid_set)
103
+
104
+ clf = EEGClassifier(
105
+ model,
106
+ cropped=True,
107
+ criterion=CroppedLoss,
108
+ criterion__loss_function=torch.nn.functional.nll_loss,
109
+ optimizer=optim.Adam,
110
+ train_split=train_split,
111
+ batch_size=32,
112
+ callbacks=['accuracy'],
113
+ )
114
+
115
+ clf.fit(train_set, y=None, epochs=4)
116
+
117
+ np.testing.assert_allclose(
118
+ clf.history[:, 'train_loss'],
119
+ np.array(
120
+ [
121
+ 1.6666231592496237,
122
+ 1.2292670885721841,
123
+ 1.1270817518234253,
124
+ 1.1752660751342774
125
+ ]
126
+ ),
127
+ rtol=1e-3,
128
+ atol=1e-4,
129
+ )
130
+
131
+ np.testing.assert_allclose(
132
+ clf.history[:, 'valid_loss'],
133
+ np.array(
134
+ [
135
+ 1.5687058925628663,
136
+ 0.8510023872057597,
137
+ 2.087181798617045,
138
+ 0.7100235184033712
139
+ ]
140
+ ),
141
+ rtol=1e-3,
142
+ atol=1e-3,
143
+ )
144
+
145
+ np.testing.assert_allclose(
146
+ clf.history[:, 'train_accuracy'],
147
+ np.array(
148
+ [
149
+ 0.48333333333333334,
150
+ 0.5,
151
+ 0.5,
152
+ 0.6333333333333333
153
+ ]
154
+ ),
155
+ rtol=1e-3,
156
+ atol=1e-4,
157
+ )
158
+
159
+ np.testing.assert_allclose(
160
+ clf.history[:, 'valid_accuracy'],
161
+ np.array(
162
+ [
163
+ 0.533333,
164
+ 0.5,
165
+ 0.466667,
166
+ 0.666667
167
+ ]
168
+ ),
169
+ rtol=1e-3,
170
+ atol=1e-4,
171
+ )