Thanks to visit codestin.com
Credit goes to github.com

Skip to content

FEA array API support for LogisticRegressionCV - #33906

Merged
virchan merged 39 commits into
scikit-learn:mainfrom
OmarManzoor:array-api-lr_cv
Jul 22, 2026
Merged

FEA array API support for LogisticRegressionCV#33906
virchan merged 39 commits into
scikit-learn:mainfrom
OmarManzoor:array-api-lr_cv

Conversation

@OmarManzoor

@OmarManzoor OmarManzoor commented Apr 30, 2026

Copy link
Copy Markdown
Contributor

Reference Issues/PRs

Fixes #33346

What does this implement/fix? Explain your changes.

  • Adds array API support to LogisticRegressionCV when the underlying scoring function is also compatible with the array API.

AI usage disclosure

I used AI assistance for:

  • Code generation (used Codex for helping implement the code and also used the Gemini web application for helping implement certain areas)
  • Test/benchmark generation (used Codex to generate the main test for array API compliance for LogisticRegressionCV)
  • Documentation (including examples)
  • Research and understanding (used both Gemini web chat application and Codex)

Any other comments?

CC: @ogrisel

@OmarManzoor

OmarManzoor commented Apr 30, 2026

Copy link
Copy Markdown
Contributor Author

Benchmarks with mps are not that good:

n_samples, n_features, n_classes = 10000, 1000, 300

Average fit time numpy: 10.513
Average fit time torch mps: 13.098
Torch mps fit speedup: 0.8X

Average predict time numpy: 0.016
Average predict time torch mps: 0.012
Torch mps predict speedup: 1.33X

Details
from time import time

import numpy as np
import torch as xp
from tqdm import tqdm

from sklearn import config_context
from sklearn.linear_model import LogisticRegressionCV

n_samples, n_features, n_classes = 10000, 1000, 300
device = "mps"
n_iter = 10

X_np = np.random.rand(n_samples, n_features).astype(np.float32)
y_np = np.random.randint(0, n_classes, n_samples)
numpy_fit_times = []
numpy_predict_times = []
for _ in tqdm(range(n_iter), desc="Numpy"):
    lr = LogisticRegressionCV(
        Cs=[0.01, 0.1, 0.8],
        solver="lbfgs",
        max_iter=200,
        tol=1e-4,
        cv=5,
        use_legacy_attributes=False,
        l1_ratios=[0.0],
        scoring="neg_log_loss",
    )
    start = time()
    lr.fit(X_np, y_np)
    numpy_fit_times.append(round(time() - start, 3))
    start = time()
    pred = lr.predict_proba(X_np)
    numpy_predict_times.append(round(time() - start, 3))

avg_numpy_fit = round(sum(numpy_fit_times) / n_iter, 3)
avg_numpy_predict = round(sum(numpy_predict_times) / n_iter, 3)

torch_fit_times = []
torch_predict_times = []
X_xp = xp.rand((n_samples, n_features), dtype=xp.float32, device=device)
y_xp = xp.randint(0, n_classes, (n_samples,), device=device)
for _ in tqdm(range(n_iter), desc=f"Torch {device}"):
    with config_context(array_api_dispatch=True):
        lr = LogisticRegressionCV(
            Cs=[0.01, 0.1, 0.8],
            solver="lbfgs",
            max_iter=200,
            tol=1e-4,
            cv=5,
            use_legacy_attributes=False,
            l1_ratios=[0.0],
            scoring="neg_log_loss",
        )
        start = time()
        lr.fit(X_xp, y_xp)
        torch_fit_times.append(round(time() - start, 3))
        start = time()
        pred = lr.predict_proba(X_xp)
        first = float(pred[0, 0])
        torch_predict_times.append(round(time() - start, 3))

avg_torch_fit = round(sum(torch_fit_times) / n_iter, 3)
avg_torch_predict = round(sum(torch_predict_times) / n_iter, 3)

print(f"Average fit time numpy: {avg_numpy_fit}")
print(f"Average fit time torch {device}: {avg_torch_fit}")
print(f"Torch {device} fit speedup: {round(avg_numpy_fit / avg_torch_fit, 2)}X")

print(f"Average predict time numpy: {avg_numpy_predict}")
print(f"Average predict time torch {device}: {avg_torch_predict}")
print(
    f"Torch {device} predict speedup: {round(avg_numpy_predict / avg_torch_predict, 2)}"
    "X"
)

With Colab they are better but then again Colab does not provide good systems that have much multi processing when running via Numpy on CPU:

n_samples, n_features, n_classes = 10000, 300, 100

Average fit time numpy: 53.339
Average fit time torch cuda: 10.261
Torch cuda fit speedup: 5.2X

Average predict time numpy: 0.042
Average predict time torch cuda: 0.007
Torch cuda predict speedup: 6.0X

Details
from time import time

import numpy as np
import torch as xp
from tqdm import tqdm

from sklearn import config_context
from sklearn.linear_model import LogisticRegressionCV

n_samples, n_features, n_classes = 10000, 300, 100
device = "cuda"
n_iter = 10

X_np = np.random.rand(n_samples, n_features).astype(np.float64)
y_np = np.random.randint(0, n_classes, n_samples)
numpy_fit_times = []
numpy_predict_times = []
for _ in tqdm(range(n_iter), desc="Numpy"):
    lr = LogisticRegressionCV(
        Cs=[0.01, 0.1, 0.8],
        solver="lbfgs",
        max_iter=200,
        tol=1e-4,
        cv=5,
        use_legacy_attributes=False,
        l1_ratios=[0.0],
        scoring="neg_log_loss",
    )
    start = time()
    lr.fit(X_np, y_np)
    numpy_fit_times.append(round(time() - start, 3))
    start = time()
    pred = lr.predict_proba(X_np)
    numpy_predict_times.append(round(time() - start, 3))

avg_numpy_fit = round(sum(numpy_fit_times) / n_iter, 3)
avg_numpy_predict = round(sum(numpy_predict_times) / n_iter, 3)

torch_fit_times = []
torch_predict_times = []
X_xp = xp.rand((n_samples, n_features), dtype=xp.float64, device=device)
y_xp = xp.randint(0, n_classes, (n_samples,), device=device)
for _ in tqdm(range(n_iter), desc=f"Torch {device}"):
    with config_context(array_api_dispatch=True):
        lr = LogisticRegressionCV(
            Cs=[0.01, 0.1, 0.8],
            solver="lbfgs",
            max_iter=200,
            tol=1e-4,
            cv=5,
            use_legacy_attributes=False,
            l1_ratios=[0.0],
            scoring="neg_log_loss",
        )
        start = time()
        lr.fit(X_xp, y_xp)
        torch_fit_times.append(round(time() - start, 3))
        start = time()
        pred = lr.predict_proba(X_xp)
        first = float(pred[0, 0])
        torch_predict_times.append(round(time() - start, 3))

avg_torch_fit = round(sum(torch_fit_times) / n_iter, 3)
avg_torch_predict = round(sum(torch_predict_times) / n_iter, 3)

print(f"Average fit time numpy: {avg_numpy_fit}")
print(f"Average fit time torch {device}: {avg_torch_fit}")
print(f"Torch {device} fit speedup: {round(avg_numpy_fit / avg_torch_fit, 2)}X")

print(f"Average predict time numpy: {avg_numpy_predict}")
print(f"Average predict time torch {device}: {avg_torch_predict}")
print(
    f"Torch {device} predict speedup: {round(avg_numpy_predict / avg_torch_predict, 2)}"
    "X"
)

@OmarManzoor

Copy link
Copy Markdown
Contributor Author

@ogrisel Could you kindly help with resolving these numerical discrepancy and tolerance issues? All tests (leaving aside CUDA) pass on my local mps system.

@ogrisel

ogrisel commented Apr 30, 2026

Copy link
Copy Markdown
Member

Maybe it could help if we would run assert allclose checks on coef_ and intercept_ after max_iter=1 first to check if the numerical discrepancies can be triggered with a limited number of arithmetic operations.

@OmarManzoor

Copy link
Copy Markdown
Contributor Author

The problem is in some cases there are numerical discrepancies whereas if you try to fix those by increasing or decreasing tol then in other cases we get convergence issues in float32 cases.

@ogrisel

ogrisel commented Apr 30, 2026

Copy link
Copy Markdown
Member

tol and convergence matters should have no effect when fitting with max_iter=1. We can check the size of the discrepancies after 1 iteration: if it's already very large, it probably means that there is a bug somewhere. If not, maybe the discrepancies we observe after many iterations are natural consequences of diverging compounding effects of rounding errors, and we would have to rewrite the tests to change our expectations.

EDIT: alternatively to the above, we could start by trying the suggestions below:

Comment thread sklearn/linear_model/tests/test_logistic.py Outdated
Comment thread sklearn/linear_model/tests/test_logistic.py
Comment thread sklearn/linear_model/_logistic.py Outdated
Comment thread sklearn/linear_model/_logistic.py Outdated
Comment thread sklearn/linear_model/_logistic.py
Comment thread sklearn/linear_model/_logistic.py Outdated
Comment thread sklearn/linear_model/_logistic.py Outdated
Comment thread sklearn/linear_model/_logistic.py Outdated
Comment thread sklearn/linear_model/_logistic.py Outdated
Comment thread sklearn/linear_model/_logistic.py Outdated
Comment thread sklearn/linear_model/_logistic.py Outdated
Comment thread sklearn/utils/_array_api.py
Comment thread sklearn/utils/_array_api.py

@lorentzenchr lorentzenchr left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Some minor comments, mostly for cleaner code.

Comment thread sklearn/linear_model/tests/test_logistic.py Outdated
Comment thread sklearn/linear_model/_logistic.py Outdated
Comment thread sklearn/linear_model/_logistic.py
Comment thread sklearn/utils/_response.py Outdated
@lorentzenchr lorentzenchr added the Waiting for Second Reviewer First reviewer is done, need a second one! label Jul 20, 2026
@OmarManzoor

Copy link
Copy Markdown
Contributor Author

@virchan Could you review this PR?

@virchan virchan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Thanks, everyone!

Let's merge this.

@virchan
virchan merged commit e8b70c0 into scikit-learn:main Jul 22, 2026
43 checks passed
@OmarManzoor

Copy link
Copy Markdown
Contributor Author

@lorentzenchr and @virchan, thank you for reviewing.

@OmarManzoor
OmarManzoor deleted the array-api-lr_cv branch July 22, 2026 05:48
prady0t pushed a commit to prady0t/scikit-learn that referenced this pull request Sep 2, 2026
@jeremiedbb jeremiedbb mentioned this pull request Sep 8, 2026
14 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

Array API support for LogisticRegressionCV with LBFGS

4 participants