scm2cpp-learn 0.19.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.19.0 → scm2cpp_learn-0.20.0}/PKG-INFO +102 -5
  2. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/README.md +101 -4
  3. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/pyproject.toml +1 -1
  4. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/__init__.py +399 -11
  5. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_compat.py +70 -2
  6. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/_loader_accel_cublas.py +12 -0
  7. scm2cpp_learn-0.19.0/src/scm2cpp_learn/_generated/_loader_gramops_loops.py → scm2cpp_learn-0.20.0/src/scm2cpp_learn/_generated/_loader_gramops_blas.py +65 -20
  8. scm2cpp_learn-0.19.0/src/scm2cpp_learn/_generated/_loader_gramops_blas.py → scm2cpp_learn-0.20.0/src/scm2cpp_learn/_generated/_loader_gramops_loops.py +84 -1
  9. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/accel_cublas/accel.cpp +2 -0
  10. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/accel_cublas/accel.hpp +89 -17
  11. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/accel_cublas/accel_capi.cpp +6 -0
  12. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/classifiers.hpp +18 -18
  13. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/derived_blas/lasso_design.hpp +12 -12
  14. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/derived_cublas/lasso_design.hpp +13 -13
  15. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/derived_loops/lasso_design.hpp +24 -24
  16. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/gramops_blas/gramops.cpp +8 -0
  17. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/gramops_blas/gramops.hpp +453 -21
  18. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/gramops_blas/gramops_capi.cpp +24 -0
  19. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/gramops_loops/gramops.cpp +8 -0
  20. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/gramops_loops/gramops.hpp +854 -422
  21. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/gramops_loops/gramops_capi.cpp +24 -0
  22. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/nmf_blas/nmf_cd.hpp +13 -13
  23. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/nmf_cublas/nmf_cd.hpp +17 -17
  24. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/nmf_loops/nmf_cd.hpp +29 -29
  25. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/scm2cpp-blas.hpp +18 -0
  26. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn.egg-info/PKG-INFO +102 -5
  27. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/setup.cfg +0 -0
  28. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/setup.py +0 -0
  29. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/__init__.py +0 -0
  30. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/_loader.py +0 -0
  31. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/_loader_classifiers.py +0 -0
  32. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/_loader_derived_blas.py +0 -0
  33. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/_loader_derived_cublas.py +0 -0
  34. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/_loader_derived_loops.py +0 -0
  35. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/_loader_gpu_cuda.py +0 -0
  36. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/_loader_nmf_blas.py +0 -0
  37. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/_loader_nmf_cublas.py +0 -0
  38. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/_loader_nmf_loops.py +0 -0
  39. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/classifiers.cpp +0 -0
  40. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/classifiers_capi.cpp +0 -0
  41. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/derived_blas/lasso_design.cpp +0 -0
  42. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/derived_blas/lasso_design_capi.cpp +0 -0
  43. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/derived_cublas/lasso_design.cpp +0 -0
  44. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/derived_cublas/lasso_design_capi.cpp +0 -0
  45. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/derived_loops/lasso_design.cpp +0 -0
  46. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/derived_loops/lasso_design_capi.cpp +0 -0
  47. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/gpu_cuda/gpu.cpp +0 -0
  48. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/gpu_cuda/gpu.hpp +0 -0
  49. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/gpu_cuda/gpu_capi.cpp +0 -0
  50. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/lasso_cov.cpp +0 -0
  51. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/lasso_cov.hpp +0 -0
  52. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/lasso_cov_capi.cpp +0 -0
  53. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/nmf_blas/nmf_cd.cpp +0 -0
  54. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/nmf_blas/nmf_cd_capi.cpp +0 -0
  55. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/nmf_cublas/nmf_cd.cpp +0 -0
  56. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/nmf_cublas/nmf_cd_capi.cpp +0 -0
  57. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/nmf_loops/nmf_cd.cpp +0 -0
  58. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/nmf_loops/nmf_cd_capi.cpp +0 -0
  59. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/scm2cpp-cublas.hpp +0 -0
  60. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/scm2cpp-cuda.hpp +0 -0
  61. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_generated/scm2cpp.hpp +0 -0
  62. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/_libfind.py +0 -0
  63. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn/batch_capi.cu +0 -0
  64. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn.egg-info/SOURCES.txt +0 -0
  65. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn.egg-info/dependency_links.txt +0 -0
  66. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn.egg-info/requires.txt +0 -0
  67. {scm2cpp_learn-0.19.0 → scm2cpp_learn-0.20.0}/src/scm2cpp_learn.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scm2cpp-learn
3
- Version: 0.19.0
3
+ Version: 0.20.0
4
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
@@ -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,8 +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 |
260
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 |
261
263
  | `f_regression, f_classif` | f_regression, f_classif | `fscore.scm` | loops / BLAS / cuBLAS | no |
262
264
  | `SelectKBest, SelectPercentile` | SelectKBest, SelectPercentile | `fscore.scm` | loops / BLAS / cuBLAS | no |
263
265
  | `GramRFE` | RFE(Ridge), RFE(LinearRegression) | `rfe.scm` | loops / BLAS / cuBLAS | inside: Ridge |
@@ -757,6 +759,101 @@ of three):
757
759
 
758
760
  PLSSVD is one SVD of X'Y on both sides, so only the product differs.
759
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
+
760
857
  ## Bayesian ridge
761
858
 
762
859
  `CovBayesianRidge` is scikit-learn's `BayesianRidge` -- the same model,
@@ -188,9 +188,10 @@ package picks the fastest one present.
188
188
 
189
189
  | class | scikit-learn | Scheme kernel | builds | sklearnex accelerates |
190
190
  |---|---|---|---|---|
191
- | `GramLasso` | Lasso(precompute=True), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | no |
192
- | `GramElasticNet` | ElasticNet(precompute=True), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | no |
193
- | `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 |
194
195
  | `GramRidgeCV` | RidgeCV (cv=None), leave-one-out over every alpha from one eigenproblem | `ridge-gcv.scm` | loops / BLAS | no |
195
196
  | `GramMultiTaskLasso` | MultiTaskLasso, fit(X, Y) with the intercept | `lasso-cov.scm` | CPU | no |
196
197
  | `GramMultiTaskElasticNet` | MultiTaskElasticNet, fit(X, Y) with the intercept | `lasso-cov.scm` | CPU | no |
@@ -238,8 +239,9 @@ package picks the fastest one present.
238
239
  | `CovPCA` | PCA(svd_solver='covariance_eigh') | `pca.scm` | loops / BLAS / cuBLAS | PCA |
239
240
  | `GramKernelPCA` | KernelPCA (no inverse_transform) | `kernel-pca.scm` | loops / BLAS | no |
240
241
  | `GramLDA` | LinearDiscriminantAnalysis (svd, lsqr, eigen) | `lda.scm` | loops / BLAS | no |
241
- | `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 |
242
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 |
243
245
  | `f_regression, f_classif` | f_regression, f_classif | `fscore.scm` | loops / BLAS / cuBLAS | no |
244
246
  | `SelectKBest, SelectPercentile` | SelectKBest, SelectPercentile | `fscore.scm` | loops / BLAS / cuBLAS | no |
245
247
  | `GramRFE` | RFE(Ridge), RFE(LinearRegression) | `rfe.scm` | loops / BLAS / cuBLAS | inside: Ridge |
@@ -739,6 +741,101 @@ of three):
739
741
 
740
742
  PLSSVD is one SVD of X'Y on both sides, so only the product differs.
741
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
+
742
839
  ## Bayesian ridge
743
840
 
744
841
  `CovBayesianRidge` is scikit-learn's `BayesianRidge` -- the same model,
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "scm2cpp-learn"
7
- version = "0.19.0"
7
+ version = "0.20.0"
8
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"