From cd9909cff342bb46c4233a0ead348dabebe9efdf Mon Sep 17 00:00:00 2001 From: arolet Date: Fri, 14 Jul 2017 15:18:57 +0900 Subject: Added a test for single process EMD The multiprocess one does not seem to work on windows --- test/test_emd.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 test/test_emd.py (limited to 'test') diff --git a/test/test_emd.py b/test/test_emd.py new file mode 100644 index 0000000..3729d5d --- /dev/null +++ b/test/test_emd.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +import numpy as np +import pylab as pl +import ot + +from ot.datasets import get_1D_gauss as gauss +reload(ot.lp) + +#%% parameters + +n=5000 # nb bins + +# bin positions +x=np.arange(n,dtype=np.float64) + +# Gaussian distributions +a=gauss(n,m=20,s=5) # m= mean, s= std + +b=gauss(n,m=30,s=10) + +# loss matrix +M=ot.dist(x.reshape((n,1)),x.reshape((n,1))) +#M/=M.max() + +#%% + +print('Computing {} EMD '.format(1)) + +# emd loss 1 proc +ot.tic() +emd_loss4 = ot.emd(a,b,M) +ot.toc('1 proc : {} s') + -- cgit v1.2.3 From d59e91450272c78dd0fdd3c6bd9bf48776f10070 Mon Sep 17 00:00:00 2001 From: arolet Date: Fri, 14 Jul 2017 15:37:46 +0900 Subject: Added a test based on closed form solution for gaussians --- test/test_emd.py | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) (limited to 'test') diff --git a/test/test_emd.py b/test/test_emd.py index 3729d5d..eb1c5c5 100644 --- a/test/test_emd.py +++ b/test/test_emd.py @@ -11,17 +11,25 @@ reload(ot.lp) #%% parameters n=5000 # nb bins +m=6000 # nb bins + +mean1 = 1000 +mean2 = 1100 + +tol = 1e-6 # bin positions x=np.arange(n,dtype=np.float64) +y=np.arange(m,dtype=np.float64) # Gaussian distributions -a=gauss(n,m=20,s=5) # m= mean, s= std +a=gauss(n,m=mean1,s=5) # m= mean, s= std -b=gauss(n,m=30,s=10) +b=gauss(m,m=mean2,s=10) # loss matrix -M=ot.dist(x.reshape((n,1)),x.reshape((n,1))) +M=ot.dist(x.reshape((-1,1)), y.reshape((-1,1))) ** (1./2) +print M[0,:] #M/=M.max() #%% @@ -30,6 +38,16 @@ print('Computing {} EMD '.format(1)) # emd loss 1 proc ot.tic() -emd_loss4 = ot.emd(a,b,M) +G = ot.emd(a,b,M) ot.toc('1 proc : {} s') +cost1 = (G * M).sum() + +ot.tic() +G = ot.emd(b, a, np.ascontiguousarray(M.T)) +ot.toc('1 proc : {} s') + +cost2 = (G * M.T).sum() + +assert np.abs(cost1-cost2) < tol +assert np.abs(cost1-np.abs(mean1-mean2)) < tol -- cgit v1.2.3 From dc3bbd4134f0e2b80e0fe72368bdcf9966f434dc Mon Sep 17 00:00:00 2001 From: arolet Date: Fri, 21 Jul 2017 12:12:21 +0900 Subject: Cleaned optimal plan and optimal cost computation --- ot/lp/EMD_wrapper.cpp | 13 ++++++------- ot/lp/emd_wrap.pyx | 5 ----- test/test_emd.py | 10 ++++++++-- 3 files changed, 14 insertions(+), 14 deletions(-) (limited to 'test') diff --git a/ot/lp/EMD_wrapper.cpp b/ot/lp/EMD_wrapper.cpp index d719c6e..cc13230 100644 --- a/ot/lp/EMD_wrapper.cpp +++ b/ot/lp/EMD_wrapper.cpp @@ -93,14 +93,13 @@ void EMD_wrap(int n1, int n2, double *X, double *Y, } } else { - for (node_id_type i=0; i a.data, b.data, M.data, G.data, &cost, maxiter) - - cost=0 - for i in range(n1): - for j in range(n2): - cost+=G[i,j]*M[i,j] return cost diff --git a/test/test_emd.py b/test/test_emd.py index eb1c5c5..4757cd1 100644 --- a/test/test_emd.py +++ b/test/test_emd.py @@ -43,11 +43,17 @@ ot.toc('1 proc : {} s') cost1 = (G * M).sum() +# emd loss 1 proc +ot.tic() +cost_emd2 = ot.emd2(a,b,M) +ot.toc('1 proc : {} s') + ot.tic() G = ot.emd(b, a, np.ascontiguousarray(M.T)) ot.toc('1 proc : {} s') cost2 = (G * M.T).sum() -assert np.abs(cost1-cost2) < tol -assert np.abs(cost1-np.abs(mean1-mean2)) < tol +assert np.abs(cost1-cost_emd2)/np.abs(cost1) < tol +assert np.abs(cost1-cost2)/np.abs(cost1) < tol +assert np.abs(cost1-np.abs(mean1-mean2))/np.abs(cost1) < tol -- cgit v1.2.3 From c1980a414c879dd1bc1d8904fd43426326741385 Mon Sep 17 00:00:00 2001 From: arolet Date: Fri, 21 Jul 2017 13:34:09 +0900 Subject: Added and passed tests for dual variables --- ot/lp/EMD_wrapper.cpp | 2 +- ot/lp/emd_wrap.pyx | 4 ++-- test/test_emd.py | 28 +++++++++++++++++++--------- 3 files changed, 22 insertions(+), 12 deletions(-) (limited to 'test') diff --git a/ot/lp/EMD_wrapper.cpp b/ot/lp/EMD_wrapper.cpp index c6cbb04..0977e75 100644 --- a/ot/lp/EMD_wrapper.cpp +++ b/ot/lp/EMD_wrapper.cpp @@ -101,7 +101,7 @@ void EMD_wrap(int n1, int n2, double *X, double *Y, double *D, double *G, double flow = net.flow(a); *cost += flow * (*(D+indI[i]*n2+indJ[j-n])); *(G+indI[i]*n2+indJ[j-n]) = flow; - *(alpha + indI[i]) = net.potential(i); + *(alpha + indI[i]) = -net.potential(i); *(beta + indJ[j-n]) = net.potential(j); } diff --git a/ot/lp/emd_wrap.pyx b/ot/lp/emd_wrap.pyx index 813596f..435a270 100644 --- a/ot/lp/emd_wrap.pyx +++ b/ot/lp/emd_wrap.pyx @@ -57,7 +57,7 @@ def emd_c( np.ndarray[double, ndim=1, mode="c"] a,np.ndarray[double, ndim=1, mod cdef int n1= M.shape[0] cdef int n2= M.shape[1] - cdef float cost=0 + cdef double cost=0 cdef np.ndarray[double, ndim=2, mode="c"] G=np.zeros([n1, n2]) cdef np.ndarray[double, ndim=1, mode="c"] alpha=np.zeros(n1) cdef np.ndarray[double, ndim=1, mode="c"] beta=np.zeros(n2) @@ -116,7 +116,7 @@ def emd2_c( np.ndarray[double, ndim=1, mode="c"] a,np.ndarray[double, ndim=1, mo cdef int n1= M.shape[0] cdef int n2= M.shape[1] - cdef float cost=0 + cdef double cost=0 cdef np.ndarray[double, ndim=2, mode="c"] G=np.zeros([n1, n2]) cdef np.ndarray[double, ndim = 1, mode = "c"] alpha = np.zeros([n1]) diff --git a/test/test_emd.py b/test/test_emd.py index 4757cd1..3bf6fa2 100644 --- a/test/test_emd.py +++ b/test/test_emd.py @@ -2,7 +2,6 @@ # -*- coding: utf-8 -*- import numpy as np -import pylab as pl import ot from ot.datasets import get_1D_gauss as gauss @@ -16,8 +15,6 @@ m=6000 # nb bins mean1 = 1000 mean2 = 1100 -tol = 1e-6 - # bin positions x=np.arange(n,dtype=np.float64) y=np.arange(m,dtype=np.float64) @@ -38,10 +35,11 @@ print('Computing {} EMD '.format(1)) # emd loss 1 proc ot.tic() -G = ot.emd(a,b,M) +G, alpha, beta = ot.emd(a,b,M, dual_variables=True) ot.toc('1 proc : {} s') cost1 = (G * M).sum() +cost_dual = np.vdot(a, alpha) + np.vdot(b, beta) # emd loss 1 proc ot.tic() @@ -49,11 +47,23 @@ cost_emd2 = ot.emd2(a,b,M) ot.toc('1 proc : {} s') ot.tic() -G = ot.emd(b, a, np.ascontiguousarray(M.T)) +G2 = ot.emd(b, a, np.ascontiguousarray(M.T)) ot.toc('1 proc : {} s') -cost2 = (G * M.T).sum() +cost2 = (G2 * M.T).sum() + +M_reduced = M - alpha.reshape(-1,1) - beta.reshape(1, -1) + +# Check that both cost computations are equivalent +np.testing.assert_almost_equal(cost1, cost_emd2) +# Check that dual and primal cost are equal +np.testing.assert_almost_equal(cost1, cost_dual) +# Check symmetry +np.testing.assert_almost_equal(cost1, cost2) +# Check with closed-form solution for gaussians +np.testing.assert_almost_equal(cost1, np.abs(mean1-mean2)) + +[ind1, ind2] = np.nonzero(G) -assert np.abs(cost1-cost_emd2)/np.abs(cost1) < tol -assert np.abs(cost1-cost2)/np.abs(cost1) < tol -assert np.abs(cost1-np.abs(mean1-mean2))/np.abs(cost1) < tol +# Check that reduced cost is zero on transport arcs +np.testing.assert_array_almost_equal((M - alpha.reshape(-1, 1) - beta.reshape(1, -1))[ind1, ind2], np.zeros(ind1.size)) \ No newline at end of file -- cgit v1.2.3 From 3007f1da1094f93fa4216386666085cf60316b04 Mon Sep 17 00:00:00 2001 From: Nicolas Courty Date: Thu, 31 Aug 2017 16:44:18 +0200 Subject: Minor corrections suggested by @agramfort + new barycenter example + test function --- README.md | 2 +- data/carre.png | Bin 0 -> 168 bytes data/coeur.png | Bin 0 -> 225 bytes data/rond.png | Bin 0 -> 230 bytes data/triangle.png | Bin 0 -> 254 bytes examples/plot_gromov.py | 14 +-- examples/plot_gromov_barycenter.py | 240 +++++++++++++++++++++++++++++++++++++ ot/gromov.py | 36 +++--- test/test_gromov.py | 38 ++++++ 9 files changed, 302 insertions(+), 28 deletions(-) create mode 100755 data/carre.png create mode 100755 data/coeur.png create mode 100755 data/rond.png create mode 100755 data/triangle.png create mode 100755 examples/plot_gromov_barycenter.py create mode 100644 test/test_gromov.py (limited to 'test') diff --git a/README.md b/README.md index a42f17b..53672ae 100644 --- a/README.md +++ b/README.md @@ -183,4 +183,4 @@ You can also post bug reports and feature requests in Github issues. Make sure t [11] Flamary, R., Cuturi, M., Courty, N., & Rakotomamonjy, A. (2016). [Wasserstein Discriminant Analysis](https://arxiv.org/pdf/1608.08063.pdf). arXiv preprint arXiv:1608.08063. -[12] Peyré, Gabriel, Marco Cuturi, and Justin Solomon, [Gromov-Wasserstein averaging of kernel and distance matrices](http://proceedings.mlr.press/v48/peyre16.html) International Conference on Machine Learning (ICML). 2016. +[12] Gabriel Peyré, Marco Cuturi, and Justin Solomon, [Gromov-Wasserstein averaging of kernel and distance matrices](http://proceedings.mlr.press/v48/peyre16.html) International Conference on Machine Learning (ICML). 2016. diff --git a/data/carre.png b/data/carre.png new file mode 100755 index 0000000..45ff0ef Binary files /dev/null and b/data/carre.png differ diff --git a/data/coeur.png b/data/coeur.png new file mode 100755 index 0000000..3f511a6 Binary files /dev/null and b/data/coeur.png differ diff --git a/data/rond.png b/data/rond.png new file mode 100755 index 0000000..1c1a068 Binary files /dev/null and b/data/rond.png differ diff --git a/data/triangle.png b/data/triangle.png new file mode 100755 index 0000000..ca36d09 Binary files /dev/null and b/data/triangle.png differ diff --git a/examples/plot_gromov.py b/examples/plot_gromov.py index a33fde1..9bbdbde 100644 --- a/examples/plot_gromov.py +++ b/examples/plot_gromov.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- """ -==================== +========================== Gromov-Wasserstein example -==================== +========================== This example is designed to show how to use the Gromov-Wassertsein distance computation in POT. """ @@ -14,14 +14,14 @@ computation in POT. import scipy as sp import numpy as np +import matplotlib.pylab as pl import ot -import matplotlib.pylab as pl """ Sample two Gaussian distributions (2D and 3D) -==================== +============================================= The Gromov-Wasserstein distance allows to compute distances with samples that do not belong to the same metric space. For demonstration purpose, we sample two Gaussian distributions in 2- and 3-dimensional spaces. """ @@ -42,7 +42,7 @@ xt = np.random.randn(n, 3).dot(P) + mu_t """ Plotting the distributions -==================== +========================== """ fig = pl.figure() ax1 = fig.add_subplot(121) @@ -54,7 +54,7 @@ pl.show() """ Compute distance kernels, normalize them and then display -==================== +========================================================= """ C1 = sp.spatial.distance.cdist(xs, xs) @@ -72,7 +72,7 @@ pl.show() """ Compute Gromov-Wasserstein plans and distance -==================== +============================================= """ p = ot.unif(n) diff --git a/examples/plot_gromov_barycenter.py b/examples/plot_gromov_barycenter.py new file mode 100755 index 0000000..6a72b3b --- /dev/null +++ b/examples/plot_gromov_barycenter.py @@ -0,0 +1,240 @@ +# -*- coding: utf-8 -*- +""" +===================================== +Gromov-Wasserstein Barycenter example +===================================== +This example is designed to show how to use the Gromov-Wassertsein distance +computation in POT. +""" + +# Author: Erwan Vautier +# Nicolas Courty +# +# License: MIT License + + +import numpy as np +import scipy as sp + +import scipy.ndimage as spi +import matplotlib.pylab as pl +from sklearn import manifold +from sklearn.decomposition import PCA + +import ot + +""" + +Smacof MDS +========== +This function allows to find an embedding of points given a dissimilarity matrix +that will be given by the output of the algorithm +""" + + +def smacof_mds(C, dim, maxIter=3000, eps=1e-9): + """ + Returns an interpolated point cloud following the dissimilarity matrix C using SMACOF + multidimensional scaling (MDS) in specific dimensionned target space + + Parameters + ---------- + C : np.ndarray(ns,ns) + dissimilarity matrix + dim : Integer + dimension of the targeted space + maxIter : Maximum number of iterations of the SMACOF algorithm for a single run + + eps : relative tolerance w.r.t stress to declare converge + + + Returns + ------- + npos : R**dim ndarray + Embedded coordinates of the interpolated point cloud (defined with one isometry) + + + """ + + seed = np.random.RandomState(seed=3) + + mds = manifold.MDS( + dim, + max_iter=3000, + eps=1e-9, + dissimilarity='precomputed', + n_init=1) + pos = mds.fit(C).embedding_ + + nmds = manifold.MDS( + 2, + max_iter=3000, + eps=1e-9, + dissimilarity="precomputed", + random_state=seed, + n_init=1) + npos = nmds.fit_transform(C, init=pos) + + return npos + + +""" +Data preparation +================ +The four distributions are constructed from 4 simple images +""" + + +def im2mat(I): + """Converts and image to matrix (one pixel per line)""" + return I.reshape((I.shape[0] * I.shape[1], I.shape[2])) + + +carre = spi.imread('../data/carre.png').astype(np.float64) / 256 +rond = spi.imread('../data/rond.png').astype(np.float64) / 256 +triangle = spi.imread('../data/triangle.png').astype(np.float64) / 256 +fleche = spi.imread('../data/coeur.png').astype(np.float64) / 256 + +shapes = [carre, rond, triangle, fleche] + +S = 4 +xs = [[] for i in range(S)] + + +for nb in range(4): + for i in range(8): + for j in range(8): + if shapes[nb][i, j] < 0.95: + xs[nb].append([j, 8 - i]) + +xs = np.array([np.array(xs[0]), np.array(xs[1]), + np.array(xs[2]), np.array(xs[3])]) + + +""" +Barycenter computation +====================== +The four distributions are constructed from 4 simple images +""" +ns = [len(xs[s]) for s in range(S)] +N = 30 + +"""Compute all distances matrices for the four shapes""" +Cs = [sp.spatial.distance.cdist(xs[s], xs[s]) for s in range(S)] +Cs = [cs / cs.max() for cs in Cs] + +ps = [ot.unif(ns[s]) for s in range(S)] +p = ot.unif(N) + + +lambdast = [[float(i) / 3, float(3 - i) / 3] for i in [1, 2]] + +Ct01 = [0 for i in range(2)] +for i in range(2): + Ct01[i] = ot.gromov.gromov_barycenters(N, [Cs[0], Cs[1]], [ + ps[0], ps[1]], p, lambdast[i], 'square_loss', 5e-4, numItermax=100, stopThr=1e-3) + +Ct02 = [0 for i in range(2)] +for i in range(2): + Ct02[i] = ot.gromov.gromov_barycenters(N, [Cs[0], Cs[2]], [ + ps[0], ps[2]], p, lambdast[i], 'square_loss', 5e-4, numItermax=100, stopThr=1e-3) + +Ct13 = [0 for i in range(2)] +for i in range(2): + Ct13[i] = ot.gromov.gromov_barycenters(N, [Cs[1], Cs[3]], [ + ps[1], ps[3]], p, lambdast[i], 'square_loss', 5e-4, numItermax=100, stopThr=1e-3) + +Ct23 = [0 for i in range(2)] +for i in range(2): + Ct23[i] = ot.gromov.gromov_barycenters(N, [Cs[2], Cs[3]], [ + ps[2], ps[3]], p, lambdast[i], 'square_loss', 5e-4, numItermax=100, stopThr=1e-3) + +""" +Visualization +============= +""" + +"""The PCA helps in getting consistency between the rotations""" + +clf = PCA(n_components=2) +npos = [0, 0, 0, 0] +npos = [smacof_mds(Cs[s], 2) for s in range(S)] + +npost01 = [0, 0] +npost01 = [smacof_mds(Ct01[s], 2) for s in range(2)] +npost01 = [clf.fit_transform(npost01[s]) for s in range(2)] + +npost02 = [0, 0] +npost02 = [smacof_mds(Ct02[s], 2) for s in range(2)] +npost02 = [clf.fit_transform(npost02[s]) for s in range(2)] + +npost13 = [0, 0] +npost13 = [smacof_mds(Ct13[s], 2) for s in range(2)] +npost13 = [clf.fit_transform(npost13[s]) for s in range(2)] + +npost23 = [0, 0] +npost23 = [smacof_mds(Ct23[s], 2) for s in range(2)] +npost23 = [clf.fit_transform(npost23[s]) for s in range(2)] + + +fig = pl.figure(figsize=(10, 10)) + +ax1 = pl.subplot2grid((4, 4), (0, 0)) +pl.xlim((-1, 1)) +pl.ylim((-1, 1)) +ax1.scatter(npos[0][:, 0], npos[0][:, 1], color='r') + +ax2 = pl.subplot2grid((4, 4), (0, 1)) +pl.xlim((-1, 1)) +pl.ylim((-1, 1)) +ax2.scatter(npost01[1][:, 0], npost01[1][:, 1], color='b') + +ax3 = pl.subplot2grid((4, 4), (0, 2)) +pl.xlim((-1, 1)) +pl.ylim((-1, 1)) +ax3.scatter(npost01[0][:, 0], npost01[0][:, 1], color='b') + +ax4 = pl.subplot2grid((4, 4), (0, 3)) +pl.xlim((-1, 1)) +pl.ylim((-1, 1)) +ax4.scatter(npos[1][:, 0], npos[1][:, 1], color='r') + +ax5 = pl.subplot2grid((4, 4), (1, 0)) +pl.xlim((-1, 1)) +pl.ylim((-1, 1)) +ax5.scatter(npost02[1][:, 0], npost02[1][:, 1], color='b') + +ax6 = pl.subplot2grid((4, 4), (1, 3)) +pl.xlim((-1, 1)) +pl.ylim((-1, 1)) +ax6.scatter(npost13[1][:, 0], npost13[1][:, 1], color='b') + +ax7 = pl.subplot2grid((4, 4), (2, 0)) +pl.xlim((-1, 1)) +pl.ylim((-1, 1)) +ax7.scatter(npost02[0][:, 0], npost02[0][:, 1], color='b') + +ax8 = pl.subplot2grid((4, 4), (2, 3)) +pl.xlim((-1, 1)) +pl.ylim((-1, 1)) +ax8.scatter(npost13[0][:, 0], npost13[0][:, 1], color='b') + +ax9 = pl.subplot2grid((4, 4), (3, 0)) +pl.xlim((-1, 1)) +pl.ylim((-1, 1)) +ax9.scatter(npos[2][:, 0], npos[2][:, 1], color='r') + +ax10 = pl.subplot2grid((4, 4), (3, 1)) +pl.xlim((-1, 1)) +pl.ylim((-1, 1)) +ax10.scatter(npost23[1][:, 0], npost23[1][:, 1], color='b') + +ax11 = pl.subplot2grid((4, 4), (3, 2)) +pl.xlim((-1, 1)) +pl.ylim((-1, 1)) +ax11.scatter(npost23[0][:, 0], npost23[0][:, 1], color='b') + +ax12 = pl.subplot2grid((4, 4), (3, 3)) +pl.xlim((-1, 1)) +pl.ylim((-1, 1)) +ax12.scatter(npos[3][:, 0], npos[3][:, 1], color='r') diff --git a/ot/gromov.py b/ot/gromov.py index 7cf3b42..421ed3f 100644 --- a/ot/gromov.py +++ b/ot/gromov.py @@ -23,7 +23,7 @@ def square_loss(a, b): Returns the value of L(a,b)=(1/2)*|a-b|^2 """ - return (1 / 2) * (a - b)**2 + return 0.5 * (a - b)**2 def kl_loss(a, b): @@ -54,9 +54,9 @@ def tensor_square_loss(C1, C2, T): Parameters ---------- - C1 : np.ndarray(ns,ns) + C1 : ndarray, shape (ns, ns) Metric cost matrix in the source space - C2 : np.ndarray(nt,nt) + C2 : ndarray, shape (nt, nt) Metric costfr matrix in the target space T : np.ndarray(ns,nt) Coupling between source and target spaces @@ -87,7 +87,7 @@ def tensor_square_loss(C1, C2, T): return b tens = -np.dot(h1(C1), T).dot(h2(C2).T) - tens = tens - tens.min() + tens -= tens.min() return np.array(tens) @@ -112,9 +112,9 @@ def tensor_kl_loss(C1, C2, T): Parameters ---------- - C1 : np.ndarray(ns,ns) + C1 : ndarray, shape (ns, ns) Metric cost matrix in the source space - C2 : np.ndarray(nt,nt) + C2 : ndarray, shape (nt, nt) Metric costfr matrix in the target space T : np.ndarray(ns,nt) Coupling between source and target spaces @@ -149,7 +149,7 @@ def tensor_kl_loss(C1, C2, T): return np.log(b + 1e-15) tens = -np.dot(h1(C1), T).dot(h2(C2).T) - tens = tens - tens.min() + tens -= tens.min() return np.array(tens) @@ -175,9 +175,8 @@ def update_square_loss(p, lambdas, T, Cs): """ - tmpsum = np.sum([lambdas[s] * np.dot(T[s].T, Cs[s]).dot(T[s]) - for s in range(len(T))]) - ppt = np.dot(p, p.T) + tmpsum = sum([lambdas[s] * np.dot(T[s].T, Cs[s]).dot(T[s]) for s in range(len(T))]) + ppt = np.outer(p, p) return(np.divide(tmpsum, ppt)) @@ -203,9 +202,8 @@ def update_kl_loss(p, lambdas, T, Cs): """ - tmpsum = np.sum([lambdas[s] * np.dot(T[s].T, Cs[s]).dot(T[s]) - for s in range(len(T))]) - ppt = np.dot(p, p.T) + tmpsum = sum([lambdas[s] * np.dot(T[s].T, Cs[s]).dot(T[s]) for s in range(len(T))]) + ppt = np.outer(p, p) return(np.exp(np.divide(tmpsum, ppt))) @@ -239,9 +237,9 @@ def gromov_wasserstein(C1, C2, p, q, loss_fun, epsilon, numItermax=1000, stopThr Parameters ---------- - C1 : np.ndarray(ns,ns) + C1 : ndarray, shape (ns, ns) Metric cost matrix in the source space - C2 : np.ndarray(nt,nt) + C2 : ndarray, shape (nt, nt) Metric costfr matrix in the target space p : np.ndarray(ns,) distribution in the source space @@ -271,7 +269,7 @@ def gromov_wasserstein(C1, C2, p, q, loss_fun, epsilon, numItermax=1000, stopThr C1 = np.asarray(C1, dtype=np.float64) C2 = np.asarray(C2, dtype=np.float64) - T = np.dot(p, q.T) # Initialization + T = np.outer(p, q) # Initialization cpt = 0 err = 1 @@ -333,9 +331,9 @@ def gromov_wasserstein2(C1, C2, p, q, loss_fun, epsilon, numItermax=1000, stopTh Parameters ---------- - C1 : np.ndarray(ns,ns) + C1 : ndarray, shape (ns, ns) Metric cost matrix in the source space - C2 : np.ndarray(nt,nt) + C2 : ndarray, shape (nt, nt) Metric costfr matrix in the target space p : np.ndarray(ns,) distribution in the source space @@ -434,8 +432,6 @@ def gromov_barycenters(N, Cs, ps, p, lambdas, loss_fun, epsilon, numItermax=1000 Cs = [np.asarray(Cs[s], dtype=np.float64) for s in range(S)] lambdas = np.asarray(lambdas, dtype=np.float64) - T = [0 for s in range(S)] - # Initialization of C : random SPD matrix xalea = np.random.randn(N, 2) C = dist(xalea, xalea) diff --git a/test/test_gromov.py b/test/test_gromov.py new file mode 100644 index 0000000..75eeaab --- /dev/null +++ b/test/test_gromov.py @@ -0,0 +1,38 @@ +"""Tests for module gromov """ + +# Author: Erwan Vautier +# Nicolas Courty +# +# License: MIT License + +import numpy as np +import ot + + +def test_gromov(): + n = 50 # nb samples + + mu_s = np.array([0, 0]) + cov_s = np.array([[1, 0], [0, 1]]) + + xs = ot.datasets.get_2D_samples_gauss(n, mu_s, cov_s) + + xt = [xs[n - (i + 1)] for i in range(n)] + xt = np.array(xt) + + p = ot.unif(n) + q = ot.unif(n) + + C1 = ot.dist(xs, xs) + C2 = ot.dist(xt, xt) + + C1 /= C1.max() + C2 /= C2.max() + + G = ot.gromov_wasserstein(C1, C2, p, q, 'square_loss', epsilon=5e-4) + + # check constratints + np.testing.assert_allclose( + p, G.sum(1), atol=1e-04) # cf convergence gromov + np.testing.assert_allclose( + q, G.sum(0), atol=1e-04) # cf convergence gromov -- cgit v1.2.3 From c7eaaf4caa03d759c4255bdf8b6eebd10ee539a5 Mon Sep 17 00:00:00 2001 From: Slasnista Date: Tue, 1 Aug 2017 10:42:09 +0200 Subject: update SinkhornTransport class + added test for class --- ot/da.py | 56 +++++++++++++++++++++----------------------------------- test/test_da.py | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 35 deletions(-) (limited to 'test') diff --git a/ot/da.py b/ot/da.py index d30c821..6b98a17 100644 --- a/ot/da.py +++ b/ot/da.py @@ -15,6 +15,7 @@ from .lp import emd from .utils import unif, dist, kernel from .optim import cg from .optim import gcg +import warnings def sinkhorn_lpl1_mm(a, labels_a, b, M, reg, eta=0.1, numItermax=10, @@ -921,15 +922,8 @@ class OTDA_mapping_kernel(OTDA_mapping_linear): # proposal ############################################################################## -# from sklearn.base import BaseEstimator -# from sklearn.metrics import pairwise_distances - -############################################################################## -# adapted from scikit-learn - -import warnings -# from .externals.six import string_types, iteritems +# adapted from sklearn class BaseEstimator(object): """Base class for all estimators in scikit-learn @@ -1067,7 +1061,7 @@ def distribution_estimation_uniform(X): The uniform distribution estimated from X """ - return np.ones(X.shape[0]) / float(X.shape[0]) + return unif(X.shape[0]) class BaseTransport(BaseEstimator): @@ -1092,29 +1086,20 @@ class BaseTransport(BaseEstimator): """ # pairwise distance - Cost = dist(Xs, Xt, metric=self.metric) + self.Cost = dist(Xs, Xt, metric=self.metric) if self.mode == "semisupervised": print("TODO: modify cost matrix accordingly") pass # distribution estimation - mu_s = self.distribution_estimation(Xs) - mu_t = self.distribution_estimation(Xt) + self.mu_s = self.distribution_estimation(Xs) + self.mu_t = self.distribution_estimation(Xt) # store arrays of samples self.Xs = Xs self.Xt = Xt - # coupling estimation - if self.method == "sinkhorn": - self.gamma_ = sinkhorn( - a=mu_s, b=mu_t, M=Cost, reg=self.reg_e, - numItermax=self.max_iter, stopThr=self.tol, - verbose=self.verbose, log=self.log) - else: - print("TODO: implement the other methods") - return self def fit_transform(self, Xs=None, ys=None, Xt=None, yt=None): @@ -1157,8 +1142,7 @@ class BaseTransport(BaseEstimator): The transport source samples. """ - # TODO: check whether Xs is new or not - if self.Xs == Xs: + if np.array_equal(self.Xs, Xs): # perform standard barycentric mapping transp = self.gamma_ / np.sum(self.gamma_, 1)[:, None] @@ -1169,7 +1153,9 @@ class BaseTransport(BaseEstimator): transp_Xs = np.dot(transp, self.Xt) else: # perform out of sample mapping - print("out of sample mapping not yet implemented") + print("Warning: out of sample mapping not yet implemented") + print("input data will be returned") + transp_Xs = Xs return transp_Xs @@ -1191,8 +1177,7 @@ class BaseTransport(BaseEstimator): The transported target samples. """ - # TODO: check whether Xt is new or not - if self.Xt == Xt: + if np.array_equal(self.Xt, Xt): # perform standard barycentric mapping transp_ = self.gamma_.T / np.sum(self.gamma_, 0)[:, None] @@ -1203,7 +1188,9 @@ class BaseTransport(BaseEstimator): transp_Xt = np.dot(transp_, self.Xs) else: # perform out of sample mapping - print("out of sample mapping not yet implemented") + print("Warning: out of sample mapping not yet implemented") + print("input data will be returned") + transp_Xt = Xt return transp_Xt @@ -1254,7 +1241,7 @@ class SinkhornTransport(BaseTransport): """ def __init__(self, reg_e=1., mode="unsupervised", max_iter=1000, - tol=10e-9, verbose=False, log=False, mapping="barycentric", + tol=10e-9, verbose=False, log=False, metric="sqeuclidean", distribution_estimation=distribution_estimation_uniform, out_of_sample_map='ferradans'): @@ -1265,7 +1252,6 @@ class SinkhornTransport(BaseTransport): self.tol = tol self.verbose = verbose self.log = log - self.mapping = mapping self.metric = metric self.distribution_estimation = distribution_estimation self.method = "sinkhorn" @@ -1290,10 +1276,10 @@ class SinkhornTransport(BaseTransport): Returns self. """ - return super(SinkhornTransport, self).fit(Xs, ys, Xt, yt) - + self = super(SinkhornTransport, self).fit(Xs, ys, Xt, yt) -if __name__ == "__main__": - print("Small test") - - st = SinkhornTransport() + # coupling estimation + self.gamma_ = sinkhorn( + a=self.mu_s, b=self.mu_t, M=self.Cost, reg=self.reg_e, + numItermax=self.max_iter, stopThr=self.tol, + verbose=self.verbose, log=self.log) diff --git a/test/test_da.py b/test/test_da.py index dfba83f..e7b4ed1 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -6,6 +6,57 @@ import numpy as np import ot +from numpy.testing.utils import assert_allclose, assert_equal +from ot.datasets import get_data_classif +from ot.utils import unif + +np.random.seed(42) + + +def test_sinkhorn_transport(): + """test_sinkhorn_transport + """ + + ns = 150 + nt = 200 + + Xs, ys = get_data_classif('3gauss', ns) + Xt, yt = get_data_classif('3gauss2', nt) + + clf = ot.da.SinkhornTransport() + + # test its computed + clf.fit(Xs=Xs, Xt=Xt) + + # test dimensions of coupling + assert_equal(clf.Cost.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(clf.gamma_.shape, ((Xs.shape[0], Xt.shape[0]))) + + # test margin constraints + mu_s = unif(ns) + mu_t = unif(nt) + assert_allclose(np.sum(clf.gamma_, axis=0), mu_t, rtol=1e-3, atol=1e-3) + assert_allclose(np.sum(clf.gamma_, axis=1), mu_s, rtol=1e-3, atol=1e-3) + + # test transform + transp_Xs = clf.transform(Xs=Xs) + assert_equal(transp_Xs.shape, Xs.shape) + + Xs_new, _ = get_data_classif('3gauss', ns + 1) + transp_Xs_new = clf.transform(Xs_new) + + # check that the oos method is not working + assert_equal(transp_Xs_new, Xs_new) + + # test inverse transform + transp_Xt = clf.inverse_transform(Xt=Xt) + assert_equal(transp_Xt.shape, Xt.shape) + + Xt_new, _ = get_data_classif('3gauss2', nt + 1) + transp_Xt_new = clf.inverse_transform(Xt=Xt_new) + + # check that the oos method is not working and returns the input data + assert_equal(transp_Xt_new, Xt_new) def test_otda(): -- cgit v1.2.3 From d5c6cc178a731d955e5eb85e9f477805fa086518 Mon Sep 17 00:00:00 2001 From: Slasnista Date: Tue, 1 Aug 2017 13:13:50 +0200 Subject: added EMDTransport Class from NG's code + added dedicated test --- ot/da.py | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- test/test_da.py | 59 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 135 insertions(+), 10 deletions(-) (limited to 'test') diff --git a/ot/da.py b/ot/da.py index 6b98a17..fb2fd36 100644 --- a/ot/da.py +++ b/ot/da.py @@ -1144,7 +1144,7 @@ class BaseTransport(BaseEstimator): if np.array_equal(self.Xs, Xs): # perform standard barycentric mapping - transp = self.gamma_ / np.sum(self.gamma_, 1)[:, None] + transp = self.Coupling_ / np.sum(self.Coupling_, 1)[:, None] # set nans to 0 transp[~ np.isfinite(transp)] = 0 @@ -1179,7 +1179,7 @@ class BaseTransport(BaseEstimator): if np.array_equal(self.Xt, Xt): # perform standard barycentric mapping - transp_ = self.gamma_.T / np.sum(self.gamma_, 0)[:, None] + transp_ = self.Coupling_.T / np.sum(self.Coupling_, 0)[:, None] # set nans to 0 transp_[~ np.isfinite(transp_)] = 0 @@ -1228,7 +1228,7 @@ class SinkhornTransport(BaseTransport): Controls the logs of the optimization algorithm Attributes ---------- - gamma_ : the optimal coupling + Coupling_ : the optimal coupling References ---------- @@ -1254,7 +1254,6 @@ class SinkhornTransport(BaseTransport): self.log = log self.metric = metric self.distribution_estimation = distribution_estimation - self.method = "sinkhorn" self.out_of_sample_map = out_of_sample_map def fit(self, Xs=None, ys=None, Xt=None, yt=None): @@ -1276,10 +1275,85 @@ class SinkhornTransport(BaseTransport): Returns self. """ - self = super(SinkhornTransport, self).fit(Xs, ys, Xt, yt) + super(SinkhornTransport, self).fit(Xs, ys, Xt, yt) # coupling estimation - self.gamma_ = sinkhorn( + self.Coupling_ = sinkhorn( a=self.mu_s, b=self.mu_t, M=self.Cost, reg=self.reg_e, numItermax=self.max_iter, stopThr=self.tol, verbose=self.verbose, log=self.log) + + +class EMDTransport(BaseTransport): + """Domain Adapatation OT method based on Earth Mover's Distance + Parameters + ---------- + mode : string, optional (default="unsupervised") + The DA mode. If "unsupervised" no target labels are taken into account + to modify the cost matrix. If "semisupervised" the target labels + are taken into account to set coefficients of the pairwise distance + matrix to 0 for row and columns indices that correspond to source and + target samples which share the same labels. + mapping : string, optional (default="barycentric") + The kind of mapping to apply to transport samples from a domain into + another one. + if "barycentric" only the samples used to estimate the coupling can + be transported from a domain to another one. + metric : string, optional (default="sqeuclidean") + The ground metric for the Wasserstein problem + distribution : string, optional (default="uniform") + The kind of distribution estimation to employ + verbose : int, optional (default=0) + Controls the verbosity of the optimization algorithm + log : int, optional (default=0) + Controls the logs of the optimization algorithm + Attributes + ---------- + Coupling_ : 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, mode="unsupervised", verbose=False, + log=False, metric="sqeuclidean", + distribution_estimation=distribution_estimation_uniform, + out_of_sample_map='ferradans'): + + self.mode = mode + self.verbose = verbose + self.log = log + self.metric = metric + 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 of shape = [n_source_samples, n_features] + The training input samples. + ys : array-like, shape = [n_source_samples] + The class labels + Xt : array-like of shape = [n_target_samples, n_features] + The training input samples. + yt : array-like, shape = [n_labeled_target_samples] + The class labels + Returns + ------- + self : object + Returns self. + """ + + super(EMDTransport, self).fit(Xs, ys, Xt, yt) + + # coupling estimation + self.Coupling_ = emd( + a=self.mu_s, b=self.mu_t, M=self.Cost, + # verbose=self.verbose, + # log=self.log + ) diff --git a/test/test_da.py b/test/test_da.py index e7b4ed1..33b3695 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -13,7 +13,7 @@ from ot.utils import unif np.random.seed(42) -def test_sinkhorn_transport(): +def test_sinkhorn_transport_class(): """test_sinkhorn_transport """ @@ -30,13 +30,59 @@ def test_sinkhorn_transport(): # test dimensions of coupling assert_equal(clf.Cost.shape, ((Xs.shape[0], Xt.shape[0]))) - assert_equal(clf.gamma_.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(clf.Coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) # test margin constraints mu_s = unif(ns) mu_t = unif(nt) - assert_allclose(np.sum(clf.gamma_, axis=0), mu_t, rtol=1e-3, atol=1e-3) - assert_allclose(np.sum(clf.gamma_, axis=1), mu_s, rtol=1e-3, atol=1e-3) + assert_allclose(np.sum(clf.Coupling_, axis=0), mu_t, rtol=1e-3, atol=1e-3) + assert_allclose(np.sum(clf.Coupling_, axis=1), mu_s, rtol=1e-3, atol=1e-3) + + # test transform + transp_Xs = clf.transform(Xs=Xs) + assert_equal(transp_Xs.shape, Xs.shape) + + Xs_new, _ = get_data_classif('3gauss', ns + 1) + transp_Xs_new = clf.transform(Xs_new) + + # check that the oos method is not working + assert_equal(transp_Xs_new, Xs_new) + + # test inverse transform + transp_Xt = clf.inverse_transform(Xt=Xt) + assert_equal(transp_Xt.shape, Xt.shape) + + Xt_new, _ = get_data_classif('3gauss2', nt + 1) + transp_Xt_new = clf.inverse_transform(Xt=Xt_new) + + # check that the oos method is not working and returns the input data + assert_equal(transp_Xt_new, Xt_new) + + +def test_emd_transport_class(): + """test_sinkhorn_transport + """ + + ns = 150 + nt = 200 + + Xs, ys = get_data_classif('3gauss', ns) + Xt, yt = get_data_classif('3gauss2', nt) + + clf = ot.da.EMDTransport() + + # test its computed + clf.fit(Xs=Xs, Xt=Xt) + + # test dimensions of coupling + assert_equal(clf.Cost.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(clf.Coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) + + # test margin constraints + mu_s = unif(ns) + mu_t = unif(nt) + assert_allclose(np.sum(clf.Coupling_, axis=0), mu_t, rtol=1e-3, atol=1e-3) + assert_allclose(np.sum(clf.Coupling_, axis=1), mu_s, rtol=1e-3, atol=1e-3) # test transform transp_Xs = clf.transform(Xs=Xs) @@ -119,3 +165,8 @@ def test_otda(): da_emd = ot.da.OTDA_mapping_kernel() # init class da_emd.fit(xs, xt, numItermax=10) # fit distributions da_emd.predict(xs) # interpolation of source samples + + +if __name__ == "__main__": + test_sinkhorn_transport_class() + test_emd_transport_class() -- cgit v1.2.3 From cd4fa7275dc65e04f7b256dec4208d68006abc25 Mon Sep 17 00:00:00 2001 From: Slasnista Date: Fri, 4 Aug 2017 11:16:30 +0200 Subject: added test for fit_transform + correction of fit_transform bug (missing return self) --- ot/da.py | 4 ++++ test/test_da.py | 13 ++++++++----- 2 files changed, 12 insertions(+), 5 deletions(-) (limited to 'test') diff --git a/ot/da.py b/ot/da.py index fb2fd36..80649a7 100644 --- a/ot/da.py +++ b/ot/da.py @@ -1283,6 +1283,8 @@ class SinkhornTransport(BaseTransport): numItermax=self.max_iter, stopThr=self.tol, verbose=self.verbose, log=self.log) + return self + class EMDTransport(BaseTransport): """Domain Adapatation OT method based on Earth Mover's Distance @@ -1357,3 +1359,5 @@ class EMDTransport(BaseTransport): # verbose=self.verbose, # log=self.log ) + + return self diff --git a/test/test_da.py b/test/test_da.py index 33b3695..68807ec 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -58,6 +58,10 @@ def test_sinkhorn_transport_class(): # check that the oos method is not working and returns the input data assert_equal(transp_Xt_new, Xt_new) + # test fit_transform + transp_Xs = clf.fit_transform(Xs=Xs, Xt=Xt) + assert_equal(transp_Xs.shape, Xs.shape) + def test_emd_transport_class(): """test_sinkhorn_transport @@ -104,6 +108,10 @@ def test_emd_transport_class(): # check that the oos method is not working and returns the input data assert_equal(transp_Xt_new, Xt_new) + # test fit_transform + transp_Xs = clf.fit_transform(Xs=Xs, Xt=Xt) + assert_equal(transp_Xs.shape, Xs.shape) + def test_otda(): @@ -165,8 +173,3 @@ def test_otda(): da_emd = ot.da.OTDA_mapping_kernel() # init class da_emd.fit(xs, xt, numItermax=10) # fit distributions da_emd.predict(xs) # interpolation of source samples - - -if __name__ == "__main__": - test_sinkhorn_transport_class() - test_emd_transport_class() -- cgit v1.2.3 From 0659abe79c15f786a017b62e2a1313f0625af329 Mon Sep 17 00:00:00 2001 From: Slasnista Date: Fri, 4 Aug 2017 11:34:21 +0200 Subject: added new class SinkhornLpl1Transport() + dedicated test --- ot/da.py | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ test/test_da.py | 50 +++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) (limited to 'test') diff --git a/ot/da.py b/ot/da.py index 80649a7..3031f63 100644 --- a/ot/da.py +++ b/ot/da.py @@ -1361,3 +1361,94 @@ class EMDTransport(BaseTransport): ) return self + + +class SinkhornLpl1Transport(BaseTransport): + """Domain Adapatation OT method based on sinkhorn algorithm + + LpL1 class regularization. + + Parameters + ---------- + mode : string, optional (default="unsupervised") + The DA mode. If "unsupervised" no target labels are taken into account + to modify the cost matrix. If "semisupervised" the target labels + are taken into account to set coefficients of the pairwise distance + matrix to 0 for row and columns indices that correspond to source and + target samples which share the same labels. + mapping : string, optional (default="barycentric") + The kind of mapping to apply to transport samples from a domain into + another one. + if "barycentric" only the samples used to estimate the coupling can + be transported from a domain to another one. + metric : string, optional (default="sqeuclidean") + The ground metric for the Wasserstein problem + distribution : string, optional (default="uniform") + The kind of distribution estimation to employ + verbose : int, optional (default=0) + Controls the verbosity of the optimization algorithm + log : int, optional (default=0) + Controls the logs of the optimization algorithm + Attributes + ---------- + Coupling_ : 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 + .. [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., reg_cl=0.1, mode="unsupervised", + max_iter=10, max_inner_iter=200, + tol=10e-9, verbose=False, log=False, + metric="sqeuclidean", + distribution_estimation=distribution_estimation_uniform, + out_of_sample_map='ferradans'): + + self.reg_e = reg_e + self.reg_cl = reg_cl + self.mode = mode + self.max_iter = max_iter + self.max_inner_iter = max_inner_iter + self.tol = tol + self.verbose = verbose + self.log = log + self.metric = metric + 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 of shape = [n_source_samples, n_features] + The training input samples. + ys : array-like, shape = [n_source_samples] + The class labels + Xt : array-like of shape = [n_target_samples, n_features] + The training input samples. + yt : array-like, shape = [n_labeled_target_samples] + The class labels + Returns + ------- + self : object + Returns self. + """ + + super(SinkhornLpl1Transport, self).fit(Xs, ys, Xt, yt) + + self.Coupling_ = sinkhorn_lpl1_mm( + a=self.mu_s, labels_a=ys, b=self.mu_t, M=self.Cost, + reg=self.reg_e, eta=self.reg_cl, numItermax=self.max_iter, + numInnerItermax=self.max_inner_iter, stopInnerThr=self.tol, + verbose=self.verbose, log=self.log) + + return self diff --git a/test/test_da.py b/test/test_da.py index 68807ec..7d00cfb 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -13,6 +13,56 @@ from ot.utils import unif np.random.seed(42) +def test_sinkhorn_lpl1_transport_class(): + """test_sinkhorn_transport + """ + + ns = 150 + nt = 200 + + Xs, ys = get_data_classif('3gauss', ns) + Xt, yt = get_data_classif('3gauss2', nt) + + clf = ot.da.SinkhornLpl1Transport() + + # test its computed + clf.fit(Xs=Xs, ys=ys, Xt=Xt) + + # test dimensions of coupling + assert_equal(clf.Cost.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(clf.Coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) + + # test margin constraints + mu_s = unif(ns) + mu_t = unif(nt) + assert_allclose(np.sum(clf.Coupling_, axis=0), mu_t, rtol=1e-3, atol=1e-3) + assert_allclose(np.sum(clf.Coupling_, axis=1), mu_s, rtol=1e-3, atol=1e-3) + + # test transform + transp_Xs = clf.transform(Xs=Xs) + assert_equal(transp_Xs.shape, Xs.shape) + + Xs_new, _ = get_data_classif('3gauss', ns + 1) + transp_Xs_new = clf.transform(Xs_new) + + # check that the oos method is not working + assert_equal(transp_Xs_new, Xs_new) + + # test inverse transform + transp_Xt = clf.inverse_transform(Xt=Xt) + assert_equal(transp_Xt.shape, Xt.shape) + + Xt_new, _ = get_data_classif('3gauss2', nt + 1) + transp_Xt_new = clf.inverse_transform(Xt=Xt_new) + + # check that the oos method is not working and returns the input data + assert_equal(transp_Xt_new, Xt_new) + + # test fit_transform + transp_Xs = clf.fit_transform(Xs=Xs, ys=ys, Xt=Xt) + assert_equal(transp_Xs.shape, Xs.shape) + + def test_sinkhorn_transport_class(): """test_sinkhorn_transport """ -- cgit v1.2.3 From 2005a09548a6f6d42cd9aafadbb4583e4029936c Mon Sep 17 00:00:00 2001 From: Slasnista Date: Fri, 4 Aug 2017 11:40:44 +0200 Subject: added new class SinkhornL1l2Transport() + dedicated test --- ot/da.py | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ test/test_da.py | 50 ++++++++++++++++++++++++++ 2 files changed, 159 insertions(+) (limited to 'test') diff --git a/ot/da.py b/ot/da.py index 3031f63..6100d15 100644 --- a/ot/da.py +++ b/ot/da.py @@ -1369,6 +1369,10 @@ class SinkhornLpl1Transport(BaseTransport): Parameters ---------- + reg_e : float, optional (default=1) + Entropic regularization parameter + reg_cl : float, optional (default=0.1) + Class regularization parameter mode : string, optional (default="unsupervised") The DA mode. If "unsupervised" no target labels are taken into account to modify the cost matrix. If "semisupervised" the target labels @@ -1384,6 +1388,11 @@ class SinkhornLpl1Transport(BaseTransport): The ground metric for the Wasserstein problem distribution : string, optional (default="uniform") The kind of distribution estimation to employ + 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 verbose : int, optional (default=0) Controls the verbosity of the optimization algorithm log : int, optional (default=0) @@ -1452,3 +1461,103 @@ class SinkhornLpl1Transport(BaseTransport): verbose=self.verbose, log=self.log) return self + + +class SinkhornL1l2Transport(BaseTransport): + """Domain Adapatation OT method based on sinkhorn algorithm + + l1l2 class regularization. + + Parameters + ---------- + reg_e : float, optional (default=1) + Entropic regularization parameter + reg_cl : float, optional (default=0.1) + Class regularization parameter + mode : string, optional (default="unsupervised") + The DA mode. If "unsupervised" no target labels are taken into account + to modify the cost matrix. If "semisupervised" the target labels + are taken into account to set coefficients of the pairwise distance + matrix to 0 for row and columns indices that correspond to source and + target samples which share the same labels. + mapping : string, optional (default="barycentric") + The kind of mapping to apply to transport samples from a domain into + another one. + if "barycentric" only the samples used to estimate the coupling can + be transported from a domain to another one. + metric : string, optional (default="sqeuclidean") + The ground metric for the Wasserstein problem + distribution : string, optional (default="uniform") + The kind of distribution estimation to employ + 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 + verbose : int, optional (default=0) + Controls the verbosity of the optimization algorithm + log : int, optional (default=0) + Controls the logs of the optimization algorithm + Attributes + ---------- + Coupling_ : 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 + .. [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., reg_cl=0.1, mode="unsupervised", + max_iter=10, max_inner_iter=200, + tol=10e-9, verbose=False, log=False, + metric="sqeuclidean", + distribution_estimation=distribution_estimation_uniform, + out_of_sample_map='ferradans'): + + self.reg_e = reg_e + self.reg_cl = reg_cl + self.mode = mode + self.max_iter = max_iter + self.max_inner_iter = max_inner_iter + self.tol = tol + self.verbose = verbose + self.log = log + self.metric = metric + 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 of shape = [n_source_samples, n_features] + The training input samples. + ys : array-like, shape = [n_source_samples] + The class labels + Xt : array-like of shape = [n_target_samples, n_features] + The training input samples. + yt : array-like, shape = [n_labeled_target_samples] + The class labels + Returns + ------- + self : object + Returns self. + """ + + super(SinkhornL1l2Transport, self).fit(Xs, ys, Xt, yt) + + self.Coupling_ = sinkhorn_l1l2_gl( + a=self.mu_s, labels_a=ys, b=self.mu_t, M=self.Cost, + reg=self.reg_e, eta=self.reg_cl, numItermax=self.max_iter, + numInnerItermax=self.max_inner_iter, stopInnerThr=self.tol, + verbose=self.verbose, log=self.log) + + return self diff --git a/test/test_da.py b/test/test_da.py index 7d00cfb..68d1958 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -63,6 +63,56 @@ def test_sinkhorn_lpl1_transport_class(): assert_equal(transp_Xs.shape, Xs.shape) +def test_sinkhorn_l1l2_transport_class(): + """test_sinkhorn_transport + """ + + ns = 150 + nt = 200 + + Xs, ys = get_data_classif('3gauss', ns) + Xt, yt = get_data_classif('3gauss2', nt) + + clf = ot.da.SinkhornL1l2Transport() + + # test its computed + clf.fit(Xs=Xs, ys=ys, Xt=Xt) + + # test dimensions of coupling + assert_equal(clf.Cost.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(clf.Coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) + + # test margin constraints + mu_s = unif(ns) + mu_t = unif(nt) + assert_allclose(np.sum(clf.Coupling_, axis=0), mu_t, rtol=1e-3, atol=1e-3) + assert_allclose(np.sum(clf.Coupling_, axis=1), mu_s, rtol=1e-3, atol=1e-3) + + # test transform + transp_Xs = clf.transform(Xs=Xs) + assert_equal(transp_Xs.shape, Xs.shape) + + Xs_new, _ = get_data_classif('3gauss', ns + 1) + transp_Xs_new = clf.transform(Xs_new) + + # check that the oos method is not working + assert_equal(transp_Xs_new, Xs_new) + + # test inverse transform + transp_Xt = clf.inverse_transform(Xt=Xt) + assert_equal(transp_Xt.shape, Xt.shape) + + Xt_new, _ = get_data_classif('3gauss2', nt + 1) + transp_Xt_new = clf.inverse_transform(Xt=Xt_new) + + # check that the oos method is not working and returns the input data + assert_equal(transp_Xt_new, Xt_new) + + # test fit_transform + transp_Xs = clf.fit_transform(Xs=Xs, ys=ys, Xt=Xt) + assert_equal(transp_Xs.shape, Xs.shape) + + def test_sinkhorn_transport_class(): """test_sinkhorn_transport """ -- cgit v1.2.3 From 4e562a1ce24119b8c9c1efb9d078762904c5d78a Mon Sep 17 00:00:00 2001 From: Slasnista Date: Fri, 4 Aug 2017 12:04:04 +0200 Subject: semi supervised mode supported --- ot/da.py | 21 +++++++++++++++++++-- test/test_da.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) (limited to 'test') diff --git a/ot/da.py b/ot/da.py index 6100d15..8294e8d 100644 --- a/ot/da.py +++ b/ot/da.py @@ -1089,8 +1089,25 @@ class BaseTransport(BaseEstimator): self.Cost = dist(Xs, Xt, metric=self.metric) if self.mode == "semisupervised": - print("TODO: modify cost matrix accordingly") - pass + + if (ys is not None) and (yt is not None): + + # assumes labeled source samples occupy the first rows + # and labeled target samples occupy the first columns + classes = np.unique(ys) + for c in classes: + ids = np.where(ys == c) + idt = np.where(yt == c) + + # all the coefficients corresponding to a source sample + # and a target sample with the same label gets a 0 + # transport cost + for j in idt[0]: + self.Cost[ids[0], j] = 0 + else: + print("Warning: using unsupervised mode\ + \nto use semisupervised mode, please provide ys and yt") + pass # distribution estimation self.mu_s = self.distribution_estimation(Xs) diff --git a/test/test_da.py b/test/test_da.py index 68d1958..497a8ee 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -62,6 +62,19 @@ def test_sinkhorn_lpl1_transport_class(): transp_Xs = clf.fit_transform(Xs=Xs, ys=ys, Xt=Xt) assert_equal(transp_Xs.shape, Xs.shape) + # test semi supervised mode + clf = ot.da.SinkhornTransport(mode="semisupervised") + clf.fit(Xs=Xs, Xt=Xt) + n_unsup = np.sum(clf.Cost) + + # test semi supervised mode + clf = ot.da.SinkhornTransport(mode="semisupervised") + clf.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) + assert_equal(clf.Cost.shape, ((Xs.shape[0], Xt.shape[0]))) + n_semisup = np.sum(clf.Cost) + + assert n_unsup != n_semisup, "semisupervised mode not working" + def test_sinkhorn_l1l2_transport_class(): """test_sinkhorn_transport @@ -112,6 +125,19 @@ def test_sinkhorn_l1l2_transport_class(): transp_Xs = clf.fit_transform(Xs=Xs, ys=ys, Xt=Xt) assert_equal(transp_Xs.shape, Xs.shape) + # test semi supervised mode + clf = ot.da.SinkhornTransport(mode="semisupervised") + clf.fit(Xs=Xs, Xt=Xt) + n_unsup = np.sum(clf.Cost) + + # test semi supervised mode + clf = ot.da.SinkhornTransport(mode="semisupervised") + clf.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) + assert_equal(clf.Cost.shape, ((Xs.shape[0], Xt.shape[0]))) + n_semisup = np.sum(clf.Cost) + + assert n_unsup != n_semisup, "semisupervised mode not working" + def test_sinkhorn_transport_class(): """test_sinkhorn_transport @@ -162,6 +188,19 @@ def test_sinkhorn_transport_class(): transp_Xs = clf.fit_transform(Xs=Xs, Xt=Xt) assert_equal(transp_Xs.shape, Xs.shape) + # test semi supervised mode + clf = ot.da.SinkhornTransport(mode="semisupervised") + clf.fit(Xs=Xs, Xt=Xt) + n_unsup = np.sum(clf.Cost) + + # test semi supervised mode + clf = ot.da.SinkhornTransport(mode="semisupervised") + clf.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) + assert_equal(clf.Cost.shape, ((Xs.shape[0], Xt.shape[0]))) + n_semisup = np.sum(clf.Cost) + + assert n_unsup != n_semisup, "semisupervised mode not working" + def test_emd_transport_class(): """test_sinkhorn_transport @@ -212,6 +251,19 @@ def test_emd_transport_class(): transp_Xs = clf.fit_transform(Xs=Xs, Xt=Xt) assert_equal(transp_Xs.shape, Xs.shape) + # test semi supervised mode + clf = ot.da.SinkhornTransport(mode="semisupervised") + clf.fit(Xs=Xs, Xt=Xt) + n_unsup = np.sum(clf.Cost) + + # test semi supervised mode + clf = ot.da.SinkhornTransport(mode="semisupervised") + clf.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) + assert_equal(clf.Cost.shape, ((Xs.shape[0], Xt.shape[0]))) + n_semisup = np.sum(clf.Cost) + + assert n_unsup != n_semisup, "semisupervised mode not working" + def test_otda(): -- cgit v1.2.3 From 62b40a9993e9ccca27d1677aa1294fff6246e904 Mon Sep 17 00:00:00 2001 From: Slasnista Date: Fri, 4 Aug 2017 13:56:51 +0200 Subject: correction of semi supervised mode --- ot/da.py | 77 +++++++++++++++++++++++++++++++++------------------------ test/test_da.py | 20 +++++++-------- 2 files changed, 55 insertions(+), 42 deletions(-) (limited to 'test') diff --git a/ot/da.py b/ot/da.py index 8294e8d..08e8a8d 100644 --- a/ot/da.py +++ b/ot/da.py @@ -1088,26 +1088,23 @@ class BaseTransport(BaseEstimator): # pairwise distance self.Cost = dist(Xs, Xt, metric=self.metric) - if self.mode == "semisupervised": - - if (ys is not None) and (yt is not None): - - # assumes labeled source samples occupy the first rows - # and labeled target samples occupy the first columns - classes = np.unique(ys) - for c in classes: - ids = np.where(ys == c) - idt = np.where(yt == c) - - # all the coefficients corresponding to a source sample - # and a target sample with the same label gets a 0 - # transport cost - for j in idt[0]: - self.Cost[ids[0], j] = 0 - else: - print("Warning: using unsupervised mode\ - \nto use semisupervised mode, please provide ys and yt") - pass + if (ys is not None) and (yt is not None): + + if self.limit_max != np.infty: + self.limit_max = self.limit_max * np.max(self.Cost) + + # assumes labeled source samples occupy the first rows + # and labeled target samples occupy the first columns + classes = np.unique(ys) + for c in classes: + idx_s = np.where((ys != c) & (ys != -1)) + idx_t = np.where(yt == c) + + # all the coefficients corresponding to a source sample + # and a target sample : + # with different labels get a infinite + for j in idx_t[0]: + self.Cost[idx_s[0], j] = self.limit_max # distribution estimation self.mu_s = self.distribution_estimation(Xs) @@ -1243,6 +1240,9 @@ class SinkhornTransport(BaseTransport): Controls the verbosity of the optimization algorithm log : int, optional (default=0) Controls the logs of the optimization algorithm + limit_max: float, optional (defaul=np.infty) + Controls the semi supervised mode. Transport between labeled source + and target samples of different classes will exhibit an infinite cost Attributes ---------- Coupling_ : the optimal coupling @@ -1257,19 +1257,19 @@ class SinkhornTransport(BaseTransport): 26, 2013 """ - def __init__(self, reg_e=1., mode="unsupervised", max_iter=1000, + def __init__(self, reg_e=1., max_iter=1000, tol=10e-9, verbose=False, log=False, metric="sqeuclidean", distribution_estimation=distribution_estimation_uniform, - out_of_sample_map='ferradans'): + out_of_sample_map='ferradans', limit_max=np.infty): self.reg_e = reg_e - self.mode = mode self.max_iter = max_iter self.tol = tol self.verbose = verbose self.log = log self.metric = metric + self.limit_max = limit_max self.distribution_estimation = distribution_estimation self.out_of_sample_map = out_of_sample_map @@ -1326,6 +1326,10 @@ class EMDTransport(BaseTransport): Controls the verbosity of the optimization algorithm log : int, optional (default=0) Controls the logs of the optimization algorithm + limit_max: float, optional (default=10) + Controls the semi supervised mode. Transport between labeled source + and target samples of different classes will exhibit an infinite cost + (10 times the maximum value of the cost matrix) Attributes ---------- Coupling_ : the optimal coupling @@ -1337,15 +1341,15 @@ class EMDTransport(BaseTransport): on Pattern Analysis and Machine Intelligence , vol.PP, no.99, pp.1-1 """ - def __init__(self, mode="unsupervised", verbose=False, + def __init__(self, verbose=False, log=False, metric="sqeuclidean", distribution_estimation=distribution_estimation_uniform, - out_of_sample_map='ferradans'): + out_of_sample_map='ferradans', limit_max=10): - self.mode = mode self.verbose = verbose self.log = log self.metric = metric + self.limit_max = limit_max self.distribution_estimation = distribution_estimation self.out_of_sample_map = out_of_sample_map @@ -1414,6 +1418,10 @@ class SinkhornLpl1Transport(BaseTransport): Controls the verbosity of the optimization algorithm log : int, optional (default=0) Controls the logs of the optimization algorithm + limit_max: float, optional (defaul=np.infty) + Controls the semi supervised mode. Transport between labeled source + and target samples of different classes will exhibit an infinite cost + Attributes ---------- Coupling_ : the optimal coupling @@ -1431,16 +1439,15 @@ class SinkhornLpl1Transport(BaseTransport): """ - def __init__(self, reg_e=1., reg_cl=0.1, mode="unsupervised", + 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", distribution_estimation=distribution_estimation_uniform, - out_of_sample_map='ferradans'): + out_of_sample_map='ferradans', limit_max=np.infty): self.reg_e = reg_e self.reg_cl = reg_cl - self.mode = mode self.max_iter = max_iter self.max_inner_iter = max_inner_iter self.tol = tol @@ -1449,6 +1456,7 @@ class SinkhornLpl1Transport(BaseTransport): self.metric = metric self.distribution_estimation = distribution_estimation 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 @@ -1514,6 +1522,11 @@ class SinkhornL1l2Transport(BaseTransport): Controls the verbosity of the optimization algorithm log : int, optional (default=0) Controls the logs of the optimization algorithm + limit_max: float, optional (default=10) + Controls the semi supervised mode. Transport between labeled source + and target samples of different classes will exhibit an infinite cost + (10 times the maximum value of the cost matrix) + Attributes ---------- Coupling_ : the optimal coupling @@ -1531,16 +1544,15 @@ class SinkhornL1l2Transport(BaseTransport): """ - def __init__(self, reg_e=1., reg_cl=0.1, mode="unsupervised", + 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", distribution_estimation=distribution_estimation_uniform, - out_of_sample_map='ferradans'): + out_of_sample_map='ferradans', limit_max=10): self.reg_e = reg_e self.reg_cl = reg_cl - self.mode = mode self.max_iter = max_iter self.max_inner_iter = max_inner_iter self.tol = tol @@ -1549,6 +1561,7 @@ class SinkhornL1l2Transport(BaseTransport): self.metric = metric self.distribution_estimation = distribution_estimation 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 diff --git a/test/test_da.py b/test/test_da.py index 497a8ee..ecd2a3a 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -63,12 +63,12 @@ def test_sinkhorn_lpl1_transport_class(): assert_equal(transp_Xs.shape, Xs.shape) # test semi supervised mode - clf = ot.da.SinkhornTransport(mode="semisupervised") - clf.fit(Xs=Xs, Xt=Xt) + clf = ot.da.SinkhornLpl1Transport() + clf.fit(Xs=Xs, ys=ys, Xt=Xt) n_unsup = np.sum(clf.Cost) # test semi supervised mode - clf = ot.da.SinkhornTransport(mode="semisupervised") + clf = ot.da.SinkhornLpl1Transport() clf.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) assert_equal(clf.Cost.shape, ((Xs.shape[0], Xt.shape[0]))) n_semisup = np.sum(clf.Cost) @@ -126,12 +126,12 @@ def test_sinkhorn_l1l2_transport_class(): assert_equal(transp_Xs.shape, Xs.shape) # test semi supervised mode - clf = ot.da.SinkhornTransport(mode="semisupervised") - clf.fit(Xs=Xs, Xt=Xt) + clf = ot.da.SinkhornL1l2Transport() + clf.fit(Xs=Xs, ys=ys, Xt=Xt) n_unsup = np.sum(clf.Cost) # test semi supervised mode - clf = ot.da.SinkhornTransport(mode="semisupervised") + clf = ot.da.SinkhornL1l2Transport() clf.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) assert_equal(clf.Cost.shape, ((Xs.shape[0], Xt.shape[0]))) n_semisup = np.sum(clf.Cost) @@ -189,12 +189,12 @@ def test_sinkhorn_transport_class(): assert_equal(transp_Xs.shape, Xs.shape) # test semi supervised mode - clf = ot.da.SinkhornTransport(mode="semisupervised") + clf = ot.da.SinkhornTransport() clf.fit(Xs=Xs, Xt=Xt) n_unsup = np.sum(clf.Cost) # test semi supervised mode - clf = ot.da.SinkhornTransport(mode="semisupervised") + clf = ot.da.SinkhornTransport() clf.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) assert_equal(clf.Cost.shape, ((Xs.shape[0], Xt.shape[0]))) n_semisup = np.sum(clf.Cost) @@ -252,12 +252,12 @@ def test_emd_transport_class(): assert_equal(transp_Xs.shape, Xs.shape) # test semi supervised mode - clf = ot.da.SinkhornTransport(mode="semisupervised") + clf = ot.da.EMDTransport() clf.fit(Xs=Xs, Xt=Xt) n_unsup = np.sum(clf.Cost) # test semi supervised mode - clf = ot.da.SinkhornTransport(mode="semisupervised") + clf = ot.da.EMDTransport() clf.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) assert_equal(clf.Cost.shape, ((Xs.shape[0], Xt.shape[0]))) n_semisup = np.sum(clf.Cost) -- cgit v1.2.3 From b8672f67639e9daa3f91e555581256f984115f56 Mon Sep 17 00:00:00 2001 From: Slasnista Date: Fri, 4 Aug 2017 14:55:54 +0200 Subject: out of samples by Ferradans supported for transform and inverse_transform --- ot/da.py | 29 +++++++++++++++++++++++------ test/test_da.py | 32 ++++++++++++++++---------------- 2 files changed, 39 insertions(+), 22 deletions(-) (limited to 'test') diff --git a/ot/da.py b/ot/da.py index 92a8f12..87d056d 100644 --- a/ot/da.py +++ b/ot/da.py @@ -1167,9 +1167,18 @@ class BaseTransport(BaseEstimator): transp_Xs = np.dot(transp, self.Xt) else: # perform out of sample mapping - print("Warning: out of sample mapping not yet implemented") - print("input data will be returned") - transp_Xs = Xs + + # get the nearest neighbor in the source domain + D0 = dist(Xs, self.Xs) + idx = np.argmin(D0, axis=1) + + # transport the source samples + transp = self.Coupling_ / np.sum(self.Coupling_, 1)[:, None] + transp[~ np.isfinite(transp)] = 0 + transp_Xs_ = np.dot(transp, self.Xt) + + # define the transported points + transp_Xs = transp_Xs_[idx, :] + Xs - self.Xs[idx, :] return transp_Xs @@ -1202,9 +1211,17 @@ class BaseTransport(BaseEstimator): transp_Xt = np.dot(transp_, self.Xs) else: # perform out of sample mapping - print("Warning: out of sample mapping not yet implemented") - print("input data will be returned") - transp_Xt = Xt + + D0 = dist(Xt, self.Xt) + idx = np.argmin(D0, axis=1) + + # transport the target samples + transp_ = self.Coupling_.T / np.sum(self.Coupling_, 0)[:, None] + transp_[~ np.isfinite(transp_)] = 0 + transp_Xt_ = np.dot(transp_, self.Xs) + + # define the transported points + transp_Xt = transp_Xt_[idx, :] + Xt - self.Xt[idx, :] return transp_Xt diff --git a/test/test_da.py b/test/test_da.py index ecd2a3a..aed9f61 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -45,8 +45,8 @@ def test_sinkhorn_lpl1_transport_class(): Xs_new, _ = get_data_classif('3gauss', ns + 1) transp_Xs_new = clf.transform(Xs_new) - # check that the oos method is not working - assert_equal(transp_Xs_new, Xs_new) + # check that the oos method is working + assert_equal(transp_Xs_new.shape, Xs_new.shape) # test inverse transform transp_Xt = clf.inverse_transform(Xt=Xt) @@ -55,8 +55,8 @@ def test_sinkhorn_lpl1_transport_class(): Xt_new, _ = get_data_classif('3gauss2', nt + 1) transp_Xt_new = clf.inverse_transform(Xt=Xt_new) - # check that the oos method is not working and returns the input data - assert_equal(transp_Xt_new, Xt_new) + # check that the oos method is working + assert_equal(transp_Xt_new.shape, Xt_new.shape) # test fit_transform transp_Xs = clf.fit_transform(Xs=Xs, ys=ys, Xt=Xt) @@ -108,8 +108,8 @@ def test_sinkhorn_l1l2_transport_class(): Xs_new, _ = get_data_classif('3gauss', ns + 1) transp_Xs_new = clf.transform(Xs_new) - # check that the oos method is not working - assert_equal(transp_Xs_new, Xs_new) + # check that the oos method is working + assert_equal(transp_Xs_new.shape, Xs_new.shape) # test inverse transform transp_Xt = clf.inverse_transform(Xt=Xt) @@ -118,8 +118,8 @@ def test_sinkhorn_l1l2_transport_class(): Xt_new, _ = get_data_classif('3gauss2', nt + 1) transp_Xt_new = clf.inverse_transform(Xt=Xt_new) - # check that the oos method is not working and returns the input data - assert_equal(transp_Xt_new, Xt_new) + # check that the oos method is working + assert_equal(transp_Xt_new.shape, Xt_new.shape) # test fit_transform transp_Xs = clf.fit_transform(Xs=Xs, ys=ys, Xt=Xt) @@ -171,8 +171,8 @@ def test_sinkhorn_transport_class(): Xs_new, _ = get_data_classif('3gauss', ns + 1) transp_Xs_new = clf.transform(Xs_new) - # check that the oos method is not working - assert_equal(transp_Xs_new, Xs_new) + # check that the oos method is working + assert_equal(transp_Xs_new.shape, Xs_new.shape) # test inverse transform transp_Xt = clf.inverse_transform(Xt=Xt) @@ -181,8 +181,8 @@ def test_sinkhorn_transport_class(): Xt_new, _ = get_data_classif('3gauss2', nt + 1) transp_Xt_new = clf.inverse_transform(Xt=Xt_new) - # check that the oos method is not working and returns the input data - assert_equal(transp_Xt_new, Xt_new) + # check that the oos method is working + assert_equal(transp_Xt_new.shape, Xt_new.shape) # test fit_transform transp_Xs = clf.fit_transform(Xs=Xs, Xt=Xt) @@ -234,8 +234,8 @@ def test_emd_transport_class(): Xs_new, _ = get_data_classif('3gauss', ns + 1) transp_Xs_new = clf.transform(Xs_new) - # check that the oos method is not working - assert_equal(transp_Xs_new, Xs_new) + # check that the oos method is working + assert_equal(transp_Xs_new.shape, Xs_new.shape) # test inverse transform transp_Xt = clf.inverse_transform(Xt=Xt) @@ -244,8 +244,8 @@ def test_emd_transport_class(): Xt_new, _ = get_data_classif('3gauss2', nt + 1) transp_Xt_new = clf.inverse_transform(Xt=Xt_new) - # check that the oos method is not working and returns the input data - assert_equal(transp_Xt_new, Xt_new) + # check that the oos method is working + assert_equal(transp_Xt_new.shape, Xt_new.shape) # test fit_transform transp_Xs = clf.fit_transform(Xs=Xs, Xt=Xt) -- cgit v1.2.3 From d20a067f91dcca318e2841ac52a8c578c78b89b2 Mon Sep 17 00:00:00 2001 From: Slasnista Date: Wed, 23 Aug 2017 11:45:06 +0200 Subject: make doc strings compliant with numpy / modif according to AG review --- ot/da.py | 139 +++++++++++++++++++++++++++++++++----------------------- test/test_da.py | 13 ++++-- 2 files changed, 93 insertions(+), 59 deletions(-) (limited to 'test') diff --git a/ot/da.py b/ot/da.py index 0616d17..044d567 100644 --- a/ot/da.py +++ b/ot/da.py @@ -967,11 +967,13 @@ class BaseEstimator(object): def get_params(self, deep=True): """Get parameters for this estimator. + Parameters ---------- deep : boolean, optional If True, will return the parameters for this estimator and contained subobjects that are estimators. + Returns ------- params : mapping of string to any @@ -1002,10 +1004,12 @@ class BaseEstimator(object): def set_params(self, **params): """Set the parameters of this estimator. + The method works on simple estimators as well as on nested objects (such as pipelines). The latter have parameters of the form ``__`` so that it's possible to update each component of a nested object. + Returns ------- self @@ -1053,11 +1057,12 @@ def distribution_estimation_uniform(X): Parameters ---------- - X : array-like of shape = (n_samples, n_features) + X : array-like, shape (n_samples, n_features) The array of samples + Returns ------- - mu : array-like, shape = (n_samples,) + mu : array-like, shape (n_samples,) The uniform distribution estimated from X """ @@ -1069,16 +1074,18 @@ class BaseTransport(BaseEstimator): 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) + Parameters ---------- - Xs : array-like of shape = (n_source_samples, n_features) + Xs : array-like, shape (n_source_samples, n_features) The training input samples. - ys : array-like, shape = (n_source_samples,) + ys : array-like, shape (n_source_samples,) The class labels - Xt : array-like of shape = (n_target_samples, n_features) + Xt : array-like, shape (n_target_samples, n_features) The training input samples. - yt : array-like, shape = (n_labeled_target_samples,) + yt : array-like, shape (n_labeled_target_samples,) The class labels + Returns ------- self : object @@ -1086,12 +1093,12 @@ class BaseTransport(BaseEstimator): """ # pairwise distance - self.Cost = dist(Xs, Xt, metric=self.metric) + self.cost_ = dist(Xs, Xt, metric=self.metric) if (ys is not None) and (yt is not None): if self.limit_max != np.infty: - self.limit_max = self.limit_max * np.max(self.Cost) + self.limit_max = self.limit_max * np.max(self.cost_) # assumes labeled source samples occupy the first rows # and labeled target samples occupy the first columns @@ -1104,7 +1111,7 @@ class BaseTransport(BaseEstimator): # and a target sample : # with different labels get a infinite for j in idx_t[0]: - self.Cost[idx_s[0], j] = self.limit_max + self.cost_[idx_s[0], j] = self.limit_max # distribution estimation self.mu_s = self.distribution_estimation(Xs) @@ -1120,19 +1127,21 @@ class BaseTransport(BaseEstimator): """Build a coupling matrix from source and target sets of samples (Xs, ys) and (Xt, yt) and transports source samples Xs onto target ones Xt + Parameters ---------- - Xs : array-like of shape = (n_source_samples, n_features) + Xs : array-like, shape (n_source_samples, n_features) The training input samples. - ys : array-like, shape = (n_source_samples,) + ys : array-like, shape (n_source_samples,) The class labels - Xt : array-like of shape = (n_target_samples, n_features) + Xt : array-like, shape (n_target_samples, n_features) The training input samples. - yt : array-like, shape = (n_labeled_target_samples,) + yt : array-like, shape (n_labeled_target_samples,) The class labels + Returns ------- - transp_Xs : array-like of shape = (n_source_samples, n_features) + transp_Xs : array-like, shape (n_source_samples, n_features) The source samples samples. """ @@ -1140,25 +1149,27 @@ class BaseTransport(BaseEstimator): def transform(self, Xs=None, ys=None, Xt=None, yt=None): """Transports source samples Xs onto target ones Xt + Parameters ---------- - Xs : array-like of shape = (n_source_samples, n_features) + Xs : array-like, shape (n_source_samples, n_features) The training input samples. - ys : array-like, shape = (n_source_samples,) + ys : array-like, shape (n_source_samples,) The class labels - Xt : array-like of shape = (n_target_samples, n_features) + Xt : array-like, shape (n_target_samples, n_features) The training input samples. - yt : array-like, shape = (n_labeled_target_samples,) + yt : array-like, shape (n_labeled_target_samples,) The class labels + Returns ------- - transp_Xs : array-like of shape = (n_source_samples, n_features) + transp_Xs : array-like, shape (n_source_samples, n_features) The transport source samples. """ if np.array_equal(self.Xs, Xs): # perform standard barycentric mapping - transp = self.Coupling_ / np.sum(self.Coupling_, 1)[:, None] + transp = self.coupling_ / np.sum(self.coupling_, 1)[:, None] # set nans to 0 transp[~ np.isfinite(transp)] = 0 @@ -1173,7 +1184,7 @@ class BaseTransport(BaseEstimator): idx = np.argmin(D0, axis=1) # transport the source samples - transp = self.Coupling_ / np.sum(self.Coupling_, 1)[:, None] + transp = self.coupling_ / np.sum(self.coupling_, 1)[:, None] transp[~ np.isfinite(transp)] = 0 transp_Xs_ = np.dot(transp, self.Xt) @@ -1184,25 +1195,27 @@ class BaseTransport(BaseEstimator): def inverse_transform(self, Xs=None, ys=None, Xt=None, yt=None): """Transports target samples Xt onto target samples Xs + Parameters ---------- - Xs : array-like of shape = (n_source_samples, n_features) + 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 of shape = (n_target_samples, n_features) + Xt : array-like, shape (n_target_samples, n_features) The training input samples. yt : array-like, shape = (n_labeled_target_samples,) The class labels + Returns ------- - transp_Xt : array-like of shape = (n_source_samples, n_features) + transp_Xt : array-like, shape (n_source_samples, n_features) The transported target samples. """ if np.array_equal(self.Xt, Xt): # perform standard barycentric mapping - transp_ = self.Coupling_.T / np.sum(self.Coupling_, 0)[:, None] + transp_ = self.coupling_.T / np.sum(self.coupling_, 0)[:, None] # set nans to 0 transp_[~ np.isfinite(transp_)] = 0 @@ -1216,7 +1229,7 @@ class BaseTransport(BaseEstimator): idx = np.argmin(D0, axis=1) # transport the target samples - transp_ = self.Coupling_.T / np.sum(self.Coupling_, 0)[:, None] + transp_ = self.coupling_.T / np.sum(self.coupling_, 0)[:, None] transp_[~ np.isfinite(transp_)] = 0 transp_Xt_ = np.dot(transp_, self.Xs) @@ -1254,9 +1267,10 @@ class SinkhornTransport(BaseTransport): limit_max: float, optional (defaul=np.infty) Controls the semi supervised mode. Transport between labeled source and target samples of different classes will exhibit an infinite cost + Attributes ---------- - Coupling_ : the optimal coupling + coupling_ : the optimal coupling References ---------- @@ -1287,16 +1301,18 @@ class SinkhornTransport(BaseTransport): 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) + Parameters ---------- - Xs : array-like of shape = (n_source_samples, n_features) + 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 of shape = (n_target_samples, n_features) + Xt : array-like, shape (n_target_samples, n_features) The training input samples. yt : array-like, shape = (n_labeled_target_samples,) The class labels + Returns ------- self : object @@ -1306,8 +1322,8 @@ class SinkhornTransport(BaseTransport): super(SinkhornTransport, self).fit(Xs, ys, Xt, yt) # coupling estimation - self.Coupling_ = sinkhorn( - a=self.mu_s, b=self.mu_t, M=self.Cost, reg=self.reg_e, + self.coupling_ = sinkhorn( + a=self.mu_s, b=self.mu_t, M=self.cost_, reg=self.reg_e, numItermax=self.max_iter, stopThr=self.tol, verbose=self.verbose, log=self.log) @@ -1316,6 +1332,7 @@ class SinkhornTransport(BaseTransport): class EMDTransport(BaseTransport): """Domain Adapatation OT method based on Earth Mover's Distance + Parameters ---------- mapping : string, optional (default="barycentric") @@ -1335,9 +1352,10 @@ class EMDTransport(BaseTransport): Controls the semi supervised mode. Transport between labeled source and target samples of different classes will exhibit an infinite cost (10 times the maximum value of the cost matrix) + Attributes ---------- - Coupling_ : the optimal coupling + coupling_ : the optimal coupling References ---------- @@ -1358,16 +1376,18 @@ class EMDTransport(BaseTransport): 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 of shape = (n_source_samples, n_features) + 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 of shape = (n_target_samples, n_features) + Xt : array-like, shape (n_target_samples, n_features) The training input samples. yt : array-like, shape = (n_labeled_target_samples,) The class labels + Returns ------- self : object @@ -1377,8 +1397,8 @@ class EMDTransport(BaseTransport): super(EMDTransport, self).fit(Xs, ys, Xt, yt) # coupling estimation - self.Coupling_ = emd( - a=self.mu_s, b=self.mu_t, M=self.Cost, + self.coupling_ = emd( + a=self.mu_s, b=self.mu_t, M=self.cost_, ) return self @@ -1418,7 +1438,7 @@ class SinkhornLpl1Transport(BaseTransport): Attributes ---------- - Coupling_ : the optimal coupling + coupling_ : the optimal coupling References ---------- @@ -1455,16 +1475,18 @@ class SinkhornLpl1Transport(BaseTransport): 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 of shape = (n_source_samples, n_features) + 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 of shape = (n_target_samples, n_features) + Xt : array-like, shape (n_target_samples, n_features) The training input samples. yt : array-like, shape = (n_labeled_target_samples,) The class labels + Returns ------- self : object @@ -1473,8 +1495,8 @@ class SinkhornLpl1Transport(BaseTransport): super(SinkhornLpl1Transport, self).fit(Xs, ys, Xt, yt) - self.Coupling_ = sinkhorn_lpl1_mm( - a=self.mu_s, labels_a=ys, b=self.mu_t, M=self.Cost, + self.coupling_ = sinkhorn_lpl1_mm( + a=self.mu_s, labels_a=ys, b=self.mu_t, M=self.cost_, reg=self.reg_e, eta=self.reg_cl, numItermax=self.max_iter, numInnerItermax=self.max_inner_iter, stopInnerThr=self.tol, verbose=self.verbose, log=self.log) @@ -1517,7 +1539,7 @@ class SinkhornL1l2Transport(BaseTransport): Attributes ---------- - Coupling_ : the optimal coupling + coupling_ : the optimal coupling References ---------- @@ -1554,16 +1576,18 @@ class SinkhornL1l2Transport(BaseTransport): 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 of shape = (n_source_samples, n_features) + 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 of shape = (n_target_samples, n_features) + Xt : array-like, shape (n_target_samples, n_features) The training input samples. yt : array-like, shape = (n_labeled_target_samples,) The class labels + Returns ------- self : object @@ -1572,8 +1596,8 @@ class SinkhornL1l2Transport(BaseTransport): super(SinkhornL1l2Transport, self).fit(Xs, ys, Xt, yt) - self.Coupling_ = sinkhorn_l1l2_gl( - a=self.mu_s, labels_a=ys, b=self.mu_t, M=self.Cost, + self.coupling_ = sinkhorn_l1l2_gl( + a=self.mu_s, labels_a=ys, b=self.mu_t, M=self.cost_, reg=self.reg_e, eta=self.reg_cl, numItermax=self.max_iter, numInnerItermax=self.max_inner_iter, stopInnerThr=self.tol, verbose=self.verbose, log=self.log) @@ -1614,8 +1638,8 @@ class MappingTransport(BaseEstimator): Attributes ---------- - Coupling_ : the optimal coupling - Mapping_ : the mapping associated + coupling_ : the optimal coupling + mapping_ : the mapping associated References ---------- @@ -1646,16 +1670,18 @@ class MappingTransport(BaseEstimator): 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) + Parameters ---------- - Xs : array-like of shape = (n_source_samples, n_features) + 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 of shape = (n_target_samples, n_features) + Xt : array-like, shape (n_target_samples, n_features) The training input samples. yt : array-like, shape = (n_labeled_target_samples,) The class labels + Returns ------- self : object @@ -1666,14 +1692,14 @@ class MappingTransport(BaseEstimator): self.Xt = Xt if self.kernel == "linear": - self.Coupling_, self.Mapping_ = joint_OT_mapping_linear( + self.coupling_, self.mapping_ = joint_OT_mapping_linear( Xs, Xt, mu=self.mu, eta=self.eta, bias=self.bias, verbose=self.verbose, verbose2=self.verbose2, numItermax=self.max_iter, numInnerItermax=self.max_inner_iter, stopThr=self.tol, stopInnerThr=self.inner_tol, log=self.log) elif self.kernel == "gaussian": - self.Coupling_, self.Mapping_ = joint_OT_mapping_kernel( + self.coupling_, self.mapping_ = joint_OT_mapping_kernel( Xs, Xt, mu=self.mu, eta=self.eta, bias=self.bias, sigma=self.sigma, verbose=self.verbose, verbose2=self.verbose, numItermax=self.max_iter, numInnerItermax=self.max_inner_iter, @@ -1683,20 +1709,21 @@ class MappingTransport(BaseEstimator): def transform(self, Xs): """Transports source samples Xs onto target ones Xt + Parameters ---------- - Xs : array-like of shape = (n_source_samples, n_features) + Xs : array-like, shape (n_source_samples, n_features) The training input samples. Returns ------- - transp_Xs : array-like of shape = (n_source_samples, n_features) + transp_Xs : array-like, shape (n_source_samples, n_features) The transport source samples. """ if np.array_equal(self.Xs, Xs): # perform standard barycentric mapping - transp = self.Coupling_ / np.sum(self.Coupling_, 1)[:, None] + transp = self.coupling_ / np.sum(self.coupling_, 1)[:, None] # set nans to 0 transp[~ np.isfinite(transp)] = 0 @@ -1710,6 +1737,6 @@ class MappingTransport(BaseEstimator): K = Xs if self.bias: K = np.hstack((K, np.ones((Xs.shape[0], 1)))) - transp_Xs = K.dot(self.Mapping_) + transp_Xs = K.dot(self.mapping_) return transp_Xs diff --git a/test/test_da.py b/test/test_da.py index aed9f61..93f7e83 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -5,13 +5,12 @@ # License: MIT License import numpy as np -import ot from numpy.testing.utils import assert_allclose, assert_equal + +import ot from ot.datasets import get_data_classif from ot.utils import unif -np.random.seed(42) - def test_sinkhorn_lpl1_transport_class(): """test_sinkhorn_transport @@ -325,3 +324,11 @@ def test_otda(): da_emd = ot.da.OTDA_mapping_kernel() # init class da_emd.fit(xs, xt, numItermax=10) # fit distributions da_emd.predict(xs) # interpolation of source samples + + +if __name__ == "__main__": + + test_sinkhorn_transport_class() + test_emd_transport_class() + test_sinkhorn_l1l2_transport_class() + test_sinkhorn_lpl1_transport_class() -- cgit v1.2.3 From 8d19d365446efc00d8443c6ddb5b93fded3fa5ab Mon Sep 17 00:00:00 2001 From: Slasnista Date: Wed, 23 Aug 2017 13:50:24 +0200 Subject: out of samples transform and inverse transform by batch --- ot/da.py | 89 +++++++++++++++++++++++++++++++++++++-------------------- test/test_da.py | 66 +++++++++++++++++++++--------------------- 2 files changed, 91 insertions(+), 64 deletions(-) (limited to 'test') diff --git a/ot/da.py b/ot/da.py index 044d567..0c83ae6 100644 --- a/ot/da.py +++ b/ot/da.py @@ -1147,7 +1147,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): + def transform(self, Xs=None, ys=None, Xt=None, yt=None, batch_size=128): """Transports source samples Xs onto target ones Xt Parameters @@ -1160,6 +1160,8 @@ class BaseTransport(BaseEstimator): The training input samples. yt : array-like, shape (n_labeled_target_samples,) The class labels + batch_size : int, optional (default=128) + The batch size for out of sample inverse transform Returns ------- @@ -1178,34 +1180,48 @@ class BaseTransport(BaseEstimator): transp_Xs = 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)] - # get the nearest neighbor in the source domain - D0 = dist(Xs, self.Xs) - idx = np.argmin(D0, axis=1) + transp_Xs = [] + for bi in batch_ind: - # transport the source samples - transp = self.coupling_ / np.sum(self.coupling_, 1)[:, None] - transp[~ np.isfinite(transp)] = 0 - transp_Xs_ = np.dot(transp, self.Xt) + # get the nearest neighbor in the source domain + D0 = dist(Xs[bi], self.Xs) + idx = np.argmin(D0, axis=1) + + # transport the source samples + transp = self.coupling_ / np.sum(self.coupling_, 1)[:, None] + transp[~ np.isfinite(transp)] = 0 + transp_Xs_ = np.dot(transp, self.Xt) - # define the transported points - transp_Xs = transp_Xs_[idx, :] + Xs - self.Xs[idx, :] + # define the transported points + transp_Xs_ = transp_Xs_[idx, :] + Xs[bi] - self.Xs[idx, :] + + transp_Xs.append(transp_Xs_) + + transp_Xs = np.concatenate(transp_Xs, axis=0) return transp_Xs - def inverse_transform(self, Xs=None, ys=None, Xt=None, yt=None): + def inverse_transform(self, Xs=None, ys=None, Xt=None, yt=None, + batch_size=128): """Transports target samples Xt onto target samples Xs Parameters ---------- Xs : array-like, shape (n_source_samples, n_features) The training input samples. - ys : array-like, shape = (n_source_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_labeled_target_samples,) + yt : array-like, shape (n_labeled_target_samples,) The class labels + batch_size : int, optional (default=128) + The batch size for out of sample inverse transform Returns ------- @@ -1224,17 +1240,28 @@ class BaseTransport(BaseEstimator): transp_Xt = np.dot(transp_, self.Xs) else: # perform out of sample mapping + indices = np.arange(Xt.shape[0]) + batch_ind = [ + indices[i:i + batch_size] + for i in range(0, len(indices), batch_size)] - D0 = dist(Xt, self.Xt) - idx = np.argmin(D0, axis=1) + transp_Xt = [] + for bi in batch_ind: - # transport the target samples - transp_ = self.coupling_.T / np.sum(self.coupling_, 0)[:, None] - transp_[~ np.isfinite(transp_)] = 0 - transp_Xt_ = np.dot(transp_, self.Xs) + D0 = dist(Xt[bi], self.Xt) + idx = np.argmin(D0, axis=1) + + # transport the target samples + transp_ = self.coupling_.T / np.sum(self.coupling_, 0)[:, None] + transp_[~ np.isfinite(transp_)] = 0 + transp_Xt_ = np.dot(transp_, self.Xs) + + # define the transported points + transp_Xt_ = transp_Xt_[idx, :] + Xt[bi] - self.Xt[idx, :] - # define the transported points - transp_Xt = transp_Xt_[idx, :] + Xt - self.Xt[idx, :] + transp_Xt.append(transp_Xt_) + + transp_Xt = np.concatenate(transp_Xt, axis=0) return transp_Xt @@ -1306,11 +1333,11 @@ class SinkhornTransport(BaseTransport): ---------- Xs : array-like, shape (n_source_samples, n_features) The training input samples. - ys : array-like, shape = (n_source_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_labeled_target_samples,) + yt : array-like, shape (n_labeled_target_samples,) The class labels Returns @@ -1381,11 +1408,11 @@ class EMDTransport(BaseTransport): ---------- Xs : array-like, shape (n_source_samples, n_features) The training input samples. - ys : array-like, shape = (n_source_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_labeled_target_samples,) + yt : array-like, shape (n_labeled_target_samples,) The class labels Returns @@ -1480,11 +1507,11 @@ class SinkhornLpl1Transport(BaseTransport): ---------- Xs : array-like, shape (n_source_samples, n_features) The training input samples. - ys : array-like, shape = (n_source_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_labeled_target_samples,) + yt : array-like, shape (n_labeled_target_samples,) The class labels Returns @@ -1581,11 +1608,11 @@ class SinkhornL1l2Transport(BaseTransport): ---------- Xs : array-like, shape (n_source_samples, n_features) The training input samples. - ys : array-like, shape = (n_source_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_labeled_target_samples,) + yt : array-like, shape (n_labeled_target_samples,) The class labels Returns @@ -1675,11 +1702,11 @@ class MappingTransport(BaseEstimator): ---------- Xs : array-like, shape (n_source_samples, n_features) The training input samples. - ys : array-like, shape = (n_source_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_labeled_target_samples,) + yt : array-like, shape (n_labeled_target_samples,) The class labels Returns diff --git a/test/test_da.py b/test/test_da.py index 93f7e83..196f4c4 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -28,14 +28,14 @@ def test_sinkhorn_lpl1_transport_class(): clf.fit(Xs=Xs, ys=ys, Xt=Xt) # test dimensions of coupling - assert_equal(clf.Cost.shape, ((Xs.shape[0], Xt.shape[0]))) - assert_equal(clf.Coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(clf.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(clf.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) # test margin constraints mu_s = unif(ns) mu_t = unif(nt) - assert_allclose(np.sum(clf.Coupling_, axis=0), mu_t, rtol=1e-3, atol=1e-3) - assert_allclose(np.sum(clf.Coupling_, axis=1), mu_s, rtol=1e-3, atol=1e-3) + assert_allclose(np.sum(clf.coupling_, axis=0), mu_t, rtol=1e-3, atol=1e-3) + assert_allclose(np.sum(clf.coupling_, axis=1), mu_s, rtol=1e-3, atol=1e-3) # test transform transp_Xs = clf.transform(Xs=Xs) @@ -64,13 +64,13 @@ def test_sinkhorn_lpl1_transport_class(): # test semi supervised mode clf = ot.da.SinkhornLpl1Transport() clf.fit(Xs=Xs, ys=ys, Xt=Xt) - n_unsup = np.sum(clf.Cost) + n_unsup = np.sum(clf.cost_) # test semi supervised mode clf = ot.da.SinkhornLpl1Transport() clf.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) - assert_equal(clf.Cost.shape, ((Xs.shape[0], Xt.shape[0]))) - n_semisup = np.sum(clf.Cost) + assert_equal(clf.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) + n_semisup = np.sum(clf.cost_) assert n_unsup != n_semisup, "semisupervised mode not working" @@ -91,14 +91,14 @@ def test_sinkhorn_l1l2_transport_class(): clf.fit(Xs=Xs, ys=ys, Xt=Xt) # test dimensions of coupling - assert_equal(clf.Cost.shape, ((Xs.shape[0], Xt.shape[0]))) - assert_equal(clf.Coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(clf.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(clf.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) # test margin constraints mu_s = unif(ns) mu_t = unif(nt) - assert_allclose(np.sum(clf.Coupling_, axis=0), mu_t, rtol=1e-3, atol=1e-3) - assert_allclose(np.sum(clf.Coupling_, axis=1), mu_s, rtol=1e-3, atol=1e-3) + assert_allclose(np.sum(clf.coupling_, axis=0), mu_t, rtol=1e-3, atol=1e-3) + assert_allclose(np.sum(clf.coupling_, axis=1), mu_s, rtol=1e-3, atol=1e-3) # test transform transp_Xs = clf.transform(Xs=Xs) @@ -127,13 +127,13 @@ def test_sinkhorn_l1l2_transport_class(): # test semi supervised mode clf = ot.da.SinkhornL1l2Transport() clf.fit(Xs=Xs, ys=ys, Xt=Xt) - n_unsup = np.sum(clf.Cost) + n_unsup = np.sum(clf.cost_) # test semi supervised mode clf = ot.da.SinkhornL1l2Transport() clf.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) - assert_equal(clf.Cost.shape, ((Xs.shape[0], Xt.shape[0]))) - n_semisup = np.sum(clf.Cost) + assert_equal(clf.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) + n_semisup = np.sum(clf.cost_) assert n_unsup != n_semisup, "semisupervised mode not working" @@ -154,14 +154,14 @@ def test_sinkhorn_transport_class(): clf.fit(Xs=Xs, Xt=Xt) # test dimensions of coupling - assert_equal(clf.Cost.shape, ((Xs.shape[0], Xt.shape[0]))) - assert_equal(clf.Coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(clf.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(clf.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) # test margin constraints mu_s = unif(ns) mu_t = unif(nt) - assert_allclose(np.sum(clf.Coupling_, axis=0), mu_t, rtol=1e-3, atol=1e-3) - assert_allclose(np.sum(clf.Coupling_, axis=1), mu_s, rtol=1e-3, atol=1e-3) + assert_allclose(np.sum(clf.coupling_, axis=0), mu_t, rtol=1e-3, atol=1e-3) + assert_allclose(np.sum(clf.coupling_, axis=1), mu_s, rtol=1e-3, atol=1e-3) # test transform transp_Xs = clf.transform(Xs=Xs) @@ -190,13 +190,13 @@ def test_sinkhorn_transport_class(): # test semi supervised mode clf = ot.da.SinkhornTransport() clf.fit(Xs=Xs, Xt=Xt) - n_unsup = np.sum(clf.Cost) + n_unsup = np.sum(clf.cost_) # test semi supervised mode clf = ot.da.SinkhornTransport() clf.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) - assert_equal(clf.Cost.shape, ((Xs.shape[0], Xt.shape[0]))) - n_semisup = np.sum(clf.Cost) + assert_equal(clf.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) + n_semisup = np.sum(clf.cost_) assert n_unsup != n_semisup, "semisupervised mode not working" @@ -217,14 +217,14 @@ def test_emd_transport_class(): clf.fit(Xs=Xs, Xt=Xt) # test dimensions of coupling - assert_equal(clf.Cost.shape, ((Xs.shape[0], Xt.shape[0]))) - assert_equal(clf.Coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(clf.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(clf.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) # test margin constraints mu_s = unif(ns) mu_t = unif(nt) - assert_allclose(np.sum(clf.Coupling_, axis=0), mu_t, rtol=1e-3, atol=1e-3) - assert_allclose(np.sum(clf.Coupling_, axis=1), mu_s, rtol=1e-3, atol=1e-3) + assert_allclose(np.sum(clf.coupling_, axis=0), mu_t, rtol=1e-3, atol=1e-3) + assert_allclose(np.sum(clf.coupling_, axis=1), mu_s, rtol=1e-3, atol=1e-3) # test transform transp_Xs = clf.transform(Xs=Xs) @@ -253,13 +253,13 @@ def test_emd_transport_class(): # test semi supervised mode clf = ot.da.EMDTransport() clf.fit(Xs=Xs, Xt=Xt) - n_unsup = np.sum(clf.Cost) + n_unsup = np.sum(clf.cost_) # test semi supervised mode clf = ot.da.EMDTransport() clf.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) - assert_equal(clf.Cost.shape, ((Xs.shape[0], Xt.shape[0]))) - n_semisup = np.sum(clf.Cost) + assert_equal(clf.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) + n_semisup = np.sum(clf.cost_) assert n_unsup != n_semisup, "semisupervised mode not working" @@ -326,9 +326,9 @@ def test_otda(): da_emd.predict(xs) # interpolation of source samples -if __name__ == "__main__": +# if __name__ == "__main__": - test_sinkhorn_transport_class() - test_emd_transport_class() - test_sinkhorn_l1l2_transport_class() - test_sinkhorn_lpl1_transport_class() +# test_sinkhorn_transport_class() +# test_emd_transport_class() +# test_sinkhorn_l1l2_transport_class() +# test_sinkhorn_lpl1_transport_class() -- cgit v1.2.3 From c8ae5843ae64dbf841deb3ad8c10024a94a93eec Mon Sep 17 00:00:00 2001 From: Slasnista Date: Wed, 23 Aug 2017 14:11:13 +0200 Subject: test functions for MappingTransport Class --- ot/da.py | 18 ++++++--- test/test_da.py | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 125 insertions(+), 10 deletions(-) (limited to 'test') diff --git a/ot/da.py b/ot/da.py index 0c83ae6..3ccb1b3 100644 --- a/ot/da.py +++ b/ot/da.py @@ -1665,8 +1665,14 @@ class MappingTransport(BaseEstimator): Attributes ---------- - coupling_ : the optimal coupling - mapping_ : the mapping associated + coupling_ : array-like, shape (n_source_samples, n_features) + The optimal coupling + mapping_ : array-like, shape (n_features (+ 1), n_features) + (if bias) for kernel == linear + The associated mapping + + array-like, shape (n_source_samples (+ 1), n_features) + (if bias) for kernel == gaussian References ---------- @@ -1679,20 +1685,22 @@ class MappingTransport(BaseEstimator): def __init__(self, mu=1, eta=0.001, bias=False, metric="sqeuclidean", kernel="linear", sigma=1, max_iter=100, tol=1e-5, - max_inner_iter=10, inner_tol=1e-6, log=False, verbose=False): + max_inner_iter=10, inner_tol=1e-6, log=False, verbose=False, + verbose2=False): self.metric = metric self.mu = mu self.eta = eta self.bias = bias self.kernel = kernel - self.sigma + self.sigma = sigma 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.verbose2 = verbose2 def fit(self, Xs=None, ys=None, Xt=None, yt=None): """Builds an optimal coupling and estimates the associated mapping @@ -1712,7 +1720,7 @@ class MappingTransport(BaseEstimator): Returns ------- self : object - Returns self. + Returns self """ self.Xs = Xs diff --git a/test/test_da.py b/test/test_da.py index 196f4c4..162f681 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -264,6 +264,112 @@ def test_emd_transport_class(): assert n_unsup != n_semisup, "semisupervised mode not working" +def test_mapping_transport_class(): + """test_mapping_transport + """ + + ns = 150 + nt = 200 + + Xs, ys = get_data_classif('3gauss', ns) + Xt, yt = get_data_classif('3gauss2', nt) + Xs_new, _ = get_data_classif('3gauss', ns + 1) + + ########################################################################## + # kernel == linear mapping tests + ########################################################################## + + # check computation and dimensions if bias == False + clf = ot.da.MappingTransport(kernel="linear", bias=False) + clf.fit(Xs=Xs, Xt=Xt) + + assert_equal(clf.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(clf.mapping_.shape, ((Xs.shape[1], Xt.shape[1]))) + + # test margin constraints + mu_s = unif(ns) + mu_t = unif(nt) + assert_allclose(np.sum(clf.coupling_, axis=0), mu_t, rtol=1e-3, atol=1e-3) + assert_allclose(np.sum(clf.coupling_, axis=1), mu_s, rtol=1e-3, atol=1e-3) + + # test transform + transp_Xs = clf.transform(Xs=Xs) + assert_equal(transp_Xs.shape, Xs.shape) + + transp_Xs_new = clf.transform(Xs_new) + + # check that the oos method is working + assert_equal(transp_Xs_new.shape, Xs_new.shape) + + # check computation and dimensions if bias == True + clf = ot.da.MappingTransport(kernel="linear", bias=True) + clf.fit(Xs=Xs, Xt=Xt) + assert_equal(clf.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(clf.mapping_.shape, ((Xs.shape[1] + 1, Xt.shape[1]))) + + # test margin constraints + mu_s = unif(ns) + mu_t = unif(nt) + assert_allclose(np.sum(clf.coupling_, axis=0), mu_t, rtol=1e-3, atol=1e-3) + assert_allclose(np.sum(clf.coupling_, axis=1), mu_s, rtol=1e-3, atol=1e-3) + + # test transform + transp_Xs = clf.transform(Xs=Xs) + assert_equal(transp_Xs.shape, Xs.shape) + + transp_Xs_new = clf.transform(Xs_new) + + # check that the oos method is working + assert_equal(transp_Xs_new.shape, Xs_new.shape) + + ########################################################################## + # kernel == gaussian mapping tests + ########################################################################## + + # check computation and dimensions if bias == False + clf = ot.da.MappingTransport(kernel="gaussian", bias=False) + clf.fit(Xs=Xs, Xt=Xt) + + assert_equal(clf.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(clf.mapping_.shape, ((Xs.shape[0], Xt.shape[1]))) + + # test margin constraints + mu_s = unif(ns) + mu_t = unif(nt) + assert_allclose(np.sum(clf.coupling_, axis=0), mu_t, rtol=1e-3, atol=1e-3) + assert_allclose(np.sum(clf.coupling_, axis=1), mu_s, rtol=1e-3, atol=1e-3) + + # test transform + transp_Xs = clf.transform(Xs=Xs) + assert_equal(transp_Xs.shape, Xs.shape) + + transp_Xs_new = clf.transform(Xs_new) + + # check that the oos method is working + assert_equal(transp_Xs_new.shape, Xs_new.shape) + + # check computation and dimensions if bias == True + clf = ot.da.MappingTransport(kernel="gaussian", bias=True) + clf.fit(Xs=Xs, Xt=Xt) + assert_equal(clf.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(clf.mapping_.shape, ((Xs.shape[0] + 1, Xt.shape[1]))) + + # test margin constraints + mu_s = unif(ns) + mu_t = unif(nt) + assert_allclose(np.sum(clf.coupling_, axis=0), mu_t, rtol=1e-3, atol=1e-3) + assert_allclose(np.sum(clf.coupling_, axis=1), mu_s, rtol=1e-3, atol=1e-3) + + # test transform + transp_Xs = clf.transform(Xs=Xs) + assert_equal(transp_Xs.shape, Xs.shape) + + transp_Xs_new = clf.transform(Xs_new) + + # check that the oos method is working + assert_equal(transp_Xs_new.shape, Xs_new.shape) + + def test_otda(): n_samples = 150 # nb samples @@ -326,9 +432,10 @@ def test_otda(): da_emd.predict(xs) # interpolation of source samples -# if __name__ == "__main__": +if __name__ == "__main__": -# test_sinkhorn_transport_class() -# test_emd_transport_class() -# test_sinkhorn_l1l2_transport_class() -# test_sinkhorn_lpl1_transport_class() + # test_sinkhorn_transport_class() + # test_emd_transport_class() + # test_sinkhorn_l1l2_transport_class() + # test_sinkhorn_lpl1_transport_class() + test_mapping_transport_class() -- cgit v1.2.3 From fc58f39fc730a9e1bb2215ef063e37c50f0ebc1f Mon Sep 17 00:00:00 2001 From: Slasnista Date: Wed, 23 Aug 2017 15:09:08 +0200 Subject: added deprecation warning on old classes --- ot/da.py | 22 ++++++++++-- ot/deprecation.py | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ test/test_da.py | 5 +-- 3 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 ot/deprecation.py (limited to 'test') diff --git a/ot/da.py b/ot/da.py index 3ccb1b3..8fa1895 100644 --- a/ot/da.py +++ b/ot/da.py @@ -10,12 +10,14 @@ Domain adaptation with optimal transport # License: MIT License import numpy as np +import warnings + from .bregman import sinkhorn from .lp import emd from .utils import unif, dist, kernel from .optim import cg from .optim import gcg -import warnings +from .deprecation import deprecated def sinkhorn_lpl1_mm(a, labels_a, b, M, reg, eta=0.1, numItermax=10, @@ -632,6 +634,9 @@ def joint_OT_mapping_kernel(xs, xt, mu=1, eta=0.001, kerneltype='gaussian', return G, L +@deprecated("The class OTDA is deprecated in 0.3.1 and will be " + "removed in 0.5" + "\n\tfor standard transport use class EMDTransport instead.") class OTDA(object): """Class for domain adaptation with optimal transport as proposed in [5] @@ -758,10 +763,15 @@ class OTDA(object): self.M = np.log(1 + np.log(1 + self.M)) +@deprecated("The class OTDA_sinkhorn is deprecated in 0.3.1 and will be" + " removed in 0.5 \nUse class SinkhornTransport instead.") class OTDA_sinkhorn(OTDA): """Class for domain adaptation with optimal transport with entropic - regularization""" + regularization + + + """ def fit(self, xs, xt, reg=1, ws=None, wt=None, norm=None, **kwargs): """Fit regularized domain adaptation between samples is xs and xt @@ -783,6 +793,8 @@ class OTDA_sinkhorn(OTDA): self.computed = True +@deprecated("The class OTDA_lpl1 is deprecated in 0.3.1 and will be" + " removed in 0.5 \nUse class SinkhornLpl1Transport instead.") class OTDA_lpl1(OTDA): """Class for domain adaptation with optimal transport with entropic and @@ -810,6 +822,8 @@ class OTDA_lpl1(OTDA): self.computed = True +@deprecated("The class OTDA_l1L2 is deprecated in 0.3.1 and will be" + " removed in 0.5 \nUse class SinkhornL1l2Transport instead.") class OTDA_l1l2(OTDA): """Class for domain adaptation with optimal transport with entropic @@ -837,6 +851,8 @@ class OTDA_l1l2(OTDA): self.computed = True +@deprecated("The class OTDA_mapping_linear is deprecated in 0.3.1 and will be" + " removed in 0.5 \nUse class MappingTransport instead.") class OTDA_mapping_linear(OTDA): """Class for optimal transport with joint linear mapping estimation as in @@ -882,6 +898,8 @@ class OTDA_mapping_linear(OTDA): return None +@deprecated("The class OTDA_mapping_kernel is deprecated in 0.3.1 and will be" + " removed in 0.5 \nUse class MappingTransport instead.") class OTDA_mapping_kernel(OTDA_mapping_linear): """Class for optimal transport with joint nonlinear mapping diff --git a/ot/deprecation.py b/ot/deprecation.py new file mode 100644 index 0000000..2b16427 --- /dev/null +++ b/ot/deprecation.py @@ -0,0 +1,103 @@ +""" + deprecated class from scikit-learn package + https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/utils/deprecation.py +""" + +import sys +import warnings + +__all__ = ["deprecated", ] + + +class deprecated(object): + """Decorator to mark a function or class as deprecated. + Issue a warning when the function is called/the class is instantiated and + adds a warning to the docstring. + The optional extra argument will be appended to the deprecation message + and the docstring. Note: to use this with the default value for extra, put + in an empty of parentheses: + >>> from ot.deprecation import deprecated + >>> @deprecated() + ... def some_function(): pass + + Parameters + ---------- + extra : string + to be added to the deprecation messages + """ + + # Adapted from http://wiki.python.org/moin/PythonDecoratorLibrary, + # but with many changes. + + def __init__(self, extra=''): + self.extra = extra + + def __call__(self, obj): + """Call method + Parameters + ---------- + obj : object + """ + if isinstance(obj, type): + return self._decorate_class(obj) + else: + return self._decorate_fun(obj) + + def _decorate_class(self, cls): + msg = "Class %s is deprecated" % cls.__name__ + if self.extra: + msg += "; %s" % self.extra + + # FIXME: we should probably reset __new__ for full generality + init = cls.__init__ + + def wrapped(*args, **kwargs): + warnings.warn(msg, category=DeprecationWarning) + return init(*args, **kwargs) + + cls.__init__ = wrapped + + wrapped.__name__ = '__init__' + wrapped.__doc__ = self._update_doc(init.__doc__) + wrapped.deprecated_original = init + + return cls + + def _decorate_fun(self, fun): + """Decorate function fun""" + + msg = "Function %s is deprecated" % fun.__name__ + if self.extra: + msg += "; %s" % self.extra + + def wrapped(*args, **kwargs): + warnings.warn(msg, category=DeprecationWarning) + return fun(*args, **kwargs) + + wrapped.__name__ = fun.__name__ + wrapped.__dict__ = fun.__dict__ + wrapped.__doc__ = self._update_doc(fun.__doc__) + + return wrapped + + def _update_doc(self, olddoc): + newdoc = "DEPRECATED" + if self.extra: + newdoc = "%s: %s" % (newdoc, self.extra) + if olddoc: + newdoc = "%s\n\n%s" % (newdoc, olddoc) + return newdoc + + +def _is_deprecated(func): + """Helper to check if func is wraped by our deprecated decorator""" + if sys.version_info < (3, 5): + raise NotImplementedError("This is only available for python3.5 " + "or above") + closures = getattr(func, '__closure__', []) + if closures is None: + closures = [] + is_deprecated = ('deprecated' in ''.join([c.cell_contents + for c in closures + if isinstance(c.cell_contents, str)])) + return is_deprecated diff --git a/test/test_da.py b/test/test_da.py index 162f681..9578b3d 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -432,10 +432,11 @@ def test_otda(): da_emd.predict(xs) # interpolation of source samples -if __name__ == "__main__": +# if __name__ == "__main__": + # test_otda() # test_sinkhorn_transport_class() # test_emd_transport_class() # test_sinkhorn_l1l2_transport_class() # test_sinkhorn_lpl1_transport_class() - test_mapping_transport_class() + # test_mapping_transport_class() -- cgit v1.2.3 From 6167f34a721886d4b9038a8b1746a2c8c81132ce Mon Sep 17 00:00:00 2001 From: Slasnista Date: Fri, 25 Aug 2017 10:29:41 +0200 Subject: solving log issues to avoid errors and adding further tests --- ot/da.py | 57 ++++++++++++++++++++++++++++++++++++++++++--------------- test/test_da.py | 39 +++++++++++++++++++++++++++++++++------ 2 files changed, 75 insertions(+), 21 deletions(-) (limited to 'test') diff --git a/ot/da.py b/ot/da.py index 8fa1895..5a34979 100644 --- a/ot/da.py +++ b/ot/da.py @@ -1315,7 +1315,10 @@ class SinkhornTransport(BaseTransport): Attributes ---------- - coupling_ : the optimal coupling + 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 ---------- @@ -1367,11 +1370,18 @@ class SinkhornTransport(BaseTransport): super(SinkhornTransport, self).fit(Xs, ys, Xt, yt) # coupling estimation - self.coupling_ = sinkhorn( + returned_ = sinkhorn( a=self.mu_s, b=self.mu_t, M=self.cost_, reg=self.reg_e, 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 @@ -1400,7 +1410,8 @@ class EMDTransport(BaseTransport): Attributes ---------- - coupling_ : the optimal coupling + coupling_ : array-like, shape (n_source_samples, n_target_samples) + The optimal coupling References ---------- @@ -1475,15 +1486,14 @@ class SinkhornLpl1Transport(BaseTransport): The number of iteration in the inner loop verbose : int, optional (default=0) Controls the verbosity of the optimization algorithm - log : int, optional (default=0) - Controls the logs of the optimization algorithm limit_max: float, optional (defaul=np.infty) Controls the semi supervised mode. Transport between labeled source and target samples of different classes will exhibit an infinite cost Attributes ---------- - coupling_ : the optimal coupling + coupling_ : array-like, shape (n_source_samples, n_target_samples) + The optimal coupling References ---------- @@ -1500,7 +1510,7 @@ class SinkhornLpl1Transport(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, + tol=10e-9, verbose=False, metric="sqeuclidean", distribution_estimation=distribution_estimation_uniform, out_of_sample_map='ferradans', limit_max=np.infty): @@ -1511,7 +1521,6 @@ class SinkhornLpl1Transport(BaseTransport): self.max_inner_iter = max_inner_iter self.tol = tol self.verbose = verbose - self.log = log self.metric = metric self.distribution_estimation = distribution_estimation self.out_of_sample_map = out_of_sample_map @@ -1544,7 +1553,7 @@ class SinkhornLpl1Transport(BaseTransport): a=self.mu_s, labels_a=ys, b=self.mu_t, M=self.cost_, reg=self.reg_e, eta=self.reg_cl, numItermax=self.max_iter, numInnerItermax=self.max_inner_iter, stopInnerThr=self.tol, - verbose=self.verbose, log=self.log) + verbose=self.verbose) return self @@ -1584,7 +1593,10 @@ class SinkhornL1l2Transport(BaseTransport): Attributes ---------- - coupling_ : the optimal coupling + 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 ---------- @@ -1641,12 +1653,19 @@ class SinkhornL1l2Transport(BaseTransport): super(SinkhornL1l2Transport, self).fit(Xs, ys, Xt, yt) - self.coupling_ = sinkhorn_l1l2_gl( + returned_ = sinkhorn_l1l2_gl( a=self.mu_s, labels_a=ys, b=self.mu_t, M=self.cost_, reg=self.reg_e, eta=self.reg_cl, numItermax=self.max_iter, numInnerItermax=self.max_inner_iter, stopInnerThr=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 @@ -1683,14 +1702,15 @@ class MappingTransport(BaseEstimator): Attributes ---------- - coupling_ : array-like, shape (n_source_samples, n_features) + coupling_ : array-like, shape (n_source_samples, n_target_samples) The optimal coupling mapping_ : array-like, shape (n_features (+ 1), n_features) (if bias) for kernel == linear The associated mapping - array-like, shape (n_source_samples (+ 1), n_features) (if bias) for kernel == gaussian + log_ : dictionary + The dictionary of log, empty dic if parameter log is not True References ---------- @@ -1745,19 +1765,26 @@ class MappingTransport(BaseEstimator): self.Xt = Xt if self.kernel == "linear": - self.coupling_, self.mapping_ = joint_OT_mapping_linear( + returned_ = joint_OT_mapping_linear( Xs, Xt, mu=self.mu, eta=self.eta, bias=self.bias, verbose=self.verbose, verbose2=self.verbose2, numItermax=self.max_iter, numInnerItermax=self.max_inner_iter, stopThr=self.tol, stopInnerThr=self.inner_tol, log=self.log) elif self.kernel == "gaussian": - self.coupling_, self.mapping_ = joint_OT_mapping_kernel( + returned_ = joint_OT_mapping_kernel( Xs, Xt, mu=self.mu, eta=self.eta, bias=self.bias, sigma=self.sigma, verbose=self.verbose, verbose2=self.verbose, numItermax=self.max_iter, numInnerItermax=self.max_inner_iter, stopInnerThr=self.inner_tol, stopThr=self.tol, log=self.log) + # deal with the value of log + if self.log: + self.coupling_, self.mapping_, self.log_ = returned_ + else: + self.coupling_, self.mapping_ = returned_ + self.log_ = dict() + return self def transform(self, Xs): diff --git a/test/test_da.py b/test/test_da.py index 9578b3d..104a798 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -26,6 +26,8 @@ def test_sinkhorn_lpl1_transport_class(): # test its computed clf.fit(Xs=Xs, ys=ys, Xt=Xt) + assert hasattr(clf, "cost_") + assert hasattr(clf, "coupling_") # test dimensions of coupling assert_equal(clf.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) @@ -89,6 +91,9 @@ def test_sinkhorn_l1l2_transport_class(): # test its computed clf.fit(Xs=Xs, ys=ys, Xt=Xt) + assert hasattr(clf, "cost_") + assert hasattr(clf, "coupling_") + assert hasattr(clf, "log_") # test dimensions of coupling assert_equal(clf.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) @@ -137,6 +142,11 @@ def test_sinkhorn_l1l2_transport_class(): assert n_unsup != n_semisup, "semisupervised mode not working" + # check everything runs well with log=True + clf = ot.da.SinkhornL1l2Transport(log=True) + clf.fit(Xs=Xs, ys=ys, Xt=Xt) + assert len(clf.log_.keys()) != 0 + def test_sinkhorn_transport_class(): """test_sinkhorn_transport @@ -152,6 +162,9 @@ def test_sinkhorn_transport_class(): # test its computed clf.fit(Xs=Xs, Xt=Xt) + assert hasattr(clf, "cost_") + assert hasattr(clf, "coupling_") + assert hasattr(clf, "log_") # test dimensions of coupling assert_equal(clf.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) @@ -200,6 +213,11 @@ def test_sinkhorn_transport_class(): assert n_unsup != n_semisup, "semisupervised mode not working" + # check everything runs well with log=True + clf = ot.da.SinkhornTransport(log=True) + clf.fit(Xs=Xs, ys=ys, Xt=Xt) + assert len(clf.log_.keys()) != 0 + def test_emd_transport_class(): """test_sinkhorn_transport @@ -215,6 +233,8 @@ def test_emd_transport_class(): # test its computed clf.fit(Xs=Xs, Xt=Xt) + assert hasattr(clf, "cost_") + assert hasattr(clf, "coupling_") # test dimensions of coupling assert_equal(clf.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) @@ -282,6 +302,9 @@ def test_mapping_transport_class(): # check computation and dimensions if bias == False clf = ot.da.MappingTransport(kernel="linear", bias=False) clf.fit(Xs=Xs, Xt=Xt) + assert hasattr(clf, "coupling_") + assert hasattr(clf, "mapping_") + assert hasattr(clf, "log_") assert_equal(clf.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) assert_equal(clf.mapping_.shape, ((Xs.shape[1], Xt.shape[1]))) @@ -369,6 +392,11 @@ def test_mapping_transport_class(): # check that the oos method is working assert_equal(transp_Xs_new.shape, Xs_new.shape) + # check everything runs well with log=True + clf = ot.da.MappingTransport(kernel="gaussian", log=True) + clf.fit(Xs=Xs, Xt=Xt) + assert len(clf.log_.keys()) != 0 + def test_otda(): @@ -434,9 +462,8 @@ def test_otda(): # if __name__ == "__main__": - # test_otda() - # test_sinkhorn_transport_class() - # test_emd_transport_class() - # test_sinkhorn_l1l2_transport_class() - # test_sinkhorn_lpl1_transport_class() - # test_mapping_transport_class() +# test_sinkhorn_transport_class() +# test_emd_transport_class() +# test_sinkhorn_l1l2_transport_class() +# test_sinkhorn_lpl1_transport_class() +# test_mapping_transport_class() -- cgit v1.2.3 From 8c525174bb664cafa98dfff73dce9d42d7818f71 Mon Sep 17 00:00:00 2001 From: Nicolas Courty Date: Thu, 31 Aug 2017 16:44:18 +0200 Subject: Minor corrections suggested by @agramfort + new barycenter example + test function --- README.md | 2 +- data/carre.png | Bin 0 -> 168 bytes data/coeur.png | Bin 0 -> 225 bytes data/rond.png | Bin 0 -> 230 bytes data/triangle.png | Bin 0 -> 254 bytes examples/plot_gromov.py | 14 +-- examples/plot_gromov_barycenter.py | 240 +++++++++++++++++++++++++++++++++++++ ot/gromov.py | 36 +++--- test/test_gromov.py | 38 ++++++ 9 files changed, 302 insertions(+), 28 deletions(-) create mode 100755 data/carre.png create mode 100755 data/coeur.png create mode 100755 data/rond.png create mode 100755 data/triangle.png create mode 100755 examples/plot_gromov_barycenter.py create mode 100644 test/test_gromov.py (limited to 'test') diff --git a/README.md b/README.md index 257244b..22b20a4 100644 --- a/README.md +++ b/README.md @@ -185,4 +185,4 @@ You can also post bug reports and feature requests in Github issues. Make sure t [11] Flamary, R., Cuturi, M., Courty, N., & Rakotomamonjy, A. (2016). [Wasserstein Discriminant Analysis](https://arxiv.org/pdf/1608.08063.pdf). arXiv preprint arXiv:1608.08063. -[12] Peyré, Gabriel, Marco Cuturi, and Justin Solomon, [Gromov-Wasserstein averaging of kernel and distance matrices](http://proceedings.mlr.press/v48/peyre16.html) International Conference on Machine Learning (ICML). 2016. +[12] Gabriel Peyré, Marco Cuturi, and Justin Solomon, [Gromov-Wasserstein averaging of kernel and distance matrices](http://proceedings.mlr.press/v48/peyre16.html) International Conference on Machine Learning (ICML). 2016. diff --git a/data/carre.png b/data/carre.png new file mode 100755 index 0000000..45ff0ef Binary files /dev/null and b/data/carre.png differ diff --git a/data/coeur.png b/data/coeur.png new file mode 100755 index 0000000..3f511a6 Binary files /dev/null and b/data/coeur.png differ diff --git a/data/rond.png b/data/rond.png new file mode 100755 index 0000000..1c1a068 Binary files /dev/null and b/data/rond.png differ diff --git a/data/triangle.png b/data/triangle.png new file mode 100755 index 0000000..ca36d09 Binary files /dev/null and b/data/triangle.png differ diff --git a/examples/plot_gromov.py b/examples/plot_gromov.py index a33fde1..9bbdbde 100644 --- a/examples/plot_gromov.py +++ b/examples/plot_gromov.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- """ -==================== +========================== Gromov-Wasserstein example -==================== +========================== This example is designed to show how to use the Gromov-Wassertsein distance computation in POT. """ @@ -14,14 +14,14 @@ computation in POT. import scipy as sp import numpy as np +import matplotlib.pylab as pl import ot -import matplotlib.pylab as pl """ Sample two Gaussian distributions (2D and 3D) -==================== +============================================= The Gromov-Wasserstein distance allows to compute distances with samples that do not belong to the same metric space. For demonstration purpose, we sample two Gaussian distributions in 2- and 3-dimensional spaces. """ @@ -42,7 +42,7 @@ xt = np.random.randn(n, 3).dot(P) + mu_t """ Plotting the distributions -==================== +========================== """ fig = pl.figure() ax1 = fig.add_subplot(121) @@ -54,7 +54,7 @@ pl.show() """ Compute distance kernels, normalize them and then display -==================== +========================================================= """ C1 = sp.spatial.distance.cdist(xs, xs) @@ -72,7 +72,7 @@ pl.show() """ Compute Gromov-Wasserstein plans and distance -==================== +============================================= """ p = ot.unif(n) diff --git a/examples/plot_gromov_barycenter.py b/examples/plot_gromov_barycenter.py new file mode 100755 index 0000000..6a72b3b --- /dev/null +++ b/examples/plot_gromov_barycenter.py @@ -0,0 +1,240 @@ +# -*- coding: utf-8 -*- +""" +===================================== +Gromov-Wasserstein Barycenter example +===================================== +This example is designed to show how to use the Gromov-Wassertsein distance +computation in POT. +""" + +# Author: Erwan Vautier +# Nicolas Courty +# +# License: MIT License + + +import numpy as np +import scipy as sp + +import scipy.ndimage as spi +import matplotlib.pylab as pl +from sklearn import manifold +from sklearn.decomposition import PCA + +import ot + +""" + +Smacof MDS +========== +This function allows to find an embedding of points given a dissimilarity matrix +that will be given by the output of the algorithm +""" + + +def smacof_mds(C, dim, maxIter=3000, eps=1e-9): + """ + Returns an interpolated point cloud following the dissimilarity matrix C using SMACOF + multidimensional scaling (MDS) in specific dimensionned target space + + Parameters + ---------- + C : np.ndarray(ns,ns) + dissimilarity matrix + dim : Integer + dimension of the targeted space + maxIter : Maximum number of iterations of the SMACOF algorithm for a single run + + eps : relative tolerance w.r.t stress to declare converge + + + Returns + ------- + npos : R**dim ndarray + Embedded coordinates of the interpolated point cloud (defined with one isometry) + + + """ + + seed = np.random.RandomState(seed=3) + + mds = manifold.MDS( + dim, + max_iter=3000, + eps=1e-9, + dissimilarity='precomputed', + n_init=1) + pos = mds.fit(C).embedding_ + + nmds = manifold.MDS( + 2, + max_iter=3000, + eps=1e-9, + dissimilarity="precomputed", + random_state=seed, + n_init=1) + npos = nmds.fit_transform(C, init=pos) + + return npos + + +""" +Data preparation +================ +The four distributions are constructed from 4 simple images +""" + + +def im2mat(I): + """Converts and image to matrix (one pixel per line)""" + return I.reshape((I.shape[0] * I.shape[1], I.shape[2])) + + +carre = spi.imread('../data/carre.png').astype(np.float64) / 256 +rond = spi.imread('../data/rond.png').astype(np.float64) / 256 +triangle = spi.imread('../data/triangle.png').astype(np.float64) / 256 +fleche = spi.imread('../data/coeur.png').astype(np.float64) / 256 + +shapes = [carre, rond, triangle, fleche] + +S = 4 +xs = [[] for i in range(S)] + + +for nb in range(4): + for i in range(8): + for j in range(8): + if shapes[nb][i, j] < 0.95: + xs[nb].append([j, 8 - i]) + +xs = np.array([np.array(xs[0]), np.array(xs[1]), + np.array(xs[2]), np.array(xs[3])]) + + +""" +Barycenter computation +====================== +The four distributions are constructed from 4 simple images +""" +ns = [len(xs[s]) for s in range(S)] +N = 30 + +"""Compute all distances matrices for the four shapes""" +Cs = [sp.spatial.distance.cdist(xs[s], xs[s]) for s in range(S)] +Cs = [cs / cs.max() for cs in Cs] + +ps = [ot.unif(ns[s]) for s in range(S)] +p = ot.unif(N) + + +lambdast = [[float(i) / 3, float(3 - i) / 3] for i in [1, 2]] + +Ct01 = [0 for i in range(2)] +for i in range(2): + Ct01[i] = ot.gromov.gromov_barycenters(N, [Cs[0], Cs[1]], [ + ps[0], ps[1]], p, lambdast[i], 'square_loss', 5e-4, numItermax=100, stopThr=1e-3) + +Ct02 = [0 for i in range(2)] +for i in range(2): + Ct02[i] = ot.gromov.gromov_barycenters(N, [Cs[0], Cs[2]], [ + ps[0], ps[2]], p, lambdast[i], 'square_loss', 5e-4, numItermax=100, stopThr=1e-3) + +Ct13 = [0 for i in range(2)] +for i in range(2): + Ct13[i] = ot.gromov.gromov_barycenters(N, [Cs[1], Cs[3]], [ + ps[1], ps[3]], p, lambdast[i], 'square_loss', 5e-4, numItermax=100, stopThr=1e-3) + +Ct23 = [0 for i in range(2)] +for i in range(2): + Ct23[i] = ot.gromov.gromov_barycenters(N, [Cs[2], Cs[3]], [ + ps[2], ps[3]], p, lambdast[i], 'square_loss', 5e-4, numItermax=100, stopThr=1e-3) + +""" +Visualization +============= +""" + +"""The PCA helps in getting consistency between the rotations""" + +clf = PCA(n_components=2) +npos = [0, 0, 0, 0] +npos = [smacof_mds(Cs[s], 2) for s in range(S)] + +npost01 = [0, 0] +npost01 = [smacof_mds(Ct01[s], 2) for s in range(2)] +npost01 = [clf.fit_transform(npost01[s]) for s in range(2)] + +npost02 = [0, 0] +npost02 = [smacof_mds(Ct02[s], 2) for s in range(2)] +npost02 = [clf.fit_transform(npost02[s]) for s in range(2)] + +npost13 = [0, 0] +npost13 = [smacof_mds(Ct13[s], 2) for s in range(2)] +npost13 = [clf.fit_transform(npost13[s]) for s in range(2)] + +npost23 = [0, 0] +npost23 = [smacof_mds(Ct23[s], 2) for s in range(2)] +npost23 = [clf.fit_transform(npost23[s]) for s in range(2)] + + +fig = pl.figure(figsize=(10, 10)) + +ax1 = pl.subplot2grid((4, 4), (0, 0)) +pl.xlim((-1, 1)) +pl.ylim((-1, 1)) +ax1.scatter(npos[0][:, 0], npos[0][:, 1], color='r') + +ax2 = pl.subplot2grid((4, 4), (0, 1)) +pl.xlim((-1, 1)) +pl.ylim((-1, 1)) +ax2.scatter(npost01[1][:, 0], npost01[1][:, 1], color='b') + +ax3 = pl.subplot2grid((4, 4), (0, 2)) +pl.xlim((-1, 1)) +pl.ylim((-1, 1)) +ax3.scatter(npost01[0][:, 0], npost01[0][:, 1], color='b') + +ax4 = pl.subplot2grid((4, 4), (0, 3)) +pl.xlim((-1, 1)) +pl.ylim((-1, 1)) +ax4.scatter(npos[1][:, 0], npos[1][:, 1], color='r') + +ax5 = pl.subplot2grid((4, 4), (1, 0)) +pl.xlim((-1, 1)) +pl.ylim((-1, 1)) +ax5.scatter(npost02[1][:, 0], npost02[1][:, 1], color='b') + +ax6 = pl.subplot2grid((4, 4), (1, 3)) +pl.xlim((-1, 1)) +pl.ylim((-1, 1)) +ax6.scatter(npost13[1][:, 0], npost13[1][:, 1], color='b') + +ax7 = pl.subplot2grid((4, 4), (2, 0)) +pl.xlim((-1, 1)) +pl.ylim((-1, 1)) +ax7.scatter(npost02[0][:, 0], npost02[0][:, 1], color='b') + +ax8 = pl.subplot2grid((4, 4), (2, 3)) +pl.xlim((-1, 1)) +pl.ylim((-1, 1)) +ax8.scatter(npost13[0][:, 0], npost13[0][:, 1], color='b') + +ax9 = pl.subplot2grid((4, 4), (3, 0)) +pl.xlim((-1, 1)) +pl.ylim((-1, 1)) +ax9.scatter(npos[2][:, 0], npos[2][:, 1], color='r') + +ax10 = pl.subplot2grid((4, 4), (3, 1)) +pl.xlim((-1, 1)) +pl.ylim((-1, 1)) +ax10.scatter(npost23[1][:, 0], npost23[1][:, 1], color='b') + +ax11 = pl.subplot2grid((4, 4), (3, 2)) +pl.xlim((-1, 1)) +pl.ylim((-1, 1)) +ax11.scatter(npost23[0][:, 0], npost23[0][:, 1], color='b') + +ax12 = pl.subplot2grid((4, 4), (3, 3)) +pl.xlim((-1, 1)) +pl.ylim((-1, 1)) +ax12.scatter(npos[3][:, 0], npos[3][:, 1], color='r') diff --git a/ot/gromov.py b/ot/gromov.py index 7cf3b42..421ed3f 100644 --- a/ot/gromov.py +++ b/ot/gromov.py @@ -23,7 +23,7 @@ def square_loss(a, b): Returns the value of L(a,b)=(1/2)*|a-b|^2 """ - return (1 / 2) * (a - b)**2 + return 0.5 * (a - b)**2 def kl_loss(a, b): @@ -54,9 +54,9 @@ def tensor_square_loss(C1, C2, T): Parameters ---------- - C1 : np.ndarray(ns,ns) + C1 : ndarray, shape (ns, ns) Metric cost matrix in the source space - C2 : np.ndarray(nt,nt) + C2 : ndarray, shape (nt, nt) Metric costfr matrix in the target space T : np.ndarray(ns,nt) Coupling between source and target spaces @@ -87,7 +87,7 @@ def tensor_square_loss(C1, C2, T): return b tens = -np.dot(h1(C1), T).dot(h2(C2).T) - tens = tens - tens.min() + tens -= tens.min() return np.array(tens) @@ -112,9 +112,9 @@ def tensor_kl_loss(C1, C2, T): Parameters ---------- - C1 : np.ndarray(ns,ns) + C1 : ndarray, shape (ns, ns) Metric cost matrix in the source space - C2 : np.ndarray(nt,nt) + C2 : ndarray, shape (nt, nt) Metric costfr matrix in the target space T : np.ndarray(ns,nt) Coupling between source and target spaces @@ -149,7 +149,7 @@ def tensor_kl_loss(C1, C2, T): return np.log(b + 1e-15) tens = -np.dot(h1(C1), T).dot(h2(C2).T) - tens = tens - tens.min() + tens -= tens.min() return np.array(tens) @@ -175,9 +175,8 @@ def update_square_loss(p, lambdas, T, Cs): """ - tmpsum = np.sum([lambdas[s] * np.dot(T[s].T, Cs[s]).dot(T[s]) - for s in range(len(T))]) - ppt = np.dot(p, p.T) + tmpsum = sum([lambdas[s] * np.dot(T[s].T, Cs[s]).dot(T[s]) for s in range(len(T))]) + ppt = np.outer(p, p) return(np.divide(tmpsum, ppt)) @@ -203,9 +202,8 @@ def update_kl_loss(p, lambdas, T, Cs): """ - tmpsum = np.sum([lambdas[s] * np.dot(T[s].T, Cs[s]).dot(T[s]) - for s in range(len(T))]) - ppt = np.dot(p, p.T) + tmpsum = sum([lambdas[s] * np.dot(T[s].T, Cs[s]).dot(T[s]) for s in range(len(T))]) + ppt = np.outer(p, p) return(np.exp(np.divide(tmpsum, ppt))) @@ -239,9 +237,9 @@ def gromov_wasserstein(C1, C2, p, q, loss_fun, epsilon, numItermax=1000, stopThr Parameters ---------- - C1 : np.ndarray(ns,ns) + C1 : ndarray, shape (ns, ns) Metric cost matrix in the source space - C2 : np.ndarray(nt,nt) + C2 : ndarray, shape (nt, nt) Metric costfr matrix in the target space p : np.ndarray(ns,) distribution in the source space @@ -271,7 +269,7 @@ def gromov_wasserstein(C1, C2, p, q, loss_fun, epsilon, numItermax=1000, stopThr C1 = np.asarray(C1, dtype=np.float64) C2 = np.asarray(C2, dtype=np.float64) - T = np.dot(p, q.T) # Initialization + T = np.outer(p, q) # Initialization cpt = 0 err = 1 @@ -333,9 +331,9 @@ def gromov_wasserstein2(C1, C2, p, q, loss_fun, epsilon, numItermax=1000, stopTh Parameters ---------- - C1 : np.ndarray(ns,ns) + C1 : ndarray, shape (ns, ns) Metric cost matrix in the source space - C2 : np.ndarray(nt,nt) + C2 : ndarray, shape (nt, nt) Metric costfr matrix in the target space p : np.ndarray(ns,) distribution in the source space @@ -434,8 +432,6 @@ def gromov_barycenters(N, Cs, ps, p, lambdas, loss_fun, epsilon, numItermax=1000 Cs = [np.asarray(Cs[s], dtype=np.float64) for s in range(S)] lambdas = np.asarray(lambdas, dtype=np.float64) - T = [0 for s in range(S)] - # Initialization of C : random SPD matrix xalea = np.random.randn(N, 2) C = dist(xalea, xalea) diff --git a/test/test_gromov.py b/test/test_gromov.py new file mode 100644 index 0000000..75eeaab --- /dev/null +++ b/test/test_gromov.py @@ -0,0 +1,38 @@ +"""Tests for module gromov """ + +# Author: Erwan Vautier +# Nicolas Courty +# +# License: MIT License + +import numpy as np +import ot + + +def test_gromov(): + n = 50 # nb samples + + mu_s = np.array([0, 0]) + cov_s = np.array([[1, 0], [0, 1]]) + + xs = ot.datasets.get_2D_samples_gauss(n, mu_s, cov_s) + + xt = [xs[n - (i + 1)] for i in range(n)] + xt = np.array(xt) + + p = ot.unif(n) + q = ot.unif(n) + + C1 = ot.dist(xs, xs) + C2 = ot.dist(xt, xt) + + C1 /= C1.max() + C2 /= C2.max() + + G = ot.gromov_wasserstein(C1, C2, p, q, 'square_loss', epsilon=5e-4) + + # check constratints + np.testing.assert_allclose( + p, G.sum(1), atol=1e-04) # cf convergence gromov + np.testing.assert_allclose( + q, G.sum(0), atol=1e-04) # cf convergence gromov -- cgit v1.2.3 From ab6ed1df93cd78bb7f1a54282103d4d830e68bcb Mon Sep 17 00:00:00 2001 From: Nicolas Courty Date: Fri, 1 Sep 2017 11:20:34 +0200 Subject: docstrings and naming --- examples/plot_gromov.py | 10 +++++----- examples/plot_gromov_barycenter.py | 20 ++++++++++---------- ot/gromov.py | 18 +++++++++--------- test/test_gromov.py | 10 +++++----- 4 files changed, 29 insertions(+), 29 deletions(-) (limited to 'test') diff --git a/examples/plot_gromov.py b/examples/plot_gromov.py index 9bbdbde..92312ae 100644 --- a/examples/plot_gromov.py +++ b/examples/plot_gromov.py @@ -26,7 +26,7 @@ The Gromov-Wasserstein distance allows to compute distances with samples that do For demonstration purpose, we sample two Gaussian distributions in 2- and 3-dimensional spaces. """ -n = 30 # nb samples +n_samples = 30 # nb samples mu_s = np.array([0, 0]) cov_s = np.array([[1, 0], [0, 1]]) @@ -35,9 +35,9 @@ mu_t = np.array([4, 4, 4]) cov_t = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) -xs = ot.datasets.get_2D_samples_gauss(n, mu_s, cov_s) +xs = ot.datasets.get_2D_samples_gauss(n_samples, mu_s, cov_s) P = sp.linalg.sqrtm(cov_t) -xt = np.random.randn(n, 3).dot(P) + mu_t +xt = np.random.randn(n_samples, 3).dot(P) + mu_t """ @@ -75,8 +75,8 @@ Compute Gromov-Wasserstein plans and distance ============================================= """ -p = ot.unif(n) -q = ot.unif(n) +p = ot.unif(n_samples) +q = ot.unif(n_samples) gw = ot.gromov_wasserstein(C1, C2, p, q, 'square_loss', epsilon=5e-4) gw_dist = ot.gromov_wasserstein2(C1, C2, p, q, 'square_loss', epsilon=5e-4) diff --git a/examples/plot_gromov_barycenter.py b/examples/plot_gromov_barycenter.py index da52768..f0657e1 100755 --- a/examples/plot_gromov_barycenter.py +++ b/examples/plot_gromov_barycenter.py @@ -91,12 +91,12 @@ def im2mat(I): return I.reshape((I.shape[0] * I.shape[1], I.shape[2])) -carre = spi.imread('../data/carre.png').astype(np.float64) / 256 -rond = spi.imread('../data/rond.png').astype(np.float64) / 256 +square = spi.imread('../data/carre.png').astype(np.float64) / 256 +circle = spi.imread('../data/rond.png').astype(np.float64) / 256 triangle = spi.imread('../data/triangle.png').astype(np.float64) / 256 -fleche = spi.imread('../data/coeur.png').astype(np.float64) / 256 +arrow = spi.imread('../data/coeur.png').astype(np.float64) / 256 -shapes = [carre, rond, triangle, fleche] +shapes = [square, circle, triangle, arrow] S = 4 xs = [[] for i in range(S)] @@ -118,36 +118,36 @@ Barycenter computation The four distributions are constructed from 4 simple images """ ns = [len(xs[s]) for s in range(S)] -N = 30 +n_samples = 30 """Compute all distances matrices for the four shapes""" Cs = [sp.spatial.distance.cdist(xs[s], xs[s]) for s in range(S)] Cs = [cs / cs.max() for cs in Cs] ps = [ot.unif(ns[s]) for s in range(S)] -p = ot.unif(N) +p = ot.unif(n_samples) lambdast = [[float(i) / 3, float(3 - i) / 3] for i in [1, 2]] Ct01 = [0 for i in range(2)] for i in range(2): - Ct01[i] = ot.gromov.gromov_barycenters(N, [Cs[0], Cs[1]], [ + Ct01[i] = ot.gromov.gromov_barycenters(n_samples, [Cs[0], Cs[1]], [ ps[0], ps[1]], p, lambdast[i], 'square_loss', 5e-4, numItermax=100, stopThr=1e-3) Ct02 = [0 for i in range(2)] for i in range(2): - Ct02[i] = ot.gromov.gromov_barycenters(N, [Cs[0], Cs[2]], [ + Ct02[i] = ot.gromov.gromov_barycenters(n_samples, [Cs[0], Cs[2]], [ ps[0], ps[2]], p, lambdast[i], 'square_loss', 5e-4, numItermax=100, stopThr=1e-3) Ct13 = [0 for i in range(2)] for i in range(2): - Ct13[i] = ot.gromov.gromov_barycenters(N, [Cs[1], Cs[3]], [ + Ct13[i] = ot.gromov.gromov_barycenters(n_samples, [Cs[1], Cs[3]], [ ps[1], ps[3]], p, lambdast[i], 'square_loss', 5e-4, numItermax=100, stopThr=1e-3) Ct23 = [0 for i in range(2)] for i in range(2): - Ct23[i] = ot.gromov.gromov_barycenters(N, [Cs[2], Cs[3]], [ + Ct23[i] = ot.gromov.gromov_barycenters(n_samples, [Cs[2], Cs[3]], [ ps[2], ps[3]], p, lambdast[i], 'square_loss', 5e-4, numItermax=100, stopThr=1e-3) """ diff --git a/ot/gromov.py b/ot/gromov.py index 421ed3f..ad85fcd 100644 --- a/ot/gromov.py +++ b/ot/gromov.py @@ -208,7 +208,7 @@ def update_kl_loss(p, lambdas, T, Cs): return(np.exp(np.divide(tmpsum, ppt))) -def gromov_wasserstein(C1, C2, p, q, loss_fun, epsilon, numItermax=1000, stopThr=1e-9, verbose=False, log=False): +def gromov_wasserstein(C1, C2, p, q, loss_fun, epsilon, max_iter=1000, stopThr=1e-9, verbose=False, log=False): """ Returns the gromov-wasserstein coupling between the two measured similarity matrices @@ -248,7 +248,7 @@ def gromov_wasserstein(C1, C2, p, q, loss_fun, epsilon, numItermax=1000, stopThr loss_fun : loss function used for the solver either 'square_loss' or 'kl_loss' epsilon : float Regularization term >0 - numItermax : int, optional + max_iter : int, optional Max number of iterations stopThr : float, optional Stop threshold on error (>0) @@ -274,7 +274,7 @@ def gromov_wasserstein(C1, C2, p, q, loss_fun, epsilon, numItermax=1000, stopThr cpt = 0 err = 1 - while (err > stopThr and cpt < numItermax): + while (err > stopThr and cpt < max_iter): Tprev = T @@ -307,7 +307,7 @@ def gromov_wasserstein(C1, C2, p, q, loss_fun, epsilon, numItermax=1000, stopThr return T -def gromov_wasserstein2(C1, C2, p, q, loss_fun, epsilon, numItermax=1000, stopThr=1e-9, verbose=False, log=False): +def gromov_wasserstein2(C1, C2, p, q, loss_fun, epsilon, max_iter=1000, stopThr=1e-9, verbose=False, log=False): """ Returns the gromov-wasserstein discrepancy between the two measured similarity matrices @@ -362,10 +362,10 @@ def gromov_wasserstein2(C1, C2, p, q, loss_fun, epsilon, numItermax=1000, stopTh if log: gw, logv = gromov_wasserstein( - C1, C2, p, q, loss_fun, epsilon, numItermax, stopThr, verbose, log) + C1, C2, p, q, loss_fun, epsilon, max_iter, stopThr, verbose, log) else: gw = gromov_wasserstein(C1, C2, p, q, loss_fun, - epsilon, numItermax, stopThr, verbose, log) + epsilon, max_iter, stopThr, verbose, log) if loss_fun == 'square_loss': gw_dist = np.sum(gw * tensor_square_loss(C1, C2, gw)) @@ -379,7 +379,7 @@ def gromov_wasserstein2(C1, C2, p, q, loss_fun, epsilon, numItermax=1000, stopTh return gw_dist -def gromov_barycenters(N, Cs, ps, p, lambdas, loss_fun, epsilon, numItermax=1000, stopThr=1e-9, verbose=False, log=False): +def gromov_barycenters(N, Cs, ps, p, lambdas, loss_fun, epsilon, max_iter=1000, stopThr=1e-9, verbose=False, log=False): """ Returns the gromov-wasserstein barycenters of S measured similarity matrices @@ -442,12 +442,12 @@ def gromov_barycenters(N, Cs, ps, p, lambdas, loss_fun, epsilon, numItermax=1000 error = [] - while(err > stopThr and cpt < numItermax): + while(err > stopThr and cpt < max_iter): Cprev = C T = [gromov_wasserstein(Cs[s], C, ps[s], p, loss_fun, epsilon, - numItermax, 1e-5, verbose, log) for s in range(S)] + max_iter, 1e-5, verbose, log) for s in range(S)] if loss_fun == 'square_loss': C = update_square_loss(p, lambdas, T, Cs) diff --git a/test/test_gromov.py b/test/test_gromov.py index 75eeaab..c26d898 100644 --- a/test/test_gromov.py +++ b/test/test_gromov.py @@ -10,18 +10,18 @@ import ot def test_gromov(): - n = 50 # nb samples + n_samples = 50 # nb samples mu_s = np.array([0, 0]) cov_s = np.array([[1, 0], [0, 1]]) - xs = ot.datasets.get_2D_samples_gauss(n, mu_s, cov_s) + xs = ot.datasets.get_2D_samples_gauss(n_samples, mu_s, cov_s) - xt = [xs[n - (i + 1)] for i in range(n)] + xt = [xs[n_samples - (i + 1)] for i in range(n_samples)] xt = np.array(xt) - p = ot.unif(n) - q = ot.unif(n) + p = ot.unif(n_samples) + q = ot.unif(n_samples) C1 = ot.dist(xs, xs) C2 = ot.dist(xt, xt) -- cgit v1.2.3 From 46fc12a298c49b715ac953cff391b18b54dab0f0 Mon Sep 17 00:00:00 2001 From: Nicolas Courty Date: Fri, 1 Sep 2017 11:43:51 +0200 Subject: solving conflicts :/ --- examples/plot_gromov.py | 15 ------------- examples/plot_gromov_barycenter.py | 33 ----------------------------- ot/gromov.py | 43 +++++--------------------------------- test/test_gromov.py | 14 ------------- 4 files changed, 5 insertions(+), 100 deletions(-) (limited to 'test') diff --git a/examples/plot_gromov.py b/examples/plot_gromov.py index 99aaf81..92312ae 100644 --- a/examples/plot_gromov.py +++ b/examples/plot_gromov.py @@ -26,11 +26,7 @@ The Gromov-Wasserstein distance allows to compute distances with samples that do For demonstration purpose, we sample two Gaussian distributions in 2- and 3-dimensional spaces. """ -<<<<<<< HEAD n_samples = 30 # nb samples -======= -n = 30 # nb samples ->>>>>>> 986f46ddde3ce2f550cb56f66620df377326423d mu_s = np.array([0, 0]) cov_s = np.array([[1, 0], [0, 1]]) @@ -39,15 +35,9 @@ mu_t = np.array([4, 4, 4]) cov_t = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) -<<<<<<< HEAD xs = ot.datasets.get_2D_samples_gauss(n_samples, mu_s, cov_s) P = sp.linalg.sqrtm(cov_t) xt = np.random.randn(n_samples, 3).dot(P) + mu_t -======= -xs = ot.datasets.get_2D_samples_gauss(n, mu_s, cov_s) -P = sp.linalg.sqrtm(cov_t) -xt = np.random.randn(n, 3).dot(P) + mu_t ->>>>>>> 986f46ddde3ce2f550cb56f66620df377326423d """ @@ -85,13 +75,8 @@ Compute Gromov-Wasserstein plans and distance ============================================= """ -<<<<<<< HEAD p = ot.unif(n_samples) q = ot.unif(n_samples) -======= -p = ot.unif(n) -q = ot.unif(n) ->>>>>>> 986f46ddde3ce2f550cb56f66620df377326423d gw = ot.gromov_wasserstein(C1, C2, p, q, 'square_loss', epsilon=5e-4) gw_dist = ot.gromov_wasserstein2(C1, C2, p, q, 'square_loss', epsilon=5e-4) diff --git a/examples/plot_gromov_barycenter.py b/examples/plot_gromov_barycenter.py index 46ec4bc..f0657e1 100755 --- a/examples/plot_gromov_barycenter.py +++ b/examples/plot_gromov_barycenter.py @@ -91,21 +91,12 @@ def im2mat(I): return I.reshape((I.shape[0] * I.shape[1], I.shape[2])) -<<<<<<< HEAD square = spi.imread('../data/carre.png').astype(np.float64) / 256 circle = spi.imread('../data/rond.png').astype(np.float64) / 256 triangle = spi.imread('../data/triangle.png').astype(np.float64) / 256 arrow = spi.imread('../data/coeur.png').astype(np.float64) / 256 shapes = [square, circle, triangle, arrow] -======= -carre = spi.imread('../data/carre.png').astype(np.float64) / 256 -rond = spi.imread('../data/rond.png').astype(np.float64) / 256 -triangle = spi.imread('../data/triangle.png').astype(np.float64) / 256 -fleche = spi.imread('../data/coeur.png').astype(np.float64) / 256 - -shapes = [carre, rond, triangle, fleche] ->>>>>>> 986f46ddde3ce2f550cb56f66620df377326423d S = 4 xs = [[] for i in range(S)] @@ -127,60 +118,36 @@ Barycenter computation The four distributions are constructed from 4 simple images """ ns = [len(xs[s]) for s in range(S)] -<<<<<<< HEAD n_samples = 30 -======= -N = 30 ->>>>>>> 986f46ddde3ce2f550cb56f66620df377326423d """Compute all distances matrices for the four shapes""" Cs = [sp.spatial.distance.cdist(xs[s], xs[s]) for s in range(S)] Cs = [cs / cs.max() for cs in Cs] ps = [ot.unif(ns[s]) for s in range(S)] -<<<<<<< HEAD p = ot.unif(n_samples) -======= -p = ot.unif(N) ->>>>>>> 986f46ddde3ce2f550cb56f66620df377326423d lambdast = [[float(i) / 3, float(3 - i) / 3] for i in [1, 2]] Ct01 = [0 for i in range(2)] for i in range(2): -<<<<<<< HEAD Ct01[i] = ot.gromov.gromov_barycenters(n_samples, [Cs[0], Cs[1]], [ -======= - Ct01[i] = ot.gromov.gromov_barycenters(N, [Cs[0], Cs[1]], [ ->>>>>>> 986f46ddde3ce2f550cb56f66620df377326423d ps[0], ps[1]], p, lambdast[i], 'square_loss', 5e-4, numItermax=100, stopThr=1e-3) Ct02 = [0 for i in range(2)] for i in range(2): -<<<<<<< HEAD Ct02[i] = ot.gromov.gromov_barycenters(n_samples, [Cs[0], Cs[2]], [ -======= - Ct02[i] = ot.gromov.gromov_barycenters(N, [Cs[0], Cs[2]], [ ->>>>>>> 986f46ddde3ce2f550cb56f66620df377326423d ps[0], ps[2]], p, lambdast[i], 'square_loss', 5e-4, numItermax=100, stopThr=1e-3) Ct13 = [0 for i in range(2)] for i in range(2): -<<<<<<< HEAD Ct13[i] = ot.gromov.gromov_barycenters(n_samples, [Cs[1], Cs[3]], [ -======= - Ct13[i] = ot.gromov.gromov_barycenters(N, [Cs[1], Cs[3]], [ ->>>>>>> 986f46ddde3ce2f550cb56f66620df377326423d ps[1], ps[3]], p, lambdast[i], 'square_loss', 5e-4, numItermax=100, stopThr=1e-3) Ct23 = [0 for i in range(2)] for i in range(2): -<<<<<<< HEAD Ct23[i] = ot.gromov.gromov_barycenters(n_samples, [Cs[2], Cs[3]], [ -======= - Ct23[i] = ot.gromov.gromov_barycenters(N, [Cs[2], Cs[3]], [ ->>>>>>> 986f46ddde3ce2f550cb56f66620df377326423d ps[2], ps[3]], p, lambdast[i], 'square_loss', 5e-4, numItermax=100, stopThr=1e-3) """ diff --git a/ot/gromov.py b/ot/gromov.py index 197e3ea..9dbf463 100644 --- a/ot/gromov.py +++ b/ot/gromov.py @@ -208,11 +208,7 @@ def update_kl_loss(p, lambdas, T, Cs): return(np.exp(np.divide(tmpsum, ppt))) -<<<<<<< HEAD def gromov_wasserstein(C1, C2, p, q, loss_fun, epsilon, max_iter=1000, stopThr=1e-9, verbose=False, log=False): -======= -def gromov_wasserstein(C1, C2, p, q, loss_fun, epsilon, numItermax=1000, stopThr=1e-9, verbose=False, log=False): ->>>>>>> 986f46ddde3ce2f550cb56f66620df377326423d """ Returns the gromov-wasserstein coupling between the two measured similarity matrices @@ -252,11 +248,11 @@ def gromov_wasserstein(C1, C2, p, q, loss_fun, epsilon, numItermax=1000, stopThr loss_fun : loss function used for the solver either 'square_loss' or 'kl_loss' epsilon : float Regularization term >0 -<<<<<<< HEAD +<<<<<<< HEAD max_iter : int, optional -======= +======= numItermax : int, optional ->>>>>>> 986f46ddde3ce2f550cb56f66620df377326423d +>>>>>>> 986f46ddde3ce2f550cb56f66620df377326423d Max number of iterations stopThr : float, optional Stop threshold on error (>0) @@ -282,11 +278,7 @@ def gromov_wasserstein(C1, C2, p, q, loss_fun, epsilon, numItermax=1000, stopThr cpt = 0 err = 1 -<<<<<<< HEAD while (err > stopThr and cpt < max_iter): -======= - while (err > stopThr and cpt < numItermax): ->>>>>>> 986f46ddde3ce2f550cb56f66620df377326423d Tprev = T @@ -319,11 +311,7 @@ def gromov_wasserstein(C1, C2, p, q, loss_fun, epsilon, numItermax=1000, stopThr return T -<<<<<<< HEAD def gromov_wasserstein2(C1, C2, p, q, loss_fun, epsilon, max_iter=1000, stopThr=1e-9, verbose=False, log=False): -======= -def gromov_wasserstein2(C1, C2, p, q, loss_fun, epsilon, numItermax=1000, stopThr=1e-9, verbose=False, log=False): ->>>>>>> 986f46ddde3ce2f550cb56f66620df377326423d """ Returns the gromov-wasserstein discrepancy between the two measured similarity matrices @@ -358,7 +346,7 @@ def gromov_wasserstein2(C1, C2, p, q, loss_fun, epsilon, numItermax=1000, stopTh loss_fun : loss function used for the solver either 'square_loss' or 'kl_loss' epsilon : float Regularization term >0 - numItermax : int, optional + max_iter : int, optional Max number of iterations stopThr : float, optional Stop threshold on error (>0) @@ -378,17 +366,10 @@ def gromov_wasserstein2(C1, C2, p, q, loss_fun, epsilon, numItermax=1000, stopTh if log: gw, logv = gromov_wasserstein( -<<<<<<< HEAD C1, C2, p, q, loss_fun, epsilon, max_iter, stopThr, verbose, log) else: gw = gromov_wasserstein(C1, C2, p, q, loss_fun, epsilon, max_iter, stopThr, verbose, log) -======= - C1, C2, p, q, loss_fun, epsilon, numItermax, stopThr, verbose, log) - else: - gw = gromov_wasserstein(C1, C2, p, q, loss_fun, - epsilon, numItermax, stopThr, verbose, log) ->>>>>>> 986f46ddde3ce2f550cb56f66620df377326423d if loss_fun == 'square_loss': gw_dist = np.sum(gw * tensor_square_loss(C1, C2, gw)) @@ -402,11 +383,7 @@ def gromov_wasserstein2(C1, C2, p, q, loss_fun, epsilon, numItermax=1000, stopTh return gw_dist -<<<<<<< HEAD def gromov_barycenters(N, Cs, ps, p, lambdas, loss_fun, epsilon, max_iter=1000, stopThr=1e-9, verbose=False, log=False): -======= -def gromov_barycenters(N, Cs, ps, p, lambdas, loss_fun, epsilon, numItermax=1000, stopThr=1e-9, verbose=False, log=False): ->>>>>>> 986f46ddde3ce2f550cb56f66620df377326423d """ Returns the gromov-wasserstein barycenters of S measured similarity matrices @@ -439,7 +416,7 @@ def gromov_barycenters(N, Cs, ps, p, lambdas, loss_fun, epsilon, numItermax=1000 with the S Ts couplings calculated at each iteration epsilon : float Regularization term >0 - numItermax : int, optional + max_iter : int, optional Max number of iterations stopThr : float, optional Stop threshol on error (>0) @@ -469,21 +446,11 @@ def gromov_barycenters(N, Cs, ps, p, lambdas, loss_fun, epsilon, numItermax=1000 error = [] -<<<<<<< HEAD while(err > stopThr and cpt < max_iter): -======= - while(err > stopThr and cpt < numItermax): ->>>>>>> 986f46ddde3ce2f550cb56f66620df377326423d - Cprev = C T = [gromov_wasserstein(Cs[s], C, ps[s], p, loss_fun, epsilon, -<<<<<<< HEAD max_iter, 1e-5, verbose, log) for s in range(S)] -======= - numItermax, 1e-5, verbose, log) for s in range(S)] ->>>>>>> 986f46ddde3ce2f550cb56f66620df377326423d - if loss_fun == 'square_loss': C = update_square_loss(p, lambdas, T, Cs) diff --git a/test/test_gromov.py b/test/test_gromov.py index a6c89f2..c26d898 100644 --- a/test/test_gromov.py +++ b/test/test_gromov.py @@ -10,16 +10,11 @@ import ot def test_gromov(): -<<<<<<< HEAD n_samples = 50 # nb samples -======= - n = 50 # nb samples ->>>>>>> 986f46ddde3ce2f550cb56f66620df377326423d mu_s = np.array([0, 0]) cov_s = np.array([[1, 0], [0, 1]]) -<<<<<<< HEAD xs = ot.datasets.get_2D_samples_gauss(n_samples, mu_s, cov_s) xt = [xs[n_samples - (i + 1)] for i in range(n_samples)] @@ -27,15 +22,6 @@ def test_gromov(): p = ot.unif(n_samples) q = ot.unif(n_samples) -======= - xs = ot.datasets.get_2D_samples_gauss(n, mu_s, cov_s) - - xt = [xs[n - (i + 1)] for i in range(n)] - xt = np.array(xt) - - p = ot.unif(n) - q = ot.unif(n) ->>>>>>> 986f46ddde3ce2f550cb56f66620df377326423d C1 = ot.dist(xs, xs) C2 = ot.dist(xt, xt) -- cgit v1.2.3 From 53e1115349ddbdff83b74c5dd15fc4b258c46cd4 Mon Sep 17 00:00:00 2001 From: Nicolas Courty Date: Fri, 1 Sep 2017 15:37:09 +0200 Subject: docstrings + naming --- examples/plot_gromov_barycenter.py | 34 ++++++++------ ot/gromov.py | 92 +++++++++++++++++++------------------- test/test_gromov.py | 2 +- 3 files changed, 68 insertions(+), 60 deletions(-) (limited to 'test') diff --git a/examples/plot_gromov_barycenter.py b/examples/plot_gromov_barycenter.py index f0657e1..4f17117 100755 --- a/examples/plot_gromov_barycenter.py +++ b/examples/plot_gromov_barycenter.py @@ -45,19 +45,19 @@ def smacof_mds(C, dim, max_iter=3000, eps=1e-9): dimension of the targeted space max_iter : int Maximum number of iterations of the SMACOF algorithm for a single run - - eps : relative tolerance w.r.t stress to declare converge + eps : float + relative tolerance w.r.t stress to declare converge Returns ------- - npos : R**dim ndarray + npos : ndarray, shape (R, dim) Embedded coordinates of the interpolated point cloud (defined with one isometry) """ - seed = np.random.RandomState(seed=3) + rng = np.random.RandomState(seed=3) mds = manifold.MDS( dim, @@ -72,7 +72,7 @@ def smacof_mds(C, dim, max_iter=3000, eps=1e-9): max_iter=max_iter, eps=1e-9, dissimilarity="precomputed", - random_state=seed, + random_state=rng, n_init=1) npos = nmds.fit_transform(C, init=pos) @@ -132,23 +132,31 @@ lambdast = [[float(i) / 3, float(3 - i) / 3] for i in [1, 2]] Ct01 = [0 for i in range(2)] for i in range(2): - Ct01[i] = ot.gromov.gromov_barycenters(n_samples, [Cs[0], Cs[1]], [ - ps[0], ps[1]], p, lambdast[i], 'square_loss', 5e-4, numItermax=100, stopThr=1e-3) + Ct01[i] = ot.gromov.gromov_barycenters(n_samples, [Cs[0], Cs[1]], + [ps[0], ps[1] + ], p, lambdast[i], 'square_loss', 5e-4, + max_iter=100, stopThr=1e-3) Ct02 = [0 for i in range(2)] for i in range(2): - Ct02[i] = ot.gromov.gromov_barycenters(n_samples, [Cs[0], Cs[2]], [ - ps[0], ps[2]], p, lambdast[i], 'square_loss', 5e-4, numItermax=100, stopThr=1e-3) + Ct02[i] = ot.gromov.gromov_barycenters(n_samples, [Cs[0], Cs[2]], + [ps[0], ps[2] + ], p, lambdast[i], 'square_loss', 5e-4, + max_iter=100, stopThr=1e-3) Ct13 = [0 for i in range(2)] for i in range(2): - Ct13[i] = ot.gromov.gromov_barycenters(n_samples, [Cs[1], Cs[3]], [ - ps[1], ps[3]], p, lambdast[i], 'square_loss', 5e-4, numItermax=100, stopThr=1e-3) + Ct13[i] = ot.gromov.gromov_barycenters(n_samples, [Cs[1], Cs[3]], + [ps[1], ps[3] + ], p, lambdast[i], 'square_loss', 5e-4, + max_iter=100, stopThr=1e-3) Ct23 = [0 for i in range(2)] for i in range(2): - Ct23[i] = ot.gromov.gromov_barycenters(n_samples, [Cs[2], Cs[3]], [ - ps[2], ps[3]], p, lambdast[i], 'square_loss', 5e-4, numItermax=100, stopThr=1e-3) + Ct23[i] = ot.gromov.gromov_barycenters(n_samples, [Cs[2], Cs[3]], + [ps[2], ps[3] + ], p, lambdast[i], 'square_loss', 5e-4, + max_iter=100, stopThr=1e-3) """ Visualization diff --git a/ot/gromov.py b/ot/gromov.py index 9dbf463..cf9c4da 100644 --- a/ot/gromov.py +++ b/ot/gromov.py @@ -58,13 +58,13 @@ def tensor_square_loss(C1, C2, T): Metric cost matrix in the source space C2 : ndarray, shape (nt, nt) Metric costfr matrix in the target space - T : np.ndarray(ns,nt) + T : ndarray, shape (ns, nt) Coupling between source and target spaces Returns ------- - tens : (ns*nt) ndarray + tens : ndarray, shape (ns, nt) \mathcal{L}(C1,C2) \otimes T tensor-matrix multiplication result @@ -89,7 +89,7 @@ def tensor_square_loss(C1, C2, T): tens = -np.dot(h1(C1), T).dot(h2(C2).T) tens -= tens.min() - return np.array(tens) + return tens def tensor_kl_loss(C1, C2, T): @@ -116,13 +116,13 @@ def tensor_kl_loss(C1, C2, T): Metric cost matrix in the source space C2 : ndarray, shape (nt, nt) Metric costfr matrix in the target space - T : np.ndarray(ns,nt) + T : ndarray, shape (ns, nt) Coupling between source and target spaces Returns ------- - tens : (ns*nt) ndarray + tens : ndarray, shape (ns, nt) \mathcal{L}(C1,C2) \otimes T tensor-matrix multiplication result References @@ -151,34 +151,36 @@ def tensor_kl_loss(C1, C2, T): tens = -np.dot(h1(C1), T).dot(h2(C2).T) tens -= tens.min() - return np.array(tens) + return tens def update_square_loss(p, lambdas, T, Cs): """ - Updates C according to the L2 Loss kernel with the S Ts couplings calculated at each iteration + Updates C according to the L2 Loss kernel with the S Ts couplings + calculated at each iteration Parameters ---------- - p : np.ndarray(N,) + p : ndarray, shape (N,) weights in the targeted barycenter lambdas : list of the S spaces' weights T : list of S np.ndarray(ns,N) the S Ts couplings calculated at each iteration - Cs : Cs : list of S np.ndarray(ns,ns) + Cs : list of S ndarray, shape(ns,ns) Metric cost matrices Returns ---------- - C updated + C : ndarray, shape (nt,nt) + updated C matrix """ tmpsum = sum([lambdas[s] * np.dot(T[s].T, Cs[s]).dot(T[s]) for s in range(len(T))]) ppt = np.outer(p, p) - return(np.divide(tmpsum, ppt)) + return np.divide(tmpsum, ppt) def update_kl_loss(p, lambdas, T, Cs): @@ -188,27 +190,28 @@ def update_kl_loss(p, lambdas, T, Cs): Parameters ---------- - p : np.ndarray(N,) + p : ndarray, shape (N,) weights in the targeted barycenter lambdas : list of the S spaces' weights T : list of S np.ndarray(ns,N) the S Ts couplings calculated at each iteration - Cs : Cs : list of S np.ndarray(ns,ns) + Cs : list of S ndarray, shape(ns,ns) Metric cost matrices Returns ---------- - C updated + C : ndarray, shape (ns,ns) + updated C matrix """ tmpsum = sum([lambdas[s] * np.dot(T[s].T, Cs[s]).dot(T[s]) for s in range(len(T))]) ppt = np.outer(p, p) - return(np.exp(np.divide(tmpsum, ppt))) + return np.exp(np.divide(tmpsum, ppt)) -def gromov_wasserstein(C1, C2, p, q, loss_fun, epsilon, max_iter=1000, stopThr=1e-9, verbose=False, log=False): +def gromov_wasserstein(C1, C2, p, q, loss_fun, epsilon, max_iter=1000, tol=1e-9, verbose=False, log=False): """ Returns the gromov-wasserstein coupling between the two measured similarity matrices @@ -241,31 +244,28 @@ def gromov_wasserstein(C1, C2, p, q, loss_fun, epsilon, max_iter=1000, stopThr=1 Metric cost matrix in the source space C2 : ndarray, shape (nt, nt) Metric costfr matrix in the target space - p : np.ndarray(ns,) + p : ndarray, shape (ns,) distribution in the source space - q : np.ndarray(nt) + q : ndarray, shape (nt,) distribution in the target space - loss_fun : loss function used for the solver either 'square_loss' or 'kl_loss' + loss_fun : string + loss function used for the solver either 'square_loss' or 'kl_loss' epsilon : float Regularization term >0 -<<<<<<< HEAD max_iter : int, optional -======= - numItermax : int, optional ->>>>>>> 986f46ddde3ce2f550cb56f66620df377326423d - Max number of iterations - stopThr : float, optional + Max number of iterations + tol : float, optional Stop threshold on error (>0) verbose : bool, optional Print information along iterations log : bool, optional record log if True - forcing : np.ndarray(N,2) - list of forced couplings (where N is the number of forcing) + Returns ------- - T : coupling between the two spaces that minimizes : + T : ndarray, shape (ns, nt) + coupling between the two spaces that minimizes : \sum_{i,j,k,l} L(C1_{i,k},C2_{j,l})*T_{i,j}*T_{k,l}-\epsilon(H(T)) """ @@ -278,7 +278,7 @@ def gromov_wasserstein(C1, C2, p, q, loss_fun, epsilon, max_iter=1000, stopThr=1 cpt = 0 err = 1 - while (err > stopThr and cpt < max_iter): + while (err > tol and cpt < max_iter): Tprev = T @@ -303,7 +303,7 @@ def gromov_wasserstein(C1, C2, p, q, loss_fun, epsilon, max_iter=1000, stopThr=1 'It.', 'Err') + '\n' + '-' * 19) print('{:5d}|{:8e}|'.format(cpt, err)) - cpt = cpt + 1 + cpt += 1 if log: return T, log @@ -311,7 +311,7 @@ def gromov_wasserstein(C1, C2, p, q, loss_fun, epsilon, max_iter=1000, stopThr=1 return T -def gromov_wasserstein2(C1, C2, p, q, loss_fun, epsilon, max_iter=1000, stopThr=1e-9, verbose=False, log=False): +def gromov_wasserstein2(C1, C2, p, q, loss_fun, epsilon, max_iter=1000, tol=1e-9, verbose=False, log=False): """ Returns the gromov-wasserstein discrepancy between the two measured similarity matrices @@ -339,37 +339,36 @@ def gromov_wasserstein2(C1, C2, p, q, loss_fun, epsilon, max_iter=1000, stopThr= Metric cost matrix in the source space C2 : ndarray, shape (nt, nt) Metric costfr matrix in the target space - p : np.ndarray(ns,) + p : ndarray, shape (ns,) distribution in the source space - q : np.ndarray(nt) + q : ndarray, shape (nt,) distribution in the target space - loss_fun : loss function used for the solver either 'square_loss' or 'kl_loss' + loss_fun : string + loss function used for the solver either 'square_loss' or 'kl_loss' epsilon : float Regularization term >0 max_iter : int, optional Max number of iterations - stopThr : float, optional + tol : float, optional Stop threshold on error (>0) verbose : bool, optional Print information along iterations log : bool, optional record log if True - forcing : np.ndarray(N,2) - list of forced couplings (where N is the number of forcing) Returns ------- - T : coupling between the two spaces that minimizes : - \sum_{i,j,k,l} L(C1_{i,k},C2_{j,l})*T_{i,j}*T_{k,l}-\epsilon(H(T)) + gw_dist : float + Gromov-Wasserstein distance """ if log: gw, logv = gromov_wasserstein( - C1, C2, p, q, loss_fun, epsilon, max_iter, stopThr, verbose, log) + C1, C2, p, q, loss_fun, epsilon, max_iter, tol, verbose, log) else: gw = gromov_wasserstein(C1, C2, p, q, loss_fun, - epsilon, max_iter, stopThr, verbose, log) + epsilon, max_iter, tol, verbose, log) if loss_fun == 'square_loss': gw_dist = np.sum(gw * tensor_square_loss(C1, C2, gw)) @@ -383,7 +382,7 @@ def gromov_wasserstein2(C1, C2, p, q, loss_fun, epsilon, max_iter=1000, stopThr= return gw_dist -def gromov_barycenters(N, Cs, ps, p, lambdas, loss_fun, epsilon, max_iter=1000, stopThr=1e-9, verbose=False, log=False): +def gromov_barycenters(N, Cs, ps, p, lambdas, loss_fun, epsilon, max_iter=1000, tol=1e-9, verbose=False, log=False): """ Returns the gromov-wasserstein barycenters of S measured similarity matrices @@ -408,7 +407,7 @@ def gromov_barycenters(N, Cs, ps, p, lambdas, loss_fun, epsilon, max_iter=1000, Metric cost matrices ps : list of S np.ndarray(ns,) sample weights in the S spaces - p : np.ndarray(N,) + p : ndarray, shape(N,) weights in the targeted barycenter lambdas : list of the S spaces' weights L : tensor-matrix multiplication function based on specific loss function @@ -418,7 +417,7 @@ def gromov_barycenters(N, Cs, ps, p, lambdas, loss_fun, epsilon, max_iter=1000, Regularization term >0 max_iter : int, optional Max number of iterations - stopThr : float, optional + tol : float, optional Stop threshol on error (>0) verbose : bool, optional Print information along iterations @@ -427,7 +426,8 @@ def gromov_barycenters(N, Cs, ps, p, lambdas, loss_fun, epsilon, max_iter=1000, Returns ------- - C : Similarity matrix in the barycenter space (permutated arbitrarily) + C : ndarray, shape (N, N) + Similarity matrix in the barycenter space (permutated arbitrarily) """ @@ -446,7 +446,7 @@ def gromov_barycenters(N, Cs, ps, p, lambdas, loss_fun, epsilon, max_iter=1000, error = [] - while(err > stopThr and cpt < max_iter): + while(err > tol and cpt < max_iter): Cprev = C T = [gromov_wasserstein(Cs[s], C, ps[s], p, loss_fun, epsilon, diff --git a/test/test_gromov.py b/test/test_gromov.py index c26d898..28495e1 100644 --- a/test/test_gromov.py +++ b/test/test_gromov.py @@ -17,7 +17,7 @@ def test_gromov(): xs = ot.datasets.get_2D_samples_gauss(n_samples, mu_s, cov_s) - xt = [xs[n_samples - (i + 1)] for i in range(n_samples)] + xt = xs[::-1] xt = np.array(xt) p = ot.unif(n_samples) -- cgit v1.2.3 From b12edc59c0a94e1f426ae314baa006e06c062923 Mon Sep 17 00:00:00 2001 From: Slasnista Date: Tue, 5 Sep 2017 09:42:32 +0200 Subject: integrated test for semi supervised case --- test/test_da.py | 96 +++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 60 insertions(+), 36 deletions(-) (limited to 'test') diff --git a/test/test_da.py b/test/test_da.py index 104a798..a757d0a 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -63,19 +63,25 @@ def test_sinkhorn_lpl1_transport_class(): transp_Xs = clf.fit_transform(Xs=Xs, ys=ys, Xt=Xt) assert_equal(transp_Xs.shape, Xs.shape) - # test semi supervised mode - clf = ot.da.SinkhornLpl1Transport() - clf.fit(Xs=Xs, ys=ys, Xt=Xt) - n_unsup = np.sum(clf.cost_) + # test unsupervised vs semi-supervised mode + clf_unsup = ot.da.SinkhornTransport() + clf_unsup.fit(Xs=Xs, Xt=Xt) + n_unsup = np.sum(clf_unsup.cost_) - # test semi supervised mode - clf = ot.da.SinkhornLpl1Transport() - clf.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) - assert_equal(clf.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) - n_semisup = np.sum(clf.cost_) + clf_semi = ot.da.SinkhornTransport() + clf_semi.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) + assert_equal(clf_semi.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) + n_semisup = np.sum(clf_semi.cost_) + # check that the cost matrix norms are indeed different assert n_unsup != n_semisup, "semisupervised mode not working" + # check that the coupling forbids mass transport between labeled source + # and labeled target samples + mass_semi = np.sum( + clf_semi.coupling_[clf_semi.cost_ == clf_semi.limit_max]) + assert mass_semi == 0, "semisupervised mode not working" + def test_sinkhorn_l1l2_transport_class(): """test_sinkhorn_transport @@ -129,19 +135,25 @@ def test_sinkhorn_l1l2_transport_class(): transp_Xs = clf.fit_transform(Xs=Xs, ys=ys, Xt=Xt) assert_equal(transp_Xs.shape, Xs.shape) - # test semi supervised mode - clf = ot.da.SinkhornL1l2Transport() - clf.fit(Xs=Xs, ys=ys, Xt=Xt) - n_unsup = np.sum(clf.cost_) + # test unsupervised vs semi-supervised mode + clf_unsup = ot.da.SinkhornTransport() + clf_unsup.fit(Xs=Xs, Xt=Xt) + n_unsup = np.sum(clf_unsup.cost_) - # test semi supervised mode - clf = ot.da.SinkhornL1l2Transport() - clf.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) - assert_equal(clf.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) - n_semisup = np.sum(clf.cost_) + clf_semi = ot.da.SinkhornTransport() + clf_semi.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) + assert_equal(clf_semi.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) + n_semisup = np.sum(clf_semi.cost_) + # check that the cost matrix norms are indeed different assert n_unsup != n_semisup, "semisupervised mode not working" + # check that the coupling forbids mass transport between labeled source + # and labeled target samples + mass_semi = np.sum( + clf_semi.coupling_[clf_semi.cost_ == clf_semi.limit_max]) + assert mass_semi == 0, "semisupervised mode not working" + # check everything runs well with log=True clf = ot.da.SinkhornL1l2Transport(log=True) clf.fit(Xs=Xs, ys=ys, Xt=Xt) @@ -200,19 +212,25 @@ def test_sinkhorn_transport_class(): transp_Xs = clf.fit_transform(Xs=Xs, Xt=Xt) assert_equal(transp_Xs.shape, Xs.shape) - # test semi supervised mode - clf = ot.da.SinkhornTransport() - clf.fit(Xs=Xs, Xt=Xt) - n_unsup = np.sum(clf.cost_) + # test unsupervised vs semi-supervised mode + clf_unsup = ot.da.SinkhornTransport() + clf_unsup.fit(Xs=Xs, Xt=Xt) + n_unsup = np.sum(clf_unsup.cost_) - # test semi supervised mode - clf = ot.da.SinkhornTransport() - clf.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) - assert_equal(clf.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) - n_semisup = np.sum(clf.cost_) + clf_semi = ot.da.SinkhornTransport() + clf_semi.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) + assert_equal(clf_semi.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) + n_semisup = np.sum(clf_semi.cost_) + # check that the cost matrix norms are indeed different assert n_unsup != n_semisup, "semisupervised mode not working" + # check that the coupling forbids mass transport between labeled source + # and labeled target samples + mass_semi = np.sum( + clf_semi.coupling_[clf_semi.cost_ == clf_semi.limit_max]) + assert mass_semi == 0, "semisupervised mode not working" + # check everything runs well with log=True clf = ot.da.SinkhornTransport(log=True) clf.fit(Xs=Xs, ys=ys, Xt=Xt) @@ -270,19 +288,25 @@ def test_emd_transport_class(): transp_Xs = clf.fit_transform(Xs=Xs, Xt=Xt) assert_equal(transp_Xs.shape, Xs.shape) - # test semi supervised mode - clf = ot.da.EMDTransport() - clf.fit(Xs=Xs, Xt=Xt) - n_unsup = np.sum(clf.cost_) + # test unsupervised vs semi-supervised mode + clf_unsup = ot.da.SinkhornTransport() + clf_unsup.fit(Xs=Xs, Xt=Xt) + n_unsup = np.sum(clf_unsup.cost_) - # test semi supervised mode - clf = ot.da.EMDTransport() - clf.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) - assert_equal(clf.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) - n_semisup = np.sum(clf.cost_) + clf_semi = ot.da.SinkhornTransport() + clf_semi.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) + assert_equal(clf_semi.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) + n_semisup = np.sum(clf_semi.cost_) + # check that the cost matrix norms are indeed different assert n_unsup != n_semisup, "semisupervised mode not working" + # check that the coupling forbids mass transport between labeled source + # and labeled target samples + mass_semi = np.sum( + clf_semi.coupling_[clf_semi.cost_ == clf_semi.limit_max]) + assert mass_semi == 0, "semisupervised mode not working" + def test_mapping_transport_class(): """test_mapping_transport -- cgit v1.2.3 From 8e4a7930cf1ff80edeb30021acaf7337a02d18a5 Mon Sep 17 00:00:00 2001 From: Slasnista Date: Tue, 5 Sep 2017 09:47:24 +0200 Subject: change name of otda object in test script: clf => otda --- test/test_da.py | 260 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 130 insertions(+), 130 deletions(-) (limited to 'test') diff --git a/test/test_da.py b/test/test_da.py index a757d0a..9fc42a3 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -22,56 +22,56 @@ def test_sinkhorn_lpl1_transport_class(): Xs, ys = get_data_classif('3gauss', ns) Xt, yt = get_data_classif('3gauss2', nt) - clf = ot.da.SinkhornLpl1Transport() + otda = ot.da.SinkhornLpl1Transport() # test its computed - clf.fit(Xs=Xs, ys=ys, Xt=Xt) - assert hasattr(clf, "cost_") - assert hasattr(clf, "coupling_") + otda.fit(Xs=Xs, ys=ys, Xt=Xt) + assert hasattr(otda, "cost_") + assert hasattr(otda, "coupling_") # test dimensions of coupling - assert_equal(clf.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) - assert_equal(clf.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(otda.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(otda.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) # test margin constraints mu_s = unif(ns) mu_t = unif(nt) - assert_allclose(np.sum(clf.coupling_, axis=0), mu_t, rtol=1e-3, atol=1e-3) - assert_allclose(np.sum(clf.coupling_, axis=1), mu_s, rtol=1e-3, atol=1e-3) + 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 = clf.transform(Xs=Xs) + transp_Xs = otda.transform(Xs=Xs) assert_equal(transp_Xs.shape, Xs.shape) Xs_new, _ = get_data_classif('3gauss', ns + 1) - transp_Xs_new = clf.transform(Xs_new) + 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 = clf.inverse_transform(Xt=Xt) + transp_Xt = otda.inverse_transform(Xt=Xt) assert_equal(transp_Xt.shape, Xt.shape) Xt_new, _ = get_data_classif('3gauss2', nt + 1) - transp_Xt_new = clf.inverse_transform(Xt=Xt_new) + 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 = clf.fit_transform(Xs=Xs, ys=ys, Xt=Xt) + transp_Xs = otda.fit_transform(Xs=Xs, ys=ys, Xt=Xt) assert_equal(transp_Xs.shape, Xs.shape) # test unsupervised vs semi-supervised mode - clf_unsup = ot.da.SinkhornTransport() - clf_unsup.fit(Xs=Xs, Xt=Xt) - n_unsup = np.sum(clf_unsup.cost_) + otda_unsup = ot.da.SinkhornTransport() + otda_unsup.fit(Xs=Xs, Xt=Xt) + n_unsup = np.sum(otda_unsup.cost_) - clf_semi = ot.da.SinkhornTransport() - clf_semi.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) - assert_equal(clf_semi.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) - n_semisup = np.sum(clf_semi.cost_) + otda_semi = ot.da.SinkhornTransport() + otda_semi.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) + assert_equal(otda_semi.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) + n_semisup = np.sum(otda_semi.cost_) # check that the cost matrix norms are indeed different assert n_unsup != n_semisup, "semisupervised mode not working" @@ -79,7 +79,7 @@ def test_sinkhorn_lpl1_transport_class(): # check that the coupling forbids mass transport between labeled source # and labeled target samples mass_semi = np.sum( - clf_semi.coupling_[clf_semi.cost_ == clf_semi.limit_max]) + otda_semi.coupling_[otda_semi.cost_ == otda_semi.limit_max]) assert mass_semi == 0, "semisupervised mode not working" @@ -93,57 +93,57 @@ def test_sinkhorn_l1l2_transport_class(): Xs, ys = get_data_classif('3gauss', ns) Xt, yt = get_data_classif('3gauss2', nt) - clf = ot.da.SinkhornL1l2Transport() + otda = ot.da.SinkhornL1l2Transport() # test its computed - clf.fit(Xs=Xs, ys=ys, Xt=Xt) - assert hasattr(clf, "cost_") - assert hasattr(clf, "coupling_") - assert hasattr(clf, "log_") + otda.fit(Xs=Xs, ys=ys, Xt=Xt) + assert hasattr(otda, "cost_") + assert hasattr(otda, "coupling_") + assert hasattr(otda, "log_") # test dimensions of coupling - assert_equal(clf.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) - assert_equal(clf.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(otda.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(otda.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) # test margin constraints mu_s = unif(ns) mu_t = unif(nt) - assert_allclose(np.sum(clf.coupling_, axis=0), mu_t, rtol=1e-3, atol=1e-3) - assert_allclose(np.sum(clf.coupling_, axis=1), mu_s, rtol=1e-3, atol=1e-3) + 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 = clf.transform(Xs=Xs) + transp_Xs = otda.transform(Xs=Xs) assert_equal(transp_Xs.shape, Xs.shape) Xs_new, _ = get_data_classif('3gauss', ns + 1) - transp_Xs_new = clf.transform(Xs_new) + 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 = clf.inverse_transform(Xt=Xt) + transp_Xt = otda.inverse_transform(Xt=Xt) assert_equal(transp_Xt.shape, Xt.shape) Xt_new, _ = get_data_classif('3gauss2', nt + 1) - transp_Xt_new = clf.inverse_transform(Xt=Xt_new) + 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 = clf.fit_transform(Xs=Xs, ys=ys, Xt=Xt) + transp_Xs = otda.fit_transform(Xs=Xs, ys=ys, Xt=Xt) assert_equal(transp_Xs.shape, Xs.shape) # test unsupervised vs semi-supervised mode - clf_unsup = ot.da.SinkhornTransport() - clf_unsup.fit(Xs=Xs, Xt=Xt) - n_unsup = np.sum(clf_unsup.cost_) + otda_unsup = ot.da.SinkhornTransport() + otda_unsup.fit(Xs=Xs, Xt=Xt) + n_unsup = np.sum(otda_unsup.cost_) - clf_semi = ot.da.SinkhornTransport() - clf_semi.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) - assert_equal(clf_semi.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) - n_semisup = np.sum(clf_semi.cost_) + otda_semi = ot.da.SinkhornTransport() + otda_semi.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) + assert_equal(otda_semi.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) + n_semisup = np.sum(otda_semi.cost_) # check that the cost matrix norms are indeed different assert n_unsup != n_semisup, "semisupervised mode not working" @@ -151,13 +151,13 @@ def test_sinkhorn_l1l2_transport_class(): # check that the coupling forbids mass transport between labeled source # and labeled target samples mass_semi = np.sum( - clf_semi.coupling_[clf_semi.cost_ == clf_semi.limit_max]) + otda_semi.coupling_[otda_semi.cost_ == otda_semi.limit_max]) assert mass_semi == 0, "semisupervised mode not working" # check everything runs well with log=True - clf = ot.da.SinkhornL1l2Transport(log=True) - clf.fit(Xs=Xs, ys=ys, Xt=Xt) - assert len(clf.log_.keys()) != 0 + otda = ot.da.SinkhornL1l2Transport(log=True) + otda.fit(Xs=Xs, ys=ys, Xt=Xt) + assert len(otda.log_.keys()) != 0 def test_sinkhorn_transport_class(): @@ -170,57 +170,57 @@ def test_sinkhorn_transport_class(): Xs, ys = get_data_classif('3gauss', ns) Xt, yt = get_data_classif('3gauss2', nt) - clf = ot.da.SinkhornTransport() + otda = ot.da.SinkhornTransport() # test its computed - clf.fit(Xs=Xs, Xt=Xt) - assert hasattr(clf, "cost_") - assert hasattr(clf, "coupling_") - assert hasattr(clf, "log_") + otda.fit(Xs=Xs, Xt=Xt) + assert hasattr(otda, "cost_") + assert hasattr(otda, "coupling_") + assert hasattr(otda, "log_") # test dimensions of coupling - assert_equal(clf.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) - assert_equal(clf.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(otda.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(otda.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) # test margin constraints mu_s = unif(ns) mu_t = unif(nt) - assert_allclose(np.sum(clf.coupling_, axis=0), mu_t, rtol=1e-3, atol=1e-3) - assert_allclose(np.sum(clf.coupling_, axis=1), mu_s, rtol=1e-3, atol=1e-3) + 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 = clf.transform(Xs=Xs) + transp_Xs = otda.transform(Xs=Xs) assert_equal(transp_Xs.shape, Xs.shape) Xs_new, _ = get_data_classif('3gauss', ns + 1) - transp_Xs_new = clf.transform(Xs_new) + 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 = clf.inverse_transform(Xt=Xt) + transp_Xt = otda.inverse_transform(Xt=Xt) assert_equal(transp_Xt.shape, Xt.shape) Xt_new, _ = get_data_classif('3gauss2', nt + 1) - transp_Xt_new = clf.inverse_transform(Xt=Xt_new) + 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 = clf.fit_transform(Xs=Xs, Xt=Xt) + transp_Xs = otda.fit_transform(Xs=Xs, Xt=Xt) assert_equal(transp_Xs.shape, Xs.shape) # test unsupervised vs semi-supervised mode - clf_unsup = ot.da.SinkhornTransport() - clf_unsup.fit(Xs=Xs, Xt=Xt) - n_unsup = np.sum(clf_unsup.cost_) + otda_unsup = ot.da.SinkhornTransport() + otda_unsup.fit(Xs=Xs, Xt=Xt) + n_unsup = np.sum(otda_unsup.cost_) - clf_semi = ot.da.SinkhornTransport() - clf_semi.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) - assert_equal(clf_semi.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) - n_semisup = np.sum(clf_semi.cost_) + otda_semi = ot.da.SinkhornTransport() + otda_semi.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) + assert_equal(otda_semi.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) + n_semisup = np.sum(otda_semi.cost_) # check that the cost matrix norms are indeed different assert n_unsup != n_semisup, "semisupervised mode not working" @@ -228,13 +228,13 @@ def test_sinkhorn_transport_class(): # check that the coupling forbids mass transport between labeled source # and labeled target samples mass_semi = np.sum( - clf_semi.coupling_[clf_semi.cost_ == clf_semi.limit_max]) + otda_semi.coupling_[otda_semi.cost_ == otda_semi.limit_max]) assert mass_semi == 0, "semisupervised mode not working" # check everything runs well with log=True - clf = ot.da.SinkhornTransport(log=True) - clf.fit(Xs=Xs, ys=ys, Xt=Xt) - assert len(clf.log_.keys()) != 0 + otda = ot.da.SinkhornTransport(log=True) + otda.fit(Xs=Xs, ys=ys, Xt=Xt) + assert len(otda.log_.keys()) != 0 def test_emd_transport_class(): @@ -247,56 +247,56 @@ def test_emd_transport_class(): Xs, ys = get_data_classif('3gauss', ns) Xt, yt = get_data_classif('3gauss2', nt) - clf = ot.da.EMDTransport() + otda = ot.da.EMDTransport() # test its computed - clf.fit(Xs=Xs, Xt=Xt) - assert hasattr(clf, "cost_") - assert hasattr(clf, "coupling_") + otda.fit(Xs=Xs, Xt=Xt) + assert hasattr(otda, "cost_") + assert hasattr(otda, "coupling_") # test dimensions of coupling - assert_equal(clf.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) - assert_equal(clf.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(otda.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(otda.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) # test margin constraints mu_s = unif(ns) mu_t = unif(nt) - assert_allclose(np.sum(clf.coupling_, axis=0), mu_t, rtol=1e-3, atol=1e-3) - assert_allclose(np.sum(clf.coupling_, axis=1), mu_s, rtol=1e-3, atol=1e-3) + 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 = clf.transform(Xs=Xs) + transp_Xs = otda.transform(Xs=Xs) assert_equal(transp_Xs.shape, Xs.shape) Xs_new, _ = get_data_classif('3gauss', ns + 1) - transp_Xs_new = clf.transform(Xs_new) + 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 = clf.inverse_transform(Xt=Xt) + transp_Xt = otda.inverse_transform(Xt=Xt) assert_equal(transp_Xt.shape, Xt.shape) Xt_new, _ = get_data_classif('3gauss2', nt + 1) - transp_Xt_new = clf.inverse_transform(Xt=Xt_new) + 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 = clf.fit_transform(Xs=Xs, Xt=Xt) + transp_Xs = otda.fit_transform(Xs=Xs, Xt=Xt) assert_equal(transp_Xs.shape, Xs.shape) # test unsupervised vs semi-supervised mode - clf_unsup = ot.da.SinkhornTransport() - clf_unsup.fit(Xs=Xs, Xt=Xt) - n_unsup = np.sum(clf_unsup.cost_) + otda_unsup = ot.da.SinkhornTransport() + otda_unsup.fit(Xs=Xs, Xt=Xt) + n_unsup = np.sum(otda_unsup.cost_) - clf_semi = ot.da.SinkhornTransport() - clf_semi.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) - assert_equal(clf_semi.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) - n_semisup = np.sum(clf_semi.cost_) + otda_semi = ot.da.SinkhornTransport() + otda_semi.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) + assert_equal(otda_semi.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) + n_semisup = np.sum(otda_semi.cost_) # check that the cost matrix norms are indeed different assert n_unsup != n_semisup, "semisupervised mode not working" @@ -304,7 +304,7 @@ def test_emd_transport_class(): # check that the coupling forbids mass transport between labeled source # and labeled target samples mass_semi = np.sum( - clf_semi.coupling_[clf_semi.cost_ == clf_semi.limit_max]) + otda_semi.coupling_[otda_semi.cost_ == otda_semi.limit_max]) assert mass_semi == 0, "semisupervised mode not working" @@ -324,47 +324,47 @@ def test_mapping_transport_class(): ########################################################################## # check computation and dimensions if bias == False - clf = ot.da.MappingTransport(kernel="linear", bias=False) - clf.fit(Xs=Xs, Xt=Xt) - assert hasattr(clf, "coupling_") - assert hasattr(clf, "mapping_") - assert hasattr(clf, "log_") + otda = ot.da.MappingTransport(kernel="linear", bias=False) + otda.fit(Xs=Xs, Xt=Xt) + assert hasattr(otda, "coupling_") + assert hasattr(otda, "mapping_") + assert hasattr(otda, "log_") - assert_equal(clf.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) - assert_equal(clf.mapping_.shape, ((Xs.shape[1], Xt.shape[1]))) + assert_equal(otda.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(otda.mapping_.shape, ((Xs.shape[1], Xt.shape[1]))) # test margin constraints mu_s = unif(ns) mu_t = unif(nt) - assert_allclose(np.sum(clf.coupling_, axis=0), mu_t, rtol=1e-3, atol=1e-3) - assert_allclose(np.sum(clf.coupling_, axis=1), mu_s, rtol=1e-3, atol=1e-3) + 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 = clf.transform(Xs=Xs) + transp_Xs = otda.transform(Xs=Xs) assert_equal(transp_Xs.shape, Xs.shape) - transp_Xs_new = clf.transform(Xs_new) + transp_Xs_new = otda.transform(Xs_new) # check that the oos method is working assert_equal(transp_Xs_new.shape, Xs_new.shape) # check computation and dimensions if bias == True - clf = ot.da.MappingTransport(kernel="linear", bias=True) - clf.fit(Xs=Xs, Xt=Xt) - assert_equal(clf.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) - assert_equal(clf.mapping_.shape, ((Xs.shape[1] + 1, Xt.shape[1]))) + otda = ot.da.MappingTransport(kernel="linear", bias=True) + otda.fit(Xs=Xs, Xt=Xt) + assert_equal(otda.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(otda.mapping_.shape, ((Xs.shape[1] + 1, Xt.shape[1]))) # test margin constraints mu_s = unif(ns) mu_t = unif(nt) - assert_allclose(np.sum(clf.coupling_, axis=0), mu_t, rtol=1e-3, atol=1e-3) - assert_allclose(np.sum(clf.coupling_, axis=1), mu_s, rtol=1e-3, atol=1e-3) + 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 = clf.transform(Xs=Xs) + transp_Xs = otda.transform(Xs=Xs) assert_equal(transp_Xs.shape, Xs.shape) - transp_Xs_new = clf.transform(Xs_new) + transp_Xs_new = otda.transform(Xs_new) # check that the oos method is working assert_equal(transp_Xs_new.shape, Xs_new.shape) @@ -374,52 +374,52 @@ def test_mapping_transport_class(): ########################################################################## # check computation and dimensions if bias == False - clf = ot.da.MappingTransport(kernel="gaussian", bias=False) - clf.fit(Xs=Xs, Xt=Xt) + otda = ot.da.MappingTransport(kernel="gaussian", bias=False) + otda.fit(Xs=Xs, Xt=Xt) - assert_equal(clf.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) - assert_equal(clf.mapping_.shape, ((Xs.shape[0], Xt.shape[1]))) + assert_equal(otda.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(otda.mapping_.shape, ((Xs.shape[0], Xt.shape[1]))) # test margin constraints mu_s = unif(ns) mu_t = unif(nt) - assert_allclose(np.sum(clf.coupling_, axis=0), mu_t, rtol=1e-3, atol=1e-3) - assert_allclose(np.sum(clf.coupling_, axis=1), mu_s, rtol=1e-3, atol=1e-3) + 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 = clf.transform(Xs=Xs) + transp_Xs = otda.transform(Xs=Xs) assert_equal(transp_Xs.shape, Xs.shape) - transp_Xs_new = clf.transform(Xs_new) + transp_Xs_new = otda.transform(Xs_new) # check that the oos method is working assert_equal(transp_Xs_new.shape, Xs_new.shape) # check computation and dimensions if bias == True - clf = ot.da.MappingTransport(kernel="gaussian", bias=True) - clf.fit(Xs=Xs, Xt=Xt) - assert_equal(clf.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) - assert_equal(clf.mapping_.shape, ((Xs.shape[0] + 1, Xt.shape[1]))) + otda = ot.da.MappingTransport(kernel="gaussian", bias=True) + otda.fit(Xs=Xs, Xt=Xt) + assert_equal(otda.coupling_.shape, ((Xs.shape[0], Xt.shape[0]))) + assert_equal(otda.mapping_.shape, ((Xs.shape[0] + 1, Xt.shape[1]))) # test margin constraints mu_s = unif(ns) mu_t = unif(nt) - assert_allclose(np.sum(clf.coupling_, axis=0), mu_t, rtol=1e-3, atol=1e-3) - assert_allclose(np.sum(clf.coupling_, axis=1), mu_s, rtol=1e-3, atol=1e-3) + 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 = clf.transform(Xs=Xs) + transp_Xs = otda.transform(Xs=Xs) assert_equal(transp_Xs.shape, Xs.shape) - transp_Xs_new = clf.transform(Xs_new) + transp_Xs_new = otda.transform(Xs_new) # check that the oos method is working assert_equal(transp_Xs_new.shape, Xs_new.shape) # check everything runs well with log=True - clf = ot.da.MappingTransport(kernel="gaussian", log=True) - clf.fit(Xs=Xs, Xt=Xt) - assert len(clf.log_.keys()) != 0 + otda = ot.da.MappingTransport(kernel="gaussian", log=True) + otda.fit(Xs=Xs, Xt=Xt) + assert len(otda.log_.keys()) != 0 def test_otda(): -- cgit v1.2.3 From d43ce6fdca486fdb0fe049ab3cae4daf8652f5d0 Mon Sep 17 00:00:00 2001 From: Antoine Rolet Date: Tue, 5 Sep 2017 16:58:10 +0900 Subject: Removed print --- test/test_emd.py | 1 - 1 file changed, 1 deletion(-) (limited to 'test') diff --git a/test/test_emd.py b/test/test_emd.py index 3bf6fa2..0025583 100644 --- a/test/test_emd.py +++ b/test/test_emd.py @@ -26,7 +26,6 @@ b=gauss(m,m=mean2,s=10) # loss matrix M=ot.dist(x.reshape((-1,1)), y.reshape((-1,1))) ** (1./2) -print M[0,:] #M/=M.max() #%% -- cgit v1.2.3 From 49c100de34583329058b39d414d2aa49b7fd15bf Mon Sep 17 00:00:00 2001 From: Slasnista Date: Tue, 5 Sep 2017 10:00:01 +0200 Subject: test semi supervised mode ok written for all class | need different tolerance for EMDTransport MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/test_da.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) (limited to 'test') diff --git a/test/test_da.py b/test/test_da.py index 9fc42a3..3602db9 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -64,11 +64,11 @@ def test_sinkhorn_lpl1_transport_class(): assert_equal(transp_Xs.shape, Xs.shape) # test unsupervised vs semi-supervised mode - otda_unsup = ot.da.SinkhornTransport() - otda_unsup.fit(Xs=Xs, Xt=Xt) + otda_unsup = ot.da.SinkhornLpl1Transport() + otda_unsup.fit(Xs=Xs, ys=ys, Xt=Xt) n_unsup = np.sum(otda_unsup.cost_) - otda_semi = ot.da.SinkhornTransport() + otda_semi = ot.da.SinkhornLpl1Transport() otda_semi.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) assert_equal(otda_semi.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) n_semisup = np.sum(otda_semi.cost_) @@ -136,11 +136,11 @@ def test_sinkhorn_l1l2_transport_class(): assert_equal(transp_Xs.shape, Xs.shape) # test unsupervised vs semi-supervised mode - otda_unsup = ot.da.SinkhornTransport() - otda_unsup.fit(Xs=Xs, Xt=Xt) + otda_unsup = ot.da.SinkhornL1l2Transport() + otda_unsup.fit(Xs=Xs, ys=ys, Xt=Xt) n_unsup = np.sum(otda_unsup.cost_) - otda_semi = ot.da.SinkhornTransport() + otda_semi = ot.da.SinkhornL1l2Transport() otda_semi.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) assert_equal(otda_semi.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) n_semisup = np.sum(otda_semi.cost_) @@ -152,7 +152,9 @@ def test_sinkhorn_l1l2_transport_class(): # and labeled target samples mass_semi = np.sum( otda_semi.coupling_[otda_semi.cost_ == otda_semi.limit_max]) - assert mass_semi == 0, "semisupervised mode not working" + mass_semi = otda_semi.coupling_[otda_semi.cost_ == otda_semi.limit_max] + assert_allclose(mass_semi, np.zeros_like(mass_semi), + rtol=1e-9, atol=1e-9) # check everything runs well with log=True otda = ot.da.SinkhornL1l2Transport(log=True) @@ -289,11 +291,11 @@ def test_emd_transport_class(): assert_equal(transp_Xs.shape, Xs.shape) # test unsupervised vs semi-supervised mode - otda_unsup = ot.da.SinkhornTransport() - otda_unsup.fit(Xs=Xs, Xt=Xt) + otda_unsup = ot.da.EMDTransport() + otda_unsup.fit(Xs=Xs, ys=ys, Xt=Xt) n_unsup = np.sum(otda_unsup.cost_) - otda_semi = ot.da.SinkhornTransport() + otda_semi = ot.da.EMDTransport() otda_semi.fit(Xs=Xs, ys=ys, Xt=Xt, yt=yt) assert_equal(otda_semi.cost_.shape, ((Xs.shape[0], Xt.shape[0]))) n_semisup = np.sum(otda_semi.cost_) @@ -305,7 +307,11 @@ def test_emd_transport_class(): # and labeled target samples mass_semi = np.sum( otda_semi.coupling_[otda_semi.cost_ == otda_semi.limit_max]) - assert mass_semi == 0, "semisupervised mode not working" + mass_semi = otda_semi.coupling_[otda_semi.cost_ == otda_semi.limit_max] + + # we need to use a small tolerance here, otherwise the test breaks + assert_allclose(mass_semi, np.zeros_like(mass_semi), + rtol=1e-2, atol=1e-2) def test_mapping_transport_class(): @@ -491,3 +497,4 @@ def test_otda(): # test_sinkhorn_l1l2_transport_class() # test_sinkhorn_lpl1_transport_class() # test_mapping_transport_class() + -- cgit v1.2.3 From 2097116c7db725a88876d617e20a94f32627f7c9 Mon Sep 17 00:00:00 2001 From: Slasnista Date: Tue, 5 Sep 2017 10:09:55 +0200 Subject: solving pb --- test/test_da.py | 61 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 27 deletions(-) (limited to 'test') diff --git a/test/test_da.py b/test/test_da.py index 3602db9..593dc53 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -36,8 +36,10 @@ def test_sinkhorn_lpl1_transport_class(): # test 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) + 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) @@ -108,8 +110,10 @@ def test_sinkhorn_l1l2_transport_class(): # test 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) + 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) @@ -187,8 +191,10 @@ def test_sinkhorn_transport_class(): # test 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) + 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) @@ -263,8 +269,10 @@ def test_emd_transport_class(): # test 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) + 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) @@ -342,8 +350,10 @@ def test_mapping_transport_class(): # test 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) + 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) @@ -363,8 +373,10 @@ def test_mapping_transport_class(): # test 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) + 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) @@ -389,8 +401,10 @@ def test_mapping_transport_class(): # test 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) + 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) @@ -410,8 +424,10 @@ def test_mapping_transport_class(): # test 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) + 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) @@ -454,7 +470,8 @@ def test_otda(): da_entrop.interp() da_entrop.predict(xs) - np.testing.assert_allclose(a, np.sum(da_entrop.G, 1), rtol=1e-3, atol=1e-3) + np.testing.assert_allclose( + a, np.sum(da_entrop.G, 1), rtol=1e-3, atol=1e-3) np.testing.assert_allclose(b, np.sum(da_entrop.G, 0), rtol=1e-3, atol=1e-3) # non-convex Group lasso regularization @@ -488,13 +505,3 @@ def test_otda(): da_emd = ot.da.OTDA_mapping_kernel() # init class da_emd.fit(xs, xt, numItermax=10) # fit distributions da_emd.predict(xs) # interpolation of source samples - - -# if __name__ == "__main__": - -# test_sinkhorn_transport_class() -# test_emd_transport_class() -# test_sinkhorn_l1l2_transport_class() -# test_sinkhorn_lpl1_transport_class() -# test_mapping_transport_class() - -- cgit v1.2.3 From d52b4ea415d9bb669be04ccd0940f9b3d258d0e1 Mon Sep 17 00:00:00 2001 From: Antoine Rolet Date: Tue, 5 Sep 2017 17:15:45 +0900 Subject: Fixed typo and merged emd tests --- ot/lp/__init__.py | 2 +- test/test_emd.py | 68 ------------------------------------------------------- test/test_ot.py | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 64 insertions(+), 72 deletions(-) delete mode 100644 test/test_emd.py (limited to 'test') diff --git a/ot/lp/__init__.py b/ot/lp/__init__.py index a14d4e4..6048f60 100644 --- a/ot/lp/__init__.py +++ b/ot/lp/__init__.py @@ -168,6 +168,6 @@ def emd2(a, b, M, processes=multiprocessing.cpu_count(), numItermax=100000): # res = [emd2_c(a, b[:, i].copy(), M, numItermax) for i in range(nb)] def f(b): - return emd2_c(a,b,M, max_iter)[0] + return emd2_c(a,b,M, numItermax)[0] res= parmap(f, [b[:,i] for i in range(nb)],processes) return np.array(res) diff --git a/test/test_emd.py b/test/test_emd.py deleted file mode 100644 index 0025583..0000000 --- a/test/test_emd.py +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env python2 -# -*- coding: utf-8 -*- - -import numpy as np -import ot - -from ot.datasets import get_1D_gauss as gauss -reload(ot.lp) - -#%% parameters - -n=5000 # nb bins -m=6000 # nb bins - -mean1 = 1000 -mean2 = 1100 - -# bin positions -x=np.arange(n,dtype=np.float64) -y=np.arange(m,dtype=np.float64) - -# Gaussian distributions -a=gauss(n,m=mean1,s=5) # m= mean, s= std - -b=gauss(m,m=mean2,s=10) - -# loss matrix -M=ot.dist(x.reshape((-1,1)), y.reshape((-1,1))) ** (1./2) -#M/=M.max() - -#%% - -print('Computing {} EMD '.format(1)) - -# emd loss 1 proc -ot.tic() -G, alpha, beta = ot.emd(a,b,M, dual_variables=True) -ot.toc('1 proc : {} s') - -cost1 = (G * M).sum() -cost_dual = np.vdot(a, alpha) + np.vdot(b, beta) - -# emd loss 1 proc -ot.tic() -cost_emd2 = ot.emd2(a,b,M) -ot.toc('1 proc : {} s') - -ot.tic() -G2 = ot.emd(b, a, np.ascontiguousarray(M.T)) -ot.toc('1 proc : {} s') - -cost2 = (G2 * M.T).sum() - -M_reduced = M - alpha.reshape(-1,1) - beta.reshape(1, -1) - -# Check that both cost computations are equivalent -np.testing.assert_almost_equal(cost1, cost_emd2) -# Check that dual and primal cost are equal -np.testing.assert_almost_equal(cost1, cost_dual) -# Check symmetry -np.testing.assert_almost_equal(cost1, cost2) -# Check with closed-form solution for gaussians -np.testing.assert_almost_equal(cost1, np.abs(mean1-mean2)) - -[ind1, ind2] = np.nonzero(G) - -# Check that reduced cost is zero on transport arcs -np.testing.assert_array_almost_equal((M - alpha.reshape(-1, 1) - beta.reshape(1, -1))[ind1, ind2], np.zeros(ind1.size)) \ No newline at end of file diff --git a/test/test_ot.py b/test/test_ot.py index acd8718..ded6c9f 100644 --- a/test/test_ot.py +++ b/test/test_ot.py @@ -6,6 +6,8 @@ import numpy as np import ot + +from ot.datasets import get_1D_gauss as gauss def test_doctest(): @@ -66,9 +68,6 @@ def test_emd_empty(): def test_emd2_multi(): - - from ot.datasets import get_1D_gauss as gauss - n = 1000 # nb bins # bin positions @@ -100,3 +99,64 @@ def test_emd2_multi(): ot.toc('multi proc : {} s') np.testing.assert_allclose(emd1, emdn) + +def test_dual_variables(): + #%% parameters + + n=5000 # nb bins + m=6000 # nb bins + + mean1 = 1000 + mean2 = 1100 + + # bin positions + x=np.arange(n,dtype=np.float64) + y=np.arange(m,dtype=np.float64) + + # Gaussian distributions + a=gauss(n,m=mean1,s=5) # m= mean, s= std + + b=gauss(m,m=mean2,s=10) + + # loss matrix + M=ot.dist(x.reshape((-1,1)), y.reshape((-1,1))) ** (1./2) + #M/=M.max() + + #%% + + print('Computing {} EMD '.format(1)) + + # emd loss 1 proc + ot.tic() + G, alpha, beta = ot.emd(a,b,M, dual_variables=True) + ot.toc('1 proc : {} s') + + cost1 = (G * M).sum() + cost_dual = np.vdot(a, alpha) + np.vdot(b, beta) + + # emd loss 1 proc + ot.tic() + cost_emd2 = ot.emd2(a,b,M) + ot.toc('1 proc : {} s') + + ot.tic() + G2 = ot.emd(b, a, np.ascontiguousarray(M.T)) + ot.toc('1 proc : {} s') + + cost2 = (G2 * M.T).sum() + + M_reduced = M - alpha.reshape(-1,1) - beta.reshape(1, -1) + + # Check that both cost computations are equivalent + np.testing.assert_almost_equal(cost1, cost_emd2) + # Check that dual and primal cost are equal + np.testing.assert_almost_equal(cost1, cost_dual) + # Check symmetry + np.testing.assert_almost_equal(cost1, cost2) + # Check with closed-form solution for gaussians + np.testing.assert_almost_equal(cost1, np.abs(mean1-mean2)) + + [ind1, ind2] = np.nonzero(G) + + # Check that reduced cost is zero on transport arcs + np.testing.assert_array_almost_equal((M - alpha.reshape(-1, 1) - beta.reshape(1, -1))[ind1, ind2], np.zeros(ind1.size)) -- cgit v1.2.3 From a3497b123b4802c7960a07a899ac7ce4525c5995 Mon Sep 17 00:00:00 2001 From: Antoine Rolet Date: Tue, 5 Sep 2017 17:51:58 +0900 Subject: Reformat --- test/test_ot.py | 67 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 33 insertions(+), 34 deletions(-) (limited to 'test') diff --git a/test/test_ot.py b/test/test_ot.py index ded6c9f..6f0f7c9 100644 --- a/test/test_ot.py +++ b/test/test_ot.py @@ -5,13 +5,12 @@ # License: MIT License import numpy as np + import ot - from ot.datasets import get_1D_gauss as gauss def test_doctest(): - import doctest # test lp solver @@ -100,53 +99,52 @@ def test_emd2_multi(): np.testing.assert_allclose(emd1, emdn) + def test_dual_variables(): - #%% parameters - - n=5000 # nb bins - m=6000 # nb bins - + # %% parameters + + n = 5000 # nb bins + m = 6000 # nb bins + mean1 = 1000 mean2 = 1100 - + # bin positions - x=np.arange(n,dtype=np.float64) - y=np.arange(m,dtype=np.float64) - + x = np.arange(n, dtype=np.float64) + y = np.arange(m, dtype=np.float64) + # Gaussian distributions - a=gauss(n,m=mean1,s=5) # m= mean, s= std - - b=gauss(m,m=mean2,s=10) - + a = gauss(n, m=mean1, s=5) # m= mean, s= std + + b = gauss(m, m=mean2, s=10) + # loss matrix - M=ot.dist(x.reshape((-1,1)), y.reshape((-1,1))) ** (1./2) - #M/=M.max() - - #%% - + M = ot.dist(x.reshape((-1, 1)), y.reshape((-1, 1))) ** (1. / 2) + # M/=M.max() + + # %% + print('Computing {} EMD '.format(1)) - + # emd loss 1 proc ot.tic() - G, alpha, beta = ot.emd(a,b,M, dual_variables=True) + G, alpha, beta = ot.emd(a, b, M, dual_variables=True) ot.toc('1 proc : {} s') - + cost1 = (G * M).sum() cost_dual = np.vdot(a, alpha) + np.vdot(b, beta) - + # emd loss 1 proc ot.tic() - cost_emd2 = ot.emd2(a,b,M) + cost_emd2 = ot.emd2(a, b, M) ot.toc('1 proc : {} s') - + ot.tic() G2 = ot.emd(b, a, np.ascontiguousarray(M.T)) ot.toc('1 proc : {} s') - + cost2 = (G2 * M.T).sum() - - M_reduced = M - alpha.reshape(-1,1) - beta.reshape(1, -1) - + # Check that both cost computations are equivalent np.testing.assert_almost_equal(cost1, cost_emd2) # Check that dual and primal cost are equal @@ -154,9 +152,10 @@ def test_dual_variables(): # Check symmetry np.testing.assert_almost_equal(cost1, cost2) # Check with closed-form solution for gaussians - np.testing.assert_almost_equal(cost1, np.abs(mean1-mean2)) - + np.testing.assert_almost_equal(cost1, np.abs(mean1 - mean2)) + [ind1, ind2] = np.nonzero(G) - + # Check that reduced cost is zero on transport arcs - np.testing.assert_array_almost_equal((M - alpha.reshape(-1, 1) - beta.reshape(1, -1))[ind1, ind2], np.zeros(ind1.size)) + np.testing.assert_array_almost_equal((M - alpha.reshape(-1, 1) - beta.reshape(1, -1))[ind1, ind2], + np.zeros(ind1.size)) -- cgit v1.2.3 From f8c1c8740f9974dcf4aaf191851d62149dceb91c Mon Sep 17 00:00:00 2001 From: Antoine Rolet Date: Thu, 7 Sep 2017 13:29:46 +0900 Subject: Added MAX_ITER_REACHED flag and warning --- ot/lp/EMD.h | 3 ++- ot/lp/EMD_wrapper.cpp | 21 +++++++---------- ot/lp/emd_wrap.pyx | 29 +++++++++++++---------- ot/lp/network_simplex_simple.h | 46 ++++++++++++++++++++----------------- test/test_ot.py | 52 ++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 102 insertions(+), 49 deletions(-) (limited to 'test') diff --git a/ot/lp/EMD.h b/ot/lp/EMD.h index 15e9115..bb486de 100644 --- a/ot/lp/EMD.h +++ b/ot/lp/EMD.h @@ -26,7 +26,8 @@ typedef unsigned int node_id_type; enum ProblemType { INFEASIBLE, OPTIMAL, - UNBOUNDED + UNBOUNDED, + MAX_ITER_REACHED }; int EMD_wrap(int n1,int n2, double *X, double *Y,double *D, double *G, double* alpha, double* beta, double *cost, int max_iter); diff --git a/ot/lp/EMD_wrapper.cpp b/ot/lp/EMD_wrapper.cpp index 8e74462..92663dc 100644 --- a/ot/lp/EMD_wrapper.cpp +++ b/ot/lp/EMD_wrapper.cpp @@ -29,14 +29,18 @@ int EMD_wrap(int n1, int n2, double *X, double *Y, double *D, double *G, double val=*(X+i); if (val>0) { n++; - } + }else if(val<0){ + return INFEASIBLE; + } } m=0; for (int i=0; i0) { m++; - } + }else if(val<0){ + return INFEASIBLE; + } } // Define the graph @@ -83,16 +87,7 @@ int EMD_wrap(int n1, int n2, double *X, double *Y, double *D, double *G, // Solve the problem with the network simplex algorithm int ret=net.run(); - if (ret!=(int)net.OPTIMAL) { - if (ret==(int)net.INFEASIBLE) { - std::cout << "Infeasible problem"; - } - if (ret==(int)net.UNBOUNDED) - { - std::cout << "Unbounded problem"; - } - } else - { + if (ret==(int)net.OPTIMAL || ret==(int)net.MAX_ITER_REACHED) { *cost = 0; Arc a; di.first(a); for (; a != INVALID; di.next(a)) { @@ -105,7 +100,7 @@ int EMD_wrap(int n1, int n2, double *X, double *Y, double *D, double *G, *(beta + indJ[j-n]) = net.potential(j); } - }; + } return ret; diff --git a/ot/lp/emd_wrap.pyx b/ot/lp/emd_wrap.pyx index 7056e0e..9bea154 100644 --- a/ot/lp/emd_wrap.pyx +++ b/ot/lp/emd_wrap.pyx @@ -7,6 +7,7 @@ Cython linker with C solver # # License: MIT License +import warnings import numpy as np cimport numpy as np @@ -15,14 +16,14 @@ cimport cython cdef extern from "EMD.h": - int EMD_wrap(int n1,int n2, double *X, double *Y,double *D, double *G, double* alpha, double* beta, double *cost, int max_iter) - cdef enum ProblemType: INFEASIBLE, OPTIMAL, UNBOUNDED + int EMD_wrap(int n1,int n2, double *X, double *Y,double *D, double *G, double* alpha, double* beta, double *cost, int numItermax) + cdef enum ProblemType: INFEASIBLE, OPTIMAL, UNBOUNDED, MAX_ITER_REACHED @cython.boundscheck(False) @cython.wraparound(False) -def emd_c( np.ndarray[double, ndim=1, mode="c"] a,np.ndarray[double, ndim=1, mode="c"] b,np.ndarray[double, ndim=2, mode="c"] M, int max_iter): +def emd_c( np.ndarray[double, ndim=1, mode="c"] a,np.ndarray[double, ndim=1, mode="c"] b,np.ndarray[double, ndim=2, mode="c"] M, int numItermax): """ Solves the Earth Movers distance problem and returns the optimal transport matrix @@ -49,7 +50,7 @@ def emd_c( np.ndarray[double, ndim=1, mode="c"] a,np.ndarray[double, ndim=1, mod target histogram M : (ns,nt) ndarray, float64 loss matrix - max_iter : int + numItermax : int The maximum number of iterations before stopping the optimization algorithm if it has not converged. @@ -76,18 +77,20 @@ def emd_c( np.ndarray[double, ndim=1, mode="c"] a,np.ndarray[double, ndim=1, mod b=np.ones((n2,))/n2 # calling the function - cdef int resultSolver = EMD_wrap(n1,n2, a.data, b.data, M.data, G.data, alpha.data, beta.data, &cost, max_iter) + cdef int resultSolver = EMD_wrap(n1,n2, a.data, b.data, M.data, G.data, alpha.data, beta.data, &cost, numItermax) if resultSolver != OPTIMAL: if resultSolver == INFEASIBLE: - print("Problem infeasible. Try to increase numItermax.") + warnings.warn("Problem infeasible. Check that a and b are in the simplex") elif resultSolver == UNBOUNDED: - print("Problem unbounded") + warnings.warn("Problem unbounded") + elif resultSolver == MAX_ITER_REACHED: + warnings.warn("numItermax reached before optimality. Try to increase numItermax.") return G, alpha, beta @cython.boundscheck(False) @cython.wraparound(False) -def emd2_c( np.ndarray[double, ndim=1, mode="c"] a,np.ndarray[double, ndim=1, mode="c"] b,np.ndarray[double, ndim=2, mode="c"] M, int max_iter): +def emd2_c( np.ndarray[double, ndim=1, mode="c"] a,np.ndarray[double, ndim=1, mode="c"] b,np.ndarray[double, ndim=2, mode="c"] M, int numItermax): """ Solves the Earth Movers distance problem and returns the optimal transport loss @@ -114,7 +117,7 @@ def emd2_c( np.ndarray[double, ndim=1, mode="c"] a,np.ndarray[double, ndim=1, mo target histogram M : (ns,nt) ndarray, float64 loss matrix - max_iter : int + numItermax : int The maximum number of iterations before stopping the optimization algorithm if it has not converged. @@ -140,12 +143,14 @@ def emd2_c( np.ndarray[double, ndim=1, mode="c"] a,np.ndarray[double, ndim=1, mo if not len(b): b=np.ones((n2,))/n2 # calling the function - cdef int resultSolver = EMD_wrap(n1,n2, a.data, b.data, M.data, G.data, alpha.data, beta.data, &cost, max_iter) + cdef int resultSolver = EMD_wrap(n1,n2, a.data, b.data, M.data, G.data, alpha.data, beta.data, &cost, numItermax) if resultSolver != OPTIMAL: if resultSolver == INFEASIBLE: - print("Problem infeasible. Try to inscrease numItermax.") + warnings.warn("Problem infeasible. Check that a and b are in the simplex") elif resultSolver == UNBOUNDED: - print("Problem unbounded") + warnings.warn("Problem unbounded") + elif resultSolver == MAX_ITER_REACHED: + warnings.warn("numItermax reached before optimality. Try to increase numItermax.") return cost, alpha, beta diff --git a/ot/lp/network_simplex_simple.h b/ot/lp/network_simplex_simple.h index a7743ee..7c6a4ce 100644 --- a/ot/lp/network_simplex_simple.h +++ b/ot/lp/network_simplex_simple.h @@ -34,7 +34,8 @@ #endif -#define EPSILON 10*2.2204460492503131e-016 +#define EPSILON 2.2204460492503131e-15 +#define _EPSILON 1e-8 #define MAX_DEBUG_ITER 100000 @@ -260,7 +261,9 @@ namespace lemon { /// The objective function of the problem is unbounded, i.e. /// there is a directed cycle having negative total cost and /// infinite upper bound. - UNBOUNDED + UNBOUNDED, + /// The maximum number of iteration has been reached + MAX_ITER_REACHED }; /// \brief Constants for selecting the type of the supply constraints. @@ -683,7 +686,7 @@ namespace lemon { /// \see resetParams(), reset() ProblemType run() { #if DEBUG_LVL>0 - std::cout << "OPTIMAL = " << OPTIMAL << "\nINFEASIBLE = " << INFEASIBLE << "nUNBOUNDED = " << UNBOUNDED << "\n"; + std::cout << "OPTIMAL = " << OPTIMAL << "\nINFEASIBLE = " << INFEASIBLE << "\nUNBOUNDED = " << UNBOUNDED << "\nMAX_ITER_REACHED" << MAX_ITER_REACHED\n"; #endif if (!init()) return INFEASIBLE; @@ -941,15 +944,15 @@ namespace lemon { // Initialize internal data structures bool init() { if (_node_num == 0) return false; - /* + // Check the sum of supply values _sum_supply = 0; for (int i = 0; i != _node_num; ++i) { _sum_supply += _supply[i]; } - if ( !((_stype == GEQ && _sum_supply <= _epsilon ) || - (_stype == LEQ && _sum_supply >= -_epsilon )) ) return false; - */ + if ( fabs(_sum_supply) > _EPSILON ) return false; + + _sum_supply = 0; // Initialize artifical cost Cost ART_COST; @@ -1416,13 +1419,11 @@ namespace lemon { ProblemType start() { PivotRuleImpl pivot(*this); double prevCost=-1; + ProblemType retVal = OPTIMAL; // Perform heuristic initial pivots if (!initialPivots()) return UNBOUNDED; -#if DEBUG_LVL>0 - int niter=0; -#endif int iter_number=0; //pivot.setDantzig(true); // Execute the Network Simplex algorithm @@ -1431,12 +1432,13 @@ namespace lemon { char errMess[1000]; sprintf( errMess, "RESULT MIGHT BE INACURATE\nMax number of iteration reached, currently \%d. Sometimes iterations go on in cycle even though the solution has been reached, to check if it's the case here have a look at the minimal reduced cost. If it is very close to machine precision, you might actually have the correct solution, if not try setting the maximum number of iterations a bit higher\n",iter_number ); std::cerr << errMess; + retVal = MAX_ITER_REACHED; break; } #if DEBUG_LVL>0 - if(niter>MAX_DEBUG_ITER) + if(iter_number>MAX_DEBUG_ITER) break; - if(++niter%1000==0||niter%1000==1){ + if(iter_number%1000==0||iter_number%1000==1){ double curCost=totalCost(); double sumFlow=0; double a; @@ -1445,7 +1447,7 @@ namespace lemon { for (int i=0; i<_flow.size(); i++) { sumFlow+=_state[i]*_flow[i]; } - std::cout << "Sum of the flow " << std::setprecision(20) << sumFlow << "\n" << niter << " iterations, current cost=" << curCost << "\nReduced cost=" << _state[in_arc] * (_cost[in_arc] + _pi[_source[in_arc]] -_pi[_target[in_arc]]) << "\nPrecision = "<< -EPSILON*(a) << "\n"; + std::cout << "Sum of the flow " << std::setprecision(20) << sumFlow << "\n" << iter_number << " iterations, current cost=" << curCost << "\nReduced cost=" << _state[in_arc] * (_cost[in_arc] + _pi[_source[in_arc]] -_pi[_target[in_arc]]) << "\nPrecision = "<< -EPSILON*(a) << "\n"; std::cout << "Arc in = (" << _node_id(_source[in_arc]) << ", " << _node_id(_target[in_arc]) <<")\n"; std::cout << "Supplies = (" << _supply[_source[in_arc]] << ", " << _supply[_target[in_arc]] << ")\n"; std::cout << _cost[in_arc] << "\n"; @@ -1503,15 +1505,17 @@ namespace lemon { std::cout << "Sum of the flow " << sumFlow << "\n"<< niter <<" iterations, current cost=" << totalCost() << "\n"; #endif // Check feasibility - for (int e = _search_arc_num; e != _all_arc_num; ++e) { - if (_flow[e] != 0){ - if (abs(_flow[e]) > EPSILON) - return INFEASIBLE; - else - _flow[e]=0; + if( retVal == OPTIMAL){ + for (int e = _search_arc_num; e != _all_arc_num; ++e) { + if (_flow[e] != 0){ + if (abs(_flow[e]) > EPSILON) + return INFEASIBLE; + else + _flow[e]=0; + } } - } + } // Shift potentials to meet the requirements of the GEQ/LEQ type // optimality conditions @@ -1537,7 +1541,7 @@ namespace lemon { } } - return OPTIMAL; + return retVal; } }; //class NetworkSimplexSimple diff --git a/test/test_ot.py b/test/test_ot.py index 6f0f7c9..8a19cf6 100644 --- a/test/test_ot.py +++ b/test/test_ot.py @@ -8,6 +8,7 @@ import numpy as np import ot from ot.datasets import get_1D_gauss as gauss +import warnings def test_doctest(): @@ -100,9 +101,56 @@ def test_emd2_multi(): np.testing.assert_allclose(emd1, emdn) -def test_dual_variables(): - # %% parameters +def test_warnings(): + n = 100 # nb bins + m = 100 # nb bins + + mean1 = 30 + mean2 = 50 + + # bin positions + x = np.arange(n, dtype=np.float64) + y = np.arange(m, dtype=np.float64) + + # Gaussian distributions + a = gauss(n, m=mean1, s=5) # m= mean, s= std + + b = gauss(m, m=mean2, s=10) + # loss matrix + M = ot.dist(x.reshape((-1, 1)), y.reshape((-1, 1))) ** (1. / 2) + # M/=M.max() + + # %% + + print('Computing {} EMD '.format(1)) + G, alpha, beta = ot.emd(a, b, M, dual_variables=True) + with warnings.catch_warnings(record=True) as w: + # Cause all warnings to always be triggered. + warnings.simplefilter("always") + # Trigger a warning. + print('Computing {} EMD '.format(1)) + G, alpha, beta = ot.emd(a, b, M, dual_variables=True, numItermax=1) + # Verify some things + assert "numItermax" in str(w[-1].message) + assert len(w) == 1 + # Trigger a warning. + a[0]=100 + print('Computing {} EMD '.format(2)) + G, alpha, beta = ot.emd(a, b, M, dual_variables=True) + # Verify some things + assert "infeasible" in str(w[-1].message) + assert len(w) == 2 + # Trigger a warning. + a[0]=-1 + print('Computing {} EMD '.format(2)) + G, alpha, beta = ot.emd(a, b, M, dual_variables=True) + # Verify some things + assert "infeasible" in str(w[-1].message) + assert len(w) == 3 + + +def test_dual_variables(): n = 5000 # nb bins m = 6000 # nb bins -- cgit v1.2.3 From 12d9b3ff72e9669ccc0162e82b7a33beb51d3e25 Mon Sep 17 00:00:00 2001 From: Antoine Rolet Date: Thu, 7 Sep 2017 13:50:41 +0900 Subject: Return dual variables in an optional dictionary Also removed some code duplication --- ot/lp/__init__.py | 24 +++++++++++++------ ot/lp/emd_wrap.pyx | 69 +----------------------------------------------------- test/test_ot.py | 20 ++++++---------- 3 files changed, 25 insertions(+), 88 deletions(-) (limited to 'test') diff --git a/ot/lp/__init__.py b/ot/lp/__init__.py index 6048f60..c15e6b9 100644 --- a/ot/lp/__init__.py +++ b/ot/lp/__init__.py @@ -9,12 +9,12 @@ Solvers for the original linear program OT problem import numpy as np # import compiled emd -from .emd_wrap import emd_c, emd2_c +from .emd_wrap import emd_c from ..utils import parmap import multiprocessing -def emd(a, b, M, numItermax=100000, dual_variables=False): +def emd(a, b, M, numItermax=100000, log=False): """Solves the Earth Movers distance problem and returns the OT matrix @@ -42,11 +42,17 @@ def emd(a, b, M, numItermax=100000, dual_variables=False): numItermax : int, optional (default=100000) The maximum number of iterations before stopping the optimization algorithm if it has not converged. + log: boolean, optional (default=False) + If True, returns a dictionary containing the cost and dual + variables. Otherwise returns only the optimal transportation matrix. Returns ------- gamma: (ns x nt) ndarray Optimal transportation matrix for the given parameters + log: dict + If input log is true, a dictionary containing the cost and dual + variables Examples @@ -86,9 +92,13 @@ def emd(a, b, M, numItermax=100000, dual_variables=False): if len(b) == 0: b = np.ones((M.shape[1], ), dtype=np.float64)/M.shape[1] - G, alpha, beta = emd_c(a, b, M, numItermax) - if dual_variables: - return G, alpha, beta + G, cost, u, v = emd_c(a, b, M, numItermax) + if log: + log = {} + log['cost'] = cost + log['u'] = u + log['v'] = v + return G, log return G def emd2(a, b, M, processes=multiprocessing.cpu_count(), numItermax=100000): @@ -163,11 +173,11 @@ def emd2(a, b, M, processes=multiprocessing.cpu_count(), numItermax=100000): b = np.ones((M.shape[1], ), dtype=np.float64)/M.shape[1] if len(b.shape)==1: - return emd2_c(a, b, M, numItermax)[0] + return emd_c(a, b, M, numItermax)[1] nb = b.shape[1] # res = [emd2_c(a, b[:, i].copy(), M, numItermax) for i in range(nb)] def f(b): - return emd2_c(a,b,M, numItermax)[0] + return emd_c(a,b,M, numItermax)[1] res= parmap(f, [b[:,i] for i in range(nb)],processes) return np.array(res) diff --git a/ot/lp/emd_wrap.pyx b/ot/lp/emd_wrap.pyx index 9bea154..5618dfc 100644 --- a/ot/lp/emd_wrap.pyx +++ b/ot/lp/emd_wrap.pyx @@ -86,71 +86,4 @@ def emd_c( np.ndarray[double, ndim=1, mode="c"] a,np.ndarray[double, ndim=1, mod elif resultSolver == MAX_ITER_REACHED: warnings.warn("numItermax reached before optimality. Try to increase numItermax.") - return G, alpha, beta - -@cython.boundscheck(False) -@cython.wraparound(False) -def emd2_c( np.ndarray[double, ndim=1, mode="c"] a,np.ndarray[double, ndim=1, mode="c"] b,np.ndarray[double, ndim=2, mode="c"] M, int numItermax): - """ - Solves the Earth Movers distance problem and returns the optimal transport loss - - gamm=emd(a,b,M) - - .. math:: - \gamma = arg\min_\gamma <\gamma,M>_F - - s.t. \gamma 1 = a - - \gamma^T 1= b - - \gamma\geq 0 - where : - - - M is the metric cost matrix - - a and b are the sample weights - - Parameters - ---------- - a : (ns,) ndarray, float64 - source histogram - b : (nt,) ndarray, float64 - target histogram - M : (ns,nt) ndarray, float64 - loss matrix - numItermax : int - The maximum number of iterations before stopping the optimization - algorithm if it has not converged. - - - Returns - ------- - gamma: (ns x nt) ndarray - Optimal transportation matrix for the given parameters - - """ - cdef int n1= M.shape[0] - cdef int n2= M.shape[1] - - cdef double cost=0 - cdef np.ndarray[double, ndim=2, mode="c"] G=np.zeros([n1, n2]) - - cdef np.ndarray[double, ndim = 1, mode = "c"] alpha = np.zeros([n1]) - cdef np.ndarray[double, ndim = 1, mode = "c"] beta = np.zeros([n2]) - - if not len(a): - a=np.ones((n1,))/n1 - - if not len(b): - b=np.ones((n2,))/n2 - # calling the function - cdef int resultSolver = EMD_wrap(n1,n2, a.data, b.data, M.data, G.data, alpha.data, beta.data, &cost, numItermax) - if resultSolver != OPTIMAL: - if resultSolver == INFEASIBLE: - warnings.warn("Problem infeasible. Check that a and b are in the simplex") - elif resultSolver == UNBOUNDED: - warnings.warn("Problem unbounded") - elif resultSolver == MAX_ITER_REACHED: - warnings.warn("numItermax reached before optimality. Try to increase numItermax.") - - return cost, alpha, beta - + return G, cost, alpha, beta diff --git a/test/test_ot.py b/test/test_ot.py index 8a19cf6..78f64ab 100644 --- a/test/test_ot.py +++ b/test/test_ot.py @@ -124,27 +124,26 @@ def test_warnings(): # %% print('Computing {} EMD '.format(1)) - G, alpha, beta = ot.emd(a, b, M, dual_variables=True) with warnings.catch_warnings(record=True) as w: # Cause all warnings to always be triggered. warnings.simplefilter("always") # Trigger a warning. print('Computing {} EMD '.format(1)) - G, alpha, beta = ot.emd(a, b, M, dual_variables=True, numItermax=1) + G = ot.emd(a, b, M, numItermax=1) # Verify some things assert "numItermax" in str(w[-1].message) assert len(w) == 1 # Trigger a warning. a[0]=100 print('Computing {} EMD '.format(2)) - G, alpha, beta = ot.emd(a, b, M, dual_variables=True) + G = ot.emd(a, b, M) # Verify some things assert "infeasible" in str(w[-1].message) assert len(w) == 2 # Trigger a warning. a[0]=-1 print('Computing {} EMD '.format(2)) - G, alpha, beta = ot.emd(a, b, M, dual_variables=True) + G = ot.emd(a, b, M) # Verify some things assert "infeasible" in str(w[-1].message) assert len(w) == 3 @@ -176,16 +175,11 @@ def test_dual_variables(): # emd loss 1 proc ot.tic() - G, alpha, beta = ot.emd(a, b, M, dual_variables=True) + G, log = ot.emd(a, b, M, log=True) ot.toc('1 proc : {} s') cost1 = (G * M).sum() - cost_dual = np.vdot(a, alpha) + np.vdot(b, beta) - - # emd loss 1 proc - ot.tic() - cost_emd2 = ot.emd2(a, b, M) - ot.toc('1 proc : {} s') + cost_dual = np.vdot(a, log['u']) + np.vdot(b, log['v']) ot.tic() G2 = ot.emd(b, a, np.ascontiguousarray(M.T)) @@ -194,7 +188,7 @@ def test_dual_variables(): cost2 = (G2 * M.T).sum() # Check that both cost computations are equivalent - np.testing.assert_almost_equal(cost1, cost_emd2) + np.testing.assert_almost_equal(cost1, log['cost']) # Check that dual and primal cost are equal np.testing.assert_almost_equal(cost1, cost_dual) # Check symmetry @@ -205,5 +199,5 @@ def test_dual_variables(): [ind1, ind2] = np.nonzero(G) # Check that reduced cost is zero on transport arcs - np.testing.assert_array_almost_equal((M - alpha.reshape(-1, 1) - beta.reshape(1, -1))[ind1, ind2], + np.testing.assert_array_almost_equal((M - log['u'].reshape(-1, 1) - log['v'].reshape(1, -1))[ind1, ind2], np.zeros(ind1.size)) -- cgit v1.2.3 From ab65f86304b03a967054eeeaf73b8c8277618d65 Mon Sep 17 00:00:00 2001 From: Antoine Rolet Date: Thu, 7 Sep 2017 14:35:35 +0900 Subject: Added log option to muliprocess emd --- ot/lp/__init__.py | 39 ++++++++++++++++++++++++------------- test/test_ot.py | 57 ++++++++++++++++++++++++++++++------------------------- 2 files changed, 57 insertions(+), 39 deletions(-) (limited to 'test') diff --git a/ot/lp/__init__.py b/ot/lp/__init__.py index c15e6b9..8edd8ec 100644 --- a/ot/lp/__init__.py +++ b/ot/lp/__init__.py @@ -7,11 +7,13 @@ Solvers for the original linear program OT problem # # License: MIT License +import multiprocessing + import numpy as np + # import compiled emd from .emd_wrap import emd_c from ..utils import parmap -import multiprocessing def emd(a, b, M, numItermax=100000, log=False): @@ -88,9 +90,9 @@ def emd(a, b, M, numItermax=100000, log=False): # if empty array given then use unifor distributions if len(a) == 0: - a = np.ones((M.shape[0], ), dtype=np.float64)/M.shape[0] + a = np.ones((M.shape[0],), dtype=np.float64) / M.shape[0] if len(b) == 0: - b = np.ones((M.shape[1], ), dtype=np.float64)/M.shape[1] + b = np.ones((M.shape[1],), dtype=np.float64) / M.shape[1] G, cost, u, v = emd_c(a, b, M, numItermax) if log: @@ -101,7 +103,8 @@ def emd(a, b, M, numItermax=100000, log=False): return G, log return G -def emd2(a, b, M, processes=multiprocessing.cpu_count(), numItermax=100000): + +def emd2(a, b, M, processes=multiprocessing.cpu_count(), numItermax=100000, log=False): """Solves the Earth Movers distance problem and returns the loss .. math:: @@ -168,16 +171,26 @@ def emd2(a, b, M, processes=multiprocessing.cpu_count(), numItermax=100000): # if empty array given then use unifor distributions if len(a) == 0: - a = np.ones((M.shape[0], ), dtype=np.float64)/M.shape[0] + a = np.ones((M.shape[0],), dtype=np.float64) / M.shape[0] if len(b) == 0: - b = np.ones((M.shape[1], ), dtype=np.float64)/M.shape[1] - - if len(b.shape)==1: - return emd_c(a, b, M, numItermax)[1] + b = np.ones((M.shape[1],), dtype=np.float64) / M.shape[1] + + if log: + def f(b): + G, cost, u, v = emd_c(a, b, M, numItermax) + log = {} + log['G'] = G + log['u'] = u + log['v'] = v + return [cost, log] + else: + def f(b): + return emd_c(a, b, M, numItermax)[1] + + if len(b.shape) == 1: + return f(b) nb = b.shape[1] # res = [emd2_c(a, b[:, i].copy(), M, numItermax) for i in range(nb)] - def f(b): - return emd_c(a,b,M, numItermax)[1] - res= parmap(f, [b[:,i] for i in range(nb)],processes) - return np.array(res) + res = parmap(f, [b[:, i] for i in range(nb)], processes) + return res diff --git a/test/test_ot.py b/test/test_ot.py index 78f64ab..feadef4 100644 --- a/test/test_ot.py +++ b/test/test_ot.py @@ -4,11 +4,12 @@ # # License: MIT License +import warnings + import numpy as np import ot from ot.datasets import get_1D_gauss as gauss -import warnings def test_doctest(): @@ -100,6 +101,21 @@ def test_emd2_multi(): np.testing.assert_allclose(emd1, emdn) + # emd loss multipro proc with log + ot.tic() + emdn = ot.emd2(a, b, M, log=True) + ot.toc('multi proc : {} s') + + for i in range(len(emdn)): + emd = emdn[i] + log = emd[1] + cost = emd[0] + check_duality_gap(a, b[:, i], M, log['G'], log['u'], log['v'], cost) + emdn[i] = cost + + emdn = np.array(emdn) + np.testing.assert_allclose(emd1, emdn) + def test_warnings(): n = 100 # nb bins @@ -119,32 +135,22 @@ def test_warnings(): # loss matrix M = ot.dist(x.reshape((-1, 1)), y.reshape((-1, 1))) ** (1. / 2) - # M/=M.max() - - # %% print('Computing {} EMD '.format(1)) with warnings.catch_warnings(record=True) as w: - # Cause all warnings to always be triggered. warnings.simplefilter("always") - # Trigger a warning. print('Computing {} EMD '.format(1)) G = ot.emd(a, b, M, numItermax=1) - # Verify some things assert "numItermax" in str(w[-1].message) assert len(w) == 1 - # Trigger a warning. - a[0]=100 + a[0] = 100 print('Computing {} EMD '.format(2)) G = ot.emd(a, b, M) - # Verify some things assert "infeasible" in str(w[-1].message) assert len(w) == 2 - # Trigger a warning. - a[0]=-1 + a[0] = -1 print('Computing {} EMD '.format(2)) G = ot.emd(a, b, M) - # Verify some things assert "infeasible" in str(w[-1].message) assert len(w) == 3 @@ -167,9 +173,6 @@ def test_dual_variables(): # loss matrix M = ot.dist(x.reshape((-1, 1)), y.reshape((-1, 1))) ** (1. / 2) - # M/=M.max() - - # %% print('Computing {} EMD '.format(1)) @@ -178,26 +181,28 @@ def test_dual_variables(): G, log = ot.emd(a, b, M, log=True) ot.toc('1 proc : {} s') - cost1 = (G * M).sum() - cost_dual = np.vdot(a, log['u']) + np.vdot(b, log['v']) - ot.tic() G2 = ot.emd(b, a, np.ascontiguousarray(M.T)) ot.toc('1 proc : {} s') - cost2 = (G2 * M.T).sum() + cost1 = (G * M).sum() + # Check symmetry + np.testing.assert_array_almost_equal(cost1, (M * G2.T).sum()) + # Check with closed-form solution for gaussians + np.testing.assert_almost_equal(cost1, np.abs(mean1 - mean2)) # Check that both cost computations are equivalent np.testing.assert_almost_equal(cost1, log['cost']) + check_duality_gap(a, b, M, G, log['u'], log['v'], log['cost']) + + +def check_duality_gap(a, b, M, G, u, v, cost): + cost_dual = np.vdot(a, u) + np.vdot(b, v) # Check that dual and primal cost are equal - np.testing.assert_almost_equal(cost1, cost_dual) - # Check symmetry - np.testing.assert_almost_equal(cost1, cost2) - # Check with closed-form solution for gaussians - np.testing.assert_almost_equal(cost1, np.abs(mean1 - mean2)) + np.testing.assert_almost_equal(cost_dual, cost) [ind1, ind2] = np.nonzero(G) # Check that reduced cost is zero on transport arcs - np.testing.assert_array_almost_equal((M - log['u'].reshape(-1, 1) - log['v'].reshape(1, -1))[ind1, ind2], + np.testing.assert_array_almost_equal((M - u.reshape(-1, 1) - v.reshape(1, -1))[ind1, ind2], np.zeros(ind1.size)) -- cgit v1.2.3 From a37e52e64f300fa0165a58932d5ac0ef1dd8c6f7 Mon Sep 17 00:00:00 2001 From: Antoine Rolet Date: Thu, 7 Sep 2017 14:38:53 +0900 Subject: Removed unused variable declaration --- test/test_ot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'test') diff --git a/test/test_ot.py b/test/test_ot.py index feadef4..cf5839e 100644 --- a/test/test_ot.py +++ b/test/test_ot.py @@ -140,17 +140,17 @@ def test_warnings(): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") print('Computing {} EMD '.format(1)) - G = ot.emd(a, b, M, numItermax=1) + ot.emd(a, b, M, numItermax=1) assert "numItermax" in str(w[-1].message) assert len(w) == 1 a[0] = 100 print('Computing {} EMD '.format(2)) - G = ot.emd(a, b, M) + ot.emd(a, b, M) assert "infeasible" in str(w[-1].message) assert len(w) == 2 a[0] = -1 print('Computing {} EMD '.format(2)) - G = ot.emd(a, b, M) + ot.emd(a, b, M) assert "infeasible" in str(w[-1].message) assert len(w) == 3 -- cgit v1.2.3 From 85c56d96f609c4ad458f0963a068386cc910c66c Mon Sep 17 00:00:00 2001 From: Antoine Rolet Date: Sat, 9 Sep 2017 17:28:38 +0900 Subject: Renamed variables --- ot/da.py | 2 +- ot/lp/__init__.py | 31 +++++++++++++++++-------------- ot/lp/emd_wrap.pyx | 18 +++++++++--------- test/test_ot.py | 2 +- 4 files changed, 28 insertions(+), 25 deletions(-) (limited to 'test') diff --git a/ot/da.py b/ot/da.py index 1d3d0ba..eb70305 100644 --- a/ot/da.py +++ b/ot/da.py @@ -1370,7 +1370,7 @@ class EMDTransport(BaseTransport): # coupling estimation self.coupling_ = emd( - a=self.mu_s, b=self.mu_t, M=self.cost_, numItermax=self.max_iter + a=self.mu_s, b=self.mu_t, M=self.cost_, num_iter_max=self.max_iter ) return self diff --git a/ot/lp/__init__.py b/ot/lp/__init__.py index 0f40c19..ab7cb97 100644 --- a/ot/lp/__init__.py +++ b/ot/lp/__init__.py @@ -12,11 +12,11 @@ import multiprocessing import numpy as np # import compiled emd -from .emd_wrap import emd_c, checkResult +from .emd_wrap import emd_c, check_result from ..utils import parmap -def emd(a, b, M, numItermax=100000, log=False): +def emd(a, b, M, num_iter_max=100000, log=False): """Solves the Earth Movers distance problem and returns the OT matrix @@ -41,7 +41,7 @@ def emd(a, b, M, numItermax=100000, log=False): Target histogram (uniform weigth if empty list) M : (ns,nt) ndarray, float64 loss matrix - numItermax : int, optional (default=100000) + num_iter_max : int, optional (default=100000) The maximum number of iterations before stopping the optimization algorithm if it has not converged. log: boolean, optional (default=False) @@ -54,7 +54,7 @@ def emd(a, b, M, numItermax=100000, log=False): Optimal transportation matrix for the given parameters log: dict If input log is true, a dictionary containing the cost and dual - variables + variables and exit status Examples @@ -94,20 +94,20 @@ def emd(a, b, M, numItermax=100000, log=False): if len(b) == 0: b = np.ones((M.shape[1],), dtype=np.float64) / M.shape[1] - G, cost, u, v, resultCode = emd_c(a, b, M, numItermax) - resultCodeString = checkResult(resultCode) + G, cost, u, v, result_code = emd_c(a, b, M, num_iter_max) + resultCodeString = check_result(result_code) if log: log = {} log['cost'] = cost log['u'] = u log['v'] = v log['warning'] = resultCodeString - log['resultCode'] = resultCode + log['result_code'] = result_code return G, log return G -def emd2(a, b, M, processes=multiprocessing.cpu_count(), numItermax=100000, log=False): +def emd2(a, b, M, processes=multiprocessing.cpu_count(), num_iter_max=100000, log=False): """Solves the Earth Movers distance problem and returns the loss .. math:: @@ -131,7 +131,7 @@ def emd2(a, b, M, processes=multiprocessing.cpu_count(), numItermax=100000, log= Target histogram (uniform weigth if empty list) M : (ns,nt) ndarray, float64 loss matrix - numItermax : int, optional (default=100000) + num_iter_max : int, optional (default=100000) The maximum number of iterations before stopping the optimization algorithm if it has not converged. @@ -139,6 +139,9 @@ def emd2(a, b, M, processes=multiprocessing.cpu_count(), numItermax=100000, log= ------- gamma: (ns x nt) ndarray Optimal transportation matrix for the given parameters + log: dict + If input log is true, a dictionary containing the cost and dual + variables and exit status Examples @@ -180,19 +183,19 @@ def emd2(a, b, M, processes=multiprocessing.cpu_count(), numItermax=100000, log= if log: def f(b): - G, cost, u, v, resultCode = emd_c(a, b, M, numItermax) - resultCodeString = checkResult(resultCode) + G, cost, u, v, resultCode = emd_c(a, b, M, num_iter_max) + resultCodeString = check_result(resultCode) log = {} log['G'] = G log['u'] = u log['v'] = v log['warning'] = resultCodeString - log['resultCode'] = resultCode + log['result_code'] = resultCode return [cost, log] else: def f(b): - G, cost, u, v, resultCode = emd_c(a, b, M, numItermax) - checkResult(resultCode) + G, cost, u, v, result_code = emd_c(a, b, M, num_iter_max) + check_result(result_code) return cost if len(b.shape) == 1: diff --git a/ot/lp/emd_wrap.pyx b/ot/lp/emd_wrap.pyx index 19bcdd8..7ebdd2a 100644 --- a/ot/lp/emd_wrap.pyx +++ b/ot/lp/emd_wrap.pyx @@ -20,15 +20,15 @@ cdef extern from "EMD.h": cdef enum ProblemType: INFEASIBLE, OPTIMAL, UNBOUNDED, MAX_ITER_REACHED -def checkResult(resultCode): - if resultCode == OPTIMAL: +def check_result(result_code): + if result_code == OPTIMAL: return None - if resultCode == INFEASIBLE: + if result_code == INFEASIBLE: message = "Problem infeasible. Check that a and b are in the simplex" - elif resultCode == UNBOUNDED: + elif result_code == UNBOUNDED: message = "Problem unbounded" - elif resultCode == MAX_ITER_REACHED: + elif result_code == MAX_ITER_REACHED: message = "numItermax reached before optimality. Try to increase numItermax." warnings.warn(message) return message @@ -36,7 +36,7 @@ def checkResult(resultCode): @cython.boundscheck(False) @cython.wraparound(False) -def emd_c( np.ndarray[double, ndim=1, mode="c"] a,np.ndarray[double, ndim=1, mode="c"] b,np.ndarray[double, ndim=2, mode="c"] M, int numItermax): +def emd_c(np.ndarray[double, ndim=1, mode="c"] a, np.ndarray[double, ndim=1, mode="c"] b, np.ndarray[double, ndim=2, mode="c"] M, int num_iter_max): """ Solves the Earth Movers distance problem and returns the optimal transport matrix @@ -63,7 +63,7 @@ def emd_c( np.ndarray[double, ndim=1, mode="c"] a,np.ndarray[double, ndim=1, mod target histogram M : (ns,nt) ndarray, float64 loss matrix - numItermax : int + num_iter_max : int The maximum number of iterations before stopping the optimization algorithm if it has not converged. @@ -90,6 +90,6 @@ def emd_c( np.ndarray[double, ndim=1, mode="c"] a,np.ndarray[double, ndim=1, mod b=np.ones((n2,))/n2 # calling the function - cdef int resultCode = EMD_wrap(n1,n2, a.data, b.data, M.data, G.data, alpha.data, beta.data, &cost, numItermax) + cdef int result_code = EMD_wrap(n1, n2, a.data, b.data, M.data, G.data, alpha.data, beta.data, &cost, num_iter_max) - return G, cost, alpha, beta, resultCode + return G, cost, alpha, beta, result_code diff --git a/test/test_ot.py b/test/test_ot.py index cf5839e..c9b5154 100644 --- a/test/test_ot.py +++ b/test/test_ot.py @@ -140,7 +140,7 @@ def test_warnings(): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") print('Computing {} EMD '.format(1)) - ot.emd(a, b, M, numItermax=1) + ot.emd(a, b, M, num_iter_max=1) assert "numItermax" in str(w[-1].message) assert len(w) == 1 a[0] = 100 -- cgit v1.2.3 From cd8c04246b6d1f15b68d6433741e8c808fd517d8 Mon Sep 17 00:00:00 2001 From: Antoine Rolet Date: Sat, 9 Sep 2017 17:38:31 +0900 Subject: Renamed variable --- ot/da.py | 2 +- ot/lp/EMD.h | 2 +- ot/lp/EMD_wrapper.cpp | 4 ++-- ot/lp/__init__.py | 14 +++++++------- ot/lp/emd_wrap.pyx | 8 ++++---- test/test_ot.py | 2 +- 6 files changed, 16 insertions(+), 16 deletions(-) (limited to 'test') diff --git a/ot/da.py b/ot/da.py index eb70305..f3e7433 100644 --- a/ot/da.py +++ b/ot/da.py @@ -1370,7 +1370,7 @@ class EMDTransport(BaseTransport): # coupling estimation self.coupling_ = emd( - a=self.mu_s, b=self.mu_t, M=self.cost_, num_iter_max=self.max_iter + a=self.mu_s, b=self.mu_t, M=self.cost_, max_iter=self.max_iter ) return self diff --git a/ot/lp/EMD.h b/ot/lp/EMD.h index bb486de..f42e222 100644 --- a/ot/lp/EMD.h +++ b/ot/lp/EMD.h @@ -30,6 +30,6 @@ enum ProblemType { MAX_ITER_REACHED }; -int EMD_wrap(int n1,int n2, double *X, double *Y,double *D, double *G, double* alpha, double* beta, double *cost, int max_iter); +int EMD_wrap(int n1,int n2, double *X, double *Y,double *D, double *G, double* alpha, double* beta, double *cost, int maxIter); #endif diff --git a/ot/lp/EMD_wrapper.cpp b/ot/lp/EMD_wrapper.cpp index 92663dc..fc7ca63 100644 --- a/ot/lp/EMD_wrapper.cpp +++ b/ot/lp/EMD_wrapper.cpp @@ -16,7 +16,7 @@ int EMD_wrap(int n1, int n2, double *X, double *Y, double *D, double *G, - double* alpha, double* beta, double *cost, int max_iter) { + double* alpha, double* beta, double *cost, int maxIter) { // beware M and C anre strored in row major C style!!! int n, m, i, cur; @@ -48,7 +48,7 @@ int EMD_wrap(int n1, int n2, double *X, double *Y, double *D, double *G, std::vector indI(n), indJ(m); std::vector weights1(n), weights2(m); Digraph di(n, m); - NetworkSimplexSimple net(di, true, n+m, n*m, max_iter); + NetworkSimplexSimple net(di, true, n+m, n*m, maxIter); // Set supply and demand, don't account for 0 values (faster) diff --git a/ot/lp/__init__.py b/ot/lp/__init__.py index 1238cdb..9a0cb1c 100644 --- a/ot/lp/__init__.py +++ b/ot/lp/__init__.py @@ -16,7 +16,7 @@ from .emd_wrap import emd_c, check_result from ..utils import parmap -def emd(a, b, M, num_iter_max=100000, log=False): +def emd(a, b, M, max_iter=100000, log=False): """Solves the Earth Movers distance problem and returns the OT matrix @@ -41,7 +41,7 @@ def emd(a, b, M, num_iter_max=100000, log=False): Target histogram (uniform weigth if empty list) M : (ns,nt) ndarray, float64 loss matrix - num_iter_max : int, optional (default=100000) + max_iter : int, optional (default=100000) The maximum number of iterations before stopping the optimization algorithm if it has not converged. log: boolean, optional (default=False) @@ -94,7 +94,7 @@ def emd(a, b, M, num_iter_max=100000, log=False): if len(b) == 0: b = np.ones((M.shape[1],), dtype=np.float64) / M.shape[1] - G, cost, u, v, result_code = emd_c(a, b, M, num_iter_max) + G, cost, u, v, result_code = emd_c(a, b, M, max_iter) result_code_string = check_result(result_code) if log: log = {} @@ -107,7 +107,7 @@ def emd(a, b, M, num_iter_max=100000, log=False): return G -def emd2(a, b, M, processes=multiprocessing.cpu_count(), num_iter_max=100000, log=False): +def emd2(a, b, M, processes=multiprocessing.cpu_count(), max_iter=100000, log=False): """Solves the Earth Movers distance problem and returns the loss .. math:: @@ -131,7 +131,7 @@ def emd2(a, b, M, processes=multiprocessing.cpu_count(), num_iter_max=100000, lo Target histogram (uniform weigth if empty list) M : (ns,nt) ndarray, float64 loss matrix - num_iter_max : int, optional (default=100000) + max_iter : int, optional (default=100000) The maximum number of iterations before stopping the optimization algorithm if it has not converged. @@ -183,7 +183,7 @@ def emd2(a, b, M, processes=multiprocessing.cpu_count(), num_iter_max=100000, lo if log: def f(b): - G, cost, u, v, resultCode = emd_c(a, b, M, num_iter_max) + G, cost, u, v, resultCode = emd_c(a, b, M, max_iter) result_code_string = check_result(resultCode) log = {} log['G'] = G @@ -194,7 +194,7 @@ def emd2(a, b, M, processes=multiprocessing.cpu_count(), num_iter_max=100000, lo return [cost, log] else: def f(b): - G, cost, u, v, result_code = emd_c(a, b, M, num_iter_max) + G, cost, u, v, result_code = emd_c(a, b, M, max_iter) check_result(result_code) return cost diff --git a/ot/lp/emd_wrap.pyx b/ot/lp/emd_wrap.pyx index 7ebdd2a..83ee6aa 100644 --- a/ot/lp/emd_wrap.pyx +++ b/ot/lp/emd_wrap.pyx @@ -16,7 +16,7 @@ import warnings cdef extern from "EMD.h": - int EMD_wrap(int n1,int n2, double *X, double *Y,double *D, double *G, double* alpha, double* beta, double *cost, int numItermax) + int EMD_wrap(int n1,int n2, double *X, double *Y,double *D, double *G, double* alpha, double* beta, double *cost, int maxIter) cdef enum ProblemType: INFEASIBLE, OPTIMAL, UNBOUNDED, MAX_ITER_REACHED @@ -36,7 +36,7 @@ def check_result(result_code): @cython.boundscheck(False) @cython.wraparound(False) -def emd_c(np.ndarray[double, ndim=1, mode="c"] a, np.ndarray[double, ndim=1, mode="c"] b, np.ndarray[double, ndim=2, mode="c"] M, int num_iter_max): +def emd_c(np.ndarray[double, ndim=1, mode="c"] a, np.ndarray[double, ndim=1, mode="c"] b, np.ndarray[double, ndim=2, mode="c"] M, int max_iter): """ Solves the Earth Movers distance problem and returns the optimal transport matrix @@ -63,7 +63,7 @@ def emd_c(np.ndarray[double, ndim=1, mode="c"] a, np.ndarray[double, ndim=1, mod target histogram M : (ns,nt) ndarray, float64 loss matrix - num_iter_max : int + max_iter : int The maximum number of iterations before stopping the optimization algorithm if it has not converged. @@ -90,6 +90,6 @@ def emd_c(np.ndarray[double, ndim=1, mode="c"] a, np.ndarray[double, ndim=1, mod b=np.ones((n2,))/n2 # calling the function - cdef int result_code = EMD_wrap(n1, n2, a.data, b.data, M.data, G.data, alpha.data, beta.data, &cost, num_iter_max) + cdef int result_code = EMD_wrap(n1, n2, a.data, b.data, M.data, G.data, alpha.data, beta.data, &cost, max_iter) return G, cost, alpha, beta, result_code diff --git a/test/test_ot.py b/test/test_ot.py index c9b5154..ca921c5 100644 --- a/test/test_ot.py +++ b/test/test_ot.py @@ -140,7 +140,7 @@ def test_warnings(): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") print('Computing {} EMD '.format(1)) - ot.emd(a, b, M, num_iter_max=1) + ot.emd(a, b, M, max_iter=1) assert "numItermax" in str(w[-1].message) assert len(w) == 1 a[0] = 100 -- cgit v1.2.3 From 8cc04ef5ae8806c81811b2081b1880b46ca063a3 Mon Sep 17 00:00:00 2001 From: Antoine Rolet Date: Sat, 9 Sep 2017 18:05:12 +0900 Subject: Renamed variable in string --- ot/lp/__init__.py | 1 - ot/lp/emd_wrap.pyx | 2 +- test/test_ot.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) (limited to 'test') diff --git a/ot/lp/__init__.py b/ot/lp/__init__.py index 9a0cb1c..ae5b08c 100644 --- a/ot/lp/__init__.py +++ b/ot/lp/__init__.py @@ -201,7 +201,6 @@ def emd2(a, b, M, processes=multiprocessing.cpu_count(), max_iter=100000, log=Fa if len(b.shape) == 1: return f(b) nb = b.shape[1] - # res = [emd2_c(a, b[:, i].copy(), M, numItermax) for i in range(nb)] res = parmap(f, [b[:, i] for i in range(nb)], processes) return res diff --git a/ot/lp/emd_wrap.pyx b/ot/lp/emd_wrap.pyx index 83ee6aa..2fcc0e4 100644 --- a/ot/lp/emd_wrap.pyx +++ b/ot/lp/emd_wrap.pyx @@ -29,7 +29,7 @@ def check_result(result_code): elif result_code == UNBOUNDED: message = "Problem unbounded" elif result_code == MAX_ITER_REACHED: - message = "numItermax reached before optimality. Try to increase numItermax." + message = "max_iter reached before optimality. Try to increase max_iter." warnings.warn(message) return message diff --git a/test/test_ot.py b/test/test_ot.py index ca921c5..46fc634 100644 --- a/test/test_ot.py +++ b/test/test_ot.py @@ -141,7 +141,7 @@ def test_warnings(): warnings.simplefilter("always") print('Computing {} EMD '.format(1)) ot.emd(a, b, M, max_iter=1) - assert "numItermax" in str(w[-1].message) + assert "max_iter" in str(w[-1].message) assert len(w) == 1 a[0] = 100 print('Computing {} EMD '.format(2)) -- cgit v1.2.3 From 06429e5a34790ec51eb1c921293b24c37b81b952 Mon Sep 17 00:00:00 2001 From: Antoine Rolet Date: Sat, 9 Sep 2017 18:23:05 +0900 Subject: Returned to old variable name to follow repo convention --- ot/da.py | 2 +- ot/lp/__init__.py | 12 ++++++------ ot/lp/emd_wrap.pyx | 2 +- test/test_ot.py | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) (limited to 'test') diff --git a/ot/da.py b/ot/da.py index f3e7433..eb70305 100644 --- a/ot/da.py +++ b/ot/da.py @@ -1370,7 +1370,7 @@ class EMDTransport(BaseTransport): # coupling estimation self.coupling_ = emd( - a=self.mu_s, b=self.mu_t, M=self.cost_, max_iter=self.max_iter + a=self.mu_s, b=self.mu_t, M=self.cost_, num_iter_max=self.max_iter ) return self diff --git a/ot/lp/__init__.py b/ot/lp/__init__.py index ae5b08c..17f5bb4 100644 --- a/ot/lp/__init__.py +++ b/ot/lp/__init__.py @@ -16,7 +16,7 @@ from .emd_wrap import emd_c, check_result from ..utils import parmap -def emd(a, b, M, max_iter=100000, log=False): +def emd(a, b, M, num_iter_max=100000, log=False): """Solves the Earth Movers distance problem and returns the OT matrix @@ -41,7 +41,7 @@ def emd(a, b, M, max_iter=100000, log=False): Target histogram (uniform weigth if empty list) M : (ns,nt) ndarray, float64 loss matrix - max_iter : int, optional (default=100000) + num_iter_max : int, optional (default=100000) The maximum number of iterations before stopping the optimization algorithm if it has not converged. log: boolean, optional (default=False) @@ -94,7 +94,7 @@ def emd(a, b, M, max_iter=100000, log=False): if len(b) == 0: b = np.ones((M.shape[1],), dtype=np.float64) / M.shape[1] - G, cost, u, v, result_code = emd_c(a, b, M, max_iter) + G, cost, u, v, result_code = emd_c(a, b, M, num_iter_max) result_code_string = check_result(result_code) if log: log = {} @@ -107,7 +107,7 @@ def emd(a, b, M, max_iter=100000, log=False): return G -def emd2(a, b, M, processes=multiprocessing.cpu_count(), max_iter=100000, log=False): +def emd2(a, b, M, processes=multiprocessing.cpu_count(), num_iter_max=100000, log=False): """Solves the Earth Movers distance problem and returns the loss .. math:: @@ -183,7 +183,7 @@ def emd2(a, b, M, processes=multiprocessing.cpu_count(), max_iter=100000, log=Fa if log: def f(b): - G, cost, u, v, resultCode = emd_c(a, b, M, max_iter) + G, cost, u, v, resultCode = emd_c(a, b, M, num_iter_max) result_code_string = check_result(resultCode) log = {} log['G'] = G @@ -194,7 +194,7 @@ def emd2(a, b, M, processes=multiprocessing.cpu_count(), max_iter=100000, log=Fa return [cost, log] else: def f(b): - G, cost, u, v, result_code = emd_c(a, b, M, max_iter) + G, cost, u, v, result_code = emd_c(a, b, M, num_iter_max) check_result(result_code) return cost diff --git a/ot/lp/emd_wrap.pyx b/ot/lp/emd_wrap.pyx index 2fcc0e4..45fc988 100644 --- a/ot/lp/emd_wrap.pyx +++ b/ot/lp/emd_wrap.pyx @@ -29,7 +29,7 @@ def check_result(result_code): elif result_code == UNBOUNDED: message = "Problem unbounded" elif result_code == MAX_ITER_REACHED: - message = "max_iter reached before optimality. Try to increase max_iter." + message = "num_iter_max reached before optimality. Try to increase num_iter_max." warnings.warn(message) return message diff --git a/test/test_ot.py b/test/test_ot.py index 46fc634..e05e8aa 100644 --- a/test/test_ot.py +++ b/test/test_ot.py @@ -140,8 +140,8 @@ def test_warnings(): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") print('Computing {} EMD '.format(1)) - ot.emd(a, b, M, max_iter=1) - assert "max_iter" in str(w[-1].message) + ot.emd(a, b, M, num_iter_max=1) + assert "num_iter_max" in str(w[-1].message) assert len(w) == 1 a[0] = 100 print('Computing {} EMD '.format(2)) -- cgit v1.2.3 From dd6f8260d01ce173ef3fe0c900112f0ed5288950 Mon Sep 17 00:00:00 2001 From: Antoine Rolet Date: Tue, 12 Sep 2017 19:58:46 +0900 Subject: Made the return of the matrix optional in emd2 --- ot/lp/__init__.py | 12 +++++++++--- test/test_ot.py | 6 +++--- 2 files changed, 12 insertions(+), 6 deletions(-) (limited to 'test') diff --git a/ot/lp/__init__.py b/ot/lp/__init__.py index f2eaa2b..d0f682b 100644 --- a/ot/lp/__init__.py +++ b/ot/lp/__init__.py @@ -107,7 +107,7 @@ def emd(a, b, M, num_iter_max=100000, log=False): return G -def emd2(a, b, M, processes=multiprocessing.cpu_count(), num_iter_max=100000, log=False): +def emd2(a, b, M, processes=multiprocessing.cpu_count(), num_iter_max=100000, log=False, return_matrix=False): """Solves the Earth Movers distance problem and returns the loss .. math:: @@ -134,6 +134,11 @@ def emd2(a, b, M, processes=multiprocessing.cpu_count(), num_iter_max=100000, lo num_iter_max : int, optional (default=100000) The maximum number of iterations before stopping the optimization algorithm if it has not converged. + log: boolean, optional (default=False) + If True, returns a dictionary containing the cost and dual + variables. Otherwise returns only the optimal transportation cost. + return_matrix: boolean, optional (default=False) + If True, returns the optimal transportation matrix in the log. Returns ------- @@ -181,12 +186,13 @@ def emd2(a, b, M, processes=multiprocessing.cpu_count(), num_iter_max=100000, lo if len(b) == 0: b = np.ones((M.shape[1],), dtype=np.float64) / M.shape[1] - if log: + if log or return_matrix: def f(b): G, cost, u, v, resultCode = emd_c(a, b, M, num_iter_max) result_code_string = check_result(resultCode) log = {} - log['G'] = G + if return_matrix: + log['G'] = G log['u'] = u log['v'] = v log['warning'] = result_code_string diff --git a/test/test_ot.py b/test/test_ot.py index e05e8aa..ea6d9dc 100644 --- a/test/test_ot.py +++ b/test/test_ot.py @@ -103,7 +103,7 @@ def test_emd2_multi(): # emd loss multipro proc with log ot.tic() - emdn = ot.emd2(a, b, M, log=True) + emdn = ot.emd2(a, b, M, log=True, return_matrix=True) ot.toc('multi proc : {} s') for i in range(len(emdn)): @@ -140,8 +140,8 @@ def test_warnings(): with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") print('Computing {} EMD '.format(1)) - ot.emd(a, b, M, num_iter_max=1) - assert "num_iter_max" in str(w[-1].message) + ot.emd(a, b, M, numItermax=1) + assert "numItermax" in str(w[-1].message) assert len(w) == 1 a[0] = 100 print('Computing {} EMD '.format(2)) -- cgit v1.2.3 From 36bf599552ff15d1ca1c6b505507e65a333fa55e Mon Sep 17 00:00:00 2001 From: Nicolas Courty Date: Tue, 12 Sep 2017 18:07:17 +0200 Subject: Corrections on Gromov --- data/carre.png | Bin 168 -> 0 bytes data/coeur.png | Bin 225 -> 0 bytes data/cross.png | Bin 0 -> 230 bytes data/rond.png | Bin 230 -> 0 bytes data/square.png | Bin 0 -> 168 bytes data/star.png | Bin 0 -> 225 bytes examples/plot_gromov.py | 5 +++-- examples/plot_gromov_barycenter.py | 13 +++++-------- test/test_gromov.py | 3 +-- 9 files changed, 9 insertions(+), 12 deletions(-) delete mode 100755 data/carre.png delete mode 100755 data/coeur.png create mode 100755 data/cross.png delete mode 100755 data/rond.png create mode 100755 data/square.png create mode 100755 data/star.png (limited to 'test') diff --git a/data/carre.png b/data/carre.png deleted file mode 100755 index 45ff0ef..0000000 Binary files a/data/carre.png and /dev/null differ diff --git a/data/coeur.png b/data/coeur.png deleted file mode 100755 index 3f511a6..0000000 Binary files a/data/coeur.png and /dev/null differ diff --git a/data/cross.png b/data/cross.png new file mode 100755 index 0000000..1c1a068 Binary files /dev/null and b/data/cross.png differ diff --git a/data/rond.png b/data/rond.png deleted file mode 100755 index 1c1a068..0000000 Binary files a/data/rond.png and /dev/null differ diff --git a/data/square.png b/data/square.png new file mode 100755 index 0000000..45ff0ef Binary files /dev/null and b/data/square.png differ diff --git a/data/star.png b/data/star.png new file mode 100755 index 0000000..3f511a6 Binary files /dev/null and b/data/star.png differ diff --git a/examples/plot_gromov.py b/examples/plot_gromov.py index 92312ae..0f839a3 100644 --- a/examples/plot_gromov.py +++ b/examples/plot_gromov.py @@ -22,8 +22,9 @@ import ot """ Sample two Gaussian distributions (2D and 3D) ============================================= -The Gromov-Wasserstein distance allows to compute distances with samples that do not belong to the same metric space. -For demonstration purpose, we sample two Gaussian distributions in 2- and 3-dimensional spaces. +The Gromov-Wasserstein distance allows to compute distances with samples that +do not belong to the same metric space. For demonstration purpose, we sample +two Gaussian distributions in 2- and 3-dimensional spaces. """ n_samples = 30 # nb samples diff --git a/examples/plot_gromov_barycenter.py b/examples/plot_gromov_barycenter.py index 4f17117..c138031 100755 --- a/examples/plot_gromov_barycenter.py +++ b/examples/plot_gromov_barycenter.py @@ -48,13 +48,10 @@ def smacof_mds(C, dim, max_iter=3000, eps=1e-9): eps : float relative tolerance w.r.t stress to declare converge - Returns ------- npos : ndarray, shape (R, dim) Embedded coordinates of the interpolated point cloud (defined with one isometry) - - """ rng = np.random.RandomState(seed=3) @@ -91,12 +88,12 @@ def im2mat(I): return I.reshape((I.shape[0] * I.shape[1], I.shape[2])) -square = spi.imread('../data/carre.png').astype(np.float64) / 256 -circle = spi.imread('../data/rond.png').astype(np.float64) / 256 -triangle = spi.imread('../data/triangle.png').astype(np.float64) / 256 -arrow = spi.imread('../data/coeur.png').astype(np.float64) / 256 +square = spi.imread('../data/square.png').astype(np.float64)[:,:,2] / 256 +cross = spi.imread('../data/cross.png').astype(np.float64)[:,:,2] / 256 +triangle = spi.imread('../data/triangle.png').astype(np.float64)[:,:,2] / 256 +star = spi.imread('../data/star.png').astype(np.float64)[:,:,2] / 256 -shapes = [square, circle, triangle, arrow] +shapes = [square, cross, triangle, star] S = 4 xs = [[] for i in range(S)] diff --git a/test/test_gromov.py b/test/test_gromov.py index 28495e1..e808292 100644 --- a/test/test_gromov.py +++ b/test/test_gromov.py @@ -17,8 +17,7 @@ def test_gromov(): xs = ot.datasets.get_2D_samples_gauss(n_samples, mu_s, cov_s) - xt = xs[::-1] - xt = np.array(xt) + xt = xs[::-1].copy() p = ot.unif(n_samples) q = ot.unif(n_samples) -- cgit v1.2.3