sklearn ConvergenceWarning: What It Means and How to Fix It

sklearn ConvergenceWarning: What It Means and How to Fix It

You ran a model, it produced predictions, and buried somewhere in your output is this:

ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.
Increase the number of iterations (max_iter) or scale the data as shown in:
    https://scikit-learn.org/stable/modules/preprocessing.html

The model did not crash. The warning is easy to ignore. You should not ignore it — the optimizer stopped before finding the best solution, and your model's parameters are not optimal. Here is what causes it and how to fix each case.


Which Models Emit ConvergenceWarning

Any sklearn estimator that uses an iterative optimizer can emit this warning. The most common ones are:

  • LogisticRegression — solvers: lbfgs (default), saga, liblinear, newton-cg, sag
  • LinearSVC / SVC — internal optimizer has its own iteration limit
  • MLPClassifier / MLPRegressor — gradient descent over neural network weights
  • SGDClassifier / SGDRegressor — stochastic gradient descent with max_iter
  • Ridge, Lasso, ElasticNet — coordinate descent variants

In each case, the optimizer runs until it hits max_iter without the loss function change falling below tol. The warning means: the algorithm was cut off mid-optimization.


Root Cause 1: Too Few Iterations

The default max_iter for LogisticRegression is 100. For many real-world datasets this is not enough. The fix is straightforward: increase max_iter.

from sklearn.linear_model import LogisticRegression
from sklearn.datasets import make_classification

X, y = make_classification(n_samples=10000, n_features=50, random_state=42)

# Before: hits ConvergenceWarning
model = LogisticRegression()
model.fit(X, y)

# Fix: increase max_iter
model = LogisticRegression(max_iter=1000)
model.fit(X, y)

How many iterations do you actually need? Check model.n_iter_ after fitting to see how many the optimizer used:

model = LogisticRegression(max_iter=10000)
model.fit(X, y)
print(model.n_iter_)  # e.g., [342]
# Set max_iter to something comfortably above this

The same attribute is available as model.n_iter_ on MLPClassifier and SGDClassifier. Use it to size max_iter empirically rather than guessing.


Root Cause 2: Unscaled Features

This is the most common cause and the one the warning message itself hints at. Gradient-based optimizers assume features are on roughly the same scale. If one feature spans 0–1 and another spans 0–1,000,000, the optimizer has to take tiny steps to avoid overshooting the small-scale features, which means it needs many more iterations — or never converges at all.

from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.datasets import make_classification
import numpy as np

X, y = make_classification(n_samples=5000, n_features=20, random_state=42)

# Simulate badly scaled data
X[:, 0] *= 1_000_000
X[:, 1] /= 1_000_000

# Fix: scale inside a Pipeline so train/test scaling stays correct
pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('model', LogisticRegression(max_iter=200))
])

pipeline.fit(X, y)

Always scale inside a Pipeline — not before the train/test split. Fitting the scaler on the full dataset before splitting leaks information from the test set into training. See the SimpleImputer leakage trap for an identical pattern with imputation.

Use StandardScaler (zero mean, unit variance) for most cases. Use MinMaxScaler when you need bounded output. Use RobustScaler when your data has significant outliers.


Root Cause 3: Wrong Solver for the Problem

Different solvers have different convergence properties. The lbfgs default in LogisticRegression is a good general-purpose choice, but it is not always the fastest to converge.

from sklearn.linear_model import LogisticRegression
from sklearn.datasets import make_classification

X, y = make_classification(n_samples=100000, n_features=100, random_state=42)

# lbfgs can struggle on very large datasets
# saga converges faster on large n_samples + supports L1
model = LogisticRegression(solver='saga', max_iter=200, n_jobs=-1)
model.fit(X, y)

Solver selection guide for LogisticRegression:

  • lbfgs — default, good for small-to-medium datasets, L2 only
  • saga — best for large datasets (n_samples > 10,000), supports L1 and ElasticNet
  • liblinear — fast for small datasets, supports L1, no multiclass one-vs-rest by default
  • sag — stochastic average gradient, similar to saga but L2 only

For MLPClassifier, try switching from solver='adam' (default) to solver='lbfgs' for small datasets — lbfgs often converges in fewer iterations on small data.

from sklearn.neural_network import MLPClassifier

# For small datasets, lbfgs converges better than adam
model = MLPClassifier(
    hidden_layer_sizes=(100, 50),
    solver='lbfgs',
    max_iter=500
)
model.fit(X, y)

When It Is Safe to Suppress the Warning

There are two legitimate reasons to suppress the warning rather than fix the root cause:

  1. You are doing a quick exploratory analysis and do not care about optimal parameters yet.
  2. You have already verified that the model's evaluation metrics are acceptable and additional iterations produce negligible improvement.

To verify that more iterations do not help:

from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
import numpy as np

# Compare scores at different max_iter values
for n in [100, 500, 1000, 5000]:
    model = LogisticRegression(max_iter=n)
    scores = cross_val_score(model, X, y, cv=5, scoring='accuracy')
    print(f"max_iter={n:5d}: {np.mean(scores):.4f} ± {np.std(scores):.4f}")

If the score plateaus after 500 iterations and you are comfortable with that, you can suppress cleanly:

import warnings
from sklearn.exceptions import ConvergenceWarning

# Suppress only ConvergenceWarning, not all warnings
with warnings.catch_warnings():
    warnings.filterwarnings('ignore', category=ConvergenceWarning)
    model.fit(X, y)

Prefer the context manager form over warnings.filterwarnings('ignore') globally — the global form silences other useful warnings for the rest of the process.


Quick Fix Checklist

  1. Add StandardScaler in a Pipeline before the model — fixes most cases instantly.
  2. Increase max_iter to 1000, check model.n_iter_, set to 2x that value.
  3. Switch solver: try saga for large datasets, liblinear for small ones.
  4. Decrease tol only as a last resort — it makes convergence criteria looser, which is not the same as actually converging.

If you are building a full ML pipeline with preprocessing, see also the top 10 ML algorithms guide for solver trade-offs across model families.