From 0d67885f64a083b3ff15709f263174423ea0e033 Mon Sep 17 00:00:00 2001 From: janezd Date: Fri, 13 Dec 2024 14:19:41 +0100 Subject: [PATCH 1/3] LogisticRegressionLearner: Remove deprecated argument 'multi_class' --- Orange/classification/logistic_regression.py | 4 ++-- Orange/widgets/visualize/tests/test_ownomogram.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Orange/classification/logistic_regression.py b/Orange/classification/logistic_regression.py index 5aeda451662..08d92772d36 100644 --- a/Orange/classification/logistic_regression.py +++ b/Orange/classification/logistic_regression.py @@ -38,7 +38,7 @@ class LogisticRegressionLearner(SklLearner, _FeatureScorerMixin): def __init__(self, penalty="l2", dual=False, tol=0.0001, C=1.0, fit_intercept=True, intercept_scaling=1, class_weight=None, random_state=None, solver="auto", max_iter=100, - multi_class="auto", verbose=0, n_jobs=1, preprocessors=None): + verbose=0, n_jobs=1, preprocessors=None): super().__init__(preprocessors=preprocessors) self.params = vars() @@ -49,7 +49,7 @@ def _initialize_wrapped(self): solver, penalty = params.pop("solver"), params.get("penalty") if solver == "auto": if penalty == "l1": - solver = "liblinear" + solver = "saga" else: solver = "lbfgs" params["solver"] = solver diff --git a/Orange/widgets/visualize/tests/test_ownomogram.py b/Orange/widgets/visualize/tests/test_ownomogram.py index 657de7dca63..ebf250448c2 100644 --- a/Orange/widgets/visualize/tests/test_ownomogram.py +++ b/Orange/widgets/visualize/tests/test_ownomogram.py @@ -98,7 +98,7 @@ def test_nomogram_lr_multiclass(self): """Check probabilities for logistic regression classifier for various values of classes and radio buttons for multiclass data""" cls = LogisticRegressionLearner( - multi_class="ovr", solver="liblinear" + solver="liblinear" )(self.lenses) self._test_helper(cls, [9, 45, 52]) From 3c2e7e28c5b1231a0f8f9088c8c6c4dfee56ca3b Mon Sep 17 00:00:00 2001 From: janezd Date: Fri, 10 Jan 2025 18:14:02 +0100 Subject: [PATCH 2/3] Update Slovenian translation --- i18n/si/msgs.jaml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/i18n/si/msgs.jaml b/i18n/si/msgs.jaml index c95d30fe77f..c082112ff35 100644 --- a/i18n/si/msgs.jaml +++ b/i18n/si/msgs.jaml @@ -521,12 +521,18 @@ classification/logistic_regression.py: def `__init__`: l2: false auto: false + deprecated: false + 'The multi_class parameter was ': false + 'deprecated in scikit-learn 1.5. Using it with ': false + scikit-learn 1.7 will lead to a crash.: false def `_initialize_wrapped`: + multi_class: false + deprecated: false solver: false penalty: false auto: false l1: false - liblinear: false + saga: false lbfgs: false classification/majority.py: MajorityLearner: false @@ -3096,9 +3102,6 @@ projection/pca.py: auto: false def `fit`: n_components: false - svd_solver: false - auto: false - arpack: false class `SparsePCA`: Sparse PCA: false def `__init__`: @@ -15437,7 +15440,6 @@ widgets/visualize/utils/error_bars_dialog.py: Upper:: Zgornje: Lower:: Spodnje: __main__: false - Error Bars: false Open: false iris: false widgets/visualize/utils/heatmap.py: From 6a7efa10459bbe1688e7fa520ec29bf861ac2476 Mon Sep 17 00:00:00 2001 From: Marko Toplak Date: Thu, 9 Jan 2025 16:17:47 +0100 Subject: [PATCH 3/3] Deprecation warning about multiclass, adapt tests, remove liblinear with multiple classes (because that is going to be unsupported) --- Orange/classification/logistic_regression.py | 17 ++++++++++++++++- Orange/evaluation/scoring.py | 4 ++-- Orange/tests/test_logistic_regression.py | 12 +++++++++++- .../evaluate/tests/test_owpredictions.py | 4 ++-- .../widgets/visualize/tests/test_ownomogram.py | 6 ++---- 5 files changed, 33 insertions(+), 10 deletions(-) diff --git a/Orange/classification/logistic_regression.py b/Orange/classification/logistic_regression.py index 08d92772d36..b97f5cca284 100644 --- a/Orange/classification/logistic_regression.py +++ b/Orange/classification/logistic_regression.py @@ -1,3 +1,5 @@ +import warnings + import numpy as np import sklearn.linear_model as skl_linear_model @@ -5,6 +7,8 @@ from Orange.preprocess import Normalize from Orange.preprocess.score import LearnerScorer from Orange.data import Variable, DiscreteVariable +from Orange.util import OrangeDeprecationWarning + __all__ = ["LogisticRegressionLearner"] @@ -38,12 +42,23 @@ class LogisticRegressionLearner(SklLearner, _FeatureScorerMixin): def __init__(self, penalty="l2", dual=False, tol=0.0001, C=1.0, fit_intercept=True, intercept_scaling=1, class_weight=None, random_state=None, solver="auto", max_iter=100, - verbose=0, n_jobs=1, preprocessors=None): + multi_class="deprecated", verbose=0, n_jobs=1, preprocessors=None): + if multi_class != "deprecated": + warnings.warn("The multi_class parameter was " + "deprecated in scikit-learn 1.5. Using it with " + "scikit-learn 1.7 will lead to a crash.", + OrangeDeprecationWarning, + stacklevel=2) super().__init__(preprocessors=preprocessors) self.params = vars() def _initialize_wrapped(self): params = self.params.copy() + + multi_class = params.pop("multi_class") + if multi_class != "deprecated": + params["multi_class"] = multi_class + # The default scikit-learn solver `lbfgs` (v0.22) does not support the # l1 penalty. solver, penalty = params.pop("solver"), params.get("penalty") diff --git a/Orange/evaluation/scoring.py b/Orange/evaluation/scoring.py index b5ae345b80a..96909dc1921 100644 --- a/Orange/evaluation/scoring.py +++ b/Orange/evaluation/scoring.py @@ -5,7 +5,7 @@ -------- >>> import Orange >>> data = Orange.data.Table('iris') ->>> learner = Orange.classification.LogisticRegressionLearner(solver="liblinear") +>>> learner = Orange.classification.LogisticRegressionLearner() >>> results = Orange.evaluation.TestOnTrainingData(data, [learner]) """ @@ -296,7 +296,7 @@ class LogLoss(ClassificationScore): Examples -------- >>> Orange.evaluation.LogLoss(results) - array([0.3...]) + array([0.1...]) """ __wraps__ = skl_metrics.log_loss diff --git a/Orange/tests/test_logistic_regression.py b/Orange/tests/test_logistic_regression.py index 88b6e1f1073..24820218ed8 100644 --- a/Orange/tests/test_logistic_regression.py +++ b/Orange/tests/test_logistic_regression.py @@ -1,6 +1,7 @@ # Test methods with long descriptive names can omit docstrings # pylint: disable=missing-docstring +from datetime import datetime import unittest import numpy as np @@ -9,6 +10,7 @@ from Orange.data import Table, ContinuousVariable, Domain from Orange.classification import LogisticRegressionLearner, Model from Orange.evaluation import CrossValidation, CA +from Orange.util import OrangeDeprecationWarning class TestLogisticRegressionLearner(unittest.TestCase): @@ -149,8 +151,16 @@ def test_auto_solver(self): # liblinear is default for l2 penalty lr = LogisticRegressionLearner(penalty="l1", solver="auto") skl_clf = lr._initialize_wrapped() - self.assertEqual(skl_clf.solver, "liblinear") + self.assertEqual(skl_clf.solver, "saga") self.assertEqual(skl_clf.penalty, "l1") def test_supports_weights(self): self.assertTrue(LogisticRegressionLearner().supports_weights) + + def test_multi_class_deprecation(self): + with self.assertWarns(OrangeDeprecationWarning): + LogisticRegressionLearner(penalty="l1", multi_class="multinomial") + now = datetime.now() + if (now.year, now.month) >= (2026, 1): + raise Exception("If Orange depends on scikit-learn >= 1.7, remove this test " + "and any mention of multi_class in LogisticRegressionLearner.") diff --git a/Orange/widgets/evaluate/tests/test_owpredictions.py b/Orange/widgets/evaluate/tests/test_owpredictions.py index 949215648ce..6c4ec9061ce 100644 --- a/Orange/widgets/evaluate/tests/test_owpredictions.py +++ b/Orange/widgets/evaluate/tests/test_owpredictions.py @@ -1304,7 +1304,7 @@ def test_output_error_cls(self): log_reg = LogisticRegressionLearner() self.send_signal(self.widget.Inputs.predictors, log_reg(data), 0) self.send_signal(self.widget.Inputs.predictors, - LogisticRegressionLearner(penalty="l1")(data), 1) + LogisticRegressionLearner(penalty="l1", max_iter=1000)(data), 1) with data.unlocked(data.Y): data.Y[1] = np.nan self.send_signal(self.widget.Inputs.data, data) @@ -1316,7 +1316,7 @@ def test_output_error_cls(self): names = [f"{log_reg.name}{x}" for x in names] self.assertEqual(names, [m.name for m in pred.domain.metas]) self.assertAlmostEqual(pred.metas[0, 4], 0.018, 3) - self.assertAlmostEqual(pred.metas[0, 9], 0.113, 3) + self.assertAlmostEqual(pred.metas[0, 9], 0.008, 3) self.assertTrue(np.isnan(pred.metas[1, 4])) self.assertTrue(np.isnan(pred.metas[1, 9])) diff --git a/Orange/widgets/visualize/tests/test_ownomogram.py b/Orange/widgets/visualize/tests/test_ownomogram.py index ebf250448c2..cfd6943dfec 100644 --- a/Orange/widgets/visualize/tests/test_ownomogram.py +++ b/Orange/widgets/visualize/tests/test_ownomogram.py @@ -97,10 +97,8 @@ def test_nomogram_nb_multiclass(self): def test_nomogram_lr_multiclass(self): """Check probabilities for logistic regression classifier for various values of classes and radio buttons for multiclass data""" - cls = LogisticRegressionLearner( - solver="liblinear" - )(self.lenses) - self._test_helper(cls, [9, 45, 52]) + cls = LogisticRegressionLearner(max_iter=100)(self.lenses) + self._test_helper(cls, [18, 56, 78]) def test_nomogram_with_instance_nb(self): """Check initialized marker values and feature sorting for naive bayes