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

Random local search over a set of permissible values #374

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions cgp/local_search/random_search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import numpy as np
from typing import Callable


class RandomLocalSearch:
def __init__(
self,
objective: Callable,
seed: int,
n_steps: int = 10, # todo: should this be dependent on the number of parameters?
HenrikMettler marked this conversation as resolved.
Show resolved Hide resolved
permissible_values: np.ndarray = np.logspace(start=0, stop=7, num=8, base=2, dtype=int),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the current implementation, is it, for example, possible to say "parameters should be uniformly sampled in the interval [0, 1]"? if not, I think it would be a good idea to make the generation of permissible values modular to support such use cases

) -> None:
self.objective = objective
self.seed = seed
self.n_steps = n_steps
self.permissible_values = permissible_values

def __call__(self, ind) -> None:
rng = np.random.RandomState(self.seed)
HenrikMettler marked this conversation as resolved.
Show resolved Hide resolved

params_values, params_names = ind.parameters_to_numpy_array(only_active_nodes=True)

if len(params_values) > 0:
for _ in range(self.n_steps):
# sample a new set of parameter values randomly
params_sampled = [
rng.choice(self.permissible_values) for param_value in params_values
]
# write the parameters into a clone of individual
new_ind = ind.clone()
new_ind.update_parameters_from_numpy_array(
params=params_sampled, params_names=params_names
)
# evaluate fitness
self.objective(new_ind)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as discussed, the local-search objective should not modify individuals but return a fitness value

# if fitness improved: replace parameter values and fitness
HenrikMettler marked this conversation as resolved.
Show resolved Hide resolved
if new_ind.fitness >= ind.fitness: # todo: should this be >= or > ??
HenrikMettler marked this conversation as resolved.
Show resolved Hide resolved
ind.update_parameters_from_numpy_array(
params=params_sampled, params_names=params_names
)
ind.fitness = new_ind.fitness


if __name__ == "__main__":
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the rest should be moved into a test I guess ;)

import cgp

def objective(ind):
params_values, _ = ind.parameters_to_numpy_array(only_active_nodes=True)
ind.fitness = np.sum(params_values)
return ind

seed = 12345
genome = cgp.Genome(primitives=(cgp.Add, cgp.Sub, cgp.Mul, cgp.Parameter), n_inputs=1)
genome.randomize(rng=np.random.RandomState(seed=seed))
ind = cgp.IndividualSingleGenome(genome=genome)

objective(ind)
print(
f"Node parameters before local search "
f"{ind.parameters_to_numpy_array(only_active_nodes=True)} \n"
)

rls = RandomLocalSearch(objective=objective, seed=seed, n_steps=1000)
rls(ind)
print(
f"Node parameters after local search "
f"{ind.parameters_to_numpy_array(only_active_nodes=True)} \n"
)
Empty file.