Skip to content

Commit 768d82b

Browse files
committed
Working on KPCovC, investigating minor errors
1 parent 4c471e4 commit 768d82b

File tree

12 files changed

+5062
-613
lines changed

12 files changed

+5062
-613
lines changed

examples/pcovc/PCovC-DecisionGraphForPaper.ipynb

Lines changed: 135 additions & 0 deletions
Large diffs are not rendered by default.

examples/pcovc/PCovC-IrisDataset.ipynb

Lines changed: 90 additions & 6 deletions
Large diffs are not rendered by default.

examples/pcovc/test_notebook.ipynb

Lines changed: 4600 additions & 420 deletions
Large diffs are not rendered by default.

src/skmatter/decomposition/_kernel_pcovr.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ def fit(self, X, Y, W=None):
334334
# Check if regressor is fitted; if not, fit with precomputed K
335335
# to avoid needing to compute the kernel a second time
336336
self.regressor_ = check_krr_fit(regressor, K, X, Y)
337-
337+
print(self.regressor_.n_features_in_)
338338
W = self.regressor_.dual_coef_.reshape(self.n_samples_in_, -1)
339339
print(W.shape)
340340
# Use this instead of `self.regressor_.predict(K)`

src/skmatter/decomposition/kernel_pcovc_new.py

Lines changed: 68 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import scipy.sparse as sp
55
from scipy import linalg
66
from scipy.sparse.linalg import svds
7+
from sklearn.calibration import LinearSVC
78
from sklearn.decomposition._base import _BasePCA
89
from sklearn.decomposition._pca import _infer_dimension
910
from sklearn.exceptions import NotFittedError
@@ -20,58 +21,15 @@
2021
from sklearn.svm import SVC
2122
from sklearn.base import clone
2223
from copy import deepcopy
24+
from sklearn.metrics import accuracy_score
2325

2426
from 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

7634
class 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_:

src/skmatter/decomposition/pcovc_new.py

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
from sklearn.calibration import column_or_1d
1313
from sklearn.naive_bayes import LabelBinarizer
1414
from sklearn.svm import LinearSVC
15+
from sklearn.svm import SVC
16+
1517
from sklearn.multioutput import MultiOutputClassifier
1618
from sklearn.utils import check_array
1719
from sklearn.utils.validation import check_is_fitted
@@ -172,7 +174,7 @@ class PCovC(_BasePCov):
172174
Examples
173175
--------
174176
>>> import numpy as np
175-
>>> from skmatter.decomposition import PCovc
177+
>>> from skmatter.decomposition import PCovC
176178
>>> X = np.array([[-1, 0, -2, 3], [3, -2, 0, 1], [-3, 0, -1, -1], [1, 3, 0, -2]])
177179
>>> Y = np.array([[0], [1], [2], [0]])
178180
>>> pcovc = PCovC(mixing=0.1, n_components=2)
@@ -256,7 +258,8 @@ class likelihoods, :math:`{\mathbf{Z}}`.
256258
LogisticRegressionCV,
257259
SGDClassifier,
258260
LinearSVC,
259-
MultiOutputClassifier,
261+
MultiOutputClassifier
262+
#check to see if all linear classifiers are here: Perceptron, LDA
260263
),
261264
),
262265
]
@@ -284,13 +287,15 @@ class likelihoods, :math:`{\mathbf{Z}}`.
284287

285288
else:
286289
W = self.z_classifier_.coef_.T.reshape(X.shape[1], -1)
287-
Z = self.z_classifier_.decision_function(X).reshape(X.shape[0], -1) #computes Z this will throw an error since pxz and ptz aren't defined yet
290+
Z = self.z_classifier_.decision_function(X).reshape(X.shape[0], -1)
291+
#computes Z this will throw an error since pxz and ptz aren't defined yet
288292

289293
else:
290294
Z = y.copy()
291295
if W is None:
292296
W = np.linalg.lstsq(X, Z, self.tol)[0] #W = weights for Pxz
293-
297+
# print("Z: "+str(Z[:4]))
298+
# print("W: "+str(W[:4]))
294299
self._label_binarizer = LabelBinarizer(neg_label=-1, pos_label=1)
295300
Y = self._label_binarizer.fit_transform(y) #check if we need this
296301

@@ -409,8 +414,8 @@ def inverse_transform(self, T):
409414
return super().inverse_transform(T)
410415

411416
def decision_function(self, X=None, T=None):
412-
print(self.pxz_.shape)
413-
print(self.ptz_.shape)
417+
# print(self.pxz_.shape)
418+
# print(self.ptz_.shape)
414419

415420
"""Predicts confidence scores from X or T."""
416421
check_is_fitted(self, attributes=["_label_binarizer", "pxz_", "ptz_"])
@@ -420,19 +425,10 @@ def decision_function(self, X=None, T=None):
420425

421426
if X is not None:
422427
X = check_array(X)
423-
return self.z_classifier_.decision_function(X)
428+
return X @ self.pxz_
424429
else:
425430
T = check_array(T)
426-
427-
return self.classifier_.decision_function(T)
428-
429-
# if X is not None:
430-
# X = check_array(X)
431-
# return X @ self.pxz_
432-
# else:
433-
# T = check_array(T)
434-
435-
# return T @ self.ptz_
431+
return T @ self.ptz_
436432

437433
def predict(self, X=None, T=None):
438434
"""Predicts the property labels using classification on T."""
@@ -460,7 +456,7 @@ def transform(self, X=None):
460456
"""
461457
return super().transform(X)
462458

463-
def score(self, X, Y, T=None, sample_weight=None):
459+
def score(self, X, Y, sample_weight=None):
464460
#taken from sklearn's LogisticRegression score() implementation:
465461
r"""Return the mean accuracy on the given test data and labels.
466462
@@ -488,4 +484,4 @@ def score(self, X, Y, T=None, sample_weight=None):
488484
score : float
489485
Mean accuracy of ``self.predict(X, T)`` w.r.t. `Y`.
490486
"""
491-
return accuracy_score(Y, self.predict(X, T), sample_weight=sample_weight)
487+
return accuracy_score(Y, self.predict(X), sample_weight=sample_weight)

0 commit comments

Comments
 (0)