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,625 @@
|
|
|
1
|
+
# This file is modified from [optax/losses](https://github.com/google-deepmind/optax).
|
|
2
|
+
# The copyright notice is as follows:
|
|
3
|
+
#
|
|
4
|
+
# Copyright 2019 DeepMind Technologies Limited. All Rights Reserved.
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
# ==============================================================================
|
|
18
|
+
|
|
19
|
+
import jax
|
|
20
|
+
import jax.numpy as jnp
|
|
21
|
+
import numpy as np
|
|
22
|
+
from absl.testing import parameterized
|
|
23
|
+
|
|
24
|
+
from braintools.metric import _classification
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SoftmaxCrossEntropyTest(parameterized.TestCase):
|
|
28
|
+
|
|
29
|
+
def setUp(self):
|
|
30
|
+
super().setUp()
|
|
31
|
+
self.ys = np.array([[10., 1., -2.], [1., 4., 0.2]], dtype=np.float32)
|
|
32
|
+
self.ts = np.array([[0., 1., 0.], [1., 0., 0.]], dtype=np.float32)
|
|
33
|
+
# taken expected outputs from rlax.
|
|
34
|
+
self.exp = np.array([9.00013, 3.0696733], dtype=np.float32)
|
|
35
|
+
|
|
36
|
+
def test_scalar(self):
|
|
37
|
+
"""Tests for a full batch."""
|
|
38
|
+
np.testing.assert_allclose(
|
|
39
|
+
(
|
|
40
|
+
_classification.softmax_cross_entropy)(self.ys[0], self.ts[0]),
|
|
41
|
+
self.exp[0], atol=1e-4)
|
|
42
|
+
|
|
43
|
+
def test_batched(self):
|
|
44
|
+
"""Tests for a full batch."""
|
|
45
|
+
np.testing.assert_allclose(
|
|
46
|
+
(_classification.softmax_cross_entropy)(self.ys, self.ts),
|
|
47
|
+
self.exp, atol=1e-4)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class SoftmaxCrossEntropyWithIntegerLabelsTest(parameterized.TestCase):
|
|
51
|
+
|
|
52
|
+
def setUp(self):
|
|
53
|
+
super().setUp()
|
|
54
|
+
self.ys = np.array([[10., 1., -2.], [1., 4., 0.2]], dtype=np.float32)
|
|
55
|
+
self.ts = np.array([1, 0], dtype=np.int32)
|
|
56
|
+
|
|
57
|
+
def test_consistent_with_softmax_cross_entropy_scalar(self):
|
|
58
|
+
"""Tests for a scalar."""
|
|
59
|
+
exp = _classification.softmax_cross_entropy(
|
|
60
|
+
self.ys[0], jax.nn.one_hot(self.ts[0], 3))
|
|
61
|
+
np.testing.assert_allclose(
|
|
62
|
+
(_classification.softmax_cross_entropy_with_integer_labels)(self.ys[0], self.ts[0]),
|
|
63
|
+
exp, rtol=1e-6
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def test_consistent_with_softmax_cross_entropy_batched(self):
|
|
67
|
+
"""Tests for a full batch."""
|
|
68
|
+
exp = _classification.softmax_cross_entropy(
|
|
69
|
+
self.ys, jax.nn.one_hot(self.ts, 3))
|
|
70
|
+
np.testing.assert_allclose(
|
|
71
|
+
(_classification.softmax_cross_entropy_with_integer_labels)(self.ys, self.ts),
|
|
72
|
+
exp, rtol=1e-6
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class SigmoidCrossEntropyTest(parameterized.TestCase):
|
|
77
|
+
|
|
78
|
+
@parameterized.parameters(
|
|
79
|
+
dict(preds=np.array([-1e+09, -1e-09]),
|
|
80
|
+
labels=np.array([1., 0.]),
|
|
81
|
+
expected=5e+08),
|
|
82
|
+
dict(preds=np.array([-1e+09, -1e-09]),
|
|
83
|
+
labels=np.array([0., 1.]),
|
|
84
|
+
expected=0.3465736),
|
|
85
|
+
dict(preds=np.array([1e+09, 1e-09]),
|
|
86
|
+
labels=np.array([1., 0.]),
|
|
87
|
+
expected=0.3465736),
|
|
88
|
+
dict(preds=np.array([1e+09, 1e-09]),
|
|
89
|
+
labels=np.array([0., 1.]),
|
|
90
|
+
expected=5e+08),
|
|
91
|
+
dict(preds=np.array([-1e+09, 1e-09]),
|
|
92
|
+
labels=np.array([1., 0.]),
|
|
93
|
+
expected=5e+08),
|
|
94
|
+
dict(preds=np.array([-1e+09, 1e-09]),
|
|
95
|
+
labels=np.array([0., 1.]),
|
|
96
|
+
expected=0.3465736),
|
|
97
|
+
dict(preds=np.array([1e+09, -1e-09]),
|
|
98
|
+
labels=np.array([1., 0.]),
|
|
99
|
+
expected=0.3465736),
|
|
100
|
+
dict(preds=np.array([1e+09, -1e-09]),
|
|
101
|
+
labels=np.array([0., 1.]),
|
|
102
|
+
expected=5e+08),
|
|
103
|
+
dict(preds=np.array([0., 0.]),
|
|
104
|
+
labels=np.array([1., 0.]),
|
|
105
|
+
expected=0.6931472),
|
|
106
|
+
dict(preds=np.array([0., 0.]),
|
|
107
|
+
labels=np.array([0., 1.]),
|
|
108
|
+
expected=0.6931472),
|
|
109
|
+
)
|
|
110
|
+
def testSigmoidCrossEntropy(self, preds, labels, expected):
|
|
111
|
+
tested = jnp.mean(
|
|
112
|
+
_classification.sigmoid_binary_cross_entropy(preds, labels))
|
|
113
|
+
np.testing.assert_allclose(tested, expected, rtol=1e-6, atol=1e-6)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class PolyLossTest(parameterized.TestCase):
|
|
117
|
+
|
|
118
|
+
def setUp(self):
|
|
119
|
+
super().setUp()
|
|
120
|
+
self.logits = np.array([0.14, 1.456, 2.356, -0.124, -2.47])
|
|
121
|
+
self.labels = np.array([0.1, 0.15, 0.2, 0.25, 0.3])
|
|
122
|
+
|
|
123
|
+
self.batched_logits = np.array([[4.0, 2.0, 1.0], [0.0, 5.0, 1.0]])
|
|
124
|
+
self.batched_labels = np.array([[1.0, 0.0, 0.0], [0.0, 0.8, 0.2]])
|
|
125
|
+
# all expected values are computed using tf version of `poly1_cross_entropy`
|
|
126
|
+
# see page 10 here https://arxiv.org/pdf/2204.12511.pdf for more
|
|
127
|
+
|
|
128
|
+
@parameterized.parameters(
|
|
129
|
+
dict(eps=2, expected=4.5317),
|
|
130
|
+
dict(eps=1, expected=3.7153),
|
|
131
|
+
dict(eps=-1, expected=2.0827),
|
|
132
|
+
dict(eps=0, expected=2.8990),
|
|
133
|
+
dict(eps=-0.5, expected=2.4908),
|
|
134
|
+
dict(eps=1.15, expected=3.8378),
|
|
135
|
+
dict(eps=1.214, expected=3.8900),
|
|
136
|
+
dict(eps=5.45, expected=7.3480),
|
|
137
|
+
)
|
|
138
|
+
def test_scalar(self, eps, expected):
|
|
139
|
+
np.testing.assert_allclose(
|
|
140
|
+
(_classification.poly_loss_cross_entropy)(
|
|
141
|
+
self.logits, self.labels, epsilon=eps
|
|
142
|
+
),
|
|
143
|
+
expected,
|
|
144
|
+
atol=1e-4,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
@parameterized.parameters(
|
|
148
|
+
dict(eps=2, expected=np.array([0.4823, 1.2567])),
|
|
149
|
+
dict(eps=1, expected=np.array([0.3261, 1.0407])),
|
|
150
|
+
dict(eps=0, expected=np.array([0.1698, 0.8247])),
|
|
151
|
+
dict(eps=-0.5, expected=np.array([0.0917, 0.7168])),
|
|
152
|
+
dict(eps=1.15, expected=np.array([0.3495, 1.0731])),
|
|
153
|
+
dict(eps=1.214, expected=np.array([0.3595, 1.0870])),
|
|
154
|
+
dict(eps=5.45, expected=np.array([1.0211, 2.0018])),
|
|
155
|
+
)
|
|
156
|
+
def test_batched(self, eps, expected):
|
|
157
|
+
np.testing.assert_allclose(
|
|
158
|
+
(_classification.poly_loss_cross_entropy)(
|
|
159
|
+
self.batched_logits, self.batched_labels, epsilon=eps
|
|
160
|
+
),
|
|
161
|
+
expected,
|
|
162
|
+
atol=1e-4,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
@parameterized.parameters(
|
|
166
|
+
dict(
|
|
167
|
+
logits=np.array(
|
|
168
|
+
[[4.0, 2.0, 1.0], [0.0, 5.0, 1.0], [0.134, 1.234, 3.235]]
|
|
169
|
+
),
|
|
170
|
+
labels=np.array(
|
|
171
|
+
[[1.0, 0.0, 0.0], [0.0, 0.8, 0.2], [0.34, 0.33, 0.33]]
|
|
172
|
+
),
|
|
173
|
+
),
|
|
174
|
+
dict(
|
|
175
|
+
logits=np.array([[4.0, 2.0, 1.0], [0.0, 5.0, 1.0]]),
|
|
176
|
+
labels=np.array([[1.0, 0.0, 0.0], [0.0, 0.8, 0.2]]),
|
|
177
|
+
),
|
|
178
|
+
dict(
|
|
179
|
+
logits=np.array(
|
|
180
|
+
[[4.0, 2.0, 1.0, 0.134, 1.3515], [0.0, 5.0, 1.0, 0.5215, 5.616]]
|
|
181
|
+
),
|
|
182
|
+
labels=np.array(
|
|
183
|
+
[[0.5, 0.0, 0.0, 0.0, 0.5], [0.0, 0.12, 0.2, 0.56, 0.12]]
|
|
184
|
+
),
|
|
185
|
+
),
|
|
186
|
+
dict(logits=np.array([1.89, 2.39]), labels=np.array([0.34, 0.66])),
|
|
187
|
+
dict(logits=np.array([0.314]), labels=np.array([1.0])),
|
|
188
|
+
)
|
|
189
|
+
def test_equals_to_cross_entropy_when_eps0(self, logits, labels):
|
|
190
|
+
np.testing.assert_allclose(
|
|
191
|
+
(_classification.poly_loss_cross_entropy)(
|
|
192
|
+
logits, labels, epsilon=0.0),
|
|
193
|
+
(_classification.softmax_cross_entropy)(
|
|
194
|
+
logits, labels),
|
|
195
|
+
atol=1e-4,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class HingeTest(parameterized.TestCase):
|
|
200
|
+
|
|
201
|
+
def test_binary(self):
|
|
202
|
+
label = jnp.array(1)
|
|
203
|
+
signed_label = jnp.array(2.0 * label - 1.0)
|
|
204
|
+
score = jnp.array(10.)
|
|
205
|
+
|
|
206
|
+
def reference_impl(label, logit):
|
|
207
|
+
return jax.nn.relu(1 - logit * (2.0 * label - 1.0))
|
|
208
|
+
|
|
209
|
+
expected = reference_impl(label, score)
|
|
210
|
+
result = _classification.hinge_loss(score, signed_label)
|
|
211
|
+
np.testing.assert_allclose(result, expected, atol=1e-4)
|
|
212
|
+
|
|
213
|
+
def test_batched_binary(self):
|
|
214
|
+
labels = jnp.array([1, 0])
|
|
215
|
+
signed_labels = jnp.array(2.0 * labels - 1.0)
|
|
216
|
+
scores = jnp.array([10., 20.])
|
|
217
|
+
|
|
218
|
+
def reference_impl(label, logit):
|
|
219
|
+
return jax.nn.relu(1 - logit * (2.0 * label - 1.0))
|
|
220
|
+
|
|
221
|
+
expected = jax.vmap(reference_impl)(labels, scores)
|
|
222
|
+
# no need to vmap the optax loss. leading dimensions automatically handled.
|
|
223
|
+
result = _classification.hinge_loss(scores, signed_labels)
|
|
224
|
+
np.testing.assert_allclose(result, expected, atol=1e-4)
|
|
225
|
+
|
|
226
|
+
def test_multi_class(self):
|
|
227
|
+
label = jnp.array(1)
|
|
228
|
+
scores = jnp.array([10., 3.])
|
|
229
|
+
|
|
230
|
+
def reference_impl(label, scores):
|
|
231
|
+
one_hot_label = jax.nn.one_hot(label, scores.shape[-1])
|
|
232
|
+
return jnp.max(scores + 1.0 - one_hot_label) - scores[label]
|
|
233
|
+
|
|
234
|
+
expected = reference_impl(label, scores)
|
|
235
|
+
result = _classification.multiclass_hinge_loss(scores, label)
|
|
236
|
+
np.testing.assert_allclose(result, expected, atol=1e-4)
|
|
237
|
+
|
|
238
|
+
def test_batched_multi_class(self):
|
|
239
|
+
label = jnp.array([1, 0])
|
|
240
|
+
scores = jnp.array([[10., 3.], [11., -2.]])
|
|
241
|
+
|
|
242
|
+
def reference_impl(label, scores):
|
|
243
|
+
one_hot_label = jax.nn.one_hot(label, scores.shape[-1])
|
|
244
|
+
return jnp.max(scores + 1.0 - one_hot_label) - scores[label]
|
|
245
|
+
|
|
246
|
+
expected = jax.vmap(reference_impl)(label, scores)
|
|
247
|
+
# no need to vmap the optax loss. leading dimensions automatically handled.
|
|
248
|
+
result = _classification.multiclass_hinge_loss(scores, label)
|
|
249
|
+
np.testing.assert_allclose(result, expected, atol=1e-4)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class ConvexKLDivergenceTest(parameterized.TestCase):
|
|
253
|
+
|
|
254
|
+
def setUp(self):
|
|
255
|
+
super().setUp()
|
|
256
|
+
self.log_ps = np.array([
|
|
257
|
+
[-2.9957, -3.5066, -3.9120, -1.2040, -0.6931, -2.3026],
|
|
258
|
+
[-1.6094, -1.6094, -1.6094, -2.3026, -1.8971, -1.8971],
|
|
259
|
+
])
|
|
260
|
+
self.qs = np.array(
|
|
261
|
+
[[0.2, 0.2, 0.2, 0.1, 0.15, 0.15], [0.05, 0.03, 0.02, 0.3, 0.5, 0.0]]
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
# Computed convex kullback-leibler divergence of P from Q.
|
|
265
|
+
self.exp = np.array([0.88757247, 0.859308])
|
|
266
|
+
|
|
267
|
+
def test_scalar(self):
|
|
268
|
+
np.testing.assert_allclose(
|
|
269
|
+
(_classification.convex_kl_divergence)(
|
|
270
|
+
self.log_ps[0], self.qs[0]),
|
|
271
|
+
self.exp[0],
|
|
272
|
+
atol=1e-4,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
def test_batched(self):
|
|
276
|
+
np.testing.assert_allclose(
|
|
277
|
+
(_classification.convex_kl_divergence)(
|
|
278
|
+
self.log_ps, self.qs),
|
|
279
|
+
self.exp,
|
|
280
|
+
atol=1e-4,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class PerceptronTest(parameterized.TestCase):
|
|
285
|
+
|
|
286
|
+
def test_binary(self):
|
|
287
|
+
label = jnp.array(1)
|
|
288
|
+
signed_label = jnp.array(2.0 * label - 1.0)
|
|
289
|
+
score = jnp.array(10.)
|
|
290
|
+
|
|
291
|
+
def reference_impl(label, logit) -> float:
|
|
292
|
+
return jax.nn.relu(- logit * (2.0 * label - 1.0))
|
|
293
|
+
|
|
294
|
+
expected = reference_impl(label, score)
|
|
295
|
+
result = _classification.perceptron_loss(score, signed_label)
|
|
296
|
+
np.testing.assert_allclose(result, expected, atol=1e-4)
|
|
297
|
+
|
|
298
|
+
def test_batched_binary(self):
|
|
299
|
+
labels = jnp.array([1, 0])
|
|
300
|
+
signed_labels = jnp.array(2.0 * labels - 1.0)
|
|
301
|
+
scores = jnp.array([10., 20.])
|
|
302
|
+
|
|
303
|
+
def reference_impl(label, logit) -> float:
|
|
304
|
+
return jax.nn.relu(- logit * (2.0 * label - 1.0))
|
|
305
|
+
|
|
306
|
+
expected = jax.vmap(reference_impl)(labels, scores)
|
|
307
|
+
# no need to vmap the optax loss. leading dimensions automatically handled.
|
|
308
|
+
result = _classification.perceptron_loss(scores, signed_labels)
|
|
309
|
+
np.testing.assert_allclose(result, expected, atol=1e-4)
|
|
310
|
+
|
|
311
|
+
def test_multi_class(self):
|
|
312
|
+
label = jnp.array(1)
|
|
313
|
+
scores = jnp.array([10., 3.])
|
|
314
|
+
|
|
315
|
+
def reference_impl(label, scores):
|
|
316
|
+
return jnp.max(scores) - scores[label]
|
|
317
|
+
|
|
318
|
+
expected = reference_impl(label, scores)
|
|
319
|
+
result = _classification.multiclass_perceptron_loss(scores, label)
|
|
320
|
+
np.testing.assert_allclose(result, expected, atol=1e-4)
|
|
321
|
+
|
|
322
|
+
def test_batched_multi_class(self):
|
|
323
|
+
label = jnp.array([1, 0])
|
|
324
|
+
scores = jnp.array([[10., 3.], [11., -2.]])
|
|
325
|
+
|
|
326
|
+
def reference_impl(label, scores):
|
|
327
|
+
return jnp.max(scores) - scores[label]
|
|
328
|
+
|
|
329
|
+
expected = jax.vmap(reference_impl)(label, scores)
|
|
330
|
+
# no need to vmap the optax loss. leading dimensions automatically handled.
|
|
331
|
+
result = _classification.multiclass_perceptron_loss(scores, label)
|
|
332
|
+
np.testing.assert_allclose(result, expected, atol=1e-4)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class KLDivergenceTest(parameterized.TestCase):
|
|
336
|
+
|
|
337
|
+
def setUp(self):
|
|
338
|
+
super().setUp()
|
|
339
|
+
self.log_ps = np.array(
|
|
340
|
+
[[-2.9957, -3.5066, -3.9120, -1.2040, -0.6931, -2.3026],
|
|
341
|
+
[-1.6094, -1.6094, -1.6094, -2.3026, -1.8971, -1.8971]])
|
|
342
|
+
self.qs = np.array([[0.2, 0.2, 0.2, 0.1, 0.15, 0.15],
|
|
343
|
+
[0.05, 0.03, 0.02, 0.3, 0.5, 0.]])
|
|
344
|
+
# Computed kullback-leibler divergence of P from Q.
|
|
345
|
+
self.exp = np.array([0.8875577, 0.7592807])
|
|
346
|
+
|
|
347
|
+
def test_scalar(self):
|
|
348
|
+
np.testing.assert_allclose(
|
|
349
|
+
(_classification.kl_divergence)(self.log_ps[0], self.qs[0]),
|
|
350
|
+
self.exp[0],
|
|
351
|
+
atol=1e-4)
|
|
352
|
+
|
|
353
|
+
def test_batched(self):
|
|
354
|
+
np.testing.assert_allclose(
|
|
355
|
+
(_classification.kl_divergence)(self.log_ps, self.qs),
|
|
356
|
+
self.exp,
|
|
357
|
+
atol=1e-4)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
class KLDivergenceWithLogTargetsTest(parameterized.TestCase):
|
|
361
|
+
|
|
362
|
+
def setUp(self):
|
|
363
|
+
super().setUp()
|
|
364
|
+
self.log_ps = np.array(
|
|
365
|
+
[[-2.9957, -3.5066, -3.9120, -1.2040, -0.6931, -2.3026],
|
|
366
|
+
[-1.6094, -1.6094, -1.6094, -2.3026, -1.8971, -1.8971]])
|
|
367
|
+
self.qs = np.array([[-1.6094, -1.6094, -1.6094, -2.3026, -1.8971, -1.8971],
|
|
368
|
+
[-2.9957, -3.5066, -3.9120, -1.2040, -0.6931, -2.3026]])
|
|
369
|
+
# Computed kullback-leibler divergence of P from Q.
|
|
370
|
+
self.exp = np.array([0.8875625, 0.7187435584901326])
|
|
371
|
+
|
|
372
|
+
def test_scalar(self):
|
|
373
|
+
np.testing.assert_allclose(
|
|
374
|
+
(_classification.kl_divergence_with_log_targets)(
|
|
375
|
+
self.log_ps[0], self.qs[0]),
|
|
376
|
+
self.exp[0],
|
|
377
|
+
atol=1e-4)
|
|
378
|
+
|
|
379
|
+
def test_batched(self):
|
|
380
|
+
np.testing.assert_allclose(
|
|
381
|
+
(_classification.kl_divergence_with_log_targets)(
|
|
382
|
+
self.log_ps, self.qs),
|
|
383
|
+
self.exp,
|
|
384
|
+
atol=1e-4)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _lengths_to_paddings(lengths, maxlength: int):
|
|
388
|
+
indices = jnp.arange(maxlength).reshape((1,) * lengths.ndim + (maxlength,))
|
|
389
|
+
lengths = jnp.expand_dims(lengths, axis=-1)
|
|
390
|
+
elem_valid = indices < lengths
|
|
391
|
+
return np.logical_not(elem_valid).astype(np.float32)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def _average_ctc_loss(
|
|
395
|
+
logprobs,
|
|
396
|
+
logprob_paddings,
|
|
397
|
+
labels,
|
|
398
|
+
label_paddings
|
|
399
|
+
):
|
|
400
|
+
return jnp.average(_classification.ctc_loss(logprobs, logprob_paddings, labels, label_paddings))
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
class CTCTest(parameterized.TestCase):
|
|
404
|
+
|
|
405
|
+
def setUp(self):
|
|
406
|
+
super().setUp()
|
|
407
|
+
np.random.seed(1234)
|
|
408
|
+
self._rtol = 5e-3 if jax.default_backend() != 'cpu' else 1e-6
|
|
409
|
+
|
|
410
|
+
def test_with_one_to_one_alignment(self):
|
|
411
|
+
# when inputsteps and outputsteps are equal, no blank will be allowed.
|
|
412
|
+
batchsize = 8
|
|
413
|
+
steps = 50
|
|
414
|
+
nclasses = 40
|
|
415
|
+
logits = np.random.randn(batchsize, steps, nclasses)
|
|
416
|
+
labels = np.random.uniform(
|
|
417
|
+
1, nclasses, size=(batchsize, steps)).astype(np.int32)
|
|
418
|
+
|
|
419
|
+
# This function only covers the cases without same-label repetition.
|
|
420
|
+
# `test_repeat_with_one_to_one_alignment` below complements those cases.
|
|
421
|
+
# So, redraw the samples for satisfying the non-repetition constraint.
|
|
422
|
+
for n in range(labels.shape[0]):
|
|
423
|
+
for t in range(1, labels.shape[1]):
|
|
424
|
+
while labels[n, t] == labels[n, t - 1]:
|
|
425
|
+
labels[n, t] = np.random.uniform(1, nclasses)
|
|
426
|
+
|
|
427
|
+
results = (_classification.ctc_loss_with_forward_probs)(
|
|
428
|
+
logits, np.zeros(logits.shape[:2]),
|
|
429
|
+
labels, np.zeros(labels.shape))
|
|
430
|
+
(per_seq_loss, logalpha_blank, logalpha_emit) = results
|
|
431
|
+
|
|
432
|
+
logprobs = jax.nn.log_softmax(logits)
|
|
433
|
+
for b in range(batchsize):
|
|
434
|
+
p = 0.0
|
|
435
|
+
for t in range(steps):
|
|
436
|
+
p += logprobs[b, t, labels[b, t]]
|
|
437
|
+
np.testing.assert_allclose(
|
|
438
|
+
np.array(-p), per_seq_loss[b], rtol=self._rtol)
|
|
439
|
+
|
|
440
|
+
# Check forward-probabilities.
|
|
441
|
+
# 1. All-phi path: logalpha_blank[-1, b, 0] must be a probability of
|
|
442
|
+
# the path that outputs blank symbols for all the frames.
|
|
443
|
+
np.testing.assert_allclose(logalpha_blank[-1, b, 0],
|
|
444
|
+
np.sum(logprobs[b, :, 0]),
|
|
445
|
+
rtol=self._rtol)
|
|
446
|
+
|
|
447
|
+
# 2. After emitting all the labels
|
|
448
|
+
# the negated loss must be identical with the forward probability of
|
|
449
|
+
# paths after consuming all the labels (because one-to-one alignment
|
|
450
|
+
# doesn't allow extra blank symbols)
|
|
451
|
+
np.testing.assert_allclose(logalpha_emit[-1, b, steps - 1],
|
|
452
|
+
-per_seq_loss[b],
|
|
453
|
+
rtol=self._rtol)
|
|
454
|
+
# and, this forward probability must be copied to the blank forward
|
|
455
|
+
# probability of the next step.
|
|
456
|
+
np.testing.assert_allclose(logalpha_blank[-1, b, steps],
|
|
457
|
+
-per_seq_loss[b],
|
|
458
|
+
rtol=self._rtol)
|
|
459
|
+
|
|
460
|
+
def test_with_one_to_one_alignment_and_paddings(self):
|
|
461
|
+
batch_size = 5
|
|
462
|
+
nclasses = 13
|
|
463
|
+
steps = 7
|
|
464
|
+
logits = np.random.normal(size=[batch_size, steps, nclasses])
|
|
465
|
+
logprobs = jax.nn.log_softmax(logits)
|
|
466
|
+
|
|
467
|
+
labels = []
|
|
468
|
+
for n in range(batch_size):
|
|
469
|
+
row = list(range(1, nclasses))
|
|
470
|
+
np.random.shuffle(row)
|
|
471
|
+
labels.append(row[:steps])
|
|
472
|
+
labels = np.array(labels)
|
|
473
|
+
|
|
474
|
+
lengths = np.random.randint(3, 6, size=(batch_size,))
|
|
475
|
+
paddings = _lengths_to_paddings(lengths, steps)
|
|
476
|
+
|
|
477
|
+
actual_loss = (_classification.ctc_loss)(
|
|
478
|
+
logits, paddings, labels, paddings)
|
|
479
|
+
|
|
480
|
+
value_and_grad = (jax.value_and_grad(_average_ctc_loss))
|
|
481
|
+
unused_avg_loss, actual_gradients = value_and_grad(
|
|
482
|
+
logits, paddings, labels, paddings)
|
|
483
|
+
|
|
484
|
+
for n in range(batch_size):
|
|
485
|
+
expected_loss = -sum(logprobs[n, t, k]
|
|
486
|
+
for t, k in enumerate(labels[n, :lengths[n]]))
|
|
487
|
+
np.testing.assert_allclose(expected_loss, actual_loss[n], rtol=self._rtol)
|
|
488
|
+
|
|
489
|
+
expected_gradients = np.array(jax.nn.softmax(logits[n]))
|
|
490
|
+
expected_gradients[lengths[n]:] = 0.0
|
|
491
|
+
for t, k in enumerate(labels[n, :lengths[n]]):
|
|
492
|
+
expected_gradients[t, k] -= 1.0
|
|
493
|
+
expected_gradients /= batch_size
|
|
494
|
+
np.testing.assert_allclose(
|
|
495
|
+
expected_gradients, actual_gradients[n], rtol=self._rtol)
|
|
496
|
+
|
|
497
|
+
def test_repeat_with_one_to_one_alignment(self):
|
|
498
|
+
# test if it can correctly handle the same-label repetition.
|
|
499
|
+
nclasses = 5
|
|
500
|
+
labels = np.array([
|
|
501
|
+
[1, 2, 2, 3],
|
|
502
|
+
[2, 3, 4, 4],
|
|
503
|
+
[1, 1, 1, 1],
|
|
504
|
+
[1, 1, 2, 3],
|
|
505
|
+
[1, 1, 1, 2],
|
|
506
|
+
])
|
|
507
|
+
expected_alignment = [ # expected minimal alignment
|
|
508
|
+
[1, 2, 0, 2, 3],
|
|
509
|
+
[2, 3, 4, 0, 4],
|
|
510
|
+
[1, 0, 1, 0, 1, 0, 1],
|
|
511
|
+
[1, 0, 1, 2, 3],
|
|
512
|
+
[1, 0, 1, 0, 1, 2],
|
|
513
|
+
]
|
|
514
|
+
batch_size = len(labels)
|
|
515
|
+
label_lens = np.array([4] * batch_size)
|
|
516
|
+
label_steps = 6
|
|
517
|
+
# Designed to have two padding elements on the right.
|
|
518
|
+
labels = np.pad(labels, [(0, 0), (0, label_steps - labels.shape[1])])
|
|
519
|
+
label_paddings = _lengths_to_paddings(label_lens, label_steps)
|
|
520
|
+
|
|
521
|
+
logit_lengths = np.array([len(seq) for seq in expected_alignment])
|
|
522
|
+
logit_steps = max(logit_lengths)
|
|
523
|
+
logits = np.random.randn(batch_size, logit_steps, nclasses)
|
|
524
|
+
logit_paddings = _lengths_to_paddings(logit_lengths, logit_steps)
|
|
525
|
+
|
|
526
|
+
per_seq_loss = (_classification.ctc_loss)(
|
|
527
|
+
logits, logit_paddings, labels, label_paddings)
|
|
528
|
+
|
|
529
|
+
logprobs = jax.nn.log_softmax(logits)
|
|
530
|
+
for n in range(batch_size):
|
|
531
|
+
expected_loss = -sum(logprobs[n, t, k]
|
|
532
|
+
for t, k in enumerate(expected_alignment[n]))
|
|
533
|
+
np.testing.assert_allclose(
|
|
534
|
+
jnp.array(expected_loss), per_seq_loss[n], rtol=self._rtol)
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
class SigmoidFocalLossTest(parameterized.TestCase):
|
|
538
|
+
|
|
539
|
+
def setUp(self):
|
|
540
|
+
super().setUp()
|
|
541
|
+
self.ys = np.array([[2.0, 0.1, -2.0], [0.3, -0.1, 1.2]], dtype=np.float32)
|
|
542
|
+
self.ts = np.array([[0.0, 0.0, 1.0], [1.0, 0.0, 0.0]])
|
|
543
|
+
self._rtol = 5e-3 if jax.default_backend() != 'cpu' else 1e-6
|
|
544
|
+
|
|
545
|
+
logit = lambda x: jnp.log(x / (1.0 - x))
|
|
546
|
+
self.large_ys = logit(jnp.array([0.9, 0.98, 0.3, 0.99]))
|
|
547
|
+
self.small_ys = logit(jnp.array([0.1, 0.02, 0.09, 0.15]))
|
|
548
|
+
self.ones_ts = jnp.array([1.0, 1.0, 1.0, 1.0])
|
|
549
|
+
|
|
550
|
+
def test_focal_equals_ce(self):
|
|
551
|
+
"""If gamma == 0 and alpha == 0 we expect a CE loss."""
|
|
552
|
+
np.testing.assert_allclose(
|
|
553
|
+
(_classification.sigmoid_focal_loss)(
|
|
554
|
+
self.ys, self.ts, gamma=0.0
|
|
555
|
+
),
|
|
556
|
+
_classification.sigmoid_binary_cross_entropy(self.ys, self.ts),
|
|
557
|
+
rtol=self._rtol,
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
def test_scale(self):
|
|
561
|
+
"""This test should catch problems with p_t."""
|
|
562
|
+
gamma = 2
|
|
563
|
+
focal_loss = (_classification.sigmoid_focal_loss)(
|
|
564
|
+
self.ys, self.ts, gamma=gamma
|
|
565
|
+
)
|
|
566
|
+
p = jax.nn.sigmoid(self.ys)
|
|
567
|
+
ce_loss = _classification.sigmoid_binary_cross_entropy(self.ys, self.ts)
|
|
568
|
+
p_t = p * self.ts + (1 - p) * (1 - self.ts)
|
|
569
|
+
scale = (1 - p_t) ** gamma
|
|
570
|
+
focal_scale = focal_loss / ce_loss
|
|
571
|
+
np.testing.assert_allclose(focal_scale, scale, rtol=self._rtol)
|
|
572
|
+
|
|
573
|
+
def test_large_logit_fl_less_than_ce(self):
|
|
574
|
+
"""If gamma == 2 and alpha == 0.5, the impact of large logits is reduced."""
|
|
575
|
+
focal_loss = (_classification.sigmoid_focal_loss)(
|
|
576
|
+
self.large_ys, self.ones_ts, gamma=2, alpha=0.5
|
|
577
|
+
)
|
|
578
|
+
ce_loss = _classification.sigmoid_binary_cross_entropy(
|
|
579
|
+
self.large_ys, self.ones_ts
|
|
580
|
+
)
|
|
581
|
+
loss_ratio = ce_loss / focal_loss
|
|
582
|
+
expected_ratio = 2.0 / ((1.0 - jax.nn.sigmoid(self.large_ys)) ** 2)
|
|
583
|
+
np.testing.assert_allclose(loss_ratio, expected_ratio, rtol=self._rtol)
|
|
584
|
+
|
|
585
|
+
def test_small_logit_fl_less_than_ce(self):
|
|
586
|
+
"""If gamma == 2, small logits retain their weight."""
|
|
587
|
+
focal_loss = (_classification.sigmoid_focal_loss)(
|
|
588
|
+
self.small_ys, self.ones_ts, gamma=2
|
|
589
|
+
)
|
|
590
|
+
ce_loss = _classification.sigmoid_binary_cross_entropy(
|
|
591
|
+
self.small_ys, self.ones_ts
|
|
592
|
+
)
|
|
593
|
+
loss_ratio = ce_loss / focal_loss
|
|
594
|
+
expected_ratio = 1.0 / ((1.0 - jax.nn.sigmoid(self.small_ys)) ** 2)
|
|
595
|
+
np.testing.assert_allclose(loss_ratio, expected_ratio, rtol=self._rtol)
|
|
596
|
+
|
|
597
|
+
def test_alpha_one(self):
|
|
598
|
+
"""Test if re-weighting with alpha=1 is ok."""
|
|
599
|
+
np.testing.assert_allclose(
|
|
600
|
+
(_classification.sigmoid_focal_loss)(
|
|
601
|
+
self.ys, self.ts, gamma=0.0, alpha=1
|
|
602
|
+
),
|
|
603
|
+
_classification.sigmoid_binary_cross_entropy(self.ys, self.ts)
|
|
604
|
+
* self.ts,
|
|
605
|
+
rtol=self._rtol,
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
def test_ignore_positive(self):
|
|
609
|
+
"""If alpha == 0 positive examples do not matter."""
|
|
610
|
+
focal_loss = (_classification.sigmoid_focal_loss)(
|
|
611
|
+
self.ys, self.ts, alpha=0
|
|
612
|
+
)
|
|
613
|
+
ce_loss = _classification.sigmoid_binary_cross_entropy(self.ys, self.ts)
|
|
614
|
+
assert all(ce_loss[self.ts == 1] > 0)
|
|
615
|
+
assert all(focal_loss[self.ts == 1] == 0)
|
|
616
|
+
|
|
617
|
+
def test_ignore_negative(self):
|
|
618
|
+
"""If alpha == 1 negative examples do not matter."""
|
|
619
|
+
focal_loss = (_classification.sigmoid_focal_loss)(
|
|
620
|
+
self.ys, self.ts, alpha=1
|
|
621
|
+
)
|
|
622
|
+
ce_loss = _classification.sigmoid_binary_cross_entropy(self.ys, self.ts)
|
|
623
|
+
assert all(ce_loss[self.ts == 0] > 0)
|
|
624
|
+
assert all(focal_loss[self.ts == 0] == 0)
|
|
625
|
+
|