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
Manually adding points and changing the utility function #337
Comments
hey, at first glance, you have the target and the suggested point as the same thing. e.g. point 0 has target 0. Depending on your objective function, that may not be a very good target value, in which case it's nor surprising the optimiser doesn't look around that point any more. You still have to evaluate what the real target value is at your suggestion, and it still has to be good compared to previously probed points for the optimizer to continue exploring it. overall, there should only be one line of difference between making a suggestion and using the optimizers internal suggestions. The below is a code snippet that won't run by itself, but I hope illustrates the idea. if (self.Nsuggestions is not None) and (self.SuggestionsProbed < self.Nsuggestions):
# evaluate any suggested solutions first
next_point_to_probe = self.Suggestions[self.SuggestionsProbed]
self.SuggestionsProbed += 1
else:
next_point_to_probe = self.optimizer.suggest(utility)
target = self.BlackBoxFunction(next_point_to_probe)
self.optimizer.register(params=next_point_to_probe, target=target) |
I didn't follow the explanation about what was wrong with my example. Let me clarify, I sample N points using suggest-register normally. If after those N points, I don't have an objective function value larger than some value V then I manually add a point, let's say 0, which I know for a fact that it has a larger objective value than all of the N points sampled until now. It might not be the true maximum over the whole range, but it is greater than the other sampled points. So I would want the optimizer to investigate in the vicinity of the added point at least, depending on the exploration-exploitation parameter knobs. I think that essentially I have the same code as you. The only difference is that I also change the utility function at the same time. Could that be it? I'll try getting into the code more and see if I see something. p.s.: |
At the optimizer.register step, you need to register the location of the point, and the value of the objective function at that point. In the example you gave, you are using the same value (0) for both. |
This is what I'm doing, it's not registering the same value for both, optimizer.register(params={'x':0}, target=f(0) ) But my |
Ok my apologies, I didn't see the f there. |
OK. Again apologies for not understanding your initial question, I was trying to answer on my phone. I've just had a look into this and I agree the behavior is not as expected. Below is a standalone example I wrote to investigate this.
I would really appreciate if you could play around with this example, and check if you agree with the conclusions I draw above... from bayes_opt import BayesianOptimization
from bayes_opt import UtilityFunction
from scipy.optimize import rosen
import numpy as np
from matplotlib import pyplot as plt
from sklearn.gaussian_process.kernels import Matern
def run_optimization(use_suggestion=False, iterations=100, set_gp_params=True):
"""
optimize the rosenbrock function; the correct answer is x=1, y=1.
:param use_suggestion: False: ignore suggestoin, True: use sugestion
:param iterations: iterations to run
:param set_gp_params: if true, will set length scales, if false uses default
"""
optimizer = BayesianOptimization(
f=None,
pbounds={'x': (-1, 1), 'y': (-1, 1)},
verbose=1,
random_state=1
)
if set_gp_params:
optimizer.set_gp_params(alpha=0,
kernel=Matern(length_scale=[.2, .2]))
utility = UtilityFunction(kind="ucb", kappa=2, xi=1e-4)
suggestion = {'x': 1, 'y': 1} # this is the correct answer
objective_values = []
x_values = []
y_values = []
for i in range(iterations):
if use_suggestion:
target = -1 * rosen(list(suggestion.values()))
optimizer.register(params=suggestion, target=target)
objective_values.append(target)
x_values.append(suggestion['x'])
y_values.append(suggestion['y'])
use_suggestion = False
next_point = optimizer.suggest(utility)
target = -1*rosen(list(next_point.values()))
# -1 because we seek max not min
try:
optimizer.register(params=next_point, target=target)
objective_values.append(target)
x_values.append(next_point['x'])
y_values.append(next_point['y'])
except KeyError:
'''
the optimiser has a tendency to try and probe points it already has probed
when it gets very confident about where the maximum is. I'm just skipping these
for this example
'''
pass
print(optimizer.max)
suggested_point_array = np.array(list(suggestion.values())).reshape(1, -1)
print(f'predicted value at true maximum is {optimizer._gp.predict(suggested_point_array)}')
return objective_values, x_values, y_values
if __name__ == '__main__':
print(f'running no suggestion sim')
no_sug_convergence, no_sug_x, no_sug_y = run_optimization(use_suggestion=False)
print(f'running with suggestion sim')
sug_convergence, sug_x, sug_y = run_optimization(use_suggestion=True)
fig, axs = plt.subplots(nrows=1, ncols=2, figsize=[8,5])
axs[0].plot(no_sug_convergence)
axs[0].plot(sug_convergence)
axs[0].legend(['without suggestion', 'with suggestion'])
axs[1].scatter(no_sug_x, no_sug_y)
axs[1].scatter(sug_x, sug_y)
axs[1].grid()
axs[1].legend(['without suggestion', 'with suggestion'])
plt.show() |
PS: changing kappa to e.g. 0.1 makes the behavior a lot clearer. I'm not entirely confident that my theory about the gp parameters is correct.... |
I will check it out. I also wonder if the acquisition function type has anything to do with it. I use EI rather than UCB and then xi is the parameter. |
Ok, I've gone inside the code and I have preliminary observations (I'm not sure that is the only time it happens, but this is what I see now): Any suggestions on how to deal with a fit that suggests that it is a constant line (the data and targets are not constant)? |
that sounds strange - could you post some code to reproduce? |
This is trying to emulate what I have: import matplotlib.pyplot as plt
import numpy as np
from bayes_opt import BayesianOptimization, UtilityFunction
data_target = np.array([[ 4.11527487e+00, -5.25998354e-02],
[ 9.87746489e-02, -4.13503230e-02],
[ 3.57381547e+00, -5.25998354e-02],
[ 1.60683672e+00, -5.25998354e-02],
[ 5.26929603e+00, -6.18080378e-02],
[ 9.08232311e-01, -5.25998354e-02],
[ 2.39112060e-01, -3.52116406e-02],
[ 4.29874823e+00, -4.23911691e-02],
[ 9.88334539e-01, -5.25998354e-02],
[ 3.10196661e+00, -5.25998354e-02],
[ 7.87734943e-01, -5.25998354e-02],
[ 8.27486483e-01, -5.16990900e-02],
[ 3.80523113e+00, -5.25998354e-02],
[ 7.03828442e-01, -2.22452462e-02],
[ 5.83227361e+00, -3.09045196e-02],
[ 4.65850859e+00, -2.24894464e-02],
[ 5.35498380e+00, -4.55683947e-02],
[ 5.15052521e+00, -2.59802818e-02],
[ 3.43894560e+00, -5.25998354e-02],
[ 3.40215825e+00, -5.25998354e-02],
[ 0.00000000e+00, -4.35041189e-03], # <- manually added point
[ 6.28318531e+00, -4.35041189e-03]]) # <- manually added point
pbounds = {'x': (0, 6.283)}
optimizer = BayesianOptimization(f=None, pbounds=pbounds, verbose=0, random_state=None)
utility = UtilityFunction(kind="ei", kappa=0, xi=10)
# this includes all the data i currently have, what was suggested from the optimizer, and the manually added points.
for i in range(data_target.shape[0]):
next_point = {'x': data_target[i,0]}
optimizer.register(params=next_point, target=data_target[i,1])
utility = UtilityFunction(kind="ei", kappa=0, xi=0.001)
optimizer._gp.fit(optimizer._space.params, optimizer._space.target) #or: data_target[:,0:1], data_target[:,1:])
# to "understand" what's happening beneath in the suggestion function
x = np.linspace(0, 6.28, 1000).reshape(-1,1)
mu, sigma = optimizer._gp.predict(x, return_std=True)
plt.scatter(data_target[:,0], data_target[:,1], marker='*')
plt.scatter(x, mu, marker='.')
plt.show()
# or, just see what is the suggestion
newpnt = optimizer.suggest(utility)
print(newpnt['x'])
# the maximum should occur near either of the bounds. Every time I run this code snippet, I get different results. When it cannot predict correctly, the suggestion is random. When it does predict correctly, I get sound suggestions most of the time. |
I guess the problem is with the rand_state parameter in the GaussianProcessRegressor. If you're unlucky, it may do a "bad" fit and then your predictions will be bad too. This is a problem whenever you call |
OK:
optimizer.set_gp_params(kernel=Matern(length_scale=[0.1])) With this I get consistently good fits for random_state =0, 1, and 2. My understanding is that the length scale should be optimized for best fit anyway, so I'm slightly surprised that this made the difference - you could investigate why that is if you have any time! Now: your GP is working, we can get back to the initial question of the clustering around suggestions! |
Yes, another thing that helped me is: optimizer.set_gp_params(n_restarts_optimizer=25) You're right about the length scale, it is weird that the length_scale matters, because after the fit if I look at I think I'm withdrawing my question now that I get that the problem is the fact that I don't get a good fit for a GP or if I do, it is overfitting the observations. The reason is that anyway it's going to have high uncertainty between the observations and essentially the problem is ill-posed so the suggestions are random or almost random. So it won't go necessarily to the maximum value of the point I added, but according to the acquisition function. And also, It is not just when adding points manually. It's going to happen every time I can't get a good fit. I'll have to think about what I can do about it, but I don't think there's any problem with your package. p.s. - thank you for your help and time!! |
if you are having issues with over fitting, you can also set the alpha parameter in the call to set_gp_params. higher values will regularity more heavily and prevent over-fitting. It's good that you checked that the gaussian process model is actually fitting your data. I think we should maybe generate a plot or something like that to make it easier for people to realise when the fit has failed... |
I'm using the suggest-register paradigm and I'm doing two things that I think break something in the operation and I wanted to know how to do it correctly.
At some point, I want to suggest a parameter by myself, externally. So what I do is
After that, I also want to change the utility to exploitation rather than exploration because
I happen to know that the point I'm manually adding, is supposed to be at least some local maxima
and also that it is for sure higher than all the points that were suggested until now.
So I change:
But now whenever I run the suggest-register combo, I don't seem to get any suggestions that are closer to
my_pnt_from_an_oracle
to try to find a better maximum. I think it may not be registered correctly? Although, it appears inoptimizer.res
.Any suggestion on how to add a point and change the utility in the middle of an operation?
The text was updated successfully, but these errors were encountered: