scm2cpp-learn 0.18.0__tar.gz → 0.20.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.
Files changed (67) hide show
  1. {scm2cpp_learn-0.18.0/src/scm2cpp_learn.egg-info → scm2cpp_learn-0.20.0}/PKG-INFO +146 -6
  2. scm2cpp_learn-0.18.0/PKG-INFO → scm2cpp_learn-0.20.0/README.md +144 -22
  3. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/pyproject.toml +2 -2
  4. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/__init__.py +654 -20
  5. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_compat.py +70 -2
  6. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/_loader_accel_cublas.py +12 -0
  7. scm2cpp_learn-0.18.0/src/scm2cpp_learn/_generated/_loader_gramops_loops.py → scm2cpp_learn-0.20.0/src/scm2cpp_learn/_generated/_loader_gramops_blas.py +149 -24
  8. scm2cpp_learn-0.18.0/src/scm2cpp_learn/_generated/_loader_gramops_blas.py → scm2cpp_learn-0.20.0/src/scm2cpp_learn/_generated/_loader_gramops_loops.py +168 -5
  9. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/accel_cublas/accel.cpp +2 -0
  10. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/accel_cublas/accel.hpp +89 -17
  11. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/accel_cublas/accel_capi.cpp +6 -0
  12. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/classifiers.hpp +18 -18
  13. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/derived_blas/lasso_design.hpp +12 -12
  14. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/derived_cublas/lasso_design.hpp +13 -13
  15. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/derived_loops/lasso_design.hpp +24 -24
  16. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/gramops_blas/gramops.cpp +17 -0
  17. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/gramops_blas/gramops.hpp +1274 -30
  18. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/gramops_blas/gramops_capi.cpp +53 -2
  19. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/gramops_loops/gramops.cpp +17 -0
  20. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/gramops_loops/gramops.hpp +1700 -384
  21. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/gramops_loops/gramops_capi.cpp +53 -2
  22. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/nmf_blas/nmf_cd.hpp +13 -13
  23. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/nmf_cublas/nmf_cd.hpp +17 -17
  24. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/nmf_loops/nmf_cd.hpp +29 -29
  25. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/scm2cpp-blas.hpp +18 -0
  26. scm2cpp_learn-0.18.0/README.md → scm2cpp_learn-0.20.0/src/scm2cpp_learn.egg-info/PKG-INFO +162 -4
  27. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/setup.cfg +0 -0
  28. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/setup.py +0 -0
  29. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/__init__.py +0 -0
  30. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/_loader.py +0 -0
  31. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/_loader_classifiers.py +0 -0
  32. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/_loader_derived_blas.py +0 -0
  33. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/_loader_derived_cublas.py +0 -0
  34. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/_loader_derived_loops.py +0 -0
  35. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/_loader_gpu_cuda.py +0 -0
  36. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/_loader_nmf_blas.py +0 -0
  37. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/_loader_nmf_cublas.py +0 -0
  38. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/_loader_nmf_loops.py +0 -0
  39. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/classifiers.cpp +0 -0
  40. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/classifiers_capi.cpp +0 -0
  41. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/derived_blas/lasso_design.cpp +0 -0
  42. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/derived_blas/lasso_design_capi.cpp +0 -0
  43. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/derived_cublas/lasso_design.cpp +0 -0
  44. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/derived_cublas/lasso_design_capi.cpp +0 -0
  45. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/derived_loops/lasso_design.cpp +0 -0
  46. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/derived_loops/lasso_design_capi.cpp +0 -0
  47. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/gpu_cuda/gpu.cpp +0 -0
  48. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/gpu_cuda/gpu.hpp +0 -0
  49. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/gpu_cuda/gpu_capi.cpp +0 -0
  50. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/lasso_cov.cpp +0 -0
  51. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/lasso_cov.hpp +0 -0
  52. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/lasso_cov_capi.cpp +0 -0
  53. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/nmf_blas/nmf_cd.cpp +0 -0
  54. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/nmf_blas/nmf_cd_capi.cpp +0 -0
  55. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/nmf_cublas/nmf_cd.cpp +0 -0
  56. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/nmf_cublas/nmf_cd_capi.cpp +0 -0
  57. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/nmf_loops/nmf_cd.cpp +0 -0
  58. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/nmf_loops/nmf_cd_capi.cpp +0 -0
  59. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/scm2cpp-cublas.hpp +0 -0
  60. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/scm2cpp-cuda.hpp +0 -0
  61. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/scm2cpp.hpp +0 -0
  62. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_libfind.py +0 -0
  63. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/batch_capi.cu +0 -0
  64. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn.egg-info/SOURCES.txt +0 -0
  65. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn.egg-info/dependency_links.txt +0 -0
  66. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn.egg-info/requires.txt +0 -0
  67. {scm2cpp_learn-0.18.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn.egg-info/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scm2cpp-learn
3
- Version: 0.18.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, TruncatedSVD, graphical lasso, RANSAC, SGD, OMP, LARS) translated from Scheme to C++ by scm2cpp; GPU batch solver and CUDA kernels included
3
+ Version: 0.20.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, TruncatedSVD, PLS, CCA, 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
@@ -206,9 +206,10 @@ package picks the fastest one present.
206
206
 
207
207
  | class | scikit-learn | Scheme kernel | builds | sklearnex accelerates |
208
208
  |---|---|---|---|---|
209
- | `GramLasso` | Lasso(precompute=True), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | no |
210
- | `GramElasticNet` | ElasticNet(precompute=True), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | no |
211
- | `GramRidge` | Ridge, fit(X, y) with the intercept, one or many targets | `gram.scm` (Cholesky; dposv) | loops / BLAS | Ridge |
209
+ | `GramLasso` | Lasso(precompute=True), fit(X, y) with the intercept, dense or sparse X | `lasso-cov.scm` | CPU | no |
210
+ | `GramElasticNet` | ElasticNet(precompute=True), fit(X, y) with the intercept, dense or sparse X | `lasso-cov.scm` | CPU | no |
211
+ | `GramRidge` | Ridge, fit(X, y) with the intercept, one or many targets, dense or sparse X | `gram.scm` (Cholesky; dposv) | loops / BLAS | Ridge |
212
+ | `GramLinearRegression` | LinearRegression, dense or sparse X, sample_weight | `linreg.scm` (Cholesky; dposv, dpocon) | loops / BLAS | no |
212
213
  | `GramRidgeCV` | RidgeCV (cv=None), leave-one-out over every alpha from one eigenproblem | `ridge-gcv.scm` | loops / BLAS | no |
213
214
  | `GramMultiTaskLasso` | MultiTaskLasso, fit(X, Y) with the intercept | `lasso-cov.scm` | CPU | no |
214
215
  | `GramMultiTaskElasticNet` | MultiTaskElasticNet, fit(X, Y) with the intercept | `lasso-cov.scm` | CPU | no |
@@ -256,7 +257,9 @@ package picks the fastest one present.
256
257
  | `CovPCA` | PCA(svd_solver='covariance_eigh') | `pca.scm` | loops / BLAS / cuBLAS | PCA |
257
258
  | `GramKernelPCA` | KernelPCA (no inverse_transform) | `kernel-pca.scm` | loops / BLAS | no |
258
259
  | `GramLDA` | LinearDiscriminantAnalysis (svd, lsqr, eigen) | `lda.scm` | loops / BLAS | no |
259
- | `GramTruncatedSVD` | TruncatedSVD on dense X (exact, as arpack) | `truncated-svd.scm` | loops / BLAS | no |
260
+ | `GramTruncatedSVD` | TruncatedSVD on dense or sparse X (exact, as arpack) | `truncated-svd.scm`, `sparse.scm` | loops / BLAS | no |
261
+ | `GramPLSRegression`, `GramPLSCanonical`, `GramCCA`, `GramPLSSVD` | PLSRegression, PLSCanonical, CCA, PLSSVD | `pls.scm` | loops / BLAS | no |
262
+ | `GramFactorAnalysis` | FactorAnalysis (exact, as svd_method='lapack'; rotations) | `fa.scm` | loops / BLAS | no |
260
263
  | `f_regression, f_classif` | f_regression, f_classif | `fscore.scm` | loops / BLAS / cuBLAS | no |
261
264
  | `SelectKBest, SelectPercentile` | SelectKBest, SelectPercentile | `fscore.scm` | loops / BLAS / cuBLAS | no |
262
265
  | `GramRFE` | RFE(Ridge), RFE(LinearRegression) | `rfe.scm` | loops / BLAS / cuBLAS | inside: Ridge |
@@ -714,6 +717,143 @@ The exact route costs O(n p^2 + p^3); with few components of many
714
717
  features (2,000 here) the randomized one does less work, and on one core
715
718
  it is faster, at its accuracy. Sparse X is not taken yet.
716
719
 
720
+ ## PLS and CCA: `GramPLSRegression`, `GramPLSCanonical`, `GramCCA`, `GramPLSSVD`
721
+
722
+ scikit-learn's `cross_decomposition` estimators over the cross products
723
+ (`examples/kernel-only/pls.scm`). NIPALS multiplies the blocks only by
724
+ weight vectors, and each such product is one of Cxx = X'X, Cxy = X'Y,
725
+ Cyy = Y'Y times a vector: the Y score is Y c for a coefficient vector c,
726
+ so X'(Y c) = Cxy c. The deflations are rank-one updates of the three
727
+ matrices (with a = Cxx w, b = Cxy'w and s2 = w'Cxx w, regression mode is
728
+ Cxx -= a a'/s2, Cxy -= a b'/s2, Cyy -= b b'/s2). So X and Y are read
729
+ once, for the three products (dsyrk and dgemm), and every power
730
+ iteration after that costs O(p^2 + pq + q^2) where scikit-learn's costs
731
+ O(n(p + q)). CCA's mode B takes pinv(X) y = Cxx^+ X'y, Cxx^+ from its
732
+ eigenproblem; PLSSVD is the SVD of Cxy.
733
+
734
+ ```python
735
+ from scm2cpp_learn import GramPLSRegression, GramCCA
736
+ pls = GramPLSRegression(n_components=5).fit(X, Y)
737
+ pls.predict(X), pls.transform(X), pls.x_scores_
738
+ ```
739
+
740
+ Weights, loadings, rotations, coefficients, scores and transforms agree
741
+ with scikit-learn's to 1e-13, with the same iteration counts. One cut
742
+ differs: a deflated block loses a rank exactly, which its SVD shows as a
743
+ singular value near eps but its Gram as an eigenvalue near eps
744
+ lam_max, so mode B's pinv also drops eigenvalues under 1e3 eps lam_max
745
+ (singular values under about 5e-7 of the largest, where scikit-learn's
746
+ cut is 2.2e-10). Measured (`bench/eigen-compare.py`, the MKL build, best
747
+ of three):
748
+
749
+ | case | scikit-learn | package | ratio, one core | ratio, 20 cores |
750
+ |---|---|---|---|---|
751
+ | PLSRegression, 200,000 x 100 -> 5, 5 components | 5.64 s | 0.26 s | 21x | 19x |
752
+ | PLSCanonical, 200,000 x 100 -> 5, 5 components | 5.63 s | 0.14 s | 40x | 59x |
753
+ | CCA, 200,000 x 100 -> 5, 5 components | 31.9 s | 0.14 s | 220x | 190x |
754
+ | PLSSVD, 200,000 x 100 -> 5, 5 components | 0.28 s | 0.14 s | 2.0x | 4.0x |
755
+ | PLSRegression, 50,000 x 500 -> 10, 10 components | 45.5 s | 0.75 s | 61x | 57x |
756
+ | PLSCanonical, 50,000 x 500 -> 10, 10 components | 45.3 s | 0.60 s | 76x | 100x |
757
+ | CCA, 50,000 x 500 -> 10, 10 components | 132 s | 1.21 s | 109x | 151x |
758
+ | PLSSVD, 50,000 x 500 -> 10, 10 components | 0.34 s | 0.34 s | 1.0x | 3.4x |
759
+
760
+ PLSSVD is one SVD of X'Y on both sides, so only the product differs.
761
+
762
+ ## Factor analysis: `GramFactorAnalysis`
763
+
764
+ scikit-learn's `FactorAnalysis` over the covariance
765
+ (`examples/kernel-only/fa.scm`). Every iteration of scikit-learn's takes
766
+ the SVD of the n x p matrix Xc / (sqrt(psi) sqrt(n)); its squared
767
+ singular values and right singular vectors are the eigenpairs of
768
+ D^-1 C D^-1, C = Xc'Xc / n and D = diag(sqrt psi). So C is formed once
769
+ (dsyrk) and each iteration is the top `n_components` eigenpairs of a
770
+ p x p matrix (dsyevr), then scikit-learn's own updates of the
771
+ components, the log-likelihood and psi. The eigenproblem is exact: what
772
+ `svd_method="lapack"` computes, which the fit matches to 1e-13 with the
773
+ same iterations; scikit-learn's default `"randomized"` approximates it
774
+ (here to 1e-6 in psi). Rotations (varimax, quartimax), `transform`,
775
+ `score`, `get_covariance` and `get_precision` are scikit-learn's.
776
+
777
+ ```python
778
+ from scm2cpp_learn import GramFactorAnalysis
779
+ fa = GramFactorAnalysis(n_components=10).fit(X)
780
+ fa.components_, fa.noise_variance_, fa.transform(X)
781
+ ```
782
+
783
+ Measured (`bench/eigen-compare.py`, the MKL build, best of two; load
784
+ average 10 to 16 from other jobs):
785
+
786
+ | case | scikit-learn | package | ratio, one core | ratio, 20 cores |
787
+ |---|---|---|---|---|
788
+ | FactorAnalysis(randomized), 200,000 x 100, 10 factors | 7.06 s | 0.11 s | 62x | 56x |
789
+ | FactorAnalysis(lapack), 200,000 x 100, 10 factors | 28.4 s | 0.10 s | 286x | 219x |
790
+ | FactorAnalysis(randomized), 50,000 x 500, 20 factors | 4.37 s | 0.33 s | 13x | 28x |
791
+ | FactorAnalysis(lapack), 50,000 x 500, 20 factors | 22.0 s | 0.33 s | 67x | 106x |
792
+
793
+ ## Least squares: `GramLinearRegression`
794
+
795
+ scikit-learn's `LinearRegression` over the Gram matrix
796
+ (`examples/kernel-only/linreg.scm`): Xc'Xc and Xc'y in one pass, then
797
+ the normal equations of the column-scaled Gram by Cholesky (dposv),
798
+ where scikit-learn runs LAPACK's gelsd on the n x p design. The normal
799
+ equations square the condition number, so the answer is taken only
800
+ when the scaled Gram's condition (dpocon's estimate) is at most 1e6,
801
+ which bounds the relative error near 1e-10, and the design has full
802
+ rank at `tol`; otherwise, and for `positive=True`, the fit is
803
+ scikit-learn's own (gelsd, or nnls). `rank_` and `singular_` come from
804
+ the Gram's eigenvalues when first read. A sparse X is taken (below).
805
+
806
+ Measured (`bench/eigen-compare.py`, as above):
807
+
808
+ | case | scikit-learn | package | ratio, one core | ratio, 20 cores |
809
+ |---|---|---|---|---|
810
+ | LinearRegression, 500,000 x 100 | 7.25 s | 0.39 s | 19x | 19x |
811
+ | LinearRegression, 200,000 x 500 | 10.9 s | 1.15 s | 9.5x | 50x |
812
+ | LinearRegression, 200,000 x 100, 10 targets | 2.83 s | 0.17 s | 17x | 18x |
813
+ | LinearRegression, 20,000 x 2,000 | 12.3 s | 1.41 s | 8.7x | 123x |
814
+
815
+ The coefficients agree to 1e-13.
816
+
817
+ ## Sparse X
818
+
819
+ `GramLasso`, `GramElasticNet`, `GramRidge`, `GramLinearRegression` and
820
+ `GramTruncatedSVD` take a scipy.sparse X (any format; CSR reaches the
821
+ kernels). X'X is formed by `examples/kernel-only/sparse.scm`'s
822
+ `csr-gram` -- each row's outer product into the upper triangle, rows in
823
+ order, blocks of rows on threads into their own G's -- and X'y and X B
824
+ by `csr-mm`; the intercept is the n xm xm' correction, as scikit-learn's
825
+ sparse coordinate descent keeps X uncentred too. After X'X the fits are
826
+ the dense ones, so the answers equal the dense X's to 1e-15 and are
827
+ exact: scikit-learn's sparse Ridge and LinearRegression are iterative
828
+ (sparse_cg, lsqr) and agree with its own dense answers only to their
829
+ tolerance (1e-5 and 1e-7 here).
830
+
831
+ Measured (`bench/sparse-compare.py`, the MKL build, best of two; load
832
+ average 10 to 29):
833
+
834
+ | case | scikit-learn | package | ratio, one core | ratio, 20 cores |
835
+ |---|---|---|---|---|
836
+ | Lasso, 200,000 x 200, 5% dense | 0.16 s | 0.067 s | 2.4x | 2.6x |
837
+ | ElasticNet, 200,000 x 200, 5% dense | 0.16 s | 0.059 s | 2.6x | 3.4x |
838
+ | Ridge, 200,000 x 200, 5% dense | 0.056 s | 0.064 s | 0.9x | 0.9x |
839
+ | LinearRegression, 200,000 x 200, 5% dense | 0.074 s | 0.076 s | 1.0x | 1.3x |
840
+ | TruncatedSVD (randomized), 200,000 x 200, 5% dense | 0.86 s | 0.11 s | 8.1x | 6.2x |
841
+ | TruncatedSVD (arpack), 200,000 x 200, 5% dense | 1.54 s | 0.11 s | 15x | 13x |
842
+ | Lasso, 100,000 x 1,000, 1% dense | 0.14 s | 0.069 s | 2.1x | 3.0x |
843
+ | Ridge, 100,000 x 1,000, 1% dense | 0.043 s | 0.082 s | 0.5x | 0.6x |
844
+ | TruncatedSVD (arpack), 100,000 x 1,000, 1% dense | 1.41 s | 0.16 s | 8.7x | 8.7x |
845
+ | Lasso, 50,000 x 2,000, 0.5% dense | 0.11 s | 0.15 s | 0.7x | 1.2x |
846
+ | Ridge, 50,000 x 2,000, 0.5% dense | 0.024 s | 0.23 s | 0.1x | 0.2x |
847
+ | TruncatedSVD (randomized), 50,000 x 2,000, 0.5% dense | 0.16 s | 1.49 s | 0.1x | 0.6x |
848
+
849
+ The Gram route pays p^2 memory and p^3 for the solve or the
850
+ eigenproblem whatever the sparsity, where scikit-learn's iterative
851
+ sparse solvers pay a few passes over the nonzeros. So it wins for
852
+ moderate p (a few hundred) and for TruncatedSVD against arpack, and
853
+ loses for p in the thousands with few nonzeros per row: there, Ridge
854
+ and LinearRegression are better left to scikit-learn's sparse solvers
855
+ (at their tolerance), and the randomized TruncatedSVD to scikit-learn.
856
+
717
857
  ## Bayesian ridge
718
858
 
719
859
  `CovBayesianRidge` is scikit-learn's `BayesianRidge` -- the same model,
@@ -1,21 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: scm2cpp-learn
3
- Version: 0.18.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, TruncatedSVD, 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-learn
9
- Keywords: scikit-learn,lasso,elastic-net,ridge,svm,logistic-regression,pca,lda,coordinate-descent,lapack,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-learn
20
2
 
21
3
  [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/niitsuma/scm2cpp/blob/master/notebooks/compare-sklearn.ipynb)
@@ -206,9 +188,10 @@ package picks the fastest one present.
206
188
 
207
189
  | class | scikit-learn | Scheme kernel | builds | sklearnex accelerates |
208
190
  |---|---|---|---|---|
209
- | `GramLasso` | Lasso(precompute=True), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | no |
210
- | `GramElasticNet` | ElasticNet(precompute=True), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | no |
211
- | `GramRidge` | Ridge, fit(X, y) with the intercept, one or many targets | `gram.scm` (Cholesky; dposv) | loops / BLAS | Ridge |
191
+ | `GramLasso` | Lasso(precompute=True), fit(X, y) with the intercept, dense or sparse X | `lasso-cov.scm` | CPU | no |
192
+ | `GramElasticNet` | ElasticNet(precompute=True), fit(X, y) with the intercept, dense or sparse X | `lasso-cov.scm` | CPU | no |
193
+ | `GramRidge` | Ridge, fit(X, y) with the intercept, one or many targets, dense or sparse X | `gram.scm` (Cholesky; dposv) | loops / BLAS | Ridge |
194
+ | `GramLinearRegression` | LinearRegression, dense or sparse X, sample_weight | `linreg.scm` (Cholesky; dposv, dpocon) | loops / BLAS | no |
212
195
  | `GramRidgeCV` | RidgeCV (cv=None), leave-one-out over every alpha from one eigenproblem | `ridge-gcv.scm` | loops / BLAS | no |
213
196
  | `GramMultiTaskLasso` | MultiTaskLasso, fit(X, Y) with the intercept | `lasso-cov.scm` | CPU | no |
214
197
  | `GramMultiTaskElasticNet` | MultiTaskElasticNet, fit(X, Y) with the intercept | `lasso-cov.scm` | CPU | no |
@@ -256,7 +239,9 @@ package picks the fastest one present.
256
239
  | `CovPCA` | PCA(svd_solver='covariance_eigh') | `pca.scm` | loops / BLAS / cuBLAS | PCA |
257
240
  | `GramKernelPCA` | KernelPCA (no inverse_transform) | `kernel-pca.scm` | loops / BLAS | no |
258
241
  | `GramLDA` | LinearDiscriminantAnalysis (svd, lsqr, eigen) | `lda.scm` | loops / BLAS | no |
259
- | `GramTruncatedSVD` | TruncatedSVD on dense X (exact, as arpack) | `truncated-svd.scm` | loops / BLAS | no |
242
+ | `GramTruncatedSVD` | TruncatedSVD on dense or sparse X (exact, as arpack) | `truncated-svd.scm`, `sparse.scm` | loops / BLAS | no |
243
+ | `GramPLSRegression`, `GramPLSCanonical`, `GramCCA`, `GramPLSSVD` | PLSRegression, PLSCanonical, CCA, PLSSVD | `pls.scm` | loops / BLAS | no |
244
+ | `GramFactorAnalysis` | FactorAnalysis (exact, as svd_method='lapack'; rotations) | `fa.scm` | loops / BLAS | no |
260
245
  | `f_regression, f_classif` | f_regression, f_classif | `fscore.scm` | loops / BLAS / cuBLAS | no |
261
246
  | `SelectKBest, SelectPercentile` | SelectKBest, SelectPercentile | `fscore.scm` | loops / BLAS / cuBLAS | no |
262
247
  | `GramRFE` | RFE(Ridge), RFE(LinearRegression) | `rfe.scm` | loops / BLAS / cuBLAS | inside: Ridge |
@@ -714,6 +699,143 @@ The exact route costs O(n p^2 + p^3); with few components of many
714
699
  features (2,000 here) the randomized one does less work, and on one core
715
700
  it is faster, at its accuracy. Sparse X is not taken yet.
716
701
 
702
+ ## PLS and CCA: `GramPLSRegression`, `GramPLSCanonical`, `GramCCA`, `GramPLSSVD`
703
+
704
+ scikit-learn's `cross_decomposition` estimators over the cross products
705
+ (`examples/kernel-only/pls.scm`). NIPALS multiplies the blocks only by
706
+ weight vectors, and each such product is one of Cxx = X'X, Cxy = X'Y,
707
+ Cyy = Y'Y times a vector: the Y score is Y c for a coefficient vector c,
708
+ so X'(Y c) = Cxy c. The deflations are rank-one updates of the three
709
+ matrices (with a = Cxx w, b = Cxy'w and s2 = w'Cxx w, regression mode is
710
+ Cxx -= a a'/s2, Cxy -= a b'/s2, Cyy -= b b'/s2). So X and Y are read
711
+ once, for the three products (dsyrk and dgemm), and every power
712
+ iteration after that costs O(p^2 + pq + q^2) where scikit-learn's costs
713
+ O(n(p + q)). CCA's mode B takes pinv(X) y = Cxx^+ X'y, Cxx^+ from its
714
+ eigenproblem; PLSSVD is the SVD of Cxy.
715
+
716
+ ```python
717
+ from scm2cpp_learn import GramPLSRegression, GramCCA
718
+ pls = GramPLSRegression(n_components=5).fit(X, Y)
719
+ pls.predict(X), pls.transform(X), pls.x_scores_
720
+ ```
721
+
722
+ Weights, loadings, rotations, coefficients, scores and transforms agree
723
+ with scikit-learn's to 1e-13, with the same iteration counts. One cut
724
+ differs: a deflated block loses a rank exactly, which its SVD shows as a
725
+ singular value near eps but its Gram as an eigenvalue near eps
726
+ lam_max, so mode B's pinv also drops eigenvalues under 1e3 eps lam_max
727
+ (singular values under about 5e-7 of the largest, where scikit-learn's
728
+ cut is 2.2e-10). Measured (`bench/eigen-compare.py`, the MKL build, best
729
+ of three):
730
+
731
+ | case | scikit-learn | package | ratio, one core | ratio, 20 cores |
732
+ |---|---|---|---|---|
733
+ | PLSRegression, 200,000 x 100 -> 5, 5 components | 5.64 s | 0.26 s | 21x | 19x |
734
+ | PLSCanonical, 200,000 x 100 -> 5, 5 components | 5.63 s | 0.14 s | 40x | 59x |
735
+ | CCA, 200,000 x 100 -> 5, 5 components | 31.9 s | 0.14 s | 220x | 190x |
736
+ | PLSSVD, 200,000 x 100 -> 5, 5 components | 0.28 s | 0.14 s | 2.0x | 4.0x |
737
+ | PLSRegression, 50,000 x 500 -> 10, 10 components | 45.5 s | 0.75 s | 61x | 57x |
738
+ | PLSCanonical, 50,000 x 500 -> 10, 10 components | 45.3 s | 0.60 s | 76x | 100x |
739
+ | CCA, 50,000 x 500 -> 10, 10 components | 132 s | 1.21 s | 109x | 151x |
740
+ | PLSSVD, 50,000 x 500 -> 10, 10 components | 0.34 s | 0.34 s | 1.0x | 3.4x |
741
+
742
+ PLSSVD is one SVD of X'Y on both sides, so only the product differs.
743
+
744
+ ## Factor analysis: `GramFactorAnalysis`
745
+
746
+ scikit-learn's `FactorAnalysis` over the covariance
747
+ (`examples/kernel-only/fa.scm`). Every iteration of scikit-learn's takes
748
+ the SVD of the n x p matrix Xc / (sqrt(psi) sqrt(n)); its squared
749
+ singular values and right singular vectors are the eigenpairs of
750
+ D^-1 C D^-1, C = Xc'Xc / n and D = diag(sqrt psi). So C is formed once
751
+ (dsyrk) and each iteration is the top `n_components` eigenpairs of a
752
+ p x p matrix (dsyevr), then scikit-learn's own updates of the
753
+ components, the log-likelihood and psi. The eigenproblem is exact: what
754
+ `svd_method="lapack"` computes, which the fit matches to 1e-13 with the
755
+ same iterations; scikit-learn's default `"randomized"` approximates it
756
+ (here to 1e-6 in psi). Rotations (varimax, quartimax), `transform`,
757
+ `score`, `get_covariance` and `get_precision` are scikit-learn's.
758
+
759
+ ```python
760
+ from scm2cpp_learn import GramFactorAnalysis
761
+ fa = GramFactorAnalysis(n_components=10).fit(X)
762
+ fa.components_, fa.noise_variance_, fa.transform(X)
763
+ ```
764
+
765
+ Measured (`bench/eigen-compare.py`, the MKL build, best of two; load
766
+ average 10 to 16 from other jobs):
767
+
768
+ | case | scikit-learn | package | ratio, one core | ratio, 20 cores |
769
+ |---|---|---|---|---|
770
+ | FactorAnalysis(randomized), 200,000 x 100, 10 factors | 7.06 s | 0.11 s | 62x | 56x |
771
+ | FactorAnalysis(lapack), 200,000 x 100, 10 factors | 28.4 s | 0.10 s | 286x | 219x |
772
+ | FactorAnalysis(randomized), 50,000 x 500, 20 factors | 4.37 s | 0.33 s | 13x | 28x |
773
+ | FactorAnalysis(lapack), 50,000 x 500, 20 factors | 22.0 s | 0.33 s | 67x | 106x |
774
+
775
+ ## Least squares: `GramLinearRegression`
776
+
777
+ scikit-learn's `LinearRegression` over the Gram matrix
778
+ (`examples/kernel-only/linreg.scm`): Xc'Xc and Xc'y in one pass, then
779
+ the normal equations of the column-scaled Gram by Cholesky (dposv),
780
+ where scikit-learn runs LAPACK's gelsd on the n x p design. The normal
781
+ equations square the condition number, so the answer is taken only
782
+ when the scaled Gram's condition (dpocon's estimate) is at most 1e6,
783
+ which bounds the relative error near 1e-10, and the design has full
784
+ rank at `tol`; otherwise, and for `positive=True`, the fit is
785
+ scikit-learn's own (gelsd, or nnls). `rank_` and `singular_` come from
786
+ the Gram's eigenvalues when first read. A sparse X is taken (below).
787
+
788
+ Measured (`bench/eigen-compare.py`, as above):
789
+
790
+ | case | scikit-learn | package | ratio, one core | ratio, 20 cores |
791
+ |---|---|---|---|---|
792
+ | LinearRegression, 500,000 x 100 | 7.25 s | 0.39 s | 19x | 19x |
793
+ | LinearRegression, 200,000 x 500 | 10.9 s | 1.15 s | 9.5x | 50x |
794
+ | LinearRegression, 200,000 x 100, 10 targets | 2.83 s | 0.17 s | 17x | 18x |
795
+ | LinearRegression, 20,000 x 2,000 | 12.3 s | 1.41 s | 8.7x | 123x |
796
+
797
+ The coefficients agree to 1e-13.
798
+
799
+ ## Sparse X
800
+
801
+ `GramLasso`, `GramElasticNet`, `GramRidge`, `GramLinearRegression` and
802
+ `GramTruncatedSVD` take a scipy.sparse X (any format; CSR reaches the
803
+ kernels). X'X is formed by `examples/kernel-only/sparse.scm`'s
804
+ `csr-gram` -- each row's outer product into the upper triangle, rows in
805
+ order, blocks of rows on threads into their own G's -- and X'y and X B
806
+ by `csr-mm`; the intercept is the n xm xm' correction, as scikit-learn's
807
+ sparse coordinate descent keeps X uncentred too. After X'X the fits are
808
+ the dense ones, so the answers equal the dense X's to 1e-15 and are
809
+ exact: scikit-learn's sparse Ridge and LinearRegression are iterative
810
+ (sparse_cg, lsqr) and agree with its own dense answers only to their
811
+ tolerance (1e-5 and 1e-7 here).
812
+
813
+ Measured (`bench/sparse-compare.py`, the MKL build, best of two; load
814
+ average 10 to 29):
815
+
816
+ | case | scikit-learn | package | ratio, one core | ratio, 20 cores |
817
+ |---|---|---|---|---|
818
+ | Lasso, 200,000 x 200, 5% dense | 0.16 s | 0.067 s | 2.4x | 2.6x |
819
+ | ElasticNet, 200,000 x 200, 5% dense | 0.16 s | 0.059 s | 2.6x | 3.4x |
820
+ | Ridge, 200,000 x 200, 5% dense | 0.056 s | 0.064 s | 0.9x | 0.9x |
821
+ | LinearRegression, 200,000 x 200, 5% dense | 0.074 s | 0.076 s | 1.0x | 1.3x |
822
+ | TruncatedSVD (randomized), 200,000 x 200, 5% dense | 0.86 s | 0.11 s | 8.1x | 6.2x |
823
+ | TruncatedSVD (arpack), 200,000 x 200, 5% dense | 1.54 s | 0.11 s | 15x | 13x |
824
+ | Lasso, 100,000 x 1,000, 1% dense | 0.14 s | 0.069 s | 2.1x | 3.0x |
825
+ | Ridge, 100,000 x 1,000, 1% dense | 0.043 s | 0.082 s | 0.5x | 0.6x |
826
+ | TruncatedSVD (arpack), 100,000 x 1,000, 1% dense | 1.41 s | 0.16 s | 8.7x | 8.7x |
827
+ | Lasso, 50,000 x 2,000, 0.5% dense | 0.11 s | 0.15 s | 0.7x | 1.2x |
828
+ | Ridge, 50,000 x 2,000, 0.5% dense | 0.024 s | 0.23 s | 0.1x | 0.2x |
829
+ | TruncatedSVD (randomized), 50,000 x 2,000, 0.5% dense | 0.16 s | 1.49 s | 0.1x | 0.6x |
830
+
831
+ The Gram route pays p^2 memory and p^3 for the solve or the
832
+ eigenproblem whatever the sparsity, where scikit-learn's iterative
833
+ sparse solvers pay a few passes over the nonzeros. So it wins for
834
+ moderate p (a few hundred) and for TruncatedSVD against arpack, and
835
+ loses for p in the thousands with few nonzeros per row: there, Ridge
836
+ and LinearRegression are better left to scikit-learn's sparse solvers
837
+ (at their tolerance), and the randomized TruncatedSVD to scikit-learn.
838
+
717
839
  ## Bayesian ridge
718
840
 
719
841
  `CovBayesianRidge` is scikit-learn's `BayesianRidge` -- the same model,
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "scm2cpp-learn"
7
- version = "0.18.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, TruncatedSVD, graphical lasso, RANSAC, SGD, OMP, LARS) translated from Scheme to C++ by scm2cpp; GPU batch solver and CUDA kernels included"
7
+ version = "0.20.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, TruncatedSVD, PLS, CCA, 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"}