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.
Files changed (38) hide show
  1. braintools/__init__.py +27 -0
  2. braintools/functional/__init__.py +25 -0
  3. braintools/functional/_activations.py +733 -0
  4. braintools/functional/_normalization.py +62 -0
  5. braintools/functional/_spikes.py +90 -0
  6. braintools/init/__init__.py +26 -0
  7. braintools/init/_base.py +36 -0
  8. braintools/init/_generic.py +205 -0
  9. braintools/init/_random_inits.py +489 -0
  10. braintools/init/_regular_inits.py +109 -0
  11. braintools/input/__init__.py +27 -0
  12. braintools/input/currents.py +380 -0
  13. braintools/input/currents_test.py +111 -0
  14. braintools/metric/__init__.py +46 -0
  15. braintools/metric/_classification.py +569 -0
  16. braintools/metric/_classification_test.py +625 -0
  17. braintools/metric/_correlation.py +302 -0
  18. braintools/metric/_correlation_test.py +124 -0
  19. braintools/metric/_fenchel_young.py +67 -0
  20. braintools/metric/_fenchel_young_test.py +55 -0
  21. braintools/metric/_firings.py +85 -0
  22. braintools/metric/_firings_test.py +41 -0
  23. braintools/metric/_lfp.py +139 -0
  24. braintools/metric/_ranking.py +163 -0
  25. braintools/metric/_ranking_test.py +242 -0
  26. braintools/metric/_regression.py +425 -0
  27. braintools/metric/_regression_test.py +133 -0
  28. braintools/metric/_smoothing.py +46 -0
  29. braintools/metric/_smoothing_test.py +43 -0
  30. braintools/metric/_util.py +25 -0
  31. braintools/optim/__init__.py +23 -0
  32. braintools/optim/_lr_scheduler.py +482 -0
  33. braintools/optim/_sgd_optimizer.py +1130 -0
  34. braintools-0.0.2.dist-info/LICENSE +202 -0
  35. braintools-0.0.2.dist-info/METADATA +93 -0
  36. braintools-0.0.2.dist-info/RECORD +38 -0
  37. braintools-0.0.2.dist-info/WHEEL +6 -0
  38. braintools-0.0.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,23 @@
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
+
17
+ from ._lr_scheduler import *
18
+ from ._lr_scheduler import __all__ as scheduler_all
19
+ from ._sgd_optimizer import *
20
+ from ._sgd_optimizer import __all__ as optimizer_all
21
+
22
+ __all__ = scheduler_all + optimizer_all
23
+
@@ -0,0 +1,482 @@
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 Sequence, Union
19
+
20
+ import braincore as bc
21
+ import jax
22
+ import jax.numpy as jnp
23
+
24
+ __all__ = [
25
+ 'LearningRateScheduler',
26
+ 'ConstantLR',
27
+ 'StepLR',
28
+ 'MultiStepLR',
29
+ 'CosineAnnealingLR',
30
+ 'CosineAnnealingWarmRestarts',
31
+ 'ExponentialLR',
32
+ 'ExponentialDecayLR',
33
+ 'InverseTimeDecayLR',
34
+ 'PolynomialDecayLR',
35
+ 'PiecewiseConstantLR',
36
+ ]
37
+
38
+
39
+ # learning rate schedules #
40
+ # ----------------------- #
41
+
42
+
43
+ def make_schedule(scalar_or_schedule):
44
+ if isinstance(scalar_or_schedule, LearningRateScheduler):
45
+ return scalar_or_schedule
46
+ elif isinstance(scalar_or_schedule, (int, float, bc.State)):
47
+ return ConstantLR(scalar_or_schedule)
48
+ else:
49
+ raise TypeError(type(scalar_or_schedule))
50
+
51
+
52
+ class LearningRateScheduler(bc.Module):
53
+ """
54
+ The learning rate scheduler.
55
+
56
+ Attributes
57
+ ----------
58
+ lr: float, State
59
+ The learning rate.
60
+ last_epoch: int
61
+ The index of last epoch.
62
+
63
+ """
64
+
65
+ def __init__(self, lr: Union[float, bc.State], last_epoch: int = -1):
66
+ super(LearningRateScheduler, self).__init__()
67
+ if isinstance(lr, bc.State):
68
+ lr.value = jnp.asarray(lr.value, dtype=bc.environ.dftype())
69
+ else:
70
+ lr = jnp.asarray(lr, dtype=bc.environ.dftype())
71
+ self._lr = lr
72
+ assert last_epoch >= -1, 'last_epoch should be greater than -1.'
73
+ self.last_epoch = bc.State(jnp.asarray(last_epoch, dtype=bc.environ.ditype()))
74
+
75
+ @property
76
+ def lr(self):
77
+ return self._lr.value if isinstance(self._lr, bc.State) else self._lr
78
+
79
+ @lr.setter
80
+ def lr(self, value):
81
+ if isinstance(value, bc.State):
82
+ value = value.value
83
+ assert jnp.ndim(value) == 0, 'The learning rate should be a scalar.'
84
+ if isinstance(self._lr, bc.State):
85
+ self._lr.value = value
86
+ else:
87
+ self._lr = value
88
+
89
+ def step_epoch(self):
90
+ """
91
+ Update the epoch count.
92
+ """
93
+ self.last_epoch.value += 1
94
+
95
+ def step_call(self):
96
+ """
97
+ Update the call count.
98
+ """
99
+ pass
100
+
101
+ def __repr__(self):
102
+ return f'{self.__class__.__name__}(lr={self.lr.value}, last_epoch={self.last_epoch.value}{self.extra_repr()})'
103
+
104
+ def extra_repr(self):
105
+ return ''
106
+
107
+ def __call__(self, i=None):
108
+ raise NotImplementedError
109
+
110
+
111
+ class ConstantLR(LearningRateScheduler):
112
+ """
113
+ Constant learning rate scheduler.
114
+ """
115
+
116
+ def __call__(self, i=None):
117
+ return self.lr
118
+
119
+
120
+ class CallBasedLRScheduler(LearningRateScheduler):
121
+ """
122
+ The learning rate scheduler based on the call count.
123
+
124
+ Attributes
125
+ ----------
126
+ lr: float
127
+ The learning rate.
128
+ last_epoch: int
129
+ The index of last epoch.
130
+ last_call: int
131
+ The index of last call.
132
+
133
+ """
134
+
135
+ def __init__(self, lr: Union[float, bc.State], last_epoch: int = -1, last_call: int = -1):
136
+ super().__init__(lr=lr, last_epoch=last_epoch)
137
+
138
+ assert last_call >= -1, 'last_call should be greater than -1.'
139
+ self.last_call = bc.State(jnp.asarray(last_call, dtype=bc.environ.ditype()))
140
+
141
+ def step_call(self):
142
+ """
143
+ Update the call count.
144
+ """
145
+ self.last_call.value += 1
146
+
147
+ def __repr__(self):
148
+ return (f'{self.__class__.__name__}(lr={self.lr.value}, '
149
+ f'last_epoch={self.last_epoch.value}, '
150
+ f'last_call={self.last_call.value}{self.extra_repr()})')
151
+
152
+
153
+ class StepLR(LearningRateScheduler):
154
+ """Decays the learning rate of each parameter group by gamma every
155
+ `step_size` epochs.
156
+
157
+ Parameters
158
+ ----------
159
+ lr: float
160
+ Initial learning rate.
161
+ step_size: int
162
+ Period of learning rate decay.
163
+ gamma: float
164
+ Multiplicative factor of learning rate decay.
165
+ Default: 0.1.
166
+ last_epoch: int
167
+ The index of last epoch. Default: -1.
168
+ """
169
+
170
+ def __init__(
171
+ self,
172
+ lr: float,
173
+ step_size: int,
174
+ gamma: float = 0.1,
175
+ last_epoch: int = -1
176
+ ):
177
+ super().__init__(lr=lr, last_epoch=last_epoch)
178
+
179
+ assert step_size >= 1, 'step_size should be greater than or equal to 1.'
180
+ assert 1. >= gamma >= 0, 'gamma should be in the range [0, 1].'
181
+ self.step_size = step_size
182
+ self.gamma = gamma
183
+
184
+ def __call__(self, i=None):
185
+ i = (self.last_epoch.value + 1) if i is None else i
186
+ return self.lr * self.gamma ** (jnp.floor_divide(i, self.step_size))
187
+
188
+ def extra_repr(self):
189
+ return f', gamma={self.gamma}, step_size={self.step_size}'
190
+
191
+
192
+ class MultiStepLR(LearningRateScheduler):
193
+ """Decays the learning rate of each parameter group by gamma once the
194
+ number of epoch reaches one of the milestones. Notice that such decay can
195
+ happen simultaneously with other changes to the learning rate from outside
196
+ this scheduler. When last_epoch=-1, sets initial lr as lr.
197
+
198
+ Parameters
199
+ ----------
200
+ lr: float
201
+ Initial learning rate.
202
+ milestones: sequence of int
203
+ List of epoch indices. Must be increasing.
204
+ gamma: float
205
+ Multiplicative factor of learning rate decay.
206
+ Default: 0.1.
207
+ last_epoch: int
208
+ The index of last epoch. Default: -1.
209
+ """
210
+
211
+ def __init__(
212
+ self,
213
+ lr: float,
214
+ milestones: Sequence[int],
215
+ gamma: float = 0.1,
216
+ last_epoch: int = -1
217
+ ):
218
+ super().__init__(lr=lr, last_epoch=last_epoch)
219
+
220
+ assert len(milestones) > 0, 'milestones should be a non-empty sequence.'
221
+ assert all([milestones[i] < milestones[i + 1] for i in range(len(milestones) - 1)]), (
222
+ 'milestones should be a sequence of increasing integers.'
223
+ )
224
+ assert 1. >= gamma >= 0, 'gamma should be in the range [0, 1].'
225
+ self.milestones = jnp.asarray((-1,) + tuple(milestones), dtype=bc.environ.ditype())
226
+ self.gamma = gamma
227
+
228
+ def __call__(self, i=None):
229
+ i = (self.last_epoch.value + 1) if i is None else i
230
+ conditions = (i > self.milestones[:-1]) & i < self.milestones[1:]
231
+ p = jnp.where(conditions, jnp.arange(0, len(self.milestones)))
232
+ return self.lr * self.gamma ** p
233
+
234
+ def extra_repr(self):
235
+ return f', milestones={self.milestones}, gamma={self.gamma}'
236
+
237
+
238
+ class CosineAnnealingLR(LearningRateScheduler):
239
+ r"""Set the learning rate of each parameter group using a cosine annealing
240
+ schedule, where :math:`\eta_{max}` is set to the initial lr and
241
+ :math:`T_{cur}` is the number of epochs since the last restart in SGDR:
242
+
243
+ .. math::
244
+ \begin{aligned}
245
+ \eta_t & = \eta_{min} + \frac{1}{2}(\eta_{max} - \eta_{min})\left(1
246
+ + \cos\left(\frac{T_{cur}}{T_{max}}\pi\right)\right),
247
+ & T_{cur} \neq (2k+1)T_{max}; \\
248
+ \eta_{t+1} & = \eta_{t} + \frac{1}{2}(\eta_{max} - \eta_{min})
249
+ \left(1 - \cos\left(\frac{1}{T_{max}}\pi\right)\right),
250
+ & T_{cur} = (2k+1)T_{max}.
251
+ \end{aligned}
252
+
253
+ When last_epoch=-1, sets initial lr as lr. Notice that because the schedule
254
+ is defined recursively, the learning rate can be simultaneously modified
255
+ outside this scheduler by other operators. If the learning rate is set
256
+ solely by this scheduler, the learning rate at each step becomes:
257
+
258
+ .. math::
259
+ \eta_t = \eta_{min} + \frac{1}{2}(\eta_{max} - \eta_{min})\left(1 +
260
+ \cos\left(\frac{T_{cur}}{T_{max}}\pi\right)\right)
261
+
262
+ It has been proposed in
263
+ `SGDR: Stochastic Gradient Descent with Warm Restarts`_. Note that this only
264
+ implements the cosine annealing part of SGDR, and not the restarts.
265
+
266
+ Parameters
267
+ ----------
268
+ lr: float
269
+ Initial learning rate.
270
+ T_max: int
271
+ Maximum number of iterations.
272
+ eta_min: float
273
+ Minimum learning rate. Default: 0.
274
+ last_epoch: int
275
+ The index of last epoch. Default: -1.
276
+
277
+ .. _SGDR\: Stochastic Gradient Descent with Warm Restarts:
278
+ https://arxiv.org/abs/1608.03983
279
+ """
280
+
281
+ def __init__(
282
+ self,
283
+ lr: float,
284
+ T_max: int,
285
+ eta_min: float = 0.,
286
+ last_epoch: int = -1,
287
+ ):
288
+ super().__init__(lr=lr, last_epoch=last_epoch)
289
+
290
+ assert T_max >= 1, 'T_max should be greater than or equal to 1.'
291
+ self._init_epoch = last_epoch
292
+ self.T_max = T_max
293
+ self.eta_min = eta_min
294
+
295
+ def __call__(self, i=None):
296
+ i = (self.last_epoch.value + 1) if i is None else i
297
+ return self.eta_min + (self.lr - self.eta_min) * (1 + jnp.cos(jnp.pi * i / self.T_max)) / 2
298
+
299
+ def extra_repr(self):
300
+ return f', T_max={self.T_max}, eta_min={self.eta_min}'
301
+
302
+
303
+ class CosineAnnealingWarmRestarts(CallBasedLRScheduler):
304
+ """Set the learning rate of each parameter group using a cosine annealing
305
+ schedule, where :math:`\eta_{max}` is set to the initial lr, :math:`T_{cur}`
306
+ is the number of epochs since the last restart and :math:`T_{i}` is the number
307
+ of epochs between two warm restarts in SGDR:
308
+
309
+ .. math::
310
+ \eta_t = \eta_{min} + \frac{1}{2}(\eta_{max} - \eta_{min})\left(1 +
311
+ \cos\left(\frac{T_{cur}}{T_{i}}\pi\right)\right)
312
+
313
+ When :math:`T_{cur}=T_{i}`, set :math:`\eta_t = \eta_{min}`.
314
+ When :math:`T_{cur}=0` after restart, set :math:`\eta_t=\eta_{max}`.
315
+
316
+ It has been proposed in
317
+ `SGDR: Stochastic Gradient Descent with Warm Restarts`_.
318
+
319
+ Parameters
320
+ ----------
321
+ lr: float
322
+ Initial learning rate.
323
+ num_call_per_epoch: int
324
+ The number the scheduler to call in each epoch.
325
+ This usually means the number of batch in each epoch training.
326
+ T_0: int
327
+ Number of iterations for the first restart.
328
+ T_mult: int
329
+ A factor increases :math:`T_{i}` after a restart. Default: 1.
330
+ eta_min: float
331
+ Minimum learning rate. Default: 0.
332
+ last_call: int
333
+ The index of last call. Default: -1.
334
+
335
+ .. _SGDR\: Stochastic Gradient Descent with Warm Restarts:
336
+ https://arxiv.org/abs/1608.03983
337
+ """
338
+
339
+ def __init__(
340
+ self,
341
+ lr: float,
342
+ num_call_per_epoch: int,
343
+ T_0: int,
344
+ T_mult: int = 1,
345
+ eta_min: float = 0.,
346
+ last_epoch: int = -1,
347
+ last_call: int = -1
348
+ ):
349
+ super().__init__(lr=lr, last_call=last_call, last_epoch=last_epoch)
350
+ if T_0 <= 0 or not isinstance(T_0, int):
351
+ raise ValueError("Expected positive integer T_0, but got {}".format(T_0))
352
+ if T_mult < 1 or not isinstance(T_mult, int):
353
+ raise ValueError("Expected integer T_mult >= 1, but got {}".format(T_mult))
354
+
355
+ self.T_mult = T_mult
356
+ self.eta_min = eta_min
357
+ self.T_0 = T_0
358
+ self.num_call_per_epoch = num_call_per_epoch
359
+
360
+ def _cond1(self, epoch):
361
+ if self.T_mult == 1:
362
+ T_cur = epoch % self.T_0
363
+ T_i = self.T_0
364
+ else:
365
+ n = jnp.floor(jnp.log(epoch / self.T_0 * (self.T_mult - 1) + 1) / jnp.log(self.T_mult))
366
+ T_cur = epoch - self.T_0 * (self.T_mult ** n - 1) / (self.T_mult - 1)
367
+ T_i = self.T_0 * self.T_mult ** n
368
+ return T_cur, T_i
369
+
370
+ def _cond2(self, epoch):
371
+ return epoch, self.T_0
372
+
373
+ def __call__(self, i=None):
374
+ epoch = self.current_epoch(i)
375
+ T_cur, T_i = jax.lax.cond(epoch >= self.T_0, self._cond1, self._cond2, epoch)
376
+ return self.eta_min + (self.lr - self.eta_min) * (1 + jnp.cos(jnp.pi * T_cur / T_i)) / 2
377
+
378
+ def current_epoch(self, i=None):
379
+ i = (self.last_call.value + 1) if i is None else i
380
+ return jnp.floor(i / self.num_call_per_epoch)
381
+
382
+ def extra_repr(self):
383
+ return f', T_0={self.T_0}, T_mult={self.T_mult}, eta_min={self.eta_min}'
384
+
385
+
386
+ class ExponentialLR(LearningRateScheduler):
387
+ """Decays the learning rate of each parameter group by gamma every epoch.
388
+ When last_epoch=-1, sets initial lr as lr.
389
+
390
+ Parameters
391
+ ----------
392
+ lr: float
393
+ Initial learning rate.
394
+ gamma: float
395
+ Multiplicative factor of learning rate decay.
396
+ last_epoch: int
397
+ The index of last epoch. Default: -1.
398
+ """
399
+
400
+ def __init__(self,
401
+ lr: float,
402
+ gamma: float,
403
+ last_epoch: int = -1):
404
+ super(ExponentialLR, self).__init__(lr=lr, last_epoch=last_epoch)
405
+ assert 1. >= gamma >= 0, 'gamma should be in the range [0, 1].'
406
+ self.gamma = gamma
407
+
408
+ def __call__(self, i: int = None):
409
+ i = (self.last_epoch.value + 1) if i is None else i
410
+ return self.lr * self.gamma ** i
411
+
412
+ def extra_repr(self):
413
+ return f', gamma={self.gamma}'
414
+
415
+
416
+ class ExponentialDecayLR(CallBasedLRScheduler):
417
+ def __init__(self, lr, decay_steps, decay_rate, last_epoch: int = -1, last_call: int = -1):
418
+ super().__init__(lr=lr, last_epoch=last_epoch, last_call=last_call)
419
+ self.decay_steps = decay_steps
420
+ self.decay_rate = decay_rate
421
+
422
+ def __call__(self, i=None):
423
+ i = (self.last_call.value + 1) if i is None else i
424
+ return self.lr * self.decay_rate ** (i / self.decay_steps)
425
+
426
+ def extra_repr(self):
427
+ return f', decay_steps={self.decay_steps}, decay_rate={self.decay_rate}'
428
+
429
+
430
+ class InverseTimeDecayLR(ExponentialDecayLR):
431
+ def __init__(self, lr, decay_steps, decay_rate, staircase=False,
432
+ last_epoch: int = -1, last_call: int = -1):
433
+ super().__init__(lr, decay_steps, decay_rate, last_epoch=last_epoch, last_call=last_call)
434
+ self.staircase = staircase
435
+
436
+ def __call__(self, i=None):
437
+ i = (self.last_call.value + 1) if i is None else i
438
+ if self.staircase:
439
+ return self.lr / (1 + self.decay_rate * jnp.floor(i / self.decay_steps))
440
+ else:
441
+ return self.lr / (1 + self.decay_rate * i / self.decay_steps)
442
+
443
+ def extra_repr(self):
444
+ return f', decay_steps={self.decay_steps}, decay_rate={self.decay_rate}, staircase={self.staircase}'
445
+
446
+
447
+ class PolynomialDecayLR(CallBasedLRScheduler):
448
+ def __init__(self, lr, decay_steps, final_lr, power=1.0, last_epoch: int = -1, last_call: int = -1):
449
+ super(PolynomialDecayLR, self).__init__(lr, last_epoch=last_epoch, last_call=last_call)
450
+ self.decay_steps = decay_steps
451
+ self.final_lr = final_lr
452
+ self.power = power
453
+
454
+ def __call__(self, i=None):
455
+ i = (self.last_call.value + 1) if i is None else i
456
+ i = jnp.minimum(i, self.decay_steps)
457
+ step_mult = (1 - i / self.decay_steps) ** self.power
458
+ return step_mult * (self.lr - self.final_lr) + self.final_lr
459
+
460
+ def extra_repr(self):
461
+ return f', decay_steps={self.decay_steps}, final_lr={self.final_lr}, power={self.power}'
462
+
463
+
464
+ class PiecewiseConstantLR(CallBasedLRScheduler):
465
+ def __init__(self, boundaries, values, last_epoch: int = -1, last_call: int = -1):
466
+ super().__init__(0., last_epoch=last_epoch, last_call=last_call)
467
+
468
+ boundaries = jnp.array(boundaries)
469
+ values = jnp.array(values)
470
+ if not boundaries.ndim == values.ndim == 1:
471
+ raise ValueError("boundaries and values must be sequences")
472
+ if not boundaries.shape[0] == values.shape[0] - 1:
473
+ raise ValueError("boundaries length must be one shorter than values length")
474
+ self.boundaries = boundaries
475
+ self.values = values
476
+
477
+ def __call__(self, i=None):
478
+ i = (self.last_call.value + 1) if i is None else i
479
+ return self.values[jnp.sum(i > self.boundaries)]
480
+
481
+ def extra_repr(self):
482
+ return f', boundaries={self.boundaries}, values={self.values}'