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

An error occurres when args is specified and ScaleCoordinates is used #205

Closed
haiiliin opened this issue May 19, 2022 · 12 comments
Closed

Comments

@haiiliin
Copy link

haiiliin commented May 19, 2022

When the args argument of the optimizer is specified and the ScaleCoordinates is used, an error occurres.

For example, I want to optimize the following simple function,

import cma

def objective_function(x, a: float = 1., b: float = 0., c: float = 0.):
    x[0] /= 2.
    return a * x[0] ** 2 + b * x[0] + c + x[1] ** 2

objective_function_scaled = cma.fitness_transformations.ScaleCoordinates(objective_function, multipliers=[2., 1.])
es = cma.CMAEvolutionStrategy([0., 0.], 1.).optimize(objective_function_scaled)

print('x = ', es.result.xbest)

it is a 2D function, f = (a * x1 ^ 2 + b * x1 + c) + x2 * 2, the function is scaled but inside the objective function, the variables are scaled again which makes it identical to the original function, the minimal point is (0, 0) when a = 1, b = c = 0, when the args is not specified, the result is correct:

(3_w,6)-aCMA-ES (mu_w=2.0,w_1=63%) in dimension 2 (seed=1117279, Thu May 19 17:24:02 2022)
Iterat #Fevals   function value  axis ratio  sigma  min&max std  t[m:s]
    1      6 1.229806342642211e-01 1.0e+00 7.16e-01  6e-01  7e-01 0:00.0
    2     12 1.620997274641983e-01 1.2e+00 6.09e-01  5e-01  5e-01 0:00.0
    3     18 5.757582159094520e-01 1.0e+00 6.66e-01  5e-01  6e-01 0:00.0
   79    474 2.061656273654042e-14 1.2e+00 8.74e-05  1e-07  2e-07 0:00.0
x =  [ 1.25596797e-08 -3.36084835e-08]

But when I specified the args argument, i.e., to optimize the function when (a = 1, b = -4, c = 4), the actual minimal point for this condition is (2, 0), but an error occurres:

import cma

def objective_function(x, a: float = 1., b: float = 0., c: float = 0.):
    x[0] /= 2.
    return a * x[0] ** 2 + b * x[0] + c + x[1] ** 2

objective_function_scaled = cma.fitness_transformations.ScaleCoordinates(objective_function, multipliers=[2., 1.])
es = cma.CMAEvolutionStrategy([0., 0.], 1.).optimize(objective_function_scaled, args=(1., -4., 4.))

print('x = ', es.result.xbest)

The above example throw the following error message:

Traceback (most recent call last):
  File "C:/Users/Hailin/OneDrive/Desktop/test.py", line 8, in <module>
    es = cma.CMAEvolutionStrategy([0., 0.], 1.).optimize(fitness_scaled, args=(1., -4., 4.))
  File "C:\Users\Hailin\AppData\Local\Programs\Python\Python310\lib\site-packages\cma\interfaces.py", line 208, in optimize
    fitvals = eval_all(X, args=args)
  File "C:\Users\Hailin\AppData\Local\Programs\Python\Python310\lib\site-packages\cma\optimization_tools.py", line 284, in __call__
    return [fitness_function(x, *args) for x in solutions]
  File "C:\Users\Hailin\AppData\Local\Programs\Python\Python310\lib\site-packages\cma\optimization_tools.py", line 284, in <listcomp>
    return [fitness_function(x, *args) for x in solutions]
  File "C:\Users\Hailin\AppData\Local\Programs\Python\Python310\lib\site-packages\cma\fitness_transformations.py", line 163, in __call__
    x = self[i](x, *args, **kwargs)
TypeError: ScaleCoordinates.scale_and_offset() takes 2 positional arguments but 5 were given

It seems that the scaled objective function can not deal with the additional arguments. The scale_and_offset method defined in the ScaleCoordinates class takes only one argument (except for the self argument), but the additional arguments are passed to this method:

# ScaleCoordinates.scale_and_offset

def scale_and_offset(self, x):
    x = np.asarray(x)
    r = lambda vec: utils.recycled(vec, as_=x)
    if self.zero is not None and self.multiplier is not None:
        x = r(self.multiplier) * (x - r(self.zero))
    elif self.zero is not None:
        x = x - r(self.zero)
    elif self.multiplier is not None:
        x = r(self.multiplier) * x
    return x
@nikohansen
Copy link
Contributor

nikohansen commented May 20, 2022

Thanks for the report. Using the builtin functools.partial before scaling seems to be a good workaround:

import functools
import cma

def objective_function(x, a: float = 1., b: float = 0., c: float = 0.):
    x[0] /= 2.
    return a * x[0] ** 2 + b * x[0] + c + x[1] ** 2
f_partial = functools.partial(objective_function, a=1., b=-4., c=4.)  # attach parameters to function
objective_function_scaled = cma.fitness_transformations.ScaleCoordinates(
                                f_partial, multipliers=[2., 1.])

es = cma.CMAEvolutionStrategy([0., 0.], 1.).optimize(objective_function_scaled)

Would that solve your issue?

@haiiliin
Copy link
Author

Thanks for the report. Using the builtin functools.partial before scaling seems to be a good workaround:

import functools
import cma

def objective_function(x, a: float = 1., b: float = 0., c: float = 0.):
    x[0] /= 2.
    return a * x[0] ** 2 + b * x[0] + c + x[1] ** 2
f_partial = functools.partial(objective_function, a=1., b=-4., c=4.)  # attach parameters to function
objective_function_scaled = cma.fitness_transformations.ScaleCoordinates(
                                f_partial, multipliers=[2., 1.])

es = cma.CMAEvolutionStrategy([0., 0.], 1.).optimize(objective_function_scaled)

Would that solve your issue?

Thanks, this solves my problem.

@pramodiisc
Copy link

#210 (comment)

@haiiliin
Copy link
Author

@ppalcx According to help(cma.fitness_transformations.ScaleCoordinates),

fun2 = cma.ScaleCoordinates(fun, multipliers, zero)
fun2(x) == fun(multipliers * (x - zero))

when multipliers = sigma and zero = -mu / sigma, fun2(x) = fun(mu + sigma * x), then when x follows the distribution N(0, 1), mu + sigma * x follows the distribution N(mu, sigma).

@pramodiisc
Copy link

Then how to do this?????

I am having a function f(x1,x2,x3,x4,x5,x6) that has to be minimised for the variable x1,x2,x3,x4,x5,x6. The range for the variables or the search domain is
x1 (300 to 1500),
x2 (0-10),
x3 (300 to 1500),
x4(0-10) ,
x5 (500 to 20000),
x6 (0-200)
which means the algo should search for variable falling in this range.

I am getting the function value from a simulator.

help(cma.fitness_transformations.ScaleCoordinates) is not clear to me how can I use it for my problem.

sigma0
scalar, initial standard deviation in each coordinate. sigma0 should be about 1/4th of the search domain width (where the optimum is to be expected). The variables in objective_function should be scaled such that they presumably have similar sensitivity. See also option scaling_of_variables.

if I am not usingcaleCoordinates I can not use sigma 0 bigger than 10 (x4(0-10) ) which is making the algo not searching in wider domain.

Can you please share or show any simpler example.

@pramodiisc
Copy link

I mean what should I write in this part according to my problem

objective_function_scaled = cma.fitness_transformations.ScaleCoordinates(objective_function, multipliers=[2., 1.])
es = cma.CMAEvolutionStrategy([0., 0.], 1.).optimize(objective_function_scaled, args=(1., -4., 4.))

@haiiliin
Copy link
Author

haiiliin commented Aug 23, 2022

Assume you want X1 ~ N(1000, 200), X2 ~ N(5, 2), X3 ~ N(1000, 200), X4 ~ N(5, 2), X5 ~ N(10000, 1000), X6 ~ N(100, 20), use

objective_function_scaled = cma.fitness_transformations.ScaleCoordinates(
    objective_function, multipliers=[200, 2, 200, 2, 1000, 20], 
    zero=[-1000/200, -5/2, -1000/200, -5/2, -10000/1000, -100/20],
)

When objective_function_scaled takes variables follow the standard multivariate normal distribution, the transformed variables will follow X1 ~ N(1000, 200), X2 ~ N(5, 2), X3 ~ N(1000, 200), X4 ~ N(5, 2), X5 ~ N(10000, 1000), X6 ~ N(100, 20) which will be passed to objective_function. Thus if you pass the objective_function_scaled as the object function, the initial search space will be the standard multivariate normal distribution space. Then

es = cma.CMAEvolutionStrategy([0, 0, 0, 0, 0, 0], 1.)
es.optimize(objective_function_scaled)

Or just scale the object function:

x0 = [1000, 5, 1000, 5, 10000, 100]
stds = [200, 2, 200, 2, 1000, 20]
objective_function_scaled = cma.fitness_transformations.ScaleCoordinates(objective_function, multipliers=stds)
es = cma.CMAEvolutionStrategy(x0=x0, sigma0=1.0)
es.optimize(objective_function_scaled)

@haiiliin
Copy link
Author

haiiliin commented Aug 23, 2022

Also, this could be a solution which I think is more natural:

x0 = [1000, 5, 1000, 5, 10000, 100]
stds = [200, 2, 200, 2, 1000, 20]
es = cma.CMAEvolutionStrategy(x0=x0, sigma0=1.0, inopts={'CMA_stds': stds})
es.optimize(objective_function)

Even though the cma does not recommend using the CMS_stds option:

cma.CMAOptions()

...
CMA_stds='None  # multipliers for sigma0 in each coordinate, not represented in C, better use `cma.ScaleCoordinates` instead'
...

@pramodiisc
Copy link

pramodiisc commented Aug 23, 2022

@haiiliin Thanks for your explanation. Will this take care of my bounds on the variable? Because none of my decision variables should go out of bound and negative.

@haiiliin
Copy link
Author

haiiliin commented Aug 23, 2022

You can pass a bounds option to the CMAEvolutionStrategy constructor:

import cma

cma.CMAOptions()
...
bounds='[None, None]  # lower (=bounds[0]) and upper domain boundaries, each a scalar or a list/vector'
...

x0 = [1000, 5, 1000, 5, 10000, 100]
stds = [200, 2, 200, 2, 1000, 20]
lb = [300, 0, 300, 0, 500, 0]
ub = [1500, 10, 1500, 10, 20000, 20]
es = cma.CMAEvolutionStrategy(x0=x0, sigma0=1.0, inopts={'CMA_stds': stds, 'bounds': [lb, ub]})
es.optimize(objective_function)

If you use the ScaleCoordinates, you have to scale the bounds too.

@pramodiisc
Copy link

How to do scale bounds here?

import cma

cma.CMAOptions()
...
bounds='[None, None]  # lower (=bounds[0]) and upper domain boundaries, each a scalar or a list/vector'
...

x0 = [1000, 5, 1000, 5, 10000, 100]
stds = [200, 2, 200, 2, 1000, 20]
lb = [300, 0, 300, 0, 500, 0]
ub = [1500, 10, 1500, 10, 20000, 20]
es = cma.CMAEvolutionStrategy(x0=x0, sigma0=1.0, inopts={'CMA_stds': stds, 'bounds': [lb, ub] })
es.optimize(objective_function)

@haiiliin
Copy link
Author

We don't use ScaleCoordinates here, so we don't need to scale it here.

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

No branches or pull requests

3 participants