diff --git a/examples/example_2.py b/examples/example_2.py index ff043e33..8f5579ce 100644 --- a/examples/example_2.py +++ b/examples/example_2.py @@ -10,7 +10,6 @@ from utils import log_scores - if __name__ == "__main__": random_state = 42 diff --git a/examples/example_3.py b/examples/example_3.py index ae674ad9..5efe77e6 100644 --- a/examples/example_3.py +++ b/examples/example_3.py @@ -10,7 +10,6 @@ from utils import log_scores - if __name__ == "__main__": random_state = 42 diff --git a/examples/example_4.py b/examples/example_4.py index 7948b39f..cdf7bac4 100644 --- a/examples/example_4.py +++ b/examples/example_4.py @@ -12,7 +12,6 @@ from utils import log_scores - if __name__ == "__main__": random_state = 42 diff --git a/suprb/optimizer/rule/origin.py b/suprb/optimizer/rule/origin.py index 505f39b2..b8f95aea 100644 --- a/suprb/optimizer/rule/origin.py +++ b/suprb/optimizer/rule/origin.py @@ -61,7 +61,7 @@ def __call__( subgroup = elitist.subpopulation if elitist is not None and self.use_elitist else pool - if subgroup: + if subgroup and elitist is not None: weights = self._calculate_weights( subgroup=subgroup, X=X, elitist=elitist, random_state=random_state, **kwargs ) @@ -69,7 +69,7 @@ def __call__( # If all weights are zero, no bias is needed probabilities = weights / weights_sum if weights_sum != 0 else None else: - # No bias needed when no rule exists + # No bias needed when no rule exists or no elitist has been fit on them probabilities = None indices = random_state.choice(np.arange(len(X)), n_rules, p=probabilities) diff --git a/suprb/suprb.py b/suprb/suprb.py index e8860599..1652a906 100644 --- a/suprb/suprb.py +++ b/suprb/suprb.py @@ -46,7 +46,9 @@ def _more_tags(self): n_iter: int Iterations the LCS will perform. n_initial_rules: int - Number of :class:`Rule`s generated before the first step. + Number of :class:`Rule`s generated before the first step. Note that + n_initial_rules + n_rules will be created before the first elitist is + selected using solution composition. n_rules: int Number of :class:`Rule`s generated in the every step. random_state : int, RandomState/Generator instance or None, default=None @@ -79,10 +81,11 @@ def _more_tags(self): random_state_: np.random.Generator rule_discovery_: RuleDiscovery - rule_discovery_seeds_: list[int] + rule_discovery_seeds_: list[np.random.SeedSequence] + initial_rule_seeds_: list[np.random.SeedSequence] solution_composition_: SolutionComposition - solution_composition_seeds_: list[int] + solution_composition_seeds_: list[np.random.SeedSequence] matching_type_: MatchingFunction @@ -169,9 +172,10 @@ def fit(self, X: np.ndarray, y: np.ndarray, cleanup=False): # Random state self.random_state_ = check_random_state(self.random_state) - seeds = np.random.SeedSequence(self.random_state).spawn(self.n_iter * 2) - self.rule_discovery_seeds_ = seeds[::2] + seeds = np.random.SeedSequence(self.random_state).spawn(self.n_iter * 2 + 1) + self.rule_discovery_seeds_ = seeds[:-1:2] self.solution_composition_seeds_ = seeds[1::2] + self.initial_rule_seeds_ = seeds[-1:] # Initialise components self.pool_ = [] @@ -195,17 +199,17 @@ def fit(self, X: np.ndarray, y: np.ndarray, cleanup=False): # Fill population before first step if self.n_initial_rules > 0: - if self._catch_errors(self._discover_rules, X, y, self.n_initial_rules): + if self._catch_errors(self._discover_rules, X, y, initial=True): return self # Main loop for self.step_ in range(self.n_iter): # Insert new rules into population - if self._catch_errors(self._discover_rules, X, y, self.n_rules): + if self._catch_errors(self._discover_rules, X, y, initial=False): return self # Optimize solutions - if self._catch_errors(self._compose_solution, X, y, False): + if self._catch_errors(self._compose_solution, X, y): return self # Log Iteration @@ -227,13 +231,9 @@ def fit(self, X: np.ndarray, y: np.ndarray, cleanup=False): return self - def _catch_errors(self, func, X, y, n_rules): + def _catch_errors(self, func, X, y, **kwargs): try: - if not n_rules: - func(X, y) - else: - func(X, y, n_rules) - + func(X, y, **kwargs) return False except ValueError as e: # Capture the full traceback and print it @@ -250,16 +250,21 @@ def _catch_errors(self, func, X, y, n_rules): self.is_error_ = True return True - def _discover_rules(self, X: np.ndarray, y: np.ndarray, n_rules: int): + def _discover_rules(self, X: np.ndarray, y: np.ndarray, initial: bool): """Performs the rule discovery / rule generation (RG) process.""" + n_rules = self.n_initial_rules if initial else self.n_rules + self._log_to_stdout(f"Generating {n_rules} rules", priority=4) # Update the current elitist self.rule_discovery_.elitist_ = self.solution_composition_.elitist() # Update the random state - self.rule_discovery_.random_state = self.rule_discovery_seeds_[self.step_] + if initial: + self.rule_discovery_.random_state = self.initial_rule_seeds_[0] + else: + self.rule_discovery_.random_state = self.rule_discovery_seeds_[self.step_] # Generate new rules new_rules = self.rule_discovery_.optimize(X, y, n_rules=n_rules) diff --git a/tests/test_suprb.py b/tests/test_suprb.py index b8b8d191..5c660595 100644 --- a/tests/test_suprb.py +++ b/tests/test_suprb.py @@ -47,6 +47,21 @@ def test_check_estimator(self): check_estimator(estimator) + def test_check_initial_rules(self): + estimator = suprb.SupRB( + n_iter=4, + n_initial_rules=4, + rule_discovery=ES1xLambda(n_iter=4, lmbda=1, delay=2), + solution_composition=suprb.optimizer.solution.ga.GeneticAlgorithm(n_iter=2, population_size=2), + logger=suprb.logging.stdout.StdoutLogger(), + verbose=10, + ) + + X, y = _regression_dataset() + estimator.fit(X, y) + + check_estimator(estimator) + def test_early_stopping(self): estimator = suprb.SupRB( n_iter=1,