From 3a2490af7b261543dfc940c065361fb0f058b1f7 Mon Sep 17 00:00:00 2001 From: Christian Lorentzen Date: Thu, 26 Jun 2025 23:07:10 +0200 Subject: [PATCH 1/4] ENH avoid np.square(X) in enet_coordinate_descent to save memory --- sklearn/linear_model/_cd_fast.pyx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/sklearn/linear_model/_cd_fast.pyx b/sklearn/linear_model/_cd_fast.pyx index ce598ebb011d2..a05f1ddc1d8a6 100644 --- a/sklearn/linear_model/_cd_fast.pyx +++ b/sklearn/linear_model/_cd_fast.pyx @@ -139,7 +139,7 @@ def enet_coordinate_descent( cdef unsigned int n_features = X.shape[1] # compute norms of the columns of X - cdef floating[::1] norm_cols_X = np.square(X).sum(axis=0) + cdef floating[::1] norm_cols_X = zeros(n_features, dtype=dtype) # initial value of the residuals cdef floating[::1] R = np.empty(n_samples, dtype=dtype) @@ -169,6 +169,16 @@ def enet_coordinate_descent( "unexpected results and is discouraged.") with nogil: + # norm_cols_X = np.square(X).sum(axis=0) + if no_sample_weights: + if not center: + for ii in range(n_features): + norm_cols_X[ii] = _dot(n_samples, &X[0, ii], 1, &X[0, ii], 1) + else: + for ii in range(n_features): + for jj in range(n_samples): + norm_cols_X[ii] += (X[jj, ii] - X_mean[ii]) ** 2 + # R = y - np.dot(X, w) _copy(n_samples, &y[0], 1, &R[0], 1) _gemv(ColMajor, NoTrans, n_samples, n_features, -1.0, &X[0, 0], From b1b621d6113a56f9240ec6a1f17d745065f8c815 Mon Sep 17 00:00:00 2001 From: Christian Lorentzen Date: Thu, 26 Jun 2025 23:20:14 +0200 Subject: [PATCH 2/4] simpler solution --- sklearn/linear_model/_cd_fast.pyx | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/sklearn/linear_model/_cd_fast.pyx b/sklearn/linear_model/_cd_fast.pyx index a05f1ddc1d8a6..76b1e3b606c3a 100644 --- a/sklearn/linear_model/_cd_fast.pyx +++ b/sklearn/linear_model/_cd_fast.pyx @@ -139,7 +139,10 @@ def enet_coordinate_descent( cdef unsigned int n_features = X.shape[1] # compute norms of the columns of X - cdef floating[::1] norm_cols_X = zeros(n_features, dtype=dtype) + # same as norm_cols_X = np.square(X).sum(axis=0) + cdef floating[::1] norm_cols_X = np.einsum( + "ij,ij->j", X.T, X.T, dtype=dtype, order="C" + ) # initial value of the residuals cdef floating[::1] R = np.empty(n_samples, dtype=dtype) @@ -169,16 +172,6 @@ def enet_coordinate_descent( "unexpected results and is discouraged.") with nogil: - # norm_cols_X = np.square(X).sum(axis=0) - if no_sample_weights: - if not center: - for ii in range(n_features): - norm_cols_X[ii] = _dot(n_samples, &X[0, ii], 1, &X[0, ii], 1) - else: - for ii in range(n_features): - for jj in range(n_samples): - norm_cols_X[ii] += (X[jj, ii] - X_mean[ii]) ** 2 - # R = y - np.dot(X, w) _copy(n_samples, &y[0], 1, &R[0], 1) _gemv(ColMajor, NoTrans, n_samples, n_features, -1.0, &X[0, 0], From da401b123e5cb28e34bc86dde626d3aa8bbd2844 Mon Sep 17 00:00:00 2001 From: Christian Lorentzen Date: Thu, 26 Jun 2025 23:25:59 +0200 Subject: [PATCH 3/4] DOC add whatsnew --- .../sklearn.linear_model/31665.enhancement.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 doc/whats_new/upcoming_changes/sklearn.linear_model/31665.enhancement.rst diff --git a/doc/whats_new/upcoming_changes/sklearn.linear_model/31665.enhancement.rst b/doc/whats_new/upcoming_changes/sklearn.linear_model/31665.enhancement.rst new file mode 100644 index 0000000000000..901873a911c56 --- /dev/null +++ b/doc/whats_new/upcoming_changes/sklearn.linear_model/31665.enhancement.rst @@ -0,0 +1,3 @@ +- class:`linear_model:ElasticNet` and class:`linear_model:Lasso` with + `precompute=False` uses less memory for dense `X` and is a bit faster. + By :user:`Christian Lorentzen ` From 47d0663403bd4bc0d0b8d486f4066b9305118208 Mon Sep 17 00:00:00 2001 From: Christian Lorentzen Date: Thu, 26 Jun 2025 23:40:10 +0200 Subject: [PATCH 4/4] FIX again --- sklearn/linear_model/_cd_fast.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sklearn/linear_model/_cd_fast.pyx b/sklearn/linear_model/_cd_fast.pyx index 76b1e3b606c3a..82a7e75cb884d 100644 --- a/sklearn/linear_model/_cd_fast.pyx +++ b/sklearn/linear_model/_cd_fast.pyx @@ -141,7 +141,7 @@ def enet_coordinate_descent( # compute norms of the columns of X # same as norm_cols_X = np.square(X).sum(axis=0) cdef floating[::1] norm_cols_X = np.einsum( - "ij,ij->j", X.T, X.T, dtype=dtype, order="C" + "ij,ij->j", X, X, dtype=dtype, order="C" ) # initial value of the residuals