braindecode 0.3.0__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {Braindecode-0.3.0 → Braindecode-0.3.2}/Braindecode.egg-info/PKG-INFO +2 -11
- {Braindecode-0.3.0 → Braindecode-0.3.2}/Braindecode.egg-info/SOURCES.txt +2 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/PKG-INFO +2 -11
- {Braindecode-0.3.0 → Braindecode-0.3.2}/README.rst +1 -10
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/datasets/bbci.py +3 -3
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/datasets/sensor_positions.py +27 -21
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/datautil/iterators.py +7 -3
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/datautil/signalproc.py +8 -2
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/datautil/splitters.py +27 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/datautil/trial_segment.py +0 -3
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/experiments/experiment.py +36 -12
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/experiments/monitors.py +19 -6
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/models/deep4.py +23 -11
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/torch_ext/modules.py +51 -9
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/torch_ext/util.py +17 -1
- Braindecode-0.3.2/braindecode/version.py +1 -0
- Braindecode-0.3.2/braindecode/visualization/input_windows.py +170 -0
- Braindecode-0.3.2/braindecode/visualization/perturbation.py +291 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/visualization/plot.py +0 -2
- Braindecode-0.3.2/braindecode/visualization/sinfit.py +89 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/docs/index.rst +1 -10
- Braindecode-0.3.2/docs/notebooks/BBCI_Data.ipynb +841 -0
- Braindecode-0.3.2/docs/notebooks/BBCI_Data_Start_Stop.ipynb +899 -0
- Braindecode-0.3.2/docs/notebooks/BBCI_Data_Trialwise.ipynb +715 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/docs/notebooks/visualization/Perturbation.ipynb +7 -24
- {Braindecode-0.3.0 → Braindecode-0.3.2}/docs/source/braindecode.visualization.rst +16 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/setup.cfg +0 -1
- Braindecode-0.3.0/braindecode/version.py +0 -1
- Braindecode-0.3.0/braindecode/visualization/perturbation.py +0 -116
- Braindecode-0.3.0/docs/notebooks/BBCI_Data.ipynb +0 -886
- Braindecode-0.3.0/docs/notebooks/BBCI_Data_Start_Stop.ipynb +0 -899
- Braindecode-0.3.0/docs/notebooks/BBCI_Data_Trialwise.ipynb +0 -761
- {Braindecode-0.3.0 → Braindecode-0.3.2}/Braindecode.egg-info/dependency_links.txt +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/Braindecode.egg-info/not-zip-safe +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/Braindecode.egg-info/requires.txt +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/Braindecode.egg-info/top_level.txt +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/LICENSE.txt +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/MANIFEST.in +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/__init__.py +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/datasets/__init__.py +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/datasets/bcic_iv_2a.py +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/datasets/multiple.py +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/datautil/__init__.py +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/datautil/signal_target.py +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/datautil/util.py +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/experiments/__init__.py +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/experiments/stopcriteria.py +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/mne_ext/__init__.py +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/mne_ext/signalproc.py +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/models/__init__.py +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/models/eegnet.py +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/models/shallow_fbcsp.py +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/models/util.py +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/torch_ext/__init__.py +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/torch_ext/constraints.py +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/torch_ext/functions.py +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/torch_ext/init.py +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/torch_ext/losses.py +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/util.py +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/braindecode/visualization/__init__.py +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/docs/Makefile +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/docs/conf.py +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/docs/notebooks/Cropped_Decoding.ipynb +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/docs/notebooks/Experiment_Class.ipynb +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/docs/notebooks/TrialWise_Decoding.ipynb +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/docs/source/braindecode.datasets.rst +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/docs/source/braindecode.datautil.rst +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/docs/source/braindecode.experiments.rst +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/docs/source/braindecode.mne_ext.rst +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/docs/source/braindecode.models.rst +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/docs/source/braindecode.rst +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/docs/source/braindecode.torch_ext.rst +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/docs/source/modules.rst +0 -0
- {Braindecode-0.3.0 → Braindecode-0.3.2}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 1.1
|
|
2
2
|
Name: Braindecode
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: A deep learning toolbox to decode raw time-domain EEG.
|
|
5
5
|
Home-page: https://github.com/robintibor/braindecode
|
|
6
6
|
Author: Robin Tibor Schirrmeister
|
|
@@ -30,16 +30,7 @@ Description: Note: The old braindecode repository has been moved to
|
|
|
30
30
|
|
|
31
31
|
pip install numpy
|
|
32
32
|
|
|
33
|
-
3. Install
|
|
34
|
-
|
|
35
|
-
.. code-block:: bash
|
|
36
|
-
|
|
37
|
-
git clone git://github.com/mne-tools/mne-python.git
|
|
38
|
-
cd mne-python
|
|
39
|
-
python setup.py install
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
4. Install braindecode via pip:
|
|
33
|
+
3. Install braindecode via pip:
|
|
43
34
|
|
|
44
35
|
.. code-block:: bash
|
|
45
36
|
|
|
@@ -42,8 +42,10 @@ braindecode/torch_ext/losses.py
|
|
|
42
42
|
braindecode/torch_ext/modules.py
|
|
43
43
|
braindecode/torch_ext/util.py
|
|
44
44
|
braindecode/visualization/__init__.py
|
|
45
|
+
braindecode/visualization/input_windows.py
|
|
45
46
|
braindecode/visualization/perturbation.py
|
|
46
47
|
braindecode/visualization/plot.py
|
|
48
|
+
braindecode/visualization/sinfit.py
|
|
47
49
|
docs/Makefile
|
|
48
50
|
docs/conf.py
|
|
49
51
|
docs/index.rst
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 1.1
|
|
2
2
|
Name: Braindecode
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: A deep learning toolbox to decode raw time-domain EEG.
|
|
5
5
|
Home-page: https://github.com/robintibor/braindecode
|
|
6
6
|
Author: Robin Tibor Schirrmeister
|
|
@@ -30,16 +30,7 @@ Description: Note: The old braindecode repository has been moved to
|
|
|
30
30
|
|
|
31
31
|
pip install numpy
|
|
32
32
|
|
|
33
|
-
3. Install
|
|
34
|
-
|
|
35
|
-
.. code-block:: bash
|
|
36
|
-
|
|
37
|
-
git clone git://github.com/mne-tools/mne-python.git
|
|
38
|
-
cd mne-python
|
|
39
|
-
python setup.py install
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
4. Install braindecode via pip:
|
|
33
|
+
3. Install braindecode via pip:
|
|
43
34
|
|
|
44
35
|
.. code-block:: bash
|
|
45
36
|
|
|
@@ -22,16 +22,7 @@ Installation
|
|
|
22
22
|
|
|
23
23
|
pip install numpy
|
|
24
24
|
|
|
25
|
-
3. Install
|
|
26
|
-
|
|
27
|
-
.. code-block:: bash
|
|
28
|
-
|
|
29
|
-
git clone git://github.com/mne-tools/mne-python.git
|
|
30
|
-
cd mne-python
|
|
31
|
-
python setup.py install
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
4. Install braindecode via pip:
|
|
25
|
+
3. Install braindecode via pip:
|
|
35
26
|
|
|
36
27
|
.. code-block:: bash
|
|
37
28
|
|
|
@@ -93,7 +93,7 @@ class BBCIDataset(object):
|
|
|
93
93
|
|
|
94
94
|
def _determine_samplingrate(self):
|
|
95
95
|
with h5py.File(self.filename, 'r') as h5file:
|
|
96
|
-
fs = h5file['
|
|
96
|
+
fs = h5file['nfo']['fs'][0, 0]
|
|
97
97
|
assert isinstance(fs, int) or fs.is_integer()
|
|
98
98
|
fs = int(fs)
|
|
99
99
|
return fs
|
|
@@ -125,7 +125,7 @@ class BBCIDataset(object):
|
|
|
125
125
|
Sensor names that match the pattern or all sensor names in the file.
|
|
126
126
|
"""
|
|
127
127
|
with h5py.File(filename, 'r') as h5file:
|
|
128
|
-
clab_set = h5file['
|
|
128
|
+
clab_set = h5file['nfo']['clab'][:].squeeze()
|
|
129
129
|
all_sensor_names = [''.join(chr(c) for c in h5file[obj_ref]) for
|
|
130
130
|
obj_ref in clab_set]
|
|
131
131
|
if pattern is not None:
|
|
@@ -137,7 +137,7 @@ class BBCIDataset(object):
|
|
|
137
137
|
def _add_markers(self, cnt):
|
|
138
138
|
with h5py.File(self.filename, 'r') as h5file:
|
|
139
139
|
event_times_in_ms = h5file['mrk']['time'][:].squeeze()
|
|
140
|
-
event_classes = h5file['mrk']['event']['desc'][:].squeeze()
|
|
140
|
+
event_classes = h5file['mrk']['event']['desc'][:].squeeze().astype(np.int64)
|
|
141
141
|
|
|
142
142
|
# Check whether class names known and correct order
|
|
143
143
|
class_name_set = h5file['nfo']['className'][:].squeeze()
|
|
@@ -192,26 +192,32 @@ def get_channelpos_from_angle(channame, chan_pos_list=CHANNEL_10_20_APPROX):
|
|
|
192
192
|
if i[0].lower() == channame:
|
|
193
193
|
# convert the 90/4th angular position into x, y, z
|
|
194
194
|
p = i[1]
|
|
195
|
-
|
|
196
|
-
ea = ea * math.pi / 180
|
|
197
|
-
eb = eb * math.pi / 180
|
|
198
|
-
x = math.sin(ea) * math.cos(eb)
|
|
199
|
-
y = math.sin(eb)
|
|
200
|
-
z = math.cos(ea) * math.cos(eb)
|
|
201
|
-
# Calculate the stereographic projection.
|
|
202
|
-
# Given a unit sphere with radius ``r = 1`` and center at
|
|
203
|
-
# the origin. Project the point ``p = (x, y, z)`` from the
|
|
204
|
-
# sphere's South pole (0, 0, -1) on a plane on the sphere's
|
|
205
|
-
# North pole (0, 0, 1).
|
|
206
|
-
#
|
|
207
|
-
# The formula is:
|
|
208
|
-
#
|
|
209
|
-
# P' = P * (2r / (r + z))
|
|
210
|
-
#
|
|
211
|
-
# We changed the values to move the point of projection
|
|
212
|
-
# further below the south pole
|
|
213
|
-
mu = 1 / (1.3 + z)
|
|
214
|
-
x *= mu
|
|
215
|
-
y *= mu
|
|
195
|
+
x, y = _convert_2d_angle_to_2d_coord(*p)
|
|
216
196
|
return x, y
|
|
217
197
|
return None
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _convert_2d_angle_to_2d_coord(a,b):
|
|
201
|
+
# convert the 90/4th angular position into x, y, z
|
|
202
|
+
ea, eb = a * (90 / 4), b * (90 / 4)
|
|
203
|
+
ea = ea * math.pi / 180
|
|
204
|
+
eb = eb * math.pi / 180
|
|
205
|
+
x = math.sin(ea) * math.cos(eb)
|
|
206
|
+
y = math.sin(eb)
|
|
207
|
+
z = math.cos(ea) * math.cos(eb)
|
|
208
|
+
# Calculate the stereographic projection.
|
|
209
|
+
# Given a unit sphere with radius ``r = 1`` and center at
|
|
210
|
+
# the origin. Project the point ``p = (x, y, z)`` from the
|
|
211
|
+
# sphere's South pole (0, 0, -1) on a plane on the sphere's
|
|
212
|
+
# North pole (0, 0, 1).
|
|
213
|
+
#
|
|
214
|
+
# The formula is:
|
|
215
|
+
#
|
|
216
|
+
# P' = P * (2r / (r + z))
|
|
217
|
+
#
|
|
218
|
+
# We changed the values to move the point of projection
|
|
219
|
+
# further below the south pole
|
|
220
|
+
mu = 1 / (1.3 + z)
|
|
221
|
+
x *= mu
|
|
222
|
+
y *= mu
|
|
223
|
+
return x, y
|
|
@@ -162,19 +162,23 @@ class CropsFromTrialsIterator(object):
|
|
|
162
162
|
Number of predictions ConvNet makes per one input. Can be computed
|
|
163
163
|
by making a forward pass with the given input time length, the
|
|
164
164
|
output length in 3rd dimension is n_preds_per_input.
|
|
165
|
+
seed: int
|
|
166
|
+
Random seed for initialization of `numpy.RandomState`.
|
|
165
167
|
|
|
166
168
|
See Also
|
|
167
169
|
--------
|
|
168
170
|
braindecode.experiments.monitors.compute_preds_per_trial_for_set : Assigns predictions to trials, removes overlaps.
|
|
169
171
|
"""
|
|
170
|
-
def __init__(self, batch_size, input_time_length, n_preds_per_input
|
|
172
|
+
def __init__(self, batch_size, input_time_length, n_preds_per_input,
|
|
173
|
+
seed=(2017, 6, 28)):
|
|
171
174
|
self.batch_size = batch_size
|
|
172
175
|
self.input_time_length = input_time_length
|
|
173
176
|
self.n_preds_per_input = n_preds_per_input
|
|
174
|
-
self.
|
|
177
|
+
self.seed = seed
|
|
178
|
+
self.rng = RandomState(self.seed)
|
|
175
179
|
|
|
176
180
|
def reset_rng(self):
|
|
177
|
-
self.rng = RandomState(
|
|
181
|
+
self.rng = RandomState(self.seed)
|
|
178
182
|
|
|
179
183
|
def get_batches(self, dataset, shuffle):
|
|
180
184
|
# start always at first predictable sample, so
|
|
@@ -146,7 +146,8 @@ def lowpass_cnt(data, high_cut_hz, fs, filt_order=3, axis=0):
|
|
|
146
146
|
return data_lowpassed
|
|
147
147
|
|
|
148
148
|
|
|
149
|
-
def bandpass_cnt(data, low_cut_hz, high_cut_hz, fs, filt_order=3, axis=0
|
|
149
|
+
def bandpass_cnt(data, low_cut_hz, high_cut_hz, fs, filt_order=3, axis=0,
|
|
150
|
+
filtfilt=False):
|
|
150
151
|
"""
|
|
151
152
|
Bandpass signal applying **causal** butterworth filter of given order.
|
|
152
153
|
|
|
@@ -158,6 +159,8 @@ def bandpass_cnt(data, low_cut_hz, high_cut_hz, fs, filt_order=3, axis=0):
|
|
|
158
159
|
high_cut_hz: float
|
|
159
160
|
fs: float
|
|
160
161
|
filt_order: int
|
|
162
|
+
filtfilt: bool
|
|
163
|
+
Whether to use filtfilt instead of lfilter
|
|
161
164
|
|
|
162
165
|
Returns
|
|
163
166
|
-------
|
|
@@ -182,7 +185,10 @@ def bandpass_cnt(data, low_cut_hz, high_cut_hz, fs, filt_order=3, axis=0):
|
|
|
182
185
|
high = high_cut_hz / nyq_freq
|
|
183
186
|
b, a = scipy.signal.butter(filt_order, [low, high], btype='bandpass')
|
|
184
187
|
assert filter_is_stable(a), "Filter should be stable..."
|
|
185
|
-
|
|
188
|
+
if filtfilt:
|
|
189
|
+
data_bandpassed = scipy.signal.filtfilt(b, a, data, axis=axis)
|
|
190
|
+
else:
|
|
191
|
+
data_bandpassed = scipy.signal.lfilter(b, a, data, axis=axis)
|
|
186
192
|
return data_bandpassed
|
|
187
193
|
|
|
188
194
|
|
|
@@ -77,3 +77,30 @@ def split_into_two_sets(dataset, first_set_fraction=None, n_first_set=None):
|
|
|
77
77
|
second_set = apply_to_X_y(lambda a: a[n_first_set:], dataset)
|
|
78
78
|
return first_set, second_set
|
|
79
79
|
|
|
80
|
+
|
|
81
|
+
def select_examples(dataset, indices):
|
|
82
|
+
"""
|
|
83
|
+
Select examples from dataset.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
dataset: :class:`.SignalAndTarget`
|
|
88
|
+
indices: list of int, 1d-array of int
|
|
89
|
+
Indices to select
|
|
90
|
+
|
|
91
|
+
Returns
|
|
92
|
+
-------
|
|
93
|
+
reduced_set: :class:`.SignalAndTarget`
|
|
94
|
+
Dataset with only examples selected.
|
|
95
|
+
"""
|
|
96
|
+
# probably not necessary
|
|
97
|
+
indices = np.array(indices)
|
|
98
|
+
if hasattr(dataset.X, 'ndim'):
|
|
99
|
+
# numpy array
|
|
100
|
+
new_X = np.array(dataset.X)[indices]
|
|
101
|
+
else:
|
|
102
|
+
# list
|
|
103
|
+
new_X = [dataset.X[i] for i in indices]
|
|
104
|
+
new_y = np.asarray(dataset.y)[indices]
|
|
105
|
+
return SignalAndTarget(new_X, new_y)
|
|
106
|
+
|
|
@@ -9,9 +9,6 @@ from braindecode.datautil.util import ms_to_samples
|
|
|
9
9
|
|
|
10
10
|
log = logging.getLogger(__name__)
|
|
11
11
|
|
|
12
|
-
marker_def = OrderedDict(
|
|
13
|
-
(['Right', 1], ['Left', [2]], ['Rest', 3], ['Feet', 4]))
|
|
14
|
-
|
|
15
12
|
|
|
16
13
|
def create_signal_target_from_raw_mne(
|
|
17
14
|
raw, name_to_start_codes, epoch_ival_ms,
|
|
@@ -134,6 +134,9 @@ class Experiment(object):
|
|
|
134
134
|
Whether to use cuda.
|
|
135
135
|
pin_memory: bool, optional
|
|
136
136
|
Whether to pin memory of inputs and targets of batch.
|
|
137
|
+
do_early_stop: bool
|
|
138
|
+
Whether to do an early stop at all. If true, reset to best model
|
|
139
|
+
even in case experiment does not run after early stop.
|
|
137
140
|
|
|
138
141
|
Attributes
|
|
139
142
|
----------
|
|
@@ -145,10 +148,21 @@ class Experiment(object):
|
|
|
145
148
|
monitors, stop_criterion, remember_best_column,
|
|
146
149
|
run_after_early_stop,
|
|
147
150
|
model_loss_function=None,
|
|
148
|
-
batch_modifier=None, cuda=True, pin_memory=False
|
|
151
|
+
batch_modifier=None, cuda=True, pin_memory=False,
|
|
152
|
+
do_early_stop=True):
|
|
153
|
+
if run_after_early_stop:
|
|
154
|
+
assert do_early_stop == True, ("Can only run after early stop if "
|
|
155
|
+
"doing an early stop")
|
|
156
|
+
if do_early_stop:
|
|
157
|
+
assert valid_set is not None
|
|
158
|
+
assert remember_best_column is not None
|
|
149
159
|
self.model = model
|
|
150
160
|
self.datasets = OrderedDict(
|
|
151
161
|
(('train', train_set), ('valid', valid_set), ('test', test_set)))
|
|
162
|
+
if valid_set is None:
|
|
163
|
+
self.datasets.pop('valid')
|
|
164
|
+
assert run_after_early_stop == False
|
|
165
|
+
assert do_early_stop == False
|
|
152
166
|
self.iterator = iterator
|
|
153
167
|
self.loss_function = loss_function
|
|
154
168
|
self.optimizer = optimizer
|
|
@@ -164,6 +178,8 @@ class Experiment(object):
|
|
|
164
178
|
self.before_stop_df = None
|
|
165
179
|
self.rememberer = None
|
|
166
180
|
self.pin_memory = pin_memory
|
|
181
|
+
self.do_early_stop = do_early_stop
|
|
182
|
+
|
|
167
183
|
|
|
168
184
|
def run(self):
|
|
169
185
|
"""
|
|
@@ -171,11 +187,12 @@ class Experiment(object):
|
|
|
171
187
|
"""
|
|
172
188
|
self.setup_training()
|
|
173
189
|
log.info("Run until first stop...")
|
|
174
|
-
self.
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
190
|
+
self.run_until_first_stop()
|
|
191
|
+
if self.do_early_stop:
|
|
192
|
+
# always setup for second stop, in order to get best model
|
|
193
|
+
# even if not running after early stop...
|
|
194
|
+
log.info("Setup for second stop...")
|
|
195
|
+
self.setup_after_stop_training()
|
|
179
196
|
if self.run_after_early_stop:
|
|
180
197
|
log.info("Run until second stop...")
|
|
181
198
|
self.run_until_second_stop()
|
|
@@ -186,19 +203,20 @@ class Experiment(object):
|
|
|
186
203
|
initialize monitoring.
|
|
187
204
|
"""
|
|
188
205
|
# reset remember best extension in case you rerun some experiment
|
|
189
|
-
|
|
206
|
+
if self.do_early_stop:
|
|
207
|
+
self.rememberer = RememberBest(self.remember_best_column)
|
|
190
208
|
self.epochs_df = pd.DataFrame()
|
|
191
209
|
set_random_seeds(seed=2382938, cuda=self.cuda)
|
|
192
210
|
if self.cuda:
|
|
193
211
|
assert th.cuda.is_available(), "Cuda not available"
|
|
194
212
|
self.model.cuda()
|
|
195
213
|
|
|
196
|
-
def
|
|
214
|
+
def run_until_first_stop(self):
|
|
197
215
|
"""
|
|
198
216
|
Run training and evaluation using only training set for training
|
|
199
217
|
until stop criterion is fulfilled.
|
|
200
218
|
"""
|
|
201
|
-
self.run_until_stop(self.datasets, remember_best=
|
|
219
|
+
self.run_until_stop(self.datasets, remember_best=self.do_early_stop)
|
|
202
220
|
|
|
203
221
|
def run_until_second_stop(self):
|
|
204
222
|
"""
|
|
@@ -313,14 +331,20 @@ class Experiment(object):
|
|
|
313
331
|
|
|
314
332
|
"""
|
|
315
333
|
self.model.eval()
|
|
316
|
-
input_vars = np_to_var(inputs, pin_memory=self.pin_memory
|
|
317
|
-
|
|
334
|
+
input_vars = np_to_var(inputs, pin_memory=self.pin_memory,
|
|
335
|
+
volatile=True)
|
|
336
|
+
target_vars = np_to_var(targets, pin_memory=self.pin_memory,
|
|
337
|
+
volatile=True)
|
|
318
338
|
if self.cuda:
|
|
319
339
|
input_vars = input_vars.cuda()
|
|
320
340
|
target_vars = target_vars.cuda()
|
|
321
341
|
outputs = self.model(input_vars)
|
|
322
342
|
loss = self.loss_function(outputs, target_vars)
|
|
323
|
-
outputs
|
|
343
|
+
if hasattr(outputs, 'cpu'):
|
|
344
|
+
outputs = outputs.cpu().data.numpy()
|
|
345
|
+
else:
|
|
346
|
+
# assume it is iterable
|
|
347
|
+
outputs = [o.cpu().data.numpy() for o in outputs]
|
|
324
348
|
loss = loss.cpu().data.numpy()
|
|
325
349
|
return outputs, loss
|
|
326
350
|
|
|
@@ -10,10 +10,15 @@ class MisclassMonitor(object):
|
|
|
10
10
|
----------
|
|
11
11
|
col_suffix: str, optional
|
|
12
12
|
Name of the column in the monitoring output.
|
|
13
|
+
threshold_for_binary_case: bool, optional
|
|
14
|
+
In case of binary classification with only one output prediction
|
|
15
|
+
per target, define the threshold for separating the classes, i.e.
|
|
16
|
+
0.5 for sigmoid outputs, or np.log(0.5) for log sigmoid outputs
|
|
13
17
|
"""
|
|
14
18
|
|
|
15
|
-
def __init__(self, col_suffix='misclass'):
|
|
19
|
+
def __init__(self, col_suffix='misclass', threshold_for_binary_case=None):
|
|
16
20
|
self.col_suffix = col_suffix
|
|
21
|
+
self.threshold_for_binary_case = threshold_for_binary_case
|
|
17
22
|
|
|
18
23
|
def monitor_epoch(self, ):
|
|
19
24
|
return
|
|
@@ -28,11 +33,19 @@ class MisclassMonitor(object):
|
|
|
28
33
|
# or just
|
|
29
34
|
# examples x classes
|
|
30
35
|
# make sure not to remove first dimension if it only has size one
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
if preds.ndim > 1:
|
|
37
|
+
only_one_row = preds.shape[0] == 1
|
|
38
|
+
|
|
39
|
+
pred_labels = np.argmax(preds, axis=1).squeeze()
|
|
40
|
+
# add first dimension again if needed
|
|
41
|
+
if only_one_row:
|
|
42
|
+
pred_labels = pred_labels[None]
|
|
43
|
+
else:
|
|
44
|
+
assert self.threshold_for_binary_case is not None, (
|
|
45
|
+
"In case of only one output, please supply the "
|
|
46
|
+
"threshold_for_binary_case parameter")
|
|
47
|
+
# binary classification case... assume logits
|
|
48
|
+
pred_labels = np.int32(preds > self.threshold_for_binary_case)
|
|
36
49
|
# now examples x time or examples
|
|
37
50
|
all_pred_labels.extend(pred_labels)
|
|
38
51
|
targets = all_targets[i_batch]
|
|
@@ -65,8 +65,7 @@ class Deep4Net(object):
|
|
|
65
65
|
later_pool_class = pool_class_dict[self.later_pool_mode]
|
|
66
66
|
model = nn.Sequential()
|
|
67
67
|
if self.split_first_layer:
|
|
68
|
-
model.add_module('dimshuffle',
|
|
69
|
-
Expression(lambda x: x.permute(0, 3, 2, 1)))
|
|
68
|
+
model.add_module('dimshuffle', Expression(_transpose_time_to_spat))
|
|
70
69
|
model.add_module('conv_time', nn.Conv2d(1, self.n_filters_time,
|
|
71
70
|
(
|
|
72
71
|
self.filter_time_length, 1),
|
|
@@ -131,6 +130,7 @@ class Deep4Net(object):
|
|
|
131
130
|
self.filter_length_4, 4)
|
|
132
131
|
|
|
133
132
|
|
|
133
|
+
model.eval()
|
|
134
134
|
if self.final_conv_length == 'auto':
|
|
135
135
|
out = model(np_to_var(np.ones(
|
|
136
136
|
(1, self.in_chans, self.input_time_length,1),
|
|
@@ -141,15 +141,7 @@ class Deep4Net(object):
|
|
|
141
141
|
nn.Conv2d(self.n_filters_4, self.n_classes,
|
|
142
142
|
(self.final_conv_length, 1), bias=True))
|
|
143
143
|
model.add_module('softmax', nn.LogSoftmax())
|
|
144
|
-
|
|
145
|
-
# do not just use squeeze as we never want to remove first dim
|
|
146
|
-
def squeeze_output(x):
|
|
147
|
-
assert x.size()[3] == 1
|
|
148
|
-
x = x[:,:,:,0]
|
|
149
|
-
if x.size()[2] == 1:
|
|
150
|
-
x = x[:,:,0]
|
|
151
|
-
return x
|
|
152
|
-
model.add_module('squeeze', Expression(squeeze_output))
|
|
144
|
+
model.add_module('squeeze', Expression(_squeeze_final_output))
|
|
153
145
|
|
|
154
146
|
# Initialization, xavier is same as in our paper...
|
|
155
147
|
# was default from lasagne
|
|
@@ -161,6 +153,9 @@ class Deep4Net(object):
|
|
|
161
153
|
init.xavier_uniform(model.conv_spat.weight, gain=1)
|
|
162
154
|
if not self.batch_norm:
|
|
163
155
|
init.constant(model.conv_spat.bias, 0)
|
|
156
|
+
if self.batch_norm:
|
|
157
|
+
init.constant(model.bnorm.weight, 1)
|
|
158
|
+
init.constant(model.bnorm.bias, 0)
|
|
164
159
|
param_dict = dict(list(model.named_parameters()))
|
|
165
160
|
for block_nr in range(2,5):
|
|
166
161
|
conv_weight = param_dict['conv_{:d}.weight'.format(block_nr)]
|
|
@@ -177,4 +172,21 @@ class Deep4Net(object):
|
|
|
177
172
|
init.xavier_uniform(model.conv_classifier.weight, gain=1)
|
|
178
173
|
init.constant(model.conv_classifier.bias, 0)
|
|
179
174
|
|
|
175
|
+
# Start in eval mode
|
|
176
|
+
model.eval()
|
|
180
177
|
return model
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# remove empty dim at end and potentially remove empty time dim
|
|
181
|
+
# do not just use squeeze as we never want to remove first dim
|
|
182
|
+
def _squeeze_final_output(x):
|
|
183
|
+
assert x.size()[3] == 1
|
|
184
|
+
x = x[:,:,:,0]
|
|
185
|
+
if x.size()[2] == 1:
|
|
186
|
+
x = x[:,:,0]
|
|
187
|
+
return x
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _transpose_time_to_spat(x):
|
|
191
|
+
return x.permute(0, 3, 2, 1)
|
|
192
|
+
|
|
@@ -8,7 +8,7 @@ from braindecode.torch_ext.util import np_to_var
|
|
|
8
8
|
class Expression(torch.nn.Module):
|
|
9
9
|
"""
|
|
10
10
|
Compute given expression on forward pass.
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
Parameters
|
|
13
13
|
----------
|
|
14
14
|
expression_fn: function
|
|
@@ -37,7 +37,7 @@ class Expression(torch.nn.Module):
|
|
|
37
37
|
class AvgPool2dWithConv(torch.nn.Module):
|
|
38
38
|
"""
|
|
39
39
|
Compute average pooling using a convolution, to have the dilation parameter.
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
Parameters
|
|
42
42
|
----------
|
|
43
43
|
kernel_size: (int,int)
|
|
@@ -52,7 +52,10 @@ class AvgPool2dWithConv(torch.nn.Module):
|
|
|
52
52
|
self.kernel_size = kernel_size
|
|
53
53
|
self.stride = stride
|
|
54
54
|
self.dilation = dilation
|
|
55
|
-
|
|
55
|
+
# don't name them "weights" to
|
|
56
|
+
# make sure these are not accidentally used by some procedure
|
|
57
|
+
# that initializes parameters or something
|
|
58
|
+
self._pool_weights = None
|
|
56
59
|
|
|
57
60
|
def forward(self, x):
|
|
58
61
|
# Create weights for the convolution on demand:
|
|
@@ -60,11 +63,11 @@ class AvgPool2dWithConv(torch.nn.Module):
|
|
|
60
63
|
in_channels = x.size()[1]
|
|
61
64
|
weight_shape = (in_channels, 1,
|
|
62
65
|
self.kernel_size[0], self.kernel_size[1])
|
|
63
|
-
if self.
|
|
64
|
-
(tuple(self.
|
|
65
|
-
self.
|
|
66
|
+
if self._pool_weights is None or (
|
|
67
|
+
(tuple(self._pool_weights.size()) != tuple(weight_shape)) or (
|
|
68
|
+
self._pool_weights.is_cuda != x.is_cuda
|
|
66
69
|
) or (
|
|
67
|
-
self.
|
|
70
|
+
self._pool_weights.data.type() != x.data.type()
|
|
68
71
|
)):
|
|
69
72
|
n_pool = np.prod(self.kernel_size)
|
|
70
73
|
weights = np_to_var(
|
|
@@ -72,9 +75,48 @@ class AvgPool2dWithConv(torch.nn.Module):
|
|
|
72
75
|
weights = weights.type_as(x)
|
|
73
76
|
if x.is_cuda:
|
|
74
77
|
weights = weights.cuda()
|
|
75
|
-
self.
|
|
78
|
+
self._pool_weights = weights
|
|
76
79
|
|
|
77
|
-
pooled = F.conv2d(x, self.
|
|
80
|
+
pooled = F.conv2d(x, self._pool_weights, bias=None, stride=self.stride,
|
|
78
81
|
dilation=self.dilation,
|
|
79
82
|
groups=in_channels,)
|
|
80
83
|
return pooled
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class IntermediateOutputWrapper(torch.nn.Module):
|
|
87
|
+
"""Wraps network model such that outputs of intermediate layers can be returned.
|
|
88
|
+
forward() returns list of intermediate activations in a network during forward pass.
|
|
89
|
+
|
|
90
|
+
Parameters
|
|
91
|
+
----------
|
|
92
|
+
to_select : list
|
|
93
|
+
list of module names for which activation should be returned
|
|
94
|
+
model : model object
|
|
95
|
+
network model
|
|
96
|
+
|
|
97
|
+
Examples
|
|
98
|
+
--------
|
|
99
|
+
>>> model = Deep4Net()
|
|
100
|
+
>>> select_modules = ['conv_spat','conv_2','conv_3','conv_4'] # Specify intermediate outputs
|
|
101
|
+
>>> model_pert = IntermediateOutputWrapper(select_modules,model) # Wrap model
|
|
102
|
+
"""
|
|
103
|
+
def __init__(self, to_select, model):
|
|
104
|
+
if not len(list(model.children()))==len(list(model.named_children())):
|
|
105
|
+
raise Exception('All modules in model need to have names!')
|
|
106
|
+
|
|
107
|
+
super(IntermediateOutputWrapper, self).__init__()
|
|
108
|
+
|
|
109
|
+
modules_list = model.named_children()
|
|
110
|
+
for key, module in modules_list:
|
|
111
|
+
self.add_module(key, module)
|
|
112
|
+
self._modules[key].load_state_dict(module.state_dict())
|
|
113
|
+
self._to_select = to_select
|
|
114
|
+
|
|
115
|
+
def forward(self,x):
|
|
116
|
+
# Call modules individually and append activation to output if module is in to_select
|
|
117
|
+
o = []
|
|
118
|
+
for name, module in self._modules.items():
|
|
119
|
+
x = module(x)
|
|
120
|
+
if name in self._to_select:
|
|
121
|
+
o.append(x)
|
|
122
|
+
return o
|
|
@@ -12,7 +12,7 @@ def np_to_var(X, requires_grad=False, dtype=None, pin_memory=False, **var_kwargs
|
|
|
12
12
|
|
|
13
13
|
Parameters
|
|
14
14
|
----------
|
|
15
|
-
X: ndarray or list
|
|
15
|
+
X: ndarray or list or number
|
|
16
16
|
Input arrays
|
|
17
17
|
requires_grad: bool
|
|
18
18
|
passed on to Variable constructor
|
|
@@ -24,6 +24,8 @@ def np_to_var(X, requires_grad=False, dtype=None, pin_memory=False, **var_kwargs
|
|
|
24
24
|
-------
|
|
25
25
|
var: `torch.autograd.Variable`
|
|
26
26
|
"""
|
|
27
|
+
if not hasattr(X, '__len__'):
|
|
28
|
+
X = [X]
|
|
27
29
|
X = np.asarray(X)
|
|
28
30
|
if dtype is not None:
|
|
29
31
|
X = X.astype(dtype)
|
|
@@ -58,3 +60,17 @@ def set_random_seeds(seed, cuda):
|
|
|
58
60
|
if cuda:
|
|
59
61
|
th.cuda.manual_seed_all(seed)
|
|
60
62
|
np.random.seed(seed)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def confirm_gpu_availability():
|
|
66
|
+
"""
|
|
67
|
+
Should crash if gpu not available, attempts to create a FloatTensor on GPU.
|
|
68
|
+
Returns
|
|
69
|
+
-------
|
|
70
|
+
success: bool
|
|
71
|
+
Always returns true, should crash if gpu not available
|
|
72
|
+
"""
|
|
73
|
+
a = th.FloatTensor(1).cuda()
|
|
74
|
+
# Just make sure a is not somehow removed by any smart compiling,
|
|
75
|
+
# probably not necessary.
|
|
76
|
+
return a is not None
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.2"
|