torch-l1-snr 0.1.1__tar.gz → 0.1.2__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: torch-l1-snr
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: L1-SNR loss functions for audio source separation in PyTorch
5
5
  Home-page: https://github.com/crlandsc/torch-l1-snr
6
6
  Author: Christopher Landschoot
@@ -237,7 +237,7 @@ While this can potentially reduce the "cleanliness" of separations and slightly
237
237
 
238
238
  The implementation is optimized for efficiency: if `l1_weight` is `0.0` or `1.0`, the unused loss component is not computed, saving computational resources.
239
239
 
240
- **Note on Gradient Balancing:** When blending losses (`0.0 < l1_weight < 1.0`), the implementation automatically scales the L1 component to approximately match the gradient magnitudes of the L1SNR component. This helps maintain stable training without manual tuning.
240
+ **Note on Gradient Balancing:** When blending losses (`0.0 < l1_weight < 1.0`), the implementation automatically scales the L1 component to approximately match gradient magnitudes while preserving distinct gradient behaviors. This helps maintain stable training without manual tuning.
241
241
 
242
242
  ## Limitations
243
243
 
@@ -209,7 +209,7 @@ While this can potentially reduce the "cleanliness" of separations and slightly
209
209
 
210
210
  The implementation is optimized for efficiency: if `l1_weight` is `0.0` or `1.0`, the unused loss component is not computed, saving computational resources.
211
211
 
212
- **Note on Gradient Balancing:** When blending losses (`0.0 < l1_weight < 1.0`), the implementation automatically scales the L1 component to approximately match the gradient magnitudes of the L1SNR component. This helps maintain stable training without manual tuning.
212
+ **Note on Gradient Balancing:** When blending losses (`0.0 < l1_weight < 1.0`), the implementation automatically scales the L1 component to approximately match gradient magnitudes while preserving distinct gradient behaviors. This helps maintain stable training without manual tuning.
213
213
 
214
214
  ## Limitations
215
215
 
@@ -542,4 +542,124 @@ def test_multi_wrapper_short_audio_4d():
542
542
 
543
543
  assert torch.allclose(wrapped_result, direct_result, atol=1e-6)
544
544
  assert wrapped_result.ndim == 0
545
- assert not torch.isnan(wrapped_result) and not torch.isinf(wrapped_result)
545
+ assert not torch.isnan(wrapped_result) and not torch.isinf(wrapped_result)
546
+
547
+
548
+ # --- Gradient Behavior Tests ---
549
+ def test_gradient_distinction_l1snr_vs_l1():
550
+ """
551
+ Verify L1SNR and L1 have distinct gradient behaviors.
552
+ L1SNR: inverse-error scaling (larger updates for small errors)
553
+ L1: uniform gradients regardless of error magnitude
554
+ """
555
+ torch.manual_seed(42)
556
+
557
+ actuals = torch.tensor([[1.0] * 100, [1.0] * 100])
558
+ estimates = actuals.clone()
559
+ estimates[0] += 0.01 # small error
560
+ estimates[1] += 0.5 # large error
561
+
562
+ # Pure L1SNR (l1_weight=0)
563
+ est_snr = estimates.clone().requires_grad_(True)
564
+ loss_snr = L1SNRLoss("test", l1_weight=0.0)(est_snr, actuals)
565
+ loss_snr.backward()
566
+ ratio_snr = est_snr.grad[0].abs().mean() / est_snr.grad[1].abs().mean()
567
+
568
+ # Pure L1 (l1_weight=1)
569
+ est_l1 = estimates.clone().requires_grad_(True)
570
+ loss_l1 = L1SNRLoss("test", l1_weight=1.0)(est_l1, actuals)
571
+ loss_l1.backward()
572
+ ratio_l1 = est_l1.grad[0].abs().mean() / est_l1.grad[1].abs().mean()
573
+
574
+ # L1SNR: larger gradient for small error sample (ratio >> 1)
575
+ assert ratio_snr > 10.0, f"L1SNR gradient ratio should be >> 1, got {ratio_snr}"
576
+ # L1: uniform gradients (ratio ~ 1)
577
+ assert 0.9 < ratio_l1 < 1.1, f"L1 gradient ratio should be ~1, got {ratio_l1}"
578
+
579
+
580
+ def test_l1_weight_interpolation():
581
+ """
582
+ Verify l1_weight actually affects gradient behavior.
583
+ Gradient ratio should decrease as l1_weight increases (from inverse-error toward uniform).
584
+ """
585
+ torch.manual_seed(42)
586
+
587
+ actuals = torch.tensor([[1.0] * 100, [1.0] * 100])
588
+ estimates = actuals.clone()
589
+ estimates[0] += 0.01 # small error
590
+ estimates[1] += 0.5 # large error
591
+
592
+ ratios = []
593
+ for w in [0.0, 0.5, 1.0]:
594
+ est = estimates.clone().requires_grad_(True)
595
+ loss = L1SNRLoss("test", l1_weight=w)(est, actuals)
596
+ loss.backward()
597
+ ratio = (est.grad[0].abs().mean() / est.grad[1].abs().mean()).item()
598
+ ratios.append(ratio)
599
+
600
+ # Gradient ratio should monotonically decrease as l1_weight increases
601
+ assert ratios[0] > ratios[1] > ratios[2], \
602
+ f"Gradient ratios should decrease with l1_weight: {ratios}"
603
+
604
+
605
+ def test_stft_gradient_distinction():
606
+ """
607
+ Same gradient distinction test for STFTL1SNRDBLoss.
608
+ """
609
+ torch.manual_seed(42)
610
+
611
+ # Need longer audio for STFT
612
+ actuals = torch.tensor([[1.0] * 4096, [1.0] * 4096])
613
+ estimates = actuals.clone()
614
+ estimates[0] += 0.01 # small error
615
+ estimates[1] += 0.5 # large error
616
+
617
+ # Pure L1SNR (l1_weight=0)
618
+ est_snr = estimates.clone().requires_grad_(True)
619
+ loss_fn_snr = STFTL1SNRDBLoss("test", l1_weight=0.0, n_ffts=[512], hop_lengths=[128], win_lengths=[512])
620
+ loss_snr = loss_fn_snr(est_snr, actuals)
621
+ loss_snr.backward()
622
+ ratio_snr = est_snr.grad[0].abs().mean() / est_snr.grad[1].abs().mean()
623
+
624
+ # Pure L1 (l1_weight=1)
625
+ est_l1 = estimates.clone().requires_grad_(True)
626
+ loss_fn_l1 = STFTL1SNRDBLoss("test", l1_weight=1.0, n_ffts=[512], hop_lengths=[128], win_lengths=[512])
627
+ loss_l1 = loss_fn_l1(est_l1, actuals)
628
+ loss_l1.backward()
629
+ ratio_l1 = est_l1.grad[0].abs().mean() / est_l1.grad[1].abs().mean()
630
+
631
+ # STFT processing smooths out per-sample differences, so ratios are smaller
632
+ # Key check: L1SNR ratio > L1 ratio (gradient behaviors differ)
633
+ assert ratio_snr > ratio_l1, f"STFT L1SNR ratio ({ratio_snr}) should be > L1 ratio ({ratio_l1})"
634
+ # L1: more uniform gradients (ratio closer to 1)
635
+ assert ratio_l1 < ratio_snr, f"STFT L1 should have more uniform gradients"
636
+
637
+
638
+ def test_stft_l1_weight_interpolation():
639
+ """
640
+ Verify l1_weight interpolation works for STFTL1SNRDBLoss.
641
+ L1 weighting should make gradients more uniform compared to pure SNR.
642
+ """
643
+ torch.manual_seed(42)
644
+
645
+ actuals = torch.tensor([[1.0] * 4096, [1.0] * 4096])
646
+ estimates = actuals.clone()
647
+ estimates[0] += 0.01
648
+ estimates[1] += 0.5
649
+
650
+ ratios = []
651
+ for w in [0.0, 0.5, 1.0]:
652
+ est = estimates.clone().requires_grad_(True)
653
+ loss_fn = STFTL1SNRDBLoss("test", l1_weight=w, n_ffts=[512], hop_lengths=[128], win_lengths=[512])
654
+ loss = loss_fn(est, actuals)
655
+ loss.backward()
656
+ ratio = (est.grad[0].abs().mean() / est.grad[1].abs().mean()).item()
657
+ ratios.append(ratio)
658
+
659
+ # L1 weighting should make gradients more uniform (ratio closer to 1)
660
+ # Pure L1 (l1_weight=1.0) should have more uniform gradients than pure SNR (l1_weight=0.0)
661
+ assert ratios[2] < ratios[0], \
662
+ f"L1 weighting should make gradients more uniform: SNR ratio {ratios[0]} should be > L1 ratio {ratios[2]}"
663
+
664
+ # All ratios should be > 1 (signal with larger error should have larger gradients)
665
+ assert all(r > 1.0 for r in ratios), f"All gradient ratios should be > 1.0: {ratios}"
@@ -14,4 +14,4 @@ __all__ = [
14
14
  "MultiL1SNRDBLoss",
15
15
  ]
16
16
 
17
- __version__ = "0.1.1"
17
+ __version__ = "0.1.2"
@@ -104,9 +104,10 @@ class L1SNRLoss(torch.nn.Module):
104
104
  l1snr_loss = torch.mean(d1)
105
105
 
106
106
  c = 10.0 / math.log(10.0)
107
- inv_mean = torch.mean(1.0 / (l1_error.detach() + self.eps))
108
- # w-independent scaling to match typical gradient magnitudes
109
- scale_time = c * inv_mean
107
+ # Scale by reference signal magnitude (not error) to preserve gradient distinction
108
+ # L1SNR has inverse-error gradients; L1 should have uniform gradients
109
+ inv_ref_mean = torch.mean(1.0 / (l1_true.detach() + self.eps))
110
+ scale_time = c * inv_ref_mean
110
111
  l1_term = torch.mean(l1_error) * scale_time
111
112
 
112
113
  loss = (1.0 - w) * l1snr_loss + w * l1_term
@@ -446,10 +447,11 @@ class STFTL1SNRDBLoss(torch.nn.Module):
446
447
  w = float(self.l1_weight)
447
448
  if 0.0 < w < 1.0:
448
449
  c = 10.0 / math.log(10.0)
449
- inv_mean_comp = torch.mean(0.5 * (1.0 / (err_re.detach() + self.l1snr_eps) +
450
- 1.0 / (err_im.detach() + self.l1snr_eps)))
451
- # w-independent scaling to match typical gradient magnitudes (factor 2.0 for Re/Im symmetry)
452
- scale_spec = 2.0 * c * inv_mean_comp
450
+ # Scale by reference signal magnitude (not error) to preserve gradient distinction
451
+ # L1SNR has inverse-error gradients; L1 should have uniform gradients
452
+ inv_ref_mean_comp = torch.mean(0.5 * (1.0 / (ref_re.detach() + self.l1snr_eps) +
453
+ 1.0 / (ref_im.detach() + self.l1snr_eps)))
454
+ scale_spec = 2.0 * c * inv_ref_mean_comp
453
455
  l1_term = 0.5 * (torch.mean(err_re) + torch.mean(err_im)) * scale_spec
454
456
 
455
457
  loss = (1.0 - w) * d1_sum + w * l1_term
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: torch-l1-snr
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: L1-SNR loss functions for audio source separation in PyTorch
5
5
  Home-page: https://github.com/crlandsc/torch-l1-snr
6
6
  Author: Christopher Landschoot
@@ -237,7 +237,7 @@ While this can potentially reduce the "cleanliness" of separations and slightly
237
237
 
238
238
  The implementation is optimized for efficiency: if `l1_weight` is `0.0` or `1.0`, the unused loss component is not computed, saving computational resources.
239
239
 
240
- **Note on Gradient Balancing:** When blending losses (`0.0 < l1_weight < 1.0`), the implementation automatically scales the L1 component to approximately match the gradient magnitudes of the L1SNR component. This helps maintain stable training without manual tuning.
240
+ **Note on Gradient Balancing:** When blending losses (`0.0 < l1_weight < 1.0`), the implementation automatically scales the L1 component to approximately match gradient magnitudes while preserving distinct gradient behaviors. This helps maintain stable training without manual tuning.
241
241
 
242
242
  ## Limitations
243
243
 
File without changes
File without changes