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

New search space def #139

Merged
merged 12 commits into from
Jul 11, 2024
507 changes: 481 additions & 26 deletions Tutorial/Example_Search_Spaces/imputation.ipynb

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def calculate_version():

''',
zip_safe=True,
install_requires=['numpy>=1.26.4',
install_requires=['numpy==1.26.4',
'scipy>=1.3.1',
'scikit-learn>=1.3.0',
'update_checker>=0.16',
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ commands = flake8 tpot2
basepython = python3.10
deps =
-r{toxinidir}/requirements_dev.txt
commands = mypy tpot2
commands = mypy tpot2
19 changes: 10 additions & 9 deletions tpot2/evolvers/steady_state_evolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,17 +299,18 @@ def optimize(self):
eval_error = "INVALID"
else: #if future is not done

#check if the future has been running for too long, cancel the future
if time.time() - submitted_futures[completed_future]["time"] > self.max_eval_time_seconds*1.25:
completed_future.cancel()
if self.max_eval_time_seconds is not None:
#check if the future has been running for too long, cancel the future
if time.time() - submitted_futures[completed_future]["time"] > self.max_eval_time_seconds*1.25:
completed_future.cancel()

if self.verbose >= 4:
print(f'WARNING AN INDIVIDUAL TIMED OUT (Fallback): \n {submitted_futures[completed_future]} \n')
if self.verbose >= 4:
print(f'WARNING AN INDIVIDUAL TIMED OUT (Fallback): \n {submitted_futures[completed_future]} \n')

scores = [np.nan for _ in range(len(self.objective_names))]
eval_error = "TIMEOUT"
else:
continue #otherwise, continue to next future
scores = [np.nan for _ in range(len(self.objective_names))]
eval_error = "TIMEOUT"
else:
continue #otherwise, continue to next future



Expand Down
22 changes: 15 additions & 7 deletions tpot2/search_spaces/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,31 @@




class SklearnIndividual(tpot2.BaseIndividual):

def __init_subclass__(cls):
cls.crossover = cls.validate_same_type(cls.crossover)


def __init__(self,) -> None:
super().__init__()

def mutate(self, rng=None):
return

@final
def crossover(self, other, rng=None, **kwargs):
if not isinstance(other, type(self)):
return False
return self._crossover(other, rng=rng, **kwargs)
return

@abstractmethod
def _crossover(self, other, rng=None):
return
@final
def validate_same_type(func):

def wrapper(self, other, rng=None, **kwargs):
if not isinstance(other, type(self)):
return False
return func(self, other, rng=None, **kwargs)

return wrapper

def export_pipeline(self) -> BaseEstimator:
return
Expand Down
2 changes: 1 addition & 1 deletion tpot2/search_spaces/nodes/estimator_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def mutate(self, rng=None):
self.check_hyperparameters_for_None()
return True

def _crossover(self, other, rng=None):
def crossover(self, other, rng=None):
if isinstance(self.space, dict):
return False

Expand Down
59 changes: 0 additions & 59 deletions tpot2/search_spaces/nodes/estimator_node_custom_sampler.py

This file was deleted.

70 changes: 0 additions & 70 deletions tpot2/search_spaces/nodes/estimator_node_simple.py

This file was deleted.

2 changes: 1 addition & 1 deletion tpot2/search_spaces/nodes/fss_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def mutate(self, rng=None):
self.sel_subset = self.subset_dict[self.selected_subset_name]


def _crossover(self, other, rng=None):
def crossover(self, other, rng=None):
self.selected_subset_name = other.selected_subset_name
self.sel_subset = other.sel_subset

Expand Down
10 changes: 5 additions & 5 deletions tpot2/search_spaces/nodes/genetic_feature_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,16 @@ def __init__( self,
start_p=0.2,
mutation_rate = 0.5,
crossover_rate = 0.5,
mutation_rate_rate = 0,
crossover_rate_rate = 0,
rng=None,
):

self.start_p = start_p
self.mutation_rate = mutation_rate
self.crossover_rate = crossover_rate
self.mutation_rate_rate = mutation_rate_rate
self.crossover_rate_rate = crossover_rate_rate
self.mutation_rate_rate = 0
self.crossover_rate_rate = 0



rng = np.random.default_rng(rng)

Expand Down Expand Up @@ -69,7 +69,7 @@ def mutate(self, rng=None):

return rng.choice(self.mutation_list)(rng)

def _crossover(self, other, rng=None):
def crossover(self, other, rng=None):
rng = np.random.default_rng(rng)

if rng.uniform() < self.crossover_rate_rate:
Expand Down
2 changes: 1 addition & 1 deletion tpot2/search_spaces/pipelines/choice.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def _mutate_select_new_node(self, rng=None):
def _mutate_node(self, rng=None):
return self.node.mutate(rng)

def _crossover(self, other, rng=None):
def crossover(self, other, rng=None):
return self.node.crossover(other.node, rng)

def export_pipeline(self):
Expand Down
28 changes: 14 additions & 14 deletions tpot2/search_spaces/pipelines/dynamic_linear.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def __init__(self, search_space : SklearnIndividualGenerator, max_length: int ,
self.pipeline = self._generate_pipeline(rng)

def _generate_pipeline(self, rng=None):
rng = np.random.default_rng()
rng = np.random.default_rng(rng)
pipeline = []
length = rng.integers(self.min_length, self.max_length)
length = min(length, 3)
Expand All @@ -37,7 +37,7 @@ def _generate_pipeline(self, rng=None):


def mutate(self, rng=None):
rng = np.random.default_rng()
rng = np.random.default_rng(rng)
options = []
if len(self.pipeline) > self.min_length:
options.append(self._mutate_remove_node)
Expand All @@ -48,28 +48,28 @@ def mutate(self, rng=None):
return rng.choice(options)(rng)

def _mutate_add_node(self, rng=None):
rng = np.random.default_rng()
rng = np.random.default_rng(rng)
new_node = self.search_space.generate(rng)
idx = rng.integers(len(self.pipeline))
self.pipeline.insert(idx, new_node)

def _mutate_remove_node(self, rng=None):
rng = np.random.default_rng()
rng = np.random.default_rng(rng)
idx = rng.integers(len(self.pipeline))
self.pipeline.pop(idx)

def _mutate_step(self, rng=None):
#choose a random step in the pipeline and mutate it
rng = np.random.default_rng()
rng = np.random.default_rng(rng)
step = rng.choice(self.pipeline)
return step.mutate(rng)


def _crossover(self, other, rng=None):
def crossover(self, other, rng=None):
#swap a random step in the pipeline with the corresponding step in the other pipeline

rng = np.random.default_rng()
cx_funcs = [self._crossover_swap_random_steps, self._crossover_inner_step]
rng = np.random.default_rng(rng)
cx_funcs = [self._crossover_swap_multiple_nodes, self._crossover_node]

rng.shuffle(cx_funcs)
for cx_func in cx_funcs:
Expand All @@ -78,8 +78,8 @@ def _crossover(self, other, rng=None):

return False

def _crossover_swap_random_steps(self, other, rng):
rng = np.random.default_rng()
def _crossover_swap_multiple_nodes(self, other, rng):
rng = np.random.default_rng(rng)

max_steps = int(min(len(self.pipeline), len(other.pipeline))/2)
max_steps = max(max_steps, 1)
Expand All @@ -99,21 +99,21 @@ def _crossover_swap_random_steps(self, other, rng):

return True

def _crossover_swap_step(self, other, rng):
def _crossover_swap_node(self, other, rng):
if len(self.pipeline) != len(other.pipeline):
return False

if len(self.pipeline) < 2:
return False

rng = np.random.default_rng()
rng = np.random.default_rng(rng)
idx = rng.integers(1,len(self.pipeline))

self.pipeline[idx], other.pipeline[idx] = other.pipeline[idx], self.pipeline[idx]
return True

def _crossover_inner_step(self, other, rng):
rng = np.random.default_rng()
def _crossover_node(self, other, rng):
rng = np.random.default_rng(rng)

pipeline1_indexes= list(range(len(self.pipeline)))
pipeline2_indexes= list(range(len(other.pipeline)))
Expand Down
Loading
Loading