scm2cpp-lasso 0.5.0.1__tar.gz → 0.5.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {scm2cpp_lasso-0.5.0.1 → scm2cpp_lasso-0.5.3}/PKG-INFO +44 -1
- {scm2cpp_lasso-0.5.0.1 → scm2cpp_lasso-0.5.3}/README.md +43 -0
- {scm2cpp_lasso-0.5.0.1 → scm2cpp_lasso-0.5.3}/pyproject.toml +1 -1
- {scm2cpp_lasso-0.5.0.1 → scm2cpp_lasso-0.5.3}/src/scm2cpp_lasso/__init__.py +133 -4
- {scm2cpp_lasso-0.5.0.1 → scm2cpp_lasso-0.5.3}/src/scm2cpp_lasso.egg-info/PKG-INFO +44 -1
- {scm2cpp_lasso-0.5.0.1 → scm2cpp_lasso-0.5.3}/setup.cfg +0 -0
- {scm2cpp_lasso-0.5.0.1 → scm2cpp_lasso-0.5.3}/setup.py +0 -0
- {scm2cpp_lasso-0.5.0.1 → scm2cpp_lasso-0.5.3}/src/scm2cpp_lasso/_generated/__init__.py +0 -0
- {scm2cpp_lasso-0.5.0.1 → scm2cpp_lasso-0.5.3}/src/scm2cpp_lasso/_generated/_loader.py +0 -0
- {scm2cpp_lasso-0.5.0.1 → scm2cpp_lasso-0.5.3}/src/scm2cpp_lasso/_generated/lasso_cov.cpp +0 -0
- {scm2cpp_lasso-0.5.0.1 → scm2cpp_lasso-0.5.3}/src/scm2cpp_lasso/_generated/lasso_cov.hpp +0 -0
- {scm2cpp_lasso-0.5.0.1 → scm2cpp_lasso-0.5.3}/src/scm2cpp_lasso/_generated/lasso_cov_capi.cpp +0 -0
- {scm2cpp_lasso-0.5.0.1 → scm2cpp_lasso-0.5.3}/src/scm2cpp_lasso/_generated/scm2cpp.hpp +0 -0
- {scm2cpp_lasso-0.5.0.1 → scm2cpp_lasso-0.5.3}/src/scm2cpp_lasso/_libfind.py +0 -0
- {scm2cpp_lasso-0.5.0.1 → scm2cpp_lasso-0.5.3}/src/scm2cpp_lasso/batch_capi.cu +0 -0
- {scm2cpp_lasso-0.5.0.1 → scm2cpp_lasso-0.5.3}/src/scm2cpp_lasso.egg-info/SOURCES.txt +0 -0
- {scm2cpp_lasso-0.5.0.1 → scm2cpp_lasso-0.5.3}/src/scm2cpp_lasso.egg-info/dependency_links.txt +0 -0
- {scm2cpp_lasso-0.5.0.1 → scm2cpp_lasso-0.5.3}/src/scm2cpp_lasso.egg-info/requires.txt +0 -0
- {scm2cpp_lasso-0.5.0.1 → scm2cpp_lasso-0.5.3}/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.5.
|
|
3
|
+
Version: 0.5.3
|
|
4
4
|
Summary: Lasso by covariance-update coordinate descent, translated from Scheme to C++ by scm2cpp; GPU batch solver included
|
|
5
5
|
Author: Hirotaka Niitsuma
|
|
6
6
|
License: MIT
|
|
@@ -70,9 +70,14 @@ from scm2cpp_lasso import cuda_available
|
|
|
70
70
|
lambdas = model.lambda_grid(num=100) # from lambda_max down
|
|
71
71
|
path = model.fit_path(lambdas) # warm-started, sequential
|
|
72
72
|
grid = model.fit_path_batch(lambdas) # every lambda from zero
|
|
73
|
+
one = model.fit(lambdas[60]) # a single lambda, if you must
|
|
73
74
|
print("GPU:", cuda_available())
|
|
74
75
|
```
|
|
75
76
|
|
|
77
|
+
`fit` is the one-element special case of `fit_path` -- the Gram matrix
|
|
78
|
+
was built in the constructor, so a whole warm-started path costs barely
|
|
79
|
+
more than one penalty, which is why the path is the primary interface.
|
|
80
|
+
|
|
76
81
|
The objective is scikit-learn's, with `fit_intercept=False`:
|
|
77
82
|
|
|
78
83
|
(1 / 2 nobs) ||y - X b||^2 + lam ||b||_1
|
|
@@ -84,6 +89,44 @@ at zero, and `lambda_grid()` walks down from it -- the construction
|
|
|
84
89
|
scikit-learn uses. If your columns differ wildly in scale, standardize
|
|
85
90
|
them first; this solver does not do it for you.
|
|
86
91
|
|
|
92
|
+
## Against the other lassos pip can install
|
|
93
|
+
|
|
94
|
+
`bench/lasso-compare.py` in the repository runs the same
|
|
95
|
+
cross-validation-shaped grid -- 4096 lambdas, each solved from zero,
|
|
96
|
+
p=200, n=1800 -- against every lasso a `pip install` produces:
|
|
97
|
+
scikit-learn 1.9.0, celer 0.7.4, skglm 0.5, and RAPIDS cuML 26.8.
|
|
98
|
+
Every solver runs at its own default tolerance (ours at the tighter
|
|
99
|
+
1e-8), each gets one untimed warm-up fit, cuML's data is on the device
|
|
100
|
+
before the clock starts, and the last column is each solver's
|
|
101
|
+
objective minus ours at the smallest lambda -- a fast row with a loose
|
|
102
|
+
answer would show as one:
|
|
103
|
+
|
|
104
|
+
| solver | time | objective gap |
|
|
105
|
+
|----------------------------------------|---------|---------------|
|
|
106
|
+
| scm2cpp-lasso, 1 CPU core (tol 1e-8) | 0.9 s | 0 |
|
|
107
|
+
| scm2cpp-lasso, GPU, one thread/lambda | 0.2 s | 0 |
|
|
108
|
+
| sklearn `Lasso.fit` per lambda | 12.5 s | +1.6e-09 |
|
|
109
|
+
| celer per lambda | 17.3 s | 0 |
|
|
110
|
+
| skglm per lambda | 16.4 s | +2.8e-17 |
|
|
111
|
+
| cuML per lambda, GPU | 57.5 s | +9.1e-07 |
|
|
112
|
+
|
|
113
|
+
celer and skglm are built for very large sparse designs, where their
|
|
114
|
+
screening rules dominate; at this size they pay their setup per fit.
|
|
115
|
+
cuML parallelises *within* one fit, which wins when a fit is large; at
|
|
116
|
+
this size its launch overhead dominates, while our GPU row
|
|
117
|
+
parallelises *across* lambdas -- one CUDA thread per penalty, the axis
|
|
118
|
+
a cross-validation grid actually offers.
|
|
119
|
+
|
|
120
|
+
`CovLassoCV` is scikit-learn's `LassoCV` over this machinery: the same
|
|
121
|
+
grid construction, the same contiguous folds, the same
|
|
122
|
+
minimum-mean-MSE choice -- and where the two disagree it is by one
|
|
123
|
+
grid step on a near-tie, which sklearn also resolves our way once its
|
|
124
|
+
tolerance is tightened. A fold's training Gram is a subtraction
|
|
125
|
+
(G - Xf'Xf; the Gram is additive over rows), and on the GPU every fold
|
|
126
|
+
and every alpha is one thread of a single launch. At cv=10 with 1000
|
|
127
|
+
alphas -- ten thousand independent problems -- that launch takes
|
|
128
|
+
1.2 s against sklearn's 1.7 s, with GPU and CPU agreeing to 1e-14.
|
|
129
|
+
|
|
87
130
|
## Which method
|
|
88
131
|
|
|
89
132
|
`fit_path` walks a single path, each lambda starting from the previous
|
|
@@ -52,9 +52,14 @@ from scm2cpp_lasso import cuda_available
|
|
|
52
52
|
lambdas = model.lambda_grid(num=100) # from lambda_max down
|
|
53
53
|
path = model.fit_path(lambdas) # warm-started, sequential
|
|
54
54
|
grid = model.fit_path_batch(lambdas) # every lambda from zero
|
|
55
|
+
one = model.fit(lambdas[60]) # a single lambda, if you must
|
|
55
56
|
print("GPU:", cuda_available())
|
|
56
57
|
```
|
|
57
58
|
|
|
59
|
+
`fit` is the one-element special case of `fit_path` -- the Gram matrix
|
|
60
|
+
was built in the constructor, so a whole warm-started path costs barely
|
|
61
|
+
more than one penalty, which is why the path is the primary interface.
|
|
62
|
+
|
|
58
63
|
The objective is scikit-learn's, with `fit_intercept=False`:
|
|
59
64
|
|
|
60
65
|
(1 / 2 nobs) ||y - X b||^2 + lam ||b||_1
|
|
@@ -66,6 +71,44 @@ at zero, and `lambda_grid()` walks down from it -- the construction
|
|
|
66
71
|
scikit-learn uses. If your columns differ wildly in scale, standardize
|
|
67
72
|
them first; this solver does not do it for you.
|
|
68
73
|
|
|
74
|
+
## Against the other lassos pip can install
|
|
75
|
+
|
|
76
|
+
`bench/lasso-compare.py` in the repository runs the same
|
|
77
|
+
cross-validation-shaped grid -- 4096 lambdas, each solved from zero,
|
|
78
|
+
p=200, n=1800 -- against every lasso a `pip install` produces:
|
|
79
|
+
scikit-learn 1.9.0, celer 0.7.4, skglm 0.5, and RAPIDS cuML 26.8.
|
|
80
|
+
Every solver runs at its own default tolerance (ours at the tighter
|
|
81
|
+
1e-8), each gets one untimed warm-up fit, cuML's data is on the device
|
|
82
|
+
before the clock starts, and the last column is each solver's
|
|
83
|
+
objective minus ours at the smallest lambda -- a fast row with a loose
|
|
84
|
+
answer would show as one:
|
|
85
|
+
|
|
86
|
+
| solver | time | objective gap |
|
|
87
|
+
|----------------------------------------|---------|---------------|
|
|
88
|
+
| scm2cpp-lasso, 1 CPU core (tol 1e-8) | 0.9 s | 0 |
|
|
89
|
+
| scm2cpp-lasso, GPU, one thread/lambda | 0.2 s | 0 |
|
|
90
|
+
| sklearn `Lasso.fit` per lambda | 12.5 s | +1.6e-09 |
|
|
91
|
+
| celer per lambda | 17.3 s | 0 |
|
|
92
|
+
| skglm per lambda | 16.4 s | +2.8e-17 |
|
|
93
|
+
| cuML per lambda, GPU | 57.5 s | +9.1e-07 |
|
|
94
|
+
|
|
95
|
+
celer and skglm are built for very large sparse designs, where their
|
|
96
|
+
screening rules dominate; at this size they pay their setup per fit.
|
|
97
|
+
cuML parallelises *within* one fit, which wins when a fit is large; at
|
|
98
|
+
this size its launch overhead dominates, while our GPU row
|
|
99
|
+
parallelises *across* lambdas -- one CUDA thread per penalty, the axis
|
|
100
|
+
a cross-validation grid actually offers.
|
|
101
|
+
|
|
102
|
+
`CovLassoCV` is scikit-learn's `LassoCV` over this machinery: the same
|
|
103
|
+
grid construction, the same contiguous folds, the same
|
|
104
|
+
minimum-mean-MSE choice -- and where the two disagree it is by one
|
|
105
|
+
grid step on a near-tie, which sklearn also resolves our way once its
|
|
106
|
+
tolerance is tightened. A fold's training Gram is a subtraction
|
|
107
|
+
(G - Xf'Xf; the Gram is additive over rows), and on the GPU every fold
|
|
108
|
+
and every alpha is one thread of a single launch. At cv=10 with 1000
|
|
109
|
+
alphas -- ten thousand independent problems -- that launch takes
|
|
110
|
+
1.2 s against sklearn's 1.7 s, with GPU and CPU agreeing to 1e-14.
|
|
111
|
+
|
|
69
112
|
## Which method
|
|
70
113
|
|
|
71
114
|
`fit_path` walks a single path, each lambda starting from the previous
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "scm2cpp-lasso"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.3"
|
|
8
8
|
description = "Lasso by covariance-update coordinate descent, translated from Scheme to C++ by scm2cpp; GPU batch solver included"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -31,7 +31,7 @@ from ._libfind import load_batch_lib
|
|
|
31
31
|
|
|
32
32
|
__all__ = ["CovLasso", "CovRidge", "CovLogistic", "CovGroupLasso",
|
|
33
33
|
"cuda_available", "kernel"]
|
|
34
|
-
__version__ = "0.5.
|
|
34
|
+
__version__ = "0.5.3"
|
|
35
35
|
|
|
36
36
|
_BATCH = load_batch_lib()
|
|
37
37
|
_DP = ctypes.POINTER(ctypes.c_double)
|
|
@@ -141,6 +141,17 @@ class CovLasso:
|
|
|
141
141
|
|
|
142
142
|
# ---------------------------- fitting ----------------------------
|
|
143
143
|
|
|
144
|
+
def fit(self, lam, **kw):
|
|
145
|
+
"""Coefficients at one lambda.
|
|
146
|
+
|
|
147
|
+
A single penalty is the one-element special case of the path:
|
|
148
|
+
the Gram matrix was built in the constructor, so this costs one
|
|
149
|
+
descent and nothing per row -- and a warm-started path over a
|
|
150
|
+
whole grid costs barely more, which is why fit_path is the
|
|
151
|
+
primary interface rather than this convenience.
|
|
152
|
+
"""
|
|
153
|
+
return self.fit_path([lam], **kw)[0]
|
|
154
|
+
|
|
144
155
|
def fit_path(self, lambdas, tol=1e-8, chunk=20, max_sweeps=100000,
|
|
145
156
|
l1_ratio=1.0):
|
|
146
157
|
"""Coefficients per lambda, each warm-started from the last.
|
|
@@ -246,7 +257,10 @@ def _batch_descend_multi(grams, corrs, lam, nobs, p, tol=1e-8, chunk=20,
|
|
|
246
257
|
beta = np.zeros((B, p))
|
|
247
258
|
c = np.ascontiguousarray(corrs.copy())
|
|
248
259
|
g = np.ascontiguousarray(grams)
|
|
249
|
-
|
|
260
|
+
# lam may be one penalty for every thread or one per thread; the
|
|
261
|
+
# kernel always reads a per-thread array
|
|
262
|
+
lams = (np.ascontiguousarray(lam, dtype=np.float64)
|
|
263
|
+
if np.ndim(lam) else np.full(B, float(lam)))
|
|
250
264
|
if _BATCH is not None and not force_cpu:
|
|
251
265
|
dp = ctypes.POINTER(ctypes.c_double)
|
|
252
266
|
rc = _BATCH.scm2cpp_batch_descend(
|
|
@@ -259,11 +273,12 @@ def _batch_descend_multi(grams, corrs, lam, nobs, p, tol=1e-8, chunk=20,
|
|
|
259
273
|
prev = np.empty(p)
|
|
260
274
|
for b in range(B):
|
|
261
275
|
gb, cb, bb = g[b], c[b], beta[b]
|
|
276
|
+
lam_b = float(lams[b])
|
|
262
277
|
swept = 0
|
|
263
278
|
while swept < max_sweeps:
|
|
264
279
|
prev[:] = bb
|
|
265
|
-
kernel_fn(gb, cb, bb,
|
|
266
|
-
|
|
280
|
+
kernel_fn(gb, cb, bb, lam_b * l1_ratio,
|
|
281
|
+
lam_b * (1.0 - l1_ratio), chunk,
|
|
267
282
|
float(nobs), p)
|
|
268
283
|
swept += chunk
|
|
269
284
|
if np.max(np.abs(bb - prev)) < tol * max(
|
|
@@ -272,6 +287,120 @@ def _batch_descend_multi(grams, corrs, lam, nobs, p, tol=1e-8, chunk=20,
|
|
|
272
287
|
return beta
|
|
273
288
|
|
|
274
289
|
|
|
290
|
+
|
|
291
|
+
class CovLassoCV:
|
|
292
|
+
"""Cross-validated lasso, scikit-learn's LassoCV semantics.
|
|
293
|
+
|
|
294
|
+
The lambda grid comes from the full data (lambda_max down, the
|
|
295
|
+
sklearn construction), the folds are contiguous splits in order
|
|
296
|
+
(sklearn's KFold default), each fold's whole path is solved from
|
|
297
|
+
zero -- which is exactly the workload fit_path_batch exists for,
|
|
298
|
+
one GPU thread per lambda -- and validation error is one matrix
|
|
299
|
+
product per fold. alpha_ minimises the mean validation MSE, ties
|
|
300
|
+
going to the larger penalty as in scikit-learn, and coef_ is the
|
|
301
|
+
refit on all rows at alpha_.
|
|
302
|
+
|
|
303
|
+
Attributes after fit: alphas_, mse_path_ (num, cv), alpha_, coef_.
|
|
304
|
+
"""
|
|
305
|
+
|
|
306
|
+
def __init__(self, cv=5, num=100, eps=1e-3, tol=1e-8, l1_ratio=1.0,
|
|
307
|
+
force_cpu=False):
|
|
308
|
+
self.cv = int(cv)
|
|
309
|
+
self.num = int(num)
|
|
310
|
+
self.eps = float(eps)
|
|
311
|
+
self.tol = float(tol)
|
|
312
|
+
self.l1_ratio = float(l1_ratio)
|
|
313
|
+
self.force_cpu = force_cpu
|
|
314
|
+
|
|
315
|
+
def fit(self, X, y):
|
|
316
|
+
X = np.ascontiguousarray(X, dtype=np.float64)
|
|
317
|
+
y = np.ascontiguousarray(y, dtype=np.float64)
|
|
318
|
+
n = len(y)
|
|
319
|
+
full = CovLasso(X, y)
|
|
320
|
+
self.alphas_ = full.lambda_grid(num=self.num, eps=self.eps,
|
|
321
|
+
l1_ratio=self.l1_ratio)
|
|
322
|
+
# contiguous folds in row order, sizes as even as they come --
|
|
323
|
+
# sklearn's KFold(shuffle=False)
|
|
324
|
+
bounds = np.linspace(0, n, self.cv + 1).astype(int)
|
|
325
|
+
self.mse_path_ = np.empty((self.num, self.cv))
|
|
326
|
+
gpu = cuda_available() and not self.force_cpu
|
|
327
|
+
G = full.g.reshape(full.p, full.p)
|
|
328
|
+
if gpu:
|
|
329
|
+
return self._fit_gpu(X, y, full, G, bounds, n)
|
|
330
|
+
for k in range(self.cv):
|
|
331
|
+
lo, hi = bounds[k], bounds[k + 1]
|
|
332
|
+
Xf, yf = X[lo:hi], y[lo:hi]
|
|
333
|
+
# The Gram is additive over rows, so a fold's training Gram
|
|
334
|
+
# is a subtraction: G_full - Xf'Xf costs O(n_fold p^2) where
|
|
335
|
+
# rebuilding from the complement costs O((n - n_fold) p^2).
|
|
336
|
+
# Over five folds that is the whole Gram work twice instead
|
|
337
|
+
# of four times, and the Gram is the dominant O(n p^2) step.
|
|
338
|
+
m = CovLasso(gram=(G - Xf.T @ Xf).ravel(),
|
|
339
|
+
corr=full.c0 - Xf.T @ yf,
|
|
340
|
+
nobs=n - (hi - lo))
|
|
341
|
+
# On the GPU every lambda is its own thread, so the batch is
|
|
342
|
+
# the right shape; on the CPU the warm-started path is: after
|
|
343
|
+
# the Gram, each lambda resumes where the previous stopped
|
|
344
|
+
# and costs O(p) per moving coordinate. That warm path is
|
|
345
|
+
# what makes searching the alpha grid cheap -- the grid
|
|
346
|
+
# costs barely more than its hardest single alpha.
|
|
347
|
+
if gpu:
|
|
348
|
+
path = m.fit_path_batch(self.alphas_, tol=self.tol,
|
|
349
|
+
l1_ratio=self.l1_ratio)
|
|
350
|
+
else:
|
|
351
|
+
path = m.fit_path(self.alphas_, tol=self.tol,
|
|
352
|
+
l1_ratio=self.l1_ratio)
|
|
353
|
+
resid = Xf @ path.T - yf[:, None]
|
|
354
|
+
self.mse_path_[:, k] = np.mean(resid * resid, axis=0)
|
|
355
|
+
mean = self.mse_path_.mean(axis=1)
|
|
356
|
+
# alphas_ descends, so argmin's first hit is the larger penalty
|
|
357
|
+
self.alpha_ = float(self.alphas_[int(np.argmin(mean))])
|
|
358
|
+
self.coef_ = full.fit(self.alpha_, tol=self.tol,
|
|
359
|
+
l1_ratio=self.l1_ratio)
|
|
360
|
+
return self
|
|
361
|
+
|
|
362
|
+
def _fit_gpu(self, X, y, full, G, bounds, n):
|
|
363
|
+
# Every fold and every alpha is an independent problem, so all of
|
|
364
|
+
# them go to the device as one batch: cv * num threads, each with
|
|
365
|
+
# its fold's Gram (the per-thread-Gram path the bootstrap added)
|
|
366
|
+
# and its own penalty. Fold sizes may differ by a row; the kernel
|
|
367
|
+
# only ever uses lam through the product lam * nobs, so each
|
|
368
|
+
# thread's nobs is absorbed into its lambda and nobs is passed
|
|
369
|
+
# as 1 -- exact, not approximate.
|
|
370
|
+
p_ = full.p
|
|
371
|
+
num, cv = self.num, self.cv
|
|
372
|
+
grams = np.empty((cv, p_ * p_))
|
|
373
|
+
corrs = np.empty((cv, p_))
|
|
374
|
+
nobs_k = np.empty(cv)
|
|
375
|
+
for k in range(cv):
|
|
376
|
+
lo, hi = bounds[k], bounds[k + 1]
|
|
377
|
+
Xf, yf = X[lo:hi], y[lo:hi]
|
|
378
|
+
grams[k] = (G - Xf.T @ Xf).ravel()
|
|
379
|
+
corrs[k] = full.c0 - Xf.T @ yf
|
|
380
|
+
nobs_k[k] = n - (hi - lo)
|
|
381
|
+
gr = np.repeat(grams, num, axis=0)
|
|
382
|
+
cr = np.repeat(corrs, num, axis=0)
|
|
383
|
+
lams = (np.tile(self.alphas_, cv) * np.repeat(nobs_k, num))
|
|
384
|
+
betas = _batch_descend_multi(gr, cr, lams, 1.0, p_,
|
|
385
|
+
tol=self.tol,
|
|
386
|
+
l1_ratio=self.l1_ratio,
|
|
387
|
+
force_cpu=self.force_cpu,
|
|
388
|
+
kernel_fn=kernel.enet_descend)
|
|
389
|
+
betas = betas.reshape(cv, num, p_)
|
|
390
|
+
for k in range(cv):
|
|
391
|
+
lo, hi = bounds[k], bounds[k + 1]
|
|
392
|
+
resid = X[lo:hi] @ betas[k].T - y[lo:hi, None]
|
|
393
|
+
self.mse_path_[:, k] = np.mean(resid * resid, axis=0)
|
|
394
|
+
mean = self.mse_path_.mean(axis=1)
|
|
395
|
+
self.alpha_ = float(self.alphas_[int(np.argmin(mean))])
|
|
396
|
+
self.coef_ = full.fit(self.alpha_, tol=self.tol,
|
|
397
|
+
l1_ratio=self.l1_ratio)
|
|
398
|
+
return self
|
|
399
|
+
|
|
400
|
+
def predict(self, X):
|
|
401
|
+
return np.ascontiguousarray(X, dtype=np.float64) @ self.coef_
|
|
402
|
+
|
|
403
|
+
|
|
275
404
|
class CovRidge:
|
|
276
405
|
"""Ridge regression over a design given by its Gram matrix.
|
|
277
406
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scm2cpp-lasso
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
4
4
|
Summary: Lasso by covariance-update coordinate descent, translated from Scheme to C++ by scm2cpp; GPU batch solver included
|
|
5
5
|
Author: Hirotaka Niitsuma
|
|
6
6
|
License: MIT
|
|
@@ -70,9 +70,14 @@ from scm2cpp_lasso import cuda_available
|
|
|
70
70
|
lambdas = model.lambda_grid(num=100) # from lambda_max down
|
|
71
71
|
path = model.fit_path(lambdas) # warm-started, sequential
|
|
72
72
|
grid = model.fit_path_batch(lambdas) # every lambda from zero
|
|
73
|
+
one = model.fit(lambdas[60]) # a single lambda, if you must
|
|
73
74
|
print("GPU:", cuda_available())
|
|
74
75
|
```
|
|
75
76
|
|
|
77
|
+
`fit` is the one-element special case of `fit_path` -- the Gram matrix
|
|
78
|
+
was built in the constructor, so a whole warm-started path costs barely
|
|
79
|
+
more than one penalty, which is why the path is the primary interface.
|
|
80
|
+
|
|
76
81
|
The objective is scikit-learn's, with `fit_intercept=False`:
|
|
77
82
|
|
|
78
83
|
(1 / 2 nobs) ||y - X b||^2 + lam ||b||_1
|
|
@@ -84,6 +89,44 @@ at zero, and `lambda_grid()` walks down from it -- the construction
|
|
|
84
89
|
scikit-learn uses. If your columns differ wildly in scale, standardize
|
|
85
90
|
them first; this solver does not do it for you.
|
|
86
91
|
|
|
92
|
+
## Against the other lassos pip can install
|
|
93
|
+
|
|
94
|
+
`bench/lasso-compare.py` in the repository runs the same
|
|
95
|
+
cross-validation-shaped grid -- 4096 lambdas, each solved from zero,
|
|
96
|
+
p=200, n=1800 -- against every lasso a `pip install` produces:
|
|
97
|
+
scikit-learn 1.9.0, celer 0.7.4, skglm 0.5, and RAPIDS cuML 26.8.
|
|
98
|
+
Every solver runs at its own default tolerance (ours at the tighter
|
|
99
|
+
1e-8), each gets one untimed warm-up fit, cuML's data is on the device
|
|
100
|
+
before the clock starts, and the last column is each solver's
|
|
101
|
+
objective minus ours at the smallest lambda -- a fast row with a loose
|
|
102
|
+
answer would show as one:
|
|
103
|
+
|
|
104
|
+
| solver | time | objective gap |
|
|
105
|
+
|----------------------------------------|---------|---------------|
|
|
106
|
+
| scm2cpp-lasso, 1 CPU core (tol 1e-8) | 0.9 s | 0 |
|
|
107
|
+
| scm2cpp-lasso, GPU, one thread/lambda | 0.2 s | 0 |
|
|
108
|
+
| sklearn `Lasso.fit` per lambda | 12.5 s | +1.6e-09 |
|
|
109
|
+
| celer per lambda | 17.3 s | 0 |
|
|
110
|
+
| skglm per lambda | 16.4 s | +2.8e-17 |
|
|
111
|
+
| cuML per lambda, GPU | 57.5 s | +9.1e-07 |
|
|
112
|
+
|
|
113
|
+
celer and skglm are built for very large sparse designs, where their
|
|
114
|
+
screening rules dominate; at this size they pay their setup per fit.
|
|
115
|
+
cuML parallelises *within* one fit, which wins when a fit is large; at
|
|
116
|
+
this size its launch overhead dominates, while our GPU row
|
|
117
|
+
parallelises *across* lambdas -- one CUDA thread per penalty, the axis
|
|
118
|
+
a cross-validation grid actually offers.
|
|
119
|
+
|
|
120
|
+
`CovLassoCV` is scikit-learn's `LassoCV` over this machinery: the same
|
|
121
|
+
grid construction, the same contiguous folds, the same
|
|
122
|
+
minimum-mean-MSE choice -- and where the two disagree it is by one
|
|
123
|
+
grid step on a near-tie, which sklearn also resolves our way once its
|
|
124
|
+
tolerance is tightened. A fold's training Gram is a subtraction
|
|
125
|
+
(G - Xf'Xf; the Gram is additive over rows), and on the GPU every fold
|
|
126
|
+
and every alpha is one thread of a single launch. At cv=10 with 1000
|
|
127
|
+
alphas -- ten thousand independent problems -- that launch takes
|
|
128
|
+
1.2 s against sklearn's 1.7 s, with GPU and CPU agreeing to 1e-14.
|
|
129
|
+
|
|
87
130
|
## Which method
|
|
88
131
|
|
|
89
132
|
`fit_path` walks a single path, each lambda starting from the previous
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scm2cpp_lasso-0.5.0.1 → scm2cpp_lasso-0.5.3}/src/scm2cpp_lasso/_generated/lasso_cov_capi.cpp
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scm2cpp_lasso-0.5.0.1 → scm2cpp_lasso-0.5.3}/src/scm2cpp_lasso.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|