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 45 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 = 5
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
43 changes: 9 additions & 34 deletions golem/core/optimisers/genetic/gp_optimizer.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
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
Expand Down Expand Up @@ -44,7 +41,12 @@ def __init__(self,
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 @@ -68,36 +70,10 @@ def _initial_population(self, evaluator: EvaluationOperator):
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)
self.initial_individuals += self.reproducer._mutate_over_population(population=self.initial_individuals,
evaluator=evaluator)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Стоит _mutate_over_population сделать public, так как он вне ReproductionController используется

# 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
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
1 change: 1 addition & 0 deletions golem/core/optimisers/genetic/gp_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class GPAlgorithmParameters(AlgorithmParameters):
mutation_prob: float = 0.8
variable_mutation_num: bool = True
max_num_of_operator_attempts: int = 100
max_num_of_mutation_attempts: int = 3
Copy link
Collaborator

@YamLyubov YamLyubov Nov 3, 2023

Choose a reason for hiding this comment

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

Теперь получается есть два парметра, для которых не совсем понятно, как и где они применяются и для чего нужны.

mutation_strength: MutationStrengthEnum = MutationStrengthEnum.mean
min_pop_size_with_elitism: int = 5
required_valid_ratio: float = 0.9
Expand Down
13 changes: 8 additions & 5 deletions golem/core/optimisers/genetic/operators/crossover.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from random import choice, random, sample
from typing import Callable, Union, Iterable, Tuple, TYPE_CHECKING

from joblib import Parallel, delayed

from golem.core.adapter import register_native
from golem.core.dag.graph_utils import nodes_from_layer, node_depth
from golem.core.optimisers.genetic.gp_operators import equivalent_subtree, replace_subtrees
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
26 changes: 21 additions & 5 deletions golem/core/optimisers/genetic/operators/mutation.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
if TYPE_CHECKING:
from golem.core.optimisers.genetic.gp_params import GPAlgorithmParameters

MutationType = Union[MutationTypesEnum, Callable]
MutationFunc = Callable[[Graph, GraphRequirements, GraphGenerationParams, AlgorithmParameters], Graph]
MutationIdType = Hashable
MutationRepo = Mapping[MutationIdType, MutationFunc]
Expand Down Expand Up @@ -81,11 +82,11 @@ def __call__(self, population: Union[Individual, PopulationT]) -> Union[Individu
if isinstance(population, Individual):
population = [population]

final_population, mutations_applied, application_attempts = tuple(zip(*map(self._mutation, population)))

# drop individuals to which mutations could not be applied
final_population = [ind for ind, init_ind, attempt in zip(final_population, population, application_attempts)
if not attempt or ind.graph != init_ind.graph]
final_population = []
kasyanovse marked this conversation as resolved.
Show resolved Hide resolved
for individual in population:
new_ind, _, applied = self._mutation(individual)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Вроде можно убрать возвращение mutations_applied в _mutation(). Не нашла, чтобы это где-то использовалось

if not applied or new_ind.graph != individual.graph:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Понимаю, что раньше такое же условие было, но я бы его переформулировала как applied and new_ind.graph == individual.graph. Мне кажется, так понятнее - мутация была применена, но граф не изменился

final_population.append(new_ind)

if len(population) == 1:
return final_population[0] if final_population else final_population
Expand Down Expand Up @@ -160,3 +161,18 @@ def _get_mutation_func(self, mutation_type: Union[MutationTypesEnum, Callable])
mutation_func = self._mutations_repo[mutation_type]
adapted_mutation_func = self.graph_generation_params.adapter.adapt_func(mutation_func)
return adapted_mutation_func


class SinglePredefinedMutation(Mutation):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Не совсем понимаю, зачем нужен этот класс. Только он используется в ReproductionController. По факту получается, что код Mutation вообще не используется

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Используется self._get_mutation_func(mutation_type). Вообще, я согласен. Думал полностью перенести весь код SinglePredefinedMutation в ReproductionController, но так получается совсем некрасиво. Хотя, казалось бы, куда уже дальше))

def __call__(self, individual: Individual, mutation_type: MutationType) -> Individual:
new_graph = deepcopy(individual.graph)
mutation_func = self._get_mutation_func(mutation_type)

new_graph = mutation_func(new_graph, requirements=self.requirements,
graph_gen_params=self.graph_generation_params,
parameters=self.parameters)

parent_operator = ParentOperator(type_='mutation', operators=mutation_type, parent_individuals=individual)
individual = Individual(new_graph, parent_operator,
metadata=self.requirements.static_individual_metadata)
return individual, mutation_type
1 change: 1 addition & 0 deletions golem/core/optimisers/genetic/operators/regularization.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def __call__(self, population: PopulationT, evaluator: EvaluationOperator) -> Po
raise ValueError(f'Required regularization type not found: {regularization_type}')

def _decremental_regularization(self, population: PopulationT, evaluator: EvaluationOperator) -> PopulationT:
# TODO: do it in parallel if it can be done
size = self.parameters.pop_size
additional_inds = []
prev_nodes_ids = set()
Expand Down
Loading
Loading