From 6aa35c1f2cc535a89b2d5774376fed1f0dcc2c88 Mon Sep 17 00:00:00 2001 From: Roman Sraj Date: Wed, 12 Oct 2022 12:50:06 +0200 Subject: [PATCH 1/9] Add adaptive mutation for ES --- suprb/optimizer/rule/es/es.py | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/suprb/optimizer/rule/es/es.py b/suprb/optimizer/rule/es/es.py index dfc82a41..554235b5 100644 --- a/suprb/optimizer/rule/es/es.py +++ b/suprb/optimizer/rule/es/es.py @@ -8,7 +8,7 @@ from suprb.rule import Rule, RuleInit from suprb.rule.initialization import MeanInit from suprb.utils import RandomState -from ..mutation import RuleMutation, HalfnormIncrease +from ..mutation import RuleMutation, HalfnormIncrease, UniformIncrease, Normal from ..selection import RuleSelection, Fittest from .. import RuleAcceptance, RuleConstraint from ..acceptance import Variance @@ -17,6 +17,29 @@ from ..origin import Matching, RuleOriginGeneration +class AdaptMutation: + def __init__(self, mutation: RuleMutation): + self.mutation = mutation + self.best_elitist_fitness = 0 + self.number_of_worse_iterations = 0 + self.worse_iteration_tolerance = 5 + self.adapt_mutation = isinstance(self.mutation, HalfnormIncrease) or isinstance(self.mutation, UniformIncrease) + + def __call__(self, elitist_fitness: float): + if self.adapt_mutation: + if elitist_fitness < self.best_elitist_fitness: + self.number_of_worse_iterations += 1 + + if self.number_of_worse_iterations > self.worse_iteration_tolerance: + self.mutation = Normal(matching_type=self.mutation.matching_type, sigma=self.mutation.sigma) + self.adapt_mutation = False + else: + self.number_of_worse_iterations = 0 + self.best_elitist_fitness = elitist_fitness + + return self.mutation + + class ES1xLambda(ParallelSingleRuleGeneration): """ The 1xLambda Evolutionary Strategy, where x is in {,+&}. @@ -86,10 +109,9 @@ def __init__(self, warnings.warn("',' operator and HalfnormIncrease mutation lead to collapsing populations") def _optimize(self, X: np.ndarray, y: np.ndarray, initial_rule: Rule, random_state: RandomState) -> Optional[Rule]: - elitist = initial_rule - elitists = deque(maxlen=self.delay) + adapt_mutation = AdaptMutation(self.mutation) # Main iteration for iteration in range(self.n_iter): @@ -121,4 +143,6 @@ def _optimize(self, X: np.ndarray, y: np.ndarray, initial_rule: Rule, random_sta elitist = elitists[0] break + self.mutation = adapt_mutation(elitist.fitness_) + return elitist From 47134e85e6c18627342823942e57ae824ec6f43d Mon Sep 17 00:00:00 2001 From: Roman Sraj Date: Wed, 12 Oct 2022 19:07:15 +0200 Subject: [PATCH 2/9] Move adaptive mutation logic into mutation.py --- suprb/optimizer/rule/es/es.py | 35 ++++---------- suprb/optimizer/rule/mutation.py | 81 ++++++++++++++++++++++---------- tests/test_suprb.py | 81 +++++++++++++++++++++++++++----- 3 files changed, 133 insertions(+), 64 deletions(-) diff --git a/suprb/optimizer/rule/es/es.py b/suprb/optimizer/rule/es/es.py index 554235b5..019ce73d 100644 --- a/suprb/optimizer/rule/es/es.py +++ b/suprb/optimizer/rule/es/es.py @@ -1,3 +1,4 @@ +from copy import deepcopy import warnings from collections import deque from typing import Optional @@ -8,7 +9,7 @@ from suprb.rule import Rule, RuleInit from suprb.rule.initialization import MeanInit from suprb.utils import RandomState -from ..mutation import RuleMutation, HalfnormIncrease, UniformIncrease, Normal +from ..mutation import RuleMutation, HalfnormIncrease from ..selection import RuleSelection, Fittest from .. import RuleAcceptance, RuleConstraint from ..acceptance import Variance @@ -17,29 +18,6 @@ from ..origin import Matching, RuleOriginGeneration -class AdaptMutation: - def __init__(self, mutation: RuleMutation): - self.mutation = mutation - self.best_elitist_fitness = 0 - self.number_of_worse_iterations = 0 - self.worse_iteration_tolerance = 5 - self.adapt_mutation = isinstance(self.mutation, HalfnormIncrease) or isinstance(self.mutation, UniformIncrease) - - def __call__(self, elitist_fitness: float): - if self.adapt_mutation: - if elitist_fitness < self.best_elitist_fitness: - self.number_of_worse_iterations += 1 - - if self.number_of_worse_iterations > self.worse_iteration_tolerance: - self.mutation = Normal(matching_type=self.mutation.matching_type, sigma=self.mutation.sigma) - self.adapt_mutation = False - else: - self.number_of_worse_iterations = 0 - self.best_elitist_fitness = elitist_fitness - - return self.mutation - - class ES1xLambda(ParallelSingleRuleGeneration): """ The 1xLambda Evolutionary Strategy, where x is in {,+&}. @@ -111,14 +89,14 @@ def __init__(self, def _optimize(self, X: np.ndarray, y: np.ndarray, initial_rule: Rule, random_state: RandomState) -> Optional[Rule]: elitist = initial_rule elitists = deque(maxlen=self.delay) - adapt_mutation = AdaptMutation(self.mutation) + mutation = deepcopy(self.mutation) # Main iteration for iteration in range(self.n_iter): elitists.append(elitist) # Generate, fit and evaluate lambda children - children = [self.constraint(self.mutation(elitist, random_state=random_state)) + children = [self.constraint(mutation(elitist, random_state=random_state)) .fit(X, y) for _ in range(self.lmbda)] # Filter children that do not match any data samples @@ -143,6 +121,9 @@ def _optimize(self, X: np.ndarray, y: np.ndarray, initial_rule: Rule, random_sta elitist = elitists[0] break - self.mutation = adapt_mutation(elitist.fitness_) + mutation = mutation.adapt(elitist.fitness_) + + # self.mutation = adapt_mutation(elitist.fitness_) + print(iteration, type(mutation), elitist.match.bounds, elitist.fitness_) return elitist diff --git a/suprb/optimizer/rule/mutation.py b/suprb/optimizer/rule/mutation.py index 306bc1ab..4b7e4a04 100644 --- a/suprb/optimizer/rule/mutation.py +++ b/suprb/optimizer/rule/mutation.py @@ -15,11 +15,18 @@ class RuleMutation(GenerationOperator): def __init__(self, matching_type: MatchingFunction = None, - sigma: Union[float, np.ndarray] = 0.1): + sigma: Union[float, np.ndarray] = 0.1, + adapt_mutation: bool = False): super().__init__(matching_type=matching_type) self.sigma = sigma - def __call__(self, rule: Rule, random_state: RandomState) -> Rule: + # Parameters necessery for adapting the mutation + self.best_elitist_fitness = 0 + self.number_of_worse_iterations = 0 + self.worse_iteration_tolerance = 5 + self.adapt_mutation = adapt_mutation + + def __call__(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False) -> Rule: # Create copy of the rule mutated_rule = rule.clone() @@ -28,9 +35,22 @@ def __call__(self, rule: Rule, random_state: RandomState) -> Rule: return mutated_rule - def execute(self, rule: Rule, random_state: RandomState): + def execute(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): pass + def adapt(self, elitist_fitness: float): + if self.adapt_mutation: + if elitist_fitness < self.best_elitist_fitness: + self.number_of_worse_iterations += 1 + + if self.number_of_worse_iterations > self.worse_iteration_tolerance: + return Normal(matching_type=self.matching_type, sigma=self.sigma, adapt_mutation=True) + else: + self.number_of_worse_iterations = 0 + self.best_elitist_fitness = elitist_fitness + + return self + class SigmaRange(RuleMutation): """ Draws the sigma used for another mutation from uniform distribution, low to high. @@ -46,7 +66,7 @@ def __init__(self, mutation: RuleMutation = None, low: float = 0.001, high: floa self.low = low self.high = high - def __call__(self, rule: Rule, random_state: RandomState) -> Rule: + def __call__(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False) -> Rule: uniform_size = None if isinstance(self.sigma, float) else len(self.sigma) self.sigma = random_state.uniform(self.low, self.high, uniform_size) self.mutation.sigma = self.sigma @@ -68,66 +88,72 @@ def min_percentage(self, rule: Rule, random_state: RandomState): class Normal(RuleMutation): """Normal noise on both bounds.""" - def individual_mutate(self, rule: Rule, random_state: RandomState): + def individual_mutate(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): bounds = rule.match.bounds for index in range(0, self.sigma.shape[0]): bounds[:, index] += random_state.normal(scale=self.sigma[index], size=rule.match.bounds.shape[0]) - def unordered_bound(self, rule: Rule, random_state: RandomState): + def unordered_bound(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): rule.match.bounds += random_state.normal(scale=self.sigma, size=rule.match.bounds.shape) - def ordered_bound(self, rule: Rule, random_state: RandomState): + def ordered_bound(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): # code inspection gives you a warning here but it is ineffectual self.unordered_bound(rule, random_state) rule.match.bounds = np.sort(rule.match.bounds, axis=1) - def center_spread(self, rule: Rule, random_state: RandomState): + def center_spread(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): self.individual_mutate(rule, random_state) - def min_percentage(self, rule: Rule, random_state: RandomState): + def min_percentage(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): self.individual_mutate(rule, random_state) + def adapt(self, elitist_fitness: float): + return self + class Halfnorm(RuleMutation): """Sample with (half)normal distribution around the center.""" - def unordered_bound(self, rule: Rule, random_state: RandomState): + def unordered_bound(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): bounds = rule.match.bounds mean = np.mean(bounds, axis=1) bounds[:, 0] = mean - halfnorm.rvs(scale=self.sigma / 2, size=bounds.shape[0], random_state=random_state) bounds[:, 1] = mean + halfnorm.rvs(scale=self.sigma / 2, size=bounds.shape[0], random_state=random_state) - def ordered_bound(self, rule: Rule, random_state: RandomState): + def ordered_bound(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): self.unordered_bound(rule, random_state) rule.match.bounds = np.sort(rule.match.bounds, axis=1) - def center_spread(self, rule: Rule, random_state: RandomState): + def center_spread(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): raise TypeError("Halform Mutation is not implemented for CSR") - def min_percentage(self, rule: Rule, random_state: RandomState): + def min_percentage(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): raise TypeError("Halform Mutation is not implemented for MPR") + def adapt(self, elitist_fitness: float): + return self + class HalfnormIncrease(RuleMutation): """Increase bounds with (half)normal noise.""" - def unordered_bound(self, rule: Rule, random_state: RandomState): + def unordered_bound(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): raise TypeError("HalformIncrease would cause UBR to behave like OBR") - def ordered_bound(self, rule: Rule, random_state: RandomState): + def ordered_bound(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): bounds = rule.match.bounds bounds[:, 0] -= halfnorm.rvs(scale=self.sigma / 2, size=bounds.shape[0], random_state=random_state) bounds[:, 1] += halfnorm.rvs(scale=self.sigma / 2, size=bounds.shape[0], random_state=random_state) rule.match.bounds = np.sort(rule.match.bounds, axis=1) - def center_spread(self, rule: Rule, random_state: RandomState): + def center_spread(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): bounds = rule.match.bounds bounds[:, 0] += random_state.normal(scale=self.sigma[0], size=bounds.shape[0]) bounds[:, 1] += halfnorm.rvs(scale=self.sigma[1] / 2, size=bounds.shape[0], random_state=random_state) - def min_percentage(self, rule: Rule, random_state: RandomState): + def min_percentage(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): bounds = rule.match.bounds bounds[:, 0] -= halfnorm.rvs(scale=self.sigma[0] / 2, size=bounds.shape[0], random_state=random_state) bounds[:, 1] += halfnorm.rvs(scale=self.sigma[1] / 2, size=bounds.shape[0], random_state=random_state) @@ -136,44 +162,47 @@ def min_percentage(self, rule: Rule, random_state: RandomState): class Uniform(RuleMutation): """Uniform noise on both bounds.""" - def individual_mutate(self, rule: Rule, random_state: RandomState): + def individual_mutate(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): bounds = rule.match.bounds for index in range(0, self.sigma.shape[0]): bounds[:, index] += random_state.uniform(-self.sigma[index], self.sigma[index], size=bounds.shape[0]) - def unordered_bound(self, rule: Rule, random_state: RandomState): + def unordered_bound(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): rule.match.bounds += random_state.uniform(-self.sigma, self.sigma, size=rule.match.bounds.shape) - def ordered_bound(self, rule: Rule, random_state: RandomState): + def ordered_bound(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): self.unordered_bound(rule, random_state) rule.match.bounds = np.sort(rule.match.bounds, axis=1) - def center_spread(self, rule: Rule, random_state: RandomState): + def center_spread(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): self.individual_mutate(rule, random_state) - def min_percentage(self, rule: Rule, random_state: RandomState): + def min_percentage(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): self.individual_mutate(rule, random_state) + def adapt(self, elitist_fitness: float): + return self + class UniformIncrease(RuleMutation): """Increase bounds with uniform noise.""" - def unordered_bound(self, rule: Rule, random_state: RandomState): + def unordered_bound(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): raise TypeError("UniformIncrease would cause UBR to behave like OBR") - def ordered_bound(self, rule: Rule, random_state: RandomState): + def ordered_bound(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): bounds = rule.match.bounds bounds[:, 0] -= random_state.uniform(0, self.sigma, size=bounds.shape[0]) bounds[:, 1] += random_state.uniform(0, self.sigma, size=bounds.shape[0]) rule.match.bounds = np.sort(rule.match.bounds, axis=1) - def center_spread(self, rule: Rule, random_state: RandomState): + def center_spread(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): bounds = rule.match.bounds bounds[:, 0] -= random_state.uniform(-self.sigma[0], self.sigma[0], size=bounds.shape[0]) bounds[:, 1] += random_state.uniform(0, self.sigma[1], size=bounds.shape[0]) - def min_percentage(self, rule: Rule, random_state: RandomState): + def min_percentage(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): bounds = rule.match.bounds bounds[:, 0] -= random_state.uniform(0, self.sigma[0], size=bounds.shape[0]) bounds[:, 1] += random_state.uniform(0, self.sigma[1], size=bounds.shape[0]) diff --git a/tests/test_suprb.py b/tests/test_suprb.py index 56c0fed8..858c0fa4 100644 --- a/tests/test_suprb.py +++ b/tests/test_suprb.py @@ -1,23 +1,82 @@ import unittest +import numpy as np from sklearn.utils.estimator_checks import check_estimator +from sklearn.utils import shuffle as apply_shuffle +from sklearn.preprocessing import StandardScaler, MinMaxScaler import suprb import suprb.logging.stdout +from suprb.rule.matching import OrderedBound +from suprb.utils import check_random_state +from suprb import SupRB +from suprb import rule +from suprb.optimizer.solution import ga +from suprb.optimizer.rule import es +from suprb.utils import check_random_state +import suprb.optimizer.rule.mutation as mutation -class TestSupRB(unittest.TestCase): - - def test_check_estimator(self): - """Tests that `check_estimator()` from sklearn passes, - i.e., that the scikit-learn interface guidelines are met.""" - # Low n_iter for speed. Still takes forever though. - estimator = suprb.SupRB( +class TestSupRB(unittest.TestCase): + def setUp(self): + self.model_ = SupRB( + rule_generation=es.ES1xLambda( + n_iter=220, + operator='&', + init=rule.initialization.MeanInit(fitness=rule.fitness.VolumeWu(alpha=0.05)), + mutation=mutation.HalfnormIncrease(sigma=0.01, adapt_mutation=True), + delay=200 + ), + solution_composition=ga.GeneticAlgorithm( + n_iter=1, + crossover=ga.crossover.Uniform(), + selection=ga.selection.Tournament(), + ), n_iter=1, - solution_composition=suprb.optimizer.solution.ga.GeneticAlgorithm(n_iter=16, population_size=16), - logger=suprb.logging.stdout.StdoutLogger(), - verbose=10 + n_rules=5, + verbose=1, + random_state=42, ) - check_estimator(estimator) + self.setup_test_example() + + def setup_test_example(self): + n_samples = 1000 + random_state = 42 + random_state_ = check_random_state(random_state) + + X = np.linspace(0, 20, num=n_samples) + y = np.zeros(n_samples) + y[X < 10] = np.sin(np.pi * X[X < 10] / 5) + 0.2 * np.cos(4 * np.pi * X[X < 10] / 5) + y[X >= 10] = X[X >= 10] / 10 - 1 + y += random_state_.normal(scale=0.1, size=n_samples) + X = X.reshape((-1, 1)) + X, y = apply_shuffle(X, y, random_state=random_state) + + self.X = MinMaxScaler(feature_range=(-1, 1)).fit_transform(X) + self.y = StandardScaler().fit_transform(y.reshape((-1, 1))).reshape((-1,)) + + # def test_check_estimator(self): + # """Tests that `check_estimator()` from sklearn passes, + # i.e., that the scikit-learn interface guidelines are met.""" + + # check_estimator(self.model_) + + def test_application(self): + """Tests if suprb is learning continously or if something + breaks in between (e.g. it stops learning).""" + self.model_.n_iter = 10 + self.model_.fit(self.X, self.y) + + # self.model_._discover_rules(self.X, self.y, self.model_.n_rules) + # self.model_._compose_solution(self.X, self.y) + + # # for i in self.model_.pool_: + # # print(i.error_, i.experience_, i.fitness_) + + # self.model_._discover_rules(self.X, self.y, self.model_.n_rules) + # self.model_._compose_solution(self.X, self.y) + + # for i in self.model_.pool_: + # print(i.error_, i.experience_, i.fitness_) From 98699a677f2c04073c5324e4f2258e4f2279f795 Mon Sep 17 00:00:00 2001 From: Roman Sraj Date: Wed, 12 Oct 2022 19:11:03 +0200 Subject: [PATCH 3/9] Remove print statements --- suprb/optimizer/rule/es/es.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/suprb/optimizer/rule/es/es.py b/suprb/optimizer/rule/es/es.py index 019ce73d..a034f328 100644 --- a/suprb/optimizer/rule/es/es.py +++ b/suprb/optimizer/rule/es/es.py @@ -123,7 +123,4 @@ def _optimize(self, X: np.ndarray, y: np.ndarray, initial_rule: Rule, random_sta mutation = mutation.adapt(elitist.fitness_) - # self.mutation = adapt_mutation(elitist.fitness_) - print(iteration, type(mutation), elitist.match.bounds, elitist.fitness_) - return elitist From 0e214e6f6be067865bd80aa55b794b0d06fe14d2 Mon Sep 17 00:00:00 2001 From: Roman Sraj Date: Wed, 12 Oct 2022 19:12:20 +0200 Subject: [PATCH 4/9] Revert changes to test_suprb.py --- tests/test_suprb.py | 81 ++++++--------------------------------------- 1 file changed, 11 insertions(+), 70 deletions(-) diff --git a/tests/test_suprb.py b/tests/test_suprb.py index 858c0fa4..56c0fed8 100644 --- a/tests/test_suprb.py +++ b/tests/test_suprb.py @@ -1,82 +1,23 @@ import unittest -import numpy as np from sklearn.utils.estimator_checks import check_estimator -from sklearn.utils import shuffle as apply_shuffle -from sklearn.preprocessing import StandardScaler, MinMaxScaler import suprb import suprb.logging.stdout -from suprb.rule.matching import OrderedBound -from suprb.utils import check_random_state - -from suprb import SupRB -from suprb import rule -from suprb.optimizer.solution import ga -from suprb.optimizer.rule import es -from suprb.utils import check_random_state -import suprb.optimizer.rule.mutation as mutation class TestSupRB(unittest.TestCase): - def setUp(self): - self.model_ = SupRB( - rule_generation=es.ES1xLambda( - n_iter=220, - operator='&', - init=rule.initialization.MeanInit(fitness=rule.fitness.VolumeWu(alpha=0.05)), - mutation=mutation.HalfnormIncrease(sigma=0.01, adapt_mutation=True), - delay=200 - ), - solution_composition=ga.GeneticAlgorithm( - n_iter=1, - crossover=ga.crossover.Uniform(), - selection=ga.selection.Tournament(), - ), - n_iter=1, - n_rules=5, - verbose=1, - random_state=42, - ) - - self.setup_test_example() - - def setup_test_example(self): - n_samples = 1000 - random_state = 42 - random_state_ = check_random_state(random_state) - - X = np.linspace(0, 20, num=n_samples) - y = np.zeros(n_samples) - y[X < 10] = np.sin(np.pi * X[X < 10] / 5) + 0.2 * np.cos(4 * np.pi * X[X < 10] / 5) - y[X >= 10] = X[X >= 10] / 10 - 1 - y += random_state_.normal(scale=0.1, size=n_samples) - X = X.reshape((-1, 1)) - X, y = apply_shuffle(X, y, random_state=random_state) - - self.X = MinMaxScaler(feature_range=(-1, 1)).fit_transform(X) - self.y = StandardScaler().fit_transform(y.reshape((-1, 1))).reshape((-1,)) - # def test_check_estimator(self): - # """Tests that `check_estimator()` from sklearn passes, - # i.e., that the scikit-learn interface guidelines are met.""" + def test_check_estimator(self): + """Tests that `check_estimator()` from sklearn passes, + i.e., that the scikit-learn interface guidelines are met.""" - # check_estimator(self.model_) - - def test_application(self): - """Tests if suprb is learning continously or if something - breaks in between (e.g. it stops learning).""" - self.model_.n_iter = 10 - self.model_.fit(self.X, self.y) - - # self.model_._discover_rules(self.X, self.y, self.model_.n_rules) - # self.model_._compose_solution(self.X, self.y) - - # # for i in self.model_.pool_: - # # print(i.error_, i.experience_, i.fitness_) - - # self.model_._discover_rules(self.X, self.y, self.model_.n_rules) - # self.model_._compose_solution(self.X, self.y) + # Low n_iter for speed. Still takes forever though. + estimator = suprb.SupRB( + n_iter=1, + solution_composition=suprb.optimizer.solution.ga.GeneticAlgorithm(n_iter=16, population_size=16), + logger=suprb.logging.stdout.StdoutLogger(), + verbose=10 + ) - # for i in self.model_.pool_: - # print(i.error_, i.experience_, i.fitness_) + check_estimator(estimator) From 6e569dd94215c1bd6bbfedca9f34e0ae7c0c95d5 Mon Sep 17 00:00:00 2001 From: Roman Sraj Date: Thu, 13 Oct 2022 12:58:21 +0200 Subject: [PATCH 5/9] Add AdaptiveMutation --- suprb/optimizer/rule/es/es.py | 3 +- suprb/optimizer/rule/generation_operator.py | 4 + suprb/optimizer/rule/mutation.py | 132 ++++++++++++-------- 3 files changed, 84 insertions(+), 55 deletions(-) diff --git a/suprb/optimizer/rule/es/es.py b/suprb/optimizer/rule/es/es.py index a034f328..e2e4cdce 100644 --- a/suprb/optimizer/rule/es/es.py +++ b/suprb/optimizer/rule/es/es.py @@ -121,6 +121,7 @@ def _optimize(self, X: np.ndarray, y: np.ndarray, initial_rule: Rule, random_sta elitist = elitists[0] break - mutation = mutation.adapt(elitist.fitness_) + mutation.adapt(elitist.fitness_) + print(iteration, type(mutation.operator_), elitist.fitness_) return elitist diff --git a/suprb/optimizer/rule/generation_operator.py b/suprb/optimizer/rule/generation_operator.py index 2a7793ac..1ce7ad95 100644 --- a/suprb/optimizer/rule/generation_operator.py +++ b/suprb/optimizer/rule/generation_operator.py @@ -43,3 +43,7 @@ def center_spread(self, rule: Rule, random_state: RandomState): @abstractmethod def min_percentage(self, rule: Rule, random_state: RandomState): pass + + @abstractmethod + def adapt(self, elitist_fitness: float): + pass diff --git a/suprb/optimizer/rule/mutation.py b/suprb/optimizer/rule/mutation.py index 4b7e4a04..8badd113 100644 --- a/suprb/optimizer/rule/mutation.py +++ b/suprb/optimizer/rule/mutation.py @@ -1,5 +1,8 @@ +from ast import operator from typing import Union +from sklearn import clone +from copy import deepcopy import numpy as np from scipy.stats import halfnorm @@ -7,7 +10,7 @@ from suprb.rule import Rule from suprb.utils import RandomState from suprb.optimizer.rule.generation_operator import GenerationOperator -from suprb.rule.matching import MatchingFunction +from suprb.rule.matching import MatchingFunction, OrderedBound class RuleMutation(GenerationOperator): @@ -15,18 +18,11 @@ class RuleMutation(GenerationOperator): def __init__(self, matching_type: MatchingFunction = None, - sigma: Union[float, np.ndarray] = 0.1, - adapt_mutation: bool = False): + sigma: Union[float, np.ndarray] = 0.1): super().__init__(matching_type=matching_type) self.sigma = sigma - # Parameters necessery for adapting the mutation - self.best_elitist_fitness = 0 - self.number_of_worse_iterations = 0 - self.worse_iteration_tolerance = 5 - self.adapt_mutation = adapt_mutation - - def __call__(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False) -> Rule: + def __call__(self, rule: Rule, random_state: RandomState) -> Rule: # Create copy of the rule mutated_rule = rule.clone() @@ -35,21 +31,11 @@ def __call__(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = return mutated_rule - def execute(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): + def execute(self, rule: Rule, random_state: RandomState): pass def adapt(self, elitist_fitness: float): - if self.adapt_mutation: - if elitist_fitness < self.best_elitist_fitness: - self.number_of_worse_iterations += 1 - - if self.number_of_worse_iterations > self.worse_iteration_tolerance: - return Normal(matching_type=self.matching_type, sigma=self.sigma, adapt_mutation=True) - else: - self.number_of_worse_iterations = 0 - self.best_elitist_fitness = elitist_fitness - - return self + pass class SigmaRange(RuleMutation): @@ -66,7 +52,7 @@ def __init__(self, mutation: RuleMutation = None, low: float = 0.001, high: floa self.low = low self.high = high - def __call__(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False) -> Rule: + def __call__(self, rule: Rule, random_state: RandomState) -> Rule: uniform_size = None if isinstance(self.sigma, float) else len(self.sigma) self.sigma = random_state.uniform(self.low, self.high, uniform_size) self.mutation.sigma = self.sigma @@ -88,72 +74,66 @@ def min_percentage(self, rule: Rule, random_state: RandomState): class Normal(RuleMutation): """Normal noise on both bounds.""" - def individual_mutate(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): + def individual_mutate(self, rule: Rule, random_state: RandomState): bounds = rule.match.bounds for index in range(0, self.sigma.shape[0]): bounds[:, index] += random_state.normal(scale=self.sigma[index], size=rule.match.bounds.shape[0]) - def unordered_bound(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): + def unordered_bound(self, rule: Rule, random_state: RandomState): rule.match.bounds += random_state.normal(scale=self.sigma, size=rule.match.bounds.shape) - def ordered_bound(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): + def ordered_bound(self, rule: Rule, random_state: RandomState): # code inspection gives you a warning here but it is ineffectual self.unordered_bound(rule, random_state) rule.match.bounds = np.sort(rule.match.bounds, axis=1) - def center_spread(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): + def center_spread(self, rule: Rule, random_state: RandomState): self.individual_mutate(rule, random_state) - def min_percentage(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): + def min_percentage(self, rule: Rule, random_state: RandomState): self.individual_mutate(rule, random_state) - def adapt(self, elitist_fitness: float): - return self - class Halfnorm(RuleMutation): """Sample with (half)normal distribution around the center.""" - def unordered_bound(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): + def unordered_bound(self, rule: Rule, random_state: RandomState): bounds = rule.match.bounds mean = np.mean(bounds, axis=1) bounds[:, 0] = mean - halfnorm.rvs(scale=self.sigma / 2, size=bounds.shape[0], random_state=random_state) bounds[:, 1] = mean + halfnorm.rvs(scale=self.sigma / 2, size=bounds.shape[0], random_state=random_state) - def ordered_bound(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): + def ordered_bound(self, rule: Rule, random_state: RandomState): self.unordered_bound(rule, random_state) rule.match.bounds = np.sort(rule.match.bounds, axis=1) - def center_spread(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): + def center_spread(self, rule: Rule, random_state: RandomState): raise TypeError("Halform Mutation is not implemented for CSR") - def min_percentage(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): + def min_percentage(self, rule: Rule, random_state: RandomState): raise TypeError("Halform Mutation is not implemented for MPR") - def adapt(self, elitist_fitness: float): - return self - class HalfnormIncrease(RuleMutation): """Increase bounds with (half)normal noise.""" - def unordered_bound(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): + def unordered_bound(self, rule: Rule, random_state: RandomState): raise TypeError("HalformIncrease would cause UBR to behave like OBR") - def ordered_bound(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): + def ordered_bound(self, rule: Rule, random_state: RandomState): bounds = rule.match.bounds bounds[:, 0] -= halfnorm.rvs(scale=self.sigma / 2, size=bounds.shape[0], random_state=random_state) bounds[:, 1] += halfnorm.rvs(scale=self.sigma / 2, size=bounds.shape[0], random_state=random_state) rule.match.bounds = np.sort(rule.match.bounds, axis=1) - def center_spread(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): + def center_spread(self, rule: Rule, random_state: RandomState): bounds = rule.match.bounds bounds[:, 0] += random_state.normal(scale=self.sigma[0], size=bounds.shape[0]) bounds[:, 1] += halfnorm.rvs(scale=self.sigma[1] / 2, size=bounds.shape[0], random_state=random_state) - def min_percentage(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): + def min_percentage(self, rule: Rule, random_state: RandomState): bounds = rule.match.bounds bounds[:, 0] -= halfnorm.rvs(scale=self.sigma[0] / 2, size=bounds.shape[0], random_state=random_state) bounds[:, 1] += halfnorm.rvs(scale=self.sigma[1] / 2, size=bounds.shape[0], random_state=random_state) @@ -162,47 +142,91 @@ def min_percentage(self, rule: Rule, random_state: RandomState, adapt_mutation: class Uniform(RuleMutation): """Uniform noise on both bounds.""" - def individual_mutate(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): + def individual_mutate(self, rule: Rule, random_state: RandomState): bounds = rule.match.bounds for index in range(0, self.sigma.shape[0]): bounds[:, index] += random_state.uniform(-self.sigma[index], self.sigma[index], size=bounds.shape[0]) - def unordered_bound(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): + def unordered_bound(self, rule: Rule, random_state: RandomState): rule.match.bounds += random_state.uniform(-self.sigma, self.sigma, size=rule.match.bounds.shape) - def ordered_bound(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): + def ordered_bound(self, rule: Rule, random_state: RandomState): self.unordered_bound(rule, random_state) rule.match.bounds = np.sort(rule.match.bounds, axis=1) - def center_spread(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): + def center_spread(self, rule: Rule, random_state: RandomState): self.individual_mutate(rule, random_state) - def min_percentage(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): + def min_percentage(self, rule: Rule, random_state: RandomState): self.individual_mutate(rule, random_state) - def adapt(self, elitist_fitness: float): - return self - class UniformIncrease(RuleMutation): """Increase bounds with uniform noise.""" - def unordered_bound(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): + def unordered_bound(self, rule: Rule, random_state: RandomState): raise TypeError("UniformIncrease would cause UBR to behave like OBR") - def ordered_bound(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): + def ordered_bound(self, rule: Rule, random_state: RandomState): bounds = rule.match.bounds bounds[:, 0] -= random_state.uniform(0, self.sigma, size=bounds.shape[0]) bounds[:, 1] += random_state.uniform(0, self.sigma, size=bounds.shape[0]) rule.match.bounds = np.sort(rule.match.bounds, axis=1) - def center_spread(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): + def center_spread(self, rule: Rule, random_state: RandomState): bounds = rule.match.bounds bounds[:, 0] -= random_state.uniform(-self.sigma[0], self.sigma[0], size=bounds.shape[0]) bounds[:, 1] += random_state.uniform(0, self.sigma[1], size=bounds.shape[0]) - def min_percentage(self, rule: Rule, random_state: RandomState, adapt_mutation: bool = False): + def min_percentage(self, rule: Rule, random_state: RandomState): bounds = rule.match.bounds bounds[:, 0] -= random_state.uniform(0, self.sigma[0], size=bounds.shape[0]) bounds[:, 1] += random_state.uniform(0, self.sigma[1], size=bounds.shape[0]) + + +class AdaptiveMutation(RuleMutation): + """Mutate with given operator until a peak is reached and then switch the mutation operator to Normal""" + + def __init__(self, matching_type: MatchingFunction = None, + sigma: Union[float, np.ndarray] = 0.1, + operator: RuleMutation = HalfnormIncrease()): + + super().__init__(matching_type=matching_type, sigma=sigma) + self.adapt_mutation = True + self.best_elitist_fitness = 0 + self.number_of_worse_iterations = 0 + self.worse_iteration_tolerance = 5 + self.operator = operator + self.operator_ = clone(self.operator) + + def __call__(self, rule: Rule, random_state: RandomState) -> Rule: + mutated_rule = rule.clone() + self.operator_.execute(mutated_rule, random_state) + + return mutated_rule + + def adapt(self, elitist_fitness: float): + if self.adapt_mutation: + if elitist_fitness < self.best_elitist_fitness: + self.number_of_worse_iterations += 1 + + if self.number_of_worse_iterations > self.worse_iteration_tolerance: + self.adapt_mutation = False + self.operator_ = Normal(matching_type=self.matching_type, sigma=self.sigma) + else: + self.number_of_worse_iterations = 0 + self.best_elitist_fitness = elitist_fitness + self.operator_ = self.operator + + def unordered_bound(self, rule: Rule, random_state: RandomState): + raise TypeError("HalformIncrease would cause UBR to behave like OBR") + + def ordered_bound(self, rule: Rule, random_state: RandomState): + pass + + def center_spread(self, rule: Rule, random_state: RandomState): + pass + + def min_percentage(self, rule: Rule, random_state: RandomState): + pass From 10c5f025dd43abd97d63316c9eb8f6e85a220e0e Mon Sep 17 00:00:00 2001 From: Roman Sraj Date: Thu, 13 Oct 2022 14:09:55 +0200 Subject: [PATCH 6/9] Remove print --- suprb/optimizer/rule/es/es.py | 1 - 1 file changed, 1 deletion(-) diff --git a/suprb/optimizer/rule/es/es.py b/suprb/optimizer/rule/es/es.py index e2e4cdce..282ab902 100644 --- a/suprb/optimizer/rule/es/es.py +++ b/suprb/optimizer/rule/es/es.py @@ -122,6 +122,5 @@ def _optimize(self, X: np.ndarray, y: np.ndarray, initial_rule: Rule, random_sta break mutation.adapt(elitist.fitness_) - print(iteration, type(mutation.operator_), elitist.fitness_) return elitist From 993f57d18070606b4002c134958cc480ed18d7e3 Mon Sep 17 00:00:00 2001 From: Roman Sraj Date: Tue, 18 Oct 2022 15:47:27 +0200 Subject: [PATCH 7/9] Address review comments --- suprb/optimizer/rule/es/es.py | 3 +- suprb/optimizer/rule/generation_operator.py | 4 - suprb/optimizer/rule/mutation.py | 41 +++++------ tests/test_suprb.py | 82 ++++++++++++++++++--- 4 files changed, 92 insertions(+), 38 deletions(-) diff --git a/suprb/optimizer/rule/es/es.py b/suprb/optimizer/rule/es/es.py index 282ab902..65e1c534 100644 --- a/suprb/optimizer/rule/es/es.py +++ b/suprb/optimizer/rule/es/es.py @@ -121,6 +121,7 @@ def _optimize(self, X: np.ndarray, y: np.ndarray, initial_rule: Rule, random_sta elitist = elitists[0] break - mutation.adapt(elitist.fitness_) + if getattr(mutation, "adapt", None): + mutation.adapt(elitist.fitness_) return elitist diff --git a/suprb/optimizer/rule/generation_operator.py b/suprb/optimizer/rule/generation_operator.py index 1ce7ad95..2a7793ac 100644 --- a/suprb/optimizer/rule/generation_operator.py +++ b/suprb/optimizer/rule/generation_operator.py @@ -43,7 +43,3 @@ def center_spread(self, rule: Rule, random_state: RandomState): @abstractmethod def min_percentage(self, rule: Rule, random_state: RandomState): pass - - @abstractmethod - def adapt(self, elitist_fitness: float): - pass diff --git a/suprb/optimizer/rule/mutation.py b/suprb/optimizer/rule/mutation.py index 8badd113..332e71b0 100644 --- a/suprb/optimizer/rule/mutation.py +++ b/suprb/optimizer/rule/mutation.py @@ -1,8 +1,6 @@ -from ast import operator from typing import Union from sklearn import clone -from copy import deepcopy import numpy as np from scipy.stats import halfnorm @@ -10,7 +8,7 @@ from suprb.rule import Rule from suprb.utils import RandomState from suprb.optimizer.rule.generation_operator import GenerationOperator -from suprb.rule.matching import MatchingFunction, OrderedBound +from suprb.rule.matching import MatchingFunction class RuleMutation(GenerationOperator): @@ -34,9 +32,6 @@ def __call__(self, rule: Rule, random_state: RandomState) -> Rule: def execute(self, rule: Rule, random_state: RandomState): pass - def adapt(self, elitist_fitness: float): - pass - class SigmaRange(RuleMutation): """ Draws the sigma used for another mutation from uniform distribution, low to high. @@ -186,47 +181,49 @@ def min_percentage(self, rule: Rule, random_state: RandomState): class AdaptiveMutation(RuleMutation): - """Mutate with given operator until a peak is reached and then switch the mutation operator to Normal""" + """Start off by using a given operator until the fitness decreased + more than self.number_of_worse_iterations times. Then change the mutation operator + to Normal + """ def __init__(self, matching_type: MatchingFunction = None, sigma: Union[float, np.ndarray] = 0.1, - operator: RuleMutation = HalfnormIncrease()): + operator: RuleMutation = HalfnormIncrease(), + worse_iteration_tolerance: int = 5, + sigma_normal: Union[float, np.ndarray] = 0.1): super().__init__(matching_type=matching_type, sigma=sigma) - self.adapt_mutation = True self.best_elitist_fitness = 0 self.number_of_worse_iterations = 0 - self.worse_iteration_tolerance = 5 + self.worse_iteration_tolerance = worse_iteration_tolerance + self.sigma_normal = sigma_normal + + # This is necessary because of sklearn guidelines self.operator = operator self.operator_ = clone(self.operator) def __call__(self, rule: Rule, random_state: RandomState) -> Rule: - mutated_rule = rule.clone() - self.operator_.execute(mutated_rule, random_state) - - return mutated_rule + return self.operator_(rule, random_state) def adapt(self, elitist_fitness: float): - if self.adapt_mutation: + if not isinstance(self.operator_, Normal): if elitist_fitness < self.best_elitist_fitness: self.number_of_worse_iterations += 1 if self.number_of_worse_iterations > self.worse_iteration_tolerance: - self.adapt_mutation = False - self.operator_ = Normal(matching_type=self.matching_type, sigma=self.sigma) + self.operator_ = Normal(matching_type=self.matching_type, sigma=self.sigma_normal) else: - self.number_of_worse_iterations = 0 self.best_elitist_fitness = elitist_fitness self.operator_ = self.operator def unordered_bound(self, rule: Rule, random_state: RandomState): - raise TypeError("HalformIncrease would cause UBR to behave like OBR") + raise TypeError("AdaptiveMutation has no implementation for unordered_bound") def ordered_bound(self, rule: Rule, random_state: RandomState): - pass + raise TypeError("AdaptiveMutation has no implementation for ordered_bound") def center_spread(self, rule: Rule, random_state: RandomState): - pass + raise TypeError("AdaptiveMutation has no implementation for center_spread") def min_percentage(self, rule: Rule, random_state: RandomState): - pass + raise TypeError("AdaptiveMutation has no implementation for min_percentage") diff --git a/tests/test_suprb.py b/tests/test_suprb.py index 56c0fed8..d82e229f 100644 --- a/tests/test_suprb.py +++ b/tests/test_suprb.py @@ -1,23 +1,83 @@ +from ast import operator import unittest +import numpy as np from sklearn.utils.estimator_checks import check_estimator +from sklearn.utils import shuffle as apply_shuffle +from sklearn.preprocessing import StandardScaler, MinMaxScaler import suprb import suprb.logging.stdout +from suprb.rule.matching import OrderedBound +from suprb.utils import check_random_state +from suprb import SupRB +from suprb import rule +from suprb.optimizer.solution import ga +from suprb.optimizer.rule import es +from suprb.utils import check_random_state +import suprb.optimizer.rule.mutation as mutation -class TestSupRB(unittest.TestCase): - - def test_check_estimator(self): - """Tests that `check_estimator()` from sklearn passes, - i.e., that the scikit-learn interface guidelines are met.""" - # Low n_iter for speed. Still takes forever though. - estimator = suprb.SupRB( +class TestSupRB(unittest.TestCase): + def setUp(self): + self.model_ = SupRB( + rule_generation=es.ES1xLambda( + n_iter=220, + operator='&', + init=rule.initialization.MeanInit(fitness=rule.fitness.VolumeWu(alpha=0.05)), + mutation=mutation.AdaptiveMutation(operator=mutation.UniformIncrease(sigma=0.01)), + delay=200 + ), + solution_composition=ga.GeneticAlgorithm( + n_iter=1, + crossover=ga.crossover.Uniform(), + selection=ga.selection.Tournament(), + ), n_iter=1, - solution_composition=suprb.optimizer.solution.ga.GeneticAlgorithm(n_iter=16, population_size=16), - logger=suprb.logging.stdout.StdoutLogger(), - verbose=10 + n_rules=5, + verbose=1, + random_state=42, ) - check_estimator(estimator) + self.setup_test_example() + + def setup_test_example(self): + n_samples = 1000 + random_state = 42 + random_state_ = check_random_state(random_state) + + X = np.linspace(0, 20, num=n_samples) + y = np.zeros(n_samples) + y[X < 10] = np.sin(np.pi * X[X < 10] / 5) + 0.2 * np.cos(4 * np.pi * X[X < 10] / 5) + y[X >= 10] = X[X >= 10] / 10 - 1 + y += random_state_.normal(scale=0.1, size=n_samples) + X = X.reshape((-1, 1)) + X, y = apply_shuffle(X, y, random_state=random_state) + + self.X = MinMaxScaler(feature_range=(-1, 1)).fit_transform(X) + self.y = StandardScaler().fit_transform(y.reshape((-1, 1))).reshape((-1,)) + + # def test_check_estimator(self): + # """Tests that `check_estimator()` from sklearn passes, + # i.e., that the scikit-learn interface guidelines are met.""" + + # check_estimator(self.model_) + + def test_application(self): + """Tests if suprb is learning continously or if something + breaks in between (e.g. it stops learning).""" + self.model_.n_iter = 10 + self.model_.fit(self.X, self.y) + + # self.model_._discover_rules(self.X, self.y, self.model_.n_rules) + # self.model_._compose_solution(self.X, self.y) + + # # for i in self.model_.pool_: + # # print(i.error_, i.experience_, i.fitness_) + + # self.model_._discover_rules(self.X, self.y, self.model_.n_rules) + # self.model_._compose_solution(self.X, self.y) + + # for i in self.model_.pool_: + # print(i.error_, i.experience_, i.fitness_) From 4d941caa6a04eae0d04de4621e6dbf73fef74336 Mon Sep 17 00:00:00 2001 From: Roman Sraj Date: Tue, 18 Oct 2022 15:49:17 +0200 Subject: [PATCH 8/9] Revert changes to test_suprb.py --- tests/test_suprb.py | 82 ++++++--------------------------------------- 1 file changed, 11 insertions(+), 71 deletions(-) diff --git a/tests/test_suprb.py b/tests/test_suprb.py index d82e229f..56c0fed8 100644 --- a/tests/test_suprb.py +++ b/tests/test_suprb.py @@ -1,83 +1,23 @@ -from ast import operator import unittest -import numpy as np from sklearn.utils.estimator_checks import check_estimator -from sklearn.utils import shuffle as apply_shuffle -from sklearn.preprocessing import StandardScaler, MinMaxScaler import suprb import suprb.logging.stdout -from suprb.rule.matching import OrderedBound -from suprb.utils import check_random_state - -from suprb import SupRB -from suprb import rule -from suprb.optimizer.solution import ga -from suprb.optimizer.rule import es -from suprb.utils import check_random_state -import suprb.optimizer.rule.mutation as mutation class TestSupRB(unittest.TestCase): - def setUp(self): - self.model_ = SupRB( - rule_generation=es.ES1xLambda( - n_iter=220, - operator='&', - init=rule.initialization.MeanInit(fitness=rule.fitness.VolumeWu(alpha=0.05)), - mutation=mutation.AdaptiveMutation(operator=mutation.UniformIncrease(sigma=0.01)), - delay=200 - ), - solution_composition=ga.GeneticAlgorithm( - n_iter=1, - crossover=ga.crossover.Uniform(), - selection=ga.selection.Tournament(), - ), - n_iter=1, - n_rules=5, - verbose=1, - random_state=42, - ) - - self.setup_test_example() - - def setup_test_example(self): - n_samples = 1000 - random_state = 42 - random_state_ = check_random_state(random_state) - - X = np.linspace(0, 20, num=n_samples) - y = np.zeros(n_samples) - y[X < 10] = np.sin(np.pi * X[X < 10] / 5) + 0.2 * np.cos(4 * np.pi * X[X < 10] / 5) - y[X >= 10] = X[X >= 10] / 10 - 1 - y += random_state_.normal(scale=0.1, size=n_samples) - X = X.reshape((-1, 1)) - X, y = apply_shuffle(X, y, random_state=random_state) - - self.X = MinMaxScaler(feature_range=(-1, 1)).fit_transform(X) - self.y = StandardScaler().fit_transform(y.reshape((-1, 1))).reshape((-1,)) - # def test_check_estimator(self): - # """Tests that `check_estimator()` from sklearn passes, - # i.e., that the scikit-learn interface guidelines are met.""" + def test_check_estimator(self): + """Tests that `check_estimator()` from sklearn passes, + i.e., that the scikit-learn interface guidelines are met.""" - # check_estimator(self.model_) - - def test_application(self): - """Tests if suprb is learning continously or if something - breaks in between (e.g. it stops learning).""" - self.model_.n_iter = 10 - self.model_.fit(self.X, self.y) - - # self.model_._discover_rules(self.X, self.y, self.model_.n_rules) - # self.model_._compose_solution(self.X, self.y) - - # # for i in self.model_.pool_: - # # print(i.error_, i.experience_, i.fitness_) - - # self.model_._discover_rules(self.X, self.y, self.model_.n_rules) - # self.model_._compose_solution(self.X, self.y) + # Low n_iter for speed. Still takes forever though. + estimator = suprb.SupRB( + n_iter=1, + solution_composition=suprb.optimizer.solution.ga.GeneticAlgorithm(n_iter=16, population_size=16), + logger=suprb.logging.stdout.StdoutLogger(), + verbose=10 + ) - # for i in self.model_.pool_: - # print(i.error_, i.experience_, i.fitness_) + check_estimator(estimator) From bbfa81eb2e12f8ad8aef294565e181573179cdfd Mon Sep 17 00:00:00 2001 From: Roman Sraj Date: Wed, 26 Oct 2022 15:03:21 +0200 Subject: [PATCH 9/9] Address review comments --- suprb/optimizer/rule/es/es.py | 1 + suprb/optimizer/rule/mutation.py | 20 +++++++++++--------- suprb/suprb.py | 2 +- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/suprb/optimizer/rule/es/es.py b/suprb/optimizer/rule/es/es.py index 65e1c534..91fbf86b 100644 --- a/suprb/optimizer/rule/es/es.py +++ b/suprb/optimizer/rule/es/es.py @@ -89,6 +89,7 @@ def __init__(self, def _optimize(self, X: np.ndarray, y: np.ndarray, initial_rule: Rule, random_state: RandomState) -> Optional[Rule]: elitist = initial_rule elitists = deque(maxlen=self.delay) + # This is needed for AdaptiveMutation as the initial mutation type needs to be resetted each RuleDiscovery cycle mutation = deepcopy(self.mutation) # Main iteration diff --git a/suprb/optimizer/rule/mutation.py b/suprb/optimizer/rule/mutation.py index 332e71b0..e6dcc74c 100644 --- a/suprb/optimizer/rule/mutation.py +++ b/suprb/optimizer/rule/mutation.py @@ -181,18 +181,17 @@ def min_percentage(self, rule: Rule, random_state: RandomState): class AdaptiveMutation(RuleMutation): - """Start off by using a given operator until the fitness decreased - more than self.number_of_worse_iterations times. Then change the mutation operator + """Start off by using a given operator until the fitness has not increased for + more than self.number_of_worse_iterations. Then change the mutation operator to Normal """ def __init__(self, matching_type: MatchingFunction = None, - sigma: Union[float, np.ndarray] = 0.1, operator: RuleMutation = HalfnormIncrease(), worse_iteration_tolerance: int = 5, sigma_normal: Union[float, np.ndarray] = 0.1): - super().__init__(matching_type=matching_type, sigma=sigma) + super().__init__(matching_type=matching_type, sigma=operator.sigma) self.best_elitist_fitness = 0 self.number_of_worse_iterations = 0 self.worse_iteration_tolerance = worse_iteration_tolerance @@ -214,16 +213,19 @@ def adapt(self, elitist_fitness: float): self.operator_ = Normal(matching_type=self.matching_type, sigma=self.sigma_normal) else: self.best_elitist_fitness = elitist_fitness - self.operator_ = self.operator def unordered_bound(self, rule: Rule, random_state: RandomState): - raise TypeError("AdaptiveMutation has no implementation for unordered_bound") + raise NotImplementedError( + "AdaptiveMutation has no implementation for unordered_bound as it is only used as a wrapper for other mutation types") def ordered_bound(self, rule: Rule, random_state: RandomState): - raise TypeError("AdaptiveMutation has no implementation for ordered_bound") + raise NotImplementedError( + "AdaptiveMutation has no implementation for ordered_bound as it is only used as a wrapper for other mutation types") def center_spread(self, rule: Rule, random_state: RandomState): - raise TypeError("AdaptiveMutation has no implementation for center_spread") + raise NotImplementedError( + "AdaptiveMutation has no implementation for center_spread as it is only used as a wrapper for other mutation types") def min_percentage(self, rule: Rule, random_state: RandomState): - raise TypeError("AdaptiveMutation has no implementation for min_percentage") + raise NotImplementedError( + "AdaptiveMutation has no implementation for min_percentage as it is only used as a wrapper for other mutation types") diff --git a/suprb/suprb.py b/suprb/suprb.py index 23aabf91..76ad54cd 100644 --- a/suprb/suprb.py +++ b/suprb/suprb.py @@ -159,9 +159,9 @@ def fit(self, X: np.ndarray, y: np.ndarray, cleanup=False): # Initialise components self.pool_ = [] + self._validate_matching_type(default=OrderedBound(np.array([]))) self._validate_rule_generation(default=ES1xLambda()) self._validate_solution_composition(default=GeneticAlgorithm()) - self._validate_matching_type(default=OrderedBound(np.array([]))) self._propagate_component_parameters() self._init_bounds(X)