@@ -54,15 +54,13 @@ def __init__( # noqa: PLR0913
54
54
primal_feature_map : KernelApproximatingFeatureMap | None = None ,
55
55
dual_feature_map : AffineSeparator | None = None ,
56
56
dual : bool | None = None ,
57
- max_epochs : int = 1 ,
58
57
refit : bool = False ,
59
58
random_state : int | np .random .RandomState | None = 42 ,
60
59
estimator_type : Literal ["classifier" , "regressor" ] | None = None ,
61
60
) -> None :
62
61
self .primal_feature_map = primal_feature_map
63
62
self .dual_feature_map = dual_feature_map
64
63
self .dual = dual
65
- self .max_epochs = max_epochs
66
64
self .refit = refit
67
65
self .random_state = random_state
68
66
self .estimator_type = estimator_type
@@ -140,9 +138,7 @@ def _optimize_β̂_γ(
140
138
rγ = 1 / (self .γs_ [np .newaxis , :] + λ [:, np .newaxis ])
141
139
with np .errstate (divide = "ignore" , invalid = "ignore" ):
142
140
loo_residuals = (φβ̂ @ rγ - y [:, np .newaxis ]) / (1 - h @ rγ )
143
- loo_residuals = loo_residuals * self .y_scale_
144
- y_true = y * self .y_scale_ + self .y_shift_
145
- ŷ_loo = loo_residuals + y_true [:, np .newaxis ]
141
+ ŷ_loo = y [:, np .newaxis ] + loo_residuals
146
142
# In the case of binary classification, clip overly positive and overly negative
147
143
# predictions' residuals to 0 when the labels are positive and negative, respectively.
148
144
if self ._estimator_type == "classifier" :
@@ -163,14 +159,14 @@ def _optimize_β̂_γ(
163
159
self .loo_leverage_ = h @ rγ [:, optimum ]
164
160
self .loo_error_ = self .loo_errors_γs_ [optimum ]
165
161
if self ._estimator_type == "classifier" :
166
- self .loo_score_ = accuracy_score (y_true , np .sign (ŷ_loo [:, optimum ]), sample_weight = s )
162
+ self .loo_score_ = accuracy_score (y , np .sign (ŷ_loo [:, optimum ]), sample_weight = s )
167
163
elif self ._estimator_type == "regressor" :
168
- self .loo_score_ = r2_score (y_true , ŷ_loo [:, optimum ], sample_weight = s )
164
+ self .loo_score_ = r2_score (y , ŷ_loo [:, optimum ], sample_weight = s )
169
165
β̂ , γ = β̂ @ rγ [:, optimum ], self .γs_ [optimum ]
170
166
# Resolve the linear system for better accuracy.
171
167
if self .refit :
172
168
β̂ = np .linalg .solve (γ * C + A , φSTSy )
173
- self .residuals_ = ( np .real (φ @ β̂ ) - y ) * self . y_scale_
169
+ self .residuals_ = np .real (φ @ β̂ ) - y
174
170
if self ._estimator_type == "classifier" :
175
171
self .residuals_ [(y > 0 ) & (self .residuals_ > 0 )] = 0
176
172
self .residuals_ [(y < 0 ) & (self .residuals_ < 0 )] = 0
@@ -273,9 +269,7 @@ def _optimize_α̂_γ(
273
269
np .fill_diagonal (F_loo , 0 )
274
270
α̂_loo = α̂ @ (1 / (self .γs_ [np .newaxis , :] * ρ + λ [:, np .newaxis ]))
275
271
ŷ_loo = np .sum (F_loo [:, np .newaxis , :] * H_loo , axis = 2 ) * α̂_loo + F_loo @ α̂_loo
276
- ŷ_loo = ŷ_loo * self .y_scale_ + self .y_shift_
277
- y_true = y * self .y_scale_ + self .y_shift_
278
- loo_residuals = ŷ_loo - y_true [:, np .newaxis ]
272
+ loo_residuals = ŷ_loo - y [:, np .newaxis ]
279
273
# In the case of binary classification, clip overly positive and overly negative
280
274
# predictions' residuals to 0 when the labels are positive and negative, respectively.
281
275
if self ._estimator_type == "classifier" :
@@ -295,21 +289,21 @@ def _optimize_α̂_γ(
295
289
self .loo_residuals_ = loo_residuals [:, optimum ]
296
290
self .loo_error_ = self .loo_errors_γs_ [optimum ]
297
291
if self ._estimator_type == "classifier" :
298
- self .loo_score_ = accuracy_score (y_true , np .sign (ŷ_loo [:, optimum ]), sample_weight = s )
292
+ self .loo_score_ = accuracy_score (y , np .sign (ŷ_loo [:, optimum ]), sample_weight = s )
299
293
elif self ._estimator_type == "regressor" :
300
- self .loo_score_ = r2_score (y_true , ŷ_loo [:, optimum ], sample_weight = s )
294
+ self .loo_score_ = r2_score (y , ŷ_loo [:, optimum ], sample_weight = s )
301
295
α̂ , γ = α̂_loo [:, optimum ], self .γs_ [optimum ]
302
296
# Resolve the linear system for better accuracy.
303
297
if self .refit :
304
298
α̂ = np .linalg .solve (γ * ρ * np .diag (sn ** - 2 ) + K , y )
305
- self .residuals_ = ( F @ α̂ - y ) * self . y_scale_
299
+ self .residuals_ = F @ α̂ - y
306
300
if self ._estimator_type == "classifier" :
307
301
self .residuals_ [(y > 0 ) & (self .residuals_ > 0 )] = 0
308
302
self .residuals_ [(y < 0 ) & (self .residuals_ < 0 )] = 0
309
303
# TODO: Print warning if optimal γ is found at the edge.
310
304
return α̂ , γ
311
305
312
- def fit ( # noqa: PLR0915
306
+ def fit (
313
307
self , X : FloatMatrix [F ], y : GenericVector , sample_weight : FloatVector [F ] | None = None
314
308
) -> "NeoLSSVM" :
315
309
"""Fit this predictor."""
@@ -347,19 +341,10 @@ def fit( # noqa: PLR0915
347
341
y_ = np .ones (y .shape , dtype = X .dtype )
348
342
y_ [negatives ] = - 1
349
343
elif self ._estimator_type == "regressor" :
350
- y_ = cast ( npt . NDArray [ np . floating [ Any ]], y )
344
+ y_ = y . astype ( X . dtype )
351
345
else :
352
346
message = "Target type not supported"
353
347
raise ValueError (message )
354
- # Fit robust shift and scale parameters for the target y.
355
- if self ._estimator_type == "classifier" :
356
- self .y_shift_ : float = 0.0
357
- self .y_scale_ : float = 1.0
358
- elif self ._estimator_type == "regressor" :
359
- l , self .y_shift_ , u = np .quantile (y_ , [0.05 , 0.5 , 0.95 ]) # noqa: E741
360
- self .y_scale_ = np .maximum (np .abs (l - self .y_shift_ ), np .abs (u - self .y_shift_ ))
361
- self .y_scale_ = 1.0 if self .y_scale_ <= np .finfo (X .dtype ).eps else self .y_scale_
362
- y_ = ((y_ - self .y_shift_ ) / self .y_scale_ ).astype (X .dtype )
363
348
# Determine whether we want to solve this in the primal or dual space.
364
349
self .dual_ = X .shape [0 ] <= 1024 if self .dual is None else self .dual # noqa: PLR2004
365
350
self .primal_ = not self .dual_
@@ -390,7 +375,7 @@ def fit( # noqa: PLR0915
390
375
self .predict_proba_calibrator_ = IsotonicRegression (
391
376
out_of_bounds = "clip" , y_min = 0 , y_max = 1 , increasing = True
392
377
)
393
- ŷ_loo = self . loo_residuals_ + y_
378
+ ŷ_loo = y_ + self . loo_residuals_
394
379
target = np .zeros_like (y_ )
395
380
target [y_ == np .max (y_ )] = 1.0
396
381
self .predict_proba_calibrator_ .fit (ŷ_loo , target , sample_weight_ )
@@ -404,10 +389,11 @@ def decision_function(self, X: FloatMatrix[F]) -> FloatVector[F]:
404
389
φ = cast (KernelApproximatingFeatureMap , self .primal_feature_map_ ).transform (X )
405
390
ŷ = np .real (φ @ self .β̂_ )
406
391
else :
407
- # Shift and scale X, then predict as ŷ(x) := k(x, X) â + 1'â.
392
+ # Apply an affine transformation to X, then predict as ŷ(x) := k(x, X) â + 1'â.
408
393
X = cast (AffineFeatureMap , self .dual_feature_map_ ).transform (X )
409
394
K = rbf_kernel (X , self .X_ , gamma = 0.5 )
410
- ŷ = K @ self .α̂_ + np .sum (self .α̂_ )
395
+ b = np .sum (self .α̂_ )
396
+ ŷ = K @ self .α̂_ + b
411
397
return ŷ
412
398
413
399
def predict (self , X : FloatMatrix [F ]) -> GenericVector :
@@ -423,8 +409,8 @@ def predict(self, X: FloatMatrix[F]) -> GenericVector:
423
409
# Remap to the original class labels.
424
410
ŷ = self .classes_ [((ŷ_df + 1 ) // 2 ).astype (np .intp )]
425
411
elif self ._estimator_type == "regressor" :
426
- # Undo the label shift and scale .
427
- ŷ = ŷ_df . astype ( np . float64 ) * self . y_scale_ + self . y_shift_
412
+ # The decision function is the point prediction .
413
+ ŷ = ŷ_df
428
414
# Map back to the training target dtype.
429
415
ŷ = ŷ .astype (self .y_dtype_ )
430
416
return ŷ
0 commit comments