Skip to content

Commit c534a68

Browse files
committed
Fixing errors with PCovC self.classifier_
1 parent 81ae690 commit c534a68

File tree

9 files changed

+128
-54
lines changed

9 files changed

+128
-54
lines changed

examples/pcovc/PCovC-BreastCancerDataset.ipynb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"\n",
2626
"import sys\n",
2727
"sys.path.append('../../')\n",
28-
"from src.skmatter.decomposition._pcovc import PCovC\n",
28+
"from src.skmatter.decomposition.pcovc_new import PCovC\n",
2929
"\n",
3030
"plt.rcParams[\"image.cmap\"] = \"tab10\"\n",
3131
"plt.rcParams['scatter.edgecolors'] = \"k\"\n",
@@ -216,7 +216,7 @@
216216
{
217217
"data": {
218218
"text/plain": [
219-
"<matplotlib.legend.Legend at 0x110136e40>"
219+
"<matplotlib.legend.Legend at 0x10fc7b620>"
220220
]
221221
},
222222
"execution_count": 4,
@@ -264,7 +264,7 @@
264264
{
265265
"data": {
266266
"text/plain": [
267-
"<matplotlib.collections.PathCollection at 0x1103cead0>"
267+
"<matplotlib.collections.PathCollection at 0x10ff1afd0>"
268268
]
269269
},
270270
"execution_count": 5,

src/skmatter/decomposition/_kernel_pcovc.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import numbers
22

33
import numpy as np
4+
import scipy.sparse as sp
45
from scipy import linalg
56
from scipy.sparse.linalg import svds
67
from sklearn.decomposition._base import _BasePCA
@@ -249,7 +250,7 @@ def __init__(
249250
gamma="scale",
250251
degree=3,
251252
coef0=0.0,
252-
kernel_params=None,
253+
# kernel_params=None,
253254
center=False,
254255
fit_inverse_transform=False,
255256
tol=1e-12,
@@ -270,7 +271,7 @@ def __init__(
270271
self.gamma = gamma
271272
self.degree = degree
272273
self.coef0 = coef0
273-
self.kernel_params = kernel_params
274+
# self.kernel_params = kernel_params
274275

275276
self.n_jobs = n_jobs
276277

@@ -279,10 +280,23 @@ def __init__(
279280
self.classifier = classifier
280281

281282
def _get_kernel(self, X, Y=None):
283+
sparse = sp.issparse(X)
284+
282285
if callable(self.kernel):
283-
params = self.kernel_params or {}
286+
params = {} #self.kernel_params or {}
284287
else:
285-
params = {"gamma": self.gamma, "degree": self.degree, "coef0": self.coef0}
288+
if self.gamma == "scale":
289+
X_var = (X.multiply(X)).mean() - (X.mean()) ** 2 if sparse else X.var()
290+
self._gamma = 1.0 / (X.shape[1] * X_var) if X_var != 0 else 1.0
291+
elif self.gamma == "auto":
292+
self._gamma = 1.0 / X.shape[1]
293+
else:
294+
self._gamma = self.gamma
295+
params = {"gamma": self._gamma, "degree": self.degree, "coef0": self.coef0}
296+
print("Params")
297+
print(params)
298+
299+
286300
return pairwise_kernels(
287301
X, Y, metric=self.kernel, filter_params=True, n_jobs=self.n_jobs, **params
288302
)
@@ -435,7 +449,7 @@ def fit(self, X, y, W=None):
435449
z_classifier_ = check_krr_fit(classifier, K, X, y)
436450
'''
437451
z_classifier_ = check_krr_fit(classifier, K, X, y) #Pkz as weights
438-
452+
print(z_classifier_)
439453
W = z_classifier_.dual_coef_.reshape(self.n_samples_in_, -1) #Pkz
440454

441455
# Use this instead of `self.classifier_.predict(K)`

src/skmatter/decomposition/_pcov.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from copy import deepcopy
12
import numbers
23
import numpy as np
34
import warnings
@@ -8,6 +9,7 @@
89
from scipy.linalg import sqrtm as MatrixSqrt
910
from scipy.sparse.linalg import svds
1011

12+
from sklearn import clone
1113
from sklearn.base import check_X_y
1214
from sklearn.calibration import column_or_1d
1315
from sklearn.decomposition._base import _BasePCA
@@ -151,15 +153,15 @@ def fit(self, X, y, W=None):
151153
else:
152154
classifier = self.classifier
153155

154-
z_classifier_ = check_cl_fit(classifier, X, y=y) #change to z classifier, fits linear classifier on x and y to get Pxz
156+
self.z_classifier_ = check_cl_fit(classifier, X, y=y) #change to z classifier, fits linear classifier on x and y to get Pxz
155157

156-
if isinstance(z_classifier_, MultiOutputClassifier):
157-
W = np.hstack([est_.coef_.T for est_ in z_classifier_.estimators_])
158+
if isinstance(self.z_classifier_, MultiOutputClassifier):
159+
W = np.hstack([est_.coef_.T for est_ in self.z_classifier_.estimators_])
158160
Z = X @ W #computes Z, basically Z=XPxz
159161

160162
else:
161-
W = z_classifier_.coef_.T.reshape(X.shape[1], -1)
162-
Z = z_classifier_.decision_function(X).reshape(X.shape[0], -1) #computes Z
163+
W = self.z_classifier_.coef_.T.reshape(X.shape[1], -1)
164+
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
163165

164166
else:
165167
Z = y.copy()
@@ -171,13 +173,34 @@ def fit(self, X, y, W=None):
171173
if not self._label_binarizer.y_type_.startswith("multilabel"):
172174
y = column_or_1d(y, warn=True)
173175

174-
175176
if self.space_ == "feature":
176177
self._fit_feature_space(X, Y.reshape(Z.shape), Z)
177178
else:
178179
self._fit_sample_space(X, Y.reshape(Z.shape), Z, W)
179180

180-
self.classifier_ = check_cl_fit(classifier, X @ self.pxt_, y=y)
181+
# instead of using linear regression solution, refit with the classifier
182+
# and steal weights to get ptz
183+
# this is failing because self.classifier is never changed from None if None is passed as classifier
184+
# change self.classifier to classifier and see what happens. if classifier is precomputed, there might be more errors so be careful.
185+
# if classifier is precomputed, I don't think we need to check if the classifier is fit or not?
186+
187+
#cases:
188+
#1. if classifier has been fit with X and Y already, we need to use classifier that hasn't been fitted and refit on T, y
189+
#2. if classifier has not been fit with X and Y, we call check_cl_fit
190+
191+
# if (fitted(X,y)):
192+
#
193+
# else:
194+
# check_cl_fit
195+
196+
#self.classifier_ = check_cl_fit(classifier, X @ self.pxt_, y=y)
197+
#we don't want to copy ALl parameters of classifier, such as n_features_in, since we are re-fitting it on T, y
198+
if self.classifier != "precomputed":
199+
self.classifier_ = clone(classifier).fit(X @ self.pxt_, y)
200+
else:
201+
self.classifier_ = LogisticRegression().fit(X @ self.pxt_, y)
202+
203+
self.classifier_._validate_data(X @ self.pxt_, y, reset=False)
181204

182205
#self.classifier_ = LogisticRegression().fit(X @ self.pxt_, y)
183206
#check_cl_fit(classifier., X @ self.pxt_, y=y) #Has Ptz as weights

src/skmatter/decomposition/_pcovc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@ def fit(self, X, y, W=None):
438438
#cases:
439439
#1. if classifier has been fit with X and Y already, we dont need to perform a check_cl_fit
440440
#2. if classifier has not been fit with X or Y, we dont need to
441-
#3. if classifier has been fit with T and Y, we need to perform check_cl_fit (doesn't make sense actually, why would we fit with T and y)
441+
#3. if classifier has been fit with T and Y, we need to perform check_cl_fit
442442

443443
# old: self.classifier_ = check_cl_fit(self.classifier, X @ self.pxt_, y=y) #Has Ptz as weights
444444

src/skmatter/decomposition/pcovc_new.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ class likelihoods, :math:`{\mathbf{Z}}`.
260260
)
261261
return super().fit(X, Y, W)
262262

263-
def _fit_feature_space(self, X, Y, Yhat):
263+
def _fit_feature_space(self, X, Y, Z):
264264
r"""In feature-space PCovC, the projectors are determined by:
265265
266266
.. math::
@@ -282,9 +282,9 @@ def _fit_feature_space(self, X, Y, Yhat):
282282
\mathbf{U}_\mathbf{\tilde{C}}^T
283283
(\mathbf{X}^T \mathbf{X})^{\frac{1}{2}}
284284
"""
285-
return super()._fit_feature_space(X, Y, Yhat)
285+
return super()._fit_feature_space(X, Y, Z)
286286

287-
def _fit_sample_space(self, X, Y, Yhat, W):
287+
def _fit_sample_space(self, X, Y, Z, W):
288288
r"""In sample-space PCovC, the projectors are determined by:
289289
290290
.. math::
@@ -303,7 +303,7 @@ def _fit_sample_space(self, X, Y, Yhat, W):
303303
\mathbf{P}_{TX} = \mathbf{\Lambda}_\mathbf{\tilde{K}}^{-\frac{1}{2}}
304304
\mathbf{U}_\mathbf{\tilde{K}}^T \mathbf{X}
305305
"""
306-
return super()._fit_sample_space(X, Y, Yhat, W)
306+
return super()._fit_sample_space(X, Y, Z, W)
307307

308308
def _decompose_truncated(self, mat):
309309
return super()._decompose_truncated(mat)

src/skmatter/decomposition/pcovr_new.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,12 @@ class PCovR(_BasePCov):
5757
mixing: float, default=0.5
5858
mixing parameter, as described in PCovR as :math:`{\alpha}`, here named to avoid
5959
confusion with regularization parameter `alpha`
60+
6061
n_components : int, float or str, default=None
6162
Number of components to keep.
6263
if n_components is not set all components are kept::
63-
6464
n_components == min(n_samples, n_features)
65+
6566
svd_solver : {'auto', 'full', 'arpack', 'randomized'}, default='auto'
6667
If auto :
6768
The solver is selected by a default policy based on `X.shape` and
@@ -78,13 +79,16 @@ class PCovR(_BasePCov):
7879
min(X.shape)
7980
If randomized :
8081
run randomized SVD by the method of Halko et al.
82+
8183
tol : float, default=1e-12
8284
Tolerance for singular values computed by svd_solver == 'arpack'. Must be of
8385
range [0.0, infinity).
86+
8487
space: {'feature', 'sample', 'auto'}, default='auto'
8588
whether to compute the PCovR in `sample` or `feature` space default=`sample`
8689
when :math:`{n_{samples} < n_{features}}` and `feature` when
8790
:math:`{n_{features} < n_{samples}}`
91+
8892
regressor: {`Ridge`, `RidgeCV`, `LinearRegression`, `precomputed`}, default=None
8993
regressor for computing approximated :math:`{\mathbf{\hat{Y}}}`. The regressor
9094
should be one `sklearn.linear_model.Ridge`, `sklearn.linear_model.RidgeCV`, or
@@ -98,42 +102,52 @@ class PCovR(_BasePCov):
98102
regressed form of the targets :math:`{\mathbf{\hat{Y}}}`. If None,
99103
``sklearn.linear_model.Ridge('alpha':1e-6, 'fit_intercept':False, 'tol':1e-12)``
100104
is used as the regressor.
105+
101106
iterated_power : int or 'auto', default='auto'
102107
Number of iterations for the power method computed by svd_solver ==
103108
'randomized'. Must be of range [0, infinity).
109+
104110
random_state : int, :class:`numpy.random.RandomState` instance or None, default=None
105111
Used when the 'arpack' or 'randomized' solvers are used. Pass an int for
106112
reproducible results across multiple function calls.
113+
107114
whiten : bool, deprecated
108115
109116
Attributes
110117
----------
111118
mixing: float, default=0.5
112119
mixing parameter, as described in PCovR as :math:`{\alpha}`
120+
113121
tol: float, default=1e-12
114122
Tolerance for singular values computed by svd_solver == 'arpack'.
115123
Must be of range [0.0, infinity).
124+
116125
space: {'feature', 'sample', 'auto'}, default='auto'
117126
whether to compute the PCovR in `sample` or `feature` space default=`sample`
118127
when :math:`{n_{samples} < n_{features}}` and `feature` when
119128
:math:`{n_{features} < n_{samples}}`
129+
120130
n_components_ : int
121131
The estimated number of components, which equals the parameter n_components, or
122132
the lesser value of n_features and n_samples if n_components is None.
133+
123134
pxt_ : numpy.ndarray of size :math:`({n_{samples}, n_{components}})`
124135
the projector, or weights, from the input space :math:`\mathbf{X}` to the
125136
latent-space projection :math:`\mathbf{T}`
137+
126138
pty_ : numpy.ndarray of size :math:`({n_{components}, n_{properties}})`
127139
the projector, or weights, from the latent-space projection :math:`\mathbf{T}`
128140
to the properties :math:`\mathbf{Y}`
141+
129142
pxy_ : numpy.ndarray of size :math:`({n_{samples}, n_{properties}})`
130143
the projector, or weights, from the input space :math:`\mathbf{X}` to the
131144
properties :math:`\mathbf{Y}`
145+
132146
explained_variance_ : numpy.ndarray of shape (n_components,)
133147
The amount of variance explained by each of the selected components.
134-
135148
Equal to n_components largest eigenvalues
136149
of the PCovR-modified covariance matrix of :math:`\mathbf{X}`.
150+
137151
singular_values_ : numpy.ndarray of shape (n_components,)
138152
The singular values corresponding to each of the selected components.
139153
@@ -195,6 +209,7 @@ def fit(self, X, Y, W=None):
195209
means and scaled. If features are related, the matrix should be scaled
196210
to have unit variance, otherwise :math:`\mathbf{X}` should be
197211
scaled so that each feature has a variance of 1 / n_features.
212+
198213
Y : numpy.ndarray, shape (n_samples, n_properties)
199214
Training data, where n_samples is the number of samples and n_properties is
200215
the number of properties
@@ -206,6 +221,7 @@ def fit(self, X, Y, W=None):
206221
207222
If the passed regressor = `precomputed`, it is assumed that Y is the
208223
regressed form of the properties, :math:`{\mathbf{\hat{Y}}}`.
224+
209225
W : numpy.ndarray, shape (n_features, n_properties)
210226
Regression weights, optional when regressor=`precomputed`. If not
211227
passed, it is assumed that `W = np.linalg.lstsq(X, Y, self.tol)[0]`

src/skmatter/decomposition/playground.py

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11

2+
from sklearn.base import check_is_fitted
23
from sklearn.discriminant_analysis import StandardScaler
4+
from sklearn.exceptions import NotFittedError
35
from sklearn.kernel_ridge import KernelRidge
46
from sklearn.linear_model import LogisticRegression, LinearRegression
57
from sklearn.svm import SVC
@@ -9,32 +11,50 @@
911
from sklearn.datasets import load_breast_cancer as get_dataset
1012
from sklearn.datasets import load_diabetes as get_dataset2
1113
from sklearn.metrics import accuracy_score
12-
from pcovr_new import PCovR
14+
from _kernel_pcovr import KernelPCovR
1315

14-
X, Y = get_dataset2(return_X_y=True)
16+
X, Y = get_dataset(return_X_y=True)
1517

18+
X_or = X
1619
scaler = StandardScaler()
1720
X = scaler.fit_transform(X)
1821

22+
23+
pcovc = PCovC(mixing=0.0, classifier=LogisticRegression(), n_components=2)
24+
pcovc.fit(X,Y)
25+
T = pcovc.transform(X)
26+
27+
pcovc2 = PCovC(mixing=0.0, classifier=LogisticRegression(), n_components=2)
28+
pcovc2.classifier.fit(X, Y)
29+
print(pcovc2.classifier.coef_.shape)
30+
pcovc2.classifier.fit(T, Y)
31+
print(pcovc2.classifier.coef_.shape)
32+
33+
34+
35+
36+
1937
# model = PCovR(mixing=0.5, regressor=LinearRegression())
2038
# model.fit(X,Y)
2139
# print(isinstance(model, PCovR))
2240

23-
import numpy as np
41+
# import numpy as np
2442

25-
X = np.array([[-1, 0, -2, 3], [3, -2, 0, 1], [-3, 0, -1, -1], [1, 3, 0, -2]])
26-
Y = np.array([[0], [1], [2], [0]])
27-
28-
pcovc = PCovC(mixing=0.1, n_components=2)
29-
pcovc.fit(X, Y)
30-
T= pcovc.transform(X)
31-
print(T)
43+
# X = np.array([[-1, 0, -2, 3], [3, -2, 0, 1], [-3, 0, -1, -1], [1, 3, 0, -2]])
44+
# Y = np.array([[0], [1], [2], [0]])
45+
46+
# print("AA23")
47+
# print(Y.shape)
48+
# pcovc = PCovC(mixing=0.1, n_components=2)
49+
# pcovc.fit(X, Y)
50+
# T= pcovc.transform(X)
51+
# print(T)
3252
# array([[ 3.2630561 , 0.06663787],
3353
# [-2.69395511, -0.41582771],
3454
# [ 3.48683147, -0.83164387],
3555
# [-4.05593245, 1.18083371]])
36-
Y = pcovc.predict(X)
37-
print(Y.shape)
56+
# Y = pcovc.predict(X)
57+
# print(Y.shape)
3858
# array([[ 0.01371776, -5.00945512],
3959
# [-1.02805338, 1.06736871],
4060
# [ 0.98166504, -4.98307078],

src/skmatter/utils/_pcovc_utils.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ def check_cl_fit(classifier, X, y):
1111

1212
# Check compatibility with X
1313
fitted_classifier._validate_data(X, y, reset=False, multi_output=True)
14-
print("X shape "+str(X.shape))
15-
print("y shape " + str(y.shape))
14+
1615
# Check compatibility with y
1716

1817
# changed from if fitted_classifier.coef_.ndim != y.ndim:
@@ -22,10 +21,11 @@ def check_cl_fit(classifier, X, y):
2221
raise ValueError(
2322
"The classifier coefficients have a shape incompatible "
2423
"with the supplied feature space. "
25-
"The coefficients have shape %d and the features "
26-
"have shape %d" % (fitted_classifier.coef_.shape, X.shape)
24+
"The coefficients have shape %r and the features "
25+
"have shape %r" % (fitted_classifier.coef_.shape, X.shape)
2726
)
28-
# LogisticRegression does not support multioutput, but RidgeClassifier does
27+
# LogisticRegression does not support multioutput, but RidgeClassifier does.
28+
# We need to check this...
2929
elif y.ndim == 2:
3030
if fitted_classifier.coef_.shape[0] != y.shape[1]:
3131
raise ValueError(

0 commit comments

Comments
 (0)