scm2cpp-lasso 0.14.0__tar.gz → 0.15.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_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/PKG-INFO +41 -8
  2. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/README.md +40 -7
  3. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/pyproject.toml +1 -1
  4. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/__init__.py +216 -9
  5. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_compat.py +2 -1
  6. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso.egg-info/PKG-INFO +41 -8
  7. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/setup.cfg +0 -0
  8. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/setup.py +0 -0
  9. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/__init__.py +0 -0
  10. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/_loader.py +0 -0
  11. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/_loader_accel_cublas.py +0 -0
  12. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/_loader_classifiers.py +0 -0
  13. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/_loader_derived_blas.py +0 -0
  14. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/_loader_derived_cublas.py +0 -0
  15. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/_loader_derived_loops.py +0 -0
  16. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/_loader_gpu_cuda.py +0 -0
  17. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/_loader_gramops_blas.py +0 -0
  18. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/_loader_gramops_loops.py +0 -0
  19. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/_loader_nmf_blas.py +0 -0
  20. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/_loader_nmf_cublas.py +0 -0
  21. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/_loader_nmf_loops.py +0 -0
  22. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/accel_cublas/accel.cpp +0 -0
  23. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/accel_cublas/accel.hpp +0 -0
  24. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/accel_cublas/accel_capi.cpp +0 -0
  25. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/classifiers.cpp +0 -0
  26. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/classifiers.hpp +0 -0
  27. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/classifiers_capi.cpp +0 -0
  28. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/derived_blas/lasso_design.cpp +0 -0
  29. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/derived_blas/lasso_design.hpp +0 -0
  30. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/derived_blas/lasso_design_capi.cpp +0 -0
  31. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/derived_cublas/lasso_design.cpp +0 -0
  32. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/derived_cublas/lasso_design.hpp +0 -0
  33. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/derived_cublas/lasso_design_capi.cpp +0 -0
  34. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/derived_loops/lasso_design.cpp +0 -0
  35. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/derived_loops/lasso_design.hpp +0 -0
  36. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/derived_loops/lasso_design_capi.cpp +0 -0
  37. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/gpu_cuda/gpu.cpp +0 -0
  38. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/gpu_cuda/gpu.hpp +0 -0
  39. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/gpu_cuda/gpu_capi.cpp +0 -0
  40. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/gramops_blas/gramops.cpp +0 -0
  41. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/gramops_blas/gramops.hpp +0 -0
  42. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/gramops_blas/gramops_capi.cpp +0 -0
  43. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/gramops_loops/gramops.cpp +0 -0
  44. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/gramops_loops/gramops.hpp +0 -0
  45. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/gramops_loops/gramops_capi.cpp +0 -0
  46. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/lasso_cov.cpp +0 -0
  47. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/lasso_cov.hpp +0 -0
  48. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/lasso_cov_capi.cpp +0 -0
  49. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/nmf_blas/nmf_cd.cpp +0 -0
  50. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/nmf_blas/nmf_cd.hpp +0 -0
  51. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/nmf_blas/nmf_cd_capi.cpp +0 -0
  52. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/nmf_cublas/nmf_cd.cpp +0 -0
  53. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/nmf_cublas/nmf_cd.hpp +0 -0
  54. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/nmf_cublas/nmf_cd_capi.cpp +0 -0
  55. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/nmf_loops/nmf_cd.cpp +0 -0
  56. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/nmf_loops/nmf_cd.hpp +0 -0
  57. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/nmf_loops/nmf_cd_capi.cpp +0 -0
  58. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/scm2cpp-blas.hpp +0 -0
  59. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/scm2cpp-cublas.hpp +0 -0
  60. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/scm2cpp-cuda.hpp +0 -0
  61. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_generated/scm2cpp.hpp +0 -0
  62. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/_libfind.py +0 -0
  63. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso/batch_capi.cu +0 -0
  64. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso.egg-info/SOURCES.txt +0 -0
  65. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso.egg-info/dependency_links.txt +0 -0
  66. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso.egg-info/requires.txt +0 -0
  67. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.0}/src/scm2cpp_lasso.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scm2cpp-lasso
3
- Version: 0.14.0
3
+ Version: 0.15.0
4
4
  Summary: scikit-learn estimators (lasso, elastic net, ridge, ARD, SVM, SVR, logistic regression, GLMs, Huber, Theil-Sen, F-score selection, RFE, NMF, Gaussian mixture, FastICA, PCA, graphical lasso, RANSAC, SGD, OMP, LARS) translated from Scheme to C++ by scm2cpp; GPU batch solver and CUDA kernels included
5
5
  Author: Hirotaka Niitsuma
6
6
  License: MIT
@@ -107,9 +107,11 @@ them first; this solver does not do it for you.
107
107
  From 0.14.0 every estimator with a `fit(X, y)` (or `fit(X)`) derives
108
108
  from scikit-learn's `BaseEstimator` and the matching mixin, so `clone`,
109
109
  `get_params` / `set_params`, `GridSearchCV`, `Pipeline` and
110
- `cross_val_score` take them, and all 39 pass
110
+ `cross_val_score` take them, and all 44 pass
111
111
  `sklearn.utils.estimator_checks.check_estimator` (`check-sklearn.py`
112
- runs it; the one expected failure is `LBFGSLogisticRegressionCV`'s
112
+ runs it; the expected failures are `DesignLasso`'s missing `n_iter_`,
113
+ since the derived kernel does not count its sweeps, and
114
+ `LBFGSLogisticRegressionCV`'s
113
115
  `n_iter_` of 0 for a C whose warm start is already converged, which
114
116
  scikit-learn's own `LogisticRegressionCV` reports on the same data).
115
117
  scikit-learn stays optional: without it a numpy-only stand-in gives
@@ -122,11 +124,37 @@ from scm2cpp_lasso import GramLasso
122
124
  GridSearchCV(GramLasso(), {"alpha": [0.01, 0.1, 1.0]}, cv=5).fit(X, y)
123
125
  ```
124
126
 
125
- `GramLasso`, `GramElasticNet` and `GramRidge` are the `fit(X, y)`
126
- forms, with the intercept, over the Gram-matrix solvers; `CovLasso`,
127
- `CovRidge`, `DerivedLasso`, `CovLogistic`, `CovGroupLasso` and
128
- `CovMultiTaskLasso` keep their data-in-the-constructor interface,
129
- which solves a whole path at once.
127
+ `CovLasso`, `CovRidge`, `DerivedLasso`, `CovLogistic`,
128
+ `CovGroupLasso` and `CovMultiTaskLasso` keep their
129
+ data-in-the-constructor interface, which solves a whole path at once;
130
+ each has a `fit(X, y)` form with the intercept beside it (0.15.0):
131
+
132
+ | data in the constructor | `fit(X, y)` | scikit-learn |
133
+ |---|---|---|
134
+ | `CovLasso` | `GramLasso`, `GramElasticNet` | Lasso, ElasticNet |
135
+ | `CovRidge` | `GramRidge` | Ridge |
136
+ | `DerivedLasso` | `DesignLasso` | Lasso |
137
+ | `CovLogistic` | `GramLogisticRegression` | LogisticRegression(l1_ratio=1, solver="liblinear") |
138
+ | `CovGroupLasso` | `GramGroupLasso` (`groups=None`: the lasso) | (none) |
139
+ | `CovMultiTaskLasso` | `GramMultiTaskLasso`, `GramMultiTaskElasticNet` | MultiTaskLasso, MultiTaskElasticNet |
140
+
141
+ Against scikit-learn on 400 x 30 with an intercept: `DesignLasso` and
142
+ `GramGroupLasso(groups=None)` agree with `Lasso` to 1.5e-10, the
143
+ multi-task pair with `MultiTaskLasso` / `MultiTaskElasticNet` to
144
+ 5e-14, and `GramLogisticRegression` reaches liblinear's objective to
145
+ eight digits with the same predictions.
146
+
147
+ Against the class each one wraps (one alpha, one core), which fits no
148
+ intercept and is unchanged: with `fit_intercept=False` the input checks
149
+ add 15 to 30 us a call (up to +50% on a 0.06 ms fit) and the finiteness
150
+ sum 8 to 14% on 20000 x 200 (none under `assume_finite`); with the
151
+ intercept, the column means add 20 to 30% on 20000 x 200 for the
152
+ Gram-matrix ones, which correct X'X
153
+ for the means rather than copy X to centre it, and about 55% for
154
+ `DesignLasso`, whose kernel needs the centred X itself.
155
+ `GramLogisticRegression` is slower than liblinear by its algorithm, 2.5x
156
+ at 20000 x 200 and more on small problems; `L1LogisticRegression` is
157
+ the fast one for that problem.
130
158
 
131
159
  What it costs is the input checks: X must be a finite 2-D float64
132
160
  array with the feature count of the fit. A plain C-ordered float64
@@ -166,6 +194,11 @@ package picks the fastest one present.
166
194
  | `GramLasso` | Lasso(precompute=True), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | no |
167
195
  | `GramElasticNet` | ElasticNet(precompute=True), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | no |
168
196
  | `GramRidge` | Ridge, fit(X, y) with the intercept, one or many targets | numpy (Cholesky) | CPU | Ridge |
197
+ | `GramMultiTaskLasso` | MultiTaskLasso, fit(X, Y) with the intercept | `lasso-cov.scm` | CPU | no |
198
+ | `GramMultiTaskElasticNet` | MultiTaskElasticNet, fit(X, Y) with the intercept | `lasso-cov.scm` | CPU | no |
199
+ | `GramGroupLasso` | (none; skglm GroupLasso), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | - |
200
+ | `GramLogisticRegression` | LogisticRegression(l1_ratio=1, solver='liblinear'), by majorization | `lasso-cov.scm` | CPU | no |
201
+ | `DesignLasso` | Lasso, fit(X, y) with the intercept, from the design | `lasso-design.scm (--derive)` | loops / BLAS / cuBLAS | no |
169
202
  | `CovLasso` | Lasso, ElasticNet, lasso_path(precompute=True) | `lasso-cov.scm` | CPU; GPU batch (fit_path_batch, bootstrap) | no |
170
203
  | `CovLassoCV` | LassoCV, ElasticNetCV | `lasso-cov.scm` | CPU; GPU batch | no |
171
204
  | `DerivedLasso` | lasso_path, from the design | `lasso-design.scm (--derive)` | loops / BLAS / cuBLAS | no |
@@ -89,9 +89,11 @@ them first; this solver does not do it for you.
89
89
  From 0.14.0 every estimator with a `fit(X, y)` (or `fit(X)`) derives
90
90
  from scikit-learn's `BaseEstimator` and the matching mixin, so `clone`,
91
91
  `get_params` / `set_params`, `GridSearchCV`, `Pipeline` and
92
- `cross_val_score` take them, and all 39 pass
92
+ `cross_val_score` take them, and all 44 pass
93
93
  `sklearn.utils.estimator_checks.check_estimator` (`check-sklearn.py`
94
- runs it; the one expected failure is `LBFGSLogisticRegressionCV`'s
94
+ runs it; the expected failures are `DesignLasso`'s missing `n_iter_`,
95
+ since the derived kernel does not count its sweeps, and
96
+ `LBFGSLogisticRegressionCV`'s
95
97
  `n_iter_` of 0 for a C whose warm start is already converged, which
96
98
  scikit-learn's own `LogisticRegressionCV` reports on the same data).
97
99
  scikit-learn stays optional: without it a numpy-only stand-in gives
@@ -104,11 +106,37 @@ from scm2cpp_lasso import GramLasso
104
106
  GridSearchCV(GramLasso(), {"alpha": [0.01, 0.1, 1.0]}, cv=5).fit(X, y)
105
107
  ```
106
108
 
107
- `GramLasso`, `GramElasticNet` and `GramRidge` are the `fit(X, y)`
108
- forms, with the intercept, over the Gram-matrix solvers; `CovLasso`,
109
- `CovRidge`, `DerivedLasso`, `CovLogistic`, `CovGroupLasso` and
110
- `CovMultiTaskLasso` keep their data-in-the-constructor interface,
111
- which solves a whole path at once.
109
+ `CovLasso`, `CovRidge`, `DerivedLasso`, `CovLogistic`,
110
+ `CovGroupLasso` and `CovMultiTaskLasso` keep their
111
+ data-in-the-constructor interface, which solves a whole path at once;
112
+ each has a `fit(X, y)` form with the intercept beside it (0.15.0):
113
+
114
+ | data in the constructor | `fit(X, y)` | scikit-learn |
115
+ |---|---|---|
116
+ | `CovLasso` | `GramLasso`, `GramElasticNet` | Lasso, ElasticNet |
117
+ | `CovRidge` | `GramRidge` | Ridge |
118
+ | `DerivedLasso` | `DesignLasso` | Lasso |
119
+ | `CovLogistic` | `GramLogisticRegression` | LogisticRegression(l1_ratio=1, solver="liblinear") |
120
+ | `CovGroupLasso` | `GramGroupLasso` (`groups=None`: the lasso) | (none) |
121
+ | `CovMultiTaskLasso` | `GramMultiTaskLasso`, `GramMultiTaskElasticNet` | MultiTaskLasso, MultiTaskElasticNet |
122
+
123
+ Against scikit-learn on 400 x 30 with an intercept: `DesignLasso` and
124
+ `GramGroupLasso(groups=None)` agree with `Lasso` to 1.5e-10, the
125
+ multi-task pair with `MultiTaskLasso` / `MultiTaskElasticNet` to
126
+ 5e-14, and `GramLogisticRegression` reaches liblinear's objective to
127
+ eight digits with the same predictions.
128
+
129
+ Against the class each one wraps (one alpha, one core), which fits no
130
+ intercept and is unchanged: with `fit_intercept=False` the input checks
131
+ add 15 to 30 us a call (up to +50% on a 0.06 ms fit) and the finiteness
132
+ sum 8 to 14% on 20000 x 200 (none under `assume_finite`); with the
133
+ intercept, the column means add 20 to 30% on 20000 x 200 for the
134
+ Gram-matrix ones, which correct X'X
135
+ for the means rather than copy X to centre it, and about 55% for
136
+ `DesignLasso`, whose kernel needs the centred X itself.
137
+ `GramLogisticRegression` is slower than liblinear by its algorithm, 2.5x
138
+ at 20000 x 200 and more on small problems; `L1LogisticRegression` is
139
+ the fast one for that problem.
112
140
 
113
141
  What it costs is the input checks: X must be a finite 2-D float64
114
142
  array with the feature count of the fit. A plain C-ordered float64
@@ -148,6 +176,11 @@ package picks the fastest one present.
148
176
  | `GramLasso` | Lasso(precompute=True), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | no |
149
177
  | `GramElasticNet` | ElasticNet(precompute=True), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | no |
150
178
  | `GramRidge` | Ridge, fit(X, y) with the intercept, one or many targets | numpy (Cholesky) | CPU | Ridge |
179
+ | `GramMultiTaskLasso` | MultiTaskLasso, fit(X, Y) with the intercept | `lasso-cov.scm` | CPU | no |
180
+ | `GramMultiTaskElasticNet` | MultiTaskElasticNet, fit(X, Y) with the intercept | `lasso-cov.scm` | CPU | no |
181
+ | `GramGroupLasso` | (none; skglm GroupLasso), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | - |
182
+ | `GramLogisticRegression` | LogisticRegression(l1_ratio=1, solver='liblinear'), by majorization | `lasso-cov.scm` | CPU | no |
183
+ | `DesignLasso` | Lasso, fit(X, y) with the intercept, from the design | `lasso-design.scm (--derive)` | loops / BLAS / cuBLAS | no |
151
184
  | `CovLasso` | Lasso, ElasticNet, lasso_path(precompute=True) | `lasso-cov.scm` | CPU; GPU batch (fit_path_batch, bootstrap) | no |
152
185
  | `CovLassoCV` | LassoCV, ElasticNetCV | `lasso-cov.scm` | CPU; GPU batch | no |
153
186
  | `DerivedLasso` | lasso_path, from the design | `lasso-design.scm (--derive)` | loops / BLAS / cuBLAS | no |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "scm2cpp-lasso"
7
- version = "0.14.0"
7
+ version = "0.15.0"
8
8
  description = "scikit-learn estimators (lasso, elastic net, ridge, ARD, SVM, SVR, logistic regression, GLMs, Huber, Theil-Sen, F-score selection, RFE, NMF, Gaussian mixture, FastICA, PCA, graphical lasso, RANSAC, SGD, OMP, LARS) translated from Scheme to C++ by scm2cpp; GPU batch solver and CUDA kernels included"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -53,8 +53,10 @@ __all__ = ["GramLasso", "GramElasticNet", "GramRidge", "CovLasso", "CovRidge", "
53
53
  "L1LinearSVM", "WeiszfeldTheilSen", "f_regression", "f_classif", "SelectKBest",
54
54
  "SelectPercentile", "GramRFE",
55
55
  "CovMultiTaskLasso", "CovMultiTaskLassoCV", "DerivedLasso",
56
+ "GramLogisticRegression", "GramGroupLasso", "GramMultiTaskLasso",
57
+ "GramMultiTaskElasticNet", "DesignLasso",
56
58
  "cuda_available", "derived_backend", "kernel"]
57
- __version__ = "0.14.0"
59
+ __version__ = "0.15.0"
58
60
 
59
61
  _BATCH = load_batch_lib()
60
62
  _DP = ctypes.POINTER(ctypes.c_double)
@@ -374,6 +376,35 @@ class DerivedLasso:
374
376
  return betas.reshape(lams.size, self.p)
375
377
 
376
378
 
379
+ class DesignLasso(Tagged, RegressorMixin, BaseEstimator):
380
+ """scikit-learn's ``Lasso`` interface over DerivedLasso: fit(X, y)
381
+ centres the data when ``fit_intercept`` and runs the derived kernel
382
+ at one penalty ``alpha``, same objective as GramLasso. ``tol`` is
383
+ the kernel's (the largest absolute move of one coefficient in a
384
+ sweep), ``max_iter`` its sweep cap and ``backend`` DerivedLasso's.
385
+ The kernel does not report how many sweeps it ran, so there is no
386
+ n_iter_.
387
+
388
+ Attributes after fit: coef_, intercept_, n_features_in_.
389
+ """
390
+
391
+ def __init__(self, alpha=1.0, *, fit_intercept=True, tol=1e-8, max_iter=100000,
392
+ backend=None):
393
+ self.alpha, self.fit_intercept, self.tol, self.max_iter = alpha, fit_intercept, tol, max_iter
394
+ self.backend = backend
395
+
396
+ def fit(self, X, y):
397
+ X, y = fit_xy(self, X, y)
398
+ Xc, yc, xm, ym = _centered(X, y, self.fit_intercept)
399
+ m = DerivedLasso(Xc, yc, backend=self.backend)
400
+ self.coef_ = m.fit(float(self.alpha), tol=float(self.tol), max_sweeps=int(self.max_iter))
401
+ self.intercept_ = float(ym - xm @ self.coef_)
402
+ return self
403
+
404
+ def predict(self, X):
405
+ return pred_x(self, X) @ self.coef_ + self.intercept_
406
+
407
+
377
408
  def derived_backend():
378
409
  """The derived kernel variant DerivedLasso takes by default: 'cublas'
379
410
  when built and a device answers, else 'blas', else 'loops'; None
@@ -669,6 +700,28 @@ def _centered(X, y, fit_intercept):
669
700
  return np.ascontiguousarray(X - xm), y - ym, xm, ym
670
701
 
671
702
 
703
+ def _centered_gram(X, y, fit_intercept):
704
+ """(Xc'Xc, Xc'yc, xm, ym) for the centred data Xc, yc, without
705
+ forming Xc: X'X - n xm xm' and X'y - n xm ym, which saves the copy
706
+ of X (as long as the Gram product itself on 20000 x 200 rows, where
707
+ it took the intercept from free to twice the fit). The correction
708
+ cancels digits when a column's mean is large against its spread,
709
+ about (mean / sd)^2 ulps, so a column with mean over 100 sd (or no
710
+ spread at all) sends the whole fit through the centred copy."""
711
+ n = X.shape[0]
712
+ G, c = X.T @ X, X.T @ y
713
+ if not fit_intercept:
714
+ return G, c, np.zeros(X.shape[1]), np.zeros(y.shape[1:]) if y.ndim > 1 else 0.0
715
+ xm, ym = X.mean(axis=0), y.mean(axis=0)
716
+ nm2 = n * xm * xm
717
+ if np.all(np.diagonal(G) - nm2 > 1e-4 * nm2):
718
+ G -= np.outer(xm, n * xm)
719
+ c -= np.multiply.outer(n * xm, ym)
720
+ return G, c, xm, ym
721
+ Xc, yc = np.ascontiguousarray(X - xm), y - ym
722
+ return Xc.T @ Xc, Xc.T @ yc, xm, ym
723
+
724
+
672
725
  class GramElasticNet(Tagged, RegressorMixin, BaseEstimator):
673
726
  """scikit-learn's ``ElasticNet(precompute=True)`` interface over
674
727
  CovLasso: fit(X, y) centres the data when ``fit_intercept``, forms
@@ -692,10 +745,10 @@ class GramElasticNet(Tagged, RegressorMixin, BaseEstimator):
692
745
 
693
746
  def fit(self, X, y):
694
747
  X, y = fit_xy(self, X, y)
695
- Xc, yc, xm, ym = _centered(X, y, self.fit_intercept)
696
- n, p = Xc.shape
697
- g = np.ascontiguousarray((Xc.T @ Xc).ravel())
698
- c = np.ascontiguousarray(Xc.T @ yc)
748
+ n, p = X.shape
749
+ G, c, xm, ym = _centered_gram(X, y, self.fit_intercept)
750
+ g = np.ascontiguousarray(G.ravel())
751
+ c = np.ascontiguousarray(c)
699
752
  lam, r = float(self.alpha), float(self.l1_ratio)
700
753
  tol, cap = float(self.tol), int(self.max_iter)
701
754
  beta, prev = np.zeros(p), np.empty(p)
@@ -747,11 +800,9 @@ class GramRidge(Tagged, RegressorMixin, BaseEstimator):
747
800
 
748
801
  def fit(self, X, y):
749
802
  X, y = fit_xy(self, X, y, multi_output=True)
750
- Xc, yc, xm, ym = _centered(X, y, self.fit_intercept)
751
- p = Xc.shape[1]
752
- A = Xc.T @ Xc
803
+ p = X.shape[1]
804
+ A, b, xm, ym = _centered_gram(X, y, self.fit_intercept)
753
805
  A.flat[::p + 1] += float(self.alpha)
754
- b = Xc.T @ yc
755
806
  try:
756
807
  coef = np.linalg.solve(A, b)
757
808
  except np.linalg.LinAlgError:
@@ -2686,7 +2737,9 @@ class CovLogistic:
2686
2737
 
2687
2738
  def _fit_one(self, beta, lam, tol, inner_sweeps, max_rounds):
2688
2739
  beta = beta.copy()
2740
+ self._rounds = 0
2689
2741
  for _ in range(max_rounds):
2742
+ self._rounds += 1
2690
2743
  prev = beta.copy()
2691
2744
  # the majorizer at beta: quadratic G/4, working
2692
2745
  # correlations exactly X'(y - mu)
@@ -2699,6 +2752,58 @@ class CovLogistic:
2699
2752
  return beta
2700
2753
 
2701
2754
 
2755
+ class GramLogisticRegression(Tagged, ClassifierMixin, BaseEstimator):
2756
+ """scikit-learn's ``LogisticRegression(l1_ratio=1, solver="liblinear")``
2757
+ interface over CovLogistic: the same problem,
2758
+
2759
+ min_w ||w||_1 + C sum_i log(1 + exp(-y_i (w.x_i + b))),
2760
+
2761
+ solved by majorization over the Gram matrix instead of liblinear's
2762
+ Newton steps. As in liblinear the intercept is the weight of a
2763
+ constant column of value ``intercept_scaling`` and is penalized
2764
+ with the rest. ``tol`` is CovLogistic's (the largest coefficient
2765
+ move in a round, relative to their size) and ``max_iter`` caps the
2766
+ rounds. Two classes.
2767
+
2768
+ Attributes after fit: classes_, coef_, intercept_, n_iter_.
2769
+ """
2770
+
2771
+ _binary_only = True
2772
+
2773
+ def __init__(self, C=1.0, *, fit_intercept=True, intercept_scaling=1.0, tol=1e-7,
2774
+ max_iter=500):
2775
+ self.C, self.fit_intercept, self.intercept_scaling = C, fit_intercept, intercept_scaling
2776
+ self.tol, self.max_iter = tol, max_iter
2777
+
2778
+ def fit(self, X, y):
2779
+ X, y = fit_xy(self, X, y, classifier=True)
2780
+ n = X.shape[0]
2781
+ self.classes_, ys = _binary_labels(y)
2782
+ s = float(self.intercept_scaling)
2783
+ if self.fit_intercept:
2784
+ X = np.column_stack([X, np.full(n, s)])
2785
+ m = CovLogistic(X, (ys > 0).astype(np.float64))
2786
+ lam = 1.0 / (n * float(self.C))
2787
+ beta = m._fit_one(np.zeros(m.p), lam, float(self.tol), 200, int(self.max_iter))
2788
+ self.n_iter_ = m._rounds
2789
+ if self.fit_intercept:
2790
+ self.coef_, self.intercept_ = beta[:-1].copy(), float(beta[-1]) * s
2791
+ else:
2792
+ self.coef_, self.intercept_ = beta, 0.0
2793
+ return self
2794
+
2795
+ def decision_function(self, X):
2796
+ return pred_x(self, X) @ self.coef_ + self.intercept_
2797
+
2798
+ def predict_proba(self, X):
2799
+ p1 = 1.0 / (1.0 + np.exp(-self.decision_function(X)))
2800
+ return np.column_stack([1.0 - p1, p1])
2801
+
2802
+ def predict(self, X):
2803
+ z = self.decision_function(X)
2804
+ return self.classes_[(z > 0).astype(int)]
2805
+
2806
+
2702
2807
  class CovGroupLasso:
2703
2808
  """Group lasso over a design given by its Gram matrix.
2704
2809
 
@@ -2778,9 +2883,13 @@ class CovGroupLasso:
2778
2883
 
2779
2884
  def _fit_one(self, beta, c, lam, tol, max_sweeps):
2780
2885
  thr = lam * self.nobs
2886
+ self._sweeps = 0
2781
2887
  for _ in range(max_sweeps):
2888
+ self._sweeps += 1
2782
2889
  moved = 0.0
2783
2890
  for g, w, L in zip(self.groups, self.wg, self.Lg):
2891
+ if L <= 0.0: # all-zero columns: the group stays at zero
2892
+ continue
2784
2893
  bg = beta[g]
2785
2894
  z = bg + c[g] / L
2786
2895
  nz = float(np.linalg.norm(z))
@@ -2809,6 +2918,41 @@ class CovGroupLasso:
2809
2918
  return quad / self.nobs + lam * pen
2810
2919
 
2811
2920
 
2921
+ class GramGroupLasso(Tagged, RegressorMixin, BaseEstimator):
2922
+ """A ``Lasso``-style interface over CovGroupLasso: fit(X, y) centres
2923
+ the data when ``fit_intercept`` and runs the block descent at
2924
+ ``alpha``,
2925
+
2926
+ (1 / 2 n) ||y - X b - b0||^2 + alpha sum_g sqrt(|g|) ||b_g||_2.
2927
+
2928
+ ``groups`` is a list of index arrays partitioning the columns, or
2929
+ None for one group per column (the lasso). ``tol`` is
2930
+ CovGroupLasso's and ``max_iter`` its sweep cap.
2931
+
2932
+ Attributes after fit: coef_, intercept_, n_iter_, n_features_in_.
2933
+ """
2934
+
2935
+ def __init__(self, alpha=1.0, *, groups=None, fit_intercept=True, tol=1e-8,
2936
+ max_iter=10000):
2937
+ self.alpha, self.groups, self.fit_intercept = alpha, groups, fit_intercept
2938
+ self.tol, self.max_iter = tol, max_iter
2939
+
2940
+ def fit(self, X, y):
2941
+ X, y = fit_xy(self, X, y)
2942
+ n, p = X.shape
2943
+ G, c, xm, ym = _centered_gram(X, y, self.fit_intercept)
2944
+ groups = [[j] for j in range(p)] if self.groups is None else self.groups
2945
+ m = CovGroupLasso(gram=G, corr=c, nobs=n, groups=groups)
2946
+ beta, _ = m._fit_one(np.zeros(p), m.c0.copy(), float(self.alpha), float(self.tol),
2947
+ int(self.max_iter))
2948
+ self.coef_, self.n_iter_ = beta, m._sweeps
2949
+ self.intercept_ = float(ym - xm @ self.coef_)
2950
+ return self
2951
+
2952
+ def predict(self, X):
2953
+ return pred_x(self, X) @ self.coef_ + self.intercept_
2954
+
2955
+
2812
2956
  class CovMultiTaskLasso:
2813
2957
  """Multi-task lasso and elastic net over a design's Gram matrix.
2814
2958
 
@@ -2929,6 +3073,69 @@ class CovMultiTaskLasso:
2929
3073
  return out.reshape(lambdas.size, self.p, self.n_tasks)
2930
3074
 
2931
3075
 
3076
+ class GramMultiTaskElasticNet(Tagged, RegressorMixin, BaseEstimator):
3077
+ """scikit-learn's ``MultiTaskElasticNet`` interface over
3078
+ CovMultiTaskLasso: fit(X, Y) centres the data when
3079
+ ``fit_intercept``, forms the Gram matrix once and runs the row-block
3080
+ sweep at ``alpha``. Y is (n, n_tasks); coef_ is (n_tasks, p) as
3081
+ scikit-learn has it. ``tol`` is CovMultiTaskLasso's and
3082
+ ``max_iter`` its sweep cap.
3083
+
3084
+ Attributes after fit: coef_, intercept_, n_iter_, n_features_in_.
3085
+ """
3086
+
3087
+ _multi_output = "only"
3088
+
3089
+ def __init__(self, alpha=1.0, *, l1_ratio=0.5, fit_intercept=True, tol=1e-8,
3090
+ max_iter=100000):
3091
+ self.alpha, self.l1_ratio, self.fit_intercept = alpha, l1_ratio, fit_intercept
3092
+ self.tol, self.max_iter = tol, max_iter
3093
+
3094
+ def fit(self, X, y):
3095
+ X, y = fit_xy(self, X, y, multi_output=True)
3096
+ if y.ndim != 2:
3097
+ raise ValueError("For mono-task outputs, use GramElasticNet or GramLasso; "
3098
+ "y must be (n_samples, n_tasks)")
3099
+ n, p = X.shape
3100
+ t = y.shape[1]
3101
+ G, c, xm, ym = _centered_gram(X, y, self.fit_intercept)
3102
+ g = np.ascontiguousarray(G.ravel())
3103
+ c = np.ascontiguousarray(c.ravel())
3104
+ lam, r = float(self.alpha), float(self.l1_ratio)
3105
+ tol, cap = float(self.tol), int(self.max_iter)
3106
+ W, prev = np.zeros(p * t), np.empty(p * t)
3107
+ swept = 0
3108
+ while swept < cap:
3109
+ chunk = min(20, cap - swept)
3110
+ prev[:] = W
3111
+ kernel.mt_descend(g, c, W, lam * r, lam * (1.0 - r), chunk, float(n), p, t)
3112
+ swept += chunk
3113
+ if np.max(np.abs(W - prev)) < tol * _scale(W):
3114
+ break
3115
+ W = W.reshape(p, t)
3116
+ self.coef_, self.n_iter_ = np.ascontiguousarray(W.T), swept
3117
+ self.intercept_ = ym - xm @ W
3118
+ return self
3119
+
3120
+ def predict(self, X):
3121
+ return pred_x(self, X) @ self.coef_.T + self.intercept_
3122
+
3123
+
3124
+ class GramMultiTaskLasso(GramMultiTaskElasticNet):
3125
+ """scikit-learn's ``MultiTaskLasso`` interface over CovMultiTaskLasso:
3126
+ GramMultiTaskElasticNet with l1_ratio 1.
3127
+
3128
+ Attributes after fit: coef_, intercept_, n_iter_, n_features_in_.
3129
+ """
3130
+
3131
+ def __init__(self, alpha=1.0, *, fit_intercept=True, tol=1e-8, max_iter=100000):
3132
+ self.alpha, self.fit_intercept, self.tol, self.max_iter = alpha, fit_intercept, tol, max_iter
3133
+
3134
+ @property
3135
+ def l1_ratio(self):
3136
+ return 1.0
3137
+
3138
+
2932
3139
  class CovMultiTaskLassoCV(Tagged, RegressorMixin, BaseEstimator):
2933
3140
  """scikit-learn's ``MultiTaskLassoCV`` (``MultiTaskElasticNetCV``
2934
3141
  with ``l1_ratio``) over the covariance machinery.
@@ -191,7 +191,8 @@ def _reset_names(est):
191
191
 
192
192
  def fit_xy(est, X, y, *, y_numeric=True, multi_output=False, classifier=False):
193
193
  """X as a float64 C-ordered 2-D array and y, validated, n_features_in_ set."""
194
- if (_plain(X) and type(y) is np.ndarray and y.ndim == 1 and y.shape[0] == X.shape[0]
194
+ if (_plain(X) and type(y) is np.ndarray and y.shape[0:1] == X.shape[0:1]
195
+ and (y.ndim == 1 or (multi_output and y.ndim == 2 and y.shape[1] > 1))
195
196
  and _finite(X)):
196
197
  if classifier:
197
198
  ok = _labels_ok(y)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scm2cpp-lasso
3
- Version: 0.14.0
3
+ Version: 0.15.0
4
4
  Summary: scikit-learn estimators (lasso, elastic net, ridge, ARD, SVM, SVR, logistic regression, GLMs, Huber, Theil-Sen, F-score selection, RFE, NMF, Gaussian mixture, FastICA, PCA, graphical lasso, RANSAC, SGD, OMP, LARS) translated from Scheme to C++ by scm2cpp; GPU batch solver and CUDA kernels included
5
5
  Author: Hirotaka Niitsuma
6
6
  License: MIT
@@ -107,9 +107,11 @@ them first; this solver does not do it for you.
107
107
  From 0.14.0 every estimator with a `fit(X, y)` (or `fit(X)`) derives
108
108
  from scikit-learn's `BaseEstimator` and the matching mixin, so `clone`,
109
109
  `get_params` / `set_params`, `GridSearchCV`, `Pipeline` and
110
- `cross_val_score` take them, and all 39 pass
110
+ `cross_val_score` take them, and all 44 pass
111
111
  `sklearn.utils.estimator_checks.check_estimator` (`check-sklearn.py`
112
- runs it; the one expected failure is `LBFGSLogisticRegressionCV`'s
112
+ runs it; the expected failures are `DesignLasso`'s missing `n_iter_`,
113
+ since the derived kernel does not count its sweeps, and
114
+ `LBFGSLogisticRegressionCV`'s
113
115
  `n_iter_` of 0 for a C whose warm start is already converged, which
114
116
  scikit-learn's own `LogisticRegressionCV` reports on the same data).
115
117
  scikit-learn stays optional: without it a numpy-only stand-in gives
@@ -122,11 +124,37 @@ from scm2cpp_lasso import GramLasso
122
124
  GridSearchCV(GramLasso(), {"alpha": [0.01, 0.1, 1.0]}, cv=5).fit(X, y)
123
125
  ```
124
126
 
125
- `GramLasso`, `GramElasticNet` and `GramRidge` are the `fit(X, y)`
126
- forms, with the intercept, over the Gram-matrix solvers; `CovLasso`,
127
- `CovRidge`, `DerivedLasso`, `CovLogistic`, `CovGroupLasso` and
128
- `CovMultiTaskLasso` keep their data-in-the-constructor interface,
129
- which solves a whole path at once.
127
+ `CovLasso`, `CovRidge`, `DerivedLasso`, `CovLogistic`,
128
+ `CovGroupLasso` and `CovMultiTaskLasso` keep their
129
+ data-in-the-constructor interface, which solves a whole path at once;
130
+ each has a `fit(X, y)` form with the intercept beside it (0.15.0):
131
+
132
+ | data in the constructor | `fit(X, y)` | scikit-learn |
133
+ |---|---|---|
134
+ | `CovLasso` | `GramLasso`, `GramElasticNet` | Lasso, ElasticNet |
135
+ | `CovRidge` | `GramRidge` | Ridge |
136
+ | `DerivedLasso` | `DesignLasso` | Lasso |
137
+ | `CovLogistic` | `GramLogisticRegression` | LogisticRegression(l1_ratio=1, solver="liblinear") |
138
+ | `CovGroupLasso` | `GramGroupLasso` (`groups=None`: the lasso) | (none) |
139
+ | `CovMultiTaskLasso` | `GramMultiTaskLasso`, `GramMultiTaskElasticNet` | MultiTaskLasso, MultiTaskElasticNet |
140
+
141
+ Against scikit-learn on 400 x 30 with an intercept: `DesignLasso` and
142
+ `GramGroupLasso(groups=None)` agree with `Lasso` to 1.5e-10, the
143
+ multi-task pair with `MultiTaskLasso` / `MultiTaskElasticNet` to
144
+ 5e-14, and `GramLogisticRegression` reaches liblinear's objective to
145
+ eight digits with the same predictions.
146
+
147
+ Against the class each one wraps (one alpha, one core), which fits no
148
+ intercept and is unchanged: with `fit_intercept=False` the input checks
149
+ add 15 to 30 us a call (up to +50% on a 0.06 ms fit) and the finiteness
150
+ sum 8 to 14% on 20000 x 200 (none under `assume_finite`); with the
151
+ intercept, the column means add 20 to 30% on 20000 x 200 for the
152
+ Gram-matrix ones, which correct X'X
153
+ for the means rather than copy X to centre it, and about 55% for
154
+ `DesignLasso`, whose kernel needs the centred X itself.
155
+ `GramLogisticRegression` is slower than liblinear by its algorithm, 2.5x
156
+ at 20000 x 200 and more on small problems; `L1LogisticRegression` is
157
+ the fast one for that problem.
130
158
 
131
159
  What it costs is the input checks: X must be a finite 2-D float64
132
160
  array with the feature count of the fit. A plain C-ordered float64
@@ -166,6 +194,11 @@ package picks the fastest one present.
166
194
  | `GramLasso` | Lasso(precompute=True), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | no |
167
195
  | `GramElasticNet` | ElasticNet(precompute=True), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | no |
168
196
  | `GramRidge` | Ridge, fit(X, y) with the intercept, one or many targets | numpy (Cholesky) | CPU | Ridge |
197
+ | `GramMultiTaskLasso` | MultiTaskLasso, fit(X, Y) with the intercept | `lasso-cov.scm` | CPU | no |
198
+ | `GramMultiTaskElasticNet` | MultiTaskElasticNet, fit(X, Y) with the intercept | `lasso-cov.scm` | CPU | no |
199
+ | `GramGroupLasso` | (none; skglm GroupLasso), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | - |
200
+ | `GramLogisticRegression` | LogisticRegression(l1_ratio=1, solver='liblinear'), by majorization | `lasso-cov.scm` | CPU | no |
201
+ | `DesignLasso` | Lasso, fit(X, y) with the intercept, from the design | `lasso-design.scm (--derive)` | loops / BLAS / cuBLAS | no |
169
202
  | `CovLasso` | Lasso, ElasticNet, lasso_path(precompute=True) | `lasso-cov.scm` | CPU; GPU batch (fit_path_batch, bootstrap) | no |
170
203
  | `CovLassoCV` | LassoCV, ElasticNetCV | `lasso-cov.scm` | CPU; GPU batch | no |
171
204
  | `DerivedLasso` | lasso_path, from the design | `lasso-design.scm (--derive)` | loops / BLAS / cuBLAS | no |
File without changes
File without changes