Skip to content

Commit

Permalink
Merge pull request #6921 from VesnaT/summary_of_fit
Browse files Browse the repository at this point in the history
[ENH] Parameter Fitter: Basic implementation
  • Loading branch information
lanzagar authored Nov 13, 2024
2 parents 747b3b2 + 932fe14 commit 2869a55
Show file tree
Hide file tree
Showing 16 changed files with 1,834 additions and 19 deletions.
15 changes: 13 additions & 2 deletions Orange/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from collections.abc import Iterable
import re
import warnings
from typing import Callable, Dict, Optional
from typing import Callable, Optional, NamedTuple, Type

import numpy as np
import scipy
Expand Down Expand Up @@ -88,6 +88,13 @@ class Learner(ReprableWithPreprocessors):
#: fitting the model
preprocessors = ()

class FittedParameter(NamedTuple):
name: str
label: str
type: Type
min: Optional[int] = None
max: Optional[int] = None

# Note: Do not use this class attribute.
# It remains here for compatibility reasons.
learner_adequacy_err_msg = ''
Expand Down Expand Up @@ -179,6 +186,10 @@ def active_preprocessors(self):
self.preprocessors is not type(self).preprocessors):
yield from type(self).preprocessors

@property
def fitted_parameters(self) -> list:
return []

# pylint: disable=no-self-use
def incompatibility_reason(self, _: Domain) -> Optional[str]:
"""Return None if a learner can fit domain or string explaining why it can not."""
Expand Down Expand Up @@ -883,5 +894,5 @@ def __init__(self, preprocessors=None, **kwargs):
self.params = kwargs

@SklLearner.params.setter
def params(self, values: Dict):
def params(self, values: dict):
self._params = values
24 changes: 17 additions & 7 deletions Orange/evaluation/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def _identity(x):


def _mp_worker(fold_i, train_data, test_data, learner_i, learner,
store_models):
store_models, suppresses_exceptions=True):
predicted, probs, model, failed = None, None, None, False
train_time, test_time = None, None
try:
Expand All @@ -45,6 +45,8 @@ def _mp_worker(fold_i, train_data, test_data, learner_i, learner,
test_time = time() - t0
# Different models can fail at any time raising any exception
except Exception as ex: # pylint: disable=broad-except
if not suppresses_exceptions:
raise ex
failed = ex
return _MpResults(fold_i, learner_i, store_models and model,
failed, len(test_data), predicted, probs,
Expand Down Expand Up @@ -96,6 +98,7 @@ def __init__(self, data=None, *,
row_indices=None, folds=None, score_by_folds=True,
learners=None, models=None, failed=None,
actual=None, predicted=None, probabilities=None,
# pylint: disable=unused-argument
store_data=None, store_models=None,
train_time=None, test_time=None):
"""
Expand Down Expand Up @@ -426,7 +429,8 @@ def fit(self, *args, **kwargs):
DeprecationWarning)
return self(*args, **kwargs)

def __call__(self, data, learners, preprocessor=None, *, callback=None):
def __call__(self, data, learners, preprocessor=None, *, callback=None,
suppresses_exceptions=True):
"""
Args:
data (Orange.data.Table): data to be used (usually split) into
Expand All @@ -435,6 +439,7 @@ def __call__(self, data, learners, preprocessor=None, *, callback=None):
preprocessor (Orange.preprocess.Preprocess): preprocessor applied
on training data
callback (Callable): a function called to notify about the progress
suppresses_exceptions (bool): suppress the exceptions if True
Returns:
results (Result): results of testing
Expand All @@ -457,7 +462,10 @@ def __call__(self, data, learners, preprocessor=None, *, callback=None):
part_results = []
parts = np.linspace(.0, .99, len(learners) * len(indices) + 1)[1:]
for progress, part in zip(parts, args_iter):
part_results.append(_mp_worker(*(part + ())))
part_results.append(
_mp_worker(*(part + ()),
suppresses_exceptions=suppresses_exceptions)
)
callback(progress)
callback(1)

Expand Down Expand Up @@ -723,7 +731,7 @@ def __new__(cls, data=None, test_data=None, learners=None,
test_data=test_data, **kwargs)

def __call__(self, data, test_data, learners, preprocessor=None,
*, callback=None):
*, callback=None, suppresses_exceptions=True):
"""
Args:
data (Orange.data.Table): training data
Expand All @@ -732,6 +740,7 @@ def __call__(self, data, test_data, learners, preprocessor=None,
preprocessor (Orange.preprocess.Preprocess): preprocessor applied
on training data
callback (Callable): a function called to notify about the progress
suppresses_exceptions (bool): suppress the exceptions if True
Returns:
results (Result): results of testing
Expand All @@ -746,7 +755,7 @@ def __call__(self, data, test_data, learners, preprocessor=None,
for (learner_i, learner) in enumerate(learners):
part_results.append(
_mp_worker(0, train_data, test_data, learner_i, learner,
self.store_models))
self.store_models, suppresses_exceptions))
callback((learner_i + 1) / len(learners))
callback(1)

Expand Down Expand Up @@ -778,13 +787,14 @@ def __new__(cls, data=None, learners=None, preprocessor=None, **kwargs):
**kwargs)

def __call__(self, data, learners, preprocessor=None, *, callback=None,
**kwargs):
suppresses_exceptions=True, **kwargs):
kwargs.setdefault("test_data", data)
# if kwargs contains anything besides test_data, this will be detected
# (and complained about) by super().__call__
return super().__call__(
data=data, learners=learners, preprocessor=preprocessor,
callback=callback, **kwargs)
callback=callback, suppresses_exceptions=suppresses_exceptions,
**kwargs)


def sample(table, n=0.7, stratified=False, replace=False,
Expand Down
7 changes: 6 additions & 1 deletion Orange/modelling/randomforest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from Orange.base import RandomForestModel
from Orange.base import RandomForestModel, Learner
from Orange.classification import RandomForestLearner as RFClassification
from Orange.data import Variable
from Orange.modelling import SklFitter
Expand All @@ -24,3 +24,8 @@ class RandomForestLearner(SklFitter, _FeatureScorerMixin):
'regression': RFRegression}

__returns__ = RandomForestModel

@property
def fitted_parameters(self) -> list[Learner.FittedParameter]:
return [self.FittedParameter("n_estimators", "Number of trees",
int, 1, None)]
12 changes: 8 additions & 4 deletions Orange/regression/pls.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
from typing import Tuple

import numpy as np
import scipy.stats as ss
import sklearn.cross_decomposition as skl_pls
from sklearn.preprocessing import StandardScaler

from Orange.base import Learner
from Orange.data import Table, Domain, Variable, \
ContinuousVariable, StringVariable
from Orange.data.util import get_unique_names, SharedComputeValue
Expand Down Expand Up @@ -163,11 +162,11 @@ def coefficients_table(self):
return coef_table

@property
def rotations(self) -> Tuple[np.ndarray, np.ndarray]:
def rotations(self) -> tuple[np.ndarray, np.ndarray]:
return self.skl_model.x_rotations_, self.skl_model.y_rotations_

@property
def loadings(self) -> Tuple[np.ndarray, np.ndarray]:
def loadings(self) -> tuple[np.ndarray, np.ndarray]:
return self.skl_model.x_loadings_, self.skl_model.y_loadings_

def residuals_normal_probability(self, data: Table) -> Table:
Expand Down Expand Up @@ -256,6 +255,11 @@ def incompatibility_reason(self, domain):
reason = "Only numeric target variables expected."
return reason

@property
def fitted_parameters(self) -> list[Learner.FittedParameter]:
return [self.FittedParameter("n_components", "Components",
int, 1, None)]


if __name__ == '__main__':
import Orange
Expand Down
5 changes: 5 additions & 0 deletions Orange/regression/tests/test_pls.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ def table(rows, attr, variables):


class TestPLSRegressionLearner(unittest.TestCase):
def test_fitted_parameters(self):
fitted_parameters = PLSRegressionLearner().fitted_parameters
self.assertIsInstance(fitted_parameters, list)
self.assertEqual(len(fitted_parameters), 1)

def test_allow_y_dim(self):
""" The current PLS version allows only a single Y dimension. """
learner = PLSRegressionLearner(n_components=2)
Expand Down
26 changes: 26 additions & 0 deletions Orange/widgets/evaluate/icons/ParameterFitter.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 2869a55

Please sign in to comment.