Skip to content

Commit b452112

Browse files
committed
Some fixups from the sklearn test suite
- Support `__sklearn_is_fitted__` since the default `check_is_fitted` doesn't work for `ProxyBase`. - Oops, `LinearRegression` _can_ be multi-target. Fix the `InteropMixin` implementations as needed.
1 parent 3b2e2cc commit b452112

6 files changed

Lines changed: 41 additions & 15 deletions

File tree

python/cuml/cuml/accel/estimator_proxy.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,10 @@ def set_params(self, **kwargs):
297297
def __sklearn_tags__(self):
298298
return self._cpu.__sklearn_tags__()
299299

300+
def __sklearn_is_fitted__(self):
301+
model = self._cpu if self._gpu is None else self._gpu
302+
return getattr(model, "n_features_in_", None) is not None
303+
300304
def __sklearn_clone__(self):
301305
cls = type(self)
302306
out = cls.__new__(cls)

python/cuml/cuml/internals/interop.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
from importlib import import_module
1919
from typing import Any
2020

21+
import numpy as np
22+
2123
from cuml.internals.device_type import DeviceType
2224
from cuml.internals.global_settings import GlobalSettings
2325
from cuml.internals.mem_type import MemoryType
@@ -28,6 +30,7 @@
2830
"UnsupportedOnCPU",
2931
"InteropMixin",
3032
"warn_legacy_device_interop",
33+
"to_cpu",
3134
"to_gpu",
3235
)
3336

@@ -63,15 +66,24 @@ def inner(self, *args, **kwargs):
6366

6467

6568
def to_gpu(x, order="K"):
66-
"""Coerce `x` to the equivalent GPU type."""
67-
# Ideally we'd get rid of this helper eventually, using it for now to reduce typing
69+
"""Coerce `x` to the equivalent gpu type."""
6870
from cuml.internals.input_utils import input_to_cuml_array
6971

72+
if np.isscalar(x):
73+
# cuml typically expects scalars on host
74+
return x
7075
return input_to_cuml_array(
7176
x, order=order, convert_to_mem_type=MemoryType.device
7277
)[0]
7378

7479

80+
def to_cpu(x):
81+
"""Coerce `x` to the equivalent cpu type."""
82+
if np.isscalar(x):
83+
return x
84+
return x.to_output("numpy")
85+
86+
7587
class UnsupportedOnGPU(ValueError):
7688
"""An exception raised when a conversion of a CPU to a GPU estimator isn't supported"""
7789

python/cuml/cuml/linear_model/elastic_net.pyx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818

1919
from inspect import signature
2020

21-
import numpy as np
22-
2321
from cuml.common import input_to_cuml_array
2422
from cuml.common.array_descriptor import CumlArrayDescriptor
2523
from cuml.common.doc_utils import generate_docstring
@@ -28,6 +26,7 @@ from cuml.internals.base import Base
2826
from cuml.internals.interop import (
2927
InteropMixin,
3028
UnsupportedOnGPU,
29+
to_cpu,
3130
to_gpu,
3231
warn_legacy_device_interop,
3332
)
@@ -210,15 +209,15 @@ class ElasticNet(Base,
210209

211210
def _attrs_from_cpu(self, model):
212211
return {
213-
"intercept_": float(model.intercept_),
212+
"intercept_": to_gpu(model.intercept_, order="F"),
214213
"coef_": to_gpu(model.coef_, order="F"),
215214
**super()._attrs_from_cpu(model),
216215
}
217216

218217
def _attrs_to_cpu(self, model):
219218
return {
220-
"intercept_": np.float64(self.intercept_),
221-
"coef_": self.coef_.to_output("numpy"),
219+
"intercept_": to_cpu(self.intercept_),
220+
"coef_": to_cpu(self.coef_),
222221
**super()._attrs_to_cpu(model),
223222
}
224223

python/cuml/cuml/linear_model/linear_regression.pyx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ from cuml.internals.base import Base
3030
from cuml.internals.interop import (
3131
InteropMixin,
3232
UnsupportedOnGPU,
33+
to_cpu,
3334
to_gpu,
3435
warn_legacy_device_interop,
3536
)
@@ -294,15 +295,15 @@ class LinearRegression(Base,
294295

295296
def _attrs_from_cpu(self, model):
296297
return {
297-
"intercept_": float(model.intercept_),
298+
"intercept_": to_gpu(model.intercept_, order="F"),
298299
"coef_": to_gpu(model.coef_, order="F"),
299300
**super()._attrs_from_cpu(model),
300301
}
301302

302303
def _attrs_to_cpu(self, model):
303304
return {
304-
"intercept_": np.float64(self.intercept_),
305-
"coef_": self.coef_.to_output("numpy"),
305+
"intercept_": to_cpu(self.intercept_),
306+
"coef_": to_cpu(self.coef_),
306307
**super()._attrs_to_cpu(model),
307308
}
308309

python/cuml/cuml/linear_model/logistic_regression.pyx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ from cuml.internals.base import Base
3333
from cuml.internals.input_utils import input_to_cuml_array
3434
from cuml.internals.interop import (
3535
InteropMixin,
36+
to_cpu,
3637
to_gpu,
3738
warn_legacy_device_interop,
3839
)
@@ -248,8 +249,8 @@ class LogisticRegression(Base,
248249
def _attrs_to_cpu(self, model):
249250
return {
250251
"classes_": self.classes_,
251-
"intercept_": self.intercept_.to_output("numpy"),
252-
"coef_": self.coef_.to_output("numpy"),
252+
"intercept_": to_cpu(self.intercept_),
253+
"coef_": to_cpu(self.coef_),
253254
**super()._attrs_to_cpu(model),
254255
}
255256

python/cuml/cuml_accel_tests/test_estimator_proxy.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@
2424
import scipy.sparse
2525
import sklearn
2626
from packaging.version import Version
27-
from sklearn.base import is_classifier, is_regressor
27+
from sklearn.base import check_is_fitted, is_classifier, is_regressor
2828
from sklearn.datasets import make_classification, make_regression
29+
from sklearn.exceptions import NotFittedError
2930
from sklearn.linear_model import (
3031
ElasticNet,
3132
LinearRegression,
@@ -395,8 +396,14 @@ def test_unpickle_cuml_not_installed():
395396
def test_fit_gpu():
396397
X, y = make_regression(n_samples=10)
397398
model = LinearRegression()
399+
400+
# Model isn't fit
401+
with pytest.raises(NotFittedError):
402+
check_is_fitted(model)
403+
398404
assert model.fit(X, y) is model
399405
# Fit happened on GPU, attrs kept on GPU
406+
check_is_fitted(model)
400407
assert model._gpu is not None
401408
assert not hasattr(model._cpu, "n_features_in_")
402409

@@ -414,7 +421,8 @@ def test_fit_unsupported_params():
414421
X, y = make_regression(n_samples=10)
415422
model = LinearRegression(positive=True)
416423
assert model.fit(X, y) is model
417-
# Fit happened on CPU, attrs kept on GPU
424+
# Fit happened on CPU
425+
check_is_fitted(model)
418426
assert model._gpu is None
419427
assert hasattr(model._cpu, "n_features_in_")
420428

@@ -428,7 +436,8 @@ def test_fit_unsupported_args():
428436
X = scipy.sparse.coo_matrix(X_dense)
429437
model = LinearRegression(fit_intercept=True)
430438
assert model.fit(X, y) is model
431-
# Fit happened on CPU, attrs kept on GPU
439+
# Fit happened on CPU
440+
check_is_fitted(model)
432441
assert model._gpu is None
433442
assert hasattr(model._cpu, "n_features_in_")
434443

0 commit comments

Comments
 (0)