diff --git a/python/cuml/cuml/covariance/ledoit_wolf.py b/python/cuml/cuml/covariance/ledoit_wolf.py index 5716233367..7417b4e9c5 100644 --- a/python/cuml/cuml/covariance/ledoit_wolf.py +++ b/python/cuml/cuml/covariance/ledoit_wolf.py @@ -2,7 +2,6 @@ # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION. # SPDX-License-Identifier: Apache-2.0 # - import warnings import cupy as cp @@ -12,9 +11,8 @@ from cuml.internals import reflect, run_in_internal_context from cuml.internals.array import CumlArray from cuml.internals.base import Base -from cuml.internals.input_utils import input_to_cupy_array from cuml.internals.interop import InteropMixin, to_cpu, to_gpu -from cuml.internals.validation import check_features, check_is_fitted +from cuml.internals.validation import check_inputs, check_is_fitted def _ledoit_wolf_shrinkage(X, assume_centered=False, block_size=1000): @@ -228,7 +226,7 @@ def __init__( self.assume_centered = assume_centered self.block_size = block_size - @reflect(reset=True) + @reflect(reset="type") def fit(self, X, y=None, *, convert_dtype=True) -> "LedoitWolf": """Fit the Ledoit-Wolf shrunk covariance model to X. @@ -247,38 +245,32 @@ def fit(self, X, y=None, *, convert_dtype=True) -> "LedoitWolf": self : LedoitWolf Returns the instance itself. """ - X_arr, n_samples, n_features, dtype = input_to_cupy_array( + X = check_inputs( + self, X, - check_dtype=[np.float32, np.float64], - order="C", - convert_to_dtype=(np.float32 if convert_dtype else None), + dtype=("float32", "float64"), + convert_dtype=convert_dtype, + reset=True, ) - - if n_samples == 0: - raise ValueError( - f"Found array with 0 sample(s) (shape={tuple(X_arr.shape)}) while a " - "minimum of 1 is required." - ) - - if n_samples == 1: + if X.shape[0] == 1: warnings.warn( "Only one sample available. " "You may want to reshape your data array" ) if self.assume_centered: - location = cp.zeros(n_features, dtype=dtype) + location = cp.zeros(X.shape[1], dtype=X.dtype) else: - location = cp.mean(X_arr, axis=0) + location = cp.mean(X, axis=0) shrinkage, emp_cov, mu = _ledoit_wolf_shrinkage( - X_arr, + X, assume_centered=self.assume_centered, block_size=self.block_size, ) shrunk_cov = (1.0 - shrinkage) * emp_cov - shrunk_cov.flat[:: n_features + 1] += shrinkage * mu + shrunk_cov.flat[:: X.shape[1] + 1] += shrinkage * mu self.shrinkage_ = shrinkage self.location_ = CumlArray(data=location) @@ -305,7 +297,7 @@ def get_precision(self): if self.store_precision: return self.precision_ else: - covariance = cp.asarray(self.covariance_) + covariance = self.covariance_.to_output("cupy") precision = cp.linalg.pinv(covariance) return precision @@ -328,24 +320,22 @@ def score(self, X_test, y=None) -> float: Log-likelihood of the data under the fitted Gaussian model. """ check_is_fitted(self) - check_features(self, X_test) - - X_arr, _, n_features, _ = input_to_cupy_array( + X_test = check_inputs( + self, X_test, - check_dtype=[np.float32, np.float64], - check_cols=self.n_features_in_, - order="C", + dtype=("float32", "float64"), ) + precision = self.get_precision().to_output("cupy") + location = self.location_.to_output("cupy") - precision = cp.asarray(self.get_precision()) - location = cp.asarray(self.location_) - - X_centered = X_arr - location + X_centered = X_test - location log_det_precision = cp.linalg.slogdet(precision)[1] mahal = cp.sum(cp.dot(X_centered, precision) * X_centered, axis=1) log_likelihood = -0.5 * ( - n_features * np.log(2 * np.pi) - log_det_precision + cp.mean(mahal) + X_test.shape[1] * np.log(2 * np.pi) + - log_det_precision + + cp.mean(mahal) ) return float(log_likelihood) @@ -375,14 +365,10 @@ def error_norm( """ check_is_fitted(self) - comp_cov_arr, _, _, _ = input_to_cupy_array( - comp_cov, - check_dtype=[np.float32, np.float64], - order="C", - ) - self_cov = cp.asarray(self.covariance_) + comp_cov = check_inputs(self, comp_cov, dtype=("float32", "float64")) + self_cov = self.covariance_.to_output("cupy") - diff = self_cov - comp_cov_arr + diff = self_cov - comp_cov if norm == "frobenius": error = cp.sum(diff**2) @@ -417,18 +403,11 @@ def mahalanobis(self, X): Squared Mahalanobis distances of the observations. """ check_is_fitted(self) - check_features(self, X) - - X_arr, _, _, _ = input_to_cupy_array( - X, - check_dtype=[np.float32, np.float64], - check_cols=self.n_features_in_, - order="C", - ) - precision = cp.asarray(self.get_precision()) - location = cp.asarray(self.location_) + X = check_inputs(self, X, dtype=("float32", "float64")) + precision = self.get_precision().to_output("cupy") + location = self.location_.to_output("cupy") - X_centered = X_arr - location + X_centered = X - location mahal = cp.sum(cp.dot(X_centered, precision) * X_centered, axis=1) return mahal diff --git a/python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml b/python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml index af02fd39f7..db611c1f22 100644 --- a/python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml +++ b/python/cuml/cuml_accel_tests/upstream/scikit-learn/xfail-list.yaml @@ -1023,9 +1023,6 @@ - "sklearn.tests.test_common::test_estimators[KernelDensity()-check_dtype_object]" - "sklearn.tests.test_common::test_estimators[KernelDensity()-check_estimators_nan_inf]" - "sklearn.tests.test_common::test_estimators[KernelDensity()-check_sample_weights_not_an_array]" - - "sklearn.tests.test_common::test_estimators[LedoitWolf()-check_dtype_object]" - - "sklearn.tests.test_common::test_estimators[LedoitWolf()-check_estimators_empty_data_messages]" - - "sklearn.tests.test_common::test_estimators[LedoitWolf()-check_estimators_nan_inf]" - "sklearn.tests.test_common::test_estimators[NearestNeighbors()-check_dtype_object]" - "sklearn.tests.test_common::test_estimators[NearestNeighbors()-check_estimators_empty_data_messages]" - "sklearn.tests.test_common::test_estimators[NearestNeighbors()-check_estimators_nan_inf]" diff --git a/python/cuml/tests/test_sklearn_compatibility.py b/python/cuml/tests/test_sklearn_compatibility.py index ae92a374d8..65d455efd4 100644 --- a/python/cuml/tests/test_sklearn_compatibility.py +++ b/python/cuml/tests/test_sklearn_compatibility.py @@ -297,9 +297,6 @@ }, LedoitWolf: { "check_estimator_tags_renamed": "No support for modern tags infrastructure", - "check_dtype_object": "LedoitWolf does not handle object dtype", - "check_estimators_empty_data_messages": "LedoitWolf does not handle empty data", - "check_estimators_nan_inf": "LedoitWolf does not check for NaN and inf", }, DBSCAN: { "check_estimator_tags_renamed": "No support for modern tags infrastructure",