braindecode 0.3.1__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.1 → Braindecode-0.3.2}/Braindecode.egg-info/PKG-INFO +1 -1
- {Braindecode-0.3.1 → Braindecode-0.3.2}/Braindecode.egg-info/SOURCES.txt +2 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/PKG-INFO +1 -1
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/datasets/bbci.py +3 -3
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/datasets/sensor_positions.py +27 -21
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/datautil/signalproc.py +8 -2
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/torch_ext/modules.py +51 -9
- 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.2/braindecode/visualization/sinfit.py +89 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/docs/notebooks/visualization/Perturbation.ipynb +5 -23
- {Braindecode-0.3.1 → Braindecode-0.3.2}/docs/source/braindecode.visualization.rst +16 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/setup.cfg +0 -1
- Braindecode-0.3.1/braindecode/version.py +0 -1
- Braindecode-0.3.1/braindecode/visualization/perturbation.py +0 -116
- {Braindecode-0.3.1 → Braindecode-0.3.2}/Braindecode.egg-info/dependency_links.txt +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/Braindecode.egg-info/not-zip-safe +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/Braindecode.egg-info/requires.txt +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/Braindecode.egg-info/top_level.txt +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/LICENSE.txt +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/MANIFEST.in +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/README.rst +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/__init__.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/datasets/__init__.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/datasets/bcic_iv_2a.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/datasets/multiple.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/datautil/__init__.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/datautil/iterators.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/datautil/signal_target.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/datautil/splitters.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/datautil/trial_segment.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/datautil/util.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/experiments/__init__.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/experiments/experiment.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/experiments/monitors.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/experiments/stopcriteria.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/mne_ext/__init__.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/mne_ext/signalproc.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/models/__init__.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/models/deep4.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/models/eegnet.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/models/shallow_fbcsp.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/models/util.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/torch_ext/__init__.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/torch_ext/constraints.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/torch_ext/functions.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/torch_ext/init.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/torch_ext/losses.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/torch_ext/util.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/util.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/visualization/__init__.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/braindecode/visualization/plot.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/docs/Makefile +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/docs/conf.py +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/docs/index.rst +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/docs/notebooks/BBCI_Data.ipynb +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/docs/notebooks/BBCI_Data_Start_Stop.ipynb +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/docs/notebooks/BBCI_Data_Trialwise.ipynb +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/docs/notebooks/Cropped_Decoding.ipynb +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/docs/notebooks/Experiment_Class.ipynb +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/docs/notebooks/TrialWise_Decoding.ipynb +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/docs/source/braindecode.datasets.rst +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/docs/source/braindecode.datautil.rst +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/docs/source/braindecode.experiments.rst +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/docs/source/braindecode.mne_ext.rst +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/docs/source/braindecode.models.rst +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/docs/source/braindecode.rst +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/docs/source/braindecode.torch_ext.rst +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/docs/source/modules.rst +0 -0
- {Braindecode-0.3.1 → Braindecode-0.3.2}/setup.py +0 -0
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
|
|
@@ -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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.2"
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import torch
|
|
3
|
+
|
|
4
|
+
def calc_Hout(Hin, kernel, stride, dilation):
|
|
5
|
+
Hout = np.floor((Hin-dilation*(kernel-1)-1)/stride+1)
|
|
6
|
+
return Hout
|
|
7
|
+
|
|
8
|
+
def calc_Hin(Hout, kernel, stride, dilation):
|
|
9
|
+
Hin = np.ceil((Hout-1)*stride+1+dilation*(kernel-1))
|
|
10
|
+
return Hin
|
|
11
|
+
|
|
12
|
+
def calc_receptive_field_size(model,layer_ind,start_receptive_field=np.ones((2))):
|
|
13
|
+
"""Calculate receptive field size for unit in specific layer of the network
|
|
14
|
+
Only tested for 2d convolutions/poolings. Dimshuffle operations may lead to a wrong result
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
model : model object
|
|
19
|
+
Network model
|
|
20
|
+
layer_ind: int
|
|
21
|
+
Index of the layer of interest in `model.children()`
|
|
22
|
+
start_receptive_field: int, optional
|
|
23
|
+
How many units are looked at in specified layer (default: [1,1])
|
|
24
|
+
|
|
25
|
+
Returns
|
|
26
|
+
-------
|
|
27
|
+
receptive_field_size : numpy array
|
|
28
|
+
[HxW] in the input layer
|
|
29
|
+
"""
|
|
30
|
+
receptive_field = start_receptive_field
|
|
31
|
+
children = list(model.children())[:layer_ind][::-1]
|
|
32
|
+
for child in children:
|
|
33
|
+
if isinstance(child,torch.nn.Sequential):
|
|
34
|
+
receptive_field = calc_receptive_field(child,-1)
|
|
35
|
+
elif isinstance(child,torch.nn.Conv2d) or isinstance(child,torch.nn.MaxPool2d) or isinstance(child,torch.nn.AvgPool2d):
|
|
36
|
+
receptive_field = calc_Hin(receptive_field,np.asarray(child.kernel_size),
|
|
37
|
+
np.asarray(child.stride), np.asarray(child.dilation))
|
|
38
|
+
|
|
39
|
+
receptive_field_size = receptive_field.astype(np.int)
|
|
40
|
+
return receptive_field_size
|
|
41
|
+
|
|
42
|
+
def get_max_act_index(activations,unique_per_input=True,n_units=None):
|
|
43
|
+
"""Retrieve index of maximum activation in a feature map
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
activations : numpy array
|
|
48
|
+
[Nx1xHxW] can only take 1 filter
|
|
49
|
+
unique_per_input : bool, optional
|
|
50
|
+
Specifies if only 1 index (maximum) for each input is returned (default: True)
|
|
51
|
+
n_units : int, optional
|
|
52
|
+
How many indeces are returned in total. If None all (default: None)
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
units : numpy array
|
|
57
|
+
[Nx4] with the columns `Input_i`,`Filter(0)`,`H_i`,`W_i` indeces of the units
|
|
58
|
+
units_activation : numpy array
|
|
59
|
+
Activation of the units
|
|
60
|
+
"""
|
|
61
|
+
assert len(activations.shape)==4,"Has to be 4d array"
|
|
62
|
+
assert activations.shape[1] == 1,"Can only handle individual filter activations"
|
|
63
|
+
|
|
64
|
+
activations_sorted = activations.argsort(axis=None)[::-1]
|
|
65
|
+
activations_sorted_ind = np.unravel_index(activations_sorted,activations.shape)
|
|
66
|
+
unique_ind = np.arange(len(activations_sorted_ind[0]))
|
|
67
|
+
if unique_per_input:
|
|
68
|
+
a,unique_ind = np.unique(activations_sorted_ind[0],return_index=True)
|
|
69
|
+
unique_ind = sorted(unique_ind)
|
|
70
|
+
|
|
71
|
+
if n_units==None:
|
|
72
|
+
n_units = len(unique_ind)
|
|
73
|
+
activations_sorted_ind = np.asarray(activations_sorted_ind).T
|
|
74
|
+
units = activations_sorted_ind[unique_ind[:n_units],:].astype(np.int)
|
|
75
|
+
units_activation = activations.flat[activations_sorted[unique_ind[:n_units]]]
|
|
76
|
+
|
|
77
|
+
return units,units_activation
|
|
78
|
+
|
|
79
|
+
def calc_receptive_field_for_units_2d(units,receptive_field_size):
|
|
80
|
+
recptive_field_tmp = receptive_field_size[np.newaxis,np.newaxis,:,:]
|
|
81
|
+
start_inds = units[:,0,]
|
|
82
|
+
stop_inds = start_inds+receptive_field_tmp
|
|
83
|
+
|
|
84
|
+
return start_inds,stop_inds
|
|
85
|
+
|
|
86
|
+
def get_input_windows_from_units_2d(inputs,units,receptive_field_size):
|
|
87
|
+
"""Cut input windows in receptive field of specified units from inputs
|
|
88
|
+
|
|
89
|
+
Parameters
|
|
90
|
+
----------
|
|
91
|
+
inputs : numpy array
|
|
92
|
+
[NxCxHxW] Inputs x Channels x Time x W
|
|
93
|
+
units : numpy array
|
|
94
|
+
[Mx4] unit indeces specifying Input and time indeces.
|
|
95
|
+
Second dimension consists of Input x Filter(1) x H x W indeces. Can only handle 1 filter.
|
|
96
|
+
receptive_field_size : int
|
|
97
|
+
Size of receptive field of units on input
|
|
98
|
+
|
|
99
|
+
Returns
|
|
100
|
+
-------
|
|
101
|
+
windows : numpy array
|
|
102
|
+
Cut input windows
|
|
103
|
+
"""
|
|
104
|
+
windows = np.zeros((units.shape[0],inputs.shape[1],receptive_field_size[0],receptive_field_size[1]))
|
|
105
|
+
for i,unit in enumerate(units):
|
|
106
|
+
windows[i] = inputs[unit[0],:,
|
|
107
|
+
unit[2]:unit[2]+receptive_field_size[0],
|
|
108
|
+
unit[3]:unit[3]+receptive_field_size[1]]
|
|
109
|
+
return windows
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def most_activating_input_windows(inputs,activations,receptive_field_size,top_percentage=0.05):
|
|
113
|
+
"""Get the input windows that evoked highest activation in a feature map
|
|
114
|
+
|
|
115
|
+
Parameters
|
|
116
|
+
----------
|
|
117
|
+
inputs : numpy array
|
|
118
|
+
[NxCxTx1] Inputs used for the calculation of activations
|
|
119
|
+
activations : numpy array
|
|
120
|
+
[NxFxHx1] Activations
|
|
121
|
+
receptive_field_size : numpy array
|
|
122
|
+
[Wx1] Receptive field of a unit of the layer on the input
|
|
123
|
+
top_percentage : float, optional
|
|
124
|
+
How many of the most activating input windows should be returned (default: top 5%)
|
|
125
|
+
|
|
126
|
+
Returns
|
|
127
|
+
-------
|
|
128
|
+
input_windows : numpy array
|
|
129
|
+
[FxUxCxWx1] Returns `U` (resulting from top_percentage) input windows for each filter
|
|
130
|
+
"""
|
|
131
|
+
n_units = int(inputs.shape[0]*top_percentage)
|
|
132
|
+
input_windows = np.zeros((activations.shape[1],n_units,inputs.shape[1],
|
|
133
|
+
receptive_field_size[0],receptive_field_size[1]))
|
|
134
|
+
for filt in range(activations.shape[1]):
|
|
135
|
+
units,w = get_max_act_index(activations[:,[filt]],n_units=n_units)
|
|
136
|
+
w = w.squeeze()
|
|
137
|
+
windows_tmp = get_input_windows_from_units_2d(inputs,units,receptive_field_size)
|
|
138
|
+
input_windows[filt] = windows_tmp
|
|
139
|
+
return input_windows
|
|
140
|
+
|
|
141
|
+
def activation_reverse_correlation(inputs,activations,receptive_field_size):
|
|
142
|
+
"""Get reverse correlations for filters
|
|
143
|
+
|
|
144
|
+
Parameters
|
|
145
|
+
----------
|
|
146
|
+
inputs : numpy array
|
|
147
|
+
[NxCxTx1] Inputs used for the calculation of activations
|
|
148
|
+
activations : numpy array
|
|
149
|
+
[NxFxHx1] Activations
|
|
150
|
+
receptive_field_size : numpy array
|
|
151
|
+
[Wx1] Receptive field of a unit of the layer on the input
|
|
152
|
+
|
|
153
|
+
Returns
|
|
154
|
+
-------
|
|
155
|
+
reverse_corr : numpy array
|
|
156
|
+
[FxCxWx1] Reverse correlations over all input windows for each filter
|
|
157
|
+
"""
|
|
158
|
+
reverse_corr = np.zeros((activations.shape[1],inputs.shape[1],
|
|
159
|
+
receptive_field_size[0],receptive_field_size[1]))
|
|
160
|
+
for filt in range(activations.shape[1]):
|
|
161
|
+
act_tmp = activations[:,filt]
|
|
162
|
+
divisor = 0
|
|
163
|
+
for start_ind in range(activations.shape[2]):
|
|
164
|
+
end_ind = start_ind+receptive_field_size[0]
|
|
165
|
+
w = act_tmp[:,[start_ind],:,np.newaxis]
|
|
166
|
+
windows_tmp = inputs[:,:,start_ind:end_ind]
|
|
167
|
+
reverse_corr[filt] += np.sum(w*windows_tmp,axis=0)
|
|
168
|
+
divisor += np.sum(np.abs(w))
|
|
169
|
+
reverse_corr[filt] /= divisor
|
|
170
|
+
return reverse_corr
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from braindecode.datautil.iterators import get_balanced_batches
|
|
6
|
+
from braindecode.util import wrap_reshape_apply_fn, corr
|
|
7
|
+
|
|
8
|
+
log = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def phase_perturbation(amps,phases,rng=np.random.RandomState()):
|
|
12
|
+
"""Takes amps and phases of BxCxF with B input, C channels, F frequencies
|
|
13
|
+
Shifts spectral phases randomly U(-pi,pi) for input and frequencies, but same for all channels
|
|
14
|
+
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
amps : numpy array
|
|
18
|
+
Spectral amplitude (not used)
|
|
19
|
+
phases : numpy array
|
|
20
|
+
Spectral phases
|
|
21
|
+
rng : object
|
|
22
|
+
Random Seed
|
|
23
|
+
|
|
24
|
+
Returns
|
|
25
|
+
-------
|
|
26
|
+
amps : numpy array
|
|
27
|
+
Input amps (not modified)
|
|
28
|
+
phases_pert : numpy array
|
|
29
|
+
Shifted phases
|
|
30
|
+
pert_vals : numpy array
|
|
31
|
+
Absolute phase shifts
|
|
32
|
+
"""
|
|
33
|
+
noise_shape = list(phases.shape)
|
|
34
|
+
noise_shape[1] = 1 # Do not sample noise for channels individually
|
|
35
|
+
|
|
36
|
+
# Sample phase perturbation noise
|
|
37
|
+
phase_noise = rng.uniform(-np.pi,np.pi,noise_shape).astype(np.float32)
|
|
38
|
+
phase_noise_rep = phase_noise.repeat(phases.shape[1],axis=1)
|
|
39
|
+
# Apply noise to inputs
|
|
40
|
+
phases_pert = phases+phase_noise_rep
|
|
41
|
+
phases_pert[phases_pert<-np.pi] += 2*np.pi
|
|
42
|
+
phases_pert[phases_pert>np.pi] -= 2*np.pi
|
|
43
|
+
|
|
44
|
+
pert_vals = np.abs(phase_noise)
|
|
45
|
+
return amps,phases_pert,pert_vals
|
|
46
|
+
|
|
47
|
+
def amp_perturbation_additive(amps,phases,rng=np.random.RandomState()):
|
|
48
|
+
"""Takes amplitudes and phases of BxCxF with B input, C channels, F frequencies
|
|
49
|
+
Adds additive noise N(0,0.02) to amplitudes
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
amps : numpy array
|
|
54
|
+
Spectral amplitude
|
|
55
|
+
phases : numpy array
|
|
56
|
+
Spectral phases (not used)
|
|
57
|
+
rng : object
|
|
58
|
+
Random Seed
|
|
59
|
+
|
|
60
|
+
Returns
|
|
61
|
+
-------
|
|
62
|
+
amps_pert : numpy array
|
|
63
|
+
Scaled amplitudes
|
|
64
|
+
phases_pert : numpy array
|
|
65
|
+
Input phases (not modified)
|
|
66
|
+
pert_vals : numpy array
|
|
67
|
+
Amplitude noise
|
|
68
|
+
"""
|
|
69
|
+
amp_noise = rng.normal(0,1,amps.shape).astype(np.float32)
|
|
70
|
+
amps_pert = amps+amp_noise
|
|
71
|
+
amps_pert[amps_pert<0] = 0
|
|
72
|
+
amp_noise = amps_pert-amps
|
|
73
|
+
return amps_pert,phases,amp_noise
|
|
74
|
+
|
|
75
|
+
def amp_perturbation_multiplicative(amps,phases,rng=np.random.RandomState()):
|
|
76
|
+
"""Takes amplitude and phases of BxCxF with B input, C channels, F frequencies
|
|
77
|
+
Adds multiplicative noise N(1,0.02) to amplitudes
|
|
78
|
+
|
|
79
|
+
Parameters
|
|
80
|
+
----------
|
|
81
|
+
amps : numpy array
|
|
82
|
+
Spectral amplitude
|
|
83
|
+
phases : numpy array
|
|
84
|
+
Spectral phases (not used)
|
|
85
|
+
rng : object
|
|
86
|
+
Random Seed
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
amps_pert : numpy array
|
|
91
|
+
Scaled amplitudes
|
|
92
|
+
phases_pert : numpy array
|
|
93
|
+
Input phases (not modified)
|
|
94
|
+
pert_vals : numpy array
|
|
95
|
+
Amplitude scaling factor
|
|
96
|
+
"""
|
|
97
|
+
amp_noise = rng.normal(1,0.02,amps.shape).astype(np.float32)
|
|
98
|
+
amps_pert = amps*amp_noise
|
|
99
|
+
amps_pert[amps_pert<0] = 0
|
|
100
|
+
return amps_pert,phases,amp_noise
|
|
101
|
+
|
|
102
|
+
def correlate_feature_maps(x,y):
|
|
103
|
+
"""Takes two activation matrices of the form Bx[F]xT where B is batch size, F number of filters (optional) and T time points
|
|
104
|
+
Returns correlations of the corresponding activations over T
|
|
105
|
+
|
|
106
|
+
Parameters
|
|
107
|
+
----------
|
|
108
|
+
x : numpy array
|
|
109
|
+
Activations Bx[F]xT
|
|
110
|
+
y : numpy array
|
|
111
|
+
Activations Bx[F]xT
|
|
112
|
+
|
|
113
|
+
Returns
|
|
114
|
+
correlations : numpy array
|
|
115
|
+
Correlations of `x` and `y` Bx[F]
|
|
116
|
+
"""
|
|
117
|
+
shape_x = x.shape
|
|
118
|
+
shape_y = y.shape
|
|
119
|
+
assert np.array_equal(shape_x,shape_y)
|
|
120
|
+
assert len(shape_x)<4
|
|
121
|
+
x = x.reshape((-1,shape_x[-1]))
|
|
122
|
+
y = y.reshape((-1,shape_y[-1]))
|
|
123
|
+
|
|
124
|
+
x = (x-x.mean(axis=1,keepdims=True))/x.std(axis=1,keepdims=True)
|
|
125
|
+
y = (y-y.mean(axis=1,keepdims=True))/y.std(axis=1,keepdims=True)
|
|
126
|
+
|
|
127
|
+
tmp_corr = x*y
|
|
128
|
+
corr_ = tmp_corr.sum(axis=1)
|
|
129
|
+
#corr_ = np.zeros((x.shape[0]))
|
|
130
|
+
#for i in range(x.shape[0]):
|
|
131
|
+
# # Correlation of standardized variables
|
|
132
|
+
# corr_[i] = np.correlate((x[i]-x[i].mean())/x[i].std(),(y[i]-y[i].mean())/y[i].std())
|
|
133
|
+
|
|
134
|
+
correlations = corr_.reshape(*shape_x[:-1])
|
|
135
|
+
return correlations
|
|
136
|
+
|
|
137
|
+
def mean_diff_feature_maps(x,y):
|
|
138
|
+
"""Takes two activation matrices of the form BxFxT where B is batch size, F number of filters and T time points
|
|
139
|
+
Returns mean difference between feature map activations
|
|
140
|
+
|
|
141
|
+
Parameters
|
|
142
|
+
----------
|
|
143
|
+
x : numpy array
|
|
144
|
+
Activations Bx[F]xT
|
|
145
|
+
y : numpy array
|
|
146
|
+
Activations Bx[F]xT
|
|
147
|
+
|
|
148
|
+
Returns
|
|
149
|
+
mean_diff : numpy array
|
|
150
|
+
Mean difference between `x` and `y` Bx[F]
|
|
151
|
+
"""
|
|
152
|
+
mean_diff = np.mean(x-y,axis=2)
|
|
153
|
+
return mean_diff
|
|
154
|
+
|
|
155
|
+
def spectral_perturbation_correlation(pert_fn, diff_fn, pred_fn, n_layers, inputs, n_iterations,
|
|
156
|
+
batch_size=30,
|
|
157
|
+
seed=((2017, 7, 10))):
|
|
158
|
+
"""Calculates perturbation correlations for layers in network by perturbing either amplitudes or phases
|
|
159
|
+
|
|
160
|
+
Parameters
|
|
161
|
+
----------
|
|
162
|
+
pert_fn : function
|
|
163
|
+
Function that perturbs spectral phase and amplitudes of inputs
|
|
164
|
+
diff_fn : function
|
|
165
|
+
Function that calculates difference between original and perturbed activations
|
|
166
|
+
pred_fn : function
|
|
167
|
+
Function that returns a list of activations.
|
|
168
|
+
Each entry in the list corresponds to the output of 1 layer in a network
|
|
169
|
+
n_layers : int
|
|
170
|
+
Number of layers pred_fn returns activations for.
|
|
171
|
+
inputs : numpy array
|
|
172
|
+
Original inputs that are used for perturbation [B,X,T,1]
|
|
173
|
+
Phase perturbations are sampled for each input individually, but applied to all X of that input
|
|
174
|
+
n_iterations : int
|
|
175
|
+
Number of iterations of correlation computation. The higher the better
|
|
176
|
+
batch_size : int
|
|
177
|
+
Number of inputs that are used for one forward pass. (Concatenated for all inputs)
|
|
178
|
+
|
|
179
|
+
Returns
|
|
180
|
+
-------
|
|
181
|
+
pert_corrs : numpy array
|
|
182
|
+
List of length n_layers containing average perturbation correlations over iterations
|
|
183
|
+
L x CxFrxFi (Channels,Frequencies,Filters)
|
|
184
|
+
"""
|
|
185
|
+
rng = np.random.RandomState(seed)
|
|
186
|
+
|
|
187
|
+
# Get batch indeces
|
|
188
|
+
batch_inds = get_balanced_batches(
|
|
189
|
+
n_trials=len(inputs), rng=rng, shuffle=False, batch_size=batch_size)
|
|
190
|
+
# Calculate layer activations and reshape
|
|
191
|
+
log.info("Compute original predictions...")
|
|
192
|
+
orig_preds = [pred_fn(inputs[inds])
|
|
193
|
+
for inds in batch_inds]
|
|
194
|
+
use_shape = []
|
|
195
|
+
for l in range(n_layers):
|
|
196
|
+
tmp = list(orig_preds[0][l].shape)
|
|
197
|
+
tmp.extend([1]*(4-len(tmp)))
|
|
198
|
+
tmp[0] = len(inputs)
|
|
199
|
+
use_shape.append(tmp)
|
|
200
|
+
orig_preds_layers = [np.concatenate([orig_preds[o][l] for o in range(len(orig_preds))]).reshape(use_shape[l])
|
|
201
|
+
for l in range(n_layers)]
|
|
202
|
+
|
|
203
|
+
# Compute FFT of inputs
|
|
204
|
+
fft_input = np.fft.rfft(inputs, n=inputs.shape[2], axis=2)
|
|
205
|
+
amps = np.abs(fft_input)
|
|
206
|
+
phases = np.angle(fft_input)
|
|
207
|
+
|
|
208
|
+
pert_corrs = [0]*n_layers
|
|
209
|
+
for i in range(n_iterations):
|
|
210
|
+
log.info("Iteration {:d}...".format(i))
|
|
211
|
+
log.info("Sample perturbation...")
|
|
212
|
+
amps_pert,phases_pert,pert_vals = pert_fn(amps,phases,rng=rng)
|
|
213
|
+
|
|
214
|
+
# Compute perturbed inputs
|
|
215
|
+
log.info("Compute perturbed complex inputs...")
|
|
216
|
+
fft_pert = amps_pert*np.exp(1j*phases_pert)
|
|
217
|
+
log.info("Compute perturbed real inputs...")
|
|
218
|
+
inputs_pert = np.fft.irfft(fft_pert, n=inputs.shape[2], axis=2).astype(np.float32)
|
|
219
|
+
|
|
220
|
+
# Calculate layer activations for perturbed inputs
|
|
221
|
+
log.info("Compute new predictions...")
|
|
222
|
+
new_preds = [pred_fn(inputs_pert[inds])
|
|
223
|
+
for inds in batch_inds]
|
|
224
|
+
new_preds_layers = [np.concatenate([new_preds[o][l] for o in range(len(new_preds))]).reshape(use_shape[l])
|
|
225
|
+
for l in range(n_layers)]
|
|
226
|
+
|
|
227
|
+
for l in range(n_layers):
|
|
228
|
+
log.info("Layer {:d}...".format(l))
|
|
229
|
+
# Calculate difference of original and perturbed feature map activations
|
|
230
|
+
log.info("Compute activation difference...")
|
|
231
|
+
preds_diff = diff_fn(new_preds_layers[l][:,:,:,0],orig_preds_layers[l][:,:,:,0])
|
|
232
|
+
|
|
233
|
+
# Calculate feature map differences with perturbations
|
|
234
|
+
log.info("Compute correlation...")
|
|
235
|
+
pert_corrs_tmp = wrap_reshape_apply_fn(corr,
|
|
236
|
+
pert_vals[:,:,:,0],preds_diff,
|
|
237
|
+
axis_a=(0,), axis_b=(0))
|
|
238
|
+
pert_corrs[l] += pert_corrs_tmp
|
|
239
|
+
|
|
240
|
+
pert_corrs = [pert_corrs[l]/n_iterations for l in range(n_layers)] #mean over iterations
|
|
241
|
+
return pert_corrs
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def compute_amplitude_prediction_correlations(pred_fn, examples, n_iterations,
|
|
245
|
+
perturb_fn=amp_perturbation_additive,
|
|
246
|
+
batch_size=30,
|
|
247
|
+
seed=((2017, 7, 10))):
|
|
248
|
+
"""
|
|
249
|
+
Perturb input amplitudes and compute correlation between amplitude
|
|
250
|
+
perturbations and prediction changes when pushing perturbed input through
|
|
251
|
+
the prediction function.
|
|
252
|
+
|
|
253
|
+
For more details, see [EEGDeepLearning]_.
|
|
254
|
+
|
|
255
|
+
Parameters
|
|
256
|
+
----------
|
|
257
|
+
pred_fn: function
|
|
258
|
+
Function accepting an numpy input and returning prediction.
|
|
259
|
+
examples: ndarray
|
|
260
|
+
Numpy examples, first axis should be example axis.
|
|
261
|
+
n_iterations: int
|
|
262
|
+
Number of iterations to compute.
|
|
263
|
+
perturb_fn: function, optional
|
|
264
|
+
Function accepting amplitude array and random generator and returning
|
|
265
|
+
perturbation. Default is Gaussian perturbation.
|
|
266
|
+
batch_size: int, optional
|
|
267
|
+
Batch size for computing predictions.
|
|
268
|
+
seed: int, optional
|
|
269
|
+
Random generator seed
|
|
270
|
+
|
|
271
|
+
Returns
|
|
272
|
+
-------
|
|
273
|
+
amplitude_pred_corrs: ndarray
|
|
274
|
+
Correlations between amplitude perturbations and prediction changes
|
|
275
|
+
for all sensors and frequency bins.
|
|
276
|
+
|
|
277
|
+
References
|
|
278
|
+
----------
|
|
279
|
+
|
|
280
|
+
.. [EEGDeepLearning] Schirrmeister, R. T., Springenberg, J. T., Fiederer, L. D. J.,
|
|
281
|
+
Glasstetter, M., Eggensperger, K., Tangermann, M., ... & Ball, T. (2017).
|
|
282
|
+
Deep learning with convolutional neural networks for EEG decoding and
|
|
283
|
+
visualization.
|
|
284
|
+
arXiv preprint arXiv:1703.05051.
|
|
285
|
+
"""
|
|
286
|
+
pred_fn_new = lambda x: [pred_fn(x)]
|
|
287
|
+
pred_corrs = spectral_perturbation_correlation(perturb_fn, mean_diff_feature_maps,
|
|
288
|
+
pred_fn_new, 1, examples, n_iterations,
|
|
289
|
+
batch_size=batch_size, seed=seed)
|
|
290
|
+
|
|
291
|
+
return pred_corrs[0]
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from scipy.optimize import leastsq
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
def err_fn_sin(p,x,y):
|
|
5
|
+
return (y-fit_fn_sin(x,*p)).flat
|
|
6
|
+
|
|
7
|
+
def err_fn_lin(p,x,y):
|
|
8
|
+
return (y-fit_fn_lin(x,*p)).flat
|
|
9
|
+
|
|
10
|
+
def fit_fn_lin(x,*kwargs):
|
|
11
|
+
return kwargs[0]+kwargs[1]*x
|
|
12
|
+
|
|
13
|
+
def fit_fn_sin(x,*kwargs):
|
|
14
|
+
freqs = kwargs[0]
|
|
15
|
+
amps = kwargs[1]
|
|
16
|
+
phases = kwargs[2]
|
|
17
|
+
offset = kwargs[3]
|
|
18
|
+
sig = np.zeros((len(x)))+offset
|
|
19
|
+
sig += amps*np.cos(x*freqs+phases)
|
|
20
|
+
return sig
|
|
21
|
+
|
|
22
|
+
def signal_fit(signals,fs):
|
|
23
|
+
"""Fits sinusoid and linear function to signals
|
|
24
|
+
see sinfit.fit_fn_sin and sinfit.fit_fn_lin
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
signals : numpy array
|
|
29
|
+
[FxCxTx1] Filters x Channels x Time x 1
|
|
30
|
+
fs : float
|
|
31
|
+
Sampling frequency
|
|
32
|
+
|
|
33
|
+
Returns
|
|
34
|
+
-------
|
|
35
|
+
params_sin : numpy array
|
|
36
|
+
[FxCx4] Parameters of sinusoid fit
|
|
37
|
+
Parameters are: Frequency,Amplitude,Phase,DCOffset
|
|
38
|
+
params_lin : numpy array
|
|
39
|
+
[FxCx2] Parameters of sinusoid fit
|
|
40
|
+
Parameters are: Frequency,Amplitude,Phase,DCOffset
|
|
41
|
+
err_sin : numpy array
|
|
42
|
+
[FxCx1] MSE for sinusoid fit
|
|
43
|
+
err_lin : numpy array
|
|
44
|
+
[FxCx1] MSE for linear fit
|
|
45
|
+
"""
|
|
46
|
+
params_sin = []
|
|
47
|
+
params_lin = []
|
|
48
|
+
|
|
49
|
+
err_sin = []
|
|
50
|
+
err_lin = []
|
|
51
|
+
|
|
52
|
+
freqs = np.fft.rfftfreq(signals.shape[2], d=1.0/fs)[1:]
|
|
53
|
+
x = np.linspace(0,signals.shape[2]/fs,signals.shape[2])*2*np.pi
|
|
54
|
+
for filt in range(signals.shape[0]):
|
|
55
|
+
params_sin_tmp = []
|
|
56
|
+
params_lin_tmp = []
|
|
57
|
+
|
|
58
|
+
err_sin_tmp = []
|
|
59
|
+
err_lin_tmp = []
|
|
60
|
+
for ch in range(signals.shape[1]):
|
|
61
|
+
X_tmp = signals[filt,ch].squeeze()
|
|
62
|
+
fft_X = np.fft.rfft(X_tmp,axis=0)
|
|
63
|
+
amps_mean = np.abs(fft_X)[1:]
|
|
64
|
+
phases_mean = np.angle(fft_X)[1:]
|
|
65
|
+
offset = X_tmp.mean()
|
|
66
|
+
|
|
67
|
+
sort = np.argsort(amps_mean)[::-1][0]
|
|
68
|
+
p0 = [freqs[sort],amps_mean[sort],phases_mean[sort],offset]
|
|
69
|
+
|
|
70
|
+
fit_sin_ch = leastsq(err_fn_sin, p0, args=(x, X_tmp),maxfev=100000)
|
|
71
|
+
fit_lin_ch = leastsq(err_fn_lin, [0,0], args=(x, X_tmp),maxfev=100000)
|
|
72
|
+
|
|
73
|
+
err_sin_ch = np.square(fit_fn_sin(x,*fit_sin_ch[0]) - X_tmp).mean()
|
|
74
|
+
err_lin_ch = np.square(fit_fn_lin(x,*fit_lin_ch[0]) - X_tmp).mean()
|
|
75
|
+
|
|
76
|
+
params_sin_tmp.append(fit_sin_ch[0])
|
|
77
|
+
params_lin_tmp.append(fit_lin_ch[0])
|
|
78
|
+
err_sin_tmp.append(err_sin_ch)
|
|
79
|
+
err_lin_tmp.append(err_lin_ch)
|
|
80
|
+
params_sin.append(params_sin_tmp)
|
|
81
|
+
params_lin.append(params_lin_tmp)
|
|
82
|
+
err_sin.append(err_sin_tmp)
|
|
83
|
+
err_lin.append(err_lin_tmp)
|
|
84
|
+
params_sin = np.asarray(params_sin)
|
|
85
|
+
params_lin = np.asarray(params_lin)
|
|
86
|
+
err_sin = np.asarray(err_sin)
|
|
87
|
+
err_lin = np.asarray(err_lin)
|
|
88
|
+
|
|
89
|
+
return params_sin,params_lin,err_sin,err_lin
|
|
@@ -553,24 +553,6 @@
|
|
|
553
553
|
"## Plot correlations"
|
|
554
554
|
]
|
|
555
555
|
},
|
|
556
|
-
{
|
|
557
|
-
"cell_type": "markdown",
|
|
558
|
-
"metadata": {},
|
|
559
|
-
"source": [
|
|
560
|
-
"Compute the mean correlations across iterations."
|
|
561
|
-
]
|
|
562
|
-
},
|
|
563
|
-
{
|
|
564
|
-
"cell_type": "code",
|
|
565
|
-
"execution_count": 8,
|
|
566
|
-
"metadata": {
|
|
567
|
-
"collapsed": true
|
|
568
|
-
},
|
|
569
|
-
"outputs": [],
|
|
570
|
-
"source": [
|
|
571
|
-
"mean_corr = np.mean(amp_pred_corrs, axis=0)"
|
|
572
|
-
]
|
|
573
|
-
},
|
|
574
556
|
{
|
|
575
557
|
"cell_type": "markdown",
|
|
576
558
|
"metadata": {},
|
|
@@ -738,21 +720,21 @@
|
|
|
738
720
|
"keep_outputs": true
|
|
739
721
|
},
|
|
740
722
|
"kernelspec": {
|
|
741
|
-
"display_name": "Python
|
|
723
|
+
"display_name": "Python 2",
|
|
742
724
|
"language": "python",
|
|
743
|
-
"name": "
|
|
725
|
+
"name": "python2"
|
|
744
726
|
},
|
|
745
727
|
"language_info": {
|
|
746
728
|
"codemirror_mode": {
|
|
747
729
|
"name": "ipython",
|
|
748
|
-
"version":
|
|
730
|
+
"version": 2
|
|
749
731
|
},
|
|
750
732
|
"file_extension": ".py",
|
|
751
733
|
"mimetype": "text/x-python",
|
|
752
734
|
"name": "python",
|
|
753
735
|
"nbconvert_exporter": "python",
|
|
754
|
-
"pygments_lexer": "
|
|
755
|
-
"version": "
|
|
736
|
+
"pygments_lexer": "ipython2",
|
|
737
|
+
"version": "2.7.13"
|
|
756
738
|
}
|
|
757
739
|
},
|
|
758
740
|
"nbformat": 4,
|
|
@@ -9,6 +9,14 @@ braindecode\.visualization package
|
|
|
9
9
|
Submodules
|
|
10
10
|
----------
|
|
11
11
|
|
|
12
|
+
braindecode\.visualization\.input\_windows module
|
|
13
|
+
-------------------------------------------------
|
|
14
|
+
|
|
15
|
+
.. automodule:: braindecode.visualization.input_windows
|
|
16
|
+
:members:
|
|
17
|
+
:undoc-members:
|
|
18
|
+
:show-inheritance:
|
|
19
|
+
|
|
12
20
|
braindecode\.visualization\.perturbation module
|
|
13
21
|
-----------------------------------------------
|
|
14
22
|
|
|
@@ -25,4 +33,12 @@ braindecode\.visualization\.plot module
|
|
|
25
33
|
:undoc-members:
|
|
26
34
|
:show-inheritance:
|
|
27
35
|
|
|
36
|
+
braindecode\.visualization\.sinfit module
|
|
37
|
+
-----------------------------------------
|
|
38
|
+
|
|
39
|
+
.. automodule:: braindecode.visualization.sinfit
|
|
40
|
+
:members:
|
|
41
|
+
:undoc-members:
|
|
42
|
+
:show-inheritance:
|
|
43
|
+
|
|
28
44
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.3.1"
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
|
|
3
|
-
import numpy as np
|
|
4
|
-
from numpy.random import RandomState
|
|
5
|
-
|
|
6
|
-
from braindecode.datautil.iterators import get_balanced_batches
|
|
7
|
-
from braindecode.util import wrap_reshape_apply_fn, corr
|
|
8
|
-
|
|
9
|
-
log = logging.getLogger(__name__)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def gaussian_perturbation(amps, rng):
|
|
13
|
-
"""
|
|
14
|
-
Create gaussian noise tensor with same shape as amplitudes.
|
|
15
|
-
|
|
16
|
-
Parameters
|
|
17
|
-
----------
|
|
18
|
-
amps: ndarray
|
|
19
|
-
Amplitudes.
|
|
20
|
-
rng: RandomState
|
|
21
|
-
Random generator.
|
|
22
|
-
|
|
23
|
-
Returns
|
|
24
|
-
-------
|
|
25
|
-
perturbation: ndarray
|
|
26
|
-
Perturbations to add to the amplitudes.
|
|
27
|
-
"""
|
|
28
|
-
perturbation = rng.randn(*amps.shape).astype(np.float32)
|
|
29
|
-
return perturbation
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def compute_amplitude_prediction_correlations(pred_fn, examples, n_iterations,
|
|
33
|
-
perturb_fn=gaussian_perturbation,
|
|
34
|
-
batch_size=30,
|
|
35
|
-
seed=((2017, 7, 10))):
|
|
36
|
-
"""
|
|
37
|
-
Perturb input amplitudes and compute correlation between amplitude
|
|
38
|
-
perturbations and prediction changes when pushing perturbed input through
|
|
39
|
-
the prediction function.
|
|
40
|
-
|
|
41
|
-
For more details, see [EEGDeepLearning]_.
|
|
42
|
-
|
|
43
|
-
Parameters
|
|
44
|
-
----------
|
|
45
|
-
pred_fn: function
|
|
46
|
-
Function accepting an numpy input and returning prediction.
|
|
47
|
-
examples: ndarray
|
|
48
|
-
Numpy examples, first axis should be example axis.
|
|
49
|
-
n_iterations: int
|
|
50
|
-
Number of iterations to compute.
|
|
51
|
-
perturb_fn: function, optional
|
|
52
|
-
Function accepting amplitude array and random generator and returning
|
|
53
|
-
perturbation. Default is Gaussian perturbation.
|
|
54
|
-
batch_size: int, optional
|
|
55
|
-
Batch size for computing predictions.
|
|
56
|
-
seed: int, optional
|
|
57
|
-
Random generator seed
|
|
58
|
-
|
|
59
|
-
Returns
|
|
60
|
-
-------
|
|
61
|
-
amplitude_pred_corrs: ndarray
|
|
62
|
-
Correlations between amplitude perturbations and prediction changes
|
|
63
|
-
for all sensors and frequency bins.
|
|
64
|
-
|
|
65
|
-
References
|
|
66
|
-
----------
|
|
67
|
-
|
|
68
|
-
.. [EEGDeepLearning] Schirrmeister, R. T., Springenberg, J. T., Fiederer, L. D. J.,
|
|
69
|
-
Glasstetter, M., Eggensperger, K., Tangermann, M., ... & Ball, T. (2017).
|
|
70
|
-
Deep learning with convolutional neural networks for EEG decoding and
|
|
71
|
-
visualization.
|
|
72
|
-
arXiv preprint arXiv:1703.05051.
|
|
73
|
-
"""
|
|
74
|
-
inds_per_batch = get_balanced_batches(
|
|
75
|
-
n_trials=len(examples), rng=None, shuffle=False, batch_size=batch_size)
|
|
76
|
-
log.info("Compute original predictions...")
|
|
77
|
-
orig_preds = [pred_fn(examples[example_inds])
|
|
78
|
-
for example_inds in inds_per_batch]
|
|
79
|
-
orig_preds_arr = np.concatenate(orig_preds)
|
|
80
|
-
rng = RandomState(seed)
|
|
81
|
-
fft_input = np.fft.rfft(examples, axis=2)
|
|
82
|
-
amps = np.abs(fft_input)
|
|
83
|
-
phases = np.angle(fft_input)
|
|
84
|
-
|
|
85
|
-
amp_pred_corrs = []
|
|
86
|
-
for i_iteration in range(n_iterations):
|
|
87
|
-
log.info("Iteration {:d}...".format(i_iteration))
|
|
88
|
-
log.info("Sample perturbation...")
|
|
89
|
-
perturbation = perturb_fn(amps, rng)
|
|
90
|
-
log.info("Compute new amplitudes...")
|
|
91
|
-
# do not allow perturbation to make amplitudes go below
|
|
92
|
-
# zero
|
|
93
|
-
perturbation = np.maximum(-amps, perturbation)
|
|
94
|
-
new_amps = amps + perturbation
|
|
95
|
-
log.info("Compute new complex inputs...")
|
|
96
|
-
new_complex = _amplitude_phase_to_complex(new_amps, phases)
|
|
97
|
-
log.info("Compute new real inputs...")
|
|
98
|
-
new_in = np.fft.irfft(new_complex, axis=2).astype(np.float32)
|
|
99
|
-
log.info("Compute new predictions...")
|
|
100
|
-
new_preds = [pred_fn(new_in[example_inds])
|
|
101
|
-
for example_inds in inds_per_batch]
|
|
102
|
-
|
|
103
|
-
new_preds_arr = np.concatenate(new_preds)
|
|
104
|
-
|
|
105
|
-
diff_preds = new_preds_arr - orig_preds_arr
|
|
106
|
-
|
|
107
|
-
log.info("Compute correlation...")
|
|
108
|
-
amp_pred_corr = wrap_reshape_apply_fn(corr, perturbation[:, :, :, 0],
|
|
109
|
-
diff_preds,
|
|
110
|
-
axis_a=(0,), axis_b=(0))
|
|
111
|
-
amp_pred_corrs.append(amp_pred_corr)
|
|
112
|
-
return amp_pred_corrs
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def _amplitude_phase_to_complex(amplitude, phase):
|
|
116
|
-
return amplitude * np.cos(phase) + amplitude * np.sin(phase) * 1j
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|