Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions docs/source/cuml-accel/limitations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,6 @@ ElasticNet
- If ``positive=True``.
- If ``warm_start=True``.
- If ``precompute`` is not ``False``.
- If ``X`` is sparse.

Additionally, the following fitted attributes are currently not computed:

Expand All @@ -290,7 +289,6 @@ Lasso
- If ``positive=True``.
- If ``warm_start=True``.
- If ``precompute`` is not ``False``.
- If ``X`` is sparse.

Additionally, the following fitted attributes are currently not computed:

Expand Down
29 changes: 19 additions & 10 deletions python/cuml/cuml/accel/_overrides/sklearn/linear_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from cuml.accel.estimator_proxy import ProxyBase
from cuml.common.sparse_utils import is_sparse
from cuml.internals.array import CumlArray
from cuml.internals.input_utils import input_to_cuml_array
from cuml.internals.interop import UnsupportedOnGPU
from cuml.internals.outputs import using_output_type

Expand All @@ -34,15 +35,6 @@ class LogisticRegression(ProxyBase):
_gpu_class = cuml.linear_model.LogisticRegression


class ElasticNet(ProxyBase):
_gpu_class = cuml.linear_model.ElasticNet
_not_implemented_attributes = frozenset(("dual_gap_",))

def _gpu_fit(self, X, y, sample_weight=None, check_input=True):
# Fixes signature mismatch with cuml.ElasticNet. check_input can be ignored.
return self._gpu.fit(X, y, sample_weight=sample_weight)


class Ridge(ProxyBase):
_gpu_class = cuml.linear_model.Ridge

Expand Down Expand Up @@ -74,10 +66,27 @@ def _gpu_fit(self, X, y, sample_weight=None):
return self


class ElasticNet(ProxyBase):
_gpu_class = cuml.linear_model.ElasticNet
_not_implemented_attributes = frozenset(("dual_gap_",))

def _gpu_fit(self, X, y, sample_weight=None, check_input=True):
# check_input is ignored, only here to fix signature mismatch with sklearn

y = input_to_cuml_array(y, convert_to_mem_type=False)[0]
if len(y.shape) > 1 and y.shape[1] > 1:
raise UnsupportedOnGPU("Multi-output targets are not supported")
return self._gpu.fit(X, y, sample_weight=sample_weight)


class Lasso(ProxyBase):
_gpu_class = cuml.linear_model.Lasso
_not_implemented_attributes = frozenset(("dual_gap_",))

def _gpu_fit(self, X, y, sample_weight=None, check_input=True):
# Fixes signature mismatch with cuml.Lasso. check_input can be ignored.
# check_input is ignored, only here to fix signature mismatch with sklearn

y = input_to_cuml_array(y, convert_to_mem_type=False)[0]
if len(y.shape) > 1 and y.shape[1] > 1:
raise UnsupportedOnGPU("Multi-output targets are not supported")
return self._gpu.fit(X, y, sample_weight=sample_weight)
50 changes: 40 additions & 10 deletions python/cuml/cuml/linear_model/elastic_net.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
# SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION.
# SPDX-License-Identifier: Apache-2.0
#
import cupyx.scipy.sparse

from cuml.common.array_descriptor import CumlArrayDescriptor
from cuml.common.doc_utils import generate_docstring
from cuml.common.sparse_utils import is_sparse
from cuml.internals.array import CumlArray
from cuml.internals.base import Base
from cuml.internals.interop import (
Expand All @@ -12,15 +15,24 @@
to_cpu,
to_gpu,
)
from cuml.internals.mixins import FMajorInputTagMixin, RegressorMixin
from cuml.internals.mixins import (
FMajorInputTagMixin,
RegressorMixin,
SparseInputTagMixin,
)
from cuml.internals.outputs import reflect
from cuml.linear_model.base import LinearPredictMixin
from cuml.solvers.cd import fit_coordinate_descent
from cuml.solvers.qn import fit_qn


class ElasticNet(
Base, InteropMixin, LinearPredictMixin, RegressorMixin, FMajorInputTagMixin
Base,
InteropMixin,
LinearPredictMixin,
RegressorMixin,
SparseInputTagMixin,
FMajorInputTagMixin,
Comment thread
jcrist marked this conversation as resolved.
):
"""
Linear regression with combined L1 and L2 priors as regularizer.
Expand Down Expand Up @@ -48,11 +60,12 @@ class ElasticNet(
The tolerance for the optimization: if the updates are smaller than
tol, the optimization code checks the dual gap for optimality and
continues until it is smaller than tol.
solver : {'cd', 'qn'}, default='cd'
Choose an algorithm:
solver : {'auto', 'cd', 'qn'}, default='auto'
The solver to use.

* 'cd' - coordinate descent
* 'qn' - quasi-newton
- 'auto': uses 'cd' for dense inputs, and 'qn' for sparse inputs
- 'cd': uses coordinate descent. Only supports dense inputs.
- 'qn': uses quasi-newton methods. Supports sparse and dense inputs.

You may find the alternative 'qn' algorithm is faster when the number
of features is sufficiently large but the sample size is small.
Expand All @@ -76,6 +89,8 @@ class ElasticNet(
----------
coef_ : array, shape (n_features)
The estimated coefficients for the linear regression model.
sparse_coef_ : sparse matrix, shape (n_targets, n_features)
Sparse matrix representation of `coef_`.
intercept_ : float
The independent term, will be 0 if `fit_intercept` is False.
n_iter_ : int
Expand Down Expand Up @@ -192,7 +207,7 @@ def __init__(
fit_intercept=True,
max_iter=1000,
tol=1e-3,
solver="cd",
solver="auto",
selection="cyclic",
output_type=None,
verbose=False,
Expand All @@ -207,6 +222,12 @@ def __init__(
self.solver = solver
self.selection = selection

@property
@reflect
def sparse_coef_(self):
"""Sparse representation of the fitted `coef_`."""
return cupyx.scipy.sparse.csr_matrix(self.coef_.to_output("cupy"))

@generate_docstring()
@reflect(reset=True)
def fit(
Expand All @@ -225,7 +246,11 @@ def fit(
f"Expected 0.0 <= l1_ratio <= 1.0, got {self.l1_ratio}"
)

if self.solver == "qn":
solver = self.solver
if solver == "auto":
solver = "qn" if is_sparse(X) else "cd"

if solver == "qn":
coef, intercept, n_iter, _ = fit_qn(
X,
y,
Expand All @@ -242,7 +267,12 @@ def fit(
)
coef = CumlArray(data=coef.to_output("cupy").flatten())
intercept = intercept.item()
elif self.solver == "cd":
elif solver == "cd":
if is_sparse(X):
raise ValueError(
"solver='cd' doesn't support sparse inputs, please use "
"solver='auto' or solver='qn' instead"
)
coef, intercept, n_iter = fit_coordinate_descent(
X,
y,
Expand All @@ -256,7 +286,7 @@ def fit(
tol=self.tol,
)
else:
raise ValueError(f"solver {self.solver} is not supported")
raise ValueError(f"solver={solver!r} is not supported")

self.coef_ = coef
self.intercept_ = intercept
Expand Down
15 changes: 9 additions & 6 deletions python/cuml/cuml/linear_model/lasso.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@ class Lasso(ElasticNet):
The tolerance for the optimization: if the updates are smaller than
tol, the optimization code checks the dual gap for optimality and
continues until it is smaller than tol.
solver : {'cd', 'qn'} (default='cd')
Choose an algorithm:
solver : {'auto', 'cd', 'qn'}, default='auto'
The solver to use.

* 'cd' - coordinate descent
* 'qn' - quasi-newton
- 'auto': uses 'cd' for dense inputs, and 'qn' for sparse inputs
- 'cd': uses coordinate descent. Only supports dense inputs.
- 'qn': uses quasi-newton methods. Supports sparse and dense inputs.

You may find the alternative 'qn' algorithm is faster when the number
of features is sufficiently large, but the sample size is small.
of features is sufficiently large but the sample size is small.
selection : {'cyclic', 'random'} (default='cyclic')
If set to 'random', a random coefficient is updated every iteration
rather than looping over features sequentially by default.
Expand All @@ -57,6 +58,8 @@ class Lasso(ElasticNet):
----------
coef_ : array, shape (n_features)
The estimated coefficients for the linear regression model.
sparse_coef_ : sparse matrix, shape (n_targets, n_features)
Sparse matrix representation of `coef_`.
intercept_ : array
The independent term. If `fit_intercept` is False, will be 0.
n_iter_ : int
Expand Down Expand Up @@ -119,7 +122,7 @@ def __init__(
fit_intercept=True,
max_iter=1000,
tol=1e-3,
solver="cd",
solver="auto",
selection="cyclic",
output_type=None,
verbose=False,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,16 @@
tests:
- "sklearn.linear_model.tests.test_coordinate_descent::test_enet_coordinate_descent[Lasso-1-kwargs1]"
- "sklearn.linear_model.tests.test_coordinate_descent::test_warm_start_convergence"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_array-False-24-6-False-Lasso]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_array-False-24-6-True-Lasso]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_array-False-6-24-False-ElasticNet]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_array-False-6-24-True-ElasticNet]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_matrix-False-24-6-False-Lasso]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_matrix-False-24-6-True-Lasso]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_matrix-False-6-24-False-ElasticNet]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_matrix-False-6-24-True-ElasticNet]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_enet_coordinate_descent[csc_array]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_enet_coordinate_descent[csc_matrix]"
- "sklearn.manifold.tests.test_t_sne::test_pca_initialization_not_compatible_with_sparse_input[csr_array]"
- "sklearn.manifold.tests.test_t_sne::test_pca_initialization_not_compatible_with_sparse_input[csr_matrix]"
- reason: Test should fail with cuml.accel
Expand Down Expand Up @@ -235,14 +241,17 @@
- "sklearn.linear_model.tests.test_base::test_linear_regression"
- "sklearn.linear_model.tests.test_base::test_linear_regression_pd_sparse_dataframe_warning"
- "sklearn.linear_model.tests.test_common::test_balance_property[42-True-LogisticRegression]"
- "sklearn.linear_model.tests.test_coordinate_descent::test_check_input_false"
- "sklearn.linear_model.tests.test_coordinate_descent::test_elasticnet_precompute_gram_weighted_samples"
- "sklearn.linear_model.tests.test_coordinate_descent::test_enet_copy_X_False_check_input_False"
- "sklearn.linear_model.tests.test_coordinate_descent::test_enet_float_precision"
- "sklearn.linear_model.tests.test_coordinate_descent::test_enet_multitarget"
- "sklearn.linear_model.tests.test_coordinate_descent::test_enet_nonfinite_params"
- "sklearn.linear_model.tests.test_coordinate_descent::test_enet_sample_weight_consistency[42-None-False-0.01-False]"
- "sklearn.linear_model.tests.test_coordinate_descent::test_enet_sample_weight_consistency[42-None-False-0.01-True]"
- "sklearn.linear_model.tests.test_coordinate_descent::test_enet_sample_weight_consistency[42-csr_array-False-0.01-False]"
- "sklearn.linear_model.tests.test_coordinate_descent::test_enet_sample_weight_consistency[42-csr_array-False-0.01-True]"
- "sklearn.linear_model.tests.test_coordinate_descent::test_enet_sample_weight_consistency[42-csr_matrix-False-0.01-False]"
- "sklearn.linear_model.tests.test_coordinate_descent::test_enet_sample_weight_consistency[42-csr_matrix-False-0.01-True]"
- "sklearn.linear_model.tests.test_coordinate_descent::test_enet_toy"
- "sklearn.linear_model.tests.test_coordinate_descent::test_lassoCV_does_not_set_precompute[False-False]"
- "sklearn.linear_model.tests.test_coordinate_descent::test_lassoCV_does_not_set_precompute[auto-False]"
Expand All @@ -251,6 +260,8 @@
- "sklearn.linear_model.tests.test_coordinate_descent::test_lasso_readonly_data"
- "sklearn.linear_model.tests.test_coordinate_descent::test_lasso_toy"
- "sklearn.linear_model.tests.test_coordinate_descent::test_lasso_zero"
- "sklearn.linear_model.tests.test_coordinate_descent::test_sparse_input_convergence_warning[csr_array]"
- "sklearn.linear_model.tests.test_coordinate_descent::test_sparse_input_convergence_warning[csr_matrix]"
- "sklearn.linear_model.tests.test_coordinate_descent::test_warm_start_convergence_with_regularizer_decrement"
- "sklearn.linear_model.tests.test_ransac::test_perfect_horizontal_line"
- "sklearn.linear_model.tests.test_ransac::test_ransac_exceed_max_skips"
Expand All @@ -273,16 +284,36 @@
- "sklearn.linear_model.tests.test_ridge::test_ridge_sample_weight_consistency[42-saga-wide-csr_matrix-False]"
- "sklearn.linear_model.tests.test_ridge::test_ridgecv_sample_weight"
- "sklearn.linear_model.tests.test_sag::test_step_size_alpha_error"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_same_multiple_output_sparse_dense[coo_array]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_same_multiple_output_sparse_dense[coo_matrix]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_enet_multitarget[csc_array]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_enet_multitarget[csc_matrix]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_enet_toy_explicit_sparse_input[lil_array]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_enet_toy_explicit_sparse_input[lil_matrix]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_enet_toy_list_input[csc_array-False]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_enet_toy_list_input[csc_array-True]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_enet_toy_list_input[csc_matrix-False]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_enet_toy_list_input[csc_matrix-True]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_lasso_zero[csc_array]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_lasso_zero[csc_matrix]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_array-False-6-24-False-ElasticNet]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_array-False-6-24-False-Lasso]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_array-False-6-24-True-ElasticNet]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_array-False-6-24-True-Lasso]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_array-True-24-6-False-ElasticNet]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_array-True-24-6-False-Lasso]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_array-True-24-6-True-ElasticNet]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_array-True-24-6-True-Lasso]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_array-True-6-24-False-ElasticNet]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_array-True-6-24-False-Lasso]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_array-True-6-24-True-ElasticNet]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_array-True-6-24-True-Lasso]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_matrix-False-6-24-False-ElasticNet]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_matrix-False-6-24-False-Lasso]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_matrix-False-6-24-True-ElasticNet]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_matrix-False-6-24-True-Lasso]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_matrix-True-24-6-False-ElasticNet]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_matrix-True-24-6-False-Lasso]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_matrix-True-24-6-True-ElasticNet]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_matrix-True-24-6-True-Lasso]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_matrix-True-6-24-False-ElasticNet]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_matrix-True-6-24-False-Lasso]"
- "sklearn.linear_model.tests.test_sparse_coordinate_descent::test_sparse_dense_equality[csc_matrix-True-6-24-True-ElasticNet]"
Expand Down Expand Up @@ -728,6 +759,8 @@
- "sklearn.neighbors.tests.test_neighbors::test_neighbor_classifiers_loocv[ball_tree-nn_model0]"
- "sklearn.neighbors.tests.test_neighbors::test_neighbor_classifiers_loocv[brute-nn_model0]"
- "sklearn.neighbors.tests.test_neighbors::test_neighbor_classifiers_loocv[kd_tree-nn_model0]"
- "sklearn.tests.test_common::test_estimators[ElasticNet()-check_sample_weight_equivalence_on_sparse_data]"
- "sklearn.tests.test_common::test_estimators[Lasso()-check_sample_weight_equivalence_on_sparse_data]"
- "sklearn.tests.test_common::test_estimators[LogisticRegression()-check_sample_weight_equivalence_on_dense_data]"
- "sklearn.tests.test_common::test_estimators[LogisticRegression()-check_sample_weight_equivalence_on_sparse_data]"
- "sklearn.tests.test_common::test_estimators[Ridge()-check_non_transformer_estimators_n_iter]"
Expand Down Expand Up @@ -1067,7 +1100,6 @@
- "sklearn.tests.test_common::test_estimators[ElasticNet()-check_dtype_object]"
- "sklearn.tests.test_common::test_estimators[ElasticNet()-check_estimators_nan_inf]"
- "sklearn.tests.test_common::test_estimators[ElasticNet()-check_regressor_data_not_an_array]"
- "sklearn.tests.test_common::test_estimators[ElasticNet()-check_regressor_multioutput]"
- "sklearn.tests.test_common::test_estimators[ElasticNet()-check_requires_y_none]"
- "sklearn.tests.test_common::test_estimators[ElasticNet()-check_sample_weights_not_an_array]"
- "sklearn.tests.test_common::test_estimators[ElasticNet()-check_supervised_y_no_nan]"
Expand All @@ -1093,7 +1125,6 @@
- "sklearn.tests.test_common::test_estimators[Lasso()-check_dtype_object]"
- "sklearn.tests.test_common::test_estimators[Lasso()-check_estimators_nan_inf]"
- "sklearn.tests.test_common::test_estimators[Lasso()-check_regressor_data_not_an_array]"
- "sklearn.tests.test_common::test_estimators[Lasso()-check_regressor_multioutput]"
- "sklearn.tests.test_common::test_estimators[Lasso()-check_requires_y_none]"
- "sklearn.tests.test_common::test_estimators[Lasso()-check_sample_weights_not_an_array]"
- "sklearn.tests.test_common::test_estimators[Lasso()-check_supervised_y_no_nan]"
Expand Down Expand Up @@ -1266,6 +1297,14 @@
tests:
- "sklearn.tests.test_calibration::test_calibrated_classifier_cv_double_sample_weights_equivalence[False-temperature]"
- "sklearn.tests.test_calibration::test_calibrated_classifier_cv_double_sample_weights_equivalence[True-temperature]"
- reason: ElasticNet sample_weight handling is a bit off
condition: scikit-learn>=1.6,<1.8
tests:
- "sklearn.linear_model.tests.test_coordinate_descent::test_enet_alpha_max_sample_weight[sample_weight0-False-True]"
- reason: ElasticNet sample_weight handling is a bit off
condition: scikit-learn>=1.8
tests:
- "sklearn.linear_model.tests.test_coordinate_descent::test_enet_alpha_max[sample_weight0-False-True]"
- reason: Elasticnet scores attribute layout differs with cuml.accel in sklearn 1.8
condition: scikit-learn>=1.8
tests:
Expand Down
Loading
Loading