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,425 @@
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
+ """Regression losses."""
17
+
18
+ from typing import Optional, Union
19
+
20
+ import braincore as bc
21
+ import jax.numpy as jnp
22
+
23
+ from ._util import _reduce
24
+
25
+ __all__ = [
26
+ 'squared_error',
27
+ 'absolute_error',
28
+ 'l1_loss',
29
+ 'l2_loss',
30
+ 'l2_norm',
31
+ 'huber_loss',
32
+ 'log_cosh',
33
+ 'cosine_similarity',
34
+ 'cosine_distance',
35
+ ]
36
+
37
+
38
+ def safe_norm(x: bc.typing.ArrayLike,
39
+ min_norm,
40
+ ord: Optional[Union[int, float, str]] = None, # pylint: disable=redefined-builtin
41
+ axis: Union[None, tuple[int, ...], int] = None,
42
+ keepdims: bool = False) -> bc.typing.ArrayLike:
43
+ """Returns jnp.maximum(jnp.linalg.norm(x), min_norm) with correct gradients.
44
+
45
+ The gradients of `jnp.maximum(jnp.linalg.norm(x), min_norm)` at 0.0 is `NaN`,
46
+ because jax will evaluate both branches of the `jnp.maximum`. This function
47
+ will instead return the correct gradient of 0.0 also in such setting.
48
+
49
+ Args:
50
+ x: jax array.
51
+ min_norm: lower bound for the returned norm.
52
+ ord: {non-zero int, inf, -inf, ‘fro’, ‘nuc’}, optional. Order of the norm.
53
+ inf means numpy’s inf object. The default is None.
54
+ axis: {None, int, 2-tuple of ints}, optional. If axis is an integer, it
55
+ specifies the axis of x along which to compute the vector norms. If axis
56
+ is a 2-tuple, it specifies the axes that hold 2-D matrices, and the matrix
57
+ norms of these matrices are computed. If axis is None then either a vector
58
+ norm (when x is 1-D) or a matrix norm (when x is 2-D) is returned. The
59
+ default is None.
60
+ keepdims: bool, optional. If this is set to True, the axes which are normed
61
+ over are left in the result as dimensions with size one. With this option
62
+ the result will broadcast correctly against the original x.
63
+
64
+ Returns:
65
+ The safe norm of the input vector, accounting for correct gradient.
66
+ """
67
+ norm = jnp.linalg.norm(x, ord=ord, axis=axis, keepdims=True)
68
+ x = jnp.where(norm <= min_norm, jnp.ones_like(x), x)
69
+ norm = jnp.squeeze(norm, axis=axis) if not keepdims else norm
70
+ masked_norm = jnp.linalg.norm(x, ord=ord, axis=axis, keepdims=keepdims)
71
+ return jnp.where(norm <= min_norm, min_norm, masked_norm)
72
+
73
+
74
+ def squared_error(
75
+ predictions: bc.typing.ArrayLike,
76
+ targets: Optional[bc.typing.ArrayLike] = None,
77
+ axis: Optional[Union[int, tuple[int, ...]]] = None,
78
+ reduction: str = 'none',
79
+ ) -> bc.typing.ArrayLike:
80
+ """Calculates the squared error for a set of predictions.
81
+
82
+ Mean Squared Error can be computed as ``squared_error(a, b, reduction='mean')``.
83
+
84
+ Note: l2_loss = 0.5 * squared_error, where the 0.5 term is standard in
85
+ "Pattern Recognition and Machine Learning" by Bishop, but not
86
+ "The Elements of Statistical Learning" by Tibshirani.
87
+
88
+ References:
89
+ [Chris Bishop, 2006](https://bit.ly/3eeP0ga)
90
+
91
+ Args:
92
+ predictions: a vector of arbitrary shape `[...]`.
93
+ targets: a vector with shape broadcastable to that of `predictions`;
94
+ if not provided then it is assumed to be a vector of zeros.
95
+ axis: the dimensions to reduce. If `None`, the loss is reduced to a scalar.
96
+ reduction: the reduction operation to apply to the output. One of
97
+ 'none', 'mean', 'sum'. Default is 'none'.
98
+
99
+ Returns:
100
+ elementwise squared differences, with same shape as `predictions`.
101
+ """
102
+ assert bc.math.is_float(predictions), 'predictions must be float.'
103
+ if targets is not None:
104
+ # Avoid broadcasting logic for "-" operator.
105
+ assert predictions.shape == targets.shape, 'predictions and targets must have the same shape.'
106
+ errors = predictions - targets if targets is not None else predictions
107
+ # return errors ** 2
108
+ return _reduce(errors ** 2, reduction, axis=axis)
109
+
110
+
111
+ def absolute_error(
112
+ predictions: bc.typing.ArrayLike,
113
+ targets: Optional[bc.typing.ArrayLike] = None,
114
+ axis: Optional[Union[int, tuple[int, ...]]] = None,
115
+ reduction: str = 'mean',
116
+ ) -> bc.typing.ArrayLike:
117
+ """Calculates the absolute error for a set of predictions.
118
+
119
+ Mean Absolute Error can be computed as absolute_error(a, b).mean().
120
+
121
+ Args:
122
+ predictions: a vector of arbitrary shape `[...]`.
123
+ targets: a vector with shape broadcastable to that of `predictions`;
124
+ if not provided then it is assumed to be a vector of zeros.
125
+ axis: the dimensions to reduce. If `None`, the loss is reduced to a scalar.
126
+ reduction: the reduction operation to apply to the output. One of
127
+ 'none', 'mean', 'sum'. Default is 'mean'.
128
+
129
+ Returns:
130
+ elementwise absolute differences, with same shape as `predictions`.
131
+ """
132
+ assert bc.math.is_float(predictions), 'predictions must be float.'
133
+ if targets is not None:
134
+ # Avoid broadcasting logic for "-" operator.
135
+ assert predictions.shape == targets.shape, 'predictions and targets must have the same shape.'
136
+ errors = predictions - targets if targets is not None else predictions
137
+ return _reduce(jnp.abs(errors), reduction, axis=axis)
138
+
139
+
140
+ class L1Loss:
141
+ r"""Creates a criterion that measures the mean absolute error (MAE) between each element in
142
+ the input :math:`x` and target :math:`y`.
143
+
144
+ The unreduced (i.e. with :attr:`reduction` set to ``'none'``) loss can be described as:
145
+
146
+ .. math::
147
+ \ell(x, y) = L = \{l_1,\dots,l_N\}^\top, \quad
148
+ l_n = \left| x_n - y_n \right|,
149
+
150
+ where :math:`N` is the batch size. If :attr:`reduction` is not ``'none'``
151
+ (default ``'mean'``), then:
152
+
153
+ .. math::
154
+ \ell(x, y) =
155
+ \begin{cases}
156
+ \operatorname{mean}(L), & \text{if reduction} = \text{`mean';}\\
157
+ \operatorname{sum}(L), & \text{if reduction} = \text{`sum'.}
158
+ \end{cases}
159
+
160
+ :math:`x` and :math:`y` are tensors of arbitrary shapes with a total
161
+ of :math:`n` elements each.
162
+
163
+ The sum operation still operates over all the elements, and divides by :math:`n`.
164
+
165
+ The division by :math:`n` can be avoided if one sets ``reduction = 'sum'``.
166
+
167
+ Supports real-valued and complex-valued inputs.
168
+
169
+ Args:
170
+ reduction (str, optional): Specifies the reduction to apply to the output:
171
+ ``'none'`` | ``'mean'`` | ``'sum'``. ``'none'``: no reduction will be applied,
172
+ ``'mean'``: the sum of the output will be divided by the number of
173
+ elements in the output, ``'sum'``: the output will be summed. Note: :attr:`size_average`
174
+ and :attr:`reduce` are in the process of being deprecated, and in the meantime,
175
+ specifying either of those two args will override :attr:`reduction`. Default: ``'mean'``
176
+
177
+ Shape:
178
+ - Input: :math:`(*)`, where :math:`*` means any number of dimensions.
179
+ - Target: :math:`(*)`, same shape as the input.
180
+ - Output: scalar. If :attr:`reduction` is ``'none'``, then
181
+ :math:`(*)`, same shape as the input.
182
+
183
+ Examples::
184
+
185
+ >>> loss = nn.L1Loss()
186
+ >>> input = bm.random.randn(3, 5)
187
+ >>> target = bm.random.randn(3, 5)
188
+ >>> output = loss(input, target)
189
+ >>> output.backward()
190
+ """
191
+
192
+ def __init__(self, reduction: str = 'mean') -> None:
193
+ self.reduction = reduction
194
+
195
+ def update(self,
196
+ input: bc.typing.ArrayLike,
197
+ target: bc.typing.ArrayLike) -> bc.typing.ArrayLike:
198
+ return l1_loss(input, target, reduction=self.reduction)
199
+
200
+
201
+ def l1_loss(logits: bc.typing.ArrayLike,
202
+ targets: bc.typing.ArrayLike,
203
+ reduction: str ='sum'):
204
+ r"""Creates a criterion that measures the mean absolute error (MAE) between each element in
205
+ the logits :math:`x` and targets :math:`y`. It is useful in regression problems.
206
+
207
+ The unreduced (i.e. with :attr:`reduction` set to ``'none'``) loss can be described as:
208
+
209
+ .. math::
210
+ \ell(x, y) = L = \{l_1,\dots,l_N\}^\top, \quad
211
+ l_n = \left| x_n - y_n \right|,
212
+
213
+ where :math:`N` is the batch size. If :attr:`reduction` is not ``'none'``
214
+ (default ``'mean'``), then:
215
+
216
+ .. math::
217
+ \ell(x, y) =
218
+ \begin{cases}
219
+ \operatorname{mean}(L), & \text{if reduction} = \text{`mean';}\\
220
+ \operatorname{sum}(L), & \text{if reduction} = \text{`sum'.}
221
+ \end{cases}
222
+
223
+ :math:`x` and :math:`y` are tensors of arbitrary shapes with a total
224
+ of :math:`n` elements each.
225
+
226
+ The sum operation still operates over all the elements, and divides by :math:`n`.
227
+
228
+ The division by :math:`n` can be avoided if one sets ``reduction = 'sum'``.
229
+
230
+ Supports real-valued and complex-valued inputs.
231
+
232
+ Parameters
233
+ ----------
234
+ logits : bc.typing.ArrayLike
235
+ :math:`(N, *)` where :math:`*` means, any number of additional dimensions.
236
+ targets : bc.typing.ArrayLike
237
+ :math:`(N, *)`, same shape as the input.
238
+ reduction : str
239
+ Specifies the reduction to apply to the output: ``'none'`` | ``'mean'`` | ``'sum'``.
240
+ Default: ``'mean'``.
241
+ - ``'none'``: no reduction will be applied,
242
+ - ``'mean'``: the sum of the output will be divided by the number of elements in the output,
243
+ - ``'sum'``: the output will be summed. Note: :attr:`size_average`
244
+
245
+ Returns
246
+ -------
247
+ output : scalar.
248
+ If :attr:`reduction` is ``'none'``, then :math:`(N, *)`, same shape as the input.
249
+ """
250
+
251
+ diff = (logits - targets).reshape((logits.shape[0], -1))
252
+ norm = jnp.linalg.norm(diff, ord=1, axis=1, keepdims=False)
253
+ return _reduce(outputs=norm, reduction=reduction)
254
+
255
+
256
+ def l2_loss(
257
+ predictions: bc.typing.ArrayLike,
258
+ targets: Optional[bc.typing.ArrayLike] = None,
259
+ ) -> bc.typing.ArrayLike:
260
+ """Calculates the L2 loss for a set of predictions.
261
+
262
+ Note: the 0.5 term is standard in "Pattern Recognition and Machine Learning"
263
+ by Bishop, but not "The Elements of Statistical Learning" by Tibshirani.
264
+
265
+ References:
266
+ [Chris Bishop, 2006](https://bit.ly/3eeP0ga)
267
+
268
+ Args:
269
+ predictions: a vector of arbitrary shape `[...]`.
270
+ targets: a vector with shape broadcastable to that of `predictions`;
271
+ if not provided then it is assumed to be a vector of zeros.
272
+
273
+ Returns:
274
+ elementwise squared differences, with same shape as `predictions`.
275
+ """
276
+ return 0.5 * squared_error(predictions, targets)
277
+
278
+
279
+ def l2_norm(
280
+ predictions: bc.typing.ArrayLike,
281
+ targets: Optional[bc.typing.ArrayLike] = None,
282
+ axis: Optional[Union[int, tuple[int, ...]]] = None,
283
+ ) -> bc.typing.ArrayLike:
284
+ """Computes the L2 norm of the difference between predictions and targets.
285
+
286
+ Args:
287
+ predictions: a vector of arbitrary shape `[...]`.
288
+ targets: a vector with shape broadcastable to that of `predictions`;
289
+ if not provided then it is assumed to be a vector of zeros.
290
+ axis: the dimensions to reduce. If `None`, the loss is reduced to a scalar.
291
+
292
+ Returns:
293
+ elementwise l2 norm of the differences, with same shape as `predictions`.
294
+ """
295
+ assert bc.math.is_float(predictions), 'predictions must be float.'
296
+ if targets is not None:
297
+ # Avoid broadcasting logic for "-" operator.
298
+ assert predictions.shape == targets.shape, 'predictions and targets must have the same shape.'
299
+ errors = predictions - targets if targets is not None else predictions
300
+ return jnp.linalg.norm(errors, axis=axis, ord=2)
301
+
302
+
303
+ def huber_loss(
304
+ predictions: bc.typing.ArrayLike,
305
+ targets: Optional[bc.typing.ArrayLike] = None,
306
+ delta: float = 1.
307
+ ) -> bc.typing.ArrayLike:
308
+ """Huber loss, similar to L2 loss close to zero, L1 loss away from zero.
309
+
310
+ If gradient descent is applied to the `huber loss`, it is equivalent to
311
+ clipping gradients of an `l2_loss` to `[-delta, delta]` in the backward pass.
312
+
313
+ References:
314
+ [Huber, 1964](www.projecteuclid.org/download/pdf_1/euclid.aoms/1177703732)
315
+
316
+ Args:
317
+ predictions: a vector of arbitrary shape `[...]`.
318
+ targets: a vector with shape broadcastable to that of `predictions`;
319
+ if not provided then it is assumed to be a vector of zeros.
320
+ delta: the bounds for the huber loss transformation, defaults at 1.
321
+
322
+ Returns:
323
+ elementwise huber losses, with the same shape of `predictions`.
324
+ """
325
+ assert bc.math.is_float(predictions), 'predictions must be float.'
326
+ errors = (predictions - targets) if (targets is not None) else predictions
327
+ # 0.5 * err^2 if |err| <= d
328
+ # 0.5 * d^2 + d * (|err| - d) if |err| > d
329
+ abs_errors = jnp.abs(errors)
330
+ quadratic = jnp.minimum(abs_errors, delta)
331
+ # Same as max(abs_x - delta, 0) but avoids potentially doubling gradient.
332
+ linear = abs_errors - quadratic
333
+ return 0.5 * quadratic ** 2 + delta * linear
334
+
335
+
336
+ def log_cosh(
337
+ predictions: bc.typing.ArrayLike,
338
+ targets: Optional[bc.typing.ArrayLike] = None,
339
+ ) -> bc.typing.ArrayLike:
340
+ """Calculates the log-cosh loss for a set of predictions.
341
+
342
+ log(cosh(x)) is approximately `(x**2) / 2` for small x and `abs(x) - log(2)`
343
+ for large x. It is a twice differentiable alternative to the Huber loss.
344
+
345
+ References:
346
+ [Chen et al, 2019](https://openreview.net/pdf?id=rkglvsC9Ym)
347
+
348
+ Args:
349
+ predictions: a vector of arbitrary shape `[...]`.
350
+ targets: a vector with shape broadcastable to that of `predictions`;
351
+ if not provided then it is assumed to be a vector of zeros.
352
+
353
+ Returns:
354
+ the log-cosh loss, with same shape as `predictions`.
355
+ """
356
+ assert bc.math.is_float(predictions), 'predictions must be float.'
357
+ errors = (predictions - targets) if (targets is not None) else predictions
358
+ # log(cosh(x)) = log((exp(x) + exp(-x))/2) = log(exp(x) + exp(-x)) - log(2)
359
+ return jnp.logaddexp(errors, -errors) - jnp.log(2.0).astype(errors.dtype)
360
+
361
+
362
+ def cosine_similarity(
363
+ predictions: bc.typing.ArrayLike,
364
+ targets: bc.typing.ArrayLike,
365
+ epsilon: float = 0.,
366
+ ) -> bc.typing.ArrayLike:
367
+ r"""Computes the cosine similarity between targets and predictions.
368
+
369
+ The cosine **similarity** is a measure of similarity between vectors defined
370
+ as the cosine of the angle between them, which is also the inner product of
371
+ those vectors normalized to have unit norm.
372
+
373
+ References:
374
+ [Wikipedia, 2021](https://en.wikipedia.org/wiki/Cosine_similarity)
375
+
376
+ Args:
377
+ predictions: The predicted vectors, with shape `[..., dim]`.
378
+ targets: Ground truth target vectors, with shape `[..., dim]`.
379
+ epsilon: minimum norm for terms in the denominator of the cosine similarity.
380
+
381
+ Returns:
382
+ cosine similarity measures, with shape `[...]`.
383
+ """
384
+ assert bc.math.is_float(predictions), 'predictions must be float.'
385
+ assert bc.math.is_float(targets), 'targets must be float.'
386
+ # vectorize norm fn, to treat all dimensions except the last as batch dims.
387
+ batched_norm_fn = jnp.vectorize(safe_norm, signature='(k)->()', excluded={1})
388
+ # normalise the last dimension of targets and predictions.
389
+ unit_targets = targets / jnp.expand_dims(
390
+ batched_norm_fn(targets, epsilon), axis=-1)
391
+ unit_predictions = predictions / jnp.expand_dims(
392
+ batched_norm_fn(predictions, epsilon), axis=-1)
393
+ # return cosine similarity.
394
+ return jnp.sum(unit_targets * unit_predictions, axis=-1)
395
+
396
+
397
+ def cosine_distance(
398
+ predictions: bc.typing.ArrayLike,
399
+ targets: bc.typing.ArrayLike,
400
+ epsilon: float = 0.,
401
+ ) -> bc.typing.ArrayLike:
402
+ r"""Computes the cosine distance between targets and predictions.
403
+
404
+ The cosine **distance**, implemented here, measures the **dissimilarity**
405
+ of two vectors as the opposite of cosine **similarity**: `1 - cos(\theta)`.
406
+
407
+ References:
408
+ [Wikipedia, 2021](https://en.wikipedia.org/wiki/Cosine_similarity)
409
+
410
+ Args:
411
+ predictions: The predicted vectors, with shape `[..., dim]`.
412
+ targets: Ground truth target vectors, with shape `[..., dim]`.
413
+ epsilon: minimum norm for terms in the denominator of the cosine similarity.
414
+
415
+ Returns:
416
+ cosine distances, with shape `[...]`.
417
+ """
418
+ assert bc.math.is_float(predictions), 'predictions must be float.'
419
+ assert bc.math.is_float(targets), 'targets must be float.'
420
+ # cosine distance = 1 - cosine similarity.
421
+ return 1. - cosine_similarity(predictions, targets, epsilon=epsilon)
422
+
423
+
424
+
425
+
@@ -0,0 +1,133 @@
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
+ import jax.numpy as jnp
18
+ import numpy as np
19
+ from absl.testing import parameterized
20
+
21
+ from braintools.metric import _regression
22
+
23
+
24
+ class SquaredErrorTest(parameterized.TestCase):
25
+
26
+ def setUp(self):
27
+ super().setUp()
28
+ self.ys = jnp.array([-2., -1., 0.5, 1.])
29
+ self.ts = jnp.array([-1.5, 0., -1, 1.])
30
+ # compute expected outputs in numpy.
31
+ self.exp = (self.ts - self.ys) ** 2
32
+
33
+ def test_scalar(self):
34
+ np.testing.assert_allclose(_regression.squared_error(self.ys[0], self.ts[0]), self.exp[0])
35
+
36
+ def test_batched(self):
37
+ np.testing.assert_allclose(_regression.squared_error(self.ys, self.ts), self.exp)
38
+
39
+ def test_shape_mismatch(self):
40
+ with self.assertRaises(AssertionError):
41
+ _ = _regression.squared_error(self.ys, jnp.expand_dims(self.ts, axis=-1))
42
+
43
+
44
+ class L2LossTest(parameterized.TestCase):
45
+
46
+ def setUp(self):
47
+ super().setUp()
48
+ self.ys = jnp.array([-2., -1., 0.5, 1.])
49
+ self.ts = jnp.array([-1.5, 0., -1, 1.])
50
+ # compute expected outputs in numpy.
51
+ self.exp = 0.5 * (self.ts - self.ys) ** 2
52
+
53
+ def test_scalar(self):
54
+ np.testing.assert_allclose((_regression.l2_loss)(self.ys[0], self.ts[0]), self.exp[0])
55
+
56
+ def test_batched(self):
57
+ np.testing.assert_allclose((_regression.l2_loss)(self.ys, self.ts), self.exp)
58
+
59
+ def test_shape_mismatch(self):
60
+ with self.assertRaises(AssertionError):
61
+ _ = (_regression.l2_loss)(self.ys, jnp.expand_dims(self.ts, axis=-1))
62
+
63
+
64
+ class HuberLossTest(parameterized.TestCase):
65
+
66
+ def setUp(self):
67
+ super().setUp()
68
+ self.ys = np.array([-2.0, 0.5, 0., 0.5, 2.0, 4.0, 132.])
69
+ self.ts = np.array([0.0, -0.5, 0., 1., 1.0, 2.0, 0.3])
70
+ # computed expected outputs manually.
71
+ self.exp = np.array([1.5, 0.5, 0., 0.125, 0.5, 1.5, 131.2])
72
+
73
+ def test_scalar(self):
74
+ np.testing.assert_allclose((_regression.huber_loss)(self.ys[0], self.ts[0], delta=1.0), self.exp[0])
75
+
76
+ def test_batched(self):
77
+ np.testing.assert_allclose((_regression.huber_loss)(self.ys, self.ts, delta=1.0), self.exp)
78
+
79
+
80
+ class LogCoshTest(parameterized.TestCase):
81
+
82
+ def setUp(self):
83
+ super().setUp()
84
+ # Test large values for overflow
85
+ self.ys = jnp.array([500, -2., -1., 0.5, 1.])
86
+ self.ts = jnp.array([-200, -1.5, 0., -1, 1.])
87
+ # computed using tensorflow.keras.losses.log_cosh v2.4.1
88
+ self.exp = jnp.array([699.3068, 0.12011445, 0.4337809, 0.85544014, 0.])
89
+ self.exp_ys_only = jnp.array(
90
+ [499.30685, 1.3250027, 0.4337809, 0.12011451, 0.43378082])
91
+
92
+ def test_scalar(self):
93
+ out = (_regression.log_cosh)(self.ys[0], self.ts[0])
94
+ np.testing.assert_allclose(out, self.exp[0], atol=1e-5)
95
+
96
+ def test_batched(self):
97
+ out = (_regression.log_cosh)(self.ys, self.ts)
98
+ np.testing.assert_allclose(out, self.exp, atol=1e-5)
99
+
100
+ def test_scalar_predictions_only(self):
101
+ out = (_regression.log_cosh)(self.ys[0])
102
+ np.testing.assert_allclose(out, self.exp_ys_only[0], atol=1e-5)
103
+
104
+ def test_batched_predictions_only(self):
105
+ out = (_regression.log_cosh)(self.ys)
106
+ np.testing.assert_allclose(out, self.exp_ys_only, atol=1e-5)
107
+
108
+
109
+ class CosineDistanceTest(parameterized.TestCase):
110
+
111
+ def setUp(self):
112
+ super().setUp()
113
+ self.ys = np.array([[10., 1., -2.], [1., 4., 0.2]], dtype=np.float32)
114
+ self.ts = np.array([[0., 1.2, 0.2], [1., -0.3, 0.]], dtype=np.float32)
115
+ # distance computed expected output from `scipy 1.20`.
116
+ self.exp = np.array([0.9358251989, 1.0464068465], dtype=np.float32)
117
+
118
+ def test_scalar_distance(self):
119
+ """Tests for a full batch."""
120
+ np.testing.assert_allclose((_regression.cosine_distance)(self.ys[0], self.ts[0]), self.exp[0], atol=1e-4)
121
+
122
+ def test_scalar_similarity(self):
123
+ """Tests for a full batch."""
124
+ np.testing.assert_allclose((_regression.cosine_similarity)(self.ys[0], self.ts[0]), 1. - self.exp[0], atol=1e-4)
125
+
126
+ def test_batched_distance(self):
127
+ """Tests for a full batch."""
128
+ np.testing.assert_allclose((_regression.cosine_distance)(self.ys, self.ts), self.exp, atol=1e-4)
129
+
130
+ def test_batched_similarity(self):
131
+ """Tests for a full batch."""
132
+ np.testing.assert_allclose((_regression.cosine_similarity)(self.ys, self.ts), 1. - self.exp, atol=1e-4)
133
+
@@ -0,0 +1,46 @@
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
+ """Smoothing functions."""
17
+
18
+ import jax.numpy as jnp
19
+ import braincore as bc
20
+
21
+ __all__ = ['smooth_labels']
22
+
23
+
24
+ def smooth_labels(
25
+ labels: bc.typing.ArrayLike,
26
+ alpha: float,
27
+ ) -> jnp.ndarray:
28
+ """Apply label smoothing.
29
+
30
+ Label smoothing is often used in combination with a cross-entropy loss.
31
+ Smoothed labels favour small logit gaps, and it has been shown that this can
32
+ provide better model calibration by preventing overconfident predictions.
33
+
34
+ References:
35
+ [Müller et al, 2019](https://arxiv.org/pdf/1906.02629.pdf)
36
+
37
+ Args:
38
+ labels: One hot labels to be smoothed.
39
+ alpha: The smoothing factor.
40
+
41
+ Returns:
42
+ a smoothed version of the one hot input labels.
43
+ """
44
+ assert bc.math.is_float(labels), f'labels should be a float.'
45
+ num_categories = labels.shape[-1]
46
+ return (1.0 - alpha) * labels + alpha / num_categories
@@ -0,0 +1,43 @@
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.numpy as jnp
17
+ import numpy as np
18
+ from absl.testing import parameterized
19
+
20
+ from braintools.metric import _smoothing
21
+
22
+
23
+ class SmoothLabelsTest(parameterized.TestCase):
24
+
25
+ def setUp(self):
26
+ super().setUp()
27
+ self.ts = np.array([[0., 1., 0.], [1., 0., 0.]], dtype=np.float32)
28
+ # compute expected outputs in numpy.
29
+ self.exp_alpha_zero = self.ts
30
+ self.exp_alpha_zero_point_one = 0.9 * self.ts + 0.1 / self.ts.shape[-1]
31
+ self.exp_alpha_one = jnp.ones_like(self.ts) / self.ts.shape[-1]
32
+
33
+ def test_scalar(self):
34
+ """Tests for a full batch."""
35
+ np.testing.assert_allclose(_smoothing.smooth_labels(self.ts[0], 0.), self.exp_alpha_zero[0], atol=1e-4)
36
+ np.testing.assert_allclose(_smoothing.smooth_labels(self.ts[0], 0.1), self.exp_alpha_zero_point_one[0], atol=1e-4)
37
+ np.testing.assert_allclose(_smoothing.smooth_labels(self.ts[0], 1.), self.exp_alpha_one[0], atol=1e-4)
38
+
39
+ def test_batched(self):
40
+ """Tests for a full batch."""
41
+ np.testing.assert_allclose(_smoothing.smooth_labels(self.ts, 0.), self.exp_alpha_zero, atol=1e-4)
42
+ np.testing.assert_allclose(_smoothing.smooth_labels(self.ts, 0.1), self.exp_alpha_zero_point_one, atol=1e-4)
43
+ np.testing.assert_allclose(_smoothing.smooth_labels(self.ts, 1.), self.exp_alpha_one, atol=1e-4)
@@ -0,0 +1,25 @@
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
+ def _reduce(outputs, reduction, axis=None):
18
+ if reduction == 'mean':
19
+ return outputs.mean(axis)
20
+ elif reduction == 'sum':
21
+ return outputs.sum(axis)
22
+ elif reduction == 'none':
23
+ return outputs
24
+ else:
25
+ raise ValueError('Only support reduction of "mean", "sum" and "none", but we got "%s".' % reduction)