44import scipy .sparse as sp
55from scipy import linalg
66from scipy .sparse .linalg import svds
7+ from sklearn .calibration import LinearSVC
78from sklearn .decomposition ._base import _BasePCA
89from sklearn .decomposition ._pca import _infer_dimension
910from sklearn .exceptions import NotFittedError
2021from sklearn .svm import SVC
2122from sklearn .base import clone
2223from copy import deepcopy
24+ from sklearn .metrics import accuracy_score
2325
2426from skmatter .preprocessing import KernelNormalizer
25- from skmatter .utils import check_krr_fit , pcovr_kernel
26-
27- def check_cl_fit (classifier , X , y ):
28- r"""
29- Checks that a (linear) classifier is fitted, and if not,
30- fits it with the provided data
31- :param regressor: sklearn-style classifier
32- :type classifier: object
33- :param X: feature matrix with which to fit the classifier
34- if it is not already fitted
35- :type X: array
36- :param y: target values with which to fit the classifier
37- if it is not already fitted
38- :type y: array
39- """
40- try :
41- check_is_fitted (classifier )
42- fitted_classifier = deepcopy (classifier )
43-
44- # Check compatibility with X
45- fitted_classifier ._validate_data (X , y , reset = False , multi_output = True )
46-
47- # Check compatibility with y
48-
49- # changed from if fitted_classifier.coef_.ndim != y.ndim:
50- # dimension of classifier coefficients is always 2, hence we don't need to check
51- # for match with Y
52- if fitted_classifier .coef_ .shape [1 ] != X .shape [1 ]:
53- raise ValueError (
54- "The classifier coefficients have a shape incompatible "
55- "with the supplied feature space. "
56- "The coefficients have shape %d and the features "
57- "have shape %d" % (fitted_classifier .coef_ .shape , X .shape )
58- )
59- # LogisticRegression does not support multioutput, but RidgeClassifier does
60- elif y .ndim == 2 :
61- if fitted_classifier .coef_ .shape [0 ] != y .shape [1 ]:
62- raise ValueError (
63- "The classifier coefficients have a shape incompatible "
64- "with the supplied target space. "
65- "The coefficients have shape %r and the targets "
66- "have shape %r" % (fitted_classifier .coef_ .shape , y .shape )
67- )
68-
69- except NotFittedError :
70- fitted_classifier = clone (classifier )
71- fitted_classifier .fit (X , y )
72-
73- return fitted_classifier
27+ from skmatter .utils import pcovr_kernel
7428
29+ import sys
30+ sys .path .append ('scikit-matter' )
31+ from src .skmatter .utils ._pcovc_utils import check_svc_fit
32+ from src .skmatter .utils ._pcovr_utils import check_krr_fit
7533
7634class KernelPCovC (_BasePCA , LinearModel ):
7735 r"""
@@ -251,7 +209,7 @@ def __init__(
251209 gamma = "scale" ,
252210 degree = 3 ,
253211 coef0 = 0.0 ,
254- # kernel_params,
212+ kernel_params = None ,
255213 center = False ,
256214 fit_inverse_transform = False ,
257215 tol = 1e-12 ,
@@ -272,7 +230,7 @@ def __init__(
272230 self .gamma = gamma
273231 self .degree = degree
274232 self .coef0 = coef0
275- # self.kernel_params = kernel_params
233+ self .kernel_params = kernel_params
276234
277235 self .n_jobs = n_jobs
278236
@@ -284,7 +242,7 @@ def _get_kernel(self, X, Y=None):
284242 sparse = sp .issparse (X )
285243
286244 if callable (self .kernel ):
287- params = {} # self.kernel_params or {}
245+ params = self .kernel_params or {}
288246 else :
289247 #this is how BaseSVC has it:
290248 if self .gamma == "scale" :
@@ -367,7 +325,7 @@ def fit(self, X, y, W=None):
367325 """
368326
369327 if self .classifier not in ["precomputed" , None ] and not isinstance (
370- self .classifier , SVC
328+ self .classifier , SVC #make sure that decision_function_shape is ONLY "ovr" otherwise this will impact Z's shape
371329 ):
372330 raise ValueError (
373331 "classifier must be an instance of `SVC`"
@@ -433,12 +391,14 @@ def fit(self, X, y, W=None):
433391 # Check if classifier is fitted; if not, fit with precomputed K
434392 # to avoid needing to compute the kernel a second time
435393 classifier .probability = True
436- self .z_classifier_ = check_krr_fit (classifier , K , X , y ) #Pkz as weights - fits on K, y
437-
438- Z = self . z_classifier_ . predict_proba ( K )
394+ self .z_classifier_ = check_svc_fit (classifier , K , X , y ) #Pkz as weights - fits on K, y
395+ Z = self . z_classifier_ . decision_function ( K )
396+
439397 # print(K.shape)
440398 # print("Z: "+str(Z.shape))
441399
400+ #problem is that with a prefitted classifeir on X, y, we are trying to refit it on K, y
401+
442402 W = np .linalg .lstsq (K , Z , self .tol )[0 ]
443403 #W should have shape (samples, classes) since Z = K*W
444404 #(samples, classes) = (samples, samples)*(samples,classes)
@@ -457,12 +417,12 @@ def fit(self, X, y, W=None):
457417 # it will work on the particular X
458418 # of the KPCovR call. The dual coefficients are kept.
459419 # Can be bypassed if the classifier is pre-fitted.
460- try :
461- check_is_fitted (classifier )
462- except NotFittedError :
463- self .z_classifier_ .set_params (** classifier .get_params ())
464- self .z_classifier_ .X_fit_ = self .X_fit_
465- self .z_classifier_ ._check_n_features (self .X_fit_ , reset = True )
420+ # try:
421+ # check_is_fitted(classifier)
422+ # except NotFittedError:
423+ # self.z_classifier_.set_params(**classifier.get_params())
424+ # self.z_classifier_.X_fit_ = self.X_fit_
425+ # self.z_classifier_._check_n_features(self.X_fit_, reset=True)
466426 else :
467427 Z = y .copy ()
468428 if W is None :
@@ -497,14 +457,30 @@ def fit(self, X, y, W=None):
497457 if self .fit_inverse_transform :
498458 self .ptx_ = self .pt__ @ X
499459
500-
501460 #self.classifier_ = check_cl_fit(classifier, K @ self.pkt_, y) # Extract weights to get Ptz
502- if self .classifier != "precomputed" :
503- self .classifier_ = clone (classifier ).fit (K @ self .pkt_ , y )
504- else :
505- self .classifier_ = SVC ().fit (K @ self .pkt_ , y )
461+ self .classifier_ = LinearSVC ().fit (K @ self .pkt_ , y )
462+ # if self.classifier != "precomputed":
463+ # self.classifier_ = clone(classifier).fit(K @ self.pkt_, y)
464+ # else:
465+ # self.classifier_ = SVC().fit(K @ self.pkt_, y)
506466 self .classifier_ ._validate_data (K @ self .pkt_ , y , reset = False )
507467
468+ if isinstance (self .classifier_ , MultiOutputClassifier ):
469+ self .ptz_ = np .hstack (
470+ [est_ .coef_ .T for est_ in self .classifier_ .estimators_ ]
471+ )
472+ self .pkz_ = self .pkt_ @ self .ptz_
473+ else :
474+ self .ptz_ = self .classifier_ .coef_ .T
475+ self .pkz_ = self .pkt_ @ self .ptz_
476+
477+ if len (Y .shape ) == 1 :
478+ self .pkz_ = self .pkz_ .reshape (
479+ X .shape [1 ],
480+ )
481+ self .ptz_ = self .ptz_ .reshape (
482+ self .n_components_ ,
483+ )
508484
509485 self .components_ = self .pkt_ .T # for sklearn compatibility
510486 return self
@@ -522,14 +498,13 @@ def decision_function(self, X=None, T=None):
522498 K = self ._get_kernel (X , self .X_fit_ )
523499 if self .center :
524500 K = self .centerer_ .transform (K )
525-
526- return self .z_classifier_ .predict_proba (K )
527- #return K @ self.pkz_
501+
502+ return K @ self .pkz_
528503
529504 else :
530505 T = check_array (T )
531- return self .classifier_ . predict_proba ( T )
532- #return T @ self.ptz_
506+ return T @ self .ptz_
507+
533508
534509 def predict (self , X = None , T = None ):
535510 """Predicts class values from X or T."""
@@ -602,67 +577,35 @@ def inverse_transform(self, T):
602577
603578 return T @ self .ptx_
604579
605- def score (self , X , Y ):
606- r"""
607- Computes the (negative) loss values for KernelPCovC on the given predictor and
608- response variables. The loss in :math:`\mathbf{K}`, as explained in
609- [Helfrecht2020]_ does not correspond to a traditional Gram loss
610- :math:`\mathbf{K} - \mathbf{TT}^T`. Indicating the kernel between set
611- A and B as :math:`\mathbf{K}_{AB}`,
612- the projection of set A as :math:`\mathbf{T}_A`, and with N and V as the
613- train and validation/test set, one obtains
580+ def score (self , X , Y , sample_weight = None ):
581+ #taken from sklearn's LogisticRegression score() implementation:
582+ r"""Return the mean accuracy on the given test data and labels.
614583
615- .. math::
584+ In multi-label classification, this is the subset accuracy
585+ which is a harsh metric since you require for each sample that
586+ each label set be correctly predicted.
587+
588+ Parameters
589+ ----------
590+ X : array-like of shape (n_samples, n_features)
591+ Test samples.
616592
617- \ell=\frac{\operatorname{Tr}\left[\mathbf{K}_{VV} - 2
618- \mathbf{K}_{VN} \mathbf{T}_N
619- (\mathbf{T}_N^T \mathbf{T}_N)^{-1} \mathbf{T}_V^T
620- +\mathbf{T}_V(\mathbf{T}_N^T \mathbf{T}_N)^{-1} \mathbf{T}_N^T
621- \mathbf{K}_{NN} \mathbf{T}_N (\mathbf{T}_N^T \mathbf{T}_N)^{-1}
622- \mathbf{T}_V^T\right]}{\operatorname{Tr}(\mathbf{K}_{VV})}
593+ Y : array-like of shape (n_samples,) or (n_samples, n_outputs)
594+ True labels for `X`.
623595
624- The negative loss is returned for easier use in sklearn pipelines, e.g., a
625- grid search, where methods named 'score' are meant to be maximized.
596+ T : ndarray, shape (n_samples, n_components)
597+ Projected data, where n_samples is the number of samples
598+ and n_components is the number of components.
626599
627- Arguments
628- ---------
629- X: independent (predictor) variable
630- Y: dependent (response) variable
600+ sample_weight : array-like of shape (n_samples,), default=None
601+ Sample weights.
631602
632603 Returns
633604 -------
634- L: Negative sum of the KPCA and KRR losses, with the KPCA loss
635- determined by the reconstruction of the kernel
636-
605+ score : float
606+ Mean accuracy of ``self.predict(X, T)`` w.r.t. `Y`.
637607 """
638-
639- check_is_fitted (self , ["pkt_" , "X_fit_" ])
640-
641- X = check_array (X )
642-
643- K_NN = self ._get_kernel (self .X_fit_ , self .X_fit_ )
644- K_VN = self ._get_kernel (X , self .X_fit_ )
645- K_VV = self ._get_kernel (X )
646-
647- if self .center :
648- K_NN = self .centerer_ .transform (K_NN )
649- K_VN = self .centerer_ .transform (K_VN )
650- K_VV = self .centerer_ .transform (K_VV )
651-
652- y = K_VN @ self .pkz_
653- Lkrr = np .linalg .norm (Y - y ) ** 2 / np .linalg .norm (Y ) ** 2
654-
655- t_n = K_NN @ self .pkt_
656- t_v = K_VN @ self .pkt_
657-
658- w = (
659- t_n
660- @ np .linalg .lstsq (t_n .T @ t_n , np .eye (t_n .shape [1 ]), rcond = self .tol )[0 ]
661- @ t_v .T
662- )
663- Lkpca = np .trace (K_VV - 2 * K_VN @ w + w .T @ K_VV @ w ) / np .trace (K_VV )
664-
665- return - sum ([Lkpca , Lkrr ])
608+ return accuracy_score (Y , self .predict (X ), sample_weight = sample_weight )
666609
667610 def _decompose_truncated (self , mat ):
668611 if not 1 <= self .n_components_ <= self .n_samples_in_ :
0 commit comments