l0-python 0.5.0__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/calibration.py CHANGED
@@ -310,6 +310,8 @@ class SparseCalibrationWeights(nn.Module):
310
310
  verbose: bool = False,
311
311
  verbose_freq: int = 100,
312
312
  target_groups: np.ndarray | None = None,
313
+ normalize_groups: bool = True,
314
+ group_multipliers: dict[int, float] | None = None,
313
315
  ) -> "SparseCalibrationWeights":
314
316
  """
315
317
  Fit calibration weights using gradient descent.
@@ -338,6 +340,12 @@ class SparseCalibrationWeights(nn.Module):
338
340
  Array of group IDs for each target. Targets in the same group
339
341
  will be averaged together so each group contributes equally to loss.
340
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.
341
349
 
342
350
  Returns
343
351
  -------
@@ -350,27 +358,53 @@ class SparseCalibrationWeights(nn.Module):
350
358
  # Convert M to torch sparse (will be cached)
351
359
  M_torch = self._convert_sparse_to_torch(M)
352
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
+
353
367
  # Compute group weights for loss averaging
354
368
  if target_groups is not None:
355
- # Convert to tensor
356
369
  target_groups = torch.tensor(
357
370
  target_groups, dtype=torch.long, device=self.device
358
371
  )
359
-
360
- # Calculate group weights: 1 / group_size for each target
361
372
  unique_groups = torch.unique(target_groups)
362
- group_weights = torch.zeros_like(y)
363
-
364
- for group_id in unique_groups:
365
- group_mask = target_groups == group_id
366
- group_size = group_mask.sum().item()
367
- # Each target in the group gets weight 1/group_size
368
- # so the group's total contribution is 1
369
- group_weights[group_mask] = 1.0 / group_size
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
370
392
  else:
371
- # No grouping - all targets weighted equally
372
393
  group_weights = torch.ones_like(y)
373
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
+
374
408
  # Add jitter to weights to break symmetry (if jitter_sd > 0)
375
409
  if self.log_weight_jitter_sd > 0:
376
410
  with torch.no_grad():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: l0-python
3
- Version: 0.5.0
3
+ Version: 0.6.0
4
4
  Summary: L0 regularization for sparse neural networks and intelligent sampling
5
5
  Author-email: PolicyEngine <hello@policyengine.org>
6
6
  License: MIT License
@@ -1,12 +1,12 @@
1
1
  l0/__init__.py,sha256=ImRgZfVg-RJhLoqNaLc8ysjddZ7CYGHJPJbRUyfSoHY,1192
2
- l0/calibration.py,sha256=1-C6B6O47l6qlEcQl3N8waAPAyj5PFlzBHl17KbXqGo,20018
2
+ l0/calibration.py,sha256=xOSkuqU72rfFV8OJi-OntYGcPwqabDGrQ12VHBZqNK0,21514
3
3
  l0/distributions.py,sha256=GvVoDasdxoDlPPdzt33lFzkuJgQsfkEn9e-abxru3nM,6325
4
4
  l0/gates.py,sha256=54OORMg9qSsdbIyzYIL9t9CCHWsKbumSKl_d4-YFuNU,11443
5
5
  l0/layers.py,sha256=j_DPEFdfUc37ojtoK2ufGzVYzZ_DwusqTcJXCVxZS68,13785
6
6
  l0/penalties.py,sha256=okqXgX2eGUmg5prcDclicTQALPPPF9FA5eQP09V4GTE,9569
7
7
  l0/sparse.py,sha256=UVpkrrJBnQ9iSNDoWK5F_sr43tQareWXzYHyMq_cKss,9726
8
- l0_python-0.5.0.dist-info/licenses/LICENSE,sha256=LoyFJ8qmpwWPp5RmxFGHhdBxsemvp_BqGyiCgNAZHlk,1069
9
- l0_python-0.5.0.dist-info/METADATA,sha256=zoUDdaSbSAWeWtWW-K_5Wgl6RbTqGHfGIyfYobvxEBQ,7095
10
- l0_python-0.5.0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
11
- l0_python-0.5.0.dist-info/top_level.txt,sha256=brP2SoElgmGhNJk-qRFEQZ5UE-d1e87Tr60hSSh8n34,3
12
- l0_python-0.5.0.dist-info/RECORD,,
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.10.1)
2
+ Generator: setuptools (82.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5