From 96c68703c510a695751072bd6a35a5add50aaf1d Mon Sep 17 00:00:00 2001 From: Julien Jerphanion Date: Mon, 12 Dec 2022 10:53:30 +0100 Subject: [PATCH 01/12] FIX Make `cd_fast` use cnp.ndarray to support readonly buffers Follow-up of #23147. --- sklearn/linear_model/_cd_fast.pyx | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/sklearn/linear_model/_cd_fast.pyx b/sklearn/linear_model/_cd_fast.pyx index 0c28d9929be63..72d2466593dab 100644 --- a/sklearn/linear_model/_cd_fast.pyx +++ b/sklearn/linear_model/_cd_fast.pyx @@ -92,11 +92,12 @@ cdef floating diff_abs_max(int n, floating* a, floating* b) nogil: def enet_coordinate_descent( - floating[::1] w, + # TODO: const-qualify fused typed memoryview when Cython 3 is used (#23147) + cnp.ndarray[floating, ndim=1, mode='c'] w, floating alpha, floating beta, floating[::1, :] X, - floating[::1] y, + cnp.ndarray[floating, ndim=1, mode='c'] y, unsigned int max_iter, floating tol, object rng, @@ -274,15 +275,16 @@ def enet_coordinate_descent( def sparse_enet_coordinate_descent( - floating [::1] w, + # TODO: const-qualify fused typed memoryview when Cython 3 is used (#23147) + cnp.ndarray[floating, ndim=1, mode='c'] w, floating alpha, floating beta, - floating[::1] X_data, # TODO: Make const after release of Cython 3 (#23147) + cnp.ndarray[floating, ndim=1, mode='c'] X_data, const int[::1] X_indices, const int[::1] X_indptr, - floating[::1] y, - floating[::1] sample_weight, - floating[::1] X_mean, + cnp.ndarray[floating, ndim=1, mode='c'] y, + cnp.ndarray[floating, ndim=1, mode='c'] sample_weight, + cnp.ndarray[floating, ndim=1, mode='c'] X_mean, unsigned int max_iter, floating tol, object rng, @@ -565,7 +567,8 @@ def sparse_enet_coordinate_descent( def enet_coordinate_descent_gram( - floating[::1] w, + # TODO: const-qualify fused typed memoryview when Cython 3 is used (#23147) + cnp.ndarray[floating, ndim=1, mode='c'] w, floating alpha, floating beta, cnp.ndarray[floating, ndim=2, mode='c'] Q, @@ -736,10 +739,10 @@ def enet_coordinate_descent_gram( def enet_coordinate_descent_multi_task( - floating[::1, :] W, + # TODO: const-qualify fused typed memoryview when Cython 3 is used (#23147) + cnp.ndarray[floating, ndim=2, mode='fortran'] W, floating l1_reg, floating l2_reg, - # TODO: use const qualified fused-typed memoryview when Cython 3.0 is used. cnp.ndarray[floating, ndim=2, mode='fortran'] X, cnp.ndarray[floating, ndim=2, mode='fortran'] Y, unsigned int max_iter, From 73fa2437b2867398c68750c41c8c9af378b20d66 Mon Sep 17 00:00:00 2001 From: Julien Jerphanion Date: Thu, 15 Dec 2022 14:19:23 +0100 Subject: [PATCH 02/12] MAINT Remove ReadonlyArrayWrapper --- sklearn/linear_model/_coordinate_descent.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sklearn/linear_model/_coordinate_descent.py b/sklearn/linear_model/_coordinate_descent.py index da4b82b19ede4..9ecfd1c2740a5 100644 --- a/sklearn/linear_model/_coordinate_descent.py +++ b/sklearn/linear_model/_coordinate_descent.py @@ -30,7 +30,6 @@ check_is_fitted, column_or_1d, ) -from ..utils._readonly_array_wrapper import ReadonlyArrayWrapper from ..utils.fixes import delayed # mypy error: Module 'sklearn.linear_model' has no attribute '_cd_fast' @@ -594,9 +593,7 @@ def enet_path( w=coef_, alpha=l1_reg, beta=l2_reg, - X_data=ReadonlyArrayWrapper( - X.data - ), # TODO: Remove after release of Cython 3 (#23147) + X_data=X.data, X_indices=X.indices, X_indptr=X.indptr, y=y, From 8d1ce0e5d4795bf2b98c9690495ba80560123fd3 Mon Sep 17 00:00:00 2001 From: Julien Jerphanion Date: Fri, 16 Dec 2022 09:28:44 +0100 Subject: [PATCH 03/12] TST Add non-regression test for #25165 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This test is built based on Loïc's reproducer: https://github.com/scikit-learn/scikit-learn/issues/25165#issuecomment-1348588230 Co-authored-by: Loïc Esteve --- .../decomposition/tests/test_dict_learning.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/sklearn/decomposition/tests/test_dict_learning.py b/sklearn/decomposition/tests/test_dict_learning.py index 67d464b3f2884..5105062355fa2 100644 --- a/sklearn/decomposition/tests/test_dict_learning.py +++ b/sklearn/decomposition/tests/test_dict_learning.py @@ -1038,6 +1038,30 @@ def test_get_feature_names_out(estimator): ) +def test_cd_work_on_joblib_memmapped_data(): + # Non-regression test for: + # https://github.com/scikit-learn/scikit-learn/issues/25165 + # The Cython implementation of coordinate descent must work + # on readonly data. + + rng = np.random.RandomState(0) + # This dataset is sufficient large to be memmapped by joblib + # and thus becomes readonly even if it originally isn't. + X_train = rng.randn(5000, 3072) + + dict_learner = DictionaryLearning( + n_components=56, + random_state=0, + n_jobs=2, + fit_algorithm="cd", + verbose=True, + max_iter=50, + ) + + # This must run and complete without error. + dict_learner.fit(X_train) + + # TODO(1.4) remove def test_minibatch_dictionary_learning_warns_and_ignore_n_iter(): """Check that we always raise a warning when `n_iter` is set even if it is From c2234811785a06306fd732b8a4007d43dbc4d9c4 Mon Sep 17 00:00:00 2001 From: Julien Jerphanion Date: Fri, 16 Dec 2022 13:52:23 +0100 Subject: [PATCH 04/12] DEBUG Use ReadonlyArrayWrapper to support const qualification Reverting to use cnp.ndarray does not solve the problem. This is a tentative workaround using ReadonlyArrayWrapper. This solves the problem, but there is now a segmentation fault. --- sklearn/linear_model/_cd_fast.pyx | 12 ++++++------ sklearn/linear_model/_coordinate_descent.py | 15 ++++++++++----- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/sklearn/linear_model/_cd_fast.pyx b/sklearn/linear_model/_cd_fast.pyx index 72d2466593dab..dc1c374d61f62 100644 --- a/sklearn/linear_model/_cd_fast.pyx +++ b/sklearn/linear_model/_cd_fast.pyx @@ -568,12 +568,12 @@ def sparse_enet_coordinate_descent( def enet_coordinate_descent_gram( # TODO: const-qualify fused typed memoryview when Cython 3 is used (#23147) - cnp.ndarray[floating, ndim=1, mode='c'] w, + floating[::1] w, floating alpha, floating beta, - cnp.ndarray[floating, ndim=2, mode='c'] Q, - cnp.ndarray[floating, ndim=1, mode='c'] q, - cnp.ndarray[floating, ndim=1] y, + floating[:, ::1] Q, + floating[::1] q, + floating[:] y, unsigned int max_iter, floating tol, object rng, @@ -633,9 +633,9 @@ def enet_coordinate_descent_gram( cdef UINT32_t* rand_r_state = &rand_r_state_seed cdef floating y_norm2 = np.dot(y, y) - cdef floating* w_ptr = &w[0] + cdef floating* w_ptr = &w[0] cdef floating* Q_ptr = &Q[0, 0] - cdef floating* q_ptr = q.data + cdef floating* q_ptr = &q[0] cdef floating* H_ptr = &H[0] cdef floating* XtA_ptr = &XtA[0] tol = tol * y_norm2 diff --git a/sklearn/linear_model/_coordinate_descent.py b/sklearn/linear_model/_coordinate_descent.py index 9ecfd1c2740a5..54bb9072c12f4 100644 --- a/sklearn/linear_model/_coordinate_descent.py +++ b/sklearn/linear_model/_coordinate_descent.py @@ -30,6 +30,7 @@ check_is_fitted, column_or_1d, ) +from ..utils._readonly_array_wrapper import ReadonlyArrayWrapper from ..utils.fixes import delayed # mypy error: Module 'sklearn.linear_model' has no attribute '_cd_fast' @@ -593,7 +594,9 @@ def enet_path( w=coef_, alpha=l1_reg, beta=l2_reg, - X_data=X.data, + # TODO: remove ReadonlyArrayWrapper when const-qualify fused typed + # memoryviews are used (this necessiates Cython 3). + X_data=ReadonlyArrayWrapper(X.data), X_indices=X.indices, X_indptr=X.indptr, y=y, @@ -615,12 +618,14 @@ def enet_path( if check_input: precompute = check_array(precompute, dtype=X.dtype.type, order="C") model = cd_fast.enet_coordinate_descent_gram( - coef_, + # TODO: remove ReadonlyArrayWrappers when const-qualify fused typed + # memoryviews are used (this necessiates Cython 3). + ReadonlyArrayWrapper(coef_), l1_reg, l2_reg, - precompute, - Xy, - y, + ReadonlyArrayWrapper(precompute), + ReadonlyArrayWrapper(Xy), + ReadonlyArrayWrapper(y), max_iter, tol, rng, From c1a4d96a6f1a09abd16c6f4fb47d40fc946dc0b3 Mon Sep 17 00:00:00 2001 From: Julien Jerphanion Date: Thu, 5 Jan 2023 14:15:11 +0100 Subject: [PATCH 05/12] MAINT Simplify and make copy of chunks of w Co-authored-by: Thomas J. Fan --- sklearn/decomposition/_dict_learning.py | 2 ++ .../decomposition/tests/test_dict_learning.py | 23 ++++++++------- sklearn/linear_model/_cd_fast.pyx | 29 +++++++++---------- sklearn/linear_model/_coordinate_descent.py | 23 ++++++++------- 4 files changed, 41 insertions(+), 36 deletions(-) diff --git a/sklearn/decomposition/_dict_learning.py b/sklearn/decomposition/_dict_learning.py index b0c252fc31698..e99465ada3b73 100644 --- a/sklearn/decomposition/_dict_learning.py +++ b/sklearn/decomposition/_dict_learning.py @@ -174,6 +174,8 @@ def _sparse_encode( ) if init is not None: + if not init.flags["WRITEABLE"]: + init = np.array(init) clf.coef_ = init clf.fit(dictionary.T, X.T, check_input=check_input) diff --git a/sklearn/decomposition/tests/test_dict_learning.py b/sklearn/decomposition/tests/test_dict_learning.py index 5105062355fa2..24b73622c418a 100644 --- a/sklearn/decomposition/tests/test_dict_learning.py +++ b/sklearn/decomposition/tests/test_dict_learning.py @@ -5,6 +5,10 @@ from functools import partial import itertools +from joblib import Parallel + +import sklearn + from sklearn.base import clone from sklearn.exceptions import ConvergenceWarning @@ -1038,24 +1042,23 @@ def test_get_feature_names_out(estimator): ) -def test_cd_work_on_joblib_memmapped_data(): - # Non-regression test for: - # https://github.com/scikit-learn/scikit-learn/issues/25165 - # The Cython implementation of coordinate descent must work - # on readonly data. +def test_cd_work_on_joblib_memmapped_data(monkeypatch): + monkeypatch.setattr( + sklearn.decomposition._dict_learning, + "Parallel", + partial(Parallel, max_nbytes=1000), + ) rng = np.random.RandomState(0) - # This dataset is sufficient large to be memmapped by joblib - # and thus becomes readonly even if it originally isn't. - X_train = rng.randn(5000, 3072) + X_train = rng.randn(100, 10) dict_learner = DictionaryLearning( - n_components=56, + n_components=5, random_state=0, n_jobs=2, fit_algorithm="cd", - verbose=True, max_iter=50, + verbose=True, ) # This must run and complete without error. diff --git a/sklearn/linear_model/_cd_fast.pyx b/sklearn/linear_model/_cd_fast.pyx index dc1c374d61f62..4ffa70161e602 100644 --- a/sklearn/linear_model/_cd_fast.pyx +++ b/sklearn/linear_model/_cd_fast.pyx @@ -92,12 +92,11 @@ cdef floating diff_abs_max(int n, floating* a, floating* b) nogil: def enet_coordinate_descent( - # TODO: const-qualify fused typed memoryview when Cython 3 is used (#23147) - cnp.ndarray[floating, ndim=1, mode='c'] w, + floating[::1] w, floating alpha, floating beta, floating[::1, :] X, - cnp.ndarray[floating, ndim=1, mode='c'] y, + floating[::1] y, unsigned int max_iter, floating tol, object rng, @@ -275,16 +274,15 @@ def enet_coordinate_descent( def sparse_enet_coordinate_descent( - # TODO: const-qualify fused typed memoryview when Cython 3 is used (#23147) - cnp.ndarray[floating, ndim=1, mode='c'] w, + floating [::1] w, floating alpha, floating beta, - cnp.ndarray[floating, ndim=1, mode='c'] X_data, + floating[::1] X_data, # TODO: Make const after release of Cython 3 (#23147) const int[::1] X_indices, const int[::1] X_indptr, - cnp.ndarray[floating, ndim=1, mode='c'] y, - cnp.ndarray[floating, ndim=1, mode='c'] sample_weight, - cnp.ndarray[floating, ndim=1, mode='c'] X_mean, + floating[::1] y, + floating[::1] sample_weight, + floating[::1] X_mean, unsigned int max_iter, floating tol, object rng, @@ -567,13 +565,12 @@ def sparse_enet_coordinate_descent( def enet_coordinate_descent_gram( - # TODO: const-qualify fused typed memoryview when Cython 3 is used (#23147) - floating[::1] w, + cnp.ndarray[floating, ndim=1, mode='c'] w, floating alpha, floating beta, - floating[:, ::1] Q, - floating[::1] q, - floating[:] y, + cnp.ndarray[floating, ndim=2, mode='c'] Q, + cnp.ndarray[floating, ndim=1, mode='c'] q, + cnp.ndarray[floating, ndim=1] y, unsigned int max_iter, floating tol, object rng, @@ -739,10 +736,10 @@ def enet_coordinate_descent_gram( def enet_coordinate_descent_multi_task( - # TODO: const-qualify fused typed memoryview when Cython 3 is used (#23147) - cnp.ndarray[floating, ndim=2, mode='fortran'] W, + floating[::1, :] W, floating l1_reg, floating l2_reg, + # TODO: use const qualified fused-typed memoryview when Cython 3.0 is used. cnp.ndarray[floating, ndim=2, mode='fortran'] X, cnp.ndarray[floating, ndim=2, mode='fortran'] Y, unsigned int max_iter, diff --git a/sklearn/linear_model/_coordinate_descent.py b/sklearn/linear_model/_coordinate_descent.py index 54bb9072c12f4..67fffac39d466 100644 --- a/sklearn/linear_model/_coordinate_descent.py +++ b/sklearn/linear_model/_coordinate_descent.py @@ -30,7 +30,6 @@ check_is_fitted, column_or_1d, ) -from ..utils._readonly_array_wrapper import ReadonlyArrayWrapper from ..utils.fixes import delayed # mypy error: Module 'sklearn.linear_model' has no attribute '_cd_fast' @@ -594,9 +593,7 @@ def enet_path( w=coef_, alpha=l1_reg, beta=l2_reg, - # TODO: remove ReadonlyArrayWrapper when const-qualify fused typed - # memoryviews are used (this necessiates Cython 3). - X_data=ReadonlyArrayWrapper(X.data), + X_data=X.data, X_indices=X.indices, X_indptr=X.indptr, y=y, @@ -618,14 +615,12 @@ def enet_path( if check_input: precompute = check_array(precompute, dtype=X.dtype.type, order="C") model = cd_fast.enet_coordinate_descent_gram( - # TODO: remove ReadonlyArrayWrappers when const-qualify fused typed - # memoryviews are used (this necessiates Cython 3). - ReadonlyArrayWrapper(coef_), + coef_, l1_reg, l2_reg, - ReadonlyArrayWrapper(precompute), - ReadonlyArrayWrapper(Xy), - ReadonlyArrayWrapper(y), + precompute, + Xy, + y, max_iter, tol, rng, @@ -1886,6 +1881,14 @@ class LassoCV(RegressorMixin, LinearModelCV): :ref:`examples/linear_model/plot_lasso_model_selection.py `. + :class:`LassoCV` leads to different results than a hyperparameter + search using :class:`~sklearn.model_selection.GridSearchCV` with a + :class:`Lasso` model. In :class:`LassoCV`, a model for a given + penalty `alpha` is warm started using the coefficients of the + closest model (trained at the previous iteration) on the + regularization path. It tends to speed up the hyperparameter + search. + Examples -------- >>> from sklearn.linear_model import LassoCV From ad6058d041c4416f85c8e225be96396fcbcbde50 Mon Sep 17 00:00:00 2001 From: Julien Jerphanion Date: Thu, 5 Jan 2023 16:30:38 +0100 Subject: [PATCH 06/12] TST Make test_cd_work_on_joblib_memmapped_data faster to run --- sklearn/decomposition/tests/test_dict_learning.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sklearn/decomposition/tests/test_dict_learning.py b/sklearn/decomposition/tests/test_dict_learning.py index 24b73622c418a..16a695d1189ab 100644 --- a/sklearn/decomposition/tests/test_dict_learning.py +++ b/sklearn/decomposition/tests/test_dict_learning.py @@ -1046,11 +1046,11 @@ def test_cd_work_on_joblib_memmapped_data(monkeypatch): monkeypatch.setattr( sklearn.decomposition._dict_learning, "Parallel", - partial(Parallel, max_nbytes=1000), + partial(Parallel, max_nbytes=100), ) rng = np.random.RandomState(0) - X_train = rng.randn(100, 10) + X_train = rng.randn(10, 10) dict_learner = DictionaryLearning( n_components=5, From 160d6d5f31310bb4f40f00ddd4945b93970d4476 Mon Sep 17 00:00:00 2001 From: Julien Jerphanion Date: Thu, 5 Jan 2023 16:33:16 +0100 Subject: [PATCH 07/12] fixup! MAINT Simplify and make copy of chunks of w --- sklearn/linear_model/_coordinate_descent.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/sklearn/linear_model/_coordinate_descent.py b/sklearn/linear_model/_coordinate_descent.py index 67fffac39d466..9ecfd1c2740a5 100644 --- a/sklearn/linear_model/_coordinate_descent.py +++ b/sklearn/linear_model/_coordinate_descent.py @@ -1881,14 +1881,6 @@ class LassoCV(RegressorMixin, LinearModelCV): :ref:`examples/linear_model/plot_lasso_model_selection.py `. - :class:`LassoCV` leads to different results than a hyperparameter - search using :class:`~sklearn.model_selection.GridSearchCV` with a - :class:`Lasso` model. In :class:`LassoCV`, a model for a given - penalty `alpha` is warm started using the coefficients of the - closest model (trained at the previous iteration) on the - regularization path. It tends to speed up the hyperparameter - search. - Examples -------- >>> from sklearn.linear_model import LassoCV From 1200183c568e5f640007f8efbc5f878a8a847bec Mon Sep 17 00:00:00 2001 From: Julien Jerphanion Date: Thu, 5 Jan 2023 16:34:52 +0100 Subject: [PATCH 08/12] [scipy-dev] Trigger CI From 5ad7565b25130392c4f5a10a38ced09a90bab4f3 Mon Sep 17 00:00:00 2001 From: Julien Jerphanion Date: Thu, 5 Jan 2023 17:22:38 +0100 Subject: [PATCH 09/12] [scipy-dev] TST cnp.ndarray all the way! --- sklearn/linear_model/_cd_fast.pyx | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/sklearn/linear_model/_cd_fast.pyx b/sklearn/linear_model/_cd_fast.pyx index 4ffa70161e602..ba9b922461775 100644 --- a/sklearn/linear_model/_cd_fast.pyx +++ b/sklearn/linear_model/_cd_fast.pyx @@ -90,13 +90,13 @@ cdef floating diff_abs_max(int n, floating* a, floating* b) nogil: m = d return m - +# TODO: use const fused typed memoryview where possible when Cython 0.29.33 is used. def enet_coordinate_descent( - floating[::1] w, + cnp.ndarray[floating, ndim=1, mode='c'] w, floating alpha, floating beta, - floating[::1, :] X, - floating[::1] y, + cnp.ndarray[floating, ndim=2, mode='fortran'] X, + cnp.ndarray[floating, ndim=1, mode='c'] y, unsigned int max_iter, floating tol, object rng, @@ -273,16 +273,17 @@ def enet_coordinate_descent( return np.asarray(w), gap, tol, n_iter + 1 +# TODO: use const fused typed memoryview where possible when Cython 0.29.33 is used. def sparse_enet_coordinate_descent( - floating [::1] w, + cnp.ndarray[floating, ndim=1, mode='c'] w, floating alpha, floating beta, - floating[::1] X_data, # TODO: Make const after release of Cython 3 (#23147) + cnp.ndarray[floating, ndim=1, mode='c'] X_data, const int[::1] X_indices, const int[::1] X_indptr, - floating[::1] y, - floating[::1] sample_weight, - floating[::1] X_mean, + cnp.ndarray[floating, ndim=1, mode='c'] y, + cnp.ndarray[floating, ndim=1, mode='c'] sample_weight, + cnp.ndarray[floating, ndim=1, mode='c'] X_mean, unsigned int max_iter, floating tol, object rng, @@ -564,6 +565,7 @@ def sparse_enet_coordinate_descent( return np.asarray(w), gap, tol, n_iter + 1 +# TODO: use const fused typed memoryview where possible when Cython 0.29.33 is used. def enet_coordinate_descent_gram( cnp.ndarray[floating, ndim=1, mode='c'] w, floating alpha, @@ -734,9 +736,9 @@ def enet_coordinate_descent_gram( return np.asarray(w), gap, tol, n_iter + 1 - +# TODO: use const fused typed memoryview where possible when Cython 0.29.33 is used. def enet_coordinate_descent_multi_task( - floating[::1, :] W, + cnp.ndarray[floating, ndim=2, mode='fortran'] W, floating l1_reg, floating l2_reg, # TODO: use const qualified fused-typed memoryview when Cython 3.0 is used. From bb4246f519e555d47de19e6ac0aac5dd91da2ac6 Mon Sep 17 00:00:00 2001 From: Julien Jerphanion Date: Fri, 6 Jan 2023 17:51:04 +0100 Subject: [PATCH 10/12] CI Trigger CI From 4697e74fa59c78a74771b796bb5d89575e364105 Mon Sep 17 00:00:00 2001 From: Julien Jerphanion Date: Fri, 13 Jan 2023 10:39:56 +0100 Subject: [PATCH 11/12] DOC Document workaround --- sklearn/decomposition/_dict_learning.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sklearn/decomposition/_dict_learning.py b/sklearn/decomposition/_dict_learning.py index f497a4f8bcb80..34bc9cd63a4be 100644 --- a/sklearn/decomposition/_dict_learning.py +++ b/sklearn/decomposition/_dict_learning.py @@ -174,6 +174,11 @@ def _sparse_encode( ) if init is not None: + # In some workflows using coordinate descent algorithms: + # - users might provide NumPy arrays with read-only buffers + # - `joblib` might memmap arrays making their buffer read-only + # TODO: move this handling (which is currently too broad) + # closer to the actual private function which need buffers to be writable. if not init.flags["WRITEABLE"]: init = np.array(init) clf.coef_ = init From 5595f7fc8f79779f5637f2747aa9ebb38b819e12 Mon Sep 17 00:00:00 2001 From: Julien Jerphanion Date: Fri, 13 Jan 2023 10:41:30 +0100 Subject: [PATCH 12/12] DOC Add a whats_new entry for 1.2.1 --- doc/whats_new/v1.2.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/whats_new/v1.2.rst b/doc/whats_new/v1.2.rst index 2795b21231779..e7db3c71d7fb5 100644 --- a/doc/whats_new/v1.2.rst +++ b/doc/whats_new/v1.2.rst @@ -22,6 +22,14 @@ Changelog - |Fix| Inheriting from :class:`base.TransformerMixin` will only wrap the `transform` method if the class defines `transform` itself. :pr:`25295` by `Thomas Fan`_. +:mod:`sklearn.decomposition` +............................ + +- |Fix| :class:`decomposition.DictionaryLearning` better supports readonly NumPy + arrays. In particular, it better supports large datasets which are memory-mapped + when it is used with coordinate descent algorithms (i.e. when `fit_algorithm='cd'`). + :pr:`25172` by :user:`Julien Jerphanion `. + :mod:`sklearn.ensemble` .......................