sklearn-morpho 0.2.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ from .classifiers import (
2
+ LDEP as LDEP,
3
+ RDEP as RDEP,
4
+ MorphoPerceptron as MorphoPerceptron,
5
+ )
@@ -0,0 +1,3 @@
1
+ from .ldep import LDEP as LDEP
2
+ from .rdep import RDEP as RDEP
3
+ from .simple_perceptron import MorphoPerceptron as MorphoPerceptron
@@ -0,0 +1,203 @@
1
+ from __future__ import annotations
2
+
3
+ import numpy as np
4
+ from typing import Literal, cast
5
+
6
+ from sklearn.base import BaseEstimator, ClassifierMixin
7
+ from sklearn.preprocessing import StandardScaler
8
+ from sklearn.utils import check_random_state
9
+ from sklearn.utils.validation import validate_data, check_is_fitted
10
+ from sklearn.utils.multiclass import unique_labels
11
+
12
+ from ..training.dccp_ldep import LDEPDccpTrainer
13
+ from ..stopping import (
14
+ StoppingMethod,
15
+ CostStoppingMethod,
16
+ EarlyStoppingMethod,
17
+ EpochStoppingMethod,
18
+ TrainStopStoppingMethod,
19
+ )
20
+ from ..weighting import SampleWeighting, NoneSampleWeighting
21
+
22
+
23
+ class LDEP(ClassifierMixin, BaseEstimator):
24
+ """
25
+ Scikit-learn estimator wrapper around a l-DEP (linear Dilation-Erosion
26
+ morphological Perceptron) for binary data classification.
27
+
28
+ The l-DEP's activation function is defined as:
29
+
30
+ \\[ y = f(\\lambda \\tau_(R_1(x)) + (1 - \\lambda) \\tau'_(R_2(x))) \\]
31
+
32
+ Where $\\tau$ refers to the activation of a (max, +) morphological
33
+ perceptron and $\\tau'$ to a (min, +) one.
34
+ $R_1, R_2$ are linear transformations to apply to the training data.
35
+ They convert it into a latent space with possibly different dimensions,
36
+ also allowing classification of arbitrarily distributed data.
37
+ A higher dimension for the latent space will result in slower training
38
+ times, but will allow the decision boundary to be more complex.
39
+ """
40
+
41
+ def __init__(
42
+ self,
43
+ latent_dims: tuple[int, int] = (10, 10),
44
+ margin=1.0,
45
+ penalty=0.0,
46
+ validation_ratio=0.3,
47
+ weighting_method: SampleWeighting | None = None,
48
+ stopping_methods: list[StoppingMethod] | None = None,
49
+ use_dccp_library: bool = False,
50
+ verbose: Literal[0, 1, 2] = 0,
51
+ random_state: np.random.RandomState | None = None,
52
+ ) -> None:
53
+ """
54
+ Initialize the classifier, see class help for more.
55
+
56
+ param latent_dims: The dimensions of the latent spaces used for the
57
+ linear transformations output
58
+ param margin: Enforce a margin between the decision boundary
59
+ and the data. May help with linearly separable
60
+ datasets, but generally lower is more accurate.
61
+ param penalty: A penalty to add to the weights squared and
62
+ avoid them exploding.
63
+ Must be a small positive number like 1e-6, or
64
+ zero to disable penalty calculation altogether.
65
+ param validation_radio: How much of the training set to dedicate to use
66
+ as validation during fitting.
67
+ Must be between 0 and 1 (inclusive, exclusive),
68
+ if set to exactly 0 then incompatible stopping
69
+ methods cannot be used (e.g. early stopping).
70
+ Ignored when using the dccp library solver.
71
+ param weighting_method: The weighting method to use: apply weights to
72
+ the cost contribution of each data point to help
73
+ avoid outliers.
74
+ If left to None, will use NoneWeightingMethod().
75
+ param stopping_methods: A list of stopping methods, must not be empty.
76
+ At each epoch, these methods will be
77
+ sequentially asked whether the training should
78
+ stop. In this case, epoch ends by rolling back
79
+ to the epoch with the best validation cost.
80
+ If left to None, will use:
81
+ [
82
+ CostStoppingMethod(),
83
+ EarlyStoppingMethod(),
84
+ EpochStoppingMethod(),
85
+ TrainStopStoppingMethod(),
86
+ ]
87
+ Ignored when using the dccp library solver.
88
+ param use_dccp_library: Whether to use the dccp library as a solver or
89
+ a manual constraints linearization in fit.
90
+ param verbose: Whether to log extra information. 0: no logging,
91
+ 1: basic logging / timing, 2: cvxpy solve() set
92
+ to verbose mode.
93
+ param random_state: A RandomState object or None to allow for seeded
94
+ randomness.
95
+ """
96
+
97
+ self.latent_dims = latent_dims
98
+ self.margin = margin
99
+ self.penalty = penalty
100
+ self.validation_ratio = validation_ratio
101
+ self.weighting_method = weighting_method
102
+ self.stopping_methods = stopping_methods
103
+ self.use_dccp_library = use_dccp_library
104
+ self.verbose: Literal[0, 1, 2] = verbose
105
+ self.random_state = random_state
106
+
107
+ def fit(self, X: np.ndarray, y: np.ndarray) -> LDEP:
108
+ """
109
+ Fit the classifier, create attributes:
110
+ - self.max_perceptron_
111
+ - self.min_perceptron_
112
+ - self.lambda_
113
+ - self.max_matrix_
114
+ - self.min_matrix_
115
+ - self.classes_: Unique labels generated from y
116
+
117
+ X and y must represent binary classifiable data.
118
+ """
119
+
120
+ # input data validation
121
+ random_state = check_random_state(self.random_state)
122
+ X, y = validate_data(self, X, y) # type: ignore
123
+ self.scaler_ = StandardScaler()
124
+ X_scaled = self.scaler_.fit_transform(X)
125
+
126
+ # set unset parameters to their default values
127
+ if self.weighting_method is None:
128
+ weighting_method = NoneSampleWeighting()
129
+ else:
130
+ weighting_method = self.weighting_method
131
+ if self.stopping_methods is None:
132
+ stopping_methods = [
133
+ CostStoppingMethod(),
134
+ EarlyStoppingMethod(),
135
+ EpochStoppingMethod(),
136
+ TrainStopStoppingMethod(),
137
+ ]
138
+ else:
139
+ stopping_methods = self.stopping_methods
140
+
141
+ # create classes and convert them to distinct integers for fitting
142
+ # the classes are persisted inside the object for use in predict
143
+ self.classes_ = unique_labels(y)
144
+ classes_list = list(self.classes_)
145
+ y_integers = np.array(
146
+ [classes_list.index(c) for c in y], dtype=np.int32
147
+ )
148
+
149
+ if len(classes_list) != 2:
150
+ raise ValueError(
151
+ 'Only binary classification is supported but '
152
+ f'got {len(classes_list)} class(es).'
153
+ )
154
+
155
+ # create and train perceptrons
156
+ trainer = LDEPDccpTrainer(
157
+ self.latent_dims,
158
+ self.margin,
159
+ self.penalty,
160
+ self.validation_ratio,
161
+ weighting_method,
162
+ stopping_methods,
163
+ self.use_dccp_library,
164
+ self.verbose,
165
+ random_state,
166
+ )
167
+
168
+ trainer.train(X_scaled, y_integers)
169
+ self.max_perceptron_ = trainer.max_perceptron
170
+ self.min_perceptron_ = trainer.min_perceptron
171
+ self.lambda_ = trainer.lambda_
172
+ self.max_matrix_ = trainer.max_matrix
173
+ self.min_matrix_ = trainer.min_matrix
174
+
175
+ return self
176
+
177
+ def decision_function(self, X: np.ndarray) -> np.ndarray:
178
+ check_is_fitted(self)
179
+ X = validate_data(self, X, reset=False) # type: ignore
180
+ X_scaled = cast(np.ndarray, self.scaler_.transform(X))
181
+
182
+ expr_max = np.max(
183
+ self.max_perceptron_ + X_scaled @ self.max_matrix_.T, axis=1
184
+ )
185
+ expr_min = np.min(
186
+ self.min_perceptron_ + X_scaled @ self.min_matrix_.T, axis=1
187
+ )
188
+ return expr_max * self.lambda_ + expr_min * (1 - self.lambda_)
189
+
190
+ def predict(self, X: np.ndarray) -> np.ndarray:
191
+ check_is_fitted(self)
192
+ return self.classes_[(self.decision_function(X) >= 0).astype(int)]
193
+
194
+ def __sklearn_tags__(self):
195
+ """
196
+ Overriden method to allow check_estimator to not run accuracy tests.
197
+ These are designed for perceptrons with a linear decision boundary,
198
+ which is not the case for a morphological perceptron.
199
+ """
200
+
201
+ tags = super().__sklearn_tags__()
202
+ tags.classifier_tags.multi_class = False
203
+ return tags
@@ -0,0 +1,197 @@
1
+ from __future__ import annotations
2
+
3
+ import numpy as np
4
+ from typing import Literal, cast
5
+
6
+ from sklearn.base import BaseEstimator, ClassifierMixin
7
+ from sklearn.preprocessing import StandardScaler
8
+ from sklearn.utils import check_random_state
9
+ from sklearn.utils.validation import validate_data, check_is_fitted
10
+ from sklearn.utils.multiclass import unique_labels
11
+
12
+ from ..training.dccp_rdep import RDEPDccpTrainer
13
+ from ..stopping import (
14
+ StoppingMethod,
15
+ CostStoppingMethod,
16
+ EarlyStoppingMethod,
17
+ EpochStoppingMethod,
18
+ TrainStopStoppingMethod,
19
+ )
20
+ from ..weighting import SampleWeighting, NoneSampleWeighting
21
+
22
+
23
+ class RDEP(ClassifierMixin, BaseEstimator):
24
+ """
25
+ Scikit-learn estimator wrapper around a r-DEP (reduced Dilation-Erosion
26
+ morphological Perceptron) for binary data classification.
27
+
28
+ The r-DEP's activation function is defined as:
29
+
30
+ \\[ y = f(\\lambda \\tau_(x) + (1 - \\lambda) \\tau'_(x)) \\]
31
+
32
+ Where $\\tau$ refers to the activation of a (max, +) morphological
33
+ perceptron and $\\tau'$ to a (min, +) one.
34
+ $\\lambda$ is a real number between 0 and 1 to guarantee correct convexity,
35
+ but in practice a smaller interval can be enforced to avoid imprecisions.
36
+ """
37
+
38
+ def __init__(
39
+ self,
40
+ lambda_bounds=(1e-3, 1 - 1e-3),
41
+ margin=0.0,
42
+ penalty=0.0,
43
+ validation_ratio=0.3,
44
+ weighting_method: SampleWeighting | None = None,
45
+ stopping_methods: list[StoppingMethod] | None = None,
46
+ use_dccp_library: bool = False,
47
+ verbose: Literal[0, 1, 2] = 0,
48
+ random_state: np.random.RandomState | None = None,
49
+ ) -> None:
50
+ """
51
+ Initialize the classifier, see class help for more.
52
+
53
+ param lambda_bounds: A pair of min and max values for lambda, to
54
+ avoid solvers (especially dccp) from failing to
55
+ optimize.
56
+ To keep the constraints at the right convexity,
57
+ the bounds must be inside [0, 1].
58
+ param margin: Enforce a margin between the decision boundary
59
+ and the data. May help with linearly separable
60
+ datasets, but generally lower is more accurate.
61
+ param penalty: A penalty to add to the weights squared and
62
+ avoid them exploding.
63
+ Must be a small positive number like 1e-6, or
64
+ zero to disable penalty calculation altogether.
65
+ param validation_radio: How much of the training set to dedicate to use
66
+ as validation during fitting.
67
+ Must be between 0 and 1 (inclusive, exclusive),
68
+ if set to exactly 0 then incompatible stopping
69
+ methods cannot be used (e.g. early stopping).
70
+ Ignored when using the dccp library solver.
71
+ param weighting_method: The weighting method to use: apply weights to
72
+ the cost contribution of each data point to help
73
+ avoid outliers.
74
+ If left to None, will use NoneWeightingMethod().
75
+ param stopping_methods: A list of stopping methods, must not be empty.
76
+ At each epoch, these methods will be
77
+ sequentially asked whether the training should
78
+ stop. In this case, epoch ends by rolling back
79
+ to the epoch with the best validation cost.
80
+ If left to None, will use:
81
+ [
82
+ CostStoppingMethod(),
83
+ EarlyStoppingMethod(),
84
+ EpochStoppingMethod(),
85
+ TrainStopStoppingMethod(),
86
+ ]
87
+ Ignored when using the dccp library solver.
88
+ param use_dccp_library: Whether to use the dccp library as a solver or
89
+ a manual constraints linearization in fit.
90
+ param verbose: Whether to log extra information. 0: no logging,
91
+ 1: basic logging / timing, 2: cvxpy solve() set
92
+ to verbose mode.
93
+ param random_state: A RandomState object or None to allow for seeded
94
+ randomness.
95
+ """
96
+
97
+ self.lambda_bounds = lambda_bounds
98
+ self.margin = margin
99
+ self.penalty = penalty
100
+ self.validation_ratio = validation_ratio
101
+ self.weighting_method = weighting_method
102
+ self.stopping_methods = stopping_methods
103
+ self.use_dccp_library = use_dccp_library
104
+ self.verbose: Literal[0, 1, 2] = verbose
105
+ self.random_state = random_state
106
+
107
+ def fit(self, X: np.ndarray, y: np.ndarray) -> RDEP:
108
+ """
109
+ Fit the classifier, create attributes:
110
+ - self.max_perceptron_
111
+ - self.min_perceptron_
112
+ - self.classes_: Unique labels generated from y
113
+
114
+ X and y must represent binary classifiable data.
115
+ """
116
+
117
+ # input data validation
118
+ random_state = check_random_state(self.random_state)
119
+ X, y = validate_data(self, X, y) # type: ignore
120
+ self.scaler_ = StandardScaler()
121
+ X_scaled = self.scaler_.fit_transform(X)
122
+
123
+ # set unset parameters to their default values
124
+ if self.weighting_method is None:
125
+ weighting_method = NoneSampleWeighting()
126
+ else:
127
+ weighting_method = self.weighting_method
128
+ if self.stopping_methods is None:
129
+ stopping_methods = [
130
+ CostStoppingMethod(),
131
+ EarlyStoppingMethod(),
132
+ EpochStoppingMethod(),
133
+ TrainStopStoppingMethod(),
134
+ ]
135
+ else:
136
+ stopping_methods = self.stopping_methods
137
+
138
+ # create classes and convert them to distinct integers for fitting
139
+ # the classes are persisted inside the object for use in predict
140
+ self.classes_ = unique_labels(y)
141
+ classes_list = list(self.classes_)
142
+ y_integers = np.array(
143
+ [classes_list.index(c) for c in y], dtype=np.int32
144
+ )
145
+
146
+ if len(classes_list) != 2:
147
+ raise ValueError(
148
+ 'Only binary classification is supported but '
149
+ f'got {len(classes_list)} class(es).'
150
+ )
151
+
152
+ # create and train perceptrons
153
+ trainer = RDEPDccpTrainer(
154
+ self.lambda_bounds,
155
+ self.margin,
156
+ self.penalty,
157
+ self.validation_ratio,
158
+ weighting_method,
159
+ stopping_methods,
160
+ self.use_dccp_library,
161
+ self.verbose,
162
+ random_state,
163
+ )
164
+
165
+ trainer.train(X_scaled, y_integers)
166
+ self.max_perceptron_ = trainer.max_perceptron
167
+ self.min_perceptron_ = trainer.min_perceptron
168
+ self.lambda_ = trainer.lambda_
169
+ self.invert_res_ = trainer.invert_res
170
+
171
+ return self
172
+
173
+ def decision_function(self, X: np.ndarray) -> np.ndarray:
174
+ check_is_fitted(self)
175
+ X = validate_data(self, X, reset=False) # type: ignore
176
+ X_scaled = cast(np.ndarray, self.scaler_.transform(X))
177
+
178
+ expr_max = np.max(self.max_perceptron_ + X_scaled, axis=1)
179
+ expr_min = np.min(self.min_perceptron_ + X_scaled, axis=1)
180
+ activation = expr_max * self.lambda_ + expr_min * (1 - self.lambda_)
181
+
182
+ return activation * (1 - 2 * self.invert_res_)
183
+
184
+ def predict(self, X: np.ndarray) -> np.ndarray:
185
+ check_is_fitted(self)
186
+ return self.classes_[(self.decision_function(X) >= 0).astype(int)]
187
+
188
+ def __sklearn_tags__(self):
189
+ """
190
+ Overriden method to allow check_estimator to not run accuracy tests.
191
+ These are designed for perceptrons with a linear decision boundary,
192
+ which is not the case for a morphological perceptron.
193
+ """
194
+
195
+ tags = super().__sklearn_tags__()
196
+ tags.classifier_tags.multi_class = False
197
+ return tags
@@ -0,0 +1,187 @@
1
+ from __future__ import annotations
2
+
3
+ import numpy as np
4
+ from typing import Literal, cast
5
+
6
+ from sklearn.base import BaseEstimator, ClassifierMixin
7
+ from sklearn.preprocessing import StandardScaler
8
+ from sklearn.utils import check_random_state
9
+ from sklearn.utils.validation import validate_data, check_is_fitted
10
+ from sklearn.utils.multiclass import unique_labels
11
+
12
+ from ..training.dccp_simple_perceptron import SimplePerceptronDccpTrainer
13
+ from ..stopping import (
14
+ StoppingMethod,
15
+ CostStoppingMethod,
16
+ EarlyStoppingMethod,
17
+ EpochStoppingMethod,
18
+ TrainStopStoppingMethod,
19
+ )
20
+ from ..weighting import SampleWeighting, NoneSampleWeighting
21
+
22
+
23
+ class MorphoPerceptron(ClassifierMixin, BaseEstimator):
24
+ """
25
+ Scikit-learn estimator wrapper around a simple morphological perceptron.
26
+
27
+ The morphological perceptron's activation function is defined as:
28
+
29
+ \\[ y = \\tau(w + x) \\]
30
+
31
+ Where $\\tau$ refers to max (resp. min), for a dilation (resp. erosion)
32
+ perceptron and $w$ the perceptron's weights.
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ kind: Literal['max', 'min'],
38
+ margin=0.0,
39
+ penalty=0.0,
40
+ validation_ratio=0.3,
41
+ weighting_method: SampleWeighting | None = None,
42
+ stopping_methods: list[StoppingMethod] | None = None,
43
+ use_dccp_library: bool = False,
44
+ verbose: Literal[0, 1, 2] = 0,
45
+ random_state: np.random.RandomState | None = None,
46
+ ) -> None:
47
+ """
48
+ Initialize the classifier, see class help for more.
49
+
50
+ param kind: Whether the perceptron is dilation or erosion.
51
+ param margin: Enforce a margin between the decision boundary
52
+ and the data. May help with linearly separable
53
+ datasets, but generally lower is more accurate.
54
+ param penalty: A penalty to add to the weights squared and
55
+ avoid them exploding.
56
+ Must be a small positive number like 1e-6, or
57
+ zero to disable penalty calculation altogether.
58
+ param validation_radio: How much of the training set to dedicate to use
59
+ as validation during fitting.
60
+ Must be between 0 and 1 (inclusive, exclusive),
61
+ if set to exactly 0 then incompatible stopping
62
+ methods cannot be used (e.g. early stopping).
63
+ Ignored when using the dccp library solver.
64
+ param weighting_method: The weighting method to use: apply weights to
65
+ the cost contribution of each data point to help
66
+ avoid outliers.
67
+ If left to None, will use NoneWeightingMethod().
68
+ param stopping_methods: A list of stopping methods, must not be empty.
69
+ At each epoch, these methods will be
70
+ sequentially asked whether the training should
71
+ stop. In this case, epoch ends by rolling back
72
+ to the epoch with the best validation cost.
73
+ If left to None, will use:
74
+ [
75
+ CostStoppingMethod(),
76
+ EarlyStoppingMethod(),
77
+ EpochStoppingMethod(),
78
+ TrainStopStoppingMethod(),
79
+ ]
80
+ Ignored when using the dccp library solver.
81
+ param use_dccp_library: Whether to use the dccp library as a solver or
82
+ a manual constraints linearization in fit.
83
+ param verbose: Whether to log extra information. 0: no logging,
84
+ 1: basic logging / timing, 2: cvxpy solve() set
85
+ to verbose mode.
86
+ param random_state: A RandomState object or None to allow for seeded
87
+ randomness.
88
+ """
89
+
90
+ self.kind: Literal['max', 'min'] = kind
91
+ self.margin = margin
92
+ self.penalty = penalty
93
+ self.validation_ratio = validation_ratio
94
+ self.weighting_method = weighting_method
95
+ self.stopping_methods = stopping_methods
96
+ self.use_dccp_library = use_dccp_library
97
+ self.verbose: Literal[0, 1, 2] = verbose
98
+ self.random_state = random_state
99
+
100
+ def fit(self, X: np.ndarray, y: np.ndarray) -> MorphoPerceptron:
101
+ """
102
+ Fit the classifier, create attributes:
103
+ - self.weights
104
+ - self.classes_: Unique labels generated from y
105
+
106
+ X and y must represent binary classifiable data.
107
+ """
108
+
109
+ # input data validation
110
+ random_state = check_random_state(self.random_state)
111
+ X, y = validate_data(self, X, y) # type: ignore
112
+ self.scaler_ = StandardScaler()
113
+ X_scaled = self.scaler_.fit_transform(X)
114
+
115
+ # set unset parameters to their default values
116
+ if self.weighting_method is None:
117
+ weighting_method = NoneSampleWeighting()
118
+ else:
119
+ weighting_method = self.weighting_method
120
+ if self.stopping_methods is None:
121
+ stopping_methods = [
122
+ CostStoppingMethod(),
123
+ EarlyStoppingMethod(),
124
+ EpochStoppingMethod(),
125
+ TrainStopStoppingMethod(),
126
+ ]
127
+ else:
128
+ stopping_methods = self.stopping_methods
129
+
130
+ # create classes and convert them to distinct integers for fitting
131
+ # the classes are persisted inside the object for use in predict
132
+ self.classes_ = unique_labels(y)
133
+ classes_list = list(self.classes_)
134
+ y_integers = np.array(
135
+ [classes_list.index(c) for c in y], dtype=np.int32
136
+ )
137
+
138
+ if len(classes_list) != 2:
139
+ raise ValueError(
140
+ 'Only binary classification is supported but '
141
+ f'got {len(classes_list)} class(es).'
142
+ )
143
+
144
+ # create and train perceptrons
145
+ trainer = SimplePerceptronDccpTrainer(
146
+ self.kind,
147
+ self.margin,
148
+ self.penalty,
149
+ self.validation_ratio,
150
+ weighting_method,
151
+ stopping_methods,
152
+ self.use_dccp_library,
153
+ self.verbose,
154
+ random_state,
155
+ )
156
+
157
+ trainer.train(X_scaled, y_integers)
158
+ self.weights_ = trainer.weights
159
+
160
+ return self
161
+
162
+ def decision_function(self, X: np.ndarray) -> np.ndarray:
163
+ check_is_fitted(self)
164
+ X = validate_data(self, X, reset=False) # type: ignore
165
+ X_scaled = cast(np.ndarray, self.scaler_.transform(X))
166
+
167
+ expr = self.weights_ + X_scaled
168
+
169
+ if self.kind == 'max':
170
+ return expr.max(axis=1)
171
+ return expr.min(axis=1)
172
+
173
+ def predict(self, X: np.ndarray) -> np.ndarray:
174
+ check_is_fitted(self)
175
+ return self.classes_[(self.decision_function(X) >= 0).astype(int)]
176
+
177
+ def __sklearn_tags__(self):
178
+ """
179
+ Overriden method to allow check_estimator to not run accuracy tests.
180
+ These are designed for perceptrons with a linear decision boundary,
181
+ which is not the case for a morphological perceptron.
182
+ """
183
+
184
+ tags = super().__sklearn_tags__()
185
+ tags.classifier_tags.multi_class = False
186
+ tags.classifier_tags.poor_score = True
187
+ return tags
@@ -0,0 +1,7 @@
1
+ from .stopping_base import StoppingMethod as StoppingMethod
2
+ from .stopping_cost import CostStoppingMethod as CostStoppingMethod
3
+ from .stopping_early import EarlyStoppingMethod as EarlyStoppingMethod
4
+ from .stopping_epoch import EpochStoppingMethod as EpochStoppingMethod
5
+ from .stopping_train_stop import (
6
+ TrainStopStoppingMethod as TrainStopStoppingMethod,
7
+ )
@@ -0,0 +1,27 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class StoppingMethod(ABC):
5
+ """
6
+ Abstract class for a fitting stopping method.
7
+ """
8
+
9
+ @abstractmethod
10
+ def requires_validation(self) -> bool:
11
+ """
12
+ Whether the stopping method requires the training to be split in
13
+ train/validation sets.
14
+
15
+ Can still be False even if using validation_cost.
16
+ In fact, the should_stop method should use the validation cost as the
17
+ main and most reliable cost, in case of no validation it will just be
18
+ the same as the training cost.
19
+ """
20
+
21
+ @abstractmethod
22
+ def should_stop(
23
+ self, n_epochs: int, train_cost: float, validation_cost: float
24
+ ) -> bool:
25
+ """
26
+ Given some information, return whether the fitting process should stop.
27
+ """
@@ -0,0 +1,24 @@
1
+ from . import StoppingMethod
2
+
3
+
4
+ class CostStoppingMethod(StoppingMethod):
5
+ """
6
+ Stopping method that triggers whenever the cost gets lower or equal to the
7
+ parameter.
8
+ """
9
+
10
+ def __init__(self, cost_threshold=1e-6):
11
+ if cost_threshold <= 0:
12
+ raise ValueError(
13
+ f'invalid done_threshold, expected > 0 but got {cost_threshold}'
14
+ )
15
+
16
+ self.cost_threshold = cost_threshold
17
+
18
+ def requires_validation(self) -> bool:
19
+ return False
20
+
21
+ def should_stop(
22
+ self, n_epochs: int, train_cost: float, validation_cost: float
23
+ ) -> bool:
24
+ return validation_cost <= self.cost_threshold