From 7f744409c08fb465d59f1f04e2cac7ebed23f339 Mon Sep 17 00:00:00 2001 From: Alex Senger <91055000+alex-senger@users.noreply.github.com> Date: Fri, 28 Apr 2023 14:43:50 +0200 Subject: [PATCH] feat: add `learning_rate` to AdaBoost classifier and regressor. (#251) Closes #167. Adds learning_rate parameter to AdaBoost classifier and regressor. --------- Co-authored-by: Lars Reimann --- .../ml/classical/classification/_ada_boost.py | 18 ++++++++++++++---- .../ml/classical/regression/_ada_boost.py | 18 ++++++++++++++---- .../classical/classification/test_ada_boost.py | 17 +++++++++++++++++ .../ml/classical/regression/test_ada_boost.py | 17 +++++++++++++++++ 4 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 tests/safeds/ml/classical/classification/test_ada_boost.py create mode 100644 tests/safeds/ml/classical/regression/test_ada_boost.py diff --git a/src/safeds/ml/classical/classification/_ada_boost.py b/src/safeds/ml/classical/classification/_ada_boost.py index b47d92b2e..b727141de 100644 --- a/src/safeds/ml/classical/classification/_ada_boost.py +++ b/src/safeds/ml/classical/classification/_ada_boost.py @@ -13,12 +13,22 @@ class AdaBoost(Classifier): - """Ada Boost classification.""" + """Ada Boost classification. - def __init__(self) -> None: + Parameters + ---------- + learning_rate : float + Weight applied to each classifier at each boosting iteration. + A higher learning rate increases the contribution of each classifier. + """ + + def __init__(self, learning_rate: float = 1.0) -> None: self._wrapped_classifier: sk_AdaBoostClassifier | None = None self._feature_names: list[str] | None = None self._target_name: str | None = None + if learning_rate <= 0: + raise ValueError("learning_rate must be positive.") + self._learning_rate = learning_rate def fit(self, training_set: TaggedTable) -> AdaBoost: """ @@ -41,10 +51,10 @@ def fit(self, training_set: TaggedTable) -> AdaBoost: LearningError If the training data contains invalid values or if the training failed. """ - wrapped_classifier = sk_AdaBoostClassifier() + wrapped_classifier = sk_AdaBoostClassifier(learning_rate=self._learning_rate) fit(wrapped_classifier, training_set) - result = AdaBoost() + result = AdaBoost(learning_rate=self._learning_rate) result._wrapped_classifier = wrapped_classifier result._feature_names = training_set.features.column_names result._target_name = training_set.target.name diff --git a/src/safeds/ml/classical/regression/_ada_boost.py b/src/safeds/ml/classical/regression/_ada_boost.py index 6982af46e..f26a9c60e 100644 --- a/src/safeds/ml/classical/regression/_ada_boost.py +++ b/src/safeds/ml/classical/regression/_ada_boost.py @@ -13,12 +13,22 @@ class AdaBoost(Regressor): - """Ada Boost regression.""" + """Ada Boost regression. - def __init__(self) -> None: + Parameters + ---------- + learning_rate : float + Weight applied to each regressor at each boosting iteration. + A higher learning rate increases the contribution of each regressor. + """ + + def __init__(self, learning_rate: float = 1.0) -> None: self._wrapped_regressor: sk_AdaBoostRegressor | None = None self._feature_names: list[str] | None = None self._target_name: str | None = None + if learning_rate <= 0: + raise ValueError("learning_rate must be positive.") + self.learning_rate = learning_rate def fit(self, training_set: TaggedTable) -> AdaBoost: """ @@ -41,10 +51,10 @@ def fit(self, training_set: TaggedTable) -> AdaBoost: LearningError If the training data contains invalid values or if the training failed. """ - wrapped_regressor = sk_AdaBoostRegressor() + wrapped_regressor = sk_AdaBoostRegressor(learning_rate=self.learning_rate) fit(wrapped_regressor, training_set) - result = AdaBoost() + result = AdaBoost(learning_rate=self.learning_rate) result._wrapped_regressor = wrapped_regressor result._feature_names = training_set.features.column_names result._target_name = training_set.target.name diff --git a/tests/safeds/ml/classical/classification/test_ada_boost.py b/tests/safeds/ml/classical/classification/test_ada_boost.py new file mode 100644 index 000000000..aea6a3c27 --- /dev/null +++ b/tests/safeds/ml/classical/classification/test_ada_boost.py @@ -0,0 +1,17 @@ +import pytest +from safeds.data.tabular.containers import Table +from safeds.ml.classical.classification import AdaBoost + + +def test_should_throw_value_error_if_learning_rate_is_non_positive() -> None: + with pytest.raises(ValueError, match="learning_rate must be positive."): + AdaBoost(learning_rate=-1) + + +def test_should_give_learning_rate_to_sklearn() -> None: + training_set = Table.from_dict({"col1": [1, 2, 3, 4], "col2": [1, 2, 3, 4]}) + tagged_table = training_set.tag_columns("col1") + + regressor = AdaBoost(learning_rate=2).fit(tagged_table) + assert regressor._wrapped_classifier is not None + assert regressor._wrapped_classifier.learning_rate == regressor._learning_rate diff --git a/tests/safeds/ml/classical/regression/test_ada_boost.py b/tests/safeds/ml/classical/regression/test_ada_boost.py new file mode 100644 index 000000000..ac51c7fb0 --- /dev/null +++ b/tests/safeds/ml/classical/regression/test_ada_boost.py @@ -0,0 +1,17 @@ +import pytest +from safeds.data.tabular.containers import Table +from safeds.ml.classical.regression import AdaBoost + + +def test_should_throw_value_error_if_learning_rate_is_non_positive() -> None: + with pytest.raises(ValueError, match="learning_rate must be positive."): + AdaBoost(learning_rate=-1) + + +def test_should_give_learning_rate_to_sklearn() -> None: + training_set = Table.from_dict({"col1": [1, 2, 3, 4], "col2": [1, 2, 3, 4]}) + tagged_table = training_set.tag_columns("col1") + + regressor = AdaBoost(learning_rate=2).fit(tagged_table) + assert regressor._wrapped_regressor is not None + assert regressor._wrapped_regressor.learning_rate == regressor.learning_rate