mirror of
https://github.com/wassname/pyrobolearn.git
synced 2026-09-09 11:31:38 +08:00
refactor/update optimizers (ongoing)
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python
|
||||
r"""Define the bounds in an optimization problem.
|
||||
|
||||
A constrained optimization problem is generally given by:
|
||||
|
||||
.. math:: \min_{x \in R^n} f(x)
|
||||
|
||||
subject to
|
||||
|
||||
.. math::
|
||||
|
||||
g_L \leq g(x) \leq g_U
|
||||
x_L \leq x \leq x_U
|
||||
|
||||
where :math:`f` is the objective function, :math:`x` are the variables that are being optimized, :math:`(x_L, x_U)`
|
||||
are the lower and upper bound on these variables, :math:`g` is a constraint function that maps the variables :math:`x`
|
||||
to another space, and :math:`(g_L, g_U)` are the lower and upper bound in that space.
|
||||
|
||||
References:
|
||||
[1] https://nlopt.readthedocs.io/en/latest/
|
||||
"""
|
||||
|
||||
import torch
|
||||
import numpy as np
|
||||
|
||||
|
||||
__author__ = "Brian Delhaisse"
|
||||
__copyright__ = "Copyright 2018, PyRoboLearn"
|
||||
__credits__ = ["Brian Delhaisse"]
|
||||
__license__ = "MIT"
|
||||
__version__ = "1.0.0"
|
||||
__maintainer__ = "Brian Delhaisse"
|
||||
__email__ = "briandelhaisse@gmail.com"
|
||||
__status__ = "Development"
|
||||
|
||||
|
||||
class Bound(object):
|
||||
r"""Lower and upper bounds
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, lower, upper):
|
||||
"""
|
||||
Initialize the bounds.
|
||||
|
||||
Args:
|
||||
lower (np.array, torch.Tensor, float, int): lower bound
|
||||
upper (np.array, torch.Tensor, float, int): upper bound
|
||||
"""
|
||||
self._lower = lower
|
||||
self._upper = upper
|
||||
|
||||
def __call__(self, variables):
|
||||
return self._lower <= variables <= self._upper
|
||||
@@ -7,6 +7,7 @@ References:
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
# CMA-ES
|
||||
try:
|
||||
@@ -14,7 +15,8 @@ try:
|
||||
except ImportError as e:
|
||||
raise ImportError(e.__str__() + "\n HINT: you can install CMA-ES or `pycma` directly via 'pip install cma'.")
|
||||
|
||||
from optimizer import Optimizer
|
||||
from pyrobolearn.optimizers import Optimizer
|
||||
|
||||
|
||||
__author__ = "Brian Delhaisse"
|
||||
__copyright__ = "Copyright 2018, PyRoboLearn"
|
||||
@@ -26,6 +28,126 @@ __email__ = "briandelhaisse@gmail.com"
|
||||
__status__ = "Development"
|
||||
|
||||
|
||||
# TODO: check the `pyrobolearn/algos/cmaes` algo
|
||||
class CMAES(object):
|
||||
pass
|
||||
class CMAES(Optimizer):
|
||||
r"""Covariance Matrix Adaptation Evolution Strategy (CMA-ES)
|
||||
|
||||
Type: population-based (genetic), stochastic and derivative-free, exploration in parameter space, optimization
|
||||
for non-linear and non-convex functions, episode-based.
|
||||
|
||||
'The Covariance Matrix Adaptation Evolution Strategy (CMA-ES) is a stochastic derivative-free numerical
|
||||
optimization algorithm for difficult (non-convex, ill-conditioned, multi-modal, rugged, noisy) optimization
|
||||
problems in continuous search spaces.' [3]
|
||||
|
||||
References:
|
||||
[1] "Completely Derandomized Self-Adaptation in Evolution Strategies", Hansen et al., 2001
|
||||
[2] "The CMA Evolution Strategy: A Tutorial", Hansen, 2016
|
||||
[3] "Python implementation of CMA-ES", Hansen et al., 2019: https://github.com/CMA-ES/pycma
|
||||
[4] pycma API documentation: cma.gforge.inria.fr/apidocs-pycma
|
||||
|
||||
Python Implementations:
|
||||
- pycma: https://github.com/CMA-ES/pycma
|
||||
"""
|
||||
|
||||
def __init__(self, population_size=20, sigma=0.5, *args, **kwargs):
|
||||
"""
|
||||
Initialize the CMA-ES optimizer.
|
||||
"""
|
||||
super(CMAES, self).__init__(*args, **kwargs)
|
||||
self.population_size = population_size
|
||||
self.sigma = sigma
|
||||
self.shape = None
|
||||
self.dtype = None
|
||||
|
||||
##############
|
||||
# Properties #
|
||||
##############
|
||||
|
||||
@property
|
||||
def population_size(self):
|
||||
"""Return the population size."""
|
||||
return self._population_size
|
||||
|
||||
@population_size.setter
|
||||
def population_size(self, size):
|
||||
"""Set the population size."""
|
||||
# check argument
|
||||
if not isinstance(size, int):
|
||||
raise TypeError("Expecting the population size to be an integer.")
|
||||
if size < 1:
|
||||
raise ValueError("Expecting the population size to be an integer bigger than 0.")
|
||||
|
||||
# set population size
|
||||
self._population_size = size
|
||||
|
||||
###########
|
||||
# Methods #
|
||||
###########
|
||||
|
||||
def convert_from(self, parameters):
|
||||
if isinstance(parameters, np.ndarray):
|
||||
self.shape = parameters.shape
|
||||
self.dtype = np.ndarray
|
||||
return parameters.reshape(-1)
|
||||
if isinstance(parameters, torch.Tensor):
|
||||
self.shape = tuple(parameters.shape)
|
||||
self.dtype = torch.Tensor
|
||||
if parameters.requires_grad:
|
||||
return parameters.detach().numpy()
|
||||
return parameters.numpy()
|
||||
|
||||
def convert_to(self, parameters):
|
||||
if isinstance(parameters, np.ndarray):
|
||||
if self.dtype == np.ndarray:
|
||||
return parameters.reshape(self.shape)
|
||||
if self.dtype == torch.Tensor:
|
||||
return torch.from_numpy(parameters.reshape(self.shape))
|
||||
|
||||
def optimize(self, parameters, loss, bounds=None, max_iters=1, seed=None, options={}, verbose=False,
|
||||
*args, **kwargs):
|
||||
"""
|
||||
Optimize the given loss function with respect to the given parameters.
|
||||
|
||||
Args:
|
||||
parameters (np.array): parameters to optimize.
|
||||
loss (callable): callable objective / loss function to minimize.
|
||||
bounds (tuple, list, np.array): parameter bounds. E.g. bounds=[0, np.inf]
|
||||
max_iters (int): number of maximum iterations.
|
||||
verbose (bool): if True, it will display information during the optimization process.
|
||||
*args: list of arguments to give to the loss function if callable.
|
||||
**kwargs: dictionary of arguments to give to the loss function if callable.
|
||||
|
||||
Returns:
|
||||
float, torch.Tensor, np.array: loss scalar value.
|
||||
object: best parameters
|
||||
"""
|
||||
# check loss function
|
||||
if not callable(loss):
|
||||
raise TypeError("Expecting the given loss function to be callable.")
|
||||
|
||||
# check optimizer options
|
||||
opts = {'popsize': self.population_size}
|
||||
if bounds is not None: # set the parameter bounds
|
||||
opts['bounds'] = bounds
|
||||
if seed is not None: # set the seed
|
||||
opts['seed'] = seed
|
||||
if max_iters is not None: # set the maximum number of iterations
|
||||
opts['maxiter'] = max_iters
|
||||
|
||||
# update the rest of options
|
||||
opts.update(options)
|
||||
|
||||
# create CMA-ES
|
||||
self.optimizer = cma.CMAEvolutionStrategy(parameters, sigma0=self.sigma, inopts=opts)
|
||||
|
||||
# optimize
|
||||
parameters = self.optimizer.ask()
|
||||
values = [loss(params, *args, **kwargs) for params in parameters]
|
||||
self.optimizer.tell(parameters, values)
|
||||
|
||||
# save the best result and parameters
|
||||
self.best_parameters = self.optimizer.result[0]
|
||||
self.best_result = self.optimizer.result[1]
|
||||
|
||||
return self.best_result, self.best_parameters
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python
|
||||
r"""Define the constraints in an optimization problem.
|
||||
|
||||
A constrained optimization problem is generally given by:
|
||||
|
||||
.. math:: \min_{x \in R^n} f(x)
|
||||
|
||||
subject to
|
||||
|
||||
.. math::
|
||||
|
||||
g_L \leq g(x) \leq g_U
|
||||
x_L \leq x \leq x_U
|
||||
|
||||
where :math:`f` is the objective function, :math:`x` are the variables that are being optimized, :math:`(x_L, x_U)`
|
||||
are the lower and upper bound on these variables, :math:`g` is a constraint function that maps the variables :math:`x`
|
||||
to another space, and :math:`(g_L, g_U)` are the lower and upper bound in that space.
|
||||
|
||||
References:
|
||||
[1] https://nlopt.readthedocs.io/en/latest/
|
||||
"""
|
||||
|
||||
import torch
|
||||
import numpy as np
|
||||
|
||||
|
||||
__author__ = "Brian Delhaisse"
|
||||
__copyright__ = "Copyright 2018, PyRoboLearn"
|
||||
__credits__ = ["Brian Delhaisse"]
|
||||
__license__ = "MIT"
|
||||
__version__ = "1.0.0"
|
||||
__maintainer__ = "Brian Delhaisse"
|
||||
__email__ = "briandelhaisse@gmail.com"
|
||||
__status__ = "Development"
|
||||
|
||||
|
||||
class Constraint(object):
|
||||
r"""(Inequality) Constraint
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, constraint, lower, upper):
|
||||
"""
|
||||
Initialize the bounds.
|
||||
|
||||
Args:
|
||||
constraint (callable): constraint function.
|
||||
lower (np.array, torch.Tensor, float, int): lower bound
|
||||
upper (np.array, torch.Tensor, float, int): upper bound
|
||||
"""
|
||||
if not callable(constraint):
|
||||
raise TypeError("Expecting the given constraint function {} to be callable.".format(constraint))
|
||||
self._constraint = constraint
|
||||
self._lower = lower
|
||||
self._upper = upper
|
||||
|
||||
def __call__(self, variables):
|
||||
return self._lower <= self._constraint(variables) <= self._upper
|
||||
@@ -5,6 +5,7 @@ References:
|
||||
[1] https://sheffieldml.github.io/GPyOpt/
|
||||
"""
|
||||
|
||||
import time
|
||||
import numpy as np
|
||||
|
||||
# Bayesian optimization
|
||||
@@ -15,7 +16,8 @@ except ImportError as e:
|
||||
raise ImportError(e.__str__() + "\n HINT: you can install GPy/GPyOpt directly via 'pip install GPy' and "
|
||||
"'pip install GPyOpt'.")
|
||||
|
||||
from optimizer import Optimizer
|
||||
from pyrobolearn.optimizers import Optimizer
|
||||
|
||||
|
||||
__author__ = "Brian Delhaisse"
|
||||
__copyright__ = "Copyright 2018, PyRoboLearn"
|
||||
@@ -27,6 +29,113 @@ __email__ = "briandelhaisse@gmail.com"
|
||||
__status__ = "Development"
|
||||
|
||||
|
||||
# TODO: check the `pyrobolearn/algos/bo` algo
|
||||
class BayesianOptimizer(object):
|
||||
pass
|
||||
class BayesianOptimizer(Optimizer):
|
||||
r"""Bayesian Optimization
|
||||
|
||||
Bayesian Optimization is a global (gradient-free), probabilistic, non-parametric, model-based, optimization of
|
||||
black-box functions.
|
||||
|
||||
Bayesian optimization can be formulated as an optimization problem:
|
||||
|
||||
.. math:: \theta^* = arg\,max_{\theta} f(\theta)
|
||||
|
||||
where :math:`\theta` are the parameters of the model we are trying to optimize, and :math:`f` is the unknown
|
||||
objective function which is modeled using a probabilistic model such as a Gaussian Process (GP). By samp
|
||||
|
||||
Popular acquisition functions which specify which parameters to test next by making a trade-off between
|
||||
exploitation and exploration, include:
|
||||
* Probability of Improvement (PI) [7]:
|
||||
* Expected Improvement (EI) [8]:
|
||||
* Upper Confidence Bound (UCB) [9]:
|
||||
|
||||
Pseudo-Algo (from [3]):
|
||||
D <-- if available: {\theta, f(\theta)}
|
||||
Prior <-- if available: prior of the response surface
|
||||
while optimize:
|
||||
train a response surface from D
|
||||
|
||||
References:
|
||||
[1] "Bayesian Approach to Global Optimization: Theory and Applications", Mockus, 1989
|
||||
[2] "A Tutorial on Bayesian Optimization of Expensive Cost Functions, with Application to Active User Modeling
|
||||
and Hierarchical Reinforcement Learning", Brochu et al., 2010
|
||||
[3] "Taking the Human Out of the Loop: a Review of Bayesian Optimization", Shahriari et al., 2016
|
||||
[4] "Bayesian Optimization for Learning Gaits under Uncertainty: An Experimental Comparison on a Dynamic
|
||||
Bipedal Walker", Calandra et al., 2015
|
||||
[5] "Gaussian Processes for Machine Learning", Rasmussen and Williams, 2006
|
||||
[6] "GPyOpt: A Bayesian Optimization framework in python" (2016), https://github.com/SheffieldML/GPyOpt
|
||||
[7] "A New Method of Locating the Maximum Point of an Arbitrary Multipeak Curve in the Presence of Noise",
|
||||
Kushner, 1964
|
||||
[8] "The Application of Bayesian Methods for Seeking the Extremum", Mockus et al., 1978
|
||||
[9] "A Statistical Method for Global Optimization", Cox et al., 1997
|
||||
"""
|
||||
|
||||
def __init__(self, num_workers=1, *args, **kwargs):
|
||||
"""
|
||||
Initialize the Bayesian optimizer.
|
||||
"""
|
||||
super(BayesianOptimizer, self).__init__(*args, **kwargs)
|
||||
self.num_workers = num_workers
|
||||
|
||||
###########
|
||||
# Methods #
|
||||
###########
|
||||
|
||||
def optimize(self, parameters, loss, max_iters=1, verbose=False, *args, **kwargs):
|
||||
"""
|
||||
Optimize the given loss function with respect to the given parameters.
|
||||
|
||||
Args:
|
||||
parameters: parameters.
|
||||
loss: callable objective / loss function to minimize.
|
||||
max_iters (int): number of maximum iterations.
|
||||
verbose (bool): if True, it will display information during the optimization process.
|
||||
*args: list of arguments to give to the loss function if callable.
|
||||
**kwargs: dictionary of arguments to give to the loss function if callable.
|
||||
|
||||
Returns:
|
||||
float, torch.Tensor, np.array: loss scalar value.
|
||||
object: best parameters
|
||||
"""
|
||||
# define domain
|
||||
# domain = [{'name': 'params', 'type': 'continuous', 'domain': self.domain, 'dimensionality': len(parameters)}]
|
||||
|
||||
# Solve the optimization
|
||||
self.optimizer = GPyOpt.methods.BayesianOptimization(f=loss,
|
||||
# domain=domain,
|
||||
# constraints=constraints,
|
||||
model_type='GP', # 'sparseGP'
|
||||
acquisition_type='EI', # 'UCB'/'LCB', 'EI', 'MPI'
|
||||
acquisition_optimizer_type='lbfgs', # 'DIRECT', 'CMA'
|
||||
num_cores=self.num_workers,
|
||||
verbosity=verbose,
|
||||
maximize=self.is_maximizing,
|
||||
verbosity_model=False, # True
|
||||
kernel=GPy.kern.RBF(input_dim=1))
|
||||
|
||||
# print(opt.model.kernel.name)
|
||||
|
||||
# Run the optimization
|
||||
max_iter = max_iters if max_iters < 5 else max_iters - 5 # evaluation budget (min=5)
|
||||
# max_time = max_time # time budget
|
||||
eps = 1.e-6 # Minimum allows distance between the last two observations
|
||||
|
||||
if verbose:
|
||||
print('Optimizing...')
|
||||
|
||||
# optimize
|
||||
start = time.time()
|
||||
self.optimizer.run_optimization(max_iter, max_time, eps)
|
||||
end = time.time()
|
||||
|
||||
if verbose:
|
||||
print('Done with total time: {}'.format(end - start))
|
||||
|
||||
# save best parameters and reward
|
||||
self.best_parameters = self.optimizer.x_opt
|
||||
self.best_result = self.optimizer.fx_opt
|
||||
|
||||
# print best reward
|
||||
if verbose:
|
||||
print("\nBest loss value found: {}".format(self.best_result))
|
||||
|
||||
return self.best_result, self.best_parameters
|
||||
|
||||
@@ -15,7 +15,8 @@ except ImportError as e:
|
||||
"If ipopt is already installed, you can install the python wrapper via "
|
||||
"`pip install ipopt`.")
|
||||
|
||||
from optimizer import Optimizer
|
||||
from pyrobolearn.optimizers import Optimizer
|
||||
|
||||
|
||||
__author__ = "Brian Delhaisse"
|
||||
__copyright__ = "Copyright 2018, PyRoboLearn"
|
||||
@@ -119,12 +120,33 @@ class IPopt(Optimizer):
|
||||
[4] Repos: https://github.com/coin-or/Ipopt and https://pypi.org/project/ipopt/
|
||||
"""
|
||||
|
||||
def __init__(self, model, losses, hyperparameters):
|
||||
super(IPopt, self).__init__(model, losses, hyperparameters)
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Initialize the interior point optimizer.
|
||||
"""
|
||||
super(IPopt, self).__init__(*args, **kwargs)
|
||||
|
||||
def optimize(self, parameters, loss, max_iters=1, verbose=False, *args, **kwargs):
|
||||
"""
|
||||
Optimize the given loss function with respect to the given parameters.
|
||||
|
||||
Args:
|
||||
parameters (np.array): parameters to optimize.
|
||||
loss (callable): callable objective / loss function to minimize.
|
||||
bounds (tuple, list, np.array): parameter bounds. E.g. bounds=[0, np.inf]
|
||||
max_iters (int): number of maximum iterations.
|
||||
verbose (bool): if True, it will display information during the optimization process.
|
||||
*args: list of arguments to give to the loss function if callable.
|
||||
**kwargs: dictionary of arguments to give to the loss function if callable.
|
||||
|
||||
Returns:
|
||||
float, torch.Tensor, np.array: loss scalar value.
|
||||
object: best parameters
|
||||
"""
|
||||
N = len(parameters)
|
||||
|
||||
def optimize(self):
|
||||
# define initial value
|
||||
x0 = np.array([0.1] * N) # important that the initial value != 0 for the computation of the grad!
|
||||
x0 = parameters # important that the initial value != 0 for the computation of the grad!
|
||||
|
||||
# define (lower and upper) bound constraints
|
||||
lb = [-1] * N
|
||||
@@ -140,9 +162,13 @@ class IPopt(Optimizer):
|
||||
opt.add_constraint(OrthogonalConstraint(x))
|
||||
|
||||
# define the nonlinear optimization problem
|
||||
nlp = ipopt.problem(n=N, m=len(cl[:i]), problem_obj=opt, lb=lb, ub=ub, cl=cl[:i], cu=cu[:i])
|
||||
self.optimizer = ipopt.problem(n=N, m=len(cl[:i]), problem_obj=opt, lb=lb, ub=ub, cl=cl[:i], cu=cu[:i])
|
||||
|
||||
# solve problem
|
||||
x, info = nlp.solve(x0)
|
||||
x, info = self.optimizer.solve(x0)
|
||||
|
||||
return x
|
||||
# save the results
|
||||
self.best_parameters = x
|
||||
self.best_result = info['obj_val']
|
||||
|
||||
return self.best_result, self.best_parameters
|
||||
|
||||
@@ -5,7 +5,9 @@ References:
|
||||
[1] https://nlopt.readthedocs.io/en/latest/
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
from autograd import numpy as np
|
||||
from autograd import grad
|
||||
import torch
|
||||
|
||||
# NLopt optimizers
|
||||
try:
|
||||
@@ -13,7 +15,7 @@ try:
|
||||
except ImportError as e:
|
||||
raise ImportError(e.__str__() + "\n HINT: you can install nlopt via `pip install nlopt`.")
|
||||
|
||||
from optimizer import Optimizer
|
||||
from pyrobolearn.optimizers import Optimizer
|
||||
|
||||
|
||||
__author__ = "Brian Delhaisse"
|
||||
@@ -26,7 +28,7 @@ __email__ = "briandelhaisse@gmail.com"
|
||||
__status__ = "Development"
|
||||
|
||||
|
||||
class NLopt(object):
|
||||
class NLopt(Optimizer):
|
||||
r"""Non-Linear Optimizer
|
||||
|
||||
Non-linear optimizers based on the `nlopt` libraries.
|
||||
@@ -50,10 +52,18 @@ class NLopt(object):
|
||||
[3] Github repo: https://github.com/stevengj/nlopt
|
||||
"""
|
||||
|
||||
# def __init__(self, model, losses, hyperparameters, seed):
|
||||
# super(NLopt, self).__init__(model, losses, hyperparameters)
|
||||
def __init__(self, method, submethod=None, seed=None, *args, **kwargs):
|
||||
"""
|
||||
Initialize the non-linear optimizer.
|
||||
|
||||
def __init__(self, method, submethod=None, seed=None):
|
||||
Args:
|
||||
method:
|
||||
submethod:
|
||||
seed:
|
||||
*args:
|
||||
**kwargs:
|
||||
"""
|
||||
super(NLopt, self).__init__(*args, **kwargs)
|
||||
|
||||
# define useful variables
|
||||
self.results = {1: 'success', 2: 'stop_val reached', 3: 'ftol reached', 4: 'xtol reached',
|
||||
@@ -64,7 +74,16 @@ class NLopt(object):
|
||||
nlopt.srand(seed)
|
||||
|
||||
# define which solver to use
|
||||
def get_opt(method):
|
||||
def get_optimizer(method):
|
||||
"""
|
||||
Get the optimizer associated with the given method.
|
||||
|
||||
Args:
|
||||
method (str): optimizer string
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
if method == 'ISRES':
|
||||
return nlopt.opt(nlopt.GN_ISRES, M)
|
||||
elif method == 'COBYLA':
|
||||
@@ -78,7 +97,7 @@ class NLopt(object):
|
||||
|
||||
if method is None:
|
||||
method = 'SLSQP'
|
||||
self.opt = get_opt(method)
|
||||
self.optimizer = get_optimizer(method)
|
||||
|
||||
# define subsolver to use (if we use the AUGLAG method)
|
||||
if method == 'AUGLAG':
|
||||
@@ -86,35 +105,52 @@ class NLopt(object):
|
||||
submethod = 'SLSQP'
|
||||
elif submethod == 'AUGLAG':
|
||||
raise ValueError("Submethod should be different from AUGLAG")
|
||||
subopt = get_opt(submethod)
|
||||
subopt = get_optimizer(submethod)
|
||||
subopt.set_lower_bounds(-1)
|
||||
subopt.set_upper_bounds(1)
|
||||
# subopt.set_ftol_rel(1e-2)
|
||||
# subopt.set_maxeval(100)
|
||||
self.opt.set_local_optimizer(subopt)
|
||||
self.optimizer.set_local_optimizer(subopt)
|
||||
|
||||
def optimize(self):
|
||||
def optimize(self, parameters, loss, max_iters=1, verbose=False, *args, **kwargs):
|
||||
"""
|
||||
Optimize the given objective function using the optimizer.
|
||||
|
||||
Args:
|
||||
parameters (np.array): parameters to optimize.
|
||||
loss (callable): callable objective / loss function to minimize.
|
||||
bounds (tuple, list, np.array): parameter bounds. E.g. bounds=[0, np.inf]
|
||||
max_iters (int): number of maximum iterations.
|
||||
verbose (bool): if True, it will display information during the optimization process.
|
||||
*args: list of arguments to give to the loss function if callable.
|
||||
**kwargs: dictionary of arguments to give to the loss function if callable.
|
||||
|
||||
Returns:
|
||||
float, torch.Tensor, np.array: loss scalar value.
|
||||
object: best parameters
|
||||
"""
|
||||
# define objective function and its gradient
|
||||
def f(x, grad):
|
||||
loss_value = loss(x)
|
||||
if grad.size > 0:
|
||||
grad[:] = 2 * x.T.dot(C)
|
||||
return x.T.dot(C).dot(x)
|
||||
grad[:] = grad(loss, x)
|
||||
return loss_value
|
||||
|
||||
# define objective function to maximize
|
||||
self.opt.set_max_objective(f)
|
||||
self.optimizer.set_min_objective(f)
|
||||
|
||||
# if nlopt.GN_ISRES, we can define the population size
|
||||
self.opt.set_population(0) # by default for ISRES: pop=20*(M+1)
|
||||
self.optimizer.set_population(0) # by default for ISRES: pop=20*(M+1)
|
||||
|
||||
# define bound constraints (should be between -1 and 1 because the norm should be 1)
|
||||
self.opt.set_lower_bounds(-1.)
|
||||
self.opt.set_upper_bounds(1.)
|
||||
self.optimizer.set_lower_bounds(-1.)
|
||||
self.optimizer.set_upper_bounds(1.)
|
||||
|
||||
# define norm constraint and its gradient
|
||||
def c1(x, grad):
|
||||
if grad.size > 0:
|
||||
grad[:] = 2 * x
|
||||
return (x.T.dot(x) - 1)
|
||||
return x.T.dot(x) - 1
|
||||
|
||||
# define orthogonal constraint
|
||||
class OrthogonalConstraint(object):
|
||||
@@ -125,18 +161,18 @@ class NLopt(object):
|
||||
def constraint(self, x, grad):
|
||||
if grad.size > 0:
|
||||
grad[:] = self.v
|
||||
return (x.T.dot(self.v))
|
||||
return x.T.dot(self.v)
|
||||
|
||||
# define equality constraints
|
||||
self.opt.add_equality_constraint(c1, 0)
|
||||
self.optimizer.add_equality_constraint(c1, 0)
|
||||
# opt.add_equality_mconstraint(constraints, tol)
|
||||
|
||||
# define stopping criteria
|
||||
# self.opt.set_stopval(stopval)
|
||||
self.opt.set_ftol_rel(1e-8)
|
||||
# self.optimizer.set_stopval(stopval)
|
||||
self.optimizer.set_ftol_rel(1e-8)
|
||||
# opt.set_xtol_rel(1e-4)
|
||||
self.opt.set_maxeval(100000) # nb of iteration
|
||||
self.opt.set_maxtime(2) # time in secs
|
||||
self.optimizer.set_maxeval(100000) # nb of iteration
|
||||
self.optimizer.set_maxtime(2) # time in secs
|
||||
|
||||
# define initial value
|
||||
x0 = np.array([0.1] * M) # important that the initial value != 0 for the computation of the grad!
|
||||
@@ -146,17 +182,17 @@ class NLopt(object):
|
||||
# add constraint
|
||||
if i > 0:
|
||||
c = OrthogonalConstraint(x)
|
||||
self.opt.add_equality_constraint(c.constraint, 0)
|
||||
self.optimizer.add_equality_constraint(c.constraint, 0)
|
||||
|
||||
# optimize
|
||||
try:
|
||||
x = self.opt.optimize(x0)
|
||||
x = self.optimizer.optimize(x0)
|
||||
except nlopt.RoundoffLimited as e:
|
||||
pass
|
||||
|
||||
# save values
|
||||
evecs.append(x) # param vector
|
||||
evals.append(self.opt.last_optimum_value()) # max value
|
||||
msgs[i] = nlopt_results[self.opt.last_optimize_result()]
|
||||
evals.append(self.optimizer.last_optimum_value()) # max value
|
||||
msgs[i] = nlopt_results[self.optimizer.last_optimize_result()]
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python
|
||||
r"""Define the objective function in an optimization problem.
|
||||
|
||||
A constrained optimization problem is generally given by:
|
||||
|
||||
.. math:: \min_{x \in R^n} f(x)
|
||||
|
||||
subject to
|
||||
|
||||
.. math::
|
||||
|
||||
g_L \leq g(x) \leq g_U
|
||||
x_L \leq x \leq x_U
|
||||
|
||||
where :math:`f` is the objective function, :math:`x` are the variables that are being optimized, :math:`(x_L, x_U)`
|
||||
are the lower and upper bound on these variables, :math:`g` is a constraint function that maps the variables :math:`x`
|
||||
to another space, and :math:`(g_L, g_U)` are the lower and upper bound in that space.
|
||||
|
||||
References:
|
||||
[1] https://nlopt.readthedocs.io/en/latest/
|
||||
"""
|
||||
|
||||
import torch
|
||||
import numpy as np
|
||||
|
||||
|
||||
__author__ = "Brian Delhaisse"
|
||||
__copyright__ = "Copyright 2018, PyRoboLearn"
|
||||
__credits__ = ["Brian Delhaisse"]
|
||||
__license__ = "MIT"
|
||||
__version__ = "1.0.0"
|
||||
__maintainer__ = "Brian Delhaisse"
|
||||
__email__ = "briandelhaisse@gmail.com"
|
||||
__status__ = "Development"
|
||||
|
||||
|
||||
class Objective(object):
|
||||
r"""Objective function
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, loss):
|
||||
"""
|
||||
Initialize the objective function.
|
||||
|
||||
Args:
|
||||
loss (callable, object): loss function
|
||||
"""
|
||||
self._loss = loss
|
||||
|
||||
def __call__(self, variables, *args, **kwargs):
|
||||
if callable(self._loss):
|
||||
return self._loss(variables, *args, **kwargs)
|
||||
return self._loss
|
||||
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env python
|
||||
r"""Define the optimization problem.
|
||||
|
||||
A constrained optimization problem is generally given by:
|
||||
|
||||
.. math:: \min_{x \in R^n} f(x)
|
||||
|
||||
subject to
|
||||
|
||||
.. math::
|
||||
|
||||
g_L \leq g(x) \leq g_U
|
||||
x_L \leq x \leq x_U
|
||||
|
||||
where :math:`f` is the objective function, :math:`x` are the variables that are being optimized, :math:`(x_L, x_U)`
|
||||
are the lower and upper bound on these variables, :math:`g` is a constraint function that maps the variables :math:`x`
|
||||
to another space, and :math:`(g_L, g_U)` are the lower and upper bound in that space.
|
||||
|
||||
References:
|
||||
[1] https://nlopt.readthedocs.io/en/latest/
|
||||
"""
|
||||
|
||||
import torch
|
||||
import numpy as np
|
||||
|
||||
from pyrobolearn.optimizers.objective import Objective
|
||||
from pyrobolearn.optimizers.constraint import Constraint
|
||||
from pyrobolearn.optimizers.bound import Bound
|
||||
|
||||
__author__ = "Brian Delhaisse"
|
||||
__copyright__ = "Copyright 2018, PyRoboLearn"
|
||||
__credits__ = ["Brian Delhaisse"]
|
||||
__license__ = "MIT"
|
||||
__version__ = "1.0.0"
|
||||
__maintainer__ = "Brian Delhaisse"
|
||||
__email__ = "briandelhaisse@gmail.com"
|
||||
__status__ = "Development"
|
||||
|
||||
|
||||
class OptimizationProblem(object):
|
||||
r"""Optimization problem
|
||||
|
||||
A constrained optimization problem is generally given by:
|
||||
|
||||
.. math:: \min_{x \in R^n} f(x)
|
||||
|
||||
subject to
|
||||
|
||||
.. math::
|
||||
|
||||
g_L \leq g(x) \leq g_U
|
||||
x_L \leq x \leq x_U
|
||||
|
||||
where :math:`f` is the objective function, :math:`x` are the variables that are being optimized, :math:`(x_L, x_U)`
|
||||
are the lower and upper bound on these variables, :math:`g` is a constraint function that maps the variables
|
||||
:math:`x` to another space, and :math:`(g_L, g_U)` are the lower and upper bound in that space.
|
||||
"""
|
||||
|
||||
def __init__(self, loss, bounds, constraints):
|
||||
"""
|
||||
Initialize the optimization problem.
|
||||
|
||||
Args:
|
||||
loss (Objective): objective / loss function.
|
||||
bounds ((list of) Bound): bounds
|
||||
constraints ((list of) Constaint): constraints
|
||||
"""
|
||||
# check loss function
|
||||
if not isinstance(loss, Objective):
|
||||
loss = Objective(loss)
|
||||
self._loss = loss
|
||||
|
||||
# check bounds
|
||||
if not isinstance(bounds, list):
|
||||
bounds = [bounds]
|
||||
for i, bound in enumerate(bounds):
|
||||
if isinstance(bound, tuple) and len(bound) == 2:
|
||||
bounds[i] = Bound(lower=bound[0], upper=bound[1])
|
||||
elif not isinstance(bound, Bound):
|
||||
raise TypeError("Expecting the given bound to be an instance of `Bound`, instead got: "
|
||||
"{}".format(type(bound)))
|
||||
self._bounds = bounds
|
||||
|
||||
# check constraints
|
||||
if not isinstance(constraints, list):
|
||||
constraints = [constraints]
|
||||
for i, constraint in enumerate(constraints):
|
||||
if isinstance(constraint, tuple) and len(constraint) == 3:
|
||||
constraint, lower, upper = constraint
|
||||
constraints[i] = Constraint(constraint, lower=lower, upper=upper)
|
||||
if not isinstance(constraint, Constraint):
|
||||
raise TypeError("Expecting the given constraint to be an instance of `Constraint`, instead got: "
|
||||
"{}".format(type(constraint)))
|
||||
self._constraints = constraints
|
||||
|
||||
@property
|
||||
def objective(self):
|
||||
"""Return the objective function."""
|
||||
return self._loss
|
||||
|
||||
loss = objective
|
||||
|
||||
@property
|
||||
def bounds(self):
|
||||
"""Return the bounds."""
|
||||
return self._bounds
|
||||
|
||||
@property
|
||||
def constraints(self):
|
||||
"""Return the constraints."""
|
||||
return self._constraints
|
||||
|
||||
def __call__(self, variables, *args, **kwargs):
|
||||
"""Return the objective function."""
|
||||
return self.loss(variables, *args, **kwargs)
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
"""Provide the abstract optimizer class.
|
||||
r"""Provide the abstract optimizer class.
|
||||
|
||||
Optimizers allows to optimize (i.e. minimize or maximize) a utility function (also known as objective function,
|
||||
fitness function, loss, etc.) with or without constraints and bounds. Notably, it assumes the functions have some
|
||||
@@ -89,9 +89,10 @@ class Optimizer(object):
|
||||
"""
|
||||
Initialize the optimizer.
|
||||
"""
|
||||
self.optimizer = None
|
||||
self.is_minimizing = True
|
||||
self.best_parameters = None
|
||||
self.best_result = None
|
||||
self.is_maximizing = True
|
||||
|
||||
##############
|
||||
# Properties #
|
||||
@@ -99,37 +100,88 @@ class Optimizer(object):
|
||||
|
||||
@property
|
||||
def best_parameters(self):
|
||||
"""Return the best parameters."""
|
||||
return self._best_parameters
|
||||
|
||||
@best_parameters.setter
|
||||
def best_parameters(self, params):
|
||||
"""Set the best parameters."""
|
||||
self._best_parameters = params
|
||||
|
||||
@property
|
||||
def best_result(self):
|
||||
"""Return the best value."""
|
||||
return self._best_result
|
||||
|
||||
@best_result.setter
|
||||
def best_result(self, result):
|
||||
"""Set the optimal value."""
|
||||
self._best_result = result
|
||||
|
||||
@property
|
||||
def is_minimizing(self):
|
||||
"""Return if the optimizer is used to minimize an objective / loss function."""
|
||||
return self._is_minimizing
|
||||
|
||||
@is_minimizing.setter
|
||||
def is_minimizing(self, boolean):
|
||||
"""Set if the optimizer is used to minimize an objective / loss function."""
|
||||
self._is_minimizing = boolean
|
||||
|
||||
@property
|
||||
def is_maximizing(self):
|
||||
return self._is_maximizing
|
||||
"""Return if we are maximizing. If False, we are minimizing."""
|
||||
return not self.is_minimizing
|
||||
|
||||
@is_maximizing.setter
|
||||
def is_maximizing(self, boolean):
|
||||
self._is_maximizing = bool(boolean)
|
||||
|
||||
@property
|
||||
def is_minimizing(self):
|
||||
return not self.is_maximizing
|
||||
"""Setting if the optimizer is used to maximize an objective function."""
|
||||
self.is_minimizing = not bool(boolean)
|
||||
|
||||
###########
|
||||
# Methods #
|
||||
###########
|
||||
|
||||
def optimize(self, *args, **kwargs):
|
||||
def convert_from(self, parameters):
|
||||
"""
|
||||
Convert the parameters to the desired form for the optimizer. This should be implemented in the child classes.
|
||||
|
||||
Args:
|
||||
parameters: initial parameters.
|
||||
|
||||
Returns:
|
||||
object: parameters in the desired form.
|
||||
"""
|
||||
return parameters
|
||||
|
||||
def convert_to(self, parameters):
|
||||
"""
|
||||
Convert back the optimized parameters to the initial form. This should be implemented in the child classes.
|
||||
|
||||
Args:
|
||||
parameters: optimized parameters.
|
||||
|
||||
Returns:
|
||||
object: parameters in the initial form.
|
||||
"""
|
||||
return parameters
|
||||
|
||||
def optimize(self, parameters, loss, max_iters=1, verbose=False, *args, **kwargs):
|
||||
"""
|
||||
Optimize the given objective function using the optimizer. This should be implemented in the child classes.
|
||||
|
||||
Args:
|
||||
parameters: parameters.
|
||||
loss: callable objective / loss function to minimize.
|
||||
max_iters (int): number of maximum iterations.
|
||||
verbose (bool): if True, it will display information during the optimization process.
|
||||
*args: list of arguments to give to the loss function if callable.
|
||||
**kwargs: dictionary of arguments to give to the loss function if callable.
|
||||
|
||||
Returns:
|
||||
float, torch.Tensor, np.array: loss scalar value.
|
||||
object: best parameters
|
||||
"""
|
||||
pass
|
||||
|
||||
#############
|
||||
@@ -137,10 +189,13 @@ class Optimizer(object):
|
||||
#############
|
||||
|
||||
def __repr__(self):
|
||||
"""Return a representation string of the object."""
|
||||
return self.__class__.__name__
|
||||
|
||||
def __str__(self):
|
||||
"""Return a string describing the object."""
|
||||
return self.__class__.__name__
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
"""Optimize the given objective function using the optimizer."""
|
||||
return self.optimize(*args, **kwargs)
|
||||
|
||||
@@ -20,7 +20,8 @@ try:
|
||||
except ImportError as e:
|
||||
raise ImportError(e.__str__() + "\n HINT: you can install qpsolvers directly via 'pip install qpsolvers'.")
|
||||
|
||||
from optimizer import Optimizer
|
||||
from pyrobolearn.optimizers import Optimizer
|
||||
|
||||
|
||||
__author__ = "Brian Delhaisse"
|
||||
__copyright__ = "Copyright 2018, PyRoboLearn"
|
||||
@@ -62,7 +63,7 @@ __status__ = "Development"
|
||||
# """
|
||||
# pass
|
||||
|
||||
class QP(object):
|
||||
class QP(Optimizer):
|
||||
r"""Quadratic Programming solvers
|
||||
|
||||
This class uses the `qpsolvers` which is a unified Python interface for multiple QP solvers [1,2].
|
||||
@@ -105,13 +106,15 @@ class QP(object):
|
||||
[2] Github repo: https://github.com/stephane-caron/qpsolvers
|
||||
"""
|
||||
|
||||
def __init__(self, method='quadprog'):
|
||||
def __init__(self, method='quadprog', *args, **kwargs):
|
||||
"""
|
||||
Initialize the QP solver.
|
||||
|
||||
Args:
|
||||
method (str): ['cvxopt', 'cvxpy', 'ecos', 'gurobi', 'mosek', 'osqp', 'qpoases', 'quadprog']
|
||||
"""
|
||||
super(QP, self).__init__(*args, **kwargs)
|
||||
|
||||
solvers = set(qpsolvers.available_solvers)
|
||||
if len(solvers) == 0:
|
||||
raise ValueError("No QP solvers have been found on this computer. Please install one of the QP modules")
|
||||
|
||||
@@ -7,8 +7,10 @@ References:
|
||||
|
||||
import numpy as np
|
||||
import scipy
|
||||
import scipy.optimize
|
||||
|
||||
from pyrobolearn.optimizers import Optimizer
|
||||
|
||||
from optimizer import Optimizer
|
||||
|
||||
__author__ = "Brian Delhaisse"
|
||||
__copyright__ = "Copyright 2018, PyRoboLearn"
|
||||
@@ -20,7 +22,7 @@ __email__ = "briandelhaisse@gmail.com"
|
||||
__status__ = "Development"
|
||||
|
||||
|
||||
class Scipy(object):
|
||||
class Scipy(Optimizer):
|
||||
r"""Scipy optimizer
|
||||
|
||||
This uses the `scipy.optimize.minimize` to optimize a given objective function under various bounds and
|
||||
@@ -53,7 +55,7 @@ class Scipy(object):
|
||||
[1] scipy.optimize.minimize: https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html
|
||||
"""
|
||||
|
||||
def __init__(self, method='SLSQP'):
|
||||
def __init__(self, method='SLSQP', *args, **kwargs):
|
||||
"""
|
||||
Initialize the scipy method
|
||||
|
||||
@@ -76,37 +78,54 @@ class Scipy(object):
|
||||
# define optimization method
|
||||
# By default, it will be 'BFGS', 'L-BFGS-B', or 'SLSQP' depending on the constraints and bounds
|
||||
# If constraints, it can only be 'COBYLA' or 'SLSQP'. COBYLA only supports inequality constraints.
|
||||
super(Scipy, self).__init__(*args, **kwargs)
|
||||
self.method = method
|
||||
|
||||
def optimize(self, maxiter=1e6, verbose=True):
|
||||
# define objective function to MINIMIZE
|
||||
# f = lambda x: -(x.T.dot(C)).dot(x)
|
||||
def f(x):
|
||||
return -(x.T.dot(C)).dot(x)
|
||||
def optimize(self, parameters, loss, max_iters=1e6, verbose=False, *args, **kwargs):
|
||||
"""
|
||||
Optimize the given objective function using the optimizer.
|
||||
|
||||
Args:
|
||||
parameters (np.array): parameters to optimize.
|
||||
loss (callable): callable objective / loss function to minimize.
|
||||
bounds (tuple, list, np.array): parameter bounds. E.g. bounds=[0, np.inf]
|
||||
max_iters (int): number of maximum iterations.
|
||||
verbose (bool): if True, it will display information during the optimization process.
|
||||
*args: list of arguments to give to the loss function if callable.
|
||||
**kwargs: dictionary of arguments to give to the loss function if callable.
|
||||
|
||||
Returns:
|
||||
float, torch.Tensor, np.array: loss scalar value.
|
||||
object: best parameters
|
||||
"""
|
||||
N = len(parameters)
|
||||
|
||||
# define initial guess
|
||||
x0 = np.ones((M,)) # np.zeros((M,))
|
||||
x0 = np.ones((N,)) # np.zeros((N,))
|
||||
|
||||
# define 1st constraints: norm of 1
|
||||
constraints = [{'type': 'eq', 'fun': lambda x: x.T.dot(x) - 1, 'jac': None, 'args': ()}]
|
||||
|
||||
# define bounds: each vector u have a norm of 1 thus each parameter is between -1 and 1
|
||||
bounds = [(-1., 1.)] * M
|
||||
bounds = [(-1., 1.)] * N
|
||||
|
||||
# optimize recursively
|
||||
evals, evecs = [], []
|
||||
messages = {}
|
||||
options = {'maxiter': maxiter, 'disp': verbose}
|
||||
for i in range(M):
|
||||
options = {'maxiter': max_iters, 'disp': verbose}
|
||||
for i in range(N):
|
||||
if i != 0:
|
||||
# add orthogonality constraint
|
||||
constraints.append({'type': 'eq', 'fun': lambda u: u1.T.dot(u)})
|
||||
|
||||
# minimize --> it returns an instance of OptimizeResult
|
||||
result = scipy.optimize.minimize(f, x0, args=(), method=self.method, jac=None, hess=None, bounds=bounds,
|
||||
constraints=constraints, tol=None, callback=None, options=options)
|
||||
result = scipy.optimize.minimize(loss, x0, args=(), method=self.method, jac=None, hess=None, bounds=bounds,
|
||||
constraints=constraints, tol=None, callback=None, options=options)
|
||||
|
||||
print(result.success)
|
||||
print(result.message)
|
||||
print(result.fun)
|
||||
print(result.x)
|
||||
if verbose:
|
||||
print(result.success)
|
||||
print(result.message)
|
||||
print(result.fun)
|
||||
print(result.x)
|
||||
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user