Skip to content

Commit c00b77a

Browse files
author
Christian Jorgensen
committed
Merge remote-tracking branch 'origin/main' into multioutput-pcovc
1 parent 7b345f3 commit c00b77a

File tree

14 files changed

+212
-18
lines changed

14 files changed

+212
-18
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
runs-on: ubuntu-latest
1111

1212
steps:
13-
- uses: actions/checkout@v4
13+
- uses: actions/checkout@v5
1414

1515
- name: Set up Python
1616
uses: actions/setup-python@v5

.github/workflows/docs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
build:
1212
runs-on: ubuntu-latest
1313
steps:
14-
- uses: actions/checkout@v4
14+
- uses: actions/checkout@v5
1515

1616
- name: setup Python
1717
uses: actions/setup-python@v5

.github/workflows/lint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99
runs-on: ubuntu-latest
1010

1111
steps:
12-
- uses: actions/checkout@v4
12+
- uses: actions/checkout@v5
1313

1414
- name: Set up Python
1515
uses: actions/setup-python@v5

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
contents: write
1717

1818
steps:
19-
- uses: actions/checkout@v4
19+
- uses: actions/checkout@v5
2020
with:
2121
fetch-depth: 0
2222
- name: setup Python

.github/workflows/tests-dev.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ jobs:
1010
runs-on: ubuntu-latest
1111
strategy:
1212
matrix:
13-
python-version: ["3.10", "3.13"]
13+
python-version:
14+
- "3.10"
15+
- "3.13"
1416

1517
steps:
16-
- uses: actions/checkout@v4
18+
- uses: actions/checkout@v5
1719

1820
- name: Set up Python ${{ matrix.python-version }}
1921
uses: actions/setup-python@v5

.github/workflows/tests.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,16 @@ jobs:
1111
runs-on: ${{ matrix.os }}
1212
strategy:
1313
matrix:
14-
os: [ubuntu-latest, macos-latest, windows-latest]
15-
python-version: ["3.10", "3.13"]
14+
os:
15+
- ubuntu-latest
16+
- macos-latest
17+
- windows-latest
18+
python-version:
19+
- "3.10"
20+
- "3.13"
1621

1722
steps:
18-
- uses: actions/checkout@v4
23+
- uses: actions/checkout@v5
1924

2025
- name: Set up Python ${{ matrix.python-version }}
2126
uses: actions/setup-python@v5

examples/pcovc/KPCovC_Comparison.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
random_state = 0
3535
n_components = 2
36+
scale_z = True
3637

3738
# %%
3839
#
@@ -85,7 +86,7 @@
8586
# Both PCA and PCovC fail to produce linearly separable latent space
8687
# maps. We will need a kernel method to effectively separate the moon classes.
8788

88-
mixing = 0.10
89+
mixing = 0.5
8990
alpha_d = 0.5
9091
alpha_p = 0.4
9192

@@ -95,6 +96,7 @@
9596
n_components=n_components,
9697
random_state=random_state,
9798
mixing=mixing,
99+
scale_z=scale_z,
98100
classifier=LinearSVC(),
99101
): "PCovC",
100102
}
@@ -138,6 +140,7 @@
138140
random_state=random_state,
139141
mixing=mixing,
140142
center=center,
143+
scale_z=scale_z,
141144
**kernel_params,
142145
): {"title": "Kernel PCovC", "eps": 2},
143146
}
@@ -220,6 +223,7 @@
220223
mixing=mixing,
221224
classifier=model,
222225
center=center,
226+
scale_z=scale_z,
223227
**models[model]["kernel_params"],
224228
)
225229
t_kpcovc_train = kpcovc.fit_transform(X_train_scaled, y_train)

examples/pcovc/KPCovC_Hyperparameters.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@
6565
fig, axs = plt.subplots(2, len(kernels), figsize=(len(kernels) * 4, 8))
6666

6767
center = True
68-
mixing = 0.10
68+
mixing = 0.5
69+
scale_z = True
6970

7071
for i, kernel in enumerate(kernels):
7172
kpca = KernelPCA(
@@ -83,6 +84,7 @@
8384
random_state=random_state,
8485
**kernel_params.get(kernel, {}),
8586
center=center,
87+
scale_z=scale_z,
8688
)
8789
t_kpcovc = kpcovc.fit_transform(X_scaled, y)
8890

@@ -118,7 +120,7 @@
118120
kpcovc = KernelPCovC(
119121
n_components=n_components,
120122
random_state=random_state,
121-
mixing=mixing,
123+
mixing=0.1,
122124
center=center,
123125
kernel="rbf",
124126
gamma=gamma,

examples/pcovc/PCovC_Hyperparameters.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
n_components=n_components,
7878
random_state=random_state,
7979
classifier=LogisticRegressionCV(),
80+
scale_z=True,
8081
)
8182

8283
pcovc.fit(X_scaled, y)
@@ -120,6 +121,7 @@
120121
n_components=n_components,
121122
random_state=random_state,
122123
classifier=model,
124+
scale_z=True,
123125
)
124126

125127
pcovc.fit(X_scaled, y)

src/skmatter/decomposition/_kernel_pcovc.py

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import warnings
12
import numpy as np
23

34
from sklearn import clone
@@ -17,7 +18,7 @@
1718
from sklearn.linear_model._base import LinearClassifierMixin
1819
from sklearn.utils.multiclass import check_classification_targets, type_of_target
1920

20-
from skmatter.preprocessing import KernelNormalizer
21+
from skmatter.preprocessing import KernelNormalizer, StandardFlexibleScaler
2122
from skmatter.utils import check_cl_fit
2223
from skmatter.decomposition import _BaseKPCov
2324

@@ -99,6 +100,9 @@ class KernelPCovC(LinearClassifierMixin, _BaseKPCov):
99100
constructed, with ``sklearn.linear_model.LogisticRegression()`` models used for each
100101
label.
101102
103+
scale_z: bool, default=False
104+
Whether to scale Z prior to eigendecomposition.
105+
102106
kernel : {"linear", "poly", "rbf", "sigmoid", "precomputed"} or callable, default="linear"
103107
Kernel.
104108
@@ -129,6 +133,14 @@ class KernelPCovC(LinearClassifierMixin, _BaseKPCov):
129133
and for matrix inversions.
130134
Must be of range [0.0, infinity).
131135
136+
z_mean_tol: float, default=1e-12
137+
Tolerance for the column means of Z.
138+
Must be of range [0.0, infinity).
139+
140+
z_var_tol: float, default=1.5
141+
Tolerance for the column variances of Z.
142+
Must be of range [0.0, infinity).
143+
132144
n_jobs : int, default=None
133145
The number of parallel jobs to run.
134146
:obj:`None` means 1 unless in a :obj:`joblib.parallel_backend` context.
@@ -185,14 +197,17 @@ class KernelPCovC(LinearClassifierMixin, _BaseKPCov):
185197
The data used to fit the model. This attribute is used to build kernels
186198
from new data.
187199
200+
scale_z: bool
201+
Whether Z is being scaled prior to eigendecomposition.
202+
188203
Examples
189204
--------
190205
>>> import numpy as np
191206
>>> from skmatter.decomposition import KernelPCovC
192207
>>> from sklearn.preprocessing import StandardScaler
193208
>>> X = np.array([[-2, 3, -1, 0], [2, 0, -3, 1], [3, 0, -1, 3], [2, -2, 1, 0]])
194209
>>> X = StandardScaler().fit_transform(X)
195-
>>> Y = np.array([[2], [0], [1], [2]])
210+
>>> Y = np.array([2, 0, 1, 2])
196211
>>> kpcovc = KernelPCovC(
197212
... mixing=0.1,
198213
... n_components=2,
@@ -218,6 +233,7 @@ def __init__(
218233
n_components=None,
219234
svd_solver="auto",
220235
classifier=None,
236+
scale_z=False,
221237
kernel="linear",
222238
gamma=None,
223239
degree=3,
@@ -226,6 +242,8 @@ def __init__(
226242
center=False,
227243
fit_inverse_transform=False,
228244
tol=1e-12,
245+
z_mean_tol=1e-12,
246+
z_var_tol=1.5,
229247
n_jobs=None,
230248
iterated_power="auto",
231249
random_state=None,
@@ -247,6 +265,9 @@ def __init__(
247265
fit_inverse_transform=fit_inverse_transform,
248266
)
249267
self.classifier = classifier
268+
self.scale_z = scale_z
269+
self.z_mean_tol = z_mean_tol
270+
self.z_var_tol = z_var_tol
250271

251272
def fit(self, X, Y, W=None):
252273
r"""Fit the model with X and Y.
@@ -368,6 +389,25 @@ def fit(self, X, Y, W=None):
368389
W = self.z_classifier_.coef_.T
369390

370391
Z = K @ W
392+
if self.scale_z:
393+
Z = StandardFlexibleScaler().fit_transform(Z)
394+
395+
z_means_ = np.mean(Z, axis=0)
396+
z_vars_ = np.var(Z, axis=0)
397+
398+
if np.max(np.abs(z_means_)) > self.z_mean_tol:
399+
warnings.warn(
400+
"This class does not automatically center Z, and the column means "
401+
"of Z are greater than the supplied tolerance. We recommend scaling "
402+
"Z (and the weights) by setting `scale_z=True`."
403+
)
404+
405+
if np.max(z_vars_) > self.z_var_tol:
406+
warnings.warn(
407+
"This class does not automatically scale Z, and the column variances "
408+
"of Z are greater than the supplied tolerance. We recommend scaling "
409+
"Z (and the weights) by setting `scale_z=True`."
410+
)
371411

372412
self._fit(K, Z, W)
373413

0 commit comments

Comments
 (0)