Source code for pynol.learner.specification.surrogate_meta
from abc import ABC, abstractmethod
from typing import Optional
import numpy as np
[docs]class SurrogateMeta(ABC):
"""The abstract class defines the surrogate loss that passed to meta-algorithm."""
def __init__(self):
pass
[docs] @abstractmethod
def compute_surrogate_meta(self):
"""Compute the surrogate loss that passed to meta-algorithm."""
pass
[docs]class SurrogateMetaFromBase(SurrogateMeta):
"""The class will set the surrogate loss of meta-algorithm as the
surrogate loss of base-learners."""
def __init__(self):
pass
[docs] def compute_surrogate_meta(self, variables):
"""Set the surrogate loss of meta-algorithm as the surrogate loss of base-learners."""
return variables['surrogate_loss_bases']
[docs]class InnerSurrogateMeta(SurrogateMeta):
"""The class defines the inner surrogate loss for meta-algorithm."""
def __init__(self):
pass
[docs] def compute_surrogate_meta(self, variables):
"""Set the surrogate loss of meta-algorithm as
.. math::
\ell'_t(x)=\langle \\nabla f_t(x_t), x_{t,i} \\rangle,
where :math:`x_t` is the submitted decision and :math:`x_{t, i}` is the
decision of base-learner i at round :math:`t`.
"""
return variables['x_bases'] @ variables['grad']
[docs]class InnerSwitchingSurrogateMeta(SurrogateMeta):
"""The class defines the inner surrogate loss with switching cost for
meta-algorithm.
Args:
penalty (float): Penalty coefficient of switching cost term.
norm (int): Order of norm :math:`p`.
order (int): Order of switching cost :math:`q`.
"""
def __init__(self, penalty: float, norm: int = 2, order: int = 2):
self.penalty = penalty
self.norm = norm
self.order = order
self.x_last = None
[docs] def compute_surrogate_meta(self, variables):
"""Set the surrogate loss of meta-algorithm as
.. math::
\ell'_t(x)=\langle \\nabla f_t(x_t), x_{t,i} \\rangle + \lVert
x_{t,i} - x_{t-1, i} \\rVert_p^q,
where :math:`x_t` is the submitted decision and :math:`x_{t, i}` is the
decision of base-learner i at round :math:`t`.
"""
loss = self.inner_switching(variables['x_bases'], variables['grad'],
self.penalty, self.x_last, self.norm,
self.order)
self.x_last = variables['x_bases']
return loss
@staticmethod
def inner_switching(x: np.ndarray,
gradient: np.ndarray,
penalty: float,
x_last: Optional[np.ndarray],
norm: int = 2,
order: int = 2):
if x_last is None:
x_last = x
return np.dot(x, gradient) + penalty * np.linalg.norm(
x - x_last, ord=norm, axis=1)**order