8000 DEP loss_ attribute in gradient boosting (#23079) · scikit-learn/scikit-learn@0d669dc · GitHub
[go: up one dir, main page]

Skip to content
Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 0d669dc

Browse files
authored
DEP loss_ attribute in gradient boosting (#23079)
1 parent 28db349 commit 0d669dc

File tree

3 files changed

+55
-18
lines changed

3 files changed

+55
-18
lines changed

doc/whats_new/v1.1.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,11 @@ Changelog
471471
the output feature names.
472472
:pr:`21762` by :user:`Zhehao Liu <MaxwellLZH>` and `Thomas Fan`_.
473473

474+
- |API| The attribute `loss_` of :class:`ensemble.GradientBoostingClassifier` and
475+
:class:`ensemble.GradientBoostingRegressor` has been deprecated and will be removed
476+
in version 1.3.
477+
:pr:`23079` by :user:`Christian Lorentzen <lorentzenchr>`.
478+
474479
:mod:`sklearn.feature_extraction`
475480
.................................
476481

sklearn/ensemble/_gb.py

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ def _fit_stage(
207207
"""Fit another stage of ``_n_classes`` trees to the boosting model."""
208208

209209
assert sample_mask.dtype == bool
210-
loss = self.loss_
210+
loss = self._loss
211211
original_y = y
212212

213213
# Need to pass a copy of raw_predictions to negative_gradient()
@@ -328,11 +328,11 @@ def _check_params(self):
328328
loss_class = _gb_losses.LOSS_FUNCTIONS[self.loss]
329329

330330
if is_classifier(self):
331-
self.loss_ = loss_class(self.n_classes_)
331+
self._loss = loss_class(self.n_classes_)
332332
elif self.loss in ("huber", "quantile"):
333-
self.loss_ = loss_class(self.alpha)
333+
self._loss = loss_class(self.alpha)
334334
else:
335-
self.loss_ = loss_class()
335+
self._loss = loss_class()
336336

337337
check_scalar(
338338
self.subsample,
@@ -346,7 +346,7 @@ def _check_params(self):
346346
if self.init is not None:
347347
# init must be an estimator or 'zero'
348348
if isinstance(self.init, BaseEstimator):
349-
self.loss_.check_init_estimator(self.init)
349+
self._loss.check_init_estimator(self.init)
350350
elif not (isinstance(self.init, str) and self.init == "zero"):
351351
raise ValueError(
352352
"The init parameter must be an estimator or 'zero'. "
@@ -439,9 +439,9 @@ def _init_state(self):
439439

440440
self.init_ = self.init
441441
if self.init_ is None:
442-
self.init_ = self.loss_.init_estimator()
442+
self.init_ = self._loss.init_estimator()
443443

444-
self.estimators_ = np.empty((self.n_estimators, self.loss_.K), dtype=object)
444+
self.estimators_ = np.empty((self.n_estimators, self._loss.K), dtype=object)
445445
self.train_score_ = np.zeros((self.n_estimators,), dtype=np.float64)
446446
# do oob?
447447
if self.subsample < 1.0:
@@ -471,7 +471,7 @@ def _resize_state(self):
471471
)
472472

473473
self.estimators_ = np.resize(
474-
self.estimators_, (total_n_estimators, self.loss_.K)
474+
self.estimators_, (total_n_estimators, self._loss.K)
475475
)
476476
self.train_score_ = np.resize(self.train_score_, total_n_estimators)
477477
if self.subsample < 1 or hasattr(self, "oob_improvement_"):
@@ -607,7 +607,7 @@ def fit(self, X, y, sample_weight=None, monitor=None):
607607
# fit initial model and initialize raw predictions
608608
if self.init_ == "zero":
609609
raw_predictions = np.zeros(
610-
shape=(X.shape[0], self.loss_.K), dtype=np.float64
610+
shape=(X.shape[0], self._loss.K), dtype=np.float64
611611
)
612612
else:
613613
# XXX clean this once we have a support_sample_weight tag
@@ -634,7 +634,7 @@ def fit(self, X, y, sample_weight=None, monitor=None):
634634
else: # regular estimator whose input checking failed
635635
raise
636636

637-
raw_predictions = self.loss_.get_init_raw_predictions(X, self.init_)
637+
raw_predictions = self._loss.get_init_raw_predictions(X, self.init_)
638638

639639
begin_at_stage = 0
640640

@@ -712,7 +712,7 @@ def _fit_stages(
712712
do_oob = self.subsample < 1.0
713713
sample_mask = np.ones((n_samples,), dtype=bool)
714714
n_inbag = max(1, int(self.subsample * n_samples))
715-
loss_ = self.loss_
715+
loss_ = self._loss
716716

717717
if self.verbose:
718718
verbose_reporter = VerboseReporter(verbose=self.verbose)
@@ -804,10 +804,10 @@ def _raw_predict_init(self, X):
804804
X = self.estimators_[0, 0]._validate_X_predict(X, check_input=True)
805805
if self.init_ == "zero":
806806
raw_predictions = np.zeros(
807-
shape=(X.shape[0], self.loss_.K), dtype=np.float64
807+
shape=(X.shape[0], self._loss.K), dtype=np.float64
808808
)
809809
else:
810-
raw_predictions = self.loss_.get_init_raw_predictions(X, self.init_).astype(
810+
raw_predictions = self._loss.get_init_raw_predictions(X, self.init_).astype(
811811
np.float64
812812
)
813813
return raw_predictions
@@ -978,6 +978,15 @@ def apply(self, X):
978978
def n_features_(self):
979979
return self.n_features_in_
980980

981+
# TODO(1.3): Remove
982+
# mypy error: Decorated property not supported
983+
@deprecated( # type: ignore
984+
"Attribute `loss_` was deprecated in version 1.1 and will be removed in 1.3."
985+
)
986+
@property
987+
def loss_(self):
988+
return self._loss
989+
981990

982991
class GradientBoostingClassifier(ClassifierMixin, BaseGradientBoosting):
983992
"""Gradient Boosting for classification.
@@ -1214,6 +1223,10 @@ class GradientBoostingClassifier(ClassifierMixin, BaseGradientBoosting):
12141223
loss_ : LossFunction
12151224
The concrete ``LossFunction`` object.
12161225
1226+
.. deprecated:: 1.1
1227+
Attribute `loss_` was deprecated in version 1.1 and will be
1228+
removed in 1.3.
1229+
12171230
init_ : estimator
12181231
The estimator that provides the initial predictions.
12191232
Set via the ``init`` argument or ``loss.init_estimator``.
@@ -1434,7 +1447,7 @@ def predict(self, X):
14341447
The predicted values.
14351448
"""
14361449
raw_predictions = self.decision_function(X)
1437-
encoded_labels = self.loss_._raw_prediction_to_decision(raw_predictions)
1450+
encoded_labels = self._loss._raw_prediction_to_decision(raw_predictions)
14381451
return self.classes_.take(encoded_labels, axis=0)
14391452

14401453
def staged_predict(self, X):
@@ -1456,7 +1469,7 @@ def staged_predict(self, X):
14561469
The predicted value of the input samples.
14571470
"""
14581471
for raw_predictions in self._staged_raw_predict(X):
1459-
encoded_labels = self.loss_._raw_prediction_to_decision(raw_predictions)
1472+
encoded_labels = self._loss._raw_prediction_to_decision(raw_predictions)
14601473
yield self.classes_.take(encoded_labels, axis=0)
14611474

14621475
def predict_proba(self, X):
@@ -1482,7 +1495,7 @@ def predict_proba(self, X):
14821495
"""
14831496
raw_predictions = self.decision_function(X)
14841497
try:
1485-
return self.loss_._raw_prediction_to_proba(raw_predictions)
1498+
return self._loss._raw_prediction_to_proba(raw_predictions)
14861499
except NotFittedError:
14871500
raise
14881501
except AttributeError as e:
@@ -1534,7 +1547,7 @@ def staged_predict_proba(self, X):
15341547
"""
15351548
try:
15361549
for raw_predictions in self._staged_raw_predict(X):
1537-
yield self.loss_._raw_prediction_to_proba(raw_predictions)
1550+
yield self._loss._raw_prediction_to_proba(raw_predictions)
15381551
except NotFittedError:
15391552
raise
15401553
except AttributeError as e:
@@ -1781,6 +1794,10 @@ class GradientBoostingRegressor(RegressorMixin, BaseGradientBoosting):
17811794
loss_ : LossFunction
17821795
The concrete ``LossFunction`` object.
17831796
1797+
.. deprecated:: 1.1
1798+
Attribute `loss_` was deprecated in version 1.1 and will be
1799+
removed in 1.3.
1800+
17841801
init_ : estimator
17851802
The estimator that provides the initial predictions.
17861803
Set via the ``init`` argument or ``loss.init_estimator``.

sklearn/ensemble/tests/test_gradient_boosting.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -475,7 +475,7 @@ def test_max_feature_regression():
475475
random_state=1,
476476
)
477477
gbrt.fit(X_train, y_train)
478-
log_loss = gbrt.loss_(y_test, gbrt.decision_function(X_test))
478+
log_loss = gbrt._loss(y_test, gbrt.decision_function(X_test))
479479
assert log_loss < 0.5, "GB failed with deviance %.4f" % log_loss
480480

481481

@@ -1535,3 +1535,18 @@ def test_loss_deprecated(old_loss, new_loss, Estimator):
15351535
est2 = Estimator(loss=new_loss, random_state=0)
15361536
est2.fit(X, y)
15371537
assert_allclose(est1.predict(X), est2.predict(X))
1538+
1539+
1540+
# TODO(1.3): remove
1541+
@pytest.mark.parametrize(
1542+
"Estimator", [GradientBoostingClassifier, GradientBoostingRegressor]
1543+
)
1544+
def test_loss_attribute_deprecation(Estimator):
1545+
# Check that we raise the proper deprecation warning if accessing
1546+
# `loss_`.
1547+
X = np.array([[1, 2], [3, 4]])
1548+
y = np.array([1, 0])
1549+
est = Estimator().fit(X, y)
1550+
1551+
with pytest.warns(FutureWarning, match="`loss_` was deprecated"):
1552+
est.loss_

0 commit comments

Comments
 (0)
0