Source code for rlaopt.atoms.lp_norms
"""Lp-norm regularizer atoms."""
from abc import ABC, abstractmethod
import torch
from rlaopt.atoms.lp_norm_helpers import (
project_onto_l1_ball,
project_onto_l2_ball,
project_onto_linf_ball,
)
from rlaopt.atoms.nonsmooth_regularizer import NonsmoothRegularizer
from rlaopt.expression import Expression
from rlaopt.ext_tensordict import TensorDict
class _Norm(NonsmoothRegularizer, ABC):
"""Base class for scaled Lp-norm regularizers.
Computes ``scaling * ||x||_p``. Subclasses bind the norm via ``_norm``
and the projection onto the dual-norm ball via ``_dual_projection``.
"""
def __init__(self, x: Expression, scaling: float | torch.Tensor = 1.0):
"""Initialize the Lp-norm regularizer with optional scaling."""
super().__init__(x, scaling_params={"scaling": scaling})
def forward(self) -> torch.Tensor:
"""Evaluate the scaled Lp-norm."""
value = self.get_input("x").forward()
return self.get_buffer("scaling") * self._norm(value)
def _prox(
self, relevant_variable_values: TensorDict, prox_scaling: float
) -> TensorDict:
"""Compute the proximal operator via Moreau decomposition."""
lam = self.get_buffer("scaling") * prox_scaling
return relevant_variable_values.apply(
lambda x: x - self._dual_projection(x, lam)
)
@abstractmethod
def _norm(self, value: torch.Tensor) -> torch.Tensor:
"""Compute the norm used by this regularizer."""
@abstractmethod
def _dual_projection(self, x: torch.Tensor, radius: torch.Tensor) -> torch.Tensor:
"""Project ``x`` onto the dual-norm ball of the given radius."""
[docs]
class L1Norm(_Norm):
"""L1-norm regularization atom.
Computes the scaled L1-norm: scaling * ||x||₁ = scaling * Σᵢ |xᵢ|
Args:
x: Expression to apply the L1-norm to.
scaling: Scaling factor for the L1-norm (default: 1.0).
Examples:
>>> x = Variable((100,), name='weights')
>>> l1 = L1Norm(x, scaling=0.01)
>>> penalty = l1.forward()
"""
def _norm(self, value: torch.Tensor) -> torch.Tensor:
return torch.sum(torch.abs(value))
def _dual_projection(self, x: torch.Tensor, radius: torch.Tensor) -> torch.Tensor:
return project_onto_linf_ball(x, radius)
[docs]
class L2Norm(_Norm):
"""L2-norm regularization atom.
Computes the scaled L2-norm: scaling * ||x||₂ = scaling * sqrt(Σᵢ xᵢ²)
Args:
x: Expression to apply the L2-norm to.
scaling: Scaling factor for the L2-norm (default: 1.0).
Examples:
>>> x = Variable((100,), name='weights')
>>> l2 = L2Norm(x, scaling=0.01)
>>> penalty = l2.forward()
"""
def _norm(self, value: torch.Tensor) -> torch.Tensor:
return torch.sqrt(torch.sum(value**2))
def _dual_projection(self, x: torch.Tensor, radius: torch.Tensor) -> torch.Tensor:
return project_onto_l2_ball(x, radius)
[docs]
class LInfNorm(_Norm):
"""L-infinity-norm regularization atom.
Computes the scaled L-infinity norm: scaling * ||x||_∞ = scaling * maxᵢ |xᵢ|
Args:
x: Expression to apply the L-infinity norm to.
scaling: Scaling factor for the L-infinity norm (default: 1.0).
Examples:
>>> x = Variable((100,), name='weights')
>>> linf = LInfNorm(x, scaling=0.01)
>>> penalty = linf.forward()
"""
def _norm(self, value: torch.Tensor) -> torch.Tensor:
return torch.max(torch.abs(value))
def _dual_projection(self, x: torch.Tensor, radius: torch.Tensor) -> torch.Tensor:
return project_onto_l1_ball(x, radius)