Skip to content

Commit a5e97f5

Browse files
Add Kliep kernels choice
1 parent 71f839c commit a5e97f5

File tree

3 files changed

+199
-54
lines changed

3 files changed

+199
-54
lines changed

adapt/instance_based/_kliep.py

Lines changed: 124 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
"""
22
Kullback-Leibler Importance Estimation Procedure
33
"""
4+
import itertools
5+
import warnings
46

57
import numpy as np
68
from sklearn.metrics import pairwise
79
from sklearn.exceptions import NotFittedError
810
from sklearn.utils import check_array
11+
from sklearn.metrics.pairwise import KERNEL_PARAMS
912

1013
from adapt.base import BaseAdaptEstimator, make_insert_doc
1114
from adapt.utils import set_random_seed
@@ -29,29 +32,29 @@ class KLIEP(BaseAdaptEstimator):
2932
3033
.. math::
3134
32-
w(x) = \sum_{x_i \in X_T} \\alpha_i K_{\sigma}(x, x_i)
35+
w(x) = \sum_{x_i \in X_T} \\alpha_i K(x, x_i)
3336
3437
Where:
3538
3639
- :math:`x, x_i` are input instances.
3740
- :math:`X_T` is the target input data.
3841
- :math:`\\alpha_i` are the basis functions coefficients.
39-
- :math:`K_{\sigma}(x, x_i) = \\text{exp}(-\\frac{||x - x_i||^2}{2\sigma^2})`
40-
are kernel functions of bandwidth :math:`\sigma`.
42+
- :math:`K(x, x_i) = \\text{exp}(-\\gamma ||x - x_i||^2)`
43+
for instance if ``kernel="rbf"``.
4144
4245
KLIEP algorithm consists in finding the optimal :math:`\\alpha_i` according to
4346
the following optimization problem:
4447
4548
.. math::
4649
4750
\max_{\\alpha_i } \sum_{x_i \in X_T} \log(
48-
\sum_{x_j \in X_T} \\alpha_i K_{\sigma}(x_j, x_i))
51+
\sum_{x_j \in X_T} \\alpha_i K(x_j, x_i))
4952
5053
Subject to:
5154
5255
.. math::
5356
54-
\sum_{x_k \in X_S} \sum_{x_j \in X_T} \\alpha_i K_{\sigma}(x_j, x_k)) = n_S
57+
\sum_{x_k \in X_S} \sum_{x_j \in X_T} \\alpha_i K(x_j, x_k)) = n_S
5558
5659
Where:
5760
@@ -60,8 +63,9 @@ class KLIEP(BaseAdaptEstimator):
6063
The above OP is solved through gradient ascent algorithm.
6164
6265
Furthemore a LCV procedure can be added to select the appropriate
63-
bandwidth :math:`\sigma`. The parameter is then selected using
64-
cross-validation on the :math:`J` score defined as follow:
66+
parameters of the kernel function math::`K` (typically, the paramter
67+
math::`\\gamma` of the Gaussian kernel). The parameter is then selected using
68+
cross-validation on the :math:`J` score defined as follows:
6569
:math:`J = \\frac{1}{|\\mathcal{X}|} \\sum_{x \\in \\mathcal{X}} \\text{log}(w(x))`
6670
6771
Finally, an estimator is fitted using the reweighted labeled source instances.
@@ -71,11 +75,16 @@ class KLIEP(BaseAdaptEstimator):
7175
target data to the training set.
7276
7377
Parameters
74-
----------
75-
sigmas : float or list of float (default=1/nb_features)
76-
Kernel bandwidths.
77-
If ``sigmas`` is a list of multiple values, the
78-
kernel bandwidth is selected with the LCV procedure.
78+
----------
79+
kernel : str (default="rbf")
80+
Kernel metric.
81+
Possible values: [‘additive_chi2’, ‘chi2’,
82+
‘linear’, ‘poly’, ‘polynomial’, ‘rbf’,
83+
‘laplacian’, ‘sigmoid’, ‘cosine’]
84+
85+
sigmas : float or list of float (default=None)
86+
Deprecated, please use the ``gamma`` parameter
87+
instead.
7988
8089
cv : int (default=5)
8190
Cross-validation split parameter.
@@ -94,23 +103,69 @@ class KLIEP(BaseAdaptEstimator):
94103
max_iter : int (default=5000)
95104
Maximal iteration of the gradient ascent
96105
optimization.
106+
107+
Yields
108+
------
109+
gamma : float or list of float
110+
Kernel parameter ``gamma``.
111+
112+
- For kernel = chi2::
113+
114+
k(x, y) = exp(-gamma Sum [(x - y)^2 / (x + y)])
115+
116+
- For kernel = poly or polynomial::
117+
118+
K(X, Y) = (gamma <X, Y> + coef0)^degree
119+
120+
- For kernel = rbf::
121+
122+
K(x, y) = exp(-gamma ||x-y||^2)
123+
124+
- For kernel = laplacian::
125+
126+
K(x, y) = exp(-gamma ||x-y||_1)
127+
128+
- For kernel = sigmoid::
129+
130+
K(X, Y) = tanh(gamma <X, Y> + coef0)
131+
132+
If a list is given, the LCV process is performed to
133+
select the best parameter ``gamma``.
134+
135+
coef0 : floaf or list of float
136+
Kernel parameter ``coef0``.
137+
Used for ploynomial and sigmoid kernels.
138+
See ``gamma`` parameter above for the
139+
kernel formulas.
140+
If a list is given, the LCV process is performed to
141+
select the best parameter ``coef0``.
142+
143+
degree : int or list of int
144+
Degree parameter for the polynomial
145+
kernel. (see formula in the ``gamma``
146+
parameter description).
147+
If a list is given, the LCV process is performed to
148+
select the best parameter ``degree``.
97149
98150
Attributes
99151
----------
100152
weights_ : numpy array
101153
Training instance weights.
102154
103-
sigma_ : float
104-
Sigma selected for the kernel
155+
best_params_ : float
156+
Best kernel params combination
157+
deduced from the LCV procedure.
105158
106159
alphas_ : numpy array
107160
Basis functions coefficients.
108161
109162
centers_ : numpy array
110163
Center points for kernels.
111164
112-
j_scores_ : list of float
113-
List of J scores.
165+
j_scores_ : dict
166+
dict of J scores with the
167+
kernel params combination as
168+
keys and the J scores as values.
114169
115170
estimator_ : object
116171
Fitted estimator.
@@ -154,6 +209,7 @@ class KLIEP(BaseAdaptEstimator):
154209
def __init__(self,
155210
estimator=None,
156211
Xt=None,
212+
kernel="rbf",
157213
sigmas=None,
158214
max_centers=100,
159215
cv=5,
@@ -165,6 +221,11 @@ def __init__(self,
165221
random_state=None,
166222
**params):
167223

224+
if sigmas is not None:
225+
warnings.warn("The `sigmas` argument is deprecated, "
226+
"please use the `gamma` argument instead.",
227+
DeprecationWarning)
228+
168229
names = self._get_param_names()
169230
kwargs = {k: v for k, v in locals().items() if k in names}
170231
kwargs.update(params)
@@ -194,9 +255,23 @@ def fit_weights(self, Xs, Xt, **kwargs):
194255
Xt = check_array(Xt)
195256
set_random_seed(self.random_state)
196257

197-
self.j_scores_ = []
258+
self.j_scores_ = {}
259+
260+
# LCV GridSearch
261+
kernel_params = {k: v for k, v in self.__dict__.items()
262+
if k in KERNEL_PARAMS[self.kernel]}
263+
264+
# Handle deprecated sigmas (will be removed)
265+
if (self.sigmas is not None) and (not "gamma" in kernel_params):
266+
kernel_params["gamma"] = self.sigmas
267+
268+
params_dict = {k: (v if hasattr(v, "__iter__") else [v]) for k, v in kernel_params.items()}
269+
options = params_dict
270+
keys = options.keys()
271+
values = (options[key] for key in keys)
272+
params_comb = [dict(zip(keys, combination)) for combination in itertools.product(*values)]
198273

199-
if hasattr(self.sigmas, "__iter__"):
274+
if len(params_comb) > 1:
200275
# Cross-validation process
201276
if len(Xt) < self.cv:
202277
raise ValueError("Length of Xt is smaller than cv value")
@@ -206,23 +281,28 @@ def fit_weights(self, Xs, Xt, **kwargs):
206281

207282
shuffled_index = np.arange(len(Xt))
208283
np.random.shuffle(shuffled_index)
209-
210-
for sigma in self.sigmas:
211-
cv_scores = self._cross_val_jscore(Xs, Xt[shuffled_index], sigma, self.cv)
212-
self.j_scores_.append(np.mean(cv_scores))
284+
285+
max_ = -np.inf
286+
for params in params_comb:
287+
cv_scores = self._cross_val_jscore(Xs, Xt[shuffled_index], params, self.cv)
288+
self.j_scores_[str(params)] = np.mean(cv_scores)
213289

214290
if self.verbose:
215-
print("Parameter sigma = %.4f -- J-score = %.3f (%.3f)"%
216-
(sigma, np.mean(cv_scores), np.std(cv_scores)))
291+
print("Parameters %s -- J-score = %.3f (%.3f)"%
292+
(str(params), np.mean(cv_scores), np.std(cv_scores)))
217293

218-
self.sigma_ = self.sigmas[np.argmax(self.j_scores_)]
294+
if self.j_scores_[str(params)] > max_:
295+
self.best_params_ = params
296+
max_ = self.j_scores_[str(params)]
219297
else:
220-
self.sigma_ = self.sigmas
298+
self.best_params_ = params_comb[0]
221299

222-
self.alphas_, self.centers_ = self._fit(Xs, Xt, self.sigma_)
300+
self.alphas_, self.centers_ = self._fit(Xs, Xt, self.best_params_)
223301

224302
self.weights_ = np.dot(
225-
pairwise.rbf_kernel(Xs, self.centers_, self.sigma_),
303+
pairwise.pairwise_kernels(Xs, self.centers_,
304+
metric=self.kernel,
305+
**self.best_params_),
226306
self.alphas_
227307
).ravel()
228308
return self.weights_
@@ -251,7 +331,9 @@ def predict_weights(self, X=None):
251331
else:
252332
X = check_array(X)
253333
weights = np.dot(
254-
pairwise.rbf_kernel(X, self.centers_, self.sigma_),
334+
pairwise.pairwise_kernels(X, self.centers_,
335+
metric=self.kernel,
336+
**self.best_params_),
255337
self.alphas_
256338
).ravel()
257339
return weights
@@ -260,15 +342,18 @@ def predict_weights(self, X=None):
260342
"call 'fit_weights' or 'fit' first.")
261343

262344

263-
def _fit(self, Xs, Xt, sigma):
345+
def _fit(self, Xs, Xt, kernel_params):
264346
index_centers = np.random.choice(
265347
len(Xt),
266348
min(len(Xt), self.max_centers),
267349
replace=False)
268350
centers = Xt[index_centers]
269-
270-
A = pairwise.rbf_kernel(Xt, centers, sigma)
271-
b = np.mean(pairwise.rbf_kernel(centers, Xs, sigma), axis=1)
351+
352+
A = pairwise.pairwise_kernels(Xt, centers, metric=self.kernel,
353+
**kernel_params)
354+
B = pairwise.pairwise_kernels(centers, Xs, metric=self.kernel,
355+
**kernel_params)
356+
b = np.mean(B, axis=1)
272357
b = b.reshape(-1, 1)
273358

274359
alpha = np.ones((len(centers), 1)) / len(centers)
@@ -297,7 +382,7 @@ def _fit(self, Xs, Xt, sigma):
297382
return alpha, centers
298383

299384

300-
def _cross_val_jscore(self, Xs, Xt, sigma, cv):
385+
def _cross_val_jscore(self, Xs, Xt, kernel_params, cv):
301386
split = int(len(Xt) / cv)
302387
cv_scores = []
303388
for i in range(cv):
@@ -308,13 +393,14 @@ def _cross_val_jscore(self, Xs, Xt, sigma, cv):
308393

309394
alphas, centers = self._fit(Xs,
310395
Xt[train_index],
311-
sigma)
396+
kernel_params)
312397

313398
j_score = np.mean(np.log(
314399
np.dot(
315-
pairwise.rbf_kernel(Xt[test_index],
316-
centers,
317-
sigma),
400+
pairwise.pairwise_kernels(Xt[test_index],
401+
centers,
402+
metric=self.kernel,
403+
**kernel_params),
318404
alphas
319405
) + EPS
320406
))

adapt/instance_based/_kmm.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,13 @@ class KMM(BaseAdaptEstimator):
5757
target data to the training set.
5858
5959
Parameters
60-
----------
60+
----------
61+
kernel : str (default="rbf")
62+
Kernel metric.
63+
Possible values: [‘additive_chi2’, ‘chi2’,
64+
‘linear’, ‘poly’, ‘polynomial’, ‘rbf’,
65+
‘laplacian’, ‘sigmoid’, ‘cosine’]
66+
6167
B: float (default=1000)
6268
Bounding weights parameter.
6369
@@ -69,12 +75,6 @@ class KMM(BaseAdaptEstimator):
6975
7076
with ``Xs`` the source input dataset.
7177
72-
kernel : str (default="rbf")
73-
Kernel metric.
74-
Possible values: [‘additive_chi2’, ‘chi2’,
75-
‘linear’, ‘poly’, ‘polynomial’, ‘rbf’,
76-
‘laplacian’, ‘sigmoid’, ‘cosine’]
77-
7878
max_size : int (default=1000)
7979
Batch computation to speed up the fitting.
8080
If ``len(Xs) > max_size``, KMM is applied
@@ -87,14 +87,6 @@ class KMM(BaseAdaptEstimator):
8787
8888
max_iter: int (default=100)
8989
Maximal iteration of the optimization.
90-
91-
Attributes
92-
----------
93-
weights_ : numpy array
94-
Training instance weights.
95-
96-
estimator_ : object
97-
Estimator.
9890
9991
Yields
10092
------
@@ -132,6 +124,14 @@ class KMM(BaseAdaptEstimator):
132124
kernel. (see formula in the ``gamma``
133125
parameter description)
134126
127+
Attributes
128+
----------
129+
weights_ : numpy array
130+
Training instance weights.
131+
132+
estimator_ : object
133+
Estimator.
134+
135135
Examples
136136
--------
137137
>>> import numpy as np
@@ -171,9 +171,9 @@ class KMM(BaseAdaptEstimator):
171171
def __init__(self,
172172
estimator=None,
173173
Xt=None,
174+
kernel="rbf",
174175
B=1000,
175176
eps=None,
176-
kernel="rbf",
177177
max_size=1000,
178178
tol=None,
179179
max_iter=100,

0 commit comments

Comments
 (0)