CMA-ES / libcmaes

libcmaes is a multithreaded C++11 library with Python bindings for high performance blackbox stochastic optimization using the CMA-ES algorithm for Covariance Matrix Adaptation Evolution Strategy
Other
321 stars 78 forks source link

[Python] Call cmaes within a fitfunc #243

Closed gaetanserre closed 3 months ago

gaetanserre commented 3 months ago

Is there a way to deal with min max problems using the Python bindings of libcmaes, i.e.: minimize a function using lcmaes f that itself maximizes an other function g using lcmaes? When I try a naive way, I got an boost::python::error_already_set error.

import lcmaes

def F(y, m):
    # input parameters for a 10-D problem
    x = [10] * 10
    lambda_ = 10  # lambda is a reserved keyword in python, using lambda_ instead.
    seed = 0  # 0 for seed auto-generated within the lib.
    sigma = 0.1
    p = lcmaes.make_simple_parameters(x, sigma, lambda_, seed)
    p.set_str_algo("acmaes")

    # objective function.
    def nfitfunc(x, n):
        val = 0.0
        for i in range(0, n):
            val += x[i] * x[i]
        return -val

    # generate a function object
    objfunc = lcmaes.fitfunc_pbf.from_callable(nfitfunc)

    # pass the function and parameter to cmaes, run optimization and collect solution object.
    cmasols = lcmaes.pcmaes(objfunc, p)

    # collect and inspect results
    bcand = cmasols.best_candidate()
    bx = lcmaes.get_candidate_x(bcand)
    return y[0] * bx.get_fvalue()

x = [10] * 10
lambda_ = 10  # lambda is a reserved keyword in python, using lambda_ instead.
seed = 0  # 0 for seed auto-generated within the lib.
sigma = 0.1
p = lcmaes.make_simple_parameters(x, sigma, lambda_, seed)
p.set_str_algo("acmaes")
objfunc = lcmaes.fitfunc_pbf.from_callable(F)
cmasols = lcmaes.pcmaes(objfunc, p)
bcand = cmasols.best_candidate()
print(bcand.get_fvalue())

Output:

terminate called after throwing an instance of 'boost::python::error_already_set'
beniz commented 3 months ago

Hi @gaetanserre this may not be the technical answer you may need, but first minimax with CMA-ES appears to come with its own technical and theoretical requirements, see https://dl.acm.org/doi/10.1145/3512290.3528702 for a recent GECCO-22 publication (I haven´t read it yet).

Now, regarding the debugging of your code, going with the Python wrapper makes it difficult [1]. You may want to try the Python CMA-ES version, that will give you more freedom for experimenting slightly outside the box use-cases.

[1] Reason is there's a low-level C++ binding through boost-python that happens once, so when using two instances of lcmaes on top of it, more or less all underlying C++ objects get shared, and thus trouble is certain to arise. Also this looks plausible regarding your error.

gaetanserre commented 3 months ago

Thank you very much for your answer and for the reference. Indeed, it works with the Python implementation of CMA-ES, but it is slow comparing to yours. I guess I will implement a simple optimizer in pure Python for the max of the min-max problem.

beniz commented 3 months ago

Another way could be to work it out directly from C++. I can't guarantee there would not be issues with shared objects out of the box though.

gaetanserre commented 3 months ago

Indeed it's more likely to work in pure C++. However, I have to stick with Python for my problem. I'll use the Python implementation, I was wrong, it is not that slow.