Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 0 additions & 1 deletion examples/example_2.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

from utils import log_scores


if __name__ == "__main__":
random_state = 42

Expand Down
1 change: 0 additions & 1 deletion examples/example_3.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

from utils import log_scores


if __name__ == "__main__":
random_state = 42

Expand Down
1 change: 0 additions & 1 deletion examples/example_4.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

from utils import log_scores


if __name__ == "__main__":
random_state = 42

Expand Down
4 changes: 2 additions & 2 deletions suprb/optimizer/rule/origin.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@ 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
)
weights_sum = np.sum(weights)
# 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)
Expand Down
37 changes: 21 additions & 16 deletions suprb/suprb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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_ = []
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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)
Expand Down
15 changes: 15 additions & 0 deletions tests/test_suprb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading