scm2cpp-lasso 0.13.0__tar.gz → 0.13.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/PKG-INFO +107 -4
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/README.md +106 -3
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/pyproject.toml +1 -1
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/setup.py +43 -5
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/__init__.py +1 -1
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/scm2cpp.hpp +31 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso.egg-info/PKG-INFO +107 -4
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/setup.cfg +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/__init__.py +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/_loader.py +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/_loader_accel_cublas.py +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/_loader_classifiers.py +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/_loader_derived_blas.py +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/_loader_derived_cublas.py +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/_loader_derived_loops.py +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/_loader_gpu_cuda.py +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/_loader_gramops_blas.py +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/_loader_gramops_loops.py +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/_loader_nmf_blas.py +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/_loader_nmf_cublas.py +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/_loader_nmf_loops.py +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/accel_cublas/accel.cpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/accel_cublas/accel.hpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/accel_cublas/accel_capi.cpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/classifiers.cpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/classifiers.hpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/classifiers_capi.cpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/derived_blas/lasso_design.cpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/derived_blas/lasso_design.hpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/derived_blas/lasso_design_capi.cpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/derived_cublas/lasso_design.cpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/derived_cublas/lasso_design.hpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/derived_cublas/lasso_design_capi.cpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/derived_loops/lasso_design.cpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/derived_loops/lasso_design.hpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/derived_loops/lasso_design_capi.cpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/gpu_cuda/gpu.cpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/gpu_cuda/gpu.hpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/gpu_cuda/gpu_capi.cpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/gramops_blas/gramops.cpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/gramops_blas/gramops.hpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/gramops_blas/gramops_capi.cpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/gramops_loops/gramops.cpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/gramops_loops/gramops.hpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/gramops_loops/gramops_capi.cpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/lasso_cov.cpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/lasso_cov.hpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/lasso_cov_capi.cpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/nmf_blas/nmf_cd.cpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/nmf_blas/nmf_cd.hpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/nmf_blas/nmf_cd_capi.cpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/nmf_cublas/nmf_cd.cpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/nmf_cublas/nmf_cd.hpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/nmf_cublas/nmf_cd_capi.cpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/nmf_loops/nmf_cd.cpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/nmf_loops/nmf_cd.hpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/nmf_loops/nmf_cd_capi.cpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/scm2cpp-blas.hpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/scm2cpp-cublas.hpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/scm2cpp-cuda.hpp +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_libfind.py +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/batch_capi.cu +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso.egg-info/SOURCES.txt +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso.egg-info/dependency_links.txt +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso.egg-info/requires.txt +0 -0
- {scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scm2cpp-lasso
|
|
3
|
-
Version: 0.13.
|
|
3
|
+
Version: 0.13.2
|
|
4
4
|
Summary: scikit-learn estimators (lasso, elastic net, ridge, ARD, SVM, SVR, logistic regression, GLMs, Huber, Theil-Sen, F-score selection, RFE, NMF, Gaussian mixture, FastICA, PCA, graphical lasso, RANSAC, SGD, OMP, LARS) translated from Scheme to C++ by scm2cpp; GPU batch solver and CUDA kernels included
|
|
5
5
|
Author: Hirotaka Niitsuma
|
|
6
6
|
License: MIT
|
|
@@ -18,6 +18,8 @@ Requires-Dist: numpy>=1.17
|
|
|
18
18
|
|
|
19
19
|
# scm2cpp-lasso
|
|
20
20
|
|
|
21
|
+
[](https://colab.research.google.com/github/niitsuma/scm2cpp/blob/master/notebooks/compare-sklearn.ipynb)
|
|
22
|
+
|
|
21
23
|
*[Japanese (README.ja.md)](README.ja.md)*
|
|
22
24
|
|
|
23
25
|
Lasso by coordinate descent over a Gram matrix, with an optional GPU
|
|
@@ -28,9 +30,20 @@ path. The solver is Scheme, translated to C++ by
|
|
|
28
30
|
$ pip install scm2cpp-lasso
|
|
29
31
|
```
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
On Linux x86_64, macOS arm64 (macOS 14 or later) and Windows AMD64
|
|
34
|
+
this installs a wheel: compiled, nothing to build (with OpenBLAS
|
|
35
|
+
inside on Linux and macOS; Windows has the loop variants). Elsewhere pip builds the sdist,
|
|
36
|
+
which needs a C++17 compiler and nothing else. The wheels carry no
|
|
37
|
+
GPU code (a wheel cannot know the device); for the GPU builds, install
|
|
38
|
+
from the sdist on a machine with `nvcc` on the path:
|
|
39
|
+
|
|
40
|
+
```console
|
|
41
|
+
$ pip install --no-binary scm2cpp-lasso scm2cpp-lasso
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The batched GPU solver, the cuBLAS variants and the `--cuda`
|
|
45
|
+
estimators are then built as well; without `nvcc` the package installs
|
|
46
|
+
and works exactly the same, minus those.
|
|
34
47
|
|
|
35
48
|
Three of a hundred candidate columns carry the signal; the solver is
|
|
36
49
|
asked which, and answers with them and nothing else:
|
|
@@ -89,6 +102,63 @@ at zero, and `lambda_grid()` walks down from it -- the construction
|
|
|
89
102
|
scikit-learn uses. If your columns differ wildly in scale, standardize
|
|
90
103
|
them first; this solver does not do it for you.
|
|
91
104
|
|
|
105
|
+
## Estimators at a glance
|
|
106
|
+
|
|
107
|
+
Every estimator, the scikit-learn estimator it reproduces, the Scheme
|
|
108
|
+
kernel under `examples/kernel-only/` it is translated from, the builds
|
|
109
|
+
it runs on, and whether Intel's sklearnex accelerates the scikit-learn
|
|
110
|
+
side. Builds: *loops* is the plain translation, *BLAS* / *cuBLAS* the
|
|
111
|
+
products handed to (cu)BLAS, *--cuda* the translator's GPU kernels
|
|
112
|
+
(`gpu_cuda/`), *GPU batch* the hand-written CUDA batch solver; the
|
|
113
|
+
package picks the fastest one present. None of the classes derives
|
|
114
|
+
from scikit-learn's `BaseEstimator` yet, so `clone`, `GridSearchCV`
|
|
115
|
+
and `check_estimator` do not apply to them.
|
|
116
|
+
|
|
117
|
+
| class | scikit-learn | Scheme kernel | builds | sklearnex accelerates |
|
|
118
|
+
|---|---|---|---|---|
|
|
119
|
+
| `CovLasso` | Lasso, ElasticNet, lasso_path(precompute=True) | `lasso-cov.scm` | CPU; GPU batch (fit_path_batch, bootstrap) | no |
|
|
120
|
+
| `CovLassoCV` | LassoCV, ElasticNetCV | `lasso-cov.scm` | CPU; GPU batch | no |
|
|
121
|
+
| `DerivedLasso` | lasso_path, from the design | `lasso-design.scm (--derive)` | loops / BLAS / cuBLAS | no |
|
|
122
|
+
| `CovMultiTaskLasso` | MultiTaskLasso, MultiTaskElasticNet | `lasso-cov.scm` | CPU | no |
|
|
123
|
+
| `CovMultiTaskLassoCV` | MultiTaskLassoCV, MultiTaskElasticNetCV | `lasso-cov.scm` | CPU; GPU batch | no |
|
|
124
|
+
| `CovGroupLasso` | (none; skglm GroupLasso) | `lasso-cov.scm` | CPU | - |
|
|
125
|
+
| `CovLogistic` | LogisticRegression(penalty='l1'), by majorization | `lasso-cov.scm` | CPU | no |
|
|
126
|
+
| `CovRidge` | Ridge(fit_intercept=False), a whole alpha path | `lasso-cov.scm` | CPU | Ridge |
|
|
127
|
+
| `CovBayesianRidge` | BayesianRidge | `lasso-cov.scm` | CPU | no |
|
|
128
|
+
| `CovARDRegression` | ARDRegression (n >= p) | `ard.scm` | loops / BLAS | no |
|
|
129
|
+
| `GramLars` | Lars | `lars.scm` | CPU | no |
|
|
130
|
+
| `GramLassoLars` | LassoLars | `lars.scm` | CPU | no |
|
|
131
|
+
| `GramLassoLarsIC` | LassoLarsIC | `lars.scm` | CPU; device Gram | no |
|
|
132
|
+
| `GramLarsCV` | LarsCV | `lars.scm` | CPU | no |
|
|
133
|
+
| `GramLassoLarsCV` | LassoLarsCV | `lars.scm` | CPU; device Gram | no |
|
|
134
|
+
| `GramOMP` | OrthogonalMatchingPursuit | `omp.scm` | loops / BLAS | no |
|
|
135
|
+
| `GramOMPCV` | OrthogonalMatchingPursuitCV | `omp.scm` | loops / BLAS | no |
|
|
136
|
+
| `LBFGSLogisticRegression` | LogisticRegression(solver='lbfgs') | `logistic-lbfgs.scm, lbfgs.scm` | loops / BLAS / --cuda | LogisticRegression |
|
|
137
|
+
| `LBFGSLogisticRegressionCV` | LogisticRegressionCV | `logistic-lbfgs.scm, lbfgs.scm` | loops / BLAS / --cuda | no |
|
|
138
|
+
| `LBFGSHuberRegressor` | HuberRegressor | `huber.scm, lbfgs.scm` | loops / BLAS / --cuda | no |
|
|
139
|
+
| `LBFGSPoissonRegressor` | PoissonRegressor | `glm.scm, lbfgs.scm` | loops / BLAS / --cuda | no |
|
|
140
|
+
| `LBFGSGammaRegressor` | GammaRegressor | `glm.scm, lbfgs.scm` | loops / BLAS / --cuda | no |
|
|
141
|
+
| `LBFGSTweedieRegressor` | TweedieRegressor | `glm.scm, lbfgs.scm` | loops / BLAS / --cuda | no |
|
|
142
|
+
| `LinearSVM` | LinearSVC(loss='hinge') | `svm-liblinear.scm` | CPU | no |
|
|
143
|
+
| `L1LinearSVM` | LinearSVC(penalty='l1', dual=False) | `svm-l1.scm` | CPU | no |
|
|
144
|
+
| `KernelSVM` | SVC | `svm-smo.scm` | CPU | SVC |
|
|
145
|
+
| `LinearSVR` | LinearSVR | `svr-liblinear.scm` | CPU | no |
|
|
146
|
+
| `KernelSVR` | SVR | `svm-smo.scm` | CPU | SVR |
|
|
147
|
+
| `L1LogisticRegression` | LogisticRegression(penalty='l1', solver='liblinear') | `logistic-l1.scm` | CPU | no |
|
|
148
|
+
| `SGDLinearClassifier` | SGDClassifier(shuffle=False) | `sgd.scm` | CPU | no |
|
|
149
|
+
| `SGDLinearRegressor` | SGDRegressor(shuffle=False) | `sgd.scm` | CPU | no |
|
|
150
|
+
| `SGDPerceptron` | Perceptron(shuffle=False) | `sgd.scm` | CPU | no |
|
|
151
|
+
| `LinearRANSAC` | RANSACRegressor (given subsets) | `ransac.scm` | CPU | no |
|
|
152
|
+
| `WeiszfeldTheilSen` | TheilSenRegressor | `theilsen.scm` | CPU | no |
|
|
153
|
+
| `CDNMF` | NMF(solver='cd') | `nmf-cd.scm (--derive)` | loops / BLAS / cuBLAS | no |
|
|
154
|
+
| `CDGraphicalLasso` | GraphicalLasso | `graphical-lasso.scm` | loops / BLAS | no |
|
|
155
|
+
| `EMGaussianMixture` | GaussianMixture(covariance_type='full') | `gmm.scm` | loops / BLAS / --cuda | no |
|
|
156
|
+
| `ParallelFastICA` | FastICA | `fastica.scm` | loops / BLAS / --cuda | no |
|
|
157
|
+
| `CovPCA` | PCA(svd_solver='covariance_eigh') | `pca.scm` | loops / BLAS / cuBLAS | PCA |
|
|
158
|
+
| `f_regression, f_classif` | f_regression, f_classif | `fscore.scm` | loops / BLAS / cuBLAS | no |
|
|
159
|
+
| `SelectKBest, SelectPercentile` | SelectKBest, SelectPercentile | `fscore.scm` | loops / BLAS / cuBLAS | no |
|
|
160
|
+
| `GramRFE` | RFE(Ridge), RFE(LinearRegression) | `rfe.scm` | loops / BLAS / cuBLAS | inside: Ridge |
|
|
161
|
+
|
|
92
162
|
## Two kernels: `CovLasso` and `DerivedLasso`
|
|
93
163
|
|
|
94
164
|
Everything above, and the cross-validation, bootstrap and GPU methods
|
|
@@ -733,6 +803,39 @@ estimators (`GramLassoLarsIC`, `GramLarsCV`, `GramLassoLarsCV`,
|
|
|
733
803
|
`GramLars`, `GramLassoLars`, `CovPCA`) form a large X'X on the device
|
|
734
804
|
when one answers. `SCM2CPP_NO_DEVICE=1` keeps everything on the CPU.
|
|
735
805
|
|
|
806
|
+
## Against Intel's sklearnex
|
|
807
|
+
|
|
808
|
+
sklearnex (scikit-learn-intelex 2026.2, oneDAL underneath) replaces
|
|
809
|
+
some scikit-learn estimators with its own. `bench/sklearnex-compare.py`
|
|
810
|
+
times the ones with a counterpart here, sklearnex's estimators
|
|
811
|
+
imported directly (not patched in) so that scikit-learn itself is
|
|
812
|
+
timed in the same process. The ratios are scikit-learn's or
|
|
813
|
+
sklearnex's time over the package's best (its CPU build, or the device
|
|
814
|
+
build where one answers); the load average was 8 to 11, so the times
|
|
815
|
+
move by some tens of percent.
|
|
816
|
+
|
|
817
|
+
| estimator, n x p | iterations (sk / sklearnex / ours) | vs scikit-learn, one core | vs sklearnex, one core | vs sklearnex, 20 cores |
|
|
818
|
+
|---|---|---|---|---|
|
|
819
|
+
| LogisticRegression, 200,000 x 100 | 6 / 12 / 6 | 10.6x (device) | 8.4x (device) | 4.4x (device); on the CPU 0.5x |
|
|
820
|
+
| Ridge (alpha 1), 200,000 x 200 | - | 2.1x | 1.2x | 0.3x |
|
|
821
|
+
| PCA (10 components), 100,000 x 50 | - | 1.5x | 2.4x | 1.7x |
|
|
822
|
+
| PCA (10 components), 50,000 x 500 | - | 1.1x | 1.4x | 0.5x |
|
|
823
|
+
| SVC (rbf), 5,000 x 20 | 4,858 / 7 / 4,882 | 1.2x | 1.4x | 0.5x |
|
|
824
|
+
| SVR (rbf), 5,000 x 20 | 12,821 / 10 / 12,824 | 1.3x | 1.4x | 0.4x |
|
|
825
|
+
| RFE(Ridge), 20 of 200 kept, 20,000 x 200 | - | 216x | 128x | 78x |
|
|
826
|
+
|
|
827
|
+
On one core the package is ahead everywhere. sklearnex uses every
|
|
828
|
+
core by default, and the package's kernels are single-threaded, so
|
|
829
|
+
with 20 cores sklearnex is faster on Ridge, the larger PCA and the
|
|
830
|
+
SVMs; what stays ahead is the device build (logistic regression) and
|
|
831
|
+
an algorithm that does less work (RFE: the Gram once, the Cholesky
|
|
832
|
+
factor downdated per drop, where scikit-learn refits). sklearnex is
|
|
833
|
+
not scikit-learn's arithmetic: its logistic regression takes 12
|
|
834
|
+
iterations to scikit-learn's 6 and its coefficients differ from
|
|
835
|
+
scikit-learn's by 6e-3, where the package's agree to 4e-15; its SVMs
|
|
836
|
+
report a different iteration count. This sklearnex accelerates no
|
|
837
|
+
lasso, so the lasso tables above stand against scikit-learn alone.
|
|
838
|
+
|
|
736
839
|
## The GPU build
|
|
737
840
|
|
|
738
841
|
Where the CUDA toolkit is at install time, `ParallelFastICA`,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# scm2cpp-lasso
|
|
2
2
|
|
|
3
|
+
[](https://colab.research.google.com/github/niitsuma/scm2cpp/blob/master/notebooks/compare-sklearn.ipynb)
|
|
4
|
+
|
|
3
5
|
*[Japanese (README.ja.md)](README.ja.md)*
|
|
4
6
|
|
|
5
7
|
Lasso by coordinate descent over a Gram matrix, with an optional GPU
|
|
@@ -10,9 +12,20 @@ path. The solver is Scheme, translated to C++ by
|
|
|
10
12
|
$ pip install scm2cpp-lasso
|
|
11
13
|
```
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
On Linux x86_64, macOS arm64 (macOS 14 or later) and Windows AMD64
|
|
16
|
+
this installs a wheel: compiled, nothing to build (with OpenBLAS
|
|
17
|
+
inside on Linux and macOS; Windows has the loop variants). Elsewhere pip builds the sdist,
|
|
18
|
+
which needs a C++17 compiler and nothing else. The wheels carry no
|
|
19
|
+
GPU code (a wheel cannot know the device); for the GPU builds, install
|
|
20
|
+
from the sdist on a machine with `nvcc` on the path:
|
|
21
|
+
|
|
22
|
+
```console
|
|
23
|
+
$ pip install --no-binary scm2cpp-lasso scm2cpp-lasso
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The batched GPU solver, the cuBLAS variants and the `--cuda`
|
|
27
|
+
estimators are then built as well; without `nvcc` the package installs
|
|
28
|
+
and works exactly the same, minus those.
|
|
16
29
|
|
|
17
30
|
Three of a hundred candidate columns carry the signal; the solver is
|
|
18
31
|
asked which, and answers with them and nothing else:
|
|
@@ -71,6 +84,63 @@ at zero, and `lambda_grid()` walks down from it -- the construction
|
|
|
71
84
|
scikit-learn uses. If your columns differ wildly in scale, standardize
|
|
72
85
|
them first; this solver does not do it for you.
|
|
73
86
|
|
|
87
|
+
## Estimators at a glance
|
|
88
|
+
|
|
89
|
+
Every estimator, the scikit-learn estimator it reproduces, the Scheme
|
|
90
|
+
kernel under `examples/kernel-only/` it is translated from, the builds
|
|
91
|
+
it runs on, and whether Intel's sklearnex accelerates the scikit-learn
|
|
92
|
+
side. Builds: *loops* is the plain translation, *BLAS* / *cuBLAS* the
|
|
93
|
+
products handed to (cu)BLAS, *--cuda* the translator's GPU kernels
|
|
94
|
+
(`gpu_cuda/`), *GPU batch* the hand-written CUDA batch solver; the
|
|
95
|
+
package picks the fastest one present. None of the classes derives
|
|
96
|
+
from scikit-learn's `BaseEstimator` yet, so `clone`, `GridSearchCV`
|
|
97
|
+
and `check_estimator` do not apply to them.
|
|
98
|
+
|
|
99
|
+
| class | scikit-learn | Scheme kernel | builds | sklearnex accelerates |
|
|
100
|
+
|---|---|---|---|---|
|
|
101
|
+
| `CovLasso` | Lasso, ElasticNet, lasso_path(precompute=True) | `lasso-cov.scm` | CPU; GPU batch (fit_path_batch, bootstrap) | no |
|
|
102
|
+
| `CovLassoCV` | LassoCV, ElasticNetCV | `lasso-cov.scm` | CPU; GPU batch | no |
|
|
103
|
+
| `DerivedLasso` | lasso_path, from the design | `lasso-design.scm (--derive)` | loops / BLAS / cuBLAS | no |
|
|
104
|
+
| `CovMultiTaskLasso` | MultiTaskLasso, MultiTaskElasticNet | `lasso-cov.scm` | CPU | no |
|
|
105
|
+
| `CovMultiTaskLassoCV` | MultiTaskLassoCV, MultiTaskElasticNetCV | `lasso-cov.scm` | CPU; GPU batch | no |
|
|
106
|
+
| `CovGroupLasso` | (none; skglm GroupLasso) | `lasso-cov.scm` | CPU | - |
|
|
107
|
+
| `CovLogistic` | LogisticRegression(penalty='l1'), by majorization | `lasso-cov.scm` | CPU | no |
|
|
108
|
+
| `CovRidge` | Ridge(fit_intercept=False), a whole alpha path | `lasso-cov.scm` | CPU | Ridge |
|
|
109
|
+
| `CovBayesianRidge` | BayesianRidge | `lasso-cov.scm` | CPU | no |
|
|
110
|
+
| `CovARDRegression` | ARDRegression (n >= p) | `ard.scm` | loops / BLAS | no |
|
|
111
|
+
| `GramLars` | Lars | `lars.scm` | CPU | no |
|
|
112
|
+
| `GramLassoLars` | LassoLars | `lars.scm` | CPU | no |
|
|
113
|
+
| `GramLassoLarsIC` | LassoLarsIC | `lars.scm` | CPU; device Gram | no |
|
|
114
|
+
| `GramLarsCV` | LarsCV | `lars.scm` | CPU | no |
|
|
115
|
+
| `GramLassoLarsCV` | LassoLarsCV | `lars.scm` | CPU; device Gram | no |
|
|
116
|
+
| `GramOMP` | OrthogonalMatchingPursuit | `omp.scm` | loops / BLAS | no |
|
|
117
|
+
| `GramOMPCV` | OrthogonalMatchingPursuitCV | `omp.scm` | loops / BLAS | no |
|
|
118
|
+
| `LBFGSLogisticRegression` | LogisticRegression(solver='lbfgs') | `logistic-lbfgs.scm, lbfgs.scm` | loops / BLAS / --cuda | LogisticRegression |
|
|
119
|
+
| `LBFGSLogisticRegressionCV` | LogisticRegressionCV | `logistic-lbfgs.scm, lbfgs.scm` | loops / BLAS / --cuda | no |
|
|
120
|
+
| `LBFGSHuberRegressor` | HuberRegressor | `huber.scm, lbfgs.scm` | loops / BLAS / --cuda | no |
|
|
121
|
+
| `LBFGSPoissonRegressor` | PoissonRegressor | `glm.scm, lbfgs.scm` | loops / BLAS / --cuda | no |
|
|
122
|
+
| `LBFGSGammaRegressor` | GammaRegressor | `glm.scm, lbfgs.scm` | loops / BLAS / --cuda | no |
|
|
123
|
+
| `LBFGSTweedieRegressor` | TweedieRegressor | `glm.scm, lbfgs.scm` | loops / BLAS / --cuda | no |
|
|
124
|
+
| `LinearSVM` | LinearSVC(loss='hinge') | `svm-liblinear.scm` | CPU | no |
|
|
125
|
+
| `L1LinearSVM` | LinearSVC(penalty='l1', dual=False) | `svm-l1.scm` | CPU | no |
|
|
126
|
+
| `KernelSVM` | SVC | `svm-smo.scm` | CPU | SVC |
|
|
127
|
+
| `LinearSVR` | LinearSVR | `svr-liblinear.scm` | CPU | no |
|
|
128
|
+
| `KernelSVR` | SVR | `svm-smo.scm` | CPU | SVR |
|
|
129
|
+
| `L1LogisticRegression` | LogisticRegression(penalty='l1', solver='liblinear') | `logistic-l1.scm` | CPU | no |
|
|
130
|
+
| `SGDLinearClassifier` | SGDClassifier(shuffle=False) | `sgd.scm` | CPU | no |
|
|
131
|
+
| `SGDLinearRegressor` | SGDRegressor(shuffle=False) | `sgd.scm` | CPU | no |
|
|
132
|
+
| `SGDPerceptron` | Perceptron(shuffle=False) | `sgd.scm` | CPU | no |
|
|
133
|
+
| `LinearRANSAC` | RANSACRegressor (given subsets) | `ransac.scm` | CPU | no |
|
|
134
|
+
| `WeiszfeldTheilSen` | TheilSenRegressor | `theilsen.scm` | CPU | no |
|
|
135
|
+
| `CDNMF` | NMF(solver='cd') | `nmf-cd.scm (--derive)` | loops / BLAS / cuBLAS | no |
|
|
136
|
+
| `CDGraphicalLasso` | GraphicalLasso | `graphical-lasso.scm` | loops / BLAS | no |
|
|
137
|
+
| `EMGaussianMixture` | GaussianMixture(covariance_type='full') | `gmm.scm` | loops / BLAS / --cuda | no |
|
|
138
|
+
| `ParallelFastICA` | FastICA | `fastica.scm` | loops / BLAS / --cuda | no |
|
|
139
|
+
| `CovPCA` | PCA(svd_solver='covariance_eigh') | `pca.scm` | loops / BLAS / cuBLAS | PCA |
|
|
140
|
+
| `f_regression, f_classif` | f_regression, f_classif | `fscore.scm` | loops / BLAS / cuBLAS | no |
|
|
141
|
+
| `SelectKBest, SelectPercentile` | SelectKBest, SelectPercentile | `fscore.scm` | loops / BLAS / cuBLAS | no |
|
|
142
|
+
| `GramRFE` | RFE(Ridge), RFE(LinearRegression) | `rfe.scm` | loops / BLAS / cuBLAS | inside: Ridge |
|
|
143
|
+
|
|
74
144
|
## Two kernels: `CovLasso` and `DerivedLasso`
|
|
75
145
|
|
|
76
146
|
Everything above, and the cross-validation, bootstrap and GPU methods
|
|
@@ -715,6 +785,39 @@ estimators (`GramLassoLarsIC`, `GramLarsCV`, `GramLassoLarsCV`,
|
|
|
715
785
|
`GramLars`, `GramLassoLars`, `CovPCA`) form a large X'X on the device
|
|
716
786
|
when one answers. `SCM2CPP_NO_DEVICE=1` keeps everything on the CPU.
|
|
717
787
|
|
|
788
|
+
## Against Intel's sklearnex
|
|
789
|
+
|
|
790
|
+
sklearnex (scikit-learn-intelex 2026.2, oneDAL underneath) replaces
|
|
791
|
+
some scikit-learn estimators with its own. `bench/sklearnex-compare.py`
|
|
792
|
+
times the ones with a counterpart here, sklearnex's estimators
|
|
793
|
+
imported directly (not patched in) so that scikit-learn itself is
|
|
794
|
+
timed in the same process. The ratios are scikit-learn's or
|
|
795
|
+
sklearnex's time over the package's best (its CPU build, or the device
|
|
796
|
+
build where one answers); the load average was 8 to 11, so the times
|
|
797
|
+
move by some tens of percent.
|
|
798
|
+
|
|
799
|
+
| estimator, n x p | iterations (sk / sklearnex / ours) | vs scikit-learn, one core | vs sklearnex, one core | vs sklearnex, 20 cores |
|
|
800
|
+
|---|---|---|---|---|
|
|
801
|
+
| LogisticRegression, 200,000 x 100 | 6 / 12 / 6 | 10.6x (device) | 8.4x (device) | 4.4x (device); on the CPU 0.5x |
|
|
802
|
+
| Ridge (alpha 1), 200,000 x 200 | - | 2.1x | 1.2x | 0.3x |
|
|
803
|
+
| PCA (10 components), 100,000 x 50 | - | 1.5x | 2.4x | 1.7x |
|
|
804
|
+
| PCA (10 components), 50,000 x 500 | - | 1.1x | 1.4x | 0.5x |
|
|
805
|
+
| SVC (rbf), 5,000 x 20 | 4,858 / 7 / 4,882 | 1.2x | 1.4x | 0.5x |
|
|
806
|
+
| SVR (rbf), 5,000 x 20 | 12,821 / 10 / 12,824 | 1.3x | 1.4x | 0.4x |
|
|
807
|
+
| RFE(Ridge), 20 of 200 kept, 20,000 x 200 | - | 216x | 128x | 78x |
|
|
808
|
+
|
|
809
|
+
On one core the package is ahead everywhere. sklearnex uses every
|
|
810
|
+
core by default, and the package's kernels are single-threaded, so
|
|
811
|
+
with 20 cores sklearnex is faster on Ridge, the larger PCA and the
|
|
812
|
+
SVMs; what stays ahead is the device build (logistic regression) and
|
|
813
|
+
an algorithm that does less work (RFE: the Gram once, the Cholesky
|
|
814
|
+
factor downdated per drop, where scikit-learn refits). sklearnex is
|
|
815
|
+
not scikit-learn's arithmetic: its logistic regression takes 12
|
|
816
|
+
iterations to scikit-learn's 6 and its coefficients differ from
|
|
817
|
+
scikit-learn's by 6e-3, where the package's agree to 4e-15; its SVMs
|
|
818
|
+
report a different iteration count. This sklearnex accelerates no
|
|
819
|
+
lasso, so the lasso tables above stand against scikit-learn alone.
|
|
820
|
+
|
|
718
821
|
## The GPU build
|
|
719
822
|
|
|
720
823
|
Where the CUDA toolkit is at install time, `ParallelFastICA`,
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "scm2cpp-lasso"
|
|
7
|
-
version = "0.13.
|
|
7
|
+
version = "0.13.2"
|
|
8
8
|
description = "scikit-learn estimators (lasso, elastic net, ridge, ARD, SVM, SVR, logistic regression, GLMs, Huber, Theil-Sen, F-score selection, RFE, NMF, Gaussian mixture, FastICA, PCA, graphical lasso, RANSAC, SGD, OMP, LARS) translated from Scheme to C++ by scm2cpp; GPU batch solver and CUDA kernels included"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -12,6 +12,7 @@ device, or a failed nvcc all leave a working CPU package behind, and
|
|
|
12
12
|
the batch path notices at import and falls back.
|
|
13
13
|
"""
|
|
14
14
|
import os
|
|
15
|
+
import re
|
|
15
16
|
import shutil
|
|
16
17
|
import subprocess
|
|
17
18
|
import sys
|
|
@@ -28,7 +29,9 @@ GEN_REL = PKG_REL + "/_generated"
|
|
|
28
29
|
PKG = HERE / "src" / "scm2cpp_lasso"
|
|
29
30
|
GEN = PKG / "_generated"
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
# MSVC: /EHsc for C++ exceptions, /bigobj for the large generated files
|
|
33
|
+
MSVC = ["/O2", "/std:c++17", "/EHsc", "/bigobj"]
|
|
34
|
+
CXXFLAGS = ["-O2", "-std=c++17"] if os.name != "nt" else MSVC
|
|
32
35
|
|
|
33
36
|
kernel = Extension(
|
|
34
37
|
"scm2cpp_lasso._lasso_cov",
|
|
@@ -92,12 +95,24 @@ else:
|
|
|
92
95
|
"scm2cpp_lasso._lasso_derived_loops",
|
|
93
96
|
sources=[GEN_REL + "/derived_loops/lasso_design_capi.cpp"],
|
|
94
97
|
include_dirs=[GEN_REL + "/derived_loops", GEN_REL],
|
|
95
|
-
extra_compile_args=(["-O3", "-std=c++17"] if os.name != "nt"
|
|
96
|
-
else ["/O2", "/std:c++17"]),
|
|
98
|
+
extra_compile_args=(["-O3", "-std=c++17"] if os.name != "nt" else MSVC),
|
|
97
99
|
)
|
|
98
100
|
|
|
99
101
|
|
|
100
102
|
class BuildWithCuda(build_ext):
|
|
103
|
+
def get_export_symbols(self, ext):
|
|
104
|
+
# The extensions are plain shared libraries the package loads
|
|
105
|
+
# through ctypes, not Python modules: no PyInit_ entry point,
|
|
106
|
+
# and on Windows a DLL shows ctypes only what it exports, so the
|
|
107
|
+
# exports are the extern "C" wrappers of the -M C API. (Unix
|
|
108
|
+
# linkers export everything and ignore this list.)
|
|
109
|
+
names = []
|
|
110
|
+
for src in ext.sources:
|
|
111
|
+
if src.endswith("_capi.cpp"):
|
|
112
|
+
text = (HERE / src).read_text()
|
|
113
|
+
names += re.findall(r'extern "C"[^(\n]*?\b(scm2cpp_\w+)\(', text)
|
|
114
|
+
return names
|
|
115
|
+
|
|
101
116
|
def build_extensions(self):
|
|
102
117
|
# the kernel is C++ compiled through the usual machinery, so
|
|
103
118
|
# wheels and cross-compilation behave as they always do
|
|
@@ -165,7 +180,7 @@ def cuda_home():
|
|
|
165
180
|
# flags carry: with signed overflow defined the compiler cannot treat
|
|
166
181
|
# the flat index i*p + j as an induction variable, and the linear SVM's
|
|
167
182
|
# sweep runs 25% slower.
|
|
168
|
-
_cls_flags = ["-O3", "-std=c++17", "-fstrict-overflow"] if os.name != "nt" else
|
|
183
|
+
_cls_flags = ["-O3", "-std=c++17", "-fstrict-overflow"] if os.name != "nt" else MSVC
|
|
169
184
|
classifiers = Extension(
|
|
170
185
|
"scm2cpp_lasso._classifiers",
|
|
171
186
|
sources=[GEN_REL + "/classifiers_capi.cpp"],
|
|
@@ -242,4 +257,27 @@ if _cuda is not None:
|
|
|
242
257
|
extra_compile_args=CXXFLAGS + ["-O3", "-fstrict-overflow"],
|
|
243
258
|
))
|
|
244
259
|
|
|
245
|
-
|
|
260
|
+
# A wheel is tagged py3-none-<platform>: the extensions are shared
|
|
261
|
+
# libraries the package loads through ctypes, using no Python API, so
|
|
262
|
+
# one build serves every Python on that platform.
|
|
263
|
+
try:
|
|
264
|
+
from setuptools.command.bdist_wheel import bdist_wheel
|
|
265
|
+
except ImportError: # setuptools before 70.1
|
|
266
|
+
try:
|
|
267
|
+
from wheel.bdist_wheel import bdist_wheel
|
|
268
|
+
except ImportError:
|
|
269
|
+
bdist_wheel = None
|
|
270
|
+
|
|
271
|
+
cmdclass = {"build_ext": BuildWithCuda}
|
|
272
|
+
if bdist_wheel is not None:
|
|
273
|
+
class PlatformWheel(bdist_wheel):
|
|
274
|
+
def finalize_options(self):
|
|
275
|
+
super().finalize_options()
|
|
276
|
+
self.root_is_pure = False
|
|
277
|
+
|
|
278
|
+
def get_tag(self):
|
|
279
|
+
return ("py3", "none", super().get_tag()[2])
|
|
280
|
+
|
|
281
|
+
cmdclass["bdist_wheel"] = PlatformWheel
|
|
282
|
+
|
|
283
|
+
setup(ext_modules=extensions, cmdclass=cmdclass)
|
|
@@ -51,7 +51,7 @@ __all__ = ["CovLasso", "CovRidge", "CovBayesianRidge", "CovLogistic", "CovGroupL
|
|
|
51
51
|
"SelectPercentile", "GramRFE",
|
|
52
52
|
"CovMultiTaskLasso", "CovMultiTaskLassoCV", "DerivedLasso",
|
|
53
53
|
"cuda_available", "derived_backend", "kernel"]
|
|
54
|
-
__version__ = "0.13.
|
|
54
|
+
__version__ = "0.13.2"
|
|
55
55
|
|
|
56
56
|
_BATCH = load_batch_lib()
|
|
57
57
|
_DP = ctypes.POINTER(ctypes.c_double)
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
#include <iostream>
|
|
5
5
|
#include <vector>
|
|
6
|
+
#include <string>
|
|
6
7
|
#include <list>
|
|
7
8
|
#include <map>
|
|
8
9
|
#include <algorithm>
|
|
@@ -1459,6 +1460,36 @@ namespace scm2cpp {
|
|
|
1459
1460
|
std::size_t size() const { return n; }
|
|
1460
1461
|
};
|
|
1461
1462
|
|
|
1463
|
+
// A memo for a pure call: (memo-call M E KEY ...) is
|
|
1464
|
+
// memo_get(M, memo_key().add(KEY)...str(), [&]() { return E; })
|
|
1465
|
+
// M a hash table from the key's bytes to E's value (the translator's
|
|
1466
|
+
// (make-hash), an std::unordered_map<std::string, V>), so E runs once
|
|
1467
|
+
// per distinct key. A key is scalars by value and, through
|
|
1468
|
+
// add_range, the first N elements of an array -- the index set a
|
|
1469
|
+
// gather read, say. The bytes are the values' object representation,
|
|
1470
|
+
// so 0.0 and -0.0 are different keys (a miss, never a wrong hit).
|
|
1471
|
+
struct memo_key {
|
|
1472
|
+
std::string b;
|
|
1473
|
+
template<typename T> memo_key& add(const T& v) {
|
|
1474
|
+
b.append(reinterpret_cast<const char*>(&v), sizeof(T));
|
|
1475
|
+
return *this;
|
|
1476
|
+
}
|
|
1477
|
+
template<typename V> memo_key& add_range(const V& v, long n) {
|
|
1478
|
+
add(n);
|
|
1479
|
+
for (long i = 0; i < n; ++i) add(v[i]);
|
|
1480
|
+
return *this;
|
|
1481
|
+
}
|
|
1482
|
+
const std::string& str() const { return b; }
|
|
1483
|
+
};
|
|
1484
|
+
template<typename M, typename F>
|
|
1485
|
+
inline typename M::mapped_type memo_get(M& m, const std::string& k, F f) {
|
|
1486
|
+
auto it = m.find(k);
|
|
1487
|
+
if (it != m.end()) return it->second;
|
|
1488
|
+
typename M::mapped_type v = f();
|
|
1489
|
+
m.emplace(k, v);
|
|
1490
|
+
return v;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1462
1493
|
template<typename T, int N> struct integral_image {
|
|
1463
1494
|
std::vector<T> table; // padded with a zero border: dim[k]+1 per axis
|
|
1464
1495
|
int dim[N]; // extents of the source array
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scm2cpp-lasso
|
|
3
|
-
Version: 0.13.
|
|
3
|
+
Version: 0.13.2
|
|
4
4
|
Summary: scikit-learn estimators (lasso, elastic net, ridge, ARD, SVM, SVR, logistic regression, GLMs, Huber, Theil-Sen, F-score selection, RFE, NMF, Gaussian mixture, FastICA, PCA, graphical lasso, RANSAC, SGD, OMP, LARS) translated from Scheme to C++ by scm2cpp; GPU batch solver and CUDA kernels included
|
|
5
5
|
Author: Hirotaka Niitsuma
|
|
6
6
|
License: MIT
|
|
@@ -18,6 +18,8 @@ Requires-Dist: numpy>=1.17
|
|
|
18
18
|
|
|
19
19
|
# scm2cpp-lasso
|
|
20
20
|
|
|
21
|
+
[](https://colab.research.google.com/github/niitsuma/scm2cpp/blob/master/notebooks/compare-sklearn.ipynb)
|
|
22
|
+
|
|
21
23
|
*[Japanese (README.ja.md)](README.ja.md)*
|
|
22
24
|
|
|
23
25
|
Lasso by coordinate descent over a Gram matrix, with an optional GPU
|
|
@@ -28,9 +30,20 @@ path. The solver is Scheme, translated to C++ by
|
|
|
28
30
|
$ pip install scm2cpp-lasso
|
|
29
31
|
```
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
On Linux x86_64, macOS arm64 (macOS 14 or later) and Windows AMD64
|
|
34
|
+
this installs a wheel: compiled, nothing to build (with OpenBLAS
|
|
35
|
+
inside on Linux and macOS; Windows has the loop variants). Elsewhere pip builds the sdist,
|
|
36
|
+
which needs a C++17 compiler and nothing else. The wheels carry no
|
|
37
|
+
GPU code (a wheel cannot know the device); for the GPU builds, install
|
|
38
|
+
from the sdist on a machine with `nvcc` on the path:
|
|
39
|
+
|
|
40
|
+
```console
|
|
41
|
+
$ pip install --no-binary scm2cpp-lasso scm2cpp-lasso
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The batched GPU solver, the cuBLAS variants and the `--cuda`
|
|
45
|
+
estimators are then built as well; without `nvcc` the package installs
|
|
46
|
+
and works exactly the same, minus those.
|
|
34
47
|
|
|
35
48
|
Three of a hundred candidate columns carry the signal; the solver is
|
|
36
49
|
asked which, and answers with them and nothing else:
|
|
@@ -89,6 +102,63 @@ at zero, and `lambda_grid()` walks down from it -- the construction
|
|
|
89
102
|
scikit-learn uses. If your columns differ wildly in scale, standardize
|
|
90
103
|
them first; this solver does not do it for you.
|
|
91
104
|
|
|
105
|
+
## Estimators at a glance
|
|
106
|
+
|
|
107
|
+
Every estimator, the scikit-learn estimator it reproduces, the Scheme
|
|
108
|
+
kernel under `examples/kernel-only/` it is translated from, the builds
|
|
109
|
+
it runs on, and whether Intel's sklearnex accelerates the scikit-learn
|
|
110
|
+
side. Builds: *loops* is the plain translation, *BLAS* / *cuBLAS* the
|
|
111
|
+
products handed to (cu)BLAS, *--cuda* the translator's GPU kernels
|
|
112
|
+
(`gpu_cuda/`), *GPU batch* the hand-written CUDA batch solver; the
|
|
113
|
+
package picks the fastest one present. None of the classes derives
|
|
114
|
+
from scikit-learn's `BaseEstimator` yet, so `clone`, `GridSearchCV`
|
|
115
|
+
and `check_estimator` do not apply to them.
|
|
116
|
+
|
|
117
|
+
| class | scikit-learn | Scheme kernel | builds | sklearnex accelerates |
|
|
118
|
+
|---|---|---|---|---|
|
|
119
|
+
| `CovLasso` | Lasso, ElasticNet, lasso_path(precompute=True) | `lasso-cov.scm` | CPU; GPU batch (fit_path_batch, bootstrap) | no |
|
|
120
|
+
| `CovLassoCV` | LassoCV, ElasticNetCV | `lasso-cov.scm` | CPU; GPU batch | no |
|
|
121
|
+
| `DerivedLasso` | lasso_path, from the design | `lasso-design.scm (--derive)` | loops / BLAS / cuBLAS | no |
|
|
122
|
+
| `CovMultiTaskLasso` | MultiTaskLasso, MultiTaskElasticNet | `lasso-cov.scm` | CPU | no |
|
|
123
|
+
| `CovMultiTaskLassoCV` | MultiTaskLassoCV, MultiTaskElasticNetCV | `lasso-cov.scm` | CPU; GPU batch | no |
|
|
124
|
+
| `CovGroupLasso` | (none; skglm GroupLasso) | `lasso-cov.scm` | CPU | - |
|
|
125
|
+
| `CovLogistic` | LogisticRegression(penalty='l1'), by majorization | `lasso-cov.scm` | CPU | no |
|
|
126
|
+
| `CovRidge` | Ridge(fit_intercept=False), a whole alpha path | `lasso-cov.scm` | CPU | Ridge |
|
|
127
|
+
| `CovBayesianRidge` | BayesianRidge | `lasso-cov.scm` | CPU | no |
|
|
128
|
+
| `CovARDRegression` | ARDRegression (n >= p) | `ard.scm` | loops / BLAS | no |
|
|
129
|
+
| `GramLars` | Lars | `lars.scm` | CPU | no |
|
|
130
|
+
| `GramLassoLars` | LassoLars | `lars.scm` | CPU | no |
|
|
131
|
+
| `GramLassoLarsIC` | LassoLarsIC | `lars.scm` | CPU; device Gram | no |
|
|
132
|
+
| `GramLarsCV` | LarsCV | `lars.scm` | CPU | no |
|
|
133
|
+
| `GramLassoLarsCV` | LassoLarsCV | `lars.scm` | CPU; device Gram | no |
|
|
134
|
+
| `GramOMP` | OrthogonalMatchingPursuit | `omp.scm` | loops / BLAS | no |
|
|
135
|
+
| `GramOMPCV` | OrthogonalMatchingPursuitCV | `omp.scm` | loops / BLAS | no |
|
|
136
|
+
| `LBFGSLogisticRegression` | LogisticRegression(solver='lbfgs') | `logistic-lbfgs.scm, lbfgs.scm` | loops / BLAS / --cuda | LogisticRegression |
|
|
137
|
+
| `LBFGSLogisticRegressionCV` | LogisticRegressionCV | `logistic-lbfgs.scm, lbfgs.scm` | loops / BLAS / --cuda | no |
|
|
138
|
+
| `LBFGSHuberRegressor` | HuberRegressor | `huber.scm, lbfgs.scm` | loops / BLAS / --cuda | no |
|
|
139
|
+
| `LBFGSPoissonRegressor` | PoissonRegressor | `glm.scm, lbfgs.scm` | loops / BLAS / --cuda | no |
|
|
140
|
+
| `LBFGSGammaRegressor` | GammaRegressor | `glm.scm, lbfgs.scm` | loops / BLAS / --cuda | no |
|
|
141
|
+
| `LBFGSTweedieRegressor` | TweedieRegressor | `glm.scm, lbfgs.scm` | loops / BLAS / --cuda | no |
|
|
142
|
+
| `LinearSVM` | LinearSVC(loss='hinge') | `svm-liblinear.scm` | CPU | no |
|
|
143
|
+
| `L1LinearSVM` | LinearSVC(penalty='l1', dual=False) | `svm-l1.scm` | CPU | no |
|
|
144
|
+
| `KernelSVM` | SVC | `svm-smo.scm` | CPU | SVC |
|
|
145
|
+
| `LinearSVR` | LinearSVR | `svr-liblinear.scm` | CPU | no |
|
|
146
|
+
| `KernelSVR` | SVR | `svm-smo.scm` | CPU | SVR |
|
|
147
|
+
| `L1LogisticRegression` | LogisticRegression(penalty='l1', solver='liblinear') | `logistic-l1.scm` | CPU | no |
|
|
148
|
+
| `SGDLinearClassifier` | SGDClassifier(shuffle=False) | `sgd.scm` | CPU | no |
|
|
149
|
+
| `SGDLinearRegressor` | SGDRegressor(shuffle=False) | `sgd.scm` | CPU | no |
|
|
150
|
+
| `SGDPerceptron` | Perceptron(shuffle=False) | `sgd.scm` | CPU | no |
|
|
151
|
+
| `LinearRANSAC` | RANSACRegressor (given subsets) | `ransac.scm` | CPU | no |
|
|
152
|
+
| `WeiszfeldTheilSen` | TheilSenRegressor | `theilsen.scm` | CPU | no |
|
|
153
|
+
| `CDNMF` | NMF(solver='cd') | `nmf-cd.scm (--derive)` | loops / BLAS / cuBLAS | no |
|
|
154
|
+
| `CDGraphicalLasso` | GraphicalLasso | `graphical-lasso.scm` | loops / BLAS | no |
|
|
155
|
+
| `EMGaussianMixture` | GaussianMixture(covariance_type='full') | `gmm.scm` | loops / BLAS / --cuda | no |
|
|
156
|
+
| `ParallelFastICA` | FastICA | `fastica.scm` | loops / BLAS / --cuda | no |
|
|
157
|
+
| `CovPCA` | PCA(svd_solver='covariance_eigh') | `pca.scm` | loops / BLAS / cuBLAS | PCA |
|
|
158
|
+
| `f_regression, f_classif` | f_regression, f_classif | `fscore.scm` | loops / BLAS / cuBLAS | no |
|
|
159
|
+
| `SelectKBest, SelectPercentile` | SelectKBest, SelectPercentile | `fscore.scm` | loops / BLAS / cuBLAS | no |
|
|
160
|
+
| `GramRFE` | RFE(Ridge), RFE(LinearRegression) | `rfe.scm` | loops / BLAS / cuBLAS | inside: Ridge |
|
|
161
|
+
|
|
92
162
|
## Two kernels: `CovLasso` and `DerivedLasso`
|
|
93
163
|
|
|
94
164
|
Everything above, and the cross-validation, bootstrap and GPU methods
|
|
@@ -733,6 +803,39 @@ estimators (`GramLassoLarsIC`, `GramLarsCV`, `GramLassoLarsCV`,
|
|
|
733
803
|
`GramLars`, `GramLassoLars`, `CovPCA`) form a large X'X on the device
|
|
734
804
|
when one answers. `SCM2CPP_NO_DEVICE=1` keeps everything on the CPU.
|
|
735
805
|
|
|
806
|
+
## Against Intel's sklearnex
|
|
807
|
+
|
|
808
|
+
sklearnex (scikit-learn-intelex 2026.2, oneDAL underneath) replaces
|
|
809
|
+
some scikit-learn estimators with its own. `bench/sklearnex-compare.py`
|
|
810
|
+
times the ones with a counterpart here, sklearnex's estimators
|
|
811
|
+
imported directly (not patched in) so that scikit-learn itself is
|
|
812
|
+
timed in the same process. The ratios are scikit-learn's or
|
|
813
|
+
sklearnex's time over the package's best (its CPU build, or the device
|
|
814
|
+
build where one answers); the load average was 8 to 11, so the times
|
|
815
|
+
move by some tens of percent.
|
|
816
|
+
|
|
817
|
+
| estimator, n x p | iterations (sk / sklearnex / ours) | vs scikit-learn, one core | vs sklearnex, one core | vs sklearnex, 20 cores |
|
|
818
|
+
|---|---|---|---|---|
|
|
819
|
+
| LogisticRegression, 200,000 x 100 | 6 / 12 / 6 | 10.6x (device) | 8.4x (device) | 4.4x (device); on the CPU 0.5x |
|
|
820
|
+
| Ridge (alpha 1), 200,000 x 200 | - | 2.1x | 1.2x | 0.3x |
|
|
821
|
+
| PCA (10 components), 100,000 x 50 | - | 1.5x | 2.4x | 1.7x |
|
|
822
|
+
| PCA (10 components), 50,000 x 500 | - | 1.1x | 1.4x | 0.5x |
|
|
823
|
+
| SVC (rbf), 5,000 x 20 | 4,858 / 7 / 4,882 | 1.2x | 1.4x | 0.5x |
|
|
824
|
+
| SVR (rbf), 5,000 x 20 | 12,821 / 10 / 12,824 | 1.3x | 1.4x | 0.4x |
|
|
825
|
+
| RFE(Ridge), 20 of 200 kept, 20,000 x 200 | - | 216x | 128x | 78x |
|
|
826
|
+
|
|
827
|
+
On one core the package is ahead everywhere. sklearnex uses every
|
|
828
|
+
core by default, and the package's kernels are single-threaded, so
|
|
829
|
+
with 20 cores sklearnex is faster on Ridge, the larger PCA and the
|
|
830
|
+
SVMs; what stays ahead is the device build (logistic regression) and
|
|
831
|
+
an algorithm that does less work (RFE: the Gram once, the Cholesky
|
|
832
|
+
factor downdated per drop, where scikit-learn refits). sklearnex is
|
|
833
|
+
not scikit-learn's arithmetic: its logistic regression takes 12
|
|
834
|
+
iterations to scikit-learn's 6 and its coefficients differ from
|
|
835
|
+
scikit-learn's by 6e-3, where the package's agree to 4e-15; its SVMs
|
|
836
|
+
report a different iteration count. This sklearnex accelerates no
|
|
837
|
+
lasso, so the lasso tables above stand against scikit-learn alone.
|
|
838
|
+
|
|
736
839
|
## The GPU build
|
|
737
840
|
|
|
738
841
|
Where the CUDA toolkit is at install time, `ParallelFastICA`,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/_loader_accel_cublas.py
RENAMED
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/_loader_classifiers.py
RENAMED
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/_loader_derived_blas.py
RENAMED
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/_loader_derived_cublas.py
RENAMED
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/_loader_derived_loops.py
RENAMED
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/_loader_gpu_cuda.py
RENAMED
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/_loader_gramops_blas.py
RENAMED
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/_loader_gramops_loops.py
RENAMED
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/_loader_nmf_blas.py
RENAMED
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/_loader_nmf_cublas.py
RENAMED
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/_loader_nmf_loops.py
RENAMED
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/accel_cublas/accel.cpp
RENAMED
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/accel_cublas/accel.hpp
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/classifiers_capi.cpp
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/gpu_cuda/gpu_capi.cpp
RENAMED
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/gramops_blas/gramops.cpp
RENAMED
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/gramops_blas/gramops.hpp
RENAMED
|
File without changes
|
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/gramops_loops/gramops.cpp
RENAMED
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/gramops_loops/gramops.hpp
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/lasso_cov_capi.cpp
RENAMED
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/nmf_blas/nmf_cd.cpp
RENAMED
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/nmf_blas/nmf_cd.hpp
RENAMED
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/nmf_blas/nmf_cd_capi.cpp
RENAMED
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/nmf_cublas/nmf_cd.cpp
RENAMED
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/nmf_cublas/nmf_cd.hpp
RENAMED
|
File without changes
|
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/nmf_loops/nmf_cd.cpp
RENAMED
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/nmf_loops/nmf_cd.hpp
RENAMED
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/nmf_loops/nmf_cd_capi.cpp
RENAMED
|
File without changes
|
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso/_generated/scm2cpp-cublas.hpp
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scm2cpp_lasso-0.13.0 → scm2cpp_lasso-0.13.2}/src/scm2cpp_lasso.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|