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,246 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pairwise kernel functions with backend-agnostic (xp) interface.
|
|
3
|
+
|
|
4
|
+
All functions accept an ``xp`` argument that should be a NumPy-compatible
|
|
5
|
+
array module (numpy, cupy, or torch). When *xp* is ``None`` the functions
|
|
6
|
+
fall back to ``numpy``.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
from statgpu.backends import xp_maximum
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
# Individual kernel functions
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
def rbf_kernel(X, Y=None, gamma=None, xp=None):
|
|
23
|
+
r"""Radial basis function (Gaussian) kernel.
|
|
24
|
+
|
|
25
|
+
.. math::
|
|
26
|
+
K(x, y) = \exp(-\gamma \|x - y\|^2)
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
X : array-like of shape (n_samples_X, n_features)
|
|
31
|
+
Y : array-like of shape (n_samples_Y, n_features), optional
|
|
32
|
+
gamma : float, optional
|
|
33
|
+
Kernel coefficient. Defaults to ``1 / n_features``.
|
|
34
|
+
xp : module, optional
|
|
35
|
+
Array module (numpy / cupy / torch).
|
|
36
|
+
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
K : array of shape (n_samples_X, n_samples_Y)
|
|
40
|
+
"""
|
|
41
|
+
if xp is None:
|
|
42
|
+
xp = np
|
|
43
|
+
if Y is None:
|
|
44
|
+
Y = X
|
|
45
|
+
if gamma is None:
|
|
46
|
+
gamma = 1.0 / X.shape[1]
|
|
47
|
+
|
|
48
|
+
# ||x - y||^2 = ||x||^2 + ||y||^2 - 2 * x @ y.T
|
|
49
|
+
XX = xp.sum(X ** 2, axis=1)[:, None]
|
|
50
|
+
YY = xp.sum(Y ** 2, axis=1)[None, :]
|
|
51
|
+
dist = XX + YY - 2.0 * (X @ Y.T)
|
|
52
|
+
# Clamp to avoid negative values from numerical noise
|
|
53
|
+
dist = xp_maximum(dist, 0.0, xp)
|
|
54
|
+
return xp.exp(-gamma * dist)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def polynomial_kernel(X, Y=None, degree=3, gamma=None, coef0=1, xp=None):
|
|
58
|
+
r"""Polynomial kernel.
|
|
59
|
+
|
|
60
|
+
.. math::
|
|
61
|
+
K(x, y) = (\gamma \, x^\top y + c_0)^d
|
|
62
|
+
|
|
63
|
+
Parameters
|
|
64
|
+
----------
|
|
65
|
+
X : array-like of shape (n_samples_X, n_features)
|
|
66
|
+
Y : array-like of shape (n_samples_Y, n_features), optional
|
|
67
|
+
degree : int, default=3
|
|
68
|
+
gamma : float, optional
|
|
69
|
+
Defaults to ``1 / n_features``.
|
|
70
|
+
coef0 : float, default=1
|
|
71
|
+
xp : module, optional
|
|
72
|
+
|
|
73
|
+
Returns
|
|
74
|
+
-------
|
|
75
|
+
K : array of shape (n_samples_X, n_samples_Y)
|
|
76
|
+
"""
|
|
77
|
+
if xp is None:
|
|
78
|
+
xp = np
|
|
79
|
+
if Y is None:
|
|
80
|
+
Y = X
|
|
81
|
+
if gamma is None:
|
|
82
|
+
gamma = 1.0 / X.shape[1]
|
|
83
|
+
|
|
84
|
+
return (gamma * (X @ Y.T) + coef0) ** degree
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def linear_kernel(X, Y=None, xp=None):
|
|
88
|
+
r"""Linear kernel.
|
|
89
|
+
|
|
90
|
+
.. math::
|
|
91
|
+
K(x, y) = x^\top y
|
|
92
|
+
|
|
93
|
+
Parameters
|
|
94
|
+
----------
|
|
95
|
+
X : array-like of shape (n_samples_X, n_features)
|
|
96
|
+
Y : array-like of shape (n_samples_Y, n_features), optional
|
|
97
|
+
xp : module, optional
|
|
98
|
+
|
|
99
|
+
Returns
|
|
100
|
+
-------
|
|
101
|
+
K : array of shape (n_samples_X, n_samples_Y)
|
|
102
|
+
"""
|
|
103
|
+
if xp is None:
|
|
104
|
+
xp = np
|
|
105
|
+
if Y is None:
|
|
106
|
+
Y = X
|
|
107
|
+
return X @ Y.T
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def laplacian_kernel(X, Y=None, gamma=None, xp=None):
|
|
111
|
+
r"""Laplacian kernel.
|
|
112
|
+
|
|
113
|
+
.. math::
|
|
114
|
+
K(x, y) = \exp(-\gamma \|x - y\|_1)
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
X : array-like of shape (n_samples_X, n_features)
|
|
119
|
+
Y : array-like of shape (n_samples_Y, n_features), optional
|
|
120
|
+
gamma : float, optional
|
|
121
|
+
Defaults to ``1 / n_features``.
|
|
122
|
+
xp : module, optional
|
|
123
|
+
|
|
124
|
+
Returns
|
|
125
|
+
-------
|
|
126
|
+
K : array of shape (n_samples_X, n_samples_Y)
|
|
127
|
+
"""
|
|
128
|
+
if xp is None:
|
|
129
|
+
xp = np
|
|
130
|
+
if Y is None:
|
|
131
|
+
Y = X
|
|
132
|
+
if gamma is None:
|
|
133
|
+
gamma = 1.0 / X.shape[1]
|
|
134
|
+
|
|
135
|
+
# L1 distance using broadcasting
|
|
136
|
+
dist = xp.sum(xp.abs(X[:, None, :] - Y[None, :, :]), axis=2)
|
|
137
|
+
return xp.exp(-gamma * dist)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def sigmoid_kernel(X, Y=None, gamma=None, coef0=1, xp=None):
|
|
141
|
+
r"""Sigmoid (hyperbolic tangent) kernel.
|
|
142
|
+
|
|
143
|
+
.. math::
|
|
144
|
+
K(x, y) = \tanh(\gamma \, x^\top y + c_0)
|
|
145
|
+
|
|
146
|
+
Parameters
|
|
147
|
+
----------
|
|
148
|
+
X : array-like of shape (n_samples_X, n_features)
|
|
149
|
+
Y : array-like of shape (n_samples_Y, n_features), optional
|
|
150
|
+
gamma : float, optional
|
|
151
|
+
Defaults to ``1 / n_features``.
|
|
152
|
+
coef0 : float, default=1
|
|
153
|
+
xp : module, optional
|
|
154
|
+
|
|
155
|
+
Returns
|
|
156
|
+
-------
|
|
157
|
+
K : array of shape (n_samples_X, n_samples_Y)
|
|
158
|
+
"""
|
|
159
|
+
if xp is None:
|
|
160
|
+
xp = np
|
|
161
|
+
if Y is None:
|
|
162
|
+
Y = X
|
|
163
|
+
if gamma is None:
|
|
164
|
+
gamma = 1.0 / X.shape[1]
|
|
165
|
+
|
|
166
|
+
return xp.tanh(gamma * (X @ Y.T) + coef0)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def cosine_kernel(X, Y=None, xp=None):
|
|
170
|
+
r"""Cosine similarity kernel.
|
|
171
|
+
|
|
172
|
+
.. math::
|
|
173
|
+
K(x, y) = \frac{x^\top y}{\|x\| \, \|y\|}
|
|
174
|
+
|
|
175
|
+
Parameters
|
|
176
|
+
----------
|
|
177
|
+
X : array-like of shape (n_samples_X, n_features)
|
|
178
|
+
Y : array-like of shape (n_samples_Y, n_features), optional
|
|
179
|
+
xp : module, optional
|
|
180
|
+
|
|
181
|
+
Returns
|
|
182
|
+
-------
|
|
183
|
+
K : array of shape (n_samples_X, n_samples_Y)
|
|
184
|
+
"""
|
|
185
|
+
if xp is None:
|
|
186
|
+
xp = np
|
|
187
|
+
if Y is None:
|
|
188
|
+
Y = X
|
|
189
|
+
|
|
190
|
+
X_norm = xp.sqrt(xp.sum(X ** 2, axis=1))[:, None]
|
|
191
|
+
Y_norm = xp.sqrt(xp.sum(Y ** 2, axis=1))[None, :]
|
|
192
|
+
return (X @ Y.T) / (X_norm * Y_norm + 1e-10)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# ---------------------------------------------------------------------------
|
|
196
|
+
# Registry
|
|
197
|
+
# ---------------------------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
KERNEL_REGISTRY = {
|
|
200
|
+
"rbf": rbf_kernel,
|
|
201
|
+
"gaussian": rbf_kernel,
|
|
202
|
+
"linear": linear_kernel,
|
|
203
|
+
"polynomial": polynomial_kernel,
|
|
204
|
+
"poly": polynomial_kernel,
|
|
205
|
+
"laplacian": laplacian_kernel,
|
|
206
|
+
"sigmoid": sigmoid_kernel,
|
|
207
|
+
"cosine": cosine_kernel,
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def pairwise_kernels(X, Y=None, metric="rbf", xp=None, **params):
|
|
212
|
+
"""Compute the kernel between arrays X and Y using the given metric.
|
|
213
|
+
|
|
214
|
+
Parameters
|
|
215
|
+
----------
|
|
216
|
+
X : array-like of shape (n_samples_X, n_features)
|
|
217
|
+
Y : array-like of shape (n_samples_Y, n_features), optional
|
|
218
|
+
metric : str or callable, default='rbf'
|
|
219
|
+
Kernel metric name or a callable.
|
|
220
|
+
xp : module, optional
|
|
221
|
+
Array module.
|
|
222
|
+
**params
|
|
223
|
+
Additional keyword arguments forwarded to the kernel function.
|
|
224
|
+
|
|
225
|
+
Returns
|
|
226
|
+
-------
|
|
227
|
+
K : array of shape (n_samples_X, n_samples_Y)
|
|
228
|
+
"""
|
|
229
|
+
if callable(metric):
|
|
230
|
+
# Try calling with xp parameter first; fall back without it
|
|
231
|
+
# for user-defined callables that don't accept xp.
|
|
232
|
+
# Pass Y as-is (including None) so callables can distinguish
|
|
233
|
+
# self-kernel (Y=None) from cross-kernel.
|
|
234
|
+
try:
|
|
235
|
+
return metric(X, Y, xp=xp, **params)
|
|
236
|
+
except TypeError:
|
|
237
|
+
return metric(X, Y, **params)
|
|
238
|
+
|
|
239
|
+
key = str(metric).strip().lower()
|
|
240
|
+
func = KERNEL_REGISTRY.get(key)
|
|
241
|
+
if func is None:
|
|
242
|
+
raise ValueError(
|
|
243
|
+
f"Unknown kernel metric '{metric}'. "
|
|
244
|
+
f"Available: {sorted(KERNEL_REGISTRY.keys())}"
|
|
245
|
+
)
|
|
246
|
+
return func(X, Y, xp=xp, **params)
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Kernel Ridge Regression with GPU acceleration.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Optional, Union
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from statgpu._base import BaseEstimator
|
|
12
|
+
from statgpu._config import Device
|
|
13
|
+
from statgpu.backends import _LINALG_ERRORS, _to_numpy, _torch_dev, xp_eye, xp_astype
|
|
14
|
+
|
|
15
|
+
from statgpu.nonparametric.kernel_methods._kernels import pairwise_kernels
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class KernelRidge(BaseEstimator):
|
|
19
|
+
r"""Kernel Ridge Regression.
|
|
20
|
+
|
|
21
|
+
Solves the dual problem:
|
|
22
|
+
|
|
23
|
+
.. math::
|
|
24
|
+
\boldsymbol{\alpha} = (K + \alpha I)^{-1} \mathbf{y}
|
|
25
|
+
|
|
26
|
+
where :math:`K` is the kernel matrix of the training data. Predictions
|
|
27
|
+
are computed as :math:`\hat{y} = K_{\text{test}} \boldsymbol{\alpha}`.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
alpha : float, default=1.0
|
|
32
|
+
Regularization strength.
|
|
33
|
+
kernel : str or callable, default='rbf'
|
|
34
|
+
Kernel metric name (``'rbf'``, ``'linear'``, ``'polynomial'``,
|
|
35
|
+
``'laplacian'``, ``'sigmoid'``, ``'cosine'``) or a callable.
|
|
36
|
+
gamma : float, optional
|
|
37
|
+
Kernel coefficient for rbf, polynomial, laplacian, sigmoid.
|
|
38
|
+
Defaults to ``1 / n_features``.
|
|
39
|
+
degree : int, default=3
|
|
40
|
+
Degree for the polynomial kernel.
|
|
41
|
+
coef0 : float, default=1
|
|
42
|
+
Independent term for polynomial and sigmoid kernels.
|
|
43
|
+
kernel_params : dict, optional
|
|
44
|
+
Additional parameters passed to the kernel function.
|
|
45
|
+
device : str or Device, default='auto'
|
|
46
|
+
Computation device.
|
|
47
|
+
n_jobs : int, optional
|
|
48
|
+
Not used; kept for API compatibility.
|
|
49
|
+
|
|
50
|
+
Attributes
|
|
51
|
+
----------
|
|
52
|
+
dual_coef_ : ndarray of shape (n_samples,) or (n_samples, n_targets)
|
|
53
|
+
Dual coefficients in the kernel space.
|
|
54
|
+
X_fit_ : ndarray of shape (n_samples, n_features)
|
|
55
|
+
Training data stored for prediction.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
alpha: float = 1.0,
|
|
61
|
+
kernel: Union[str, callable] = "rbf",
|
|
62
|
+
gamma: Optional[float] = None,
|
|
63
|
+
degree: int = 3,
|
|
64
|
+
coef0: float = 1,
|
|
65
|
+
kernel_params: Optional[dict] = None,
|
|
66
|
+
device: Union[str, Device] = Device.AUTO,
|
|
67
|
+
n_jobs: Optional[int] = None,
|
|
68
|
+
):
|
|
69
|
+
super().__init__(device=device, n_jobs=n_jobs)
|
|
70
|
+
self.alpha = alpha
|
|
71
|
+
self.kernel = kernel
|
|
72
|
+
self.gamma = gamma
|
|
73
|
+
self.degree = degree
|
|
74
|
+
self.coef0 = coef0
|
|
75
|
+
self.kernel_params = kernel_params
|
|
76
|
+
|
|
77
|
+
# Fitted attributes
|
|
78
|
+
self.dual_coef_ = None
|
|
79
|
+
self.X_fit_ = None
|
|
80
|
+
self._xp = None
|
|
81
|
+
self._backend = None
|
|
82
|
+
|
|
83
|
+
def _get_kernel_params(self):
|
|
84
|
+
"""Collect kernel-specific parameters."""
|
|
85
|
+
params = {}
|
|
86
|
+
if self.kernel_params is not None:
|
|
87
|
+
params.update(self.kernel_params)
|
|
88
|
+
# Only pass gamma/degree/coef0 for kernels that use them
|
|
89
|
+
k = str(self.kernel).strip().lower() if isinstance(self.kernel, str) else ""
|
|
90
|
+
if k in ("rbf", "gaussian", "polynomial", "poly", "laplacian", "sigmoid"):
|
|
91
|
+
if self.gamma is not None:
|
|
92
|
+
params["gamma"] = self.gamma
|
|
93
|
+
if k in ("polynomial", "poly", "sigmoid"):
|
|
94
|
+
if self.degree != 3 and k in ("polynomial", "poly"):
|
|
95
|
+
params["degree"] = self.degree
|
|
96
|
+
if self.coef0 != 1:
|
|
97
|
+
params["coef0"] = self.coef0
|
|
98
|
+
return params
|
|
99
|
+
|
|
100
|
+
def fit(self, X, y, sample_weight=None):
|
|
101
|
+
"""Fit Kernel Ridge Regression model.
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
X : array-like of shape (n_samples, n_features)
|
|
106
|
+
Training data.
|
|
107
|
+
y : array-like of shape (n_samples,) or (n_samples, n_targets)
|
|
108
|
+
Target values.
|
|
109
|
+
sample_weight : ignored
|
|
110
|
+
Not used; kept for API compatibility.
|
|
111
|
+
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
self
|
|
115
|
+
"""
|
|
116
|
+
# Resolve backend and convert arrays
|
|
117
|
+
self._backend = self._get_backend()
|
|
118
|
+
xp = self._backend.xp
|
|
119
|
+
self._xp = xp
|
|
120
|
+
|
|
121
|
+
X_arr = self._to_array(X)
|
|
122
|
+
y_arr = xp_astype(self._to_array(y), xp.float64, xp)
|
|
123
|
+
if y_arr.ndim == 1:
|
|
124
|
+
y_arr = y_arr.reshape(-1, 1)
|
|
125
|
+
|
|
126
|
+
n_samples = X_arr.shape[0]
|
|
127
|
+
alpha = float(self.alpha)
|
|
128
|
+
|
|
129
|
+
# Compute kernel matrix
|
|
130
|
+
kernel_params = self._get_kernel_params()
|
|
131
|
+
K = pairwise_kernels(X_arr, X_arr, metric=self.kernel, xp=xp, **kernel_params)
|
|
132
|
+
|
|
133
|
+
# Regularize: K + alpha * I
|
|
134
|
+
K_reg = K + alpha * xp_eye(n_samples, K.dtype, xp, K)
|
|
135
|
+
|
|
136
|
+
# Solve (K + alpha I) * dual_coef = y with jitter fallback
|
|
137
|
+
try:
|
|
138
|
+
self.dual_coef_ = xp.linalg.solve(K_reg, y_arr)
|
|
139
|
+
except _LINALG_ERRORS:
|
|
140
|
+
# Matrix may be ill-conditioned; add jitter and retry
|
|
141
|
+
jitter = float(xp.max(xp.abs(xp.diag(K)))) * 1e-10
|
|
142
|
+
for _ in range(6):
|
|
143
|
+
K_reg = K_reg + jitter * xp_eye(n_samples, K.dtype, xp, K)
|
|
144
|
+
try:
|
|
145
|
+
self.dual_coef_ = xp.linalg.solve(K_reg, y_arr)
|
|
146
|
+
break
|
|
147
|
+
except _LINALG_ERRORS:
|
|
148
|
+
jitter *= 10
|
|
149
|
+
else:
|
|
150
|
+
raise ValueError(
|
|
151
|
+
"KernelRidge: regularized kernel matrix is singular "
|
|
152
|
+
"even after jitter escalation. Try increasing alpha."
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
self.X_fit_ = X_arr
|
|
156
|
+
self._fitted = True
|
|
157
|
+
return self
|
|
158
|
+
|
|
159
|
+
def predict(self, X):
|
|
160
|
+
"""Predict using the kernel ridge model.
|
|
161
|
+
|
|
162
|
+
Parameters
|
|
163
|
+
----------
|
|
164
|
+
X : array-like of shape (n_samples_test, n_features)
|
|
165
|
+
|
|
166
|
+
Returns
|
|
167
|
+
-------
|
|
168
|
+
y_pred : ndarray of shape (n_samples_test,) or (n_samples_test, n_targets)
|
|
169
|
+
"""
|
|
170
|
+
self._check_is_fitted()
|
|
171
|
+
xp = self._xp
|
|
172
|
+
|
|
173
|
+
X_arr = self._to_array(X)
|
|
174
|
+
kernel_params = self._get_kernel_params()
|
|
175
|
+
K_test = pairwise_kernels(X_arr, self.X_fit_, metric=self.kernel, xp=xp, **kernel_params)
|
|
176
|
+
|
|
177
|
+
y_pred = K_test @ self.dual_coef_
|
|
178
|
+
# Flatten if single target
|
|
179
|
+
if y_pred.ndim == 2 and y_pred.shape[1] == 1:
|
|
180
|
+
y_pred = y_pred.ravel()
|
|
181
|
+
return y_pred
|
|
182
|
+
|
|
183
|
+
def score(self, X, y):
|
|
184
|
+
"""Return the coefficient of determination R^2.
|
|
185
|
+
|
|
186
|
+
Parameters
|
|
187
|
+
----------
|
|
188
|
+
X : array-like of shape (n_samples, n_features)
|
|
189
|
+
y : array-like of shape (n_samples,) or (n_samples, n_targets)
|
|
190
|
+
|
|
191
|
+
Returns
|
|
192
|
+
-------
|
|
193
|
+
score : float
|
|
194
|
+
R^2 score.
|
|
195
|
+
"""
|
|
196
|
+
self._check_is_fitted()
|
|
197
|
+
xp = self._xp
|
|
198
|
+
|
|
199
|
+
y_pred = self.predict(X)
|
|
200
|
+
y_arr = xp_astype(self._to_array(y), xp.float64, xp)
|
|
201
|
+
# Ensure 1D to avoid broadcasting issues
|
|
202
|
+
y_arr = y_arr.ravel()
|
|
203
|
+
y_pred = y_pred.ravel()
|
|
204
|
+
|
|
205
|
+
ss_res = xp.sum((y_arr - y_pred) ** 2)
|
|
206
|
+
ss_tot = xp.sum((y_arr - xp.mean(y_arr)) ** 2)
|
|
207
|
+
|
|
208
|
+
ss_res_val = float(ss_res.item()) if hasattr(ss_res, "item") else float(ss_res)
|
|
209
|
+
ss_tot_val = float(ss_tot.item()) if hasattr(ss_tot, "item") else float(ss_tot)
|
|
210
|
+
|
|
211
|
+
if ss_tot_val == 0.0:
|
|
212
|
+
return 0.0
|
|
213
|
+
return 1.0 - ss_res_val / ss_tot_val
|
|
214
|
+
|
|
215
|
+
def get_params(self, deep=True):
|
|
216
|
+
"""Get parameters for this estimator."""
|
|
217
|
+
params = super().get_params(deep=deep)
|
|
218
|
+
params.update({
|
|
219
|
+
"alpha": self.alpha,
|
|
220
|
+
"kernel": self.kernel,
|
|
221
|
+
"gamma": self.gamma,
|
|
222
|
+
"degree": self.degree,
|
|
223
|
+
"coef0": self.coef0,
|
|
224
|
+
"kernel_params": self.kernel_params,
|
|
225
|
+
})
|
|
226
|
+
return params
|
|
227
|
+
|
|
228
|
+
def set_params(self, **params):
|
|
229
|
+
"""Set parameters for this estimator."""
|
|
230
|
+
super().set_params(**params)
|
|
231
|
+
for key in ("alpha", "kernel", "gamma", "degree", "coef0", "kernel_params"):
|
|
232
|
+
if key in params:
|
|
233
|
+
setattr(self, key, params[key])
|
|
234
|
+
return self
|