Source code for metaheuristic_designer.strategies.EDA.PBIL

"""
Population-Based Incremental Learning (PBIL) strategies.
"""

from __future__ import annotations
from typing import Optional
import numpy as np
from ...operators import create_operator
from ...parent_selection_base import ParentSelection
from ...survivor_selection_base import SurvivorSelection
from ...initializer import Initializer
from ..variable_population import VariablePopulation
from ...schedulable_parameter import SchedulableParameter
from ...utils import check_random_state


[docs] class BernoulliPBIL(VariablePopulation): """ PBIL for binary vectors using a Bernoulli distribution. The probability vector *p* is updated each generation with a learning rate and optional Gaussian noise, then a new population is sampled. Reference: https://doi.org/10.1016/j.swevo.2011.08.003 Parameters ---------- initializer : Initializer Population initializer. parent_sel : ParentSelection, optional Parent selection method. survivor_sel : SurvivorSelection, optional Survivor selection method. name : str, optional Display name (default ``"BernoulliPBIL"``). offspring_size : int or SchedulableParameter, optional Number of offspring per generation. random_state : RNGLike, optional Random number generator. p : array-like, optional Initial probability vector. Defaults to uniform over [0,1]. lr : float, optional Learning rate for updating *p* (default 1e-3). noise : float, optional Standard deviation of Gaussian noise added to *p* (default 0). **kwargs Forwarded to :class:`VariablePopulation`. """ def __init__( self, initializer: Initializer, parent_sel: ParentSelection = None, survivor_sel: SurvivorSelection = None, name: str = "BernoulliPBIL", offspring_size: Optional[int | SchedulableParameter] = None, random_state=None, p=None, lr=1e-3, noise=0, **kwargs, ): self.random_state = check_random_state(random_state) super().__init__( initializer, operator=create_operator("full_resampling", distribution="bernoulli", p=p, random_state=random_state), parent_sel=parent_sel, survivor_sel=survivor_sel, offspring_size=offspring_size, name=name, lr=lr, noise=noise, **kwargs, ) def _batch_fit(self, population): population_matrix = population.genotype_matrix p_hat = population_matrix.mean(axis=0) return p_hat
[docs] def perturb(self, parents, **kwargs): old_p = self.operator.params.p new_p = self._batch_fit(parents) if old_p is not None: new_p = (1 - self.params.lr) * old_p + self.params.lr * new_p new_p += self.random_state.normal(0, self.params.noise, size=np.asarray(old_p).shape) new_p = np.clip(new_p, 0, 1) self.operator.update_kwargs(p=new_p) return super().perturb(parents, **kwargs)
[docs] class BinomialPBIL(VariablePopulation): """ PBIL for discrete vectors using a Binomial distribution. Reference: https://doi.org/10.1016/j.swevo.2011.08.003 Parameters ---------- initializer : Initializer Population initializer. parent_sel : ParentSelection, optional Parent selection method. survivor_sel : SurvivorSelection, optional Survivor selection method. name : str, optional Display name (default ``"BinomialPBIL"``). offspring_size : int or SchedulableParameter, optional Number of offspring per generation. random_state : RNGLike, optional Random number generator. p : float or array-like, optional Initial success probability (default 0.5). n : int or array-like Number of trials. **Must be provided**; there is no default. lr : float, optional Learning rate (default 1e-3). noise : float, optional Gaussian noise standard deviation (default 0). **kwargs Forwarded to :class:`VariablePopulation`. """ def __init__( self, initializer: Initializer, parent_sel: ParentSelection = None, survivor_sel: SurvivorSelection = None, name: str = "BernoulliPBIL", offspring_size: Optional[int | SchedulableParameter] = None, random_state=None, p=0.5, n=None, lr=1e-3, noise=0, **kwargs, ): self.random_state = check_random_state(random_state) if n is None: raise ValueError("You must specify the value for the parameters `n`, usually it will be the number of possible categorical values.") super().__init__( initializer, operator=create_operator("full_resampling", distribution="Binomial", p=np.asarray(p), n=np.asarray(n), random_state=random_state), parent_sel=parent_sel, survivor_sel=survivor_sel, offspring_size=offspring_size, name=name, # Forced kwargs noise=noise, lr=lr, **kwargs, ) def _batch_fit(self, population): n = self.operator.params.n population_matrix = population.genotype_matrix p_hat = population_matrix.sum(axis=0) / (n * population_matrix.shape[0]) return p_hat
[docs] def perturb(self, parents, **kwargs): old_p = self.operator.params.p new_p = self._batch_fit(parents) if old_p is not None: new_p = (1 - self.params.lr) * old_p + self.params.lr * new_p new_p += self.random_state.normal(0, self.params.noise, size=old_p.shape) new_p = np.clip(new_p, 0, 1) self.operator.update_kwargs(p=new_p) return super().perturb(parents, **kwargs)
[docs] class GaussianPBIL(VariablePopulation): """ PBIL for continuous vectors using a Gaussian distribution. The location vector *loc* is updated each generation with a learning rate and optional Gaussian noise, then a new population is sampled. Reference: https://doi.org/10.1016/j.swevo.2011.08.003 Parameters ---------- initializer : Initializer Population initializer. parent_sel : ParentSelection, optional Parent selection method. survivor_sel : SurvivorSelection, optional Survivor selection method. name : str, optional Display name (default ``"GaussianPBIL"``). offspring_size : int or SchedulableParameter, optional Number of offspring per generation. random_state : RNGLike, optional Random number generator. loc : array-like, optional Initial mean vector (default ``None``; the operator uses a fallback). scale : float or array-like, optional Standard deviation (default 1). lr : float, optional Learning rate (default 1e-3). noise : float, optional Gaussian noise standard deviation added to *loc* (default 0). **kwargs Forwarded to :class:`VariablePopulation`. """ def __init__( self, initializer: Initializer, parent_sel: ParentSelection = None, survivor_sel: SurvivorSelection = None, name: str = "GaussianPBIL", offspring_size: Optional[int | SchedulableParameter] = None, random_state=None, loc=None, scale=1, lr=1e-3, noise=0, **kwargs, ): self.random_state = check_random_state(random_state) super().__init__( initializer, operator=create_operator("full_resampling", distribution="gaussian", loc=loc, scale=np.asarray(scale)), parent_sel=parent_sel, survivor_sel=survivor_sel, offspring_size=offspring_size, name=name, # Forced kwargs lr=lr, noise=noise, **kwargs, ) def _batch_fit(self, population): population_matrix = population.genotype_matrix loc_hat = population_matrix.mean(axis=0) return loc_hat
[docs] def perturb(self, parents, **kwargs): old_loc = self.operator.params.loc new_loc = self._batch_fit(parents) if old_loc is not None: new_loc = (1 - self.params.lr) * old_loc + self.params.lr * new_loc new_loc += self.random_state.normal(0, self.params.noise, size=old_loc.shape) self.operator.update_kwargs(loc=new_loc) return super().perturb(parents, **kwargs)