scm2cpp-lasso 0.15.1__tar.gz → 0.17.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.1/src/scm2cpp_lasso.egg-info → scm2cpp_lasso-0.17.0}/PKG-INFO +147 -19
- scm2cpp_lasso-0.15.1/PKG-INFO → scm2cpp_lasso-0.17.0/README.md +145 -35
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/pyproject.toml +2 -2
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/setup.py +83 -10
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/__init__.py +512 -23
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/_loader_accel_cublas.py +19 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/_loader_classifiers.py +27 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/_loader_gpu_cuda.py +19 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/_loader_gramops_blas.py +157 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/_loader_gramops_loops.py +176 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/accel_cublas/accel.cpp +2 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/accel_cublas/accel.hpp +103 -17
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/accel_cublas/accel_capi.cpp +6 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/classifiers.cpp +3 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/classifiers.hpp +1284 -364
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/classifiers_capi.cpp +9 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/gpu_cuda/gpu.cpp +2 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/gpu_cuda/gpu.hpp +377 -246
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/gpu_cuda/gpu_capi.cpp +19 -0
- {scm2cpp_lasso-0.15.1/src/scm2cpp_lasso/_generated/gramops_loops → scm2cpp_lasso-0.17.0/src/scm2cpp_lasso/_generated/gramops_blas}/gramops.cpp +20 -0
- scm2cpp_lasso-0.17.0/src/scm2cpp_lasso/_generated/gramops_blas/gramops.hpp +5219 -0
- {scm2cpp_lasso-0.15.1/src/scm2cpp_lasso/_generated/gramops_loops → scm2cpp_lasso-0.17.0/src/scm2cpp_lasso/_generated/gramops_blas}/gramops_capi.cpp +54 -0
- {scm2cpp_lasso-0.15.1/src/scm2cpp_lasso/_generated/gramops_blas → scm2cpp_lasso-0.17.0/src/scm2cpp_lasso/_generated/gramops_loops}/gramops.cpp +20 -0
- scm2cpp_lasso-0.17.0/src/scm2cpp_lasso/_generated/gramops_loops/gramops.hpp +6130 -0
- {scm2cpp_lasso-0.15.1/src/scm2cpp_lasso/_generated/gramops_blas → scm2cpp_lasso-0.17.0/src/scm2cpp_lasso/_generated/gramops_loops}/gramops_capi.cpp +60 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/scm2cpp-blas.hpp +72 -0
- scm2cpp_lasso-0.15.1/README.md → scm2cpp_lasso-0.17.0/src/scm2cpp_lasso.egg-info/PKG-INFO +163 -17
- scm2cpp_lasso-0.15.1/src/scm2cpp_lasso/_generated/gramops_blas/gramops.hpp +0 -3033
- scm2cpp_lasso-0.15.1/src/scm2cpp_lasso/_generated/gramops_loops/gramops.hpp +0 -3302
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/setup.cfg +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_compat.py +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/__init__.py +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/_loader.py +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/_loader_derived_blas.py +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/_loader_derived_cublas.py +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/_loader_derived_loops.py +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/_loader_nmf_blas.py +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/_loader_nmf_cublas.py +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/_loader_nmf_loops.py +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/derived_blas/lasso_design.cpp +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/derived_blas/lasso_design.hpp +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/derived_blas/lasso_design_capi.cpp +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/derived_cublas/lasso_design.cpp +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/derived_cublas/lasso_design.hpp +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/derived_cublas/lasso_design_capi.cpp +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/derived_loops/lasso_design.cpp +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/derived_loops/lasso_design.hpp +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/derived_loops/lasso_design_capi.cpp +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/lasso_cov.cpp +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/lasso_cov.hpp +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/lasso_cov_capi.cpp +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/nmf_blas/nmf_cd.cpp +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/nmf_blas/nmf_cd.hpp +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/nmf_blas/nmf_cd_capi.cpp +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/nmf_cublas/nmf_cd.cpp +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/nmf_cublas/nmf_cd.hpp +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/nmf_cublas/nmf_cd_capi.cpp +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/nmf_loops/nmf_cd.cpp +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/nmf_loops/nmf_cd.hpp +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/nmf_loops/nmf_cd_capi.cpp +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/scm2cpp-cublas.hpp +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/scm2cpp-cuda.hpp +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_generated/scm2cpp.hpp +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/_libfind.py +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso/batch_capi.cu +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso.egg-info/SOURCES.txt +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso.egg-info/dependency_links.txt +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.0}/src/scm2cpp_lasso.egg-info/requires.txt +0 -0
- {scm2cpp_lasso-0.15.1 → scm2cpp_lasso-0.17.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.17.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, LDA, 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.
|
|
@@ -191,7 +201,8 @@ package picks the fastest one present.
|
|
|
191
201
|
|---|---|---|---|---|
|
|
192
202
|
| `GramLasso` | Lasso(precompute=True), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | no |
|
|
193
203
|
| `GramElasticNet` | ElasticNet(precompute=True), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | no |
|
|
194
|
-
| `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 |
|
|
195
206
|
| `GramMultiTaskLasso` | MultiTaskLasso, fit(X, Y) with the intercept | `lasso-cov.scm` | CPU | no |
|
|
196
207
|
| `GramMultiTaskElasticNet` | MultiTaskElasticNet, fit(X, Y) with the intercept | `lasso-cov.scm` | CPU | no |
|
|
197
208
|
| `GramGroupLasso` | (none; skglm GroupLasso), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | - |
|
|
@@ -236,6 +247,8 @@ package picks the fastest one present.
|
|
|
236
247
|
| `EMGaussianMixture` | GaussianMixture(covariance_type='full') | `gmm.scm` | loops / BLAS / --cuda | no |
|
|
237
248
|
| `ParallelFastICA` | FastICA | `fastica.scm` | loops / BLAS / --cuda | no |
|
|
238
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 |
|
|
251
|
+
| `GramLDA` | LinearDiscriminantAnalysis (svd, lsqr, eigen) | `lda.scm` | loops / BLAS | no |
|
|
239
252
|
| `f_regression, f_classif` | f_regression, f_classif | `fscore.scm` | loops / BLAS / cuBLAS | no |
|
|
240
253
|
| `SelectKBest, SelectPercentile` | SelectKBest, SelectPercentile | `fscore.scm` | loops / BLAS / cuBLAS | no |
|
|
241
254
|
| `GramRFE` | RFE(Ridge), RFE(LinearRegression) | `rfe.scm` | loops / BLAS / cuBLAS | inside: Ridge |
|
|
@@ -558,6 +571,116 @@ on the GPU: the ridge is one eigendecomposition and the elastic net's
|
|
|
558
571
|
sweeps are the lasso's; `fit_path_batch(l1_ratio=..)` is the GPU
|
|
559
572
|
grid for the elastic net.
|
|
560
573
|
|
|
574
|
+
## Leave-one-out ridge: `GramRidgeCV`
|
|
575
|
+
|
|
576
|
+
`GramRidgeCV` is scikit-learn's `RidgeCV` with `cv=None`: the alpha
|
|
577
|
+
whose leave-one-out mean squared error is least, without a refit per
|
|
578
|
+
row. With the centred design Xc and H = Xc (Xc'Xc + alpha I)^-1 Xc'
|
|
579
|
+
(plus 1/n for the intercept), the leave-one-out residual is
|
|
580
|
+
(y - yhat) / (1 - diag H). One eigenproblem gives H = Z diag(1 /
|
|
581
|
+
(lam + alpha)) Z' for every alpha: Z = Xc V from Xc'Xc = V diag(lam) V'
|
|
582
|
+
when n > p (X'X by dsyrk), Z = Q diag(sqrt mu) from Xc Xc' = Q diag(mu)
|
|
583
|
+
Q' otherwise. Then diag H and yhat at all the alphas are two products,
|
|
584
|
+
(Z o Z) W and Z (R o W) with W[k, a] = 1 / (lam_k + alpha_a): the
|
|
585
|
+
alphas cost one dgemm, where scikit-learn loops over them in numpy
|
|
586
|
+
(`examples/kernel-only/ridge-gcv.scm`; the eigenproblem is LAPACK's
|
|
587
|
+
dsyevd in the BLAS build).
|
|
588
|
+
|
|
589
|
+
```python
|
|
590
|
+
from scm2cpp_lasso import GramRidgeCV
|
|
591
|
+
m = GramRidgeCV(alphas=np.logspace(-2, 4, 50)).fit(X, y)
|
|
592
|
+
m.alpha_, m.best_score_, m.coef_, m.intercept_
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
It takes `fit_intercept`, `gcv_mode` (`"svd"` / `"eigen"` choose the
|
|
596
|
+
two Zs), `store_cv_results` and `alpha_per_target`, y one target or
|
|
597
|
+
several; `cv` and `scoring` must stay None. The chosen alpha is
|
|
598
|
+
scikit-learn's, the coefficients agree to 1e-12 and `cv_results_` to
|
|
599
|
+
1e-7 relative (the wide case near interpolation). Measured
|
|
600
|
+
(`bench/eigen-compare.py`, the MKL build, best of three):
|
|
601
|
+
|
|
602
|
+
| case | scikit-learn `RidgeCV` | `GramRidgeCV` | ratio, one core | ratio, 20 cores |
|
|
603
|
+
|---|---|---|---|---|
|
|
604
|
+
| 200,000 x 200, 3 alphas | 2.04 s | 0.80 s | 2.6x | 3.6x |
|
|
605
|
+
| 200,000 x 200, 50 alphas | 26.6 s | 0.96 s | 27.7x | 29.0x |
|
|
606
|
+
| 20,000 x 500, 50 alphas | 9.64 s | 0.33 s | 28.9x | 29.2x |
|
|
607
|
+
| 2,000 x 5,000, 50 alphas | 3.16 s | 1.47 s | 2.1x | 4.5x |
|
|
608
|
+
|
|
609
|
+
## Kernel PCA: `GramKernelPCA`
|
|
610
|
+
|
|
611
|
+
`GramKernelPCA` is scikit-learn's `KernelPCA`: the kernel matrix
|
|
612
|
+
(linear, rbf, poly, sigmoid, cosine or precomputed), centred in feature
|
|
613
|
+
space, its largest eigenpairs with svd_flip's signs
|
|
614
|
+
(`examples/kernel-only/kernel-pca.scm`). Where the eigenpairs come from
|
|
615
|
+
is what differs:
|
|
616
|
+
|
|
617
|
+
- fewer than 10 components of more than 200 rows, where scikit-learn
|
|
618
|
+
takes ARPACK (Lanczos, one matrix-vector product at a time): subspace
|
|
619
|
+
iteration on c + 25 columns, so each step is one dgemm, stopped when
|
|
620
|
+
every residual |K u - lam u| is under 1e-12 of the largest value
|
|
621
|
+
(falling back to the dense route if it is not reached);
|
|
622
|
+
- otherwise LAPACK's dsyevr over the wanted index range, which is what
|
|
623
|
+
scipy's `eigh(subset_by_index=..)` runs for scikit-learn;
|
|
624
|
+
- the linear kernel with no more components than features, n > p:
|
|
625
|
+
through Xc'Xc, p x p (the eigenvectors of Xc Xc' are Xc v / sqrt(lam)),
|
|
626
|
+
where scikit-learn forms the n x n kernel.
|
|
627
|
+
|
|
628
|
+
```python
|
|
629
|
+
from scm2cpp_lasso import GramKernelPCA
|
|
630
|
+
Z = GramKernelPCA(5, kernel="rbf", gamma=0.05).fit_transform(X)
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
Eigenvalues, eigenvectors and transforms agree with scikit-learn's to
|
|
634
|
+
1e-12 on every kernel; no `inverse_transform`. Measured
|
|
635
|
+
(`bench/eigen-compare.py`, the MKL build, best of three):
|
|
636
|
+
|
|
637
|
+
| case | scikit-learn `KernelPCA` | `GramKernelPCA` | ratio, one core | ratio, 20 cores |
|
|
638
|
+
|---|---|---|---|---|
|
|
639
|
+
| rbf, 3,000 x 20, 5 components | 1.11 s (ARPACK) | 0.35 s (subspace) | 3.2x | 3.0x |
|
|
640
|
+
| rbf, 6,000 x 20, 5 components | 4.63 s (ARPACK) | 1.33 s (subspace) | 3.5x | 4.2x |
|
|
641
|
+
| rbf, 3,000 x 20, 50 components | 3.48 s (dense) | 3.63 s (dense) | 1.0x | 1.2x |
|
|
642
|
+
| 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 |
|
|
643
|
+
|
|
644
|
+
The linear row is the algorithm (an n x n problem against a p x p one),
|
|
645
|
+
not the code.
|
|
646
|
+
|
|
647
|
+
## Linear discriminant analysis: `GramLDA`
|
|
648
|
+
|
|
649
|
+
`GramLDA` is scikit-learn's `LinearDiscriminantAnalysis` with the n x p
|
|
650
|
+
work moved to the Gram matrix (`examples/kernel-only/lda.scm`): one pass
|
|
651
|
+
gives the class means and X'X (dsyrk), and the within-class covariance
|
|
652
|
+
is Sw = (X'X - sum_k n_k mu_k mu_k') / n. Each solver then runs its own
|
|
653
|
+
steps on p x p matrices:
|
|
654
|
+
|
|
655
|
+
- `"svd"` (the default) takes the SVD of the standardised within-class
|
|
656
|
+
design Xc D^-1 / sqrt(n), n x p. Its right singular vectors and values
|
|
657
|
+
are the eigenvectors and square roots of the eigenvalues of
|
|
658
|
+
D^-1 Sw D^-1, so one p x p eigenproblem (dsyevd) replaces it; the
|
|
659
|
+
second, classes x rank SVD and the coefficients follow as in
|
|
660
|
+
scikit-learn;
|
|
661
|
+
- `"lsqr"` solves Sw W = M' by Cholesky;
|
|
662
|
+
- `"eigen"` solves Sb v = lam Sw v (`sym-eigen-gen`, LAPACK's dsygvd).
|
|
663
|
+
|
|
664
|
+
```python
|
|
665
|
+
from scm2cpp_lasso import GramLDA
|
|
666
|
+
clf = GramLDA().fit(X, y) # solver="svd"
|
|
667
|
+
clf.predict(X), clf.transform(X)
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
`shrinkage` takes None or a float (it is linear in Sw, so the Gram route
|
|
671
|
+
keeps it), not "auto"; custom `priors` form a Gram per class. The
|
|
672
|
+
coefficients, intercepts, probabilities and explained variance agree
|
|
673
|
+
with scikit-learn's to 1e-12, the predictions exactly, and `transform`
|
|
674
|
+
up to the sign of each column. Measured (`bench/eigen-compare.py`, the
|
|
675
|
+
MKL build, best of three):
|
|
676
|
+
|
|
677
|
+
| case | scikit-learn | `GramLDA` | ratio, one core | ratio, 20 cores |
|
|
678
|
+
|---|---|---|---|---|
|
|
679
|
+
| svd (default), 200,000 x 100, 5 classes | 4.80 s | 0.092 s | 52x | 35x |
|
|
680
|
+
| svd, 50,000 x 500, 10 classes | 4.07 s | 0.25 s | 16x | 14x |
|
|
681
|
+
| lsqr, 200,000 x 100, 5 classes | 0.42 s | 0.089 s | 4.7x | 8.9x |
|
|
682
|
+
| eigen, 200,000 x 100, 5 classes | 0.56 s | 0.094 s | 6.0x | 12x |
|
|
683
|
+
|
|
561
684
|
## Bayesian ridge
|
|
562
685
|
|
|
563
686
|
`CovBayesianRidge` is scikit-learn's `BayesianRidge` -- the same model,
|
|
@@ -901,25 +1024,30 @@ times the ones with a counterpart here, sklearnex's estimators
|
|
|
901
1024
|
imported directly (not patched in) so that scikit-learn itself is
|
|
902
1025
|
timed in the same process. The ratios are scikit-learn's or
|
|
903
1026
|
sklearnex's time over the package's best (its CPU build, or the device
|
|
904
|
-
build where one answers)
|
|
905
|
-
|
|
1027
|
+
build where one answers). The package was built against Intel MKL
|
|
1028
|
+
(see the build section), its loops under OpenMP; the load average was
|
|
1029
|
+
about 2.3, so the times move by some percent.
|
|
906
1030
|
|
|
907
1031
|
| estimator, n x p | iterations (sk / sklearnex / ours) | vs scikit-learn, one core | vs sklearnex, one core | vs sklearnex, 20 cores |
|
|
908
1032
|
|---|---|---|---|---|
|
|
909
|
-
| LogisticRegression, 200,000 x 100 | 6 / 12 / 6 |
|
|
910
|
-
| Ridge (alpha 1), 200,000 x 200 | - |
|
|
911
|
-
| PCA (10 components), 100,000 x 50 | - | 1.
|
|
912
|
-
| PCA (10 components), 50,000 x 500 | - | 1.1x | 1.4x |
|
|
913
|
-
| SVC (rbf), 5,000 x 20 | 4,858 / 7 / 4,882 | 1.
|
|
914
|
-
| SVR (rbf), 5,000 x 20 | 12,821 / 10 / 12,824 | 1.
|
|
915
|
-
| RFE(Ridge), 20 of 200 kept, 20,000 x 200 | - |
|
|
916
|
-
|
|
917
|
-
On one core the package is ahead everywhere
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1033
|
+
| LogisticRegression, 200,000 x 100 | 6 / 12 / 6 | 6.8x (device) | 6.4x (device) | 1.8x (device); on the CPU 0.9x |
|
|
1034
|
+
| Ridge (alpha 1, no intercept), 200,000 x 200 | - | 1.7x | 0.8x | 2.3x |
|
|
1035
|
+
| PCA (10 components), 100,000 x 50 | - | 1.4x | 2.2x | 1.4x |
|
|
1036
|
+
| PCA (10 components), 50,000 x 500 | - | 1.1x | 1.4x | 1.6x (device); on the CPU 1.2x |
|
|
1037
|
+
| SVC (rbf), 5,000 x 20 | 4,858 / 7 / 4,882 | 1.4x | 1.6x | 0.5x |
|
|
1038
|
+
| SVR (rbf), 5,000 x 20 | 12,821 / 10 / 12,824 | 1.4x | 1.4x | 0.4x |
|
|
1039
|
+
| RFE(Ridge), 20 of 200 kept, 20,000 x 200 | - | 138x | 106x | 116x |
|
|
1040
|
+
|
|
1041
|
+
On one core the package is ahead everywhere but Ridge, where both
|
|
1042
|
+
sides are one symmetric rank-k update of the same size and sklearnex
|
|
1043
|
+
is 0.180 s to 0.219 s. With 20 cores both sides use every core: the
|
|
1044
|
+
package's loops run under OpenMP and its products in MKL's threads,
|
|
1045
|
+
so it is ahead on Ridge (the Gram by dsyrk and a Cholesky solve), PCA,
|
|
1046
|
+
the device build of logistic regression and RFE (an algorithm that
|
|
1047
|
+
does less work: the Gram once, the Cholesky factor downdated per drop,
|
|
1048
|
+
where scikit-learn refits), level on logistic regression on the CPU,
|
|
1049
|
+
and behind on the SVMs: SMO takes thousands of short steps, each
|
|
1050
|
+
waiting on the last, where sklearnex's solver takes 7 to 10 long ones. sklearnex is
|
|
923
1051
|
not scikit-learn's arithmetic: its logistic regression takes 12
|
|
924
1052
|
iterations to scikit-learn's 6 and its coefficients differ from
|
|
925
1053
|
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.1
|
|
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.
|
|
@@ -191,7 +183,8 @@ package picks the fastest one present.
|
|
|
191
183
|
|---|---|---|---|---|
|
|
192
184
|
| `GramLasso` | Lasso(precompute=True), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | no |
|
|
193
185
|
| `GramElasticNet` | ElasticNet(precompute=True), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | no |
|
|
194
|
-
| `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 |
|
|
195
188
|
| `GramMultiTaskLasso` | MultiTaskLasso, fit(X, Y) with the intercept | `lasso-cov.scm` | CPU | no |
|
|
196
189
|
| `GramMultiTaskElasticNet` | MultiTaskElasticNet, fit(X, Y) with the intercept | `lasso-cov.scm` | CPU | no |
|
|
197
190
|
| `GramGroupLasso` | (none; skglm GroupLasso), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | - |
|
|
@@ -236,6 +229,8 @@ package picks the fastest one present.
|
|
|
236
229
|
| `EMGaussianMixture` | GaussianMixture(covariance_type='full') | `gmm.scm` | loops / BLAS / --cuda | no |
|
|
237
230
|
| `ParallelFastICA` | FastICA | `fastica.scm` | loops / BLAS / --cuda | no |
|
|
238
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 |
|
|
233
|
+
| `GramLDA` | LinearDiscriminantAnalysis (svd, lsqr, eigen) | `lda.scm` | loops / BLAS | no |
|
|
239
234
|
| `f_regression, f_classif` | f_regression, f_classif | `fscore.scm` | loops / BLAS / cuBLAS | no |
|
|
240
235
|
| `SelectKBest, SelectPercentile` | SelectKBest, SelectPercentile | `fscore.scm` | loops / BLAS / cuBLAS | no |
|
|
241
236
|
| `GramRFE` | RFE(Ridge), RFE(LinearRegression) | `rfe.scm` | loops / BLAS / cuBLAS | inside: Ridge |
|
|
@@ -558,6 +553,116 @@ on the GPU: the ridge is one eigendecomposition and the elastic net's
|
|
|
558
553
|
sweeps are the lasso's; `fit_path_batch(l1_ratio=..)` is the GPU
|
|
559
554
|
grid for the elastic net.
|
|
560
555
|
|
|
556
|
+
## Leave-one-out ridge: `GramRidgeCV`
|
|
557
|
+
|
|
558
|
+
`GramRidgeCV` is scikit-learn's `RidgeCV` with `cv=None`: the alpha
|
|
559
|
+
whose leave-one-out mean squared error is least, without a refit per
|
|
560
|
+
row. With the centred design Xc and H = Xc (Xc'Xc + alpha I)^-1 Xc'
|
|
561
|
+
(plus 1/n for the intercept), the leave-one-out residual is
|
|
562
|
+
(y - yhat) / (1 - diag H). One eigenproblem gives H = Z diag(1 /
|
|
563
|
+
(lam + alpha)) Z' for every alpha: Z = Xc V from Xc'Xc = V diag(lam) V'
|
|
564
|
+
when n > p (X'X by dsyrk), Z = Q diag(sqrt mu) from Xc Xc' = Q diag(mu)
|
|
565
|
+
Q' otherwise. Then diag H and yhat at all the alphas are two products,
|
|
566
|
+
(Z o Z) W and Z (R o W) with W[k, a] = 1 / (lam_k + alpha_a): the
|
|
567
|
+
alphas cost one dgemm, where scikit-learn loops over them in numpy
|
|
568
|
+
(`examples/kernel-only/ridge-gcv.scm`; the eigenproblem is LAPACK's
|
|
569
|
+
dsyevd in the BLAS build).
|
|
570
|
+
|
|
571
|
+
```python
|
|
572
|
+
from scm2cpp_lasso import GramRidgeCV
|
|
573
|
+
m = GramRidgeCV(alphas=np.logspace(-2, 4, 50)).fit(X, y)
|
|
574
|
+
m.alpha_, m.best_score_, m.coef_, m.intercept_
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
It takes `fit_intercept`, `gcv_mode` (`"svd"` / `"eigen"` choose the
|
|
578
|
+
two Zs), `store_cv_results` and `alpha_per_target`, y one target or
|
|
579
|
+
several; `cv` and `scoring` must stay None. The chosen alpha is
|
|
580
|
+
scikit-learn's, the coefficients agree to 1e-12 and `cv_results_` to
|
|
581
|
+
1e-7 relative (the wide case near interpolation). Measured
|
|
582
|
+
(`bench/eigen-compare.py`, the MKL build, best of three):
|
|
583
|
+
|
|
584
|
+
| case | scikit-learn `RidgeCV` | `GramRidgeCV` | ratio, one core | ratio, 20 cores |
|
|
585
|
+
|---|---|---|---|---|
|
|
586
|
+
| 200,000 x 200, 3 alphas | 2.04 s | 0.80 s | 2.6x | 3.6x |
|
|
587
|
+
| 200,000 x 200, 50 alphas | 26.6 s | 0.96 s | 27.7x | 29.0x |
|
|
588
|
+
| 20,000 x 500, 50 alphas | 9.64 s | 0.33 s | 28.9x | 29.2x |
|
|
589
|
+
| 2,000 x 5,000, 50 alphas | 3.16 s | 1.47 s | 2.1x | 4.5x |
|
|
590
|
+
|
|
591
|
+
## Kernel PCA: `GramKernelPCA`
|
|
592
|
+
|
|
593
|
+
`GramKernelPCA` is scikit-learn's `KernelPCA`: the kernel matrix
|
|
594
|
+
(linear, rbf, poly, sigmoid, cosine or precomputed), centred in feature
|
|
595
|
+
space, its largest eigenpairs with svd_flip's signs
|
|
596
|
+
(`examples/kernel-only/kernel-pca.scm`). Where the eigenpairs come from
|
|
597
|
+
is what differs:
|
|
598
|
+
|
|
599
|
+
- fewer than 10 components of more than 200 rows, where scikit-learn
|
|
600
|
+
takes ARPACK (Lanczos, one matrix-vector product at a time): subspace
|
|
601
|
+
iteration on c + 25 columns, so each step is one dgemm, stopped when
|
|
602
|
+
every residual |K u - lam u| is under 1e-12 of the largest value
|
|
603
|
+
(falling back to the dense route if it is not reached);
|
|
604
|
+
- otherwise LAPACK's dsyevr over the wanted index range, which is what
|
|
605
|
+
scipy's `eigh(subset_by_index=..)` runs for scikit-learn;
|
|
606
|
+
- the linear kernel with no more components than features, n > p:
|
|
607
|
+
through Xc'Xc, p x p (the eigenvectors of Xc Xc' are Xc v / sqrt(lam)),
|
|
608
|
+
where scikit-learn forms the n x n kernel.
|
|
609
|
+
|
|
610
|
+
```python
|
|
611
|
+
from scm2cpp_lasso import GramKernelPCA
|
|
612
|
+
Z = GramKernelPCA(5, kernel="rbf", gamma=0.05).fit_transform(X)
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
Eigenvalues, eigenvectors and transforms agree with scikit-learn's to
|
|
616
|
+
1e-12 on every kernel; no `inverse_transform`. Measured
|
|
617
|
+
(`bench/eigen-compare.py`, the MKL build, best of three):
|
|
618
|
+
|
|
619
|
+
| case | scikit-learn `KernelPCA` | `GramKernelPCA` | ratio, one core | ratio, 20 cores |
|
|
620
|
+
|---|---|---|---|---|
|
|
621
|
+
| rbf, 3,000 x 20, 5 components | 1.11 s (ARPACK) | 0.35 s (subspace) | 3.2x | 3.0x |
|
|
622
|
+
| rbf, 6,000 x 20, 5 components | 4.63 s (ARPACK) | 1.33 s (subspace) | 3.5x | 4.2x |
|
|
623
|
+
| rbf, 3,000 x 20, 50 components | 3.48 s (dense) | 3.63 s (dense) | 1.0x | 1.2x |
|
|
624
|
+
| 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 |
|
|
625
|
+
|
|
626
|
+
The linear row is the algorithm (an n x n problem against a p x p one),
|
|
627
|
+
not the code.
|
|
628
|
+
|
|
629
|
+
## Linear discriminant analysis: `GramLDA`
|
|
630
|
+
|
|
631
|
+
`GramLDA` is scikit-learn's `LinearDiscriminantAnalysis` with the n x p
|
|
632
|
+
work moved to the Gram matrix (`examples/kernel-only/lda.scm`): one pass
|
|
633
|
+
gives the class means and X'X (dsyrk), and the within-class covariance
|
|
634
|
+
is Sw = (X'X - sum_k n_k mu_k mu_k') / n. Each solver then runs its own
|
|
635
|
+
steps on p x p matrices:
|
|
636
|
+
|
|
637
|
+
- `"svd"` (the default) takes the SVD of the standardised within-class
|
|
638
|
+
design Xc D^-1 / sqrt(n), n x p. Its right singular vectors and values
|
|
639
|
+
are the eigenvectors and square roots of the eigenvalues of
|
|
640
|
+
D^-1 Sw D^-1, so one p x p eigenproblem (dsyevd) replaces it; the
|
|
641
|
+
second, classes x rank SVD and the coefficients follow as in
|
|
642
|
+
scikit-learn;
|
|
643
|
+
- `"lsqr"` solves Sw W = M' by Cholesky;
|
|
644
|
+
- `"eigen"` solves Sb v = lam Sw v (`sym-eigen-gen`, LAPACK's dsygvd).
|
|
645
|
+
|
|
646
|
+
```python
|
|
647
|
+
from scm2cpp_lasso import GramLDA
|
|
648
|
+
clf = GramLDA().fit(X, y) # solver="svd"
|
|
649
|
+
clf.predict(X), clf.transform(X)
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
`shrinkage` takes None or a float (it is linear in Sw, so the Gram route
|
|
653
|
+
keeps it), not "auto"; custom `priors` form a Gram per class. The
|
|
654
|
+
coefficients, intercepts, probabilities and explained variance agree
|
|
655
|
+
with scikit-learn's to 1e-12, the predictions exactly, and `transform`
|
|
656
|
+
up to the sign of each column. Measured (`bench/eigen-compare.py`, the
|
|
657
|
+
MKL build, best of three):
|
|
658
|
+
|
|
659
|
+
| case | scikit-learn | `GramLDA` | ratio, one core | ratio, 20 cores |
|
|
660
|
+
|---|---|---|---|---|
|
|
661
|
+
| svd (default), 200,000 x 100, 5 classes | 4.80 s | 0.092 s | 52x | 35x |
|
|
662
|
+
| svd, 50,000 x 500, 10 classes | 4.07 s | 0.25 s | 16x | 14x |
|
|
663
|
+
| lsqr, 200,000 x 100, 5 classes | 0.42 s | 0.089 s | 4.7x | 8.9x |
|
|
664
|
+
| eigen, 200,000 x 100, 5 classes | 0.56 s | 0.094 s | 6.0x | 12x |
|
|
665
|
+
|
|
561
666
|
## Bayesian ridge
|
|
562
667
|
|
|
563
668
|
`CovBayesianRidge` is scikit-learn's `BayesianRidge` -- the same model,
|
|
@@ -901,25 +1006,30 @@ times the ones with a counterpart here, sklearnex's estimators
|
|
|
901
1006
|
imported directly (not patched in) so that scikit-learn itself is
|
|
902
1007
|
timed in the same process. The ratios are scikit-learn's or
|
|
903
1008
|
sklearnex's time over the package's best (its CPU build, or the device
|
|
904
|
-
build where one answers)
|
|
905
|
-
|
|
1009
|
+
build where one answers). The package was built against Intel MKL
|
|
1010
|
+
(see the build section), its loops under OpenMP; the load average was
|
|
1011
|
+
about 2.3, so the times move by some percent.
|
|
906
1012
|
|
|
907
1013
|
| estimator, n x p | iterations (sk / sklearnex / ours) | vs scikit-learn, one core | vs sklearnex, one core | vs sklearnex, 20 cores |
|
|
908
1014
|
|---|---|---|---|---|
|
|
909
|
-
| LogisticRegression, 200,000 x 100 | 6 / 12 / 6 |
|
|
910
|
-
| Ridge (alpha 1), 200,000 x 200 | - |
|
|
911
|
-
| PCA (10 components), 100,000 x 50 | - | 1.
|
|
912
|
-
| PCA (10 components), 50,000 x 500 | - | 1.1x | 1.4x |
|
|
913
|
-
| SVC (rbf), 5,000 x 20 | 4,858 / 7 / 4,882 | 1.
|
|
914
|
-
| SVR (rbf), 5,000 x 20 | 12,821 / 10 / 12,824 | 1.
|
|
915
|
-
| RFE(Ridge), 20 of 200 kept, 20,000 x 200 | - |
|
|
916
|
-
|
|
917
|
-
On one core the package is ahead everywhere
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1015
|
+
| LogisticRegression, 200,000 x 100 | 6 / 12 / 6 | 6.8x (device) | 6.4x (device) | 1.8x (device); on the CPU 0.9x |
|
|
1016
|
+
| Ridge (alpha 1, no intercept), 200,000 x 200 | - | 1.7x | 0.8x | 2.3x |
|
|
1017
|
+
| PCA (10 components), 100,000 x 50 | - | 1.4x | 2.2x | 1.4x |
|
|
1018
|
+
| PCA (10 components), 50,000 x 500 | - | 1.1x | 1.4x | 1.6x (device); on the CPU 1.2x |
|
|
1019
|
+
| SVC (rbf), 5,000 x 20 | 4,858 / 7 / 4,882 | 1.4x | 1.6x | 0.5x |
|
|
1020
|
+
| SVR (rbf), 5,000 x 20 | 12,821 / 10 / 12,824 | 1.4x | 1.4x | 0.4x |
|
|
1021
|
+
| RFE(Ridge), 20 of 200 kept, 20,000 x 200 | - | 138x | 106x | 116x |
|
|
1022
|
+
|
|
1023
|
+
On one core the package is ahead everywhere but Ridge, where both
|
|
1024
|
+
sides are one symmetric rank-k update of the same size and sklearnex
|
|
1025
|
+
is 0.180 s to 0.219 s. With 20 cores both sides use every core: the
|
|
1026
|
+
package's loops run under OpenMP and its products in MKL's threads,
|
|
1027
|
+
so it is ahead on Ridge (the Gram by dsyrk and a Cholesky solve), PCA,
|
|
1028
|
+
the device build of logistic regression and RFE (an algorithm that
|
|
1029
|
+
does less work: the Gram once, the Cholesky factor downdated per drop,
|
|
1030
|
+
where scikit-learn refits), level on logistic regression on the CPU,
|
|
1031
|
+
and behind on the SVMs: SMO takes thousands of short steps, each
|
|
1032
|
+
waiting on the last, where sklearnex's solver takes 7 to 10 long ones. sklearnex is
|
|
923
1033
|
not scikit-learn's arithmetic: its logistic regression takes 12
|
|
924
1034
|
iterations to scikit-learn's 6 and its coefficients differ from
|
|
925
1035
|
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.17.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, LDA, 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
|