Skip to content
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

Unexpected behavior of stopping criterion: maxeval #570

Closed
fmkroci opened this issue Oct 14, 2024 · 5 comments · Fixed by #571
Closed

Unexpected behavior of stopping criterion: maxeval #570

fmkroci opened this issue Oct 14, 2024 · 5 comments · Fixed by #571
Labels

Comments

@fmkroci
Copy link

fmkroci commented Oct 14, 2024

Hello,

I noticed some unexpected behavior when disabling the 'maxeval' stopping criterion in the python interface in combination with the opt.LD_LBFGS algorithm.

1.) using a large upper number of max. evaluations

If I use opt.set_maxeval(very_large_integer) the optimization works as expected.
I get 'neval << very_large_integer' when the optimization is done.

2.) disabling with -1
If I try to disable the maximum number of evaluations completely using opt.set_maxeval(-1), I noticed very long optimization times in some settings. The number of function evaluation increases even though the number of function evaluation was much lower than 'very_large_integer' before.

3.) disabling with 0
If I, however, use opt.set_maxeval(0) the optimization works as expected and the number of function evaluations remains basically the same as when using a large upper limit.

From the documentation I would have expected both opt.set_maxeval(0) and opt.set_maxeval(-1) to behave identically.
To me it seems like setting it to a negative value additionally changes the behavior of other parameters. (Maybe some maximum number of line search steps?)
Maybe I missed a detail in the documentation, if so I am sorry for opening this issues.

Thank you very much in advance already!

@jschueller
Copy link
Collaborator

could you please provide a minimal script to reproduce ?

@fmkroci
Copy link
Author

fmkroci commented Oct 14, 2024

Hello,

thank you for the super fast answer!
It's hard to provide a complete example as it seems 'strength' of the effect seems to depend on the likelihood/function structure.
I can however provide the snippet of the minimization setup:

import nlopt
import numpy as np

# pick algorithm
algorithm = nlopt.LD_LBFGS

# algo. options
vectorStorage = None

# set stopping criteria
xtolRel = -1
ftolAbs = 1e-6

# does not work as expected
maxeval = -1  # shouldn't this work the same as maxeval = 0?

# works as expected
#maxeval = 0
#maxeval = 50000

opt = nlopt.opt(algorithm, nmbParameters)

opt.set_maxeval(maxeval)
opt.set_xtol_rel(xtolRel)
opt.set_ftol_abs(ftolAbs)
if vectorStorage is not None:
	opt.set_vector_storage(vectorStorage)
opt.set_min_objective(likelihood)

optimizedParameters = opt.optimize(startParameters)
status = self.opt.last_optimize_result()

I was wondering if setting maxeval to -1 will make LBFGS act differently somehow.
I will try to build a minimal working example.
By the way the NLopt version is 2.7.0

Thanks a lot for the help!

@fmkroci
Copy link
Author

fmkroci commented Oct 14, 2024

@jschueller I managed to write a self-contained example:

import nlopt
import numpy as np
import autograd.numpy as anp
from autograd import value_and_grad

# seed
seed = 42
rng = np.random.default_rng(seed)

# define likelihood
dim = 300

# start parameters
startParameters = rng.uniform(size=dim)

# generate 'data'
# random cov. mat.
sqrt_cov = rng.uniform(size=(dim,dim))
cov = sqrt_cov.T @ sqrt_cov

# random mean
mean = rng.uniform(size=dim) 

# define negative log-likelihood
def negative_log_likelihood(x):
    res = x-mean
    return 0.5*res@cov@res

# ... and value and gradient
vgr_negative_log_likelihood = value_and_grad(negative_log_likelihood)

def f(x, grad):
    negative_log_likelihood_value = 0
    if grad.size > 0:	
        negative_log_likelihood_value, gradient = vgr_negative_log_likelihood(x)
        grad[:] = gradient
    else:
        negative_log_likelihood_value  = negative_log_likelihood(x)
    return negative_log_likelihood_value


# pick algorithm
#algorithm = nlopt.LD_SLSQP # not affected!
algorithm = nlopt.LD_LBFGS # <- affected
#algorithm = nlopt.LD_VAR2  # <- affected

# algo. options
vectorStorage = None

# set stopping criteria
xtolRel = -1
ftolAbs = 1e-6

# does not work as expected
#maxeval = -1  # shouldn't this work the same as maxeval = 0?
# works as expected
#maxeval = 0
#maxeval = 50000

# iterate over test:
for maxeval in [None, -9999, -2, -1, 0, 10, 5000]:

    opt = nlopt.opt(algorithm, dim)

    if maxeval is not None:
        opt.set_maxeval(maxeval)
    opt.set_xtol_rel(xtolRel)
    opt.set_ftol_abs(ftolAbs)
    if vectorStorage is not None:
        opt.set_vector_storage(vectorStorage)
    opt.set_min_objective(f)

    optimizedParameters = opt.optimize(startParameters)
    status = opt.last_optimize_result()

    print('############################################')
    print(f'Results for maxeval={maxeval}:')
    print('status:', status)
    print('minimized objective function:', opt.last_optimum_value())
    print('number of evaluations:', opt.get_numevals())
    #print('parameter residuals:', optimizedParameters-mean)

The output should be:

############################################
Results for maxeval=None:
status: 3
minimized objective function: 0.00016240822633059766
number of evaluations: 206
############################################
Results for maxeval=-9999:
status: 3
minimized objective function: 0.004273600011026816
number of evaluations: 863
############################################
Results for maxeval=-2:
status: 3
minimized objective function: 0.004273600011026816
number of evaluations: 863
############################################
Results for maxeval=-1:
status: 3
minimized objective function: 0.004273600011026816
number of evaluations: 863
############################################
Results for maxeval=0:
status: 3
minimized objective function: 0.00016240822633059766
number of evaluations: 206
############################################
Results for maxeval=10:
status: 5
minimized objective function: 13.556695600049467
number of evaluations: 10
############################################
Results for maxeval=5000:
status: 3
minimized objective function: 0.00016240822633059766
number of evaluations: 206

As you can see, setting maxeval to -1 or another negative value results in more iterations and a worse optimization result.
Setting it to 0 or not setting it at all behaves the same way.

I also tested (randomly) two other algorithms: nlopt.LD_SLSQP is not affected but nlopt.LD_VAR2 is.

EDIT: I can also confirm that the issue appears also in version 2.8.0
EDIT2: I updated the example to test some other values and algorithms

@stevengj
Copy link
Owner

Thanks, should be fixed by #571.

@stevengj stevengj added the bug label Oct 15, 2024
@fmkroci
Copy link
Author

fmkroci commented Oct 15, 2024

Thank you for taking care of it so fast!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants