diff --git a/autotm/algorithms_for_tuning/genetic_algorithm/ga.py b/autotm/algorithms_for_tuning/genetic_algorithm/ga.py index 605c016..6599701 100644 --- a/autotm/algorithms_for_tuning/genetic_algorithm/ga.py +++ b/autotm/algorithms_for_tuning/genetic_algorithm/ga.py @@ -66,7 +66,6 @@ def __init__( crossover_type="blend_crossover", selection_type="fitness_prop", elem_cross_prob=0.2, - num_fitness_evaluations: Optional[int] = 500, early_stopping_iterations: Optional[int] = 500, best_proc=0.3, alpha=None, @@ -123,8 +122,6 @@ def __init__( self.selection = selection(selection_type) self.elem_cross_prob = elem_cross_prob self.alpha = alpha - self.evaluations_counter = 0 - self.num_fitness_evaluations = num_fitness_evaluations self.early_stopping_iterations = early_stopping_iterations self.fitness_obj_type = fitness_obj_type self.best_proc = best_proc @@ -278,7 +275,9 @@ def apply_nelder_mead(self, starting_points_set, num_gen, num_iterations=2): return new_population def run(self, verbose=False, visualize_results=False) -> Individual: - self.evaluations_counter = 0 + assert self.fitness_estimator.evaluations_counter == 0, \ + "Fitness estimator has non-zero evaluations count and cannot be reused" + ftime = str(int(time.time())) # os.makedirs(LOG_FILE_PATH, exist_ok=True) @@ -288,7 +287,7 @@ def run(self, verbose=False, visualize_results=False) -> Individual: logger.info( f"ALGORITHM PARAMS number of individuals {self.num_individuals}; " f"number of fitness evals " - f"{self.num_fitness_evaluations if self.num_fitness_evaluations else 'unlimited'}; " + f"{self.fitness_estimator.num_fitness_evaluations if self.fitness_estimator.num_fitness_evaluations else 'unlimited'}; " f"number of early stopping iterations " f"{self.early_stopping_iterations if self.early_stopping_iterations else 'unlimited'}; " f"crossover prob {self.elem_cross_prob}" @@ -310,7 +309,10 @@ def run(self, verbose=False, visualize_results=False) -> Individual: self._sort_population(population) if self.statistics_collector is not None: - self.statistics_collector.log_iteration(self.evaluations_counter, population[0].fitness_value) + self.statistics_collector.log_iteration( + self.fitness_estimator.evaluations_counter, + population[0].fitness_value + ) pairs_generator = self.selection( population=population, best_proc=self.best_proc, @@ -401,7 +403,7 @@ def run(self, verbose=False, visualize_results=False) -> Individual: ) population[i] = elem - if self.num_fitness_evaluations and self.evaluations_counter >= self.num_fitness_evaluations: + if self.fitness_estimator.num_fitness_evaluations and self.fitness_estimator.evaluations_counter >= self.fitness_estimator.num_fitness_evaluations: self.metric_collector.save_fitness( generation=ii, params=[i.params for i in population], @@ -483,7 +485,10 @@ def run(self, verbose=False, visualize_results=False) -> Individual: self.metric_collector.save_trace() if self.statistics_collector is not None: - self.statistics_collector.log_iteration(self.evaluations_counter, population[0].fitness_value) + self.statistics_collector.log_iteration( + self.fitness_estimator.evaluations_counter, + population[0].fitness_value + ) logger.info(f"Y: {y}") best_individual = population[0] ind = log_best_solution(self.ibuilder, best_individual, alg_args=" ".join(sys.argv)) diff --git a/autotm/fitness/estimator.py b/autotm/fitness/estimator.py index 6d421fa..6f3ad0a 100644 --- a/autotm/fitness/estimator.py +++ b/autotm/fitness/estimator.py @@ -18,38 +18,37 @@ logger = logging.getLogger(__name__) -class FitnessEstimator(ABC): - @abstractmethod - def fit(self, iter_num: int) -> None: - ... - - @abstractmethod - def estimate(self, iter_num: int, population: List[Individual]) -> List[Individual]: - ... +class FitnessEstimator: + def __init__(self, num_fitness_evaluations: Optional[int] = None, statistics_collector: Optional[StatisticsCollector] = None): + self._num_fitness_evaluations = num_fitness_evaluations + self._evaluations_counter = 0 + self._statistics_collector = statistics_collector + super().__init__() + @property + def num_fitness_evaluations(self) -> Optional[int]: + return self._num_fitness_evaluations -class EstimationLimitedFitnessEstimator(FitnessEstimator): - def __init__(self, num_fitness_evaluations: int, statistics_collector: Optional[StatisticsCollector] = None): - self.num_fitness_evaluations = num_fitness_evaluations - self.evaluations_counter = 0 - self.statistics_collector = statistics_collector - super().__init__() + @property + def evaluations_counter(self) -> int: + return self._evaluations_counter + @abstractmethod def fit(self, iter_num: int) -> None: - pass + ... def estimate(self, iter_num: int, population: List[Individual]) -> List[Individual]: evaluated = [individual for individual in population if individual.dto.fitness_value is not None] not_evaluated = [individual for individual in population if individual.dto.fitness_value is None] - evaluations_limit = max(0, self.num_fitness_evaluations - self.evaluations_counter) \ - if self.num_fitness_evaluations else len(not_evaluated) + evaluations_limit = max(0, self._num_fitness_evaluations - self._evaluations_counter) \ + if self._num_fitness_evaluations else len(not_evaluated) if len(not_evaluated) > evaluations_limit: not_evaluated = not_evaluated[:evaluations_limit] - self.evaluations_counter += len(not_evaluated) + self._evaluations_counter += len(not_evaluated) new_evaluated = self._estimate(iter_num, not_evaluated) - if self.statistics_collector: + if self._statistics_collector: for individual in new_evaluated: - self.statistics_collector.log_individual(individual) + self._statistics_collector.log_individual(individual) return evaluated + new_evaluated @abstractmethod