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,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)
|