Skip to content

Commit 39e1cc1

Browse files
ogrisellorentzenchrglemaitre
authored
FIX properly report n_iter_ in case of fallback from Newton-Cholesky to LBFGS (scikit-learn#30100)
Co-authored-by: Christian Lorentzen <lorentzen.ch@gmail.com> Co-authored-by: Guillaume Lemaitre <g.lemaitre58@gmail.com>
1 parent bc8eb66 commit 39e1cc1

File tree

3 files changed

+50
-3
lines changed

3 files changed

+50
-3
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
- :class:`linear_model.LogisticRegression` and and other linear models that
2+
accept `solver="newton-cholesky"` now report the correct number of iterations
3+
when they fall back to the `"lbfgs"` solver because of a rank deficient
4+
Hessian matrix.
5+
By :user:`Olivier Grisel <ogrisel>`

sklearn/linear_model/_glm/_newton_solver.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,15 +184,15 @@ def fallback_lbfgs_solve(self, X, y, sample_weight):
184184
method="L-BFGS-B",
185185
jac=True,
186186
options={
187-
"maxiter": self.max_iter,
187+
"maxiter": self.max_iter - self.iteration,
188188
"maxls": 50, # default is 20
189189
"iprint": self.verbose - 1,
190190
"gtol": self.tol,
191191
"ftol": 64 * np.finfo(np.float64).eps,
192192
},
193193
args=(X, y, sample_weight, self.l2_reg_strength, self.n_threads),
194194
)
195-
self.n_iter_ = _check_optimize_result("lbfgs", opt_res)
195+
self.iteration += _check_optimize_result("lbfgs", opt_res)
196196
self.coef = opt_res.x
197197
self.converged = opt_res.status == 0
198198

sklearn/linear_model/tests/test_logistic.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
assert_array_equal,
1313
)
1414
from scipy import sparse
15-
from scipy.linalg import svd
15+
from scipy.linalg import LinAlgWarning, svd
1616

1717
from sklearn import config_context
1818
from sklearn._loss import HalfMultinomialLoss
@@ -2374,3 +2374,45 @@ def test_multi_class_deprecated():
23742374
lrCV = LogisticRegressionCV(multi_class="multinomial")
23752375
with pytest.warns(FutureWarning, match=msg):
23762376
lrCV.fit(X, y)
2377+
2378+
2379+
def test_newton_cholesky_fallback_to_lbfgs(global_random_seed):
2380+
# Wide data matrix should lead to a rank-deficient Hessian matrix
2381+
# hence make the Newton-Cholesky solver raise a warning and fallback to
2382+
# lbfgs.
2383+
X, y = make_classification(
2384+
n_samples=10, n_features=20, random_state=global_random_seed
2385+
)
2386+
C = 1e30 # very high C to nearly disable regularization
2387+
2388+
# Check that LBFGS can converge without any warning on this problem.
2389+
lr_lbfgs = LogisticRegression(solver="lbfgs", C=C)
2390+
with warnings.catch_warnings():
2391+
warnings.simplefilter("error")
2392+
lr_lbfgs.fit(X, y)
2393+
n_iter_lbfgs = lr_lbfgs.n_iter_[0]
2394+
2395+
assert n_iter_lbfgs >= 1
2396+
2397+
# Check that the Newton-Cholesky solver raises a warning and falls back to
2398+
# LBFGS. This should converge with the same number of iterations as the
2399+
# above call of lbfgs since the Newton-Cholesky triggers the fallback
2400+
# before completing the first iteration, for the problem setting at hand.
2401+
lr_nc = LogisticRegression(solver="newton-cholesky", C=C)
2402+
with ignore_warnings(category=LinAlgWarning):
2403+
lr_nc.fit(X, y)
2404+
n_iter_nc = lr_nc.n_iter_[0]
2405+
2406+
assert n_iter_nc == n_iter_lbfgs
2407+
2408+
# Trying to fit the same model again with a small iteration budget should
2409+
# therefore raise a ConvergenceWarning:
2410+
lr_nc_limited = LogisticRegression(
2411+
solver="newton-cholesky", C=C, max_iter=n_iter_lbfgs - 1
2412+
)
2413+
with ignore_warnings(category=LinAlgWarning):
2414+
with pytest.warns(ConvergenceWarning, match="lbfgs failed to converge"):
2415+
lr_nc_limited.fit(X, y)
2416+
n_iter_nc_limited = lr_nc_limited.n_iter_[0]
2417+
2418+
assert n_iter_nc_limited == lr_nc_limited.max_iter - 1

0 commit comments

Comments
 (0)