l0-python 0.4.3__py3-none-any.whl → 0.6.0__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.
- l0/__init__.py +1 -1
- l0/calibration.py +96 -14
- {l0_python-0.4.3.dist-info → l0_python-0.6.0.dist-info}/METADATA +1 -1
- l0_python-0.6.0.dist-info/RECORD +12 -0
- {l0_python-0.4.3.dist-info → l0_python-0.6.0.dist-info}/WHEEL +1 -1
- l0_python-0.4.3.dist-info/RECORD +0 -12
- {l0_python-0.4.3.dist-info → l0_python-0.6.0.dist-info}/licenses/LICENSE +0 -0
- {l0_python-0.4.3.dist-info → l0_python-0.6.0.dist-info}/top_level.txt +0 -0
l0/__init__.py
CHANGED
|
@@ -5,7 +5,7 @@ A PyTorch implementation of L0 regularization for neural network sparsification
|
|
|
5
5
|
and intelligent sampling, based on Louizos, Welling, & Kingma (2017).
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
__version__ = "0.
|
|
8
|
+
__version__ = "0.5.0"
|
|
9
9
|
|
|
10
10
|
from .distributions import HardConcrete
|
|
11
11
|
from .gates import FeatureGate, HybridGate, L0Gate, SampleGate
|
l0/calibration.py
CHANGED
|
@@ -60,6 +60,7 @@ class SparseCalibrationWeights(nn.Module):
|
|
|
60
60
|
log_weight_jitter_sd: float = 0.0,
|
|
61
61
|
log_alpha_jitter_sd: float = 0.01,
|
|
62
62
|
device: str | torch.device = "cpu",
|
|
63
|
+
use_gates: bool = True,
|
|
63
64
|
):
|
|
64
65
|
super().__init__()
|
|
65
66
|
self.n_features = n_features
|
|
@@ -69,6 +70,7 @@ class SparseCalibrationWeights(nn.Module):
|
|
|
69
70
|
self.log_weight_jitter_sd = log_weight_jitter_sd
|
|
70
71
|
self.log_alpha_jitter_sd = log_alpha_jitter_sd
|
|
71
72
|
self.device = torch.device(device)
|
|
73
|
+
self.use_gates = use_gates
|
|
72
74
|
|
|
73
75
|
# Initialize weights (on original scale)
|
|
74
76
|
if init_weights is None:
|
|
@@ -193,6 +195,10 @@ class SparseCalibrationWeights(nn.Module):
|
|
|
193
195
|
torch.Tensor
|
|
194
196
|
Positive calibration weights with L0 sparsity applied
|
|
195
197
|
"""
|
|
198
|
+
if not self.use_gates:
|
|
199
|
+
# No gates - just return positive weights
|
|
200
|
+
return torch.exp(self.log_weight)
|
|
201
|
+
|
|
196
202
|
# Sample or get deterministic gates
|
|
197
203
|
if deterministic:
|
|
198
204
|
gates = self.get_deterministic_gates()
|
|
@@ -304,6 +310,8 @@ class SparseCalibrationWeights(nn.Module):
|
|
|
304
310
|
verbose: bool = False,
|
|
305
311
|
verbose_freq: int = 100,
|
|
306
312
|
target_groups: np.ndarray | None = None,
|
|
313
|
+
normalize_groups: bool = True,
|
|
314
|
+
group_multipliers: dict[int, float] | None = None,
|
|
307
315
|
) -> "SparseCalibrationWeights":
|
|
308
316
|
"""
|
|
309
317
|
Fit calibration weights using gradient descent.
|
|
@@ -332,6 +340,12 @@ class SparseCalibrationWeights(nn.Module):
|
|
|
332
340
|
Array of group IDs for each target. Targets in the same group
|
|
333
341
|
will be averaged together so each group contributes equally to loss.
|
|
334
342
|
If None, all targets are treated independently.
|
|
343
|
+
normalize_groups : bool
|
|
344
|
+
Whether to normalize within groups so each group contributes
|
|
345
|
+
equally to the loss. Default True (backward-compatible).
|
|
346
|
+
group_multipliers : dict[int, float], optional
|
|
347
|
+
Per-group loss scaling factors. Applied after normalization
|
|
348
|
+
(if enabled). Requires target_groups to be set.
|
|
335
349
|
|
|
336
350
|
Returns
|
|
337
351
|
-------
|
|
@@ -344,27 +358,53 @@ class SparseCalibrationWeights(nn.Module):
|
|
|
344
358
|
# Convert M to torch sparse (will be cached)
|
|
345
359
|
M_torch = self._convert_sparse_to_torch(M)
|
|
346
360
|
|
|
361
|
+
# Validate group_multipliers
|
|
362
|
+
if group_multipliers is not None and target_groups is None:
|
|
363
|
+
raise ValueError(
|
|
364
|
+
"group_multipliers requires target_groups to be set"
|
|
365
|
+
)
|
|
366
|
+
|
|
347
367
|
# Compute group weights for loss averaging
|
|
348
368
|
if target_groups is not None:
|
|
349
|
-
# Convert to tensor
|
|
350
369
|
target_groups = torch.tensor(
|
|
351
370
|
target_groups, dtype=torch.long, device=self.device
|
|
352
371
|
)
|
|
353
|
-
|
|
354
|
-
# Calculate group weights: 1 / group_size for each target
|
|
355
372
|
unique_groups = torch.unique(target_groups)
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
373
|
+
|
|
374
|
+
if normalize_groups:
|
|
375
|
+
group_weights = torch.zeros_like(y)
|
|
376
|
+
for group_id in unique_groups:
|
|
377
|
+
group_mask = target_groups == group_id
|
|
378
|
+
group_size = group_mask.sum().item()
|
|
379
|
+
group_weights[group_mask] = 1.0 / group_size
|
|
380
|
+
else:
|
|
381
|
+
group_weights = torch.ones_like(y)
|
|
382
|
+
|
|
383
|
+
if group_multipliers is not None:
|
|
384
|
+
for gid, mult in group_multipliers.items():
|
|
385
|
+
group_mask = target_groups == gid
|
|
386
|
+
if not group_mask.any():
|
|
387
|
+
raise ValueError(
|
|
388
|
+
f"group_multipliers key {gid} not found "
|
|
389
|
+
f"in target_groups"
|
|
390
|
+
)
|
|
391
|
+
group_weights[group_mask] *= mult
|
|
364
392
|
else:
|
|
365
|
-
# No grouping - all targets weighted equally
|
|
366
393
|
group_weights = torch.ones_like(y)
|
|
367
394
|
|
|
395
|
+
if verbose:
|
|
396
|
+
if target_groups is not None:
|
|
397
|
+
n_groups = len(torch.unique(target_groups))
|
|
398
|
+
parts = [f"{n_groups} groups"]
|
|
399
|
+
parts.append(
|
|
400
|
+
f"normalize={'on' if normalize_groups else 'off'}"
|
|
401
|
+
)
|
|
402
|
+
if group_multipliers is not None:
|
|
403
|
+
parts.append(f"multipliers={group_multipliers}")
|
|
404
|
+
print(f"Groups: {', '.join(parts)}")
|
|
405
|
+
else:
|
|
406
|
+
print("Groups: none (uniform weights)")
|
|
407
|
+
|
|
368
408
|
# Add jitter to weights to break symmetry (if jitter_sd > 0)
|
|
369
409
|
if self.log_weight_jitter_sd > 0:
|
|
370
410
|
with torch.no_grad():
|
|
@@ -453,6 +493,46 @@ class SparseCalibrationWeights(nn.Module):
|
|
|
453
493
|
1 - active_info["count"] / self.n_features
|
|
454
494
|
)
|
|
455
495
|
|
|
496
|
+
# Weight distribution diagnostic
|
|
497
|
+
if active_weights.numel() > 0:
|
|
498
|
+
w_tiny = (active_weights < 0.01).sum().item()
|
|
499
|
+
w_small = (
|
|
500
|
+
((active_weights >= 0.01) & (active_weights < 0.1))
|
|
501
|
+
.sum()
|
|
502
|
+
.item()
|
|
503
|
+
)
|
|
504
|
+
w_med = (
|
|
505
|
+
((active_weights >= 0.1) & (active_weights < 1.0))
|
|
506
|
+
.sum()
|
|
507
|
+
.item()
|
|
508
|
+
)
|
|
509
|
+
w_normal = (
|
|
510
|
+
((active_weights >= 1.0) & (active_weights < 10.0))
|
|
511
|
+
.sum()
|
|
512
|
+
.item()
|
|
513
|
+
)
|
|
514
|
+
w_large = (
|
|
515
|
+
(
|
|
516
|
+
(active_weights >= 10.0)
|
|
517
|
+
& (active_weights < 1000.0)
|
|
518
|
+
)
|
|
519
|
+
.sum()
|
|
520
|
+
.item()
|
|
521
|
+
)
|
|
522
|
+
w_huge = (active_weights >= 1000.0).sum().item()
|
|
523
|
+
|
|
524
|
+
total_active = active_weights.numel()
|
|
525
|
+
weight_dist = (
|
|
526
|
+
f"[<0.01: {100*w_tiny/total_active:.1f}%, "
|
|
527
|
+
f"0.01-0.1: {100*w_small/total_active:.1f}%, "
|
|
528
|
+
f"0.1-1: {100*w_med/total_active:.1f}%, "
|
|
529
|
+
f"1-10: {100*w_normal/total_active:.1f}%, "
|
|
530
|
+
f"10-1000: {100*w_large/total_active:.1f}%, "
|
|
531
|
+
f">1000: {100*w_huge/total_active:.1f}%]"
|
|
532
|
+
)
|
|
533
|
+
else:
|
|
534
|
+
weight_dist = "[no active weights]"
|
|
535
|
+
|
|
456
536
|
# Calculate components of the actual loss being minimized
|
|
457
537
|
actual_data_loss = data_loss.item()
|
|
458
538
|
actual_l0_loss = l0_loss.item()
|
|
@@ -464,7 +544,8 @@ class SparseCalibrationWeights(nn.Module):
|
|
|
464
544
|
f"mean_group_mare={mean_group_mare:.4%}, "
|
|
465
545
|
f"max_error={max_rel_err:.1%}, "
|
|
466
546
|
f"total_loss={actual_total_loss:.3f}, "
|
|
467
|
-
f"active={active_info['count']:4d}/{self.n_features} ({sparsity_pct:.1f}% sparse)"
|
|
547
|
+
f"active={active_info['count']:4d}/{self.n_features} ({sparsity_pct:.1f}% sparse)\n"
|
|
548
|
+
f" Weight dist: {weight_dist}"
|
|
468
549
|
)
|
|
469
550
|
else:
|
|
470
551
|
print(
|
|
@@ -472,7 +553,8 @@ class SparseCalibrationWeights(nn.Module):
|
|
|
472
553
|
f"mean_error={mean_rel_err:.4%}, "
|
|
473
554
|
f"max_error={max_rel_err:.1%}, "
|
|
474
555
|
f"total_loss={actual_total_loss:.3f}, "
|
|
475
|
-
f"active={active_info['count']:4d}/{self.n_features} ({sparsity_pct:.1f}% sparse)"
|
|
556
|
+
f"active={active_info['count']:4d}/{self.n_features} ({sparsity_pct:.1f}% sparse)\n"
|
|
557
|
+
f" Weight dist: {weight_dist}"
|
|
476
558
|
)
|
|
477
559
|
|
|
478
560
|
return self
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
l0/__init__.py,sha256=ImRgZfVg-RJhLoqNaLc8ysjddZ7CYGHJPJbRUyfSoHY,1192
|
|
2
|
+
l0/calibration.py,sha256=xOSkuqU72rfFV8OJi-OntYGcPwqabDGrQ12VHBZqNK0,21514
|
|
3
|
+
l0/distributions.py,sha256=GvVoDasdxoDlPPdzt33lFzkuJgQsfkEn9e-abxru3nM,6325
|
|
4
|
+
l0/gates.py,sha256=54OORMg9qSsdbIyzYIL9t9CCHWsKbumSKl_d4-YFuNU,11443
|
|
5
|
+
l0/layers.py,sha256=j_DPEFdfUc37ojtoK2ufGzVYzZ_DwusqTcJXCVxZS68,13785
|
|
6
|
+
l0/penalties.py,sha256=okqXgX2eGUmg5prcDclicTQALPPPF9FA5eQP09V4GTE,9569
|
|
7
|
+
l0/sparse.py,sha256=UVpkrrJBnQ9iSNDoWK5F_sr43tQareWXzYHyMq_cKss,9726
|
|
8
|
+
l0_python-0.6.0.dist-info/licenses/LICENSE,sha256=LoyFJ8qmpwWPp5RmxFGHhdBxsemvp_BqGyiCgNAZHlk,1069
|
|
9
|
+
l0_python-0.6.0.dist-info/METADATA,sha256=81inHMQ72BeOFNDDkYvZSzZ9C75e23rSprrBjFPxAFw,7095
|
|
10
|
+
l0_python-0.6.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
|
|
11
|
+
l0_python-0.6.0.dist-info/top_level.txt,sha256=brP2SoElgmGhNJk-qRFEQZ5UE-d1e87Tr60hSSh8n34,3
|
|
12
|
+
l0_python-0.6.0.dist-info/RECORD,,
|
l0_python-0.4.3.dist-info/RECORD
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
l0/__init__.py,sha256=ezGVLGbdU_jAElAJSdLKjSWCnZRILAZi8qMBPjcHwQc,1192
|
|
2
|
-
l0/calibration.py,sha256=3VM-28M1yp-DM6AlHX7sqZCaE7OJevpY9Ey-syjSPik,17838
|
|
3
|
-
l0/distributions.py,sha256=GvVoDasdxoDlPPdzt33lFzkuJgQsfkEn9e-abxru3nM,6325
|
|
4
|
-
l0/gates.py,sha256=54OORMg9qSsdbIyzYIL9t9CCHWsKbumSKl_d4-YFuNU,11443
|
|
5
|
-
l0/layers.py,sha256=j_DPEFdfUc37ojtoK2ufGzVYzZ_DwusqTcJXCVxZS68,13785
|
|
6
|
-
l0/penalties.py,sha256=okqXgX2eGUmg5prcDclicTQALPPPF9FA5eQP09V4GTE,9569
|
|
7
|
-
l0/sparse.py,sha256=UVpkrrJBnQ9iSNDoWK5F_sr43tQareWXzYHyMq_cKss,9726
|
|
8
|
-
l0_python-0.4.3.dist-info/licenses/LICENSE,sha256=LoyFJ8qmpwWPp5RmxFGHhdBxsemvp_BqGyiCgNAZHlk,1069
|
|
9
|
-
l0_python-0.4.3.dist-info/METADATA,sha256=gN2ufd7zkVDielkTS2iGbUeKkf9bIMTiGZtvCkpc960,7095
|
|
10
|
-
l0_python-0.4.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
11
|
-
l0_python-0.4.3.dist-info/top_level.txt,sha256=brP2SoElgmGhNJk-qRFEQZ5UE-d1e87Tr60hSSh8n34,3
|
|
12
|
-
l0_python-0.4.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|