Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/pcovc/KPCovC_Comparison.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
# Both PCA and PCovC fail to produce linearly separable latent space
# maps. We will need a kernel method to effectively separate the moon classes.

mixing = 0.10
mixing = 0.5
alpha_d = 0.5
alpha_p = 0.4

Expand Down
2 changes: 1 addition & 1 deletion examples/pcovc/KPCovC_Hyperparameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
fig, axs = plt.subplots(2, len(kernels), figsize=(len(kernels) * 4, 8))

center = True
mixing = 0.10
mixing = 0.5

for i, kernel in enumerate(kernels):
kpca = KernelPCA(
Expand Down
9 changes: 8 additions & 1 deletion src/skmatter/decomposition/_kernel_pcovc.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from sklearn.linear_model._base import LinearClassifierMixin
from sklearn.utils.multiclass import check_classification_targets, type_of_target

from skmatter.preprocessing import KernelNormalizer
from skmatter.preprocessing import KernelNormalizer, StandardFlexibleScaler
from skmatter.utils import check_cl_fit
from skmatter.decomposition import _BaseKPCov

Expand Down Expand Up @@ -86,6 +86,9 @@ class KernelPCovC(LinearClassifierMixin, _BaseKPCov):
If None, ``sklearn.linear_model.LogisticRegression()``
is used as the classifier.

scale_z: bool, default=True
Whether to scale Z prior to eigendecomposition.

kernel : {"linear", "poly", "rbf", "sigmoid", "precomputed"} or callable, default="linear"
Kernel.

Expand Down Expand Up @@ -200,6 +203,7 @@ def __init__(
n_components=None,
svd_solver="auto",
classifier=None,
scale_z=True,
kernel="linear",
gamma=None,
degree=3,
Expand Down Expand Up @@ -229,6 +233,7 @@ def __init__(
fit_inverse_transform=fit_inverse_transform,
)
self.classifier = classifier
self.scale_z = scale_z

def fit(self, X, Y, W=None):
r"""Fit the model with X and Y.
Expand Down Expand Up @@ -323,6 +328,8 @@ def fit(self, X, Y, W=None):
W = LogisticRegression().fit(K, Y).coef_.T

Z = K @ W
if self.scale_z:
Z = StandardFlexibleScaler().fit_transform(Z)

self._fit(K, Z, W)

Expand Down
13 changes: 12 additions & 1 deletion src/skmatter/decomposition/_pcovc.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from sklearn.utils.validation import check_is_fitted, validate_data
from skmatter.decomposition import _BasePCov
from skmatter.utils import check_cl_fit
from skmatter.preprocessing import StandardFlexibleScaler


class PCovC(LinearClassifierMixin, _BasePCov):
Expand Down Expand Up @@ -123,6 +124,9 @@ class PCovC(LinearClassifierMixin, _BasePCov):
If None, ``sklearn.linear_model.LogisticRegression()``
is used as the classifier.

scale_z: bool, default=True
Whether to scale Z prior to eigendecomposition.

iterated_power : int or 'auto', default='auto'
Number of iterations for the power method computed by
svd_solver == 'randomized'.
Expand Down Expand Up @@ -210,6 +214,7 @@ def __init__(
tol=1e-12,
space="auto",
classifier=None,
scale_z=True,
iterated_power="auto",
random_state=None,
whiten=False,
Expand All @@ -225,6 +230,7 @@ def __init__(
whiten=whiten,
)
self.classifier = classifier
self.scale_z = scale_z

def fit(self, X, Y, W=None):
r"""Fit the model with X and Y.
Expand Down Expand Up @@ -291,7 +297,7 @@ def fit(self, X, Y, W=None):
classifier = self.classifier

self.z_classifier_ = check_cl_fit(classifier, X, Y)
W = self.z_classifier_.coef_.T
W = self.z_classifier_.coef_.T.copy()

else:
# If precomputed, use default classifier to predict Y from T
Expand All @@ -301,6 +307,11 @@ def fit(self, X, Y, W=None):

Z = X @ W

if self.scale_z:
z_scaler = StandardFlexibleScaler().fit(Z)
Z = z_scaler.transform(Z)
W /= z_scaler.scale_.reshape(1, -1)

if self.space_ == "feature":
self._fit_feature_space(X, Y, Z)
else:
Expand Down
9 changes: 9 additions & 0 deletions tests/test_kernel_pcovc.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,15 @@ def test_precomputed_classification(self):
self.assertTrue(np.linalg.norm(t3 - t2) < self.error_tol)
self.assertTrue(np.linalg.norm(t3 - t1) < self.error_tol)

def test_scale_z_parameter(self):
"""Check that changing scale_z changes the eigendecomposition."""
kpcovc_scaled = self.model(scale_z=True)
kpcovc_scaled.fit(self.X, self.Y)

kpcovc_unscaled = self.model(scale_z=False)
kpcovc_unscaled.fit(self.X, self.Y)
assert not np.allclose(kpcovc_scaled.pkt_, kpcovc_unscaled.pkt_)


class KernelTests(KernelPCovCBaseTest):
def test_kernel_types(self):
Expand Down
14 changes: 14 additions & 0 deletions tests/test_pcovc.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,9 @@ def test_default_ncomponents(self):
self.assertEqual(pcovc.n_components_, min(self.X.shape))

def test_prefit_classifier(self):
"""Check that a passed prefit classifier is not modified in
PCovC's `fit` call.
"""
classifier = LinearSVC()
classifier.fit(self.X, self.Y)
pcovc = self.model(mixing=0.5, classifier=classifier)
Expand Down Expand Up @@ -575,6 +578,17 @@ def test_incompatible_coef_shape(self):
% (len(pcovc_multi.classes_), self.X.shape[1], cl_binary.coef_.shape),
)

def test_scale_z_parameter(self):
"""Check that changing scale_z changes the eigendecomposition."""
pcovc_scaled = self.model(scale_z=True)
pcovc_scaled.fit(self.X, self.Y)

pcovc_unscaled = self.model(scale_z=False)
pcovc_unscaled.fit(self.X, self.Y)
assert not np.allclose(
pcovc_scaled.singular_values_, pcovc_unscaled.singular_values_
)


if __name__ == "__main__":
unittest.main(verbosity=2)
Loading