refactor/update optimizers (ongoing)

This commit is contained in:
Brian Delhaisse
2019-04-19 17:43:52 +02:00
parent 2d53773406
commit 17e7b454e1
11 changed files with 725 additions and 74 deletions
+54
View File
@@ -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
+126 -4
View File
@@ -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
+58
View File
@@ -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
+113 -4
View File
@@ -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
+34 -8
View File
@@ -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
+64 -28
View File
@@ -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()]
+54
View File
@@ -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)
+64 -9
View File
@@ -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")
+37 -18
View File
@@ -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