braintools 0.0.2__py2.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.
- braintools/__init__.py +27 -0
- braintools/functional/__init__.py +25 -0
- braintools/functional/_activations.py +733 -0
- braintools/functional/_normalization.py +62 -0
- braintools/functional/_spikes.py +90 -0
- braintools/init/__init__.py +26 -0
- braintools/init/_base.py +36 -0
- braintools/init/_generic.py +205 -0
- braintools/init/_random_inits.py +489 -0
- braintools/init/_regular_inits.py +109 -0
- braintools/input/__init__.py +27 -0
- braintools/input/currents.py +380 -0
- braintools/input/currents_test.py +111 -0
- braintools/metric/__init__.py +46 -0
- braintools/metric/_classification.py +569 -0
- braintools/metric/_classification_test.py +625 -0
- braintools/metric/_correlation.py +302 -0
- braintools/metric/_correlation_test.py +124 -0
- braintools/metric/_fenchel_young.py +67 -0
- braintools/metric/_fenchel_young_test.py +55 -0
- braintools/metric/_firings.py +85 -0
- braintools/metric/_firings_test.py +41 -0
- braintools/metric/_lfp.py +139 -0
- braintools/metric/_ranking.py +163 -0
- braintools/metric/_ranking_test.py +242 -0
- braintools/metric/_regression.py +425 -0
- braintools/metric/_regression_test.py +133 -0
- braintools/metric/_smoothing.py +46 -0
- braintools/metric/_smoothing_test.py +43 -0
- braintools/metric/_util.py +25 -0
- braintools/optim/__init__.py +23 -0
- braintools/optim/_lr_scheduler.py +482 -0
- braintools/optim/_sgd_optimizer.py +1130 -0
- braintools-0.0.2.dist-info/LICENSE +202 -0
- braintools-0.0.2.dist-info/METADATA +93 -0
- braintools-0.0.2.dist-info/RECORD +38 -0
- braintools-0.0.2.dist-info/WHEEL +6 -0
- braintools-0.0.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# Copyright 2024 BrainPy Ecosystem Limited. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
|
|
16
|
+
# -*- coding: utf-8 -*-
|
|
17
|
+
|
|
18
|
+
from typing import Union
|
|
19
|
+
|
|
20
|
+
import braincore as bc
|
|
21
|
+
import numpy as onp
|
|
22
|
+
from jax import vmap, lax, numpy as jnp
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
'cross_correlation',
|
|
26
|
+
'voltage_fluctuation',
|
|
27
|
+
'matrix_correlation',
|
|
28
|
+
'weighted_correlation',
|
|
29
|
+
'functional_connectivity',
|
|
30
|
+
'functional_connectivity_dynamics',
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def cross_correlation(spikes: bc.typing.ArrayLike,
|
|
35
|
+
bin: Union[int, float],
|
|
36
|
+
dt: Union[int, float] =None,
|
|
37
|
+
method: str = 'loop'):
|
|
38
|
+
r"""Calculate cross correlation index between neurons.
|
|
39
|
+
|
|
40
|
+
The coherence [1]_ between two neurons i and j is measured by their
|
|
41
|
+
cross-correlation of spike trains at zero time lag within a time bin
|
|
42
|
+
of :math:`\Delta t = \tau`. More specifically, suppose that a long
|
|
43
|
+
time interval T is divided into small bins of :math:`\Delta t` and
|
|
44
|
+
that two spike trains are given by :math:`X(l)=` 0 or 1, :math:`Y(l)=` 0
|
|
45
|
+
or 1, :math:`l=1,2, \ldots, K(T / K=\tau)`. Thus, we define a coherence
|
|
46
|
+
measure for the pair as:
|
|
47
|
+
|
|
48
|
+
.. math::
|
|
49
|
+
|
|
50
|
+
\kappa_{i j}(\tau)=\frac{\sum_{l=1}^{K} X(l) Y(l)}
|
|
51
|
+
{\sqrt{\sum_{l=1}^{K} X(l) \sum_{l=1}^{K} Y(l)}}
|
|
52
|
+
|
|
53
|
+
The population coherence measure :math:`\kappa(\tau)` is defined by the
|
|
54
|
+
average of :math:`\kappa_{i j}(\tau)` over many pairs of neurons in the
|
|
55
|
+
network.
|
|
56
|
+
|
|
57
|
+
.. note::
|
|
58
|
+
To JIT compile this function, users should make ``bin``, ``dt``, ``numpy`` static.
|
|
59
|
+
For example, ``partial(brainpy.measure.cross_correlation, bin=10, numpy=False)``.
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
spikes : ndarray
|
|
64
|
+
The history of spike states of the neuron group.
|
|
65
|
+
bin : float, int
|
|
66
|
+
The time bin to normalize spike states.
|
|
67
|
+
dt : float, optional
|
|
68
|
+
The time precision.
|
|
69
|
+
method: str
|
|
70
|
+
The method to calculate all pairs of cross correlation.
|
|
71
|
+
Supports two kinds of methods: `loop` and `vmap`.
|
|
72
|
+
`vmap` method needs much more memory.
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
cc_index : float
|
|
77
|
+
The cross correlation value which represents the synchronization index.
|
|
78
|
+
|
|
79
|
+
References
|
|
80
|
+
----------
|
|
81
|
+
.. [1] Wang, Xiao-Jing, and György Buzsáki. "Gamma oscillation by synaptic
|
|
82
|
+
inhibition in a hippocampal interneuronal network model." Journal of
|
|
83
|
+
neuroscience 16.20 (1996): 6402-6413.
|
|
84
|
+
"""
|
|
85
|
+
dt = bc.environ.get_dt() if dt is None else dt
|
|
86
|
+
bin_size = int(bin / dt)
|
|
87
|
+
num_hist, num_neu = spikes.shape
|
|
88
|
+
num_bin = int(onp.ceil(num_hist / bin_size))
|
|
89
|
+
if num_bin * bin_size != num_hist:
|
|
90
|
+
spikes = jnp.append(spikes, jnp.zeros((num_bin * bin_size - num_hist, num_neu)), axis=0)
|
|
91
|
+
states = spikes.T.reshape((num_neu, num_bin, bin_size))
|
|
92
|
+
states = jnp.asarray(jnp.sum(states, axis=2) > 0., dtype=jnp.float_)
|
|
93
|
+
indices = jnp.tril_indices(num_neu, k=-1)
|
|
94
|
+
|
|
95
|
+
if method == 'loop':
|
|
96
|
+
def _f(i, j):
|
|
97
|
+
sqrt_ij = jnp.sqrt(jnp.sum(states[i]) * jnp.sum(states[j]))
|
|
98
|
+
return lax.cond(sqrt_ij == 0.,
|
|
99
|
+
lambda _: 0.,
|
|
100
|
+
lambda _: jnp.sum(states[i] * states[j]) / sqrt_ij,
|
|
101
|
+
None)
|
|
102
|
+
|
|
103
|
+
res = bc.transform.for_loop(_f, *indices)
|
|
104
|
+
|
|
105
|
+
elif method == 'vmap':
|
|
106
|
+
@vmap
|
|
107
|
+
def _cc(i, j):
|
|
108
|
+
sqrt_ij = jnp.sqrt(jnp.sum(states[i]) * jnp.sum(states[j]))
|
|
109
|
+
return lax.cond(sqrt_ij == 0.,
|
|
110
|
+
lambda _: 0.,
|
|
111
|
+
lambda _: jnp.sum(states[i] * states[j]) / sqrt_ij,
|
|
112
|
+
None)
|
|
113
|
+
|
|
114
|
+
res = _cc(*indices)
|
|
115
|
+
else:
|
|
116
|
+
raise ValueError(f'Do not support {method}. We only support "loop" or "vmap".')
|
|
117
|
+
|
|
118
|
+
return jnp.mean(jnp.asarray(res))
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _f_signal(signal):
|
|
122
|
+
return jnp.mean(signal * signal) - jnp.mean(signal) ** 2
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def voltage_fluctuation(potentials, method='loop'):
|
|
126
|
+
r"""Calculate neuronal synchronization via voltage variance.
|
|
127
|
+
|
|
128
|
+
The method comes from [1]_ [2]_ [3]_.
|
|
129
|
+
|
|
130
|
+
First, average over the membrane potential :math:`V`
|
|
131
|
+
|
|
132
|
+
.. math::
|
|
133
|
+
|
|
134
|
+
V(t) = \frac{1}{N} \sum_{i=1}^{N} V_i(t)
|
|
135
|
+
|
|
136
|
+
The variance of the time fluctuations of :math:`V(t)` is
|
|
137
|
+
|
|
138
|
+
.. math::
|
|
139
|
+
|
|
140
|
+
\sigma_V^2 = \left\langle \left[ V(t) \right]^2 \right\rangle_t -
|
|
141
|
+
\left[ \left\langle V(t) \right\rangle_t \right]^2
|
|
142
|
+
|
|
143
|
+
where :math:`\left\langle \ldots \right\rangle_t = (1 / T_m) \int_0^{T_m} dt \, \ldots`
|
|
144
|
+
denotes time-averaging over a large time, :math:`\tau_m`. After normalization
|
|
145
|
+
of :math:`\sigma_V` to the average over the population of the single cell
|
|
146
|
+
membrane potentials
|
|
147
|
+
|
|
148
|
+
.. math::
|
|
149
|
+
|
|
150
|
+
\sigma_{V_i}^2 = \left\langle\left[ V_i(t) \right]^2 \right\rangle_t -
|
|
151
|
+
\left[ \left\langle V_i(t) \right\rangle_t \right]^2
|
|
152
|
+
|
|
153
|
+
one defines a synchrony measure, :math:`\chi (N)`, for the activity of a system
|
|
154
|
+
of :math:`N` neurons by:
|
|
155
|
+
|
|
156
|
+
.. math::
|
|
157
|
+
|
|
158
|
+
\chi^2 \left( N \right) = \frac{\sigma_V^2}{ \frac{1}{N} \sum_{i=1}^N
|
|
159
|
+
\sigma_{V_i}^2}
|
|
160
|
+
|
|
161
|
+
.. [1] Golomb, D. and Rinzel J. (1993) Dynamics of globally coupled
|
|
162
|
+
inhibitory neurons with heterogeneity. Phys. Rev. E 48:4810-4814.
|
|
163
|
+
.. [2] Golomb D. and Rinzel J. (1994) Clustering in globally coupled
|
|
164
|
+
inhibitory neurons. Physica D 72:259-282.
|
|
165
|
+
.. [3] David Golomb (2007) Neuronal synchrony measures. Scholarpedia, 2(1):1347.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
potentials: The membrane potential matrix of the neuron group.
|
|
169
|
+
method: The method to calculate all pairs of cross correlation.
|
|
170
|
+
Supports two kinds of methods: `loop` and `vmap`.
|
|
171
|
+
`vmap` method will consume much more memory.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
sync_index: The synchronization index.
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
avg = jnp.mean(potentials, axis=1)
|
|
178
|
+
avg_var = jnp.mean(avg * avg) - jnp.mean(avg) ** 2
|
|
179
|
+
|
|
180
|
+
if method == 'loop':
|
|
181
|
+
_var = bc.transform.for_loop(_f_signal, jnp.moveaxis(potentials, 0, 1))
|
|
182
|
+
elif method == 'vmap':
|
|
183
|
+
_var = vmap(_f_signal, in_axes=1)(potentials)
|
|
184
|
+
else:
|
|
185
|
+
raise ValueError(f'Do not support {method}. We only support "loop" or "vmap".')
|
|
186
|
+
|
|
187
|
+
var_mean = jnp.mean(_var)
|
|
188
|
+
r = jnp.where(var_mean == 0., 1., avg_var / var_mean)
|
|
189
|
+
return r
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def matrix_correlation(x, y):
|
|
193
|
+
"""Pearson correlation of the lower triagonal of two matrices.
|
|
194
|
+
|
|
195
|
+
The triangular matrix is offset by k = 1 in order to ignore the diagonal line
|
|
196
|
+
|
|
197
|
+
Parameters
|
|
198
|
+
----------
|
|
199
|
+
x: ndarray
|
|
200
|
+
First matrix.
|
|
201
|
+
y: ndarray
|
|
202
|
+
Second matrix
|
|
203
|
+
numpy: bool
|
|
204
|
+
Whether we use numpy array as the functional output.
|
|
205
|
+
If ``False``, this function can be JIT compiled.
|
|
206
|
+
|
|
207
|
+
Returns
|
|
208
|
+
-------
|
|
209
|
+
coef: ndarray
|
|
210
|
+
Correlation coefficient
|
|
211
|
+
"""
|
|
212
|
+
if x.ndim != 2:
|
|
213
|
+
raise ValueError(f'Only support 2d array, but we got a array '
|
|
214
|
+
f'with the shape of {x.shape}')
|
|
215
|
+
if y.ndim != 2:
|
|
216
|
+
raise ValueError(f'Only support 2d array, but we got a array '
|
|
217
|
+
f'with the shape of {y.shape}')
|
|
218
|
+
x = x[jnp.triu_indices_from(x, k=1)]
|
|
219
|
+
y = y[jnp.triu_indices_from(y, k=1)]
|
|
220
|
+
cc = jnp.corrcoef(x, y)[0, 1]
|
|
221
|
+
return cc
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def functional_connectivity(activities):
|
|
225
|
+
"""Functional connectivity matrix of timeseries activities.
|
|
226
|
+
|
|
227
|
+
Parameters
|
|
228
|
+
----------
|
|
229
|
+
activities: ndarray
|
|
230
|
+
The multidimensional array with the shape of ``(num_time, num_sample)``.
|
|
231
|
+
numpy: bool
|
|
232
|
+
Whether we use numpy array as the functional output.
|
|
233
|
+
If ``False``, this function can be JIT compiled.
|
|
234
|
+
|
|
235
|
+
Returns
|
|
236
|
+
-------
|
|
237
|
+
connectivity_matrix: ndarray
|
|
238
|
+
``num_sample x num_sample`` functional connectivity matrix.
|
|
239
|
+
"""
|
|
240
|
+
if activities.ndim != 2:
|
|
241
|
+
raise ValueError('Only support 2d array with shape of "(num_time, num_sample)". '
|
|
242
|
+
f'But we got a array with the shape of {activities.shape}')
|
|
243
|
+
fc = jnp.corrcoef(activities.T)
|
|
244
|
+
return jnp.nan_to_num(fc)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def functional_connectivity_dynamics(activities, window_size=30, step_size=5):
|
|
248
|
+
"""Computes functional connectivity dynamics (FCD) matrix.
|
|
249
|
+
|
|
250
|
+
Parameters
|
|
251
|
+
----------
|
|
252
|
+
activities: ndarray
|
|
253
|
+
The time series with shape of ``(num_time, num_sample)``.
|
|
254
|
+
window_size: int
|
|
255
|
+
Size of each rolling window in time steps, defaults to 30.
|
|
256
|
+
step_size: int
|
|
257
|
+
Step size between each rolling window, defaults to 5.
|
|
258
|
+
|
|
259
|
+
Returns
|
|
260
|
+
-------
|
|
261
|
+
fcd_matrix: ndarray
|
|
262
|
+
FCD matrix.
|
|
263
|
+
"""
|
|
264
|
+
pass
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def weighted_correlation(x, y, w):
|
|
268
|
+
"""Weighted Pearson correlation of two data series.
|
|
269
|
+
|
|
270
|
+
Parameters
|
|
271
|
+
----------
|
|
272
|
+
x: ndarray
|
|
273
|
+
The data series 1.
|
|
274
|
+
y: ndarray
|
|
275
|
+
The data series 2.
|
|
276
|
+
w: ndarray
|
|
277
|
+
Weight vector, must have same length as x and y.
|
|
278
|
+
|
|
279
|
+
Returns
|
|
280
|
+
-------
|
|
281
|
+
corr: ndarray
|
|
282
|
+
Weighted correlation coefficient.
|
|
283
|
+
"""
|
|
284
|
+
|
|
285
|
+
def _weighted_mean(x, w):
|
|
286
|
+
"""Weighted Mean"""
|
|
287
|
+
return jnp.sum(x * w) / jnp.sum(w)
|
|
288
|
+
|
|
289
|
+
def _weighted_cov(x, y, w):
|
|
290
|
+
"""Weighted Covariance"""
|
|
291
|
+
return jnp.sum(w * (x - _weighted_mean(x, w)) * (y - _weighted_mean(y, w))) / jnp.sum(w)
|
|
292
|
+
|
|
293
|
+
if x.ndim != 1:
|
|
294
|
+
raise ValueError(f'Only support 1d array, but we got a array '
|
|
295
|
+
f'with the shape of {x.shape}')
|
|
296
|
+
if y.ndim != 1:
|
|
297
|
+
raise ValueError(f'Only support 1d array, but we got a array '
|
|
298
|
+
f'with the shape of {y.shape}')
|
|
299
|
+
if w.ndim != 1:
|
|
300
|
+
raise ValueError(f'Only support 1d array, but we got a array '
|
|
301
|
+
f'with the shape of {w.shape}')
|
|
302
|
+
return _weighted_cov(x, y, w) / jnp.sqrt(_weighted_cov(x, x, w) * _weighted_cov(y, y, w))
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Copyright 2024- BrainPy Ecosystem Limited. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
|
|
16
|
+
# -*- coding: utf-8 -*-
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
import unittest
|
|
20
|
+
from functools import partial
|
|
21
|
+
import braintools as bt
|
|
22
|
+
|
|
23
|
+
from jax import jit
|
|
24
|
+
import jax.numpy as jnp
|
|
25
|
+
import braincore as bc
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TestCrossCorrelation(unittest.TestCase):
|
|
29
|
+
def test_c(self):
|
|
30
|
+
bc.random.seed()
|
|
31
|
+
spikes = jnp.asarray([[1, 0, 1, 0, 1, 0, 1, 0, 0], [1, 1, 1, 1, 1, 1, 1, 0, 0]]).T
|
|
32
|
+
cc1 = bt.metric.cross_correlation(spikes, 1., dt=1.)
|
|
33
|
+
f_cc = jit(partial(bt.metric.cross_correlation, bin=1, dt=1.))
|
|
34
|
+
cc2 = f_cc(spikes)
|
|
35
|
+
print(cc1, cc2)
|
|
36
|
+
self.assertTrue(cc1 == cc2)
|
|
37
|
+
bc.util.clear_buffer_memory()
|
|
38
|
+
|
|
39
|
+
def test_cc(self):
|
|
40
|
+
bc.random.seed()
|
|
41
|
+
spikes = jnp.ones((1000, 10))
|
|
42
|
+
cc1 = bt.metric.cross_correlation(spikes, 1.)
|
|
43
|
+
self.assertTrue(cc1 == 1.)
|
|
44
|
+
|
|
45
|
+
spikes = jnp.zeros((1000, 10))
|
|
46
|
+
cc2 = bt.metric.cross_correlation(spikes, 1.)
|
|
47
|
+
self.assertTrue(cc2 == 0.)
|
|
48
|
+
|
|
49
|
+
bc.util.clear_buffer_memory()
|
|
50
|
+
|
|
51
|
+
def test_cc2(self):
|
|
52
|
+
bc.random.seed()
|
|
53
|
+
spikes = bc.random.randint(0, 2, (1000, 10))
|
|
54
|
+
print(bt.metric.cross_correlation(spikes, 1.))
|
|
55
|
+
print(bt.metric.cross_correlation(spikes, 0.5))
|
|
56
|
+
bc.util.clear_buffer_memory()
|
|
57
|
+
|
|
58
|
+
def test_cc3(self):
|
|
59
|
+
bc.random.seed()
|
|
60
|
+
spikes = bc.random.random((1000, 100)) < 0.8
|
|
61
|
+
print(bt.metric.cross_correlation(spikes, 1.))
|
|
62
|
+
print(bt.metric.cross_correlation(spikes, 0.5))
|
|
63
|
+
bc.util.clear_buffer_memory()
|
|
64
|
+
|
|
65
|
+
def test_cc4(self):
|
|
66
|
+
bc.random.seed()
|
|
67
|
+
spikes = bc.random.random((1000, 100)) < 0.2
|
|
68
|
+
print(bt.metric.cross_correlation(spikes, 1.))
|
|
69
|
+
print(bt.metric.cross_correlation(spikes, 0.5))
|
|
70
|
+
bc.util.clear_buffer_memory()
|
|
71
|
+
|
|
72
|
+
def test_cc5(self):
|
|
73
|
+
bc.random.seed()
|
|
74
|
+
spikes = bc.random.random((1000, 100)) < 0.05
|
|
75
|
+
print(bt.metric.cross_correlation(spikes, 1.))
|
|
76
|
+
print(bt.metric.cross_correlation(spikes, 0.5))
|
|
77
|
+
bc.util.clear_buffer_memory()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class TestVoltageFluctuation(unittest.TestCase):
|
|
81
|
+
def test_vf1(self):
|
|
82
|
+
bc.random.seed()
|
|
83
|
+
voltages = bc.random.normal(0, 10, size=(100, 10))
|
|
84
|
+
print(bt.metric.voltage_fluctuation(voltages))
|
|
85
|
+
|
|
86
|
+
with bc.environ.context(precision=64):
|
|
87
|
+
voltages = jnp.ones((100, 10))
|
|
88
|
+
r1 = bt.metric.voltage_fluctuation(voltages)
|
|
89
|
+
|
|
90
|
+
jit_f = jit(partial(bt.metric.voltage_fluctuation))
|
|
91
|
+
jit_f = jit(lambda a: bt.metric.voltage_fluctuation(a))
|
|
92
|
+
r2 = jit_f(voltages)
|
|
93
|
+
print(r1, r2) # TODO: JIT results are different?
|
|
94
|
+
# self.assertTrue(r1 == r2)
|
|
95
|
+
|
|
96
|
+
bc.util.clear_buffer_memory()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class TestFunctionalConnectivity(unittest.TestCase):
|
|
100
|
+
def test_cf1(self):
|
|
101
|
+
bc.random.seed()
|
|
102
|
+
act = bc.random.random((10000, 3))
|
|
103
|
+
r1 = bt.metric.functional_connectivity(act)
|
|
104
|
+
|
|
105
|
+
jit_f = jit(partial(bt.metric.functional_connectivity))
|
|
106
|
+
r2 = jit_f(act)
|
|
107
|
+
|
|
108
|
+
self.assertTrue(jnp.allclose(r1, r2))
|
|
109
|
+
bc.util.clear_buffer_memory()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class TestMatrixCorrelation(unittest.TestCase):
|
|
113
|
+
def test_mc(self):
|
|
114
|
+
bc.random.seed()
|
|
115
|
+
A = bc.random.random((100, 100))
|
|
116
|
+
B = bc.random.random((100, 100))
|
|
117
|
+
r1 = (bt.metric.matrix_correlation(A, B))
|
|
118
|
+
|
|
119
|
+
jit_f = jit(partial(bt.metric.matrix_correlation))
|
|
120
|
+
r2 = jit_f(A, B)
|
|
121
|
+
self.assertTrue(jnp.allclose(r1, r2))
|
|
122
|
+
bc.util.clear_buffer_memory()
|
|
123
|
+
|
|
124
|
+
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Copyright 2024 BrainPy Ecosystem Limited. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
|
|
16
|
+
"""Fenchel-Young losses."""
|
|
17
|
+
|
|
18
|
+
from typing import Any, Protocol
|
|
19
|
+
|
|
20
|
+
import jax.numpy as jnp
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"make_fenchel_young_loss",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class MaxFun(Protocol):
|
|
28
|
+
|
|
29
|
+
def __call__(self, scores, *args, **kwargs: Any):
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def make_fenchel_young_loss(
|
|
34
|
+
max_fun: MaxFun
|
|
35
|
+
):
|
|
36
|
+
"""Creates a Fenchel-Young loss from a max function.
|
|
37
|
+
|
|
38
|
+
WARNING: The resulting loss accepts an arbitrary number of leading dimensions
|
|
39
|
+
with the fy_loss operating over the last dimension. The jaxopt version of this
|
|
40
|
+
function would instead flatten any vector in a single big 1D vector.
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
Given a max function, e.g., the log sum exp, you can construct a
|
|
44
|
+
Fenchel-Young loss easily as follows:
|
|
45
|
+
|
|
46
|
+
>>> from jax.scipy.special import logsumexp
|
|
47
|
+
>>> fy_loss = make_fy_loss(max_fun=logsumexp)
|
|
48
|
+
|
|
49
|
+
Reference:
|
|
50
|
+
Blondel et al. `Learning with Fenchel-Young Losses
|
|
51
|
+
<https://arxiv.org/pdf/1901.02324.pdf>`_, 2020
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
max_fun: the max function on which the Fenchel-Young loss is built.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
A Fenchel-Young loss function with the same signature.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
vdot_last_dim = jnp.vectorize(jnp.vdot, signature="(n),(n)->()")
|
|
61
|
+
max_fun_last_dim = jnp.vectorize(max_fun, signature="(n)->()")
|
|
62
|
+
|
|
63
|
+
def fenchel_young_loss(scores, targets, *args, **kwargs):
|
|
64
|
+
max_value = max_fun_last_dim(scores, *args, **kwargs)
|
|
65
|
+
return max_value - vdot_last_dim(targets, scores)
|
|
66
|
+
|
|
67
|
+
return fenchel_young_loss
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Copyright 2024 BrainPy Ecosystem Limited. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ========================================================================
|
|
15
|
+
|
|
16
|
+
import jax
|
|
17
|
+
import jax.numpy as jnp
|
|
18
|
+
from absl.testing import parameterized
|
|
19
|
+
from jax.scipy.special import logsumexp
|
|
20
|
+
|
|
21
|
+
from braintools.metric import _classification
|
|
22
|
+
from braintools.metric import _fenchel_young
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def one_hot_argmax(inputs: jnp.ndarray) -> jnp.ndarray:
|
|
26
|
+
"""An argmax one-hot function for arbitrary shapes."""
|
|
27
|
+
inputs_flat = jnp.reshape(inputs, (-1))
|
|
28
|
+
flat_one_hot = jax.nn.one_hot(jnp.argmax(inputs_flat), inputs_flat.shape[0])
|
|
29
|
+
return jnp.reshape(flat_one_hot, inputs.shape)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class FenchelYoungTest(parameterized.TestCase):
|
|
33
|
+
|
|
34
|
+
def test_fenchel_young_reg(self):
|
|
35
|
+
# Checks the behavior of the Fenchel-Young loss.
|
|
36
|
+
fy_loss = (_fenchel_young.make_fenchel_young_loss(logsumexp))
|
|
37
|
+
rng = jax.random.PRNGKey(0)
|
|
38
|
+
rngs = jax.random.split(rng, 2)
|
|
39
|
+
theta_true = jax.random.uniform(rngs[0], (8, 5))
|
|
40
|
+
y_true = jax.vmap(jax.nn.softmax)(theta_true)
|
|
41
|
+
theta_random = jax.random.uniform(rngs[1], (8, 5))
|
|
42
|
+
y_random = jax.vmap(jax.nn.softmax)(theta_random)
|
|
43
|
+
grad_random = jax.vmap(jax.grad(fy_loss))(theta_random, y_true)
|
|
44
|
+
# Checks that the gradient of the loss takes the correct form.
|
|
45
|
+
self.assertTrue(jnp.allclose(grad_random, y_random - y_true, rtol=1e-4))
|
|
46
|
+
y_one_hot = jax.vmap(one_hot_argmax)(theta_true)
|
|
47
|
+
int_one_hot = jnp.where(y_one_hot == 1.)[1]
|
|
48
|
+
loss_one_hot = jax.vmap(fy_loss)(theta_random, y_one_hot)
|
|
49
|
+
log_loss = jax.vmap(_classification.softmax_cross_entropy_with_integer_labels)(theta_random, int_one_hot)
|
|
50
|
+
# Checks that the FY loss associated to logsumexp is correct.
|
|
51
|
+
self.assertTrue(jnp.allclose(loss_one_hot, log_loss, rtol=1e-4))
|
|
52
|
+
# Checks that vmapping or not is equivalent.
|
|
53
|
+
loss_one_hot_no_vmap = fy_loss(theta_random, y_one_hot)
|
|
54
|
+
self.assertTrue(jnp.allclose(loss_one_hot, loss_one_hot_no_vmap, rtol=1e-4))
|
|
55
|
+
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Copyright 2024- BrainPy Ecosystem Limited. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
|
|
16
|
+
# -*- coding: utf-8 -*-
|
|
17
|
+
|
|
18
|
+
from typing import Union
|
|
19
|
+
import numpy as onp
|
|
20
|
+
import jax.numpy as jnp
|
|
21
|
+
import braincore as bc
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
'raster_plot',
|
|
26
|
+
'firing_rate',
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def raster_plot(sp_matrix: bc.typing.ArrayLike,
|
|
31
|
+
times: bc.typing.ArrayLike):
|
|
32
|
+
"""Get spike raster plot which displays the spiking activity
|
|
33
|
+
of a group of neurons over time.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
sp_matrix : bnp.ndarray
|
|
38
|
+
The matrix which record spiking activities.
|
|
39
|
+
times : bnp.ndarray
|
|
40
|
+
The time steps.
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
-------
|
|
44
|
+
raster_plot : tuple
|
|
45
|
+
Include (neuron index, spike time).
|
|
46
|
+
"""
|
|
47
|
+
times = onp.asarray(times)
|
|
48
|
+
elements = onp.where(sp_matrix > 0.)
|
|
49
|
+
index = elements[1]
|
|
50
|
+
time = times[elements[0]]
|
|
51
|
+
return index, time
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def firing_rate(spikes: bc.typing.ArrayLike,
|
|
55
|
+
width: Union[int, float],
|
|
56
|
+
dt: Union[int, float]=None):
|
|
57
|
+
r"""Calculate the mean firing rate over in a neuron group.
|
|
58
|
+
|
|
59
|
+
This method is adopted from Brian2.
|
|
60
|
+
|
|
61
|
+
The firing rate in trial :math:`k` is the spike count :math:`n_{k}^{sp}`
|
|
62
|
+
in an interval of duration :math:`T` divided by :math:`T`:
|
|
63
|
+
|
|
64
|
+
.. math::
|
|
65
|
+
|
|
66
|
+
v_k = {n_k^{sp} \over T}
|
|
67
|
+
|
|
68
|
+
Parameters
|
|
69
|
+
----------
|
|
70
|
+
spikes : ndarray
|
|
71
|
+
The spike matrix which record spiking activities.
|
|
72
|
+
width : int, float
|
|
73
|
+
The width of the ``window`` in millisecond.
|
|
74
|
+
dt : float, optional
|
|
75
|
+
The sample rate.
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
rate : ndarray
|
|
80
|
+
The population rate in Hz, smoothed with the given window.
|
|
81
|
+
"""
|
|
82
|
+
dt = bc.environ.get_dt() if (dt is None) else dt
|
|
83
|
+
width1 = int(width / 2 / dt) * 2 + 1
|
|
84
|
+
window = jnp.ones(width1) * 1000 / width
|
|
85
|
+
return jnp.convolve(jnp.mean(spikes, axis=1), window, mode='same')
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Copyright 2024- BrainPy Ecosystem Limited. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
|
|
16
|
+
# -*- coding: utf-8 -*-
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
import unittest
|
|
20
|
+
import braincore as bc
|
|
21
|
+
import jax.numpy as jnp
|
|
22
|
+
import braintools as bt
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TestFiringRate(unittest.TestCase):
|
|
26
|
+
def test_fr1(self):
|
|
27
|
+
spikes = jnp.ones((1000, 10))
|
|
28
|
+
print(bt.metric.firing_rate(spikes, 1.))
|
|
29
|
+
|
|
30
|
+
def test_fr2(self):
|
|
31
|
+
bc.random.seed()
|
|
32
|
+
spikes = bc.random.random((1000, 10)) < 0.2
|
|
33
|
+
print(bt.metric.firing_rate(spikes, 1.))
|
|
34
|
+
print(bt.metric.firing_rate(spikes, 10.))
|
|
35
|
+
|
|
36
|
+
def test_fr3(self):
|
|
37
|
+
bc.random.seed()
|
|
38
|
+
spikes = bc.random.random((1000, 10)) < 0.02
|
|
39
|
+
print(bt.metric.firing_rate(spikes, 1.))
|
|
40
|
+
print(bt.metric.firing_rate(spikes, 5.))
|
|
41
|
+
|