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