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

Parallel population processing #199

Closed
wants to merge 65 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
e989f1c
Add multiprocessing. Not finished yet.
kasyanovse Sep 18, 2023
9107d68
Refactoring. Not tested yet.
kasyanovse Sep 18, 2023
25681d4
Some fixes
kasyanovse Sep 19, 2023
9be885d
Some fixes
kasyanovse Sep 19, 2023
47fc380
Some fixes
kasyanovse Sep 19, 2023
39101cb
Some fixes
kasyanovse Sep 19, 2023
f3d9907
Some fixes
kasyanovse Sep 19, 2023
db34ad1
Some fixes
kasyanovse Sep 19, 2023
106fe0a
Some fixes
kasyanovse Sep 19, 2023
4a67f81
Some fixes
kasyanovse Sep 19, 2023
83f3c0b
Fix `MultiprocessingDispatcher`
kasyanovse Oct 27, 2023
0d19cb7
Fix `EvoGraphOptimizer`
kasyanovse Sep 26, 2023
b5abd1a
Add all graph comparison
kasyanovse Sep 26, 2023
9cf88de
wip
kasyanovse Oct 27, 2023
d19bcde
wip
kasyanovse Oct 20, 2023
835d326
wip
kasyanovse Oct 23, 2023
13f0ac1
wip
kasyanovse Oct 23, 2023
fb39d95
wip
kasyanovse Oct 23, 2023
0052e52
wip, drive to 3 stages of controlling
kasyanovse Oct 24, 2023
72ee29a
wip, back to simple version
kasyanovse Oct 24, 2023
0845c11
simple version is finished but not polished
kasyanovse Oct 24, 2023
1b19d34
some fixes
kasyanovse Oct 24, 2023
97d6d9c
some fixes after tests
kasyanovse Oct 25, 2023
d14272f
Some fixes
kasyanovse Oct 27, 2023
1a4e5e1
wip
kasyanovse Oct 26, 2023
cdbef7d
wip
kasyanovse Oct 27, 2023
c3f8cf4
new approach with shared memory objects
kasyanovse Oct 27, 2023
74bebbf
simple way to repeat mutations
kasyanovse Oct 27, 2023
70af4c6
a little bit complex way to repeat mutations
kasyanovse Oct 27, 2023
f3c1014
delete structural diversity check and polish reproducer
kasyanovse Oct 27, 2023
696e6e6
add some reqiured changes
kasyanovse Oct 27, 2023
ebad0d6
strange problem with workers
kasyanovse Oct 27, 2023
3c77cfa
annoying problem with workers is solved, woohoo
kasyanovse Oct 30, 2023
461bfe8
fix tests and reproducer
kasyanovse Oct 30, 2023
eb876ac
fix problem with inds but not with workers stopping
kasyanovse Oct 31, 2023
cb94833
may be fix
kasyanovse Oct 31, 2023
8045085
small fixes
kasyanovse Oct 31, 2023
14a458b
delete tests for deleted functional
kasyanovse Oct 31, 2023
ce75592
small fixes
kasyanovse Oct 31, 2023
063826e
small fixes
kasyanovse Oct 31, 2023
5c95a71
make requested changes
kasyanovse Nov 2, 2023
7ed6107
pep8
kasyanovse Nov 2, 2023
cfe8e55
fix python 3.8 error
kasyanovse Nov 2, 2023
1a1aa61
small future extracting
kasyanovse Nov 3, 2023
cbc9f92
small fix
kasyanovse Nov 3, 2023
2112b4c
speedup wip
kasyanovse Nov 3, 2023
596cf52
wip
kasyanovse Nov 3, 2023
47d92f1
wip
kasyanovse Nov 3, 2023
aab0bcd
Fix misprint in reproducer single thread evaluation
kasyanovse Nov 8, 2023
d301ade
fix error with infinity reproducing
kasyanovse Nov 8, 2023
0b0b549
fix unstopping workers
kasyanovse Nov 8, 2023
1cea85d
add parallelization for crossover
kasyanovse Nov 8, 2023
2eb41e5
wip
kasyanovse Nov 9, 2023
7f4194c
wip
kasyanovse Nov 9, 2023
b03a979
New way to parallel population reproducing
kasyanovse Nov 9, 2023
1d40d90
Fix some problems
kasyanovse Nov 9, 2023
30d03a6
Fix error with random state handler
kasyanovse Nov 9, 2023
eedbebe
wip
kasyanovse Nov 9, 2023
bf6e283
wip
kasyanovse Nov 10, 2023
8987b87
add genetic node
kasyanovse Nov 14, 2023
6ff8db4
wip
kasyanovse Nov 15, 2023
1172caa
wip
kasyanovse Nov 15, 2023
e62fa65
wip
kasyanovse Nov 16, 2023
f68a039
wip
kasyanovse Nov 16, 2023
fe38500
wip
kasyanovse Nov 24, 2023
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
1 change: 1 addition & 0 deletions golem/core/constants.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import numpy as np

MAX_GRAPH_GEN_ATTEMPTS_PER_IND = 20
MAX_GRAPH_GEN_ATTEMPTS = 1000
MAX_TUNING_METRIC_VALUE = np.inf
MIN_TIME_FOR_TUNING_IN_SEC = 3
Expand Down
15 changes: 9 additions & 6 deletions golem/core/optimisers/genetic/evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,15 +241,18 @@ def dispatch(self, objective: ObjectiveFunction, timer: Optional[Timer] = None)

def evaluate_population(self, individuals: PopulationT) -> PopulationT:
individuals_to_evaluate, individuals_to_skip = self.split_individuals_to_evaluate(individuals)
# Evaluate individuals without valid fitness in parallel.
n_jobs = determine_n_jobs(self._n_jobs, self.logger)

parallel = Parallel(n_jobs=n_jobs, verbose=0, pre_dispatch="2*n_jobs")
# Evaluate individuals without valid fitness
eval_func = partial(self.evaluate_single, logs_initializer=Log().get_parameters())
evaluation_results = parallel(delayed(eval_func)(ind.graph, ind.uid) for ind in individuals_to_evaluate)

if len(individuals_to_evaluate) == 1 or self._n_jobs == 1:
evaluation_results = [eval_func(ind.graph, ind.uid) for ind in individuals_to_evaluate]
else:
n_jobs = determine_n_jobs(self._n_jobs, self.logger)
parallel = Parallel(n_jobs=n_jobs)
evaluation_results = parallel(delayed(eval_func)(ind.graph, ind.uid) for ind in individuals_to_evaluate)

individuals_evaluated = self.apply_evaluation_results(individuals_to_evaluate, evaluation_results)
# If there were no successful evals then try once again getting at least one,
# even if time limit was reached
successful_evals = individuals_evaluated + individuals_to_skip
self.population_evaluation_info(evaluated_pop_size=len(successful_evals),
pop_size=len(individuals))
Expand Down
59 changes: 17 additions & 42 deletions golem/core/optimisers/genetic/gp_optimizer.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
from copy import deepcopy
from random import choice
from typing import Sequence, Union, Any

from golem.core.constants import MAX_GRAPH_GEN_ATTEMPTS
from golem.core.dag.graph import Graph
from golem.core.optimisers.genetic.gp_params import GPAlgorithmParameters
from golem.core.optimisers.genetic.operators.crossover import Crossover
from golem.core.optimisers.genetic.operators.crossover import Crossover, SinglePredefinedGraphCrossover
from golem.core.optimisers.genetic.operators.elitism import Elitism
from golem.core.optimisers.genetic.operators.inheritance import Inheritance
from golem.core.optimisers.genetic.operators.mutation import Mutation
from golem.core.optimisers.genetic.operators.mutation import Mutation, SinglePredefinedGraphMutation
from golem.core.optimisers.genetic.operators.operator import PopulationT, EvaluationOperator
from golem.core.optimisers.genetic.operators.regularization import Regularization
from golem.core.optimisers.genetic.operators.reproduction import ReproductionController
Expand Down Expand Up @@ -38,13 +35,18 @@ def __init__(self,
# Define genetic operators
self.regularization = Regularization(graph_optimizer_params, graph_generation_params)
self.selection = Selection(graph_optimizer_params)
self.crossover = Crossover(graph_optimizer_params, requirements, graph_generation_params)
self.mutation = Mutation(graph_optimizer_params, requirements, graph_generation_params)
self.crossover = SinglePredefinedGraphCrossover(graph_optimizer_params, requirements, graph_generation_params)
self.mutation = SinglePredefinedGraphMutation(graph_optimizer_params, requirements, graph_generation_params)
self.inheritance = Inheritance(graph_optimizer_params, self.selection)
self.elitism = Elitism(graph_optimizer_params)
self.operators = [self.regularization, self.selection, self.crossover,
self.mutation, self.inheritance, self.elitism]
self.reproducer = ReproductionController(graph_optimizer_params, self.selection, self.mutation, self.crossover)

self.reproducer = ReproductionController(parameters=graph_optimizer_params,
selection=self.selection,
mutation=self.mutation,
crossover=self.crossover,
verifier=self.graph_generation_params.verifier)

# Define adaptive parameters
self._pop_size: PopulationSize = init_adaptive_pop_size(graph_optimizer_params, self.generations)
Expand All @@ -65,39 +67,13 @@ def _initial_population(self, evaluator: EvaluationOperator):
""" Initializes the initial population """
# Adding of initial assumptions to history as zero generation
self._update_population(evaluator(self.initial_individuals), 'initial_assumptions')
pop_size = self.graph_optimizer_params.pop_size

if len(self.initial_individuals) < pop_size:
self.initial_individuals = self._extend_population(self.initial_individuals, pop_size)
# Adding of extended population to history
self._update_population(evaluator(self.initial_individuals), 'extended_initial_assumptions')

def _extend_population(self, pop: PopulationT, target_pop_size: int) -> PopulationT:
verifier = self.graph_generation_params.verifier
extended_pop = list(pop)
pop_graphs = [ind.graph for ind in extended_pop]

# Set mutation probabilities to 1.0
initial_req = deepcopy(self.requirements)
initial_req.mutation_prob = 1.0
self.mutation.update_requirements(requirements=initial_req)

for iter_num in range(MAX_GRAPH_GEN_ATTEMPTS):
if len(extended_pop) == target_pop_size:
break
new_ind = self.mutation(choice(pop))
if new_ind:
new_graph = new_ind.graph
if new_graph not in pop_graphs and verifier(new_graph):
extended_pop.append(new_ind)
pop_graphs.append(new_graph)
else:
self.log.warning(f'Exceeded max number of attempts for extending initial graphs, stopping.'
f'Current size {len(pop)}, required {target_pop_size} graphs.')

# Reset mutation probabilities to default
self.mutation.update_requirements(requirements=self.requirements)
return extended_pop
# pop_size = self.graph_optimizer_params.pop_size
#
# if len(self.initial_individuals) < pop_size:
# self.initial_individuals += self.reproducer._reproduce(population=self.initial_individuals,
# evaluator=evaluator)
# # Adding of extended population to history
# self._update_population(self.initial_individuals, 'extended_initial_assumptions')

def _evolve_population(self, evaluator: EvaluationOperator) -> PopulationT:
""" Method realizing full evolution cycle """
Expand All @@ -120,7 +96,6 @@ def _evolve_population(self, evaluator: EvaluationOperator) -> PopulationT:
# Use some part of previous pop in the next pop
new_population = self.inheritance(self.population, new_population)
new_population = self.elitism(self.generations.best_individuals, new_population)

return new_population

def _update_requirements(self):
Expand Down
116 changes: 116 additions & 0 deletions golem/core/optimisers/genetic/gp_optimizer_new.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from typing import Sequence, Union, Any

from golem.core.dag.graph import Graph
from golem.core.optimisers.genetic.gp_params import GPAlgorithmParameters
from golem.core.optimisers.genetic.operators.crossover import Crossover, SinglePredefinedGraphCrossover
from golem.core.optimisers.genetic.operators.elitism import Elitism
from golem.core.optimisers.genetic.operators.inheritance import Inheritance
from golem.core.optimisers.genetic.operators.mutation import Mutation, SinglePredefinedGraphMutation
from golem.core.optimisers.genetic.operators.operator import PopulationT, EvaluationOperator
from golem.core.optimisers.genetic.operators.regularization import Regularization
from golem.core.optimisers.genetic.operators.reproduction import ReproductionController
from golem.core.optimisers.genetic.operators.selection import Selection
from golem.core.optimisers.genetic.parameters.graph_depth import AdaptiveGraphDepth
from golem.core.optimisers.genetic.parameters.operators_prob import init_adaptive_operators_prob
from golem.core.optimisers.genetic.parameters.population_size import init_adaptive_pop_size, PopulationSize
from golem.core.optimisers.objective.objective import Objective
from golem.core.optimisers.opt_history_objects.individual import Individual
from golem.core.optimisers.optimization_parameters import GraphRequirements
from golem.core.optimisers.optimizer import GraphGenerationParams
from golem.core.optimisers.populational_optimizer import PopulationalOptimizer


class EvoGraphOptimizer(PopulationalOptimizer):
"""
Multi-objective evolutionary graph optimizer named GPComp
"""

def __init__(self,
objective: Objective,
initial_graphs: Sequence[Union[Graph, Any]],
requirements: GraphRequirements,
graph_generation_params: GraphGenerationParams,
graph_optimizer_params: GPAlgorithmParameters):
super().__init__(objective, initial_graphs, requirements, graph_generation_params, graph_optimizer_params)
# Define genetic operators
self.regularization = Regularization(graph_optimizer_params, graph_generation_params)
self.selection = Selection(graph_optimizer_params)
self.crossover = SinglePredefinedGraphCrossover(graph_optimizer_params, requirements, graph_generation_params)
self.mutation = SinglePredefinedGraphMutation(graph_optimizer_params, requirements, graph_generation_params)
self.inheritance = Inheritance(graph_optimizer_params, self.selection)
self.elitism = Elitism(graph_optimizer_params)
self.operators = [self.regularization, self.selection, self.crossover,
self.mutation, self.inheritance, self.elitism]

self.reproducer = ReproductionController(parameters=graph_optimizer_params,
selection=self.selection,
mutation=self.mutation,
crossover=self.crossover,
verifier=self.graph_generation_params.verifier)

# Define adaptive parameters
self._pop_size: PopulationSize = init_adaptive_pop_size(graph_optimizer_params, self.generations)
self._operators_prob = init_adaptive_operators_prob(graph_optimizer_params)
self._graph_depth = AdaptiveGraphDepth(self.generations,
start_depth=requirements.start_depth,
max_depth=requirements.max_depth,
max_stagnation_gens=graph_optimizer_params.adaptive_depth_max_stagnation,
adaptive=graph_optimizer_params.adaptive_depth)

# Define initial parameters
self.requirements.max_depth = self._graph_depth.initial
self.graph_optimizer_params.pop_size = self._pop_size.initial
self.initial_individuals = [Individual(graph, metadata=requirements.static_individual_metadata)
for graph in self.initial_graphs]

def _initial_population(self, evaluator: EvaluationOperator):
""" Initializes the initial population """
# Adding of initial assumptions to history as zero generation
self._update_population(evaluator(self.initial_individuals), 'initial_assumptions')
# pop_size = self.graph_optimizer_params.pop_size
#
# if len(self.initial_individuals) < pop_size:
# self.initial_individuals += self.reproducer._reproduce(population=self.initial_individuals,
# evaluator=evaluator)
# # Adding of extended population to history
# self._update_population(self.initial_individuals, 'extended_initial_assumptions')

def _evolve_population(self, evaluator: EvaluationOperator) -> PopulationT:
""" Method realizing full evolution cycle """

# Defines adaptive changes to algorithm parameters
# like pop_size and operator probabilities
self._update_requirements()

# Regularize previous population
individuals_to_select = self.regularization(self.population, evaluator)
# Reproduce from previous pop to get next population
new_population = self.reproducer.reproduce(individuals_to_select, evaluator)

# Adaptive agent experience collection & learning
# Must be called after reproduction (that collects the new experience)
experience = self.mutation.agent_experience
experience.collect_results(new_population)
self.mutation.agent.partial_fit(experience)

# Use some part of previous pop in the next pop
new_population = self.inheritance(self.population, new_population)
new_population = self.elitism(self.generations.best_individuals, new_population)
return new_population

def _update_requirements(self):
if not self.generations.is_any_improved:
self.graph_optimizer_params.mutation_prob, self.graph_optimizer_params.crossover_prob = \
self._operators_prob.next(self.population)
self.log.info(
f'Next mutation proba: {self.graph_optimizer_params.mutation_prob}; '
f'Next crossover proba: {self.graph_optimizer_params.crossover_prob}')
self.graph_optimizer_params.pop_size = self._pop_size.next(self.population)
self.requirements.max_depth = self._graph_depth.next()
self.log.info(
f'Next population size: {self.graph_optimizer_params.pop_size}; '
f'max graph depth: {self.requirements.max_depth}')

# update requirements in operators
for operator in self.operators:
operator.update_requirements(self.graph_optimizer_params, self.requirements)
3 changes: 3 additions & 0 deletions golem/core/optimisers/genetic/gp_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ class GPAlgorithmParameters(AlgorithmParameters):
mutation_prob: float = 0.8
variable_mutation_num: bool = True
max_num_of_operator_attempts: int = 100
max_num_of_crossover_reproducer_attempts: int = 1
max_num_of_mutation_reproducer_attempts: int = 2
mutation_attempts_per_each_crossover_reproducer: int = 8
mutation_strength: MutationStrengthEnum = MutationStrengthEnum.mean
min_pop_size_with_elitism: int = 5
required_valid_ratio: float = 0.9
Expand Down
44 changes: 36 additions & 8 deletions golem/core/optimisers/genetic/operators/crossover.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
from itertools import chain
from math import ceil
from random import choice, random, sample
from typing import Callable, Union, Iterable, Tuple, TYPE_CHECKING
from typing import Callable, Union, Iterable, Tuple, TYPE_CHECKING, Optional, List

from joblib import Parallel, delayed

from golem.core.adapter import register_native
from golem.core.dag.graph_utils import nodes_from_layer, node_depth
Expand Down Expand Up @@ -40,12 +42,13 @@ def __init__(self,
self.graph_generation_params = graph_generation_params

def __call__(self, population: PopulationT) -> PopulationT:
if len(population) == 1:
new_population = population
if len(population) > 1:
kasyanovse marked this conversation as resolved.
Show resolved Hide resolved
with Parallel(n_jobs=self.requirements.n_jobs) as parallel:
new_population = parallel(delayed(self._crossover)(ind_1, ind_2)
for ind_1, ind_2 in Crossover.crossover_parents_selection(population))
new_population = list(chain(*new_population))
else:
new_population = []
for ind_1, ind_2 in Crossover.crossover_parents_selection(population):
new_population += self._crossover(ind_1, ind_2)
new_population = population[:]
kasyanovse marked this conversation as resolved.
Show resolved Hide resolved
return new_population

@staticmethod
Expand Down Expand Up @@ -93,19 +96,44 @@ def _crossover_by_type(self, crossover_type: CrossoverTypesEnum) -> CrossoverCal
raise ValueError(f'Required crossover type is not found: {crossover_type}')

def _get_individuals(self, new_graphs: Tuple[OptGraph, OptGraph], parent_individuals: Tuple[Individual, Individual],
crossover_type: Union[CrossoverTypesEnum, Callable]) -> Tuple[Individual, Individual]:
crossover_type: Union[CrossoverTypesEnum, Callable], **kwargs) -> Tuple[Individual, Individual]:
operator = ParentOperator(type_='crossover',
operators=str(crossover_type),
parent_individuals=parent_individuals)
metadata = self.requirements.static_individual_metadata
return tuple(Individual(graph, operator, metadata=metadata) for graph in new_graphs)
return tuple(Individual(graph, operator, metadata=metadata, **kwargs) for graph in new_graphs)

def _will_crossover_be_applied(self, graph_first, graph_second, crossover_type) -> bool:
return not (graph_first is graph_second or
random() > self.parameters.crossover_prob or
crossover_type is CrossoverTypesEnum.none)


class SinglePredefinedGraphCrossover(Crossover):
""" Crossover that tries to create new graph/graphs from only two graphs
in one attempt without any checks
"""
def __call__(self,
individuals: List[Individual],
crossover_type: Optional[CrossoverTypesEnum] = None) -> Tuple[List[Individual], CrossoverTypesEnum]:
if len(individuals) < 2:
raise ValueError(f"Crossover needs 2 individuals, get {len(individuals)}")
elif len(individuals) > 2:
individuals = sample(individuals, 2)
graphs = [deepcopy(ind.graph) for ind in individuals]

crossover_type = crossover_type or choice(self.parameters.crossover_types)
if crossover_type is CrossoverTypesEnum.none:
return individuals, crossover_type
crossover_func = self._get_crossover_function(crossover_type)

new_graphs = crossover_func(*graphs, max_depth=self.requirements.max_depth)
new_individuals = self._get_individuals(new_graphs=new_graphs,
parent_individuals=individuals,
crossover_type=crossover_type)
return new_individuals, crossover_type


@register_native
def subtree_crossover(graph_1: OptGraph, graph_2: OptGraph,
max_depth: int, inplace: bool = True) -> Tuple[OptGraph, OptGraph]:
Expand Down
Loading
Loading