From 171b962cea369aee2513884a1fb3dca8920b77cd Mon Sep 17 00:00:00 2001 From: ievred Date: Tue, 31 Mar 2020 09:43:15 +0200 Subject: added jcpot --- ot/da.py | 288 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 287 insertions(+), 1 deletion(-) (limited to 'ot/da.py') diff --git a/ot/da.py b/ot/da.py index 108a38d..fd5da4b 100644 --- a/ot/da.py +++ b/ot/da.py @@ -13,13 +13,14 @@ Domain adaptation with optimal transport import numpy as np import scipy.linalg as linalg -from .bregman import sinkhorn +from .bregman import sinkhorn, projR, projC from .lp import emd from .utils import unif, dist, kernel, cost_normalization from .utils import check_params, BaseEstimator from .unbalanced import sinkhorn_unbalanced from .optim import cg from .optim import gcg +from functools import reduce def sinkhorn_lpl1_mm(a, labels_a, b, M, reg, eta=0.1, numItermax=10, @@ -745,6 +746,183 @@ def OT_mapping_linear(xs, xt, reg=1e-6, ws=None, return A, b +def jcpot_barycenter(Xs, Ys, Xt, reg, metric='sqeuclidean', numItermax=100, + stopThr=1e-6, verbose=False, log=False, **kwargs): + """Joint OT and proportion estimation as proposed in [27] + + The function solves the following optimization problem: + + .. math:: + + \mathbf{h} = \argmin_{\mathbf{h} \in \Delta_C}\quad \sum_{k=1}^K \lambda_k + W_{reg}\left((\mathbf{D}_2^{(k)} \mathbf{h})^T \mathbf{\delta}_{\mathbf{X}^{(k)}}, \mu\right) + + + s.t. \gamma^T_k \mathbf{1}_n = \mathbf{1}_n/n + + \mathbf{D}_1^{(k)} \gamma_k \mathbf{1}_n= \mathbf{h} + + \gamma\geq 0 + where : + + - M is the (ns,nt) squared euclidean cost matrix between samples in + Xs and Xt (scaled by ns) + - :math:`L` is a ns x d linear operator on a kernel matrix that + approximates the barycentric mapping + - a and b are uniform source and target weights + + The problem consist in solving jointly an optimal transport matrix + :math:`\gamma` and the nonlinear mapping that fits the barycentric mapping + :math:`n_s\gamma X_t`. + + One can also estimate a mapping with constant bias (see supplementary + material of [8]) using the bias optional argument. + + The algorithm used for solving the problem is the block coordinate + descent that alternates between updates of G (using conditional gradient) + and the update of L using a classical kernel least square solver. + + + Parameters + ---------- + xs : np.ndarray (ns,d) + samples in the source domain + xt : np.ndarray (nt,d) + samples in the target domain + mu : float,optional + Weight for the linear OT loss (>0) + eta : float, optional + Regularization term for the linear mapping L (>0) + kerneltype : str,optional + kernel used by calling function ot.utils.kernel (gaussian by default) + sigma : float, optional + Gaussian kernel bandwidth. + bias : bool,optional + Estimate linear mapping with constant bias + verbose : bool, optional + Print information along iterations + verbose2 : bool, optional + Print information along iterations + numItermax : int, optional + Max number of BCD iterations + numInnerItermax : int, optional + Max number of iterations (inner CG solver) + stopInnerThr : float, optional + Stop threshold on error (inner CG solver) (>0) + stopThr : float, optional + Stop threshold on relative loss decrease (>0) + log : bool, optional + record log if True + + + Returns + ------- + gamma : (ns x nt) ndarray + Optimal transportation matrix for the given parameters + L : (ns x d) ndarray + Nonlinear mapping matrix (ns+1 x d if bias) + log : dict + log dictionary return only if log==True in parameters + + + References + ---------- + + .. [8] M. Perrot, N. Courty, R. Flamary, A. Habrard, + "Mapping estimation for discrete optimal transport", + Neural Information Processing Systems (NIPS), 2016. + + See Also + -------- + ot.lp.emd : Unregularized OT + ot.optim.cg : General regularized OT + + """ + nbclasses = len(np.unique(Ys[0])) + nbdomains = len(Xs) + + # we then build, for each source domain, specific information + all_domains = [] + for d in range(nbdomains): + dom = {} + # get number of elements for this domain + nb_elem = Xs[d].shape[0] + dom['nbelem'] = nb_elem + classes = np.unique(Ys[d]) + + if np.min(classes) != 0: + Ys[d] = Ys[d] - np.min(classes) + classes = np.unique(Ys[d]) + + # build the corresponding D matrix + D1 = np.zeros((nbclasses, nb_elem)) + D2 = np.zeros((nbclasses, nb_elem)) + classes_d = np.zeros(nbclasses) + + classes_d[np.unique(Ys[d]).astype(int)] = 1 + dom['classes'] = classes_d + + for c in classes: + nbelemperclass = np.sum(Ys[d] == c) + if nbelemperclass != 0: + D1[int(c), Ys[d] == c] = 1. + D2[int(c), Ys[d] == c] = 1. / (nbelemperclass) # *nbclasses_d) + dom['D1'] = D1 + dom['D2'] = D2 + + # build the distance matrix + M = dist(Xs[d], Xt, metric=metric) + M = M / np.median(M) + + dom['K'] = np.exp(-M/reg) + + all_domains.append(dom) + + distribT = unif(np.shape(Xt)[0]) + + if log: + log = {'niter': 0, 'err': []} + + cpt = 0 + err = 1 + old_bary = np.ones((nbclasses)) + + while (err > stopThr and cpt < numItermax): + + bary = np.zeros((nbclasses)) + + for d in range(nbdomains): + all_domains[d]['K'] = projC(all_domains[d]['K'], distribT) + other = np.sum(all_domains[d]['K'], axis=1) + bary = bary + np.log(np.dot(all_domains[d]['D1'], other)) / nbdomains + + bary = np.exp(bary) + + for d in range(nbdomains): + new = np.dot(all_domains[d]['D2'].T, bary) + all_domains[d]['K'] = projR(all_domains[d]['K'], new) + + err = np.linalg.norm(bary - old_bary) + cpt = cpt + 1 + old_bary = bary + + if log: + log['err'].append(err) + + if verbose: + if cpt % 200 == 0: + print('{:5s}|{:12s}'.format('It.', 'Err') + '\n' + '-' * 19) + print('{:5d}|{:8e}|'.format(cpt, err)) + + bary = bary / np.sum(bary) + + if log: + log['niter'] = cpt + return bary, log + else: + return bary + + def distribution_estimation_uniform(X): """estimates a uniform distribution from an array of samples X @@ -1914,3 +2092,111 @@ class UnbalancedSinkhornTransport(BaseTransport): self.log_ = dict() return self + +class JCPOTTransport(BaseTransport): + + """Domain Adapatation OT method for target shift based on sinkhorn algorithm. + + Parameters + ---------- + reg_e : float, optional (default=1) + Entropic regularization parameter + max_iter : int, float, optional (default=10) + The minimum number of iteration before stopping the optimization + algorithm if no it has not converged + max_inner_iter : int, float, optional (default=200) + The number of iteration in the inner loop + tol : float, optional (default=10e-9) + Stop threshold on error (inner sinkhorn solver) (>0) + verbose : bool, optional (default=False) + Controls the verbosity of the optimization algorithm + log : bool, optional (default=False) + Controls the logs of the optimization algorithm + metric : string, optional (default="sqeuclidean") + The ground metric for the Wasserstein problem + norm : string, optional (default=None) + If given, normalize the ground metric to avoid numerical errors that + can occur with large metric values. + distribution_estimation : callable, optional (defaults to the uniform) + The kind of distribution estimation to employ + out_of_sample_map : string, optional (default="ferradans") + The kind of out of sample mapping to apply to transport samples + from a domain into another one. Currently the only possible option is + "ferradans" which uses the method proposed in [6]. + + Attributes + ---------- + coupling_ : array-like, shape (n_source_samples, n_target_samples) + The optimal coupling + log_ : dictionary + The dictionary of log, empty dic if parameter log is not True + + References + ---------- + + .. [1] N. Courty; R. Flamary; D. Tuia; A. Rakotomamonjy, + "Optimal Transport for Domain Adaptation," in IEEE + Transactions on Pattern Analysis and Machine Intelligence , + vol.PP, no.99, pp.1-1 + .. [2] Rakotomamonjy, A., Flamary, R., & Courty, N. (2015). + Generalized conditional gradient: analysis of convergence + and applications. arXiv preprint arXiv:1510.06567. + + """ + + def __init__(self, reg_e=.1, max_iter=10, + tol=10e-9, verbose=False, log=False, + metric="sqeuclidean", norm=None, + distribution_estimation=distribution_estimation_uniform, + out_of_sample_map='ferradans'): + + self.reg_e = reg_e + self.max_iter = max_iter + self.tol = tol + self.verbose = verbose + self.log = log + self.metric = metric + self.norm = norm + self.distribution_estimation = distribution_estimation + self.out_of_sample_map = out_of_sample_map + + def fit(self, Xs, ys=None, Xt=None, yt=None): + """Build a coupling matrix from source and target sets of samples + (Xs, ys) and (Xt, yt) + + Parameters + ---------- + Xs : array-like, shape (n_source_samples, n_features) + The training input samples. + ys : array-like, shape (n_source_samples,) + The class labels + Xt : array-like, shape (n_target_samples, n_features) + The training input samples. + yt : array-like, shape (n_target_samples,) + The class labels. If some target samples are unlabeled, fill the + yt's elements with -1. + + Warning: Note that, due to this convention -1 cannot be used as a + class label + + Returns + ------- + self : object + Returns self. + """ + + # check the necessary inputs parameters are here + if check_params(Xs=Xs, Xt=Xt, ys=ys): + + returned_ = jcpot_barycenter(Xs=Xs, Ys=ys, Xt=Xt, reg = self.reg_e, + metric=self.metric, numItermax=self.max_iter, stopThr=self.tol, + verbose=self.verbose, log=self.log) + + # deal with the value of log + if self.log: + self.coupling_, self.log_ = returned_ + else: + self.coupling_ = returned_ + self.log_ = dict() + + return self -- cgit v1.2.3 From 6aa0f1f4e275098948d4b312530119e5d95b8884 Mon Sep 17 00:00:00 2001 From: ievred Date: Tue, 31 Mar 2020 17:12:28 +0200 Subject: v1 jcpot example test --- examples/plot_otda_jcpot.py | 185 +++++++++++++++++++++++++++++++ ot/da.py | 263 ++++++++++++++++++++++++++------------------ test/test_da.py | 63 ++++++++++- 3 files changed, 404 insertions(+), 107 deletions(-) create mode 100644 examples/plot_otda_jcpot.py (limited to 'ot/da.py') diff --git a/examples/plot_otda_jcpot.py b/examples/plot_otda_jcpot.py new file mode 100644 index 0000000..5e5fff8 --- /dev/null +++ b/examples/plot_otda_jcpot.py @@ -0,0 +1,185 @@ +# -*- coding: utf-8 -*- +""" +======================== +OT for multi-source target shift +======================== + +This example introduces a target shift problem with two 2D source and 1 target domain. + +""" + +# Authors: Remi Flamary +# Ievgen Redko +# +# License: MIT License + +import pylab as pl +import numpy as np +import ot + +############################################################################## +# Generate data +# ------------- +n = 50 +sigma = 0.3 +np.random.seed(1985) + + +def get_data(n, p, dec): + y = np.concatenate((np.ones(int(p * n)), np.zeros(int((1 - p) * n)))) + x = np.hstack((0 * y[:, None] - 0, 1 - 2 * y[:, None])) + sigma * np.random.randn(len(y), 2) + + x[:, 0] += dec[0] + x[:, 1] += dec[1] + + return x, y + + +p1 = .2 +dec1 = [0, 2] + +p2 = .9 +dec2 = [0, -2] + +pt = .4 +dect = [4, 0] + +xs1, ys1 = get_data(n, p1, dec1) +xs2, ys2 = get_data(n + 1, p2, dec2) +xt, yt = get_data(n, pt, dect) +all_Xr = [xs1, xs2] +all_Yr = [ys1, ys2] +# %% +da = 1.5 + + +def plot_ax(dec, name): + pl.plot([dec[0], dec[0]], [dec[1] - da, dec[1] + da], 'k', alpha=0.5) + pl.plot([dec[0] - da, dec[0] + da], [dec[1], dec[1]], 'k', alpha=0.5) + pl.text(dec[0] - .5, dec[1] + 2, name) + + +############################################################################## +# Fig 1 : plots source and target samples +# --------------------------------------- + +pl.figure(1) +pl.clf() +plot_ax(dec1, 'Source 1') +plot_ax(dec2, 'Source 2') +plot_ax(dect, 'Target') +pl.scatter(xs1[:, 0], xs1[:, 1], c=ys1, s=35, marker='x', cmap='Set1', vmax=9, label='Source 1 (0.8,0.2)') +pl.scatter(xs2[:, 0], xs2[:, 1], c=ys2, s=35, marker='+', cmap='Set1', vmax=9, label='Source 2 (0.1,0.9)') +pl.scatter(xt[:, 0], xt[:, 1], c=yt, s=35, marker='o', cmap='Set1', vmax=9, label='Target (0.6,0.4)') +pl.title('Data') + +pl.legend() +pl.axis('equal') +pl.axis('off') + + +############################################################################## +# Instantiate Sinkhorn transport algorithm and fit them for all source domains +# ---------------------------------------------------------------------------- +ot_sinkhorn = ot.da.SinkhornTransport(reg_e=1e-2, metric='euclidean') + +M1 = ot.dist(xs1, xt, 'euclidean') +M2 = ot.dist(xs2, xt, 'euclidean') + + +def print_G(G, xs, ys, xt): + for i in range(G.shape[0]): + for j in range(G.shape[1]): + if G[i, j] > 5e-4: + if ys[i]: + c = 'b' + else: + c = 'r' + pl.plot([xs[i, 0], xt[j, 0]], [xs[i, 1], xt[j, 1]], c, alpha=.2) + + +############################################################################## +# Fig 2 : plot optimal couplings and transported samples +# ------------------------------------------------------ +pl.figure(2) +pl.clf() +plot_ax(dec1, 'Source 1') +plot_ax(dec2, 'Source 2') +plot_ax(dect, 'Target') +print_G(ot_sinkhorn.fit(Xs=xs1, Xt=xt).coupling_, xs1, ys1, xt) +print_G(ot_sinkhorn.fit(Xs=xs2, Xt=xt).coupling_, xs2, ys2, xt) +pl.scatter(xs1[:, 0], xs1[:, 1], c=ys1, s=35, marker='x', cmap='Set1', vmax=9) +pl.scatter(xs2[:, 0], xs2[:, 1], c=ys2, s=35, marker='+', cmap='Set1', vmax=9) +pl.scatter(xt[:, 0], xt[:, 1], c=yt, s=35, marker='o', cmap='Set1', vmax=9) + +pl.plot([], [], 'r', alpha=.2, label='Mass from Class 1') +pl.plot([], [], 'b', alpha=.2, label='Mass from Class 2') + +pl.title('Independent OT') + +pl.legend() +pl.axis('equal') +pl.axis('off') + + +############################################################################## +# Instantiate JCPOT adaptation algorithm and fit it +# ---------------------------------------------------------------------------- +otda = ot.da.JCPOTTransport(reg_e=1e-2, max_iter=1000, tol=1e-9, verbose=True, log=True) +otda.fit(all_Xr, all_Yr, xt) + +ws1 = otda.proportions_.dot(otda.log_['all_domains'][0]['D2']) +ws2 = otda.proportions_.dot(otda.log_['all_domains'][1]['D2']) + +pl.figure(3) +pl.clf() +plot_ax(dec1, 'Source 1') +plot_ax(dec2, 'Source 2') +plot_ax(dect, 'Target') +print_G(ot.bregman.sinkhorn(ws1, [], M1, reg=1e-2), xs1, ys1, xt) +print_G(ot.bregman.sinkhorn(ws2, [], M2, reg=1e-2), xs2, ys2, xt) +pl.scatter(xs1[:, 0], xs1[:, 1], c=ys1, s=35, marker='x', cmap='Set1', vmax=9) +pl.scatter(xs2[:, 0], xs2[:, 1], c=ys2, s=35, marker='+', cmap='Set1', vmax=9) +pl.scatter(xt[:, 0], xt[:, 1], c=yt, s=35, marker='o', cmap='Set1', vmax=9) + +pl.plot([], [], 'r', alpha=.2, label='Mass from Class 1') +pl.plot([], [], 'b', alpha=.2, label='Mass from Class 2') + +pl.title('OT with prop estimation ({:1.3f},{:1.3f})'.format(otda.proportions_[0], otda.proportions_[1])) + +pl.legend() +pl.axis('equal') +pl.axis('off') + +############################################################################## +# Run oracle transport algorithm with known proportions +# ---------------------------------------------------------------------------- + +otda = ot.da.JCPOTTransport(reg_e=0.01, max_iter=1000, tol=1e-9, verbose=True, log=True) +otda.fit(all_Xr, all_Yr, xt) + +h_res = np.array([1 - pt, pt]) + +ws1 = h_res.dot(otda.log_['all_domains'][0]['D2']) +ws2 = h_res.dot(otda.log_['all_domains'][1]['D2']) + +pl.figure(4) +pl.clf() +plot_ax(dec1, 'Source 1') +plot_ax(dec2, 'Source 2') +plot_ax(dect, 'Target') +print_G(ot.bregman.sinkhorn(ws1, [], M1, reg=1e-2), xs1, ys1, xt) +print_G(ot.bregman.sinkhorn(ws2, [], M2, reg=1e-2), xs2, ys2, xt) +pl.scatter(xs1[:, 0], xs1[:, 1], c=ys1, s=35, marker='x', cmap='Set1', vmax=9) +pl.scatter(xs2[:, 0], xs2[:, 1], c=ys2, s=35, marker='+', cmap='Set1', vmax=9) +pl.scatter(xt[:, 0], xt[:, 1], c=yt, s=35, marker='o', cmap='Set1', vmax=9) + +pl.plot([], [], 'r', alpha=.2, label='Mass from Class 1') +pl.plot([], [], 'b', alpha=.2, label='Mass from Class 2') + +pl.title('OT with known proportion ({:1.1f},{:1.1f})'.format(h_res[0], h_res[1])) + +pl.legend() +pl.axis('equal') +pl.axis('off') +pl.show() diff --git a/ot/da.py b/ot/da.py index fd5da4b..a3da8c1 100644 --- a/ot/da.py +++ b/ot/da.py @@ -748,79 +748,58 @@ def OT_mapping_linear(xs, xt, reg=1e-6, ws=None, def jcpot_barycenter(Xs, Ys, Xt, reg, metric='sqeuclidean', numItermax=100, stopThr=1e-6, verbose=False, log=False, **kwargs): - """Joint OT and proportion estimation as proposed in [27] + r'''Joint OT and proportion estimation for multi-source target shift as proposed in [27] The function solves the following optimization problem: .. math:: - \mathbf{h} = \argmin_{\mathbf{h} \in \Delta_C}\quad \sum_{k=1}^K \lambda_k - W_{reg}\left((\mathbf{D}_2^{(k)} \mathbf{h})^T \mathbf{\delta}_{\mathbf{X}^{(k)}}, \mu\right) + \mathbf{h} = arg\min_{\mathbf{h}}\quad \sum_{k=1}^{K} \lambda_k + W_{reg}((\mathbf{D}_2^{(k)} \mathbf{h})^T, \mathbf{a}) + s.t. \ \forall k, \mathbf{D}_1^{(k)} \gamma_k \mathbf{1}_n= \mathbf{h} - s.t. \gamma^T_k \mathbf{1}_n = \mathbf{1}_n/n - - \mathbf{D}_1^{(k)} \gamma_k \mathbf{1}_n= \mathbf{h} - - \gamma\geq 0 where : - - M is the (ns,nt) squared euclidean cost matrix between samples in - Xs and Xt (scaled by ns) - - :math:`L` is a ns x d linear operator on a kernel matrix that - approximates the barycentric mapping - - a and b are uniform source and target weights - - The problem consist in solving jointly an optimal transport matrix - :math:`\gamma` and the nonlinear mapping that fits the barycentric mapping - :math:`n_s\gamma X_t`. + - :math:`\lambda_k` is the weight of k-th source domain + - :math:`W_{reg}(\cdot,\cdot)` is the entropic regularized Wasserstein distance (see ot.bregman.sinkhorn) + - :math:`\mathbf{D}_2^{(k)}` is a matrix of weights related to k-th source domain defined as in [p. 5, 27], its expected shape is `(n_k, C)` where `n_k` is the number of elements in the k-th source domain and `C` is the number of classes + - :math:`\mathbf{h}` is a vector of estimated proportions in the target domain of size C + - :math:`\mathbf{a}` is a uniform vector of weights in the target domain of size `n` + - :math:`\mathbf{D}_1^{(k)}` is a matrix of class assignments defined as in [p. 5, 27], its expected shape is `(n_k, C)` - One can also estimate a mapping with constant bias (see supplementary - material of [8]) using the bias optional argument. - - The algorithm used for solving the problem is the block coordinate - descent that alternates between updates of G (using conditional gradient) - and the update of L using a classical kernel least square solver. + The problem consist in solving a Wasserstein barycenter problem to estimate the proportions :math:`\mathbf{h}` in the target domain. + The algorithm used for solving the problem is the Iterative Bregman projections algorithm + with two sets of marginal constraints related to the unknown vector :math:`\mathbf{h}` and uniform tarhet distribution. Parameters ---------- - xs : np.ndarray (ns,d) - samples in the source domain - xt : np.ndarray (nt,d) + Xs : list of K np.ndarray(nsk,d) + features of all source domains' samples + Ys : list of K np.ndarray(nsk,) + labels of all source domains' samples + Xt : np.ndarray (nt,d) samples in the target domain - mu : float,optional - Weight for the linear OT loss (>0) - eta : float, optional - Regularization term for the linear mapping L (>0) - kerneltype : str,optional - kernel used by calling function ot.utils.kernel (gaussian by default) - sigma : float, optional - Gaussian kernel bandwidth. - bias : bool,optional - Estimate linear mapping with constant bias - verbose : bool, optional - Print information along iterations - verbose2 : bool, optional - Print information along iterations + reg : float + Regularization term > 0 + metric : string, optional (default="sqeuclidean") + The ground metric for the Wasserstein problem numItermax : int, optional - Max number of BCD iterations - numInnerItermax : int, optional - Max number of iterations (inner CG solver) - stopInnerThr : float, optional - Stop threshold on error (inner CG solver) (>0) + Max number of iterations stopThr : float, optional - Stop threshold on relative loss decrease (>0) + Stop threshold on relative change in the barycenter (>0) log : bool, optional record log if True - + verbose : bool, optional (default=False) + Controls the verbosity of the optimization algorithm Returns ------- - gamma : (ns x nt) ndarray - Optimal transportation matrix for the given parameters - L : (ns x d) ndarray - Nonlinear mapping matrix (ns+1 x d if bias) + gamma : List of K (nsk x nt) ndarrays + Optimal transportation matrices for the given parameters for each pair of source and target domains + h : (C,) ndarray + proportion estimation in the target domain log : dict log dictionary return only if log==True in parameters @@ -828,62 +807,59 @@ def jcpot_barycenter(Xs, Ys, Xt, reg, metric='sqeuclidean', numItermax=100, References ---------- - .. [8] M. Perrot, N. Courty, R. Flamary, A. Habrard, - "Mapping estimation for discrete optimal transport", - Neural Information Processing Systems (NIPS), 2016. - - See Also - -------- - ot.lp.emd : Unregularized OT - ot.optim.cg : General regularized OT + .. [27] Ievgen Redko, Nicolas Courty, Rémi Flamary, Devis Tuia + "Optimal transport for multi-source domain adaptation under target shift", + International Conference on Artificial Intelligence and Statistics (AISTATS), 2019. - """ + ''' nbclasses = len(np.unique(Ys[0])) nbdomains = len(Xs) - # we then build, for each source domain, specific information + # For each source domain, build cost matrices M, Gibbs kernels K and corresponding matrices D_1 and D_2 all_domains = [] + + # log dictionary + if log: + log = {'niter': 0, 'err': [], 'all_domains': []} + for d in range(nbdomains): dom = {} - # get number of elements for this domain - nb_elem = Xs[d].shape[0] - dom['nbelem'] = nb_elem - classes = np.unique(Ys[d]) + nsk = Xs[d].shape[0] # get number of elements for this domain + dom['nbelem'] = nsk + classes = np.unique(Ys[d]) # get number of classes for this domain + # format classes to start from 0 for convenience if np.min(classes) != 0: Ys[d] = Ys[d] - np.min(classes) classes = np.unique(Ys[d]) - # build the corresponding D matrix - D1 = np.zeros((nbclasses, nb_elem)) - D2 = np.zeros((nbclasses, nb_elem)) - classes_d = np.zeros(nbclasses) - - classes_d[np.unique(Ys[d]).astype(int)] = 1 - dom['classes'] = classes_d + # build the corresponding D_1 and D_2 matrices + D1 = np.zeros((nbclasses, nsk)) + D2 = np.zeros((nbclasses, nsk)) for c in classes: nbelemperclass = np.sum(Ys[d] == c) if nbelemperclass != 0: D1[int(c), Ys[d] == c] = 1. - D2[int(c), Ys[d] == c] = 1. / (nbelemperclass) # *nbclasses_d) + D2[int(c), Ys[d] == c] = 1. / (nbelemperclass) dom['D1'] = D1 dom['D2'] = D2 - # build the distance matrix + # build the cost matrix and the Gibbs kernel M = dist(Xs[d], Xt, metric=metric) M = M / np.median(M) - dom['K'] = np.exp(-M/reg) + K = np.empty(M.shape, dtype=M.dtype) + np.divide(M, -reg, out=K) + np.exp(K, out=K) + dom['K'] = K all_domains.append(dom) - distribT = unif(np.shape(Xt)[0]) - - if log: - log = {'niter': 0, 'err': []} + # uniform target distribution + a = unif(np.shape(Xt)[0]) - cpt = 0 + cpt = 0 # iterations count err = 1 old_bary = np.ones((nbclasses)) @@ -891,13 +867,15 @@ def jcpot_barycenter(Xs, Ys, Xt, reg, metric='sqeuclidean', numItermax=100, bary = np.zeros((nbclasses)) + # update coupling matrices for marginal constraints w.r.t. uniform target distribution for d in range(nbdomains): - all_domains[d]['K'] = projC(all_domains[d]['K'], distribT) + all_domains[d]['K'] = projC(all_domains[d]['K'], a) other = np.sum(all_domains[d]['K'], axis=1) bary = bary + np.log(np.dot(all_domains[d]['D1'], other)) / nbdomains bary = np.exp(bary) + # update coupling matrices for marginal constraints w.r.t. unknown proportions based on [Prop 4., 27] for d in range(nbdomains): new = np.dot(all_domains[d]['D2'].T, bary) all_domains[d]['K'] = projR(all_domains[d]['K'], new) @@ -915,12 +893,14 @@ def jcpot_barycenter(Xs, Ys, Xt, reg, metric='sqeuclidean', numItermax=100, print('{:5d}|{:8e}|'.format(cpt, err)) bary = bary / np.sum(bary) + couplings = [all_domains[d]['K'] for d in range(nbdomains)] if log: log['niter'] = cpt - return bary, log + log['all_domains'] = all_domains + return couplings, bary, log else: - return bary + return couplings, bary def distribution_estimation_uniform(X): @@ -2093,9 +2073,10 @@ class UnbalancedSinkhornTransport(BaseTransport): return self + class JCPOTTransport(BaseTransport): - """Domain Adapatation OT method for target shift based on sinkhorn algorithm. + """Domain Adapatation OT method for multi-source target shift based on Wasserstein barycenter algorithm. Parameters ---------- @@ -2104,8 +2085,6 @@ class JCPOTTransport(BaseTransport): max_iter : int, float, optional (default=10) The minimum number of iteration before stopping the optimization algorithm if no it has not converged - max_inner_iter : int, float, optional (default=200) - The number of iteration in the inner loop tol : float, optional (default=10e-9) Stop threshold on error (inner sinkhorn solver) (>0) verbose : bool, optional (default=False) @@ -2126,21 +2105,20 @@ class JCPOTTransport(BaseTransport): Attributes ---------- - coupling_ : array-like, shape (n_source_samples, n_target_samples) - The optimal coupling + coupling_ : list of array-like objects, shape K x (n_source_samples, n_target_samples) + A set of optimal couplings between each source domain and the target domain + proportions_ : array-like, shape (n_classes,) + Estimated class proportions in the target domain log_ : dictionary The dictionary of log, empty dic if parameter log is not True References ---------- - .. [1] N. Courty; R. Flamary; D. Tuia; A. Rakotomamonjy, - "Optimal Transport for Domain Adaptation," in IEEE - Transactions on Pattern Analysis and Machine Intelligence , - vol.PP, no.99, pp.1-1 - .. [2] Rakotomamonjy, A., Flamary, R., & Courty, N. (2015). - Generalized conditional gradient: analysis of convergence - and applications. arXiv preprint arXiv:1510.06567. + .. [1] Ievgen Redko, Nicolas Courty, Rémi Flamary, Devis Tuia + "Optimal transport for multi-source domain adaptation under target shift", + International Conference on Artificial Intelligence and Statistics (AISTATS), + vol. 89, p.849-858, 2019. """ @@ -2156,20 +2134,18 @@ class JCPOTTransport(BaseTransport): self.verbose = verbose self.log = log self.metric = metric - self.norm = norm - self.distribution_estimation = distribution_estimation self.out_of_sample_map = out_of_sample_map def fit(self, Xs, ys=None, Xt=None, yt=None): - """Build a coupling matrix from source and target sets of samples + """Building coupling matrices from a list of source and target sets of samples (Xs, ys) and (Xt, yt) Parameters ---------- - Xs : array-like, shape (n_source_samples, n_features) - The training input samples. - ys : array-like, shape (n_source_samples,) - The class labels + Xs : list of K array-like objects, shape K x (nk_source_samples, n_features) + A list of the training input samples. + ys : list of K array-like objects, shape K x (nk_source_samples,) + A list of the class labels Xt : array-like, shape (n_target_samples, n_features) The training input samples. yt : array-like, shape (n_target_samples,) @@ -2188,15 +2164,90 @@ class JCPOTTransport(BaseTransport): # check the necessary inputs parameters are here if check_params(Xs=Xs, Xt=Xt, ys=ys): - returned_ = jcpot_barycenter(Xs=Xs, Ys=ys, Xt=Xt, reg = self.reg_e, - metric=self.metric, numItermax=self.max_iter, stopThr=self.tol, + self.xs_ = Xs + self.xt_ = Xt + + returned_ = jcpot_barycenter(Xs=Xs, Ys=ys, Xt=Xt, reg=self.reg_e, + metric=self.metric, distrinumItermax=self.max_iter, stopThr=self.tol, verbose=self.verbose, log=self.log) # deal with the value of log if self.log: - self.coupling_, self.log_ = returned_ + self.coupling_, self.proportions_, self.log_ = returned_ else: - self.coupling_ = returned_ + self.coupling_, self.proportions_ = returned_ self.log_ = dict() return self + + def transform(self, Xs=None, ys=None, Xt=None, yt=None, batch_size=128): + """Transports source samples Xs onto target ones Xt + + Parameters + ---------- + Xs : array-like, shape (n_source_samples, n_features) + The training input samples. + ys : array-like, shape (n_source_samples,) + The class labels + Xt : array-like, shape (n_target_samples, n_features) + The training input samples. + yt : array-like, shape (n_target_samples,) + The class labels. If some target samples are unlabeled, fill the + yt's elements with -1. + + Warning: Note that, due to this convention -1 cannot be used as a + class label + batch_size : int, optional (default=128) + The batch size for out of sample inverse transform + """ + + transp_Xs = [] + + # check the necessary inputs parameters are here + if check_params(Xs=Xs): + + if all([np.allclose(x, y) for x, y in zip(self.xs_, Xs)]): + + # perform standard barycentric mapping for each source domain + + for coupling in self.coupling_: + transp = coupling / np.sum(coupling, 1)[:, None] + + # set nans to 0 + transp[~ np.isfinite(transp)] = 0 + + # compute transported samples + transp_Xs.append(np.dot(transp, self.xt_)) + else: + + # perform out of sample mapping + indices = np.arange(Xs.shape[0]) + batch_ind = [ + indices[i:i + batch_size] + for i in range(0, len(indices), batch_size)] + + transp_Xs = [] + + for bi in batch_ind: + transp_Xs_ = [] + + # get the nearest neighbor in the sources domains + xs = np.concatenate(self.xs_, axis=0) + idx = np.argmin(dist(Xs[bi], xs), axis=1) + + # transport the source samples + for coupling in self.coupling_: + transp = coupling / np.sum( + coupling, 1)[:, None] + transp[~ np.isfinite(transp)] = 0 + transp_Xs_.append(np.dot(transp, self.xt_)) + + transp_Xs_ = np.concatenate(transp_Xs_, axis=0) + + # define the transported points + transp_Xs_ = transp_Xs_[idx, :] + Xs[bi] - xs[idx, :] + transp_Xs.append(transp_Xs_) + + transp_Xs = np.concatenate(transp_Xs, axis=0) + + return transp_Xs diff --git a/test/test_da.py b/test/test_da.py index 2a5e50e..a8c258a 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -5,7 +5,7 @@ # License: MIT License import numpy as np -from numpy.testing.utils import assert_allclose, assert_equal +from numpy.testing import assert_allclose, assert_equal import ot from ot.datasets import make_data_classif @@ -549,3 +549,64 @@ def test_linear_mapping_class(): Cst = np.cov(Xst.T) np.testing.assert_allclose(Ct, Cst, rtol=1e-2, atol=1e-2) + + +def test_jcpot_transport_class(): + """test_jcpot_transport + """ + + ns1 = 150 + ns2 = 150 + nt = 200 + + Xs1, ys1 = make_data_classif('3gauss', ns1) + Xs2, ys2 = make_data_classif('3gauss', ns2) + + Xt, yt = make_data_classif('3gauss2', nt) + + Xs = [Xs1, Xs2] + ys = [ys1, ys2] + + otda = ot.da.JCPOTTransport(reg_e=0.01, max_iter=1000, tol=1e-9, verbose=True) + + # test its computed + otda.fit(Xs=Xs, ys=ys, Xt=Xt) + print(otda.proportions_) + + assert hasattr(otda, "coupling_") + assert hasattr(otda, "proportions_") + assert hasattr(otda, "log_") + + # test dimensions of coupling + for i, xs in enumerate(Xs): + assert_equal(otda.coupling_[i].shape, ((xs.shape[0], Xt.shape[0]))) + + # test all margin constraints + mu_t = unif(nt) + + for i in range(len(Xs)): + # test margin constraints w.r.t. uniform target weights for each coupling matrix + assert_allclose( + np.sum(otda.coupling_[i], axis=0), mu_t, rtol=1e-3, atol=1e-3) + + # test margin constraints w.r.t. modified source weights for each source domain + + D1 = np.zeros((len(np.unique(ys[i])), len(ys[i]))) + for c in np.unique(ys[i]): + nbelemperclass = np.sum(ys[i] == c) + if nbelemperclass != 0: + D1[int(c), ys[i] == c] = 1. + + assert_allclose( + np.dot(D1, np.sum(otda.coupling_[i], axis=1)), otda.proportions_, rtol=1e-3, atol=1e-3) + + # test transform + transp_Xs = otda.transform(Xs=Xs) + [assert_equal(x.shape, y.shape) for x, y in zip(transp_Xs, Xs)] + #assert_equal(transp_Xs.shape, Xs.shape) + + Xs_new, _ = make_data_classif('3gauss', ns1 + 1) + transp_Xs_new = otda.transform(Xs_new) + + # check that the oos method is working + assert_equal(transp_Xs_new.shape, Xs_new.shape) -- cgit v1.2.3 From ba493aa5488507937b7f9707faa17128c9aa1872 Mon Sep 17 00:00:00 2001 From: ievred Date: Tue, 31 Mar 2020 17:36:00 +0200 Subject: readme move to bregman --- README.md | 3 + ot/bregman.py | 157 +++++++++++++++++++++++++++++++++++++++++++++- ot/da.py | 190 ++++---------------------------------------------------- test/test_da.py | 2 +- 4 files changed, 171 insertions(+), 181 deletions(-) (limited to 'ot/da.py') diff --git a/README.md b/README.md index c115776..f439405 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ It provides the following solvers: * Non regularized free support Wasserstein barycenters [20]. * Unbalanced OT with KL relaxation distance and barycenter [10, 25]. * Screening Sinkhorn Algorithm for OT [26]. +* JCPOT algorithm for multi-source target shift [27]. Some demonstrations (both in Python and Jupyter Notebook format) are available in the examples folder. @@ -257,3 +258,5 @@ You can also post bug reports and feature requests in Github issues. Make sure t [25] Frogner C., Zhang C., Mobahi H., Araya-Polo M., Poggio T. (2015). [Learning with a Wasserstein Loss](http://cbcl.mit.edu/wasserstein/) Advances in Neural Information Processing Systems (NIPS). [26] Alaya M. Z., Bérar M., Gasso G., Rakotomamonjy A. (2019). [Screening Sinkhorn Algorithm for Regularized Optimal Transport](https://papers.nips.cc/paper/9386-screening-sinkhorn-algorithm-for-regularized-optimal-transport), Advances in Neural Information Processing Systems 33 (NeurIPS). + +[27] Redko I., Courty N., Flamary R., Tuia D. (2019). [Optimal Transport for Multi-source Domain Adaptation under Target Shift](http://proceedings.mlr.press/v89/redko19a.html), Proceedings of the Twenty-Second International Conference on Artificial Intelligence and Statistics (AISTATS) 22, 2019. \ No newline at end of file diff --git a/ot/bregman.py b/ot/bregman.py index d5e3563..d17aaf0 100644 --- a/ot/bregman.py +++ b/ot/bregman.py @@ -10,6 +10,7 @@ Bregman projections for regularized OT # Hicham Janati # Mokhtar Z. Alaya # Alexander Tong +# Ievgen Redko # # License: MIT License @@ -18,7 +19,6 @@ import warnings from .utils import unif, dist from scipy.optimize import fmin_l_bfgs_b - def sinkhorn(a, b, M, reg, method='sinkhorn', numItermax=1000, stopThr=1e-9, verbose=False, log=False, **kwargs): r""" @@ -1501,6 +1501,161 @@ def unmix(a, D, M, M0, h0, reg, reg0, alpha, numItermax=1000, else: return np.sum(K0, axis=1) +def jcpot_barycenter(Xs, Ys, Xt, reg, metric='sqeuclidean', numItermax=100, + stopThr=1e-6, verbose=False, log=False, **kwargs): + r'''Joint OT and proportion estimation for multi-source target shift as proposed in [27] + + The function solves the following optimization problem: + + .. math:: + + \mathbf{h} = arg\min_{\mathbf{h}}\quad \sum_{k=1}^{K} \lambda_k + W_{reg}((\mathbf{D}_2^{(k)} \mathbf{h})^T, \mathbf{a}) + + s.t. \ \forall k, \mathbf{D}_1^{(k)} \gamma_k \mathbf{1}_n= \mathbf{h} + + where : + + - :math:`\lambda_k` is the weight of k-th source domain + - :math:`W_{reg}(\cdot,\cdot)` is the entropic regularized Wasserstein distance (see ot.bregman.sinkhorn) + - :math:`\mathbf{D}_2^{(k)}` is a matrix of weights related to k-th source domain defined as in [p. 5, 27], its expected shape is `(n_k, C)` where `n_k` is the number of elements in the k-th source domain and `C` is the number of classes + - :math:`\mathbf{h}` is a vector of estimated proportions in the target domain of size C + - :math:`\mathbf{a}` is a uniform vector of weights in the target domain of size `n` + - :math:`\mathbf{D}_1^{(k)}` is a matrix of class assignments defined as in [p. 5, 27], its expected shape is `(n_k, C)` + + The problem consist in solving a Wasserstein barycenter problem to estimate the proportions :math:`\mathbf{h}` in the target domain. + + The algorithm used for solving the problem is the Iterative Bregman projections algorithm + with two sets of marginal constraints related to the unknown vector :math:`\mathbf{h}` and uniform tarhet distribution. + + Parameters + ---------- + Xs : list of K np.ndarray(nsk,d) + features of all source domains' samples + Ys : list of K np.ndarray(nsk,) + labels of all source domains' samples + Xt : np.ndarray (nt,d) + samples in the target domain + reg : float + Regularization term > 0 + metric : string, optional (default="sqeuclidean") + The ground metric for the Wasserstein problem + numItermax : int, optional + Max number of iterations + stopThr : float, optional + Stop threshold on relative change in the barycenter (>0) + log : bool, optional + record log if True + verbose : bool, optional (default=False) + Controls the verbosity of the optimization algorithm + + Returns + ------- + gamma : List of K (nsk x nt) ndarrays + Optimal transportation matrices for the given parameters for each pair of source and target domains + h : (C,) ndarray + proportion estimation in the target domain + log : dict + log dictionary return only if log==True in parameters + + + References + ---------- + + .. [27] Ievgen Redko, Nicolas Courty, Rémi Flamary, Devis Tuia + "Optimal transport for multi-source domain adaptation under target shift", + International Conference on Artificial Intelligence and Statistics (AISTATS), 2019. + + ''' + nbclasses = len(np.unique(Ys[0])) + nbdomains = len(Xs) + + # For each source domain, build cost matrices M, Gibbs kernels K and corresponding matrices D_1 and D_2 + all_domains = [] + + # log dictionary + if log: + log = {'niter': 0, 'err': [], 'all_domains': []} + + for d in range(nbdomains): + dom = {} + nsk = Xs[d].shape[0] # get number of elements for this domain + dom['nbelem'] = nsk + classes = np.unique(Ys[d]) # get number of classes for this domain + + # format classes to start from 0 for convenience + if np.min(classes) != 0: + Ys[d] = Ys[d] - np.min(classes) + classes = np.unique(Ys[d]) + + # build the corresponding D_1 and D_2 matrices + D1 = np.zeros((nbclasses, nsk)) + D2 = np.zeros((nbclasses, nsk)) + + for c in classes: + nbelemperclass = np.sum(Ys[d] == c) + if nbelemperclass != 0: + D1[int(c), Ys[d] == c] = 1. + D2[int(c), Ys[d] == c] = 1. / (nbelemperclass) + dom['D1'] = D1 + dom['D2'] = D2 + + # build the cost matrix and the Gibbs kernel + M = dist(Xs[d], Xt, metric=metric) + M = M / np.median(M) + + K = np.empty(M.shape, dtype=M.dtype) + np.divide(M, -reg, out=K) + np.exp(K, out=K) + dom['K'] = K + + all_domains.append(dom) + + # uniform target distribution + a = unif(np.shape(Xt)[0]) + + cpt = 0 # iterations count + err = 1 + old_bary = np.ones((nbclasses)) + + while (err > stopThr and cpt < numItermax): + + bary = np.zeros((nbclasses)) + + # update coupling matrices for marginal constraints w.r.t. uniform target distribution + for d in range(nbdomains): + all_domains[d]['K'] = projC(all_domains[d]['K'], a) + other = np.sum(all_domains[d]['K'], axis=1) + bary = bary + np.log(np.dot(all_domains[d]['D1'], other)) / nbdomains + + bary = np.exp(bary) + + # update coupling matrices for marginal constraints w.r.t. unknown proportions based on [Prop 4., 27] + for d in range(nbdomains): + new = np.dot(all_domains[d]['D2'].T, bary) + all_domains[d]['K'] = projR(all_domains[d]['K'], new) + + err = np.linalg.norm(bary - old_bary) + cpt = cpt + 1 + old_bary = bary + + if log: + log['err'].append(err) + + if verbose: + if cpt % 200 == 0: + print('{:5s}|{:12s}'.format('It.', 'Err') + '\n' + '-' * 19) + print('{:5d}|{:8e}|'.format(cpt, err)) + + bary = bary / np.sum(bary) + couplings = [all_domains[d]['K'] for d in range(nbdomains)] + + if log: + log['niter'] = cpt + log['all_domains'] = all_domains + return couplings, bary, log + else: + return couplings, bary def empirical_sinkhorn(X_s, X_t, reg, a=None, b=None, metric='sqeuclidean', numIterMax=10000, stopThr=1e-9, verbose=False, diff --git a/ot/da.py b/ot/da.py index a3da8c1..a9c3cea 100644 --- a/ot/da.py +++ b/ot/da.py @@ -7,20 +7,20 @@ Domain adaptation with optimal transport # Nicolas Courty # Michael Perrot # Nathalie Gayraud +# Ievgen Redko # # License: MIT License import numpy as np import scipy.linalg as linalg -from .bregman import sinkhorn, projR, projC +from .bregman import sinkhorn from .lp import emd from .utils import unif, dist, kernel, cost_normalization from .utils import check_params, BaseEstimator from .unbalanced import sinkhorn_unbalanced from .optim import cg from .optim import gcg -from functools import reduce def sinkhorn_lpl1_mm(a, labels_a, b, M, reg, eta=0.1, numItermax=10, @@ -128,7 +128,7 @@ def sinkhorn_lpl1_mm(a, labels_a, b, M, reg, eta=0.1, numItermax=10, W = np.ones(M.shape) for (i, c) in enumerate(classes): majs = np.sum(transp[indices_labels[i]], axis=0) - majs = p * ((majs + epsilon)**(p - 1)) + majs = p * ((majs + epsilon) ** (p - 1)) W[indices_labels[i]] = majs return transp @@ -360,8 +360,8 @@ def joint_OT_mapping_linear(xs, xt, mu=1, eta=0.001, bias=False, verbose=False, def loss(L, G): """Compute full loss""" - return np.sum((xs1.dot(L) - ns * G.dot(xt))**2) + mu * \ - np.sum(G * M) + eta * np.sum(sel(L - I0)**2) + return np.sum((xs1.dot(L) - ns * G.dot(xt)) ** 2) + mu * \ + np.sum(G * M) + eta * np.sum(sel(L - I0) ** 2) def solve_L(G): """ solve L problem with fixed G (least square)""" @@ -373,10 +373,11 @@ def joint_OT_mapping_linear(xs, xt, mu=1, eta=0.001, bias=False, verbose=False, xsi = xs1.dot(L) def f(G): - return np.sum((xsi - ns * G.dot(xt))**2) + return np.sum((xsi - ns * G.dot(xt)) ** 2) def df(G): return -2 * ns * (xsi - ns * G.dot(xt)).dot(xt.T) + G = cg(a, b, M, 1.0 / mu, f, df, G0=G0, numItermax=numInnerItermax, stopThr=stopInnerThr) return G @@ -563,8 +564,8 @@ def joint_OT_mapping_kernel(xs, xt, mu=1, eta=0.001, kerneltype='gaussian', def loss(L, G): """Compute full loss""" - return np.sum((K1.dot(L) - ns * G.dot(xt))**2) + mu * \ - np.sum(G * M) + eta * np.trace(L.T.dot(Kreg).dot(L)) + return np.sum((K1.dot(L) - ns * G.dot(xt)) ** 2) + mu * \ + np.sum(G * M) + eta * np.trace(L.T.dot(Kreg).dot(L)) def solve_L_nobias(G): """ solve L problem with fixed G (least square)""" @@ -581,10 +582,11 @@ def joint_OT_mapping_kernel(xs, xt, mu=1, eta=0.001, kerneltype='gaussian', xsi = K1.dot(L) def f(G): - return np.sum((xsi - ns * G.dot(xt))**2) + return np.sum((xsi - ns * G.dot(xt)) ** 2) def df(G): return -2 * ns * (xsi - ns * G.dot(xt)).dot(xt.T) + G = cg(a, b, M, 1.0 / mu, f, df, G0=G0, numItermax=numInnerItermax, stopThr=stopInnerThr) return G @@ -746,163 +748,6 @@ def OT_mapping_linear(xs, xt, reg=1e-6, ws=None, return A, b -def jcpot_barycenter(Xs, Ys, Xt, reg, metric='sqeuclidean', numItermax=100, - stopThr=1e-6, verbose=False, log=False, **kwargs): - r'''Joint OT and proportion estimation for multi-source target shift as proposed in [27] - - The function solves the following optimization problem: - - .. math:: - - \mathbf{h} = arg\min_{\mathbf{h}}\quad \sum_{k=1}^{K} \lambda_k - W_{reg}((\mathbf{D}_2^{(k)} \mathbf{h})^T, \mathbf{a}) - - s.t. \ \forall k, \mathbf{D}_1^{(k)} \gamma_k \mathbf{1}_n= \mathbf{h} - - where : - - - :math:`\lambda_k` is the weight of k-th source domain - - :math:`W_{reg}(\cdot,\cdot)` is the entropic regularized Wasserstein distance (see ot.bregman.sinkhorn) - - :math:`\mathbf{D}_2^{(k)}` is a matrix of weights related to k-th source domain defined as in [p. 5, 27], its expected shape is `(n_k, C)` where `n_k` is the number of elements in the k-th source domain and `C` is the number of classes - - :math:`\mathbf{h}` is a vector of estimated proportions in the target domain of size C - - :math:`\mathbf{a}` is a uniform vector of weights in the target domain of size `n` - - :math:`\mathbf{D}_1^{(k)}` is a matrix of class assignments defined as in [p. 5, 27], its expected shape is `(n_k, C)` - - The problem consist in solving a Wasserstein barycenter problem to estimate the proportions :math:`\mathbf{h}` in the target domain. - - The algorithm used for solving the problem is the Iterative Bregman projections algorithm - with two sets of marginal constraints related to the unknown vector :math:`\mathbf{h}` and uniform tarhet distribution. - - Parameters - ---------- - Xs : list of K np.ndarray(nsk,d) - features of all source domains' samples - Ys : list of K np.ndarray(nsk,) - labels of all source domains' samples - Xt : np.ndarray (nt,d) - samples in the target domain - reg : float - Regularization term > 0 - metric : string, optional (default="sqeuclidean") - The ground metric for the Wasserstein problem - numItermax : int, optional - Max number of iterations - stopThr : float, optional - Stop threshold on relative change in the barycenter (>0) - log : bool, optional - record log if True - verbose : bool, optional (default=False) - Controls the verbosity of the optimization algorithm - - Returns - ------- - gamma : List of K (nsk x nt) ndarrays - Optimal transportation matrices for the given parameters for each pair of source and target domains - h : (C,) ndarray - proportion estimation in the target domain - log : dict - log dictionary return only if log==True in parameters - - - References - ---------- - - .. [27] Ievgen Redko, Nicolas Courty, Rémi Flamary, Devis Tuia - "Optimal transport for multi-source domain adaptation under target shift", - International Conference on Artificial Intelligence and Statistics (AISTATS), 2019. - - ''' - nbclasses = len(np.unique(Ys[0])) - nbdomains = len(Xs) - - # For each source domain, build cost matrices M, Gibbs kernels K and corresponding matrices D_1 and D_2 - all_domains = [] - - # log dictionary - if log: - log = {'niter': 0, 'err': [], 'all_domains': []} - - for d in range(nbdomains): - dom = {} - nsk = Xs[d].shape[0] # get number of elements for this domain - dom['nbelem'] = nsk - classes = np.unique(Ys[d]) # get number of classes for this domain - - # format classes to start from 0 for convenience - if np.min(classes) != 0: - Ys[d] = Ys[d] - np.min(classes) - classes = np.unique(Ys[d]) - - # build the corresponding D_1 and D_2 matrices - D1 = np.zeros((nbclasses, nsk)) - D2 = np.zeros((nbclasses, nsk)) - - for c in classes: - nbelemperclass = np.sum(Ys[d] == c) - if nbelemperclass != 0: - D1[int(c), Ys[d] == c] = 1. - D2[int(c), Ys[d] == c] = 1. / (nbelemperclass) - dom['D1'] = D1 - dom['D2'] = D2 - - # build the cost matrix and the Gibbs kernel - M = dist(Xs[d], Xt, metric=metric) - M = M / np.median(M) - - K = np.empty(M.shape, dtype=M.dtype) - np.divide(M, -reg, out=K) - np.exp(K, out=K) - dom['K'] = K - - all_domains.append(dom) - - # uniform target distribution - a = unif(np.shape(Xt)[0]) - - cpt = 0 # iterations count - err = 1 - old_bary = np.ones((nbclasses)) - - while (err > stopThr and cpt < numItermax): - - bary = np.zeros((nbclasses)) - - # update coupling matrices for marginal constraints w.r.t. uniform target distribution - for d in range(nbdomains): - all_domains[d]['K'] = projC(all_domains[d]['K'], a) - other = np.sum(all_domains[d]['K'], axis=1) - bary = bary + np.log(np.dot(all_domains[d]['D1'], other)) / nbdomains - - bary = np.exp(bary) - - # update coupling matrices for marginal constraints w.r.t. unknown proportions based on [Prop 4., 27] - for d in range(nbdomains): - new = np.dot(all_domains[d]['D2'].T, bary) - all_domains[d]['K'] = projR(all_domains[d]['K'], new) - - err = np.linalg.norm(bary - old_bary) - cpt = cpt + 1 - old_bary = bary - - if log: - log['err'].append(err) - - if verbose: - if cpt % 200 == 0: - print('{:5s}|{:12s}'.format('It.', 'Err') + '\n' + '-' * 19) - print('{:5d}|{:8e}|'.format(cpt, err)) - - bary = bary / np.sum(bary) - couplings = [all_domains[d]['K'] for d in range(nbdomains)] - - if log: - log['niter'] = cpt - log['all_domains'] = all_domains - return couplings, bary, log - else: - return couplings, bary - - def distribution_estimation_uniform(X): """estimates a uniform distribution from an array of samples X @@ -921,7 +766,6 @@ def distribution_estimation_uniform(X): class BaseTransport(BaseEstimator): - """Base class for OTDA objects Notes @@ -1079,7 +923,6 @@ class BaseTransport(BaseEstimator): transp_Xs = [] for bi in batch_ind: - # get the nearest neighbor in the source domain D0 = dist(Xs[bi], self.xs_) idx = np.argmin(D0, axis=1) @@ -1148,7 +991,6 @@ class BaseTransport(BaseEstimator): transp_Xt = [] for bi in batch_ind: - D0 = dist(Xt[bi], self.xt_) idx = np.argmin(D0, axis=1) @@ -1294,7 +1136,6 @@ class LinearTransport(BaseTransport): # check the necessary inputs parameters are here if check_params(Xs=Xs): - transp_Xs = Xs.dot(self.A_) + self.B_ return transp_Xs @@ -1328,14 +1169,12 @@ class LinearTransport(BaseTransport): # check the necessary inputs parameters are here if check_params(Xt=Xt): - transp_Xt = Xt.dot(self.A1_) + self.B1_ return transp_Xt class SinkhornTransport(BaseTransport): - """Domain Adapatation OT method based on Sinkhorn Algorithm Parameters @@ -1445,7 +1284,6 @@ class SinkhornTransport(BaseTransport): class EMDTransport(BaseTransport): - """Domain Adapatation OT method based on Earth Mover's Distance Parameters @@ -1537,7 +1375,6 @@ class EMDTransport(BaseTransport): class SinkhornLpl1Transport(BaseTransport): - """Domain Adapatation OT method based on sinkhorn algorithm + LpL1 class regularization. @@ -1639,7 +1476,6 @@ class SinkhornLpl1Transport(BaseTransport): # check the necessary inputs parameters are here if check_params(Xs=Xs, Xt=Xt, ys=ys): - super(SinkhornLpl1Transport, self).fit(Xs, ys, Xt, yt) returned_ = sinkhorn_lpl1_mm( @@ -1658,7 +1494,6 @@ class SinkhornLpl1Transport(BaseTransport): class SinkhornL1l2Transport(BaseTransport): - """Domain Adapatation OT method based on sinkhorn algorithm + l1l2 class regularization. @@ -1782,7 +1617,6 @@ class SinkhornL1l2Transport(BaseTransport): class MappingTransport(BaseEstimator): - """MappingTransport: DA methods that aims at jointly estimating a optimal transport coupling and the associated mapping @@ -1956,7 +1790,6 @@ class MappingTransport(BaseEstimator): class UnbalancedSinkhornTransport(BaseTransport): - """Domain Adapatation unbalanced OT method based on sinkhorn algorithm Parameters @@ -2075,7 +1908,6 @@ class UnbalancedSinkhornTransport(BaseTransport): class JCPOTTransport(BaseTransport): - """Domain Adapatation OT method for multi-source target shift based on Wasserstein barycenter algorithm. Parameters diff --git a/test/test_da.py b/test/test_da.py index a8c258a..958df7b 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -8,6 +8,7 @@ import numpy as np from numpy.testing import assert_allclose, assert_equal import ot +from ot.bregman import jcpot_barycenter from ot.datasets import make_data_classif from ot.utils import unif @@ -603,7 +604,6 @@ def test_jcpot_transport_class(): # test transform transp_Xs = otda.transform(Xs=Xs) [assert_equal(x.shape, y.shape) for x, y in zip(transp_Xs, Xs)] - #assert_equal(transp_Xs.shape, Xs.shape) Xs_new, _ = make_data_classif('3gauss', ns1 + 1) transp_Xs_new = otda.transform(Xs_new) -- cgit v1.2.3 From 439860609df786a877383775dd901afe28480cc9 Mon Sep 17 00:00:00 2001 From: ievred Date: Wed, 1 Apr 2020 09:00:03 +0200 Subject: fix imports remove checks --- ot/da.py | 5 ++--- test/test_da.py | 4 +++- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'ot/da.py') diff --git a/ot/da.py b/ot/da.py index a9c3cea..e62e495 100644 --- a/ot/da.py +++ b/ot/da.py @@ -14,7 +14,7 @@ Domain adaptation with optimal transport import numpy as np import scipy.linalg as linalg -from .bregman import sinkhorn +from .bregman import sinkhorn, jcpot_barycenter from .lp import emd from .utils import unif, dist, kernel, cost_normalization from .utils import check_params, BaseEstimator @@ -1956,8 +1956,7 @@ class JCPOTTransport(BaseTransport): def __init__(self, reg_e=.1, max_iter=10, tol=10e-9, verbose=False, log=False, - metric="sqeuclidean", norm=None, - distribution_estimation=distribution_estimation_uniform, + metric="sqeuclidean", out_of_sample_map='ferradans'): self.reg_e = reg_e diff --git a/test/test_da.py b/test/test_da.py index 958df7b..7526f30 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -572,7 +572,6 @@ def test_jcpot_transport_class(): # test its computed otda.fit(Xs=Xs, ys=ys, Xt=Xt) - print(otda.proportions_) assert hasattr(otda, "coupling_") assert hasattr(otda, "proportions_") @@ -610,3 +609,6 @@ def test_jcpot_transport_class(): # check that the oos method is working assert_equal(transp_Xs_new.shape, Xs_new.shape) + + +test_jcpot_transport_class() \ No newline at end of file -- cgit v1.2.3 From fa99199c02e497354e34c6ce76e7b4ba15b44d05 Mon Sep 17 00:00:00 2001 From: ievred Date: Fri, 3 Apr 2020 16:06:39 +0200 Subject: v2 laplace emd sinkhorn --- ot/da.py | 485 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- ot/utils.py | 5 + 2 files changed, 481 insertions(+), 9 deletions(-) (limited to 'ot/da.py') diff --git a/ot/da.py b/ot/da.py index e62e495..39e8c4c 100644 --- a/ot/da.py +++ b/ot/da.py @@ -16,7 +16,7 @@ import scipy.linalg as linalg from .bregman import sinkhorn, jcpot_barycenter from .lp import emd -from .utils import unif, dist, kernel, cost_normalization +from .utils import unif, dist, kernel, cost_normalization, laplacian from .utils import check_params, BaseEstimator from .unbalanced import sinkhorn_unbalanced from .optim import cg @@ -748,6 +748,233 @@ def OT_mapping_linear(xs, xt, reg=1e-6, ws=None, return A, b +def emd_laplace(a, b, xs, xt, M, eta=1., alpha=0.5, + numItermax=1000, stopThr=1e-5, numInnerItermax=1000, + stopInnerThr=1e-6, log=False, verbose=False, **kwargs): + r"""Solve the optimal transport problem (OT) with Laplacian regularization + + .. math:: + \gamma = arg\min_\gamma <\gamma,M>_F + eta\Omega_\alpha(\gamma) + + s.t.\ \gamma 1 = a + + \gamma^T 1= b + + \gamma\geq 0 + + where: + + - a and b are source and target weights (sum to 1) + - xs and xt are source and target samples + - M is the (ns,nt) metric cost matrix + - :math:`\Omega_\alpha` is the Laplacian regularization term + :math:`\Omega_\alpha = (1-\alpha)/n_s^2\sum_{i,j}S^s_{i,j}\|T(\mathbf{x}^s_i)-T(\mathbf{x}^s_j)\|^2+\alpha/n_t^2\sum_{i,j}S^t_{i,j}^'\|T(\mathbf{x}^t_i)-T(\mathbf{x}^t_j)\|^2` + with :math:`S^s_{i,j}, S^t_{i,j}` denoting source and target similarity matrices and :math:`T(\cdot)` being a barycentric mapping + + The algorithm used for solving the problem is the conditional gradient algorithm as proposed in [5]. + + Parameters + ---------- + a : np.ndarray (ns,) + samples weights in the source domain + b : np.ndarray (nt,) + samples weights in the target domain + xs : np.ndarray (ns,d) + samples in the source domain + xt : np.ndarray (nt,d) + samples in the target domain + M : np.ndarray (ns,nt) + loss matrix + eta : float + Regularization term for Laplacian regularization + alpha : float + Regularization term for source domain's importance in regularization + numItermax : int, optional + Max number of iterations + stopThr : float, optional + Stop threshold on error (inner emd solver) (>0) + numInnerItermax : int, optional + Max number of iterations (inner CG solver) + stopInnerThr : float, optional + Stop threshold on error (inner CG solver) (>0) + verbose : bool, optional + Print information along iterations + log : bool, optional + record log if True + + + Returns + ------- + gamma : (ns x nt) ndarray + Optimal transportation matrix for the given parameters + log : dict + log dictionary return only if log==True in parameters + + + References + ---------- + + .. [5] N. Courty; R. Flamary; D. Tuia; A. Rakotomamonjy, + "Optimal Transport for Domain Adaptation," in IEEE + Transactions on Pattern Analysis and Machine Intelligence , + vol.PP, no.99, pp.1-1 + + See Also + -------- + ot.lp.emd : Unregularized OT + ot.optim.cg : General regularized OT + + """ + if 'sim' not in kwargs: + kwargs['sim'] = 'knn' + + if kwargs['sim'] == 'gauss': + if 'rbfparam' not in kwargs: + kwargs['rbfparam'] = 1 / (2 * (np.mean(dist(xs, xs, 'sqeuclidean')) ** 2)) + sS = kernel(xs, xs, method=kwargs['sim'], sigma=kwargs['rbfparam']) + sT = kernel(xt, xt, method=kwargs['sim'], sigma=kwargs['rbfparam']) + + elif kwargs['sim'] == 'knn': + if 'nn' not in kwargs: + kwargs['nn'] = 5 + + from sklearn.neighbors import kneighbors_graph + + sS = kneighbors_graph(xs, kwargs['nn']).toarray() + sS = (sS + sS.T) / 2 + sT = kneighbors_graph(xt, kwargs['nn']).toarray() + sT = (sT + sT.T) / 2 + + lS = laplacian(sS) + lT = laplacian(sT) + + def f(G): + return alpha*np.trace(np.dot(xt.T, np.dot(G.T, np.dot(lS, np.dot(G, xt))))) \ + + (1-alpha)*np.trace(np.dot(xs.T, np.dot(G, np.dot(lT, np.dot(G.T, xs))))) + + def df(G): + return alpha*np.dot(lS + lS.T, np.dot(G, np.dot(xt, xt.T)))\ + +(1-alpha)*np.dot(xs, np.dot(xs.T, np.dot(G, lT + lT.T))) + + return cg(a, b, M, reg=eta, f=f, df=df, G0=None, numItermax=numItermax, numItermaxEmd=numInnerItermax, + stopThr=stopThr, stopThr2=stopInnerThr, verbose=verbose, log=log) + +def sinkhorn_laplace(a, b, xs, xt, M, reg=.1, eta=1., alpha=0.5, + numItermax=1000, stopThr=1e-5, numInnerItermax=1000, + stopInnerThr=1e-6, log=False, verbose=False, **kwargs): + r"""Solve the entropic regularized optimal transport problem (OT) with Laplacian regularization + + .. math:: + \gamma = arg\min_\gamma <\gamma,M>_F + reg\Omega_e(\gamma) + eta\Omega_\alpha(\gamma) + + s.t.\ \gamma 1 = a + + \gamma^T 1= b + + \gamma\geq 0 + + where: + + - a and b are source and target weights (sum to 1) + - xs and xt are source and target samples + - M is the (ns,nt) metric cost matrix + - :math:`\Omega_e` is the entropic regularization term :math:`\Omega_e + (\gamma)=\sum_{i,j} \gamma_{i,j}\log(\gamma_{i,j})` + - :math:`\Omega_\alpha` is the Laplacian regularization term + :math:`\Omega_\alpha = (1-\alpha)/n_s^2\sum_{i,j}S^s_{i,j}\|T(\mathbf{x}^s_i)-T(\mathbf{x}^s_j)\|^2+\alpha/n_t^2\sum_{i,j}S^t_{i,j}^'\|T(\mathbf{x}^t_i)-T(\mathbf{x}^t_j)\|^2` + with :math:`S^s_{i,j}, S^t_{i,j}` denoting source and target similarity matrices and :math:`T(\cdot)` being a barycentric mapping + + The algorithm used for solving the problem is the conditional gradient algorithm as proposed in [5]. + + Parameters + ---------- + a : np.ndarray (ns,) + samples weights in the source domain + b : np.ndarray (nt,) + samples weights in the target domain + xs : np.ndarray (ns,d) + samples in the source domain + xt : np.ndarray (nt,d) + samples in the target domain + M : np.ndarray (ns,nt) + loss matrix + reg : float + Regularization term for entropic regularization >0 + eta : float + Regularization term for Laplacian regularization + alpha : float + Regularization term for source domain's importance in regularization + numItermax : int, optional + Max number of iterations + stopThr : float, optional + Stop threshold on error (inner sinkhorn solver) (>0) + numInnerItermax : int, optional + Max number of iterations (inner CG solver) + stopInnerThr : float, optional + Stop threshold on error (inner CG solver) (>0) + verbose : bool, optional + Print information along iterations + log : bool, optional + record log if True + + + Returns + ------- + gamma : (ns x nt) ndarray + Optimal transportation matrix for the given parameters + log : dict + log dictionary return only if log==True in parameters + + + References + ---------- + + .. [5] N. Courty; R. Flamary; D. Tuia; A. Rakotomamonjy, + "Optimal Transport for Domain Adaptation," in IEEE + Transactions on Pattern Analysis and Machine Intelligence , + vol.PP, no.99, pp.1-1 + + See Also + -------- + ot.lp.emd : Unregularized OT + ot.optim.cg : General regularized OT + + """ + if 'sim' not in kwargs: + kwargs['sim'] = 'knn' + + if kwargs['sim'] == 'gauss': + if 'rbfparam' not in kwargs: + kwargs['rbfparam'] = 1 / (2 * (np.mean(dist(xs, xs, 'sqeuclidean')) ** 2)) + sS = kernel(xs, xs, method=kwargs['sim'], sigma=kwargs['rbfparam']) + sT = kernel(xt, xt, method=kwargs['sim'], sigma=kwargs['rbfparam']) + + elif kwargs['sim'] == 'knn': + if 'nn' not in kwargs: + kwargs['nn'] = 5 + + from sklearn.neighbors import kneighbors_graph + + sS = kneighbors_graph(xs, kwargs['nn']).toarray() + sS = (sS + sS.T) / 2 + sT = kneighbors_graph(xt, kwargs['nn']).toarray() + sT = (sT + sT.T) / 2 + + lS = laplacian(sS) + lT = laplacian(sT) + + def f(G): + return alpha*np.trace(np.dot(xt.T, np.dot(G.T, np.dot(lS, np.dot(G, xt))))) \ + + (1-alpha)*np.trace(np.dot(xs.T, np.dot(G, np.dot(lT, np.dot(G.T, xs))))) + + def df(G): + return alpha*np.dot(lS + lS.T, np.dot(G, np.dot(xt, xt.T)))\ + +(1-alpha)*np.dot(xs, np.dot(xs.T, np.dot(G, lT + lT.T))) + + return gcg(a, b, M, reg, eta, f, df, G0=None, numItermax=numItermax, stopThr=stopThr, + numInnerItermax=numInnerItermax, stopThr2=stopInnerThr, + verbose=verbose, log=log) + def distribution_estimation_uniform(X): """estimates a uniform distribution from an array of samples X @@ -762,10 +989,12 @@ def distribution_estimation_uniform(X): The uniform distribution estimated from X """ + return unif(X.shape[0]) class BaseTransport(BaseEstimator): + """Base class for OTDA objects Notes @@ -787,6 +1016,7 @@ class BaseTransport(BaseEstimator): inverse_transform method should always get as input a Xt parameter """ + def fit(self, Xs=None, ys=None, Xt=None, yt=None): """Build a coupling matrix from source and target sets of samples (Xs, ys) and (Xt, yt) @@ -847,6 +1077,7 @@ class BaseTransport(BaseEstimator): return self + def fit_transform(self, Xs=None, ys=None, Xt=None, yt=None): """Build a coupling matrix from source and target sets of samples (Xs, ys) and (Xt, yt) and transports source samples Xs onto target @@ -875,6 +1106,7 @@ class BaseTransport(BaseEstimator): return self.fit(Xs, ys, Xt, yt).transform(Xs, ys, Xt, yt) + def transform(self, Xs=None, ys=None, Xt=None, yt=None, batch_size=128): """Transports source samples Xs onto target ones Xt @@ -942,6 +1174,7 @@ class BaseTransport(BaseEstimator): return transp_Xs + def inverse_transform(self, Xs=None, ys=None, Xt=None, yt=None, batch_size=128): """Transports target samples Xt onto target samples Xs @@ -1011,6 +1244,7 @@ class BaseTransport(BaseEstimator): class LinearTransport(BaseTransport): + """ OT linear operator between empirical distributions The function estimates the optimal linear operator that aligns the two @@ -1053,14 +1287,15 @@ class LinearTransport(BaseTransport): """ + def __init__(self, reg=1e-8, bias=True, log=False, distribution_estimation=distribution_estimation_uniform): - self.bias = bias self.log = log self.reg = reg self.distribution_estimation = distribution_estimation + def fit(self, Xs=None, ys=None, Xt=None, yt=None): """Build a coupling matrix from source and target sets of samples (Xs, ys) and (Xt, yt) @@ -1108,6 +1343,7 @@ class LinearTransport(BaseTransport): return self + def transform(self, Xs=None, ys=None, Xt=None, yt=None, batch_size=128): """Transports source samples Xs onto target ones Xt @@ -1140,6 +1376,7 @@ class LinearTransport(BaseTransport): return transp_Xs + def inverse_transform(self, Xs=None, ys=None, Xt=None, yt=None, batch_size=128): """Transports target samples Xt onto target samples Xs @@ -1175,6 +1412,7 @@ class LinearTransport(BaseTransport): class SinkhornTransport(BaseTransport): + """Domain Adapatation OT method based on Sinkhorn Algorithm Parameters @@ -1223,12 +1461,12 @@ class SinkhornTransport(BaseTransport): 26, 2013 """ + def __init__(self, reg_e=1., max_iter=1000, tol=10e-9, verbose=False, log=False, metric="sqeuclidean", norm=None, distribution_estimation=distribution_estimation_uniform, out_of_sample_map='ferradans', limit_max=np.infty): - self.reg_e = reg_e self.max_iter = max_iter self.tol = tol @@ -1240,6 +1478,7 @@ class SinkhornTransport(BaseTransport): self.distribution_estimation = distribution_estimation self.out_of_sample_map = out_of_sample_map + def fit(self, Xs=None, ys=None, Xt=None, yt=None): """Build a coupling matrix from source and target sets of samples (Xs, ys) and (Xt, yt) @@ -1284,6 +1523,7 @@ class SinkhornTransport(BaseTransport): class EMDTransport(BaseTransport): + """Domain Adapatation OT method based on Earth Mover's Distance Parameters @@ -1321,11 +1561,11 @@ class EMDTransport(BaseTransport): on Pattern Analysis and Machine Intelligence , vol.PP, no.99, pp.1-1 """ + def __init__(self, metric="sqeuclidean", norm=None, log=False, distribution_estimation=distribution_estimation_uniform, out_of_sample_map='ferradans', limit_max=10, max_iter=100000): - self.metric = metric self.norm = norm self.log = log @@ -1334,6 +1574,7 @@ class EMDTransport(BaseTransport): self.out_of_sample_map = out_of_sample_map self.max_iter = max_iter + def fit(self, Xs, ys=None, Xt=None, yt=None): """Build a coupling matrix from source and target sets of samples (Xs, ys) and (Xt, yt) @@ -1375,6 +1616,7 @@ class EMDTransport(BaseTransport): class SinkhornLpl1Transport(BaseTransport): + """Domain Adapatation OT method based on sinkhorn algorithm + LpL1 class regularization. @@ -1429,13 +1671,13 @@ class SinkhornLpl1Transport(BaseTransport): """ + def __init__(self, reg_e=1., reg_cl=0.1, max_iter=10, max_inner_iter=200, log=False, tol=10e-9, verbose=False, metric="sqeuclidean", norm=None, distribution_estimation=distribution_estimation_uniform, out_of_sample_map='ferradans', limit_max=np.infty): - self.reg_e = reg_e self.reg_cl = reg_cl self.max_iter = max_iter @@ -1449,6 +1691,7 @@ class SinkhornLpl1Transport(BaseTransport): self.out_of_sample_map = out_of_sample_map self.limit_max = limit_max + def fit(self, Xs, ys=None, Xt=None, yt=None): """Build a coupling matrix from source and target sets of samples (Xs, ys) and (Xt, yt) @@ -1493,7 +1736,222 @@ class SinkhornLpl1Transport(BaseTransport): return self +class EMDLaplaceTransport(BaseTransport): + + """Domain Adapatation OT method based on Earth Mover's Distance with Laplacian regularization + + Parameters + ---------- + reg_lap : float, optional (default=1) + Laplacian regularization parameter + reg_src : float, optional (default=0.5) + Source relative importance in regularization + metric : string, optional (default="sqeuclidean") + The ground metric for the Wasserstein problem + norm : string, optional (default=None) + If given, normalize the ground metric to avoid numerical errors that + can occur with large metric values. + max_iter : int, optional (default=100) + Max number of BCD iterations + tol : float, optional (default=1e-5) + Stop threshold on relative loss decrease (>0) + max_inner_iter : int, optional (default=10) + Max number of iterations (inner CG solver) + inner_tol : float, optional (default=1e-6) + Stop threshold on error (inner CG solver) (>0) + log : int, optional (default=False) + Controls the logs of the optimization algorithm + distribution_estimation : callable, optional (defaults to the uniform) + The kind of distribution estimation to employ + out_of_sample_map : string, optional (default="ferradans") + The kind of out of sample mapping to apply to transport samples + from a domain into another one. Currently the only possible option is + "ferradans" which uses the method proposed in [6]. + + Attributes + ---------- + coupling_ : array-like, shape (n_source_samples, n_target_samples) + The optimal coupling + + References + ---------- + .. [1] N. Courty; R. Flamary; D. Tuia; A. Rakotomamonjy, + "Optimal Transport for Domain Adaptation," in IEEE Transactions + on Pattern Analysis and Machine Intelligence , vol.PP, no.99, pp.1-1 + """ + + + def __init__(self, reg_lap = 1., reg_src=1., alpha=0.5, + metric="sqeuclidean", norm=None, max_iter=100, tol=1e-5, + max_inner_iter=100000, inner_tol=1e-6, log=False, verbose=False, + distribution_estimation=distribution_estimation_uniform, + out_of_sample_map='ferradans'): + self.reg_lap = reg_lap + self.reg_src = reg_src + self.alpha = alpha + self.metric = metric + self.norm = norm + self.max_iter = max_iter + self.tol = tol + self.max_inner_iter = max_inner_iter + self.inner_tol = inner_tol + self.log = log + self.verbose = verbose + self.distribution_estimation = distribution_estimation + self.out_of_sample_map = out_of_sample_map + + + def fit(self, Xs, ys=None, Xt=None, yt=None): + """Build a coupling matrix from source and target sets of samples + (Xs, ys) and (Xt, yt) + + Parameters + ---------- + Xs : array-like, shape (n_source_samples, n_features) + The training input samples. + ys : array-like, shape (n_source_samples,) + The class labels + Xt : array-like, shape (n_target_samples, n_features) + The training input samples. + yt : array-like, shape (n_target_samples,) + The class labels. If some target samples are unlabeled, fill the + yt's elements with -1. + + Warning: Note that, due to this convention -1 cannot be used as a + class label + + Returns + ------- + self : object + Returns self. + """ + + super(EMDLaplaceTransport, self).fit(Xs, ys, Xt, yt) + + returned_ = emd_laplace(a=self.mu_s, b=self.mu_t, xs=self.xs_, + xt=self.xt_, M=self.cost_, eta=self.reg_lap, alpha=self.reg_src, + numItermax=self.max_iter, stopThr=self.tol, numInnerItermax=self.max_inner_iter, + stopInnerThr=self.inner_tol, log=self.log, verbose=self.verbose) + + # coupling estimation + if self.log: + self.coupling_, self.log_ = returned_ + else: + self.coupling_ = returned_ + self.log_ = dict() + return self + +class SinkhornLaplaceTransport(BaseTransport): + + """Domain Adapatation OT method based on entropic regularized OT with Laplacian regularization + + Parameters + ---------- + reg_e : float, optional (default=1) + Entropic regularization parameter + reg_lap : float, optional (default=1) + Laplacian regularization parameter + reg_src : float, optional (default=0.5) + Source relative importance in regularization + metric : string, optional (default="sqeuclidean") + The ground metric for the Wasserstein problem + norm : string, optional (default=None) + If given, normalize the ground metric to avoid numerical errors that + can occur with large metric values. + max_iter : int, optional (default=100) + Max number of BCD iterations + tol : float, optional (default=1e-5) + Stop threshold on relative loss decrease (>0) + max_inner_iter : int, optional (default=10) + Max number of iterations (inner CG solver) + inner_tol : float, optional (default=1e-6) + Stop threshold on error (inner CG solver) (>0) + log : int, optional (default=False) + Controls the logs of the optimization algorithm + distribution_estimation : callable, optional (defaults to the uniform) + The kind of distribution estimation to employ + out_of_sample_map : string, optional (default="ferradans") + The kind of out of sample mapping to apply to transport samples + from a domain into another one. Currently the only possible option is + "ferradans" which uses the method proposed in [6]. + + Attributes + ---------- + coupling_ : array-like, shape (n_source_samples, n_target_samples) + The optimal coupling + + References + ---------- + .. [1] N. Courty; R. Flamary; D. Tuia; A. Rakotomamonjy, + "Optimal Transport for Domain Adaptation," in IEEE Transactions + on Pattern Analysis and Machine Intelligence , vol.PP, no.99, pp.1-1 + """ + + + def __init__(self, reg_e=1., reg_lap=1., reg_src=0.5, + metric="sqeuclidean", norm=None, max_iter=100, tol=1e-9, + max_inner_iter=200, inner_tol=1e-6, log=False, verbose=False, + distribution_estimation=distribution_estimation_uniform, + out_of_sample_map='ferradans'): + + self.reg_e = reg_e + self.reg_lap = reg_lap + self.reg_src = reg_src + self.metric = metric + self.norm = norm + self.max_iter = max_iter + self.tol = tol + self.max_inner_iter = max_inner_iter + self.inner_tol = inner_tol + self.log = log + self.verbose = verbose + self.distribution_estimation = distribution_estimation + self.out_of_sample_map = out_of_sample_map + + + def fit(self, Xs, ys=None, Xt=None, yt=None): + """Build a coupling matrix from source and target sets of samples + (Xs, ys) and (Xt, yt) + + Parameters + ---------- + Xs : array-like, shape (n_source_samples, n_features) + The training input samples. + ys : array-like, shape (n_source_samples,) + The class labels + Xt : array-like, shape (n_target_samples, n_features) + The training input samples. + yt : array-like, shape (n_target_samples,) + The class labels. If some target samples are unlabeled, fill the + yt's elements with -1. + + Warning: Note that, due to this convention -1 cannot be used as a + class label + + Returns + ------- + self : object + Returns self. + """ + + super(SinkhornLaplaceTransport, self).fit(Xs, ys, Xt, yt) + + returned_ = sinkhorn_laplace(a=self.mu_s, b=self.mu_t, xs=self.xs_, + xt=self.xt_, M=self.cost_, reg=self.reg_e, eta=self.reg_lap, alpha=self.reg_src, + numItermax=self.max_iter, stopThr=self.tol, numInnerItermax=self.max_inner_iter, + stopInnerThr=self.inner_tol, log=self.log, verbose=self.verbose) + + # coupling estimation + if self.log: + self.coupling_, self.log_ = returned_ + else: + self.coupling_ = returned_ + self.log_ = dict() + return self + + class SinkhornL1l2Transport(BaseTransport): + """Domain Adapatation OT method based on sinkhorn algorithm + l1l2 class regularization. @@ -1550,13 +2008,13 @@ class SinkhornL1l2Transport(BaseTransport): """ + def __init__(self, reg_e=1., reg_cl=0.1, max_iter=10, max_inner_iter=200, tol=10e-9, verbose=False, log=False, metric="sqeuclidean", norm=None, distribution_estimation=distribution_estimation_uniform, out_of_sample_map='ferradans', limit_max=10): - self.reg_e = reg_e self.reg_cl = reg_cl self.max_iter = max_iter @@ -1570,6 +2028,7 @@ class SinkhornL1l2Transport(BaseTransport): self.out_of_sample_map = out_of_sample_map self.limit_max = limit_max + def fit(self, Xs, ys=None, Xt=None, yt=None): """Build a coupling matrix from source and target sets of samples (Xs, ys) and (Xt, yt) @@ -1617,6 +2076,7 @@ class SinkhornL1l2Transport(BaseTransport): class MappingTransport(BaseEstimator): + """MappingTransport: DA methods that aims at jointly estimating a optimal transport coupling and the associated mapping @@ -1673,11 +2133,11 @@ class MappingTransport(BaseEstimator): """ + def __init__(self, mu=1, eta=0.001, bias=False, metric="sqeuclidean", norm=None, kernel="linear", sigma=1, max_iter=100, tol=1e-5, max_inner_iter=10, inner_tol=1e-6, log=False, verbose=False, verbose2=False): - self.metric = metric self.norm = norm self.mu = mu @@ -1693,6 +2153,7 @@ class MappingTransport(BaseEstimator): self.verbose = verbose self.verbose2 = verbose2 + def fit(self, Xs=None, ys=None, Xt=None, yt=None): """Builds an optimal coupling and estimates the associated mapping from source and target sets of samples (Xs, ys) and (Xt, yt) @@ -1750,6 +2211,7 @@ class MappingTransport(BaseEstimator): return self + def transform(self, Xs): """Transports source samples Xs onto target ones Xt @@ -1790,6 +2252,7 @@ class MappingTransport(BaseEstimator): class UnbalancedSinkhornTransport(BaseTransport): + """Domain Adapatation unbalanced OT method based on sinkhorn algorithm Parameters @@ -1842,12 +2305,12 @@ class UnbalancedSinkhornTransport(BaseTransport): """ + def __init__(self, reg_e=1., reg_m=0.1, method='sinkhorn', max_iter=10, tol=1e-9, verbose=False, log=False, metric="sqeuclidean", norm=None, distribution_estimation=distribution_estimation_uniform, out_of_sample_map='ferradans', limit_max=10): - self.reg_e = reg_e self.reg_m = reg_m self.method = method @@ -1861,6 +2324,7 @@ class UnbalancedSinkhornTransport(BaseTransport): self.out_of_sample_map = out_of_sample_map self.limit_max = limit_max + def fit(self, Xs, ys=None, Xt=None, yt=None): """Build a coupling matrix from source and target sets of samples (Xs, ys) and (Xt, yt) @@ -1908,6 +2372,7 @@ class UnbalancedSinkhornTransport(BaseTransport): class JCPOTTransport(BaseTransport): + """Domain Adapatation OT method for multi-source target shift based on Wasserstein barycenter algorithm. Parameters @@ -1954,11 +2419,11 @@ class JCPOTTransport(BaseTransport): """ + def __init__(self, reg_e=.1, max_iter=10, tol=10e-9, verbose=False, log=False, metric="sqeuclidean", out_of_sample_map='ferradans'): - self.reg_e = reg_e self.max_iter = max_iter self.tol = tol @@ -1967,6 +2432,7 @@ class JCPOTTransport(BaseTransport): self.metric = metric self.out_of_sample_map = out_of_sample_map + def fit(self, Xs, ys=None, Xt=None, yt=None): """Building coupling matrices from a list of source and target sets of samples (Xs, ys) and (Xt, yt) @@ -2011,6 +2477,7 @@ class JCPOTTransport(BaseTransport): return self + def transform(self, Xs=None, ys=None, Xt=None, yt=None, batch_size=128): """Transports source samples Xs onto target ones Xt diff --git a/ot/utils.py b/ot/utils.py index b71458b..b8a6f44 100644 --- a/ot/utils.py +++ b/ot/utils.py @@ -48,6 +48,11 @@ def kernel(x1, x2, method='gaussian', sigma=1, **kwargs): K = np.exp(-dist(x1, x2) / (2 * sigma**2)) return K +def laplacian(x): + """Compute Laplacian matrix""" + L = np.diag(np.sum(x, axis=0)) - x + return L + def unif(n): """ return a uniform histogram of length n (simplex) -- cgit v1.2.3 From 98b68f1edc916d3802eeb24a19d0e10d855e01c6 Mon Sep 17 00:00:00 2001 From: ievred Date: Fri, 3 Apr 2020 17:29:13 +0200 Subject: autopep+remove sinkhorn+add simtype --- examples/plot_otda_laplacian.py | 38 ++---- ot/da.py | 286 +++------------------------------------- ot/utils.py | 1 + test/test_da.py | 54 +------- 4 files changed, 28 insertions(+), 351 deletions(-) (limited to 'ot/da.py') diff --git a/examples/plot_otda_laplacian.py b/examples/plot_otda_laplacian.py index d9ae280..965380c 100644 --- a/examples/plot_otda_laplacian.py +++ b/examples/plot_otda_laplacian.py @@ -5,7 +5,7 @@ OT for domain adaptation ======================== This example introduces a domain adaptation in a 2D setting and OTDA -approaches with Laplacian regularization. +approache with Laplacian regularization. """ @@ -36,22 +36,17 @@ ot_emd = ot.da.EMDTransport() ot_emd.fit(Xs=Xs, Xt=Xt) # Sinkhorn Transport -ot_sinkhorn = ot.da.SinkhornTransport(reg_e=.5) +ot_sinkhorn = ot.da.SinkhornTransport(reg_e=.01) ot_sinkhorn.fit(Xs=Xs, Xt=Xt) # EMD Transport with Laplacian regularization ot_emd_laplace = ot.da.EMDLaplaceTransport(reg_lap=100, reg_src=1) ot_emd_laplace.fit(Xs=Xs, Xt=Xt) -# Sinkhorn Transport with Laplacian regularization -ot_sinkhorn_laplace = ot.da.SinkhornLaplaceTransport(reg_e=.5, reg_lap=100, reg_src=1) -ot_sinkhorn_laplace.fit(Xs=Xs, Xt=Xt) - # transport source samples onto target samples transp_Xs_emd = ot_emd.transform(Xs=Xs) transp_Xs_sinkhorn = ot_sinkhorn.transform(Xs=Xs) transp_Xs_emd_laplace = ot_emd_laplace.transform(Xs=Xs) -transp_Xs_sinkhorn_laplace = ot_sinkhorn_laplace.transform(Xs=Xs) ############################################################################## # Fig 1 : plots source and target samples @@ -80,35 +75,27 @@ pl.tight_layout() param_img = {'interpolation': 'nearest'} -n_plots = 2 - pl.figure(2, figsize=(15, 8)) -pl.subplot(2, 2*n_plots, 1) +pl.subplot(2, 3, 1) pl.imshow(ot_emd.coupling_, **param_img) pl.xticks([]) pl.yticks([]) pl.title('Optimal coupling\nEMDTransport') pl.figure(2, figsize=(15, 8)) -pl.subplot(2, 2*n_plots, 2) +pl.subplot(2, 3, 2) pl.imshow(ot_sinkhorn.coupling_, **param_img) pl.xticks([]) pl.yticks([]) pl.title('Optimal coupling\nSinkhornTransport') -pl.subplot(2, 2*n_plots, 3) +pl.subplot(2, 3, 3) pl.imshow(ot_emd_laplace.coupling_, **param_img) pl.xticks([]) pl.yticks([]) pl.title('Optimal coupling\nEMDLaplaceTransport') -pl.subplot(2, 2*n_plots, 4) -pl.imshow(ot_emd_laplace.coupling_, **param_img) -pl.xticks([]) -pl.yticks([]) -pl.title('Optimal coupling\nSinkhornLaplaceTransport') - -pl.subplot(2, 2*n_plots, 5) +pl.subplot(2, 3, 4) pl.scatter(Xt[:, 0], Xt[:, 1], c=yt, marker='o', label='Target samples', alpha=0.3) pl.scatter(transp_Xs_emd[:, 0], transp_Xs_emd[:, 1], c=ys, @@ -118,7 +105,7 @@ pl.yticks([]) pl.title('Transported samples\nEmdTransport') pl.legend(loc="lower left") -pl.subplot(2, 2*n_plots, 6) +pl.subplot(2, 3, 5) pl.scatter(Xt[:, 0], Xt[:, 1], c=yt, marker='o', label='Target samples', alpha=0.3) pl.scatter(transp_Xs_sinkhorn[:, 0], transp_Xs_sinkhorn[:, 1], c=ys, @@ -127,7 +114,7 @@ pl.xticks([]) pl.yticks([]) pl.title('Transported samples\nSinkhornTransport') -pl.subplot(2, 2*n_plots, 7) +pl.subplot(2, 3, 6) pl.scatter(Xt[:, 0], Xt[:, 1], c=yt, marker='o', label='Target samples', alpha=0.3) pl.scatter(transp_Xs_emd_laplace[:, 0], transp_Xs_emd_laplace[:, 1], c=ys, @@ -135,15 +122,6 @@ pl.scatter(transp_Xs_emd_laplace[:, 0], transp_Xs_emd_laplace[:, 1], c=ys, pl.xticks([]) pl.yticks([]) pl.title('Transported samples\nEMDLaplaceTransport') - -pl.subplot(2, 2*n_plots, 8) -pl.scatter(Xt[:, 0], Xt[:, 1], c=yt, marker='o', - label='Target samples', alpha=0.3) -pl.scatter(transp_Xs_sinkhorn_laplace[:, 0], transp_Xs_sinkhorn_laplace[:, 1], c=ys, - marker='+', label='Transp samples', s=30) -pl.xticks([]) -pl.yticks([]) -pl.title('Transported samples\nSinkhornLaplaceTransport') pl.tight_layout() pl.show() diff --git a/ot/da.py b/ot/da.py index 39e8c4c..0fdd3be 100644 --- a/ot/da.py +++ b/ot/da.py @@ -361,7 +361,7 @@ def joint_OT_mapping_linear(xs, xt, mu=1, eta=0.001, bias=False, verbose=False, def loss(L, G): """Compute full loss""" return np.sum((xs1.dot(L) - ns * G.dot(xt)) ** 2) + mu * \ - np.sum(G * M) + eta * np.sum(sel(L - I0) ** 2) + np.sum(G * M) + eta * np.sum(sel(L - I0) ** 2) def solve_L(G): """ solve L problem with fixed G (least square)""" @@ -565,7 +565,7 @@ def joint_OT_mapping_kernel(xs, xt, mu=1, eta=0.001, kerneltype='gaussian', def loss(L, G): """Compute full loss""" return np.sum((K1.dot(L) - ns * G.dot(xt)) ** 2) + mu * \ - np.sum(G * M) + eta * np.trace(L.T.dot(Kreg).dot(L)) + np.sum(G * M) + eta * np.trace(L.T.dot(Kreg).dot(L)) def solve_L_nobias(G): """ solve L problem with fixed G (least square)""" @@ -748,9 +748,9 @@ def OT_mapping_linear(xs, xt, reg=1e-6, ws=None, return A, b -def emd_laplace(a, b, xs, xt, M, eta=1., alpha=0.5, - numItermax=1000, stopThr=1e-5, numInnerItermax=1000, - stopInnerThr=1e-6, log=False, verbose=False, **kwargs): +def emd_laplace(a, b, xs, xt, M, sim, eta, alpha, + numItermax, stopThr, numInnerItermax, + stopInnerThr, log=False, verbose=False, **kwargs): r"""Solve the optimal transport problem (OT) with Laplacian regularization .. math:: @@ -825,16 +825,13 @@ def emd_laplace(a, b, xs, xt, M, eta=1., alpha=0.5, ot.optim.cg : General regularized OT """ - if 'sim' not in kwargs: - kwargs['sim'] = 'knn' - - if kwargs['sim'] == 'gauss': + if sim == 'gauss': if 'rbfparam' not in kwargs: kwargs['rbfparam'] = 1 / (2 * (np.mean(dist(xs, xs, 'sqeuclidean')) ** 2)) sS = kernel(xs, xs, method=kwargs['sim'], sigma=kwargs['rbfparam']) sT = kernel(xt, xt, method=kwargs['sim'], sigma=kwargs['rbfparam']) - elif kwargs['sim'] == 'knn': + elif sim == 'knn': if 'nn' not in kwargs: kwargs['nn'] = 5 @@ -849,131 +846,16 @@ def emd_laplace(a, b, xs, xt, M, eta=1., alpha=0.5, lT = laplacian(sT) def f(G): - return alpha*np.trace(np.dot(xt.T, np.dot(G.T, np.dot(lS, np.dot(G, xt))))) \ - + (1-alpha)*np.trace(np.dot(xs.T, np.dot(G, np.dot(lT, np.dot(G.T, xs))))) + return alpha * np.trace(np.dot(xt.T, np.dot(G.T, np.dot(lS, np.dot(G, xt))))) \ + + (1 - alpha) * np.trace(np.dot(xs.T, np.dot(G, np.dot(lT, np.dot(G.T, xs))))) def df(G): - return alpha*np.dot(lS + lS.T, np.dot(G, np.dot(xt, xt.T)))\ - +(1-alpha)*np.dot(xs, np.dot(xs.T, np.dot(G, lT + lT.T))) + return alpha * np.dot(lS + lS.T, np.dot(G, np.dot(xt, xt.T)))\ + + (1 - alpha) * np.dot(xs, np.dot(xs.T, np.dot(G, lT + lT.T))) return cg(a, b, M, reg=eta, f=f, df=df, G0=None, numItermax=numItermax, numItermaxEmd=numInnerItermax, stopThr=stopThr, stopThr2=stopInnerThr, verbose=verbose, log=log) -def sinkhorn_laplace(a, b, xs, xt, M, reg=.1, eta=1., alpha=0.5, - numItermax=1000, stopThr=1e-5, numInnerItermax=1000, - stopInnerThr=1e-6, log=False, verbose=False, **kwargs): - r"""Solve the entropic regularized optimal transport problem (OT) with Laplacian regularization - - .. math:: - \gamma = arg\min_\gamma <\gamma,M>_F + reg\Omega_e(\gamma) + eta\Omega_\alpha(\gamma) - - s.t.\ \gamma 1 = a - - \gamma^T 1= b - - \gamma\geq 0 - - where: - - - a and b are source and target weights (sum to 1) - - xs and xt are source and target samples - - M is the (ns,nt) metric cost matrix - - :math:`\Omega_e` is the entropic regularization term :math:`\Omega_e - (\gamma)=\sum_{i,j} \gamma_{i,j}\log(\gamma_{i,j})` - - :math:`\Omega_\alpha` is the Laplacian regularization term - :math:`\Omega_\alpha = (1-\alpha)/n_s^2\sum_{i,j}S^s_{i,j}\|T(\mathbf{x}^s_i)-T(\mathbf{x}^s_j)\|^2+\alpha/n_t^2\sum_{i,j}S^t_{i,j}^'\|T(\mathbf{x}^t_i)-T(\mathbf{x}^t_j)\|^2` - with :math:`S^s_{i,j}, S^t_{i,j}` denoting source and target similarity matrices and :math:`T(\cdot)` being a barycentric mapping - - The algorithm used for solving the problem is the conditional gradient algorithm as proposed in [5]. - - Parameters - ---------- - a : np.ndarray (ns,) - samples weights in the source domain - b : np.ndarray (nt,) - samples weights in the target domain - xs : np.ndarray (ns,d) - samples in the source domain - xt : np.ndarray (nt,d) - samples in the target domain - M : np.ndarray (ns,nt) - loss matrix - reg : float - Regularization term for entropic regularization >0 - eta : float - Regularization term for Laplacian regularization - alpha : float - Regularization term for source domain's importance in regularization - numItermax : int, optional - Max number of iterations - stopThr : float, optional - Stop threshold on error (inner sinkhorn solver) (>0) - numInnerItermax : int, optional - Max number of iterations (inner CG solver) - stopInnerThr : float, optional - Stop threshold on error (inner CG solver) (>0) - verbose : bool, optional - Print information along iterations - log : bool, optional - record log if True - - - Returns - ------- - gamma : (ns x nt) ndarray - Optimal transportation matrix for the given parameters - log : dict - log dictionary return only if log==True in parameters - - - References - ---------- - - .. [5] N. Courty; R. Flamary; D. Tuia; A. Rakotomamonjy, - "Optimal Transport for Domain Adaptation," in IEEE - Transactions on Pattern Analysis and Machine Intelligence , - vol.PP, no.99, pp.1-1 - - See Also - -------- - ot.lp.emd : Unregularized OT - ot.optim.cg : General regularized OT - - """ - if 'sim' not in kwargs: - kwargs['sim'] = 'knn' - - if kwargs['sim'] == 'gauss': - if 'rbfparam' not in kwargs: - kwargs['rbfparam'] = 1 / (2 * (np.mean(dist(xs, xs, 'sqeuclidean')) ** 2)) - sS = kernel(xs, xs, method=kwargs['sim'], sigma=kwargs['rbfparam']) - sT = kernel(xt, xt, method=kwargs['sim'], sigma=kwargs['rbfparam']) - - elif kwargs['sim'] == 'knn': - if 'nn' not in kwargs: - kwargs['nn'] = 5 - - from sklearn.neighbors import kneighbors_graph - - sS = kneighbors_graph(xs, kwargs['nn']).toarray() - sS = (sS + sS.T) / 2 - sT = kneighbors_graph(xt, kwargs['nn']).toarray() - sT = (sT + sT.T) / 2 - - lS = laplacian(sS) - lT = laplacian(sT) - - def f(G): - return alpha*np.trace(np.dot(xt.T, np.dot(G.T, np.dot(lS, np.dot(G, xt))))) \ - + (1-alpha)*np.trace(np.dot(xs.T, np.dot(G, np.dot(lT, np.dot(G.T, xs))))) - - def df(G): - return alpha*np.dot(lS + lS.T, np.dot(G, np.dot(xt, xt.T)))\ - +(1-alpha)*np.dot(xs, np.dot(xs.T, np.dot(G, lT + lT.T))) - - return gcg(a, b, M, reg, eta, f, df, G0=None, numItermax=numItermax, stopThr=stopThr, - numInnerItermax=numInnerItermax, stopThr2=stopInnerThr, - verbose=verbose, log=log) def distribution_estimation_uniform(X): """estimates a uniform distribution from an array of samples X @@ -989,7 +871,6 @@ def distribution_estimation_uniform(X): The uniform distribution estimated from X """ - return unif(X.shape[0]) @@ -1016,7 +897,6 @@ class BaseTransport(BaseEstimator): inverse_transform method should always get as input a Xt parameter """ - def fit(self, Xs=None, ys=None, Xt=None, yt=None): """Build a coupling matrix from source and target sets of samples (Xs, ys) and (Xt, yt) @@ -1077,7 +957,6 @@ class BaseTransport(BaseEstimator): return self - def fit_transform(self, Xs=None, ys=None, Xt=None, yt=None): """Build a coupling matrix from source and target sets of samples (Xs, ys) and (Xt, yt) and transports source samples Xs onto target @@ -1106,7 +985,6 @@ class BaseTransport(BaseEstimator): return self.fit(Xs, ys, Xt, yt).transform(Xs, ys, Xt, yt) - def transform(self, Xs=None, ys=None, Xt=None, yt=None, batch_size=128): """Transports source samples Xs onto target ones Xt @@ -1174,7 +1052,6 @@ class BaseTransport(BaseEstimator): return transp_Xs - def inverse_transform(self, Xs=None, ys=None, Xt=None, yt=None, batch_size=128): """Transports target samples Xt onto target samples Xs @@ -1287,7 +1164,6 @@ class LinearTransport(BaseTransport): """ - def __init__(self, reg=1e-8, bias=True, log=False, distribution_estimation=distribution_estimation_uniform): self.bias = bias @@ -1295,7 +1171,6 @@ class LinearTransport(BaseTransport): self.reg = reg self.distribution_estimation = distribution_estimation - def fit(self, Xs=None, ys=None, Xt=None, yt=None): """Build a coupling matrix from source and target sets of samples (Xs, ys) and (Xt, yt) @@ -1343,7 +1218,6 @@ class LinearTransport(BaseTransport): return self - def transform(self, Xs=None, ys=None, Xt=None, yt=None, batch_size=128): """Transports source samples Xs onto target ones Xt @@ -1376,7 +1250,6 @@ class LinearTransport(BaseTransport): return transp_Xs - def inverse_transform(self, Xs=None, ys=None, Xt=None, yt=None, batch_size=128): """Transports target samples Xt onto target samples Xs @@ -1461,7 +1334,6 @@ class SinkhornTransport(BaseTransport): 26, 2013 """ - def __init__(self, reg_e=1., max_iter=1000, tol=10e-9, verbose=False, log=False, metric="sqeuclidean", norm=None, @@ -1478,7 +1350,6 @@ class SinkhornTransport(BaseTransport): self.distribution_estimation = distribution_estimation self.out_of_sample_map = out_of_sample_map - def fit(self, Xs=None, ys=None, Xt=None, yt=None): """Build a coupling matrix from source and target sets of samples (Xs, ys) and (Xt, yt) @@ -1561,7 +1432,6 @@ class EMDTransport(BaseTransport): on Pattern Analysis and Machine Intelligence , vol.PP, no.99, pp.1-1 """ - def __init__(self, metric="sqeuclidean", norm=None, log=False, distribution_estimation=distribution_estimation_uniform, out_of_sample_map='ferradans', limit_max=10, @@ -1574,7 +1444,6 @@ class EMDTransport(BaseTransport): self.out_of_sample_map = out_of_sample_map self.max_iter = max_iter - def fit(self, Xs, ys=None, Xt=None, yt=None): """Build a coupling matrix from source and target sets of samples (Xs, ys) and (Xt, yt) @@ -1671,7 +1540,6 @@ class SinkhornLpl1Transport(BaseTransport): """ - def __init__(self, reg_e=1., reg_cl=0.1, max_iter=10, max_inner_iter=200, log=False, tol=10e-9, verbose=False, @@ -1691,7 +1559,6 @@ class SinkhornLpl1Transport(BaseTransport): self.out_of_sample_map = out_of_sample_map self.limit_max = limit_max - def fit(self, Xs, ys=None, Xt=None, yt=None): """Build a coupling matrix from source and target sets of samples (Xs, ys) and (Xt, yt) @@ -1751,6 +1618,8 @@ class EMDLaplaceTransport(BaseTransport): norm : string, optional (default=None) If given, normalize the ground metric to avoid numerical errors that can occur with large metric values. + similarity : string, optional (default="knn") + The similarity to use either knn or gaussian max_iter : int, optional (default=100) Max number of BCD iterations tol : float, optional (default=1e-5) @@ -1780,10 +1649,9 @@ class EMDLaplaceTransport(BaseTransport): on Pattern Analysis and Machine Intelligence , vol.PP, no.99, pp.1-1 """ - - def __init__(self, reg_lap = 1., reg_src=1., alpha=0.5, - metric="sqeuclidean", norm=None, max_iter=100, tol=1e-5, - max_inner_iter=100000, inner_tol=1e-6, log=False, verbose=False, + def __init__(self, reg_lap=1., reg_src=1., alpha=0.5, + metric="sqeuclidean", norm=None, similarity="knn", max_iter=100, tol=1e-9, + max_inner_iter=100000, inner_tol=1e-9, log=False, verbose=False, distribution_estimation=distribution_estimation_uniform, out_of_sample_map='ferradans'): self.reg_lap = reg_lap @@ -1791,6 +1659,7 @@ class EMDLaplaceTransport(BaseTransport): self.alpha = alpha self.metric = metric self.norm = norm + self.similarity = similarity self.max_iter = max_iter self.tol = tol self.max_inner_iter = max_inner_iter @@ -1800,7 +1669,6 @@ class EMDLaplaceTransport(BaseTransport): self.distribution_estimation = distribution_estimation self.out_of_sample_map = out_of_sample_map - def fit(self, Xs, ys=None, Xt=None, yt=None): """Build a coupling matrix from source and target sets of samples (Xs, ys) and (Xt, yt) @@ -1829,115 +1697,7 @@ class EMDLaplaceTransport(BaseTransport): super(EMDLaplaceTransport, self).fit(Xs, ys, Xt, yt) returned_ = emd_laplace(a=self.mu_s, b=self.mu_t, xs=self.xs_, - xt=self.xt_, M=self.cost_, eta=self.reg_lap, alpha=self.reg_src, - numItermax=self.max_iter, stopThr=self.tol, numInnerItermax=self.max_inner_iter, - stopInnerThr=self.inner_tol, log=self.log, verbose=self.verbose) - - # coupling estimation - if self.log: - self.coupling_, self.log_ = returned_ - else: - self.coupling_ = returned_ - self.log_ = dict() - return self - -class SinkhornLaplaceTransport(BaseTransport): - - """Domain Adapatation OT method based on entropic regularized OT with Laplacian regularization - - Parameters - ---------- - reg_e : float, optional (default=1) - Entropic regularization parameter - reg_lap : float, optional (default=1) - Laplacian regularization parameter - reg_src : float, optional (default=0.5) - Source relative importance in regularization - metric : string, optional (default="sqeuclidean") - The ground metric for the Wasserstein problem - norm : string, optional (default=None) - If given, normalize the ground metric to avoid numerical errors that - can occur with large metric values. - max_iter : int, optional (default=100) - Max number of BCD iterations - tol : float, optional (default=1e-5) - Stop threshold on relative loss decrease (>0) - max_inner_iter : int, optional (default=10) - Max number of iterations (inner CG solver) - inner_tol : float, optional (default=1e-6) - Stop threshold on error (inner CG solver) (>0) - log : int, optional (default=False) - Controls the logs of the optimization algorithm - distribution_estimation : callable, optional (defaults to the uniform) - The kind of distribution estimation to employ - out_of_sample_map : string, optional (default="ferradans") - The kind of out of sample mapping to apply to transport samples - from a domain into another one. Currently the only possible option is - "ferradans" which uses the method proposed in [6]. - - Attributes - ---------- - coupling_ : array-like, shape (n_source_samples, n_target_samples) - The optimal coupling - - References - ---------- - .. [1] N. Courty; R. Flamary; D. Tuia; A. Rakotomamonjy, - "Optimal Transport for Domain Adaptation," in IEEE Transactions - on Pattern Analysis and Machine Intelligence , vol.PP, no.99, pp.1-1 - """ - - - def __init__(self, reg_e=1., reg_lap=1., reg_src=0.5, - metric="sqeuclidean", norm=None, max_iter=100, tol=1e-9, - max_inner_iter=200, inner_tol=1e-6, log=False, verbose=False, - distribution_estimation=distribution_estimation_uniform, - out_of_sample_map='ferradans'): - - self.reg_e = reg_e - self.reg_lap = reg_lap - self.reg_src = reg_src - self.metric = metric - self.norm = norm - self.max_iter = max_iter - self.tol = tol - self.max_inner_iter = max_inner_iter - self.inner_tol = inner_tol - self.log = log - self.verbose = verbose - self.distribution_estimation = distribution_estimation - self.out_of_sample_map = out_of_sample_map - - - def fit(self, Xs, ys=None, Xt=None, yt=None): - """Build a coupling matrix from source and target sets of samples - (Xs, ys) and (Xt, yt) - - Parameters - ---------- - Xs : array-like, shape (n_source_samples, n_features) - The training input samples. - ys : array-like, shape (n_source_samples,) - The class labels - Xt : array-like, shape (n_target_samples, n_features) - The training input samples. - yt : array-like, shape (n_target_samples,) - The class labels. If some target samples are unlabeled, fill the - yt's elements with -1. - - Warning: Note that, due to this convention -1 cannot be used as a - class label - - Returns - ------- - self : object - Returns self. - """ - - super(SinkhornLaplaceTransport, self).fit(Xs, ys, Xt, yt) - - returned_ = sinkhorn_laplace(a=self.mu_s, b=self.mu_t, xs=self.xs_, - xt=self.xt_, M=self.cost_, reg=self.reg_e, eta=self.reg_lap, alpha=self.reg_src, + xt=self.xt_, M=self.cost_, sim=self.similarity, eta=self.reg_lap, alpha=self.reg_src, numItermax=self.max_iter, stopThr=self.tol, numInnerItermax=self.max_inner_iter, stopInnerThr=self.inner_tol, log=self.log, verbose=self.verbose) @@ -2008,7 +1768,6 @@ class SinkhornL1l2Transport(BaseTransport): """ - def __init__(self, reg_e=1., reg_cl=0.1, max_iter=10, max_inner_iter=200, tol=10e-9, verbose=False, log=False, @@ -2028,7 +1787,6 @@ class SinkhornL1l2Transport(BaseTransport): self.out_of_sample_map = out_of_sample_map self.limit_max = limit_max - def fit(self, Xs, ys=None, Xt=None, yt=None): """Build a coupling matrix from source and target sets of samples (Xs, ys) and (Xt, yt) @@ -2133,7 +1891,6 @@ class MappingTransport(BaseEstimator): """ - def __init__(self, mu=1, eta=0.001, bias=False, metric="sqeuclidean", norm=None, kernel="linear", sigma=1, max_iter=100, tol=1e-5, max_inner_iter=10, inner_tol=1e-6, log=False, verbose=False, @@ -2153,7 +1910,6 @@ class MappingTransport(BaseEstimator): self.verbose = verbose self.verbose2 = verbose2 - def fit(self, Xs=None, ys=None, Xt=None, yt=None): """Builds an optimal coupling and estimates the associated mapping from source and target sets of samples (Xs, ys) and (Xt, yt) @@ -2211,7 +1967,6 @@ class MappingTransport(BaseEstimator): return self - def transform(self, Xs): """Transports source samples Xs onto target ones Xt @@ -2305,7 +2060,6 @@ class UnbalancedSinkhornTransport(BaseTransport): """ - def __init__(self, reg_e=1., reg_m=0.1, method='sinkhorn', max_iter=10, tol=1e-9, verbose=False, log=False, metric="sqeuclidean", norm=None, @@ -2324,7 +2078,6 @@ class UnbalancedSinkhornTransport(BaseTransport): self.out_of_sample_map = out_of_sample_map self.limit_max = limit_max - def fit(self, Xs, ys=None, Xt=None, yt=None): """Build a coupling matrix from source and target sets of samples (Xs, ys) and (Xt, yt) @@ -2419,7 +2172,6 @@ class JCPOTTransport(BaseTransport): """ - def __init__(self, reg_e=.1, max_iter=10, tol=10e-9, verbose=False, log=False, metric="sqeuclidean", @@ -2432,7 +2184,6 @@ class JCPOTTransport(BaseTransport): self.metric = metric self.out_of_sample_map = out_of_sample_map - def fit(self, Xs, ys=None, Xt=None, yt=None): """Building coupling matrices from a list of source and target sets of samples (Xs, ys) and (Xt, yt) @@ -2477,7 +2228,6 @@ class JCPOTTransport(BaseTransport): return self - def transform(self, Xs=None, ys=None, Xt=None, yt=None, batch_size=128): """Transports source samples Xs onto target ones Xt diff --git a/ot/utils.py b/ot/utils.py index b8a6f44..a633be2 100644 --- a/ot/utils.py +++ b/ot/utils.py @@ -48,6 +48,7 @@ def kernel(x1, x2, method='gaussian', sigma=1, **kwargs): K = np.exp(-dist(x1, x2) / (2 * sigma**2)) return K + def laplacian(x): """Compute Laplacian matrix""" L = np.diag(np.sum(x, axis=0)) - x diff --git a/test/test_da.py b/test/test_da.py index 15f4308..372ebd4 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -602,6 +602,7 @@ def test_jcpot_transport_class(): # check that the oos method is working assert_equal(transp_Xs_new.shape, Xs_new.shape) + def test_emd_laplace_class(): """test_emd_laplace_transport """ @@ -654,56 +655,3 @@ def test_emd_laplace_class(): # test fit_transform transp_Xs = otda.fit_transform(Xs=Xs, Xt=Xt) assert_equal(transp_Xs.shape, Xs.shape) - -def test_sinkhorn_laplace_class(): - """test_sinkhorn_laplace_transport - """ - ns = 150 - nt = 200 - - Xs, ys = make_data_classif('3gauss', ns) - Xt, yt = make_data_classif('3gauss2', nt) - - otda = ot.da.SinkhornLaplaceTransport(reg_e = 1, reg_lap=0.01, max_iter=1000, tol=1e-9, verbose=False, log=True) - - # test its computed - otda.fit(Xs=Xs, ys=ys, Xt=Xt) - - assert hasattr(otda, "coupling_") - assert hasattr(otda, "log_") - - # test dimensions of coupling - assert_equal(otda.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) - - # test all margin constraints - mu_s = unif(ns) - mu_t = unif(nt) - - assert_allclose( - np.sum(otda.coupling_, axis=0), mu_t, rtol=1e-3, atol=1e-3) - assert_allclose( - np.sum(otda.coupling_, axis=1), mu_s, rtol=1e-3, atol=1e-3) - - # test transform - transp_Xs = otda.transform(Xs=Xs) - [assert_equal(x.shape, y.shape) for x, y in zip(transp_Xs, Xs)] - - Xs_new, _ = make_data_classif('3gauss', ns + 1) - transp_Xs_new = otda.transform(Xs_new) - - # check that the oos method is working - assert_equal(transp_Xs_new.shape, Xs_new.shape) - - # test inverse transform - transp_Xt = otda.inverse_transform(Xt=Xt) - assert_equal(transp_Xt.shape, Xt.shape) - - Xt_new, _ = make_data_classif('3gauss2', nt + 1) - transp_Xt_new = otda.inverse_transform(Xt=Xt_new) - - # check that the oos method is working - assert_equal(transp_Xt_new.shape, Xt_new.shape) - - # test fit_transform - transp_Xs = otda.fit_transform(Xs=Xs, Xt=Xt) - assert_equal(transp_Xs.shape, Xs.shape) \ No newline at end of file -- cgit v1.2.3 From c68b52d1623683e86555484bf9a4875a66957bb6 Mon Sep 17 00:00:00 2001 From: ievred Date: Wed, 8 Apr 2020 10:08:47 +0200 Subject: remove laplace from jcpot --- examples/plot_otda_jcpot.py | 10 +- examples/plot_otda_laplacian.py | 127 ----------------------- ot/bregman.py | 1 - ot/da.py | 216 ---------------------------------------- test/test_da.py | 54 ---------- 5 files changed, 5 insertions(+), 403 deletions(-) delete mode 100644 examples/plot_otda_laplacian.py (limited to 'ot/da.py') diff --git a/examples/plot_otda_jcpot.py b/examples/plot_otda_jcpot.py index 316fa8b..c495690 100644 --- a/examples/plot_otda_jcpot.py +++ b/examples/plot_otda_jcpot.py @@ -115,7 +115,7 @@ pl.axis('off') ############################################################################## # Instantiate JCPOT adaptation algorithm and fit it # ---------------------------------------------------------------------------- -otda = ot.da.JCPOTTransport(reg_e=1e-2, max_iter=1000, metric='sqeuclidean', tol=1e-9, verbose=True, log=True) +otda = ot.da.JCPOTTransport(reg_e=1, max_iter=1000, metric='sqeuclidean', tol=1e-9, verbose=True, log=True) otda.fit(all_Xr, all_Yr, xt) ws1 = otda.proportions_.dot(otda.log_['D2'][0]) @@ -126,8 +126,8 @@ pl.clf() plot_ax(dec1, 'Source 1') plot_ax(dec2, 'Source 2') plot_ax(dect, 'Target') -print_G(ot.bregman.sinkhorn(ws1, [], otda.log_['M'][0], reg=1e-2), xs1, ys1, xt) -print_G(ot.bregman.sinkhorn(ws2, [], otda.log_['M'][1], reg=1e-2), xs2, ys2, xt) +print_G(ot.bregman.sinkhorn(ws1, [], otda.log_['M'][0], reg=1e-1), xs1, ys1, xt) +print_G(ot.bregman.sinkhorn(ws2, [], otda.log_['M'][1], reg=1e-1), xs2, ys2, xt) pl.scatter(xs1[:, 0], xs1[:, 1], c=ys1, s=35, marker='x', cmap='Set1', vmax=9) pl.scatter(xs2[:, 0], xs2[:, 1], c=ys2, s=35, marker='+', cmap='Set1', vmax=9) pl.scatter(xt[:, 0], xt[:, 1], c=yt, s=35, marker='o', cmap='Set1', vmax=9) @@ -154,8 +154,8 @@ pl.clf() plot_ax(dec1, 'Source 1') plot_ax(dec2, 'Source 2') plot_ax(dect, 'Target') -print_G(ot.bregman.sinkhorn(ws1, [], otda.log_['M'][0], reg=1e-2), xs1, ys1, xt) -print_G(ot.bregman.sinkhorn(ws2, [], otda.log_['M'][1], reg=1e-2), xs2, ys2, xt) +print_G(ot.bregman.sinkhorn(ws1, [], otda.log_['M'][0], reg=1e-1), xs1, ys1, xt) +print_G(ot.bregman.sinkhorn(ws2, [], otda.log_['M'][1], reg=1e-1), xs2, ys2, xt) pl.scatter(xs1[:, 0], xs1[:, 1], c=ys1, s=35, marker='x', cmap='Set1', vmax=9) pl.scatter(xs2[:, 0], xs2[:, 1], c=ys2, s=35, marker='+', cmap='Set1', vmax=9) pl.scatter(xt[:, 0], xt[:, 1], c=yt, s=35, marker='o', cmap='Set1', vmax=9) diff --git a/examples/plot_otda_laplacian.py b/examples/plot_otda_laplacian.py deleted file mode 100644 index 965380c..0000000 --- a/examples/plot_otda_laplacian.py +++ /dev/null @@ -1,127 +0,0 @@ -# -*- coding: utf-8 -*- -""" -======================== -OT for domain adaptation -======================== - -This example introduces a domain adaptation in a 2D setting and OTDA -approache with Laplacian regularization. - -""" - -# Authors: Ievgen Redko - -# License: MIT License - -import matplotlib.pylab as pl -import ot - -############################################################################## -# Generate data -# ------------- - -n_source_samples = 150 -n_target_samples = 150 - -Xs, ys = ot.datasets.make_data_classif('3gauss', n_source_samples) -Xt, yt = ot.datasets.make_data_classif('3gauss2', n_target_samples) - - -############################################################################## -# Instantiate the different transport algorithms and fit them -# ----------------------------------------------------------- - -# EMD Transport -ot_emd = ot.da.EMDTransport() -ot_emd.fit(Xs=Xs, Xt=Xt) - -# Sinkhorn Transport -ot_sinkhorn = ot.da.SinkhornTransport(reg_e=.01) -ot_sinkhorn.fit(Xs=Xs, Xt=Xt) - -# EMD Transport with Laplacian regularization -ot_emd_laplace = ot.da.EMDLaplaceTransport(reg_lap=100, reg_src=1) -ot_emd_laplace.fit(Xs=Xs, Xt=Xt) - -# transport source samples onto target samples -transp_Xs_emd = ot_emd.transform(Xs=Xs) -transp_Xs_sinkhorn = ot_sinkhorn.transform(Xs=Xs) -transp_Xs_emd_laplace = ot_emd_laplace.transform(Xs=Xs) - -############################################################################## -# Fig 1 : plots source and target samples -# --------------------------------------- - -pl.figure(1, figsize=(10, 5)) -pl.subplot(1, 2, 1) -pl.scatter(Xs[:, 0], Xs[:, 1], c=ys, marker='+', label='Source samples') -pl.xticks([]) -pl.yticks([]) -pl.legend(loc=0) -pl.title('Source samples') - -pl.subplot(1, 2, 2) -pl.scatter(Xt[:, 0], Xt[:, 1], c=yt, marker='o', label='Target samples') -pl.xticks([]) -pl.yticks([]) -pl.legend(loc=0) -pl.title('Target samples') -pl.tight_layout() - - -############################################################################## -# Fig 2 : plot optimal couplings and transported samples -# ------------------------------------------------------ - -param_img = {'interpolation': 'nearest'} - -pl.figure(2, figsize=(15, 8)) -pl.subplot(2, 3, 1) -pl.imshow(ot_emd.coupling_, **param_img) -pl.xticks([]) -pl.yticks([]) -pl.title('Optimal coupling\nEMDTransport') - -pl.figure(2, figsize=(15, 8)) -pl.subplot(2, 3, 2) -pl.imshow(ot_sinkhorn.coupling_, **param_img) -pl.xticks([]) -pl.yticks([]) -pl.title('Optimal coupling\nSinkhornTransport') - -pl.subplot(2, 3, 3) -pl.imshow(ot_emd_laplace.coupling_, **param_img) -pl.xticks([]) -pl.yticks([]) -pl.title('Optimal coupling\nEMDLaplaceTransport') - -pl.subplot(2, 3, 4) -pl.scatter(Xt[:, 0], Xt[:, 1], c=yt, marker='o', - label='Target samples', alpha=0.3) -pl.scatter(transp_Xs_emd[:, 0], transp_Xs_emd[:, 1], c=ys, - marker='+', label='Transp samples', s=30) -pl.xticks([]) -pl.yticks([]) -pl.title('Transported samples\nEmdTransport') -pl.legend(loc="lower left") - -pl.subplot(2, 3, 5) -pl.scatter(Xt[:, 0], Xt[:, 1], c=yt, marker='o', - label='Target samples', alpha=0.3) -pl.scatter(transp_Xs_sinkhorn[:, 0], transp_Xs_sinkhorn[:, 1], c=ys, - marker='+', label='Transp samples', s=30) -pl.xticks([]) -pl.yticks([]) -pl.title('Transported samples\nSinkhornTransport') - -pl.subplot(2, 3, 6) -pl.scatter(Xt[:, 0], Xt[:, 1], c=yt, marker='o', - label='Target samples', alpha=0.3) -pl.scatter(transp_Xs_emd_laplace[:, 0], transp_Xs_emd_laplace[:, 1], c=ys, - marker='+', label='Transp samples', s=30) -pl.xticks([]) -pl.yticks([]) -pl.title('Transported samples\nEMDLaplaceTransport') -pl.tight_layout() - -pl.show() diff --git a/ot/bregman.py b/ot/bregman.py index 61dfa52..410ae85 100644 --- a/ot/bregman.py +++ b/ot/bregman.py @@ -1607,7 +1607,6 @@ def jcpot_barycenter(Xs, Ys, Xt, reg, metric='sqeuclidean', numItermax=100, # build the cost matrix and the Gibbs kernel Mtmp = dist(Xs[d], Xt, metric=metric) - Mtmp = Mtmp / np.median(Mtmp) M.append(Mtmp) Ktmp = np.empty(Mtmp.shape, dtype=Mtmp.dtype) diff --git a/ot/da.py b/ot/da.py index 0fdd3be..90e9e92 100644 --- a/ot/da.py +++ b/ot/da.py @@ -748,115 +748,6 @@ def OT_mapping_linear(xs, xt, reg=1e-6, ws=None, return A, b -def emd_laplace(a, b, xs, xt, M, sim, eta, alpha, - numItermax, stopThr, numInnerItermax, - stopInnerThr, log=False, verbose=False, **kwargs): - r"""Solve the optimal transport problem (OT) with Laplacian regularization - - .. math:: - \gamma = arg\min_\gamma <\gamma,M>_F + eta\Omega_\alpha(\gamma) - - s.t.\ \gamma 1 = a - - \gamma^T 1= b - - \gamma\geq 0 - - where: - - - a and b are source and target weights (sum to 1) - - xs and xt are source and target samples - - M is the (ns,nt) metric cost matrix - - :math:`\Omega_\alpha` is the Laplacian regularization term - :math:`\Omega_\alpha = (1-\alpha)/n_s^2\sum_{i,j}S^s_{i,j}\|T(\mathbf{x}^s_i)-T(\mathbf{x}^s_j)\|^2+\alpha/n_t^2\sum_{i,j}S^t_{i,j}^'\|T(\mathbf{x}^t_i)-T(\mathbf{x}^t_j)\|^2` - with :math:`S^s_{i,j}, S^t_{i,j}` denoting source and target similarity matrices and :math:`T(\cdot)` being a barycentric mapping - - The algorithm used for solving the problem is the conditional gradient algorithm as proposed in [5]. - - Parameters - ---------- - a : np.ndarray (ns,) - samples weights in the source domain - b : np.ndarray (nt,) - samples weights in the target domain - xs : np.ndarray (ns,d) - samples in the source domain - xt : np.ndarray (nt,d) - samples in the target domain - M : np.ndarray (ns,nt) - loss matrix - eta : float - Regularization term for Laplacian regularization - alpha : float - Regularization term for source domain's importance in regularization - numItermax : int, optional - Max number of iterations - stopThr : float, optional - Stop threshold on error (inner emd solver) (>0) - numInnerItermax : int, optional - Max number of iterations (inner CG solver) - stopInnerThr : float, optional - Stop threshold on error (inner CG solver) (>0) - verbose : bool, optional - Print information along iterations - log : bool, optional - record log if True - - - Returns - ------- - gamma : (ns x nt) ndarray - Optimal transportation matrix for the given parameters - log : dict - log dictionary return only if log==True in parameters - - - References - ---------- - - .. [5] N. Courty; R. Flamary; D. Tuia; A. Rakotomamonjy, - "Optimal Transport for Domain Adaptation," in IEEE - Transactions on Pattern Analysis and Machine Intelligence , - vol.PP, no.99, pp.1-1 - - See Also - -------- - ot.lp.emd : Unregularized OT - ot.optim.cg : General regularized OT - - """ - if sim == 'gauss': - if 'rbfparam' not in kwargs: - kwargs['rbfparam'] = 1 / (2 * (np.mean(dist(xs, xs, 'sqeuclidean')) ** 2)) - sS = kernel(xs, xs, method=kwargs['sim'], sigma=kwargs['rbfparam']) - sT = kernel(xt, xt, method=kwargs['sim'], sigma=kwargs['rbfparam']) - - elif sim == 'knn': - if 'nn' not in kwargs: - kwargs['nn'] = 5 - - from sklearn.neighbors import kneighbors_graph - - sS = kneighbors_graph(xs, kwargs['nn']).toarray() - sS = (sS + sS.T) / 2 - sT = kneighbors_graph(xt, kwargs['nn']).toarray() - sT = (sT + sT.T) / 2 - - lS = laplacian(sS) - lT = laplacian(sT) - - def f(G): - return alpha * np.trace(np.dot(xt.T, np.dot(G.T, np.dot(lS, np.dot(G, xt))))) \ - + (1 - alpha) * np.trace(np.dot(xs.T, np.dot(G, np.dot(lT, np.dot(G.T, xs))))) - - def df(G): - return alpha * np.dot(lS + lS.T, np.dot(G, np.dot(xt, xt.T)))\ - + (1 - alpha) * np.dot(xs, np.dot(xs.T, np.dot(G, lT + lT.T))) - - return cg(a, b, M, reg=eta, f=f, df=df, G0=None, numItermax=numItermax, numItermaxEmd=numInnerItermax, - stopThr=stopThr, stopThr2=stopInnerThr, verbose=verbose, log=log) - - def distribution_estimation_uniform(X): """estimates a uniform distribution from an array of samples X @@ -1603,113 +1494,6 @@ class SinkhornLpl1Transport(BaseTransport): return self -class EMDLaplaceTransport(BaseTransport): - - """Domain Adapatation OT method based on Earth Mover's Distance with Laplacian regularization - - Parameters - ---------- - reg_lap : float, optional (default=1) - Laplacian regularization parameter - reg_src : float, optional (default=0.5) - Source relative importance in regularization - metric : string, optional (default="sqeuclidean") - The ground metric for the Wasserstein problem - norm : string, optional (default=None) - If given, normalize the ground metric to avoid numerical errors that - can occur with large metric values. - similarity : string, optional (default="knn") - The similarity to use either knn or gaussian - max_iter : int, optional (default=100) - Max number of BCD iterations - tol : float, optional (default=1e-5) - Stop threshold on relative loss decrease (>0) - max_inner_iter : int, optional (default=10) - Max number of iterations (inner CG solver) - inner_tol : float, optional (default=1e-6) - Stop threshold on error (inner CG solver) (>0) - log : int, optional (default=False) - Controls the logs of the optimization algorithm - distribution_estimation : callable, optional (defaults to the uniform) - The kind of distribution estimation to employ - out_of_sample_map : string, optional (default="ferradans") - The kind of out of sample mapping to apply to transport samples - from a domain into another one. Currently the only possible option is - "ferradans" which uses the method proposed in [6]. - - Attributes - ---------- - coupling_ : array-like, shape (n_source_samples, n_target_samples) - The optimal coupling - - References - ---------- - .. [1] N. Courty; R. Flamary; D. Tuia; A. Rakotomamonjy, - "Optimal Transport for Domain Adaptation," in IEEE Transactions - on Pattern Analysis and Machine Intelligence , vol.PP, no.99, pp.1-1 - """ - - def __init__(self, reg_lap=1., reg_src=1., alpha=0.5, - metric="sqeuclidean", norm=None, similarity="knn", max_iter=100, tol=1e-9, - max_inner_iter=100000, inner_tol=1e-9, log=False, verbose=False, - distribution_estimation=distribution_estimation_uniform, - out_of_sample_map='ferradans'): - self.reg_lap = reg_lap - self.reg_src = reg_src - self.alpha = alpha - self.metric = metric - self.norm = norm - self.similarity = similarity - self.max_iter = max_iter - self.tol = tol - self.max_inner_iter = max_inner_iter - self.inner_tol = inner_tol - self.log = log - self.verbose = verbose - self.distribution_estimation = distribution_estimation - self.out_of_sample_map = out_of_sample_map - - def fit(self, Xs, ys=None, Xt=None, yt=None): - """Build a coupling matrix from source and target sets of samples - (Xs, ys) and (Xt, yt) - - Parameters - ---------- - Xs : array-like, shape (n_source_samples, n_features) - The training input samples. - ys : array-like, shape (n_source_samples,) - The class labels - Xt : array-like, shape (n_target_samples, n_features) - The training input samples. - yt : array-like, shape (n_target_samples,) - The class labels. If some target samples are unlabeled, fill the - yt's elements with -1. - - Warning: Note that, due to this convention -1 cannot be used as a - class label - - Returns - ------- - self : object - Returns self. - """ - - super(EMDLaplaceTransport, self).fit(Xs, ys, Xt, yt) - - returned_ = emd_laplace(a=self.mu_s, b=self.mu_t, xs=self.xs_, - xt=self.xt_, M=self.cost_, sim=self.similarity, eta=self.reg_lap, alpha=self.reg_src, - numItermax=self.max_iter, stopThr=self.tol, numInnerItermax=self.max_inner_iter, - stopInnerThr=self.inner_tol, log=self.log, verbose=self.verbose) - - # coupling estimation - if self.log: - self.coupling_, self.log_ = returned_ - else: - self.coupling_ = returned_ - self.log_ = dict() - return self - - class SinkhornL1l2Transport(BaseTransport): """Domain Adapatation OT method based on sinkhorn algorithm + diff --git a/test/test_da.py b/test/test_da.py index 4eaf193..1517cec 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -601,57 +601,3 @@ def test_jcpot_transport_class(): # check that the oos method is working assert_equal(transp_Xs_new.shape, Xs_new.shape) - - -def test_emd_laplace_class(): - """test_emd_laplace_transport - """ - ns = 150 - nt = 200 - - Xs, ys = make_data_classif('3gauss', ns) - Xt, yt = make_data_classif('3gauss2', nt) - - otda = ot.da.EMDLaplaceTransport(reg_lap=0.01, max_iter=1000, tol=1e-9, verbose=False, log=True) - - # test its computed - otda.fit(Xs=Xs, ys=ys, Xt=Xt) - - assert hasattr(otda, "coupling_") - assert hasattr(otda, "log_") - - # test dimensions of coupling - assert_equal(otda.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) - - # test all margin constraints - mu_s = unif(ns) - mu_t = unif(nt) - - assert_allclose( - np.sum(otda.coupling_, axis=0), mu_t, rtol=1e-3, atol=1e-3) - assert_allclose( - np.sum(otda.coupling_, axis=1), mu_s, rtol=1e-3, atol=1e-3) - - # test transform - transp_Xs = otda.transform(Xs=Xs) - [assert_equal(x.shape, y.shape) for x, y in zip(transp_Xs, Xs)] - - Xs_new, _ = make_data_classif('3gauss', ns + 1) - transp_Xs_new = otda.transform(Xs_new) - - # check that the oos method is working - assert_equal(transp_Xs_new.shape, Xs_new.shape) - - # test inverse transform - transp_Xt = otda.inverse_transform(Xt=Xt) - assert_equal(transp_Xt.shape, Xt.shape) - - Xt_new, _ = make_data_classif('3gauss2', nt + 1) - transp_Xt_new = otda.inverse_transform(Xt=Xt_new) - - # check that the oos method is working - assert_equal(transp_Xt_new.shape, Xt_new.shape) - - # test fit_transform - transp_Xs = otda.fit_transform(Xs=Xs, Xt=Xt) - assert_equal(transp_Xs.shape, Xs.shape) -- cgit v1.2.3 From 55926517470df6ced506a934b8b9b5e23e023464 Mon Sep 17 00:00:00 2001 From: ievred Date: Wed, 8 Apr 2020 10:18:02 +0200 Subject: test+utils+readme --- README.md | 2 +- ot/da.py | 2 +- test/test_da.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'ot/da.py') diff --git a/README.md b/README.md index f439405..b6baf14 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ It provides the following solvers: * Non regularized free support Wasserstein barycenters [20]. * Unbalanced OT with KL relaxation distance and barycenter [10, 25]. * Screening Sinkhorn Algorithm for OT [26]. -* JCPOT algorithm for multi-source target shift [27]. +* JCPOT algorithm for multi-source domain adaptation with target shift [27]. Some demonstrations (both in Python and Jupyter Notebook format) are available in the examples folder. diff --git a/ot/da.py b/ot/da.py index 90e9e92..3a458eb 100644 --- a/ot/da.py +++ b/ot/da.py @@ -16,7 +16,7 @@ import scipy.linalg as linalg from .bregman import sinkhorn, jcpot_barycenter from .lp import emd -from .utils import unif, dist, kernel, cost_normalization, laplacian +from .utils import unif, dist, kernel, cost_normalization from .utils import check_params, BaseEstimator from .unbalanced import sinkhorn_unbalanced from .optim import cg diff --git a/test/test_da.py b/test/test_da.py index 1517cec..b58cf51 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -565,7 +565,7 @@ def test_jcpot_transport_class(): Xs = [Xs1, Xs2] ys = [ys1, ys2] - otda = ot.da.JCPOTTransport(reg_e=0.01, max_iter=1000, tol=1e-9, verbose=True, log=True) + otda = ot.da.JCPOTTransport(reg_e=1, max_iter=10000, tol=1e-9, verbose=True, log=True) # test its computed otda.fit(Xs=Xs, ys=ys, Xt=Xt) -- cgit v1.2.3 From d6ef8676cc3f94ba5d80acc9fd9745c9ed91819a Mon Sep 17 00:00:00 2001 From: ievred Date: Wed, 8 Apr 2020 10:28:57 +0200 Subject: remove jcpot from laplace --- examples/plot_otda_jcpot.py | 171 ----------------------------------------- ot/bregman.py | 160 --------------------------------------- ot/da.py | 181 +------------------------------------------- test/test_da.py | 56 +------------- 4 files changed, 3 insertions(+), 565 deletions(-) delete mode 100644 examples/plot_otda_jcpot.py (limited to 'ot/da.py') diff --git a/examples/plot_otda_jcpot.py b/examples/plot_otda_jcpot.py deleted file mode 100644 index 316fa8b..0000000 --- a/examples/plot_otda_jcpot.py +++ /dev/null @@ -1,171 +0,0 @@ -# -*- coding: utf-8 -*- -""" -======================== -OT for multi-source target shift -======================== - -This example introduces a target shift problem with two 2D source and 1 target domain. - -""" - -# Authors: Remi Flamary -# Ievgen Redko -# -# License: MIT License - -import pylab as pl -import numpy as np -import ot -from ot.datasets import make_data_classif - -############################################################################## -# Generate data -# ------------- -n = 50 -sigma = 0.3 -np.random.seed(1985) - -p1 = .2 -dec1 = [0, 2] - -p2 = .9 -dec2 = [0, -2] - -pt = .4 -dect = [4, 0] - -xs1, ys1 = make_data_classif('2gauss_prop', n, nz=sigma, p=p1, bias=dec1) -xs2, ys2 = make_data_classif('2gauss_prop', n + 1, nz=sigma, p=p2, bias=dec2) -xt, yt = make_data_classif('2gauss_prop', n, nz=sigma, p=pt, bias=dect) - -all_Xr = [xs1, xs2] -all_Yr = [ys1, ys2] -# %% - -da = 1.5 - - -def plot_ax(dec, name): - pl.plot([dec[0], dec[0]], [dec[1] - da, dec[1] + da], 'k', alpha=0.5) - pl.plot([dec[0] - da, dec[0] + da], [dec[1], dec[1]], 'k', alpha=0.5) - pl.text(dec[0] - .5, dec[1] + 2, name) - - -############################################################################## -# Fig 1 : plots source and target samples -# --------------------------------------- - -pl.figure(1) -pl.clf() -plot_ax(dec1, 'Source 1') -plot_ax(dec2, 'Source 2') -plot_ax(dect, 'Target') -pl.scatter(xs1[:, 0], xs1[:, 1], c=ys1, s=35, marker='x', cmap='Set1', vmax=9, - label='Source 1 ({:1.2f}, {:1.2f})'.format(1 - p1, p1)) -pl.scatter(xs2[:, 0], xs2[:, 1], c=ys2, s=35, marker='+', cmap='Set1', vmax=9, - label='Source 2 ({:1.2f}, {:1.2f})'.format(1 - p2, p2)) -pl.scatter(xt[:, 0], xt[:, 1], c=yt, s=35, marker='o', cmap='Set1', vmax=9, - label='Target ({:1.2f}, {:1.2f})'.format(1 - pt, pt)) -pl.title('Data') - -pl.legend() -pl.axis('equal') -pl.axis('off') - -############################################################################## -# Instantiate Sinkhorn transport algorithm and fit them for all source domains -# ---------------------------------------------------------------------------- -ot_sinkhorn = ot.da.SinkhornTransport(reg_e=1e-1, metric='sqeuclidean') - - -def print_G(G, xs, ys, xt): - for i in range(G.shape[0]): - for j in range(G.shape[1]): - if G[i, j] > 5e-4: - if ys[i]: - c = 'b' - else: - c = 'r' - pl.plot([xs[i, 0], xt[j, 0]], [xs[i, 1], xt[j, 1]], c, alpha=.2) - - -############################################################################## -# Fig 2 : plot optimal couplings and transported samples -# ------------------------------------------------------ -pl.figure(2) -pl.clf() -plot_ax(dec1, 'Source 1') -plot_ax(dec2, 'Source 2') -plot_ax(dect, 'Target') -print_G(ot_sinkhorn.fit(Xs=xs1, Xt=xt).coupling_, xs1, ys1, xt) -print_G(ot_sinkhorn.fit(Xs=xs2, Xt=xt).coupling_, xs2, ys2, xt) -pl.scatter(xs1[:, 0], xs1[:, 1], c=ys1, s=35, marker='x', cmap='Set1', vmax=9) -pl.scatter(xs2[:, 0], xs2[:, 1], c=ys2, s=35, marker='+', cmap='Set1', vmax=9) -pl.scatter(xt[:, 0], xt[:, 1], c=yt, s=35, marker='o', cmap='Set1', vmax=9) - -pl.plot([], [], 'r', alpha=.2, label='Mass from Class 1') -pl.plot([], [], 'b', alpha=.2, label='Mass from Class 2') - -pl.title('Independent OT') - -pl.legend() -pl.axis('equal') -pl.axis('off') - -############################################################################## -# Instantiate JCPOT adaptation algorithm and fit it -# ---------------------------------------------------------------------------- -otda = ot.da.JCPOTTransport(reg_e=1e-2, max_iter=1000, metric='sqeuclidean', tol=1e-9, verbose=True, log=True) -otda.fit(all_Xr, all_Yr, xt) - -ws1 = otda.proportions_.dot(otda.log_['D2'][0]) -ws2 = otda.proportions_.dot(otda.log_['D2'][1]) - -pl.figure(3) -pl.clf() -plot_ax(dec1, 'Source 1') -plot_ax(dec2, 'Source 2') -plot_ax(dect, 'Target') -print_G(ot.bregman.sinkhorn(ws1, [], otda.log_['M'][0], reg=1e-2), xs1, ys1, xt) -print_G(ot.bregman.sinkhorn(ws2, [], otda.log_['M'][1], reg=1e-2), xs2, ys2, xt) -pl.scatter(xs1[:, 0], xs1[:, 1], c=ys1, s=35, marker='x', cmap='Set1', vmax=9) -pl.scatter(xs2[:, 0], xs2[:, 1], c=ys2, s=35, marker='+', cmap='Set1', vmax=9) -pl.scatter(xt[:, 0], xt[:, 1], c=yt, s=35, marker='o', cmap='Set1', vmax=9) - -pl.plot([], [], 'r', alpha=.2, label='Mass from Class 1') -pl.plot([], [], 'b', alpha=.2, label='Mass from Class 2') - -pl.title('OT with prop estimation ({:1.3f},{:1.3f})'.format(otda.proportions_[0], otda.proportions_[1])) - -pl.legend() -pl.axis('equal') -pl.axis('off') - -############################################################################## -# Run oracle transport algorithm with known proportions -# ---------------------------------------------------------------------------- -h_res = np.array([1 - pt, pt]) - -ws1 = h_res.dot(otda.log_['D2'][0]) -ws2 = h_res.dot(otda.log_['D2'][1]) - -pl.figure(4) -pl.clf() -plot_ax(dec1, 'Source 1') -plot_ax(dec2, 'Source 2') -plot_ax(dect, 'Target') -print_G(ot.bregman.sinkhorn(ws1, [], otda.log_['M'][0], reg=1e-2), xs1, ys1, xt) -print_G(ot.bregman.sinkhorn(ws2, [], otda.log_['M'][1], reg=1e-2), xs2, ys2, xt) -pl.scatter(xs1[:, 0], xs1[:, 1], c=ys1, s=35, marker='x', cmap='Set1', vmax=9) -pl.scatter(xs2[:, 0], xs2[:, 1], c=ys2, s=35, marker='+', cmap='Set1', vmax=9) -pl.scatter(xt[:, 0], xt[:, 1], c=yt, s=35, marker='o', cmap='Set1', vmax=9) - -pl.plot([], [], 'r', alpha=.2, label='Mass from Class 1') -pl.plot([], [], 'b', alpha=.2, label='Mass from Class 2') - -pl.title('OT with known proportion ({:1.1f},{:1.1f})'.format(h_res[0], h_res[1])) - -pl.legend() -pl.axis('equal') -pl.axis('off') -pl.show() diff --git a/ot/bregman.py b/ot/bregman.py index 61dfa52..f737e81 100644 --- a/ot/bregman.py +++ b/ot/bregman.py @@ -1503,166 +1503,6 @@ def unmix(a, D, M, M0, h0, reg, reg0, alpha, numItermax=1000, return np.sum(K0, axis=1) -def jcpot_barycenter(Xs, Ys, Xt, reg, metric='sqeuclidean', numItermax=100, - stopThr=1e-6, verbose=False, log=False, **kwargs): - r'''Joint OT and proportion estimation for multi-source target shift as proposed in [27] - - The function solves the following optimization problem: - - .. math:: - - \mathbf{h} = arg\min_{\mathbf{h}}\quad \sum_{k=1}^{K} \lambda_k - W_{reg}((\mathbf{D}_2^{(k)} \mathbf{h})^T, \mathbf{a}) - - s.t. \ \forall k, \mathbf{D}_1^{(k)} \gamma_k \mathbf{1}_n= \mathbf{h} - - where : - - - :math:`\lambda_k` is the weight of k-th source domain - - :math:`W_{reg}(\cdot,\cdot)` is the entropic regularized Wasserstein distance (see ot.bregman.sinkhorn) - - :math:`\mathbf{D}_2^{(k)}` is a matrix of weights related to k-th source domain defined as in [p. 5, 27], its expected shape is `(n_k, C)` where `n_k` is the number of elements in the k-th source domain and `C` is the number of classes - - :math:`\mathbf{h}` is a vector of estimated proportions in the target domain of size C - - :math:`\mathbf{a}` is a uniform vector of weights in the target domain of size `n` - - :math:`\mathbf{D}_1^{(k)}` is a matrix of class assignments defined as in [p. 5, 27], its expected shape is `(n_k, C)` - - The problem consist in solving a Wasserstein barycenter problem to estimate the proportions :math:`\mathbf{h}` in the target domain. - - The algorithm used for solving the problem is the Iterative Bregman projections algorithm - with two sets of marginal constraints related to the unknown vector :math:`\mathbf{h}` and uniform tarhet distribution. - - Parameters - ---------- - Xs : list of K np.ndarray(nsk,d) - features of all source domains' samples - Ys : list of K np.ndarray(nsk,) - labels of all source domains' samples - Xt : np.ndarray (nt,d) - samples in the target domain - reg : float - Regularization term > 0 - metric : string, optional (default="sqeuclidean") - The ground metric for the Wasserstein problem - numItermax : int, optional - Max number of iterations - stopThr : float, optional - Stop threshold on relative change in the barycenter (>0) - log : bool, optional - record log if True - verbose : bool, optional (default=False) - Controls the verbosity of the optimization algorithm - - Returns - ------- - gamma : List of K (nsk x nt) ndarrays - Optimal transportation matrices for the given parameters for each pair of source and target domains - h : (C,) ndarray - proportion estimation in the target domain - log : dict - log dictionary return only if log==True in parameters - - - References - ---------- - - .. [27] Ievgen Redko, Nicolas Courty, Rémi Flamary, Devis Tuia - "Optimal transport for multi-source domain adaptation under target shift", - International Conference on Artificial Intelligence and Statistics (AISTATS), 2019. - - ''' - nbclasses = len(np.unique(Ys[0])) - nbdomains = len(Xs) - - # log dictionary - if log: - log = {'niter': 0, 'err': [], 'M': [], 'D1': [], 'D2': []} - - K = [] - M = [] - D1 = [] - D2 = [] - - # For each source domain, build cost matrices M, Gibbs kernels K and corresponding matrices D_1 and D_2 - for d in range(nbdomains): - dom = {} - nsk = Xs[d].shape[0] # get number of elements for this domain - dom['nbelem'] = nsk - classes = np.unique(Ys[d]) # get number of classes for this domain - - # format classes to start from 0 for convenience - if np.min(classes) != 0: - Ys[d] = Ys[d] - np.min(classes) - classes = np.unique(Ys[d]) - - # build the corresponding D_1 and D_2 matrices - Dtmp1 = np.zeros((nbclasses, nsk)) - Dtmp2 = np.zeros((nbclasses, nsk)) - - for c in classes: - nbelemperclass = np.sum(Ys[d] == c) - if nbelemperclass != 0: - Dtmp1[int(c), Ys[d] == c] = 1. - Dtmp2[int(c), Ys[d] == c] = 1. / (nbelemperclass) - D1.append(Dtmp1) - D2.append(Dtmp2) - - # build the cost matrix and the Gibbs kernel - Mtmp = dist(Xs[d], Xt, metric=metric) - Mtmp = Mtmp / np.median(Mtmp) - M.append(Mtmp) - - Ktmp = np.empty(Mtmp.shape, dtype=Mtmp.dtype) - np.divide(Mtmp, -reg, out=Ktmp) - np.exp(Ktmp, out=Ktmp) - K.append(Ktmp) - - # uniform target distribution - a = unif(np.shape(Xt)[0]) - - cpt = 0 # iterations count - err = 1 - old_bary = np.ones((nbclasses)) - - while (err > stopThr and cpt < numItermax): - - bary = np.zeros((nbclasses)) - - # update coupling matrices for marginal constraints w.r.t. uniform target distribution - for d in range(nbdomains): - K[d] = projC(K[d], a) - other = np.sum(K[d], axis=1) - bary = bary + np.log(np.dot(D1[d], other)) / nbdomains - - bary = np.exp(bary) - - # update coupling matrices for marginal constraints w.r.t. unknown proportions based on [Prop 4., 27] - for d in range(nbdomains): - new = np.dot(D2[d].T, bary) - K[d] = projR(K[d], new) - - err = np.linalg.norm(bary - old_bary) - cpt = cpt + 1 - old_bary = bary - - if log: - log['err'].append(err) - - if verbose: - if cpt % 200 == 0: - print('{:5s}|{:12s}'.format('It.', 'Err') + '\n' + '-' * 19) - print('{:5d}|{:8e}|'.format(cpt, err)) - - bary = bary / np.sum(bary) - - if log: - log['niter'] = cpt - log['M'] = M - log['D1'] = D1 - log['D2'] = D2 - return K, bary, log - else: - return K, bary - - def empirical_sinkhorn(X_s, X_t, reg, a=None, b=None, metric='sqeuclidean', numIterMax=10000, stopThr=1e-9, verbose=False, log=False, **kwargs): diff --git a/ot/da.py b/ot/da.py index 0fdd3be..474c944 100644 --- a/ot/da.py +++ b/ot/da.py @@ -14,7 +14,7 @@ Domain adaptation with optimal transport import numpy as np import scipy.linalg as linalg -from .bregman import sinkhorn, jcpot_barycenter +from .bregman import sinkhorn from .lp import emd from .utils import unif, dist, kernel, cost_normalization, laplacian from .utils import check_params, BaseEstimator @@ -2121,181 +2121,4 @@ class UnbalancedSinkhornTransport(BaseTransport): self.coupling_ = returned_ self.log_ = dict() - return self - - -class JCPOTTransport(BaseTransport): - - """Domain Adapatation OT method for multi-source target shift based on Wasserstein barycenter algorithm. - - Parameters - ---------- - reg_e : float, optional (default=1) - Entropic regularization parameter - max_iter : int, float, optional (default=10) - The minimum number of iteration before stopping the optimization - algorithm if no it has not converged - tol : float, optional (default=10e-9) - Stop threshold on error (inner sinkhorn solver) (>0) - verbose : bool, optional (default=False) - Controls the verbosity of the optimization algorithm - log : bool, optional (default=False) - Controls the logs of the optimization algorithm - metric : string, optional (default="sqeuclidean") - The ground metric for the Wasserstein problem - norm : string, optional (default=None) - If given, normalize the ground metric to avoid numerical errors that - can occur with large metric values. - distribution_estimation : callable, optional (defaults to the uniform) - The kind of distribution estimation to employ - out_of_sample_map : string, optional (default="ferradans") - The kind of out of sample mapping to apply to transport samples - from a domain into another one. Currently the only possible option is - "ferradans" which uses the method proposed in [6]. - - Attributes - ---------- - coupling_ : list of array-like objects, shape K x (n_source_samples, n_target_samples) - A set of optimal couplings between each source domain and the target domain - proportions_ : array-like, shape (n_classes,) - Estimated class proportions in the target domain - log_ : dictionary - The dictionary of log, empty dic if parameter log is not True - - References - ---------- - - .. [1] Ievgen Redko, Nicolas Courty, Rémi Flamary, Devis Tuia - "Optimal transport for multi-source domain adaptation under target shift", - International Conference on Artificial Intelligence and Statistics (AISTATS), - vol. 89, p.849-858, 2019. - - """ - - def __init__(self, reg_e=.1, max_iter=10, - tol=10e-9, verbose=False, log=False, - metric="sqeuclidean", - out_of_sample_map='ferradans'): - self.reg_e = reg_e - self.max_iter = max_iter - self.tol = tol - self.verbose = verbose - self.log = log - self.metric = metric - self.out_of_sample_map = out_of_sample_map - - def fit(self, Xs, ys=None, Xt=None, yt=None): - """Building coupling matrices from a list of source and target sets of samples - (Xs, ys) and (Xt, yt) - - Parameters - ---------- - Xs : list of K array-like objects, shape K x (nk_source_samples, n_features) - A list of the training input samples. - ys : list of K array-like objects, shape K x (nk_source_samples,) - A list of the class labels - Xt : array-like, shape (n_target_samples, n_features) - The training input samples. - yt : array-like, shape (n_target_samples,) - The class labels. If some target samples are unlabeled, fill the - yt's elements with -1. - - Warning: Note that, due to this convention -1 cannot be used as a - class label - - Returns - ------- - self : object - Returns self. - """ - - # check the necessary inputs parameters are here - if check_params(Xs=Xs, Xt=Xt, ys=ys): - - self.xs_ = Xs - self.xt_ = Xt - - returned_ = jcpot_barycenter(Xs=Xs, Ys=ys, Xt=Xt, reg=self.reg_e, - metric=self.metric, distrinumItermax=self.max_iter, stopThr=self.tol, - verbose=self.verbose, log=self.log) - - # deal with the value of log - if self.log: - self.coupling_, self.proportions_, self.log_ = returned_ - else: - self.coupling_, self.proportions_ = returned_ - self.log_ = dict() - - return self - - def transform(self, Xs=None, ys=None, Xt=None, yt=None, batch_size=128): - """Transports source samples Xs onto target ones Xt - - Parameters - ---------- - Xs : array-like, shape (n_source_samples, n_features) - The training input samples. - ys : array-like, shape (n_source_samples,) - The class labels - Xt : array-like, shape (n_target_samples, n_features) - The training input samples. - yt : array-like, shape (n_target_samples,) - The class labels. If some target samples are unlabeled, fill the - yt's elements with -1. - - Warning: Note that, due to this convention -1 cannot be used as a - class label - batch_size : int, optional (default=128) - The batch size for out of sample inverse transform - """ - - transp_Xs = [] - - # check the necessary inputs parameters are here - if check_params(Xs=Xs): - - if all([np.allclose(x, y) for x, y in zip(self.xs_, Xs)]): - - # perform standard barycentric mapping for each source domain - - for coupling in self.coupling_: - transp = coupling / np.sum(coupling, 1)[:, None] - - # set nans to 0 - transp[~ np.isfinite(transp)] = 0 - - # compute transported samples - transp_Xs.append(np.dot(transp, self.xt_)) - else: - - # perform out of sample mapping - indices = np.arange(Xs.shape[0]) - batch_ind = [ - indices[i:i + batch_size] - for i in range(0, len(indices), batch_size)] - - transp_Xs = [] - - for bi in batch_ind: - transp_Xs_ = [] - - # get the nearest neighbor in the sources domains - xs = np.concatenate(self.xs_, axis=0) - idx = np.argmin(dist(Xs[bi], xs), axis=1) - - # transport the source samples - for coupling in self.coupling_: - transp = coupling / np.sum( - coupling, 1)[:, None] - transp[~ np.isfinite(transp)] = 0 - transp_Xs_.append(np.dot(transp, self.xt_)) - - transp_Xs_ = np.concatenate(transp_Xs_, axis=0) - - # define the transported points - transp_Xs_ = transp_Xs_[idx, :] + Xs[bi] - xs[idx, :] - transp_Xs.append(transp_Xs_) - - transp_Xs = np.concatenate(transp_Xs, axis=0) - - return transp_Xs + return self \ No newline at end of file diff --git a/test/test_da.py b/test/test_da.py index 4eaf193..0e31f26 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -549,60 +549,6 @@ def test_linear_mapping_class(): np.testing.assert_allclose(Ct, Cst, rtol=1e-2, atol=1e-2) -def test_jcpot_transport_class(): - """test_jcpot_transport - """ - - ns1 = 150 - ns2 = 150 - nt = 200 - - Xs1, ys1 = make_data_classif('3gauss', ns1) - Xs2, ys2 = make_data_classif('3gauss', ns2) - - Xt, yt = make_data_classif('3gauss2', nt) - - Xs = [Xs1, Xs2] - ys = [ys1, ys2] - - otda = ot.da.JCPOTTransport(reg_e=0.01, max_iter=1000, tol=1e-9, verbose=True, log=True) - - # test its computed - otda.fit(Xs=Xs, ys=ys, Xt=Xt) - - assert hasattr(otda, "coupling_") - assert hasattr(otda, "proportions_") - assert hasattr(otda, "log_") - - # test dimensions of coupling - for i, xs in enumerate(Xs): - assert_equal(otda.coupling_[i].shape, ((xs.shape[0], Xt.shape[0]))) - - # test all margin constraints - mu_t = unif(nt) - - for i in range(len(Xs)): - # test margin constraints w.r.t. uniform target weights for each coupling matrix - assert_allclose( - np.sum(otda.coupling_[i], axis=0), mu_t, rtol=1e-3, atol=1e-3) - - # test margin constraints w.r.t. modified source weights for each source domain - - assert_allclose( - np.dot(otda.log_['D1'][i], np.sum(otda.coupling_[i], axis=1)), otda.proportions_, rtol=1e-3, - atol=1e-3) - - # test transform - transp_Xs = otda.transform(Xs=Xs) - [assert_equal(x.shape, y.shape) for x, y in zip(transp_Xs, Xs)] - - Xs_new, _ = make_data_classif('3gauss', ns1 + 1) - transp_Xs_new = otda.transform(Xs_new) - - # check that the oos method is working - assert_equal(transp_Xs_new.shape, Xs_new.shape) - - def test_emd_laplace_class(): """test_emd_laplace_transport """ @@ -654,4 +600,4 @@ def test_emd_laplace_class(): # test fit_transform transp_Xs = otda.fit_transform(Xs=Xs, Xt=Xt) - assert_equal(transp_Xs.shape, Xs.shape) + assert_equal(transp_Xs.shape, Xs.shape) \ No newline at end of file -- cgit v1.2.3 From 37412f59a94e8607fdbd5f7f29434a70ebe18688 Mon Sep 17 00:00:00 2001 From: ievred Date: Wed, 8 Apr 2020 11:05:50 +0200 Subject: remove blank line --- ot/da.py | 2 +- test/test_da.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'ot/da.py') diff --git a/ot/da.py b/ot/da.py index 474c944..108609f 100644 --- a/ot/da.py +++ b/ot/da.py @@ -2121,4 +2121,4 @@ class UnbalancedSinkhornTransport(BaseTransport): self.coupling_ = returned_ self.log_ = dict() - return self \ No newline at end of file + return self diff --git a/test/test_da.py b/test/test_da.py index 0e31f26..befec43 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -600,4 +600,4 @@ def test_emd_laplace_class(): # test fit_transform transp_Xs = otda.fit_transform(Xs=Xs, Xt=Xt) - assert_equal(transp_Xs.shape, Xs.shape) \ No newline at end of file + assert_equal(transp_Xs.shape, Xs.shape) -- cgit v1.2.3 From 0b402fd7c7e07043afd3a9df9d75bc424730b06f Mon Sep 17 00:00:00 2001 From: ievred Date: Wed, 8 Apr 2020 16:05:01 +0200 Subject: add label prop + inverse --- ot/da.py | 171 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- test/test_da.py | 47 ++++++++++++++++ 2 files changed, 214 insertions(+), 4 deletions(-) (limited to 'ot/da.py') diff --git a/ot/da.py b/ot/da.py index 3a458eb..29b0a8b 100644 --- a/ot/da.py +++ b/ot/da.py @@ -943,6 +943,46 @@ class BaseTransport(BaseEstimator): return transp_Xs + def transform_labels(self, ys=None): + """Propagate source labels ys to obtain estimated target labels + + Parameters + ---------- + ys : array-like, shape (n_source_samples,) + The class labels + + Returns + ------- + transp_ys : array-like, shape (n_target_samples,) + Estimated target labels. + """ + + # check the necessary inputs parameters are here + if check_params(ys=ys): + + classes = np.unique(ys) + n = len(classes) + D1 = np.zeros((n, len(ys))) + + # perform label propagation + transp = self.coupling_ / np.sum(self.coupling_, 1)[:, None] + + # set nans to 0 + transp[~ np.isfinite(transp)] = 0 + + if np.min(classes) != 0: + ys = ys - np.min(classes) + classes = np.unique(ys) + + for c in classes: + D1[int(c), ys == c] = 1 + + # compute transported samples + transp_ys = np.dot(D1, transp) + + return np.argmax(transp_ys,axis=0) + + def inverse_transform(self, Xs=None, ys=None, Xt=None, yt=None, batch_size=128): """Transports target samples Xt onto target samples Xs @@ -1010,6 +1050,44 @@ class BaseTransport(BaseEstimator): return transp_Xt + def inverse_transform_labels(self, yt=None): + """Propagate target labels yt to obtain estimated source labels ys + + Parameters + ---------- + yt : array-like, shape (n_target_samples,) + + Returns + ------- + transp_ys : array-like, shape (n_source_samples,) + Estimated source labels. + """ + + # check the necessary inputs parameters are here + if check_params(yt=yt): + + classes = np.unique(yt) + n = len(classes) + D1 = np.zeros((n, len(yt))) + + # perform label propagation + transp = self.coupling_ / np.sum(self.coupling_, 1)[:, None] + + # set nans to 0 + transp[~ np.isfinite(transp)] = 0 + + if np.min(classes) != 0: + yt = yt - np.min(classes) + classes = np.unique(yt) + + for c in classes: + D1[int(c), yt == c] = 1 + + # compute transported samples + transp_ys = np.dot(D1, transp.T) + + return np.argmax(transp_ys,axis=0) + class LinearTransport(BaseTransport): @@ -2017,10 +2095,10 @@ class JCPOTTransport(BaseTransport): Parameters ---------- - Xs : array-like, shape (n_source_samples, n_features) - The training input samples. - ys : array-like, shape (n_source_samples,) - The class labels + Xs : list of K array-like objects, shape K x (nk_source_samples, n_features) + A list of the training input samples. + ys : list of K array-like objects, shape K x (nk_source_samples,) + A list of the class labels Xt : array-like, shape (n_target_samples, n_features) The training input samples. yt : array-like, shape (n_target_samples,) @@ -2083,3 +2161,88 @@ class JCPOTTransport(BaseTransport): transp_Xs = np.concatenate(transp_Xs, axis=0) return transp_Xs + + def transform_labels(self, ys=None): + """Propagate source labels ys to obtain target labels + + Parameters + ---------- + ys : list of K array-like objects, shape K x (nk_source_samples,) + A list of the class labels + + Returns + ------- + yt : array-like, shape (n_target_samples,) + Estimated target labels. + """ + + # check the necessary inputs parameters are here + if check_params(ys=ys): + yt = np.zeros((len(np.unique(np.concatenate(ys))),self.xt_.shape[0])) + for i in range(len(ys)): + classes = np.unique(ys[i]) + n = len(classes) + ns = len(ys[i]) + + # perform label propagation + transp = self.coupling_[i] / np.sum(self.coupling_[i], 1)[:, None] + + # set nans to 0 + transp[~ np.isfinite(transp)] = 0 + + if self.log: + D1 = self.log_['D1'][i] + else: + D1 = np.zeros((n, ns)) + + if np.min(classes) != 0: + ys = ys - np.min(classes) + classes = np.unique(ys) + + for c in classes: + D1[int(c), ys == c] = 1 + # compute transported samples + yt = yt + np.dot(D1, transp)/len(ys) + + return np.argmax(yt,axis=0) + + def inverse_transform_labels(self, yt=None): + """Propagate source labels ys to obtain target labels + + Parameters + ---------- + yt : array-like, shape (n_source_samples,) + The target class labels + + Returns + ------- + transp_ys : list of K array-like objects, shape K x (nk_source_samples,) + A list of estimated source labels + """ + + # check the necessary inputs parameters are here + if check_params(yt=yt): + transp_ys = [] + classes = np.unique(yt) + n = len(classes) + D1 = np.zeros((n, len(yt))) + + if np.min(classes) != 0: + yt = yt - np.min(classes) + classes = np.unique(yt) + + for c in classes: + D1[int(c), yt == c] = 1 + + for i in range(len(self.xs_)): + + # perform label propagation + transp = self.coupling_[i] / np.sum(self.coupling_[i], 1)[:, None] + + # set nans to 0 + transp[~ np.isfinite(transp)] = 0 + + # compute transported labels + transp_ys.append(np.argmax(np.dot(D1, transp.T),axis=0)) + + return transp_ys diff --git a/test/test_da.py b/test/test_da.py index c54dab7..4eb6de0 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -65,6 +65,14 @@ def test_sinkhorn_lpl1_transport_class(): transp_Xs = otda.fit_transform(Xs=Xs, ys=ys, Xt=Xt) assert_equal(transp_Xs.shape, Xs.shape) + # check label propagation + transp_yt = otda.transform_labels(ys) + assert_equal(transp_yt.shape[0], yt.shape[0]) + + # check inverse label propagation + transp_ys = otda.inverse_transform_labels(yt) + assert_equal(transp_ys.shape[0], ys.shape[0]) + # test unsupervised vs semi-supervised mode otda_unsup = ot.da.SinkhornLpl1Transport() otda_unsup.fit(Xs=Xs, ys=ys, Xt=Xt) @@ -129,6 +137,14 @@ def test_sinkhorn_l1l2_transport_class(): transp_Xt = otda.inverse_transform(Xt=Xt) assert_equal(transp_Xt.shape, Xt.shape) + # check label propagation + transp_yt = otda.transform_labels(ys) + assert_equal(transp_yt.shape[0], yt.shape[0]) + + # check inverse label propagation + transp_ys = otda.inverse_transform_labels(yt) + assert_equal(transp_ys.shape[0], ys.shape[0]) + Xt_new, _ = make_data_classif('3gauss2', nt + 1) transp_Xt_new = otda.inverse_transform(Xt=Xt_new) @@ -210,6 +226,14 @@ def test_sinkhorn_transport_class(): transp_Xt = otda.inverse_transform(Xt=Xt) assert_equal(transp_Xt.shape, Xt.shape) + # check label propagation + transp_yt = otda.transform_labels(ys) + assert_equal(transp_yt.shape[0], yt.shape[0]) + + # check inverse label propagation + transp_ys = otda.inverse_transform_labels(yt) + assert_equal(transp_ys.shape[0], ys.shape[0]) + Xt_new, _ = make_data_classif('3gauss2', nt + 1) transp_Xt_new = otda.inverse_transform(Xt=Xt_new) @@ -271,6 +295,14 @@ def test_unbalanced_sinkhorn_transport_class(): transp_Xs = otda.transform(Xs=Xs) assert_equal(transp_Xs.shape, Xs.shape) + # check label propagation + transp_yt = otda.transform_labels(ys) + assert_equal(transp_yt.shape[0], yt.shape[0]) + + # check inverse label propagation + transp_ys = otda.inverse_transform_labels(yt) + assert_equal(transp_ys.shape[0], ys.shape[0]) + Xs_new, _ = make_data_classif('3gauss', ns + 1) transp_Xs_new = otda.transform(Xs_new) @@ -353,6 +385,14 @@ def test_emd_transport_class(): transp_Xt = otda.inverse_transform(Xt=Xt) assert_equal(transp_Xt.shape, Xt.shape) + # check label propagation + transp_yt = otda.transform_labels(ys) + assert_equal(transp_yt.shape[0], yt.shape[0]) + + # check inverse label propagation + transp_ys = otda.inverse_transform_labels(yt) + assert_equal(transp_ys.shape[0], ys.shape[0]) + Xt_new, _ = make_data_classif('3gauss2', nt + 1) transp_Xt_new = otda.inverse_transform(Xt=Xt_new) @@ -602,6 +642,13 @@ def test_jcpot_transport_class(): # check that the oos method is working assert_equal(transp_Xs_new.shape, Xs_new.shape) + # check label propagation + transp_yt = otda.transform_labels(ys) + assert_equal(transp_yt.shape[0], yt.shape[0]) + + # check inverse label propagation + transp_ys = otda.inverse_transform_labels(yt) + [assert_equal(x.shape, y.shape) for x, y in zip(transp_ys, ys)] def test_jcpot_barycenter(): """test_jcpot_barycenter -- cgit v1.2.3 From 1a4c264cc9b2cb0bb89840ee9175177e86eef3ef Mon Sep 17 00:00:00 2001 From: ievred Date: Wed, 8 Apr 2020 16:34:39 +0200 Subject: added label normalization to utils --- ot/da.py | 75 ++++++++++++++++++++++++++++----------------------------- ot/utils.py | 22 +++++++++++++++++ test/test_da.py | 1 + 3 files changed, 60 insertions(+), 38 deletions(-) (limited to 'ot/da.py') diff --git a/ot/da.py b/ot/da.py index 29b0a8b..4318c0d 100644 --- a/ot/da.py +++ b/ot/da.py @@ -16,7 +16,7 @@ import scipy.linalg as linalg from .bregman import sinkhorn, jcpot_barycenter from .lp import emd -from .utils import unif, dist, kernel, cost_normalization +from .utils import unif, dist, kernel, cost_normalization, label_normalization from .utils import check_params, BaseEstimator from .unbalanced import sinkhorn_unbalanced from .optim import cg @@ -786,6 +786,9 @@ class BaseTransport(BaseEstimator): transform method should always get as input a Xs parameter inverse_transform method should always get as input a Xt parameter + + transform_labels method should always get as input a ys parameter + inverse_transform_labels method should always get as input a yt parameter """ def fit(self, Xs=None, ys=None, Xt=None, yt=None): @@ -944,7 +947,7 @@ class BaseTransport(BaseEstimator): return transp_Xs def transform_labels(self, ys=None): - """Propagate source labels ys to obtain estimated target labels + """Propagate source labels ys to obtain estimated target labels as in [27] Parameters ---------- @@ -955,14 +958,23 @@ class BaseTransport(BaseEstimator): ------- transp_ys : array-like, shape (n_target_samples,) Estimated target labels. + + References + ---------- + + .. [27] Ievgen Redko, Nicolas Courty, Rémi Flamary, Devis Tuia + "Optimal transport for multi-source domain adaptation under target shift", + International Conference on Artificial Intelligence and Statistics (AISTATS), 2019. + """ # check the necessary inputs parameters are here if check_params(ys=ys): - classes = np.unique(ys) + ysTemp = label_normalization(np.copy(ys)) + classes = np.unique(ysTemp) n = len(classes) - D1 = np.zeros((n, len(ys))) + D1 = np.zeros((n, len(ysTemp))) # perform label propagation transp = self.coupling_ / np.sum(self.coupling_, 1)[:, None] @@ -970,18 +982,13 @@ class BaseTransport(BaseEstimator): # set nans to 0 transp[~ np.isfinite(transp)] = 0 - if np.min(classes) != 0: - ys = ys - np.min(classes) - classes = np.unique(ys) - for c in classes: - D1[int(c), ys == c] = 1 + D1[int(c), ysTemp == c] = 1 # compute transported samples transp_ys = np.dot(D1, transp) - return np.argmax(transp_ys,axis=0) - + return np.argmax(transp_ys, axis=0) def inverse_transform(self, Xs=None, ys=None, Xt=None, yt=None, batch_size=128): @@ -1066,9 +1073,10 @@ class BaseTransport(BaseEstimator): # check the necessary inputs parameters are here if check_params(yt=yt): - classes = np.unique(yt) + ytTemp = label_normalization(np.copy(yt)) + classes = np.unique(ytTemp) n = len(classes) - D1 = np.zeros((n, len(yt))) + D1 = np.zeros((n, len(ytTemp))) # perform label propagation transp = self.coupling_ / np.sum(self.coupling_, 1)[:, None] @@ -1076,17 +1084,13 @@ class BaseTransport(BaseEstimator): # set nans to 0 transp[~ np.isfinite(transp)] = 0 - if np.min(classes) != 0: - yt = yt - np.min(classes) - classes = np.unique(yt) - for c in classes: - D1[int(c), yt == c] = 1 + D1[int(c), ytTemp == c] = 1 # compute transported samples transp_ys = np.dot(D1, transp.T) - return np.argmax(transp_ys,axis=0) + return np.argmax(transp_ys, axis=0) class LinearTransport(BaseTransport): @@ -2163,7 +2167,7 @@ class JCPOTTransport(BaseTransport): return transp_Xs def transform_labels(self, ys=None): - """Propagate source labels ys to obtain target labels + """Propagate source labels ys to obtain target labels as in [27] Parameters ---------- @@ -2178,11 +2182,12 @@ class JCPOTTransport(BaseTransport): # check the necessary inputs parameters are here if check_params(ys=ys): - yt = np.zeros((len(np.unique(np.concatenate(ys))),self.xt_.shape[0])) + yt = np.zeros((len(np.unique(np.concatenate(ys))), self.xt_.shape[0])) for i in range(len(ys)): - classes = np.unique(ys[i]) + ysTemp = label_normalization(np.copy(ys[i])) + classes = np.unique(ysTemp) n = len(classes) - ns = len(ys[i]) + ns = len(ysTemp) # perform label propagation transp = self.coupling_[i] / np.sum(self.coupling_[i], 1)[:, None] @@ -2195,16 +2200,13 @@ class JCPOTTransport(BaseTransport): else: D1 = np.zeros((n, ns)) - if np.min(classes) != 0: - ys = ys - np.min(classes) - classes = np.unique(ys) - for c in classes: - D1[int(c), ys == c] = 1 + D1[int(c), ysTemp == c] = 1 + # compute transported samples - yt = yt + np.dot(D1, transp)/len(ys) + yt = yt + np.dot(D1, transp) / len(ys) - return np.argmax(yt,axis=0) + return np.argmax(yt, axis=0) def inverse_transform_labels(self, yt=None): """Propagate source labels ys to obtain target labels @@ -2223,16 +2225,13 @@ class JCPOTTransport(BaseTransport): # check the necessary inputs parameters are here if check_params(yt=yt): transp_ys = [] - classes = np.unique(yt) + ytTemp = label_normalization(np.copy(yt)) + classes = np.unique(ytTemp) n = len(classes) - D1 = np.zeros((n, len(yt))) - - if np.min(classes) != 0: - yt = yt - np.min(classes) - classes = np.unique(yt) + D1 = np.zeros((n, len(ytTemp))) for c in classes: - D1[int(c), yt == c] = 1 + D1[int(c), ytTemp == c] = 1 for i in range(len(self.xs_)): @@ -2243,6 +2242,6 @@ class JCPOTTransport(BaseTransport): transp[~ np.isfinite(transp)] = 0 # compute transported labels - transp_ys.append(np.argmax(np.dot(D1, transp.T),axis=0)) + transp_ys.append(np.argmax(np.dot(D1, transp.T), axis=0)) return transp_ys diff --git a/ot/utils.py b/ot/utils.py index b71458b..c154f99 100644 --- a/ot/utils.py +++ b/ot/utils.py @@ -200,6 +200,28 @@ def dots(*args): return reduce(np.dot, args) +def label_normalization(y, start=0): + """ Transform labels to start at a given value + + Parameters + ---------- + y : array-like, shape (n, ) + The vector of labels to be normalized. + start : int + Desired value for the smallest label in y (default=0) + + Returns + ------- + y : array-like, shape (n1, ) + The input vector of labels normalized according to given start value. + """ + + diff = np.min(np.unique(y)) - start + if diff != 0: + y -= diff + return y + + def fun(f, q_in, q_out): """ Utility function for parmap with no serializing problems """ while True: diff --git a/test/test_da.py b/test/test_da.py index 4eb6de0..d96046d 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -650,6 +650,7 @@ def test_jcpot_transport_class(): transp_ys = otda.inverse_transform_labels(yt) [assert_equal(x.shape, y.shape) for x, y in zip(transp_ys, ys)] + def test_jcpot_barycenter(): """test_jcpot_barycenter """ -- cgit v1.2.3 From 749378a50abd763c87f5cf24a4b2e0dff2a6ec6a Mon Sep 17 00:00:00 2001 From: ievred Date: Wed, 15 Apr 2020 11:12:23 +0200 Subject: fix soft labels, remove gammas from jcpot --- ot/bregman.py | 9 ++++----- ot/da.py | 40 +++++++++++++++++++++------------------- test/test_da.py | 14 +++++++++++++- 3 files changed, 38 insertions(+), 25 deletions(-) (limited to 'ot/da.py') diff --git a/ot/bregman.py b/ot/bregman.py index c44c141..543dbaa 100644 --- a/ot/bregman.py +++ b/ot/bregman.py @@ -1553,8 +1553,6 @@ def jcpot_barycenter(Xs, Ys, Xt, reg, metric='sqeuclidean', numItermax=100, Returns ------- - gamma : List of K (nsk x nt) ndarrays - Optimal transportation matrices for the given parameters for each pair of source and target domains h : (C,) ndarray proportion estimation in the target domain log : dict @@ -1574,7 +1572,7 @@ def jcpot_barycenter(Xs, Ys, Xt, reg, metric='sqeuclidean', numItermax=100, # log dictionary if log: - log = {'niter': 0, 'err': [], 'M': [], 'D1': [], 'D2': []} + log = {'niter': 0, 'err': [], 'M': [], 'D1': [], 'D2': [], 'gamma': []} K = [] M = [] @@ -1657,9 +1655,10 @@ def jcpot_barycenter(Xs, Ys, Xt, reg, metric='sqeuclidean', numItermax=100, log['M'] = M log['D1'] = D1 log['D2'] = D2 - return K, bary, log + log['gamma'] = K + return bary, log else: - return K, bary + return bary def empirical_sinkhorn(X_s, X_t, reg, a=None, b=None, metric='sqeuclidean', diff --git a/ot/da.py b/ot/da.py index 4318c0d..30e5a61 100644 --- a/ot/da.py +++ b/ot/da.py @@ -956,8 +956,8 @@ class BaseTransport(BaseEstimator): Returns ------- - transp_ys : array-like, shape (n_target_samples,) - Estimated target labels. + transp_ys : array-like, shape (n_target_samples, nb_classes) + Estimated soft target labels. References ---------- @@ -985,10 +985,10 @@ class BaseTransport(BaseEstimator): for c in classes: D1[int(c), ysTemp == c] = 1 - # compute transported samples + # compute propagated labels transp_ys = np.dot(D1, transp) - return np.argmax(transp_ys, axis=0) + return transp_ys.T def inverse_transform(self, Xs=None, ys=None, Xt=None, yt=None, batch_size=128): @@ -1066,8 +1066,8 @@ class BaseTransport(BaseEstimator): Returns ------- - transp_ys : array-like, shape (n_source_samples,) - Estimated source labels. + transp_ys : array-like, shape (n_source_samples, nb_classes) + Estimated soft source labels. """ # check the necessary inputs parameters are here @@ -1087,10 +1087,10 @@ class BaseTransport(BaseEstimator): for c in classes: D1[int(c), ytTemp == c] = 1 - # compute transported samples + # compute propagated samples transp_ys = np.dot(D1, transp.T) - return np.argmax(transp_ys, axis=0) + return transp_ys.T class LinearTransport(BaseTransport): @@ -2083,13 +2083,15 @@ class JCPOTTransport(BaseTransport): returned_ = jcpot_barycenter(Xs=Xs, Ys=ys, Xt=Xt, reg=self.reg_e, metric=self.metric, distrinumItermax=self.max_iter, stopThr=self.tol, - verbose=self.verbose, log=self.log) + verbose=self.verbose, log=True) + + self.coupling_ = returned_[1]['gamma'] # deal with the value of log if self.log: - self.coupling_, self.proportions_, self.log_ = returned_ + self.proportions_, self.log_ = returned_ else: - self.coupling_, self.proportions_ = returned_ + self.proportions_ = returned_ self.log_ = dict() return self @@ -2176,8 +2178,8 @@ class JCPOTTransport(BaseTransport): Returns ------- - yt : array-like, shape (n_target_samples,) - Estimated target labels. + yt : array-like, shape (n_target_samples, nb_classes) + Estimated soft target labels. """ # check the necessary inputs parameters are here @@ -2203,10 +2205,10 @@ class JCPOTTransport(BaseTransport): for c in classes: D1[int(c), ysTemp == c] = 1 - # compute transported samples + # compute propagated labels yt = yt + np.dot(D1, transp) / len(ys) - return np.argmax(yt, axis=0) + return yt.T def inverse_transform_labels(self, yt=None): """Propagate source labels ys to obtain target labels @@ -2218,8 +2220,8 @@ class JCPOTTransport(BaseTransport): Returns ------- - transp_ys : list of K array-like objects, shape K x (nk_source_samples,) - A list of estimated source labels + transp_ys : list of K array-like objects, shape K x (nk_source_samples, nb_classes) + A list of estimated soft source labels """ # check the necessary inputs parameters are here @@ -2241,7 +2243,7 @@ class JCPOTTransport(BaseTransport): # set nans to 0 transp[~ np.isfinite(transp)] = 0 - # compute transported labels - transp_ys.append(np.argmax(np.dot(D1, transp.T), axis=0)) + # compute propagated labels + transp_ys.append(np.dot(D1, transp.T).T) return transp_ys diff --git a/test/test_da.py b/test/test_da.py index d96046d..70296bf 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -68,10 +68,12 @@ def test_sinkhorn_lpl1_transport_class(): # check label propagation transp_yt = otda.transform_labels(ys) assert_equal(transp_yt.shape[0], yt.shape[0]) + assert_equal(transp_yt.shape[1], len(np.unique(ys))) # check inverse label propagation transp_ys = otda.inverse_transform_labels(yt) assert_equal(transp_ys.shape[0], ys.shape[0]) + assert_equal(transp_ys.shape[1], len(np.unique(yt))) # test unsupervised vs semi-supervised mode otda_unsup = ot.da.SinkhornLpl1Transport() @@ -140,10 +142,12 @@ def test_sinkhorn_l1l2_transport_class(): # check label propagation transp_yt = otda.transform_labels(ys) assert_equal(transp_yt.shape[0], yt.shape[0]) + assert_equal(transp_yt.shape[1], len(np.unique(ys))) # check inverse label propagation transp_ys = otda.inverse_transform_labels(yt) assert_equal(transp_ys.shape[0], ys.shape[0]) + assert_equal(transp_ys.shape[1], len(np.unique(yt))) Xt_new, _ = make_data_classif('3gauss2', nt + 1) transp_Xt_new = otda.inverse_transform(Xt=Xt_new) @@ -229,10 +233,12 @@ def test_sinkhorn_transport_class(): # check label propagation transp_yt = otda.transform_labels(ys) assert_equal(transp_yt.shape[0], yt.shape[0]) + assert_equal(transp_yt.shape[1], len(np.unique(ys))) # check inverse label propagation transp_ys = otda.inverse_transform_labels(yt) assert_equal(transp_ys.shape[0], ys.shape[0]) + assert_equal(transp_ys.shape[1], len(np.unique(yt))) Xt_new, _ = make_data_classif('3gauss2', nt + 1) transp_Xt_new = otda.inverse_transform(Xt=Xt_new) @@ -298,10 +304,12 @@ def test_unbalanced_sinkhorn_transport_class(): # check label propagation transp_yt = otda.transform_labels(ys) assert_equal(transp_yt.shape[0], yt.shape[0]) + assert_equal(transp_yt.shape[1], len(np.unique(ys))) # check inverse label propagation transp_ys = otda.inverse_transform_labels(yt) assert_equal(transp_ys.shape[0], ys.shape[0]) + assert_equal(transp_ys.shape[1], len(np.unique(yt))) Xs_new, _ = make_data_classif('3gauss', ns + 1) transp_Xs_new = otda.transform(Xs_new) @@ -388,10 +396,12 @@ def test_emd_transport_class(): # check label propagation transp_yt = otda.transform_labels(ys) assert_equal(transp_yt.shape[0], yt.shape[0]) + assert_equal(transp_yt.shape[1], len(np.unique(ys))) # check inverse label propagation transp_ys = otda.inverse_transform_labels(yt) assert_equal(transp_ys.shape[0], ys.shape[0]) + assert_equal(transp_ys.shape[1], len(np.unique(yt))) Xt_new, _ = make_data_classif('3gauss2', nt + 1) transp_Xt_new = otda.inverse_transform(Xt=Xt_new) @@ -645,10 +655,12 @@ def test_jcpot_transport_class(): # check label propagation transp_yt = otda.transform_labels(ys) assert_equal(transp_yt.shape[0], yt.shape[0]) + assert_equal(transp_yt.shape[1], len(np.unique(ys))) # check inverse label propagation transp_ys = otda.inverse_transform_labels(yt) - [assert_equal(x.shape, y.shape) for x, y in zip(transp_ys, ys)] + [assert_equal(x.shape[0], y.shape[0]) for x, y in zip(transp_ys, ys)] + [assert_equal(x.shape[1], len(np.unique(y))) for x, y in zip(transp_ys, ys)] def test_jcpot_barycenter(): -- cgit v1.2.3 From 77cae32e956d87cfc1f69a0ea7a28c906347070d Mon Sep 17 00:00:00 2001 From: ievred Date: Wed, 15 Apr 2020 16:43:40 +0200 Subject: check conflict da --- ot/da.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ot/da.py') diff --git a/ot/da.py b/ot/da.py index 108609f..272af91 100644 --- a/ot/da.py +++ b/ot/da.py @@ -16,7 +16,7 @@ import scipy.linalg as linalg from .bregman import sinkhorn from .lp import emd -from .utils import unif, dist, kernel, cost_normalization, laplacian +from .utils import unif, dist, kernel, cost_normalization, label_normalization from .utils import check_params, BaseEstimator from .unbalanced import sinkhorn_unbalanced from .optim import cg -- cgit v1.2.3 From 6c64f16acd7421ff6278119eb68877d845820fac Mon Sep 17 00:00:00 2001 From: ievred Date: Wed, 15 Apr 2020 17:13:31 +0200 Subject: import laplacian --- ot/da.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ot/da.py') diff --git a/ot/da.py b/ot/da.py index ef05181..a6d7d9b 100644 --- a/ot/da.py +++ b/ot/da.py @@ -16,7 +16,7 @@ import scipy.linalg as linalg from .bregman import sinkhorn, jcpot_barycenter from .lp import emd -from .utils import unif, dist, kernel, cost_normalization, label_normalization +from .utils import unif, dist, kernel, cost_normalization, label_normalization, laplacian from .utils import check_params, BaseEstimator from .unbalanced import sinkhorn_unbalanced from .optim import cg -- cgit v1.2.3 From 14fbb88333971f575510747fd6e9217ec50d9780 Mon Sep 17 00:00:00 2001 From: ievred Date: Thu, 16 Apr 2020 16:09:22 +0200 Subject: references added --- README.md | 7 +++++-- ot/da.py | 6 ++++++ 2 files changed, 11 insertions(+), 2 deletions(-) (limited to 'ot/da.py') diff --git a/README.md b/README.md index b6baf14..304f249 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ It provides the following solvers: * Smooth optimal transport solvers (dual and semi-dual) for KL and squared L2 regularizations [17]. * Non regularized Wasserstein barycenters [16] with LP solver (only small scale). * Bregman projections for Wasserstein barycenter [3], convolutional barycenter [21] and unmixing [4]. -* Optimal transport for domain adaptation with group lasso regularization [5] +* Optimal transport for domain adaptation with group lasso and Laplacian regularization [5] * Conditional gradient [6] and Generalized conditional gradient for regularized OT [7]. * Linear OT [14] and Joint OT matrix and mapping estimation [8]. * Wasserstein Discriminant Analysis [11] (requires autograd + pymanopt). @@ -183,6 +183,7 @@ The contributors to this library are * [Hicham Janati](https://hichamjanati.github.io/) (Unbalanced OT) * [Romain Tavenard](https://rtavenar.github.io/) (1d Wasserstein) * [Mokhtar Z. Alaya](http://mzalaya.github.io/) (Screenkhorn) +* [Ievgen Redko](https://ievred.github.io/) This toolbox benefit a lot from open source research and we would like to thank the following persons for providing some code (in various languages): @@ -259,4 +260,6 @@ You can also post bug reports and feature requests in Github issues. Make sure t [26] Alaya M. Z., Bérar M., Gasso G., Rakotomamonjy A. (2019). [Screening Sinkhorn Algorithm for Regularized Optimal Transport](https://papers.nips.cc/paper/9386-screening-sinkhorn-algorithm-for-regularized-optimal-transport), Advances in Neural Information Processing Systems 33 (NeurIPS). -[27] Redko I., Courty N., Flamary R., Tuia D. (2019). [Optimal Transport for Multi-source Domain Adaptation under Target Shift](http://proceedings.mlr.press/v89/redko19a.html), Proceedings of the Twenty-Second International Conference on Artificial Intelligence and Statistics (AISTATS) 22, 2019. \ No newline at end of file +[27] Redko I., Courty N., Flamary R., Tuia D. (2019). [Optimal Transport for Multi-source Domain Adaptation under Target Shift](http://proceedings.mlr.press/v89/redko19a.html), Proceedings of the Twenty-Second International Conference on Artificial Intelligence and Statistics (AISTATS) 22, 2019. + +[28] Flamary R., Courty N., Tuia D., Rakotomamonjy A. (2014). [Optimal transport with Laplacian regularization: Applications to domain adaptation and shape matching](https://remi.flamary.com/biblio/flamary2014optlaplace.pdf), NIPS Workshop on Optimal Transport and Machine Learning OTML, 2014. diff --git a/ot/da.py b/ot/da.py index a6d7d9b..be959d6 100644 --- a/ot/da.py +++ b/ot/da.py @@ -818,6 +818,9 @@ def emd_laplace(a, b, xs, xt, M, sim, eta, alpha, "Optimal Transport for Domain Adaptation," in IEEE Transactions on Pattern Analysis and Machine Intelligence , vol.PP, no.99, pp.1-1 + .. [28] R. Flamary, N. Courty, D. Tuia, A. Rakotomamonjy, + "Optimal transport with Laplacian regularization: Applications to domain adaptation and shape matching," + in NIPS Workshop on Optimal Transport and Machine Learning OTML, 2014. See Also -------- @@ -1729,6 +1732,9 @@ class EMDLaplaceTransport(BaseTransport): .. [1] N. Courty; R. Flamary; D. Tuia; A. Rakotomamonjy, "Optimal Transport for Domain Adaptation," in IEEE Transactions on Pattern Analysis and Machine Intelligence , vol.PP, no.99, pp.1-1 + .. [2] R. Flamary, N. Courty, D. Tuia, A. Rakotomamonjy, + "Optimal transport with Laplacian regularization: Applications to domain adaptation and shape matching," + in NIPS Workshop on Optimal Transport and Machine Learning OTML, 2014. """ def __init__(self, reg_lap=1., reg_src=1., alpha=0.5, -- cgit v1.2.3 From 126903381374a1d2f934190b208d134a0495dc65 Mon Sep 17 00:00:00 2001 From: ievred Date: Fri, 17 Apr 2020 16:41:14 +0200 Subject: added regulrization from [6]+fix other issues --- ot/da.py | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) (limited to 'ot/da.py') diff --git a/ot/da.py b/ot/da.py index be959d6..9e00dce 100644 --- a/ot/da.py +++ b/ot/da.py @@ -16,7 +16,7 @@ import scipy.linalg as linalg from .bregman import sinkhorn, jcpot_barycenter from .lp import emd -from .utils import unif, dist, kernel, cost_normalization, label_normalization, laplacian +from .utils import unif, dist, kernel, cost_normalization, label_normalization, laplacian, dots from .utils import check_params, BaseEstimator from .unbalanced import sinkhorn_unbalanced from .optim import cg @@ -748,7 +748,7 @@ def OT_mapping_linear(xs, xt, reg=1e-6, ws=None, return A, b -def emd_laplace(a, b, xs, xt, M, sim, eta, alpha, +def emd_laplace(a, b, xs, xt, M, sim, reg, eta, alpha, numItermax, stopThr, numInnerItermax, stopInnerThr, log=False, verbose=False, **kwargs): r"""Solve the optimal transport problem (OT) with Laplacian regularization @@ -785,6 +785,8 @@ def emd_laplace(a, b, xs, xt, M, sim, eta, alpha, samples in the target domain M : np.ndarray (ns,nt) loss matrix + reg : string + Type of Laplacian regularization eta : float Regularization term for Laplacian regularization alpha : float @@ -844,6 +846,8 @@ def emd_laplace(a, b, xs, xt, M, sim, eta, alpha, sS = (sS + sS.T) / 2 sT = kneighbors_graph(xt, kwargs['nn']).toarray() sT = (sT + sT.T) / 2 + else: + raise ValueError('Unknown similarity type {sim}. Currently supported similarity types are "knn" and "gauss".'.format(sim=sim)) lS = laplacian(sS) lT = laplacian(sT) @@ -852,9 +856,18 @@ def emd_laplace(a, b, xs, xt, M, sim, eta, alpha, return alpha * np.trace(np.dot(xt.T, np.dot(G.T, np.dot(lS, np.dot(G, xt))))) \ + (1 - alpha) * np.trace(np.dot(xs.T, np.dot(G, np.dot(lT, np.dot(G.T, xs))))) + ls2 = lS + lS.T + lt2 = lT + lT.T + xt2 = np.dot(xt, xt.T) + + if reg == 'disp': + Cs = -eta * alpha / xs.shape[0] * dots(ls2, xs, xt.T) + Ct = -eta * (1 - alpha) / xt.shape[0] * dots(xs, xt.T, lt2) + M = M + Cs + Ct + def df(G): - return alpha * np.dot(lS + lS.T, np.dot(G, np.dot(xt, xt.T)))\ - + (1 - alpha) * np.dot(xs, np.dot(xs.T, np.dot(G, lT + lT.T))) + return alpha * np.dot(ls2, np.dot(G, xt2))\ + + (1 - alpha) * np.dot(xs, np.dot(xs.T, np.dot(G, lt2))) return cg(a, b, M, reg=eta, f=f, df=df, G0=None, numItermax=numItermax, numItermaxEmd=numInnerItermax, stopThr=stopThr, stopThr2=stopInnerThr, verbose=verbose, log=log) @@ -1694,6 +1707,9 @@ class EMDLaplaceTransport(BaseTransport): Parameters ---------- + reg_type : string optional (default='pos') + Type of the regularization term: 'pos' and 'disp' for + regularization term defined in [2] and [6], respectively. reg_lap : float, optional (default=1) Laplacian regularization parameter reg_src : float, optional (default=0.5) @@ -1737,11 +1753,12 @@ class EMDLaplaceTransport(BaseTransport): in NIPS Workshop on Optimal Transport and Machine Learning OTML, 2014. """ - def __init__(self, reg_lap=1., reg_src=1., alpha=0.5, + def __init__(self, reg_type='pos', reg_lap=1., reg_src=1., alpha=0.5, metric="sqeuclidean", norm=None, similarity="knn", max_iter=100, tol=1e-9, max_inner_iter=100000, inner_tol=1e-9, log=False, verbose=False, distribution_estimation=distribution_estimation_uniform, out_of_sample_map='ferradans'): + self.reg = reg_type self.reg_lap = reg_lap self.reg_src = reg_src self.alpha = alpha @@ -1785,7 +1802,7 @@ class EMDLaplaceTransport(BaseTransport): super(EMDLaplaceTransport, self).fit(Xs, ys, Xt, yt) returned_ = emd_laplace(a=self.mu_s, b=self.mu_t, xs=self.xs_, - xt=self.xt_, M=self.cost_, sim=self.similarity, eta=self.reg_lap, alpha=self.reg_src, + xt=self.xt_, M=self.cost_, reg=self.reg, sim=self.similarity, eta=self.reg_lap, alpha=self.reg_src, numItermax=self.max_iter, stopThr=self.tol, numInnerItermax=self.max_inner_iter, stopInnerThr=self.inner_tol, log=self.log, verbose=self.verbose) -- cgit v1.2.3 From 07463285317ed5c989040edcefb5c0e8cd3ac034 Mon Sep 17 00:00:00 2001 From: ievred Date: Mon, 20 Apr 2020 10:39:13 +0200 Subject: added kwargs to sim + doc --- ot/da.py | 48 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 16 deletions(-) (limited to 'ot/da.py') diff --git a/ot/da.py b/ot/da.py index 9e00dce..8e26e31 100644 --- a/ot/da.py +++ b/ot/da.py @@ -748,7 +748,7 @@ def OT_mapping_linear(xs, xt, reg=1e-6, ws=None, return A, b -def emd_laplace(a, b, xs, xt, M, sim, reg, eta, alpha, +def emd_laplace(a, b, xs, xt, M, reg, eta, alpha, numItermax, stopThr, numInnerItermax, stopInnerThr, log=False, verbose=False, **kwargs): r"""Solve the optimal transport problem (OT) with Laplacian regularization @@ -803,7 +803,11 @@ def emd_laplace(a, b, xs, xt, M, sim, reg, eta, alpha, Print information along iterations log : bool, optional record log if True - + kwargs : dict + Dictionary with attributes 'sim' ('knn' or 'gauss') and + 'param' (int, float or None) for similarity type and its parameter to be used. + If 'param' is None, it is computed as mean pairwise Euclidean distance over the data set + or set to 3 when sim is 'gauss' or 'knn', respectively. Returns ------- @@ -830,24 +834,28 @@ def emd_laplace(a, b, xs, xt, M, sim, reg, eta, alpha, ot.optim.cg : General regularized OT """ - if sim == 'gauss': - if 'rbfparam' not in kwargs: - kwargs['rbfparam'] = 1 / (2 * (np.mean(dist(xs, xs, 'sqeuclidean')) ** 2)) - sS = kernel(xs, xs, method=kwargs['sim'], sigma=kwargs['rbfparam']) - sT = kernel(xt, xt, method=kwargs['sim'], sigma=kwargs['rbfparam']) + if not isinstance(kwargs['param'], (int, float, type(None))): + raise ValueError( + 'Similarity parameter should be an int or a float. Got {type} instead.'.format(type=type(kwargs['param']))) + + if kwargs['sim'] == 'gauss': + if kwargs['param'] is None: + kwargs['param'] = 1 / (2 * (np.mean(dist(xs, xs, 'sqeuclidean')) ** 2)) + sS = kernel(xs, xs, method=kwargs['sim'], sigma=kwargs['param']) + sT = kernel(xt, xt, method=kwargs['sim'], sigma=kwargs['param']) - elif sim == 'knn': - if 'nn' not in kwargs: - kwargs['nn'] = 5 + elif kwargs['sim'] == 'knn': + if kwargs['param'] is None: + kwargs['param'] = 3 from sklearn.neighbors import kneighbors_graph - sS = kneighbors_graph(xs, kwargs['nn']).toarray() + sS = kneighbors_graph(X=xs, n_neighbors=int(kwargs['param'])).toarray() sS = (sS + sS.T) / 2 - sT = kneighbors_graph(xt, kwargs['nn']).toarray() + sT = kneighbors_graph(xt, n_neighbors=int(kwargs['param'])).toarray() sT = (sT + sT.T) / 2 else: - raise ValueError('Unknown similarity type {sim}. Currently supported similarity types are "knn" and "gauss".'.format(sim=sim)) + raise ValueError('Unknown similarity type {sim}. Currently supported similarity types are "knn" and "gauss".'.format(sim=kwargs['sim'])) lS = laplacian(sS) lT = laplacian(sT) @@ -1721,6 +1729,9 @@ class EMDLaplaceTransport(BaseTransport): can occur with large metric values. similarity : string, optional (default="knn") The similarity to use either knn or gaussian + similarity_param : int or float, optional (default=3) + Parameter for the similarity: number of nearest neighbors or bandwidth + if similarity="knn" or "gaussian", respectively. max_iter : int, optional (default=100) Max number of BCD iterations tol : float, optional (default=1e-5) @@ -1754,7 +1765,7 @@ class EMDLaplaceTransport(BaseTransport): """ def __init__(self, reg_type='pos', reg_lap=1., reg_src=1., alpha=0.5, - metric="sqeuclidean", norm=None, similarity="knn", max_iter=100, tol=1e-9, + metric="sqeuclidean", norm=None, similarity="knn", similarity_param=None, max_iter=100, tol=1e-9, max_inner_iter=100000, inner_tol=1e-9, log=False, verbose=False, distribution_estimation=distribution_estimation_uniform, out_of_sample_map='ferradans'): @@ -1765,6 +1776,7 @@ class EMDLaplaceTransport(BaseTransport): self.metric = metric self.norm = norm self.similarity = similarity + self.sim_param = similarity_param self.max_iter = max_iter self.tol = tol self.max_inner_iter = max_inner_iter @@ -1801,10 +1813,14 @@ class EMDLaplaceTransport(BaseTransport): super(EMDLaplaceTransport, self).fit(Xs, ys, Xt, yt) + kwargs = dict() + kwargs["sim"] = self.similarity + kwargs["param"] = self.sim_param + returned_ = emd_laplace(a=self.mu_s, b=self.mu_t, xs=self.xs_, - xt=self.xt_, M=self.cost_, reg=self.reg, sim=self.similarity, eta=self.reg_lap, alpha=self.reg_src, + xt=self.xt_, M=self.cost_, reg=self.reg, eta=self.reg_lap, alpha=self.reg_src, numItermax=self.max_iter, stopThr=self.tol, numInnerItermax=self.max_inner_iter, - stopInnerThr=self.inner_tol, log=self.log, verbose=self.verbose) + stopInnerThr=self.inner_tol, log=self.log, verbose=self.verbose, **kwargs) # coupling estimation if self.log: -- cgit v1.2.3 From fd115a538deb8fa9dcf3169fcfa6b85aebd36b07 Mon Sep 17 00:00:00 2001 From: ievred Date: Mon, 20 Apr 2020 13:55:45 +0200 Subject: sim+sim param fixed --- ot/da.py | 53 +++++++++++++++++++++++++---------------------------- 1 file changed, 25 insertions(+), 28 deletions(-) (limited to 'ot/da.py') diff --git a/ot/da.py b/ot/da.py index 8e26e31..300af30 100644 --- a/ot/da.py +++ b/ot/da.py @@ -748,7 +748,7 @@ def OT_mapping_linear(xs, xt, reg=1e-6, ws=None, return A, b -def emd_laplace(a, b, xs, xt, M, reg, eta, alpha, +def emd_laplace(a, b, xs, xt, M, sim, sim_param, reg, eta, alpha, numItermax, stopThr, numInnerItermax, stopInnerThr, log=False, verbose=False, **kwargs): r"""Solve the optimal transport problem (OT) with Laplacian regularization @@ -785,6 +785,11 @@ def emd_laplace(a, b, xs, xt, M, reg, eta, alpha, samples in the target domain M : np.ndarray (ns,nt) loss matrix + sim : string, optional + Type of similarity ('knn' or 'gauss') used to construct the Laplacian. + sim_param : int or float, optional + Parameter (number of the nearest neighbors for sim='knn' + or bandwidth for sim='gauss' used to compute the Laplacian. reg : string Type of Laplacian regularization eta : float @@ -803,11 +808,6 @@ def emd_laplace(a, b, xs, xt, M, reg, eta, alpha, Print information along iterations log : bool, optional record log if True - kwargs : dict - Dictionary with attributes 'sim' ('knn' or 'gauss') and - 'param' (int, float or None) for similarity type and its parameter to be used. - If 'param' is None, it is computed as mean pairwise Euclidean distance over the data set - or set to 3 when sim is 'gauss' or 'knn', respectively. Returns ------- @@ -824,7 +824,7 @@ def emd_laplace(a, b, xs, xt, M, reg, eta, alpha, "Optimal Transport for Domain Adaptation," in IEEE Transactions on Pattern Analysis and Machine Intelligence , vol.PP, no.99, pp.1-1 - .. [28] R. Flamary, N. Courty, D. Tuia, A. Rakotomamonjy, + .. [30] R. Flamary, N. Courty, D. Tuia, A. Rakotomamonjy, "Optimal transport with Laplacian regularization: Applications to domain adaptation and shape matching," in NIPS Workshop on Optimal Transport and Machine Learning OTML, 2014. @@ -834,28 +834,28 @@ def emd_laplace(a, b, xs, xt, M, reg, eta, alpha, ot.optim.cg : General regularized OT """ - if not isinstance(kwargs['param'], (int, float, type(None))): + if not isinstance(sim_param, (int, float, type(None))): raise ValueError( - 'Similarity parameter should be an int or a float. Got {type} instead.'.format(type=type(kwargs['param']))) + 'Similarity parameter should be an int or a float. Got {type} instead.'.format(type=type(sim_param).__name__)) - if kwargs['sim'] == 'gauss': - if kwargs['param'] is None: - kwargs['param'] = 1 / (2 * (np.mean(dist(xs, xs, 'sqeuclidean')) ** 2)) - sS = kernel(xs, xs, method=kwargs['sim'], sigma=kwargs['param']) - sT = kernel(xt, xt, method=kwargs['sim'], sigma=kwargs['param']) + if sim == 'gauss': + if sim_param is None: + sim_param = 1 / (2 * (np.mean(dist(xs, xs, 'sqeuclidean')) ** 2)) + sS = kernel(xs, xs, method=sim, sigma=sim_param) + sT = kernel(xt, xt, method=sim, sigma=sim_param) - elif kwargs['sim'] == 'knn': - if kwargs['param'] is None: - kwargs['param'] = 3 + elif sim == 'knn': + if sim_param is None: + sim_param = 3 from sklearn.neighbors import kneighbors_graph - sS = kneighbors_graph(X=xs, n_neighbors=int(kwargs['param'])).toarray() + sS = kneighbors_graph(X=xs, n_neighbors=int(sim_param)).toarray() sS = (sS + sS.T) / 2 - sT = kneighbors_graph(xt, n_neighbors=int(kwargs['param'])).toarray() + sT = kneighbors_graph(xt, n_neighbors=int(sim_param)).toarray() sT = (sT + sT.T) / 2 else: - raise ValueError('Unknown similarity type {sim}. Currently supported similarity types are "knn" and "gauss".'.format(sim=kwargs['sim'])) + raise ValueError('Unknown similarity type {sim}. Currently supported similarity types are "knn" and "gauss".'.format(sim=sim)) lS = laplacian(sS) lT = laplacian(sT) @@ -1729,9 +1729,10 @@ class EMDLaplaceTransport(BaseTransport): can occur with large metric values. similarity : string, optional (default="knn") The similarity to use either knn or gaussian - similarity_param : int or float, optional (default=3) + similarity_param : int or float, optional (default=None) Parameter for the similarity: number of nearest neighbors or bandwidth - if similarity="knn" or "gaussian", respectively. + if similarity="knn" or "gaussian", respectively. If None is provided, + it is set to 3 or the average pairwise squared Euclidean distance, respectively. max_iter : int, optional (default=100) Max number of BCD iterations tol : float, optional (default=1e-5) @@ -1813,14 +1814,10 @@ class EMDLaplaceTransport(BaseTransport): super(EMDLaplaceTransport, self).fit(Xs, ys, Xt, yt) - kwargs = dict() - kwargs["sim"] = self.similarity - kwargs["param"] = self.sim_param - returned_ = emd_laplace(a=self.mu_s, b=self.mu_t, xs=self.xs_, - xt=self.xt_, M=self.cost_, reg=self.reg, eta=self.reg_lap, alpha=self.reg_src, + xt=self.xt_, M=self.cost_, sim=self.similarity, sim_param=self.sim_param, reg=self.reg, eta=self.reg_lap, alpha=self.reg_src, numItermax=self.max_iter, stopThr=self.tol, numInnerItermax=self.max_inner_iter, - stopInnerThr=self.inner_tol, log=self.log, verbose=self.verbose, **kwargs) + stopInnerThr=self.inner_tol, log=self.log, verbose=self.verbose) # coupling estimation if self.log: -- cgit v1.2.3 From 36b2e92d9ad5148208cc1bec9bc9133999bcdb1c Mon Sep 17 00:00:00 2001 From: ievred Date: Mon, 20 Apr 2020 14:04:32 +0200 Subject: added defaults for emd_laplace --- ot/da.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) (limited to 'ot/da.py') diff --git a/ot/da.py b/ot/da.py index 300af30..e615993 100644 --- a/ot/da.py +++ b/ot/da.py @@ -748,9 +748,9 @@ def OT_mapping_linear(xs, xt, reg=1e-6, ws=None, return A, b -def emd_laplace(a, b, xs, xt, M, sim, sim_param, reg, eta, alpha, - numItermax, stopThr, numInnerItermax, - stopInnerThr, log=False, verbose=False, **kwargs): +def emd_laplace(a, b, xs, xt, M, sim='knn', sim_param=None, reg='pos', eta=1, alpha=.5, + numItermax=100, stopThr=1e-9, numInnerItermax=100000, + stopInnerThr=1e-9, log=False, verbose=False): r"""Solve the optimal transport problem (OT) with Laplacian regularization .. math:: @@ -1765,15 +1765,14 @@ class EMDLaplaceTransport(BaseTransport): in NIPS Workshop on Optimal Transport and Machine Learning OTML, 2014. """ - def __init__(self, reg_type='pos', reg_lap=1., reg_src=1., alpha=0.5, - metric="sqeuclidean", norm=None, similarity="knn", similarity_param=None, max_iter=100, tol=1e-9, + def __init__(self, reg_type='pos', reg_lap=1., reg_src=1., metric="sqeuclidean", + norm=None, similarity="knn", similarity_param=None, max_iter=100, tol=1e-9, max_inner_iter=100000, inner_tol=1e-9, log=False, verbose=False, distribution_estimation=distribution_estimation_uniform, out_of_sample_map='ferradans'): self.reg = reg_type self.reg_lap = reg_lap self.reg_src = reg_src - self.alpha = alpha self.metric = metric self.norm = norm self.similarity = similarity @@ -1815,8 +1814,8 @@ class EMDLaplaceTransport(BaseTransport): super(EMDLaplaceTransport, self).fit(Xs, ys, Xt, yt) returned_ = emd_laplace(a=self.mu_s, b=self.mu_t, xs=self.xs_, - xt=self.xt_, M=self.cost_, sim=self.similarity, sim_param=self.sim_param, reg=self.reg, eta=self.reg_lap, alpha=self.reg_src, - numItermax=self.max_iter, stopThr=self.tol, numInnerItermax=self.max_inner_iter, + xt=self.xt_, M=self.cost_, sim=self.similarity, sim_param=self.sim_param, reg=self.reg, eta=self.reg_lap, + alpha=self.reg_src, numItermax=self.max_iter, stopThr=self.tol, numInnerItermax=self.max_inner_iter, stopInnerThr=self.inner_tol, log=self.log, verbose=self.verbose) # coupling estimation -- cgit v1.2.3 From 470fce2b6ae01134e3e2fb7a27d9966fd776dae8 Mon Sep 17 00:00:00 2001 From: ievred Date: Mon, 20 Apr 2020 14:12:49 +0200 Subject: added defaults for emd_laplace --- ot/da.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ot/da.py') diff --git a/ot/da.py b/ot/da.py index e615993..6249f08 100644 --- a/ot/da.py +++ b/ot/da.py @@ -789,7 +789,7 @@ def emd_laplace(a, b, xs, xt, M, sim='knn', sim_param=None, reg='pos', eta=1, al Type of similarity ('knn' or 'gauss') used to construct the Laplacian. sim_param : int or float, optional Parameter (number of the nearest neighbors for sim='knn' - or bandwidth for sim='gauss' used to compute the Laplacian. + or bandwidth for sim='gauss') used to compute the Laplacian. reg : string Type of Laplacian regularization eta : float -- cgit v1.2.3 From 6931f78c7e5b1da4f62a1a85d87409f3f95029c7 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Fri, 24 Apr 2020 11:28:21 +0200 Subject: better documentation --- ot/da.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) (limited to 'ot/da.py') diff --git a/ot/da.py b/ot/da.py index 6249f08..4d2bb6c 100644 --- a/ot/da.py +++ b/ot/da.py @@ -908,7 +908,8 @@ class BaseTransport(BaseEstimator): at the class level in their ``__init__`` as explicit keyword arguments (no ``*args`` or ``**kwargs``). - fit method should: + the fit method should: + - estimate a cost matrix and store it in a `cost_` attribute - estimate a coupling matrix and store it in a `coupling_` attribute @@ -933,7 +934,7 @@ class BaseTransport(BaseEstimator): Xs : array-like, shape (n_source_samples, n_features) The training input samples. ys : array-like, shape (n_source_samples,) - The class labels + The training class labels Xt : array-like, shape (n_target_samples, n_features) The training input samples. yt : array-like, shape (n_target_samples,) @@ -994,7 +995,7 @@ class BaseTransport(BaseEstimator): Xs : array-like, shape (n_source_samples, n_features) The training input samples. ys : array-like, shape (n_source_samples,) - The class labels + The class labels for training samples Xt : array-like, shape (n_target_samples, n_features) The training input samples. yt : array-like, shape (n_target_samples,) @@ -1018,13 +1019,13 @@ class BaseTransport(BaseEstimator): Parameters ---------- Xs : array-like, shape (n_source_samples, n_features) - The training input samples. + The source input samples. ys : array-like, shape (n_source_samples,) - The class labels + The class labels for source samples Xt : array-like, shape (n_target_samples, n_features) - The training input samples. + The target input samples. yt : array-like, shape (n_target_samples,) - The class labels. If some target samples are unlabeled, fill the + The class labels for target. If some target samples are unlabeled, fill the yt's elements with -1. Warning: Note that, due to this convention -1 cannot be used as a @@ -1085,7 +1086,7 @@ class BaseTransport(BaseEstimator): Parameters ---------- ys : array-like, shape (n_source_samples,) - The class labels + The source class labels Returns ------- @@ -1125,18 +1126,18 @@ class BaseTransport(BaseEstimator): def inverse_transform(self, Xs=None, ys=None, Xt=None, yt=None, batch_size=128): - """Transports target samples Xt onto target samples Xs + """Transports target samples Xt onto source samples Xs Parameters ---------- Xs : array-like, shape (n_source_samples, n_features) - The training input samples. + The source input samples. ys : array-like, shape (n_source_samples,) - The class labels + The source class labels Xt : array-like, shape (n_target_samples, n_features) - The training input samples. + The target input samples. yt : array-like, shape (n_target_samples,) - The class labels. If some target samples are unlabeled, fill the + The target class labels. If some target samples are unlabeled, fill the yt's elements with -1. Warning: Note that, due to this convention -1 cannot be used as a @@ -1227,7 +1228,6 @@ class BaseTransport(BaseEstimator): class LinearTransport(BaseTransport): - """ OT linear operator between empirical distributions The function estimates the optimal linear operator that aligns the two -- cgit v1.2.3 From a5b1b18f25298ed88d958d62647165bd63b83b9d Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Fri, 24 Apr 2020 11:45:27 +0200 Subject: add ferradans to da classes documentation --- ot/da.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) (limited to 'ot/da.py') diff --git a/ot/da.py b/ot/da.py index 4d2bb6c..7b22de8 100644 --- a/ot/da.py +++ b/ot/da.py @@ -1438,6 +1438,9 @@ class SinkhornTransport(BaseTransport): .. [2] M. Cuturi, Sinkhorn Distances : Lightspeed Computation of Optimal Transport, Advances in Neural Information Processing Systems (NIPS) 26, 2013 + .. [6] Ferradans, S., Papadakis, N., Peyré, G., & Aujol, J. F. (2014). + Regularized discrete optimal transport. SIAM Journal on Imaging + Sciences, 7(3), 1853-1882. """ def __init__(self, reg_e=1., max_iter=1000, @@ -1536,6 +1539,9 @@ class EMDTransport(BaseTransport): .. [1] N. Courty; R. Flamary; D. Tuia; A. Rakotomamonjy, "Optimal Transport for Domain Adaptation," in IEEE Transactions on Pattern Analysis and Machine Intelligence , vol.PP, no.99, pp.1-1 + .. [6] Ferradans, S., Papadakis, N., Peyré, G., & Aujol, J. F. (2014). + Regularized discrete optimal transport. SIAM Journal on Imaging + Sciences, 7(3), 1853-1882. """ def __init__(self, metric="sqeuclidean", norm=None, log=False, @@ -1643,7 +1649,9 @@ class SinkhornLpl1Transport(BaseTransport): .. [2] Rakotomamonjy, A., Flamary, R., & Courty, N. (2015). Generalized conditional gradient: analysis of convergence and applications. arXiv preprint arXiv:1510.06567. - + .. [6] Ferradans, S., Papadakis, N., Peyré, G., & Aujol, J. F. (2014). + Regularized discrete optimal transport. SIAM Journal on Imaging + Sciences, 7(3), 1853-1882. """ def __init__(self, reg_e=1., reg_cl=0.1, @@ -1763,6 +1771,9 @@ class EMDLaplaceTransport(BaseTransport): .. [2] R. Flamary, N. Courty, D. Tuia, A. Rakotomamonjy, "Optimal transport with Laplacian regularization: Applications to domain adaptation and shape matching," in NIPS Workshop on Optimal Transport and Machine Learning OTML, 2014. + .. [6] Ferradans, S., Papadakis, N., Peyré, G., & Aujol, J. F. (2014). + Regularized discrete optimal transport. SIAM Journal on Imaging + Sciences, 7(3), 1853-1882. """ def __init__(self, reg_type='pos', reg_lap=1., reg_src=1., metric="sqeuclidean", @@ -1882,7 +1893,9 @@ class SinkhornL1l2Transport(BaseTransport): .. [2] Rakotomamonjy, A., Flamary, R., & Courty, N. (2015). Generalized conditional gradient: analysis of convergence and applications. arXiv preprint arXiv:1510.06567. - + .. [6] Ferradans, S., Papadakis, N., Peyré, G., & Aujol, J. F. (2014). + Regularized discrete optimal transport. SIAM Journal on Imaging + Sciences, 7(3), 1853-1882. """ def __init__(self, reg_e=1., reg_cl=0.1, @@ -2174,7 +2187,9 @@ class UnbalancedSinkhornTransport(BaseTransport): .. [1] Chizat, L., Peyré, G., Schmitzer, B., & Vialard, F. X. (2016). Scaling algorithms for unbalanced transport problems. arXiv preprint arXiv:1607.05816. - + .. [6] Ferradans, S., Papadakis, N., Peyré, G., & Aujol, J. F. (2014). + Regularized discrete optimal transport. SIAM Journal on Imaging + Sciences, 7(3), 1853-1882. """ def __init__(self, reg_e=1., reg_m=0.1, method='sinkhorn', @@ -2287,6 +2302,11 @@ class JCPOTTransport(BaseTransport): International Conference on Artificial Intelligence and Statistics (AISTATS), vol. 89, p.849-858, 2019. + .. [6] Ferradans, S., Papadakis, N., Peyré, G., & Aujol, J. F. (2014). + Regularized discrete optimal transport. SIAM Journal on Imaging + Sciences, 7(3), 1853-1882. + + """ def __init__(self, reg_e=.1, max_iter=10, -- cgit v1.2.3 From bed16804bb903b43d769c03421d75c7f03910b04 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Fri, 24 Apr 2020 11:51:12 +0200 Subject: pep8 --- ot/da.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) (limited to 'ot/da.py') diff --git a/ot/da.py b/ot/da.py index 7b22de8..b881a8b 100644 --- a/ot/da.py +++ b/ot/da.py @@ -1438,8 +1438,8 @@ class SinkhornTransport(BaseTransport): .. [2] M. Cuturi, Sinkhorn Distances : Lightspeed Computation of Optimal Transport, Advances in Neural Information Processing Systems (NIPS) 26, 2013 - .. [6] Ferradans, S., Papadakis, N., Peyré, G., & Aujol, J. F. (2014). - Regularized discrete optimal transport. SIAM Journal on Imaging + .. [6] Ferradans, S., Papadakis, N., Peyré, G., & Aujol, J. F. (2014). + Regularized discrete optimal transport. SIAM Journal on Imaging Sciences, 7(3), 1853-1882. """ @@ -1539,8 +1539,8 @@ class EMDTransport(BaseTransport): .. [1] N. Courty; R. Flamary; D. Tuia; A. Rakotomamonjy, "Optimal Transport for Domain Adaptation," in IEEE Transactions on Pattern Analysis and Machine Intelligence , vol.PP, no.99, pp.1-1 - .. [6] Ferradans, S., Papadakis, N., Peyré, G., & Aujol, J. F. (2014). - Regularized discrete optimal transport. SIAM Journal on Imaging + .. [6] Ferradans, S., Papadakis, N., Peyré, G., & Aujol, J. F. (2014). + Regularized discrete optimal transport. SIAM Journal on Imaging Sciences, 7(3), 1853-1882. """ @@ -1649,8 +1649,8 @@ class SinkhornLpl1Transport(BaseTransport): .. [2] Rakotomamonjy, A., Flamary, R., & Courty, N. (2015). Generalized conditional gradient: analysis of convergence and applications. arXiv preprint arXiv:1510.06567. - .. [6] Ferradans, S., Papadakis, N., Peyré, G., & Aujol, J. F. (2014). - Regularized discrete optimal transport. SIAM Journal on Imaging + .. [6] Ferradans, S., Papadakis, N., Peyré, G., & Aujol, J. F. (2014). + Regularized discrete optimal transport. SIAM Journal on Imaging Sciences, 7(3), 1853-1882. """ @@ -1771,8 +1771,8 @@ class EMDLaplaceTransport(BaseTransport): .. [2] R. Flamary, N. Courty, D. Tuia, A. Rakotomamonjy, "Optimal transport with Laplacian regularization: Applications to domain adaptation and shape matching," in NIPS Workshop on Optimal Transport and Machine Learning OTML, 2014. - .. [6] Ferradans, S., Papadakis, N., Peyré, G., & Aujol, J. F. (2014). - Regularized discrete optimal transport. SIAM Journal on Imaging + .. [6] Ferradans, S., Papadakis, N., Peyré, G., & Aujol, J. F. (2014). + Regularized discrete optimal transport. SIAM Journal on Imaging Sciences, 7(3), 1853-1882. """ @@ -1893,8 +1893,8 @@ class SinkhornL1l2Transport(BaseTransport): .. [2] Rakotomamonjy, A., Flamary, R., & Courty, N. (2015). Generalized conditional gradient: analysis of convergence and applications. arXiv preprint arXiv:1510.06567. - .. [6] Ferradans, S., Papadakis, N., Peyré, G., & Aujol, J. F. (2014). - Regularized discrete optimal transport. SIAM Journal on Imaging + .. [6] Ferradans, S., Papadakis, N., Peyré, G., & Aujol, J. F. (2014). + Regularized discrete optimal transport. SIAM Journal on Imaging Sciences, 7(3), 1853-1882. """ @@ -2187,8 +2187,8 @@ class UnbalancedSinkhornTransport(BaseTransport): .. [1] Chizat, L., Peyré, G., Schmitzer, B., & Vialard, F. X. (2016). Scaling algorithms for unbalanced transport problems. arXiv preprint arXiv:1607.05816. - .. [6] Ferradans, S., Papadakis, N., Peyré, G., & Aujol, J. F. (2014). - Regularized discrete optimal transport. SIAM Journal on Imaging + .. [6] Ferradans, S., Papadakis, N., Peyré, G., & Aujol, J. F. (2014). + Regularized discrete optimal transport. SIAM Journal on Imaging Sciences, 7(3), 1853-1882. """ @@ -2302,8 +2302,8 @@ class JCPOTTransport(BaseTransport): International Conference on Artificial Intelligence and Statistics (AISTATS), vol. 89, p.849-858, 2019. - .. [6] Ferradans, S., Papadakis, N., Peyré, G., & Aujol, J. F. (2014). - Regularized discrete optimal transport. SIAM Journal on Imaging + .. [6] Ferradans, S., Papadakis, N., Peyré, G., & Aujol, J. F. (2014). + Regularized discrete optimal transport. SIAM Journal on Imaging Sciences, 7(3), 1853-1882. -- cgit v1.2.3