From 2f0d2db245001728dcb5478704a3f98723b25583 Mon Sep 17 00:00:00 2001 From: ethanglaser <42726565+ethanglaser@users.noreply.github.com> Date: Tue, 14 Nov 2023 21:15:25 -0600 Subject: [PATCH] Linear Regression out of preview (#1557) * changing import to preview * test_preview_linear upd * no assert daal4py in module * actual removal * address public and private CI fails * cleanup additional mentions of preview * isorted * minor paths update * adding pytest args to function * alignment with #1508 * Update sklearnex/tests/test_memory_usage.py Co-authored-by: Alexander Andreev * Update sklearnex/tests/test_memory_usage.py Co-authored-by: Alexander Andreev * some cpu deselected test removals * restoring one deselected for pub CI * restoring one deselected for private CI * temp debugging * another temp for debug * debug removal * alphabetical tweak after merging * minor space * deselected pca tweak --------- Co-authored-by: Alexander Andreev --- deselected_tests.yaml | 5 +- setup_sklearnex.py | 1 - sklearnex/dispatcher.py | 32 +- sklearnex/linear_model/linear.py | 358 +++++++++++++++++- sklearnex/linear_model/tests/test_linear.py | 19 +- sklearnex/preview/__init__.py | 2 +- sklearnex/preview/linear_model/__init__.py | 20 - sklearnex/preview/linear_model/_common.py | 66 ---- sklearnex/preview/linear_model/linear.py | 331 ---------------- .../linear_model/tests/test_preview_linear.py | 47 --- sklearnex/tests/test_memory_usage.py | 2 - sklearnex/tests/test_monkeypatch.py | 7 +- 12 files changed, 395 insertions(+), 495 deletions(-) mode change 100755 => 100644 sklearnex/linear_model/tests/test_linear.py delete mode 100755 sklearnex/preview/linear_model/__init__.py delete mode 100644 sklearnex/preview/linear_model/_common.py delete mode 100644 sklearnex/preview/linear_model/linear.py delete mode 100755 sklearnex/preview/linear_model/tests/test_preview_linear.py diff --git a/deselected_tests.yaml b/deselected_tests.yaml index d26bb95e0f..261f971cd8 100755 --- a/deselected_tests.yaml +++ b/deselected_tests.yaml @@ -239,13 +239,10 @@ deselected_tests: - tests/test_multioutput.py::test_multi_output_classification # Linear Regression - badly defined equation system - - ensemble/tests/test_stacking.py::test_stacking_prefit[StackingRegressor-DummyRegressor-predict-final_estimator1-X1-y1] - tests/test_common.py::test_estimators[RandomForestClassifier()-check_requires_y_none] - tests/test_common.py::test_estimators[RandomForestRegressor()-check_requires_y_none] - - tests/test_common.py::test_estimators[LinearRegression()-check_requires_y_none] # Linear Regression - minor mismatches in error/warning messages - - tree/tests/test_export.py::test_precision - model_selection/tests/test_search.py::test_grid_search_pipeline_steps - linear_model/tests/test_base.py::test_linear_regression_pd_sparse_dataframe_warning @@ -1192,7 +1189,7 @@ gpu: - tests/test_common.py::test_check_n_features_in_after_fitting[DBSCAN()] - tests/test_common.py::test_check_n_features_in_after_fitting[SVC()] # originated with pca dpctl/dpnp fit, to be re-assesed with pca out-of-preview - - test_pca_n_components_mostly_explained_variance_ratio + - decomposition/tests/test_pca.py::test_pca_n_components_mostly_explained_variance_ratio preview: - cluster/tests/test_k_means.py::test_kmeans_elkan_results diff --git a/setup_sklearnex.py b/setup_sklearnex.py index 33bf1eb922..467d4b4211 100755 --- a/setup_sklearnex.py +++ b/setup_sklearnex.py @@ -96,7 +96,6 @@ "sklearnex.preview", "sklearnex.preview.cluster", "sklearnex.preview.decomposition", - "sklearnex.preview.linear_model", "sklearnex.svm", "sklearnex.utils", ] diff --git a/sklearnex/dispatcher.py b/sklearnex/dispatcher.py index 014d1bc453..308b9070b9 100644 --- a/sklearnex/dispatcher.py +++ b/sklearnex/dispatcher.py @@ -68,6 +68,7 @@ def get_patch_map(): from .ensemble import ExtraTreesRegressor as ExtraTreesRegressor_sklearnex from .ensemble import RandomForestClassifier as RandomForestClassifier_sklearnex from .ensemble import RandomForestRegressor as RandomForestRegressor_sklearnex + from .linear_model import LinearRegression as LinearRegression_sklearnex from .neighbors import KNeighborsClassifier as KNeighborsClassifier_sklearnex from .neighbors import KNeighborsRegressor as KNeighborsRegressor_sklearnex from .neighbors import LocalOutlierFactor as LocalOutlierFactor_sklearnex @@ -76,7 +77,6 @@ def get_patch_map(): # Preview classes for patching from .preview.cluster import KMeans as KMeans_sklearnex from .preview.decomposition import PCA as PCA_sklearnex - from .preview.linear_model import LinearRegression as LinearRegression_sklearnex from .svm import SVC as SVC_sklearnex from .svm import SVR as SVR_sklearnex from .svm import NuSVC as NuSVC_sklearnex @@ -88,21 +88,6 @@ def get_patch_map(): mapping.pop("pca") mapping["pca"] = [[(decomposition_module, "PCA", PCA_sklearnex), None]] - # Linear Regression - mapping.pop("linear") - mapping.pop("linearregression") - mapping["linear"] = [ - [ - ( - linear_model_module, - "LinearRegression", - LinearRegression_sklearnex, - ), - None, - ] - ] - mapping["linearregression"] = mapping["linear"] - # KMeans mapping.pop("kmeans") mapping["kmeans"] = [ @@ -128,6 +113,21 @@ def get_patch_map(): mapping["nusvr"] = [[(svm_module, "NuSVR", NuSVR_sklearnex), None]] mapping["nusvc"] = [[(svm_module, "NuSVC", NuSVC_sklearnex), None]] + # Linear Regression + mapping.pop("linear") + mapping.pop("linearregression") + mapping["linear"] = [ + [ + ( + linear_model_module, + "LinearRegression", + LinearRegression_sklearnex, + ), + None, + ] + ] + mapping["linearregression"] = mapping["linear"] + # kNN mapping.pop("knn_classifier") mapping.pop("kneighborsclassifier") diff --git a/sklearnex/linear_model/linear.py b/sklearnex/linear_model/linear.py index 65a2b03d5f..be855fb9e7 100644 --- a/sklearnex/linear_model/linear.py +++ b/sklearnex/linear_model/linear.py @@ -14,4 +14,360 @@ # limitations under the License. # =============================================================================== -from daal4py.sklearn.linear_model import LinearRegression +import logging +from abc import ABC + +from daal4py.sklearn._utils import daal_check_version + + +def get_coef(self): + return self._coef_ + + +def set_coef(self, value): + self._coef_ = value + if hasattr(self, "_onedal_estimator"): + self._onedal_estimator.coef_ = value + if not self._is_in_fit: + del self._onedal_estimator._onedal_model + + +def get_intercept(self): + return self._intercept_ + + +def set_intercept(self, value): + self._intercept_ = value + if hasattr(self, "_onedal_estimator"): + self._onedal_estimator.intercept_ = value + if not self._is_in_fit: + del self._onedal_estimator._onedal_model + + +class BaseLinearRegression(ABC): + def _save_attributes(self): + self.n_features_in_ = self._onedal_estimator.n_features_in_ + self.fit_status_ = 0 + self._coef_ = self._onedal_estimator.coef_ + self._intercept_ = self._onedal_estimator.intercept_ + self._sparse = False + + self.coef_ = property(get_coef, set_coef) + self.intercept_ = property(get_intercept, set_intercept) + + self._is_in_fit = True + self.coef_ = self._coef_ + self.intercept_ = self._intercept_ + self._is_in_fit = False + + +if daal_check_version((2023, "P", 100)): + import numpy as np + from sklearn.linear_model import LinearRegression as sklearn_LinearRegression + + from daal4py.sklearn._utils import get_dtype, make2d, sklearn_check_version + + from .._device_offload import dispatch, wrap_output_data + from .._utils import PatchingConditionsChain, get_patch_message + from ..utils.validation import _assert_all_finite + + if sklearn_check_version("1.0") and not sklearn_check_version("1.2"): + from sklearn.linear_model._base import _deprecate_normalize + + from scipy.sparse import issparse + from sklearn.exceptions import NotFittedError + from sklearn.utils.validation import _deprecate_positional_args, check_X_y + + from onedal.linear_model import LinearRegression as onedal_LinearRegression + from onedal.utils import _num_features, _num_samples + + class LinearRegression(sklearn_LinearRegression, BaseLinearRegression): + __doc__ = sklearn_LinearRegression.__doc__ + intercept_, coef_ = None, None + + if sklearn_check_version("1.2"): + _parameter_constraints: dict = { + **sklearn_LinearRegression._parameter_constraints + } + + def __init__( + self, + fit_intercept=True, + copy_X=True, + n_jobs=None, + positive=False, + ): + super().__init__( + fit_intercept=fit_intercept, + copy_X=copy_X, + n_jobs=n_jobs, + positive=positive, + ) + + elif sklearn_check_version("0.24"): + + def __init__( + self, + fit_intercept=True, + normalize="deprecated" if sklearn_check_version("1.0") else False, + copy_X=True, + n_jobs=None, + positive=False, + ): + super().__init__( + fit_intercept=fit_intercept, + normalize=normalize, + copy_X=copy_X, + n_jobs=n_jobs, + positive=positive, + ) + + else: + + def __init__( + self, + fit_intercept=True, + normalize=False, + copy_X=True, + n_jobs=None, + ): + super().__init__( + fit_intercept=fit_intercept, + normalize=normalize, + copy_X=copy_X, + n_jobs=n_jobs, + ) + + def fit(self, X, y, sample_weight=None): + """ + Fit linear model. + Parameters + ---------- + X : {array-like, sparse matrix} of shape (n_samples, n_features) + Training data. + y : array-like of shape (n_samples,) or (n_samples, n_targets) + Target values. Will be cast to X's dtype if necessary. + sample_weight : array-like of shape (n_samples,), default=None + Individual weights for each sample. + .. versionadded:: 0.17 + parameter *sample_weight* support to LinearRegression. + Returns + ------- + self : object + Fitted Estimator. + """ + if sklearn_check_version("1.0"): + self._check_feature_names(X, reset=True) + if sklearn_check_version("1.2"): + self._validate_params() + + dispatch( + self, + "fit", + { + "onedal": self.__class__._onedal_fit, + "sklearn": sklearn_LinearRegression.fit, + }, + X, + y, + sample_weight, + ) + return self + + @wrap_output_data + def predict(self, X): + """ + Predict using the linear model. + Parameters + ---------- + X : array-like or sparse matrix, shape (n_samples, n_features) + Samples. + Returns + ------- + C : array, shape (n_samples, n_targets) + Returns predicted values. + """ + if sklearn_check_version("1.0"): + self._check_feature_names(X, reset=False) + return dispatch( + self, + "predict", + { + "onedal": self.__class__._onedal_predict, + "sklearn": sklearn_LinearRegression.predict, + }, + X, + ) + + def _test_type_and_finiteness(self, X_in): + X = X_in if isinstance(X_in, np.ndarray) else np.asarray(X_in) + + dtype = X.dtype + if "complex" in str(type(dtype)): + return False + + try: + _assert_all_finite(X) + except BaseException: + return False + return True + + def _onedal_fit_supported(self, method_name, *data): + assert method_name == "fit" + assert len(data) == 3 + X, y, sample_weight = data + + class_name = self.__class__.__name__ + patching_status = PatchingConditionsChain( + f"sklearn.linear_model.{class_name}.fit" + ) + + normalize_is_set = ( + hasattr(self, "normalize") + and self.normalize + and self.normalize != "deprecated" + ) + positive_is_set = hasattr(self, "positive") and self.positive + + n_samples = _num_samples(X) + n_features = _num_features(X, fallback_1d=True) + + # Check if equations are well defined + is_good_for_onedal = n_samples > (n_features + int(self.fit_intercept)) + + dal_ready = patching_status.and_conditions( + [ + (sample_weight is None, "Sample weight is not supported."), + ( + not issparse(X) and not issparse(y), + "Sparse input is not supported.", + ), + (not normalize_is_set, "Normalization is not supported."), + ( + not positive_is_set, + "Forced positive coefficients are not supported.", + ), + ( + is_good_for_onedal, + "The shape of X (fitting) does not satisfy oneDAL requirements:." + "Number of features + 1 >= number of samples.", + ), + ] + ) + if not dal_ready: + return patching_status + + if not patching_status.and_condition( + self._test_type_and_finiteness(X), "Input X is not supported." + ): + return patching_status + + patching_status.and_condition( + self._test_type_and_finiteness(y), "Input y is not supported." + ) + + return patching_status + + def _onedal_predict_supported(self, method_name, *data): + assert method_name == "predict" + assert len(data) == 1 + + class_name = self.__class__.__name__ + patching_status = PatchingConditionsChain( + f"sklearn.linear_model.{class_name}.predict" + ) + + n_samples = _num_samples(*data) + model_is_sparse = issparse(self.coef_) or ( + self.fit_intercept and issparse(self.intercept_) + ) + dal_ready = patching_status.and_conditions( + [ + (n_samples > 0, "Number of samples is less than 1."), + (not issparse(*data), "Sparse input is not supported."), + (not model_is_sparse, "Sparse coefficients are not supported."), + (hasattr(self, "_onedal_estimator"), "oneDAL model was not trained."), + ] + ) + if not dal_ready: + return patching_status + + patching_status.and_condition( + self._test_type_and_finiteness(*data), "Input X is not supported." + ) + + return patching_status + + def _onedal_supported(self, method_name, *data): + if method_name == "fit": + return self._onedal_fit_supported(method_name, *data) + if method_name == "predict": + return self._onedal_predict_supported(method_name, *data) + raise RuntimeError( + f"Unknown method {method_name} in {self.__class__.__name__}" + ) + + def _onedal_gpu_supported(self, method_name, *data): + return self._onedal_supported(method_name, *data) + + def _onedal_cpu_supported(self, method_name, *data): + return self._onedal_supported(method_name, *data) + + def _initialize_onedal_estimator(self): + onedal_params = {"fit_intercept": self.fit_intercept, "copy_X": self.copy_X} + self._onedal_estimator = onedal_LinearRegression(**onedal_params) + + def _onedal_fit(self, X, y, sample_weight, queue=None): + assert sample_weight is None + + check_params = { + "X": X, + "y": y, + "dtype": [np.float64, np.float32], + "accept_sparse": ["csr", "csc", "coo"], + "y_numeric": True, + "multi_output": True, + "force_all_finite": False, + } + if sklearn_check_version("1.2"): + X, y = self._validate_data(**check_params) + else: + X, y = check_X_y(**check_params) + + if sklearn_check_version("1.0") and not sklearn_check_version("1.2"): + self._normalize = _deprecate_normalize( + self.normalize, + default=False, + estimator_name=self.__class__.__name__, + ) + + self._initialize_onedal_estimator() + try: + self._onedal_estimator.fit(X, y, queue=queue) + self._save_attributes() + + except RuntimeError: + logging.getLogger("sklearnex").info( + f"{self.__class__.__name__}.fit " + + get_patch_message("sklearn_after_onedal") + ) + + del self._onedal_estimator + super().fit(X, y) + + def _onedal_predict(self, X, queue=None): + X = self._validate_data(X, accept_sparse=False, reset=False) + if not hasattr(self, "_onedal_estimator"): + self._initialize_onedal_estimator() + self._onedal_estimator.coef_ = self.coef_ + self._onedal_estimator.intercept_ = self.intercept_ + + return self._onedal_estimator.predict(X, queue=queue) + +else: + from daal4py.sklearn.linear_model import LinearRegression + + logging.warning( + "Sklearnex LinearRegression requires oneDAL version >= 2023.1 " + "but it was not found" + ) diff --git a/sklearnex/linear_model/tests/test_linear.py b/sklearnex/linear_model/tests/test_linear.py old mode 100755 new mode 100644 index 3b8dd9d3ab..4637e194b0 --- a/sklearnex/linear_model/tests/test_linear.py +++ b/sklearnex/linear_model/tests/test_linear.py @@ -16,22 +16,33 @@ # =============================================================================== import numpy as np +import pytest from numpy.testing import assert_allclose from sklearn.datasets import make_regression from daal4py.sklearn._utils import daal_check_version +from onedal.tests.utils._dataframes_support import ( + _as_numpy, + _convert_to_dataframe, + get_dataframes_and_queues, +) -def test_sklearnex_import_linear(): +@pytest.mark.parametrize("dataframe,queue", get_dataframes_and_queues()) +def test_sklearnex_import_linear(dataframe, queue): from sklearnex.linear_model import LinearRegression X = np.array([[1, 1], [1, 2], [2, 2], [2, 3]]) y = np.dot(X, np.array([1, 2])) + 3 + X = _convert_to_dataframe(X, sycl_queue=queue, target_df=dataframe) + y = _convert_to_dataframe(y, sycl_queue=queue, target_df=dataframe) linreg = LinearRegression().fit(X, y) - assert "daal4py" in linreg.__module__ + if daal_check_version((2023, "P", 100)): + assert hasattr(linreg, "_onedal_estimator") + assert "sklearnex" in linreg.__module__ assert linreg.n_features_in_ == 2 - assert_allclose(linreg.intercept_, 3.0) - assert_allclose(linreg.coef_, [1.0, 2.0]) + assert_allclose(_as_numpy(linreg.intercept_), 3.0) + assert_allclose(_as_numpy(linreg.coef_), [1.0, 2.0]) def test_sklearnex_import_ridge(): diff --git a/sklearnex/preview/__init__.py b/sklearnex/preview/__init__.py index fd46e9cc72..1f5d8347a9 100644 --- a/sklearnex/preview/__init__.py +++ b/sklearnex/preview/__init__.py @@ -15,4 +15,4 @@ # limitations under the License. # ============================================================================== -__all__ = ["cluster", "decomposition", "linear_model"] +__all__ = ["cluster", "decomposition"] diff --git a/sklearnex/preview/linear_model/__init__.py b/sklearnex/preview/linear_model/__init__.py deleted file mode 100755 index a244f823a8..0000000000 --- a/sklearnex/preview/linear_model/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env python -# =============================================================================== -# Copyright 2023 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# =============================================================================== - -from .linear import LinearRegression - -__all__ = ["LinearRegression"] diff --git a/sklearnex/preview/linear_model/_common.py b/sklearnex/preview/linear_model/_common.py deleted file mode 100644 index fda3cc7794..0000000000 --- a/sklearnex/preview/linear_model/_common.py +++ /dev/null @@ -1,66 +0,0 @@ -# ============================================================================== -# Copyright 2023 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# ============================================================================== - -from abc import ABC - -import numpy as np -from sklearn.calibration import CalibratedClassifierCV -from sklearn.model_selection import StratifiedKFold -from sklearn.preprocessing import LabelEncoder - -from daal4py.sklearn._utils import sklearn_check_version -from onedal.utils import _column_or_1d - - -def get_coef(self): - return self._coef_ - - -def set_coef(self, value): - self._coef_ = value - if hasattr(self, "_onedal_estimator"): - self._onedal_estimator.coef_ = value - if not self._is_in_fit: - del self._onedal_estimator._onedal_model - - -def get_intercept(self): - return self._intercept_ - - -def set_intercept(self, value): - self._intercept_ = value - if hasattr(self, "_onedal_estimator"): - self._onedal_estimator.intercept_ = value - if not self._is_in_fit: - del self._onedal_estimator._onedal_model - - -class BaseLinearRegression(ABC): - def _save_attributes(self): - self.n_features_in_ = self._onedal_estimator.n_features_in_ - self.fit_status_ = 0 - self._coef_ = self._onedal_estimator.coef_ - self._intercept_ = self._onedal_estimator.intercept_ - self._sparse = False - - self.coef_ = property(get_coef, set_coef) - self.intercept_ = property(get_intercept, set_intercept) - - self._is_in_fit = True - self.coef_ = self._coef_ - self.intercept_ = self._intercept_ - self._is_in_fit = False diff --git a/sklearnex/preview/linear_model/linear.py b/sklearnex/preview/linear_model/linear.py deleted file mode 100644 index 37d3182a5d..0000000000 --- a/sklearnex/preview/linear_model/linear.py +++ /dev/null @@ -1,331 +0,0 @@ -# =============================================================================== -# Copyright 2023 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# =============================================================================== - -import logging - -from daal4py.sklearn._utils import daal_check_version - -if daal_check_version((2023, "P", 100)): - import numpy as np - from sklearn.linear_model import LinearRegression as sklearn_LinearRegression - - from daal4py.sklearn._utils import get_dtype, make2d, sklearn_check_version - - from ..._device_offload import dispatch, wrap_output_data - from ..._utils import PatchingConditionsChain, get_patch_message - from ...utils.validation import _assert_all_finite - from ._common import BaseLinearRegression - - if sklearn_check_version("1.0") and not sklearn_check_version("1.2"): - from sklearn.linear_model._base import _deprecate_normalize - - from scipy.sparse import issparse - from sklearn.exceptions import NotFittedError - from sklearn.utils.validation import _deprecate_positional_args, check_X_y - - from onedal.linear_model import LinearRegression as onedal_LinearRegression - from onedal.utils import _num_features, _num_samples - - class LinearRegression(sklearn_LinearRegression, BaseLinearRegression): - __doc__ = sklearn_LinearRegression.__doc__ - intercept_, coef_ = None, None - - if sklearn_check_version("1.2"): - _parameter_constraints: dict = { - **sklearn_LinearRegression._parameter_constraints - } - - def __init__( - self, - fit_intercept=True, - copy_X=True, - n_jobs=None, - positive=False, - ): - super().__init__( - fit_intercept=fit_intercept, - copy_X=copy_X, - n_jobs=n_jobs, - positive=positive, - ) - - elif sklearn_check_version("0.24"): - - def __init__( - self, - fit_intercept=True, - normalize="deprecated" if sklearn_check_version("1.0") else False, - copy_X=True, - n_jobs=None, - positive=False, - ): - super().__init__( - fit_intercept=fit_intercept, - normalize=normalize, - copy_X=copy_X, - n_jobs=n_jobs, - positive=positive, - ) - - else: - - def __init__( - self, - fit_intercept=True, - normalize=False, - copy_X=True, - n_jobs=None, - ): - super().__init__( - fit_intercept=fit_intercept, - normalize=normalize, - copy_X=copy_X, - n_jobs=n_jobs, - ) - - def fit(self, X, y, sample_weight=None): - """ - Fit linear model. - Parameters - ---------- - X : {array-like, sparse matrix} of shape (n_samples, n_features) - Training data. - y : array-like of shape (n_samples,) or (n_samples, n_targets) - Target values. Will be cast to X's dtype if necessary. - sample_weight : array-like of shape (n_samples,), default=None - Individual weights for each sample. - .. versionadded:: 0.17 - parameter *sample_weight* support to LinearRegression. - Returns - ------- - self : object - Fitted Estimator. - """ - if sklearn_check_version("1.0"): - self._check_feature_names(X, reset=True) - if sklearn_check_version("1.2"): - self._validate_params() - - dispatch( - self, - "fit", - { - "onedal": self.__class__._onedal_fit, - "sklearn": sklearn_LinearRegression.fit, - }, - X, - y, - sample_weight, - ) - return self - - @wrap_output_data - def predict(self, X): - """ - Predict using the linear model. - Parameters - ---------- - X : array-like or sparse matrix, shape (n_samples, n_features) - Samples. - Returns - ------- - C : array, shape (n_samples, n_targets) - Returns predicted values. - """ - if sklearn_check_version("1.0"): - self._check_feature_names(X, reset=False) - return dispatch( - self, - "predict", - { - "onedal": self.__class__._onedal_predict, - "sklearn": sklearn_LinearRegression.predict, - }, - X, - ) - - def _test_type_and_finiteness(self, X_in): - X = X_in if isinstance(X_in, np.ndarray) else np.asarray(X_in) - - dtype = X.dtype - if "complex" in str(type(dtype)): - return False - - try: - _assert_all_finite(X) - except BaseException: - return False - return True - - def _onedal_fit_supported(self, method_name, *data): - assert method_name == "fit" - assert len(data) == 3 - X, y, sample_weight = data - - class_name = self.__class__.__name__ - patching_status = PatchingConditionsChain( - f"sklearn.linear_model.{class_name}.fit" - ) - - normalize_is_set = ( - hasattr(self, "normalize") - and self.normalize - and self.normalize != "deprecated" - ) - positive_is_set = hasattr(self, "positive") and self.positive - - n_samples = _num_samples(X) - n_features = _num_features(X, fallback_1d=True) - - # Check if equations are well defined - is_good_for_onedal = n_samples > (n_features + int(self.fit_intercept)) - - dal_ready = patching_status.and_conditions( - [ - (sample_weight is None, "Sample weight is not supported."), - ( - not issparse(X) and not issparse(y), - "Sparse input is not supported.", - ), - (not normalize_is_set, "Normalization is not supported."), - ( - not positive_is_set, - "Forced positive coefficients are not supported.", - ), - ( - is_good_for_onedal, - "The shape of X (fitting) does not satisfy oneDAL requirements:." - "Number of features + 1 >= number of samples.", - ), - ] - ) - if not dal_ready: - return patching_status - - if not patching_status.and_condition( - self._test_type_and_finiteness(X), "Input X is not supported." - ): - return patching_status - - patching_status.and_condition( - self._test_type_and_finiteness(y), "Input y is not supported." - ) - - return patching_status - - def _onedal_predict_supported(self, method_name, *data): - assert method_name == "predict" - assert len(data) == 1 - - class_name = self.__class__.__name__ - patching_status = PatchingConditionsChain( - f"sklearn.linear_model.{class_name}.predict" - ) - - n_samples = _num_samples(*data) - model_is_sparse = issparse(self.coef_) or ( - self.fit_intercept and issparse(self.intercept_) - ) - dal_ready = patching_status.and_conditions( - [ - (n_samples > 0, "Number of samples is less than 1."), - (not issparse(*data), "Sparse input is not supported."), - (not model_is_sparse, "Sparse coefficients are not supported."), - (hasattr(self, "_onedal_estimator"), "oneDAL model was not trained."), - ] - ) - if not dal_ready: - return patching_status - - patching_status.and_condition( - self._test_type_and_finiteness(*data), "Input X is not supported." - ) - - return patching_status - - def _onedal_supported(self, method_name, *data): - if method_name == "fit": - return self._onedal_fit_supported(method_name, *data) - if method_name == "predict": - return self._onedal_predict_supported(method_name, *data) - raise RuntimeError( - f"Unknown method {method_name} in {self.__class__.__name__}" - ) - - def _onedal_gpu_supported(self, method_name, *data): - return self._onedal_supported(method_name, *data) - - def _onedal_cpu_supported(self, method_name, *data): - return self._onedal_supported(method_name, *data) - - def _initialize_onedal_estimator(self): - onedal_params = {"fit_intercept": self.fit_intercept, "copy_X": self.copy_X} - self._onedal_estimator = onedal_LinearRegression(**onedal_params) - - def _onedal_fit(self, X, y, sample_weight, queue=None): - assert sample_weight is None - - check_params = { - "X": X, - "y": y, - "dtype": [np.float64, np.float32], - "accept_sparse": ["csr", "csc", "coo"], - "y_numeric": True, - "multi_output": True, - "force_all_finite": False, - } - if sklearn_check_version("1.2"): - X, y = self._validate_data(**check_params) - else: - X, y = check_X_y(**check_params) - - if sklearn_check_version("1.0") and not sklearn_check_version("1.2"): - self._normalize = _deprecate_normalize( - self.normalize, - default=False, - estimator_name=self.__class__.__name__, - ) - - self._initialize_onedal_estimator() - try: - self._onedal_estimator.fit(X, y, queue=queue) - self._save_attributes() - - except RuntimeError: - logging.getLogger("sklearnex").info( - f"{self.__class__.__name__}.fit " - + get_patch_message("sklearn_after_onedal") - ) - - del self._onedal_estimator - super().fit(X, y) - - def _onedal_predict(self, X, queue=None): - X = self._validate_data(X, accept_sparse=False, reset=False) - if not hasattr(self, "_onedal_estimator"): - self._initialize_onedal_estimator() - self._onedal_estimator.coef_ = self.coef_ - self._onedal_estimator.intercept_ = self.intercept_ - - return self._onedal_estimator.predict(X, queue=queue) - -else: - from daal4py.sklearn.linear_model import LinearRegression - - logging.warning( - "Preview LinearRegression requires oneDAL version >= 2023.1 " - "but it was not found" - ) diff --git a/sklearnex/preview/linear_model/tests/test_preview_linear.py b/sklearnex/preview/linear_model/tests/test_preview_linear.py deleted file mode 100755 index bb643dfd4b..0000000000 --- a/sklearnex/preview/linear_model/tests/test_preview_linear.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python -# =============================================================================== -# Copyright 2021 Intel Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# =============================================================================== - -import numpy as np -import pytest -from numpy.testing import assert_allclose -from sklearn.datasets import make_regression - -from daal4py.sklearn._utils import daal_check_version -from onedal.tests.utils._dataframes_support import ( - _as_numpy, - _convert_to_dataframe, - get_dataframes_and_queues, -) - - -@pytest.mark.parametrize("dataframe,queue", get_dataframes_and_queues()) -def test_sklearnex_import_linear(dataframe, queue): - from sklearnex.preview.linear_model import LinearRegression - - X = np.array([[1, 1], [1, 2], [2, 2], [2, 3]]) - y = np.dot(X, np.array([1, 2])) + 3 - X = _convert_to_dataframe(X, sycl_queue=queue, target_df=dataframe) - y = _convert_to_dataframe(y, sycl_queue=queue, target_df=dataframe) - linreg = LinearRegression().fit(X, y) - if daal_check_version((2023, "P", 100)): - assert "sklearnex" in linreg.__module__ - assert hasattr(linreg, "_onedal_estimator") - else: - assert "daal4py" in linreg.__module__ - assert linreg.n_features_in_ == 2 - assert_allclose(_as_numpy(linreg.intercept_), 3.0) - assert_allclose(_as_numpy(linreg.coef_), [1.0, 2.0]) diff --git a/sklearnex/tests/test_memory_usage.py b/sklearnex/tests/test_memory_usage.py index e0a94d87da..f9b43d9d47 100644 --- a/sklearnex/tests/test_memory_usage.py +++ b/sklearnex/tests/test_memory_usage.py @@ -31,7 +31,6 @@ from sklearnex.metrics import pairwise_distances, roc_auc_score from sklearnex.model_selection import train_test_split from sklearnex.preview.decomposition import PCA as PreviewPCA -from sklearnex.preview.linear_model import LinearRegression as PreviewLinearRegression from sklearnex.utils import _assert_all_finite @@ -101,7 +100,6 @@ def remove_duplicated_estimators(estimators_list): ) estimators = [ PreviewPCA, - PreviewLinearRegression, TrainTestSplitEstimator, FiniteCheckEstimator, CosineDistancesEstimator, diff --git a/sklearnex/tests/test_monkeypatch.py b/sklearnex/tests/test_monkeypatch.py index a43744eb3d..3cb1d70c23 100755 --- a/sklearnex/tests/test_monkeypatch.py +++ b/sklearnex/tests/test_monkeypatch.py @@ -177,7 +177,7 @@ def get_estimators(): assert "sklearnex" in rfc.__module__ if daal_check_version((2023, "P", 100)): - assert "sklearnex.preview" in lr.__module__ + assert "sklearnex" in lr.__module__ else: assert "daal4py" in lr.__module__ @@ -199,7 +199,10 @@ def get_estimators(): assert not sklearnex.dispatcher._is_preview_enabled() lr, pca, dbscan, svc, rfc = get_estimators() - assert "daal4py" in lr.__module__ + if daal_check_version((2023, "P", 100)): + assert "sklearnex" in lr.__module__ + else: + assert "daal4py" in lr.__module__ assert "daal4py" in pca.__module__ assert "sklearnex" in rfc.__module__ assert "sklearnex" in dbscan.__module__