scm2cpp-lasso 0.14.0__tar.gz → 0.15.1__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.1}/PKG-INFO +47 -7
  2. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/README.md +46 -6
  3. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/pyproject.toml +1 -1
  4. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/__init__.py +222 -13
  5. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_compat.py +2 -1
  6. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/derived_blas/lasso_design.hpp +17 -14
  7. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/derived_cublas/lasso_design.hpp +18 -15
  8. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/derived_loops/lasso_design.hpp +29 -26
  9. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso.egg-info/PKG-INFO +47 -7
  10. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/setup.cfg +0 -0
  11. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/setup.py +0 -0
  12. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/__init__.py +0 -0
  13. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/_loader.py +0 -0
  14. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/_loader_accel_cublas.py +0 -0
  15. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/_loader_classifiers.py +0 -0
  16. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/_loader_derived_blas.py +0 -0
  17. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/_loader_derived_cublas.py +0 -0
  18. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/_loader_derived_loops.py +0 -0
  19. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/_loader_gpu_cuda.py +0 -0
  20. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/_loader_gramops_blas.py +0 -0
  21. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/_loader_gramops_loops.py +0 -0
  22. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/_loader_nmf_blas.py +0 -0
  23. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/_loader_nmf_cublas.py +0 -0
  24. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/_loader_nmf_loops.py +0 -0
  25. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/accel_cublas/accel.cpp +0 -0
  26. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/accel_cublas/accel.hpp +0 -0
  27. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/accel_cublas/accel_capi.cpp +0 -0
  28. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/classifiers.cpp +0 -0
  29. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/classifiers.hpp +0 -0
  30. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/classifiers_capi.cpp +0 -0
  31. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/derived_blas/lasso_design.cpp +0 -0
  32. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/derived_blas/lasso_design_capi.cpp +0 -0
  33. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/derived_cublas/lasso_design.cpp +0 -0
  34. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/derived_cublas/lasso_design_capi.cpp +0 -0
  35. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/derived_loops/lasso_design.cpp +0 -0
  36. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/derived_loops/lasso_design_capi.cpp +0 -0
  37. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/gpu_cuda/gpu.cpp +0 -0
  38. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/gpu_cuda/gpu.hpp +0 -0
  39. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/gpu_cuda/gpu_capi.cpp +0 -0
  40. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/gramops_blas/gramops.cpp +0 -0
  41. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/gramops_blas/gramops.hpp +0 -0
  42. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/gramops_blas/gramops_capi.cpp +0 -0
  43. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/gramops_loops/gramops.cpp +0 -0
  44. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/gramops_loops/gramops.hpp +0 -0
  45. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/gramops_loops/gramops_capi.cpp +0 -0
  46. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/lasso_cov.cpp +0 -0
  47. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/lasso_cov.hpp +0 -0
  48. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/lasso_cov_capi.cpp +0 -0
  49. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/nmf_blas/nmf_cd.cpp +0 -0
  50. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/nmf_blas/nmf_cd.hpp +0 -0
  51. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/nmf_blas/nmf_cd_capi.cpp +0 -0
  52. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/nmf_cublas/nmf_cd.cpp +0 -0
  53. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/nmf_cublas/nmf_cd.hpp +0 -0
  54. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/nmf_cublas/nmf_cd_capi.cpp +0 -0
  55. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/nmf_loops/nmf_cd.cpp +0 -0
  56. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/nmf_loops/nmf_cd.hpp +0 -0
  57. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/nmf_loops/nmf_cd_capi.cpp +0 -0
  58. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/scm2cpp-blas.hpp +0 -0
  59. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/scm2cpp-cublas.hpp +0 -0
  60. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/scm2cpp-cuda.hpp +0 -0
  61. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_generated/scm2cpp.hpp +0 -0
  62. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/_libfind.py +0 -0
  63. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso/batch_capi.cu +0 -0
  64. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso.egg-info/SOURCES.txt +0 -0
  65. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso.egg-info/dependency_links.txt +0 -0
  66. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/src/scm2cpp_lasso.egg-info/requires.txt +0 -0
  67. {scm2cpp_lasso-0.14.0 → scm2cpp_lasso-0.15.1}/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.1
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,7 +107,7 @@ 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
112
  runs it; the one expected failure is `LBFGSLogisticRegressionCV`'s
113
113
  `n_iter_` of 0 for a C whose warm start is already converged, which
@@ -122,11 +122,37 @@ from scm2cpp_lasso import GramLasso
122
122
  GridSearchCV(GramLasso(), {"alpha": [0.01, 0.1, 1.0]}, cv=5).fit(X, y)
123
123
  ```
124
124
 
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.
125
+ `CovLasso`, `CovRidge`, `DerivedLasso`, `CovLogistic`,
126
+ `CovGroupLasso` and `CovMultiTaskLasso` keep their
127
+ data-in-the-constructor interface, which solves a whole path at once;
128
+ each has a `fit(X, y)` form with the intercept beside it (0.15.0):
129
+
130
+ | data in the constructor | `fit(X, y)` | scikit-learn |
131
+ |---|---|---|
132
+ | `CovLasso` | `GramLasso`, `GramElasticNet` | Lasso, ElasticNet |
133
+ | `CovRidge` | `GramRidge` | Ridge |
134
+ | `DerivedLasso` | `DesignLasso` | Lasso |
135
+ | `CovLogistic` | `GramLogisticRegression` | LogisticRegression(l1_ratio=1, solver="liblinear") |
136
+ | `CovGroupLasso` | `GramGroupLasso` (`groups=None`: the lasso) | (none) |
137
+ | `CovMultiTaskLasso` | `GramMultiTaskLasso`, `GramMultiTaskElasticNet` | MultiTaskLasso, MultiTaskElasticNet |
138
+
139
+ Against scikit-learn on 400 x 30 with an intercept: `DesignLasso` and
140
+ `GramGroupLasso(groups=None)` agree with `Lasso` to 1.5e-10, the
141
+ multi-task pair with `MultiTaskLasso` / `MultiTaskElasticNet` to
142
+ 5e-14, and `GramLogisticRegression` reaches liblinear's objective to
143
+ eight digits with the same predictions.
144
+
145
+ Against the class each one wraps (one alpha, one core), which fits no
146
+ intercept and is unchanged: with `fit_intercept=False` the input checks
147
+ add 15 to 30 us a call (up to +50% on a 0.06 ms fit) and the finiteness
148
+ sum 8 to 14% on 20000 x 200 (none under `assume_finite`); with the
149
+ intercept, the column means add 20 to 30% on 20000 x 200 for the
150
+ Gram-matrix ones, which correct X'X
151
+ for the means rather than copy X to centre it, and about 55% for
152
+ `DesignLasso`, whose kernel needs the centred X itself.
153
+ `GramLogisticRegression` is slower than liblinear by its algorithm, 2.5x
154
+ at 20000 x 200 and more on small problems; `L1LogisticRegression` is
155
+ the fast one for that problem.
130
156
 
131
157
  What it costs is the input checks: X must be a finite 2-D float64
132
158
  array with the feature count of the fit. A plain C-ordered float64
@@ -166,6 +192,11 @@ package picks the fastest one present.
166
192
  | `GramLasso` | Lasso(precompute=True), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | no |
167
193
  | `GramElasticNet` | ElasticNet(precompute=True), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | no |
168
194
  | `GramRidge` | Ridge, fit(X, y) with the intercept, one or many targets | numpy (Cholesky) | CPU | Ridge |
195
+ | `GramMultiTaskLasso` | MultiTaskLasso, fit(X, Y) with the intercept | `lasso-cov.scm` | CPU | no |
196
+ | `GramMultiTaskElasticNet` | MultiTaskElasticNet, fit(X, Y) with the intercept | `lasso-cov.scm` | CPU | no |
197
+ | `GramGroupLasso` | (none; skglm GroupLasso), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | - |
198
+ | `GramLogisticRegression` | LogisticRegression(l1_ratio=1, solver='liblinear'), by majorization | `lasso-cov.scm` | CPU | no |
199
+ | `DesignLasso` | Lasso, fit(X, y) with the intercept, from the design | `lasso-design.scm (--derive)` | loops / BLAS / cuBLAS | no |
169
200
  | `CovLasso` | Lasso, ElasticNet, lasso_path(precompute=True) | `lasso-cov.scm` | CPU; GPU batch (fit_path_batch, bootstrap) | no |
170
201
  | `CovLassoCV` | LassoCV, ElasticNetCV | `lasso-cov.scm` | CPU; GPU batch | no |
171
202
  | `DerivedLasso` | lasso_path, from the design | `lasso-design.scm (--derive)` | loops / BLAS / cuBLAS | no |
@@ -612,6 +643,15 @@ matrix (`KernelSVM`) or transposing X into columns
612
643
  | `L1LogisticRegression`, 20,000 x 500, C=0.05 | 1.20 s | 2.23 s | 1.9x | 106.77913 / 106.77915 | 100% |
613
644
  | `L1LogisticRegression`, 100,000 x 200, C=0.05 | 9.06 s | 18.7 s | 2.1x | 349.61154 / 349.61216 | 100% |
614
645
 
646
+ On 2,000 x 20,000 neither side reaches the optimum at tol 1e-4 (the
647
+ objective at tol 1e-10 is 0.0519588), and where each stops depends on
648
+ the coordinate order: over eight seeds ours stops at 0.05240 to
649
+ 0.05251 (0.9 to 1.1% above the optimum), `LinearSVC`, whose order is
650
+ random unless `random_state` is given, at 0.05242 to 0.05319 (0.9 to
651
+ 2.4%). When `LinearSVC` draws one of its worse orders the gap passes
652
+ the script's 0.5% bound and the row prints `<-- disagrees`; ours is
653
+ then the better solution of the two, so the flag there is not a fault.
654
+
615
655
  The regressors, from the same script (C = 1, epsilon 0.1):
616
656
 
617
657
  | problem | ours | scikit-learn | ratio | agreement |
@@ -89,7 +89,7 @@ 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
94
  runs it; the one expected failure is `LBFGSLogisticRegressionCV`'s
95
95
  `n_iter_` of 0 for a C whose warm start is already converged, which
@@ -104,11 +104,37 @@ from scm2cpp_lasso import GramLasso
104
104
  GridSearchCV(GramLasso(), {"alpha": [0.01, 0.1, 1.0]}, cv=5).fit(X, y)
105
105
  ```
106
106
 
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.
107
+ `CovLasso`, `CovRidge`, `DerivedLasso`, `CovLogistic`,
108
+ `CovGroupLasso` and `CovMultiTaskLasso` keep their
109
+ data-in-the-constructor interface, which solves a whole path at once;
110
+ each has a `fit(X, y)` form with the intercept beside it (0.15.0):
111
+
112
+ | data in the constructor | `fit(X, y)` | scikit-learn |
113
+ |---|---|---|
114
+ | `CovLasso` | `GramLasso`, `GramElasticNet` | Lasso, ElasticNet |
115
+ | `CovRidge` | `GramRidge` | Ridge |
116
+ | `DerivedLasso` | `DesignLasso` | Lasso |
117
+ | `CovLogistic` | `GramLogisticRegression` | LogisticRegression(l1_ratio=1, solver="liblinear") |
118
+ | `CovGroupLasso` | `GramGroupLasso` (`groups=None`: the lasso) | (none) |
119
+ | `CovMultiTaskLasso` | `GramMultiTaskLasso`, `GramMultiTaskElasticNet` | MultiTaskLasso, MultiTaskElasticNet |
120
+
121
+ Against scikit-learn on 400 x 30 with an intercept: `DesignLasso` and
122
+ `GramGroupLasso(groups=None)` agree with `Lasso` to 1.5e-10, the
123
+ multi-task pair with `MultiTaskLasso` / `MultiTaskElasticNet` to
124
+ 5e-14, and `GramLogisticRegression` reaches liblinear's objective to
125
+ eight digits with the same predictions.
126
+
127
+ Against the class each one wraps (one alpha, one core), which fits no
128
+ intercept and is unchanged: with `fit_intercept=False` the input checks
129
+ add 15 to 30 us a call (up to +50% on a 0.06 ms fit) and the finiteness
130
+ sum 8 to 14% on 20000 x 200 (none under `assume_finite`); with the
131
+ intercept, the column means add 20 to 30% on 20000 x 200 for the
132
+ Gram-matrix ones, which correct X'X
133
+ for the means rather than copy X to centre it, and about 55% for
134
+ `DesignLasso`, whose kernel needs the centred X itself.
135
+ `GramLogisticRegression` is slower than liblinear by its algorithm, 2.5x
136
+ at 20000 x 200 and more on small problems; `L1LogisticRegression` is
137
+ the fast one for that problem.
112
138
 
113
139
  What it costs is the input checks: X must be a finite 2-D float64
114
140
  array with the feature count of the fit. A plain C-ordered float64
@@ -148,6 +174,11 @@ package picks the fastest one present.
148
174
  | `GramLasso` | Lasso(precompute=True), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | no |
149
175
  | `GramElasticNet` | ElasticNet(precompute=True), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | no |
150
176
  | `GramRidge` | Ridge, fit(X, y) with the intercept, one or many targets | numpy (Cholesky) | CPU | Ridge |
177
+ | `GramMultiTaskLasso` | MultiTaskLasso, fit(X, Y) with the intercept | `lasso-cov.scm` | CPU | no |
178
+ | `GramMultiTaskElasticNet` | MultiTaskElasticNet, fit(X, Y) with the intercept | `lasso-cov.scm` | CPU | no |
179
+ | `GramGroupLasso` | (none; skglm GroupLasso), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | - |
180
+ | `GramLogisticRegression` | LogisticRegression(l1_ratio=1, solver='liblinear'), by majorization | `lasso-cov.scm` | CPU | no |
181
+ | `DesignLasso` | Lasso, fit(X, y) with the intercept, from the design | `lasso-design.scm (--derive)` | loops / BLAS / cuBLAS | no |
151
182
  | `CovLasso` | Lasso, ElasticNet, lasso_path(precompute=True) | `lasso-cov.scm` | CPU; GPU batch (fit_path_batch, bootstrap) | no |
152
183
  | `CovLassoCV` | LassoCV, ElasticNetCV | `lasso-cov.scm` | CPU; GPU batch | no |
153
184
  | `DerivedLasso` | lasso_path, from the design | `lasso-design.scm (--derive)` | loops / BLAS / cuBLAS | no |
@@ -594,6 +625,15 @@ matrix (`KernelSVM`) or transposing X into columns
594
625
  | `L1LogisticRegression`, 20,000 x 500, C=0.05 | 1.20 s | 2.23 s | 1.9x | 106.77913 / 106.77915 | 100% |
595
626
  | `L1LogisticRegression`, 100,000 x 200, C=0.05 | 9.06 s | 18.7 s | 2.1x | 349.61154 / 349.61216 | 100% |
596
627
 
628
+ On 2,000 x 20,000 neither side reaches the optimum at tol 1e-4 (the
629
+ objective at tol 1e-10 is 0.0519588), and where each stops depends on
630
+ the coordinate order: over eight seeds ours stops at 0.05240 to
631
+ 0.05251 (0.9 to 1.1% above the optimum), `LinearSVC`, whose order is
632
+ random unless `random_state` is given, at 0.05242 to 0.05319 (0.9 to
633
+ 2.4%). When `LinearSVC` draws one of its worse orders the gap passes
634
+ the script's 0.5% bound and the row prints `<-- disagrees`; ours is
635
+ then the better solution of the two, so the flag there is not a fault.
636
+
597
637
  The regressors, from the same script (C = 1, epsilon 0.1):
598
638
 
599
639
  | problem | ours | scikit-learn | ratio | agreement |
@@ -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.1"
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.1"
58
60
 
59
61
  _BATCH = load_batch_lib()
60
62
  _DP = ctypes.POINTER(ctypes.c_double)
@@ -361,19 +363,50 @@ class DerivedLasso:
361
363
  the column norms (folded into the Gram's diagonal when p < n),
362
364
  builds the Gram matrix, walks the penalties, and leaves the
363
365
  coefficient at each in a row of the result. tol is the largest move of
364
- one coefficient in a sweep below which that lambda is done.
366
+ one coefficient in a sweep below which that lambda is done. The
367
+ kernel's value, the sweeps run over the whole path, is kept as
368
+ n_iter_.
365
369
  """
366
370
  lams = np.ascontiguousarray(lambdas, dtype=np.float64).ravel()
367
371
  beta = np.zeros(self.p)
368
372
  resid = np.empty(self.nobs) # filled by the kernel from y
369
373
  xnorm = np.empty(self.p) # the column norms, taken inside
370
374
  betas = np.zeros(lams.size * self.p)
371
- self._kernel.lasso_design(self.X, self.y, resid, xnorm, beta, lams,
372
- betas, int(max_sweeps), float(tol),
373
- self.nobs, self.p, lams.size)
375
+ self.n_iter_ = int(self._kernel.lasso_design(
376
+ self.X, self.y, resid, xnorm, beta, lams, betas, int(max_sweeps), float(tol),
377
+ self.nobs, self.p, lams.size))
374
378
  return betas.reshape(lams.size, self.p)
375
379
 
376
380
 
381
+ class DesignLasso(Tagged, RegressorMixin, BaseEstimator):
382
+ """scikit-learn's ``Lasso`` interface over DerivedLasso: fit(X, y)
383
+ centres the data when ``fit_intercept`` and runs the derived kernel
384
+ at one penalty ``alpha``, same objective as GramLasso. ``tol`` is
385
+ the kernel's (the largest absolute move of one coefficient in a
386
+ sweep), ``max_iter`` its sweep cap and ``backend`` DerivedLasso's.
387
+
388
+ Attributes after fit: coef_, intercept_, n_iter_ (the sweeps the
389
+ kernel ran), n_features_in_.
390
+ """
391
+
392
+ def __init__(self, alpha=1.0, *, fit_intercept=True, tol=1e-8, max_iter=100000,
393
+ backend=None):
394
+ self.alpha, self.fit_intercept, self.tol, self.max_iter = alpha, fit_intercept, tol, max_iter
395
+ self.backend = backend
396
+
397
+ def fit(self, X, y):
398
+ X, y = fit_xy(self, X, y)
399
+ Xc, yc, xm, ym = _centered(X, y, self.fit_intercept)
400
+ m = DerivedLasso(Xc, yc, backend=self.backend)
401
+ self.coef_ = m.fit(float(self.alpha), tol=float(self.tol), max_sweeps=int(self.max_iter))
402
+ self.n_iter_ = m.n_iter_
403
+ self.intercept_ = float(ym - xm @ self.coef_)
404
+ return self
405
+
406
+ def predict(self, X):
407
+ return pred_x(self, X) @ self.coef_ + self.intercept_
408
+
409
+
377
410
  def derived_backend():
378
411
  """The derived kernel variant DerivedLasso takes by default: 'cublas'
379
412
  when built and a device answers, else 'blas', else 'loops'; None
@@ -669,6 +702,28 @@ def _centered(X, y, fit_intercept):
669
702
  return np.ascontiguousarray(X - xm), y - ym, xm, ym
670
703
 
671
704
 
705
+ def _centered_gram(X, y, fit_intercept):
706
+ """(Xc'Xc, Xc'yc, xm, ym) for the centred data Xc, yc, without
707
+ forming Xc: X'X - n xm xm' and X'y - n xm ym, which saves the copy
708
+ of X (as long as the Gram product itself on 20000 x 200 rows, where
709
+ it took the intercept from free to twice the fit). The correction
710
+ cancels digits when a column's mean is large against its spread,
711
+ about (mean / sd)^2 ulps, so a column with mean over 100 sd (or no
712
+ spread at all) sends the whole fit through the centred copy."""
713
+ n = X.shape[0]
714
+ G, c = X.T @ X, X.T @ y
715
+ if not fit_intercept:
716
+ return G, c, np.zeros(X.shape[1]), np.zeros(y.shape[1:]) if y.ndim > 1 else 0.0
717
+ xm, ym = X.mean(axis=0), y.mean(axis=0)
718
+ nm2 = n * xm * xm
719
+ if np.all(np.diagonal(G) - nm2 > 1e-4 * nm2):
720
+ G -= np.outer(xm, n * xm)
721
+ c -= np.multiply.outer(n * xm, ym)
722
+ return G, c, xm, ym
723
+ Xc, yc = np.ascontiguousarray(X - xm), y - ym
724
+ return Xc.T @ Xc, Xc.T @ yc, xm, ym
725
+
726
+
672
727
  class GramElasticNet(Tagged, RegressorMixin, BaseEstimator):
673
728
  """scikit-learn's ``ElasticNet(precompute=True)`` interface over
674
729
  CovLasso: fit(X, y) centres the data when ``fit_intercept``, forms
@@ -692,10 +747,10 @@ class GramElasticNet(Tagged, RegressorMixin, BaseEstimator):
692
747
 
693
748
  def fit(self, X, y):
694
749
  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)
750
+ n, p = X.shape
751
+ G, c, xm, ym = _centered_gram(X, y, self.fit_intercept)
752
+ g = np.ascontiguousarray(G.ravel())
753
+ c = np.ascontiguousarray(c)
699
754
  lam, r = float(self.alpha), float(self.l1_ratio)
700
755
  tol, cap = float(self.tol), int(self.max_iter)
701
756
  beta, prev = np.zeros(p), np.empty(p)
@@ -747,11 +802,9 @@ class GramRidge(Tagged, RegressorMixin, BaseEstimator):
747
802
 
748
803
  def fit(self, X, y):
749
804
  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
805
+ p = X.shape[1]
806
+ A, b, xm, ym = _centered_gram(X, y, self.fit_intercept)
753
807
  A.flat[::p + 1] += float(self.alpha)
754
- b = Xc.T @ yc
755
808
  try:
756
809
  coef = np.linalg.solve(A, b)
757
810
  except np.linalg.LinAlgError:
@@ -2686,7 +2739,9 @@ class CovLogistic:
2686
2739
 
2687
2740
  def _fit_one(self, beta, lam, tol, inner_sweeps, max_rounds):
2688
2741
  beta = beta.copy()
2742
+ self._rounds = 0
2689
2743
  for _ in range(max_rounds):
2744
+ self._rounds += 1
2690
2745
  prev = beta.copy()
2691
2746
  # the majorizer at beta: quadratic G/4, working
2692
2747
  # correlations exactly X'(y - mu)
@@ -2699,6 +2754,58 @@ class CovLogistic:
2699
2754
  return beta
2700
2755
 
2701
2756
 
2757
+ class GramLogisticRegression(Tagged, ClassifierMixin, BaseEstimator):
2758
+ """scikit-learn's ``LogisticRegression(l1_ratio=1, solver="liblinear")``
2759
+ interface over CovLogistic: the same problem,
2760
+
2761
+ min_w ||w||_1 + C sum_i log(1 + exp(-y_i (w.x_i + b))),
2762
+
2763
+ solved by majorization over the Gram matrix instead of liblinear's
2764
+ Newton steps. As in liblinear the intercept is the weight of a
2765
+ constant column of value ``intercept_scaling`` and is penalized
2766
+ with the rest. ``tol`` is CovLogistic's (the largest coefficient
2767
+ move in a round, relative to their size) and ``max_iter`` caps the
2768
+ rounds. Two classes.
2769
+
2770
+ Attributes after fit: classes_, coef_, intercept_, n_iter_.
2771
+ """
2772
+
2773
+ _binary_only = True
2774
+
2775
+ def __init__(self, C=1.0, *, fit_intercept=True, intercept_scaling=1.0, tol=1e-7,
2776
+ max_iter=500):
2777
+ self.C, self.fit_intercept, self.intercept_scaling = C, fit_intercept, intercept_scaling
2778
+ self.tol, self.max_iter = tol, max_iter
2779
+
2780
+ def fit(self, X, y):
2781
+ X, y = fit_xy(self, X, y, classifier=True)
2782
+ n = X.shape[0]
2783
+ self.classes_, ys = _binary_labels(y)
2784
+ s = float(self.intercept_scaling)
2785
+ if self.fit_intercept:
2786
+ X = np.column_stack([X, np.full(n, s)])
2787
+ m = CovLogistic(X, (ys > 0).astype(np.float64))
2788
+ lam = 1.0 / (n * float(self.C))
2789
+ beta = m._fit_one(np.zeros(m.p), lam, float(self.tol), 200, int(self.max_iter))
2790
+ self.n_iter_ = m._rounds
2791
+ if self.fit_intercept:
2792
+ self.coef_, self.intercept_ = beta[:-1].copy(), float(beta[-1]) * s
2793
+ else:
2794
+ self.coef_, self.intercept_ = beta, 0.0
2795
+ return self
2796
+
2797
+ def decision_function(self, X):
2798
+ return pred_x(self, X) @ self.coef_ + self.intercept_
2799
+
2800
+ def predict_proba(self, X):
2801
+ p1 = 1.0 / (1.0 + np.exp(-self.decision_function(X)))
2802
+ return np.column_stack([1.0 - p1, p1])
2803
+
2804
+ def predict(self, X):
2805
+ z = self.decision_function(X)
2806
+ return self.classes_[(z > 0).astype(int)]
2807
+
2808
+
2702
2809
  class CovGroupLasso:
2703
2810
  """Group lasso over a design given by its Gram matrix.
2704
2811
 
@@ -2778,9 +2885,13 @@ class CovGroupLasso:
2778
2885
 
2779
2886
  def _fit_one(self, beta, c, lam, tol, max_sweeps):
2780
2887
  thr = lam * self.nobs
2888
+ self._sweeps = 0
2781
2889
  for _ in range(max_sweeps):
2890
+ self._sweeps += 1
2782
2891
  moved = 0.0
2783
2892
  for g, w, L in zip(self.groups, self.wg, self.Lg):
2893
+ if L <= 0.0: # all-zero columns: the group stays at zero
2894
+ continue
2784
2895
  bg = beta[g]
2785
2896
  z = bg + c[g] / L
2786
2897
  nz = float(np.linalg.norm(z))
@@ -2809,6 +2920,41 @@ class CovGroupLasso:
2809
2920
  return quad / self.nobs + lam * pen
2810
2921
 
2811
2922
 
2923
+ class GramGroupLasso(Tagged, RegressorMixin, BaseEstimator):
2924
+ """A ``Lasso``-style interface over CovGroupLasso: fit(X, y) centres
2925
+ the data when ``fit_intercept`` and runs the block descent at
2926
+ ``alpha``,
2927
+
2928
+ (1 / 2 n) ||y - X b - b0||^2 + alpha sum_g sqrt(|g|) ||b_g||_2.
2929
+
2930
+ ``groups`` is a list of index arrays partitioning the columns, or
2931
+ None for one group per column (the lasso). ``tol`` is
2932
+ CovGroupLasso's and ``max_iter`` its sweep cap.
2933
+
2934
+ Attributes after fit: coef_, intercept_, n_iter_, n_features_in_.
2935
+ """
2936
+
2937
+ def __init__(self, alpha=1.0, *, groups=None, fit_intercept=True, tol=1e-8,
2938
+ max_iter=10000):
2939
+ self.alpha, self.groups, self.fit_intercept = alpha, groups, fit_intercept
2940
+ self.tol, self.max_iter = tol, max_iter
2941
+
2942
+ def fit(self, X, y):
2943
+ X, y = fit_xy(self, X, y)
2944
+ n, p = X.shape
2945
+ G, c, xm, ym = _centered_gram(X, y, self.fit_intercept)
2946
+ groups = [[j] for j in range(p)] if self.groups is None else self.groups
2947
+ m = CovGroupLasso(gram=G, corr=c, nobs=n, groups=groups)
2948
+ beta, _ = m._fit_one(np.zeros(p), m.c0.copy(), float(self.alpha), float(self.tol),
2949
+ int(self.max_iter))
2950
+ self.coef_, self.n_iter_ = beta, m._sweeps
2951
+ self.intercept_ = float(ym - xm @ self.coef_)
2952
+ return self
2953
+
2954
+ def predict(self, X):
2955
+ return pred_x(self, X) @ self.coef_ + self.intercept_
2956
+
2957
+
2812
2958
  class CovMultiTaskLasso:
2813
2959
  """Multi-task lasso and elastic net over a design's Gram matrix.
2814
2960
 
@@ -2929,6 +3075,69 @@ class CovMultiTaskLasso:
2929
3075
  return out.reshape(lambdas.size, self.p, self.n_tasks)
2930
3076
 
2931
3077
 
3078
+ class GramMultiTaskElasticNet(Tagged, RegressorMixin, BaseEstimator):
3079
+ """scikit-learn's ``MultiTaskElasticNet`` interface over
3080
+ CovMultiTaskLasso: fit(X, Y) centres the data when
3081
+ ``fit_intercept``, forms the Gram matrix once and runs the row-block
3082
+ sweep at ``alpha``. Y is (n, n_tasks); coef_ is (n_tasks, p) as
3083
+ scikit-learn has it. ``tol`` is CovMultiTaskLasso's and
3084
+ ``max_iter`` its sweep cap.
3085
+
3086
+ Attributes after fit: coef_, intercept_, n_iter_, n_features_in_.
3087
+ """
3088
+
3089
+ _multi_output = "only"
3090
+
3091
+ def __init__(self, alpha=1.0, *, l1_ratio=0.5, fit_intercept=True, tol=1e-8,
3092
+ max_iter=100000):
3093
+ self.alpha, self.l1_ratio, self.fit_intercept = alpha, l1_ratio, fit_intercept
3094
+ self.tol, self.max_iter = tol, max_iter
3095
+
3096
+ def fit(self, X, y):
3097
+ X, y = fit_xy(self, X, y, multi_output=True)
3098
+ if y.ndim != 2:
3099
+ raise ValueError("For mono-task outputs, use GramElasticNet or GramLasso; "
3100
+ "y must be (n_samples, n_tasks)")
3101
+ n, p = X.shape
3102
+ t = y.shape[1]
3103
+ G, c, xm, ym = _centered_gram(X, y, self.fit_intercept)
3104
+ g = np.ascontiguousarray(G.ravel())
3105
+ c = np.ascontiguousarray(c.ravel())
3106
+ lam, r = float(self.alpha), float(self.l1_ratio)
3107
+ tol, cap = float(self.tol), int(self.max_iter)
3108
+ W, prev = np.zeros(p * t), np.empty(p * t)
3109
+ swept = 0
3110
+ while swept < cap:
3111
+ chunk = min(20, cap - swept)
3112
+ prev[:] = W
3113
+ kernel.mt_descend(g, c, W, lam * r, lam * (1.0 - r), chunk, float(n), p, t)
3114
+ swept += chunk
3115
+ if np.max(np.abs(W - prev)) < tol * _scale(W):
3116
+ break
3117
+ W = W.reshape(p, t)
3118
+ self.coef_, self.n_iter_ = np.ascontiguousarray(W.T), swept
3119
+ self.intercept_ = ym - xm @ W
3120
+ return self
3121
+
3122
+ def predict(self, X):
3123
+ return pred_x(self, X) @ self.coef_.T + self.intercept_
3124
+
3125
+
3126
+ class GramMultiTaskLasso(GramMultiTaskElasticNet):
3127
+ """scikit-learn's ``MultiTaskLasso`` interface over CovMultiTaskLasso:
3128
+ GramMultiTaskElasticNet with l1_ratio 1.
3129
+
3130
+ Attributes after fit: coef_, intercept_, n_iter_, n_features_in_.
3131
+ """
3132
+
3133
+ def __init__(self, alpha=1.0, *, fit_intercept=True, tol=1e-8, max_iter=100000):
3134
+ self.alpha, self.fit_intercept, self.tol, self.max_iter = alpha, fit_intercept, tol, max_iter
3135
+
3136
+ @property
3137
+ def l1_ratio(self):
3138
+ return 1.0
3139
+
3140
+
2932
3141
  class CovMultiTaskLassoCV(Tagged, RegressorMixin, BaseEstimator):
2933
3142
  """scikit-learn's ``MultiTaskLassoCV`` (``MultiTaskElasticNetCV``
2934
3143
  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)
@@ -30,26 +30,28 @@ soft_threshold( double z, double g )
30
30
 
31
31
  inline int
32
32
  lasso_design( scm2cpp::cspan<double> x, scm2cpp::cspan<double> y, scm2cpp::span<double> resid, scm2cpp::span<double> xnorm, scm2cpp::span<double> beta, scm2cpp::cspan<double> lams, scm2cpp::span<double> betas, int iters, double tol, int n, int p, int nlam )
33
- { if( p < n ) {
33
+ { int swept = 0;
34
+ if( p < n ) {
34
35
  for( int i = 0 ; !(i == n) ; i = (i+1) ) {
35
36
  resid[ i ] = y[i] ;
36
37
  } ;
37
38
  0 ;
38
- std::vector<double> b024258(p,0.0);
39
- std::vector<double> g24257((p*p),0.0);
40
- std::vector<double> c24256(p,0.0);
41
- scm2cpp::blas_syrk(g24257, x, 1.0, 0.0, 1, p, n) ;
42
- scm2cpp::blas_gemv(c24256, x, resid, 1.0, 0.0, 1, p, n) ;
39
+ std::vector<double> b03446(p,0.0);
40
+ std::vector<double> g3445((p*p),0.0);
41
+ std::vector<double> c3444(p,0.0);
42
+ scm2cpp::blas_syrk(g3445, x, 1.0, 0.0, 1, p, n) ;
43
+ scm2cpp::blas_gemv(c3444, x, resid, 1.0, 0.0, 1, p, n) ;
43
44
  for( int l = 0 ; !(l == nlam) ; l = (l+1) ) {
44
45
  double lam = lams[l];
45
46
  int stop = 0;
46
47
  for( int sweep = 0 ; !((sweep == iters || stop == 1)) ; sweep = (sweep+1) ) {
48
+ swept = (swept+1) ;
47
49
  double maxmove = 0.0;
48
50
  for( int j = 0 ; !(j == p) ; j = (j+1) ) {
49
- double rho = c24256[j];
51
+ double rho = c3444[j];
50
52
  double old = beta[j];
51
- rho = (rho+(old*g24257[((j*p)+j)])) ;
52
- double bnew = (soft_threshold(rho,double((lam*(1.0*n))))/g24257[((j*p)+j)]);
53
+ rho = (rho+(old*g3445[((j*p)+j)])) ;
54
+ double bnew = (soft_threshold(rho,double((lam*(1.0*n))))/g3445[((j*p)+j)]);
53
55
  beta[ j ] = bnew ;
54
56
  if( !(bnew == old) ) {
55
57
  if( (bnew-old) > maxmove ) {
@@ -62,9 +64,9 @@ lasso_design( scm2cpp::cspan<double> x, scm2cpp::cspan<double> y, scm2cpp::spa
62
64
  } else {
63
65
  0 ;
64
66
  } ;
65
- b024258[ j ] = (b024258[j]+(bnew-old)) ;
66
- for( int k24260 = 0 ; !(k24260 == p) ; k24260 = (k24260+1) ) {
67
- c24256[ k24260 ] = (c24256[k24260]-(g24257[((j*p)+k24260)]*(bnew-old))) ;
67
+ b03446[ j ] = (b03446[j]+(bnew-old)) ;
68
+ for( int k3448 = 0 ; !(k3448 == p) ; k3448 = (k3448+1) ) {
69
+ c3444[ k3448 ] = (c3444[k3448]-(g3445[((j*p)+k3448)]*(bnew-old))) ;
68
70
  } ;
69
71
  } else {
70
72
  false ;
@@ -85,7 +87,7 @@ lasso_design( scm2cpp::cspan<double> x, scm2cpp::cspan<double> y, scm2cpp::spa
85
87
  } ;
86
88
  ;
87
89
  } ;
88
- scm2cpp::blas_gemv(resid, x, b024258, -1.0, 1.0, 0, n, p) ;
90
+ scm2cpp::blas_gemv(resid, x, b03446, -1.0, 1.0, 0, n, p) ;
89
91
  0 ;
90
92
  ;
91
93
  } else {
@@ -104,6 +106,7 @@ lasso_design( scm2cpp::cspan<double> x, scm2cpp::cspan<double> y, scm2cpp::spa
104
106
  double lam = lams[l];
105
107
  int stop = 0;
106
108
  for( int sweep = 0 ; !((sweep == iters || stop == 1)) ; sweep = (sweep+1) ) {
109
+ swept = (swept+1) ;
107
110
  double maxmove = 0.0;
108
111
  for( int j = 0 ; !(j == p) ; j = (j+1) ) {
109
112
  double rho = 0.0;
@@ -148,6 +151,6 @@ lasso_design( scm2cpp::cspan<double> x, scm2cpp::cspan<double> y, scm2cpp::spa
148
151
  ;
149
152
  } ;
150
153
  } ;
151
- return 0 ;
154
+ return swept ;
152
155
  }
153
156
  #endif // LASSO_DESIGN_HPP
@@ -30,27 +30,29 @@ soft_threshold( double z, double g )
30
30
 
31
31
  inline int
32
32
  lasso_design( scm2cpp::cspan<double> x, scm2cpp::cspan<double> y, scm2cpp::span<double> resid, scm2cpp::span<double> xnorm, scm2cpp::span<double> beta, scm2cpp::cspan<double> lams, scm2cpp::span<double> betas, int iters, double tol, int n, int p, int nlam )
33
- { if( p < n ) {
33
+ { int swept = 0;
34
+ if( p < n ) {
34
35
  for( int i = 0 ; !(i == n) ; i = (i+1) ) {
35
36
  resid[ i ] = y[i] ;
36
37
  } ;
37
38
  0 ;
38
- std::vector<double> b024258(p,0.0);
39
- std::vector<double> g24257((p*p),0.0);
40
- std::vector<double> c24256(p,0.0);
41
- scm2cpp::device_matrix d24266 = scm2cpp::dmat_upload(x, (n*p), 1);
42
- scm2cpp::blas_syrk(g24257, d24266, 1.0, 0.0, 1, p, n) ;
43
- scm2cpp::blas_gemv(c24256, d24266, resid, 1.0, 0.0, 1, p, n) ;
39
+ std::vector<double> b03446(p,0.0);
40
+ std::vector<double> g3445((p*p),0.0);
41
+ std::vector<double> c3444(p,0.0);
42
+ scm2cpp::device_matrix d3454 = scm2cpp::dmat_upload(x, (n*p), 1);
43
+ scm2cpp::blas_syrk(g3445, d3454, 1.0, 0.0, 1, p, n) ;
44
+ scm2cpp::blas_gemv(c3444, d3454, resid, 1.0, 0.0, 1, p, n) ;
44
45
  for( int l = 0 ; !(l == nlam) ; l = (l+1) ) {
45
46
  double lam = lams[l];
46
47
  int stop = 0;
47
48
  for( int sweep = 0 ; !((sweep == iters || stop == 1)) ; sweep = (sweep+1) ) {
49
+ swept = (swept+1) ;
48
50
  double maxmove = 0.0;
49
51
  for( int j = 0 ; !(j == p) ; j = (j+1) ) {
50
- double rho = c24256[j];
52
+ double rho = c3444[j];
51
53
  double old = beta[j];
52
- rho = (rho+(old*g24257[((j*p)+j)])) ;
53
- double bnew = (soft_threshold(rho,double((lam*(1.0*n))))/g24257[((j*p)+j)]);
54
+ rho = (rho+(old*g3445[((j*p)+j)])) ;
55
+ double bnew = (soft_threshold(rho,double((lam*(1.0*n))))/g3445[((j*p)+j)]);
54
56
  beta[ j ] = bnew ;
55
57
  if( !(bnew == old) ) {
56
58
  if( (bnew-old) > maxmove ) {
@@ -63,9 +65,9 @@ lasso_design( scm2cpp::cspan<double> x, scm2cpp::cspan<double> y, scm2cpp::spa
63
65
  } else {
64
66
  0 ;
65
67
  } ;
66
- b024258[ j ] = (b024258[j]+(bnew-old)) ;
67
- for( int k24260 = 0 ; !(k24260 == p) ; k24260 = (k24260+1) ) {
68
- c24256[ k24260 ] = (c24256[k24260]-(g24257[((j*p)+k24260)]*(bnew-old))) ;
68
+ b03446[ j ] = (b03446[j]+(bnew-old)) ;
69
+ for( int k3448 = 0 ; !(k3448 == p) ; k3448 = (k3448+1) ) {
70
+ c3444[ k3448 ] = (c3444[k3448]-(g3445[((j*p)+k3448)]*(bnew-old))) ;
69
71
  } ;
70
72
  } else {
71
73
  false ;
@@ -86,7 +88,7 @@ lasso_design( scm2cpp::cspan<double> x, scm2cpp::cspan<double> y, scm2cpp::spa
86
88
  } ;
87
89
  ;
88
90
  } ;
89
- scm2cpp::blas_gemv(resid, d24266, b024258, -1.0, 1.0, 0, n, p) ;
91
+ scm2cpp::blas_gemv(resid, d3454, b03446, -1.0, 1.0, 0, n, p) ;
90
92
  0 ;
91
93
  ;
92
94
  ;
@@ -106,6 +108,7 @@ lasso_design( scm2cpp::cspan<double> x, scm2cpp::cspan<double> y, scm2cpp::spa
106
108
  double lam = lams[l];
107
109
  int stop = 0;
108
110
  for( int sweep = 0 ; !((sweep == iters || stop == 1)) ; sweep = (sweep+1) ) {
111
+ swept = (swept+1) ;
109
112
  double maxmove = 0.0;
110
113
  for( int j = 0 ; !(j == p) ; j = (j+1) ) {
111
114
  double rho = 0.0;
@@ -150,6 +153,6 @@ lasso_design( scm2cpp::cspan<double> x, scm2cpp::cspan<double> y, scm2cpp::spa
150
153
  ;
151
154
  } ;
152
155
  } ;
153
- return 0 ;
156
+ return swept ;
154
157
  }
155
158
  #endif // LASSO_DESIGN_HPP
@@ -29,43 +29,45 @@ soft_threshold( double z, double g )
29
29
 
30
30
  inline int
31
31
  lasso_design( scm2cpp::cspan<double> x, scm2cpp::cspan<double> y, scm2cpp::span<double> resid, scm2cpp::span<double> xnorm, scm2cpp::span<double> beta, scm2cpp::cspan<double> lams, scm2cpp::span<double> betas, int iters, double tol, int n, int p, int nlam )
32
- { if( p < n ) {
32
+ { int swept = 0;
33
+ if( p < n ) {
33
34
  for( int i = 0 ; !(i == n) ; i = (i+1) ) {
34
35
  resid[ i ] = y[i] ;
35
36
  } ;
36
37
  0 ;
37
- std::vector<double> b024258(p,0.0);
38
- std::vector<double> g24257((p*p),0.0);
39
- std::vector<double> c24256(p,0.0);
38
+ std::vector<double> b03446(p,0.0);
39
+ std::vector<double> g3445((p*p),0.0);
40
+ std::vector<double> c3444(p,0.0);
40
41
  for( int y2491 = 0 ; !(y2491 == p) ; y2491 = (y2491+1) ) {
41
42
  for( int y2492 = y2491 ; !(y2492 == p) ; y2492 = (y2492+1) ) {
42
- g24257[ ((y2491*p)+y2492) ] = ([&]() -> double { int k2493 = 0; double sum2498 = 0.0; while (!(k2493 == n)) {
43
- int k2493_next24266 = (k2493+1);
44
- double sum2498_next24267 = (sum2498+(x[((k2493*p)+y2491)]*x[((k2493*p)+y2492)]));
45
- k2493 = k2493_next24266;
46
- sum2498 = sum2498_next24267;
43
+ g3445[ ((y2491*p)+y2492) ] = ([&]() -> double { int k2493 = 0; double sum2498 = 0.0; while (!(k2493 == n)) {
44
+ int k2493_next3454 = (k2493+1);
45
+ double sum2498_next3455 = (sum2498+(x[((k2493*p)+y2491)]*x[((k2493*p)+y2492)]));
46
+ k2493 = k2493_next3454;
47
+ sum2498 = sum2498_next3455;
47
48
  } return sum2498; })() ;
48
- g24257[ ((y2492*p)+y2491) ] = g24257[((y2491*p)+y2492)] ;
49
+ g3445[ ((y2492*p)+y2491) ] = g3445[((y2491*p)+y2492)] ;
49
50
  } ;
50
51
  } ;
51
52
  for( int y2494 = 0 ; !(y2494 == p) ; y2494 = (y2494+1) ) {
52
- c24256[ y2494 ] = ([&]() -> double { int k2495 = 0; double sum2500 = 0.0; while (!(k2495 == n)) {
53
- int k2495_next24268 = (k2495+1);
54
- double sum2500_next24269 = (sum2500+(x[((k2495*p)+y2494)]*resid[k2495]));
55
- k2495 = k2495_next24268;
56
- sum2500 = sum2500_next24269;
53
+ c3444[ y2494 ] = ([&]() -> double { int k2495 = 0; double sum2500 = 0.0; while (!(k2495 == n)) {
54
+ int k2495_next3456 = (k2495+1);
55
+ double sum2500_next3457 = (sum2500+(x[((k2495*p)+y2494)]*resid[k2495]));
56
+ k2495 = k2495_next3456;
57
+ sum2500 = sum2500_next3457;
57
58
  } return sum2500; })() ;
58
59
  } ;
59
60
  for( int l = 0 ; !(l == nlam) ; l = (l+1) ) {
60
61
  double lam = lams[l];
61
62
  int stop = 0;
62
63
  for( int sweep = 0 ; !((sweep == iters || stop == 1)) ; sweep = (sweep+1) ) {
64
+ swept = (swept+1) ;
63
65
  double maxmove = 0.0;
64
66
  for( int j = 0 ; !(j == p) ; j = (j+1) ) {
65
- double rho = c24256[j];
67
+ double rho = c3444[j];
66
68
  double old = beta[j];
67
- rho = (rho+(old*g24257[((j*p)+j)])) ;
68
- double bnew = (soft_threshold(rho,double((lam*(1.0*n))))/g24257[((j*p)+j)]);
69
+ rho = (rho+(old*g3445[((j*p)+j)])) ;
70
+ double bnew = (soft_threshold(rho,double((lam*(1.0*n))))/g3445[((j*p)+j)]);
69
71
  beta[ j ] = bnew ;
70
72
  if( !(bnew == old) ) {
71
73
  if( (bnew-old) > maxmove ) {
@@ -78,9 +80,9 @@ lasso_design( scm2cpp::cspan<double> x, scm2cpp::cspan<double> y, scm2cpp::spa
78
80
  } else {
79
81
  0 ;
80
82
  } ;
81
- b024258[ j ] = (b024258[j]+(bnew-old)) ;
82
- for( int k24260 = 0 ; !(k24260 == p) ; k24260 = (k24260+1) ) {
83
- c24256[ k24260 ] = (c24256[k24260]-(g24257[((j*p)+k24260)]*(bnew-old))) ;
83
+ b03446[ j ] = (b03446[j]+(bnew-old)) ;
84
+ for( int k3448 = 0 ; !(k3448 == p) ; k3448 = (k3448+1) ) {
85
+ c3444[ k3448 ] = (c3444[k3448]-(g3445[((j*p)+k3448)]*(bnew-old))) ;
84
86
  } ;
85
87
  } else {
86
88
  false ;
@@ -103,10 +105,10 @@ lasso_design( scm2cpp::cspan<double> x, scm2cpp::cspan<double> y, scm2cpp::spa
103
105
  } ;
104
106
  for( int y2496 = 0 ; !(y2496 == n) ; y2496 = (y2496+1) ) {
105
107
  resid[ y2496 ] = (resid[y2496]-([&]() -> double { int k2497 = 0; double sum2502 = 0.0; while (!(k2497 == p)) {
106
- int k2497_next24270 = (k2497+1);
107
- double sum2502_next24271 = (sum2502+(b024258[k2497]*x[((y2496*p)+k2497)]));
108
- k2497 = k2497_next24270;
109
- sum2502 = sum2502_next24271;
108
+ int k2497_next3458 = (k2497+1);
109
+ double sum2502_next3459 = (sum2502+(b03446[k2497]*x[((y2496*p)+k2497)]));
110
+ k2497 = k2497_next3458;
111
+ sum2502 = sum2502_next3459;
110
112
  } return sum2502; })()) ;
111
113
  } ;
112
114
  0 ;
@@ -127,6 +129,7 @@ lasso_design( scm2cpp::cspan<double> x, scm2cpp::cspan<double> y, scm2cpp::spa
127
129
  double lam = lams[l];
128
130
  int stop = 0;
129
131
  for( int sweep = 0 ; !((sweep == iters || stop == 1)) ; sweep = (sweep+1) ) {
132
+ swept = (swept+1) ;
130
133
  double maxmove = 0.0;
131
134
  for( int j = 0 ; !(j == p) ; j = (j+1) ) {
132
135
  double rho = 0.0;
@@ -171,6 +174,6 @@ lasso_design( scm2cpp::cspan<double> x, scm2cpp::cspan<double> y, scm2cpp::spa
171
174
  ;
172
175
  } ;
173
176
  } ;
174
- return 0 ;
177
+ return swept ;
175
178
  }
176
179
  #endif // LASSO_DESIGN_HPP
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scm2cpp-lasso
3
- Version: 0.14.0
3
+ Version: 0.15.1
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,7 +107,7 @@ 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
112
  runs it; the one expected failure is `LBFGSLogisticRegressionCV`'s
113
113
  `n_iter_` of 0 for a C whose warm start is already converged, which
@@ -122,11 +122,37 @@ from scm2cpp_lasso import GramLasso
122
122
  GridSearchCV(GramLasso(), {"alpha": [0.01, 0.1, 1.0]}, cv=5).fit(X, y)
123
123
  ```
124
124
 
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.
125
+ `CovLasso`, `CovRidge`, `DerivedLasso`, `CovLogistic`,
126
+ `CovGroupLasso` and `CovMultiTaskLasso` keep their
127
+ data-in-the-constructor interface, which solves a whole path at once;
128
+ each has a `fit(X, y)` form with the intercept beside it (0.15.0):
129
+
130
+ | data in the constructor | `fit(X, y)` | scikit-learn |
131
+ |---|---|---|
132
+ | `CovLasso` | `GramLasso`, `GramElasticNet` | Lasso, ElasticNet |
133
+ | `CovRidge` | `GramRidge` | Ridge |
134
+ | `DerivedLasso` | `DesignLasso` | Lasso |
135
+ | `CovLogistic` | `GramLogisticRegression` | LogisticRegression(l1_ratio=1, solver="liblinear") |
136
+ | `CovGroupLasso` | `GramGroupLasso` (`groups=None`: the lasso) | (none) |
137
+ | `CovMultiTaskLasso` | `GramMultiTaskLasso`, `GramMultiTaskElasticNet` | MultiTaskLasso, MultiTaskElasticNet |
138
+
139
+ Against scikit-learn on 400 x 30 with an intercept: `DesignLasso` and
140
+ `GramGroupLasso(groups=None)` agree with `Lasso` to 1.5e-10, the
141
+ multi-task pair with `MultiTaskLasso` / `MultiTaskElasticNet` to
142
+ 5e-14, and `GramLogisticRegression` reaches liblinear's objective to
143
+ eight digits with the same predictions.
144
+
145
+ Against the class each one wraps (one alpha, one core), which fits no
146
+ intercept and is unchanged: with `fit_intercept=False` the input checks
147
+ add 15 to 30 us a call (up to +50% on a 0.06 ms fit) and the finiteness
148
+ sum 8 to 14% on 20000 x 200 (none under `assume_finite`); with the
149
+ intercept, the column means add 20 to 30% on 20000 x 200 for the
150
+ Gram-matrix ones, which correct X'X
151
+ for the means rather than copy X to centre it, and about 55% for
152
+ `DesignLasso`, whose kernel needs the centred X itself.
153
+ `GramLogisticRegression` is slower than liblinear by its algorithm, 2.5x
154
+ at 20000 x 200 and more on small problems; `L1LogisticRegression` is
155
+ the fast one for that problem.
130
156
 
131
157
  What it costs is the input checks: X must be a finite 2-D float64
132
158
  array with the feature count of the fit. A plain C-ordered float64
@@ -166,6 +192,11 @@ package picks the fastest one present.
166
192
  | `GramLasso` | Lasso(precompute=True), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | no |
167
193
  | `GramElasticNet` | ElasticNet(precompute=True), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | no |
168
194
  | `GramRidge` | Ridge, fit(X, y) with the intercept, one or many targets | numpy (Cholesky) | CPU | Ridge |
195
+ | `GramMultiTaskLasso` | MultiTaskLasso, fit(X, Y) with the intercept | `lasso-cov.scm` | CPU | no |
196
+ | `GramMultiTaskElasticNet` | MultiTaskElasticNet, fit(X, Y) with the intercept | `lasso-cov.scm` | CPU | no |
197
+ | `GramGroupLasso` | (none; skglm GroupLasso), fit(X, y) with the intercept | `lasso-cov.scm` | CPU | - |
198
+ | `GramLogisticRegression` | LogisticRegression(l1_ratio=1, solver='liblinear'), by majorization | `lasso-cov.scm` | CPU | no |
199
+ | `DesignLasso` | Lasso, fit(X, y) with the intercept, from the design | `lasso-design.scm (--derive)` | loops / BLAS / cuBLAS | no |
169
200
  | `CovLasso` | Lasso, ElasticNet, lasso_path(precompute=True) | `lasso-cov.scm` | CPU; GPU batch (fit_path_batch, bootstrap) | no |
170
201
  | `CovLassoCV` | LassoCV, ElasticNetCV | `lasso-cov.scm` | CPU; GPU batch | no |
171
202
  | `DerivedLasso` | lasso_path, from the design | `lasso-design.scm (--derive)` | loops / BLAS / cuBLAS | no |
@@ -612,6 +643,15 @@ matrix (`KernelSVM`) or transposing X into columns
612
643
  | `L1LogisticRegression`, 20,000 x 500, C=0.05 | 1.20 s | 2.23 s | 1.9x | 106.77913 / 106.77915 | 100% |
613
644
  | `L1LogisticRegression`, 100,000 x 200, C=0.05 | 9.06 s | 18.7 s | 2.1x | 349.61154 / 349.61216 | 100% |
614
645
 
646
+ On 2,000 x 20,000 neither side reaches the optimum at tol 1e-4 (the
647
+ objective at tol 1e-10 is 0.0519588), and where each stops depends on
648
+ the coordinate order: over eight seeds ours stops at 0.05240 to
649
+ 0.05251 (0.9 to 1.1% above the optimum), `LinearSVC`, whose order is
650
+ random unless `random_state` is given, at 0.05242 to 0.05319 (0.9 to
651
+ 2.4%). When `LinearSVC` draws one of its worse orders the gap passes
652
+ the script's 0.5% bound and the row prints `<-- disagrees`; ours is
653
+ then the better solution of the two, so the flag there is not a fault.
654
+
615
655
  The regressors, from the same script (C = 1, epsilon 0.1):
616
656
 
617
657
  | problem | ours | scikit-learn | ratio | agreement |
File without changes
File without changes