scm2cpp-lasso 0.15.0__tar.gz → 0.16.0__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.15.0/src/scm2cpp_lasso.egg-info → scm2cpp_lasso-0.16.0}/PKG-INFO +119 -22
- scm2cpp_lasso-0.15.0/PKG-INFO → scm2cpp_lasso-0.16.0/README.md +117 -38
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/pyproject.toml +2 -2
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/setup.py +83 -10
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/__init__.py +357 -30
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/_loader_accel_cublas.py +9 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/_loader_classifiers.py +17 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/_loader_gpu_cuda.py +9 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/_loader_gramops_blas.py +121 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/_loader_gramops_loops.py +130 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/accel_cublas/accel.cpp +1 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/accel_cublas/accel.hpp +38 -17
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/accel_cublas/accel_capi.cpp +3 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/classifiers.cpp +2 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/classifiers.hpp +1221 -366
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/classifiers_capi.cpp +6 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/derived_blas/lasso_design.hpp +17 -14
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/derived_cublas/lasso_design.hpp +18 -15
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/derived_loops/lasso_design.hpp +29 -26
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/gpu_cuda/gpu.cpp +1 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/gpu_cuda/gpu.hpp +299 -246
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/gpu_cuda/gpu_capi.cpp +9 -0
- {scm2cpp_lasso-0.15.0/src/scm2cpp_lasso/_generated/gramops_loops → scm2cpp_lasso-0.16.0/src/scm2cpp_lasso/_generated/gramops_blas}/gramops.cpp +15 -0
- scm2cpp_lasso-0.16.0/src/scm2cpp_lasso/_generated/gramops_blas/gramops.hpp +4959 -0
- {scm2cpp_lasso-0.15.0/src/scm2cpp_lasso/_generated/gramops_loops → scm2cpp_lasso-0.16.0/src/scm2cpp_lasso/_generated/gramops_blas}/gramops_capi.cpp +42 -0
- {scm2cpp_lasso-0.15.0/src/scm2cpp_lasso/_generated/gramops_blas → scm2cpp_lasso-0.16.0/src/scm2cpp_lasso/_generated/gramops_loops}/gramops.cpp +15 -0
- scm2cpp_lasso-0.16.0/src/scm2cpp_lasso/_generated/gramops_loops/gramops.hpp +5860 -0
- {scm2cpp_lasso-0.15.0/src/scm2cpp_lasso/_generated/gramops_blas → scm2cpp_lasso-0.16.0/src/scm2cpp_lasso/_generated/gramops_loops}/gramops_capi.cpp +45 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/scm2cpp-blas.hpp +61 -0
- scm2cpp_lasso-0.15.0/README.md → scm2cpp_lasso-0.16.0/src/scm2cpp_lasso.egg-info/PKG-INFO +135 -20
- scm2cpp_lasso-0.15.0/src/scm2cpp_lasso/_generated/gramops_blas/gramops.hpp +0 -3033
- scm2cpp_lasso-0.15.0/src/scm2cpp_lasso/_generated/gramops_loops/gramops.hpp +0 -3302
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/setup.cfg +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_compat.py +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/__init__.py +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/_loader.py +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/_loader_derived_blas.py +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/_loader_derived_cublas.py +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/_loader_derived_loops.py +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/_loader_nmf_blas.py +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/_loader_nmf_cublas.py +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/_loader_nmf_loops.py +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/derived_blas/lasso_design.cpp +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/derived_blas/lasso_design_capi.cpp +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/derived_cublas/lasso_design.cpp +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/derived_cublas/lasso_design_capi.cpp +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/derived_loops/lasso_design.cpp +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/derived_loops/lasso_design_capi.cpp +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/lasso_cov.cpp +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/lasso_cov.hpp +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/lasso_cov_capi.cpp +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/nmf_blas/nmf_cd.cpp +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/nmf_blas/nmf_cd.hpp +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/nmf_blas/nmf_cd_capi.cpp +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/nmf_cublas/nmf_cd.cpp +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/nmf_cublas/nmf_cd.hpp +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/nmf_cublas/nmf_cd_capi.cpp +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/nmf_loops/nmf_cd.cpp +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/nmf_loops/nmf_cd.hpp +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/nmf_loops/nmf_cd_capi.cpp +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/scm2cpp-cublas.hpp +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/scm2cpp-cuda.hpp +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_generated/scm2cpp.hpp +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/_libfind.py +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso/batch_capi.cu +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso.egg-info/SOURCES.txt +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso.egg-info/dependency_links.txt +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso.egg-info/requires.txt +0 -0
- {scm2cpp_lasso-0.15.0 → scm2cpp_lasso-0.16.0}/src/scm2cpp_lasso.egg-info/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scm2cpp-lasso
|
|
3
|
-
Version: 0.
|
|
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
|
|
3
|
+
Version: 0.16.0
|
|
4
|
+
Summary: scikit-learn estimators (lasso, elastic net, ridge, RidgeCV, ARD, SVM, SVR, logistic regression, GLMs, Huber, Theil-Sen, F-score selection, RFE, NMF, Gaussian mixture, FastICA, PCA, KernelPCA, 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
|
|
7
7
|
Project-URL: Homepage, https://github.com/niitsuma/scm2cpp
|
|
@@ -41,6 +41,16 @@ from the sdist on a machine with `nvcc` on the path:
|
|
|
41
41
|
$ pip install --no-binary scm2cpp-lasso scm2cpp-lasso
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
Built from the sdist, the products and the eigen- and singular value
|
|
45
|
+
problems go to Intel MKL where pkg-config knows it
|
|
46
|
+
(`mkl-dynamic-lp64-gomp`, GNU OpenMP threading, as Gentoo's
|
|
47
|
+
`sci-libs/mkl` installs it), else to an OpenBLAS with LAPACKE, else to
|
|
48
|
+
the translated loops; `SCM2CPP_NO_MKL=1` skips MKL. The translated
|
|
49
|
+
loops that the dependence test proves independent run on OpenMP where
|
|
50
|
+
the compiler has it (`SCM2CPP_NO_OPENMP=1` builds them serial). Both
|
|
51
|
+
use every core unless told otherwise: `OMP_NUM_THREADS` (and
|
|
52
|
+
`MKL_NUM_THREADS` / `OPENBLAS_NUM_THREADS`) set the threads.
|
|
53
|
+
|
|
44
54
|
The batched GPU solver, the cuBLAS variants and the `--cuda`
|
|
45
55
|
estimators are then built as well; without `nvcc` the package installs
|
|
46
56
|
and works exactly the same, minus those.
|
|
@@ -109,9 +119,7 @@ from scikit-learn's `BaseEstimator` and the matching mixin, so `clone`,
|
|
|
109
119
|
`get_params` / `set_params`, `GridSearchCV`, `Pipeline` and
|
|
110
120
|
`cross_val_score` take them, and all 44 pass
|
|
111
121
|
`sklearn.utils.estimator_checks.check_estimator` (`check-sklearn.py`
|
|
112
|
-
runs it; the expected
|
|
113
|
-
since the derived kernel does not count its sweeps, and
|
|
114
|
-
`LBFGSLogisticRegressionCV`'s
|
|
122
|
+
runs it; the one expected failure is `LBFGSLogisticRegressionCV`'s
|
|
115
123
|
`n_iter_` of 0 for a C whose warm start is already converged, which
|
|
116
124
|
scikit-learn's own `LogisticRegressionCV` reports on the same data).
|
|
117
125
|
scikit-learn stays optional: without it a numpy-only stand-in gives
|
|
@@ -193,7 +201,8 @@ package picks the fastest one present.
|
|
|
193
201
|
|---|---|---|---|---|
|
|
194
202
|
| `GramLasso` | Lasso(precompute=True), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | no |
|
|
195
203
|
| `GramElasticNet` | ElasticNet(precompute=True), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | no |
|
|
196
|
-
| `GramRidge` | Ridge, fit(X, y) with the intercept, one or many targets |
|
|
204
|
+
| `GramRidge` | Ridge, fit(X, y) with the intercept, one or many targets | `rfe.scm` (Cholesky) | loops / BLAS | Ridge |
|
|
205
|
+
| `GramRidgeCV` | RidgeCV (cv=None), leave-one-out over every alpha from one eigenproblem | `ridge-gcv.scm` | loops / BLAS | no |
|
|
197
206
|
| `GramMultiTaskLasso` | MultiTaskLasso, fit(X, Y) with the intercept | `lasso-cov.scm` | CPU | no |
|
|
198
207
|
| `GramMultiTaskElasticNet` | MultiTaskElasticNet, fit(X, Y) with the intercept | `lasso-cov.scm` | CPU | no |
|
|
199
208
|
| `GramGroupLasso` | (none; skglm GroupLasso), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | - |
|
|
@@ -238,6 +247,7 @@ package picks the fastest one present.
|
|
|
238
247
|
| `EMGaussianMixture` | GaussianMixture(covariance_type='full') | `gmm.scm` | loops / BLAS / --cuda | no |
|
|
239
248
|
| `ParallelFastICA` | FastICA | `fastica.scm` | loops / BLAS / --cuda | no |
|
|
240
249
|
| `CovPCA` | PCA(svd_solver='covariance_eigh') | `pca.scm` | loops / BLAS / cuBLAS | PCA |
|
|
250
|
+
| `GramKernelPCA` | KernelPCA (no inverse_transform) | `kernel-pca.scm` | loops / BLAS | no |
|
|
241
251
|
| `f_regression, f_classif` | f_regression, f_classif | `fscore.scm` | loops / BLAS / cuBLAS | no |
|
|
242
252
|
| `SelectKBest, SelectPercentile` | SelectKBest, SelectPercentile | `fscore.scm` | loops / BLAS / cuBLAS | no |
|
|
243
253
|
| `GramRFE` | RFE(Ridge), RFE(LinearRegression) | `rfe.scm` | loops / BLAS / cuBLAS | inside: Ridge |
|
|
@@ -560,6 +570,79 @@ on the GPU: the ridge is one eigendecomposition and the elastic net's
|
|
|
560
570
|
sweeps are the lasso's; `fit_path_batch(l1_ratio=..)` is the GPU
|
|
561
571
|
grid for the elastic net.
|
|
562
572
|
|
|
573
|
+
## Leave-one-out ridge: `GramRidgeCV`
|
|
574
|
+
|
|
575
|
+
`GramRidgeCV` is scikit-learn's `RidgeCV` with `cv=None`: the alpha
|
|
576
|
+
whose leave-one-out mean squared error is least, without a refit per
|
|
577
|
+
row. With the centred design Xc and H = Xc (Xc'Xc + alpha I)^-1 Xc'
|
|
578
|
+
(plus 1/n for the intercept), the leave-one-out residual is
|
|
579
|
+
(y - yhat) / (1 - diag H). One eigenproblem gives H = Z diag(1 /
|
|
580
|
+
(lam + alpha)) Z' for every alpha: Z = Xc V from Xc'Xc = V diag(lam) V'
|
|
581
|
+
when n > p (X'X by dsyrk), Z = Q diag(sqrt mu) from Xc Xc' = Q diag(mu)
|
|
582
|
+
Q' otherwise. Then diag H and yhat at all the alphas are two products,
|
|
583
|
+
(Z o Z) W and Z (R o W) with W[k, a] = 1 / (lam_k + alpha_a): the
|
|
584
|
+
alphas cost one dgemm, where scikit-learn loops over them in numpy
|
|
585
|
+
(`examples/kernel-only/ridge-gcv.scm`; the eigenproblem is LAPACK's
|
|
586
|
+
dsyevd in the BLAS build).
|
|
587
|
+
|
|
588
|
+
```python
|
|
589
|
+
from scm2cpp_lasso import GramRidgeCV
|
|
590
|
+
m = GramRidgeCV(alphas=np.logspace(-2, 4, 50)).fit(X, y)
|
|
591
|
+
m.alpha_, m.best_score_, m.coef_, m.intercept_
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
It takes `fit_intercept`, `gcv_mode` (`"svd"` / `"eigen"` choose the
|
|
595
|
+
two Zs), `store_cv_results` and `alpha_per_target`, y one target or
|
|
596
|
+
several; `cv` and `scoring` must stay None. The chosen alpha is
|
|
597
|
+
scikit-learn's, the coefficients agree to 1e-12 and `cv_results_` to
|
|
598
|
+
1e-7 relative (the wide case near interpolation). Measured
|
|
599
|
+
(`bench/eigen-compare.py`, the MKL build, best of three):
|
|
600
|
+
|
|
601
|
+
| case | scikit-learn `RidgeCV` | `GramRidgeCV` | ratio, one core | ratio, 20 cores |
|
|
602
|
+
|---|---|---|---|---|
|
|
603
|
+
| 200,000 x 200, 3 alphas | 2.04 s | 0.80 s | 2.6x | 3.6x |
|
|
604
|
+
| 200,000 x 200, 50 alphas | 26.6 s | 0.96 s | 27.7x | 29.0x |
|
|
605
|
+
| 20,000 x 500, 50 alphas | 9.64 s | 0.33 s | 28.9x | 29.2x |
|
|
606
|
+
| 2,000 x 5,000, 50 alphas | 3.16 s | 1.47 s | 2.1x | 4.5x |
|
|
607
|
+
|
|
608
|
+
## Kernel PCA: `GramKernelPCA`
|
|
609
|
+
|
|
610
|
+
`GramKernelPCA` is scikit-learn's `KernelPCA`: the kernel matrix
|
|
611
|
+
(linear, rbf, poly, sigmoid, cosine or precomputed), centred in feature
|
|
612
|
+
space, its largest eigenpairs with svd_flip's signs
|
|
613
|
+
(`examples/kernel-only/kernel-pca.scm`). Where the eigenpairs come from
|
|
614
|
+
is what differs:
|
|
615
|
+
|
|
616
|
+
- fewer than 10 components of more than 200 rows, where scikit-learn
|
|
617
|
+
takes ARPACK (Lanczos, one matrix-vector product at a time): subspace
|
|
618
|
+
iteration on c + 25 columns, so each step is one dgemm, stopped when
|
|
619
|
+
every residual |K u - lam u| is under 1e-12 of the largest value
|
|
620
|
+
(falling back to the dense route if it is not reached);
|
|
621
|
+
- otherwise LAPACK's dsyevr over the wanted index range, which is what
|
|
622
|
+
scipy's `eigh(subset_by_index=..)` runs for scikit-learn;
|
|
623
|
+
- the linear kernel with no more components than features, n > p:
|
|
624
|
+
through Xc'Xc, p x p (the eigenvectors of Xc Xc' are Xc v / sqrt(lam)),
|
|
625
|
+
where scikit-learn forms the n x n kernel.
|
|
626
|
+
|
|
627
|
+
```python
|
|
628
|
+
from scm2cpp_lasso import GramKernelPCA
|
|
629
|
+
Z = GramKernelPCA(5, kernel="rbf", gamma=0.05).fit_transform(X)
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
Eigenvalues, eigenvectors and transforms agree with scikit-learn's to
|
|
633
|
+
1e-12 on every kernel; no `inverse_transform`. Measured
|
|
634
|
+
(`bench/eigen-compare.py`, the MKL build, best of three):
|
|
635
|
+
|
|
636
|
+
| case | scikit-learn `KernelPCA` | `GramKernelPCA` | ratio, one core | ratio, 20 cores |
|
|
637
|
+
|---|---|---|---|---|
|
|
638
|
+
| rbf, 3,000 x 20, 5 components | 1.11 s (ARPACK) | 0.35 s (subspace) | 3.2x | 3.0x |
|
|
639
|
+
| rbf, 6,000 x 20, 5 components | 4.63 s (ARPACK) | 1.33 s (subspace) | 3.5x | 4.2x |
|
|
640
|
+
| rbf, 3,000 x 20, 50 components | 3.48 s (dense) | 3.63 s (dense) | 1.0x | 1.2x |
|
|
641
|
+
| linear, 5,000 x 50, 10 components | 19.8 s (ARPACK on 5,000 x 5,000) | 0.002 s (X'X) | 10,000x | 2,100x |
|
|
642
|
+
|
|
643
|
+
The linear row is the algorithm (an n x n problem against a p x p one),
|
|
644
|
+
not the code.
|
|
645
|
+
|
|
563
646
|
## Bayesian ridge
|
|
564
647
|
|
|
565
648
|
`CovBayesianRidge` is scikit-learn's `BayesianRidge` -- the same model,
|
|
@@ -645,6 +728,15 @@ matrix (`KernelSVM`) or transposing X into columns
|
|
|
645
728
|
| `L1LogisticRegression`, 20,000 x 500, C=0.05 | 1.20 s | 2.23 s | 1.9x | 106.77913 / 106.77915 | 100% |
|
|
646
729
|
| `L1LogisticRegression`, 100,000 x 200, C=0.05 | 9.06 s | 18.7 s | 2.1x | 349.61154 / 349.61216 | 100% |
|
|
647
730
|
|
|
731
|
+
On 2,000 x 20,000 neither side reaches the optimum at tol 1e-4 (the
|
|
732
|
+
objective at tol 1e-10 is 0.0519588), and where each stops depends on
|
|
733
|
+
the coordinate order: over eight seeds ours stops at 0.05240 to
|
|
734
|
+
0.05251 (0.9 to 1.1% above the optimum), `LinearSVC`, whose order is
|
|
735
|
+
random unless `random_state` is given, at 0.05242 to 0.05319 (0.9 to
|
|
736
|
+
2.4%). When `LinearSVC` draws one of its worse orders the gap passes
|
|
737
|
+
the script's 0.5% bound and the row prints `<-- disagrees`; ours is
|
|
738
|
+
then the better solution of the two, so the flag there is not a fault.
|
|
739
|
+
|
|
648
740
|
The regressors, from the same script (C = 1, epsilon 0.1):
|
|
649
741
|
|
|
650
742
|
| problem | ours | scikit-learn | ratio | agreement |
|
|
@@ -894,25 +986,30 @@ times the ones with a counterpart here, sklearnex's estimators
|
|
|
894
986
|
imported directly (not patched in) so that scikit-learn itself is
|
|
895
987
|
timed in the same process. The ratios are scikit-learn's or
|
|
896
988
|
sklearnex's time over the package's best (its CPU build, or the device
|
|
897
|
-
build where one answers)
|
|
898
|
-
|
|
989
|
+
build where one answers). The package was built against Intel MKL
|
|
990
|
+
(see the build section), its loops under OpenMP; the load average was
|
|
991
|
+
about 2.3, so the times move by some percent.
|
|
899
992
|
|
|
900
993
|
| estimator, n x p | iterations (sk / sklearnex / ours) | vs scikit-learn, one core | vs sklearnex, one core | vs sklearnex, 20 cores |
|
|
901
994
|
|---|---|---|---|---|
|
|
902
|
-
| LogisticRegression, 200,000 x 100 | 6 / 12 / 6 |
|
|
903
|
-
| Ridge (alpha 1), 200,000 x 200 | - |
|
|
904
|
-
| PCA (10 components), 100,000 x 50 | - | 1.
|
|
905
|
-
| PCA (10 components), 50,000 x 500 | - | 1.1x | 1.4x |
|
|
906
|
-
| SVC (rbf), 5,000 x 20 | 4,858 / 7 / 4,882 | 1.
|
|
907
|
-
| SVR (rbf), 5,000 x 20 | 12,821 / 10 / 12,824 | 1.
|
|
908
|
-
| RFE(Ridge), 20 of 200 kept, 20,000 x 200 | - |
|
|
909
|
-
|
|
910
|
-
On one core the package is ahead everywhere
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
995
|
+
| LogisticRegression, 200,000 x 100 | 6 / 12 / 6 | 6.8x (device) | 6.4x (device) | 1.8x (device); on the CPU 0.9x |
|
|
996
|
+
| Ridge (alpha 1, no intercept), 200,000 x 200 | - | 1.7x | 0.8x | 2.3x |
|
|
997
|
+
| PCA (10 components), 100,000 x 50 | - | 1.4x | 2.2x | 1.4x |
|
|
998
|
+
| PCA (10 components), 50,000 x 500 | - | 1.1x | 1.4x | 1.6x (device); on the CPU 1.2x |
|
|
999
|
+
| SVC (rbf), 5,000 x 20 | 4,858 / 7 / 4,882 | 1.4x | 1.6x | 0.5x |
|
|
1000
|
+
| SVR (rbf), 5,000 x 20 | 12,821 / 10 / 12,824 | 1.4x | 1.4x | 0.4x |
|
|
1001
|
+
| RFE(Ridge), 20 of 200 kept, 20,000 x 200 | - | 138x | 106x | 116x |
|
|
1002
|
+
|
|
1003
|
+
On one core the package is ahead everywhere but Ridge, where both
|
|
1004
|
+
sides are one symmetric rank-k update of the same size and sklearnex
|
|
1005
|
+
is 0.180 s to 0.219 s. With 20 cores both sides use every core: the
|
|
1006
|
+
package's loops run under OpenMP and its products in MKL's threads,
|
|
1007
|
+
so it is ahead on Ridge (the Gram by dsyrk and a Cholesky solve), PCA,
|
|
1008
|
+
the device build of logistic regression and RFE (an algorithm that
|
|
1009
|
+
does less work: the Gram once, the Cholesky factor downdated per drop,
|
|
1010
|
+
where scikit-learn refits), level on logistic regression on the CPU,
|
|
1011
|
+
and behind on the SVMs: SMO takes thousands of short steps, each
|
|
1012
|
+
waiting on the last, where sklearnex's solver takes 7 to 10 long ones. sklearnex is
|
|
916
1013
|
not scikit-learn's arithmetic: its logistic regression takes 12
|
|
917
1014
|
iterations to scikit-learn's 6 and its coefficients differ from
|
|
918
1015
|
scikit-learn's by 6e-3, where the package's agree to 4e-15; its SVMs
|
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: scm2cpp-lasso
|
|
3
|
-
Version: 0.15.0
|
|
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
|
-
Author: Hirotaka Niitsuma
|
|
6
|
-
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/niitsuma/scm2cpp
|
|
8
|
-
Project-URL: Source, https://github.com/niitsuma/scm2cpp/tree/master/python/scm2cpp-lasso
|
|
9
|
-
Keywords: lasso,elastic-net,ridge,svm,logistic-regression,coordinate-descent,cuda,compiler
|
|
10
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: Programming Language :: C++
|
|
13
|
-
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
14
|
-
Classifier: Intended Audience :: Science/Research
|
|
15
|
-
Requires-Python: >=3.8
|
|
16
|
-
Description-Content-Type: text/markdown
|
|
17
|
-
Requires-Dist: numpy>=1.17
|
|
18
|
-
|
|
19
1
|
# scm2cpp-lasso
|
|
20
2
|
|
|
21
3
|
[](https://colab.research.google.com/github/niitsuma/scm2cpp/blob/master/notebooks/compare-sklearn.ipynb)
|
|
@@ -41,6 +23,16 @@ from the sdist on a machine with `nvcc` on the path:
|
|
|
41
23
|
$ pip install --no-binary scm2cpp-lasso scm2cpp-lasso
|
|
42
24
|
```
|
|
43
25
|
|
|
26
|
+
Built from the sdist, the products and the eigen- and singular value
|
|
27
|
+
problems go to Intel MKL where pkg-config knows it
|
|
28
|
+
(`mkl-dynamic-lp64-gomp`, GNU OpenMP threading, as Gentoo's
|
|
29
|
+
`sci-libs/mkl` installs it), else to an OpenBLAS with LAPACKE, else to
|
|
30
|
+
the translated loops; `SCM2CPP_NO_MKL=1` skips MKL. The translated
|
|
31
|
+
loops that the dependence test proves independent run on OpenMP where
|
|
32
|
+
the compiler has it (`SCM2CPP_NO_OPENMP=1` builds them serial). Both
|
|
33
|
+
use every core unless told otherwise: `OMP_NUM_THREADS` (and
|
|
34
|
+
`MKL_NUM_THREADS` / `OPENBLAS_NUM_THREADS`) set the threads.
|
|
35
|
+
|
|
44
36
|
The batched GPU solver, the cuBLAS variants and the `--cuda`
|
|
45
37
|
estimators are then built as well; without `nvcc` the package installs
|
|
46
38
|
and works exactly the same, minus those.
|
|
@@ -109,9 +101,7 @@ from scikit-learn's `BaseEstimator` and the matching mixin, so `clone`,
|
|
|
109
101
|
`get_params` / `set_params`, `GridSearchCV`, `Pipeline` and
|
|
110
102
|
`cross_val_score` take them, and all 44 pass
|
|
111
103
|
`sklearn.utils.estimator_checks.check_estimator` (`check-sklearn.py`
|
|
112
|
-
runs it; the expected
|
|
113
|
-
since the derived kernel does not count its sweeps, and
|
|
114
|
-
`LBFGSLogisticRegressionCV`'s
|
|
104
|
+
runs it; the one expected failure is `LBFGSLogisticRegressionCV`'s
|
|
115
105
|
`n_iter_` of 0 for a C whose warm start is already converged, which
|
|
116
106
|
scikit-learn's own `LogisticRegressionCV` reports on the same data).
|
|
117
107
|
scikit-learn stays optional: without it a numpy-only stand-in gives
|
|
@@ -193,7 +183,8 @@ package picks the fastest one present.
|
|
|
193
183
|
|---|---|---|---|---|
|
|
194
184
|
| `GramLasso` | Lasso(precompute=True), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | no |
|
|
195
185
|
| `GramElasticNet` | ElasticNet(precompute=True), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | no |
|
|
196
|
-
| `GramRidge` | Ridge, fit(X, y) with the intercept, one or many targets |
|
|
186
|
+
| `GramRidge` | Ridge, fit(X, y) with the intercept, one or many targets | `rfe.scm` (Cholesky) | loops / BLAS | Ridge |
|
|
187
|
+
| `GramRidgeCV` | RidgeCV (cv=None), leave-one-out over every alpha from one eigenproblem | `ridge-gcv.scm` | loops / BLAS | no |
|
|
197
188
|
| `GramMultiTaskLasso` | MultiTaskLasso, fit(X, Y) with the intercept | `lasso-cov.scm` | CPU | no |
|
|
198
189
|
| `GramMultiTaskElasticNet` | MultiTaskElasticNet, fit(X, Y) with the intercept | `lasso-cov.scm` | CPU | no |
|
|
199
190
|
| `GramGroupLasso` | (none; skglm GroupLasso), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | - |
|
|
@@ -238,6 +229,7 @@ package picks the fastest one present.
|
|
|
238
229
|
| `EMGaussianMixture` | GaussianMixture(covariance_type='full') | `gmm.scm` | loops / BLAS / --cuda | no |
|
|
239
230
|
| `ParallelFastICA` | FastICA | `fastica.scm` | loops / BLAS / --cuda | no |
|
|
240
231
|
| `CovPCA` | PCA(svd_solver='covariance_eigh') | `pca.scm` | loops / BLAS / cuBLAS | PCA |
|
|
232
|
+
| `GramKernelPCA` | KernelPCA (no inverse_transform) | `kernel-pca.scm` | loops / BLAS | no |
|
|
241
233
|
| `f_regression, f_classif` | f_regression, f_classif | `fscore.scm` | loops / BLAS / cuBLAS | no |
|
|
242
234
|
| `SelectKBest, SelectPercentile` | SelectKBest, SelectPercentile | `fscore.scm` | loops / BLAS / cuBLAS | no |
|
|
243
235
|
| `GramRFE` | RFE(Ridge), RFE(LinearRegression) | `rfe.scm` | loops / BLAS / cuBLAS | inside: Ridge |
|
|
@@ -560,6 +552,79 @@ on the GPU: the ridge is one eigendecomposition and the elastic net's
|
|
|
560
552
|
sweeps are the lasso's; `fit_path_batch(l1_ratio=..)` is the GPU
|
|
561
553
|
grid for the elastic net.
|
|
562
554
|
|
|
555
|
+
## Leave-one-out ridge: `GramRidgeCV`
|
|
556
|
+
|
|
557
|
+
`GramRidgeCV` is scikit-learn's `RidgeCV` with `cv=None`: the alpha
|
|
558
|
+
whose leave-one-out mean squared error is least, without a refit per
|
|
559
|
+
row. With the centred design Xc and H = Xc (Xc'Xc + alpha I)^-1 Xc'
|
|
560
|
+
(plus 1/n for the intercept), the leave-one-out residual is
|
|
561
|
+
(y - yhat) / (1 - diag H). One eigenproblem gives H = Z diag(1 /
|
|
562
|
+
(lam + alpha)) Z' for every alpha: Z = Xc V from Xc'Xc = V diag(lam) V'
|
|
563
|
+
when n > p (X'X by dsyrk), Z = Q diag(sqrt mu) from Xc Xc' = Q diag(mu)
|
|
564
|
+
Q' otherwise. Then diag H and yhat at all the alphas are two products,
|
|
565
|
+
(Z o Z) W and Z (R o W) with W[k, a] = 1 / (lam_k + alpha_a): the
|
|
566
|
+
alphas cost one dgemm, where scikit-learn loops over them in numpy
|
|
567
|
+
(`examples/kernel-only/ridge-gcv.scm`; the eigenproblem is LAPACK's
|
|
568
|
+
dsyevd in the BLAS build).
|
|
569
|
+
|
|
570
|
+
```python
|
|
571
|
+
from scm2cpp_lasso import GramRidgeCV
|
|
572
|
+
m = GramRidgeCV(alphas=np.logspace(-2, 4, 50)).fit(X, y)
|
|
573
|
+
m.alpha_, m.best_score_, m.coef_, m.intercept_
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
It takes `fit_intercept`, `gcv_mode` (`"svd"` / `"eigen"` choose the
|
|
577
|
+
two Zs), `store_cv_results` and `alpha_per_target`, y one target or
|
|
578
|
+
several; `cv` and `scoring` must stay None. The chosen alpha is
|
|
579
|
+
scikit-learn's, the coefficients agree to 1e-12 and `cv_results_` to
|
|
580
|
+
1e-7 relative (the wide case near interpolation). Measured
|
|
581
|
+
(`bench/eigen-compare.py`, the MKL build, best of three):
|
|
582
|
+
|
|
583
|
+
| case | scikit-learn `RidgeCV` | `GramRidgeCV` | ratio, one core | ratio, 20 cores |
|
|
584
|
+
|---|---|---|---|---|
|
|
585
|
+
| 200,000 x 200, 3 alphas | 2.04 s | 0.80 s | 2.6x | 3.6x |
|
|
586
|
+
| 200,000 x 200, 50 alphas | 26.6 s | 0.96 s | 27.7x | 29.0x |
|
|
587
|
+
| 20,000 x 500, 50 alphas | 9.64 s | 0.33 s | 28.9x | 29.2x |
|
|
588
|
+
| 2,000 x 5,000, 50 alphas | 3.16 s | 1.47 s | 2.1x | 4.5x |
|
|
589
|
+
|
|
590
|
+
## Kernel PCA: `GramKernelPCA`
|
|
591
|
+
|
|
592
|
+
`GramKernelPCA` is scikit-learn's `KernelPCA`: the kernel matrix
|
|
593
|
+
(linear, rbf, poly, sigmoid, cosine or precomputed), centred in feature
|
|
594
|
+
space, its largest eigenpairs with svd_flip's signs
|
|
595
|
+
(`examples/kernel-only/kernel-pca.scm`). Where the eigenpairs come from
|
|
596
|
+
is what differs:
|
|
597
|
+
|
|
598
|
+
- fewer than 10 components of more than 200 rows, where scikit-learn
|
|
599
|
+
takes ARPACK (Lanczos, one matrix-vector product at a time): subspace
|
|
600
|
+
iteration on c + 25 columns, so each step is one dgemm, stopped when
|
|
601
|
+
every residual |K u - lam u| is under 1e-12 of the largest value
|
|
602
|
+
(falling back to the dense route if it is not reached);
|
|
603
|
+
- otherwise LAPACK's dsyevr over the wanted index range, which is what
|
|
604
|
+
scipy's `eigh(subset_by_index=..)` runs for scikit-learn;
|
|
605
|
+
- the linear kernel with no more components than features, n > p:
|
|
606
|
+
through Xc'Xc, p x p (the eigenvectors of Xc Xc' are Xc v / sqrt(lam)),
|
|
607
|
+
where scikit-learn forms the n x n kernel.
|
|
608
|
+
|
|
609
|
+
```python
|
|
610
|
+
from scm2cpp_lasso import GramKernelPCA
|
|
611
|
+
Z = GramKernelPCA(5, kernel="rbf", gamma=0.05).fit_transform(X)
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
Eigenvalues, eigenvectors and transforms agree with scikit-learn's to
|
|
615
|
+
1e-12 on every kernel; no `inverse_transform`. Measured
|
|
616
|
+
(`bench/eigen-compare.py`, the MKL build, best of three):
|
|
617
|
+
|
|
618
|
+
| case | scikit-learn `KernelPCA` | `GramKernelPCA` | ratio, one core | ratio, 20 cores |
|
|
619
|
+
|---|---|---|---|---|
|
|
620
|
+
| rbf, 3,000 x 20, 5 components | 1.11 s (ARPACK) | 0.35 s (subspace) | 3.2x | 3.0x |
|
|
621
|
+
| rbf, 6,000 x 20, 5 components | 4.63 s (ARPACK) | 1.33 s (subspace) | 3.5x | 4.2x |
|
|
622
|
+
| rbf, 3,000 x 20, 50 components | 3.48 s (dense) | 3.63 s (dense) | 1.0x | 1.2x |
|
|
623
|
+
| linear, 5,000 x 50, 10 components | 19.8 s (ARPACK on 5,000 x 5,000) | 0.002 s (X'X) | 10,000x | 2,100x |
|
|
624
|
+
|
|
625
|
+
The linear row is the algorithm (an n x n problem against a p x p one),
|
|
626
|
+
not the code.
|
|
627
|
+
|
|
563
628
|
## Bayesian ridge
|
|
564
629
|
|
|
565
630
|
`CovBayesianRidge` is scikit-learn's `BayesianRidge` -- the same model,
|
|
@@ -645,6 +710,15 @@ matrix (`KernelSVM`) or transposing X into columns
|
|
|
645
710
|
| `L1LogisticRegression`, 20,000 x 500, C=0.05 | 1.20 s | 2.23 s | 1.9x | 106.77913 / 106.77915 | 100% |
|
|
646
711
|
| `L1LogisticRegression`, 100,000 x 200, C=0.05 | 9.06 s | 18.7 s | 2.1x | 349.61154 / 349.61216 | 100% |
|
|
647
712
|
|
|
713
|
+
On 2,000 x 20,000 neither side reaches the optimum at tol 1e-4 (the
|
|
714
|
+
objective at tol 1e-10 is 0.0519588), and where each stops depends on
|
|
715
|
+
the coordinate order: over eight seeds ours stops at 0.05240 to
|
|
716
|
+
0.05251 (0.9 to 1.1% above the optimum), `LinearSVC`, whose order is
|
|
717
|
+
random unless `random_state` is given, at 0.05242 to 0.05319 (0.9 to
|
|
718
|
+
2.4%). When `LinearSVC` draws one of its worse orders the gap passes
|
|
719
|
+
the script's 0.5% bound and the row prints `<-- disagrees`; ours is
|
|
720
|
+
then the better solution of the two, so the flag there is not a fault.
|
|
721
|
+
|
|
648
722
|
The regressors, from the same script (C = 1, epsilon 0.1):
|
|
649
723
|
|
|
650
724
|
| problem | ours | scikit-learn | ratio | agreement |
|
|
@@ -894,25 +968,30 @@ times the ones with a counterpart here, sklearnex's estimators
|
|
|
894
968
|
imported directly (not patched in) so that scikit-learn itself is
|
|
895
969
|
timed in the same process. The ratios are scikit-learn's or
|
|
896
970
|
sklearnex's time over the package's best (its CPU build, or the device
|
|
897
|
-
build where one answers)
|
|
898
|
-
|
|
971
|
+
build where one answers). The package was built against Intel MKL
|
|
972
|
+
(see the build section), its loops under OpenMP; the load average was
|
|
973
|
+
about 2.3, so the times move by some percent.
|
|
899
974
|
|
|
900
975
|
| estimator, n x p | iterations (sk / sklearnex / ours) | vs scikit-learn, one core | vs sklearnex, one core | vs sklearnex, 20 cores |
|
|
901
976
|
|---|---|---|---|---|
|
|
902
|
-
| LogisticRegression, 200,000 x 100 | 6 / 12 / 6 |
|
|
903
|
-
| Ridge (alpha 1), 200,000 x 200 | - |
|
|
904
|
-
| PCA (10 components), 100,000 x 50 | - | 1.
|
|
905
|
-
| PCA (10 components), 50,000 x 500 | - | 1.1x | 1.4x |
|
|
906
|
-
| SVC (rbf), 5,000 x 20 | 4,858 / 7 / 4,882 | 1.
|
|
907
|
-
| SVR (rbf), 5,000 x 20 | 12,821 / 10 / 12,824 | 1.
|
|
908
|
-
| RFE(Ridge), 20 of 200 kept, 20,000 x 200 | - |
|
|
909
|
-
|
|
910
|
-
On one core the package is ahead everywhere
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
977
|
+
| LogisticRegression, 200,000 x 100 | 6 / 12 / 6 | 6.8x (device) | 6.4x (device) | 1.8x (device); on the CPU 0.9x |
|
|
978
|
+
| Ridge (alpha 1, no intercept), 200,000 x 200 | - | 1.7x | 0.8x | 2.3x |
|
|
979
|
+
| PCA (10 components), 100,000 x 50 | - | 1.4x | 2.2x | 1.4x |
|
|
980
|
+
| PCA (10 components), 50,000 x 500 | - | 1.1x | 1.4x | 1.6x (device); on the CPU 1.2x |
|
|
981
|
+
| SVC (rbf), 5,000 x 20 | 4,858 / 7 / 4,882 | 1.4x | 1.6x | 0.5x |
|
|
982
|
+
| SVR (rbf), 5,000 x 20 | 12,821 / 10 / 12,824 | 1.4x | 1.4x | 0.4x |
|
|
983
|
+
| RFE(Ridge), 20 of 200 kept, 20,000 x 200 | - | 138x | 106x | 116x |
|
|
984
|
+
|
|
985
|
+
On one core the package is ahead everywhere but Ridge, where both
|
|
986
|
+
sides are one symmetric rank-k update of the same size and sklearnex
|
|
987
|
+
is 0.180 s to 0.219 s. With 20 cores both sides use every core: the
|
|
988
|
+
package's loops run under OpenMP and its products in MKL's threads,
|
|
989
|
+
so it is ahead on Ridge (the Gram by dsyrk and a Cholesky solve), PCA,
|
|
990
|
+
the device build of logistic regression and RFE (an algorithm that
|
|
991
|
+
does less work: the Gram once, the Cholesky factor downdated per drop,
|
|
992
|
+
where scikit-learn refits), level on logistic regression on the CPU,
|
|
993
|
+
and behind on the SVMs: SMO takes thousands of short steps, each
|
|
994
|
+
waiting on the last, where sklearnex's solver takes 7 to 10 long ones. sklearnex is
|
|
916
995
|
not scikit-learn's arithmetic: its logistic regression takes 12
|
|
917
996
|
iterations to scikit-learn's 6 and its coefficients differ from
|
|
918
997
|
scikit-learn's by 6e-3, where the package's agree to 4e-15; its SVMs
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "scm2cpp-lasso"
|
|
7
|
-
version = "0.
|
|
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"
|
|
7
|
+
version = "0.16.0"
|
|
8
|
+
description = "scikit-learn estimators (lasso, elastic net, ridge, RidgeCV, ARD, SVM, SVR, logistic regression, GLMs, Huber, Theil-Sen, F-score selection, RFE, NMF, Gaussian mixture, FastICA, PCA, KernelPCA, 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"
|
|
11
11
|
license = {text = "MIT"}
|
|
@@ -41,6 +41,32 @@ kernel = Extension(
|
|
|
41
41
|
)
|
|
42
42
|
|
|
43
43
|
|
|
44
|
+
def mkl_flags():
|
|
45
|
+
"""Intel MKL where pkg-config knows it (mkl-dynamic-lp64-gomp: the GNU
|
|
46
|
+
OpenMP threading layer, so MKL's threads and the -P omp loops share
|
|
47
|
+
one libgomp) and a test program links, with SCM2CPP_MKL defined for
|
|
48
|
+
scm2cpp-blas.hpp's headers; else None. SCM2CPP_NO_MKL=1 skips it."""
|
|
49
|
+
if os.name == "nt" or os.environ.get("SCM2CPP_NO_MKL"):
|
|
50
|
+
return None
|
|
51
|
+
try:
|
|
52
|
+
pc = subprocess.run(["pkg-config", "--cflags", "--libs", "mkl-dynamic-lp64-gomp"],
|
|
53
|
+
capture_output=True, text=True, check=True)
|
|
54
|
+
except (subprocess.CalledProcessError, OSError):
|
|
55
|
+
return None
|
|
56
|
+
flags = pc.stdout.split() + ["-DSCM2CPP_MKL"]
|
|
57
|
+
probe = "#include <mkl_cblas.h>\n#include <mkl_lapacke.h>\nint main(){double a=1,b=0,w=0;" \
|
|
58
|
+
"cblas_dgemv(CblasRowMajor,CblasNoTrans,1,1,1.0,&a,1,&a,1,0.0,&b,1);" \
|
|
59
|
+
"LAPACKE_dsyevd(LAPACK_ROW_MAJOR,'V','U',1,&a,1,&w);" \
|
|
60
|
+
"return b==1?0:1;}\n"
|
|
61
|
+
cxx = os.environ.get("CXX", "c++")
|
|
62
|
+
try:
|
|
63
|
+
r = subprocess.run([cxx, "-x", "c++", "-", "-o", os.devnull] + flags,
|
|
64
|
+
input=probe, capture_output=True, text=True)
|
|
65
|
+
except OSError:
|
|
66
|
+
return None
|
|
67
|
+
return flags if r.returncode == 0 else None
|
|
68
|
+
|
|
69
|
+
|
|
44
70
|
def cblas_flags():
|
|
45
71
|
"""How to compile and link against a CBLAS here, or None.
|
|
46
72
|
|
|
@@ -49,9 +75,25 @@ def cblas_flags():
|
|
|
49
75
|
kernel's BLAS variant needs cblas.h and a library; without them the
|
|
50
76
|
loop variant is built instead (same answers, the Gram as a scalar
|
|
51
77
|
loop nest).
|
|
78
|
+
|
|
79
|
+
On Windows there is no pkg-config and no system OpenBLAS: the
|
|
80
|
+
directory of an unpacked OpenBLAS release (include/cblas.h,
|
|
81
|
+
lib/libopenblas.lib) is named by SCM2CPP_OPENBLAS_DIR, and the
|
|
82
|
+
wheel build bundles its bin/libopenblas.dll (delvewheel).
|
|
52
83
|
"""
|
|
53
|
-
if os.
|
|
84
|
+
if os.environ.get("SCM2CPP_NO_BLAS"):
|
|
54
85
|
return None
|
|
86
|
+
mkl = mkl_flags()
|
|
87
|
+
if mkl is not None:
|
|
88
|
+
return mkl
|
|
89
|
+
if os.name == "nt":
|
|
90
|
+
root = os.environ.get("SCM2CPP_OPENBLAS_DIR")
|
|
91
|
+
if not root:
|
|
92
|
+
return None
|
|
93
|
+
inc, lib = Path(root) / "include", Path(root) / "lib" / "libopenblas.lib"
|
|
94
|
+
if not ((inc / "cblas.h").exists() and lib.exists()):
|
|
95
|
+
return None
|
|
96
|
+
return ["-I" + str(inc), str(lib)]
|
|
55
97
|
cxx = os.environ.get("CXX", "c++")
|
|
56
98
|
candidates = []
|
|
57
99
|
try:
|
|
@@ -61,8 +103,11 @@ def cblas_flags():
|
|
|
61
103
|
except (subprocess.CalledProcessError, OSError):
|
|
62
104
|
pass
|
|
63
105
|
candidates.append(["-lopenblas"])
|
|
64
|
-
|
|
106
|
+
# the BLAS variants call LAPACKE too (lapack-syevd! for sym-eigen),
|
|
107
|
+
# so a CBLAS without LAPACKE counts as none
|
|
108
|
+
probe = "#include <cblas.h>\n#include <lapacke.h>\nint main(){double a=1,b=0,w=0;" \
|
|
65
109
|
"cblas_dgemv(CblasRowMajor,CblasNoTrans,1,1,1.0,&a,1,&a,1,0.0,&b,1);" \
|
|
110
|
+
"LAPACKE_dsyevd(LAPACK_ROW_MAJOR,'V','U',1,&a,1,&w);" \
|
|
66
111
|
"return b==1?0:1;}\n"
|
|
67
112
|
for flags in candidates:
|
|
68
113
|
try:
|
|
@@ -75,6 +120,32 @@ def cblas_flags():
|
|
|
75
120
|
return None
|
|
76
121
|
|
|
77
122
|
|
|
123
|
+
def openmp_flags():
|
|
124
|
+
"""-fopenmp where the compiler takes the directives the translator
|
|
125
|
+
writes (-P omp: parallel for, reduction, an if clause on the work),
|
|
126
|
+
else nothing -- the same sources then compile serial. Not on
|
|
127
|
+
Windows (MSVC's OpenMP needs vcomp140.dll beside the wheel) nor
|
|
128
|
+
with SCM2CPP_NO_OPENMP set. OMP_NUM_THREADS sets the threads at
|
|
129
|
+
run time, all cores by default.
|
|
130
|
+
"""
|
|
131
|
+
if os.name == "nt" or os.environ.get("SCM2CPP_NO_OPENMP"):
|
|
132
|
+
return []
|
|
133
|
+
cxx = os.environ.get("CXX", "c++")
|
|
134
|
+
probe = "int main(){double s=0;int n=4;\n" \
|
|
135
|
+
"#pragma omp parallel for reduction(+:s) if(n > 1)\n" \
|
|
136
|
+
"for(int i = 0 ; i < n ; i = (i+1)) s = (s+i);\n" \
|
|
137
|
+
"return s==6?0:1;}\n"
|
|
138
|
+
try:
|
|
139
|
+
r = subprocess.run([cxx, "-fopenmp", "-x", "c++", "-", "-o", os.devnull],
|
|
140
|
+
input=probe, capture_output=True, text=True)
|
|
141
|
+
except OSError:
|
|
142
|
+
return []
|
|
143
|
+
return ["-fopenmp"] if r.returncode == 0 else []
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
_omp = openmp_flags()
|
|
147
|
+
|
|
148
|
+
|
|
78
149
|
# The derived kernel: lasso-design.scm, the plain residual-form
|
|
79
150
|
# coordinate descent over X as numpy holds it, turned into the Gram form by the translator
|
|
80
151
|
# (--derive) with its products as CBLAS calls (--blas). Which variant
|
|
@@ -185,7 +256,8 @@ classifiers = Extension(
|
|
|
185
256
|
"scm2cpp_lasso._classifiers",
|
|
186
257
|
sources=[GEN_REL + "/classifiers_capi.cpp"],
|
|
187
258
|
include_dirs=[GEN_REL],
|
|
188
|
-
extra_compile_args=_cls_flags,
|
|
259
|
+
extra_compile_args=_cls_flags + _omp,
|
|
260
|
+
extra_link_args=_omp,
|
|
189
261
|
)
|
|
190
262
|
|
|
191
263
|
# The derived NMF, in the same variants as the derived lasso.
|
|
@@ -194,7 +266,7 @@ if _blas is not None:
|
|
|
194
266
|
"scm2cpp_lasso._nmf_blas",
|
|
195
267
|
sources=[GEN_REL + "/nmf_blas/nmf_cd_capi.cpp"],
|
|
196
268
|
include_dirs=[GEN_REL + "/nmf_blas", GEN_REL],
|
|
197
|
-
extra_compile_args=
|
|
269
|
+
extra_compile_args=_cls_flags
|
|
198
270
|
+ [f for f in _blas if f.startswith("-I") or f.startswith("-D")],
|
|
199
271
|
extra_link_args=[f for f in _blas if not f.startswith("-I")
|
|
200
272
|
and not f.startswith("-D")],
|
|
@@ -215,17 +287,18 @@ if _blas is not None:
|
|
|
215
287
|
"scm2cpp_lasso._gramops_blas",
|
|
216
288
|
sources=[GEN_REL + "/gramops_blas/gramops_capi.cpp"],
|
|
217
289
|
include_dirs=[GEN_REL + "/gramops_blas", GEN_REL],
|
|
218
|
-
extra_compile_args=
|
|
290
|
+
extra_compile_args=_cls_flags + _omp
|
|
219
291
|
+ [f for f in _blas if f.startswith("-I") or f.startswith("-D")],
|
|
220
|
-
extra_link_args=[f for f in _blas if not f.startswith("-I")
|
|
221
|
-
|
|
292
|
+
extra_link_args=_omp + [f for f in _blas if not f.startswith("-I")
|
|
293
|
+
and not f.startswith("-D")],
|
|
222
294
|
)
|
|
223
295
|
else:
|
|
224
296
|
gramops = Extension(
|
|
225
297
|
"scm2cpp_lasso._gramops_loops",
|
|
226
298
|
sources=[GEN_REL + "/gramops_loops/gramops_capi.cpp"],
|
|
227
299
|
include_dirs=[GEN_REL + "/gramops_loops", GEN_REL],
|
|
228
|
-
extra_compile_args=_cls_flags,
|
|
300
|
+
extra_compile_args=_cls_flags + _omp,
|
|
301
|
+
extra_link_args=_omp,
|
|
229
302
|
)
|
|
230
303
|
|
|
231
304
|
_cuda = cuda_home()
|
|
@@ -245,7 +318,7 @@ if _cuda is not None:
|
|
|
245
318
|
include_dirs=[GEN_REL + "/nmf_cublas", GEN_REL, str(_cuda / "include")],
|
|
246
319
|
library_dirs=[str(_cuda / "lib64"), str(_cuda / "lib")],
|
|
247
320
|
libraries=["cublas", "cudart"],
|
|
248
|
-
extra_compile_args=
|
|
321
|
+
extra_compile_args=_cls_flags,
|
|
249
322
|
))
|
|
250
323
|
|
|
251
324
|
extensions.append(Extension(
|
|
@@ -254,7 +327,7 @@ if _cuda is not None:
|
|
|
254
327
|
include_dirs=[GEN_REL + "/accel_cublas", GEN_REL, str(_cuda / "include")],
|
|
255
328
|
library_dirs=[str(_cuda / "lib64"), str(_cuda / "lib")],
|
|
256
329
|
libraries=["cublas", "cudart"],
|
|
257
|
-
extra_compile_args=
|
|
330
|
+
extra_compile_args=_cls_flags,
|
|
258
331
|
))
|
|
259
332
|
|
|
260
333
|
# A wheel is tagged py3-none-<platform>: the extensions are shared
|