Source code for piglot.optimisers.spsa_adam

"""Hybrid SPSA-Adam optimiser module."""
from typing import Tuple, Callable, Optional
import numpy as np
from scipy.stats import bernoulli
from piglot.objective import Objective
from piglot.optimiser import ScalarOptimiser, boundary_check


[docs] class SPSA_Adam(ScalarOptimiser): """ Hybrid Simultaneous Perturbation Stochastic Approximation-Adam method for optimisation. References: https://ieeexplore.ieee.org/document/705889 https://arxiv.org/abs/1412.6980 Methods ------- _optimise(self, func, n_dim, n_iter, bound, init_shot): Solves the optimization problem """ def __init__(self, objective: Objective, alpha=0.01, beta1=0.9, beta2=0.999, epsilon=1e-8, gamma=0.101, prob=0.5, c=None, seed=1): """Constructs all necessary attributes for the SPSA-Adam optimiser. Parameters ---------- objective : Objective Objective function to optimise. alpha : float, optional Model parameter, refer to documentation, by default 0.01 beta1 : float, optional Model parameter, refer to documentation, by default 0.9 beta2 : float, optional Model parameter, refer to documentation, by default 0.999 epsilon : float, optional Model parameter, refer to documentation, by default 1e-8 gamma : float, optional Model parameter, refer to documentation, by default 0.101 prob : float, optional Model parameter, refer to documentation, by default 0.5 c : float, optional Model parameter, refer to documentation, by default None If None, this parameter is defined according to internal heuristics. """ super().__init__('AdamSPSA', objective) self.alpha = alpha self.beta1 = beta1 self.beta2 = beta2 self.epsilon = epsilon self.gamma = gamma self.prob = prob self.c = 1e-6 if c is None else c self.seed = seed def _scalar_optimise( self, objective: Callable[[np.ndarray, Optional[bool]], float], n_dim: int, n_iter: int, bound: np.ndarray, init_shot: np.ndarray, ) -> Tuple[float, np.ndarray]: """ Abstract method for optimising the objective. Parameters ---------- objective : Callable[[np.ndarray], float] Objective function to optimise. n_dim : int Number of parameters to optimise. n_iter : int Maximum number of iterations. bound : np.ndarray Array where first and second columns correspond to lower and upper bounds, respectively. init_shot : np.ndarray Initial shot for the optimisation problem. Returns ------- float Best observed objective value. np.ndarray Observed optimum of the objective. """ x = init_shot new_value = objective(x) if self._progress_check(0, new_value, x): return x, new_value # First and second moments for Adam m = np.zeros(n_dim) v = np.zeros(n_dim) for i in range(0, n_iter): c_k = self.c / (i + 1) ** self.gamma # [-1,1] Bernoulli distribution delta = 2 * bernoulli.rvs(self.prob, size=n_dim, random_state=self.seed + i) - 1 # Bound check up = boundary_check(x + c_k * delta, bound) low = boundary_check(x - c_k * delta, bound) pos_loss = objective(up) neg_loss = objective(low) gradient = (pos_loss - neg_loss) / (up - low) # Update solution with Adam m = self.beta1 * m + (1 - self.beta1) * gradient v = self.beta2 * v + (1 - self.beta2) * np.square(gradient) mhat = m / (1 - self.beta1**(i+1)) vhat = v / (1 - self.beta2**(i+1)) x = x - self.alpha * mhat / (np.sqrt(vhat) + self.epsilon) # Bound check x = boundary_check(x, bound) new_value = objective(x) # Update progress and check convergence if self._progress_check(i+1, new_value, x): break return x, new_value