statgpu 0.1.0__py3-none-any.whl
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.
- statgpu/__init__.py +174 -0
- statgpu/_base.py +544 -0
- statgpu/_config.py +127 -0
- statgpu/anova/__init__.py +5 -0
- statgpu/anova/_oneway.py +194 -0
- statgpu/backends/__init__.py +83 -0
- statgpu/backends/_array_ops.py +529 -0
- statgpu/backends/_base.py +184 -0
- statgpu/backends/_cupy.py +453 -0
- statgpu/backends/_factory.py +65 -0
- statgpu/backends/_gpu_inference_cupy.py +214 -0
- statgpu/backends/_gpu_inference_torch.py +422 -0
- statgpu/backends/_numpy.py +324 -0
- statgpu/backends/_torch.py +685 -0
- statgpu/backends/_torch_safe.py +47 -0
- statgpu/backends/_utils.py +423 -0
- statgpu/core/__init__.py +10 -0
- statgpu/core/formula/__init__.py +33 -0
- statgpu/core/formula/_design.py +99 -0
- statgpu/core/formula/_parser.py +191 -0
- statgpu/core/formula/_terms.py +70 -0
- statgpu/core/formula/tests/__init__.py +0 -0
- statgpu/core/formula/tests/test_parser.py +194 -0
- statgpu/covariance/__init__.py +6 -0
- statgpu/covariance/_empirical.py +310 -0
- statgpu/covariance/_shrinkage.py +248 -0
- statgpu/cross_validation/__init__.py +31 -0
- statgpu/cross_validation/_base.py +410 -0
- statgpu/cross_validation/_engine.py +167 -0
- statgpu/diagnostics/__init__.py +7 -0
- statgpu/diagnostics/_regression_diagnostics.py +188 -0
- statgpu/feature_selection/__init__.py +24 -0
- statgpu/feature_selection/_knockoff.py +870 -0
- statgpu/feature_selection/_knockoff_utils.py +1003 -0
- statgpu/feature_selection/_stepwise.py +300 -0
- statgpu/glm_core/__init__.py +81 -0
- statgpu/glm_core/_base.py +202 -0
- statgpu/glm_core/_family.py +362 -0
- statgpu/glm_core/_fused.py +149 -0
- statgpu/glm_core/_gamma.py +111 -0
- statgpu/glm_core/_inverse_gaussian.py +62 -0
- statgpu/glm_core/_irls.py +561 -0
- statgpu/glm_core/_logistic.py +82 -0
- statgpu/glm_core/_negative_binomial.py +68 -0
- statgpu/glm_core/_poisson.py +60 -0
- statgpu/glm_core/_solver_legacy.py +100 -0
- statgpu/glm_core/_squared.py +53 -0
- statgpu/glm_core/_tweedie.py +74 -0
- statgpu/inference/__init__.py +239 -0
- statgpu/inference/_distributions_backend.py +2610 -0
- statgpu/inference/_multiple_testing.py +391 -0
- statgpu/inference/_resampling.py +1400 -0
- statgpu/inference/_results.py +265 -0
- statgpu/linear_model/__init__.py +75 -0
- statgpu/linear_model/_gaussian_inference.py +306 -0
- statgpu/linear_model/_glm_base.py +1261 -0
- statgpu/linear_model/_ordered_logit.py +52 -0
- statgpu/linear_model/_ordered_probit.py +50 -0
- statgpu/linear_model/_stats.py +170 -0
- statgpu/linear_model/cv/__init__.py +13 -0
- statgpu/linear_model/cv/_elasticnet_cv.py +892 -0
- statgpu/linear_model/cv/_lasso_cv.py +253 -0
- statgpu/linear_model/cv/_logistic_cv.py +895 -0
- statgpu/linear_model/cv/_ridge_cv.py +1160 -0
- statgpu/linear_model/legacy/__init__.py +1 -0
- statgpu/linear_model/legacy/_distributions_legacy_gpu.py +340 -0
- statgpu/linear_model/legacy/_elasticnet_legacy.py +936 -0
- statgpu/linear_model/legacy/_lasso_legacy.py +4876 -0
- statgpu/linear_model/legacy/_penalized_legacy.py +1174 -0
- statgpu/linear_model/legacy/_ridge_legacy.py +863 -0
- statgpu/linear_model/legacy/_solver_legacy.py +104 -0
- statgpu/linear_model/penalized/__init__.py +25 -0
- statgpu/linear_model/penalized/_base.py +437 -0
- statgpu/linear_model/penalized/_fit_mixin.py +1877 -0
- statgpu/linear_model/penalized/_inference_mixin.py +1179 -0
- statgpu/linear_model/penalized/_penalized_cv.py +2699 -0
- statgpu/linear_model/penalized/_penalized_gamma.py +86 -0
- statgpu/linear_model/penalized/_penalized_inverse_gaussian.py +62 -0
- statgpu/linear_model/penalized/_penalized_linear.py +236 -0
- statgpu/linear_model/penalized/_penalized_logistic.py +100 -0
- statgpu/linear_model/penalized/_penalized_negative_binomial.py +65 -0
- statgpu/linear_model/penalized/_penalized_poisson.py +62 -0
- statgpu/linear_model/penalized/_penalized_tweedie.py +65 -0
- statgpu/linear_model/penalized/_predict_mixin.py +182 -0
- statgpu/linear_model/wrappers/__init__.py +31 -0
- statgpu/linear_model/wrappers/_adaptive_lasso.py +63 -0
- statgpu/linear_model/wrappers/_elasticnet.py +75 -0
- statgpu/linear_model/wrappers/_gamma.py +67 -0
- statgpu/linear_model/wrappers/_inverse_gaussian.py +47 -0
- statgpu/linear_model/wrappers/_lasso.py +2124 -0
- statgpu/linear_model/wrappers/_linear.py +1127 -0
- statgpu/linear_model/wrappers/_logistic.py +1435 -0
- statgpu/linear_model/wrappers/_mcp.py +58 -0
- statgpu/linear_model/wrappers/_negative_binomial.py +58 -0
- statgpu/linear_model/wrappers/_poisson.py +48 -0
- statgpu/linear_model/wrappers/_ridge.py +166 -0
- statgpu/linear_model/wrappers/_scad.py +58 -0
- statgpu/linear_model/wrappers/_tweedie.py +57 -0
- statgpu/metrics/__init__.py +21 -0
- statgpu/metrics/_classification.py +591 -0
- statgpu/nonparametric/__init__.py +50 -0
- statgpu/nonparametric/kernel_methods/__init__.py +25 -0
- statgpu/nonparametric/kernel_methods/_kernels.py +246 -0
- statgpu/nonparametric/kernel_methods/_krr.py +234 -0
- statgpu/nonparametric/kernel_methods/_krr_cv.py +380 -0
- statgpu/nonparametric/kernel_smoothing/__init__.py +39 -0
- statgpu/nonparametric/kernel_smoothing/_bandwidth_selection.py +1083 -0
- statgpu/nonparametric/kernel_smoothing/_kde.py +761 -0
- statgpu/nonparametric/kernel_smoothing/_kernel_common.py +348 -0
- statgpu/nonparametric/kernel_smoothing/_kernel_regression.py +748 -0
- statgpu/nonparametric/splines/__init__.py +5 -0
- statgpu/nonparametric/splines/_bspline_basis.py +336 -0
- statgpu/nonparametric/splines/_penalized.py +349 -0
- statgpu/panel/__init__.py +19 -0
- statgpu/panel/_covariance.py +140 -0
- statgpu/panel/_fixed_effects.py +420 -0
- statgpu/panel/_random_effects.py +385 -0
- statgpu/panel/_utils.py +482 -0
- statgpu/penalties/__init__.py +139 -0
- statgpu/penalties/_adaptive_l1.py +313 -0
- statgpu/penalties/_base.py +261 -0
- statgpu/penalties/_categories.py +39 -0
- statgpu/penalties/_elasticnet.py +98 -0
- statgpu/penalties/_group_lasso.py +678 -0
- statgpu/penalties/_group_mcp.py +553 -0
- statgpu/penalties/_group_scad.py +605 -0
- statgpu/penalties/_l1.py +107 -0
- statgpu/penalties/_l2.py +77 -0
- statgpu/penalties/_mcp.py +237 -0
- statgpu/penalties/_scad.py +260 -0
- statgpu/semiparametric/__init__.py +5 -0
- statgpu/semiparametric/_gam.py +401 -0
- statgpu/solvers/__init__.py +24 -0
- statgpu/solvers/_admm.py +241 -0
- statgpu/solvers/_constants.py +15 -0
- statgpu/solvers/_convergence.py +6 -0
- statgpu/solvers/_fista.py +436 -0
- statgpu/solvers/_fista_bb.py +513 -0
- statgpu/solvers/_fista_lla.py +541 -0
- statgpu/solvers/_lbfgs.py +206 -0
- statgpu/solvers/_newton.py +149 -0
- statgpu/solvers/_utils.py +277 -0
- statgpu/survival/__init__.py +14 -0
- statgpu/survival/_cox.py +3974 -0
- statgpu/survival/_cox_breslow_triton_kernel.py +106 -0
- statgpu/survival/_cox_cv.py +1159 -0
- statgpu/survival/_cox_efron_cuda.py +1280 -0
- statgpu/survival/_cox_efron_triton.py +359 -0
- statgpu/unsupervised/__init__.py +29 -0
- statgpu/unsupervised/_agglomerative.py +307 -0
- statgpu/unsupervised/_dbscan.py +263 -0
- statgpu/unsupervised/_dbscan_cpu.pyx +125 -0
- statgpu/unsupervised/_gmm.py +332 -0
- statgpu/unsupervised/_incremental_pca.py +176 -0
- statgpu/unsupervised/_kmeans.py +261 -0
- statgpu/unsupervised/_minibatch_kmeans.py +299 -0
- statgpu/unsupervised/_minibatch_nmf.py +252 -0
- statgpu/unsupervised/_nmf.py +190 -0
- statgpu/unsupervised/_pca.py +189 -0
- statgpu/unsupervised/_truncated_svd.py +132 -0
- statgpu/unsupervised/_tsne.py +192 -0
- statgpu/unsupervised/_umap.py +224 -0
- statgpu/unsupervised/_utils.py +134 -0
- statgpu-0.1.0.dist-info/METADATA +245 -0
- statgpu-0.1.0.dist-info/RECORD +168 -0
- statgpu-0.1.0.dist-info/WHEEL +5 -0
- statgpu-0.1.0.dist-info/licenses/LICENSE +199 -0
- statgpu-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Negative Binomial loss: negative log-likelihood with log link.
|
|
3
|
+
|
|
4
|
+
For overdispersed count data:
|
|
5
|
+
Var(Y) = mu + alpha * mu^2
|
|
6
|
+
where mu = exp(X @ coef), alpha is the dispersion parameter.
|
|
7
|
+
|
|
8
|
+
Supports numpy / cupy / torch backends via _array_ops helpers.
|
|
9
|
+
"""
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
from statgpu.backends._array_ops import _clip, _exp, _log, _sum, _max_eigval_power
|
|
13
|
+
from statgpu.glm_core._base import GLMLoss, register_glm_loss
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@register_glm_loss('negative_binomial')
|
|
17
|
+
class NegativeBinomialLoss(GLMLoss):
|
|
18
|
+
name = "negative_binomial"
|
|
19
|
+
y_type = "count"
|
|
20
|
+
smooth_gradient = True
|
|
21
|
+
has_hessian = True
|
|
22
|
+
|
|
23
|
+
_MU_LO = 1e-300
|
|
24
|
+
|
|
25
|
+
def __init__(self, alpha=1.0):
|
|
26
|
+
if not np.isfinite(alpha) or alpha <= 0.0:
|
|
27
|
+
raise ValueError("alpha must be a finite positive scalar for negative binomial loss")
|
|
28
|
+
self.alpha = alpha
|
|
29
|
+
|
|
30
|
+
def _mu_from_eta(self, eta):
|
|
31
|
+
return _clip(_exp(_clip(eta, -30, 30)), self._MU_LO, None)
|
|
32
|
+
|
|
33
|
+
# ── Per-sample formulas (single source of truth) ──────────────────
|
|
34
|
+
|
|
35
|
+
def per_sample_value(self, eta, y):
|
|
36
|
+
mu = self._mu_from_eta(eta)
|
|
37
|
+
a = self.alpha
|
|
38
|
+
one_plus_a_mu = 1.0 + a * mu
|
|
39
|
+
return -y * _log(mu / one_plus_a_mu) + (1.0 / a) * _log(one_plus_a_mu)
|
|
40
|
+
|
|
41
|
+
def per_sample_gradient(self, eta, y):
|
|
42
|
+
mu = self._mu_from_eta(eta)
|
|
43
|
+
return (mu - y) / (1.0 + self.alpha * mu)
|
|
44
|
+
|
|
45
|
+
def hessian(self, X, y, coef, sample_weight=None):
|
|
46
|
+
z = _clip(X @ coef, -30, 30)
|
|
47
|
+
mu = _exp(z)
|
|
48
|
+
W = _clip(mu, 1e-10, None) / (1.0 + self.alpha * _clip(mu, 1e-10, None))
|
|
49
|
+
if sample_weight is not None:
|
|
50
|
+
W = W * sample_weight
|
|
51
|
+
n_eff = float(sample_weight.sum()) if sample_weight is not None else X.shape[0]
|
|
52
|
+
return X.T @ (X * W[:, None]) / n_eff
|
|
53
|
+
|
|
54
|
+
_lipschitz_safety = 2.0 # NB Hessian varies moderately with mu
|
|
55
|
+
|
|
56
|
+
def lipschitz(self, X, coef, y=None, sample_weight=None):
|
|
57
|
+
z = _clip(X @ coef, -30, 30)
|
|
58
|
+
mu = _exp(z)
|
|
59
|
+
W = _clip(mu, 1e-10, 1e6) / (1.0 + self.alpha * _clip(mu, 1e-10, 1e6))
|
|
60
|
+
if sample_weight is not None:
|
|
61
|
+
W = W * sample_weight
|
|
62
|
+
n_eff = float(sample_weight.sum()) if sample_weight is not None else X.shape[0]
|
|
63
|
+
XtWX = X.T @ (X * W[:, None])
|
|
64
|
+
L = _max_eigval_power(XtWX) / n_eff
|
|
65
|
+
return max(L, 1e-8) # Safety factor applied by solver via _lipschitz_safety
|
|
66
|
+
|
|
67
|
+
def predict(self, X, coef):
|
|
68
|
+
return _exp(X @ coef)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Poisson loss: negative Poisson log-likelihood.
|
|
3
|
+
|
|
4
|
+
For count data:
|
|
5
|
+
loss = (1/n) * sum(mu - y*log(mu))
|
|
6
|
+
where mu = exp(X @ coef).
|
|
7
|
+
|
|
8
|
+
Supports numpy / cupy / torch backends via _backend helpers.
|
|
9
|
+
"""
|
|
10
|
+
from statgpu.backends._array_ops import _clip, _exp, _log, _sum, _max_eigval_power
|
|
11
|
+
from statgpu.glm_core._base import GLMLoss, register_glm_loss
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@register_glm_loss('poisson')
|
|
15
|
+
class PoissonLoss(GLMLoss):
|
|
16
|
+
name = "poisson"
|
|
17
|
+
y_type = "count"
|
|
18
|
+
smooth_gradient = True
|
|
19
|
+
has_hessian = True
|
|
20
|
+
_momentum_beta_cap = 0.5
|
|
21
|
+
_poisson_like = True
|
|
22
|
+
|
|
23
|
+
_MU_LO = 1e-10
|
|
24
|
+
_MU_HI = 1e6 # must exceed typical max(y); clip prevents extreme weights
|
|
25
|
+
_ETA_LO = -30
|
|
26
|
+
_ETA_HI = 30
|
|
27
|
+
|
|
28
|
+
# ── Per-sample formulas (single source of truth) ──────────────────
|
|
29
|
+
|
|
30
|
+
def _mu_from_eta(self, eta):
|
|
31
|
+
return _clip(_exp(_clip(eta, self._ETA_LO, self._ETA_HI)), self._MU_LO, self._MU_HI)
|
|
32
|
+
|
|
33
|
+
def per_sample_value(self, eta, y):
|
|
34
|
+
mu = self._mu_from_eta(eta)
|
|
35
|
+
return mu - y * _log(mu)
|
|
36
|
+
|
|
37
|
+
def per_sample_gradient(self, eta, y):
|
|
38
|
+
mu = self._mu_from_eta(eta)
|
|
39
|
+
return mu - y
|
|
40
|
+
|
|
41
|
+
# ── Hessian / Lipschitz ───────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
def hessian(self, X, y, coef, sample_weight=None):
|
|
44
|
+
z = _clip(X @ coef, -30, 30)
|
|
45
|
+
mu = _clip(_exp(z), self._MU_LO, self._MU_HI)
|
|
46
|
+
W = mu if sample_weight is None else mu * sample_weight
|
|
47
|
+
n_eff = float(sample_weight.sum()) if sample_weight is not None else X.shape[0]
|
|
48
|
+
return X.T @ (X * W[:, None]) / n_eff
|
|
49
|
+
|
|
50
|
+
def lipschitz(self, X, coef, y=None, sample_weight=None):
|
|
51
|
+
z = _clip(X @ coef, -30, 30)
|
|
52
|
+
mu = _clip(_exp(z), self._MU_LO, self._MU_HI)
|
|
53
|
+
W = mu if sample_weight is None else mu * sample_weight
|
|
54
|
+
n_eff = float(sample_weight.sum()) if sample_weight is not None else X.shape[0]
|
|
55
|
+
XtWX = X.T @ (X * W[:, None])
|
|
56
|
+
L = _max_eigval_power(XtWX) / n_eff
|
|
57
|
+
return max(L, 1e-8)
|
|
58
|
+
|
|
59
|
+
def predict(self, X, coef):
|
|
60
|
+
return _exp(X @ coef)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Legacy solver methods from _solver.py.
|
|
2
|
+
|
|
3
|
+
DO NOT import in production code."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
def fista_sqerr_adaptive_l1_fused(
|
|
10
|
+
X, y, penalty_weights, alpha,
|
|
11
|
+
XtX, Xty, yty, n_samples,
|
|
12
|
+
L_init, max_iter, tol,
|
|
13
|
+
backend, no_momentum=False,
|
|
14
|
+
):
|
|
15
|
+
"""Fused FISTA for squared_error + AdaptiveL1 with pre-computed XtX/Xty.
|
|
16
|
+
|
|
17
|
+
Eliminates:
|
|
18
|
+
- Redundant X@coef matmul (uses XtX instead)
|
|
19
|
+
- GPU→CPU syncs (convergence check deferred)
|
|
20
|
+
- Element-wise kernel overhead (fused update+proximal+momentum)
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
X, y : array (centered)
|
|
25
|
+
penalty_weights : array (p,) — LLA weights
|
|
26
|
+
alpha : float — penalty alpha
|
|
27
|
+
XtX, Xty, yty : pre-computed
|
|
28
|
+
n_samples : int
|
|
29
|
+
L_init : float — initial Lipschitz
|
|
30
|
+
max_iter, tol : FISTA params
|
|
31
|
+
backend : 'torch' or 'cupy'
|
|
32
|
+
no_momentum : bool
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
coef : array (p,)
|
|
37
|
+
n_iter : int
|
|
38
|
+
"""
|
|
39
|
+
p = XtX.shape[0]
|
|
40
|
+
step = 1.0 / L_init
|
|
41
|
+
L = L_init
|
|
42
|
+
|
|
43
|
+
if backend == "torch":
|
|
44
|
+
import torch
|
|
45
|
+
thresh = torch.tensor(
|
|
46
|
+
alpha * penalty_weights * step,
|
|
47
|
+
device=XtX.device, dtype=XtX.dtype,
|
|
48
|
+
)
|
|
49
|
+
coef = torch.zeros(p, device=XtX.device, dtype=XtX.dtype)
|
|
50
|
+
coef_old = coef.clone()
|
|
51
|
+
y_k = coef.clone()
|
|
52
|
+
_fused = _get_sqerr_proximal_torch()
|
|
53
|
+
# Pre-allocate for momentum-free case
|
|
54
|
+
_zero_beta = 0.0
|
|
55
|
+
else:
|
|
56
|
+
import cupy as cp
|
|
57
|
+
thresh = cp.asarray(alpha * penalty_weights * step, dtype=cp.float64)
|
|
58
|
+
coef = cp.zeros(p, dtype=cp.float64)
|
|
59
|
+
coef_old = coef.copy()
|
|
60
|
+
y_k = coef.copy()
|
|
61
|
+
_fused = _get_sqerr_proximal_cupy()
|
|
62
|
+
_zero_beta = 0.0
|
|
63
|
+
|
|
64
|
+
t_k = 1.0
|
|
65
|
+
_sync_interval = 10 # Only check convergence every N iterations
|
|
66
|
+
|
|
67
|
+
iteration = -1 # default if max_iter=0
|
|
68
|
+
for iteration in range(max_iter):
|
|
69
|
+
# Gradient: grad = (XtX @ y_k - Xty) / n
|
|
70
|
+
grad = (XtX @ y_k - Xty) / n_samples
|
|
71
|
+
|
|
72
|
+
# Clip gradients (avoid sync — do it on GPU)
|
|
73
|
+
if iteration % 10 == 0:
|
|
74
|
+
grad = _clip_grad_on_device(grad, coef_old, backend)
|
|
75
|
+
|
|
76
|
+
# Proximal gradient step (no backtracking — Lipschitz is exact for squared_error)
|
|
77
|
+
# Pre-compute momentum coefficient so the fused kernel can apply it in one pass.
|
|
78
|
+
if no_momentum:
|
|
79
|
+
beta_mom = 0.0
|
|
80
|
+
else:
|
|
81
|
+
t_new = (1.0 + np.sqrt(1.0 + 4.0 * t_k * t_k)) / 2.0
|
|
82
|
+
beta_mom = (t_k - 1.0) / t_new
|
|
83
|
+
coef_new, y_k = _fused(y_k, grad, step, thresh, coef_old, beta_mom)
|
|
84
|
+
coef = coef_new
|
|
85
|
+
|
|
86
|
+
# Momentum state update
|
|
87
|
+
if not no_momentum:
|
|
88
|
+
t_k = t_new
|
|
89
|
+
|
|
90
|
+
# Convergence check (device-side, minimal sync)
|
|
91
|
+
if iteration < 20 or iteration % _sync_interval == 0:
|
|
92
|
+
coef_diff_dev = _abs_sum_dev(coef - coef_old)
|
|
93
|
+
if _to_float_scalar(coef_diff_dev) < tol:
|
|
94
|
+
break
|
|
95
|
+
|
|
96
|
+
coef_old = _copy_arr(coef)
|
|
97
|
+
|
|
98
|
+
return _to_numpy(coef), iteration + 1
|
|
99
|
+
|
|
100
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Squared error loss: (1/(2n)) * ||y - Xw||^2
|
|
3
|
+
|
|
4
|
+
Convention: loss = (1/(2n)) * sum(resid^2).
|
|
5
|
+
All penalties use alpha*n in the normal equations / CD updates,
|
|
6
|
+
matching the PenalizedGeneralizedLinearModel convention and sklearn.
|
|
7
|
+
|
|
8
|
+
sklearn compatibility mapping:
|
|
9
|
+
- Ridge: sklearn alpha = statgpu alpha * n (statgpu alpha = sklearn alpha / n)
|
|
10
|
+
- Lasso: statgpu alpha = sklearn alpha
|
|
11
|
+
- ElasticNet: statgpu alpha = sklearn alpha
|
|
12
|
+
|
|
13
|
+
Internal consistency: Ridge(alpha=a) == PGLM(alpha=a, penalty='l2')
|
|
14
|
+
for all alpha values (verified to machine precision).
|
|
15
|
+
|
|
16
|
+
Supports numpy / cupy / torch backends via _backend helpers.
|
|
17
|
+
"""
|
|
18
|
+
from statgpu.backends._array_ops import _max_eigval_power
|
|
19
|
+
from statgpu.glm_core._base import GLMLoss, register_glm_loss
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@register_glm_loss('squared_error')
|
|
23
|
+
class SquaredErrorLoss(GLMLoss):
|
|
24
|
+
name = "squared_error"
|
|
25
|
+
y_type = "continuous"
|
|
26
|
+
smooth_gradient = True
|
|
27
|
+
has_hessian = True
|
|
28
|
+
_is_quadratic = True
|
|
29
|
+
_supports_cholesky = True
|
|
30
|
+
|
|
31
|
+
# ── Per-sample formulas (single source of truth) ──────────────────
|
|
32
|
+
|
|
33
|
+
def per_sample_value(self, eta, y):
|
|
34
|
+
resid = y - eta
|
|
35
|
+
return 0.5 * resid * resid
|
|
36
|
+
|
|
37
|
+
def per_sample_gradient(self, eta, y):
|
|
38
|
+
return eta - y
|
|
39
|
+
|
|
40
|
+
# ── Hessian / Lipschitz (override for weighted support) ───────────
|
|
41
|
+
|
|
42
|
+
def hessian(self, X, y, coef, sample_weight=None):
|
|
43
|
+
if sample_weight is not None:
|
|
44
|
+
return X.T @ (X * sample_weight[:, None]) / sample_weight.sum()
|
|
45
|
+
return X.T @ X / X.shape[0]
|
|
46
|
+
|
|
47
|
+
def lipschitz(self, X, coef, y=None, sample_weight=None):
|
|
48
|
+
if sample_weight is not None:
|
|
49
|
+
sw = sample_weight[:, None] if hasattr(sample_weight, '__len__') else sample_weight
|
|
50
|
+
XtWX = X.T @ (X * sw)
|
|
51
|
+
return _max_eigval_power(XtWX) / sample_weight.sum()
|
|
52
|
+
XtX = X.T @ X
|
|
53
|
+
return _max_eigval_power(XtX) / X.shape[0]
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tweedie loss: negative Tweedie log-likelihood with log link.
|
|
3
|
+
|
|
4
|
+
For compound Poisson-Gamma (1 < p < 2) outcomes:
|
|
5
|
+
loss = (1/n) * sum(-y * mu^(1-p)/(1-p) + mu^(2-p)/(2-p))
|
|
6
|
+
where mu = exp(X @ coef), p is the Tweedie power parameter.
|
|
7
|
+
|
|
8
|
+
Supports numpy / cupy / torch backends via _array_ops helpers.
|
|
9
|
+
"""
|
|
10
|
+
from statgpu.backends._array_ops import _clip, _exp, _sum, _max_eigval_power
|
|
11
|
+
from statgpu.glm_core._base import GLMLoss, register_glm_loss
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@register_glm_loss('tweedie')
|
|
15
|
+
class TweedieLoss(GLMLoss):
|
|
16
|
+
name = "tweedie"
|
|
17
|
+
y_type = "nonnegative"
|
|
18
|
+
smooth_gradient = True
|
|
19
|
+
has_hessian = True
|
|
20
|
+
_lipschitz_uses_y = True
|
|
21
|
+
_lipschitz_safety = 5.0
|
|
22
|
+
_tweedie = True # Tweedie variance function requires large safety
|
|
23
|
+
|
|
24
|
+
# Clip z to [-50, 50] instead of [-500, 500] to prevent
|
|
25
|
+
# mu^(-0.5) explosion: mu >= exp(-50) ~ 1.9e-22 -> mu^(-0.5) <= 2.3e10.
|
|
26
|
+
_Z_CLIP = 50.0
|
|
27
|
+
|
|
28
|
+
_MU_LO = 1e-3
|
|
29
|
+
_MU_HI = 1e4
|
|
30
|
+
|
|
31
|
+
def __init__(self, power=1.5):
|
|
32
|
+
if not 1.0 < power < 2.0:
|
|
33
|
+
raise ValueError(f"Tweedie power must be in (1, 2), got {power}")
|
|
34
|
+
self.power = power
|
|
35
|
+
|
|
36
|
+
def _mu_from_eta(self, eta):
|
|
37
|
+
return _clip(_exp(_clip(eta, -self._Z_CLIP, self._Z_CLIP)), self._MU_LO, self._MU_HI)
|
|
38
|
+
|
|
39
|
+
# ── Per-sample formulas (single source of truth) ──────────────────
|
|
40
|
+
|
|
41
|
+
def per_sample_value(self, eta, y):
|
|
42
|
+
mu = self._mu_from_eta(eta)
|
|
43
|
+
p = self.power
|
|
44
|
+
return -y * mu ** (1.0 - p) / (1.0 - p) + mu ** (2.0 - p) / (2.0 - p)
|
|
45
|
+
|
|
46
|
+
def per_sample_gradient(self, eta, y):
|
|
47
|
+
mu = self._mu_from_eta(eta)
|
|
48
|
+
p = self.power
|
|
49
|
+
return mu ** (1.0 - p) * (mu - y)
|
|
50
|
+
|
|
51
|
+
def hessian(self, X, y, coef, sample_weight=None):
|
|
52
|
+
z = _clip(X @ coef, -self._Z_CLIP, self._Z_CLIP)
|
|
53
|
+
mu = _clip(_exp(z), self._MU_LO, self._MU_HI)
|
|
54
|
+
p = self.power
|
|
55
|
+
W = mu ** (2.0 - p)
|
|
56
|
+
if sample_weight is not None:
|
|
57
|
+
W = W * sample_weight
|
|
58
|
+
n_eff = float(sample_weight.sum()) if sample_weight is not None else X.shape[0]
|
|
59
|
+
return X.T @ (X * W[:, None]) / n_eff
|
|
60
|
+
|
|
61
|
+
def lipschitz(self, X, coef, y=None, sample_weight=None):
|
|
62
|
+
z = _clip(X @ coef, -self._Z_CLIP, self._Z_CLIP)
|
|
63
|
+
mu = _clip(_exp(z), self._MU_LO, self._MU_HI)
|
|
64
|
+
p = self.power
|
|
65
|
+
W = mu ** (2.0 - p)
|
|
66
|
+
if sample_weight is not None:
|
|
67
|
+
W = W * sample_weight
|
|
68
|
+
n_eff = float(sample_weight.sum()) if sample_weight is not None else X.shape[0]
|
|
69
|
+
XtWX = X.T @ (X * W[:, None])
|
|
70
|
+
L = _max_eigval_power(XtWX) / n_eff
|
|
71
|
+
return max(L, 1e-8)
|
|
72
|
+
|
|
73
|
+
def predict(self, X, coef):
|
|
74
|
+
return _exp(X @ coef)
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""Inference helper utilities shared across models."""
|
|
2
|
+
|
|
3
|
+
from ._distributions_backend import (
|
|
4
|
+
BetaDistributionBase,
|
|
5
|
+
BinomDistributionBase,
|
|
6
|
+
CauchyDistributionBase,
|
|
7
|
+
Chi2DistributionBase,
|
|
8
|
+
ExponDistributionBase,
|
|
9
|
+
FDistributionBase,
|
|
10
|
+
GammaDistributionBase,
|
|
11
|
+
LaplaceDistributionBase,
|
|
12
|
+
LogisticDistributionBase,
|
|
13
|
+
LognormDistributionBase,
|
|
14
|
+
NormDistributionBase,
|
|
15
|
+
PoissonDistributionBase,
|
|
16
|
+
ScipyFallbackDistribution,
|
|
17
|
+
TDistributionBase,
|
|
18
|
+
UniformDistributionBase,
|
|
19
|
+
WeibullMinDistributionBase,
|
|
20
|
+
# Backward-compatible aliases
|
|
21
|
+
BetaDistributionBase as BetaDistributionGPU,
|
|
22
|
+
BinomDistributionBase as BinomDistributionGPU,
|
|
23
|
+
CauchyDistributionBase as CauchyDistributionGPU,
|
|
24
|
+
Chi2DistributionBase as Chi2DistributionGPU,
|
|
25
|
+
ExponDistributionBase as ExponDistributionGPU,
|
|
26
|
+
FDistributionBase as FDistributionGPU,
|
|
27
|
+
GammaDistributionBase as GammaDistributionGPU,
|
|
28
|
+
LaplaceDistributionBase as LaplaceDistributionGPU,
|
|
29
|
+
LogisticDistributionBase as LogisticDistributionGPU,
|
|
30
|
+
LognormDistributionBase as LognormDistributionGPU,
|
|
31
|
+
NormDistributionBase as NormDistributionGPU,
|
|
32
|
+
PoissonDistributionBase as PoissonDistributionGPU,
|
|
33
|
+
TDistributionBase as TDistributionGPU,
|
|
34
|
+
UniformDistributionBase as UniformDistributionGPU,
|
|
35
|
+
WeibullMinDistributionBase as WeibullMinDistributionGPU,
|
|
36
|
+
beta,
|
|
37
|
+
binom,
|
|
38
|
+
cauchy,
|
|
39
|
+
chi2,
|
|
40
|
+
expon,
|
|
41
|
+
f,
|
|
42
|
+
gamma,
|
|
43
|
+
get_distribution,
|
|
44
|
+
get_distribution_gpu,
|
|
45
|
+
laplace,
|
|
46
|
+
list_available_distributions,
|
|
47
|
+
list_available_distributions_gpu,
|
|
48
|
+
logistic,
|
|
49
|
+
lognorm,
|
|
50
|
+
norm,
|
|
51
|
+
poisson,
|
|
52
|
+
t,
|
|
53
|
+
uniform,
|
|
54
|
+
weibull_min,
|
|
55
|
+
)
|
|
56
|
+
from statgpu.linear_model.legacy._distributions_legacy_gpu import (
|
|
57
|
+
dbeta_gpu,
|
|
58
|
+
dbinom_gpu,
|
|
59
|
+
dchisq_gpu,
|
|
60
|
+
df_gpu,
|
|
61
|
+
dgamma_gpu,
|
|
62
|
+
dnorm_gpu,
|
|
63
|
+
dpois_gpu,
|
|
64
|
+
dt_gpu,
|
|
65
|
+
norm_cdf_gpu,
|
|
66
|
+
norm_isf_gpu,
|
|
67
|
+
norm_ppf_gpu,
|
|
68
|
+
norm_sf_gpu,
|
|
69
|
+
norm_two_sided_critical_value_gpu,
|
|
70
|
+
norm_two_sided_pvalue_gpu,
|
|
71
|
+
pbeta_gpu,
|
|
72
|
+
pbinom_gpu,
|
|
73
|
+
pchisq_gpu,
|
|
74
|
+
pf_gpu,
|
|
75
|
+
pgamma_gpu,
|
|
76
|
+
pnorm_gpu,
|
|
77
|
+
ppois_gpu,
|
|
78
|
+
pt_gpu,
|
|
79
|
+
qbeta_gpu,
|
|
80
|
+
qbinom_gpu,
|
|
81
|
+
qchisq_gpu,
|
|
82
|
+
qf_gpu,
|
|
83
|
+
qgamma_gpu,
|
|
84
|
+
qnorm_gpu,
|
|
85
|
+
qpois_gpu,
|
|
86
|
+
qt_gpu,
|
|
87
|
+
rbeta_gpu,
|
|
88
|
+
rbinom_gpu,
|
|
89
|
+
rchisq_gpu,
|
|
90
|
+
rf_gpu,
|
|
91
|
+
rgamma_gpu,
|
|
92
|
+
rnorm_gpu,
|
|
93
|
+
rpois_gpu,
|
|
94
|
+
rt_gpu,
|
|
95
|
+
t_cdf_gpu,
|
|
96
|
+
t_ppf_gpu,
|
|
97
|
+
t_sf_gpu,
|
|
98
|
+
t_two_sided_critical_value_gpu,
|
|
99
|
+
t_two_sided_pvalue_gpu,
|
|
100
|
+
)
|
|
101
|
+
from ._multiple_testing import adjust_pvalues, combine_pvalues, multipletests
|
|
102
|
+
from ._results import (
|
|
103
|
+
BaseInferenceResult,
|
|
104
|
+
DebiasedInferenceResult,
|
|
105
|
+
GaussianInferenceResult,
|
|
106
|
+
OracleActiveSetInferenceResult,
|
|
107
|
+
ParameterInferenceResult,
|
|
108
|
+
ResamplingInferenceResult,
|
|
109
|
+
)
|
|
110
|
+
from ._resampling import (
|
|
111
|
+
BootstrapResult,
|
|
112
|
+
PermutationTestResult,
|
|
113
|
+
bootstrap_statistic,
|
|
114
|
+
permutation_test,
|
|
115
|
+
)
|
|
116
|
+
try:
|
|
117
|
+
from statgpu.nonparametric.kernel_smoothing._kde import (
|
|
118
|
+
KDE,
|
|
119
|
+
KDEBootstrapResult,
|
|
120
|
+
fit_kde,
|
|
121
|
+
kde_pdf,
|
|
122
|
+
kde_bootstrap_confidence_interval,
|
|
123
|
+
)
|
|
124
|
+
except ImportError:
|
|
125
|
+
KDE = None
|
|
126
|
+
KDEBootstrapResult = None
|
|
127
|
+
fit_kde = None
|
|
128
|
+
kde_pdf = None
|
|
129
|
+
kde_bootstrap_confidence_interval = None
|
|
130
|
+
|
|
131
|
+
__all__ = [
|
|
132
|
+
"adjust_pvalues",
|
|
133
|
+
"combine_pvalues",
|
|
134
|
+
"multipletests",
|
|
135
|
+
# Inference results
|
|
136
|
+
"BaseInferenceResult",
|
|
137
|
+
"ParameterInferenceResult",
|
|
138
|
+
"GaussianInferenceResult",
|
|
139
|
+
"DebiasedInferenceResult",
|
|
140
|
+
"OracleActiveSetInferenceResult",
|
|
141
|
+
"ResamplingInferenceResult",
|
|
142
|
+
# Distribution base classes
|
|
143
|
+
"NormDistributionBase",
|
|
144
|
+
"TDistributionBase",
|
|
145
|
+
"UniformDistributionBase",
|
|
146
|
+
"ExponDistributionBase",
|
|
147
|
+
"CauchyDistributionBase",
|
|
148
|
+
"LaplaceDistributionBase",
|
|
149
|
+
"LogisticDistributionBase",
|
|
150
|
+
"Chi2DistributionBase",
|
|
151
|
+
"GammaDistributionBase",
|
|
152
|
+
"BetaDistributionBase",
|
|
153
|
+
"FDistributionBase",
|
|
154
|
+
"WeibullMinDistributionBase",
|
|
155
|
+
"LognormDistributionBase",
|
|
156
|
+
"PoissonDistributionBase",
|
|
157
|
+
"BinomDistributionBase",
|
|
158
|
+
"ScipyFallbackDistribution",
|
|
159
|
+
# Factory
|
|
160
|
+
"get_distribution",
|
|
161
|
+
"get_distribution_gpu",
|
|
162
|
+
"list_available_distributions",
|
|
163
|
+
"list_available_distributions_gpu",
|
|
164
|
+
# Module-level proxies
|
|
165
|
+
"norm", "t", "uniform", "expon", "cauchy", "laplace",
|
|
166
|
+
"logistic", "chi2", "gamma", "beta", "f",
|
|
167
|
+
"weibull_min", "lognorm", "poisson", "binom",
|
|
168
|
+
# Legacy GPU names (backward compat)
|
|
169
|
+
"NormDistributionGPU",
|
|
170
|
+
"TDistributionGPU",
|
|
171
|
+
"UniformDistributionGPU",
|
|
172
|
+
"ExponDistributionGPU",
|
|
173
|
+
"CauchyDistributionGPU",
|
|
174
|
+
"LaplaceDistributionGPU",
|
|
175
|
+
"LogisticDistributionGPU",
|
|
176
|
+
"Chi2DistributionGPU",
|
|
177
|
+
"GammaDistributionGPU",
|
|
178
|
+
"BetaDistributionGPU",
|
|
179
|
+
"FDistributionGPU",
|
|
180
|
+
"WeibullMinDistributionGPU",
|
|
181
|
+
"LognormDistributionGPU",
|
|
182
|
+
"PoissonDistributionGPU",
|
|
183
|
+
"BinomDistributionGPU",
|
|
184
|
+
# Legacy function names
|
|
185
|
+
"norm_cdf_gpu",
|
|
186
|
+
"norm_isf_gpu",
|
|
187
|
+
"norm_ppf_gpu",
|
|
188
|
+
"norm_sf_gpu",
|
|
189
|
+
"norm_two_sided_critical_value_gpu",
|
|
190
|
+
"norm_two_sided_pvalue_gpu",
|
|
191
|
+
"t_cdf_gpu",
|
|
192
|
+
"t_ppf_gpu",
|
|
193
|
+
"t_sf_gpu",
|
|
194
|
+
"t_two_sided_critical_value_gpu",
|
|
195
|
+
"t_two_sided_pvalue_gpu",
|
|
196
|
+
"dnorm_gpu",
|
|
197
|
+
"pnorm_gpu",
|
|
198
|
+
"qnorm_gpu",
|
|
199
|
+
"rnorm_gpu",
|
|
200
|
+
"dt_gpu",
|
|
201
|
+
"pt_gpu",
|
|
202
|
+
"qt_gpu",
|
|
203
|
+
"rt_gpu",
|
|
204
|
+
"dchisq_gpu",
|
|
205
|
+
"pchisq_gpu",
|
|
206
|
+
"qchisq_gpu",
|
|
207
|
+
"rchisq_gpu",
|
|
208
|
+
"dgamma_gpu",
|
|
209
|
+
"pgamma_gpu",
|
|
210
|
+
"qgamma_gpu",
|
|
211
|
+
"rgamma_gpu",
|
|
212
|
+
"dbeta_gpu",
|
|
213
|
+
"pbeta_gpu",
|
|
214
|
+
"qbeta_gpu",
|
|
215
|
+
"rbeta_gpu",
|
|
216
|
+
"df_gpu",
|
|
217
|
+
"pf_gpu",
|
|
218
|
+
"qf_gpu",
|
|
219
|
+
"rf_gpu",
|
|
220
|
+
"dpois_gpu",
|
|
221
|
+
"ppois_gpu",
|
|
222
|
+
"qpois_gpu",
|
|
223
|
+
"rpois_gpu",
|
|
224
|
+
"dbinom_gpu",
|
|
225
|
+
"pbinom_gpu",
|
|
226
|
+
"qbinom_gpu",
|
|
227
|
+
"rbinom_gpu",
|
|
228
|
+
# Resampling
|
|
229
|
+
"BootstrapResult",
|
|
230
|
+
"PermutationTestResult",
|
|
231
|
+
"bootstrap_statistic",
|
|
232
|
+
"permutation_test",
|
|
233
|
+
# KDE
|
|
234
|
+
"KDE",
|
|
235
|
+
"KDEBootstrapResult",
|
|
236
|
+
"fit_kde",
|
|
237
|
+
"kde_pdf",
|
|
238
|
+
"kde_bootstrap_confidence_interval",
|
|
239
|
+
]
|