-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Advanced constrained optimization #344
Merged
bwheelz36
merged 16 commits into
bayesian-optimization:master
from
till-m:constrained-optimization
Aug 30, 2022
Merged
Changes from 13 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
74aa511
Working constraint optimization
till-m c7e5bf2
Remove unnecessary `try`/`except` block
till-m 5a7edac
Extend coverage
till-m 3fb2f1c
Fix typo
till-m 59cd0bd
Update docstring
till-m 90f3d17
Remove unused imports
till-m 90ef124
Docstring formatting
till-m 4e147cb
Merge branch 'master' into constrained-optimization
till-m 6a0c55a
Linting/formatting
till-m a1f2c47
Rename `constraint.py` -> `advanced_constraint.py`.
till-m 86540f8
Expand documentation in notebook
till-m c5efb24
Undo renaming
till-m b751820
Fix typos
till-m e1fad9e
Merge branch 'fmfn:master' into constrained-optimization
till-m 1a49eb1
Use scipy `NonLinearConstraint`, change the way `ConstraintModel` is …
till-m a46c335
Merge branch 'master' into constrained-optimization
till-m File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import numpy as np | ||
from sklearn.gaussian_process.kernels import Matern | ||
from sklearn.gaussian_process import GaussianProcessRegressor | ||
from scipy.stats import norm | ||
|
||
|
||
class ConstraintModel(): | ||
""" | ||
This class takes the function to optimize as well as the parameters bounds | ||
in order to find which values for the parameters yield the maximum value | ||
using bayesian optimization. | ||
|
||
Parameters | ||
---------- | ||
func: function | ||
Constraint function. If multiple constraints are handled, this should | ||
return a numpy.ndarray of appropriate size. | ||
|
||
limits: numeric or numpy.ndarray | ||
Upper limit(s) for the constraints. The return value of `func` should | ||
have exactly this shape. | ||
|
||
random_state: int or numpy.random.RandomState, optional(default=None) | ||
If the value is an integer, it is used as the seed for creating a | ||
numpy.random.RandomState. Otherwise the random state provided is used. | ||
When set to None, an unseeded random state is generated. | ||
|
||
Note | ||
---- | ||
In case of multiple constraints, this model assumes conditional | ||
independence. This means that for each constraint, the probability of | ||
fulfillment is the cdf of a univariate Gaussian. The overall probability | ||
is a simply the product of the individual probabilities. | ||
""" | ||
|
||
def __init__(self, func, limits, random_state=None): | ||
self.func = func | ||
|
||
if isinstance(limits, float): | ||
self._limits = np.array([limits]) | ||
else: | ||
self._limits = limits | ||
|
||
basis = lambda: GaussianProcessRegressor( | ||
kernel=Matern(nu=2.5), | ||
alpha=1e-6, | ||
normalize_y=True, | ||
n_restarts_optimizer=5, | ||
random_state=random_state, | ||
) | ||
self._model = [basis() for _ in range(len(self._limits))] | ||
|
||
@property | ||
def limits(self): | ||
return self._limits | ||
|
||
def eval(self, **kwargs): | ||
""" | ||
Evaluates the constraint function. | ||
""" | ||
try: | ||
return self.func(**kwargs) | ||
except TypeError as e: | ||
msg = ( | ||
"Encountered TypeError when evaluating constraint " + | ||
"function. This could be because your constraint function " + | ||
"doesn't use the same keyword arguments as the target " + | ||
f"function. Original error message:\n\n{e}" | ||
) | ||
raise TypeError(msg) | ||
|
||
def fit(self, X, Y): | ||
""" | ||
Fits internal GaussianProcessRegressor's to the data. | ||
""" | ||
if len(self._model) == 1: | ||
self._model[0].fit(X, Y) | ||
else: | ||
for i, gp in enumerate(self._model): | ||
gp.fit(X, Y[:, i]) | ||
|
||
def predict(self, X): | ||
""" | ||
Returns the probability that the constraint is fulfilled at `X` based | ||
on the internal Gaussian Process Regressors. | ||
|
||
Note that this does not try to approximate the values of the constraint | ||
function, but probability that the constraint function is fulfilled. | ||
For the former, see `ConstraintModel.approx()`. | ||
""" | ||
X_shape = X.shape | ||
X = X.reshape((-1, self._model[0].n_features_in_)) | ||
if len(self._model) == 1: | ||
y_mean, y_std = self._model[0].predict(X, return_std=True) | ||
result = norm(loc=y_mean, scale=y_std).cdf(self._limits[0]) | ||
return result.reshape(X_shape[:-1]) | ||
else: | ||
result = np.ones(X.shape[0]) | ||
for j, gp in enumerate(self._model): | ||
y_mean, y_std = gp.predict(X, return_std=True) | ||
result = result * norm(loc=y_mean, scale=y_std).cdf( | ||
self._limits[j]) | ||
return result.reshape(X_shape[:-1]) | ||
|
||
def approx(self, X): | ||
""" | ||
Returns the approximation of the constraint function using the internal | ||
Gaussian Process Regressors. | ||
""" | ||
X_shape = X.shape | ||
X = X.reshape((-1, self._model[0].n_features_in_)) | ||
if len(self._model) == 1: | ||
return self._model[0].predict(X).reshape(X_shape[:-1]) | ||
else: | ||
result = np.column_stack([gp.predict(X) for gp in self._model]) | ||
return result.reshape(X_shape[:-1] + (len(self._limits), )) | ||
|
||
def allowed(self, constraint_values): | ||
""" | ||
Checks whether `constraint_values` are below the specified limits. | ||
""" | ||
if self._limits.size == 1: | ||
return np.less_equal(constraint_values, self._limits) | ||
|
||
return np.all(constraint_values <= self._limits, axis=-1) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I notice a lot of these properties are prepended with a
_
and equipped with a separate method essentially acting as agetter
, presumably to avoid users setting these properties. Let me know which properties you want me to handle this way.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hey, I might consider putting self.constraint as self._constraint - which would be consistent with how _gp is currently handled.