From d18564e74ca9b6b82928d38ab751855cfd39526e Mon Sep 17 00:00:00 2001 From: "Dougal J. Sutherland" Date: Wed, 19 Jul 2017 18:06:23 +0100 Subject: include LICENSE in MANIFEST.in --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 2fd6a10..e0acb7a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,6 @@ graft ot/lp/ include README.md +include LICENSE include ot/lp/core.h include ot/lp/EMD.h include ot/lp/EMD_wrapper.cpp -- cgit v1.2.3 From 0e20886a48b8d99e437bc26f1c39fde1d9ad433f Mon Sep 17 00:00:00 2001 From: "Dougal J. Sutherland" Date: Thu, 20 Jul 2017 12:12:13 +0100 Subject: add conda-forge to installation instructions Now that POT is in conda-forge (https://github.com/conda-forge/staged-recipes/pull/3332 / https://github.com/conda-forge/pot-feedstock/), might as well put that in the installation instructions. Note that it's available on Windows too, and [this basic test](https://github.com/conda-forge/pot-feedstock/blob/master/recipe/run_test.py) at least runs. --- README.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d53387b..1f4c307 100644 --- a/README.md +++ b/README.md @@ -22,28 +22,31 @@ Some demonstrations (both in Python and Jupyter Notebook format) are available i ## Installation -The Library has been tested on Linux and MacOSX. It requires a C++ compiler for using the EMD solver and rely on the following Python modules: +The library has been tested on Linux and MacOSX. It requires a C++ compiler for using the EMD solver and relies on the following Python modules: - Numpy (>=1.11) - Scipy (>=0.17) - Cython (>=0.23) - Matplotlib (>=1.5) - -Under debian based linux the dependencies can be installed with +If you use the Anaconda python distribution, POT is available in [conda-forge](conda-forge.github.io). To install it and the required dependencies: ``` -sudo apt-get install python-numpy python-scipy python-matplotlib cython +conda install -c conda-forge pot ``` -To install the library, you can install it locally (after downloading it) on you machine using +Otherwise, under Debian-based Linux the dependencies can be installed with: ``` -python setup.py install --user # for user install (no root) +sudo apt-get install python-numpy python-scipy python-matplotlib cython ``` -The toolbox is also available on PyPI with a possibly slightly older version. You can install it with: +You can then install the toolbox through PyPI with: ``` pip install POT ``` +or get the very latest version by downloading it and then running: +``` +python setup.py install --user # for user install (no root) +``` After a correct installation, you should be able to import the module without errors: ```python -- cgit v1.2.3 From fbc43503386def50b8a79bcf3ca2104934747229 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Thu, 20 Jul 2017 14:02:45 +0200 Subject: Update README.md add subtitle for better installation instructions --- README.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 1f4c307..bb3f9e4 100644 --- a/README.md +++ b/README.md @@ -22,24 +22,16 @@ Some demonstrations (both in Python and Jupyter Notebook format) are available i ## Installation -The library has been tested on Linux and MacOSX. It requires a C++ compiler for using the EMD solver and relies on the following Python modules: +The library has been tested on Linux, MacOSX and Windows. It requires a C++ compiler for using the EMD solver and relies on the following Python modules: - Numpy (>=1.11) - Scipy (>=0.17) - Cython (>=0.23) - Matplotlib (>=1.5) -If you use the Anaconda python distribution, POT is available in [conda-forge](conda-forge.github.io). To install it and the required dependencies: -``` -conda install -c conda-forge pot -``` - -Otherwise, under Debian-based Linux the dependencies can be installed with: -``` -sudo apt-get install python-numpy python-scipy python-matplotlib cython -``` +#### Pip installation -You can then install the toolbox through PyPI with: +You can install the toolbox through PyPI with: ``` pip install POT ``` @@ -48,11 +40,18 @@ or get the very latest version by downloading it and then running: python setup.py install --user # for user install (no root) ``` +#### Anaconda installation with conda-forge + +If you use the Anaconda python distribution, POT is available in [conda-forge](conda-forge.github.io). To install it and the required dependencies: +``` +conda install -c conda-forge pot +``` + +#### Post installation check After a correct installation, you should be able to import the module without errors: ```python import ot ``` - Note that for easier access the module is name ot instead of pot. -- cgit v1.2.3 From d0258f103946eb78f2fff6a3a82d85744ba27ec8 Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Tue, 11 Jul 2017 21:32:41 +0200 Subject: add flake8 to travis + misc --- .gitignore | 3 +++ .travis.yml | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 21be4c9..6edce05 100644 --- a/.gitignore +++ b/.gitignore @@ -97,3 +97,6 @@ ENV/ # Rope project settings .ropeproject + +# Mac stuff +.DS_Store diff --git a/.travis.yml b/.travis.yml index 6126b3a..8023ec5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,10 @@ before_install: # command to install dependencies install: - pip install -r requirements.txt + - pip install flake8 pytest - python setup.py install -# command to run tests -script: python test/test_load_module.py -v +# command to run tests + check syntax style +script: + - python test/test_load_module.py -v + - flake8 . + - py.test ot test -- cgit v1.2.3 From 95b2a584d02da1a08e71f7ff3895d958e42ed2dc Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Tue, 11 Jul 2017 21:33:13 +0200 Subject: pep8 + pimp plot1D_mat rendering --- examples/plot_OT_1D.py | 43 +++++++++--------- ot/plot.py | 116 ++++++++++++++++++++++--------------------------- 2 files changed, 75 insertions(+), 84 deletions(-) diff --git a/examples/plot_OT_1D.py b/examples/plot_OT_1D.py index 6661aa3..b36fa6a 100644 --- a/examples/plot_OT_1D.py +++ b/examples/plot_OT_1D.py @@ -8,49 +8,50 @@ """ import numpy as np -import matplotlib.pylab as pl +import matplotlib.pylab as plt import ot from ot.datasets import get_1D_gauss as gauss - #%% parameters -n=100 # nb bins +n = 100 # nb bins # bin positions -x=np.arange(n,dtype=np.float64) +x = np.arange(n, dtype=np.float64) # Gaussian distributions -a=gauss(n,m=20,s=5) # m= mean, s= std -b=gauss(n,m=60,s=10) +a = gauss(n, m=20, s=5) # m= mean, s= std +b = gauss(n, m=60, s=10) # loss matrix -M=ot.dist(x.reshape((n,1)),x.reshape((n,1))) -M/=M.max() +M = ot.dist(x.reshape((n, 1)), x.reshape((n, 1))) +M /= M.max() #%% plot the distributions -pl.figure(1) -pl.plot(x,a,'b',label='Source distribution') -pl.plot(x,b,'r',label='Target distribution') -pl.legend() +plt.figure(1) +plt.plot(x, a, 'b', label='Source distribution') +plt.plot(x, b, 'r', label='Target distribution') +plt.legend() #%% plot distributions and loss matrix -pl.figure(2) -ot.plot.plot1D_mat(a,b,M,'Cost matrix M') +plt.figure(2, figsize=(5, 5)) +ot.plot.plot1D_mat(a, b, M, 'Cost matrix M') #%% EMD -G0=ot.emd(a,b,M) +G0 = ot.emd(a, b, M) -pl.figure(3) -ot.plot.plot1D_mat(a,b,G0,'OT matrix G0') +plt.figure(3, figsize=(5, 5)) +ot.plot.plot1D_mat(a, b, G0, 'OT matrix G0') #%% Sinkhorn -lambd=1e-3 -Gs=ot.sinkhorn(a,b,M,lambd,verbose=True) +lambd = 1e-3 +Gs = ot.sinkhorn(a, b, M, lambd, verbose=True) + +plt.figure(4, figsize=(5, 5)) +ot.plot.plot1D_mat(a, b, Gs, 'OT matrix Sinkhorn') -pl.figure(4) -ot.plot.plot1D_mat(a,b,Gs,'OT matrix Sinkhorn') +plt.show() diff --git a/ot/plot.py b/ot/plot.py index 9737f8a..6f01731 100644 --- a/ot/plot.py +++ b/ot/plot.py @@ -4,89 +4,79 @@ Functions for plotting OT matrices import numpy as np -import matplotlib.pylab as pl +import matplotlib.pylab as plt from matplotlib import gridspec -def plot1D_mat(a,b,M,title=''): - """ Plot matrix M with the source and target 1D distribution - - Creates a subplot with the source distribution a on the left and +def plot1D_mat(a, b, M, title=''): + """ Plot matrix M with the source and target 1D distribution + + Creates a subplot with the source distribution a on the left and target distribution b on the tot. The matrix M is shown in between. - - + + Parameters ---------- - - a : np.array (na,) + a : np.array, shape (na,) Source distribution - b : np.array (nb,) - Target distribution - M : np.array (na,nb) + b : np.array, shape (nb,) + Target distribution + M : np.array, shape (na,nb) Matrix to plot - - - """ - - na=M.shape[0] - nb=M.shape[1] - + na, nb = M.shape + gs = gridspec.GridSpec(3, 3) - - - xa=np.arange(na) - xb=np.arange(nb) - - - ax1=pl.subplot(gs[0,1:]) - pl.plot(xb,b,'r',label='Target distribution') - pl.yticks(()) - pl.title(title) - - #pl.axis('off') - - ax2=pl.subplot(gs[1:,0]) - pl.plot(a,xa,'b',label='Source distribution') - pl.gca().invert_xaxis() - pl.gca().invert_yaxis() - pl.xticks(()) - #pl.ylim((0,n)) - #pl.axis('off') - - pl.subplot(gs[1:,1:],sharex=ax1,sharey=ax2) - pl.imshow(M,interpolation='nearest') - - pl.xlim((0,nb)) - - -def plot2D_samples_mat(xs,xt,G,thr=1e-8,**kwargs): + + xa = np.arange(na) + xb = np.arange(nb) + + ax1 = plt.subplot(gs[0, 1:]) + plt.plot(xb, b, 'r', label='Target distribution') + plt.yticks(()) + plt.title(title) + + ax2 = plt.subplot(gs[1:, 0]) + plt.plot(a, xa, 'b', label='Source distribution') + plt.gca().invert_xaxis() + plt.gca().invert_yaxis() + plt.xticks(()) + + plt.subplot(gs[1:, 1:], sharex=ax1, sharey=ax2) + plt.imshow(M, interpolation='nearest') + plt.axis('off') + + plt.xlim((0, nb)) + plt.tight_layout() + plt.subplots_adjust(wspace=0., hspace=0.2) + + +def plot2D_samples_mat(xs, xt, G, thr=1e-8, **kwargs): """ Plot matrix M in 2D with lines using alpha values - - Plot lines between source and target 2D samples with a color + + Plot lines between source and target 2D samples with a color proportional to the value of the matrix G between samples. - - + + Parameters ---------- - - xs : np.array (ns,2) + xs : ndarray, shape (ns,2) Source samples positions - b : np.array (nt,2) + b : ndarray, shape (nt,2) Target samples positions - G : np.array (na,nb) + G : ndarray, shape (na,nb) OT matrix thr : float, optional threshold above which the line is drawn **kwargs : dict - paameters given to the plot functions (default color is black if nothing given) - + paameters given to the plot functions (default color is black if + nothing given) """ - if ('color' not in kwargs) and ('c' not in kwargs): - kwargs['color']='k' - mx=G.max() + if ('color' not in kwargs) and ('c' not in kwargs): + kwargs['color'] = 'k' + mx = G.max() for i in range(xs.shape[0]): for j in range(xt.shape[0]): - if G[i,j]/mx>thr: - pl.plot([xs[i,0],xt[j,0]],[xs[i,1],xt[j,1]],alpha=G[i,j]/mx,**kwargs) - \ No newline at end of file + if G[i, j] / mx > thr: + plt.plot([xs[i, 0], xt[j, 0]], [xs[i, 1], xt[j, 1]], + alpha=G[i, j] / mx, **kwargs) -- cgit v1.2.3 From 35b25adf1fd9ec35fb6f6da105e268ed5b467d79 Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Tue, 11 Jul 2017 21:43:22 +0200 Subject: pimp + pep8 on plot_OT_2D_samples --- examples/plot_OT_2D_samples.py | 82 +++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/examples/plot_OT_2D_samples.py b/examples/plot_OT_2D_samples.py index edfb781..3a93591 100644 --- a/examples/plot_OT_2D_samples.py +++ b/examples/plot_OT_2D_samples.py @@ -8,71 +8,73 @@ """ import numpy as np -import matplotlib.pylab as pl +import matplotlib.pylab as plt import ot #%% parameters and data generation -n=50 # nb samples +n = 50 # nb samples -mu_s=np.array([0,0]) -cov_s=np.array([[1,0],[0,1]]) +mu_s = np.array([0, 0]) +cov_s = np.array([[1, 0], [0, 1]]) -mu_t=np.array([4,4]) -cov_t=np.array([[1,-.8],[-.8,1]]) +mu_t = np.array([4, 4]) +cov_t = np.array([[1, -.8], [-.8, 1]]) -xs=ot.datasets.get_2D_samples_gauss(n,mu_s,cov_s) -xt=ot.datasets.get_2D_samples_gauss(n,mu_t,cov_t) +xs = ot.datasets.get_2D_samples_gauss(n, mu_s, cov_s) +xt = ot.datasets.get_2D_samples_gauss(n, mu_t, cov_t) -a,b = ot.unif(n),ot.unif(n) # uniform distribution on samples +a, b = np.ones((n,)) / n, np.ones((n,)) / n # uniform distribution on samples # loss matrix -M=ot.dist(xs,xt) -M/=M.max() +M = ot.dist(xs, xt) +M /= M.max() #%% plot samples -pl.figure(1) -pl.plot(xs[:,0],xs[:,1],'+b',label='Source samples') -pl.plot(xt[:,0],xt[:,1],'xr',label='Target samples') -pl.legend(loc=0) -pl.title('Source and traget distributions') +plt.figure(1) +plt.plot(xs[:, 0], xs[:, 1], '+b', label='Source samples') +plt.plot(xt[:, 0], xt[:, 1], 'xr', label='Target samples') +plt.legend(loc=0) +plt.title('Source and target distributions') -pl.figure(2) -pl.imshow(M,interpolation='nearest') -pl.title('Cost matrix M') +plt.figure(2) +plt.imshow(M, interpolation='nearest') +plt.title('Cost matrix M') #%% EMD -G0=ot.emd(a,b,M) +G0 = ot.emd(a, b, M) -pl.figure(3) -pl.imshow(G0,interpolation='nearest') -pl.title('OT matrix G0') +plt.figure(3) +plt.imshow(G0, interpolation='nearest') +plt.title('OT matrix G0') -pl.figure(4) -ot.plot.plot2D_samples_mat(xs,xt,G0,c=[.5,.5,1]) -pl.plot(xs[:,0],xs[:,1],'+b',label='Source samples') -pl.plot(xt[:,0],xt[:,1],'xr',label='Target samples') -pl.legend(loc=0) -pl.title('OT matrix with samples') +plt.figure(4) +ot.plot.plot2D_samples_mat(xs, xt, G0, c=[.5, .5, 1]) +plt.plot(xs[:, 0], xs[:, 1], '+b', label='Source samples') +plt.plot(xt[:, 0], xt[:, 1], 'xr', label='Target samples') +plt.legend(loc=0) +plt.title('OT matrix with samples') #%% sinkhorn # reg term -lambd=5e-4 +lambd = 5e-4 -Gs=ot.sinkhorn(a,b,M,lambd) +Gs = ot.sinkhorn(a, b, M, lambd) -pl.figure(5) -pl.imshow(Gs,interpolation='nearest') -pl.title('OT matrix sinkhorn') +plt.figure(5) +plt.imshow(Gs, interpolation='nearest') +plt.title('OT matrix sinkhorn') -pl.figure(6) -ot.plot.plot2D_samples_mat(xs,xt,Gs,color=[.5,.5,1]) -pl.plot(xs[:,0],xs[:,1],'+b',label='Source samples') -pl.plot(xt[:,0],xt[:,1],'xr',label='Target samples') -pl.legend(loc=0) -pl.title('OT matrix Sinkhorn with samples') +plt.figure(6) +ot.plot.plot2D_samples_mat(xs, xt, Gs, color=[.5, .5, 1]) +plt.plot(xs[:, 0], xs[:, 1], '+b', label='Source samples') +plt.plot(xt[:, 0], xt[:, 1], 'xr', label='Target samples') +plt.legend(loc=0) +plt.title('OT matrix Sinkhorn with samples') + +plt.show() -- cgit v1.2.3 From c6cb1cd666a3e1b761b83a6e0f9339268e69f099 Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Tue, 11 Jul 2017 21:51:29 +0200 Subject: pimp + pep8 on plot_OT_L1_vs_L2 --- examples/plot_OT_L1_vs_L2.py | 168 ++++++++++++++++++++++--------------------- 1 file changed, 86 insertions(+), 82 deletions(-) diff --git a/examples/plot_OT_L1_vs_L2.py b/examples/plot_OT_L1_vs_L2.py index 9bb92fe..e11d6ad 100644 --- a/examples/plot_OT_L1_vs_L2.py +++ b/examples/plot_OT_L1_vs_L2.py @@ -4,7 +4,7 @@ 2D Optimal transport for different metrics ========================================== -Stole the figure idea from Fig. 1 and 2 in +Stole the figure idea from Fig. 1 and 2 in https://arxiv.org/pdf/1706.07650.pdf @@ -12,7 +12,7 @@ https://arxiv.org/pdf/1706.07650.pdf """ import numpy as np -import matplotlib.pylab as pl +import matplotlib.pylab as plt import ot #%% parameters and data generation @@ -20,89 +20,93 @@ import ot for data in range(2): if data: - n=20 # nb samples - xs=np.zeros((n,2)) - xs[:,0]=np.arange(n)+1 - xs[:,1]=(np.arange(n)+1)*-0.001 # to make it strictly convex... - - xt=np.zeros((n,2)) - xt[:,1]=np.arange(n)+1 + n = 20 # nb samples + xs = np.zeros((n, 2)) + xs[:, 0] = np.arange(n) + 1 + xs[:, 1] = (np.arange(n) + 1) * -0.001 # to make it strictly convex... + + xt = np.zeros((n, 2)) + xt[:, 1] = np.arange(n) + 1 else: - - n=50 # nb samples - xtot=np.zeros((n+1,2)) - xtot[:,0]=np.cos((np.arange(n+1)+1.0)*0.9/(n+2)*2*np.pi) - xtot[:,1]=np.sin((np.arange(n+1)+1.0)*0.9/(n+2)*2*np.pi) - - xs=xtot[:n,:] - xt=xtot[1:,:] - - - - a,b = ot.unif(n),ot.unif(n) # uniform distribution on samples - + + n = 50 # nb samples + xtot = np.zeros((n + 1, 2)) + xtot[:, 0] = np.cos( + (np.arange(n + 1) + 1.0) * 0.9 / (n + 2) * 2 * np.pi) + xtot[:, 1] = np.sin( + (np.arange(n + 1) + 1.0) * 0.9 / (n + 2) * 2 * np.pi) + + xs = xtot[:n, :] + xt = xtot[1:, :] + + a, b = ot.unif(n), ot.unif(n) # uniform distribution on samples + # loss matrix - M1=ot.dist(xs,xt,metric='euclidean') - M1/=M1.max() - + M1 = ot.dist(xs, xt, metric='euclidean') + M1 /= M1.max() + # loss matrix - M2=ot.dist(xs,xt,metric='sqeuclidean') - M2/=M2.max() - + M2 = ot.dist(xs, xt, metric='sqeuclidean') + M2 /= M2.max() + # loss matrix - Mp=np.sqrt(ot.dist(xs,xt,metric='euclidean')) - Mp/=Mp.max() - + Mp = np.sqrt(ot.dist(xs, xt, metric='euclidean')) + Mp /= Mp.max() + #%% plot samples - - pl.figure(1+3*data) - pl.clf() - pl.plot(xs[:,0],xs[:,1],'+b',label='Source samples') - pl.plot(xt[:,0],xt[:,1],'xr',label='Target samples') - pl.axis('equal') - pl.title('Source and traget distributions') - - pl.figure(2+3*data,(15,5)) - pl.subplot(1,3,1) - pl.imshow(M1,interpolation='nearest') - pl.title('Eucidean cost') - pl.subplot(1,3,2) - pl.imshow(M2,interpolation='nearest') - pl.title('Squared Euclidean cost') - - pl.subplot(1,3,3) - pl.imshow(Mp,interpolation='nearest') - pl.title('Sqrt Euclidean cost') + + plt.figure(1 + 3 * data, figsize=(7, 3)) + plt.clf() + plt.plot(xs[:, 0], xs[:, 1], '+b', label='Source samples') + plt.plot(xt[:, 0], xt[:, 1], 'xr', label='Target samples') + plt.axis('equal') + plt.title('Source and traget distributions') + + plt.figure(2 + 3 * data, figsize=(7, 3)) + + plt.subplot(1, 3, 1) + plt.imshow(M1, interpolation='nearest') + plt.title('Euclidean cost') + + plt.subplot(1, 3, 2) + plt.imshow(M2, interpolation='nearest') + plt.title('Squared Euclidean cost') + + plt.subplot(1, 3, 3) + plt.imshow(Mp, interpolation='nearest') + plt.title('Sqrt Euclidean cost') + plt.tight_layout() + #%% EMD - - G1=ot.emd(a,b,M1) - G2=ot.emd(a,b,M2) - Gp=ot.emd(a,b,Mp) - - pl.figure(3+3*data,(15,5)) - - pl.subplot(1,3,1) - ot.plot.plot2D_samples_mat(xs,xt,G1,c=[.5,.5,1]) - pl.plot(xs[:,0],xs[:,1],'+b',label='Source samples') - pl.plot(xt[:,0],xt[:,1],'xr',label='Target samples') - pl.axis('equal') - #pl.legend(loc=0) - pl.title('OT Euclidean') - - pl.subplot(1,3,2) - - ot.plot.plot2D_samples_mat(xs,xt,G2,c=[.5,.5,1]) - pl.plot(xs[:,0],xs[:,1],'+b',label='Source samples') - pl.plot(xt[:,0],xt[:,1],'xr',label='Target samples') - pl.axis('equal') - #pl.legend(loc=0) - pl.title('OT squared Euclidean') - - pl.subplot(1,3,3) - - ot.plot.plot2D_samples_mat(xs,xt,Gp,c=[.5,.5,1]) - pl.plot(xs[:,0],xs[:,1],'+b',label='Source samples') - pl.plot(xt[:,0],xt[:,1],'xr',label='Target samples') - pl.axis('equal') - #pl.legend(loc=0) - pl.title('OT sqrt Euclidean') + G1 = ot.emd(a, b, M1) + G2 = ot.emd(a, b, M2) + Gp = ot.emd(a, b, Mp) + + plt.figure(3 + 3 * data, figsize=(7, 3)) + + plt.subplot(1, 3, 1) + ot.plot.plot2D_samples_mat(xs, xt, G1, c=[.5, .5, 1]) + plt.plot(xs[:, 0], xs[:, 1], '+b', label='Source samples') + plt.plot(xt[:, 0], xt[:, 1], 'xr', label='Target samples') + plt.axis('equal') + # plt.legend(loc=0) + plt.title('OT Euclidean') + + plt.subplot(1, 3, 2) + ot.plot.plot2D_samples_mat(xs, xt, G2, c=[.5, .5, 1]) + plt.plot(xs[:, 0], xs[:, 1], '+b', label='Source samples') + plt.plot(xt[:, 0], xt[:, 1], 'xr', label='Target samples') + plt.axis('equal') + # plt.legend(loc=0) + plt.title('OT squared Euclidean') + + plt.subplot(1, 3, 3) + ot.plot.plot2D_samples_mat(xs, xt, Gp, c=[.5, .5, 1]) + plt.plot(xs[:, 0], xs[:, 1], '+b', label='Source samples') + plt.plot(xt[:, 0], xt[:, 1], 'xr', label='Target samples') + plt.axis('equal') + # plt.legend(loc=0) + plt.title('OT sqrt Euclidean') + plt.tight_layout() + +plt.show() -- cgit v1.2.3 From 75c988f515f0a1ee51f88f5fc429a1301a1ca8c5 Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Wed, 12 Jul 2017 22:11:25 +0200 Subject: do plot_barycenter_1D --- examples/plot_barycenter_1D.py | 111 ++++++++++++++++++++--------------------- setup.cfg | 4 ++ 2 files changed, 58 insertions(+), 57 deletions(-) diff --git a/examples/plot_barycenter_1D.py b/examples/plot_barycenter_1D.py index 30eecbf..ab236e1 100644 --- a/examples/plot_barycenter_1D.py +++ b/examples/plot_barycenter_1D.py @@ -11,128 +11,125 @@ import numpy as np import matplotlib.pylab as pl import ot -from mpl_toolkits.mplot3d import Axes3D #necessary for 3d plot even if not used +# necessary for 3d plot even if not used +from mpl_toolkits.mplot3d import Axes3D # noqa from matplotlib.collections import PolyCollection #%% parameters -n=100 # nb bins +n = 100 # nb bins # bin positions -x=np.arange(n,dtype=np.float64) +x = np.arange(n, dtype=np.float64) # Gaussian distributions -a1=ot.datasets.get_1D_gauss(n,m=20,s=5) # m= mean, s= std -a2=ot.datasets.get_1D_gauss(n,m=60,s=8) +a1 = ot.datasets.get_1D_gauss(n, m=20, s=5) # m= mean, s= std +a2 = ot.datasets.get_1D_gauss(n, m=60, s=8) # creating matrix A containing all distributions -A=np.vstack((a1,a2)).T -nbd=A.shape[1] +A = np.vstack((a1, a2)).T +n_distributions = A.shape[1] # loss matrix + normalization -M=ot.utils.dist0(n) -M/=M.max() +M = ot.utils.dist0(n) +M /= M.max() #%% plot the distributions -pl.figure(1) -for i in range(nbd): - pl.plot(x,A[:,i]) +pl.figure(1, figsize=(6.4, 3)) +for i in range(n_distributions): + pl.plot(x, A[:, i]) pl.title('Distributions') +pl.tight_layout() #%% barycenter computation -alpha=0.2 # 0<=alpha<=1 -weights=np.array([1-alpha,alpha]) +alpha = 0.2 # 0<=alpha<=1 +weights = np.array([1 - alpha, alpha]) # l2bary -bary_l2=A.dot(weights) +bary_l2 = A.dot(weights) # wasserstein -reg=1e-3 -bary_wass=ot.bregman.barycenter(A,M,reg,weights) +reg = 1e-3 +bary_wass = ot.bregman.barycenter(A, M, reg, weights) pl.figure(2) pl.clf() -pl.subplot(2,1,1) -for i in range(nbd): - pl.plot(x,A[:,i]) +pl.subplot(2, 1, 1) +for i in range(n_distributions): + pl.plot(x, A[:, i]) pl.title('Distributions') -pl.subplot(2,1,2) -pl.plot(x,bary_l2,'r',label='l2') -pl.plot(x,bary_wass,'g',label='Wasserstein') +pl.subplot(2, 1, 2) +pl.plot(x, bary_l2, 'r', label='l2') +pl.plot(x, bary_wass, 'g', label='Wasserstein') pl.legend() pl.title('Barycenters') - +pl.tight_layout() #%% barycenter interpolation -nbalpha=11 -alphalist=np.linspace(0,1,nbalpha) +n_alpha = 11 +alpha_list = np.linspace(0, 1, n_alpha) -B_l2=np.zeros((n,nbalpha)) +B_l2 = np.zeros((n, n_alpha)) -B_wass=np.copy(B_l2) +B_wass = np.copy(B_l2) -for i in range(0,nbalpha): - alpha=alphalist[i] - weights=np.array([1-alpha,alpha]) - B_l2[:,i]=A.dot(weights) - B_wass[:,i]=ot.bregman.barycenter(A,M,reg,weights) +for i in range(0, n_alpha): + alpha = alpha_list[i] + weights = np.array([1 - alpha, alpha]) + B_l2[:, i] = A.dot(weights) + B_wass[:, i] = ot.bregman.barycenter(A, M, reg, weights) #%% plot interpolation -pl.figure(3,(10,5)) +pl.figure(3) -#pl.subplot(1,2,1) -cmap=pl.cm.get_cmap('viridis') +cmap = pl.cm.get_cmap('viridis') verts = [] -zs = alphalist -for i,z in enumerate(zs): - ys = B_l2[:,i] +zs = alpha_list +for i, z in enumerate(zs): + ys = B_l2[:, i] verts.append(list(zip(x, ys))) ax = pl.gcf().gca(projection='3d') -poly = PolyCollection(verts,facecolors=[cmap(a) for a in alphalist]) +poly = PolyCollection(verts, facecolors=[cmap(a) for a in alpha_list]) poly.set_alpha(0.7) ax.add_collection3d(poly, zs=zs, zdir='y') - ax.set_xlabel('x') ax.set_xlim3d(0, n) ax.set_ylabel('$\\alpha$') -ax.set_ylim3d(0,1) +ax.set_ylim3d(0, 1) ax.set_zlabel('') -ax.set_zlim3d(0, B_l2.max()*1.01) +ax.set_zlim3d(0, B_l2.max() * 1.01) pl.title('Barycenter interpolation with l2') +pl.tight_layout() -pl.show() - -pl.figure(4,(10,5)) - -#pl.subplot(1,2,1) -cmap=pl.cm.get_cmap('viridis') +pl.figure(4) +cmap = pl.cm.get_cmap('viridis') verts = [] -zs = alphalist -for i,z in enumerate(zs): - ys = B_wass[:,i] +zs = alpha_list +for i, z in enumerate(zs): + ys = B_wass[:, i] verts.append(list(zip(x, ys))) ax = pl.gcf().gca(projection='3d') -poly = PolyCollection(verts,facecolors=[cmap(a) for a in alphalist]) +poly = PolyCollection(verts, facecolors=[cmap(a) for a in alpha_list]) poly.set_alpha(0.7) ax.add_collection3d(poly, zs=zs, zdir='y') - ax.set_xlabel('x') ax.set_xlim3d(0, n) ax.set_ylabel('$\\alpha$') -ax.set_ylim3d(0,1) +ax.set_ylim3d(0, 1) ax.set_zlabel('') -ax.set_zlim3d(0, B_l2.max()*1.01) +ax.set_zlim3d(0, B_l2.max() * 1.01) pl.title('Barycenter interpolation with Wasserstein') +pl.tight_layout() -pl.show() \ No newline at end of file +pl.show() diff --git a/setup.cfg b/setup.cfg index b88034e..d010702 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,6 @@ [metadata] description-file = README.md + +[flake8] +exclude = __init__.py +ignore = E265 -- cgit v1.2.3 From de5903618d2d525e48e3f5ffc205c3f566f0095d Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Wed, 12 Jul 2017 22:13:43 +0200 Subject: do plot_compute_emd --- examples/plot_compute_emd.py | 59 +++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/examples/plot_compute_emd.py b/examples/plot_compute_emd.py index f2cdc35..558facb 100644 --- a/examples/plot_compute_emd.py +++ b/examples/plot_compute_emd.py @@ -15,60 +15,63 @@ from ot.datasets import get_1D_gauss as gauss #%% parameters -n=100 # nb bins -n_target=50 # nb target distributions +n = 100 # nb bins +n_target = 50 # nb target distributions # bin positions -x=np.arange(n,dtype=np.float64) +x = np.arange(n, dtype=np.float64) -lst_m=np.linspace(20,90,n_target) +lst_m = np.linspace(20, 90, n_target) # Gaussian distributions -a=gauss(n,m=20,s=5) # m= mean, s= std +a = gauss(n, m=20, s=5) # m= mean, s= std -B=np.zeros((n,n_target)) +B = np.zeros((n, n_target)) -for i,m in enumerate(lst_m): - B[:,i]=gauss(n,m=m,s=5) +for i, m in enumerate(lst_m): + B[:, i] = gauss(n, m=m, s=5) # loss matrix and normalization -M=ot.dist(x.reshape((n,1)),x.reshape((n,1)),'euclidean') -M/=M.max() -M2=ot.dist(x.reshape((n,1)),x.reshape((n,1)),'sqeuclidean') -M2/=M2.max() +M = ot.dist(x.reshape((n, 1)), x.reshape((n, 1)), 'euclidean') +M /= M.max() +M2 = ot.dist(x.reshape((n, 1)), x.reshape((n, 1)), 'sqeuclidean') +M2 /= M2.max() #%% plot the distributions pl.figure(1) -pl.subplot(2,1,1) -pl.plot(x,a,'b',label='Source distribution') +pl.subplot(2, 1, 1) +pl.plot(x, a, 'b', label='Source distribution') pl.title('Source distribution') -pl.subplot(2,1,2) -pl.plot(x,B,label='Target distributions') +pl.subplot(2, 1, 2) +pl.plot(x, B, label='Target distributions') pl.title('Target distributions') +pl.tight_layout() #%% Compute and plot distributions and loss matrix -d_emd=ot.emd2(a,B,M) # direct computation of EMD -d_emd2=ot.emd2(a,B,M2) # direct computation of EMD with loss M3 +d_emd = ot.emd2(a, B, M) # direct computation of EMD +d_emd2 = ot.emd2(a, B, M2) # direct computation of EMD with loss M3 pl.figure(2) -pl.plot(d_emd,label='Euclidean EMD') -pl.plot(d_emd2,label='Squared Euclidean EMD') +pl.plot(d_emd, label='Euclidean EMD') +pl.plot(d_emd2, label='Squared Euclidean EMD') pl.title('EMD distances') pl.legend() #%% -reg=1e-2 -d_sinkhorn=ot.sinkhorn2(a,B,M,reg) -d_sinkhorn2=ot.sinkhorn2(a,B,M2,reg) +reg = 1e-2 +d_sinkhorn = ot.sinkhorn2(a, B, M, reg) +d_sinkhorn2 = ot.sinkhorn2(a, B, M2, reg) pl.figure(2) pl.clf() -pl.plot(d_emd,label='Euclidean EMD') -pl.plot(d_emd2,label='Squared Euclidean EMD') -pl.plot(d_sinkhorn,'+',label='Euclidean Sinkhorn') -pl.plot(d_sinkhorn2,'+',label='Squared Euclidean Sinkhorn') +pl.plot(d_emd, label='Euclidean EMD') +pl.plot(d_emd2, label='Squared Euclidean EMD') +pl.plot(d_sinkhorn, '+', label='Euclidean Sinkhorn') +pl.plot(d_sinkhorn2, '+', label='Squared Euclidean Sinkhorn') pl.title('EMD distances') -pl.legend() \ No newline at end of file +pl.legend() + +pl.show() -- cgit v1.2.3 From c5a72cc1ff6a1f5462fdca6ced837180820fe19b Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Wed, 12 Jul 2017 22:17:52 +0200 Subject: do plot_optim_OTreg.py --- examples/plot_optim_OTreg.py | 65 ++++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/examples/plot_optim_OTreg.py b/examples/plot_optim_OTreg.py index 8abb426..0041770 100644 --- a/examples/plot_optim_OTreg.py +++ b/examples/plot_optim_OTreg.py @@ -12,63 +12,76 @@ import matplotlib.pylab as pl import ot - #%% parameters -n=100 # nb bins +n = 100 # nb bins # bin positions -x=np.arange(n,dtype=np.float64) +x = np.arange(n, dtype=np.float64) # Gaussian distributions -a=ot.datasets.get_1D_gauss(n,m=20,s=5) # m= mean, s= std -b=ot.datasets.get_1D_gauss(n,m=60,s=10) +a = ot.datasets.get_1D_gauss(n, m=20, s=5) # m= mean, s= std +b = ot.datasets.get_1D_gauss(n, m=60, s=10) # loss matrix -M=ot.dist(x.reshape((n,1)),x.reshape((n,1))) -M/=M.max() +M = ot.dist(x.reshape((n, 1)), x.reshape((n, 1))) +M /= M.max() #%% EMD -G0=ot.emd(a,b,M) +G0 = ot.emd(a, b, M) pl.figure(3) -ot.plot.plot1D_mat(a,b,G0,'OT matrix G0') +ot.plot.plot1D_mat(a, b, G0, 'OT matrix G0') #%% Example with Frobenius norm regularization -def f(G): return 0.5*np.sum(G**2) -def df(G): return G +def f(G): + return 0.5 * np.sum(G**2) + + +def df(G): + return G -reg=1e-1 +reg = 1e-1 -Gl2=ot.optim.cg(a,b,M,reg,f,df,verbose=True) +Gl2 = ot.optim.cg(a, b, M, reg, f, df, verbose=True) pl.figure(3) -ot.plot.plot1D_mat(a,b,Gl2,'OT matrix Frob. reg') +ot.plot.plot1D_mat(a, b, Gl2, 'OT matrix Frob. reg') #%% Example with entropic regularization -def f(G): return np.sum(G*np.log(G)) -def df(G): return np.log(G)+1 -reg=1e-3 +def f(G): + return np.sum(G * np.log(G)) + + +def df(G): + return np.log(G) + 1. -Ge=ot.optim.cg(a,b,M,reg,f,df,verbose=True) +reg = 1e-3 + +Ge = ot.optim.cg(a, b, M, reg, f, df, verbose=True) pl.figure(4) -ot.plot.plot1D_mat(a,b,Ge,'OT matrix Entrop. reg') +ot.plot.plot1D_mat(a, b, Ge, 'OT matrix Entrop. reg') #%% Example with Frobenius norm + entropic regularization with gcg -def f(G): return 0.5*np.sum(G**2) -def df(G): return G -reg1=1e-3 -reg2=1e-1 +def f(G): + return 0.5 * np.sum(G**2) + + +def df(G): + return G + +reg1 = 1e-3 +reg2 = 1e-1 -Gel2=ot.optim.gcg(a,b,M,reg1,reg2,f,df,verbose=True) +Gel2 = ot.optim.gcg(a, b, M, reg1, reg2, f, df, verbose=True) pl.figure(5) -ot.plot.plot1D_mat(a,b,Gel2,'OT entropic + matrix Frob. reg') -pl.show() \ No newline at end of file +ot.plot.plot1D_mat(a, b, Gel2, 'OT entropic + matrix Frob. reg') +pl.show() -- cgit v1.2.3 From d6091dae858a82f69e6843859f164269fa338c6b Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Wed, 12 Jul 2017 22:20:08 +0200 Subject: more --- examples/plot_OT_1D.py | 18 +++++++++--------- examples/plot_optim_OTreg.py | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/plot_OT_1D.py b/examples/plot_OT_1D.py index b36fa6a..2f3b924 100644 --- a/examples/plot_OT_1D.py +++ b/examples/plot_OT_1D.py @@ -8,7 +8,7 @@ """ import numpy as np -import matplotlib.pylab as plt +import matplotlib.pylab as pl import ot from ot.datasets import get_1D_gauss as gauss @@ -29,21 +29,21 @@ M /= M.max() #%% plot the distributions -plt.figure(1) -plt.plot(x, a, 'b', label='Source distribution') -plt.plot(x, b, 'r', label='Target distribution') -plt.legend() +pl.figure(1, figsize=(6.4, 3)) +pl.plot(x, a, 'b', label='Source distribution') +pl.plot(x, b, 'r', label='Target distribution') +pl.legend() #%% plot distributions and loss matrix -plt.figure(2, figsize=(5, 5)) +pl.figure(2, figsize=(5, 5)) ot.plot.plot1D_mat(a, b, M, 'Cost matrix M') #%% EMD G0 = ot.emd(a, b, M) -plt.figure(3, figsize=(5, 5)) +pl.figure(3, figsize=(5, 5)) ot.plot.plot1D_mat(a, b, G0, 'OT matrix G0') #%% Sinkhorn @@ -51,7 +51,7 @@ ot.plot.plot1D_mat(a, b, G0, 'OT matrix G0') lambd = 1e-3 Gs = ot.sinkhorn(a, b, M, lambd, verbose=True) -plt.figure(4, figsize=(5, 5)) +pl.figure(4, figsize=(5, 5)) ot.plot.plot1D_mat(a, b, Gs, 'OT matrix Sinkhorn') -plt.show() +pl.show() diff --git a/examples/plot_optim_OTreg.py b/examples/plot_optim_OTreg.py index 0041770..1c3a1c5 100644 --- a/examples/plot_optim_OTreg.py +++ b/examples/plot_optim_OTreg.py @@ -31,7 +31,7 @@ M /= M.max() G0 = ot.emd(a, b, M) -pl.figure(3) +pl.figure(3, figsize=(5, 5)) ot.plot.plot1D_mat(a, b, G0, 'OT matrix G0') #%% Example with Frobenius norm regularization @@ -64,7 +64,7 @@ reg = 1e-3 Ge = ot.optim.cg(a, b, M, reg, f, df, verbose=True) -pl.figure(4) +pl.figure(4, figsize=(5, 5)) ot.plot.plot1D_mat(a, b, Ge, 'OT matrix Entrop. reg') #%% Example with Frobenius norm + entropic regularization with gcg @@ -82,6 +82,6 @@ reg2 = 1e-1 Gel2 = ot.optim.gcg(a, b, M, reg1, reg2, f, df, verbose=True) -pl.figure(5) +pl.figure(5, figsize=(5, 5)) ot.plot.plot1D_mat(a, b, Gel2, 'OT entropic + matrix Frob. reg') pl.show() -- cgit v1.2.3 From 25ef32ff892fba105a4a116a804b1e4f08ae57cd Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Wed, 12 Jul 2017 22:51:17 +0200 Subject: more --- examples/plot_OTDA_2D.py | 114 ++++++++++++++++---------------- examples/plot_OTDA_classes.py | 129 +++++++++++++++++++------------------ examples/plot_OTDA_color_images.py | 115 +++++++++++++++++---------------- examples/plot_OT_2D_samples.py | 52 +++++++-------- examples/plot_OT_L1_vs_L2.py | 80 +++++++++++------------ 5 files changed, 247 insertions(+), 243 deletions(-) diff --git a/examples/plot_OTDA_2D.py b/examples/plot_OTDA_2D.py index a1fb804..1bda59c 100644 --- a/examples/plot_OTDA_2D.py +++ b/examples/plot_OTDA_2D.py @@ -11,110 +11,112 @@ import matplotlib.pylab as pl import ot - #%% parameters -n=150 # nb bins +n = 150 # nb bins -xs,ys=ot.datasets.get_data_classif('3gauss',n) -xt,yt=ot.datasets.get_data_classif('3gauss2',n) +xs, ys = ot.datasets.get_data_classif('3gauss', n) +xt, yt = ot.datasets.get_data_classif('3gauss2', n) -a,b = ot.unif(n),ot.unif(n) +a, b = ot.unif(n), ot.unif(n) # loss matrix -M=ot.dist(xs,xt) -#M/=M.max() +M = ot.dist(xs, xt) +# M/=M.max() #%% plot samples pl.figure(1) - -pl.subplot(2,2,1) -pl.scatter(xs[:,0],xs[:,1],c=ys,marker='+',label='Source samples') +pl.subplot(2, 2, 1) +pl.scatter(xs[:, 0], xs[:, 1], c=ys, marker='+', label='Source samples') pl.legend(loc=0) pl.title('Source distributions') -pl.subplot(2,2,2) -pl.scatter(xt[:,0],xt[:,1],c=yt,marker='o',label='Target samples') +pl.subplot(2, 2, 2) +pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', label='Target samples') pl.legend(loc=0) pl.title('target distributions') pl.figure(2) -pl.imshow(M,interpolation='nearest') +pl.imshow(M, interpolation='nearest') pl.title('Cost matrix M') #%% OT estimation # EMD -G0=ot.emd(a,b,M) +G0 = ot.emd(a, b, M) # sinkhorn -lambd=1e-1 -Gs=ot.sinkhorn(a,b,M,lambd) +lambd = 1e-1 +Gs = ot.sinkhorn(a, b, M, lambd) # Group lasso regularization -reg=1e-1 -eta=1e0 -Gg=ot.da.sinkhorn_lpl1_mm(a,ys.astype(np.int),b,M,reg,eta) +reg = 1e-1 +eta = 1e0 +Gg = ot.da.sinkhorn_lpl1_mm(a, ys.astype(np.int), b, M, reg, eta) #%% visu matrices pl.figure(3) -pl.subplot(2,3,1) -pl.imshow(G0,interpolation='nearest') +pl.subplot(2, 3, 1) +pl.imshow(G0, interpolation='nearest') pl.title('OT matrix ') -pl.subplot(2,3,2) -pl.imshow(Gs,interpolation='nearest') +pl.subplot(2, 3, 2) +pl.imshow(Gs, interpolation='nearest') pl.title('OT matrix Sinkhorn') -pl.subplot(2,3,3) -pl.imshow(Gg,interpolation='nearest') +pl.subplot(2, 3, 3) +pl.imshow(Gg, interpolation='nearest') pl.title('OT matrix Group lasso') -pl.subplot(2,3,4) -ot.plot.plot2D_samples_mat(xs,xt,G0,c=[.5,.5,1]) -pl.scatter(xs[:,0],xs[:,1],c=ys,marker='+',label='Source samples') -pl.scatter(xt[:,0],xt[:,1],c=yt,marker='o',label='Target samples') +pl.subplot(2, 3, 4) +ot.plot.plot2D_samples_mat(xs, xt, G0, c=[.5, .5, 1]) +pl.scatter(xs[:, 0], xs[:, 1], c=ys, marker='+', label='Source samples') +pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', label='Target samples') -pl.subplot(2,3,5) -ot.plot.plot2D_samples_mat(xs,xt,Gs,c=[.5,.5,1]) -pl.scatter(xs[:,0],xs[:,1],c=ys,marker='+',label='Source samples') -pl.scatter(xt[:,0],xt[:,1],c=yt,marker='o',label='Target samples') +pl.subplot(2, 3, 5) +ot.plot.plot2D_samples_mat(xs, xt, Gs, c=[.5, .5, 1]) +pl.scatter(xs[:, 0], xs[:, 1], c=ys, marker='+', label='Source samples') +pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', label='Target samples') -pl.subplot(2,3,6) -ot.plot.plot2D_samples_mat(xs,xt,Gg,c=[.5,.5,1]) -pl.scatter(xs[:,0],xs[:,1],c=ys,marker='+',label='Source samples') -pl.scatter(xt[:,0],xt[:,1],c=yt,marker='o',label='Target samples') +pl.subplot(2, 3, 6) +ot.plot.plot2D_samples_mat(xs, xt, Gg, c=[.5, .5, 1]) +pl.scatter(xs[:, 0], xs[:, 1], c=ys, marker='+', label='Source samples') +pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', label='Target samples') +pl.tight_layout() #%% sample interpolation -xst0=n*G0.dot(xt) -xsts=n*Gs.dot(xt) -xstg=n*Gg.dot(xt) - -pl.figure(4) -pl.subplot(2,3,1) - +xst0 = n * G0.dot(xt) +xsts = n * Gs.dot(xt) +xstg = n * Gg.dot(xt) -pl.scatter(xt[:,0],xt[:,1],c=yt,marker='o',label='Target samples',alpha=0.5) -pl.scatter(xst0[:,0],xst0[:,1],c=ys,marker='+',label='Transp samples',s=30) +pl.figure(4, figsize=(8, 3)) +pl.subplot(1, 3, 1) +pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', + label='Target samples', alpha=0.5) +pl.scatter(xst0[:, 0], xst0[:, 1], c=ys, + marker='+', label='Transp samples', s=30) pl.title('Interp samples') pl.legend(loc=0) -pl.subplot(2,3,2) - - -pl.scatter(xt[:,0],xt[:,1],c=yt,marker='o',label='Target samples',alpha=0.5) -pl.scatter(xsts[:,0],xsts[:,1],c=ys,marker='+',label='Transp samples',s=30) +pl.subplot(1, 3, 2) +pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', + label='Target samples', alpha=0.5) +pl.scatter(xsts[:, 0], xsts[:, 1], c=ys, + marker='+', label='Transp samples', s=30) pl.title('Interp samples Sinkhorn') -pl.subplot(2,3,3) - -pl.scatter(xt[:,0],xt[:,1],c=yt,marker='o',label='Target samples',alpha=0.5) -pl.scatter(xstg[:,0],xstg[:,1],c=ys,marker='+',label='Transp samples',s=30) -pl.title('Interp samples Grouplasso') \ No newline at end of file +pl.subplot(1, 3, 3) +pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', + label='Target samples', alpha=0.5) +pl.scatter(xstg[:, 0], xstg[:, 1], c=ys, + marker='+', label='Transp samples', s=30) +pl.title('Interp samples Grouplasso') +pl.tight_layout() +pl.show() diff --git a/examples/plot_OTDA_classes.py b/examples/plot_OTDA_classes.py index 089b45b..4d3846a 100644 --- a/examples/plot_OTDA_classes.py +++ b/examples/plot_OTDA_classes.py @@ -10,29 +10,25 @@ import matplotlib.pylab as pl import ot - - #%% parameters -n=150 # nb samples in source and target datasets - -xs,ys=ot.datasets.get_data_classif('3gauss',n) -xt,yt=ot.datasets.get_data_classif('3gauss2',n) - +n = 150 # nb samples in source and target datasets +xs, ys = ot.datasets.get_data_classif('3gauss', n) +xt, yt = ot.datasets.get_data_classif('3gauss2', n) #%% plot samples -pl.figure(1) +pl.figure(1, figsize=(6.4, 3)) -pl.subplot(2,2,1) -pl.scatter(xs[:,0],xs[:,1],c=ys,marker='+',label='Source samples') +pl.subplot(1, 2, 1) +pl.scatter(xs[:, 0], xs[:, 1], c=ys, marker='+', label='Source samples') pl.legend(loc=0) pl.title('Source distributions') -pl.subplot(2,2,2) -pl.scatter(xt[:,0],xt[:,1],c=yt,marker='o',label='Target samples') +pl.subplot(1, 2, 2) +pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', label='Target samples') pl.legend(loc=0) pl.title('target distributions') @@ -40,73 +36,78 @@ pl.title('target distributions') #%% OT estimation # LP problem -da_emd=ot.da.OTDA() # init class -da_emd.fit(xs,xt) # fit distributions -xst0=da_emd.interp() # interpolation of source samples - +da_emd = ot.da.OTDA() # init class +da_emd.fit(xs, xt) # fit distributions +xst0 = da_emd.interp() # interpolation of source samples # sinkhorn regularization -lambd=1e-1 -da_entrop=ot.da.OTDA_sinkhorn() -da_entrop.fit(xs,xt,reg=lambd) -xsts=da_entrop.interp() +lambd = 1e-1 +da_entrop = ot.da.OTDA_sinkhorn() +da_entrop.fit(xs, xt, reg=lambd) +xsts = da_entrop.interp() # non-convex Group lasso regularization -reg=1e-1 -eta=1e0 -da_lpl1=ot.da.OTDA_lpl1() -da_lpl1.fit(xs,ys,xt,reg=reg,eta=eta) -xstg=da_lpl1.interp() - +reg = 1e-1 +eta = 1e0 +da_lpl1 = ot.da.OTDA_lpl1() +da_lpl1.fit(xs, ys, xt, reg=reg, eta=eta) +xstg = da_lpl1.interp() # True Group lasso regularization -reg=1e-1 -eta=2e0 -da_l1l2=ot.da.OTDA_l1l2() -da_l1l2.fit(xs,ys,xt,reg=reg,eta=eta,numItermax=20,verbose=True) -xstgl=da_l1l2.interp() - +reg = 1e-1 +eta = 2e0 +da_l1l2 = ot.da.OTDA_l1l2() +da_l1l2.fit(xs, ys, xt, reg=reg, eta=eta, numItermax=20, verbose=True) +xstgl = da_l1l2.interp() #%% plot interpolated source samples -pl.figure(4,(15,8)) -param_img={'interpolation':'nearest','cmap':'jet'} +param_img = {'interpolation': 'nearest', 'cmap': 'spectral'} -pl.subplot(2,4,1) -pl.imshow(da_emd.G,**param_img) +pl.figure(2, figsize=(8, 4.5)) +pl.subplot(2, 4, 1) +pl.imshow(da_emd.G, **param_img) pl.title('OT matrix') +pl.subplot(2, 4, 2) +pl.imshow(da_entrop.G, **param_img) +pl.title('OT matrix\nsinkhorn') -pl.subplot(2,4,2) -pl.imshow(da_entrop.G,**param_img) -pl.title('OT matrix sinkhorn') - -pl.subplot(2,4,3) -pl.imshow(da_lpl1.G,**param_img) -pl.title('OT matrix non-convex Group Lasso') - -pl.subplot(2,4,4) -pl.imshow(da_l1l2.G,**param_img) -pl.title('OT matrix Group Lasso') +pl.subplot(2, 4, 3) +pl.imshow(da_lpl1.G, **param_img) +pl.title('OT matrix\nnon-convex Group Lasso') +pl.subplot(2, 4, 4) +pl.imshow(da_l1l2.G, **param_img) +pl.title('OT matrix\nGroup Lasso') -pl.subplot(2,4,5) -pl.scatter(xt[:,0],xt[:,1],c=yt,marker='o',label='Target samples',alpha=0.3) -pl.scatter(xst0[:,0],xst0[:,1],c=ys,marker='+',label='Transp samples',s=30) +pl.subplot(2, 4, 5) +pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', + label='Target samples', alpha=0.3) +pl.scatter(xst0[:, 0], xst0[:, 1], c=ys, + marker='+', label='Transp samples', s=30) pl.title('Interp samples') pl.legend(loc=0) -pl.subplot(2,4,6) -pl.scatter(xt[:,0],xt[:,1],c=yt,marker='o',label='Target samples',alpha=0.3) -pl.scatter(xsts[:,0],xsts[:,1],c=ys,marker='+',label='Transp samples',s=30) -pl.title('Interp samples Sinkhorn') - -pl.subplot(2,4,7) -pl.scatter(xt[:,0],xt[:,1],c=yt,marker='o',label='Target samples',alpha=0.3) -pl.scatter(xstg[:,0],xstg[:,1],c=ys,marker='+',label='Transp samples',s=30) -pl.title('Interp samples non-convex Group Lasso') - -pl.subplot(2,4,8) -pl.scatter(xt[:,0],xt[:,1],c=yt,marker='o',label='Target samples',alpha=0.3) -pl.scatter(xstgl[:,0],xstgl[:,1],c=ys,marker='+',label='Transp samples',s=30) -pl.title('Interp samples Group Lasso') \ No newline at end of file +pl.subplot(2, 4, 6) +pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', + label='Target samples', alpha=0.3) +pl.scatter(xsts[:, 0], xsts[:, 1], c=ys, + marker='+', label='Transp samples', s=30) +pl.title('Interp samples\nSinkhorn') + +pl.subplot(2, 4, 7) +pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', + label='Target samples', alpha=0.3) +pl.scatter(xstg[:, 0], xstg[:, 1], c=ys, + marker='+', label='Transp samples', s=30) +pl.title('Interp samples\nnon-convex Group Lasso') + +pl.subplot(2, 4, 8) +pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', + label='Target samples', alpha=0.3) +pl.scatter(xstgl[:, 0], xstgl[:, 1], c=ys, + marker='+', label='Transp samples', s=30) +pl.title('Interp samples\nGroup Lasso') +pl.tight_layout() +pl.show() diff --git a/examples/plot_OTDA_color_images.py b/examples/plot_OTDA_color_images.py index 68eee44..a8861c6 100644 --- a/examples/plot_OTDA_color_images.py +++ b/examples/plot_OTDA_color_images.py @@ -4,142 +4,143 @@ OT for domain adaptation with image color adaptation [6] ======================================================== -[6] Ferradans, S., Papadakis, N., Peyre, G., & Aujol, J. F. (2014). Regularized discrete optimal transport. SIAM Journal on Imaging Sciences, 7(3), 1853-1882. +[6] Ferradans, S., Papadakis, N., Peyre, G., & Aujol, J. F. (2014). +Regularized discrete optimal transport. +SIAM Journal on Imaging Sciences, 7(3), 1853-1882. """ import numpy as np -import scipy.ndimage as spi +from scipy import ndimage import matplotlib.pylab as pl import ot #%% Loading images -I1=spi.imread('../data/ocean_day.jpg').astype(np.float64)/256 -I2=spi.imread('../data/ocean_sunset.jpg').astype(np.float64)/256 +I1 = ndimage.imread('../data/ocean_day.jpg').astype(np.float64) / 256 +I2 = ndimage.imread('../data/ocean_sunset.jpg').astype(np.float64) / 256 #%% Plot images -pl.figure(1) +pl.figure(1, figsize=(6.4, 3)) -pl.subplot(1,2,1) +pl.subplot(1, 2, 1) pl.imshow(I1) +pl.axis('off') pl.title('Image 1') -pl.subplot(1,2,2) +pl.subplot(1, 2, 2) pl.imshow(I2) +pl.axis('off') pl.title('Image 2') pl.show() #%% Image conversion and dataset generation + def im2mat(I): """Converts and image to matrix (one pixel per line)""" - return I.reshape((I.shape[0]*I.shape[1],I.shape[2])) + return I.reshape((I.shape[0] * I.shape[1], I.shape[2])) + -def mat2im(X,shape): +def mat2im(X, shape): """Converts back a matrix to an image""" return X.reshape(shape) -X1=im2mat(I1) -X2=im2mat(I2) +X1 = im2mat(I1) +X2 = im2mat(I2) # training samples -nb=1000 -idx1=np.random.randint(X1.shape[0],size=(nb,)) -idx2=np.random.randint(X2.shape[0],size=(nb,)) +nb = 1000 +idx1 = np.random.randint(X1.shape[0], size=(nb,)) +idx2 = np.random.randint(X2.shape[0], size=(nb,)) -xs=X1[idx1,:] -xt=X2[idx2,:] +xs = X1[idx1, :] +xt = X2[idx2, :] #%% Plot image distributions -pl.figure(2,(10,5)) +pl.figure(2, figsize=(6.4, 3)) -pl.subplot(1,2,1) -pl.scatter(xs[:,0],xs[:,2],c=xs) -pl.axis([0,1,0,1]) +pl.subplot(1, 2, 1) +pl.scatter(xs[:, 0], xs[:, 2], c=xs) +pl.axis([0, 1, 0, 1]) pl.xlabel('Red') pl.ylabel('Blue') pl.title('Image 1') -pl.subplot(1,2,2) -#pl.imshow(I2) -pl.scatter(xt[:,0],xt[:,2],c=xt) -pl.axis([0,1,0,1]) +pl.subplot(1, 2, 2) +pl.scatter(xt[:, 0], xt[:, 2], c=xt) +pl.axis([0, 1, 0, 1]) pl.xlabel('Red') pl.ylabel('Blue') pl.title('Image 2') - -pl.show() - - +pl.tight_layout() #%% domain adaptation between images # LP problem -da_emd=ot.da.OTDA() # init class -da_emd.fit(xs,xt) # fit distributions - +da_emd = ot.da.OTDA() # init class +da_emd.fit(xs, xt) # fit distributions # sinkhorn regularization -lambd=1e-1 -da_entrop=ot.da.OTDA_sinkhorn() -da_entrop.fit(xs,xt,reg=lambd) - - +lambd = 1e-1 +da_entrop = ot.da.OTDA_sinkhorn() +da_entrop.fit(xs, xt, reg=lambd) #%% prediction between images (using out of sample prediction as in [6]) -X1t=da_emd.predict(X1) -X2t=da_emd.predict(X2,-1) +X1t = da_emd.predict(X1) +X2t = da_emd.predict(X2, -1) - -X1te=da_entrop.predict(X1) -X2te=da_entrop.predict(X2,-1) +X1te = da_entrop.predict(X1) +X2te = da_entrop.predict(X2, -1) def minmax(I): - return np.minimum(np.maximum(I,0),1) + return np.clip(I, 0, 1) -I1t=minmax(mat2im(X1t,I1.shape)) -I2t=minmax(mat2im(X2t,I2.shape)) +I1t = minmax(mat2im(X1t, I1.shape)) +I2t = minmax(mat2im(X2t, I2.shape)) -I1te=minmax(mat2im(X1te,I1.shape)) -I2te=minmax(mat2im(X2te,I2.shape)) +I1te = minmax(mat2im(X1te, I1.shape)) +I2te = minmax(mat2im(X2te, I2.shape)) #%% plot all images -pl.figure(2,(10,8)) - -pl.subplot(2,3,1) +pl.figure(2, figsize=(8, 4)) +pl.subplot(2, 3, 1) pl.imshow(I1) +pl.axis('off') pl.title('Image 1') -pl.subplot(2,3,2) +pl.subplot(2, 3, 2) pl.imshow(I1t) +pl.axis('off') pl.title('Image 1 Adapt') - -pl.subplot(2,3,3) +pl.subplot(2, 3, 3) pl.imshow(I1te) +pl.axis('off') pl.title('Image 1 Adapt (reg)') -pl.subplot(2,3,4) - +pl.subplot(2, 3, 4) pl.imshow(I2) +pl.axis('off') pl.title('Image 2') -pl.subplot(2,3,5) +pl.subplot(2, 3, 5) pl.imshow(I2t) +pl.axis('off') pl.title('Image 2 Adapt') - -pl.subplot(2,3,6) +pl.subplot(2, 3, 6) pl.imshow(I2te) +pl.axis('off') pl.title('Image 2 Adapt (reg)') +pl.tight_layout() pl.show() diff --git a/examples/plot_OT_2D_samples.py b/examples/plot_OT_2D_samples.py index 3a93591..75ed7db 100644 --- a/examples/plot_OT_2D_samples.py +++ b/examples/plot_OT_2D_samples.py @@ -8,7 +8,7 @@ """ import numpy as np -import matplotlib.pylab as plt +import matplotlib.pylab as pl import ot #%% parameters and data generation @@ -32,31 +32,31 @@ M /= M.max() #%% plot samples -plt.figure(1) -plt.plot(xs[:, 0], xs[:, 1], '+b', label='Source samples') -plt.plot(xt[:, 0], xt[:, 1], 'xr', label='Target samples') -plt.legend(loc=0) -plt.title('Source and target distributions') +pl.figure(1) +pl.plot(xs[:, 0], xs[:, 1], '+b', label='Source samples') +pl.plot(xt[:, 0], xt[:, 1], 'xr', label='Target samples') +pl.legend(loc=0) +pl.title('Source and target distributions') -plt.figure(2) -plt.imshow(M, interpolation='nearest') -plt.title('Cost matrix M') +pl.figure(2) +pl.imshow(M, interpolation='nearest') +pl.title('Cost matrix M') #%% EMD G0 = ot.emd(a, b, M) -plt.figure(3) -plt.imshow(G0, interpolation='nearest') -plt.title('OT matrix G0') +pl.figure(3) +pl.imshow(G0, interpolation='nearest') +pl.title('OT matrix G0') -plt.figure(4) +pl.figure(4) ot.plot.plot2D_samples_mat(xs, xt, G0, c=[.5, .5, 1]) -plt.plot(xs[:, 0], xs[:, 1], '+b', label='Source samples') -plt.plot(xt[:, 0], xt[:, 1], 'xr', label='Target samples') -plt.legend(loc=0) -plt.title('OT matrix with samples') +pl.plot(xs[:, 0], xs[:, 1], '+b', label='Source samples') +pl.plot(xt[:, 0], xt[:, 1], 'xr', label='Target samples') +pl.legend(loc=0) +pl.title('OT matrix with samples') #%% sinkhorn @@ -66,15 +66,15 @@ lambd = 5e-4 Gs = ot.sinkhorn(a, b, M, lambd) -plt.figure(5) -plt.imshow(Gs, interpolation='nearest') -plt.title('OT matrix sinkhorn') +pl.figure(5) +pl.imshow(Gs, interpolation='nearest') +pl.title('OT matrix sinkhorn') -plt.figure(6) +pl.figure(6) ot.plot.plot2D_samples_mat(xs, xt, Gs, color=[.5, .5, 1]) -plt.plot(xs[:, 0], xs[:, 1], '+b', label='Source samples') -plt.plot(xt[:, 0], xt[:, 1], 'xr', label='Target samples') -plt.legend(loc=0) -plt.title('OT matrix Sinkhorn with samples') +pl.plot(xs[:, 0], xs[:, 1], '+b', label='Source samples') +pl.plot(xt[:, 0], xt[:, 1], 'xr', label='Target samples') +pl.legend(loc=0) +pl.title('OT matrix Sinkhorn with samples') -plt.show() +pl.show() diff --git a/examples/plot_OT_L1_vs_L2.py b/examples/plot_OT_L1_vs_L2.py index e11d6ad..86d902b 100644 --- a/examples/plot_OT_L1_vs_L2.py +++ b/examples/plot_OT_L1_vs_L2.py @@ -12,7 +12,7 @@ https://arxiv.org/pdf/1706.07650.pdf """ import numpy as np -import matplotlib.pylab as plt +import matplotlib.pylab as pl import ot #%% parameters and data generation @@ -55,58 +55,58 @@ for data in range(2): #%% plot samples - plt.figure(1 + 3 * data, figsize=(7, 3)) - plt.clf() - plt.plot(xs[:, 0], xs[:, 1], '+b', label='Source samples') - plt.plot(xt[:, 0], xt[:, 1], 'xr', label='Target samples') - plt.axis('equal') - plt.title('Source and traget distributions') + pl.figure(1 + 3 * data, figsize=(7, 3)) + pl.clf() + pl.plot(xs[:, 0], xs[:, 1], '+b', label='Source samples') + pl.plot(xt[:, 0], xt[:, 1], 'xr', label='Target samples') + pl.axis('equal') + pl.title('Source and traget distributions') - plt.figure(2 + 3 * data, figsize=(7, 3)) + pl.figure(2 + 3 * data, figsize=(7, 3)) - plt.subplot(1, 3, 1) - plt.imshow(M1, interpolation='nearest') - plt.title('Euclidean cost') + pl.subplot(1, 3, 1) + pl.imshow(M1, interpolation='nearest') + pl.title('Euclidean cost') - plt.subplot(1, 3, 2) - plt.imshow(M2, interpolation='nearest') - plt.title('Squared Euclidean cost') + pl.subplot(1, 3, 2) + pl.imshow(M2, interpolation='nearest') + pl.title('Squared Euclidean cost') - plt.subplot(1, 3, 3) - plt.imshow(Mp, interpolation='nearest') - plt.title('Sqrt Euclidean cost') - plt.tight_layout() + pl.subplot(1, 3, 3) + pl.imshow(Mp, interpolation='nearest') + pl.title('Sqrt Euclidean cost') + pl.tight_layout() #%% EMD G1 = ot.emd(a, b, M1) G2 = ot.emd(a, b, M2) Gp = ot.emd(a, b, Mp) - plt.figure(3 + 3 * data, figsize=(7, 3)) + pl.figure(3 + 3 * data, figsize=(7, 3)) - plt.subplot(1, 3, 1) + pl.subplot(1, 3, 1) ot.plot.plot2D_samples_mat(xs, xt, G1, c=[.5, .5, 1]) - plt.plot(xs[:, 0], xs[:, 1], '+b', label='Source samples') - plt.plot(xt[:, 0], xt[:, 1], 'xr', label='Target samples') - plt.axis('equal') - # plt.legend(loc=0) - plt.title('OT Euclidean') + pl.plot(xs[:, 0], xs[:, 1], '+b', label='Source samples') + pl.plot(xt[:, 0], xt[:, 1], 'xr', label='Target samples') + pl.axis('equal') + # pl.legend(loc=0) + pl.title('OT Euclidean') - plt.subplot(1, 3, 2) + pl.subplot(1, 3, 2) ot.plot.plot2D_samples_mat(xs, xt, G2, c=[.5, .5, 1]) - plt.plot(xs[:, 0], xs[:, 1], '+b', label='Source samples') - plt.plot(xt[:, 0], xt[:, 1], 'xr', label='Target samples') - plt.axis('equal') - # plt.legend(loc=0) - plt.title('OT squared Euclidean') + pl.plot(xs[:, 0], xs[:, 1], '+b', label='Source samples') + pl.plot(xt[:, 0], xt[:, 1], 'xr', label='Target samples') + pl.axis('equal') + # pl.legend(loc=0) + pl.title('OT squared Euclidean') - plt.subplot(1, 3, 3) + pl.subplot(1, 3, 3) ot.plot.plot2D_samples_mat(xs, xt, Gp, c=[.5, .5, 1]) - plt.plot(xs[:, 0], xs[:, 1], '+b', label='Source samples') - plt.plot(xt[:, 0], xt[:, 1], 'xr', label='Target samples') - plt.axis('equal') - # plt.legend(loc=0) - plt.title('OT sqrt Euclidean') - plt.tight_layout() - -plt.show() + pl.plot(xs[:, 0], xs[:, 1], '+b', label='Source samples') + pl.plot(xt[:, 0], xt[:, 1], 'xr', label='Target samples') + pl.axis('equal') + # pl.legend(loc=0) + pl.title('OT sqrt Euclidean') + pl.tight_layout() + +pl.show() -- cgit v1.2.3 From 823942312962134a3b49fcedbca7c4e2a7419df3 Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Wed, 12 Jul 2017 22:55:00 +0200 Subject: more --- examples/plot_OTDA_mapping.py | 118 +++++++++++++++++++++++------------------- 1 file changed, 64 insertions(+), 54 deletions(-) diff --git a/examples/plot_OTDA_mapping.py b/examples/plot_OTDA_mapping.py index 78b57e7..a5c2b21 100644 --- a/examples/plot_OTDA_mapping.py +++ b/examples/plot_OTDA_mapping.py @@ -4,8 +4,9 @@ OT mapping estimation for domain adaptation [8] =============================================== -[8] M. Perrot, N. Courty, R. Flamary, A. Habrard, "Mapping estimation for - discrete optimal transport", Neural Information Processing Systems (NIPS), 2016. +[8] M. Perrot, N. Courty, R. Flamary, A. Habrard, + "Mapping estimation for discrete optimal transport", + Neural Information Processing Systems (NIPS), 2016. """ import numpy as np @@ -13,98 +14,107 @@ import matplotlib.pylab as pl import ot - #%% dataset generation -np.random.seed(0) # makes example reproducible +np.random.seed(0) # makes example reproducible -n=100 # nb samples in source and target datasets -theta=2*np.pi/20 -nz=0.1 -xs,ys=ot.datasets.get_data_classif('gaussrot',n,nz=nz) -xt,yt=ot.datasets.get_data_classif('gaussrot',n,theta=theta,nz=nz) +n = 100 # nb samples in source and target datasets +theta = 2 * np.pi / 20 +nz = 0.1 +xs, ys = ot.datasets.get_data_classif('gaussrot', n, nz=nz) +xt, yt = ot.datasets.get_data_classif('gaussrot', n, theta=theta, nz=nz) # one of the target mode changes its variance (no linear mapping) -xt[yt==2]*=3 -xt=xt+4 +xt[yt == 2] *= 3 +xt = xt + 4 #%% plot samples -pl.figure(1,(8,5)) +pl.figure(1, (6.4, 3)) pl.clf() - -pl.scatter(xs[:,0],xs[:,1],c=ys,marker='+',label='Source samples') -pl.scatter(xt[:,0],xt[:,1],c=yt,marker='o',label='Target samples') - +pl.scatter(xs[:, 0], xs[:, 1], c=ys, marker='+', label='Source samples') +pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', label='Target samples') pl.legend(loc=0) pl.title('Source and target distributions') - #%% OT linear mapping estimation -eta=1e-8 # quadratic regularization for regression -mu=1e0 # weight of the OT linear term -bias=True # estimate a bias +eta = 1e-8 # quadratic regularization for regression +mu = 1e0 # weight of the OT linear term +bias = True # estimate a bias -ot_mapping=ot.da.OTDA_mapping_linear() -ot_mapping.fit(xs,xt,mu=mu,eta=eta,bias=bias,numItermax = 20,verbose=True) +ot_mapping = ot.da.OTDA_mapping_linear() +ot_mapping.fit(xs, xt, mu=mu, eta=eta, bias=bias, numItermax=20, verbose=True) -xst=ot_mapping.predict(xs) # use the estimated mapping -xst0=ot_mapping.interp() # use barycentric mapping +xst = ot_mapping.predict(xs) # use the estimated mapping +xst0 = ot_mapping.interp() # use barycentric mapping -pl.figure(2,(10,7)) +pl.figure(2) pl.clf() -pl.subplot(2,2,1) -pl.scatter(xt[:,0],xt[:,1],c=yt,marker='o',label='Target samples',alpha=.3) -pl.scatter(xst0[:,0],xst0[:,1],c=ys,marker='+',label='barycentric mapping') +pl.subplot(2, 2, 1) +pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', + label='Target samples', alpha=.3) +pl.scatter(xst0[:, 0], xst0[:, 1], c=ys, + marker='+', label='barycentric mapping') pl.title("barycentric mapping") -pl.subplot(2,2,2) -pl.scatter(xt[:,0],xt[:,1],c=yt,marker='o',label='Target samples',alpha=.3) -pl.scatter(xst[:,0],xst[:,1],c=ys,marker='+',label='Learned mapping') +pl.subplot(2, 2, 2) +pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', + label='Target samples', alpha=.3) +pl.scatter(xst[:, 0], xst[:, 1], c=ys, marker='+', label='Learned mapping') pl.title("Learned mapping") - - +pl.tight_layout() #%% Kernel mapping estimation -eta=1e-5 # quadratic regularization for regression -mu=1e-1 # weight of the OT linear term -bias=True # estimate a bias -sigma=1 # sigma bandwidth fot gaussian kernel +eta = 1e-5 # quadratic regularization for regression +mu = 1e-1 # weight of the OT linear term +bias = True # estimate a bias +sigma = 1 # sigma bandwidth fot gaussian kernel -ot_mapping_kernel=ot.da.OTDA_mapping_kernel() -ot_mapping_kernel.fit(xs,xt,mu=mu,eta=eta,sigma=sigma,bias=bias,numItermax = 10,verbose=True) +ot_mapping_kernel = ot.da.OTDA_mapping_kernel() +ot_mapping_kernel.fit( + xs, xt, mu=mu, eta=eta, sigma=sigma, bias=bias, numItermax=10, verbose=True) -xst_kernel=ot_mapping_kernel.predict(xs) # use the estimated mapping -xst0_kernel=ot_mapping_kernel.interp() # use barycentric mapping +xst_kernel = ot_mapping_kernel.predict(xs) # use the estimated mapping +xst0_kernel = ot_mapping_kernel.interp() # use barycentric mapping #%% Plotting the mapped samples -pl.figure(2,(10,7)) +pl.figure(2) pl.clf() -pl.subplot(2,2,1) -pl.scatter(xt[:,0],xt[:,1],c=yt,marker='o',label='Target samples',alpha=.2) -pl.scatter(xst0[:,0],xst0[:,1],c=ys,marker='+',label='Mapped source samples') +pl.subplot(2, 2, 1) +pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', + label='Target samples', alpha=.2) +pl.scatter(xst0[:, 0], xst0[:, 1], c=ys, marker='+', + label='Mapped source samples') pl.title("Bary. mapping (linear)") pl.legend(loc=0) -pl.subplot(2,2,2) -pl.scatter(xt[:,0],xt[:,1],c=yt,marker='o',label='Target samples',alpha=.2) -pl.scatter(xst[:,0],xst[:,1],c=ys,marker='+',label='Learned mapping') +pl.subplot(2, 2, 2) +pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', + label='Target samples', alpha=.2) +pl.scatter(xst[:, 0], xst[:, 1], c=ys, marker='+', label='Learned mapping') pl.title("Estim. mapping (linear)") -pl.subplot(2,2,3) -pl.scatter(xt[:,0],xt[:,1],c=yt,marker='o',label='Target samples',alpha=.2) -pl.scatter(xst0_kernel[:,0],xst0_kernel[:,1],c=ys,marker='+',label='barycentric mapping') +pl.subplot(2, 2, 3) +pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', + label='Target samples', alpha=.2) +pl.scatter(xst0_kernel[:, 0], xst0_kernel[:, 1], c=ys, + marker='+', label='barycentric mapping') pl.title("Bary. mapping (kernel)") -pl.subplot(2,2,4) -pl.scatter(xt[:,0],xt[:,1],c=yt,marker='o',label='Target samples',alpha=.2) -pl.scatter(xst_kernel[:,0],xst_kernel[:,1],c=ys,marker='+',label='Learned mapping') +pl.subplot(2, 2, 4) +pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', + label='Target samples', alpha=.2) +pl.scatter(xst_kernel[:, 0], xst_kernel[:, 1], c=ys, + marker='+', label='Learned mapping') pl.title("Estim. mapping (kernel)") +pl.tight_layout() + +pl.show() -- cgit v1.2.3 From 7ad472500dcce284231fc5968effa755802ab4ea Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Wed, 12 Jul 2017 23:09:20 +0200 Subject: more --- examples/plot_OTDA_mapping_color_images.py | 136 +++++++++++++++-------------- setup.cfg | 2 +- 2 files changed, 70 insertions(+), 68 deletions(-) diff --git a/examples/plot_OTDA_mapping_color_images.py b/examples/plot_OTDA_mapping_color_images.py index f07dc6c..b42dcdc 100644 --- a/examples/plot_OTDA_mapping_color_images.py +++ b/examples/plot_OTDA_mapping_color_images.py @@ -12,147 +12,149 @@ OT for domain adaptation with image color adaptation [6] with mapping estimation """ import numpy as np -import scipy.ndimage as spi +from scipy import ndimage import matplotlib.pylab as pl import ot #%% Loading images -I1=spi.imread('../data/ocean_day.jpg').astype(np.float64)/256 -I2=spi.imread('../data/ocean_sunset.jpg').astype(np.float64)/256 +I1 = ndimage.imread('../data/ocean_day.jpg').astype(np.float64) / 256 +I2 = ndimage.imread('../data/ocean_sunset.jpg').astype(np.float64) / 256 #%% Plot images -pl.figure(1) - -pl.subplot(1,2,1) +pl.figure(1, figsize=(6.4, 3)) +pl.subplot(1, 2, 1) pl.imshow(I1) +pl.axis('off') pl.title('Image 1') -pl.subplot(1,2,2) +pl.subplot(1, 2, 2) pl.imshow(I2) +pl.axis('off') pl.title('Image 2') - -pl.show() +pl.tight_layout() #%% Image conversion and dataset generation def im2mat(I): """Converts and image to matrix (one pixel per line)""" - return I.reshape((I.shape[0]*I.shape[1],I.shape[2])) + return I.reshape((I.shape[0] * I.shape[1], I.shape[2])) + -def mat2im(X,shape): +def mat2im(X, shape): """Converts back a matrix to an image""" return X.reshape(shape) -X1=im2mat(I1) -X2=im2mat(I2) +X1 = im2mat(I1) +X2 = im2mat(I2) # training samples -nb=1000 -idx1=np.random.randint(X1.shape[0],size=(nb,)) -idx2=np.random.randint(X2.shape[0],size=(nb,)) +nb = 1000 +idx1 = np.random.randint(X1.shape[0], size=(nb,)) +idx2 = np.random.randint(X2.shape[0], size=(nb,)) -xs=X1[idx1,:] -xt=X2[idx2,:] +xs = X1[idx1, :] +xt = X2[idx2, :] #%% Plot image distributions -pl.figure(2,(10,5)) +pl.figure(2, figsize=(6.4, 5)) -pl.subplot(1,2,1) -pl.scatter(xs[:,0],xs[:,2],c=xs) -pl.axis([0,1,0,1]) +pl.subplot(1, 2, 1) +pl.scatter(xs[:, 0], xs[:, 2], c=xs) +pl.axis([0, 1, 0, 1]) pl.xlabel('Red') pl.ylabel('Blue') pl.title('Image 1') -pl.subplot(1,2,2) -#pl.imshow(I2) -pl.scatter(xt[:,0],xt[:,2],c=xt) -pl.axis([0,1,0,1]) +pl.subplot(1, 2, 2) +pl.scatter(xt[:, 0], xt[:, 2], c=xt) +pl.axis([0, 1, 0, 1]) pl.xlabel('Red') pl.ylabel('Blue') pl.title('Image 2') - -pl.show() - - +pl.tight_layout() #%% domain adaptation between images def minmax(I): - return np.minimum(np.maximum(I,0),1) + return np.clip(I, 0, 1) + # LP problem -da_emd=ot.da.OTDA() # init class -da_emd.fit(xs,xt) # fit distributions +da_emd = ot.da.OTDA() # init class +da_emd.fit(xs, xt) # fit distributions -X1t=da_emd.predict(X1) # out of sample -I1t=minmax(mat2im(X1t,I1.shape)) +X1t = da_emd.predict(X1) # out of sample +I1t = minmax(mat2im(X1t, I1.shape)) # sinkhorn regularization -lambd=1e-1 -da_entrop=ot.da.OTDA_sinkhorn() -da_entrop.fit(xs,xt,reg=lambd) +lambd = 1e-1 +da_entrop = ot.da.OTDA_sinkhorn() +da_entrop.fit(xs, xt, reg=lambd) -X1te=da_entrop.predict(X1) -I1te=minmax(mat2im(X1te,I1.shape)) +X1te = da_entrop.predict(X1) +I1te = minmax(mat2im(X1te, I1.shape)) # linear mapping estimation -eta=1e-8 # quadratic regularization for regression -mu=1e0 # weight of the OT linear term -bias=True # estimate a bias +eta = 1e-8 # quadratic regularization for regression +mu = 1e0 # weight of the OT linear term +bias = True # estimate a bias -ot_mapping=ot.da.OTDA_mapping_linear() -ot_mapping.fit(xs,xt,mu=mu,eta=eta,bias=bias,numItermax = 20,verbose=True) +ot_mapping = ot.da.OTDA_mapping_linear() +ot_mapping.fit(xs, xt, mu=mu, eta=eta, bias=bias, numItermax=20, verbose=True) -X1tl=ot_mapping.predict(X1) # use the estimated mapping -I1tl=minmax(mat2im(X1tl,I1.shape)) +X1tl = ot_mapping.predict(X1) # use the estimated mapping +I1tl = minmax(mat2im(X1tl, I1.shape)) # nonlinear mapping estimation -eta=1e-2 # quadratic regularization for regression -mu=1e0 # weight of the OT linear term -bias=False # estimate a bias -sigma=1 # sigma bandwidth fot gaussian kernel - +eta = 1e-2 # quadratic regularization for regression +mu = 1e0 # weight of the OT linear term +bias = False # estimate a bias +sigma = 1 # sigma bandwidth fot gaussian kernel -ot_mapping_kernel=ot.da.OTDA_mapping_kernel() -ot_mapping_kernel.fit(xs,xt,mu=mu,eta=eta,sigma=sigma,bias=bias,numItermax = 10,verbose=True) -X1tn=ot_mapping_kernel.predict(X1) # use the estimated mapping -I1tn=minmax(mat2im(X1tn,I1.shape)) -#%% plot images +ot_mapping_kernel = ot.da.OTDA_mapping_kernel() +ot_mapping_kernel.fit( + xs, xt, mu=mu, eta=eta, sigma=sigma, bias=bias, numItermax=10, verbose=True) +X1tn = ot_mapping_kernel.predict(X1) # use the estimated mapping +I1tn = minmax(mat2im(X1tn, I1.shape)) -pl.figure(2,(10,8)) +#%% plot images -pl.subplot(2,3,1) +pl.figure(2, figsize=(8, 4)) +pl.subplot(2, 3, 1) pl.imshow(I1) +pl.axis('off') pl.title('Im. 1') -pl.subplot(2,3,2) - +pl.subplot(2, 3, 2) pl.imshow(I2) +pl.axis('off') pl.title('Im. 2') - -pl.subplot(2,3,3) +pl.subplot(2, 3, 3) pl.imshow(I1t) +pl.axis('off') pl.title('Im. 1 Interp LP') -pl.subplot(2,3,4) +pl.subplot(2, 3, 4) pl.imshow(I1te) +pl.axis('off') pl.title('Im. 1 Interp Entrop') - -pl.subplot(2,3,5) +pl.subplot(2, 3, 5) pl.imshow(I1tl) +pl.axis('off') pl.title('Im. 1 Linear mapping') -pl.subplot(2,3,6) +pl.subplot(2, 3, 6) pl.imshow(I1tn) +pl.axis('off') pl.title('Im. 1 nonlinear mapping') +pl.tight_layout() pl.show() diff --git a/setup.cfg b/setup.cfg index d010702..b2a2415 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,4 +3,4 @@ description-file = README.md [flake8] exclude = __init__.py -ignore = E265 +ignore = E265,E501 -- cgit v1.2.3 From 6d7fd7e9faffa777cef222bdfc48c7ad732ab950 Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Wed, 12 Jul 2017 23:16:00 +0200 Subject: more --- examples/plot_WDA.py | 86 +++++++++++++++++++++++++++------------------------- requirements.txt | 2 ++ 2 files changed, 46 insertions(+), 42 deletions(-) diff --git a/examples/plot_WDA.py b/examples/plot_WDA.py index 5fa2ab1..8a44022 100644 --- a/examples/plot_WDA.py +++ b/examples/plot_WDA.py @@ -16,81 +16,83 @@ from ot.dr import wda, fda #%% parameters -n=1000 # nb samples in source and target datasets -nz=0.2 +n = 1000 # nb samples in source and target datasets +nz = 0.2 # generate circle dataset -t=np.random.rand(n)*2*np.pi -ys=np.floor((np.arange(n)*1.0/n*3))+1 -xs=np.concatenate((np.cos(t).reshape((-1,1)),np.sin(t).reshape((-1,1))),1) -xs=xs*ys.reshape(-1,1)+nz*np.random.randn(n,2) +t = np.random.rand(n) * 2 * np.pi +ys = np.floor((np.arange(n) * 1.0 / n * 3)) + 1 +xs = np.concatenate( + (np.cos(t).reshape((-1, 1)), np.sin(t).reshape((-1, 1))), 1) +xs = xs * ys.reshape(-1, 1) + nz * np.random.randn(n, 2) -t=np.random.rand(n)*2*np.pi -yt=np.floor((np.arange(n)*1.0/n*3))+1 -xt=np.concatenate((np.cos(t).reshape((-1,1)),np.sin(t).reshape((-1,1))),1) -xt=xt*yt.reshape(-1,1)+nz*np.random.randn(n,2) +t = np.random.rand(n) * 2 * np.pi +yt = np.floor((np.arange(n) * 1.0 / n * 3)) + 1 +xt = np.concatenate( + (np.cos(t).reshape((-1, 1)), np.sin(t).reshape((-1, 1))), 1) +xt = xt * yt.reshape(-1, 1) + nz * np.random.randn(n, 2) -nbnoise=8 +nbnoise = 8 -xs=np.hstack((xs,np.random.randn(n,nbnoise))) -xt=np.hstack((xt,np.random.randn(n,nbnoise))) +xs = np.hstack((xs, np.random.randn(n, nbnoise))) +xt = np.hstack((xt, np.random.randn(n, nbnoise))) #%% plot samples -pl.figure(1,(10,5)) +pl.figure(1, figsize=(6.4, 3.5)) -pl.subplot(1,2,1) -pl.scatter(xt[:,0],xt[:,1],c=ys,marker='+',label='Source samples') +pl.subplot(1, 2, 1) +pl.scatter(xt[:, 0], xt[:, 1], c=ys, marker='+', label='Source samples') pl.legend(loc=0) pl.title('Discriminant dimensions') -pl.subplot(1,2,2) -pl.scatter(xt[:,2],xt[:,3],c=ys,marker='+',label='Source samples') +pl.subplot(1, 2, 2) +pl.scatter(xt[:, 2], xt[:, 3], c=ys, marker='+', label='Source samples') pl.legend(loc=0) pl.title('Other dimensions') -pl.show() +pl.tight_layout() #%% Compute FDA -p=2 +p = 2 -Pfda,projfda = fda(xs,ys,p) +Pfda, projfda = fda(xs, ys, p) #%% Compute WDA -p=2 -reg=1e0 -k=10 -maxiter=100 +p = 2 +reg = 1e0 +k = 10 +maxiter = 100 -Pwda,projwda = wda(xs,ys,p,reg,k,maxiter=maxiter) +Pwda, projwda = wda(xs, ys, p, reg, k, maxiter=maxiter) #%% plot samples -xsp=projfda(xs) -xtp=projfda(xt) +xsp = projfda(xs) +xtp = projfda(xt) -xspw=projwda(xs) -xtpw=projwda(xt) +xspw = projwda(xs) +xtpw = projwda(xt) -pl.figure(1,(10,10)) +pl.figure(2) -pl.subplot(2,2,1) -pl.scatter(xsp[:,0],xsp[:,1],c=ys,marker='+',label='Projected samples') +pl.subplot(2, 2, 1) +pl.scatter(xsp[:, 0], xsp[:, 1], c=ys, marker='+', label='Projected samples') pl.legend(loc=0) pl.title('Projected training samples FDA') - -pl.subplot(2,2,2) -pl.scatter(xtp[:,0],xtp[:,1],c=ys,marker='+',label='Projected samples') +pl.subplot(2, 2, 2) +pl.scatter(xtp[:, 0], xtp[:, 1], c=ys, marker='+', label='Projected samples') pl.legend(loc=0) pl.title('Projected test samples FDA') - -pl.subplot(2,2,3) -pl.scatter(xspw[:,0],xspw[:,1],c=ys,marker='+',label='Projected samples') +pl.subplot(2, 2, 3) +pl.scatter(xspw[:, 0], xspw[:, 1], c=ys, marker='+', label='Projected samples') pl.legend(loc=0) pl.title('Projected training samples WDA') - -pl.subplot(2,2,4) -pl.scatter(xtpw[:,0],xtpw[:,1],c=ys,marker='+',label='Projected samples') +pl.subplot(2, 2, 4) +pl.scatter(xtpw[:, 0], xtpw[:, 1], c=ys, marker='+', label='Projected samples') pl.legend(loc=0) pl.title('Projected test samples WDA') +pl.tight_layout() + +pl.show() diff --git a/requirements.txt b/requirements.txt index 319f5e1..9bfca43 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,5 @@ scipy cython matplotlib sphinx-gallery +autograd +pymanopt -- cgit v1.2.3 From da21b9888e77f7512727a4f50c60bd475e2c9606 Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Wed, 12 Jul 2017 23:19:09 +0200 Subject: pep8 --- examples/plot_OTDA_mapping_color_images.py | 3 +++ examples/plot_WDA.py | 3 +-- examples/plot_optim_OTreg.py | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/plot_OTDA_mapping_color_images.py b/examples/plot_OTDA_mapping_color_images.py index b42dcdc..85c4b6b 100644 --- a/examples/plot_OTDA_mapping_color_images.py +++ b/examples/plot_OTDA_mapping_color_images.py @@ -36,6 +36,7 @@ pl.axis('off') pl.title('Image 2') pl.tight_layout() + #%% Image conversion and dataset generation def im2mat(I): @@ -78,7 +79,9 @@ pl.ylabel('Blue') pl.title('Image 2') pl.tight_layout() + #%% domain adaptation between images + def minmax(I): return np.clip(I, 0, 1) diff --git a/examples/plot_WDA.py b/examples/plot_WDA.py index 8a44022..9eb8693 100644 --- a/examples/plot_WDA.py +++ b/examples/plot_WDA.py @@ -9,8 +9,7 @@ Wasserstein Discriminant Analysis import numpy as np import matplotlib.pylab as pl -import ot -from ot.datasets import get_1D_gauss as gauss + from ot.dr import wda, fda diff --git a/examples/plot_optim_OTreg.py b/examples/plot_optim_OTreg.py index 1c3a1c5..e38253c 100644 --- a/examples/plot_optim_OTreg.py +++ b/examples/plot_optim_OTreg.py @@ -36,6 +36,7 @@ ot.plot.plot1D_mat(a, b, G0, 'OT matrix G0') #%% Example with Frobenius norm regularization + def f(G): return 0.5 * np.sum(G**2) -- cgit v1.2.3 From 38e696fc821d0e9bd977b69e3c38ecb3db4662ca Mon Sep 17 00:00:00 2001 From: "Dougal J. Sutherland" Date: Thu, 20 Jul 2017 13:07:23 +0100 Subject: fix conda-forge URL [skip ci] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bb3f9e4..c3dbf68 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ python setup.py install --user # for user install (no root) #### Anaconda installation with conda-forge -If you use the Anaconda python distribution, POT is available in [conda-forge](conda-forge.github.io). To install it and the required dependencies: +If you use the Anaconda python distribution, POT is available in [conda-forge](https://conda-forge.org). To install it and the required dependencies: ``` conda install -c conda-forge pot ``` -- cgit v1.2.3 From 37ca3142a4c8c808382f5eb1c23bf198c3e4610e Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Wed, 12 Jul 2017 23:52:57 +0200 Subject: pep8 --- ot/bregman.py | 495 +++++++++++++++++++++++++++++--------------------------- ot/da.py | 504 +++++++++++++++++++++++++++++---------------------------- ot/datasets.py | 107 ++++++------ ot/dr.py | 197 +++++++++++----------- ot/optim.py | 133 +++++++-------- 5 files changed, 735 insertions(+), 701 deletions(-) diff --git a/ot/bregman.py b/ot/bregman.py index 0d68602..fe10880 100644 --- a/ot/bregman.py +++ b/ot/bregman.py @@ -5,7 +5,8 @@ Bregman projections for regularized OT import numpy as np -def sinkhorn(a,b, M, reg,method='sinkhorn', numItermax = 1000, stopThr=1e-9, verbose=False, log=False,**kwargs): + +def sinkhorn(a, b, M, reg, method='sinkhorn', numItermax=1000, stopThr=1e-9, verbose=False, log=False, **kwargs): u""" Solve the entropic regularization optimal transport problem and return the OT matrix @@ -92,22 +93,28 @@ def sinkhorn(a,b, M, reg,method='sinkhorn', numItermax = 1000, stopThr=1e-9, ver """ - if method.lower()=='sinkhorn': - sink= lambda: sinkhorn_knopp(a,b, M, reg,numItermax=numItermax, - stopThr=stopThr, verbose=verbose, log=log,**kwargs) - elif method.lower()=='sinkhorn_stabilized': - sink= lambda: sinkhorn_stabilized(a,b, M, reg,numItermax=numItermax, - stopThr=stopThr, verbose=verbose, log=log, **kwargs) - elif method.lower()=='sinkhorn_epsilon_scaling': - sink= lambda: sinkhorn_epsilon_scaling(a,b, M, reg,numItermax=numItermax, - stopThr=stopThr, verbose=verbose, log=log, **kwargs) + if method.lower() == 'sinkhorn': + def sink(): + return sinkhorn_knopp(a, b, M, reg, numItermax=numItermax, + stopThr=stopThr, verbose=verbose, log=log, **kwargs) + elif method.lower() == 'sinkhorn_stabilized': + def sink(): + return sinkhorn_stabilized(a, b, M, reg, numItermax=numItermax, + stopThr=stopThr, verbose=verbose, log=log, **kwargs) + elif method.lower() == 'sinkhorn_epsilon_scaling': + def sink(): + return sinkhorn_epsilon_scaling(a, b, M, reg, numItermax=numItermax, + stopThr=stopThr, verbose=verbose, log=log, **kwargs) else: print('Warning : unknown method using classic Sinkhorn Knopp') - sink= lambda: sinkhorn_knopp(a,b, M, reg, **kwargs) + + def sink(): + return sinkhorn_knopp(a, b, M, reg, **kwargs) return sink() -def sinkhorn2(a,b, M, reg,method='sinkhorn', numItermax = 1000, stopThr=1e-9, verbose=False, log=False,**kwargs): + +def sinkhorn2(a, b, M, reg, method='sinkhorn', numItermax=1000, stopThr=1e-9, verbose=False, log=False, **kwargs): u""" Solve the entropic regularization optimal transport problem and return the loss @@ -170,7 +177,7 @@ def sinkhorn2(a,b, M, reg,method='sinkhorn', numItermax = 1000, stopThr=1e-9, ve >>> M=[[0.,1.],[1.,0.]] >>> ot.sinkhorn2(a,b,M,1) array([ 0.26894142]) - + References @@ -194,27 +201,32 @@ def sinkhorn2(a,b, M, reg,method='sinkhorn', numItermax = 1000, stopThr=1e-9, ve """ - if method.lower()=='sinkhorn': - sink= lambda: sinkhorn_knopp(a,b, M, reg,numItermax=numItermax, - stopThr=stopThr, verbose=verbose, log=log,**kwargs) - elif method.lower()=='sinkhorn_stabilized': - sink= lambda: sinkhorn_stabilized(a,b, M, reg,numItermax=numItermax, - stopThr=stopThr, verbose=verbose, log=log, **kwargs) - elif method.lower()=='sinkhorn_epsilon_scaling': - sink= lambda: sinkhorn_epsilon_scaling(a,b, M, reg,numItermax=numItermax, - stopThr=stopThr, verbose=verbose, log=log, **kwargs) + if method.lower() == 'sinkhorn': + def sink(): + return sinkhorn_knopp(a, b, M, reg, numItermax=numItermax, + stopThr=stopThr, verbose=verbose, log=log, **kwargs) + elif method.lower() == 'sinkhorn_stabilized': + def sink(): + return sinkhorn_stabilized(a, b, M, reg, numItermax=numItermax, + stopThr=stopThr, verbose=verbose, log=log, **kwargs) + elif method.lower() == 'sinkhorn_epsilon_scaling': + def sink(): + return sinkhorn_epsilon_scaling(a, b, M, reg, numItermax=numItermax, + stopThr=stopThr, verbose=verbose, log=log, **kwargs) else: print('Warning : unknown method using classic Sinkhorn Knopp') - sink= lambda: sinkhorn_knopp(a,b, M, reg, **kwargs) - - b=np.asarray(b,dtype=np.float64) - if len(b.shape)<2: - b=b.reshape((-1,1)) + + def sink(): + return sinkhorn_knopp(a, b, M, reg, **kwargs) + + b = np.asarray(b, dtype=np.float64) + if len(b.shape) < 2: + b = b.reshape((-1, 1)) return sink() -def sinkhorn_knopp(a,b, M, reg, numItermax = 1000, stopThr=1e-9, verbose=False, log=False,**kwargs): +def sinkhorn_knopp(a, b, M, reg, numItermax=1000, stopThr=1e-9, verbose=False, log=False, **kwargs): """ Solve the entropic regularization optimal transport problem and return the OT matrix @@ -290,100 +302,101 @@ def sinkhorn_knopp(a,b, M, reg, numItermax = 1000, stopThr=1e-9, verbose=False, """ - a=np.asarray(a,dtype=np.float64) - b=np.asarray(b,dtype=np.float64) - M=np.asarray(M,dtype=np.float64) - - - if len(a)==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] + a = np.asarray(a, dtype=np.float64) + b = np.asarray(b, dtype=np.float64) + M = np.asarray(M, dtype=np.float64) + if len(a) == 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] # init data Nini = len(a) Nfin = len(b) - if len(b.shape)>1: - nbb=b.shape[1] + if len(b.shape) > 1: + nbb = b.shape[1] else: - nbb=0 - + nbb = 0 if log: - log={'err':[]} + log = {'err': []} - # we assume that no distances are null except those of the diagonal of distances + # we assume that no distances are null except those of the diagonal of + # distances if nbb: - u = np.ones((Nini,nbb))/Nini - v = np.ones((Nfin,nbb))/Nfin + u = np.ones((Nini, nbb)) / Nini + v = np.ones((Nfin, nbb)) / Nfin else: - u = np.ones(Nini)/Nini - v = np.ones(Nfin)/Nfin + u = np.ones(Nini) / Nini + v = np.ones(Nfin) / Nfin + # print(reg) - #print(reg) + K = np.exp(-M / reg) + # print(np.min(K)) - K = np.exp(-M/reg) - #print(np.min(K)) - - Kp = (1/a).reshape(-1, 1) * K + Kp = (1 / a).reshape(-1, 1) * K cpt = 0 - err=1 - while (err>stopThr and cpt stopThr and cpt < numItermax): uprev = u vprev = v KtransposeU = np.dot(K.T, u) v = np.divide(b, KtransposeU) - u = 1./np.dot(Kp,v) + u = 1. / np.dot(Kp, v) - if (np.any(KtransposeU==0) or - np.any(np.isnan(u)) or np.any(np.isnan(v)) or - np.any(np.isinf(u)) or np.any(np.isinf(v))): + if (np.any(KtransposeU == 0) or + np.any(np.isnan(u)) or np.any(np.isnan(v)) or + np.any(np.isinf(u)) or np.any(np.isinf(v))): # we have reached the machine precision # come back to previous solution and quit loop print('Warning: numerical errors at iteration', cpt) u = uprev v = vprev break - if cpt%10==0: - # we can speed up the process by checking for the error only all the 10th iterations + if cpt % 10 == 0: + # we can speed up the process by checking for the error only all + # the 10th iterations if nbb: - err = np.sum((u-uprev)**2)/np.sum((u)**2)+np.sum((v-vprev)**2)/np.sum((v)**2) + err = np.sum((u - uprev)**2) / np.sum((u)**2) + \ + np.sum((v - vprev)**2) / np.sum((v)**2) else: transp = u.reshape(-1, 1) * (K * v) - err = np.linalg.norm((np.sum(transp,axis=0)-b))**2 + err = np.linalg.norm((np.sum(transp, axis=0) - b))**2 if log: log['err'].append(err) if verbose: - if cpt%200 ==0: - print('{:5s}|{:12s}'.format('It.','Err')+'\n'+'-'*19) - print('{:5d}|{:8e}|'.format(cpt,err)) - cpt = cpt +1 + if cpt % 200 == 0: + print( + '{:5s}|{:12s}'.format('It.', 'Err') + '\n' + '-' * 19) + print('{:5d}|{:8e}|'.format(cpt, err)) + cpt = cpt + 1 if log: - log['u']=u - log['v']=v + log['u'] = u + log['v'] = v - if nbb: #return only loss - res=np.zeros((nbb)) + if nbb: # return only loss + res = np.zeros((nbb)) for i in range(nbb): - res[i]=np.sum(u[:,i].reshape((-1,1))*K*v[:,i].reshape((1,-1))*M) + res[i] = np.sum( + u[:, i].reshape((-1, 1)) * K * v[:, i].reshape((1, -1)) * M) if log: - return res,log + return res, log else: return res - else: # return OT matrix + else: # return OT matrix if log: - return u.reshape((-1,1))*K*v.reshape((1,-1)),log + return u.reshape((-1, 1)) * K * v.reshape((1, -1)), log else: - return u.reshape((-1,1))*K*v.reshape((1,-1)) + return u.reshape((-1, 1)) * K * v.reshape((1, -1)) -def sinkhorn_stabilized(a,b, M, reg, numItermax = 1000,tau=1e3, stopThr=1e-9,warmstart=None, verbose=False,print_period=20, log=False,**kwargs): +def sinkhorn_stabilized(a, b, M, reg, numItermax=1000, tau=1e3, stopThr=1e-9, warmstart=None, verbose=False, print_period=20, log=False, **kwargs): """ Solve the entropic regularization OT problem with log stabilization @@ -468,21 +481,21 @@ def sinkhorn_stabilized(a,b, M, reg, numItermax = 1000,tau=1e3, stopThr=1e-9,war """ - a=np.asarray(a,dtype=np.float64) - b=np.asarray(b,dtype=np.float64) - M=np.asarray(M,dtype=np.float64) + a = np.asarray(a, dtype=np.float64) + b = np.asarray(b, dtype=np.float64) + M = np.asarray(M, dtype=np.float64) - if len(a)==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(a) == 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] # test if multiple target - if len(b.shape)>1: - nbb=b.shape[1] - a=a[:,np.newaxis] + if len(b.shape) > 1: + nbb = b.shape[1] + a = a[:, np.newaxis] else: - nbb=0 + nbb = 0 # init data na = len(a) @@ -490,81 +503,80 @@ def sinkhorn_stabilized(a,b, M, reg, numItermax = 1000,tau=1e3, stopThr=1e-9,war cpt = 0 if log: - log={'err':[]} + log = {'err': []} - # we assume that no distances are null except those of the diagonal of distances + # we assume that no distances are null except those of the diagonal of + # distances if warmstart is None: - alpha,beta=np.zeros(na),np.zeros(nb) + alpha, beta = np.zeros(na), np.zeros(nb) else: - alpha,beta=warmstart + alpha, beta = warmstart if nbb: - u,v = np.ones((na,nbb))/na,np.ones((nb,nbb))/nb + u, v = np.ones((na, nbb)) / na, np.ones((nb, nbb)) / nb else: - u,v = np.ones(na)/na,np.ones(nb)/nb + u, v = np.ones(na) / na, np.ones(nb) / nb - def get_K(alpha,beta): + def get_K(alpha, beta): """log space computation""" - return np.exp(-(M-alpha.reshape((na,1))-beta.reshape((1,nb)))/reg) + return np.exp(-(M - alpha.reshape((na, 1)) - beta.reshape((1, nb))) / reg) - def get_Gamma(alpha,beta,u,v): + def get_Gamma(alpha, beta, u, v): """log space gamma computation""" - return np.exp(-(M-alpha.reshape((na,1))-beta.reshape((1,nb)))/reg+np.log(u.reshape((na,1)))+np.log(v.reshape((1,nb)))) + return np.exp(-(M - alpha.reshape((na, 1)) - beta.reshape((1, nb))) / reg + np.log(u.reshape((na, 1))) + np.log(v.reshape((1, nb)))) - #print(np.min(K)) + # print(np.min(K)) - K=get_K(alpha,beta) + K = get_K(alpha, beta) transp = K - loop=1 + loop = 1 cpt = 0 - err=1 + err = 1 while loop: - - uprev = u vprev = v # sinkhorn update - v = b/(np.dot(K.T,u)+1e-16) - u = a/(np.dot(K,v)+1e-16) - + v = b / (np.dot(K.T, u) + 1e-16) + u = a / (np.dot(K, v) + 1e-16) # remove numerical problems and store them in K - if np.abs(u).max()>tau or np.abs(v).max()>tau: + if np.abs(u).max() > tau or np.abs(v).max() > tau: if nbb: - alpha,beta=alpha+reg*np.max(np.log(u),1),beta+reg*np.max(np.log(v)) + alpha, beta = alpha + reg * \ + np.max(np.log(u), 1), beta + reg * np.max(np.log(v)) else: - alpha,beta=alpha+reg*np.log(u),beta+reg*np.log(v) + alpha, beta = alpha + reg * np.log(u), beta + reg * np.log(v) if nbb: - u,v = np.ones((na,nbb))/na,np.ones((nb,nbb))/nb + u, v = np.ones((na, nbb)) / na, np.ones((nb, nbb)) / nb else: - u,v = np.ones(na)/na,np.ones(nb)/nb - K=get_K(alpha,beta) - + u, v = np.ones(na) / na, np.ones(nb) / nb + K = get_K(alpha, beta) - if cpt%print_period==0: - # we can speed up the process by checking for the error only all the 10th iterations + if cpt % print_period == 0: + # we can speed up the process by checking for the error only all + # the 10th iterations if nbb: - err = np.sum((u-uprev)**2)/np.sum((u)**2)+np.sum((v-vprev)**2)/np.sum((v)**2) + err = np.sum((u - uprev)**2) / np.sum((u)**2) + \ + np.sum((v - vprev)**2) / np.sum((v)**2) else: - transp = get_Gamma(alpha,beta,u,v) - err = np.linalg.norm((np.sum(transp,axis=0)-b))**2 + transp = get_Gamma(alpha, beta, u, v) + err = np.linalg.norm((np.sum(transp, axis=0) - b))**2 if log: log['err'].append(err) if verbose: - if cpt%(print_period*20) ==0: - print('{:5s}|{:12s}'.format('It.','Err')+'\n'+'-'*19) - print('{:5d}|{:8e}|'.format(cpt,err)) + if cpt % (print_period * 20) == 0: + print( + '{:5s}|{:12s}'.format('It.', 'Err') + '\n' + '-' * 19) + print('{:5d}|{:8e}|'.format(cpt, err)) + if err <= stopThr: + loop = False - if err<=stopThr: - loop=False - - if cpt>=numItermax: - loop=False - + if cpt >= numItermax: + loop = False if np.any(np.isnan(u)) or np.any(np.isnan(v)): # we have reached the machine precision @@ -574,34 +586,34 @@ def sinkhorn_stabilized(a,b, M, reg, numItermax = 1000,tau=1e3, stopThr=1e-9,war v = vprev break - cpt = cpt +1 - + cpt = cpt + 1 #print('err=',err,' cpt=',cpt) if log: - log['logu']=alpha/reg+np.log(u) - log['logv']=beta/reg+np.log(v) - log['alpha']=alpha+reg*np.log(u) - log['beta']=beta+reg*np.log(v) - log['warmstart']=(log['alpha'],log['beta']) + log['logu'] = alpha / reg + np.log(u) + log['logv'] = beta / reg + np.log(v) + log['alpha'] = alpha + reg * np.log(u) + log['beta'] = beta + reg * np.log(v) + log['warmstart'] = (log['alpha'], log['beta']) if nbb: - res=np.zeros((nbb)) + res = np.zeros((nbb)) for i in range(nbb): - res[i]=np.sum(get_Gamma(alpha,beta,u[:,i],v[:,i])*M) - return res,log + res[i] = np.sum(get_Gamma(alpha, beta, u[:, i], v[:, i]) * M) + return res, log else: - return get_Gamma(alpha,beta,u,v),log + return get_Gamma(alpha, beta, u, v), log else: if nbb: - res=np.zeros((nbb)) + res = np.zeros((nbb)) for i in range(nbb): - res[i]=np.sum(get_Gamma(alpha,beta,u[:,i],v[:,i])*M) + res[i] = np.sum(get_Gamma(alpha, beta, u[:, i], v[:, i]) * M) return res else: - return get_Gamma(alpha,beta,u,v) + return get_Gamma(alpha, beta, u, v) + -def sinkhorn_epsilon_scaling(a,b, M, reg, numItermax = 100, epsilon0=1e4, numInnerItermax = 100,tau=1e3, stopThr=1e-9,warmstart=None, verbose=False,print_period=10, log=False,**kwargs): +def sinkhorn_epsilon_scaling(a, b, M, reg, numItermax=100, epsilon0=1e4, numInnerItermax=100, tau=1e3, stopThr=1e-9, warmstart=None, verbose=False, print_period=10, log=False, **kwargs): """ Solve the entropic regularization optimal transport problem with log stabilization and epsilon scaling. @@ -690,14 +702,14 @@ def sinkhorn_epsilon_scaling(a,b, M, reg, numItermax = 100, epsilon0=1e4, numInn """ - a=np.asarray(a,dtype=np.float64) - b=np.asarray(b,dtype=np.float64) - M=np.asarray(M,dtype=np.float64) + a = np.asarray(a, dtype=np.float64) + b = np.asarray(b, dtype=np.float64) + M = np.asarray(M, dtype=np.float64) - if len(a)==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(a) == 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] # init data na = len(a) @@ -705,88 +717,94 @@ def sinkhorn_epsilon_scaling(a,b, M, reg, numItermax = 100, epsilon0=1e4, numInn # nrelative umerical precision with 64 bits numItermin = 35 - numItermax=max(numItermin,numItermax) # ensure that last velue is exact - + numItermax = max(numItermin, numItermax) # ensure that last velue is exact cpt = 0 if log: - log={'err':[]} + log = {'err': []} - # we assume that no distances are null except those of the diagonal of distances + # we assume that no distances are null except those of the diagonal of + # distances if warmstart is None: - alpha,beta=np.zeros(na),np.zeros(nb) + alpha, beta = np.zeros(na), np.zeros(nb) else: - alpha,beta=warmstart - + alpha, beta = warmstart - def get_K(alpha,beta): + def get_K(alpha, beta): """log space computation""" - return np.exp(-(M-alpha.reshape((na,1))-beta.reshape((1,nb)))/reg) + return np.exp(-(M - alpha.reshape((na, 1)) - beta.reshape((1, nb))) / reg) - #print(np.min(K)) - def get_reg(n): # exponential decreasing - return (epsilon0-reg)*np.exp(-n)+reg + # print(np.min(K)) + def get_reg(n): # exponential decreasing + return (epsilon0 - reg) * np.exp(-n) + reg - loop=1 + loop = 1 cpt = 0 - err=1 + err = 1 while loop: - regi=get_reg(cpt) + regi = get_reg(cpt) - G,logi=sinkhorn_stabilized(a,b, M, regi, numItermax = numInnerItermax, stopThr=1e-9,warmstart=(alpha,beta), verbose=False,print_period=20,tau=tau, log=True) + G, logi = sinkhorn_stabilized(a, b, M, regi, numItermax=numInnerItermax, stopThr=1e-9, warmstart=( + alpha, beta), verbose=False, print_period=20, tau=tau, log=True) - alpha=logi['alpha'] - beta=logi['beta'] + alpha = logi['alpha'] + beta = logi['beta'] - if cpt>=numItermax: - loop=False + if cpt >= numItermax: + loop = False - if cpt%(print_period)==0: # spsion nearly converged - # we can speed up the process by checking for the error only all the 10th iterations + if cpt % (print_period) == 0: # spsion nearly converged + # we can speed up the process by checking for the error only all + # the 10th iterations transp = G - err = np.linalg.norm((np.sum(transp,axis=0)-b))**2+np.linalg.norm((np.sum(transp,axis=1)-a))**2 + err = np.linalg.norm( + (np.sum(transp, axis=0) - b))**2 + np.linalg.norm((np.sum(transp, axis=1) - a))**2 if log: log['err'].append(err) if verbose: - if cpt%(print_period*10) ==0: - print('{:5s}|{:12s}'.format('It.','Err')+'\n'+'-'*19) - print('{:5d}|{:8e}|'.format(cpt,err)) + if cpt % (print_period * 10) == 0: + print( + '{:5s}|{:12s}'.format('It.', 'Err') + '\n' + '-' * 19) + print('{:5d}|{:8e}|'.format(cpt, err)) - if err<=stopThr and cpt>numItermin: - loop=False + if err <= stopThr and cpt > numItermin: + loop = False - cpt = cpt +1 + cpt = cpt + 1 #print('err=',err,' cpt=',cpt) if log: - log['alpha']=alpha - log['beta']=beta - log['warmstart']=(log['alpha'],log['beta']) - return G,log + log['alpha'] = alpha + log['beta'] = beta + log['warmstart'] = (log['alpha'], log['beta']) + return G, log else: return G -def geometricBar(weights,alldistribT): +def geometricBar(weights, alldistribT): """return the weighted geometric mean of distributions""" - assert(len(weights)==alldistribT.shape[1]) - return np.exp(np.dot(np.log(alldistribT),weights.T)) + assert(len(weights) == alldistribT.shape[1]) + return np.exp(np.dot(np.log(alldistribT), weights.T)) + def geometricMean(alldistribT): """return the geometric mean of distributions""" - return np.exp(np.mean(np.log(alldistribT),axis=1)) + return np.exp(np.mean(np.log(alldistribT), axis=1)) -def projR(gamma,p): + +def projR(gamma, p): """return the KL projection on the row constrints """ - return np.multiply(gamma.T,p/np.maximum(np.sum(gamma,axis=1),1e-10)).T + return np.multiply(gamma.T, p / np.maximum(np.sum(gamma, axis=1), 1e-10)).T + -def projC(gamma,q): +def projC(gamma, q): """return the KL projection on the column constrints """ - return np.multiply(gamma,q/np.maximum(np.sum(gamma,axis=0),1e-10)) + return np.multiply(gamma, q / np.maximum(np.sum(gamma, axis=0), 1e-10)) -def barycenter(A,M,reg, weights=None, numItermax = 1000, stopThr=1e-4,verbose=False,log=False): +def barycenter(A, M, reg, weights=None, numItermax=1000, stopThr=1e-4, verbose=False, log=False): """Compute the entropic regularized wasserstein barycenter of distributions A The function solves the following optimization problem: @@ -837,49 +855,49 @@ def barycenter(A,M,reg, weights=None, numItermax = 1000, stopThr=1e-4,verbose=Fa """ - if weights is None: - weights=np.ones(A.shape[1])/A.shape[1] + weights = np.ones(A.shape[1]) / A.shape[1] else: - assert(len(weights)==A.shape[1]) + assert(len(weights) == A.shape[1]) if log: - log={'err':[]} + log = {'err': []} - #M = M/np.median(M) # suggested by G. Peyre - K = np.exp(-M/reg) + # M = M/np.median(M) # suggested by G. Peyre + K = np.exp(-M / reg) cpt = 0 - err=1 + err = 1 - UKv=np.dot(K,np.divide(A.T,np.sum(K,axis=0)).T) - u = (geometricMean(UKv)/UKv.T).T + UKv = np.dot(K, np.divide(A.T, np.sum(K, axis=0)).T) + u = (geometricMean(UKv) / UKv.T).T - while (err>stopThr and cpt stopThr and cpt < numItermax): + cpt = cpt + 1 + UKv = u * np.dot(K, np.divide(A, np.dot(K, u))) + u = (u.T * geometricBar(weights, UKv)).T / UKv - if cpt%10==1: - err=np.sum(np.std(UKv,axis=1)) + if cpt % 10 == 1: + err = np.sum(np.std(UKv, axis=1)) # log and verbose print if log: log['err'].append(err) if verbose: - if cpt%200 ==0: - print('{:5s}|{:12s}'.format('It.','Err')+'\n'+'-'*19) - print('{:5d}|{:8e}|'.format(cpt,err)) + if cpt % 200 == 0: + print( + '{:5s}|{:12s}'.format('It.', 'Err') + '\n' + '-' * 19) + print('{:5d}|{:8e}|'.format(cpt, err)) if log: - log['niter']=cpt - return geometricBar(weights,UKv),log + log['niter'] = cpt + return geometricBar(weights, UKv), log else: - return geometricBar(weights,UKv) + return geometricBar(weights, UKv) -def unmix(a,D,M,M0,h0,reg,reg0,alpha,numItermax = 1000, stopThr=1e-3,verbose=False,log=False): +def unmix(a, D, M, M0, h0, reg, reg0, alpha, numItermax=1000, stopThr=1e-3, verbose=False, log=False): """ Compute the unmixing of an observation with a given dictionary using Wasserstein distance @@ -943,43 +961,44 @@ def unmix(a,D,M,M0,h0,reg,reg0,alpha,numItermax = 1000, stopThr=1e-3,verbose=Fal """ #M = M/np.median(M) - K = np.exp(-M/reg) + K = np.exp(-M / reg) #M0 = M0/np.median(M0) - K0 = np.exp(-M0/reg0) + K0 = np.exp(-M0 / reg0) old = h0 - err=1 - cpt=0 + err = 1 + cpt = 0 #log = {'niter':0, 'all_err':[]} if log: - log={'err':[]} - - - while (err>stopThr and cpt stopThr and cpt < numItermax): + K = projC(K, a) + K0 = projC(K0, h0) + new = np.sum(K0, axis=1) + # we recombine the current selection from dictionnary + inv_new = np.dot(D, new) + other = np.sum(K, axis=1) + # geometric interpolation + delta = np.exp(alpha * np.log(other) + (1 - alpha) * np.log(inv_new)) + K = projR(K, delta) + K0 = np.dot(np.diag(np.dot(D.T, delta / inv_new)), K0) + + err = np.linalg.norm(np.sum(K0, axis=1) - old) old = new if log: log['err'].append(err) if verbose: - if cpt%200 ==0: - print('{:5s}|{:12s}'.format('It.','Err')+'\n'+'-'*19) - print('{:5d}|{:8e}|'.format(cpt,err)) + if cpt % 200 == 0: + print('{:5s}|{:12s}'.format('It.', 'Err') + '\n' + '-' * 19) + print('{:5d}|{:8e}|'.format(cpt, err)) - cpt = cpt+1 + cpt = cpt + 1 if log: - log['niter']=cpt - return np.sum(K0,axis=1),log + log['niter'] = cpt + return np.sum(K0, axis=1), log else: - return np.sum(K0,axis=1) + return np.sum(K0, axis=1) diff --git a/ot/da.py b/ot/da.py index ddf1c60..5039fbd 100644 --- a/ot/da.py +++ b/ot/da.py @@ -6,12 +6,12 @@ Domain adaptation with optimal transport import numpy as np from .bregman import sinkhorn from .lp import emd -from .utils import unif,dist,kernel +from .utils import unif, dist, kernel from .optim import cg from .optim import gcg -def sinkhorn_lpl1_mm(a,labels_a, b, M, reg, eta=0.1,numItermax = 10,numInnerItermax = 200,stopInnerThr=1e-9,verbose=False,log=False): +def sinkhorn_lpl1_mm(a, labels_a, b, M, reg, eta=0.1, numItermax=10, numInnerItermax=200, stopInnerThr=1e-9, verbose=False, log=False): """ Solve the entropic regularization optimal transport problem with nonconvex group lasso regularization @@ -94,7 +94,7 @@ def sinkhorn_lpl1_mm(a,labels_a, b, M, reg, eta=0.1,numItermax = 10,numInnerIter W = np.zeros(M.shape) for cpt in range(numItermax): - Mreg = M + eta*W + Mreg = M + eta * W transp = sinkhorn(a, b, Mreg, reg, numItermax=numInnerItermax, stopThr=stopInnerThr) # the transport has been computed. Check if classes are really @@ -102,12 +102,13 @@ def sinkhorn_lpl1_mm(a,labels_a, b, M, reg, eta=0.1,numItermax = 10,numInnerIter W = np.ones(M.shape) for (i, c) in enumerate(classes): majs = np.sum(transp[indices_labels[i]], axis=0) - majs = p*((majs+epsilon)**(p-1)) + majs = p * ((majs + epsilon)**(p - 1)) W[indices_labels[i]] = majs return transp -def sinkhorn_l1l2_gl(a,labels_a, b, M, reg, eta=0.1,numItermax = 10,numInnerItermax = 200,stopInnerThr=1e-9,verbose=False,log=False): + +def sinkhorn_l1l2_gl(a, labels_a, b, M, reg, eta=0.1, numItermax=10, numInnerItermax=200, stopInnerThr=1e-9, verbose=False, log=False): """ Solve the entropic regularization optimal transport problem with group lasso regularization @@ -176,32 +177,30 @@ def sinkhorn_l1l2_gl(a,labels_a, b, M, reg, eta=0.1,numItermax = 10,numInnerIter ot.optim.gcg : Generalized conditional gradient for OT problems """ - lstlab=np.unique(labels_a) + lstlab = np.unique(labels_a) def f(G): - res=0 + res = 0 for i in range(G.shape[1]): for lab in lstlab: - temp=G[labels_a==lab,i] - res+=np.linalg.norm(temp) + temp = G[labels_a == lab, i] + res += np.linalg.norm(temp) return res def df(G): - W=np.zeros(G.shape) + W = np.zeros(G.shape) for i in range(G.shape[1]): for lab in lstlab: - temp=G[labels_a==lab,i] - n=np.linalg.norm(temp) + temp = G[labels_a == lab, i] + n = np.linalg.norm(temp) if n: - W[labels_a==lab,i]=temp/n + W[labels_a == lab, i] = temp / n return W - - return gcg(a,b,M,reg,eta,f,df,G0=None,numItermax = numItermax,numInnerItermax=numInnerItermax, stopThr=stopInnerThr,verbose=verbose,log=log) - + return gcg(a, b, M, reg, eta, f, df, G0=None, numItermax=numItermax, numInnerItermax=numInnerItermax, stopThr=stopInnerThr, verbose=verbose, log=log) -def joint_OT_mapping_linear(xs,xt,mu=1,eta=0.001,bias=False,verbose=False,verbose2=False,numItermax = 100,numInnerItermax = 10,stopInnerThr=1e-6,stopThr=1e-5,log=False,**kwargs): +def joint_OT_mapping_linear(xs, xt, mu=1, eta=0.001, bias=False, verbose=False, verbose2=False, numItermax=100, numInnerItermax=10, stopInnerThr=1e-6, stopThr=1e-5, log=False, **kwargs): """Joint OT and linear mapping estimation as proposed in [8] The function solves the following optimization problem: @@ -281,97 +280,105 @@ def joint_OT_mapping_linear(xs,xt,mu=1,eta=0.001,bias=False,verbose=False,verbos """ - ns,nt,d=xs.shape[0],xt.shape[0],xt.shape[1] + ns, nt, d = xs.shape[0], xt.shape[0], xt.shape[1] if bias: - xs1=np.hstack((xs,np.ones((ns,1)))) - xstxs=xs1.T.dot(xs1) - I=np.eye(d+1) - I[-1]=0 - I0=I[:,:-1] - sel=lambda x : x[:-1,:] + xs1 = np.hstack((xs, np.ones((ns, 1)))) + xstxs = xs1.T.dot(xs1) + I = np.eye(d + 1) + I[-1] = 0 + I0 = I[:, :-1] + + def sel(x): + return x[:-1, :] else: - xs1=xs - xstxs=xs1.T.dot(xs1) - I=np.eye(d) - I0=I - sel=lambda x : x + xs1 = xs + xstxs = xs1.T.dot(xs1) + I = np.eye(d) + I0 = I + + def sel(x): + return x if log: - log={'err':[]} + log = {'err': []} - a,b=unif(ns),unif(nt) - M=dist(xs,xt)*ns - G=emd(a,b,M) + a, b = unif(ns), unif(nt) + M = dist(xs, xt) * ns + G = emd(a, b, M) - vloss=[] + vloss = [] - def loss(L,G): + def loss(L, G): """Compute full loss""" - return np.sum((xs1.dot(L)-ns*G.dot(xt))**2)+mu*np.sum(G*M)+eta*np.sum(sel(L-I0)**2) + return np.sum((xs1.dot(L) - ns * G.dot(xt))**2) + mu * np.sum(G * M) + eta * np.sum(sel(L - I0)**2) def solve_L(G): """ solve L problem with fixed G (least square)""" - xst=ns*G.dot(xt) - return np.linalg.solve(xstxs+eta*I,xs1.T.dot(xst)+eta*I0) + xst = ns * G.dot(xt) + return np.linalg.solve(xstxs + eta * I, xs1.T.dot(xst) + eta * I0) - def solve_G(L,G0): + def solve_G(L, G0): """Update G with CG algorithm""" - xsi=xs1.dot(L) + xsi = xs1.dot(L) + def f(G): - return np.sum((xsi-ns*G.dot(xt))**2) + return np.sum((xsi - ns * G.dot(xt))**2) + def df(G): - return -2*ns*(xsi-ns*G.dot(xt)).dot(xt.T) - G=cg(a,b,M,1.0/mu,f,df,G0=G0,numItermax=numInnerItermax,stopThr=stopInnerThr) + return -2 * ns * (xsi - ns * G.dot(xt)).dot(xt.T) + G = cg(a, b, M, 1.0 / mu, f, df, G0=G0, + numItermax=numInnerItermax, stopThr=stopInnerThr) return G + L = solve_L(G) - L=solve_L(G) - - vloss.append(loss(L,G)) + vloss.append(loss(L, G)) if verbose: - print('{:5s}|{:12s}|{:8s}'.format('It.','Loss','Delta loss')+'\n'+'-'*32) - print('{:5d}|{:8e}|{:8e}'.format(0,vloss[-1],0)) - + print('{:5s}|{:12s}|{:8s}'.format( + 'It.', 'Loss', 'Delta loss') + '\n' + '-' * 32) + print('{:5d}|{:8e}|{:8e}'.format(0, vloss[-1], 0)) # init loop - if numItermax>0: - loop=1 + if numItermax > 0: + loop = 1 else: - loop=0 - it=0 + loop = 0 + it = 0 while loop: - it+=1 + it += 1 # update G - G=solve_G(L,G) + G = solve_G(L, G) - #update L - L=solve_L(G) + # update L + L = solve_L(G) - vloss.append(loss(L,G)) + vloss.append(loss(L, G)) - if it>=numItermax: - loop=0 + if it >= numItermax: + loop = 0 - if abs(vloss[-1]-vloss[-2])/abs(vloss[-2])0: - loop=1 + if numItermax > 0: + loop = 1 else: - loop=0 - it=0 + loop = 0 + it = 0 while loop: - it+=1 + it += 1 # update G - G=solve_G(L,G) + G = solve_G(L, G) - #update L - L=solve_L(G) + # update L + L = solve_L(G) - vloss.append(loss(L,G)) + vloss.append(loss(L, G)) - if it>=numItermax: - loop=0 + if it >= numItermax: + loop = 0 - if abs(vloss[-1]-vloss[-2])/abs(vloss[-2])0: # >0 then source to target - G=self.G - w=self.ws.reshape((self.xs.shape[0],1)) - x=self.xt + if direction > 0: # >0 then source to target + G = self.G + w = self.ws.reshape((self.xs.shape[0], 1)) + x = self.xt else: - G=self.G.T - w=self.wt.reshape((self.xt.shape[0],1)) - x=self.xs + G = self.G.T + w = self.wt.reshape((self.xt.shape[0], 1)) + x = self.xs if self.computed: - if self.metric=='sqeuclidean': - return np.dot(G/w,x) # weighted mean + if self.metric == 'sqeuclidean': + return np.dot(G / w, x) # weighted mean else: - print("Warning, metric not handled yet, using weighted average") - return np.dot(G/w,x) # weighted mean + print( + "Warning, metric not handled yet, using weighted average") + return np.dot(G / w, x) # weighted mean return None else: print("Warning, model not fitted yet, returning None") return None - - def predict(self,x,direction=1): + def predict(self, x, direction=1): """ Out of sample mapping using the formulation from [6] For each sample x to map, it finds the nearest source sample xs and @@ -657,29 +666,30 @@ class OTDA(object): .. [6] Ferradans, S., Papadakis, N., Peyré, G., & Aujol, J. F. (2014). Regularized discrete optimal transport. SIAM Journal on Imaging Sciences, 7(3), 1853-1882. """ - if direction>0: # >0 then source to target - xf=self.xt - x0=self.xs + if direction > 0: # >0 then source to target + xf = self.xt + x0 = self.xs else: - xf=self.xs - x0=self.xt + xf = self.xs + x0 = self.xt - D0=dist(x,x0) # dist netween new samples an source - idx=np.argmin(D0,1) # closest one - xf=self.interp(direction)# interp the source samples - return xf[idx,:]+x-x0[idx,:] # aply the delta to the interpolation + D0 = dist(x, x0) # dist netween new samples an source + idx = np.argmin(D0, 1) # closest one + xf = self.interp(direction) # interp the source samples + # aply the delta to the interpolation + return xf[idx, :] + x - x0[idx, :] def normalizeM(self, norm): """ Apply normalization to the loss matrix - - + + Parameters ---------- norm : str type of normalization from 'median','max','log','loglog' - + """ - + if norm == "median": self.M /= float(np.median(self.M)) elif norm == "max": @@ -688,149 +698,149 @@ class OTDA(object): self.M = np.log(1 + self.M) elif norm == "loglog": self.M = np.log(1 + np.log(1 + self.M)) - class OTDA_sinkhorn(OTDA): + """Class for domain adaptation with optimal transport with entropic regularization""" - def fit(self,xs,xt,reg=1,ws=None,wt=None,norm=None,**kwargs): + def fit(self, xs, xt, reg=1, ws=None, wt=None, norm=None, **kwargs): """ Fit regularized domain adaptation between samples is xs and xt (with optional weights)""" - self.xs=xs - self.xt=xt + self.xs = xs + self.xt = xt if wt is None: - wt=unif(xt.shape[0]) + wt = unif(xt.shape[0]) if ws is None: - ws=unif(xs.shape[0]) + ws = unif(xs.shape[0]) - self.ws=ws - self.wt=wt + self.ws = ws + self.wt = wt - self.M=dist(xs,xt,metric=self.metric) + self.M = dist(xs, xt, metric=self.metric) self.normalizeM(norm) - self.G=sinkhorn(ws,wt,self.M,reg,**kwargs) - self.computed=True + self.G = sinkhorn(ws, wt, self.M, reg, **kwargs) + self.computed = True class OTDA_lpl1(OTDA): - """Class for domain adaptation with optimal transport with entropic and group regularization""" + """Class for domain adaptation with optimal transport with entropic and group regularization""" - def fit(self,xs,ys,xt,reg=1,eta=1,ws=None,wt=None,norm=None,**kwargs): + def fit(self, xs, ys, xt, reg=1, eta=1, ws=None, wt=None, norm=None, **kwargs): """ Fit regularized domain adaptation between samples is xs and xt (with optional weights), See ot.da.sinkhorn_lpl1_mm for fit parameters""" - self.xs=xs - self.xt=xt + self.xs = xs + self.xt = xt if wt is None: - wt=unif(xt.shape[0]) + wt = unif(xt.shape[0]) if ws is None: - ws=unif(xs.shape[0]) + ws = unif(xs.shape[0]) - self.ws=ws - self.wt=wt + self.ws = ws + self.wt = wt - self.M=dist(xs,xt,metric=self.metric) + self.M = dist(xs, xt, metric=self.metric) self.normalizeM(norm) - self.G=sinkhorn_lpl1_mm(ws,ys,wt,self.M,reg,eta,**kwargs) - self.computed=True + self.G = sinkhorn_lpl1_mm(ws, ys, wt, self.M, reg, eta, **kwargs) + self.computed = True + class OTDA_l1l2(OTDA): - """Class for domain adaptation with optimal transport with entropic and group lasso regularization""" + """Class for domain adaptation with optimal transport with entropic and group lasso regularization""" - def fit(self,xs,ys,xt,reg=1,eta=1,ws=None,wt=None,norm=None,**kwargs): + def fit(self, xs, ys, xt, reg=1, eta=1, ws=None, wt=None, norm=None, **kwargs): """ Fit regularized domain adaptation between samples is xs and xt (with optional weights), See ot.da.sinkhorn_lpl1_gl for fit parameters""" - self.xs=xs - self.xt=xt + self.xs = xs + self.xt = xt if wt is None: - wt=unif(xt.shape[0]) + wt = unif(xt.shape[0]) if ws is None: - ws=unif(xs.shape[0]) + ws = unif(xs.shape[0]) - self.ws=ws - self.wt=wt + self.ws = ws + self.wt = wt - self.M=dist(xs,xt,metric=self.metric) + self.M = dist(xs, xt, metric=self.metric) self.normalizeM(norm) - self.G=sinkhorn_l1l2_gl(ws,ys,wt,self.M,reg,eta,**kwargs) - self.computed=True + self.G = sinkhorn_l1l2_gl(ws, ys, wt, self.M, reg, eta, **kwargs) + self.computed = True + class OTDA_mapping_linear(OTDA): - """Class for optimal transport with joint linear mapping estimation as in [8]""" + """Class for optimal transport with joint linear mapping estimation as in [8]""" def __init__(self): """ Class initialization""" + self.xs = 0 + self.xt = 0 + self.G = 0 + self.L = 0 + self.bias = False + self.computed = False + self.metric = 'sqeuclidean' - self.xs=0 - self.xt=0 - self.G=0 - self.L=0 - self.bias=False - self.computed=False - self.metric='sqeuclidean' - - def fit(self,xs,xt,mu=1,eta=1,bias=False,**kwargs): + def fit(self, xs, xt, mu=1, eta=1, bias=False, **kwargs): """ Fit domain adaptation between samples is xs and xt (with optional weights)""" - self.xs=xs - self.xt=xt - self.bias=bias - + self.xs = xs + self.xt = xt + self.bias = bias - self.ws=unif(xs.shape[0]) - self.wt=unif(xt.shape[0]) + self.ws = unif(xs.shape[0]) + self.wt = unif(xt.shape[0]) - self.G,self.L=joint_OT_mapping_linear(xs,xt,mu=mu,eta=eta,bias=bias,**kwargs) - self.computed=True + self.G, self.L = joint_OT_mapping_linear( + xs, xt, mu=mu, eta=eta, bias=bias, **kwargs) + self.computed = True def mapping(self): return lambda x: self.predict(x) - - def predict(self,x): + def predict(self, x): """ Out of sample mapping estimated during the call to fit""" if self.computed: if self.bias: - x=np.hstack((x,np.ones((x.shape[0],1)))) - return x.dot(self.L) # aply the delta to the interpolation + x = np.hstack((x, np.ones((x.shape[0], 1)))) + return x.dot(self.L) # aply the delta to the interpolation else: print("Warning, model not fitted yet, returning None") return None -class OTDA_mapping_kernel(OTDA_mapping_linear): - """Class for optimal transport with joint nonlinear mapping estimation as in [8]""" +class OTDA_mapping_kernel(OTDA_mapping_linear): + """Class for optimal transport with joint nonlinear mapping estimation as in [8]""" - def fit(self,xs,xt,mu=1,eta=1,bias=False,kerneltype='gaussian',sigma=1,**kwargs): + def fit(self, xs, xt, mu=1, eta=1, bias=False, kerneltype='gaussian', sigma=1, **kwargs): """ Fit domain adaptation between samples is xs and xt """ - self.xs=xs - self.xt=xt - self.bias=bias - - self.ws=unif(xs.shape[0]) - self.wt=unif(xt.shape[0]) - self.kernel=kerneltype - self.sigma=sigma - self.kwargs=kwargs + self.xs = xs + self.xt = xt + self.bias = bias + self.ws = unif(xs.shape[0]) + self.wt = unif(xt.shape[0]) + self.kernel = kerneltype + self.sigma = sigma + self.kwargs = kwargs - self.G,self.L=joint_OT_mapping_kernel(xs,xt,mu=mu,eta=eta,bias=bias,**kwargs) - self.computed=True + self.G, self.L = joint_OT_mapping_kernel( + xs, xt, mu=mu, eta=eta, bias=bias, **kwargs) + self.computed = True - - def predict(self,x): + def predict(self, x): """ Out of sample mapping estimated during the call to fit""" if self.computed: - K=kernel(x,self.xs,method=self.kernel,sigma=self.sigma,**self.kwargs) + K = kernel( + x, self.xs, method=self.kernel, sigma=self.sigma, **self.kwargs) if self.bias: - K=np.hstack((K,np.ones((x.shape[0],1)))) + K = np.hstack((K, np.ones((x.shape[0], 1)))) return K.dot(self.L) else: print("Warning, model not fitted yet, returning None") - return None \ No newline at end of file + return None diff --git a/ot/datasets.py b/ot/datasets.py index 7816833..4371a23 100644 --- a/ot/datasets.py +++ b/ot/datasets.py @@ -7,7 +7,7 @@ import numpy as np import scipy as sp -def get_1D_gauss(n,m,s): +def get_1D_gauss(n, m, s): """return a 1D histogram for a gaussian distribution (n bins, mean m and std s) Parameters @@ -27,12 +27,12 @@ def get_1D_gauss(n,m,s): 1D histogram for a gaussian distribution """ - x=np.arange(n,dtype=np.float64) - h=np.exp(-(x-m)**2/(2*s**2)) - return h/h.sum() + x = np.arange(n, dtype=np.float64) + h = np.exp(-(x - m)**2 / (2 * s**2)) + return h / h.sum() -def get_2D_samples_gauss(n,m,sigma): +def get_2D_samples_gauss(n, m, sigma): """return n samples drawn from 2D gaussian N(m,sigma) Parameters @@ -52,17 +52,17 @@ def get_2D_samples_gauss(n,m,sigma): n samples drawn from N(m,sigma) """ - if np.isscalar(sigma): - sigma=np.array([sigma,]) - if len(sigma)>1: - P=sp.linalg.sqrtm(sigma) - res= np.random.randn(n,2).dot(P)+m + if np.isscalar(sigma): + sigma = np.array([sigma, ]) + if len(sigma) > 1: + P = sp.linalg.sqrtm(sigma) + res = np.random.randn(n, 2).dot(P) + m else: - res= np.random.randn(n,2)*np.sqrt(sigma)+m + res = np.random.randn(n, 2) * np.sqrt(sigma) + m return res -def get_data_classif(dataset,n,nz=.5,theta=0,**kwargs): +def get_data_classif(dataset, n, nz=.5, theta=0, **kwargs): """ dataset generation for classification problems Parameters @@ -84,48 +84,53 @@ def get_data_classif(dataset,n,nz=.5,theta=0,**kwargs): labels of the samples """ - if dataset.lower()=='3gauss': - y=np.floor((np.arange(n)*1.0/n*3))+1 - x=np.zeros((n,2)) + if dataset.lower() == '3gauss': + y = np.floor((np.arange(n) * 1.0 / n * 3)) + 1 + x = np.zeros((n, 2)) # class 1 - x[y==1,0]=-1.; x[y==1,1]=-1. - x[y==2,0]=-1.; x[y==2,1]=1. - x[y==3,0]=1. ; x[y==3,1]=0 - - x[y!=3,:]+=1.5*nz*np.random.randn(sum(y!=3),2) - x[y==3,:]+=2*nz*np.random.randn(sum(y==3),2) - - elif dataset.lower()=='3gauss2': - y=np.floor((np.arange(n)*1.0/n*3))+1 - x=np.zeros((n,2)) - y[y==4]=3 + x[y == 1, 0] = -1. + x[y == 1, 1] = -1. + x[y == 2, 0] = -1. + x[y == 2, 1] = 1. + x[y == 3, 0] = 1. + x[y == 3, 1] = 0 + + x[y != 3, :] += 1.5 * nz * np.random.randn(sum(y != 3), 2) + x[y == 3, :] += 2 * nz * np.random.randn(sum(y == 3), 2) + + elif dataset.lower() == '3gauss2': + y = np.floor((np.arange(n) * 1.0 / n * 3)) + 1 + x = np.zeros((n, 2)) + y[y == 4] = 3 # class 1 - x[y==1,0]=-2.; x[y==1,1]=-2. - x[y==2,0]=-2.; x[y==2,1]=2. - x[y==3,0]=2. ; x[y==3,1]=0 - - x[y!=3,:]+=nz*np.random.randn(sum(y!=3),2) - x[y==3,:]+=2*nz*np.random.randn(sum(y==3),2) - - elif dataset.lower()=='gaussrot' : - rot=np.array([[np.cos(theta),np.sin(theta)],[-np.sin(theta),np.cos(theta)]]) - m1=np.array([-1,1]) - m2=np.array([1,-1]) - y=np.floor((np.arange(n)*1.0/n*2))+1 - n1=np.sum(y==1) - n2=np.sum(y==2) - x=np.zeros((n,2)) - - x[y==1,:]=get_2D_samples_gauss(n1,m1,nz) - x[y==2,:]=get_2D_samples_gauss(n2,m2,nz) - - x=x.dot(rot) - - + x[y == 1, 0] = -2. + x[y == 1, 1] = -2. + x[y == 2, 0] = -2. + x[y == 2, 1] = 2. + x[y == 3, 0] = 2. + x[y == 3, 1] = 0 + + x[y != 3, :] += nz * np.random.randn(sum(y != 3), 2) + x[y == 3, :] += 2 * nz * np.random.randn(sum(y == 3), 2) + + elif dataset.lower() == 'gaussrot': + rot = np.array( + [[np.cos(theta), np.sin(theta)], [-np.sin(theta), np.cos(theta)]]) + m1 = np.array([-1, 1]) + m2 = np.array([1, -1]) + y = np.floor((np.arange(n) * 1.0 / n * 2)) + 1 + n1 = np.sum(y == 1) + n2 = np.sum(y == 2) + x = np.zeros((n, 2)) + + x[y == 1, :] = get_2D_samples_gauss(n1, m1, nz) + x[y == 2, :] = get_2D_samples_gauss(n2, m2, nz) + + x = x.dot(rot) else: - x=np.array(0) - y=np.array(0) + x = np.array(0) + y = np.array(0) print("unknown dataset") - return x,y.astype(int) \ No newline at end of file + return x, y.astype(int) diff --git a/ot/dr.py b/ot/dr.py index 763ce35..77cbae2 100644 --- a/ot/dr.py +++ b/ot/dr.py @@ -3,43 +3,46 @@ Dimension reduction with optimal transport """ +from scipy import linalg import autograd.numpy as np from pymanopt.manifolds import Stiefel from pymanopt import Problem from pymanopt.solvers import SteepestDescent, TrustRegions -import scipy.linalg as la -def dist(x1,x2): + +def dist(x1, x2): """ Compute squared euclidean distance between samples (autograd) """ - x1p2=np.sum(np.square(x1),1) - x2p2=np.sum(np.square(x2),1) - return x1p2.reshape((-1,1))+x2p2.reshape((1,-1))-2*np.dot(x1,x2.T) + x1p2 = np.sum(np.square(x1), 1) + x2p2 = np.sum(np.square(x2), 1) + return x1p2.reshape((-1, 1)) + x2p2.reshape((1, -1)) - 2 * np.dot(x1, x2.T) + -def sinkhorn(w1,w2,M,reg,k): +def sinkhorn(w1, w2, M, reg, k): """Sinkhorn algorithm with fixed number of iteration (autograd) """ - K=np.exp(-M/reg) - ui=np.ones((M.shape[0],)) - vi=np.ones((M.shape[1],)) + K = np.exp(-M / reg) + ui = np.ones((M.shape[0],)) + vi = np.ones((M.shape[1],)) for i in range(k): - vi=w2/(np.dot(K.T,ui)) - ui=w1/(np.dot(K,vi)) - G=ui.reshape((M.shape[0],1))*K*vi.reshape((1,M.shape[1])) + vi = w2 / (np.dot(K.T, ui)) + ui = w1 / (np.dot(K, vi)) + G = ui.reshape((M.shape[0], 1)) * K * vi.reshape((1, M.shape[1])) return G -def split_classes(X,y): + +def split_classes(X, y): """split samples in X by classes in y """ - lstsclass=np.unique(y) - return [X[y==i,:].astype(np.float32) for i in lstsclass] + lstsclass = np.unique(y) + return [X[y == i, :].astype(np.float32) for i in lstsclass] + + +def fda(X, y, p=2, reg=1e-16): + """ + Fisher Discriminant Analysis -def fda(X,y,p=2,reg=1e-16): - """ - Fisher Discriminant Analysis - - Parameters ---------- X : numpy.ndarray (n,d) @@ -59,62 +62,62 @@ def fda(X,y,p=2,reg=1e-16): proj : fun projection function including mean centering - - """ - - mx=np.mean(X) - X-=mx.reshape((1,-1)) - + + """ + + mx = np.mean(X) + X -= mx.reshape((1, -1)) + # data split between classes - d=X.shape[1] - xc=split_classes(X,y) - nc=len(xc) - - p=min(nc-1,p) - - Cw=0 + d = X.shape[1] + xc = split_classes(X, y) + nc = len(xc) + + p = min(nc - 1, p) + + Cw = 0 for x in xc: - Cw+=np.cov(x,rowvar=False) - Cw/=nc - - mxc=np.zeros((d,nc)) - + Cw += np.cov(x, rowvar=False) + Cw /= nc + + mxc = np.zeros((d, nc)) + for i in range(nc): - mxc[:,i]=np.mean(xc[i]) - - mx0=np.mean(mxc,1) - Cb=0 + mxc[:, i] = np.mean(xc[i]) + + mx0 = np.mean(mxc, 1) + Cb = 0 for i in range(nc): - Cb+=(mxc[:,i]-mx0).reshape((-1,1))*(mxc[:,i]-mx0).reshape((1,-1)) - - w,V=la.eig(Cb,Cw+reg*np.eye(d)) - - idx=np.argsort(w.real) - - Popt=V[:,idx[-p:]] - - - + Cb += (mxc[:, i] - mx0).reshape((-1, 1)) * \ + (mxc[:, i] - mx0).reshape((1, -1)) + + w, V = linalg.eig(Cb, Cw + reg * np.eye(d)) + + idx = np.argsort(w.real) + + Popt = V[:, idx[-p:]] + def proj(X): - return (X-mx.reshape((1,-1))).dot(Popt) - + return (X - mx.reshape((1, -1))).dot(Popt) + return Popt, proj -def wda(X,y,p=2,reg=1,k=10,solver = None,maxiter=100,verbose=0,P0=None): - """ + +def wda(X, y, p=2, reg=1, k=10, solver=None, maxiter=100, verbose=0, P0=None): + """ Wasserstein Discriminant Analysis [11]_ - + The function solves the following optimization problem: .. math:: P = \\text{arg}\min_P \\frac{\\sum_i W(PX^i,PX^i)}{\\sum_{i,j\\neq i} W(PX^i,PX^j)} where : - + - :math:`P` is a linear projection operator in the Stiefel(p,d) manifold - :math:`W` is entropic regularized Wasserstein distances - - :math:`X^i` are samples in the dataset corresponding to class i - + - :math:`X^i` are samples in the dataset corresponding to class i + Parameters ---------- X : numpy.ndarray (n,d) @@ -147,54 +150,50 @@ def wda(X,y,p=2,reg=1,k=10,solver = None,maxiter=100,verbose=0,P0=None): ---------- .. [11] Flamary, R., Cuturi, M., Courty, N., & Rakotomamonjy, A. (2016). Wasserstein Discriminant Analysis. arXiv preprint arXiv:1608.08063. - - """ - - mx=np.mean(X) - X-=mx.reshape((1,-1)) - + + """ # noqa + + mx = np.mean(X) + X -= mx.reshape((1, -1)) + # data split between classes - d=X.shape[1] - xc=split_classes(X,y) + d = X.shape[1] + xc = split_classes(X, y) # compute uniform weighs - wc=[np.ones((x.shape[0]),dtype=np.float32)/x.shape[0] for x in xc] - + wc = [np.ones((x.shape[0]), dtype=np.float32) / x.shape[0] for x in xc] + def cost(P): # wda loss - loss_b=0 - loss_w=0 - - for i,xi in enumerate(xc): - xi=np.dot(xi,P) - for j,xj in enumerate(xc[i:]): - xj=np.dot(xj,P) - M=dist(xi,xj) - G=sinkhorn(wc[i],wc[j+i],M,reg,k) - if j==0: - loss_w+=np.sum(G*M) + loss_b = 0 + loss_w = 0 + + for i, xi in enumerate(xc): + xi = np.dot(xi, P) + for j, xj in enumerate(xc[i:]): + xj = np.dot(xj, P) + M = dist(xi, xj) + G = sinkhorn(wc[i], wc[j + i], M, reg, k) + if j == 0: + loss_w += np.sum(G * M) else: - loss_b+=np.sum(G*M) - - # loss inversed because minimization - return loss_w/loss_b - - + loss_b += np.sum(G * M) + + # loss inversed because minimization + return loss_w / loss_b + # declare manifold and problem - manifold = Stiefel(d, p) + manifold = Stiefel(d, p) problem = Problem(manifold=manifold, cost=cost) - + # declare solver and solve if solver is None: - solver= SteepestDescent(maxiter=maxiter,logverbosity=verbose) - elif solver in ['tr','TrustRegions']: - solver= TrustRegions(maxiter=maxiter,logverbosity=verbose) - - Popt = solver.solve(problem,x=P0) - - def proj(X): - return (X-mx.reshape((1,-1))).dot(Popt) - - return Popt, proj + solver = SteepestDescent(maxiter=maxiter, logverbosity=verbose) + elif solver in ['tr', 'TrustRegions']: + solver = TrustRegions(maxiter=maxiter, logverbosity=verbose) + Popt = solver.solve(problem, x=P0) + def proj(X): + return (X - mx.reshape((1, -1))).dot(Popt) + return Popt, proj diff --git a/ot/optim.py b/ot/optim.py index 79f4f66..adad95e 100644 --- a/ot/optim.py +++ b/ot/optim.py @@ -9,7 +9,9 @@ from .lp import emd from .bregman import sinkhorn # The corresponding scipy function does not work for matrices -def line_search_armijo(f,xk,pk,gfk,old_fval,args=(),c1=1e-4,alpha0=0.99): + + +def line_search_armijo(f, xk, pk, gfk, old_fval, args=(), c1=1e-4, alpha0=0.99): """ Armijo linesearch function that works with matrices @@ -51,20 +53,21 @@ def line_search_armijo(f,xk,pk,gfk,old_fval,args=(),c1=1e-4,alpha0=0.99): def phi(alpha1): fc[0] += 1 - return f(xk + alpha1*pk, *args) + return f(xk + alpha1 * pk, *args) if old_fval is None: phi0 = phi(0.) else: phi0 = old_fval - derphi0 = np.sum(pk*gfk) # Quickfix for matrices - alpha,phi1 = scalar_search_armijo(phi,phi0,derphi0,c1=c1,alpha0=alpha0) + derphi0 = np.sum(pk * gfk) # Quickfix for matrices + alpha, phi1 = scalar_search_armijo( + phi, phi0, derphi0, c1=c1, alpha0=alpha0) - return alpha,fc[0],phi1 + return alpha, fc[0], phi1 -def cg(a,b,M,reg,f,df,G0=None,numItermax = 200,stopThr=1e-9,verbose=False,log=False): +def cg(a, b, M, reg, f, df, G0=None, numItermax=200, stopThr=1e-9, verbose=False, log=False): """ Solve the general regularized OT problem with conditional gradient @@ -128,74 +131,74 @@ def cg(a,b,M,reg,f,df,G0=None,numItermax = 200,stopThr=1e-9,verbose=False,log=Fa """ - loop=1 + loop = 1 if log: - log={'loss':[]} + log = {'loss': []} if G0 is None: - G=np.outer(a,b) + G = np.outer(a, b) else: - G=G0 + G = G0 def cost(G): - return np.sum(M*G)+reg*f(G) + return np.sum(M * G) + reg * f(G) - f_val=cost(G) + f_val = cost(G) if log: log['loss'].append(f_val) - it=0 + it = 0 if verbose: - print('{:5s}|{:12s}|{:8s}'.format('It.','Loss','Delta loss')+'\n'+'-'*32) - print('{:5d}|{:8e}|{:8e}'.format(it,f_val,0)) + print('{:5s}|{:12s}|{:8s}'.format( + 'It.', 'Loss', 'Delta loss') + '\n' + '-' * 32) + print('{:5d}|{:8e}|{:8e}'.format(it, f_val, 0)) while loop: - it+=1 - old_fval=f_val - + it += 1 + old_fval = f_val # problem linearization - Mi=M+reg*df(G) + Mi = M + reg * df(G) # set M positive - Mi+=Mi.min() + Mi += Mi.min() # solve linear program - Gc=emd(a,b,Mi) + Gc = emd(a, b, Mi) - deltaG=Gc-G + deltaG = Gc - G # line search - alpha,fc,f_val = line_search_armijo(cost,G,deltaG,Mi,f_val) + alpha, fc, f_val = line_search_armijo(cost, G, deltaG, Mi, f_val) - G=G+alpha*deltaG + G = G + alpha * deltaG # test convergence - if it>=numItermax: - loop=0 - - delta_fval=(f_val-old_fval)/abs(f_val) - if abs(delta_fval)= numItermax: + loop = 0 + delta_fval = (f_val - old_fval) / abs(f_val) + if abs(delta_fval) < stopThr: + loop = 0 if log: log['loss'].append(f_val) if verbose: - if it%20 ==0: - print('{:5s}|{:12s}|{:8s}'.format('It.','Loss','Delta loss')+'\n'+'-'*32) - print('{:5d}|{:8e}|{:8e}'.format(it,f_val,delta_fval)) - + if it % 20 == 0: + print('{:5s}|{:12s}|{:8s}'.format( + 'It.', 'Loss', 'Delta loss') + '\n' + '-' * 32) + print('{:5d}|{:8e}|{:8e}'.format(it, f_val, delta_fval)) if log: - return G,log + return G, log else: return G -def gcg(a,b,M,reg1,reg2,f,df,G0=None,numItermax = 10,numInnerItermax = 200,stopThr=1e-9,verbose=False,log=False): + +def gcg(a, b, M, reg1, reg2, f, df, G0=None, numItermax=10, numInnerItermax=200, stopThr=1e-9, verbose=False, log=False): """ Solve the general regularized OT problem with the generalized conditional gradient @@ -264,70 +267,68 @@ def gcg(a,b,M,reg1,reg2,f,df,G0=None,numItermax = 10,numInnerItermax = 200,stopT """ - loop=1 + loop = 1 if log: - log={'loss':[]} + log = {'loss': []} if G0 is None: - G=np.outer(a,b) + G = np.outer(a, b) else: - G=G0 + G = G0 def cost(G): - return np.sum(M*G)+ reg1*np.sum(G*np.log(G)) + reg2*f(G) + return np.sum(M * G) + reg1 * np.sum(G * np.log(G)) + reg2 * f(G) - f_val=cost(G) + f_val = cost(G) if log: log['loss'].append(f_val) - it=0 + it = 0 if verbose: - print('{:5s}|{:12s}|{:8s}'.format('It.','Loss','Delta loss')+'\n'+'-'*32) - print('{:5d}|{:8e}|{:8e}'.format(it,f_val,0)) + print('{:5s}|{:12s}|{:8s}'.format( + 'It.', 'Loss', 'Delta loss') + '\n' + '-' * 32) + print('{:5d}|{:8e}|{:8e}'.format(it, f_val, 0)) while loop: - it+=1 - old_fval=f_val - + it += 1 + old_fval = f_val # problem linearization - Mi=M+reg2*df(G) + Mi = M + reg2 * df(G) # solve linear program with Sinkhorn #Gc = sinkhorn_stabilized(a,b, Mi, reg1, numItermax = numInnerItermax) - Gc = sinkhorn(a,b, Mi, reg1, numItermax = numInnerItermax) + Gc = sinkhorn(a, b, Mi, reg1, numItermax=numInnerItermax) - deltaG=Gc-G + deltaG = Gc - G # line search - dcost=Mi+reg1*(1+np.log(G)) #?? - alpha,fc,f_val = line_search_armijo(cost,G,deltaG,dcost,f_val) + dcost = Mi + reg1 * (1 + np.log(G)) # ?? + alpha, fc, f_val = line_search_armijo(cost, G, deltaG, dcost, f_val) - G=G+alpha*deltaG + G = G + alpha * deltaG # test convergence - if it>=numItermax: - loop=0 - - delta_fval=(f_val-old_fval)/abs(f_val) - if abs(delta_fval)= numItermax: + loop = 0 + delta_fval = (f_val - old_fval) / abs(f_val) + if abs(delta_fval) < stopThr: + loop = 0 if log: log['loss'].append(f_val) if verbose: - if it%20 ==0: - print('{:5s}|{:12s}|{:8s}'.format('It.','Loss','Delta loss')+'\n'+'-'*32) - print('{:5d}|{:8e}|{:8e}'.format(it,f_val,delta_fval)) - + if it % 20 == 0: + print('{:5s}|{:12s}|{:8s}'.format( + 'It.', 'Loss', 'Delta loss') + '\n' + '-' * 32) + print('{:5d}|{:8e}|{:8e}'.format(it, f_val, delta_fval)) if log: - return G,log + return G, log else: return G - -- cgit v1.2.3 From 95db977e8b931277af5dadbd79eccbd5fbb8bb62 Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Wed, 12 Jul 2017 23:56:27 +0200 Subject: pep8 --- ot/gpu/bregman.py | 11 +++++---- ot/gpu/da.py | 2 +- ot/utils.py | 62 ++++++++++++++++++++++++++--------------------- test/test_emd_multi.py | 27 ++++++++++----------- test/test_gpu_sinkhorn.py | 6 +++-- test/test_load_module.py | 4 +-- 6 files changed, 61 insertions(+), 51 deletions(-) diff --git a/ot/gpu/bregman.py b/ot/gpu/bregman.py index 2c3e317..7881c65 100644 --- a/ot/gpu/bregman.py +++ b/ot/gpu/bregman.py @@ -82,7 +82,7 @@ def sinkhorn(a, b, M_GPU, reg, numItermax=1000, stopThr=1e-9, verbose=False, ot.lp.emd : Unregularized OT ot.optim.cg : General regularized OT - """ + """ # init data Nini = len(a) Nfin = len(b) @@ -92,11 +92,11 @@ def sinkhorn(a, b, M_GPU, reg, numItermax=1000, stopThr=1e-9, verbose=False, # we assume that no distances are null except those of the diagonal of # distances - u = (np.ones(Nini)/Nini).reshape((Nini, 1)) + u = (np.ones(Nini) / Nini).reshape((Nini, 1)) u_GPU = cudamat.CUDAMatrix(u) a_GPU = cudamat.CUDAMatrix(a.reshape((Nini, 1))) ones_GPU = cudamat.empty(u_GPU.shape).assign(1) - v = (np.ones(Nfin)/Nfin).reshape((Nfin, 1)) + v = (np.ones(Nfin) / Nfin).reshape((Nfin, 1)) v_GPU = cudamat.CUDAMatrix(v) b_GPU = cudamat.CUDAMatrix(b.reshape((Nfin, 1))) @@ -121,7 +121,7 @@ def sinkhorn(a, b, M_GPU, reg, numItermax=1000, stopThr=1e-9, verbose=False, ones_GPU.divide(Kp_GPU.dot(v_GPU), target=u_GPU) if (np.any(KtransposeU_GPU.asarray() == 0) or - not u_GPU.allfinite() or not v_GPU.allfinite()): + not u_GPU.allfinite() or not v_GPU.allfinite()): # we have reached the machine precision # come back to previous solution and quit loop print('Warning: numerical errors at iteration', cpt) @@ -142,7 +142,8 @@ def sinkhorn(a, b, M_GPU, reg, numItermax=1000, stopThr=1e-9, verbose=False, if verbose: if cpt % 200 == 0: - print('{:5s}|{:12s}'.format('It.', 'Err')+'\n'+'-'*19) + print( + '{:5s}|{:12s}'.format('It.', 'Err') + '\n' + '-' * 19) print('{:5d}|{:8e}|'.format(cpt, err)) cpt += 1 if log: diff --git a/ot/gpu/da.py b/ot/gpu/da.py index b05ff70..c66e755 100644 --- a/ot/gpu/da.py +++ b/ot/gpu/da.py @@ -167,7 +167,7 @@ def sinkhorn_lpl1_mm(a, labels_a, b, M_GPU, reg, eta=0.1, numItermax=10, tmpC_GPU = cudamat.empty((Nfin, nbRow)).assign(0) transp_GPU.transpose().select_columns(indices_labels[i], tmpC_GPU) majs_GPU = tmpC_GPU.sum(axis=1).add(epsilon) - cudamat.pow(majs_GPU, (p-1)) + cudamat.pow(majs_GPU, (p - 1)) majs_GPU.mult(p) tmpC_GPU.assign(0) diff --git a/ot/utils.py b/ot/utils.py index 7ad7637..6a43f61 100644 --- a/ot/utils.py +++ b/ot/utils.py @@ -7,31 +7,35 @@ from scipy.spatial.distance import cdist import multiprocessing import time -__time_tic_toc=time.time() +__time_tic_toc = time.time() + def tic(): """ Python implementation of Matlab tic() function """ global __time_tic_toc - __time_tic_toc=time.time() + __time_tic_toc = time.time() + def toc(message='Elapsed time : {} s'): """ Python implementation of Matlab toc() function """ - t=time.time() - print(message.format(t-__time_tic_toc)) - return t-__time_tic_toc + t = time.time() + print(message.format(t - __time_tic_toc)) + return t - __time_tic_toc + def toq(): """ Python implementation of Julia toc() function """ - t=time.time() - return t-__time_tic_toc + t = time.time() + return t - __time_tic_toc -def kernel(x1,x2,method='gaussian',sigma=1,**kwargs): +def kernel(x1, x2, method='gaussian', sigma=1, **kwargs): """Compute kernel matrix""" - if method.lower() in ['gaussian','gauss','rbf']: - K=np.exp(-dist(x1,x2)/(2*sigma**2)) + if method.lower() in ['gaussian', 'gauss', 'rbf']: + K = np.exp(-dist(x1, x2) / (2 * sigma**2)) return K + def unif(n): """ return a uniform histogram of length n (simplex) @@ -48,17 +52,19 @@ def unif(n): """ - return np.ones((n,))/n + return np.ones((n,)) / n -def clean_zeros(a,b,M): - """ Remove all components with zeros weights in a and b + +def clean_zeros(a, b, M): + """ Remove all components with zeros weights in a and b """ - M2=M[a>0,:][:,b>0].copy() # copy force c style matrix (froemd) - a2=a[a>0] - b2=b[b>0] - return a2,b2,M2 + M2 = M[a > 0, :][:, b > 0].copy() # copy force c style matrix (froemd) + a2 = a[a > 0] + b2 = b[b > 0] + return a2, b2, M2 + -def dist(x1,x2=None,metric='sqeuclidean'): +def dist(x1, x2=None, metric='sqeuclidean'): """Compute distance between samples in x1 and x2 using function scipy.spatial.distance.cdist Parameters @@ -84,12 +90,12 @@ def dist(x1,x2=None,metric='sqeuclidean'): """ if x2 is None: - x2=x1 + x2 = x1 - return cdist(x1,x2,metric=metric) + return cdist(x1, x2, metric=metric) -def dist0(n,method='lin_square'): +def dist0(n, method='lin_square'): """Compute standard cost matrices of size (n,n) for OT problems Parameters @@ -111,16 +117,17 @@ def dist0(n,method='lin_square'): """ - res=0 - if method=='lin_square': - x=np.arange(n,dtype=np.float64).reshape((n,1)) - res=dist(x,x) + res = 0 + if method == 'lin_square': + x = np.arange(n, dtype=np.float64).reshape((n, 1)) + res = dist(x, x) return res def dots(*args): """ dots function for multiple matrix multiply """ - return reduce(np.dot,args) + return reduce(np.dot, args) + def fun(f, q_in, q_out): """ Utility function for parmap with no serializing problems """ @@ -130,6 +137,7 @@ def fun(f, q_in, q_out): break q_out.put((i, f(x))) + def parmap(f, X, nprocs=multiprocessing.cpu_count()): """ paralell map for multiprocessing """ q_in = multiprocessing.Queue(1) @@ -147,4 +155,4 @@ def parmap(f, X, nprocs=multiprocessing.cpu_count()): [p.join() for p in proc] - return [x for i, x in sorted(res)] \ No newline at end of file + return [x for i, x in sorted(res)] diff --git a/test/test_emd_multi.py b/test/test_emd_multi.py index ee0a20e..99173e9 100644 --- a/test/test_emd_multi.py +++ b/test/test_emd_multi.py @@ -7,31 +7,30 @@ Created on Fri Mar 10 09:56:06 2017 """ import numpy as np -import pylab as pl -import ot +import ot from ot.datasets import get_1D_gauss as gauss -reload(ot.lp) +# reload(ot.lp) #%% parameters -n=5000 # nb bins +n = 5000 # nb bins # bin positions -x=np.arange(n,dtype=np.float64) +x = np.arange(n, dtype=np.float64) # Gaussian distributions -a=gauss(n,m=20,s=5) # m= mean, s= std +a = gauss(n, m=20, s=5) # m= mean, s= std -ls= range(20,1000,10) -nb=len(ls) -b=np.zeros((n,nb)) +ls = range(20, 1000, 10) +nb = len(ls) +b = np.zeros((n, nb)) for i in range(nb): - b[:,i]=gauss(n,m=ls[i],s=10) + b[:, i] = gauss(n, m=ls[i], s=10) # loss matrix -M=ot.dist(x.reshape((n,1)),x.reshape((n,1))) -#M/=M.max() +M = ot.dist(x.reshape((n, 1)), x.reshape((n, 1))) +# M/=M.max() #%% @@ -39,10 +38,10 @@ print('Computing {} EMD '.format(nb)) # emd loss 1 proc ot.tic() -emd_loss4=ot.emd2(a,b,M,1) +emd_loss4 = ot.emd2(a, b, M, 1) ot.toc('1 proc : {} s') # emd loss multipro proc ot.tic() -emd_loss4=ot.emd2(a,b,M) +emd_loss4 = ot.emd2(a, b, M) ot.toc('multi proc : {} s') diff --git a/test/test_gpu_sinkhorn.py b/test/test_gpu_sinkhorn.py index bfa2cd2..841f062 100644 --- a/test/test_gpu_sinkhorn.py +++ b/test/test_gpu_sinkhorn.py @@ -3,8 +3,10 @@ import numpy as np import time import ot.gpu + def describeRes(r): - print("min:{:.3E}, max::{:.3E}, mean::{:.3E}, std::{:.3E}".format(np.min(r),np.max(r),np.mean(r),np.std(r))) + print("min:{:.3E}, max::{:.3E}, mean::{:.3E}, std::{:.3E}".format( + np.min(r), np.max(r), np.mean(r), np.std(r))) for n in [5000, 10000, 15000, 20000]: @@ -23,4 +25,4 @@ for n in [5000, 10000, 15000, 20000]: print("Normal sinkhorn, time: {:6.2f} sec ".format(time2 - time1)) describeRes(G1) print(" GPU sinkhorn, time: {:6.2f} sec ".format(time3 - time2)) - describeRes(G2) \ No newline at end of file + describeRes(G2) diff --git a/test/test_load_module.py b/test/test_load_module.py index a04c5df..d77261e 100644 --- a/test/test_load_module.py +++ b/test/test_load_module.py @@ -4,7 +4,7 @@ import ot import doctest # test lp solver -doctest.testmod(ot.lp,verbose=True) +doctest.testmod(ot.lp, verbose=True) # test bregman solver -doctest.testmod(ot.bregman,verbose=True) +doctest.testmod(ot.bregman, verbose=True) -- cgit v1.2.3 From f15336be3bb91b619803bfbb58e7931997c46574 Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Wed, 12 Jul 2017 23:57:11 +0200 Subject: update travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8023ec5..050510b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,5 +21,5 @@ install: # command to run tests + check syntax style script: - python test/test_load_module.py -v - - flake8 . + - flake8 examples/ ot/ test/ - py.test ot test -- cgit v1.2.3 From cd9842dc2978cba757a51c32cce0272858c9a385 Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Thu, 13 Jul 2017 00:04:49 +0200 Subject: more --- .travis.yml | 2 +- test/test_emd_multi.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 050510b..8a95d7c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,4 +22,4 @@ install: script: - python test/test_load_module.py -v - flake8 examples/ ot/ test/ - - py.test ot test + # - py.test ot test diff --git a/test/test_emd_multi.py b/test/test_emd_multi.py index 99173e9..2eef242 100644 --- a/test/test_emd_multi.py +++ b/test/test_emd_multi.py @@ -22,7 +22,7 @@ x = np.arange(n, dtype=np.float64) # Gaussian distributions a = gauss(n, m=20, s=5) # m= mean, s= std -ls = range(20, 1000, 10) +ls = np.arange(20, 1000, 10) nb = len(ls) b = np.zeros((n, nb)) for i in range(nb): -- cgit v1.2.3 From ced35d10cb9c92e3cfd98f16f2fee778a969de97 Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Thu, 20 Jul 2017 14:09:59 +0200 Subject: pt not plt --- ot/plot.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/ot/plot.py b/ot/plot.py index 6f01731..61afc9f 100644 --- a/ot/plot.py +++ b/ot/plot.py @@ -4,7 +4,7 @@ Functions for plotting OT matrices import numpy as np -import matplotlib.pylab as plt +import matplotlib.pylab as pl from matplotlib import gridspec @@ -31,24 +31,24 @@ def plot1D_mat(a, b, M, title=''): xa = np.arange(na) xb = np.arange(nb) - ax1 = plt.subplot(gs[0, 1:]) - plt.plot(xb, b, 'r', label='Target distribution') - plt.yticks(()) - plt.title(title) + ax1 = pl.subplot(gs[0, 1:]) + pl.plot(xb, b, 'r', label='Target distribution') + pl.yticks(()) + pl.title(title) - ax2 = plt.subplot(gs[1:, 0]) - plt.plot(a, xa, 'b', label='Source distribution') - plt.gca().invert_xaxis() - plt.gca().invert_yaxis() - plt.xticks(()) + ax2 = pl.subplot(gs[1:, 0]) + pl.plot(a, xa, 'b', label='Source distribution') + pl.gca().invert_xaxis() + pl.gca().invert_yaxis() + pl.xticks(()) - plt.subplot(gs[1:, 1:], sharex=ax1, sharey=ax2) - plt.imshow(M, interpolation='nearest') - plt.axis('off') + pl.subplot(gs[1:, 1:], sharex=ax1, sharey=ax2) + pl.imshow(M, interpolation='nearest') + pl.axis('off') - plt.xlim((0, nb)) - plt.tight_layout() - plt.subplots_adjust(wspace=0., hspace=0.2) + pl.xlim((0, nb)) + pl.tight_layout() + pl.subplots_adjust(wspace=0., hspace=0.2) def plot2D_samples_mat(xs, xt, G, thr=1e-8, **kwargs): @@ -78,5 +78,5 @@ def plot2D_samples_mat(xs, xt, G, thr=1e-8, **kwargs): for i in range(xs.shape[0]): for j in range(xt.shape[0]): if G[i, j] / mx > thr: - plt.plot([xs[i, 0], xt[j, 0]], [xs[i, 1], xt[j, 1]], - alpha=G[i, j] / mx, **kwargs) + pl.plot([xs[i, 0], xt[j, 0]], [xs[i, 1], xt[j, 1]], + alpha=G[i, j] / mx, **kwargs) -- cgit v1.2.3 From 20c016c5dcb310361ecfd2f18f951c9c366ef2b9 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Thu, 20 Jul 2017 14:22:40 +0200 Subject: add code of cobnduct --- CODE_OF_CONDUCT.md | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..9c1c621 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ -- cgit v1.2.3 From 608882349326832ae72051569410ca4397e9ad8b Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Thu, 20 Jul 2017 14:37:16 +0200 Subject: add contributing code and update readme --- CONTRIBUTING.md | 204 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 6 ++ 2 files changed, 210 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..84ef29a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,204 @@ + +Contributing to POT +=================== + + +First off, thank you for considering contributing to POT. + +How to contribute +----------------- + +The preferred workflow for contributing to POT is to fork the +[main repository](https://github.com/rflamary/POT) on +GitHub, clone, and develop on a branch. Steps: + +1. Fork the [project repository](https://github.com/rflamary/POT) + by clicking on the 'Fork' button near the top right of the page. This creates + a copy of the code under your GitHub user account. For more details on + how to fork a repository see [this guide](https://help.github.com/articles/fork-a-repo/). + +2. Clone your fork of the scikit-learn repo from your GitHub account to your local disk: + + ```bash + $ git clone git@github.com:YourLogin/POT.git + $ cd POT + ``` + +3. Create a ``feature`` branch to hold your development changes: + + ```bash + $ git checkout -b my-feature + ``` + + Always use a ``feature`` branch. It's good practice to never work on the ``master`` branch! + +4. Develop the feature on your feature branch. Add changed files using ``git add`` and then ``git commit`` files: + + ```bash + $ git add modified_files + $ git commit + ``` + + to record your changes in Git, then push the changes to your GitHub account with: + + ```bash + $ git push -u origin my-feature + ``` + +5. Follow [these instructions](https://help.github.com/articles/creating-a-pull-request-from-a-fork) +to create a pull request from your fork. This will send an email to the committers. + +(If any of the above seems like magic to you, please look up the +[Git documentation](https://git-scm.com/documentation) on the web, or ask a friend or another contributor for help.) + +Pull Request Checklist +---------------------- + +We recommended that your contribution complies with the +following rules before you submit a pull request: + +- Follow the PEP8 Guidelines. + +- If your pull request addresses an issue, please use the pull request title + to describe the issue and mention the issue number in the pull request description. This will make sure a link back to the original issue is + created. + +- All public methods should have informative docstrings with sample + usage presented as doctests when appropriate. + +- Please prefix the title of your pull request with `[MRG]` (Ready for + Merge), if the contribution is complete and ready for a detailed review. + Two core developers will review your code and change the prefix of the pull + request to `[MRG + 1]` and `[MRG + 2]` on approval, making it eligible + for merging. An incomplete contribution -- where you expect to do more work before + receiving a full review -- should be prefixed `[WIP]` (to indicate a work + in progress) and changed to `[MRG]` when it matures. WIPs may be useful + to: indicate you are working on something to avoid duplicated work, + request broad review of functionality or API, or seek collaborators. + WIPs often benefit from the inclusion of a + [task list](https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments) + in the PR description. + + +- When adding additional functionality, provide at least one + example script in the ``examples/`` folder. Have a look at other + examples for reference. Examples should demonstrate why the new + functionality is useful in practice and, if possible, compare it + to other methods available in scikit-learn. + +- Documentation and high-coverage tests are necessary for enhancements to be + accepted. Bug-fixes or new features should be provided with + [non-regression tests](https://en.wikipedia.org/wiki/Non-regression_testing). + These tests verify the correct behavior of the fix or feature. In this + manner, further modifications on the code base are granted to be consistent + with the desired behavior. + For the Bug-fixes case, at the time of the PR, this tests should fail for + the code base in master and pass for the PR code. + +- At least one paragraph of narrative documentation with links to + references in the literature (with PDF links when possible) and + the example. + +You can also check for common programming errors with the following +tools: + + +- No pyflakes warnings, check with: + + ```bash + $ pip install pyflakes + $ pyflakes path/to/module.py + ``` + +- No PEP8 warnings, check with: + + ```bash + $ pip install pep8 + $ pep8 path/to/module.py + ``` + +- AutoPEP8 can help you fix some of the easy redundant errors: + + ```bash + $ pip install autopep8 + $ autopep8 path/to/pep8.py + ``` + +Bonus points for contributions that include a performance analysis with +a benchmark script and profiling output (please report on the mailing +list or on the GitHub issue). + +Filing bugs +----------- +We use Github issues to track all bugs and feature requests; feel free to +open an issue if you have found a bug or wish to see a feature implemented. + +It is recommended to check that your issue complies with the +following rules before submitting: + +- Verify that your issue is not being currently addressed by other + [issues](https://github.com/rflamary/POT/issues?q=) + or [pull requests](https://github.com/rflamary/POT/pulls?q=). + +- Please ensure all code snippets and error messages are formatted in + appropriate code blocks. + See [Creating and highlighting code blocks](https://help.github.com/articles/creating-and-highlighting-code-blocks). + +- Please include your operating system type and version number, as well + as your Python, scikit-learn, numpy, and scipy versions. This information + can be found by running the following code snippet: + + ```python + import platform; print(platform.platform()) + import sys; print("Python", sys.version) + import numpy; print("NumPy", numpy.__version__) + import scipy; print("SciPy", scipy.__version__) + import ot; print("POT", ot.__version__) + ``` + +- Please be specific about what estimators and/or functions are involved + and the shape of the data, as appropriate; please include a + [reproducible](http://stackoverflow.com/help/mcve) code snippet + or link to a [gist](https://gist.github.com). If an exception is raised, + please provide the traceback. + +New contributor tips +-------------------- + +A great way to start contributing to scikit-learn is to pick an item +from the list of [Easy issues](https://github.com/scikit-learn/scikit-learn/issues?labels=Easy) +in the issue tracker. Resolving these issues allow you to start +contributing to the project without much prior knowledge. Your +assistance in this area will be greatly appreciated by the more +experienced developers as it helps free up their time to concentrate on +other issues. + +Documentation +------------- + +We are glad to accept any sort of documentation: function docstrings, +reStructuredText documents (like this one), tutorials, etc. +reStructuredText documents live in the source code repository under the +doc/ directory. + +You can edit the documentation using any text editor and then generate +the HTML output by typing ``make html`` from the doc/ directory. +Alternatively, ``make`` can be used to quickly generate the +documentation without the example gallery. The resulting HTML files will +be placed in ``_build/html/`` and are viewable in a web browser. See the +``README`` file in the ``doc/`` directory for more information. + +For building the documentation, you will need +[sphinx](http://sphinx.pocoo.org/), +[matplotlib](http://matplotlib.org/), and +[pillow](http://pillow.readthedocs.io/en/latest/). + +When you are writing documentation, it is important to keep a good +compromise between mathematical and algorithmic details, and give +intuition to the reader on what the algorithm does. It is best to always +start with a small paragraph with a hand-waving explanation of what the +method does to the data and a figure (coming from an example) +illustrating it. + + +This Contrubution guide is strongly inpired by the one of the [scikit-learn](https://github.com/scikit-learn/scikit-learn) team. diff --git a/README.md b/README.md index c3dbf68..6324649 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,7 @@ The contributors to this library are: * [Rémi Flamary](http://remi.flamary.com/) * [Nicolas Courty](http://people.irisa.fr/Nicolas.Courty/) +* [Alexandre Gramfort](http://alexandre.gramfort.net/) * [Laetitia Chapel](http://people.irisa.fr/Laetitia.Chapel/) * [Michael Perrot](http://perso.univ-st-etienne.fr/pem82055/) (Mapping estimation) * [Léo Gautheron](https://github.com/aje) (GPU implementation) @@ -143,6 +144,11 @@ This toolbox benefit a lot from open source research and we would like to thank * [Antoine Rolet](https://arolet.github.io/) ( Mex file for EMD ) * [Marco Cuturi](http://marcocuturi.net/) (Sinkhorn Knopp in Matlab/Cuda) + +## Contributions and code of conduct + +Every contribution is welcome and should respect the [contribution guidelines](CONTRIBUTING.md). Each member of the project is expected to follow the [code of conduct](CODE_OF_CONDUCT.md). + ## References [1] Bonneel, N., Van De Panne, M., Paris, S., & Heidrich, W. (2011, December). [Displacement interpolation using Lagrangian mass transport](https://people.csail.mit.edu/sparis/publi/2011/sigasia/Bonneel_11_Displacement_Interpolation.pdf). In ACM Transactions on Graphics (TOG) (Vol. 30, No. 6, p. 158). ACM. -- cgit v1.2.3 From 6ada23e5a672b08f28e21123c4135bc787e83b19 Mon Sep 17 00:00:00 2001 From: Alexandre Gramfort Date: Thu, 20 Jul 2017 15:39:50 +0200 Subject: pep8 --- examples/plot_OTDA_color_images.py | 2 ++ examples/plot_OTDA_mapping_color_images.py | 2 ++ examples/plot_optim_OTreg.py | 3 +++ ot/utils.py | 6 ++++-- test/test_gpu_sinkhorn_lpl1.py | 1 + 5 files changed, 12 insertions(+), 2 deletions(-) diff --git a/examples/plot_OTDA_color_images.py b/examples/plot_OTDA_color_images.py index a8861c6..75ac5b6 100644 --- a/examples/plot_OTDA_color_images.py +++ b/examples/plot_OTDA_color_images.py @@ -48,6 +48,7 @@ def mat2im(X, shape): """Converts back a matrix to an image""" return X.reshape(shape) + X1 = im2mat(I1) X2 = im2mat(I2) @@ -102,6 +103,7 @@ X2te = da_entrop.predict(X2, -1) def minmax(I): return np.clip(I, 0, 1) + I1t = minmax(mat2im(X1t, I1.shape)) I2t = minmax(mat2im(X2t, I2.shape)) diff --git a/examples/plot_OTDA_mapping_color_images.py b/examples/plot_OTDA_mapping_color_images.py index 85c4b6b..9710461 100644 --- a/examples/plot_OTDA_mapping_color_images.py +++ b/examples/plot_OTDA_mapping_color_images.py @@ -48,6 +48,7 @@ def mat2im(X, shape): """Converts back a matrix to an image""" return X.reshape(shape) + X1 = im2mat(I1) X2 = im2mat(I2) @@ -85,6 +86,7 @@ pl.tight_layout() def minmax(I): return np.clip(I, 0, 1) + # LP problem da_emd = ot.da.OTDA() # init class da_emd.fit(xs, xt) # fit distributions diff --git a/examples/plot_optim_OTreg.py b/examples/plot_optim_OTreg.py index e38253c..276b250 100644 --- a/examples/plot_optim_OTreg.py +++ b/examples/plot_optim_OTreg.py @@ -44,6 +44,7 @@ def f(G): def df(G): return G + reg = 1e-1 Gl2 = ot.optim.cg(a, b, M, reg, f, df, verbose=True) @@ -61,6 +62,7 @@ def f(G): def df(G): return np.log(G) + 1. + reg = 1e-3 Ge = ot.optim.cg(a, b, M, reg, f, df, verbose=True) @@ -78,6 +80,7 @@ def f(G): def df(G): return G + reg1 = 1e-3 reg2 = 1e-1 diff --git a/ot/utils.py b/ot/utils.py index 6a43f61..1dee932 100644 --- a/ot/utils.py +++ b/ot/utils.py @@ -2,11 +2,13 @@ """ Various function that can be usefull """ +import multiprocessing +from functools import reduce +import time + import numpy as np from scipy.spatial.distance import cdist -import multiprocessing -import time __time_tic_toc = time.time() diff --git a/test/test_gpu_sinkhorn_lpl1.py b/test/test_gpu_sinkhorn_lpl1.py index e6cdd31..f0eb7e6 100644 --- a/test/test_gpu_sinkhorn_lpl1.py +++ b/test/test_gpu_sinkhorn_lpl1.py @@ -8,6 +8,7 @@ def describeRes(r): print("min:{:.3E}, max:{:.3E}, mean:{:.3E}, std:{:.3E}" .format(np.min(r), np.max(r), np.mean(r), np.std(r))) + for n in [5000, 10000, 15000, 20000]: print(n) a = np.random.rand(n // 4, 100) -- cgit v1.2.3 From aaf80bbef65c1b8cee9bdec512ab81f00e8329e1 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Mon, 24 Jul 2017 09:21:12 +0200 Subject: add slack and mailing list --- Makefile | 5 ++++- README.md | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 22c1b50..c6a83c8 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,10 @@ sremove : clean : $(PYTHON) setup.py clean - + +pep8 : + flake8 examples/ ot/ test/ + test: pytest diff --git a/README.md b/README.md index 6324649..84f5228 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,16 @@ This toolbox benefit a lot from open source research and we would like to thank Every contribution is welcome and should respect the [contribution guidelines](CONTRIBUTING.md). Each member of the project is expected to follow the [code of conduct](CODE_OF_CONDUCT.md). +## Support + +You can ask questions and join the development discussion: + +* On the [POT Slack channel](pot-toolbox.slack.com) +* On the POT [mailing list](https://mail.python.org/mm3/mailman3/lists/pot.python.org/) + + +You can also post bug reports and feature requests in Github issues. Make sure to read our [guidelines](CONTRIBUTING.md) first. + ## References [1] Bonneel, N., Van De Panne, M., Paris, S., & Heidrich, W. (2011, December). [Displacement interpolation using Lagrangian mass transport](https://people.csail.mit.edu/sparis/publi/2011/sigasia/Bonneel_11_Displacement_Interpolation.pdf). In ACM Transactions on Graphics (TOG) (Vol. 30, No. 6, p. 158). ACM. -- cgit v1.2.3 From 82da63f1020835a412f6174500099694a78ab6be Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Mon, 24 Jul 2017 09:29:34 +0200 Subject: correct url --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 84f5228..7a65106 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ Every contribution is welcome and should respect the [contribution guidelines](C You can ask questions and join the development discussion: -* On the [POT Slack channel](pot-toolbox.slack.com) +* On the [POT Slack channel](https://pot-toolbox.slack.com) * On the POT [mailing list](https://mail.python.org/mm3/mailman3/lists/pot.python.org/) -- cgit v1.2.3 From 5a6b5de9b2f28c93bef1a9db2e3b94693c05ff4f Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Mon, 24 Jul 2017 11:15:33 +0200 Subject: add proper testing --- .travis.yml | 2 +- Makefile | 13 ++++++---- docs/source/readme.rst | 52 +++++++++++++++++++++++++++++-------- test/test_emd_multi.py | 47 --------------------------------- test/test_gpu.py | 59 ++++++++++++++++++++++++++++++++++++++++++ test/test_gpu_sinkhorn.py | 28 -------------------- test/test_gpu_sinkhorn_lpl1.py | 29 --------------------- test/test_load_module.py | 10 ------- test/test_ot.py | 55 +++++++++++++++++++++++++++++++++++++++ 9 files changed, 164 insertions(+), 131 deletions(-) delete mode 100644 test/test_emd_multi.py create mode 100644 test/test_gpu.py delete mode 100644 test/test_gpu_sinkhorn.py delete mode 100644 test/test_gpu_sinkhorn_lpl1.py delete mode 100644 test/test_load_module.py create mode 100644 test/test_ot.py diff --git a/.travis.yml b/.travis.yml index 8a95d7c..1c3a18c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,6 @@ install: - python setup.py install # command to run tests + check syntax style script: - - python test/test_load_module.py -v - flake8 examples/ ot/ test/ + - python -m py.test -v # - py.test ot test diff --git a/Makefile b/Makefile index c6a83c8..ff03a63 100644 --- a/Makefile +++ b/Makefile @@ -31,22 +31,25 @@ sremove : tr '\n' '\0' < files.txt | sudo xargs -0 rm -f -- rm files.txt -clean : +clean : FORCE $(PYTHON) setup.py clean pep8 : flake8 examples/ ot/ test/ -test: - pytest +test : FORCE pep8 + python -m py.test -v -uploadpypi: +uploadpypi : #python setup.py register python setup.py sdist upload -r pypi -rdoc: +rdoc : pandoc --from=markdown --to=rst --output=docs/source/readme.rst README.md notebook : ipython notebook --matplotlib=inline --notebook-dir=notebooks/ + + +FORCE : diff --git a/docs/source/readme.rst b/docs/source/readme.rst index 611001b..c1e0017 100644 --- a/docs/source/readme.rst +++ b/docs/source/readme.rst @@ -28,8 +28,8 @@ available in the examples folder. Installation ------------ -The Library has been tested on Linux and MacOSX. It requires a C++ -compiler for using the EMD solver and rely on the following Python +The library has been tested on Linux, MacOSX and Windows. It requires a +C++ compiler for using the EMD solver and relies on the following Python modules: - Numpy (>=1.11) @@ -37,25 +37,34 @@ modules: - Cython (>=0.23) - Matplotlib (>=1.5) -Under debian based linux the dependencies can be installed with +Pip installation +^^^^^^^^^^^^^^^^ + +You can install the toolbox through PyPI with: :: - sudo apt-get install python-numpy python-scipy python-matplotlib cython + pip install POT -To install the library, you can install it locally (after downloading -it) on you machine using +or get the very latest version by downloading it and then running: :: python setup.py install --user # for user install (no root) -The toolbox is also available on PyPI with a possibly slightly older -version. You can install it with: +Anaconda installation with conda-forge +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you use the Anaconda python distribution, POT is available in +`conda-forge `__. To install it and the +required dependencies: :: - pip install POT + conda install -c conda-forge pot + +Post installation check +^^^^^^^^^^^^^^^^^^^^^^^ After a correct installation, you should be able to import the module without errors: @@ -109,6 +118,7 @@ Short examples # a,b are 1D histograms (sum to 1 and positive) # M is the ground cost matrix Wd=ot.emd2(a,b,M) # exact linear program + Wd_reg=ot.sinkhorn2(a,b,M,reg) # entropic regularized OT # if b is a matrix compute all distances to a and return a vector - Compute OT matrix @@ -117,8 +127,8 @@ Short examples # a,b are 1D histograms (sum to 1 and positive) # M is the ground cost matrix - Totp=ot.emd(a,b,M) # exact linear program - Totp_reg=ot.sinkhorn(a,b,M,reg) # entropic regularized OT + T=ot.emd(a,b,M) # exact linear program + T_reg=ot.sinkhorn(a,b,M,reg) # entropic regularized OT - Compute Wasserstein barycenter @@ -172,6 +182,7 @@ The contributors to this library are: - `Rémi Flamary `__ - `Nicolas Courty `__ +- `Alexandre Gramfort `__ - `Laetitia Chapel `__ - `Michael Perrot `__ (Mapping estimation) @@ -189,6 +200,25 @@ languages): - `Marco Cuturi `__ (Sinkhorn Knopp in Matlab/Cuda) +Contributions and code of conduct +--------------------------------- + +Every contribution is welcome and should respect the `contribution +guidelines `__. Each member of the project is expected +to follow the `code of conduct `__. + +Support +------- + +You can ask questions and join the development discussion: + +- On the `POT Slack channel `__ +- On the POT `mailing + list `__ + +You can also post bug reports and feature requests in Github issues. +Make sure to read our `guidelines `__ first. + References ---------- diff --git a/test/test_emd_multi.py b/test/test_emd_multi.py deleted file mode 100644 index 2eef242..0000000 --- a/test/test_emd_multi.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python2 -# -*- coding: utf-8 -*- -""" -Created on Fri Mar 10 09:56:06 2017 - -@author: rflamary -""" - -import numpy as np - -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 - -ls = np.arange(20, 1000, 10) -nb = len(ls) -b = np.zeros((n, nb)) -for i in range(nb): - b[:, i] = gauss(n, m=ls[i], s=10) - -# loss matrix -M = ot.dist(x.reshape((n, 1)), x.reshape((n, 1))) -# M/=M.max() - -#%% - -print('Computing {} EMD '.format(nb)) - -# emd loss 1 proc -ot.tic() -emd_loss4 = ot.emd2(a, b, M, 1) -ot.toc('1 proc : {} s') - -# emd loss multipro proc -ot.tic() -emd_loss4 = ot.emd2(a, b, M) -ot.toc('multi proc : {} s') diff --git a/test/test_gpu.py b/test/test_gpu.py new file mode 100644 index 0000000..312a2d4 --- /dev/null +++ b/test/test_gpu.py @@ -0,0 +1,59 @@ +import ot +import numpy as np +import time +import pytest + + +@pytest.mark.skip(reason="No way to test GPU on travis yet") +def test_gpu_sinkhorn(): + import ot.gpu + + def describeRes(r): + print("min:{:.3E}, max::{:.3E}, mean::{:.3E}, std::{:.3E}".format( + np.min(r), np.max(r), np.mean(r), np.std(r))) + + for n in [5000]: + print(n) + a = np.random.rand(n // 4, 100) + b = np.random.rand(n, 100) + time1 = time.time() + transport = ot.da.OTDA_sinkhorn() + transport.fit(a, b) + G1 = transport.G + time2 = time.time() + transport = ot.gpu.da.OTDA_sinkhorn() + transport.fit(a, b) + G2 = transport.G + time3 = time.time() + print("Normal sinkhorn, time: {:6.2f} sec ".format(time2 - time1)) + describeRes(G1) + print(" GPU sinkhorn, time: {:6.2f} sec ".format(time3 - time2)) + describeRes(G2) + + +@pytest.mark.skip(reason="No way to test GPU on travis yet") +def test_gpu_sinkhorn_lpl1(): + def describeRes(r): + print("min:{:.3E}, max:{:.3E}, mean:{:.3E}, std:{:.3E}" + .format(np.min(r), np.max(r), np.mean(r), np.std(r))) + + for n in [5000]: + print(n) + a = np.random.rand(n // 4, 100) + labels_a = np.random.randint(10, size=(n // 4)) + b = np.random.rand(n, 100) + time1 = time.time() + transport = ot.da.OTDA_lpl1() + transport.fit(a, labels_a, b) + G1 = transport.G + time2 = time.time() + transport = ot.gpu.da.OTDA_lpl1() + transport.fit(a, labels_a, b) + G2 = transport.G + time3 = time.time() + print("Normal sinkhorn lpl1, time: {:6.2f} sec ".format( + time2 - time1)) + describeRes(G1) + print(" GPU sinkhorn lpl1, time: {:6.2f} sec ".format( + time3 - time2)) + describeRes(G2) diff --git a/test/test_gpu_sinkhorn.py b/test/test_gpu_sinkhorn.py deleted file mode 100644 index 841f062..0000000 --- a/test/test_gpu_sinkhorn.py +++ /dev/null @@ -1,28 +0,0 @@ -import ot -import numpy as np -import time -import ot.gpu - - -def describeRes(r): - print("min:{:.3E}, max::{:.3E}, mean::{:.3E}, std::{:.3E}".format( - np.min(r), np.max(r), np.mean(r), np.std(r))) - - -for n in [5000, 10000, 15000, 20000]: - print(n) - a = np.random.rand(n // 4, 100) - b = np.random.rand(n, 100) - time1 = time.time() - transport = ot.da.OTDA_sinkhorn() - transport.fit(a, b) - G1 = transport.G - time2 = time.time() - transport = ot.gpu.da.OTDA_sinkhorn() - transport.fit(a, b) - G2 = transport.G - time3 = time.time() - print("Normal sinkhorn, time: {:6.2f} sec ".format(time2 - time1)) - describeRes(G1) - print(" GPU sinkhorn, time: {:6.2f} sec ".format(time3 - time2)) - describeRes(G2) diff --git a/test/test_gpu_sinkhorn_lpl1.py b/test/test_gpu_sinkhorn_lpl1.py deleted file mode 100644 index f0eb7e6..0000000 --- a/test/test_gpu_sinkhorn_lpl1.py +++ /dev/null @@ -1,29 +0,0 @@ -import ot -import numpy as np -import time -import ot.gpu - - -def describeRes(r): - print("min:{:.3E}, max:{:.3E}, mean:{:.3E}, std:{:.3E}" - .format(np.min(r), np.max(r), np.mean(r), np.std(r))) - - -for n in [5000, 10000, 15000, 20000]: - print(n) - a = np.random.rand(n // 4, 100) - labels_a = np.random.randint(10, size=(n // 4)) - b = np.random.rand(n, 100) - time1 = time.time() - transport = ot.da.OTDA_lpl1() - transport.fit(a, labels_a, b) - G1 = transport.G - time2 = time.time() - transport = ot.gpu.da.OTDA_lpl1() - transport.fit(a, labels_a, b) - G2 = transport.G - time3 = time.time() - print("Normal sinkhorn lpl1, time: {:6.2f} sec ".format(time2 - time1)) - describeRes(G1) - print(" GPU sinkhorn lpl1, time: {:6.2f} sec ".format(time3 - time2)) - describeRes(G2) diff --git a/test/test_load_module.py b/test/test_load_module.py deleted file mode 100644 index d77261e..0000000 --- a/test/test_load_module.py +++ /dev/null @@ -1,10 +0,0 @@ - - -import ot -import doctest - -# test lp solver -doctest.testmod(ot.lp, verbose=True) - -# test bregman solver -doctest.testmod(ot.bregman, verbose=True) diff --git a/test/test_ot.py b/test/test_ot.py new file mode 100644 index 0000000..51ee510 --- /dev/null +++ b/test/test_ot.py @@ -0,0 +1,55 @@ + + +import ot +import numpy as np + +#import pytest + + +def test_doctest(): + + import doctest + + # test lp solver + doctest.testmod(ot.lp, verbose=True) + + # test bregman solver + doctest.testmod(ot.bregman, verbose=True) + + +#@pytest.mark.skip(reason="Seems to be a conflict between pytest and multiprocessing") +def test_emd_multi(): + + from ot.datasets import get_1D_gauss as gauss + + n = 1000 # nb bins + + # bin positions + x = np.arange(n, dtype=np.float64) + + # Gaussian distributions + a = gauss(n, m=20, s=5) # m= mean, s= std + + ls = np.arange(20, 1000, 10) + nb = len(ls) + b = np.zeros((n, nb)) + for i in range(nb): + b[:, i] = gauss(n, m=ls[i], s=10) + + # loss matrix + M = ot.dist(x.reshape((n, 1)), x.reshape((n, 1))) + # M/=M.max() + + print('Computing {} EMD '.format(nb)) + + # emd loss 1 proc + ot.tic() + emd1 = ot.emd2(a, b, M, 1) + ot.toc('1 proc : {} s') + + # emd loss multipro proc + ot.tic() + emdn = ot.emd2(a, b, M) + ot.toc('multi proc : {} s') + + assert np.allclose(emd1, emdn) -- cgit v1.2.3 From beed8f49ee8d0bf828fc0b63f23254561d7566b0 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Mon, 24 Jul 2017 11:21:00 +0200 Subject: update test --- .travis.yml | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1c3a18c..c5ca62b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,5 +21,5 @@ install: # command to run tests + check syntax style script: - flake8 examples/ ot/ test/ - - python -m py.test -v + - python -m py.test -v test/ # - py.test ot test diff --git a/Makefile b/Makefile index ff03a63..cabe6a9 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ pep8 : flake8 examples/ ot/ test/ test : FORCE pep8 - python -m py.test -v + python -m py.test -v test/ uploadpypi : #python setup.py register -- cgit v1.2.3 From 01f8c44d3e6dbe129b4b621af370bb6f015ab2b2 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Mon, 24 Jul 2017 11:29:25 +0200 Subject: try debug travis --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c5ca62b..dc415a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,9 +17,10 @@ before_install: install: - pip install -r requirements.txt - pip install flake8 pytest - - python setup.py install + - pip install . # command to run tests + check syntax style script: + - python setup.py develop - flake8 examples/ ot/ test/ - python -m py.test -v test/ # - py.test ot test -- cgit v1.2.3 From ff104a6dde2d652283f72d7901bbe79dfb8571ed Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Mon, 24 Jul 2017 11:54:59 +0200 Subject: add test for emd and emd2 --- test/test_ot.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/test/test_ot.py b/test/test_ot.py index 51ee510..6976818 100644 --- a/test/test_ot.py +++ b/test/test_ot.py @@ -3,7 +3,7 @@ import ot import numpy as np -#import pytest +# import pytest def test_doctest(): @@ -17,8 +17,28 @@ def test_doctest(): doctest.testmod(ot.bregman, verbose=True) +def test_emd_emd2(): + # test emd + n = 100 + + x = np.random.randn(n, 2) + u = ot.utils.unif(n) + + M = ot.dist(x, x) + + G = ot.emd(u, u, M) + + # check G is identity + assert np.allclose(G, np.eye(n) / n) + + w = ot.emd2(u, u, M) + + # check loss=0 + assert np.allclose(w, 0) + + #@pytest.mark.skip(reason="Seems to be a conflict between pytest and multiprocessing") -def test_emd_multi(): +def test_emd2_multi(): from ot.datasets import get_1D_gauss as gauss -- cgit v1.2.3 From 75492827c89a47cbc6807d4859be178d255c49bc Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Mon, 24 Jul 2017 12:09:15 +0200 Subject: add test sinkhorn --- Makefile | 3 +++ ot/gpu/bregman.py | 2 +- test/test_ot.py | 46 +++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index cabe6a9..577bbbe 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,9 @@ pep8 : test : FORCE pep8 python -m py.test -v test/ + +pytest : FORCE + python -m py.test -v test/ uploadpypi : #python setup.py register diff --git a/ot/gpu/bregman.py b/ot/gpu/bregman.py index 7881c65..2302f80 100644 --- a/ot/gpu/bregman.py +++ b/ot/gpu/bregman.py @@ -9,7 +9,7 @@ import cudamat def sinkhorn(a, b, M_GPU, reg, numItermax=1000, stopThr=1e-9, verbose=False, log=False, returnAsGPU=False): - """ + r""" Solve the entropic regularization optimal transport problem on GPU The function solves the following optimization problem: diff --git a/test/test_ot.py b/test/test_ot.py index 6976818..b69d080 100644 --- a/test/test_ot.py +++ b/test/test_ot.py @@ -18,8 +18,9 @@ def test_doctest(): def test_emd_emd2(): - # test emd + # test emd and emd2 for simple identity n = 100 + np.random.seed(0) x = np.random.randn(n, 2) u = ot.utils.unif(n) @@ -35,14 +36,13 @@ def test_emd_emd2(): # check loss=0 assert np.allclose(w, 0) - - -#@pytest.mark.skip(reason="Seems to be a conflict between pytest and multiprocessing") + def test_emd2_multi(): from ot.datasets import get_1D_gauss as gauss n = 1000 # nb bins + np.random.seed(0) # bin positions x = np.arange(n, dtype=np.float64) @@ -72,4 +72,40 @@ def test_emd2_multi(): emdn = ot.emd2(a, b, M) ot.toc('multi proc : {} s') - assert np.allclose(emd1, emdn) + assert np.allclose(emd1, emdn) + + +def test_sinkhorn(): + # test sinkhorn + n = 100 + np.random.seed(0) + + x = np.random.randn(n, 2) + u = ot.utils.unif(n) + + M = ot.dist(x, x) + + G = ot.sinkhorn(u, u, M,1,stopThr=1e-10) + + # check constratints + assert np.allclose(u, G.sum(1), atol=1e-05) # cf convergence sinkhorn + assert np.allclose(u, G.sum(0), atol=1e-05) # cf convergence sinkhorn + +def test_sinkhorn_variants(): + # test sinkhorn + n = 100 + np.random.seed(0) + + x = np.random.randn(n, 2) + u = ot.utils.unif(n) + + M = ot.dist(x, x) + + G0 = ot.sinkhorn(u, u, M,1, method='sinkhorn',stopThr=1e-10) + Gs = ot.sinkhorn(u, u, M,1, method='sinkhorn_stabilized',stopThr=1e-10) + Ges = ot.sinkhorn(u, u, M,1, method='sinkhorn_epsilon_scaling',stopThr=1e-10) + + # check constratints + assert np.allclose(G0, Gs, atol=1e-05) + assert np.allclose(G0, Ges, atol=1e-05) # + -- cgit v1.2.3 From a8a0995edefd437f56b91b95c2628fb031428a08 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Mon, 24 Jul 2017 12:11:34 +0200 Subject: pep8 tests --- test/test_bregman.py | 43 +++++++++++++++++++++++++++++++++++++++++++ test/test_ot.py | 34 ++++++++++++++++++---------------- 2 files changed, 61 insertions(+), 16 deletions(-) create mode 100644 test/test_bregman.py diff --git a/test/test_bregman.py b/test/test_bregman.py new file mode 100644 index 0000000..fd2c972 --- /dev/null +++ b/test/test_bregman.py @@ -0,0 +1,43 @@ + + +import ot +import numpy as np + +# import pytest + + +def test_sinkhorn(): + # test sinkhorn + n = 100 + np.random.seed(0) + + x = np.random.randn(n, 2) + u = ot.utils.unif(n) + + M = ot.dist(x, x) + + G = ot.sinkhorn(u, u, M, 1, stopThr=1e-10) + + # check constratints + assert np.allclose(u, G.sum(1), atol=1e-05) # cf convergence sinkhorn + assert np.allclose(u, G.sum(0), atol=1e-05) # cf convergence sinkhorn + + +def test_sinkhorn_variants(): + # test sinkhorn + n = 100 + np.random.seed(0) + + x = np.random.randn(n, 2) + u = ot.utils.unif(n) + + M = ot.dist(x, x) + + G0 = ot.sinkhorn(u, u, M, 1, method='sinkhorn', stopThr=1e-10) + Gs = ot.sinkhorn(u, u, M, 1, method='sinkhorn_stabilized', stopThr=1e-10) + Ges = ot.sinkhorn( + u, u, M, 1, method='sinkhorn_epsilon_scaling', stopThr=1e-10) + + # check constratints + assert np.allclose(G0, Gs, atol=1e-05) + assert np.allclose(G0, Ges, atol=1e-05) diff --git a/test/test_ot.py b/test/test_ot.py index b69d080..9103ac8 100644 --- a/test/test_ot.py +++ b/test/test_ot.py @@ -36,7 +36,8 @@ def test_emd_emd2(): # check loss=0 assert np.allclose(w, 0) - + + def test_emd2_multi(): from ot.datasets import get_1D_gauss as gauss @@ -72,11 +73,11 @@ def test_emd2_multi(): emdn = ot.emd2(a, b, M) ot.toc('multi proc : {} s') - assert np.allclose(emd1, emdn) - - + assert np.allclose(emd1, emdn) + + def test_sinkhorn(): - # test sinkhorn + # test sinkhorn n = 100 np.random.seed(0) @@ -85,14 +86,15 @@ def test_sinkhorn(): M = ot.dist(x, x) - G = ot.sinkhorn(u, u, M,1,stopThr=1e-10) + G = ot.sinkhorn(u, u, M, 1, stopThr=1e-10) # check constratints - assert np.allclose(u, G.sum(1), atol=1e-05) # cf convergence sinkhorn - assert np.allclose(u, G.sum(0), atol=1e-05) # cf convergence sinkhorn - + assert np.allclose(u, G.sum(1), atol=1e-05) # cf convergence sinkhorn + assert np.allclose(u, G.sum(0), atol=1e-05) # cf convergence sinkhorn + + def test_sinkhorn_variants(): - # test sinkhorn + # test sinkhorn n = 100 np.random.seed(0) @@ -101,11 +103,11 @@ def test_sinkhorn_variants(): M = ot.dist(x, x) - G0 = ot.sinkhorn(u, u, M,1, method='sinkhorn',stopThr=1e-10) - Gs = ot.sinkhorn(u, u, M,1, method='sinkhorn_stabilized',stopThr=1e-10) - Ges = ot.sinkhorn(u, u, M,1, method='sinkhorn_epsilon_scaling',stopThr=1e-10) + G0 = ot.sinkhorn(u, u, M, 1, method='sinkhorn', stopThr=1e-10) + Gs = ot.sinkhorn(u, u, M, 1, method='sinkhorn_stabilized', stopThr=1e-10) + Ges = ot.sinkhorn( + u, u, M, 1, method='sinkhorn_epsilon_scaling', stopThr=1e-10) # check constratints - assert np.allclose(G0, Gs, atol=1e-05) - assert np.allclose(G0, Ges, atol=1e-05) # - + assert np.allclose(G0, Gs, atol=1e-05) + assert np.allclose(G0, Ges, atol=1e-05) -- cgit v1.2.3 From d9205c886219d5410bc4705b46d9f14710c81ddd Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Mon, 24 Jul 2017 12:13:37 +0200 Subject: clean tests --- test/test_ot.py | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/test/test_ot.py b/test/test_ot.py index 9103ac8..3fa1bc4 100644 --- a/test/test_ot.py +++ b/test/test_ot.py @@ -74,40 +74,3 @@ def test_emd2_multi(): ot.toc('multi proc : {} s') assert np.allclose(emd1, emdn) - - -def test_sinkhorn(): - # test sinkhorn - n = 100 - np.random.seed(0) - - x = np.random.randn(n, 2) - u = ot.utils.unif(n) - - M = ot.dist(x, x) - - G = ot.sinkhorn(u, u, M, 1, stopThr=1e-10) - - # check constratints - assert np.allclose(u, G.sum(1), atol=1e-05) # cf convergence sinkhorn - assert np.allclose(u, G.sum(0), atol=1e-05) # cf convergence sinkhorn - - -def test_sinkhorn_variants(): - # test sinkhorn - n = 100 - np.random.seed(0) - - x = np.random.randn(n, 2) - u = ot.utils.unif(n) - - M = ot.dist(x, x) - - G0 = ot.sinkhorn(u, u, M, 1, method='sinkhorn', stopThr=1e-10) - Gs = ot.sinkhorn(u, u, M, 1, method='sinkhorn_stabilized', stopThr=1e-10) - Ges = ot.sinkhorn( - u, u, M, 1, method='sinkhorn_epsilon_scaling', stopThr=1e-10) - - # check constratints - assert np.allclose(G0, Gs, atol=1e-05) - assert np.allclose(G0, Ges, atol=1e-05) -- cgit v1.2.3 From 1cf304cee298e2752ce29c83e5201f593722c3af Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Mon, 24 Jul 2017 13:40:51 +0200 Subject: add tests for utils --- test/test_utils.py | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 test/test_utils.py diff --git a/test/test_utils.py b/test/test_utils.py new file mode 100644 index 0000000..3219fce --- /dev/null +++ b/test/test_utils.py @@ -0,0 +1,76 @@ + + +import ot +import numpy as np + +# import pytest + + +def test_parmap(): + + n = 100 + + def f(i): + return 1.0 * i * i + + a = np.arange(n) + + l1 = map(f, a) + + l2 = ot.utils.parmap(f, a) + + assert np.allclose(l1, l2) + + +def test_tic_toc(): + + import time + + ot.tic() + time.sleep(0.5) + t = ot.toc() + t2 = ot.toq() + + # test timing + assert np.allclose(0.5, t, rtol=1e-2, atol=1e-2) + + # test toc vs toq + assert np.allclose(t, t2, rtol=1e-2, atol=1e-2) + + +def test_kernel(): + + n = 100 + + x = np.random.randn(n, 2) + + K = ot.utils.kernel(x, x) + + # gaussian kernel has ones on the diagonal + assert np.allclose(np.diag(K), np.ones(n)) + + +def test_unif(): + + n = 100 + + u = ot.unif(n) + + assert np.allclose(1, np.sum(u)) + + +def test_dist(): + + n = 100 + + x = np.random.randn(n, 2) + + D = np.zeros((n, n)) + for i in range(n): + for j in range(n): + D[i, j] = np.sum(np.square(x[i, :] - x[j, :])) + + D2 = ot.dist(x, x) + + # dist shoul return squared euclidean + assert np.allclose(D, D2) -- cgit v1.2.3 From b2f91f24796a996a82db41e91f56ba6a51989159 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Mon, 24 Jul 2017 14:26:25 +0200 Subject: full coveragre utils --- Makefile | 4 ++-- test/test_gpu.py | 18 ++++++++++++++---- test/test_ot.py | 4 +++- test/test_utils.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 577bbbe..98f5614 100644 --- a/Makefile +++ b/Makefile @@ -38,10 +38,10 @@ pep8 : flake8 examples/ ot/ test/ test : FORCE pep8 - python -m py.test -v test/ + python -m py.test -v test/ --cov=ot --cov-report html:cov_html pytest : FORCE - python -m py.test -v test/ + python -m py.test -v test/ --cov=ot uploadpypi : #python setup.py register diff --git a/test/test_gpu.py b/test/test_gpu.py index 312a2d4..49b98d0 100644 --- a/test/test_gpu.py +++ b/test/test_gpu.py @@ -3,8 +3,14 @@ import numpy as np import time import pytest +try: # test if cudamat installed + import ot.gpu + nogpu = False +except ImportError: + nogpu = True + -@pytest.mark.skip(reason="No way to test GPU on travis yet") +@pytest.mark.skipif(nogpu, reason="No GPU available") def test_gpu_sinkhorn(): import ot.gpu @@ -12,7 +18,7 @@ def test_gpu_sinkhorn(): print("min:{:.3E}, max::{:.3E}, mean::{:.3E}, std::{:.3E}".format( np.min(r), np.max(r), np.mean(r), np.std(r))) - for n in [5000]: + for n in [50, 100, 500, 1000]: print(n) a = np.random.rand(n // 4, 100) b = np.random.rand(n, 100) @@ -30,14 +36,16 @@ def test_gpu_sinkhorn(): print(" GPU sinkhorn, time: {:6.2f} sec ".format(time3 - time2)) describeRes(G2) + assert np.allclose(G1, G2, rtol=1e-5, atol=1e-5) -@pytest.mark.skip(reason="No way to test GPU on travis yet") + +@pytest.mark.skipif(nogpu, reason="No GPU available") def test_gpu_sinkhorn_lpl1(): def describeRes(r): print("min:{:.3E}, max:{:.3E}, mean:{:.3E}, std:{:.3E}" .format(np.min(r), np.max(r), np.mean(r), np.std(r))) - for n in [5000]: + for n in [50, 100, 500, 1000]: print(n) a = np.random.rand(n // 4, 100) labels_a = np.random.randint(10, size=(n // 4)) @@ -57,3 +65,5 @@ def test_gpu_sinkhorn_lpl1(): print(" GPU sinkhorn lpl1, time: {:6.2f} sec ".format( time3 - time2)) describeRes(G2) + + assert np.allclose(G1, G2, rtol=1e-5, atol=1e-5) diff --git a/test/test_ot.py b/test/test_ot.py index 3fa1bc4..16fd510 100644 --- a/test/test_ot.py +++ b/test/test_ot.py @@ -31,9 +31,11 @@ def test_emd_emd2(): # check G is identity assert np.allclose(G, np.eye(n) / n) + # check constratints + assert np.allclose(u, G.sum(1)) # cf convergence sinkhorn + assert np.allclose(u, G.sum(0)) # cf convergence sinkhorn w = ot.emd2(u, u, M) - # check loss=0 assert np.allclose(w, 0) diff --git a/test/test_utils.py b/test/test_utils.py index 3219fce..e85e5b7 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -71,6 +71,52 @@ def test_dist(): D[i, j] = np.sum(np.square(x[i, :] - x[j, :])) D2 = ot.dist(x, x) + D3 = ot.dist(x) # dist shoul return squared euclidean assert np.allclose(D, D2) + assert np.allclose(D, D3) + + +def test_dist0(): + + n = 100 + M = ot.utils.dist0(n, method='lin_square') + + # dist0 default to linear sampling with quadratic loss + assert np.allclose(M[0, -1], (n - 1) * (n - 1)) + + +def test_dots(): + + n1, n2, n3, n4 = 100, 50, 200, 100 + + A = np.random.randn(n1, n2) + B = np.random.randn(n2, n3) + C = np.random.randn(n3, n4) + + X1 = ot.utils.dots(A, B, C) + + X2 = A.dot(B.dot(C)) + + assert np.allclose(X1, X2) + + +def test_clean_zeros(): + + n = 100 + nz = 50 + nz2 = 20 + u1 = ot.unif(n) + u1[:nz] = 0 + u1 = u1 / u1.sum() + u2 = ot.unif(n) + u2[:nz2] = 0 + u2 = u2 / u2.sum() + + M = ot.utils.dist0(n) + + a, b, M2 = ot.utils.clean_zeros(u1, u2, M) + + assert len(a) == n - nz + assert len(b) == n - nz2 -- cgit v1.2.3 From c6e648fbebd1297428f514d7bd48d3eb8814aafd Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Mon, 24 Jul 2017 14:31:00 +0200 Subject: test parmap python 3.5 --- test/test_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_utils.py b/test/test_utils.py index e85e5b7..1a1ab02 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -15,9 +15,9 @@ def test_parmap(): a = np.arange(n) - l1 = map(f, a) + l1 = np.array(map(f, a)) - l2 = ot.utils.parmap(f, a) + l2 = np.array(ot.utils.parmap(f, a)) assert np.allclose(l1, l2) -- cgit v1.2.3 From a31d3c2375ffec7eb3754ab4b66f75ce9a51eddd Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Mon, 24 Jul 2017 14:35:20 +0200 Subject: map to list --- test/test_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_utils.py b/test/test_utils.py index 1a1ab02..0883a8e 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -15,9 +15,9 @@ def test_parmap(): a = np.arange(n) - l1 = np.array(map(f, a)) + l1 = list(map(f, a)) - l2 = np.array(ot.utils.parmap(f, a)) + l2 = list(ot.utils.parmap(f, a)) assert np.allclose(l1, l2) -- cgit v1.2.3 From f8e822c48eff02a3d65fc83d09dc0471bc9555aa Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Mon, 24 Jul 2017 14:49:14 +0200 Subject: test sinkhorn with empty marginals --- test/test_bregman.py | 31 ++++++++++++++++++++++++++++++- test/test_ot.py | 23 +++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/test/test_bregman.py b/test/test_bregman.py index fd2c972..b65de11 100644 --- a/test/test_bregman.py +++ b/test/test_bregman.py @@ -23,6 +23,33 @@ def test_sinkhorn(): assert np.allclose(u, G.sum(0), atol=1e-05) # cf convergence sinkhorn +def test_sinkhorn_empty(): + # test sinkhorn + n = 100 + np.random.seed(0) + + x = np.random.randn(n, 2) + u = ot.utils.unif(n) + + M = ot.dist(x, x) + + G = ot.sinkhorn([], [], M, 1, stopThr=1e-10) + # check constratints + assert np.allclose(u, G.sum(1), atol=1e-05) # cf convergence sinkhorn + assert np.allclose(u, G.sum(0), atol=1e-05) # cf convergence sinkhorn + + G = ot.sinkhorn([], [], M, 1, stopThr=1e-10, method='sinkhorn_stabilized') + # check constratints + assert np.allclose(u, G.sum(1), atol=1e-05) # cf convergence sinkhorn + assert np.allclose(u, G.sum(0), atol=1e-05) # cf convergence sinkhorn + + G = ot.sinkhorn( + [], [], M, 1, stopThr=1e-10, method='sinkhorn_epsilon_scaling') + # check constratints + assert np.allclose(u, G.sum(1), atol=1e-05) # cf convergence sinkhorn + assert np.allclose(u, G.sum(0), atol=1e-05) # cf convergence sinkhorn + + def test_sinkhorn_variants(): # test sinkhorn n = 100 @@ -37,7 +64,9 @@ def test_sinkhorn_variants(): Gs = ot.sinkhorn(u, u, M, 1, method='sinkhorn_stabilized', stopThr=1e-10) Ges = ot.sinkhorn( u, u, M, 1, method='sinkhorn_epsilon_scaling', stopThr=1e-10) + Gerr = ot.sinkhorn(u, u, M, 1, method='do_not_exists', stopThr=1e-10) - # check constratints + # check values assert np.allclose(G0, Gs, atol=1e-05) assert np.allclose(G0, Ges, atol=1e-05) + assert np.allclose(G0, Gerr) diff --git a/test/test_ot.py b/test/test_ot.py index 16fd510..3897397 100644 --- a/test/test_ot.py +++ b/test/test_ot.py @@ -40,6 +40,29 @@ def test_emd_emd2(): assert np.allclose(w, 0) +def test_emd_empty(): + # test emd and emd2 for simple identity + n = 100 + np.random.seed(0) + + x = np.random.randn(n, 2) + u = ot.utils.unif(n) + + M = ot.dist(x, x) + + G = ot.emd([], [], M) + + # check G is identity + assert np.allclose(G, np.eye(n) / n) + # check constratints + assert np.allclose(u, G.sum(1)) # cf convergence sinkhorn + assert np.allclose(u, G.sum(0)) # cf convergence sinkhorn + + w = ot.emd2([], [], M) + # check loss=0 + assert np.allclose(w, 0) + + def test_emd2_multi(): from ot.datasets import get_1D_gauss as gauss -- cgit v1.2.3 From 7d9c5e7ef81cfb1cd4725058c09a7f683ca03eef Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Mon, 24 Jul 2017 14:58:15 +0200 Subject: add test optim --- test/test_optim.py | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 test/test_optim.py diff --git a/test/test_optim.py b/test/test_optim.py new file mode 100644 index 0000000..43cba7d --- /dev/null +++ b/test/test_optim.py @@ -0,0 +1,65 @@ + + +import ot +import numpy as np + +# import pytest + + +def test_conditional_gradient(): + + n = 100 # nb bins + + # bin positions + x = np.arange(n, dtype=np.float64) + + # Gaussian distributions + a = ot.datasets.get_1D_gauss(n, m=20, s=5) # m= mean, s= std + b = ot.datasets.get_1D_gauss(n, m=60, s=10) + + # loss matrix + M = ot.dist(x.reshape((n, 1)), x.reshape((n, 1))) + M /= M.max() + + def f(G): + return 0.5 * np.sum(G**2) + + def df(G): + return G + + reg = 1e-1 + + G, log = ot.optim.cg(a, b, M, reg, f, df, verbose=True, log=True) + + assert np.allclose(a, G.sum(1)) + assert np.allclose(b, G.sum(0)) + + +def test_generalized_conditional_gradient(): + + n = 100 # nb bins + + # bin positions + x = np.arange(n, dtype=np.float64) + + # Gaussian distributions + a = ot.datasets.get_1D_gauss(n, m=20, s=5) # m= mean, s= std + b = ot.datasets.get_1D_gauss(n, m=60, s=10) + + # loss matrix + M = ot.dist(x.reshape((n, 1)), x.reshape((n, 1))) + M /= M.max() + + def f(G): + return 0.5 * np.sum(G**2) + + def df(G): + return G + + reg1 = 1e-3 + reg2 = 1e-1 + + G, log = ot.optim.gcg(a, b, M, reg1, reg2, f, df, verbose=True, log=True) + + assert np.allclose(a, G.sum(1), atol=1e-05) + assert np.allclose(b, G.sum(0), atol=1e-05) -- cgit v1.2.3 From 709d8cbc9f9961a5175eb64ae497b854e0b9b184 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Mon, 24 Jul 2017 15:14:59 +0200 Subject: add dr tests --- test/test_dr.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ test/test_gpu.py | 53 +++++++++++++++++++++++---------------------- test/test_optim.py | 4 ++-- 3 files changed, 93 insertions(+), 27 deletions(-) create mode 100644 test/test_dr.py diff --git a/test/test_dr.py b/test/test_dr.py new file mode 100644 index 0000000..24ccaa1 --- /dev/null +++ b/test/test_dr.py @@ -0,0 +1,63 @@ +import ot +import numpy as np +import pytest + +try: # test if cudamat installed + import ot.dr + nogo = False +except ImportError: + nogo = True + + +@pytest.mark.skipif(nogo, reason="Missing modules (autograd or pymanopt)") +def test_fda(): + + n = 100 # nb samples in source and target datasets + nz = 0.2 + np.random.seed(0) + + # generate circle dataset + t = np.random.rand(n) * 2 * np.pi + ys = np.floor((np.arange(n) * 1.0 / n * 3)) + 1 + xs = np.concatenate( + (np.cos(t).reshape((-1, 1)), np.sin(t).reshape((-1, 1))), 1) + xs = xs * ys.reshape(-1, 1) + nz * np.random.randn(n, 2) + + nbnoise = 8 + + xs = np.hstack((xs, np.random.randn(n, nbnoise))) + + p = 2 + + Pfda, projfda = ot.dr.fda(xs, ys, p) + + projfda(xs) + + assert np.allclose(np.sum(Pfda**2, 0), np.ones(p)) + + +@pytest.mark.skipif(nogo, reason="Missing modules (autograd or pymanopt)") +def test_wda(): + + n = 100 # nb samples in source and target datasets + nz = 0.2 + np.random.seed(0) + + # generate circle dataset + t = np.random.rand(n) * 2 * np.pi + ys = np.floor((np.arange(n) * 1.0 / n * 3)) + 1 + xs = np.concatenate( + (np.cos(t).reshape((-1, 1)), np.sin(t).reshape((-1, 1))), 1) + xs = xs * ys.reshape(-1, 1) + nz * np.random.randn(n, 2) + + nbnoise = 8 + + xs = np.hstack((xs, np.random.randn(n, nbnoise))) + + p = 2 + + Pwda, projwda = ot.dr.wda(xs, ys, p, maxiter=10) + + projwda(xs) + + assert np.allclose(np.sum(Pwda**2, 0), np.ones(p)) diff --git a/test/test_gpu.py b/test/test_gpu.py index 49b98d0..9cc39d7 100644 --- a/test/test_gpu.py +++ b/test/test_gpu.py @@ -12,7 +12,8 @@ except ImportError: @pytest.mark.skipif(nogpu, reason="No GPU available") def test_gpu_sinkhorn(): - import ot.gpu + + np.random.seed(0) def describeRes(r): print("min:{:.3E}, max::{:.3E}, mean::{:.3E}, std::{:.3E}".format( @@ -41,29 +42,31 @@ def test_gpu_sinkhorn(): @pytest.mark.skipif(nogpu, reason="No GPU available") def test_gpu_sinkhorn_lpl1(): - def describeRes(r): - print("min:{:.3E}, max:{:.3E}, mean:{:.3E}, std:{:.3E}" - .format(np.min(r), np.max(r), np.mean(r), np.std(r))) + np.random.seed(0) + + def describeRes(r): + print("min:{:.3E}, max:{:.3E}, mean:{:.3E}, std:{:.3E}" + .format(np.min(r), np.max(r), np.mean(r), np.std(r))) - for n in [50, 100, 500, 1000]: - print(n) - a = np.random.rand(n // 4, 100) - labels_a = np.random.randint(10, size=(n // 4)) - b = np.random.rand(n, 100) - time1 = time.time() - transport = ot.da.OTDA_lpl1() - transport.fit(a, labels_a, b) - G1 = transport.G - time2 = time.time() - transport = ot.gpu.da.OTDA_lpl1() - transport.fit(a, labels_a, b) - G2 = transport.G - time3 = time.time() - print("Normal sinkhorn lpl1, time: {:6.2f} sec ".format( - time2 - time1)) - describeRes(G1) - print(" GPU sinkhorn lpl1, time: {:6.2f} sec ".format( - time3 - time2)) - describeRes(G2) + for n in [50, 100, 500, 1000]: + print(n) + a = np.random.rand(n // 4, 100) + labels_a = np.random.randint(10, size=(n // 4)) + b = np.random.rand(n, 100) + time1 = time.time() + transport = ot.da.OTDA_lpl1() + transport.fit(a, labels_a, b) + G1 = transport.G + time2 = time.time() + transport = ot.gpu.da.OTDA_lpl1() + transport.fit(a, labels_a, b) + G2 = transport.G + time3 = time.time() + print("Normal sinkhorn lpl1, time: {:6.2f} sec ".format( + time2 - time1)) + describeRes(G1) + print(" GPU sinkhorn lpl1, time: {:6.2f} sec ".format( + time3 - time2)) + describeRes(G2) - assert np.allclose(G1, G2, rtol=1e-5, atol=1e-5) + assert np.allclose(G1, G2, rtol=1e-5, atol=1e-5) diff --git a/test/test_optim.py b/test/test_optim.py index 43cba7d..a77a37c 100644 --- a/test/test_optim.py +++ b/test/test_optim.py @@ -9,7 +9,7 @@ import numpy as np def test_conditional_gradient(): n = 100 # nb bins - + np.random.seed(0) # bin positions x = np.arange(n, dtype=np.float64) @@ -38,7 +38,7 @@ def test_conditional_gradient(): def test_generalized_conditional_gradient(): n = 100 # nb bins - + np.random.seed(0) # bin positions x = np.arange(n, dtype=np.float64) -- cgit v1.2.3 From 83ecc6df836d1a6b05bd641dfef465cc02b25b8f Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Mon, 24 Jul 2017 15:23:14 +0200 Subject: bregman coverage --- test/test_bregman.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/test_bregman.py b/test/test_bregman.py index b65de11..78666c7 100644 --- a/test/test_bregman.py +++ b/test/test_bregman.py @@ -33,18 +33,20 @@ def test_sinkhorn_empty(): M = ot.dist(x, x) - G = ot.sinkhorn([], [], M, 1, stopThr=1e-10) + G, log = ot.sinkhorn([], [], M, 1, stopThr=1e-10, verbose=True, log=True) # check constratints assert np.allclose(u, G.sum(1), atol=1e-05) # cf convergence sinkhorn assert np.allclose(u, G.sum(0), atol=1e-05) # cf convergence sinkhorn - G = ot.sinkhorn([], [], M, 1, stopThr=1e-10, method='sinkhorn_stabilized') + G, log = ot.sinkhorn([], [], M, 1, stopThr=1e-10, + method='sinkhorn_stabilized', verbose=True, log=True) # check constratints assert np.allclose(u, G.sum(1), atol=1e-05) # cf convergence sinkhorn assert np.allclose(u, G.sum(0), atol=1e-05) # cf convergence sinkhorn - G = ot.sinkhorn( - [], [], M, 1, stopThr=1e-10, method='sinkhorn_epsilon_scaling') + G, log = ot.sinkhorn( + [], [], M, 1, stopThr=1e-10, method='sinkhorn_epsilon_scaling', + verbose=True, log=True) # check constratints assert np.allclose(u, G.sum(1), atol=1e-05) # cf convergence sinkhorn assert np.allclose(u, G.sum(0), atol=1e-05) # cf convergence sinkhorn -- cgit v1.2.3 From 64cf2fc4f9a9331d510afd93e9bd3b8963ff879e Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Mon, 24 Jul 2017 15:28:43 +0200 Subject: tets barycenter --- test/test_bregman.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/test_bregman.py b/test/test_bregman.py index 78666c7..2dd3498 100644 --- a/test/test_bregman.py +++ b/test/test_bregman.py @@ -72,3 +72,32 @@ def test_sinkhorn_variants(): assert np.allclose(G0, Gs, atol=1e-05) assert np.allclose(G0, Ges, atol=1e-05) assert np.allclose(G0, Gerr) + + +def test_bary(): + + n = 100 # nb bins + + # bin positions + x = np.arange(n, dtype=np.float64) + + # Gaussian distributions + a1 = ot.datasets.get_1D_gauss(n, m=30, s=10) # m= mean, s= std + a2 = ot.datasets.get_1D_gauss(n, m=40, s=10) + + # creating matrix A containing all distributions + A = np.vstack((a1, a2)).T + n_distributions = A.shape[1] + + # loss matrix + normalization + M = ot.utils.dist0(n) + M /= M.max() + + alpha = 0.5 # 0<=alpha<=1 + weights = np.array([1 - alpha, alpha]) + + # wasserstein + reg = 1e-3 + bary_wass = ot.bregman.barycenter(A, M, reg, weights) + + assert np.allclose(1, np.sum(bary_wass)) -- cgit v1.2.3 From 33f3d309209baa8c5e127d02f00aae0660ed7bfb Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Mon, 24 Jul 2017 15:29:48 +0200 Subject: clean pep8 --- test/test_bregman.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/test_bregman.py b/test/test_bregman.py index 2dd3498..b204fe4 100644 --- a/test/test_bregman.py +++ b/test/test_bregman.py @@ -78,16 +78,12 @@ def test_bary(): n = 100 # nb bins - # bin positions - x = np.arange(n, dtype=np.float64) - # Gaussian distributions a1 = ot.datasets.get_1D_gauss(n, m=30, s=10) # m= mean, s= std a2 = ot.datasets.get_1D_gauss(n, m=40, s=10) # creating matrix A containing all distributions A = np.vstack((a1, a2)).T - n_distributions = A.shape[1] # loss matrix + normalization M = ot.utils.dist0(n) -- cgit v1.2.3 From bd705ed847dd7e43082e9d2771a59e539d6b7440 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Mon, 24 Jul 2017 15:45:09 +0200 Subject: add test yunmlix and bary --- test/test_bregman.py | 34 ++++++++++++++++++++++++++++++++++ test/test_gpu.py | 2 +- test/test_ot.py | 2 +- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/test/test_bregman.py b/test/test_bregman.py index b204fe4..025568c 100644 --- a/test/test_bregman.py +++ b/test/test_bregman.py @@ -97,3 +97,37 @@ def test_bary(): bary_wass = ot.bregman.barycenter(A, M, reg, weights) assert np.allclose(1, np.sum(bary_wass)) + + ot.bregman.barycenter(A, M, reg, log=True, verbose=True) + + +def test_unmix(): + + n = 50 # nb bins + + # Gaussian distributions + a1 = ot.datasets.get_1D_gauss(n, m=20, s=10) # m= mean, s= std + a2 = ot.datasets.get_1D_gauss(n, m=40, s=10) + + a = ot.datasets.get_1D_gauss(n, m=30, s=10) + + # creating matrix A containing all distributions + D = np.vstack((a1, a2)).T + + # loss matrix + normalization + M = ot.utils.dist0(n) + M /= M.max() + + M0 = ot.utils.dist0(2) + M0 /= M0.max() + h0 = ot.unif(2) + + # wasserstein + reg = 1e-3 + um = ot.bregman.unmix(a, D, M, M0, h0, reg, 1, alpha=0.01,) + + assert np.allclose(1, np.sum(um), rtol=1e-03, atol=1e-03) + assert np.allclose([0.5, 0.5], um, rtol=1e-03, atol=1e-03) + + ot.bregman.unmix(a, D, M, M0, h0, reg, + 1, alpha=0.01, log=True, verbose=True) diff --git a/test/test_gpu.py b/test/test_gpu.py index 9cc39d7..24797f2 100644 --- a/test/test_gpu.py +++ b/test/test_gpu.py @@ -48,7 +48,7 @@ def test_gpu_sinkhorn_lpl1(): print("min:{:.3E}, max:{:.3E}, mean:{:.3E}, std:{:.3E}" .format(np.min(r), np.max(r), np.mean(r), np.std(r))) - for n in [50, 100, 500, 1000]: + for n in [50, 100, 500]: print(n) a = np.random.rand(n // 4, 100) labels_a = np.random.randint(10, size=(n // 4)) diff --git a/test/test_ot.py b/test/test_ot.py index 3897397..5bf65c6 100644 --- a/test/test_ot.py +++ b/test/test_ot.py @@ -76,7 +76,7 @@ def test_emd2_multi(): # Gaussian distributions a = gauss(n, m=20, s=5) # m= mean, s= std - ls = np.arange(20, 1000, 10) + ls = np.arange(20, 1000, 20) nb = len(ls) b = np.zeros((n, nb)) for i in range(nb): -- cgit v1.2.3 From f204e983d969ed38c46d0bc85d0868a84c585db0 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Mon, 24 Jul 2017 16:02:15 +0200 Subject: add test da 58% coverage --- test/test_da.py | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 test/test_da.py diff --git a/test/test_da.py b/test/test_da.py new file mode 100644 index 0000000..50d3aba --- /dev/null +++ b/test/test_da.py @@ -0,0 +1,67 @@ + + +import ot +import numpy as np + +# import pytest + + +def test_OTDA(): + + n = 150 # nb bins + + xs, ys = ot.datasets.get_data_classif('3gauss', n) + xt, yt = ot.datasets.get_data_classif('3gauss2', n) + + a, b = ot.unif(n), ot.unif(n) + + # LP problem + da_emd = ot.da.OTDA() # init class + da_emd.fit(xs, xt) # fit distributions + da_emd.interp() # interpolation of source samples + da_emd.predict(xs) # interpolation of source samples + + assert np.allclose(a, np.sum(da_emd.G, 1)) + assert np.allclose(b, np.sum(da_emd.G, 0)) + + # sinkhorn regularization + lambd = 1e-1 + da_entrop = ot.da.OTDA_sinkhorn() + da_entrop.fit(xs, xt, reg=lambd) + da_entrop.interp() + da_entrop.predict(xs) + + assert np.allclose(a, np.sum(da_entrop.G, 1), rtol=1e-3, atol=1e-3) + assert np.allclose(b, np.sum(da_entrop.G, 0), rtol=1e-3, atol=1e-3) + + # non-convex Group lasso regularization + reg = 1e-1 + eta = 1e0 + da_lpl1 = ot.da.OTDA_lpl1() + da_lpl1.fit(xs, ys, xt, reg=reg, eta=eta) + da_lpl1.interp() + da_lpl1.predict(xs) + + assert np.allclose(a, np.sum(da_lpl1.G, 1), rtol=1e-3, atol=1e-3) + assert np.allclose(b, np.sum(da_lpl1.G, 0), rtol=1e-3, atol=1e-3) + + # True Group lasso regularization + reg = 1e-1 + eta = 2e0 + da_l1l2 = ot.da.OTDA_l1l2() + da_l1l2.fit(xs, ys, xt, reg=reg, eta=eta, numItermax=20, verbose=True) + da_l1l2.interp() + da_l1l2.predict(xs) + + assert np.allclose(a, np.sum(da_l1l2.G, 1), rtol=1e-3, atol=1e-3) + assert np.allclose(b, np.sum(da_l1l2.G, 0), rtol=1e-3, atol=1e-3) + + # linear mapping + da_emd = ot.da.OTDA_mapping_linear() # init class + da_emd.fit(xs, xt, numItermax=10) # fit distributions + da_emd.predict(xs) # interpolation of source samples + + # nonlinear mapping + 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 -- cgit v1.2.3 From 5aad08aff3e1a171ef9263af4488d175139085a0 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Mon, 24 Jul 2017 16:30:57 +0200 Subject: add test plot --- test/test_plot.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 test/test_plot.py diff --git a/test/test_plot.py b/test/test_plot.py new file mode 100644 index 0000000..8916a85 --- /dev/null +++ b/test/test_plot.py @@ -0,0 +1,42 @@ + + +import ot +import numpy as np + +# import pytest + + +def test_plot1D_mat(): + + n = 100 # nb bins + + # bin positions + x = np.arange(n, dtype=np.float64) + + # Gaussian distributions + a = ot.datasets.get_1D_gauss(n, m=20, s=5) # m= mean, s= std + b = ot.datasets.get_1D_gauss(n, m=60, s=10) + + # loss matrix + M = ot.dist(x.reshape((n, 1)), x.reshape((n, 1))) + M /= M.max() + + ot.plot.plot1D_mat(a, b, M, 'Cost matrix M') + + +def test_plot2D_samples_mat(): + + n = 50 # nb samples + + mu_s = np.array([0, 0]) + cov_s = np.array([[1, 0], [0, 1]]) + + mu_t = np.array([4, 4]) + cov_t = np.array([[1, -.8], [-.8, 1]]) + + xs = ot.datasets.get_2D_samples_gauss(n, mu_s, cov_s) + xt = ot.datasets.get_2D_samples_gauss(n, mu_t, cov_t) + + G = 1.0 * (np.random.rand(n, n) < 0.01) + + ot.plot.plot2D_samples_mat(xs, xt, G, thr=1e-5) -- cgit v1.2.3 From a8d7301c132a225b5e4d78cae64683a5e08eae7f Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Mon, 24 Jul 2017 16:39:19 +0200 Subject: add test plot and dataset --- test/test_dr.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/test/test_dr.py b/test/test_dr.py index 24ccaa1..3da7705 100644 --- a/test/test_dr.py +++ b/test/test_dr.py @@ -12,22 +12,17 @@ except ImportError: @pytest.mark.skipif(nogo, reason="Missing modules (autograd or pymanopt)") def test_fda(): - n = 100 # nb samples in source and target datasets - nz = 0.2 + n = 90 # nb samples in source and target datasets np.random.seed(0) # generate circle dataset - t = np.random.rand(n) * 2 * np.pi - ys = np.floor((np.arange(n) * 1.0 / n * 3)) + 1 - xs = np.concatenate( - (np.cos(t).reshape((-1, 1)), np.sin(t).reshape((-1, 1))), 1) - xs = xs * ys.reshape(-1, 1) + nz * np.random.randn(n, 2) + xs, ys = ot.datasets.get_data_classif('gaussrot', n) nbnoise = 8 xs = np.hstack((xs, np.random.randn(n, nbnoise))) - p = 2 + p = 1 Pfda, projfda = ot.dr.fda(xs, ys, p) -- cgit v1.2.3 From e11b1d1a77f201896fd3f70bc5b910e99610e951 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Mon, 24 Jul 2017 17:08:25 +0200 Subject: test plot with no X --- test/test_plot.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/test_plot.py b/test/test_plot.py index 8916a85..69789fa 100644 --- a/test/test_plot.py +++ b/test/test_plot.py @@ -1,13 +1,14 @@ -import ot import numpy as np - -# import pytest +import matplotlib +matplotlib.use('Agg') def test_plot1D_mat(): + import ot + n = 100 # nb bins # bin positions @@ -26,6 +27,8 @@ def test_plot1D_mat(): def test_plot2D_samples_mat(): + import ot + n = 50 # nb samples mu_s = np.array([0, 0]) -- cgit v1.2.3 From 11f065237982516c08f91e7757dd7a81c9c525d6 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Mon, 24 Jul 2017 18:26:48 +0200 Subject: matplotlib travis --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index dc415a9..ec2b3d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,10 @@ matrix: python: 2.7 before_install: - ./.travis/before_install.sh +before_script: # configure a headless display to test plot generation + - "export DISPLAY=:99.0" + - "sh -e /etc/init.d/xvfb start" + - sleep 3 # give xvfb some time to start # command to install dependencies install: - pip install -r requirements.txt -- cgit v1.2.3 From 46f297f678de0051dc6d5067291d1e1046b4705e Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Wed, 26 Jul 2017 11:26:08 +0200 Subject: import nmpy before ot --- test/test_bregman.py | 4 ++-- test/test_da.py | 4 ++-- test/test_dr.py | 5 +++-- test/test_gpu.py | 3 ++- test/test_optim.py | 4 ++-- test/test_ot.py | 4 ++-- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/test/test_bregman.py b/test/test_bregman.py index 025568c..aaa2efc 100644 --- a/test/test_bregman.py +++ b/test/test_bregman.py @@ -1,7 +1,7 @@ - -import ot import numpy as np +import ot + # import pytest diff --git a/test/test_da.py b/test/test_da.py index 50d3aba..0d92b95 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -1,7 +1,7 @@ - -import ot import numpy as np +import ot + # import pytest diff --git a/test/test_dr.py b/test/test_dr.py index 3da7705..3faba48 100644 --- a/test/test_dr.py +++ b/test/test_dr.py @@ -1,8 +1,9 @@ -import ot + import numpy as np +import ot import pytest -try: # test if cudamat installed +try: # test if autograd and pymanopt are installed import ot.dr nogo = False except ImportError: diff --git a/test/test_gpu.py b/test/test_gpu.py index 24797f2..5184a6c 100644 --- a/test/test_gpu.py +++ b/test/test_gpu.py @@ -1,5 +1,6 @@ -import ot + import numpy as np +import ot import time import pytest diff --git a/test/test_optim.py b/test/test_optim.py index a77a37c..d5c4ad0 100644 --- a/test/test_optim.py +++ b/test/test_optim.py @@ -1,7 +1,7 @@ - -import ot import numpy as np +import ot + # import pytest diff --git a/test/test_ot.py b/test/test_ot.py index 5bf65c6..a30491d 100644 --- a/test/test_ot.py +++ b/test/test_ot.py @@ -1,7 +1,7 @@ - -import ot import numpy as np +import ot + # import pytest -- cgit v1.2.3 From 68d74902bcd3d988fff8cb7713314063f04c0089 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Wed, 26 Jul 2017 11:34:11 +0200 Subject: numpy assert + n_bins --- test/test_bregman.py | 63 ++++++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/test/test_bregman.py b/test/test_bregman.py index aaa2efc..1638ef6 100644 --- a/test/test_bregman.py +++ b/test/test_bregman.py @@ -3,15 +3,12 @@ import numpy as np import ot -# import pytest - - def test_sinkhorn(): # test sinkhorn n = 100 - np.random.seed(0) + rng = np.random.RandomState(0) - x = np.random.randn(n, 2) + x = rng.randn(n, 2) u = ot.utils.unif(n) M = ot.dist(x, x) @@ -19,45 +16,47 @@ def test_sinkhorn(): G = ot.sinkhorn(u, u, M, 1, stopThr=1e-10) # check constratints - assert np.allclose(u, G.sum(1), atol=1e-05) # cf convergence sinkhorn - assert np.allclose(u, G.sum(0), atol=1e-05) # cf convergence sinkhorn + np.testing.assert_allclose( + u, G.sum(1), atol=1e-05) # cf convergence sinkhorn + np.testing.assert_allclose( + u, G.sum(0), atol=1e-05) # cf convergence sinkhorn def test_sinkhorn_empty(): # test sinkhorn n = 100 - np.random.seed(0) + rng = np.random.RandomState(0) - x = np.random.randn(n, 2) + x = rng.randn(n, 2) u = ot.utils.unif(n) M = ot.dist(x, x) G, log = ot.sinkhorn([], [], M, 1, stopThr=1e-10, verbose=True, log=True) # check constratints - assert np.allclose(u, G.sum(1), atol=1e-05) # cf convergence sinkhorn - assert np.allclose(u, G.sum(0), atol=1e-05) # cf convergence sinkhorn + np.testing.assert_allclose(u, G.sum(1), atol=1e-05) + np.testing.assert_allclose(u, G.sum(0), atol=1e-05) G, log = ot.sinkhorn([], [], M, 1, stopThr=1e-10, method='sinkhorn_stabilized', verbose=True, log=True) # check constratints - assert np.allclose(u, G.sum(1), atol=1e-05) # cf convergence sinkhorn - assert np.allclose(u, G.sum(0), atol=1e-05) # cf convergence sinkhorn + np.testing.assert_allclose(u, G.sum(1), atol=1e-05) + np.testing.assert_allclose(u, G.sum(0), atol=1e-05) G, log = ot.sinkhorn( [], [], M, 1, stopThr=1e-10, method='sinkhorn_epsilon_scaling', verbose=True, log=True) # check constratints - assert np.allclose(u, G.sum(1), atol=1e-05) # cf convergence sinkhorn - assert np.allclose(u, G.sum(0), atol=1e-05) # cf convergence sinkhorn + np.testing.assert_allclose(u, G.sum(1), atol=1e-05) + np.testing.assert_allclose(u, G.sum(0), atol=1e-05) def test_sinkhorn_variants(): # test sinkhorn n = 100 - np.random.seed(0) + rng = np.random.RandomState(0) - x = np.random.randn(n, 2) + x = rng.randn(n, 2) u = ot.utils.unif(n) M = ot.dist(x, x) @@ -69,24 +68,24 @@ def test_sinkhorn_variants(): Gerr = ot.sinkhorn(u, u, M, 1, method='do_not_exists', stopThr=1e-10) # check values - assert np.allclose(G0, Gs, atol=1e-05) - assert np.allclose(G0, Ges, atol=1e-05) - assert np.allclose(G0, Gerr) + np.testing.assert_allclose(G0, Gs, atol=1e-05) + np.testing.assert_allclose(G0, Ges, atol=1e-05) + np.testing.assert_allclose(G0, Gerr) def test_bary(): - n = 100 # nb bins + n_bins = 100 # nb bins # Gaussian distributions - a1 = ot.datasets.get_1D_gauss(n, m=30, s=10) # m= mean, s= std - a2 = ot.datasets.get_1D_gauss(n, m=40, s=10) + a1 = ot.datasets.get_1D_gauss(n_bins, m=30, s=10) # m= mean, s= std + a2 = ot.datasets.get_1D_gauss(n_bins, m=40, s=10) # creating matrix A containing all distributions A = np.vstack((a1, a2)).T # loss matrix + normalization - M = ot.utils.dist0(n) + M = ot.utils.dist0(n_bins) M /= M.max() alpha = 0.5 # 0<=alpha<=1 @@ -96,26 +95,26 @@ def test_bary(): reg = 1e-3 bary_wass = ot.bregman.barycenter(A, M, reg, weights) - assert np.allclose(1, np.sum(bary_wass)) + np.testing.assert_allclose(1, np.sum(bary_wass)) ot.bregman.barycenter(A, M, reg, log=True, verbose=True) def test_unmix(): - n = 50 # nb bins + n_bins = 50 # nb bins # Gaussian distributions - a1 = ot.datasets.get_1D_gauss(n, m=20, s=10) # m= mean, s= std - a2 = ot.datasets.get_1D_gauss(n, m=40, s=10) + a1 = ot.datasets.get_1D_gauss(n_bins, m=20, s=10) # m= mean, s= std + a2 = ot.datasets.get_1D_gauss(n_bins, m=40, s=10) - a = ot.datasets.get_1D_gauss(n, m=30, s=10) + a = ot.datasets.get_1D_gauss(n_bins, m=30, s=10) # creating matrix A containing all distributions D = np.vstack((a1, a2)).T # loss matrix + normalization - M = ot.utils.dist0(n) + M = ot.utils.dist0(n_bins) M /= M.max() M0 = ot.utils.dist0(2) @@ -126,8 +125,8 @@ def test_unmix(): reg = 1e-3 um = ot.bregman.unmix(a, D, M, M0, h0, reg, 1, alpha=0.01,) - assert np.allclose(1, np.sum(um), rtol=1e-03, atol=1e-03) - assert np.allclose([0.5, 0.5], um, rtol=1e-03, atol=1e-03) + np.testing.assert_allclose(1, np.sum(um), rtol=1e-03, atol=1e-03) + np.testing.assert_allclose([0.5, 0.5], um, rtol=1e-03, atol=1e-03) ot.bregman.unmix(a, D, M, M0, h0, reg, 1, alpha=0.01, log=True, verbose=True) -- cgit v1.2.3 From 67b011a2a6a0cb8dffbb7a2619875f0e0d79588c Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Wed, 26 Jul 2017 11:38:17 +0200 Subject: numpy assert test_da --- test/test_da.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/test/test_da.py b/test/test_da.py index 0d92b95..8df4795 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -6,9 +6,10 @@ import ot # import pytest -def test_OTDA(): +def test_otda(): - n = 150 # nb bins + n = 150 # nb samples + np.random.seed(0) xs, ys = ot.datasets.get_data_classif('3gauss', n) xt, yt = ot.datasets.get_data_classif('3gauss2', n) @@ -21,8 +22,8 @@ def test_OTDA(): da_emd.interp() # interpolation of source samples da_emd.predict(xs) # interpolation of source samples - assert np.allclose(a, np.sum(da_emd.G, 1)) - assert np.allclose(b, np.sum(da_emd.G, 0)) + np.testing.assert_allclose(a, np.sum(da_emd.G, 1)) + np.testing.assert_allclose(b, np.sum(da_emd.G, 0)) # sinkhorn regularization lambd = 1e-1 @@ -31,8 +32,8 @@ def test_OTDA(): da_entrop.interp() da_entrop.predict(xs) - assert np.allclose(a, np.sum(da_entrop.G, 1), rtol=1e-3, atol=1e-3) - assert np.allclose(b, np.sum(da_entrop.G, 0), 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 reg = 1e-1 @@ -42,8 +43,8 @@ def test_OTDA(): da_lpl1.interp() da_lpl1.predict(xs) - assert np.allclose(a, np.sum(da_lpl1.G, 1), rtol=1e-3, atol=1e-3) - assert np.allclose(b, np.sum(da_lpl1.G, 0), rtol=1e-3, atol=1e-3) + np.testing.assert_allclose(a, np.sum(da_lpl1.G, 1), rtol=1e-3, atol=1e-3) + np.testing.assert_allclose(b, np.sum(da_lpl1.G, 0), rtol=1e-3, atol=1e-3) # True Group lasso regularization reg = 1e-1 @@ -53,8 +54,8 @@ def test_OTDA(): da_l1l2.interp() da_l1l2.predict(xs) - assert np.allclose(a, np.sum(da_l1l2.G, 1), rtol=1e-3, atol=1e-3) - assert np.allclose(b, np.sum(da_l1l2.G, 0), rtol=1e-3, atol=1e-3) + np.testing.assert_allclose(a, np.sum(da_l1l2.G, 1), rtol=1e-3, atol=1e-3) + np.testing.assert_allclose(b, np.sum(da_l1l2.G, 0), rtol=1e-3, atol=1e-3) # linear mapping da_emd = ot.da.OTDA_mapping_linear() # init class -- cgit v1.2.3 From 347e6288b87cbeef9b8fbc1a08cd130b96de1d61 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Wed, 26 Jul 2017 11:42:35 +0200 Subject: n to n_samples --- test/test_da.py | 11 ++++------- test/test_dr.py | 25 ++++++++++--------------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/test/test_da.py b/test/test_da.py index 8df4795..a38390f 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -3,18 +3,15 @@ import numpy as np import ot -# import pytest - - def test_otda(): - n = 150 # nb samples + n_samples = 150 # nb samples np.random.seed(0) - xs, ys = ot.datasets.get_data_classif('3gauss', n) - xt, yt = ot.datasets.get_data_classif('3gauss2', n) + xs, ys = ot.datasets.get_data_classif('3gauss', n_samples) + xt, yt = ot.datasets.get_data_classif('3gauss2', n_samples) - a, b = ot.unif(n), ot.unif(n) + a, b = ot.unif(n_samples), ot.unif(n_samples) # LP problem da_emd = ot.da.OTDA() # init class diff --git a/test/test_dr.py b/test/test_dr.py index 3faba48..e3d1e6b 100644 --- a/test/test_dr.py +++ b/test/test_dr.py @@ -13,15 +13,15 @@ except ImportError: @pytest.mark.skipif(nogo, reason="Missing modules (autograd or pymanopt)") def test_fda(): - n = 90 # nb samples in source and target datasets + n_samples = 90 # nb samples in source and target datasets np.random.seed(0) - # generate circle dataset - xs, ys = ot.datasets.get_data_classif('gaussrot', n) + # generate gaussian dataset + xs, ys = ot.datasets.get_data_classif('gaussrot', n_samples) - nbnoise = 8 + n_features_noise = 8 - xs = np.hstack((xs, np.random.randn(n, nbnoise))) + xs = np.hstack((xs, np.random.randn(n_samples, n_features_noise))) p = 1 @@ -35,20 +35,15 @@ def test_fda(): @pytest.mark.skipif(nogo, reason="Missing modules (autograd or pymanopt)") def test_wda(): - n = 100 # nb samples in source and target datasets - nz = 0.2 + n_samples = 100 # nb samples in source and target datasets np.random.seed(0) - # generate circle dataset - t = np.random.rand(n) * 2 * np.pi - ys = np.floor((np.arange(n) * 1.0 / n * 3)) + 1 - xs = np.concatenate( - (np.cos(t).reshape((-1, 1)), np.sin(t).reshape((-1, 1))), 1) - xs = xs * ys.reshape(-1, 1) + nz * np.random.randn(n, 2) + # generate gaussian dataset + xs, ys = ot.datasets.get_data_classif('gaussrot', n_samples) - nbnoise = 8 + n_features_noise = 8 - xs = np.hstack((xs, np.random.randn(n, nbnoise))) + xs = np.hstack((xs, np.random.randn(n_samples, n_features_noise))) p = 2 -- cgit v1.2.3 From 4a45135dfa3f1aeae8b3bdf0c42422f0f60426e8 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Wed, 26 Jul 2017 11:47:29 +0200 Subject: dr +gpu numpy assert --- test/test_dr.py | 4 ++-- test/test_gpu.py | 34 +++++++++++++++++----------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/test/test_dr.py b/test/test_dr.py index e3d1e6b..bdb920e 100644 --- a/test/test_dr.py +++ b/test/test_dr.py @@ -29,7 +29,7 @@ def test_fda(): projfda(xs) - assert np.allclose(np.sum(Pfda**2, 0), np.ones(p)) + np.testing.assert_allclose(np.sum(Pfda**2, 0), np.ones(p)) @pytest.mark.skipif(nogo, reason="Missing modules (autograd or pymanopt)") @@ -51,4 +51,4 @@ def test_wda(): projwda(xs) - assert np.allclose(np.sum(Pwda**2, 0), np.ones(p)) + np.testing.assert_allclose(np.sum(Pwda**2, 0), np.ones(p)) diff --git a/test/test_gpu.py b/test/test_gpu.py index 5184a6c..7ae159b 100644 --- a/test/test_gpu.py +++ b/test/test_gpu.py @@ -16,14 +16,14 @@ def test_gpu_sinkhorn(): np.random.seed(0) - def describeRes(r): + def describe_res(r): print("min:{:.3E}, max::{:.3E}, mean::{:.3E}, std::{:.3E}".format( np.min(r), np.max(r), np.mean(r), np.std(r))) - for n in [50, 100, 500, 1000]: - print(n) - a = np.random.rand(n // 4, 100) - b = np.random.rand(n, 100) + for n_samples in [50, 100, 500, 1000]: + print(n_samples) + a = np.random.rand(n_samples // 4, 100) + b = np.random.rand(n_samples, 100) time1 = time.time() transport = ot.da.OTDA_sinkhorn() transport.fit(a, b) @@ -34,26 +34,26 @@ def test_gpu_sinkhorn(): G2 = transport.G time3 = time.time() print("Normal sinkhorn, time: {:6.2f} sec ".format(time2 - time1)) - describeRes(G1) + describe_res(G1) print(" GPU sinkhorn, time: {:6.2f} sec ".format(time3 - time2)) - describeRes(G2) + describe_res(G2) - assert np.allclose(G1, G2, rtol=1e-5, atol=1e-5) + np.testing.assert_allclose(G1, G2, rtol=1e-5, atol=1e-5) @pytest.mark.skipif(nogpu, reason="No GPU available") def test_gpu_sinkhorn_lpl1(): np.random.seed(0) - def describeRes(r): + def describe_res(r): print("min:{:.3E}, max:{:.3E}, mean:{:.3E}, std:{:.3E}" .format(np.min(r), np.max(r), np.mean(r), np.std(r))) - for n in [50, 100, 500]: - print(n) - a = np.random.rand(n // 4, 100) - labels_a = np.random.randint(10, size=(n // 4)) - b = np.random.rand(n, 100) + for n_samples in [50, 100, 500]: + print(n_samples) + a = np.random.rand(n_samples // 4, 100) + labels_a = np.random.randint(10, size=(n_samples // 4)) + b = np.random.rand(n_samples, 100) time1 = time.time() transport = ot.da.OTDA_lpl1() transport.fit(a, labels_a, b) @@ -65,9 +65,9 @@ def test_gpu_sinkhorn_lpl1(): time3 = time.time() print("Normal sinkhorn lpl1, time: {:6.2f} sec ".format( time2 - time1)) - describeRes(G1) + describe_res(G1) print(" GPU sinkhorn lpl1, time: {:6.2f} sec ".format( time3 - time2)) - describeRes(G2) + describe_res(G2) - assert np.allclose(G1, G2, rtol=1e-5, atol=1e-5) + np.testing.assert_allclose(G1, G2, rtol=1e-5, atol=1e-5) -- cgit v1.2.3 From 2bc41ad8bb54c76bade6db2c0e04fa387ff29500 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Wed, 26 Jul 2017 11:48:13 +0200 Subject: rng gpu --- test/test_gpu.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/test_gpu.py b/test/test_gpu.py index 7ae159b..98f59f7 100644 --- a/test/test_gpu.py +++ b/test/test_gpu.py @@ -14,7 +14,7 @@ except ImportError: @pytest.mark.skipif(nogpu, reason="No GPU available") def test_gpu_sinkhorn(): - np.random.seed(0) + rng = np.random.RandomState(0) def describe_res(r): print("min:{:.3E}, max::{:.3E}, mean::{:.3E}, std::{:.3E}".format( @@ -22,8 +22,8 @@ def test_gpu_sinkhorn(): for n_samples in [50, 100, 500, 1000]: print(n_samples) - a = np.random.rand(n_samples // 4, 100) - b = np.random.rand(n_samples, 100) + a = rng.rand(n_samples // 4, 100) + b = rng.rand(n_samples, 100) time1 = time.time() transport = ot.da.OTDA_sinkhorn() transport.fit(a, b) @@ -43,7 +43,8 @@ def test_gpu_sinkhorn(): @pytest.mark.skipif(nogpu, reason="No GPU available") def test_gpu_sinkhorn_lpl1(): - np.random.seed(0) + + rng = np.random.RandomState(0) def describe_res(r): print("min:{:.3E}, max:{:.3E}, mean:{:.3E}, std:{:.3E}" @@ -51,9 +52,9 @@ def test_gpu_sinkhorn_lpl1(): for n_samples in [50, 100, 500]: print(n_samples) - a = np.random.rand(n_samples // 4, 100) + a = rng.rand(n_samples // 4, 100) labels_a = np.random.randint(10, size=(n_samples // 4)) - b = np.random.rand(n_samples, 100) + b = rng.rand(n_samples, 100) time1 = time.time() transport = ot.da.OTDA_lpl1() transport.fit(a, labels_a, b) -- cgit v1.2.3 From 6a02db058e24914cd79b638f15be9a90bce7e4f3 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Wed, 26 Jul 2017 11:51:07 +0200 Subject: test_optim --- test/test_optim.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/test/test_optim.py b/test/test_optim.py index d5c4ad0..bc0b706 100644 --- a/test/test_optim.py +++ b/test/test_optim.py @@ -3,22 +3,20 @@ import numpy as np import ot -# import pytest - def test_conditional_gradient(): - n = 100 # nb bins + n_bins = 100 # nb bins np.random.seed(0) # bin positions - x = np.arange(n, dtype=np.float64) + x = np.arange(n_bins, dtype=np.float64) # Gaussian distributions - a = ot.datasets.get_1D_gauss(n, m=20, s=5) # m= mean, s= std - b = ot.datasets.get_1D_gauss(n, m=60, s=10) + a = ot.datasets.get_1D_gauss(n_bins, m=20, s=5) # m= mean, s= std + b = ot.datasets.get_1D_gauss(n_bins, m=60, s=10) # loss matrix - M = ot.dist(x.reshape((n, 1)), x.reshape((n, 1))) + M = ot.dist(x.reshape((n_bins, 1)), x.reshape((n_bins, 1))) M /= M.max() def f(G): @@ -37,17 +35,17 @@ def test_conditional_gradient(): def test_generalized_conditional_gradient(): - n = 100 # nb bins + n_bins = 100 # nb bins np.random.seed(0) # bin positions - x = np.arange(n, dtype=np.float64) + x = np.arange(n_bins, dtype=np.float64) # Gaussian distributions - a = ot.datasets.get_1D_gauss(n, m=20, s=5) # m= mean, s= std - b = ot.datasets.get_1D_gauss(n, m=60, s=10) + a = ot.datasets.get_1D_gauss(n_bins, m=20, s=5) # m= mean, s= std + b = ot.datasets.get_1D_gauss(n_bins, m=60, s=10) # loss matrix - M = ot.dist(x.reshape((n, 1)), x.reshape((n, 1))) + M = ot.dist(x.reshape((n_bins, 1)), x.reshape((n_bins, 1))) M /= M.max() def f(G): -- cgit v1.2.3 From 86418ebf5adc11879c580e88e3eaa02691de30e7 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Wed, 26 Jul 2017 11:51:45 +0200 Subject: test_optim allclose --- test/test_optim.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/test_optim.py b/test/test_optim.py index bc0b706..2840cad 100644 --- a/test/test_optim.py +++ b/test/test_optim.py @@ -29,8 +29,8 @@ def test_conditional_gradient(): G, log = ot.optim.cg(a, b, M, reg, f, df, verbose=True, log=True) - assert np.allclose(a, G.sum(1)) - assert np.allclose(b, G.sum(0)) + np.testing.assert_allclose(a, G.sum(1)) + np.testing.assert_allclose(b, G.sum(0)) def test_generalized_conditional_gradient(): @@ -59,5 +59,5 @@ def test_generalized_conditional_gradient(): G, log = ot.optim.gcg(a, b, M, reg1, reg2, f, df, verbose=True, log=True) - assert np.allclose(a, G.sum(1), atol=1e-05) - assert np.allclose(b, G.sum(0), atol=1e-05) + np.testing.assert_allclose(a, G.sum(1), atol=1e-05) + np.testing.assert_allclose(b, G.sum(0), atol=1e-05) -- cgit v1.2.3 From 286de0a955bbb7e26079a8dc75abf622bf461523 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Wed, 26 Jul 2017 11:52:38 +0200 Subject: clean test_ot --- test/test_ot.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/test_ot.py b/test/test_ot.py index a30491d..9c0acab 100644 --- a/test/test_ot.py +++ b/test/test_ot.py @@ -3,8 +3,6 @@ import numpy as np import ot -# import pytest - def test_doctest(): -- cgit v1.2.3 From 81118f22197cdf4553427038526c8f730be256d7 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Wed, 26 Jul 2017 11:55:57 +0200 Subject: test_ot random state --- test/test_ot.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/test/test_ot.py b/test/test_ot.py index 9c0acab..7fe665f 100644 --- a/test/test_ot.py +++ b/test/test_ot.py @@ -18,9 +18,9 @@ def test_doctest(): def test_emd_emd2(): # test emd and emd2 for simple identity n = 100 - np.random.seed(0) + rng = np.random.RandomState(0) - x = np.random.randn(n, 2) + x = rng.randn(n, 2) u = ot.utils.unif(n) M = ot.dist(x, x) @@ -28,22 +28,22 @@ def test_emd_emd2(): G = ot.emd(u, u, M) # check G is identity - assert np.allclose(G, np.eye(n) / n) + np.testing.assert_allclose(G, np.eye(n) / n) # check constratints - assert np.allclose(u, G.sum(1)) # cf convergence sinkhorn - assert np.allclose(u, G.sum(0)) # cf convergence sinkhorn + np.testing.assert_allclose(u, G.sum(1)) # cf convergence sinkhorn + np.testing.assert_allclose(u, G.sum(0)) # cf convergence sinkhorn w = ot.emd2(u, u, M) # check loss=0 - assert np.allclose(w, 0) + np.testing.assert_allclose(w, 0) def test_emd_empty(): # test emd and emd2 for simple identity n = 100 - np.random.seed(0) + rng = np.random.RandomState(0) - x = np.random.randn(n, 2) + x = rng.randn(n, 2) u = ot.utils.unif(n) M = ot.dist(x, x) @@ -51,14 +51,14 @@ def test_emd_empty(): G = ot.emd([], [], M) # check G is identity - assert np.allclose(G, np.eye(n) / n) + np.testing.assert_allclose(G, np.eye(n) / n) # check constratints - assert np.allclose(u, G.sum(1)) # cf convergence sinkhorn - assert np.allclose(u, G.sum(0)) # cf convergence sinkhorn + np.testing.assert_allclose(u, G.sum(1)) # cf convergence sinkhorn + np.testing.assert_allclose(u, G.sum(0)) # cf convergence sinkhorn w = ot.emd2([], [], M) # check loss=0 - assert np.allclose(w, 0) + np.testing.assert_allclose(w, 0) def test_emd2_multi(): @@ -66,7 +66,6 @@ def test_emd2_multi(): from ot.datasets import get_1D_gauss as gauss n = 1000 # nb bins - np.random.seed(0) # bin positions x = np.arange(n, dtype=np.float64) @@ -96,4 +95,4 @@ def test_emd2_multi(): emdn = ot.emd2(a, b, M) ot.toc('multi proc : {} s') - assert np.allclose(emd1, emdn) + np.testing.assert_allclose(emd1, emdn) -- cgit v1.2.3 From 109fc2a9243d2c0f9a911fa8c02079d2fc0277ab Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Wed, 26 Jul 2017 11:57:43 +0200 Subject: flake8 --- test/test_optim.py | 1 - test/test_ot.py | 1 - 2 files changed, 2 deletions(-) diff --git a/test/test_optim.py b/test/test_optim.py index 2840cad..05ca895 100644 --- a/test/test_optim.py +++ b/test/test_optim.py @@ -3,7 +3,6 @@ import numpy as np import ot - def test_conditional_gradient(): n_bins = 100 # nb bins diff --git a/test/test_ot.py b/test/test_ot.py index 7fe665f..531e6e0 100644 --- a/test/test_ot.py +++ b/test/test_ot.py @@ -3,7 +3,6 @@ import numpy as np import ot - def test_doctest(): import doctest -- cgit v1.2.3 From e0fa14ba146e6f92a3060b5f2f0a5c01bd18bdc4 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Wed, 26 Jul 2017 11:58:58 +0200 Subject: flake8 --- test/test_plot.py | 18 +++++++++--------- test/test_utils.py | 3 --- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/test/test_plot.py b/test/test_plot.py index 69789fa..d826988 100644 --- a/test/test_plot.py +++ b/test/test_plot.py @@ -9,17 +9,17 @@ def test_plot1D_mat(): import ot - n = 100 # nb bins + n_bins = 100 # nb bins # bin positions - x = np.arange(n, dtype=np.float64) + x = np.arange(n_bins, dtype=np.float64) # Gaussian distributions - a = ot.datasets.get_1D_gauss(n, m=20, s=5) # m= mean, s= std - b = ot.datasets.get_1D_gauss(n, m=60, s=10) + a = ot.datasets.get_1D_gauss(n_bins, m=20, s=5) # m= mean, s= std + b = ot.datasets.get_1D_gauss(n_bins, m=60, s=10) # loss matrix - M = ot.dist(x.reshape((n, 1)), x.reshape((n, 1))) + M = ot.dist(x.reshape((n_bins, 1)), x.reshape((n_bins, 1))) M /= M.max() ot.plot.plot1D_mat(a, b, M, 'Cost matrix M') @@ -29,7 +29,7 @@ def test_plot2D_samples_mat(): import ot - n = 50 # nb samples + n_bins = 50 # nb samples mu_s = np.array([0, 0]) cov_s = np.array([[1, 0], [0, 1]]) @@ -37,9 +37,9 @@ def test_plot2D_samples_mat(): mu_t = np.array([4, 4]) cov_t = np.array([[1, -.8], [-.8, 1]]) - xs = ot.datasets.get_2D_samples_gauss(n, mu_s, cov_s) - xt = ot.datasets.get_2D_samples_gauss(n, mu_t, cov_t) + xs = ot.datasets.get_2D_samples_gauss(n_bins, mu_s, cov_s) + xt = ot.datasets.get_2D_samples_gauss(n_bins, mu_t, cov_t) - G = 1.0 * (np.random.rand(n, n) < 0.01) + G = 1.0 * (np.random.rand(n_bins, n_bins) < 0.01) ot.plot.plot2D_samples_mat(xs, xt, G, thr=1e-5) diff --git a/test/test_utils.py b/test/test_utils.py index 0883a8e..fe1b88d 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -3,9 +3,6 @@ import ot import numpy as np -# import pytest - - def test_parmap(): n = 100 -- cgit v1.2.3 From d101e088b72fa0be4648d57524946ebfc93bf34b Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Wed, 26 Jul 2017 11:59:25 +0200 Subject: nearly all review done --- test/test_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_utils.py b/test/test_utils.py index fe1b88d..230d126 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -3,6 +3,7 @@ import ot import numpy as np + def test_parmap(): n = 100 -- cgit v1.2.3 From 77037cc2cb4b138d3d73d24b1ff4fc999cc64243 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Wed, 26 Jul 2017 12:00:05 +0200 Subject: gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 6edce05..42a9aad 100644 --- a/.gitignore +++ b/.gitignore @@ -100,3 +100,6 @@ ENV/ # Mac stuff .DS_Store + +# coverage output folder +cov_html/ -- cgit v1.2.3 From fac003de3d3a159bb8fb6228786479cdede2df4e Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Wed, 26 Jul 2017 12:07:03 +0200 Subject: author and license for tets files --- test/test_bregman.py | 5 +++++ test/test_da.py | 5 +++++ test/test_dr.py | 5 +++++ test/test_gpu.py | 5 +++++ test/test_optim.py | 5 +++++ test/test_ot.py | 5 +++++ test/test_plot.py | 4 ++++ test/test_utils.py | 5 +++++ 8 files changed, 39 insertions(+) diff --git a/test/test_bregman.py b/test/test_bregman.py index 1638ef6..4a800fd 100644 --- a/test/test_bregman.py +++ b/test/test_bregman.py @@ -1,3 +1,8 @@ +"""Tests for module bregman on OT with bregman projections """ + +# Author: Remi Flamary +# +# License: MIT License import numpy as np import ot diff --git a/test/test_da.py b/test/test_da.py index a38390f..dfba83f 100644 --- a/test/test_da.py +++ b/test/test_da.py @@ -1,3 +1,8 @@ +"""Tests for module da on Domain Adaptation """ + +# Author: Remi Flamary +# +# License: MIT License import numpy as np import ot diff --git a/test/test_dr.py b/test/test_dr.py index bdb920e..915012d 100644 --- a/test/test_dr.py +++ b/test/test_dr.py @@ -1,3 +1,8 @@ +"""Tests for module dr on Dimensionality Reduction """ + +# Author: Remi Flamary +# +# License: MIT License import numpy as np import ot diff --git a/test/test_gpu.py b/test/test_gpu.py index 98f59f7..615c2a7 100644 --- a/test/test_gpu.py +++ b/test/test_gpu.py @@ -1,3 +1,8 @@ +"""Tests for module gpu for gpu acceleration """ + +# Author: Remi Flamary +# +# License: MIT License import numpy as np import ot diff --git a/test/test_optim.py b/test/test_optim.py index 05ca895..69496a5 100644 --- a/test/test_optim.py +++ b/test/test_optim.py @@ -1,3 +1,8 @@ +"""Tests for module optim fro OT optimization """ + +# Author: Remi Flamary +# +# License: MIT License import numpy as np import ot diff --git a/test/test_ot.py b/test/test_ot.py index 531e6e0..acd8718 100644 --- a/test/test_ot.py +++ b/test/test_ot.py @@ -1,3 +1,8 @@ +"""Tests for main module ot """ + +# Author: Remi Flamary +# +# License: MIT License import numpy as np import ot diff --git a/test/test_plot.py b/test/test_plot.py index d826988..f7debee 100644 --- a/test/test_plot.py +++ b/test/test_plot.py @@ -1,4 +1,8 @@ +"""Tests for module plot for visualization """ +# Author: Remi Flamary +# +# License: MIT License import numpy as np import matplotlib diff --git a/test/test_utils.py b/test/test_utils.py index 230d126..9b140db 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -1,3 +1,8 @@ +"""Tests for module utils for timing and parallel computation """ + +# Author: Remi Flamary +# +# License: MIT License import ot -- cgit v1.2.3 From 00970175c0f8ba9a99b61a182b32e329f219d382 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Wed, 26 Jul 2017 12:15:40 +0200 Subject: add license and authors on all modules --- ot/__init__.py | 4 ++++ ot/bregman.py | 5 +++++ ot/da.py | 6 ++++++ ot/datasets.py | 4 ++++ ot/dr.py | 4 ++++ ot/gpu/__init__.py | 5 +++++ ot/gpu/bregman.py | 5 +++++ ot/gpu/da.py | 9 +++++++++ ot/lp/__init__.py | 4 ++++ ot/lp/emd_wrap.pyx | 9 ++++++--- ot/optim.py | 4 ++++ ot/plot.py | 3 +++ ot/utils.py | 5 +++++ 13 files changed, 64 insertions(+), 3 deletions(-) diff --git a/ot/__init__.py b/ot/__init__.py index a79a5ce..c2161e4 100644 --- a/ot/__init__.py +++ b/ot/__init__.py @@ -4,6 +4,10 @@ """ +# Author: Remi Flamary +# +# License: MIT License + # All submodules and packages from . import lp diff --git a/ot/bregman.py b/ot/bregman.py index fe10880..71a5548 100644 --- a/ot/bregman.py +++ b/ot/bregman.py @@ -3,6 +3,11 @@ Bregman projections for regularized OT """ +# Author: Remi Flamary +# Nicolas Courty +# +# License: MIT License + import numpy as np diff --git a/ot/da.py b/ot/da.py index 5039fbd..977d532 100644 --- a/ot/da.py +++ b/ot/da.py @@ -3,6 +3,12 @@ Domain adaptation with optimal transport """ +# Author: Remi Flamary +# Nicolas Courty +# Michael Perrot +# +# License: MIT License + import numpy as np from .bregman import sinkhorn from .lp import emd diff --git a/ot/datasets.py b/ot/datasets.py index 4371a23..e4fe118 100644 --- a/ot/datasets.py +++ b/ot/datasets.py @@ -2,6 +2,10 @@ Simple example datasets for OT """ +# Author: Remi Flamary +# +# License: MIT License + import numpy as np import scipy as sp diff --git a/ot/dr.py b/ot/dr.py index 77cbae2..d30ab30 100644 --- a/ot/dr.py +++ b/ot/dr.py @@ -3,6 +3,10 @@ Dimension reduction with optimal transport """ +# Author: Remi Flamary +# +# License: MIT License + from scipy import linalg import autograd.numpy as np from pymanopt.manifolds import Stiefel diff --git a/ot/gpu/__init__.py b/ot/gpu/__init__.py index 40b11c0..c8f9433 100644 --- a/ot/gpu/__init__.py +++ b/ot/gpu/__init__.py @@ -4,4 +4,9 @@ from . import bregman from . import da from .bregman import sinkhorn +# Author: Remi Flamary +# Leo Gautheron +# +# License: MIT License + __all__ = ["bregman", "da", "sinkhorn"] diff --git a/ot/gpu/bregman.py b/ot/gpu/bregman.py index 2302f80..86bfec1 100644 --- a/ot/gpu/bregman.py +++ b/ot/gpu/bregman.py @@ -3,6 +3,11 @@ Bregman projections for regularized OT with GPU """ +# Author: Remi Flamary +# Leo Gautheron +# +# License: MIT License + import numpy as np import cudamat diff --git a/ot/gpu/da.py b/ot/gpu/da.py index c66e755..7fb488d 100644 --- a/ot/gpu/da.py +++ b/ot/gpu/da.py @@ -3,6 +3,15 @@ Domain adaptation with optimal transport with GPU implementation """ +# Author: Remi Flamary +# Nicolas Courty +# Michael Perrot +# Leo Gautheron +# +# License: MIT License + + + import numpy as np from ..utils import unif from ..da import OTDA diff --git a/ot/lp/__init__.py b/ot/lp/__init__.py index db3da78..6e0bdb8 100644 --- a/ot/lp/__init__.py +++ b/ot/lp/__init__.py @@ -3,6 +3,10 @@ Solvers for the original linear program OT problem """ +# Author: Remi Flamary +# +# License: MIT License + import numpy as np # import compiled emd from .emd_wrap import emd_c, emd2_c diff --git a/ot/lp/emd_wrap.pyx b/ot/lp/emd_wrap.pyx index 46794ab..46c96c1 100644 --- a/ot/lp/emd_wrap.pyx +++ b/ot/lp/emd_wrap.pyx @@ -1,9 +1,12 @@ # -*- coding: utf-8 -*- """ -Created on Thu Sep 11 08:42:08 2014 - -@author: rflamary +Cython linker with C solver """ + +# Author: Remi Flamary +# +# License: MIT License + import numpy as np cimport numpy as np diff --git a/ot/optim.py b/ot/optim.py index adad95e..c59089c 100644 --- a/ot/optim.py +++ b/ot/optim.py @@ -3,6 +3,10 @@ Optimization algorithms for OT """ +# Author: Remi Flamary +# +# License: MIT License + import numpy as np from scipy.optimize.linesearch import scalar_search_armijo from .lp import emd diff --git a/ot/plot.py b/ot/plot.py index 61afc9f..784a372 100644 --- a/ot/plot.py +++ b/ot/plot.py @@ -2,6 +2,9 @@ Functions for plotting OT matrices """ +# Author: Remi Flamary +# +# License: MIT License import numpy as np import matplotlib.pylab as pl diff --git a/ot/utils.py b/ot/utils.py index 1dee932..2b2f8b3 100644 --- a/ot/utils.py +++ b/ot/utils.py @@ -2,6 +2,11 @@ """ Various function that can be usefull """ + +# Author: Remi Flamary +# +# License: MIT License + import multiprocessing from functools import reduce import time -- cgit v1.2.3 From 251af8eec2b39e74000242cbf5bff5e13910cfe8 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Wed, 26 Jul 2017 12:18:33 +0200 Subject: add author to all examples --- examples/plot_OTDA_2D.py | 4 ++++ examples/plot_OTDA_classes.py | 4 ++++ examples/plot_OTDA_color_images.py | 4 ++++ examples/plot_OTDA_mapping.py | 4 ++++ examples/plot_OTDA_mapping_color_images.py | 4 ++++ examples/plot_OT_1D.py | 5 ++++- examples/plot_OT_2D_samples.py | 5 ++++- examples/plot_OT_L1_vs_L2.py | 5 ++++- examples/plot_WDA.py | 5 ++++- examples/plot_barycenter_1D.py | 6 ++++-- examples/plot_compute_emd.py | 5 ++++- 11 files changed, 44 insertions(+), 7 deletions(-) diff --git a/examples/plot_OTDA_2D.py b/examples/plot_OTDA_2D.py index 1bda59c..f2108c6 100644 --- a/examples/plot_OTDA_2D.py +++ b/examples/plot_OTDA_2D.py @@ -6,6 +6,10 @@ OT for empirical distributions """ +# Author: Remi Flamary +# +# License: MIT License + import numpy as np import matplotlib.pylab as pl import ot diff --git a/examples/plot_OTDA_classes.py b/examples/plot_OTDA_classes.py index 4d3846a..53e4bae 100644 --- a/examples/plot_OTDA_classes.py +++ b/examples/plot_OTDA_classes.py @@ -6,6 +6,10 @@ OT for domain adaptation """ +# Author: Remi Flamary +# +# License: MIT License + import matplotlib.pylab as pl import ot diff --git a/examples/plot_OTDA_color_images.py b/examples/plot_OTDA_color_images.py index 75ac5b6..c5ff873 100644 --- a/examples/plot_OTDA_color_images.py +++ b/examples/plot_OTDA_color_images.py @@ -9,6 +9,10 @@ Regularized discrete optimal transport. SIAM Journal on Imaging Sciences, 7(3), 1853-1882. """ +# Author: Remi Flamary +# +# License: MIT License + import numpy as np from scipy import ndimage import matplotlib.pylab as pl diff --git a/examples/plot_OTDA_mapping.py b/examples/plot_OTDA_mapping.py index a5c2b21..a0d7f8b 100644 --- a/examples/plot_OTDA_mapping.py +++ b/examples/plot_OTDA_mapping.py @@ -9,6 +9,10 @@ OT mapping estimation for domain adaptation [8] Neural Information Processing Systems (NIPS), 2016. """ +# Author: Remi Flamary +# +# License: MIT License + import numpy as np import matplotlib.pylab as pl import ot diff --git a/examples/plot_OTDA_mapping_color_images.py b/examples/plot_OTDA_mapping_color_images.py index 9710461..8064b25 100644 --- a/examples/plot_OTDA_mapping_color_images.py +++ b/examples/plot_OTDA_mapping_color_images.py @@ -11,6 +11,10 @@ OT for domain adaptation with image color adaptation [6] with mapping estimation """ +# Author: Remi Flamary +# +# License: MIT License + import numpy as np from scipy import ndimage import matplotlib.pylab as pl diff --git a/examples/plot_OT_1D.py b/examples/plot_OT_1D.py index 2f3b924..0f3a26a 100644 --- a/examples/plot_OT_1D.py +++ b/examples/plot_OT_1D.py @@ -4,9 +4,12 @@ 1D optimal transport ==================== -@author: rflamary """ +# Author: Remi Flamary +# +# License: MIT License + import numpy as np import matplotlib.pylab as pl import ot diff --git a/examples/plot_OT_2D_samples.py b/examples/plot_OT_2D_samples.py index 75ed7db..023e645 100644 --- a/examples/plot_OT_2D_samples.py +++ b/examples/plot_OT_2D_samples.py @@ -4,9 +4,12 @@ 2D Optimal transport between empirical distributions ==================================================== -@author: rflamary """ +# Author: Remi Flamary +# +# License: MIT License + import numpy as np import matplotlib.pylab as pl import ot diff --git a/examples/plot_OT_L1_vs_L2.py b/examples/plot_OT_L1_vs_L2.py index 86d902b..dfc9462 100644 --- a/examples/plot_OT_L1_vs_L2.py +++ b/examples/plot_OT_L1_vs_L2.py @@ -8,9 +8,12 @@ Stole the figure idea from Fig. 1 and 2 in https://arxiv.org/pdf/1706.07650.pdf -@author: rflamary """ +# Author: Remi Flamary +# +# License: MIT License + import numpy as np import matplotlib.pylab as pl import ot diff --git a/examples/plot_WDA.py b/examples/plot_WDA.py index 9eb8693..42789f2 100644 --- a/examples/plot_WDA.py +++ b/examples/plot_WDA.py @@ -4,9 +4,12 @@ Wasserstein Discriminant Analysis ================================= -@author: rflamary """ +# Author: Remi Flamary +# +# License: MIT License + import numpy as np import matplotlib.pylab as pl diff --git a/examples/plot_barycenter_1D.py b/examples/plot_barycenter_1D.py index ab236e1..875f44c 100644 --- a/examples/plot_barycenter_1D.py +++ b/examples/plot_barycenter_1D.py @@ -4,10 +4,12 @@ 1D Wasserstein barycenter demo ============================== - -@author: rflamary """ +# Author: Remi Flamary +# +# License: MIT License + import numpy as np import matplotlib.pylab as pl import ot diff --git a/examples/plot_compute_emd.py b/examples/plot_compute_emd.py index 558facb..893eecf 100644 --- a/examples/plot_compute_emd.py +++ b/examples/plot_compute_emd.py @@ -4,9 +4,12 @@ 1D optimal transport ==================== -@author: rflamary """ +# Author: Remi Flamary +# +# License: MIT License + import numpy as np import matplotlib.pylab as pl import ot -- cgit v1.2.3 From 84aa3183491260b9c3dbb9f928499cc18e5341c1 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Wed, 26 Jul 2017 12:22:00 +0200 Subject: pep8 --- ot/__init__.py | 2 +- ot/bregman.py | 16 +++++++++------- ot/da.py | 4 ++-- ot/optim.py | 2 +- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/ot/__init__.py b/ot/__init__.py index c2161e4..6d4c4c6 100644 --- a/ot/__init__.py +++ b/ot/__init__.py @@ -28,6 +28,6 @@ from .utils import dist, unif, tic, toc, toq __version__ = "0.3.1" -__all__ = ["emd", "emd2", "sinkhorn","sinkhorn2", "utils", 'datasets', +__all__ = ["emd", "emd2", "sinkhorn", "sinkhorn2", "utils", 'datasets', 'bregman', 'lp', 'plot', 'tic', 'toc', 'toq', 'dist', 'unif', 'barycenter', 'sinkhorn_lpl1_mm', 'da', 'optim'] diff --git a/ot/bregman.py b/ot/bregman.py index 71a5548..929388e 100644 --- a/ot/bregman.py +++ b/ot/bregman.py @@ -108,7 +108,8 @@ def sinkhorn(a, b, M, reg, method='sinkhorn', numItermax=1000, stopThr=1e-9, ver stopThr=stopThr, verbose=verbose, log=log, **kwargs) elif method.lower() == 'sinkhorn_epsilon_scaling': def sink(): - return sinkhorn_epsilon_scaling(a, b, M, reg, numItermax=numItermax, + return sinkhorn_epsilon_scaling( + a, b, M, reg, numItermax=numItermax, stopThr=stopThr, verbose=verbose, log=log, **kwargs) else: print('Warning : unknown method using classic Sinkhorn Knopp') @@ -216,7 +217,8 @@ def sinkhorn2(a, b, M, reg, method='sinkhorn', numItermax=1000, stopThr=1e-9, ve stopThr=stopThr, verbose=verbose, log=log, **kwargs) elif method.lower() == 'sinkhorn_epsilon_scaling': def sink(): - return sinkhorn_epsilon_scaling(a, b, M, reg, numItermax=numItermax, + return sinkhorn_epsilon_scaling( + a, b, M, reg, numItermax=numItermax, stopThr=stopThr, verbose=verbose, log=log, **kwargs) else: print('Warning : unknown method using classic Sinkhorn Knopp') @@ -593,7 +595,7 @@ def sinkhorn_stabilized(a, b, M, reg, numItermax=1000, tau=1e3, stopThr=1e-9, wa cpt = cpt + 1 - #print('err=',err,' cpt=',cpt) + # print('err=',err,' cpt=',cpt) if log: log['logu'] = alpha / reg + np.log(u) log['logv'] = beta / reg + np.log(v) @@ -778,7 +780,7 @@ def sinkhorn_epsilon_scaling(a, b, M, reg, numItermax=100, epsilon0=1e4, numInne loop = False cpt = cpt + 1 - #print('err=',err,' cpt=',cpt) + # print('err=',err,' cpt=',cpt) if log: log['alpha'] = alpha log['beta'] = beta @@ -965,16 +967,16 @@ def unmix(a, D, M, M0, h0, reg, reg0, alpha, numItermax=1000, stopThr=1e-3, verb """ - #M = M/np.median(M) + # M = M/np.median(M) K = np.exp(-M / reg) - #M0 = M0/np.median(M0) + # M0 = M0/np.median(M0) K0 = np.exp(-M0 / reg0) old = h0 err = 1 cpt = 0 - #log = {'niter':0, 'all_err':[]} + # log = {'niter':0, 'all_err':[]} if log: log = {'err': []} diff --git a/ot/da.py b/ot/da.py index 977d532..4f9bce5 100644 --- a/ot/da.py +++ b/ot/da.py @@ -478,7 +478,7 @@ def joint_OT_mapping_kernel(xs, xt, mu=1, eta=0.001, kerneltype='gaussian', sigm Kp[:ns, :ns] = K # ls regu - #K0 = K1.T.dot(K1)+eta*I + # K0 = K1.T.dot(K1)+eta*I # Kreg=I # RKHS regul @@ -490,7 +490,7 @@ def joint_OT_mapping_kernel(xs, xt, mu=1, eta=0.001, kerneltype='gaussian', sigm I = np.eye(ns) # ls regul - #K0 = K1.T.dot(K1)+eta*I + # K0 = K1.T.dot(K1)+eta*I # Kreg=I # proper kernel ridge diff --git a/ot/optim.py b/ot/optim.py index c59089c..1d09adc 100644 --- a/ot/optim.py +++ b/ot/optim.py @@ -304,7 +304,7 @@ def gcg(a, b, M, reg1, reg2, f, df, G0=None, numItermax=10, numInnerItermax=200, Mi = M + reg2 * df(G) # solve linear program with Sinkhorn - #Gc = sinkhorn_stabilized(a,b, Mi, reg1, numItermax = numInnerItermax) + # Gc = sinkhorn_stabilized(a,b, Mi, reg1, numItermax = numInnerItermax) Gc = sinkhorn(a, b, Mi, reg1, numItermax=numInnerItermax) deltaG = Gc - G -- cgit v1.2.3 From 96f8b96cdd50be633d4f3c6f3255cb456e492e08 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Wed, 26 Jul 2017 12:33:14 +0200 Subject: valid flake8 --- ot/bregman.py | 4 ++-- ot/gpu/bregman.py | 2 +- ot/gpu/da.py | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ot/bregman.py b/ot/bregman.py index 929388e..d63c51d 100644 --- a/ot/bregman.py +++ b/ot/bregman.py @@ -110,7 +110,7 @@ def sinkhorn(a, b, M, reg, method='sinkhorn', numItermax=1000, stopThr=1e-9, ver def sink(): return sinkhorn_epsilon_scaling( a, b, M, reg, numItermax=numItermax, - stopThr=stopThr, verbose=verbose, log=log, **kwargs) + stopThr=stopThr, verbose=verbose, log=log, **kwargs) else: print('Warning : unknown method using classic Sinkhorn Knopp') @@ -219,7 +219,7 @@ def sinkhorn2(a, b, M, reg, method='sinkhorn', numItermax=1000, stopThr=1e-9, ve def sink(): return sinkhorn_epsilon_scaling( a, b, M, reg, numItermax=numItermax, - stopThr=stopThr, verbose=verbose, log=log, **kwargs) + stopThr=stopThr, verbose=verbose, log=log, **kwargs) else: print('Warning : unknown method using classic Sinkhorn Knopp') diff --git a/ot/gpu/bregman.py b/ot/gpu/bregman.py index 86bfec1..47939c4 100644 --- a/ot/gpu/bregman.py +++ b/ot/gpu/bregman.py @@ -4,7 +4,7 @@ Bregman projections for regularized OT with GPU """ # Author: Remi Flamary -# Leo Gautheron +# Leo Gautheron # # License: MIT License diff --git a/ot/gpu/da.py b/ot/gpu/da.py index 7fb488d..05c580f 100644 --- a/ot/gpu/da.py +++ b/ot/gpu/da.py @@ -6,12 +6,11 @@ Domain adaptation with optimal transport with GPU implementation # Author: Remi Flamary # Nicolas Courty # Michael Perrot -# Leo Gautheron +# Leo Gautheron # # License: MIT License - import numpy as np from ..utils import unif from ..da import OTDA -- cgit v1.2.3 From 838550ead9cc8a66d9b9c1212c5dda2457dc59a5 Mon Sep 17 00:00:00 2001 From: Rémi Flamary Date: Wed, 26 Jul 2017 15:12:44 +0200 Subject: last stuff --- test/test_utils.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/test_utils.py b/test/test_utils.py index 9b140db..1bd37cd 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -22,7 +22,7 @@ def test_parmap(): l2 = list(ot.utils.parmap(f, a)) - assert np.allclose(l1, l2) + np.testing.assert_allclose(l1, l2) def test_tic_toc(): @@ -35,10 +35,10 @@ def test_tic_toc(): t2 = ot.toq() # test timing - assert np.allclose(0.5, t, rtol=1e-2, atol=1e-2) + np.testing.assert_allclose(0.5, t, rtol=1e-2, atol=1e-2) # test toc vs toq - assert np.allclose(t, t2, rtol=1e-2, atol=1e-2) + np.testing.assert_allclose(t, t2, rtol=1e-2, atol=1e-2) def test_kernel(): @@ -50,7 +50,7 @@ def test_kernel(): K = ot.utils.kernel(x, x) # gaussian kernel has ones on the diagonal - assert np.allclose(np.diag(K), np.ones(n)) + np.testing.assert_allclose(np.diag(K), np.ones(n)) def test_unif(): @@ -59,7 +59,7 @@ def test_unif(): u = ot.unif(n) - assert np.allclose(1, np.sum(u)) + np.testing.assert_allclose(1, np.sum(u)) def test_dist(): @@ -77,8 +77,8 @@ def test_dist(): D3 = ot.dist(x) # dist shoul return squared euclidean - assert np.allclose(D, D2) - assert np.allclose(D, D3) + np.testing.assert_allclose(D, D2) + np.testing.assert_allclose(D, D3) def test_dist0(): @@ -87,7 +87,7 @@ def test_dist0(): M = ot.utils.dist0(n, method='lin_square') # dist0 default to linear sampling with quadratic loss - assert np.allclose(M[0, -1], (n - 1) * (n - 1)) + np.testing.assert_allclose(M[0, -1], (n - 1) * (n - 1)) def test_dots(): @@ -102,7 +102,7 @@ def test_dots(): X2 = A.dot(B.dot(C)) - assert np.allclose(X1, X2) + np.testing.assert_allclose(X1, X2) def test_clean_zeros(): -- cgit v1.2.3 From 553a45678c829896cbb076b8a89934525431c62c Mon Sep 17 00:00:00 2001 From: Slasnista Date: Fri, 28 Jul 2017 08:00:35 +0200 Subject: remove linewidth error message --- ot/da.py | 150 +++++++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 108 insertions(+), 42 deletions(-) diff --git a/ot/da.py b/ot/da.py index 4f9bce5..1dd4011 100644 --- a/ot/da.py +++ b/ot/da.py @@ -17,14 +17,18 @@ from .optim import cg from .optim import gcg -def sinkhorn_lpl1_mm(a, labels_a, b, M, reg, eta=0.1, numItermax=10, numInnerItermax=200, stopInnerThr=1e-9, verbose=False, log=False): +def sinkhorn_lpl1_mm(a, labels_a, b, M, reg, eta=0.1, numItermax=10, + numInnerItermax=200, stopInnerThr=1e-9, verbose=False, + log=False): """ - Solve the entropic regularization optimal transport problem with nonconvex group lasso regularization + Solve the entropic regularization optimal transport problem with nonconvex + group lasso regularization The function solves the following optimization problem: .. math:: - \gamma = arg\min_\gamma <\gamma,M>_F + reg\cdot\Omega_e(\gamma)+ \eta \Omega_g(\gamma) + \gamma = arg\min_\gamma <\gamma,M>_F + reg\cdot\Omega_e(\gamma) + + \eta \Omega_g(\gamma) s.t. \gamma 1 = a @@ -34,11 +38,16 @@ def sinkhorn_lpl1_mm(a, labels_a, b, M, reg, eta=0.1, numItermax=10, numInnerIte where : - M is the (ns,nt) metric cost matrix - - :math:`\Omega_e` is the entropic regularization term :math:`\Omega_e(\gamma)=\sum_{i,j} \gamma_{i,j}\log(\gamma_{i,j})` - - :math:`\Omega_g` is the group lasso regulaization term :math:`\Omega_g(\gamma)=\sum_{i,c} \|\gamma_{i,\mathcal{I}_c}\|^{1/2}_1` where :math:`\mathcal{I}_c` are the index of samples from class c in the source domain. + - :math:`\Omega_e` is the entropic regularization term + :math:`\Omega_e(\gamma)=\sum_{i,j} \gamma_{i,j}\log(\gamma_{i,j})` + - :math:`\Omega_g` is the group lasso regulaization term + :math:`\Omega_g(\gamma)=\sum_{i,c} \|\gamma_{i,\mathcal{I}_c}\|^{1/2}_1` + where :math:`\mathcal{I}_c` are the index of samples from class c + in the source domain. - a and b are source and target weights (sum to 1) - The algorithm used for solving the problem is the generalised conditional gradient as proposed in [5]_ [7]_ + The algorithm used for solving the problem is the generalised conditional + gradient as proposed in [5]_ [7]_ Parameters @@ -78,8 +87,13 @@ def sinkhorn_lpl1_mm(a, labels_a, b, M, reg, eta=0.1, numItermax=10, numInnerIte References ---------- - .. [5] N. Courty; R. Flamary; D. Tuia; A. Rakotomamonjy, "Optimal Transport for Domain Adaptation," in IEEE Transactions on Pattern Analysis and Machine Intelligence , vol.PP, no.99, pp.1-1 - .. [7] Rakotomamonjy, A., Flamary, R., & Courty, N. (2015). Generalized conditional gradient: analysis of convergence and applications. arXiv preprint arXiv:1510.06567. + .. [5] N. Courty; R. Flamary; D. Tuia; A. Rakotomamonjy, + "Optimal Transport for Domain Adaptation," in IEEE + Transactions on Pattern Analysis and Machine Intelligence , + vol.PP, no.99, pp.1-1 + .. [7] Rakotomamonjy, A., Flamary, R., & Courty, N. (2015). + Generalized conditional gradient: analysis of convergence + and applications. arXiv preprint arXiv:1510.06567. See Also -------- @@ -114,14 +128,18 @@ def sinkhorn_lpl1_mm(a, labels_a, b, M, reg, eta=0.1, numItermax=10, numInnerIte return transp -def sinkhorn_l1l2_gl(a, labels_a, b, M, reg, eta=0.1, numItermax=10, numInnerItermax=200, stopInnerThr=1e-9, verbose=False, log=False): +def sinkhorn_l1l2_gl(a, labels_a, b, M, reg, eta=0.1, numItermax=10, + numInnerItermax=200, stopInnerThr=1e-9, verbose=False, + log=False): """ - Solve the entropic regularization optimal transport problem with group lasso regularization + Solve the entropic regularization optimal transport problem with group + lasso regularization The function solves the following optimization problem: .. math:: - \gamma = arg\min_\gamma <\gamma,M>_F + reg\cdot\Omega_e(\gamma)+ \eta \Omega_g(\gamma) + \gamma = arg\min_\gamma <\gamma,M>_F + reg\cdot\Omega_e(\gamma)+ + \eta \Omega_g(\gamma) s.t. \gamma 1 = a @@ -131,11 +149,16 @@ def sinkhorn_l1l2_gl(a, labels_a, b, M, reg, eta=0.1, numItermax=10, numInnerIte where : - M is the (ns,nt) metric cost matrix - - :math:`\Omega_e` is the entropic regularization term :math:`\Omega_e(\gamma)=\sum_{i,j} \gamma_{i,j}\log(\gamma_{i,j})` - - :math:`\Omega_g` is the group lasso regulaization term :math:`\Omega_g(\gamma)=\sum_{i,c} \|\gamma_{i,\mathcal{I}_c}\|^2` where :math:`\mathcal{I}_c` are the index of samples from class c in the source domain. + - :math:`\Omega_e` is the entropic regularization term + :math:`\Omega_e(\gamma)=\sum_{i,j} \gamma_{i,j}\log(\gamma_{i,j})` + - :math:`\Omega_g` is the group lasso regulaization term + :math:`\Omega_g(\gamma)=\sum_{i,c} \|\gamma_{i,\mathcal{I}_c}\|^2` + where :math:`\mathcal{I}_c` are the index of samples from class + c in the source domain. - a and b are source and target weights (sum to 1) - The algorithm used for solving the problem is the generalised conditional gradient as proposed in [5]_ [7]_ + The algorithm used for solving the problem is the generalised conditional + gradient as proposed in [5]_ [7]_ Parameters @@ -175,8 +198,12 @@ def sinkhorn_l1l2_gl(a, labels_a, b, M, reg, eta=0.1, numItermax=10, numInnerIte References ---------- - .. [5] N. Courty; R. Flamary; D. Tuia; A. Rakotomamonjy, "Optimal Transport for Domain Adaptation," in IEEE Transactions on Pattern Analysis and Machine Intelligence , vol.PP, no.99, pp.1-1 - .. [7] Rakotomamonjy, A., Flamary, R., & Courty, N. (2015). Generalized conditional gradient: analysis of convergence and applications. arXiv preprint arXiv:1510.06567. + .. [5] N. Courty; R. Flamary; D. Tuia; A. Rakotomamonjy, + "Optimal Transport for Domain Adaptation," in IEEE Transactions + on Pattern Analysis and Machine Intelligence , vol.PP, no.99, pp.1-1 + .. [7] Rakotomamonjy, A., Flamary, R., & Courty, N. (2015). + Generalized conditional gradient: analysis of convergence and + applications. arXiv preprint arXiv:1510.06567. See Also -------- @@ -203,16 +230,22 @@ def sinkhorn_l1l2_gl(a, labels_a, b, M, reg, eta=0.1, numItermax=10, numInnerIte W[labels_a == lab, i] = temp / n return W - return gcg(a, b, M, reg, eta, f, df, G0=None, numItermax=numItermax, numInnerItermax=numInnerItermax, stopThr=stopInnerThr, verbose=verbose, log=log) + return gcg(a, b, M, reg, eta, f, df, G0=None, numItermax=numItermax, + numInnerItermax=numInnerItermax, stopThr=stopInnerThr, + verbose=verbose, log=log) -def joint_OT_mapping_linear(xs, xt, mu=1, eta=0.001, bias=False, verbose=False, verbose2=False, numItermax=100, numInnerItermax=10, stopInnerThr=1e-6, stopThr=1e-5, log=False, **kwargs): +def joint_OT_mapping_linear(xs, xt, mu=1, eta=0.001, bias=False, verbose=False, + verbose2=False, numItermax=100, numInnerItermax=10, + stopInnerThr=1e-6, stopThr=1e-5, log=False, + **kwargs): """Joint OT and linear mapping estimation as proposed in [8] The function solves the following optimization problem: .. math:: - \min_{\gamma,L}\quad \|L(X_s) -n_s\gamma X_t\|^2_F + \mu<\gamma,M>_F + \eta \|L -I\|^2_F + \min_{\gamma,L}\quad \|L(X_s) -n_s\gamma X_t\|^2_F + + \mu<\gamma,M>_F + \eta \|L -I\|^2_F s.t. \gamma 1 = a @@ -221,8 +254,10 @@ def joint_OT_mapping_linear(xs, xt, mu=1, eta=0.001, bias=False, verbose=False, \gamma\geq 0 where : - - M is the (ns,nt) squared euclidean cost matrix between samples in Xs and Xt (scaled by ns) - - :math:`L` is a dxd linear operator that approximates the barycentric mapping + - M is the (ns,nt) squared euclidean cost matrix between samples in + Xs and Xt (scaled by ns) + - :math:`L` is a dxd linear operator that approximates the barycentric + mapping - :math:`I` is the identity matrix (neutral linear mapping) - a and b are uniform source and target weights @@ -277,7 +312,9 @@ def joint_OT_mapping_linear(xs, xt, mu=1, eta=0.001, bias=False, verbose=False, References ---------- - .. [8] M. Perrot, N. Courty, R. Flamary, A. Habrard, "Mapping estimation for discrete optimal transport", Neural Information Processing Systems (NIPS), 2016. + .. [8] M. Perrot, N. Courty, R. Flamary, A. Habrard, + "Mapping estimation for discrete optimal transport", + Neural Information Processing Systems (NIPS), 2016. See Also -------- @@ -384,13 +421,18 @@ def joint_OT_mapping_linear(xs, xt, mu=1, eta=0.001, bias=False, verbose=False, return G, L -def joint_OT_mapping_kernel(xs, xt, mu=1, eta=0.001, kerneltype='gaussian', sigma=1, bias=False, verbose=False, verbose2=False, numItermax=100, numInnerItermax=10, stopInnerThr=1e-6, stopThr=1e-5, log=False, **kwargs): +def joint_OT_mapping_kernel(xs, xt, mu=1, eta=0.001, kerneltype='gaussian', + sigma=1, bias=False, verbose=False, verbose2=False, + numItermax=100, numInnerItermax=10, + stopInnerThr=1e-6, stopThr=1e-5, log=False, + **kwargs): """Joint OT and nonlinear mapping estimation with kernels as proposed in [8] The function solves the following optimization problem: .. math:: - \min_{\gamma,L\in\mathcal{H}}\quad \|L(X_s) -n_s\gamma X_t\|^2_F + \mu<\gamma,M>_F + \eta \|L\|^2_\mathcal{H} + \min_{\gamma,L\in\mathcal{H}}\quad \|L(X_s) - + n_s\gamma X_t\|^2_F + \mu<\gamma,M>_F + \eta \|L\|^2_\mathcal{H} s.t. \gamma 1 = a @@ -399,8 +441,10 @@ def joint_OT_mapping_kernel(xs, xt, mu=1, eta=0.001, kerneltype='gaussian', sigm \gamma\geq 0 where : - - M is the (ns,nt) squared euclidean cost matrix between samples in Xs and Xt (scaled by ns) - - :math:`L` is a ns x d linear operator on a kernel matrix that approximates the barycentric mapping + - M is the (ns,nt) squared euclidean cost matrix between samples in + Xs and Xt (scaled by ns) + - :math:`L` is a ns x d linear operator on a kernel matrix that + approximates the barycentric mapping - a and b are uniform source and target weights The problem consist in solving jointly an optimal transport matrix @@ -458,7 +502,9 @@ def joint_OT_mapping_kernel(xs, xt, mu=1, eta=0.001, kerneltype='gaussian', sigm References ---------- - .. [8] M. Perrot, N. Courty, R. Flamary, A. Habrard, "Mapping estimation for discrete optimal transport", Neural Information Processing Systems (NIPS), 2016. + .. [8] M. Perrot, N. Courty, R. Flamary, A. Habrard, + "Mapping estimation for discrete optimal transport", + Neural Information Processing Systems (NIPS), 2016. See Also -------- @@ -593,7 +639,9 @@ class OTDA(object): References ---------- - .. [5] N. Courty; R. Flamary; D. Tuia; A. Rakotomamonjy, "Optimal Transport for Domain Adaptation," in IEEE Transactions on Pattern Analysis and Machine Intelligence , vol.PP, no.99, pp.1-1 + .. [5] N. Courty; R. Flamary; D. Tuia; A. Rakotomamonjy, + "Optimal Transport for Domain Adaptation," in IEEE Transactions on + Pattern Analysis and Machine Intelligence , vol.PP, no.99, pp.1-1 """ @@ -606,7 +654,8 @@ class OTDA(object): self.computed = False def fit(self, xs, xt, ws=None, wt=None, norm=None): - """ Fit domain adaptation between samples is xs and xt (with optional weights)""" + """Fit domain adaptation between samples is xs and xt + (with optional weights)""" self.xs = xs self.xt = xt @@ -669,7 +718,9 @@ class OTDA(object): References ---------- - .. [6] Ferradans, S., Papadakis, N., Peyré, G., & Aujol, J. F. (2014). Regularized discrete optimal transport. SIAM Journal on Imaging Sciences, 7(3), 1853-1882. + .. [6] Ferradans, S., Papadakis, N., Peyré, G., & Aujol, J. F. (2014). + Regularized discrete optimal transport. SIAM Journal on Imaging + Sciences, 7(3), 1853-1882. """ if direction > 0: # >0 then source to target @@ -708,10 +759,12 @@ class OTDA(object): class OTDA_sinkhorn(OTDA): - """Class for domain adaptation with optimal transport with entropic regularization""" + """Class for domain adaptation with optimal transport with entropic + 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 (with optional weights)""" + """Fit regularized domain adaptation between samples is xs and xt + (with optional weights)""" self.xs = xs self.xt = xt @@ -731,10 +784,14 @@ class OTDA_sinkhorn(OTDA): class OTDA_lpl1(OTDA): - """Class for domain adaptation with optimal transport with entropic and group regularization""" + """Class for domain adaptation with optimal transport with entropic and + group regularization""" - def fit(self, xs, ys, xt, reg=1, eta=1, ws=None, wt=None, norm=None, **kwargs): - """ Fit regularized domain adaptation between samples is xs and xt (with optional weights), See ot.da.sinkhorn_lpl1_mm for fit parameters""" + def fit(self, xs, ys, xt, reg=1, eta=1, ws=None, wt=None, norm=None, + **kwargs): + """Fit regularized domain adaptation between samples is xs and xt + (with optional weights), See ot.da.sinkhorn_lpl1_mm for fit + parameters""" self.xs = xs self.xt = xt @@ -754,10 +811,14 @@ class OTDA_lpl1(OTDA): class OTDA_l1l2(OTDA): - """Class for domain adaptation with optimal transport with entropic and group lasso regularization""" + """Class for domain adaptation with optimal transport with entropic + and group lasso regularization""" - def fit(self, xs, ys, xt, reg=1, eta=1, ws=None, wt=None, norm=None, **kwargs): - """ Fit regularized domain adaptation between samples is xs and xt (with optional weights), See ot.da.sinkhorn_lpl1_gl for fit parameters""" + def fit(self, xs, ys, xt, reg=1, eta=1, ws=None, wt=None, norm=None, + **kwargs): + """Fit regularized domain adaptation between samples is xs and xt + (with optional weights), See ot.da.sinkhorn_lpl1_gl for fit + parameters""" self.xs = xs self.xt = xt @@ -777,7 +838,9 @@ class OTDA_l1l2(OTDA): class OTDA_mapping_linear(OTDA): - """Class for optimal transport with joint linear mapping estimation as in [8]""" + """Class for optimal transport with joint linear mapping estimation as in + [8] + """ def __init__(self): """ Class initialization""" @@ -820,9 +883,11 @@ class OTDA_mapping_linear(OTDA): class OTDA_mapping_kernel(OTDA_mapping_linear): - """Class for optimal transport with joint nonlinear mapping estimation as in [8]""" + """Class for optimal transport with joint nonlinear mapping + estimation as in [8]""" - def fit(self, xs, xt, mu=1, eta=1, bias=False, kerneltype='gaussian', sigma=1, **kwargs): + def fit(self, xs, xt, mu=1, eta=1, bias=False, kerneltype='gaussian', + sigma=1, **kwargs): """ Fit domain adaptation between samples is xs and xt """ self.xs = xs self.xt = xt @@ -843,7 +908,8 @@ class OTDA_mapping_kernel(OTDA_mapping_linear): if self.computed: K = kernel( - x, self.xs, method=self.kernel, sigma=self.sigma, **self.kwargs) + x, self.xs, method=self.kernel, sigma=self.sigma, + **self.kwargs) if self.bias: K = np.hstack((K, np.ones((x.shape[0], 1)))) return K.dot(self.L) -- cgit v1.2.3 From ca9c9d6d8ecef6a38e0fd6240538a8af35ad06f5 Mon Sep 17 00:00:00 2001 From: Slasnista Date: Fri, 28 Jul 2017 08:49:10 +0200 Subject: first proposal for OT wrappers --- ot/da.py | 179 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) diff --git a/ot/da.py b/ot/da.py index 1dd4011..f534bf5 100644 --- a/ot/da.py +++ b/ot/da.py @@ -916,3 +916,182 @@ class OTDA_mapping_kernel(OTDA_mapping_linear): else: print("Warning, model not fitted yet, returning None") return None + +############################################################################## +# proposal +############################################################################## + +from sklearn.base import BaseEstimator +from sklearn.metrics import pairwise_distances + +""" +- all methods have the same input parameters: Xs, Xt, ys, yt (what order ?) +- ref: is the entropic reg parameter +- eta: is the second reg parameter +- gamma_: is the optimal coupling +- mapping barycentric for the moment + +Questions: +- Cost matrix estimation: from sklearn or from internal function ? +- distribution estimation ? Look at Nathalie's approach +- should everything been done into the fit from BaseTransport ? +""" + + +class BaseTransport(BaseEstimator): + + def fit(self, Xs=None, ys=None, Xt=None, yt=None, method="sinkhorn"): + """fit: estimates the optimal coupling + + Parameters: + ----------- + - Xs: source samples, (ns samples, d features) numpy-like array + - ys: source labels + - Xt: target samples (nt samples, d features) numpy-like array + - yt: target labels + - method: algorithm to use to compute optimal coupling + (default: sinkhorn) + + Returns: + -------- + - self + """ + + # pairwise distance + Cost = pairwise_distances(Xs, Xt, metric=self.metric) + + if self.mode == "semisupervised": + print("TODO: modify cost matrix accordingly") + pass + + # distribution estimation: should we change it ? + mu_s = np.ones(Xs.shape[0]) / float(Xs.shape[0]) + mu_t = np.ones(Xt.shape[0]) / float(Xt.shape[0]) + + if method == "sinkhorn": + self.gamma_ = sinkhorn( + a=mu_s, b=mu_t, M=Cost, reg=self.reg, + 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): + """fit_transform + + Parameters: + ----------- + - Xs: source samples, (ns samples, d features) numpy-like array + - ys: source labels + - Xt: target samples (nt samples, d features) numpy-like array + - yt: target labels + + Returns: + -------- + - transp_Xt + """ + + return self.fit(Xs, ys, Xt, yt, self.method).transform(Xs, ys, Xt, yt) + + def transform(self, Xs=None, ys=None, Xt=None, yt=None): + """transform: as a convention transports source samples + onto target samples + + Parameters: + ----------- + - Xs: source samples, (ns samples, d features) numpy-like array + - ys: source labels + - Xt: target samples (nt samples, d features) numpy-like array + - yt: target labels + + Returns: + -------- + - transp_Xt + """ + + if self.mapping == "barycentric": + transp = self.gamma_ / np.sum(self.gamma_, 1)[:, None] + + # set nans to 0 + transp[~ np.isfinite(transp)] = 0 + + # compute transported samples + transp_Xs = np.dot(transp, Xt) + + return transp_Xs + + def inverse_transform(self, Xs=None, ys=None, Xt=None, yt=None): + """inverse_transform: as a convention transports target samples + onto source samples + + Parameters: + ----------- + - Xs: source samples, (ns samples, d features) numpy-like array + - ys: source labels + - Xt: target samples (nt samples, d features) numpy-like array + - yt: target labels + + Returns: + -------- + - transp_Xt + """ + + if self.mapping == "barycentric": + transp_ = self.gamma_.T / np.sum(self.gamma_, 0)[:, None] + + # set nans to 0 + transp_[~ np.isfinite(transp_)] = 0 + + # compute transported samples + transp_Xt = np.dot(transp_, Xs) + else: + print("mapping not yet implemented") + + return transp_Xt + + +class SinkhornTransport(BaseTransport): + """SinkhornTransport: class wrapper for optimal transport based on + Sinkhorn's algorithm + + Parameters + ---------- + - reg : parameter for entropic regularization + - mode: unsupervised (default) or semi supervised: controls whether + labels are taken into accout to construct the optimal coupling + - max_iter : maximum number of iterations + - tol : precision + - verbose : control verbosity + - log : control log + + Attributes + ---------- + - gamma_: optimal coupling estimated by the fit function + """ + + def __init__(self, reg=1., mode="unsupervised", max_iter=1000, + tol=10e-9, verbose=False, log=False, mapping="barycentric", + metric="sqeuclidean"): + self.reg = reg + self.mode = mode + self.max_iter = max_iter + self.tol = tol + self.verbose = verbose + self.log = log + self.mapping = mapping + self.metric = metric + self.method = "sinkhorn" + + def fit(self, Xs=None, ys=None, Xt=None, yt=None): + """_fit + """ + return super(SinkhornTransport, self).fit( + Xs, ys, Xt, yt, method=self.method) + + +if __name__ == "__main__": + print("Small test") + + st = SinkhornTransport() -- cgit v1.2.3 From 84adadd69aef12826aa3970d135d499ef4d13f64 Mon Sep 17 00:00:00 2001 From: Slasnista Date: Fri, 28 Jul 2017 14:52:36 +0200 Subject: small modifs according to NG proposals --- ot/da.py | 43 +++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/ot/da.py b/ot/da.py index f534bf5..a422f7c 100644 --- a/ot/da.py +++ b/ot/da.py @@ -926,8 +926,8 @@ from sklearn.metrics import pairwise_distances """ - all methods have the same input parameters: Xs, Xt, ys, yt (what order ?) -- ref: is the entropic reg parameter -- eta: is the second reg parameter +- reg_e: is the entropic reg parameter +- reg_cl: is the second reg parameter - gamma_: is the optimal coupling - mapping barycentric for the moment @@ -940,7 +940,7 @@ Questions: class BaseTransport(BaseEstimator): - def fit(self, Xs=None, ys=None, Xt=None, yt=None, method="sinkhorn"): + def fit(self, Xs=None, ys=None, Xt=None, yt=None, method=None): """fit: estimates the optimal coupling Parameters: @@ -964,13 +964,17 @@ class BaseTransport(BaseEstimator): print("TODO: modify cost matrix accordingly") pass - # distribution estimation: should we change it ? - mu_s = np.ones(Xs.shape[0]) / float(Xs.shape[0]) - mu_t = np.ones(Xt.shape[0]) / float(Xt.shape[0]) + # distribution estimation + if self.distribution == "uniform": + mu_s = np.ones(Xs.shape[0]) / float(Xs.shape[0]) + mu_t = np.ones(Xt.shape[0]) / float(Xt.shape[0]) + else: + print("TODO: implement kernelized approach") + # coupling estimation if method == "sinkhorn": self.gamma_ = sinkhorn( - a=mu_s, b=mu_t, M=Cost, reg=self.reg, + 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: @@ -1058,7 +1062,7 @@ class SinkhornTransport(BaseTransport): Parameters ---------- - - reg : parameter for entropic regularization + - reg_e : parameter for entropic regularization - mode: unsupervised (default) or semi supervised: controls whether labels are taken into accout to construct the optimal coupling - max_iter : maximum number of iterations @@ -1071,10 +1075,10 @@ class SinkhornTransport(BaseTransport): - gamma_: optimal coupling estimated by the fit function """ - def __init__(self, reg=1., mode="unsupervised", max_iter=1000, + def __init__(self, reg_e=1., mode="unsupervised", max_iter=1000, tol=10e-9, verbose=False, log=False, mapping="barycentric", - metric="sqeuclidean"): - self.reg = reg + metric="sqeuclidean", distribution="uniform"): + self.reg_e = reg_e self.mode = mode self.max_iter = max_iter self.tol = tol @@ -1082,11 +1086,26 @@ class SinkhornTransport(BaseTransport): self.log = log self.mapping = mapping self.metric = metric + self.distribution = distribution self.method = "sinkhorn" def fit(self, Xs=None, ys=None, Xt=None, yt=None): - """_fit + """fit + + Parameters: + ----------- + - Xs: source samples, (ns samples, d features) numpy-like array + - ys: source labels + - Xt: target samples (nt samples, d features) numpy-like array + - yt: target labels + - method: algorithm to use to compute optimal coupling + (default: sinkhorn) + + Returns: + -------- + - self """ + return super(SinkhornTransport, self).fit( Xs, ys, Xt, yt, method=self.method) -- cgit v1.2.3 From cd3397f852d8bf99e5de59069529b95d9ba00a05 Mon Sep 17 00:00:00 2001 From: Slasnista Date: Fri, 28 Jul 2017 15:34:36 +0200 Subject: integrate AG comments --- ot/da.py | 202 +++++++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 120 insertions(+), 82 deletions(-) diff --git a/ot/da.py b/ot/da.py index a422f7c..828efc2 100644 --- a/ot/da.py +++ b/ot/da.py @@ -940,21 +940,23 @@ Questions: class BaseTransport(BaseEstimator): - def fit(self, Xs=None, ys=None, Xt=None, yt=None, method=None): - """fit: estimates the optimal coupling - - Parameters: - ----------- - - Xs: source samples, (ns samples, d features) numpy-like array - - ys: source labels - - Xt: target samples (nt samples, d features) numpy-like array - - yt: target labels - - method: algorithm to use to compute optimal coupling - (default: sinkhorn) - - Returns: - -------- - - self + 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] + 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. """ # pairwise distance @@ -972,7 +974,7 @@ class BaseTransport(BaseEstimator): print("TODO: implement kernelized approach") # coupling estimation - if method == "sinkhorn": + 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, @@ -983,36 +985,43 @@ class BaseTransport(BaseEstimator): return self def fit_transform(self, Xs=None, ys=None, Xt=None, yt=None): - """fit_transform - - Parameters: - ----------- - - Xs: source samples, (ns samples, d features) numpy-like array - - ys: source labels - - Xt: target samples (nt samples, d features) numpy-like array - - yt: target labels - - Returns: - -------- - - transp_Xt + """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] + 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 + ------- + transp_Xs : array-like of shape = [n_source_samples, n_features] + The source samples samples. """ - return self.fit(Xs, ys, Xt, yt, self.method).transform(Xs, ys, Xt, yt) + return self.fit(Xs, ys, Xt, yt).transform(Xs, ys, Xt, yt) def transform(self, Xs=None, ys=None, Xt=None, yt=None): - """transform: as a convention transports source samples - onto target samples - - Parameters: - ----------- - - Xs: source samples, (ns samples, d features) numpy-like array - - ys: source labels - - Xt: target samples (nt samples, d features) numpy-like array - - yt: target labels - - Returns: - -------- - - transp_Xt + """Transports source samples Xs onto target ones Xt + 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 + ------- + transp_Xs : array-like of shape = [n_source_samples, n_features] + The transport source samples. """ if self.mapping == "barycentric": @@ -1027,19 +1036,21 @@ class BaseTransport(BaseEstimator): return transp_Xs def inverse_transform(self, Xs=None, ys=None, Xt=None, yt=None): - """inverse_transform: as a convention transports target samples - onto source samples - - Parameters: - ----------- - - Xs: source samples, (ns samples, d features) numpy-like array - - ys: source labels - - Xt: target samples (nt samples, d features) numpy-like array - - yt: target labels - - Returns: - -------- - - transp_Xt + """Transports target samples Xt onto target samples Xs + 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 + ------- + transp_Xt : array-like of shape = [n_source_samples, n_features] + The transported target samples. """ if self.mapping == "barycentric": @@ -1057,22 +1068,48 @@ class BaseTransport(BaseEstimator): class SinkhornTransport(BaseTransport): - """SinkhornTransport: class wrapper for optimal transport based on - Sinkhorn's algorithm + """Domain Adapatation OT method based on Sinkhorn Algorithm Parameters ---------- - - reg_e : parameter for entropic regularization - - mode: unsupervised (default) or semi supervised: controls whether - labels are taken into accout to construct the optimal coupling - - max_iter : maximum number of iterations - - tol : precision - - verbose : control verbosity - - log : control log - + reg_e : float, optional (default=1) + Entropic 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. + max_iter : int, float, optional (default=1000) + The minimum number of iteration before stopping the optimization + algorithm if no it has not converged + tol : float, optional (default=10e-9) + The precision required to stop the optimization algorithm. + 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 ---------- - - gamma_: optimal coupling estimated by the fit function + gamma_ : 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] M. Cuturi, Sinkhorn Distances : Lightspeed Computation of Optimal + Transport, Advances in Neural Information Processing Systems (NIPS) + 26, 2013 """ def __init__(self, reg_e=1., mode="unsupervised", max_iter=1000, @@ -1090,24 +1127,25 @@ class SinkhornTransport(BaseTransport): self.method = "sinkhorn" def fit(self, Xs=None, ys=None, Xt=None, yt=None): - """fit - - Parameters: - ----------- - - Xs: source samples, (ns samples, d features) numpy-like array - - ys: source labels - - Xt: target samples (nt samples, d features) numpy-like array - - yt: target labels - - method: algorithm to use to compute optimal coupling - (default: sinkhorn) - - Returns: - -------- - - self + """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. """ - return super(SinkhornTransport, self).fit( - Xs, ys, Xt, yt, method=self.method) + return super(SinkhornTransport, self).fit(Xs, ys, Xt, yt) if __name__ == "__main__": -- cgit v1.2.3 From bd7c7d2534980d3105d060dd24a444433422134d Mon Sep 17 00:00:00 2001 From: Slasnista Date: Mon, 31 Jul 2017 10:54:28 +0200 Subject: own BaseEstimator class written + rflamary comments addressed --- ot/da.py | 199 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 172 insertions(+), 27 deletions(-) diff --git a/ot/da.py b/ot/da.py index 828efc2..d30c821 100644 --- a/ot/da.py +++ b/ot/da.py @@ -921,21 +921,153 @@ class OTDA_mapping_kernel(OTDA_mapping_linear): # proposal ############################################################################## -from sklearn.base import BaseEstimator -from sklearn.metrics import pairwise_distances +# from sklearn.base import BaseEstimator +# from sklearn.metrics import pairwise_distances + +############################################################################## +# adapted from scikit-learn + +import warnings +# from .externals.six import string_types, iteritems -""" -- all methods have the same input parameters: Xs, Xt, ys, yt (what order ?) -- reg_e: is the entropic reg parameter -- reg_cl: is the second reg parameter -- gamma_: is the optimal coupling -- mapping barycentric for the moment - -Questions: -- Cost matrix estimation: from sklearn or from internal function ? -- distribution estimation ? Look at Nathalie's approach -- should everything been done into the fit from BaseTransport ? -""" + +class BaseEstimator(object): + """Base class for all estimators in scikit-learn + Notes + ----- + All estimators should specify all the parameters that can be set + at the class level in their ``__init__`` as explicit keyword + arguments (no ``*args`` or ``**kwargs``). + """ + + @classmethod + def _get_param_names(cls): + """Get parameter names for the estimator""" + try: + from inspect import signature + except ImportError: + from .externals.funcsigs import signature + # fetch the constructor or the original constructor before + # deprecation wrapping if any + init = getattr(cls.__init__, 'deprecated_original', cls.__init__) + if init is object.__init__: + # No explicit constructor to introspect + return [] + + # introspect the constructor arguments to find the model parameters + # to represent + init_signature = signature(init) + # Consider the constructor parameters excluding 'self' + parameters = [p for p in init_signature.parameters.values() + if p.name != 'self' and p.kind != p.VAR_KEYWORD] + for p in parameters: + if p.kind == p.VAR_POSITIONAL: + raise RuntimeError("scikit-learn estimators should always " + "specify their parameters in the signature" + " of their __init__ (no varargs)." + " %s with constructor %s doesn't " + " follow this convention." + % (cls, init_signature)) + # Extract and sort argument names excluding 'self' + return sorted([p.name for p in parameters]) + + 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 + Parameter names mapped to their values. + """ + out = dict() + for key in self._get_param_names(): + # We need deprecation warnings to always be on in order to + # catch deprecated param values. + # This is set in utils/__init__.py but it gets overwritten + # when running under python3 somehow. + warnings.simplefilter("always", DeprecationWarning) + try: + with warnings.catch_warnings(record=True) as w: + value = getattr(self, key, None) + if len(w) and w[0].category == DeprecationWarning: + # if the parameter is deprecated, don't show it + continue + finally: + warnings.filters.pop(0) + + # XXX: should we rather test if instance of estimator? + if deep and hasattr(value, 'get_params'): + deep_items = value.get_params().items() + out.update((key + '__' + k, val) for k, val in deep_items) + out[key] = value + return out + + 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 + """ + if not params: + # Simple optimisation to gain speed (inspect is slow) + return self + valid_params = self.get_params(deep=True) + # for key, value in iteritems(params): + for key, value in params.items(): + split = key.split('__', 1) + if len(split) > 1: + # nested objects case + name, sub_name = split + if name not in valid_params: + raise ValueError('Invalid parameter %s for estimator %s. ' + 'Check the list of available parameters ' + 'with `estimator.get_params().keys()`.' % + (name, self)) + sub_object = valid_params[name] + sub_object.set_params(**{sub_name: value}) + else: + # simple objects case + if key not in valid_params: + raise ValueError('Invalid parameter %s for estimator %s. ' + 'Check the list of available parameters ' + 'with `estimator.get_params().keys()`.' % + (key, self.__class__.__name__)) + setattr(self, key, value) + return self + + def __repr__(self): + from sklearn.base import _pprint + class_name = self.__class__.__name__ + return '%s(%s)' % (class_name, _pprint(self.get_params(deep=False), + offset=len(class_name),),) + + # __getstate__ and __setstate__ are omitted because they only contain + # conditionals that are not satisfied by our objects (e.g., + # ``if type(self).__module__.startswith('sklearn.')``. + + +def distribution_estimation_uniform(X): + """estimates a uniform distribution from an array of samples X + + Parameters + ---------- + X : array-like of shape = [n_samples, n_features] + The array of samples + Returns + ------- + mu : array-like, shape = [n_samples,] + The uniform distribution estimated from X + """ + + return np.ones(X.shape[0]) / float(X.shape[0]) class BaseTransport(BaseEstimator): @@ -960,18 +1092,19 @@ class BaseTransport(BaseEstimator): """ # pairwise distance - Cost = pairwise_distances(Xs, Xt, metric=self.metric) + Cost = dist(Xs, Xt, metric=self.metric) if self.mode == "semisupervised": print("TODO: modify cost matrix accordingly") pass # distribution estimation - if self.distribution == "uniform": - mu_s = np.ones(Xs.shape[0]) / float(Xs.shape[0]) - mu_t = np.ones(Xt.shape[0]) / float(Xt.shape[0]) - else: - print("TODO: implement kernelized approach") + mu_s = self.distribution_estimation(Xs) + mu_t = self.distribution_estimation(Xt) + + # store arrays of samples + self.Xs = Xs + self.Xt = Xt # coupling estimation if self.method == "sinkhorn": @@ -1024,14 +1157,19 @@ class BaseTransport(BaseEstimator): The transport source samples. """ - if self.mapping == "barycentric": + # TODO: check whether Xs is new or not + if self.Xs == Xs: + # perform standard barycentric mapping transp = self.gamma_ / np.sum(self.gamma_, 1)[:, None] # set nans to 0 transp[~ np.isfinite(transp)] = 0 # compute transported samples - transp_Xs = np.dot(transp, Xt) + transp_Xs = np.dot(transp, self.Xt) + else: + # perform out of sample mapping + print("out of sample mapping not yet implemented") return transp_Xs @@ -1053,16 +1191,19 @@ class BaseTransport(BaseEstimator): The transported target samples. """ - if self.mapping == "barycentric": + # TODO: check whether Xt is new or not + if self.Xt == Xt: + # perform standard barycentric mapping transp_ = self.gamma_.T / np.sum(self.gamma_, 0)[:, None] # set nans to 0 transp_[~ np.isfinite(transp_)] = 0 # compute transported samples - transp_Xt = np.dot(transp_, Xs) + transp_Xt = np.dot(transp_, self.Xs) else: - print("mapping not yet implemented") + # perform out of sample mapping + print("out of sample mapping not yet implemented") return transp_Xt @@ -1114,7 +1255,10 @@ class SinkhornTransport(BaseTransport): def __init__(self, reg_e=1., mode="unsupervised", max_iter=1000, tol=10e-9, verbose=False, log=False, mapping="barycentric", - metric="sqeuclidean", distribution="uniform"): + metric="sqeuclidean", + distribution_estimation=distribution_estimation_uniform, + out_of_sample_map='ferradans'): + self.reg_e = reg_e self.mode = mode self.max_iter = max_iter @@ -1123,8 +1267,9 @@ class SinkhornTransport(BaseTransport): self.log = log self.mapping = mapping self.metric = metric - self.distribution = distribution + 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): """Build a coupling matrix from source and target sets of samples -- cgit v1.2.3 From 122b5bf2c0c8b6ff7b46adf19c7dd72e62c85b1f 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(-) 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 d9be6c2da1c0953de1720f1e93f194c71699c3cd 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(-) 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 70be03461db45de50ecd073b9795093ead1ba5f5 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(-) 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 64880e721f45c56de4815dd41cd21b8570c9776f 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(+) 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 727077ad7db503955aea0751abf9f361f1d82af7 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(+) 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 0b005906f9d78adbf4d52d2ea9610eb3fde96a7c 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(-) 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 d793f1f73e6f816458d8b307762675aa9fa84d22 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(-) 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 778f4f76d7f162e7630c9ba5369a0e389e18433c Mon Sep 17 00:00:00 2001 From: Slasnista Date: Fri, 4 Aug 2017 14:02:06 +0200 Subject: reformat doc strings + remove useless log / verbose parameters for emd --- ot/da.py | 81 ++++++++++++++++++++++++++++++---------------------------------- 1 file changed, 38 insertions(+), 43 deletions(-) diff --git a/ot/da.py b/ot/da.py index 08e8a8d..92a8f12 100644 --- a/ot/da.py +++ b/ot/da.py @@ -1053,11 +1053,11 @@ def distribution_estimation_uniform(X): Parameters ---------- - X : array-like of shape = [n_samples, n_features] + X : array-like of 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 """ @@ -1071,13 +1071,13 @@ class BaseTransport(BaseEstimator): (Xs, ys) and (Xt, yt) Parameters ---------- - Xs : array-like of shape = [n_source_samples, n_features] + Xs : array-like of 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 of 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 ------- @@ -1122,17 +1122,17 @@ class BaseTransport(BaseEstimator): ones Xt Parameters ---------- - Xs : array-like of shape = [n_source_samples, n_features] + Xs : array-like of 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 of 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 of shape = (n_source_samples, n_features) The source samples samples. """ @@ -1142,17 +1142,17 @@ class BaseTransport(BaseEstimator): """Transports source samples Xs onto target ones Xt Parameters ---------- - Xs : array-like of shape = [n_source_samples, n_features] + Xs : array-like of 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 of 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 of shape = (n_source_samples, n_features) The transport source samples. """ @@ -1177,17 +1177,17 @@ class BaseTransport(BaseEstimator): """Transports target samples Xt onto target samples Xs Parameters ---------- - Xs : array-like of shape = [n_source_samples, n_features] + Xs : array-like of 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 of 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_Xt : array-like of shape = [n_source_samples, n_features] + transp_Xt : array-like of shape = (n_source_samples, n_features) The transported target samples. """ @@ -1278,13 +1278,13 @@ class SinkhornTransport(BaseTransport): (Xs, ys) and (Xt, yt) Parameters ---------- - Xs : array-like of shape = [n_source_samples, n_features] + Xs : array-like of 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 of 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 ------- @@ -1341,13 +1341,10 @@ class EMDTransport(BaseTransport): on Pattern Analysis and Machine Intelligence , vol.PP, no.99, pp.1-1 """ - def __init__(self, verbose=False, - log=False, metric="sqeuclidean", + def __init__(self, metric="sqeuclidean", distribution_estimation=distribution_estimation_uniform, out_of_sample_map='ferradans', limit_max=10): - self.verbose = verbose - self.log = log self.metric = metric self.limit_max = limit_max self.distribution_estimation = distribution_estimation @@ -1358,13 +1355,13 @@ class EMDTransport(BaseTransport): (Xs, ys) and (Xt, yt) Parameters ---------- - Xs : array-like of shape = [n_source_samples, n_features] + Xs : array-like of 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 of 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 ------- @@ -1377,8 +1374,6 @@ class EMDTransport(BaseTransport): # coupling estimation self.Coupling_ = emd( a=self.mu_s, b=self.mu_t, M=self.Cost, - # verbose=self.verbose, - # log=self.log ) return self @@ -1463,13 +1458,13 @@ class SinkhornLpl1Transport(BaseTransport): (Xs, ys) and (Xt, yt) Parameters ---------- - Xs : array-like of shape = [n_source_samples, n_features] + Xs : array-like of 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 of 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 ------- @@ -1568,13 +1563,13 @@ class SinkhornL1l2Transport(BaseTransport): (Xs, ys) and (Xt, yt) Parameters ---------- - Xs : array-like of shape = [n_source_samples, n_features] + Xs : array-like of 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 of 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 ------- -- cgit v1.2.3 From 738bfb1c560ff4e349f5083fc1f81a54e4be4980 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(-) 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 8a214292884a868ea805f22481faad2c9270a770 Mon Sep 17 00:00:00 2001 From: Slasnista Date: Fri, 4 Aug 2017 15:49:42 +0200 Subject: added new class MappingTransport to support linear and kernel mapping, not yet tested --- ot/da.py | 158 +++++++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 134 insertions(+), 24 deletions(-) diff --git a/ot/da.py b/ot/da.py index 87d056d..0616d17 100644 --- a/ot/da.py +++ b/ot/da.py @@ -1233,12 +1233,6 @@ class SinkhornTransport(BaseTransport): ---------- reg_e : float, optional (default=1) Entropic 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. max_iter : int, float, optional (default=1000) The minimum number of iteration before stopping the optimization algorithm if no it has not converged @@ -1324,12 +1318,6 @@ 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. @@ -1406,12 +1394,6 @@ class SinkhornLpl1Transport(BaseTransport): 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. @@ -1510,12 +1492,6 @@ class SinkhornL1l2Transport(BaseTransport): 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. @@ -1603,3 +1579,137 @@ class SinkhornL1l2Transport(BaseTransport): verbose=self.verbose, log=self.log) return self + + +class MappingTransport(BaseEstimator): + """MappingTransport: DA methods that aims at jointly estimating a optimal + transport coupling and the associated mapping + + Parameters + ---------- + mu : float, optional (default=1) + Weight for the linear OT loss (>0) + eta : float, optional (default=0.001) + Regularization term for the linear mapping L (>0) + bias : bool, optional (default=False) + Estimate linear mapping with constant bias + metric : string, optional (default="sqeuclidean") + The ground metric for the Wasserstein problem + kernel : string, optional (default="linear") + The kernel to use either linear or gaussian + sigma : float, optional (default=1) + The gaussian kernel parameter + max_iter : int, optional (default=100) + Max number of BCD iterations + tol : float, optional (default=1e-5) + Stop threshold on relative loss decrease (>0) + max_inner_iter : int, optional (default=10) + Max number of iterations (inner CG solver) + inner_tol : float, optional (default=1e-6) + Stop threshold on error (inner CG solver) (>0) + verbose : bool, optional (default=False) + Print information along iterations + log : bool, optional (default=False) + record log if True + + Attributes + ---------- + Coupling_ : the optimal coupling + Mapping_ : the mapping associated + + References + ---------- + + .. [8] M. Perrot, N. Courty, R. Flamary, A. Habrard, + "Mapping estimation for discrete optimal transport", + Neural Information Processing Systems (NIPS), 2016. + + """ + + 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): + + self.metric = metric + self.mu = mu + self.eta = eta + self.bias = bias + self.kernel = kernel + self.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 + + 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) + 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. + """ + + self.Xs = Xs + self.Xt = Xt + + if self.kernel == "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( + 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) + + return self + + def transform(self, Xs): + """Transports source samples Xs onto target ones Xt + Parameters + ---------- + Xs : array-like of shape = (n_source_samples, n_features) + The training input samples. + + Returns + ------- + transp_Xs : array-like of 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] + + # set nans to 0 + transp[~ np.isfinite(transp)] = 0 + + # compute transported samples + transp_Xs = np.dot(transp, self.Xt) + else: + if self.kernel == "gaussian": + K = kernel(Xs, self.Xs, method=self.kernel, sigma=self.sigma) + elif self.kernel == "linear": + K = Xs + if self.bias: + K = np.hstack((K, np.ones((Xs.shape[0], 1)))) + transp_Xs = K.dot(self.Mapping_) + + return transp_Xs -- cgit v1.2.3 From 8149e059be7f715834d11b365855f2684bd3d6f5 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(-) 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 791a4a6f215033a75d5f56cd16fe2412301bec14 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(-) 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 326d163db029515c338a963978a5d95948f78c29 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(-) 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 09302239b3e4e1a90c1a4e2d7a85b0af86b01365 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 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 2d4d0b46f88c66ebc5502c840703ba6ce8910376 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(-) 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 74ca2d77bca479785c581d2f48d87628d5c1444d Mon Sep 17 00:00:00 2001 From: Slasnista Date: Fri, 25 Aug 2017 15:12:20 +0200 Subject: refactoring examples according to new DA classes --- examples/da/plot_otda_classes.py | 142 +++++++++++++++++++++ examples/da/plot_otda_color_images.py | 151 ++++++++++++++++++++++ examples/da/plot_otda_d2.py | 163 ++++++++++++++++++++++++ examples/da/plot_otda_mapping.py | 119 +++++++++++++++++ examples/da/plot_otda_mapping_colors_images.py | 169 +++++++++++++++++++++++++ 5 files changed, 744 insertions(+) create mode 100644 examples/da/plot_otda_classes.py create mode 100644 examples/da/plot_otda_color_images.py create mode 100644 examples/da/plot_otda_d2.py create mode 100644 examples/da/plot_otda_mapping.py create mode 100644 examples/da/plot_otda_mapping_colors_images.py diff --git a/examples/da/plot_otda_classes.py b/examples/da/plot_otda_classes.py new file mode 100644 index 0000000..1bfe2bb --- /dev/null +++ b/examples/da/plot_otda_classes.py @@ -0,0 +1,142 @@ +# -*- coding: utf-8 -*- +""" +======================== +OT for domain adaptation +======================== + +This example introduces a domain adaptation in a 2D setting and the 4 OTDA +approaches currently supported in POT. + +""" + +# Authors: Remi Flamary +# Stanilslas Chambon +# +# License: MIT License + +import matplotlib.pylab as pl +import ot + + +# number of source and target points to generate +ns = 150 +nt = 150 + +Xs, ys = ot.datasets.get_data_classif('3gauss', ns) +Xt, yt = ot.datasets.get_data_classif('3gauss2', nt) + +# Instantiate the different transport algorithms and fit them + +# EMD Transport +ot_emd = ot.da.EMDTransport() +ot_emd.fit(Xs=Xs, Xt=Xt) + +# Sinkhorn Transport +ot_sinkhorn = ot.da.SinkhornTransport(reg_e=1e-1) +ot_sinkhorn.fit(Xs=Xs, Xt=Xt) + +# Sinkhorn Transport with Group lasso regularization +ot_lpl1 = ot.da.SinkhornLpl1Transport(reg_e=1e-1, reg_cl=1e0) +ot_lpl1.fit(Xs=Xs, ys=ys, Xt=Xt) + +# Sinkhorn Transport with Group lasso regularization l1l2 +ot_l1l2 = ot.da.SinkhornL1l2Transport(reg_e=1e-1, reg_cl=2e0, max_iter=20, + verbose=True) +ot_l1l2.fit(Xs=Xs, ys=ys, Xt=Xt) + +# transport source samples onto target samples +transp_Xs_emd = ot_emd.transform(Xs=Xs) +transp_Xs_sinkhorn = ot_sinkhorn.transform(Xs=Xs) +transp_Xs_lpl1 = ot_lpl1.transform(Xs=Xs) +transp_Xs_l1l2 = ot_l1l2.transform(Xs=Xs) + +############################################################################## +# Fig 1 : plots source and target samples +############################################################################## + +pl.figure(1, figsize=(10, 5)) +pl.subplot(1, 2, 1) +pl.scatter(Xs[:, 0], Xs[:, 1], c=ys, marker='+', label='Source samples') +pl.xticks([]) +pl.yticks([]) +pl.legend(loc=0) +pl.title('Source samples') + +pl.subplot(1, 2, 2) +pl.scatter(Xt[:, 0], Xt[:, 1], c=yt, marker='o', label='Target samples') +pl.xticks([]) +pl.yticks([]) +pl.legend(loc=0) +pl.title('Target samples') +pl.tight_layout() + +############################################################################## +# Fig 2 : plot optimal couplings and transported samples +############################################################################## + +param_img = {'interpolation': 'nearest', 'cmap': 'spectral'} + +pl.figure(2, figsize=(15, 8)) +pl.subplot(2, 4, 1) +pl.imshow(ot_emd.coupling_, **param_img) +pl.xticks([]) +pl.yticks([]) +pl.title('Optimal coupling\nEMDTransport') + +pl.subplot(2, 4, 2) +pl.imshow(ot_sinkhorn.coupling_, **param_img) +pl.xticks([]) +pl.yticks([]) +pl.title('Optimal coupling\nSinkhornTransport') + +pl.subplot(2, 4, 3) +pl.imshow(ot_lpl1.coupling_, **param_img) +pl.xticks([]) +pl.yticks([]) +pl.title('Optimal coupling\nSinkhornLpl1Transport') + +pl.subplot(2, 4, 4) +pl.imshow(ot_l1l2.coupling_, **param_img) +pl.xticks([]) +pl.yticks([]) +pl.title('Optimal coupling\nSinkhornL1l2Transport') + +pl.subplot(2, 4, 5) +pl.scatter(Xt[:, 0], Xt[:, 1], c=yt, marker='o', + label='Target samples', alpha=0.3) +pl.scatter(transp_Xs_emd[:, 0], transp_Xs_emd[:, 1], c=ys, + marker='+', label='Transp samples', s=30) +pl.xticks([]) +pl.yticks([]) +pl.title('Transported samples\nEmdTransport') +pl.legend(loc="lower left") + +pl.subplot(2, 4, 6) +pl.scatter(Xt[:, 0], Xt[:, 1], c=yt, marker='o', + label='Target samples', alpha=0.3) +pl.scatter(transp_Xs_sinkhorn[:, 0], transp_Xs_sinkhorn[:, 1], c=ys, + marker='+', label='Transp samples', s=30) +pl.xticks([]) +pl.yticks([]) +pl.title('Transported samples\nSinkhornTransport') + +pl.subplot(2, 4, 7) +pl.scatter(Xt[:, 0], Xt[:, 1], c=yt, marker='o', + label='Target samples', alpha=0.3) +pl.scatter(transp_Xs_lpl1[:, 0], transp_Xs_lpl1[:, 1], c=ys, + marker='+', label='Transp samples', s=30) +pl.xticks([]) +pl.yticks([]) +pl.title('Transported samples\nSinkhornLpl1Transport') + +pl.subplot(2, 4, 8) +pl.scatter(Xt[:, 0], Xt[:, 1], c=yt, marker='o', + label='Target samples', alpha=0.3) +pl.scatter(transp_Xs_l1l2[:, 0], transp_Xs_l1l2[:, 1], c=ys, + marker='+', label='Transp samples', s=30) +pl.xticks([]) +pl.yticks([]) +pl.title('Transported samples\nSinkhornL1l2Transport') +pl.tight_layout() + +pl.show() diff --git a/examples/da/plot_otda_color_images.py b/examples/da/plot_otda_color_images.py new file mode 100644 index 0000000..a46ac29 --- /dev/null +++ b/examples/da/plot_otda_color_images.py @@ -0,0 +1,151 @@ +# -*- coding: utf-8 -*- +""" +======================================================== +OT for domain adaptation with image color adaptation [6] +======================================================== + +This example presents a way of transferring colors between two image +with Optimal Transport as introduced in [6] + +[6] Ferradans, S., Papadakis, N., Peyre, G., & Aujol, J. F. (2014). +Regularized discrete optimal transport. +SIAM Journal on Imaging Sciences, 7(3), 1853-1882. +""" + +# Authors: Remi Flamary +# Stanilslas Chambon +# +# License: MIT License + +import numpy as np +from scipy import ndimage +import matplotlib.pylab as pl + +import ot + + +def im2mat(I): + """Converts and image to matrix (one pixel per line)""" + return I.reshape((I.shape[0] * I.shape[1], I.shape[2])) + + +def mat2im(X, shape): + """Converts back a matrix to an image""" + return X.reshape(shape) + + +def minmax(I): + return np.clip(I, 0, 1) + + +# Loading images +I1 = ndimage.imread('../../data/ocean_day.jpg').astype(np.float64) / 256 +I2 = ndimage.imread('../../data/ocean_sunset.jpg').astype(np.float64) / 256 + +X1 = im2mat(I1) +X2 = im2mat(I2) + +# training samples +nb = 1000 +idx1 = np.random.randint(X1.shape[0], size=(nb,)) +idx2 = np.random.randint(X2.shape[0], size=(nb,)) + +Xs = X1[idx1, :] +Xt = X2[idx2, :] + +# EMDTransport +ot_emd = ot.da.EMDTransport() +ot_emd.fit(Xs=Xs, Xt=Xt) + +# SinkhornTransport +ot_sinkhorn = ot.da.SinkhornTransport(reg_e=1e-1) +ot_sinkhorn.fit(Xs=Xs, Xt=Xt) + +# prediction between images (using out of sample prediction as in [6]) +transp_Xs_emd = ot_emd.transform(Xs=X1) +transp_Xt_emd = ot_emd.inverse_transform(Xt=X2) + +transp_Xs_sinkhorn = ot_emd.transform(Xs=X1) +transp_Xt_sinkhorn = ot_emd.inverse_transform(Xt=X2) + +I1t = minmax(mat2im(transp_Xs_emd, I1.shape)) +I2t = minmax(mat2im(transp_Xt_emd, I2.shape)) + +I1te = minmax(mat2im(transp_Xs_sinkhorn, I1.shape)) +I2te = minmax(mat2im(transp_Xt_sinkhorn, I2.shape)) + +############################################################################## +# plot original image +############################################################################## + +pl.figure(1, figsize=(6.4, 3)) + +pl.subplot(1, 2, 1) +pl.imshow(I1) +pl.axis('off') +pl.title('Image 1') + +pl.subplot(1, 2, 2) +pl.imshow(I2) +pl.axis('off') +pl.title('Image 2') + +############################################################################## +# scatter plot of colors +############################################################################## + +pl.figure(2, figsize=(6.4, 3)) + +pl.subplot(1, 2, 1) +pl.scatter(Xs[:, 0], Xs[:, 2], c=Xs) +pl.axis([0, 1, 0, 1]) +pl.xlabel('Red') +pl.ylabel('Blue') +pl.title('Image 1') + +pl.subplot(1, 2, 2) +pl.scatter(Xt[:, 0], Xt[:, 2], c=Xt) +pl.axis([0, 1, 0, 1]) +pl.xlabel('Red') +pl.ylabel('Blue') +pl.title('Image 2') +pl.tight_layout() + +############################################################################## +# plot new images +############################################################################## + +pl.figure(3, figsize=(8, 4)) + +pl.subplot(2, 3, 1) +pl.imshow(I1) +pl.axis('off') +pl.title('Image 1') + +pl.subplot(2, 3, 2) +pl.imshow(I1t) +pl.axis('off') +pl.title('Image 1 Adapt') + +pl.subplot(2, 3, 3) +pl.imshow(I1te) +pl.axis('off') +pl.title('Image 1 Adapt (reg)') + +pl.subplot(2, 3, 4) +pl.imshow(I2) +pl.axis('off') +pl.title('Image 2') + +pl.subplot(2, 3, 5) +pl.imshow(I2t) +pl.axis('off') +pl.title('Image 2 Adapt') + +pl.subplot(2, 3, 6) +pl.imshow(I2te) +pl.axis('off') +pl.title('Image 2 Adapt (reg)') +pl.tight_layout() + +pl.show() diff --git a/examples/da/plot_otda_d2.py b/examples/da/plot_otda_d2.py new file mode 100644 index 0000000..78c0372 --- /dev/null +++ b/examples/da/plot_otda_d2.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +""" +============================== +OT for empirical distributions +============================== + +This example introduces a domain adaptation in a 2D setting. It explicits +the problem of domain adaptation and introduces some optimal transport +approaches to solve it. + +Quantities such as optimal couplings, greater coupling coefficients and +transported samples are represented in order to give a visual understanding +of what the transport methods are doing. +""" + +# Authors: Remi Flamary +# Stanilslas Chambon +# +# License: MIT License + +import matplotlib.pylab as pl +import ot + +# number of source and target points to generate +ns = 150 +nt = 150 + +Xs, ys = ot.datasets.get_data_classif('3gauss', ns) +Xt, yt = ot.datasets.get_data_classif('3gauss2', nt) + +# Cost matrix +M = ot.dist(Xs, Xt) + +# Instantiate the different transport algorithms and fit them + +# EMD Transport +ot_emd = ot.da.EMDTransport() +ot_emd.fit(Xs=Xs, Xt=Xt) + +# Sinkhorn Transport +ot_sinkhorn = ot.da.SinkhornTransport(reg_e=1e-1) +ot_sinkhorn.fit(Xs=Xs, Xt=Xt) + +# Sinkhorn Transport with Group lasso regularization +ot_lpl1 = ot.da.SinkhornLpl1Transport(reg_e=1e-1, reg_cl=1e0) +ot_lpl1.fit(Xs=Xs, ys=ys, Xt=Xt) + +# transport source samples onto target samples +transp_Xs_emd = ot_emd.transform(Xs=Xs) +transp_Xs_sinkhorn = ot_sinkhorn.transform(Xs=Xs) +transp_Xs_lpl1 = ot_lpl1.transform(Xs=Xs) + +############################################################################## +# Fig 1 : plots source and target samples + matrix of pairwise distance +############################################################################## + +pl.figure(1, figsize=(10, 10)) +pl.subplot(2, 2, 1) +pl.scatter(Xs[:, 0], Xs[:, 1], c=ys, marker='+', label='Source samples') +pl.xticks([]) +pl.yticks([]) +pl.legend(loc=0) +pl.title('Source samples') + +pl.subplot(2, 2, 2) +pl.scatter(Xt[:, 0], Xt[:, 1], c=yt, marker='o', label='Target samples') +pl.xticks([]) +pl.yticks([]) +pl.legend(loc=0) +pl.title('Target samples') + +pl.subplot(2, 2, 3) +pl.imshow(M, interpolation='nearest') +pl.xticks([]) +pl.yticks([]) +pl.title('Matrix of pairwise distances') +pl.tight_layout() + +############################################################################## +# Fig 2 : plots optimal couplings for the different methods +############################################################################## + +pl.figure(2, figsize=(10, 6)) + +pl.subplot(2, 3, 1) +pl.imshow(ot_emd.coupling_, interpolation='nearest') +pl.xticks([]) +pl.yticks([]) +pl.title('Optimal coupling\nEMDTransport') + +pl.subplot(2, 3, 2) +pl.imshow(ot_sinkhorn.coupling_, interpolation='nearest') +pl.xticks([]) +pl.yticks([]) +pl.title('Optimal coupling\nSinkhornTransport') + +pl.subplot(2, 3, 3) +pl.imshow(ot_lpl1.coupling_, interpolation='nearest') +pl.xticks([]) +pl.yticks([]) +pl.title('Optimal coupling\nSinkhornLpl1Transport') + +pl.subplot(2, 3, 4) +ot.plot.plot2D_samples_mat(Xs, Xt, ot_emd.coupling_, c=[.5, .5, 1]) +pl.scatter(Xs[:, 0], Xs[:, 1], c=ys, marker='+', label='Source samples') +pl.scatter(Xt[:, 0], Xt[:, 1], c=yt, marker='o', label='Target samples') +pl.xticks([]) +pl.yticks([]) +pl.title('Main coupling coefficients\nEMDTransport') + +pl.subplot(2, 3, 5) +ot.plot.plot2D_samples_mat(Xs, Xt, ot_sinkhorn.coupling_, c=[.5, .5, 1]) +pl.scatter(Xs[:, 0], Xs[:, 1], c=ys, marker='+', label='Source samples') +pl.scatter(Xt[:, 0], Xt[:, 1], c=yt, marker='o', label='Target samples') +pl.xticks([]) +pl.yticks([]) +pl.title('Main coupling coefficients\nSinkhornTransport') + +pl.subplot(2, 3, 6) +ot.plot.plot2D_samples_mat(Xs, Xt, ot_lpl1.coupling_, c=[.5, .5, 1]) +pl.scatter(Xs[:, 0], Xs[:, 1], c=ys, marker='+', label='Source samples') +pl.scatter(Xt[:, 0], Xt[:, 1], c=yt, marker='o', label='Target samples') +pl.xticks([]) +pl.yticks([]) +pl.title('Main coupling coefficients\nSinkhornLpl1Transport') +pl.tight_layout() + +############################################################################## +# Fig 3 : plot transported samples +############################################################################## + +# display transported samples +pl.figure(4, figsize=(10, 4)) +pl.subplot(1, 3, 1) +pl.scatter(Xt[:, 0], Xt[:, 1], c=yt, marker='o', + label='Target samples', alpha=0.5) +pl.scatter(transp_Xs_emd[:, 0], transp_Xs_emd[:, 1], c=ys, + marker='+', label='Transp samples', s=30) +pl.title('Transported samples\nEmdTransport') +pl.legend(loc=0) +pl.xticks([]) +pl.yticks([]) + +pl.subplot(1, 3, 2) +pl.scatter(Xt[:, 0], Xt[:, 1], c=yt, marker='o', + label='Target samples', alpha=0.5) +pl.scatter(transp_Xs_sinkhorn[:, 0], transp_Xs_sinkhorn[:, 1], c=ys, + marker='+', label='Transp samples', s=30) +pl.title('Transported samples\nSinkhornTransport') +pl.xticks([]) +pl.yticks([]) + +pl.subplot(1, 3, 3) +pl.scatter(Xt[:, 0], Xt[:, 1], c=yt, marker='o', + label='Target samples', alpha=0.5) +pl.scatter(transp_Xs_lpl1[:, 0], transp_Xs_lpl1[:, 1], c=ys, + marker='+', label='Transp samples', s=30) +pl.title('Transported samples\nSinkhornLpl1Transport') +pl.xticks([]) +pl.yticks([]) + +pl.tight_layout() +pl.show() diff --git a/examples/da/plot_otda_mapping.py b/examples/da/plot_otda_mapping.py new file mode 100644 index 0000000..ed234f5 --- /dev/null +++ b/examples/da/plot_otda_mapping.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +""" +=============================================== +OT mapping estimation for domain adaptation [8] +=============================================== + +This example presents how to use MappingTransport to estimate at the same +time both the coupling transport and approximate the transport map with either +a linear or a kernelized mapping as introduced in [8] + +[8] M. Perrot, N. Courty, R. Flamary, A. Habrard, + "Mapping estimation for discrete optimal transport", + Neural Information Processing Systems (NIPS), 2016. +""" + +# Authors: Remi Flamary +# Stanilslas Chambon +# +# License: MIT License + +import numpy as np +import matplotlib.pylab as pl +import ot + + +np.random.seed(0) + +############################################################################## +# generate +############################################################################## + +n = 100 # nb samples in source and target datasets +theta = 2 * np.pi / 20 +nz = 0.1 +Xs, ys = ot.datasets.get_data_classif('gaussrot', n, nz=nz) +Xs_new, _ = ot.datasets.get_data_classif('gaussrot', n, nz=nz) +Xt, yt = ot.datasets.get_data_classif('gaussrot', n, theta=theta, nz=nz) + +# one of the target mode changes its variance (no linear mapping) +Xt[yt == 2] *= 3 +Xt = Xt + 4 + + +# MappingTransport with linear kernel +ot_mapping_linear = ot.da.MappingTransport( + kernel="linear", mu=1e0, eta=1e-8, bias=True, + max_iter=20, verbose=True) + +ot_mapping_linear.fit( + Xs=Xs, Xt=Xt) + +# for original source samples, transform applies barycentric mapping +transp_Xs_linear = ot_mapping_linear.transform(Xs=Xs) + +# for out of source samples, transform applies the linear mapping +transp_Xs_linear_new = ot_mapping_linear.transform(Xs=Xs_new) + + +# MappingTransport with gaussian kernel +ot_mapping_gaussian = ot.da.MappingTransport( + kernel="gaussian", eta=1e-5, mu=1e-1, bias=True, sigma=1, + max_iter=10, verbose=True) +ot_mapping_gaussian.fit(Xs=Xs, Xt=Xt) + +# for original source samples, transform applies barycentric mapping +transp_Xs_gaussian = ot_mapping_gaussian.transform(Xs=Xs) + +# for out of source samples, transform applies the gaussian mapping +transp_Xs_gaussian_new = ot_mapping_gaussian.transform(Xs=Xs_new) + + +############################################################################## +# plot data +############################################################################## + +pl.figure(1, (10, 5)) +pl.clf() +pl.scatter(Xs[:, 0], Xs[:, 1], c=ys, marker='+', label='Source samples') +pl.scatter(Xt[:, 0], Xt[:, 1], c=yt, marker='o', label='Target samples') +pl.legend(loc=0) +pl.title('Source and target distributions') + +############################################################################## +# plot transported samples +############################################################################## + +pl.figure(2) +pl.clf() +pl.subplot(2, 2, 1) +pl.scatter(Xt[:, 0], Xt[:, 1], c=yt, marker='o', + label='Target samples', alpha=.2) +pl.scatter(transp_Xs_linear[:, 0], transp_Xs_linear[:, 1], c=ys, marker='+', + label='Mapped source samples') +pl.title("Bary. mapping (linear)") +pl.legend(loc=0) + +pl.subplot(2, 2, 2) +pl.scatter(Xt[:, 0], Xt[:, 1], c=yt, marker='o', + label='Target samples', alpha=.2) +pl.scatter(transp_Xs_linear_new[:, 0], transp_Xs_linear_new[:, 1], + c=ys, marker='+', label='Learned mapping') +pl.title("Estim. mapping (linear)") + +pl.subplot(2, 2, 3) +pl.scatter(Xt[:, 0], Xt[:, 1], c=yt, marker='o', + label='Target samples', alpha=.2) +pl.scatter(transp_Xs_gaussian[:, 0], transp_Xs_gaussian[:, 1], c=ys, + marker='+', label='barycentric mapping') +pl.title("Bary. mapping (kernel)") + +pl.subplot(2, 2, 4) +pl.scatter(Xt[:, 0], Xt[:, 1], c=yt, marker='o', + label='Target samples', alpha=.2) +pl.scatter(transp_Xs_gaussian_new[:, 0], transp_Xs_gaussian_new[:, 1], c=ys, + marker='+', label='Learned mapping') +pl.title("Estim. mapping (kernel)") +pl.tight_layout() + +pl.show() diff --git a/examples/da/plot_otda_mapping_colors_images.py b/examples/da/plot_otda_mapping_colors_images.py new file mode 100644 index 0000000..56b5a6f --- /dev/null +++ b/examples/da/plot_otda_mapping_colors_images.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- +""" +==================================================================================== +OT for domain adaptation with image color adaptation [6] with mapping estimation [8] +==================================================================================== + +[6] Ferradans, S., Papadakis, N., Peyre, G., & Aujol, J. F. (2014). Regularized + discrete optimal transport. SIAM Journal on Imaging Sciences, 7(3), + 1853-1882. +[8] M. Perrot, N. Courty, R. Flamary, A. Habrard, "Mapping estimation for + discrete optimal transport", Neural Information Processing Systems (NIPS), + 2016. + +""" + +# Authors: Remi Flamary +# Stanilslas Chambon +# +# License: MIT License + +import numpy as np +from scipy import ndimage +import matplotlib.pylab as pl +import ot + + +def im2mat(I): + """Converts and image to matrix (one pixel per line)""" + return I.reshape((I.shape[0] * I.shape[1], I.shape[2])) + + +def mat2im(X, shape): + """Converts back a matrix to an image""" + return X.reshape(shape) + + +def minmax(I): + return np.clip(I, 0, 1) + + +############################################################################## +# Generate data +############################################################################## + +# Loading images +# I1 = ndimage.imread('../../data/ocean_day.jpg').astype(np.float64) / 256 +# I2 = ndimage.imread('../../data/ocean_sunset.jpg').astype(np.float64) / 256 + +I1 = ndimage.imread('data/ocean_day.jpg').astype(np.float64) / 256 +I2 = ndimage.imread('data/ocean_sunset.jpg').astype(np.float64) / 256 + + +X1 = im2mat(I1) +X2 = im2mat(I2) + +# training samples +nb = 1000 +idx1 = np.random.randint(X1.shape[0], size=(nb,)) +idx2 = np.random.randint(X2.shape[0], size=(nb,)) + +Xs = X1[idx1, :] +Xt = X2[idx2, :] + + +############################################################################## +# Domain adaptation for pixel distribution transfer +############################################################################## + +# EMDTransport +ot_emd = ot.da.EMDTransport() +ot_emd.fit(Xs=Xs, Xt=Xt) +transp_Xs_emd = ot_emd.transform(Xs=X1) +Image_emd = minmax(mat2im(transp_Xs_emd, I1.shape)) + +# SinkhornTransport +ot_sinkhorn = ot.da.SinkhornTransport(reg_e=1e-1) +ot_sinkhorn.fit(Xs=Xs, Xt=Xt) +transp_Xs_sinkhorn = ot_emd.transform(Xs=X1) +Image_sinkhorn = minmax(mat2im(transp_Xs_sinkhorn, I1.shape)) + +ot_mapping_linear = ot.da.MappingTransport( + mu=1e0, eta=1e-8, bias=True, max_iter=20, verbose=True) +ot_mapping_linear.fit(Xs=Xs, Xt=Xt) + +X1tl = ot_mapping_linear.transform(X1) +Image_mapping_linear = minmax(mat2im(X1tl, I1.shape)) + +ot_mapping_gaussian = ot.da.MappingTransport( + mu=1e0, eta=1e-2, sigma=1, bias=False, max_iter=10, verbose=True) +ot_mapping_gaussian.fit(Xs=Xs, Xt=Xt) + +X1tn = ot_mapping_gaussian.transform(X1) # use the estimated mapping +Image_mapping_gaussian = minmax(mat2im(X1tn, I1.shape)) + +############################################################################## +# plot original images +############################################################################## + +pl.figure(1, figsize=(6.4, 3)) +pl.subplot(1, 2, 1) +pl.imshow(I1) +pl.axis('off') +pl.title('Image 1') + +pl.subplot(1, 2, 2) +pl.imshow(I2) +pl.axis('off') +pl.title('Image 2') +pl.tight_layout() + +############################################################################## +# plot pixel values distribution +############################################################################## + +pl.figure(2, figsize=(6.4, 5)) + +pl.subplot(1, 2, 1) +pl.scatter(Xs[:, 0], Xs[:, 2], c=Xs) +pl.axis([0, 1, 0, 1]) +pl.xlabel('Red') +pl.ylabel('Blue') +pl.title('Image 1') + +pl.subplot(1, 2, 2) +pl.scatter(Xt[:, 0], Xt[:, 2], c=Xt) +pl.axis([0, 1, 0, 1]) +pl.xlabel('Red') +pl.ylabel('Blue') +pl.title('Image 2') +pl.tight_layout() + +############################################################################## +# plot transformed images +############################################################################## + +pl.figure(2, figsize=(10, 5)) + +pl.subplot(2, 3, 1) +pl.imshow(I1) +pl.axis('off') +pl.title('Im. 1') + +pl.subplot(2, 3, 4) +pl.imshow(I2) +pl.axis('off') +pl.title('Im. 2') + +pl.subplot(2, 3, 2) +pl.imshow(Image_emd) +pl.axis('off') +pl.title('EmdTransport') + +pl.subplot(2, 3, 5) +pl.imshow(Image_sinkhorn) +pl.axis('off') +pl.title('SinkhornTransport') + +pl.subplot(2, 3, 3) +pl.imshow(Image_mapping_linear) +pl.axis('off') +pl.title('MappingTransport (linear)') + +pl.subplot(2, 3, 6) +pl.imshow(Image_mapping_gaussian) +pl.axis('off') +pl.title('MappingTransport (gaussian)') +pl.tight_layout() + +pl.show() -- cgit v1.2.3 From f80693b545ac40817cb95eb31260fe7598514b96 Mon Sep 17 00:00:00 2001 From: Slasnista Date: Fri, 25 Aug 2017 15:15:52 +0200 Subject: small corrections for examples --- examples/da/plot_otda_classes.py | 2 +- examples/da/plot_otda_color_images.py | 2 +- examples/da/plot_otda_d2.py | 2 +- examples/da/plot_otda_mapping.py | 2 +- examples/da/plot_otda_mapping_colors_images.py | 6 +++--- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/da/plot_otda_classes.py b/examples/da/plot_otda_classes.py index 1bfe2bb..e5c82fb 100644 --- a/examples/da/plot_otda_classes.py +++ b/examples/da/plot_otda_classes.py @@ -10,7 +10,7 @@ approaches currently supported in POT. """ # Authors: Remi Flamary -# Stanilslas Chambon +# Stanislas Chambon # # License: MIT License diff --git a/examples/da/plot_otda_color_images.py b/examples/da/plot_otda_color_images.py index a46ac29..bca7350 100644 --- a/examples/da/plot_otda_color_images.py +++ b/examples/da/plot_otda_color_images.py @@ -13,7 +13,7 @@ SIAM Journal on Imaging Sciences, 7(3), 1853-1882. """ # Authors: Remi Flamary -# Stanilslas Chambon +# Stanislas Chambon # # License: MIT License diff --git a/examples/da/plot_otda_d2.py b/examples/da/plot_otda_d2.py index 78c0372..1d2192f 100644 --- a/examples/da/plot_otda_d2.py +++ b/examples/da/plot_otda_d2.py @@ -14,7 +14,7 @@ of what the transport methods are doing. """ # Authors: Remi Flamary -# Stanilslas Chambon +# Stanislas Chambon # # License: MIT License diff --git a/examples/da/plot_otda_mapping.py b/examples/da/plot_otda_mapping.py index ed234f5..6d83507 100644 --- a/examples/da/plot_otda_mapping.py +++ b/examples/da/plot_otda_mapping.py @@ -14,7 +14,7 @@ a linear or a kernelized mapping as introduced in [8] """ # Authors: Remi Flamary -# Stanilslas Chambon +# Stanislas Chambon # # License: MIT License diff --git a/examples/da/plot_otda_mapping_colors_images.py b/examples/da/plot_otda_mapping_colors_images.py index 56b5a6f..05d9046 100644 --- a/examples/da/plot_otda_mapping_colors_images.py +++ b/examples/da/plot_otda_mapping_colors_images.py @@ -14,7 +14,7 @@ OT for domain adaptation with image color adaptation [6] with mapping estimation """ # Authors: Remi Flamary -# Stanilslas Chambon +# Stanislas Chambon # # License: MIT License @@ -82,14 +82,14 @@ ot_mapping_linear = ot.da.MappingTransport( mu=1e0, eta=1e-8, bias=True, max_iter=20, verbose=True) ot_mapping_linear.fit(Xs=Xs, Xt=Xt) -X1tl = ot_mapping_linear.transform(X1) +X1tl = ot_mapping_linear.transform(Xs=X1) Image_mapping_linear = minmax(mat2im(X1tl, I1.shape)) ot_mapping_gaussian = ot.da.MappingTransport( mu=1e0, eta=1e-2, sigma=1, bias=False, max_iter=10, verbose=True) ot_mapping_gaussian.fit(Xs=Xs, Xt=Xt) -X1tn = ot_mapping_gaussian.transform(X1) # use the estimated mapping +X1tn = ot_mapping_gaussian.transform(Xs=X1) # use the estimated mapping Image_mapping_gaussian = minmax(mat2im(X1tn, I1.shape)) ############################################################################## -- cgit v1.2.3 From 892d7ce10912de3d07692b5083578932a32ab61f Mon Sep 17 00:00:00 2001 From: Slasnista Date: Fri, 25 Aug 2017 15:18:05 +0200 Subject: set properly path of data --- examples/da/plot_otda_mapping_colors_images.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/examples/da/plot_otda_mapping_colors_images.py b/examples/da/plot_otda_mapping_colors_images.py index 05d9046..4209020 100644 --- a/examples/da/plot_otda_mapping_colors_images.py +++ b/examples/da/plot_otda_mapping_colors_images.py @@ -43,11 +43,8 @@ def minmax(I): ############################################################################## # Loading images -# I1 = ndimage.imread('../../data/ocean_day.jpg').astype(np.float64) / 256 -# I2 = ndimage.imread('../../data/ocean_sunset.jpg').astype(np.float64) / 256 - -I1 = ndimage.imread('data/ocean_day.jpg').astype(np.float64) / 256 -I2 = ndimage.imread('data/ocean_sunset.jpg').astype(np.float64) / 256 +I1 = ndimage.imread('../../data/ocean_day.jpg').astype(np.float64) / 256 +I2 = ndimage.imread('../../data/ocean_sunset.jpg').astype(np.float64) / 256 X1 = im2mat(I1) -- cgit v1.2.3 From 55840f6bccadd79caf722d86f06da857e3045453 Mon Sep 17 00:00:00 2001 From: Slasnista Date: Mon, 28 Aug 2017 10:43:31 +0200 Subject: move no da objects into utils.py --- README.md | 2 + ot/da.py | 136 +-------------------------------- ot/deprecation.py | 103 ------------------------- ot/utils.py | 219 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 222 insertions(+), 238 deletions(-) delete mode 100644 ot/deprecation.py diff --git a/README.md b/README.md index 7a65106..27b4643 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,8 @@ The contributors to this library are: * [Laetitia Chapel](http://people.irisa.fr/Laetitia.Chapel/) * [Michael Perrot](http://perso.univ-st-etienne.fr/pem82055/) (Mapping estimation) * [Léo Gautheron](https://github.com/aje) (GPU implementation) +* [Nathalie Gayraud]() +* [Stanislas Chambon](https://slasnista.github.io/) This toolbox benefit a lot from open source research and we would like to thank the following persons for providing some code (in various languages): diff --git a/ot/da.py b/ot/da.py index 5a34979..8c62669 100644 --- a/ot/da.py +++ b/ot/da.py @@ -10,14 +10,13 @@ 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 .utils import deprecated, BaseEstimator from .optim import cg from .optim import gcg -from .deprecation import deprecated def sinkhorn_lpl1_mm(a, labels_a, b, M, reg, eta=0.1, numItermax=10, @@ -936,139 +935,6 @@ class OTDA_mapping_kernel(OTDA_mapping_linear): print("Warning, model not fitted yet, returning None") return None -############################################################################## -# proposal -############################################################################## - - -# adapted from sklearn - -class BaseEstimator(object): - """Base class for all estimators in scikit-learn - Notes - ----- - All estimators should specify all the parameters that can be set - at the class level in their ``__init__`` as explicit keyword - arguments (no ``*args`` or ``**kwargs``). - """ - - @classmethod - def _get_param_names(cls): - """Get parameter names for the estimator""" - try: - from inspect import signature - except ImportError: - from .externals.funcsigs import signature - # fetch the constructor or the original constructor before - # deprecation wrapping if any - init = getattr(cls.__init__, 'deprecated_original', cls.__init__) - if init is object.__init__: - # No explicit constructor to introspect - return [] - - # introspect the constructor arguments to find the model parameters - # to represent - init_signature = signature(init) - # Consider the constructor parameters excluding 'self' - parameters = [p for p in init_signature.parameters.values() - if p.name != 'self' and p.kind != p.VAR_KEYWORD] - for p in parameters: - if p.kind == p.VAR_POSITIONAL: - raise RuntimeError("scikit-learn estimators should always " - "specify their parameters in the signature" - " of their __init__ (no varargs)." - " %s with constructor %s doesn't " - " follow this convention." - % (cls, init_signature)) - # Extract and sort argument names excluding 'self' - return sorted([p.name for p in parameters]) - - 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 - Parameter names mapped to their values. - """ - out = dict() - for key in self._get_param_names(): - # We need deprecation warnings to always be on in order to - # catch deprecated param values. - # This is set in utils/__init__.py but it gets overwritten - # when running under python3 somehow. - warnings.simplefilter("always", DeprecationWarning) - try: - with warnings.catch_warnings(record=True) as w: - value = getattr(self, key, None) - if len(w) and w[0].category == DeprecationWarning: - # if the parameter is deprecated, don't show it - continue - finally: - warnings.filters.pop(0) - - # XXX: should we rather test if instance of estimator? - if deep and hasattr(value, 'get_params'): - deep_items = value.get_params().items() - out.update((key + '__' + k, val) for k, val in deep_items) - out[key] = value - return out - - 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 - """ - if not params: - # Simple optimisation to gain speed (inspect is slow) - return self - valid_params = self.get_params(deep=True) - # for key, value in iteritems(params): - for key, value in params.items(): - split = key.split('__', 1) - if len(split) > 1: - # nested objects case - name, sub_name = split - if name not in valid_params: - raise ValueError('Invalid parameter %s for estimator %s. ' - 'Check the list of available parameters ' - 'with `estimator.get_params().keys()`.' % - (name, self)) - sub_object = valid_params[name] - sub_object.set_params(**{sub_name: value}) - else: - # simple objects case - if key not in valid_params: - raise ValueError('Invalid parameter %s for estimator %s. ' - 'Check the list of available parameters ' - 'with `estimator.get_params().keys()`.' % - (key, self.__class__.__name__)) - setattr(self, key, value) - return self - - def __repr__(self): - from sklearn.base import _pprint - class_name = self.__class__.__name__ - return '%s(%s)' % (class_name, _pprint(self.get_params(deep=False), - offset=len(class_name),),) - - # __getstate__ and __setstate__ are omitted because they only contain - # conditionals that are not satisfied by our objects (e.g., - # ``if type(self).__module__.startswith('sklearn.')``. - def distribution_estimation_uniform(X): """estimates a uniform distribution from an array of samples X diff --git a/ot/deprecation.py b/ot/deprecation.py deleted file mode 100644 index 2b16427..0000000 --- a/ot/deprecation.py +++ /dev/null @@ -1,103 +0,0 @@ -""" - 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/ot/utils.py b/ot/utils.py index 2b2f8b3..29ad536 100644 --- a/ot/utils.py +++ b/ot/utils.py @@ -13,6 +13,9 @@ import time import numpy as np from scipy.spatial.distance import cdist +import sys +import warnings + __time_tic_toc = time.time() @@ -163,3 +166,219 @@ def parmap(f, X, nprocs=multiprocessing.cpu_count()): [p.join() for p in proc] return [x for i, x in sorted(res)] + + +class deprecated(object): + """Decorator to mark a function or class as deprecated. + + deprecated class from scikit-learn package + https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/utils/deprecation.py + 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 + + +class BaseEstimator(object): + """Base class for most objects in POT + adapted from sklearn BaseEstimator class + + Notes + ----- + All estimators should specify all the parameters that can be set + at the class level in their ``__init__`` as explicit keyword + arguments (no ``*args`` or ``**kwargs``). + """ + + @classmethod + def _get_param_names(cls): + """Get parameter names for the estimator""" + try: + from inspect import signature + except ImportError: + from .externals.funcsigs import signature + # fetch the constructor or the original constructor before + # deprecation wrapping if any + init = getattr(cls.__init__, 'deprecated_original', cls.__init__) + if init is object.__init__: + # No explicit constructor to introspect + return [] + + # introspect the constructor arguments to find the model parameters + # to represent + init_signature = signature(init) + # Consider the constructor parameters excluding 'self' + parameters = [p for p in init_signature.parameters.values() + if p.name != 'self' and p.kind != p.VAR_KEYWORD] + for p in parameters: + if p.kind == p.VAR_POSITIONAL: + raise RuntimeError("POT estimators should always " + "specify their parameters in the signature" + " of their __init__ (no varargs)." + " %s with constructor %s doesn't " + " follow this convention." + % (cls, init_signature)) + # Extract and sort argument names excluding 'self' + return sorted([p.name for p in parameters]) + + 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 + Parameter names mapped to their values. + """ + out = dict() + for key in self._get_param_names(): + # We need deprecation warnings to always be on in order to + # catch deprecated param values. + # This is set in utils/__init__.py but it gets overwritten + # when running under python3 somehow. + warnings.simplefilter("always", DeprecationWarning) + try: + with warnings.catch_warnings(record=True) as w: + value = getattr(self, key, None) + if len(w) and w[0].category == DeprecationWarning: + # if the parameter is deprecated, don't show it + continue + finally: + warnings.filters.pop(0) + + # XXX: should we rather test if instance of estimator? + if deep and hasattr(value, 'get_params'): + deep_items = value.get_params().items() + out.update((key + '__' + k, val) for k, val in deep_items) + out[key] = value + return out + + 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 + """ + if not params: + # Simple optimisation to gain speed (inspect is slow) + return self + valid_params = self.get_params(deep=True) + # for key, value in iteritems(params): + for key, value in params.items(): + split = key.split('__', 1) + if len(split) > 1: + # nested objects case + name, sub_name = split + if name not in valid_params: + raise ValueError('Invalid parameter %s for estimator %s. ' + 'Check the list of available parameters ' + 'with `estimator.get_params().keys()`.' % + (name, self)) + sub_object = valid_params[name] + sub_object.set_params(**{sub_name: value}) + else: + # simple objects case + if key not in valid_params: + raise ValueError('Invalid parameter %s for estimator %s. ' + 'Check the list of available parameters ' + 'with `estimator.get_params().keys()`.' % + (key, self.__class__.__name__)) + setattr(self, key, value) + return self -- cgit v1.2.3 From a8fa91bec26caa93329e61a104e0ad6afdf37363 Mon Sep 17 00:00:00 2001 From: Slasnista Date: Mon, 28 Aug 2017 11:03:28 +0200 Subject: handling input arguments in fit, transform... methods + remove old examples --- examples/plot_OTDA_2D.py | 126 ----------------- examples/plot_OTDA_classes.py | 117 ---------------- examples/plot_OTDA_color_images.py | 152 -------------------- examples/plot_OTDA_mapping.py | 124 ----------------- examples/plot_OTDA_mapping_color_images.py | 169 ---------------------- ot/da.py | 217 ++++++++++++++++------------- 6 files changed, 121 insertions(+), 784 deletions(-) delete mode 100644 examples/plot_OTDA_2D.py delete mode 100644 examples/plot_OTDA_classes.py delete mode 100644 examples/plot_OTDA_color_images.py delete mode 100644 examples/plot_OTDA_mapping.py delete mode 100644 examples/plot_OTDA_mapping_color_images.py diff --git a/examples/plot_OTDA_2D.py b/examples/plot_OTDA_2D.py deleted file mode 100644 index f2108c6..0000000 --- a/examples/plot_OTDA_2D.py +++ /dev/null @@ -1,126 +0,0 @@ -# -*- coding: utf-8 -*- -""" -============================== -OT for empirical distributions -============================== - -""" - -# Author: Remi Flamary -# -# License: MIT License - -import numpy as np -import matplotlib.pylab as pl -import ot - - -#%% parameters - -n = 150 # nb bins - -xs, ys = ot.datasets.get_data_classif('3gauss', n) -xt, yt = ot.datasets.get_data_classif('3gauss2', n) - -a, b = ot.unif(n), ot.unif(n) -# loss matrix -M = ot.dist(xs, xt) -# M/=M.max() - -#%% plot samples - -pl.figure(1) -pl.subplot(2, 2, 1) -pl.scatter(xs[:, 0], xs[:, 1], c=ys, marker='+', label='Source samples') -pl.legend(loc=0) -pl.title('Source distributions') - -pl.subplot(2, 2, 2) -pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', label='Target samples') -pl.legend(loc=0) -pl.title('target distributions') - -pl.figure(2) -pl.imshow(M, interpolation='nearest') -pl.title('Cost matrix M') - - -#%% OT estimation - -# EMD -G0 = ot.emd(a, b, M) - -# sinkhorn -lambd = 1e-1 -Gs = ot.sinkhorn(a, b, M, lambd) - - -# Group lasso regularization -reg = 1e-1 -eta = 1e0 -Gg = ot.da.sinkhorn_lpl1_mm(a, ys.astype(np.int), b, M, reg, eta) - - -#%% visu matrices - -pl.figure(3) - -pl.subplot(2, 3, 1) -pl.imshow(G0, interpolation='nearest') -pl.title('OT matrix ') - -pl.subplot(2, 3, 2) -pl.imshow(Gs, interpolation='nearest') -pl.title('OT matrix Sinkhorn') - -pl.subplot(2, 3, 3) -pl.imshow(Gg, interpolation='nearest') -pl.title('OT matrix Group lasso') - -pl.subplot(2, 3, 4) -ot.plot.plot2D_samples_mat(xs, xt, G0, c=[.5, .5, 1]) -pl.scatter(xs[:, 0], xs[:, 1], c=ys, marker='+', label='Source samples') -pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', label='Target samples') - - -pl.subplot(2, 3, 5) -ot.plot.plot2D_samples_mat(xs, xt, Gs, c=[.5, .5, 1]) -pl.scatter(xs[:, 0], xs[:, 1], c=ys, marker='+', label='Source samples') -pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', label='Target samples') - -pl.subplot(2, 3, 6) -ot.plot.plot2D_samples_mat(xs, xt, Gg, c=[.5, .5, 1]) -pl.scatter(xs[:, 0], xs[:, 1], c=ys, marker='+', label='Source samples') -pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', label='Target samples') -pl.tight_layout() - -#%% sample interpolation - -xst0 = n * G0.dot(xt) -xsts = n * Gs.dot(xt) -xstg = n * Gg.dot(xt) - -pl.figure(4, figsize=(8, 3)) -pl.subplot(1, 3, 1) -pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', - label='Target samples', alpha=0.5) -pl.scatter(xst0[:, 0], xst0[:, 1], c=ys, - marker='+', label='Transp samples', s=30) -pl.title('Interp samples') -pl.legend(loc=0) - -pl.subplot(1, 3, 2) -pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', - label='Target samples', alpha=0.5) -pl.scatter(xsts[:, 0], xsts[:, 1], c=ys, - marker='+', label='Transp samples', s=30) -pl.title('Interp samples Sinkhorn') - -pl.subplot(1, 3, 3) -pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', - label='Target samples', alpha=0.5) -pl.scatter(xstg[:, 0], xstg[:, 1], c=ys, - marker='+', label='Transp samples', s=30) -pl.title('Interp samples Grouplasso') -pl.tight_layout() -pl.show() diff --git a/examples/plot_OTDA_classes.py b/examples/plot_OTDA_classes.py deleted file mode 100644 index 53e4bae..0000000 --- a/examples/plot_OTDA_classes.py +++ /dev/null @@ -1,117 +0,0 @@ -# -*- coding: utf-8 -*- -""" -======================== -OT for domain adaptation -======================== - -""" - -# Author: Remi Flamary -# -# License: MIT License - -import matplotlib.pylab as pl -import ot - - -#%% parameters - -n = 150 # nb samples in source and target datasets - -xs, ys = ot.datasets.get_data_classif('3gauss', n) -xt, yt = ot.datasets.get_data_classif('3gauss2', n) - - -#%% plot samples - -pl.figure(1, figsize=(6.4, 3)) - -pl.subplot(1, 2, 1) -pl.scatter(xs[:, 0], xs[:, 1], c=ys, marker='+', label='Source samples') -pl.legend(loc=0) -pl.title('Source distributions') - -pl.subplot(1, 2, 2) -pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', label='Target samples') -pl.legend(loc=0) -pl.title('target distributions') - - -#%% OT estimation - -# LP problem -da_emd = ot.da.OTDA() # init class -da_emd.fit(xs, xt) # fit distributions -xst0 = da_emd.interp() # interpolation of source samples - -# sinkhorn regularization -lambd = 1e-1 -da_entrop = ot.da.OTDA_sinkhorn() -da_entrop.fit(xs, xt, reg=lambd) -xsts = da_entrop.interp() - -# non-convex Group lasso regularization -reg = 1e-1 -eta = 1e0 -da_lpl1 = ot.da.OTDA_lpl1() -da_lpl1.fit(xs, ys, xt, reg=reg, eta=eta) -xstg = da_lpl1.interp() - -# True Group lasso regularization -reg = 1e-1 -eta = 2e0 -da_l1l2 = ot.da.OTDA_l1l2() -da_l1l2.fit(xs, ys, xt, reg=reg, eta=eta, numItermax=20, verbose=True) -xstgl = da_l1l2.interp() - -#%% plot interpolated source samples - -param_img = {'interpolation': 'nearest', 'cmap': 'spectral'} - -pl.figure(2, figsize=(8, 4.5)) -pl.subplot(2, 4, 1) -pl.imshow(da_emd.G, **param_img) -pl.title('OT matrix') - -pl.subplot(2, 4, 2) -pl.imshow(da_entrop.G, **param_img) -pl.title('OT matrix\nsinkhorn') - -pl.subplot(2, 4, 3) -pl.imshow(da_lpl1.G, **param_img) -pl.title('OT matrix\nnon-convex Group Lasso') - -pl.subplot(2, 4, 4) -pl.imshow(da_l1l2.G, **param_img) -pl.title('OT matrix\nGroup Lasso') - -pl.subplot(2, 4, 5) -pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', - label='Target samples', alpha=0.3) -pl.scatter(xst0[:, 0], xst0[:, 1], c=ys, - marker='+', label='Transp samples', s=30) -pl.title('Interp samples') -pl.legend(loc=0) - -pl.subplot(2, 4, 6) -pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', - label='Target samples', alpha=0.3) -pl.scatter(xsts[:, 0], xsts[:, 1], c=ys, - marker='+', label='Transp samples', s=30) -pl.title('Interp samples\nSinkhorn') - -pl.subplot(2, 4, 7) -pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', - label='Target samples', alpha=0.3) -pl.scatter(xstg[:, 0], xstg[:, 1], c=ys, - marker='+', label='Transp samples', s=30) -pl.title('Interp samples\nnon-convex Group Lasso') - -pl.subplot(2, 4, 8) -pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', - label='Target samples', alpha=0.3) -pl.scatter(xstgl[:, 0], xstgl[:, 1], c=ys, - marker='+', label='Transp samples', s=30) -pl.title('Interp samples\nGroup Lasso') -pl.tight_layout() -pl.show() diff --git a/examples/plot_OTDA_color_images.py b/examples/plot_OTDA_color_images.py deleted file mode 100644 index c5ff873..0000000 --- a/examples/plot_OTDA_color_images.py +++ /dev/null @@ -1,152 +0,0 @@ -# -*- coding: utf-8 -*- -""" -======================================================== -OT for domain adaptation with image color adaptation [6] -======================================================== - -[6] Ferradans, S., Papadakis, N., Peyre, G., & Aujol, J. F. (2014). -Regularized discrete optimal transport. -SIAM Journal on Imaging Sciences, 7(3), 1853-1882. -""" - -# Author: Remi Flamary -# -# License: MIT License - -import numpy as np -from scipy import ndimage -import matplotlib.pylab as pl -import ot - - -#%% Loading images - -I1 = ndimage.imread('../data/ocean_day.jpg').astype(np.float64) / 256 -I2 = ndimage.imread('../data/ocean_sunset.jpg').astype(np.float64) / 256 - -#%% Plot images - -pl.figure(1, figsize=(6.4, 3)) - -pl.subplot(1, 2, 1) -pl.imshow(I1) -pl.axis('off') -pl.title('Image 1') - -pl.subplot(1, 2, 2) -pl.imshow(I2) -pl.axis('off') -pl.title('Image 2') - -pl.show() - -#%% Image conversion and dataset generation - - -def im2mat(I): - """Converts and image to matrix (one pixel per line)""" - return I.reshape((I.shape[0] * I.shape[1], I.shape[2])) - - -def mat2im(X, shape): - """Converts back a matrix to an image""" - return X.reshape(shape) - - -X1 = im2mat(I1) -X2 = im2mat(I2) - -# training samples -nb = 1000 -idx1 = np.random.randint(X1.shape[0], size=(nb,)) -idx2 = np.random.randint(X2.shape[0], size=(nb,)) - -xs = X1[idx1, :] -xt = X2[idx2, :] - -#%% Plot image distributions - - -pl.figure(2, figsize=(6.4, 3)) - -pl.subplot(1, 2, 1) -pl.scatter(xs[:, 0], xs[:, 2], c=xs) -pl.axis([0, 1, 0, 1]) -pl.xlabel('Red') -pl.ylabel('Blue') -pl.title('Image 1') - -pl.subplot(1, 2, 2) -pl.scatter(xt[:, 0], xt[:, 2], c=xt) -pl.axis([0, 1, 0, 1]) -pl.xlabel('Red') -pl.ylabel('Blue') -pl.title('Image 2') -pl.tight_layout() - -#%% domain adaptation between images - -# LP problem -da_emd = ot.da.OTDA() # init class -da_emd.fit(xs, xt) # fit distributions - -# sinkhorn regularization -lambd = 1e-1 -da_entrop = ot.da.OTDA_sinkhorn() -da_entrop.fit(xs, xt, reg=lambd) - -#%% prediction between images (using out of sample prediction as in [6]) - -X1t = da_emd.predict(X1) -X2t = da_emd.predict(X2, -1) - -X1te = da_entrop.predict(X1) -X2te = da_entrop.predict(X2, -1) - - -def minmax(I): - return np.clip(I, 0, 1) - - -I1t = minmax(mat2im(X1t, I1.shape)) -I2t = minmax(mat2im(X2t, I2.shape)) - -I1te = minmax(mat2im(X1te, I1.shape)) -I2te = minmax(mat2im(X2te, I2.shape)) - -#%% plot all images - -pl.figure(2, figsize=(8, 4)) - -pl.subplot(2, 3, 1) -pl.imshow(I1) -pl.axis('off') -pl.title('Image 1') - -pl.subplot(2, 3, 2) -pl.imshow(I1t) -pl.axis('off') -pl.title('Image 1 Adapt') - -pl.subplot(2, 3, 3) -pl.imshow(I1te) -pl.axis('off') -pl.title('Image 1 Adapt (reg)') - -pl.subplot(2, 3, 4) -pl.imshow(I2) -pl.axis('off') -pl.title('Image 2') - -pl.subplot(2, 3, 5) -pl.imshow(I2t) -pl.axis('off') -pl.title('Image 2 Adapt') - -pl.subplot(2, 3, 6) -pl.imshow(I2te) -pl.axis('off') -pl.title('Image 2 Adapt (reg)') -pl.tight_layout() - -pl.show() diff --git a/examples/plot_OTDA_mapping.py b/examples/plot_OTDA_mapping.py deleted file mode 100644 index a0d7f8b..0000000 --- a/examples/plot_OTDA_mapping.py +++ /dev/null @@ -1,124 +0,0 @@ -# -*- coding: utf-8 -*- -""" -=============================================== -OT mapping estimation for domain adaptation [8] -=============================================== - -[8] M. Perrot, N. Courty, R. Flamary, A. Habrard, - "Mapping estimation for discrete optimal transport", - Neural Information Processing Systems (NIPS), 2016. -""" - -# Author: Remi Flamary -# -# License: MIT License - -import numpy as np -import matplotlib.pylab as pl -import ot - - -#%% dataset generation - -np.random.seed(0) # makes example reproducible - -n = 100 # nb samples in source and target datasets -theta = 2 * np.pi / 20 -nz = 0.1 -xs, ys = ot.datasets.get_data_classif('gaussrot', n, nz=nz) -xt, yt = ot.datasets.get_data_classif('gaussrot', n, theta=theta, nz=nz) - -# one of the target mode changes its variance (no linear mapping) -xt[yt == 2] *= 3 -xt = xt + 4 - - -#%% plot samples - -pl.figure(1, (6.4, 3)) -pl.clf() -pl.scatter(xs[:, 0], xs[:, 1], c=ys, marker='+', label='Source samples') -pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', label='Target samples') -pl.legend(loc=0) -pl.title('Source and target distributions') - - -#%% OT linear mapping estimation - -eta = 1e-8 # quadratic regularization for regression -mu = 1e0 # weight of the OT linear term -bias = True # estimate a bias - -ot_mapping = ot.da.OTDA_mapping_linear() -ot_mapping.fit(xs, xt, mu=mu, eta=eta, bias=bias, numItermax=20, verbose=True) - -xst = ot_mapping.predict(xs) # use the estimated mapping -xst0 = ot_mapping.interp() # use barycentric mapping - - -pl.figure(2) -pl.clf() -pl.subplot(2, 2, 1) -pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', - label='Target samples', alpha=.3) -pl.scatter(xst0[:, 0], xst0[:, 1], c=ys, - marker='+', label='barycentric mapping') -pl.title("barycentric mapping") - -pl.subplot(2, 2, 2) -pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', - label='Target samples', alpha=.3) -pl.scatter(xst[:, 0], xst[:, 1], c=ys, marker='+', label='Learned mapping') -pl.title("Learned mapping") -pl.tight_layout() - -#%% Kernel mapping estimation - -eta = 1e-5 # quadratic regularization for regression -mu = 1e-1 # weight of the OT linear term -bias = True # estimate a bias -sigma = 1 # sigma bandwidth fot gaussian kernel - - -ot_mapping_kernel = ot.da.OTDA_mapping_kernel() -ot_mapping_kernel.fit( - xs, xt, mu=mu, eta=eta, sigma=sigma, bias=bias, numItermax=10, verbose=True) - -xst_kernel = ot_mapping_kernel.predict(xs) # use the estimated mapping -xst0_kernel = ot_mapping_kernel.interp() # use barycentric mapping - - -#%% Plotting the mapped samples - -pl.figure(2) -pl.clf() -pl.subplot(2, 2, 1) -pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', - label='Target samples', alpha=.2) -pl.scatter(xst0[:, 0], xst0[:, 1], c=ys, marker='+', - label='Mapped source samples') -pl.title("Bary. mapping (linear)") -pl.legend(loc=0) - -pl.subplot(2, 2, 2) -pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', - label='Target samples', alpha=.2) -pl.scatter(xst[:, 0], xst[:, 1], c=ys, marker='+', label='Learned mapping') -pl.title("Estim. mapping (linear)") - -pl.subplot(2, 2, 3) -pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', - label='Target samples', alpha=.2) -pl.scatter(xst0_kernel[:, 0], xst0_kernel[:, 1], c=ys, - marker='+', label='barycentric mapping') -pl.title("Bary. mapping (kernel)") - -pl.subplot(2, 2, 4) -pl.scatter(xt[:, 0], xt[:, 1], c=yt, marker='o', - label='Target samples', alpha=.2) -pl.scatter(xst_kernel[:, 0], xst_kernel[:, 1], c=ys, - marker='+', label='Learned mapping') -pl.title("Estim. mapping (kernel)") -pl.tight_layout() - -pl.show() diff --git a/examples/plot_OTDA_mapping_color_images.py b/examples/plot_OTDA_mapping_color_images.py deleted file mode 100644 index 8064b25..0000000 --- a/examples/plot_OTDA_mapping_color_images.py +++ /dev/null @@ -1,169 +0,0 @@ -# -*- coding: utf-8 -*- -""" -==================================================================================== -OT for domain adaptation with image color adaptation [6] with mapping estimation [8] -==================================================================================== - -[6] Ferradans, S., Papadakis, N., Peyre, G., & Aujol, J. F. (2014). Regularized - discrete optimal transport. SIAM Journal on Imaging Sciences, 7(3), 1853-1882. -[8] M. Perrot, N. Courty, R. Flamary, A. Habrard, "Mapping estimation for - discrete optimal transport", Neural Information Processing Systems (NIPS), 2016. - -""" - -# Author: Remi Flamary -# -# License: MIT License - -import numpy as np -from scipy import ndimage -import matplotlib.pylab as pl -import ot - - -#%% Loading images - -I1 = ndimage.imread('../data/ocean_day.jpg').astype(np.float64) / 256 -I2 = ndimage.imread('../data/ocean_sunset.jpg').astype(np.float64) / 256 - -#%% Plot images - -pl.figure(1, figsize=(6.4, 3)) -pl.subplot(1, 2, 1) -pl.imshow(I1) -pl.axis('off') -pl.title('Image 1') - -pl.subplot(1, 2, 2) -pl.imshow(I2) -pl.axis('off') -pl.title('Image 2') -pl.tight_layout() - - -#%% Image conversion and dataset generation - -def im2mat(I): - """Converts and image to matrix (one pixel per line)""" - return I.reshape((I.shape[0] * I.shape[1], I.shape[2])) - - -def mat2im(X, shape): - """Converts back a matrix to an image""" - return X.reshape(shape) - - -X1 = im2mat(I1) -X2 = im2mat(I2) - -# training samples -nb = 1000 -idx1 = np.random.randint(X1.shape[0], size=(nb,)) -idx2 = np.random.randint(X2.shape[0], size=(nb,)) - -xs = X1[idx1, :] -xt = X2[idx2, :] - -#%% Plot image distributions - - -pl.figure(2, figsize=(6.4, 5)) - -pl.subplot(1, 2, 1) -pl.scatter(xs[:, 0], xs[:, 2], c=xs) -pl.axis([0, 1, 0, 1]) -pl.xlabel('Red') -pl.ylabel('Blue') -pl.title('Image 1') - -pl.subplot(1, 2, 2) -pl.scatter(xt[:, 0], xt[:, 2], c=xt) -pl.axis([0, 1, 0, 1]) -pl.xlabel('Red') -pl.ylabel('Blue') -pl.title('Image 2') -pl.tight_layout() - - -#%% domain adaptation between images - -def minmax(I): - return np.clip(I, 0, 1) - - -# LP problem -da_emd = ot.da.OTDA() # init class -da_emd.fit(xs, xt) # fit distributions - -X1t = da_emd.predict(X1) # out of sample -I1t = minmax(mat2im(X1t, I1.shape)) - -# sinkhorn regularization -lambd = 1e-1 -da_entrop = ot.da.OTDA_sinkhorn() -da_entrop.fit(xs, xt, reg=lambd) - -X1te = da_entrop.predict(X1) -I1te = minmax(mat2im(X1te, I1.shape)) - -# linear mapping estimation -eta = 1e-8 # quadratic regularization for regression -mu = 1e0 # weight of the OT linear term -bias = True # estimate a bias - -ot_mapping = ot.da.OTDA_mapping_linear() -ot_mapping.fit(xs, xt, mu=mu, eta=eta, bias=bias, numItermax=20, verbose=True) - -X1tl = ot_mapping.predict(X1) # use the estimated mapping -I1tl = minmax(mat2im(X1tl, I1.shape)) - -# nonlinear mapping estimation -eta = 1e-2 # quadratic regularization for regression -mu = 1e0 # weight of the OT linear term -bias = False # estimate a bias -sigma = 1 # sigma bandwidth fot gaussian kernel - - -ot_mapping_kernel = ot.da.OTDA_mapping_kernel() -ot_mapping_kernel.fit( - xs, xt, mu=mu, eta=eta, sigma=sigma, bias=bias, numItermax=10, verbose=True) - -X1tn = ot_mapping_kernel.predict(X1) # use the estimated mapping -I1tn = minmax(mat2im(X1tn, I1.shape)) - -#%% plot images - -pl.figure(2, figsize=(8, 4)) - -pl.subplot(2, 3, 1) -pl.imshow(I1) -pl.axis('off') -pl.title('Im. 1') - -pl.subplot(2, 3, 2) -pl.imshow(I2) -pl.axis('off') -pl.title('Im. 2') - -pl.subplot(2, 3, 3) -pl.imshow(I1t) -pl.axis('off') -pl.title('Im. 1 Interp LP') - -pl.subplot(2, 3, 4) -pl.imshow(I1te) -pl.axis('off') -pl.title('Im. 1 Interp Entrop') - -pl.subplot(2, 3, 5) -pl.imshow(I1tl) -pl.axis('off') -pl.title('Im. 1 Linear mapping') - -pl.subplot(2, 3, 6) -pl.imshow(I1tn) -pl.axis('off') -pl.title('Im. 1 nonlinear mapping') -pl.tight_layout() - -pl.show() diff --git a/ot/da.py b/ot/da.py index 8c62669..369b6a2 100644 --- a/ot/da.py +++ b/ot/da.py @@ -976,36 +976,41 @@ class BaseTransport(BaseEstimator): Returns self. """ - # pairwise distance - self.cost_ = dist(Xs, Xt, metric=self.metric) + if Xs is not None and Xt is not None: + # pairwise distance + self.cost_ = dist(Xs, Xt, metric=self.metric) - if (ys is not None) and (yt is not None): + 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_) + 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) + # 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 + # 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) - self.mu_t = self.distribution_estimation(Xt) + # distribution estimation + self.mu_s = self.distribution_estimation(Xs) + self.mu_t = self.distribution_estimation(Xt) - # store arrays of samples - self.Xs = Xs - self.Xt = Xt + # store arrays of samples + self.Xs = Xs + self.Xt = Xt - return self + return self + else: + print("POT-Warning") + print("Please provide both Xs and Xt arguments when calling") + print("fit method") def fit_transform(self, Xs=None, ys=None, Xt=None, yt=None): """Build a coupling matrix from source and target sets of samples @@ -1053,42 +1058,47 @@ class BaseTransport(BaseEstimator): The transport source samples. """ - if np.array_equal(self.Xs, Xs): - # perform standard barycentric mapping - transp = self.coupling_ / np.sum(self.coupling_, 1)[:, None] + if Xs is not None: + if np.array_equal(self.Xs, Xs): + # perform standard barycentric mapping + transp = self.coupling_ / np.sum(self.coupling_, 1)[:, None] - # set nans to 0 - transp[~ np.isfinite(transp)] = 0 + # set nans to 0 + transp[~ np.isfinite(transp)] = 0 - # compute transported samples - 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)] + # compute transported samples + 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)] - transp_Xs = [] - for bi in batch_ind: + transp_Xs = [] + for bi in batch_ind: - # get the nearest neighbor in the source domain - D0 = dist(Xs[bi], self.Xs) - idx = np.argmin(D0, axis=1) + # 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) + # 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[bi] - self.Xs[idx, :] + # define the transported points + transp_Xs_ = transp_Xs_[idx, :] + Xs[bi] - self.Xs[idx, :] - transp_Xs.append(transp_Xs_) + transp_Xs.append(transp_Xs_) - transp_Xs = np.concatenate(transp_Xs, axis=0) + transp_Xs = np.concatenate(transp_Xs, axis=0) - return transp_Xs + return transp_Xs + else: + print("POT-Warning") + print("Please provide Xs argument when calling transform method") def inverse_transform(self, Xs=None, ys=None, Xt=None, yt=None, batch_size=128): @@ -1113,41 +1123,46 @@ class BaseTransport(BaseEstimator): 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] + if Xt is not None: + if np.array_equal(self.Xt, Xt): + # perform standard barycentric mapping + transp_ = self.coupling_.T / np.sum(self.coupling_, 0)[:, None] - # set nans to 0 - transp_[~ np.isfinite(transp_)] = 0 + # set nans to 0 + transp_[~ np.isfinite(transp_)] = 0 - # compute transported samples - 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)] + # compute transported samples + 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)] - transp_Xt = [] - for bi in batch_ind: + transp_Xt = [] + for bi in batch_ind: - D0 = dist(Xt[bi], self.Xt) - idx = np.argmin(D0, axis=1) + 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) + # 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[bi] - self.Xt[idx, :] - transp_Xt.append(transp_Xt_) + transp_Xt.append(transp_Xt_) - transp_Xt = np.concatenate(transp_Xt, axis=0) + transp_Xt = np.concatenate(transp_Xt, axis=0) - return transp_Xt + return transp_Xt + else: + print("POT-Warning") + print("Please provide Xt argument when calling inverse_transform") class SinkhornTransport(BaseTransport): @@ -1413,15 +1428,20 @@ class SinkhornLpl1Transport(BaseTransport): Returns self. """ - super(SinkhornLpl1Transport, self).fit(Xs, ys, Xt, yt) + if Xs is not None and Xt is not None and ys is not None: - 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) + super(SinkhornLpl1Transport, self).fit(Xs, ys, Xt, yt) - return self + 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) + + return self + else: + print("POT-Warning") + print("Please provide both Xs, Xt, ys arguments to fit method") class SinkhornL1l2Transport(BaseTransport): @@ -1517,22 +1537,27 @@ class SinkhornL1l2Transport(BaseTransport): Returns self. """ - super(SinkhornL1l2Transport, self).fit(Xs, ys, Xt, yt) + if Xs is not None and Xt is not None and ys is not None: - 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) + super(SinkhornL1l2Transport, self).fit(Xs, ys, Xt, yt) - # deal with the value of log - if self.log: - self.coupling_, self.log_ = returned_ - else: - self.coupling_ = returned_ - self.log_ = dict() + 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) - return self + # deal with the value of log + if self.log: + self.coupling_, self.log_ = returned_ + else: + self.coupling_ = returned_ + self.log_ = dict() + + return self + else: + print("POT-Warning") + print("Please, provide both Xs, Xt and ys argument to fit method") class MappingTransport(BaseEstimator): -- cgit v1.2.3 From c5d7c40c1d850879abd5f2c513afa1a2c5d5987e Mon Sep 17 00:00:00 2001 From: Slasnista Date: Mon, 28 Aug 2017 14:07:55 +0200 Subject: check input parameters with helper functions --- ot/da.py | 174 ++++++++++++++++++++++++++++++++++-------------------------- ot/utils.py | 21 ++++++++ 2 files changed, 120 insertions(+), 75 deletions(-) diff --git a/ot/da.py b/ot/da.py index 369b6a2..78dc150 100644 --- a/ot/da.py +++ b/ot/da.py @@ -14,7 +14,7 @@ import numpy as np from .bregman import sinkhorn from .lp import emd from .utils import unif, dist, kernel -from .utils import deprecated, BaseEstimator +from .utils import check_params, deprecated, BaseEstimator from .optim import cg from .optim import gcg @@ -954,6 +954,26 @@ def distribution_estimation_uniform(X): class BaseTransport(BaseEstimator): + """Base class for OTDA objects + + Notes + ----- + All estimators should specify all the parameters that can be set + at the class level in their ``__init__`` as explicit keyword + arguments (no ``*args`` or ``**kwargs``). + + fit method should: + - estimate a cost matrix and store it in a `cost_` attribute + - estimate a coupling matrix and store it in a `coupling_` + attribute + - estimate distributions from source and target data and store them in + mu_s and mu_t attributes + - store Xs and Xt in attributes to be used later on in transform and + inverse_transform methods + + transform method should always get as input a Xs parameter + inverse_transform method should always get as input a Xt parameter + """ def fit(self, Xs=None, ys=None, Xt=None, yt=None): """Build a coupling matrix from source and target sets of samples @@ -976,7 +996,9 @@ class BaseTransport(BaseEstimator): Returns self. """ - if Xs is not None and Xt is not None: + # check the necessary inputs parameters are here + if check_params(Xs=Xs, Xt=Xt): + # pairwise distance self.cost_ = dist(Xs, Xt, metric=self.metric) @@ -1003,14 +1025,10 @@ class BaseTransport(BaseEstimator): self.mu_t = self.distribution_estimation(Xt) # store arrays of samples - self.Xs = Xs - self.Xt = Xt + self.xs_ = Xs + self.xt_ = Xt - return self - else: - print("POT-Warning") - print("Please provide both Xs and Xt arguments when calling") - print("fit method") + return self def fit_transform(self, Xs=None, ys=None, Xt=None, yt=None): """Build a coupling matrix from source and target sets of samples @@ -1058,8 +1076,11 @@ class BaseTransport(BaseEstimator): The transport source samples. """ - if Xs is not None: - if np.array_equal(self.Xs, Xs): + # check the necessary inputs parameters are here + if check_params(Xs=Xs): + + if np.array_equal(self.xs_, Xs): + # perform standard barycentric mapping transp = self.coupling_ / np.sum(self.coupling_, 1)[:, None] @@ -1067,7 +1088,7 @@ class BaseTransport(BaseEstimator): transp[~ np.isfinite(transp)] = 0 # compute transported samples - transp_Xs = np.dot(transp, self.Xt) + transp_Xs = np.dot(transp, self.xt_) else: # perform out of sample mapping indices = np.arange(Xs.shape[0]) @@ -1079,26 +1100,23 @@ class BaseTransport(BaseEstimator): for bi in batch_ind: # get the nearest neighbor in the source domain - D0 = dist(Xs[bi], self.Xs) + 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) + transp_Xs_ = np.dot(transp, self.xt_) # define the transported points - transp_Xs_ = transp_Xs_[idx, :] + Xs[bi] - self.Xs[idx, :] + 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 - else: - print("POT-Warning") - print("Please provide Xs argument when calling transform method") def inverse_transform(self, Xs=None, ys=None, Xt=None, yt=None, batch_size=128): @@ -1123,8 +1141,11 @@ class BaseTransport(BaseEstimator): The transported target samples. """ - if Xt is not None: - if np.array_equal(self.Xt, Xt): + # check the necessary inputs parameters are here + if check_params(Xt=Xt): + + if np.array_equal(self.xt_, Xt): + # perform standard barycentric mapping transp_ = self.coupling_.T / np.sum(self.coupling_, 0)[:, None] @@ -1132,7 +1153,7 @@ class BaseTransport(BaseEstimator): transp_[~ np.isfinite(transp_)] = 0 # compute transported samples - transp_Xt = np.dot(transp_, self.Xs) + transp_Xt = np.dot(transp_, self.xs_) else: # perform out of sample mapping indices = np.arange(Xt.shape[0]) @@ -1143,26 +1164,23 @@ class BaseTransport(BaseEstimator): transp_Xt = [] for bi in batch_ind: - D0 = dist(Xt[bi], self.Xt) + 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) + transp_Xt_ = np.dot(transp_, self.xs_) # define the transported points - transp_Xt_ = transp_Xt_[idx, :] + Xt[bi] - self.Xt[idx, :] + transp_Xt_ = transp_Xt_[idx, :] + Xt[bi] - self.xt_[idx, :] transp_Xt.append(transp_Xt_) transp_Xt = np.concatenate(transp_Xt, axis=0) return transp_Xt - else: - print("POT-Warning") - print("Please provide Xt argument when calling inverse_transform") class SinkhornTransport(BaseTransport): @@ -1428,7 +1446,8 @@ class SinkhornLpl1Transport(BaseTransport): Returns self. """ - if Xs is not None and Xt is not None and ys is not None: + # check the necessary inputs parameters are here + if check_params(Xs=Xs, Xt=Xt, ys=ys): super(SinkhornLpl1Transport, self).fit(Xs, ys, Xt, yt) @@ -1438,10 +1457,7 @@ class SinkhornLpl1Transport(BaseTransport): numInnerItermax=self.max_inner_iter, stopInnerThr=self.tol, verbose=self.verbose) - return self - else: - print("POT-Warning") - print("Please provide both Xs, Xt, ys arguments to fit method") + return self class SinkhornL1l2Transport(BaseTransport): @@ -1537,7 +1553,8 @@ class SinkhornL1l2Transport(BaseTransport): Returns self. """ - if Xs is not None and Xt is not None and ys is not None: + # check the necessary inputs parameters are here + if check_params(Xs=Xs, Xt=Xt, ys=ys): super(SinkhornL1l2Transport, self).fit(Xs, ys, Xt, yt) @@ -1554,10 +1571,7 @@ class SinkhornL1l2Transport(BaseTransport): self.coupling_ = returned_ self.log_ = dict() - return self - else: - print("POT-Warning") - print("Please, provide both Xs, Xt and ys argument to fit method") + return self class MappingTransport(BaseEstimator): @@ -1652,29 +1666,35 @@ class MappingTransport(BaseEstimator): Returns self """ - self.Xs = Xs - self.Xt = Xt - - if self.kernel == "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) + # check the necessary inputs parameters are here + if check_params(Xs=Xs, Xt=Xt): + + self.xs_ = Xs + self.xt_ = Xt + + if self.kernel == "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": + 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) - elif self.kernel == "gaussian": - 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() + # 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 @@ -1692,22 +1712,26 @@ class MappingTransport(BaseEstimator): The transport source samples. """ - if np.array_equal(self.Xs, Xs): - # perform standard barycentric mapping - transp = self.coupling_ / np.sum(self.coupling_, 1)[:, None] + # check the necessary inputs parameters are here + if check_params(Xs=Xs): - # set nans to 0 - transp[~ np.isfinite(transp)] = 0 + if np.array_equal(self.xs_, Xs): + # perform standard barycentric mapping + transp = self.coupling_ / np.sum(self.coupling_, 1)[:, None] - # compute transported samples - transp_Xs = np.dot(transp, self.Xt) - else: - if self.kernel == "gaussian": - K = kernel(Xs, self.Xs, method=self.kernel, sigma=self.sigma) - elif self.kernel == "linear": - K = Xs - if self.bias: - K = np.hstack((K, np.ones((Xs.shape[0], 1)))) - transp_Xs = K.dot(self.mapping_) + # set nans to 0 + transp[~ np.isfinite(transp)] = 0 - return transp_Xs + # compute transported samples + transp_Xs = np.dot(transp, self.xt_) + else: + if self.kernel == "gaussian": + K = kernel(Xs, self.xs_, method=self.kernel, + sigma=self.sigma) + elif self.kernel == "linear": + K = Xs + if self.bias: + K = np.hstack((K, np.ones((Xs.shape[0], 1)))) + transp_Xs = K.dot(self.mapping_) + + return transp_Xs diff --git a/ot/utils.py b/ot/utils.py index 29ad536..01f2a67 100644 --- a/ot/utils.py +++ b/ot/utils.py @@ -168,6 +168,27 @@ def parmap(f, X, nprocs=multiprocessing.cpu_count()): return [x for i, x in sorted(res)] +def check_params(**kwargs): + """check_params: check whether some parameters are missing + """ + + missing_params = [] + check = True + + for param in kwargs: + if kwargs[param] is None: + missing_params.append(param) + + if len(missing_params) > 0: + print("POT - Warning: following necessary parameters are missing") + for p in missing_params: + print("\n", p) + + check = False + + return check + + class deprecated(object): """Decorator to mark a function or class as deprecated. -- cgit v1.2.3 From 7d3fc95abe059cc7404f3c213dfd5019cf110737 Mon Sep 17 00:00:00 2001 From: Slasnista Date: Mon, 28 Aug 2017 14:08:03 +0200 Subject: update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 27b4643..33eea6e 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ The contributors to this library are: * [Laetitia Chapel](http://people.irisa.fr/Laetitia.Chapel/) * [Michael Perrot](http://perso.univ-st-etienne.fr/pem82055/) (Mapping estimation) * [Léo Gautheron](https://github.com/aje) (GPU implementation) -* [Nathalie Gayraud]() +* [Nathalie Gayraud](https://www.linkedin.com/in/nathalie-t-h-gayraud/?ppe=1) * [Stanislas Chambon](https://slasnista.github.io/) This toolbox benefit a lot from open source research and we would like to thank the following persons for providing some code (in various languages): -- cgit v1.2.3 From a29e22db4772ebc4a8266c917e2e662f624c6baa Mon Sep 17 00:00:00 2001 From: Slasnista Date: Tue, 29 Aug 2017 09:05:01 +0200 Subject: addressed AG comments + adding random seed --- examples/da/plot_otda_classes.py | 2 ++ examples/da/plot_otda_color_images.py | 3 ++- examples/da/plot_otda_d2.py | 14 ++++++++------ examples/da/plot_otda_mapping.py | 14 +++++++------- examples/da/plot_otda_mapping_colors_images.py | 2 ++ 5 files changed, 21 insertions(+), 14 deletions(-) diff --git a/examples/da/plot_otda_classes.py b/examples/da/plot_otda_classes.py index e5c82fb..6870fa4 100644 --- a/examples/da/plot_otda_classes.py +++ b/examples/da/plot_otda_classes.py @@ -15,8 +15,10 @@ approaches currently supported in POT. # License: MIT License import matplotlib.pylab as pl +import numpy as np import ot +np.random.seed(42) # number of source and target points to generate ns = 150 diff --git a/examples/da/plot_otda_color_images.py b/examples/da/plot_otda_color_images.py index bca7350..805d0b0 100644 --- a/examples/da/plot_otda_color_images.py +++ b/examples/da/plot_otda_color_images.py @@ -20,9 +20,10 @@ SIAM Journal on Imaging Sciences, 7(3), 1853-1882. import numpy as np from scipy import ndimage import matplotlib.pylab as pl - import ot +np.random.seed(42) + def im2mat(I): """Converts and image to matrix (one pixel per line)""" diff --git a/examples/da/plot_otda_d2.py b/examples/da/plot_otda_d2.py index 1d2192f..8833eb2 100644 --- a/examples/da/plot_otda_d2.py +++ b/examples/da/plot_otda_d2.py @@ -19,17 +19,19 @@ of what the transport methods are doing. # License: MIT License import matplotlib.pylab as pl +import numpy as np import ot -# number of source and target points to generate -ns = 150 -nt = 150 +np.random.seed(42) -Xs, ys = ot.datasets.get_data_classif('3gauss', ns) -Xt, yt = ot.datasets.get_data_classif('3gauss2', nt) +n_samples_source = 150 +n_samples_target = 150 + +Xs, ys = ot.datasets.get_data_classif('3gauss', n_samples_source) +Xt, yt = ot.datasets.get_data_classif('3gauss2', n_samples_target) # Cost matrix -M = ot.dist(Xs, Xt) +M = ot.dist(Xs, Xt, metric='sqeuclidean') # Instantiate the different transport algorithms and fit them diff --git a/examples/da/plot_otda_mapping.py b/examples/da/plot_otda_mapping.py index 6d83507..aea7f09 100644 --- a/examples/da/plot_otda_mapping.py +++ b/examples/da/plot_otda_mapping.py @@ -23,7 +23,7 @@ import matplotlib.pylab as pl import ot -np.random.seed(0) +np.random.seed(42) ############################################################################## # generate @@ -31,10 +31,11 @@ np.random.seed(0) n = 100 # nb samples in source and target datasets theta = 2 * np.pi / 20 -nz = 0.1 -Xs, ys = ot.datasets.get_data_classif('gaussrot', n, nz=nz) -Xs_new, _ = ot.datasets.get_data_classif('gaussrot', n, nz=nz) -Xt, yt = ot.datasets.get_data_classif('gaussrot', n, theta=theta, nz=nz) +noise_level = 0.1 +Xs, ys = ot.datasets.get_data_classif('gaussrot', n, nz=noise_level) +Xs_new, _ = ot.datasets.get_data_classif('gaussrot', n, nz=noise_level) +Xt, yt = ot.datasets.get_data_classif( + 'gaussrot', n, theta=theta, nz=noise_level) # one of the target mode changes its variance (no linear mapping) Xt[yt == 2] *= 3 @@ -46,8 +47,7 @@ ot_mapping_linear = ot.da.MappingTransport( kernel="linear", mu=1e0, eta=1e-8, bias=True, max_iter=20, verbose=True) -ot_mapping_linear.fit( - Xs=Xs, Xt=Xt) +ot_mapping_linear.fit(Xs=Xs, Xt=Xt) # for original source samples, transform applies barycentric mapping transp_Xs_linear = ot_mapping_linear.transform(Xs=Xs) diff --git a/examples/da/plot_otda_mapping_colors_images.py b/examples/da/plot_otda_mapping_colors_images.py index 4209020..6c024ea 100644 --- a/examples/da/plot_otda_mapping_colors_images.py +++ b/examples/da/plot_otda_mapping_colors_images.py @@ -23,6 +23,8 @@ from scipy import ndimage import matplotlib.pylab as pl import ot +np.random.seed(42) + def im2mat(I): """Converts and image to matrix (one pixel per line)""" -- cgit v1.2.3 From 65de6fc9add57b95b8968e1e75fe1af342f81d01 Mon Sep 17 00:00:00 2001 From: Slasnista Date: Tue, 29 Aug 2017 13:34:34 +0200 Subject: pass on examples | introduced RandomState --- examples/da/plot_otda_classes.py | 20 +++++++++++++------- examples/da/plot_otda_color_images.py | 19 ++++++++++++++++--- examples/da/plot_otda_d2.py | 12 ++++++++++-- examples/da/plot_otda_mapping.py | 21 ++++++++++++++------- examples/da/plot_otda_mapping_colors_images.py | 9 ++++++--- 5 files changed, 59 insertions(+), 22 deletions(-) diff --git a/examples/da/plot_otda_classes.py b/examples/da/plot_otda_classes.py index 6870fa4..ec57a37 100644 --- a/examples/da/plot_otda_classes.py +++ b/examples/da/plot_otda_classes.py @@ -15,19 +15,23 @@ approaches currently supported in POT. # License: MIT License import matplotlib.pylab as pl -import numpy as np import ot -np.random.seed(42) -# number of source and target points to generate -ns = 150 -nt = 150 +############################################################################## +# generate data +############################################################################## + +n_source_samples = 150 +n_target_samples = 150 + +Xs, ys = ot.datasets.get_data_classif('3gauss', n_source_samples) +Xt, yt = ot.datasets.get_data_classif('3gauss2', n_target_samples) -Xs, ys = ot.datasets.get_data_classif('3gauss', ns) -Xt, yt = ot.datasets.get_data_classif('3gauss2', nt) +############################################################################## # Instantiate the different transport algorithms and fit them +############################################################################## # EMD Transport ot_emd = ot.da.EMDTransport() @@ -52,6 +56,7 @@ transp_Xs_sinkhorn = ot_sinkhorn.transform(Xs=Xs) transp_Xs_lpl1 = ot_lpl1.transform(Xs=Xs) transp_Xs_l1l2 = ot_l1l2.transform(Xs=Xs) + ############################################################################## # Fig 1 : plots source and target samples ############################################################################## @@ -72,6 +77,7 @@ pl.legend(loc=0) pl.title('Target samples') pl.tight_layout() + ############################################################################## # Fig 2 : plot optimal couplings and transported samples ############################################################################## diff --git a/examples/da/plot_otda_color_images.py b/examples/da/plot_otda_color_images.py index 805d0b0..3984afb 100644 --- a/examples/da/plot_otda_color_images.py +++ b/examples/da/plot_otda_color_images.py @@ -22,7 +22,8 @@ from scipy import ndimage import matplotlib.pylab as pl import ot -np.random.seed(42) + +r = np.random.RandomState(42) def im2mat(I): @@ -39,6 +40,10 @@ def minmax(I): return np.clip(I, 0, 1) +############################################################################## +# generate data +############################################################################## + # Loading images I1 = ndimage.imread('../../data/ocean_day.jpg').astype(np.float64) / 256 I2 = ndimage.imread('../../data/ocean_sunset.jpg').astype(np.float64) / 256 @@ -48,12 +53,17 @@ X2 = im2mat(I2) # training samples nb = 1000 -idx1 = np.random.randint(X1.shape[0], size=(nb,)) -idx2 = np.random.randint(X2.shape[0], size=(nb,)) +idx1 = r.randint(X1.shape[0], size=(nb,)) +idx2 = r.randint(X2.shape[0], size=(nb,)) Xs = X1[idx1, :] Xt = X2[idx2, :] + +############################################################################## +# Instantiate the different transport algorithms and fit them +############################################################################## + # EMDTransport ot_emd = ot.da.EMDTransport() ot_emd.fit(Xs=Xs, Xt=Xt) @@ -75,6 +85,7 @@ I2t = minmax(mat2im(transp_Xt_emd, I2.shape)) I1te = minmax(mat2im(transp_Xs_sinkhorn, I1.shape)) I2te = minmax(mat2im(transp_Xt_sinkhorn, I2.shape)) + ############################################################################## # plot original image ############################################################################## @@ -91,6 +102,7 @@ pl.imshow(I2) pl.axis('off') pl.title('Image 2') + ############################################################################## # scatter plot of colors ############################################################################## @@ -112,6 +124,7 @@ pl.ylabel('Blue') pl.title('Image 2') pl.tight_layout() + ############################################################################## # plot new images ############################################################################## diff --git a/examples/da/plot_otda_d2.py b/examples/da/plot_otda_d2.py index 8833eb2..3daa0a6 100644 --- a/examples/da/plot_otda_d2.py +++ b/examples/da/plot_otda_d2.py @@ -19,10 +19,12 @@ of what the transport methods are doing. # License: MIT License import matplotlib.pylab as pl -import numpy as np import ot -np.random.seed(42) + +############################################################################## +# generate data +############################################################################## n_samples_source = 150 n_samples_target = 150 @@ -33,7 +35,10 @@ Xt, yt = ot.datasets.get_data_classif('3gauss2', n_samples_target) # Cost matrix M = ot.dist(Xs, Xt, metric='sqeuclidean') + +############################################################################## # Instantiate the different transport algorithms and fit them +############################################################################## # EMD Transport ot_emd = ot.da.EMDTransport() @@ -52,6 +57,7 @@ transp_Xs_emd = ot_emd.transform(Xs=Xs) transp_Xs_sinkhorn = ot_sinkhorn.transform(Xs=Xs) transp_Xs_lpl1 = ot_lpl1.transform(Xs=Xs) + ############################################################################## # Fig 1 : plots source and target samples + matrix of pairwise distance ############################################################################## @@ -78,6 +84,7 @@ pl.yticks([]) pl.title('Matrix of pairwise distances') pl.tight_layout() + ############################################################################## # Fig 2 : plots optimal couplings for the different methods ############################################################################## @@ -127,6 +134,7 @@ pl.yticks([]) pl.title('Main coupling coefficients\nSinkhornLpl1Transport') pl.tight_layout() + ############################################################################## # Fig 3 : plot transported samples ############################################################################## diff --git a/examples/da/plot_otda_mapping.py b/examples/da/plot_otda_mapping.py index aea7f09..09d2cb4 100644 --- a/examples/da/plot_otda_mapping.py +++ b/examples/da/plot_otda_mapping.py @@ -23,25 +23,31 @@ import matplotlib.pylab as pl import ot -np.random.seed(42) - ############################################################################## -# generate +# generate data ############################################################################## -n = 100 # nb samples in source and target datasets +n_source_samples = 100 +n_target_samples = 100 theta = 2 * np.pi / 20 noise_level = 0.1 -Xs, ys = ot.datasets.get_data_classif('gaussrot', n, nz=noise_level) -Xs_new, _ = ot.datasets.get_data_classif('gaussrot', n, nz=noise_level) + +Xs, ys = ot.datasets.get_data_classif( + 'gaussrot', n_source_samples, nz=noise_level) +Xs_new, _ = ot.datasets.get_data_classif( + 'gaussrot', n_source_samples, nz=noise_level) Xt, yt = ot.datasets.get_data_classif( - 'gaussrot', n, theta=theta, nz=noise_level) + 'gaussrot', n_target_samples, theta=theta, nz=noise_level) # one of the target mode changes its variance (no linear mapping) Xt[yt == 2] *= 3 Xt = Xt + 4 +############################################################################## +# Instantiate the different transport algorithms and fit them +############################################################################## + # MappingTransport with linear kernel ot_mapping_linear = ot.da.MappingTransport( kernel="linear", mu=1e0, eta=1e-8, bias=True, @@ -80,6 +86,7 @@ pl.scatter(Xt[:, 0], Xt[:, 1], c=yt, marker='o', label='Target samples') pl.legend(loc=0) pl.title('Source and target distributions') + ############################################################################## # plot transported samples ############################################################################## diff --git a/examples/da/plot_otda_mapping_colors_images.py b/examples/da/plot_otda_mapping_colors_images.py index 6c024ea..a628b05 100644 --- a/examples/da/plot_otda_mapping_colors_images.py +++ b/examples/da/plot_otda_mapping_colors_images.py @@ -23,7 +23,7 @@ from scipy import ndimage import matplotlib.pylab as pl import ot -np.random.seed(42) +r = np.random.RandomState(42) def im2mat(I): @@ -54,8 +54,8 @@ X2 = im2mat(I2) # training samples nb = 1000 -idx1 = np.random.randint(X1.shape[0], size=(nb,)) -idx2 = np.random.randint(X2.shape[0], size=(nb,)) +idx1 = r.randint(X1.shape[0], size=(nb,)) +idx2 = r.randint(X2.shape[0], size=(nb,)) Xs = X1[idx1, :] Xt = X2[idx2, :] @@ -91,6 +91,7 @@ ot_mapping_gaussian.fit(Xs=Xs, Xt=Xt) X1tn = ot_mapping_gaussian.transform(Xs=X1) # use the estimated mapping Image_mapping_gaussian = minmax(mat2im(X1tn, I1.shape)) + ############################################################################## # plot original images ############################################################################## @@ -107,6 +108,7 @@ pl.axis('off') pl.title('Image 2') pl.tight_layout() + ############################################################################## # plot pixel values distribution ############################################################################## @@ -128,6 +130,7 @@ pl.ylabel('Blue') pl.title('Image 2') pl.tight_layout() + ############################################################################## # plot transformed images ############################################################################## -- cgit v1.2.3 From 3cecc18f53453e79ccc00484e6cbfaa5643f269c Mon Sep 17 00:00:00 2001 From: aje Date: Tue, 29 Aug 2017 15:38:11 +0200 Subject: Changes to LP solver: - Allow to modify the maximal number of iterations - Display an error message in the python console if the solver encountered an issue --- ot/da.py | 4 ++-- ot/lp/EMD.h | 7 ++++++- ot/lp/EMD_wrapper.cpp | 7 +++---- ot/lp/__init__.py | 16 ++++++++++------ ot/lp/emd_wrap.pyx | 25 ++++++++++++++++++++----- 5 files changed, 41 insertions(+), 18 deletions(-) diff --git a/ot/da.py b/ot/da.py index 78dc150..0dfd02f 100644 --- a/ot/da.py +++ b/ot/da.py @@ -658,7 +658,7 @@ class OTDA(object): self.metric = metric self.computed = False - def fit(self, xs, xt, ws=None, wt=None, norm=None): + def fit(self, xs, xt, ws=None, wt=None, norm=None, numItermax=10000): """Fit domain adaptation between samples is xs and xt (with optional weights)""" self.xs = xs @@ -674,7 +674,7 @@ class OTDA(object): self.M = dist(xs, xt, metric=self.metric) self.normalizeM(norm) - self.G = emd(ws, wt, self.M) + self.G = emd(ws, wt, self.M, numItermax) self.computed = True def interp(self, direction=1): diff --git a/ot/lp/EMD.h b/ot/lp/EMD.h index 40d7192..956830c 100644 --- a/ot/lp/EMD.h +++ b/ot/lp/EMD.h @@ -23,7 +23,12 @@ using namespace lemon; typedef unsigned int node_id_type; +enum ProblemType { + INFEASIBLE, + OPTIMAL, + UNBOUNDED +}; -void EMD_wrap(int n1,int n2, double *X, double *Y,double *D, double *G, double *cost); +int EMD_wrap(int n1,int n2, double *X, double *Y,double *D, double *G, double *cost, int numItermax); #endif diff --git a/ot/lp/EMD_wrapper.cpp b/ot/lp/EMD_wrapper.cpp index cad4750..83ed56c 100644 --- a/ot/lp/EMD_wrapper.cpp +++ b/ot/lp/EMD_wrapper.cpp @@ -15,11 +15,10 @@ #include "EMD.h" -void EMD_wrap(int n1,int n2, double *X, double *Y,double *D, double *G, double *cost) { +int EMD_wrap(int n1,int n2, double *X, double *Y,double *D, double *G, double *cost, int numItermax) { // beware M and C anre strored in row major C style!!! int n, m, i,cur; double max; - int max_iter=10000; typedef FullBipartiteDigraph Digraph; DIGRAPH_TYPEDEFS(FullBipartiteDigraph); @@ -46,7 +45,7 @@ void EMD_wrap(int n1,int n2, double *X, double *Y,double *D, double *G, double * 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, numItermax); // Set supply and demand, don't account for 0 values (faster) @@ -116,5 +115,5 @@ void EMD_wrap(int n1,int n2, double *X, double *Y,double *D, double *G, double * }; - + return ret; } diff --git a/ot/lp/__init__.py b/ot/lp/__init__.py index 6e0bdb8..62afe76 100644 --- a/ot/lp/__init__.py +++ b/ot/lp/__init__.py @@ -15,7 +15,7 @@ import multiprocessing -def emd(a, b, M): +def emd(a, b, M, numItermax=10000): """Solves the Earth Movers distance problem and returns the OT matrix @@ -40,6 +40,8 @@ def emd(a, b, M): Target histogram (uniform weigth if empty list) M : (ns,nt) ndarray, float64 loss matrix + numItermax : int + Maximum number of iterations made by the LP solver. Returns ------- @@ -84,9 +86,9 @@ def emd(a, b, M): if len(b) == 0: b = np.ones((M.shape[1], ), dtype=np.float64)/M.shape[1] - return emd_c(a, b, M) + return emd_c(a, b, M, numItermax) -def emd2(a, b, M,processes=multiprocessing.cpu_count()): +def emd2(a, b, M, numItermax=10000, processes=multiprocessing.cpu_count()): """Solves the Earth Movers distance problem and returns the loss .. math:: @@ -110,6 +112,8 @@ def emd2(a, b, M,processes=multiprocessing.cpu_count()): Target histogram (uniform weigth if empty list) M : (ns,nt) ndarray, float64 loss matrix + numItermax : int + Maximum number of iterations made by the LP solver. Returns ------- @@ -155,12 +159,12 @@ def emd2(a, b, M,processes=multiprocessing.cpu_count()): b = np.ones((M.shape[1], ), dtype=np.float64)/M.shape[1] if len(b.shape)==1: - return emd2_c(a, b, M) + return emd2_c(a, b, M, numItermax) else: nb=b.shape[1] - #res=[emd2_c(a,b[:,i].copy(),M) for i in range(nb)] + #res=[emd2_c(a,b[:,i].copy(),M, numItermax) for i in range(nb)] def f(b): - return emd2_c(a,b,M) + return emd2_c(a,b,M, numItermax) 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 46c96c1..ed8c416 100644 --- a/ot/lp/emd_wrap.pyx +++ b/ot/lp/emd_wrap.pyx @@ -15,13 +15,14 @@ cimport cython cdef extern from "EMD.h": - void EMD_wrap(int n1,int n2, double *X, double *Y,double *D, double *G, double *cost) + int EMD_wrap(int n1,int n2, double *X, double *Y,double *D, double *G, double *cost, int numItermax) + cdef enum ProblemType: INFEASIBLE, OPTIMAL, UNBOUNDED @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): +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 @@ -48,6 +49,8 @@ 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 + Maximum number of iterations made by the LP solver. Returns @@ -69,13 +72,18 @@ 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 - EMD_wrap(n1,n2, a.data, b.data, M.data, G.data, &cost) + cdef int resultSolver = EMD_wrap(n1,n2, a.data, b.data, M.data, G.data, &cost, numItermax) + if resultSolver != OPTIMAL: + if resultSolver == INFEASIBLE: + print("Problem infeasible. Try to inscrease numItermax.") + elif resultSolver == UNBOUNDED: + print("Problem unbounded") return G @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): +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 @@ -102,6 +110,8 @@ 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 + numItermax : int + Maximum number of iterations made by the LP solver. Returns @@ -123,7 +133,12 @@ def emd2_c( np.ndarray[double, ndim=1, mode="c"] a,np.ndarray[double, ndim=1, mo b=np.ones((n2,))/n2 # calling the function - EMD_wrap(n1,n2, a.data, b.data, M.data, G.data, &cost) + cdef int resultSolver = EMD_wrap(n1,n2, a.data, b.data, M.data, G.data, &cost, numItermax) + if resultSolver != OPTIMAL: + if resultSolver == INFEASIBLE: + print("Problem infeasible. Try to inscrease numItermax.") + elif resultSolver == UNBOUNDED: + print("Problem unbounded") cost=0 for i in range(n1): -- cgit v1.2.3 From 99c0a24c6467631b326838667998ba4f219d8a24 Mon Sep 17 00:00:00 2001 From: aje Date: Tue, 29 Aug 2017 16:53:06 +0200 Subject: Fix param order --- ot/lp/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ot/lp/__init__.py b/ot/lp/__init__.py index 62afe76..5143a70 100644 --- a/ot/lp/__init__.py +++ b/ot/lp/__init__.py @@ -88,7 +88,7 @@ def emd(a, b, M, numItermax=10000): return emd_c(a, b, M, numItermax) -def emd2(a, b, M, numItermax=10000, processes=multiprocessing.cpu_count()): +def emd2(a, b, M, processes=multiprocessing.cpu_count(), numItermax=10000): """Solves the Earth Movers distance problem and returns the loss .. math:: -- cgit v1.2.3 From 308ce24b705dfad9d058138d058da8b18002e081 Mon Sep 17 00:00:00 2001 From: aje Date: Tue, 29 Aug 2017 17:01:17 +0200 Subject: Type print --- ot/lp/emd_wrap.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ot/lp/emd_wrap.pyx b/ot/lp/emd_wrap.pyx index ed8c416..6039e1f 100644 --- a/ot/lp/emd_wrap.pyx +++ b/ot/lp/emd_wrap.pyx @@ -75,7 +75,7 @@ def emd_c( np.ndarray[double, ndim=1, mode="c"] a,np.ndarray[double, ndim=1, mod cdef int resultSolver = EMD_wrap(n1,n2, a.data, b.data, M.data, G.data, &cost, numItermax) if resultSolver != OPTIMAL: if resultSolver == INFEASIBLE: - print("Problem infeasible. Try to inscrease numItermax.") + print("Problem infeasible. Try to increase numItermax.") elif resultSolver == UNBOUNDED: print("Problem unbounded") -- cgit v1.2.3 From 982f36cb0a5f3a6a14454238a26053de7251b0f0 Mon Sep 17 00:00:00 2001 From: aje Date: Wed, 30 Aug 2017 09:56:37 +0200 Subject: Changes: - Rename numItermax to max_iter - Default value to 100000 instead of 10000 - Add max_iter to class SinkhornTransport(BaseTransport) - Add norm to all BaseTransport --- ot/da.py | 64 ++++++++++++++++++++++++++++++++++------ ot/lp/EMD.h | 2 +- ot/lp/EMD_wrapper.cpp | 4 +-- ot/lp/__init__.py | 46 ++++++++++++++--------------- ot/lp/emd_wrap.pyx | 82 ++++++++++++++++++++++++++------------------------- 5 files changed, 123 insertions(+), 75 deletions(-) diff --git a/ot/da.py b/ot/da.py index 0dfd02f..5871aba 100644 --- a/ot/da.py +++ b/ot/da.py @@ -658,7 +658,7 @@ class OTDA(object): self.metric = metric self.computed = False - def fit(self, xs, xt, ws=None, wt=None, norm=None, numItermax=10000): + def fit(self, xs, xt, ws=None, wt=None, norm=None, max_iter=100000): """Fit domain adaptation between samples is xs and xt (with optional weights)""" self.xs = xs @@ -674,7 +674,7 @@ class OTDA(object): self.M = dist(xs, xt, metric=self.metric) self.normalizeM(norm) - self.G = emd(ws, wt, self.M, numItermax) + self.G = emd(ws, wt, self.M, max_iter) self.computed = True def interp(self, direction=1): @@ -1001,6 +1001,7 @@ class BaseTransport(BaseEstimator): # pairwise distance self.cost_ = dist(Xs, Xt, metric=self.metric) + self.normalizeCost_(self.norm) if (ys is not None) and (yt is not None): @@ -1182,6 +1183,26 @@ class BaseTransport(BaseEstimator): return transp_Xt + def normalizeCost_(self, norm): + """ Apply normalization to the loss matrix + + + Parameters + ---------- + norm : str + type of normalization from 'median','max','log','loglog' + + """ + + if norm == "median": + self.cost_ /= float(np.median(self.cost_)) + elif norm == "max": + self.cost_ /= float(np.max(self.cost_)) + elif norm == "log": + self.cost_ = np.log(1 + self.cost_) + elif norm == "loglog": + self.cost_ = np.log(1 + np.log(1 + self.cost_)) + class SinkhornTransport(BaseTransport): """Domain Adapatation OT method based on Sinkhorn Algorithm @@ -1202,6 +1223,9 @@ class SinkhornTransport(BaseTransport): be transported from a domain to another one. metric : string, optional (default="sqeuclidean") The ground metric for the Wasserstein problem + norm : string, optional (default=None) + If given, normalize the ground metric to avoid numerical errors that + can occur with large metric values. distribution : string, optional (default="uniform") The kind of distribution estimation to employ verbose : int, optional (default=0) @@ -1231,7 +1255,7 @@ class SinkhornTransport(BaseTransport): def __init__(self, reg_e=1., max_iter=1000, tol=10e-9, verbose=False, log=False, - metric="sqeuclidean", + metric="sqeuclidean", norm=None, distribution_estimation=distribution_estimation_uniform, out_of_sample_map='ferradans', limit_max=np.infty): @@ -1241,6 +1265,7 @@ class SinkhornTransport(BaseTransport): self.verbose = verbose self.log = log self.metric = metric + self.norm = norm self.limit_max = limit_max self.distribution_estimation = distribution_estimation self.out_of_sample_map = out_of_sample_map @@ -1296,6 +1321,9 @@ class EMDTransport(BaseTransport): be transported from a domain to another one. metric : string, optional (default="sqeuclidean") The ground metric for the Wasserstein problem + norm : string, optional (default=None) + If given, normalize the ground metric to avoid numerical errors that + can occur with large metric values. distribution : string, optional (default="uniform") The kind of distribution estimation to employ verbose : int, optional (default=0) @@ -1306,6 +1334,9 @@ 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) + max_iter : int, optional (default=100000) + The maximum number of iterations before stopping the optimization + algorithm if it has not converged. Attributes ---------- @@ -1319,14 +1350,17 @@ class EMDTransport(BaseTransport): on Pattern Analysis and Machine Intelligence , vol.PP, no.99, pp.1-1 """ - def __init__(self, metric="sqeuclidean", + def __init__(self, metric="sqeuclidean", norm=None, distribution_estimation=distribution_estimation_uniform, - out_of_sample_map='ferradans', limit_max=10): + out_of_sample_map='ferradans', limit_max=10, + max_iter=100000): self.metric = metric + self.norm = norm self.limit_max = limit_max self.distribution_estimation = distribution_estimation self.out_of_sample_map = out_of_sample_map + self.max_iter = max_iter def fit(self, Xs, ys=None, Xt=None, yt=None): """Build a coupling matrix from source and target sets of samples @@ -1353,7 +1387,7 @@ class EMDTransport(BaseTransport): # coupling estimation self.coupling_ = emd( - a=self.mu_s, b=self.mu_t, M=self.cost_, + a=self.mu_s, b=self.mu_t, M=self.cost_, max_iter=self.max_iter ) return self @@ -1376,6 +1410,9 @@ class SinkhornLpl1Transport(BaseTransport): be transported from a domain to another one. metric : string, optional (default="sqeuclidean") The ground metric for the Wasserstein problem + norm : string, optional (default=None) + If given, normalize the ground metric to avoid numerical errors that + can occur with large metric values. distribution : string, optional (default="uniform") The kind of distribution estimation to employ max_iter : int, float, optional (default=10) @@ -1410,7 +1447,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, - metric="sqeuclidean", + metric="sqeuclidean", norm=None, distribution_estimation=distribution_estimation_uniform, out_of_sample_map='ferradans', limit_max=np.infty): @@ -1421,6 +1458,7 @@ class SinkhornLpl1Transport(BaseTransport): self.tol = tol self.verbose = verbose self.metric = metric + self.norm = norm self.distribution_estimation = distribution_estimation self.out_of_sample_map = out_of_sample_map self.limit_max = limit_max @@ -1477,6 +1515,9 @@ class SinkhornL1l2Transport(BaseTransport): be transported from a domain to another one. metric : string, optional (default="sqeuclidean") The ground metric for the Wasserstein problem + norm : string, optional (default=None) + If given, normalize the ground metric to avoid numerical errors that + can occur with large metric values. distribution : string, optional (default="uniform") The kind of distribution estimation to employ max_iter : int, float, optional (default=10) @@ -1516,7 +1557,7 @@ class SinkhornL1l2Transport(BaseTransport): def __init__(self, reg_e=1., reg_cl=0.1, max_iter=10, max_inner_iter=200, tol=10e-9, verbose=False, log=False, - metric="sqeuclidean", + metric="sqeuclidean", norm=None, distribution_estimation=distribution_estimation_uniform, out_of_sample_map='ferradans', limit_max=10): @@ -1528,6 +1569,7 @@ class SinkhornL1l2Transport(BaseTransport): self.verbose = verbose self.log = log self.metric = metric + self.norm = norm self.distribution_estimation = distribution_estimation self.out_of_sample_map = out_of_sample_map self.limit_max = limit_max @@ -1588,6 +1630,9 @@ class MappingTransport(BaseEstimator): Estimate linear mapping with constant bias metric : string, optional (default="sqeuclidean") The ground metric for the Wasserstein problem + norm : string, optional (default=None) + If given, normalize the ground metric to avoid numerical errors that + can occur with large metric values. kernel : string, optional (default="linear") The kernel to use either linear or gaussian sigma : float, optional (default=1) @@ -1627,11 +1672,12 @@ 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, + norm=None, kernel="linear", sigma=1, max_iter=100, tol=1e-5, max_inner_iter=10, inner_tol=1e-6, log=False, verbose=False, verbose2=False): self.metric = metric + self.norm = norm self.mu = mu self.eta = eta self.bias = bias diff --git a/ot/lp/EMD.h b/ot/lp/EMD.h index 956830c..aa92441 100644 --- a/ot/lp/EMD.h +++ b/ot/lp/EMD.h @@ -29,6 +29,6 @@ enum ProblemType { UNBOUNDED }; -int EMD_wrap(int n1,int n2, double *X, double *Y,double *D, double *G, double *cost, int numItermax); +int EMD_wrap(int n1,int n2, double *X, double *Y,double *D, double *G, double *cost, int max_iter); #endif diff --git a/ot/lp/EMD_wrapper.cpp b/ot/lp/EMD_wrapper.cpp index 83ed56c..c8c2eb3 100644 --- a/ot/lp/EMD_wrapper.cpp +++ b/ot/lp/EMD_wrapper.cpp @@ -15,7 +15,7 @@ #include "EMD.h" -int EMD_wrap(int n1,int n2, double *X, double *Y,double *D, double *G, double *cost, int numItermax) { +int EMD_wrap(int n1,int n2, double *X, double *Y,double *D, double *G, double *cost, int max_iter) { // beware M and C anre strored in row major C style!!! int n, m, i,cur; double max; @@ -45,7 +45,7 @@ int EMD_wrap(int n1,int n2, double *X, double *Y,double *D, double *G, double *c std::vector indI(n), indJ(m); std::vector weights1(n), weights2(m); Digraph di(n, m); - NetworkSimplexSimple net(di, true, n+m, n*m, numItermax); + NetworkSimplexSimple net(di, true, n+m, n*m, max_iter); // Set supply and demand, don't account for 0 values (faster) diff --git a/ot/lp/__init__.py b/ot/lp/__init__.py index 5143a70..7bef648 100644 --- a/ot/lp/__init__.py +++ b/ot/lp/__init__.py @@ -14,8 +14,7 @@ from ..utils import parmap import multiprocessing - -def emd(a, b, M, numItermax=10000): +def emd(a, b, M, max_iter=100000): """Solves the Earth Movers distance problem and returns the OT matrix @@ -40,8 +39,9 @@ def emd(a, b, M, numItermax=10000): Target histogram (uniform weigth if empty list) M : (ns,nt) ndarray, float64 loss matrix - numItermax : int - Maximum number of iterations made by the LP solver. + max_iter : int, optional (default=100000) + The maximum number of iterations before stopping the optimization + algorithm if it has not converged. Returns ------- @@ -54,7 +54,7 @@ def emd(a, b, M, numItermax=10000): Simple example with obvious solution. The function emd accepts lists and perform automatic conversion to numpy arrays - + >>> import ot >>> a=[.5,.5] >>> b=[.5,.5] @@ -86,10 +86,11 @@ def emd(a, b, M, numItermax=10000): if len(b) == 0: b = np.ones((M.shape[1], ), dtype=np.float64)/M.shape[1] - return emd_c(a, b, M, numItermax) + return emd_c(a, b, M, max_iter) + -def emd2(a, b, M, processes=multiprocessing.cpu_count(), numItermax=10000): - """Solves the Earth Movers distance problem and returns the loss +def emd2(a, b, M, processes=multiprocessing.cpu_count(), max_iter=100000): + """Solves the Earth Movers distance problem and returns the loss .. math:: \gamma = arg\min_\gamma <\gamma,M>_F @@ -112,8 +113,9 @@ def emd2(a, b, M, processes=multiprocessing.cpu_count(), numItermax=10000): Target histogram (uniform weigth if empty list) M : (ns,nt) ndarray, float64 loss matrix - numItermax : int - Maximum number of iterations made by the LP solver. + max_iter : int, optional (default=100000) + The maximum number of iterations before stopping the optimization + algorithm if it has not converged. Returns ------- @@ -126,15 +128,15 @@ def emd2(a, b, M, processes=multiprocessing.cpu_count(), numItermax=10000): Simple example with obvious solution. The function emd accepts lists and perform automatic conversion to numpy arrays - - + + >>> import ot >>> a=[.5,.5] >>> b=[.5,.5] >>> M=[[0.,1.],[1.,0.]] >>> ot.emd2(a,b,M) 0.0 - + References ---------- @@ -157,16 +159,14 @@ def emd2(a, b, M, processes=multiprocessing.cpu_count(), numItermax=10000): 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 emd2_c(a, b, M, numItermax) + + if len(b.shape) == 1: + return emd2_c(a, b, M, max_iter) else: - nb=b.shape[1] - #res=[emd2_c(a,b[:,i].copy(),M, numItermax) for i in range(nb)] + nb = b.shape[1] + # res = [emd2_c(a, b[:, i].copy(), M, max_iter) for i in range(nb)] + def f(b): - return emd2_c(a,b,M, numItermax) - res= parmap(f, [b[:,i] for i in range(nb)],processes) + return emd2_c(a, b, M, max_iter) + res = parmap(f, [b[:, i] for i in range(nb)], processes) return np.array(res) - - - \ No newline at end of file diff --git a/ot/lp/emd_wrap.pyx b/ot/lp/emd_wrap.pyx index 6039e1f..26d3330 100644 --- a/ot/lp/emd_wrap.pyx +++ b/ot/lp/emd_wrap.pyx @@ -15,56 +15,57 @@ cimport cython cdef extern from "EMD.h": - int EMD_wrap(int n1,int n2, double *X, double *Y,double *D, double *G, double *cost, int numItermax) + int EMD_wrap(int n1,int n2, double *X, double *Y,double *D, double *G, double *cost, int max_iter) cdef enum ProblemType: INFEASIBLE, OPTIMAL, UNBOUNDED @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 max_iter): """ Solves the Earth Movers distance problem and returns the optimal transport matrix - + gamm=emd(a,b,M) - + .. math:: - \gamma = arg\min_\gamma <\gamma,M>_F - + \gamma = arg\min_\gamma <\gamma,M>_F + s.t. \gamma 1 = a - - \gamma^T 1= b - + + \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 + source histogram b : (nt,) ndarray, float64 target histogram M : (ns,nt) ndarray, float64 - loss matrix - numItermax : int - Maximum number of iterations made by the LP solver. - - + loss matrix + max_iter : 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 float cost=0 cdef np.ndarray[double, ndim=2, mode="c"] G=np.zeros([n1, n2]) - + if not len(a): a=np.ones((n1,))/n1 @@ -72,7 +73,7 @@ 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, &cost, numItermax) + cdef int resultSolver = EMD_wrap(n1,n2, a.data, b.data, M.data, G.data, &cost, max_iter) if resultSolver != OPTIMAL: if resultSolver == INFEASIBLE: print("Problem infeasible. Try to increase numItermax.") @@ -83,49 +84,50 @@ def emd_c( np.ndarray[double, ndim=1, mode="c"] a,np.ndarray[double, ndim=1, mod @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): +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): """ 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 - + \gamma = arg\min_\gamma <\gamma,M>_F + s.t. \gamma 1 = a - - \gamma^T 1= b - + + \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 + source histogram b : (nt,) ndarray, float64 target histogram M : (ns,nt) ndarray, float64 - loss matrix - numItermax : int - Maximum number of iterations made by the LP solver. - - + loss matrix + max_iter : 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 float cost=0 cdef np.ndarray[double, ndim=2, mode="c"] G=np.zeros([n1, n2]) - + if not len(a): a=np.ones((n1,))/n1 @@ -133,13 +135,13 @@ def emd2_c( np.ndarray[double, ndim=1, mode="c"] a,np.ndarray[double, ndim=1, mo b=np.ones((n2,))/n2 # calling the function - cdef int resultSolver = EMD_wrap(n1,n2, a.data, b.data, M.data, G.data, &cost, numItermax) + cdef int resultSolver = EMD_wrap(n1,n2, a.data, b.data, M.data, G.data, &cost, max_iter) if resultSolver != OPTIMAL: if resultSolver == INFEASIBLE: print("Problem infeasible. Try to inscrease numItermax.") elif resultSolver == UNBOUNDED: print("Problem unbounded") - + cost=0 for i in range(n1): for j in range(n2): -- cgit v1.2.3 From feef989b155c0b77a1a014e58db7db390b1ee6d4 Mon Sep 17 00:00:00 2001 From: aje Date: Wed, 30 Aug 2017 10:06:41 +0200 Subject: Rename for emd and emd2 --- ot/lp/__init__.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ot/lp/__init__.py b/ot/lp/__init__.py index 7bef648..de91e74 100644 --- a/ot/lp/__init__.py +++ b/ot/lp/__init__.py @@ -14,7 +14,7 @@ from ..utils import parmap import multiprocessing -def emd(a, b, M, max_iter=100000): +def emd(a, b, M, numItermax=100000): """Solves the Earth Movers distance problem and returns the OT matrix @@ -39,7 +39,7 @@ def emd(a, b, M, max_iter=100000): Target histogram (uniform weigth if empty list) M : (ns,nt) ndarray, float64 loss matrix - max_iter : int, optional (default=100000) + numItermax : int, optional (default=100000) The maximum number of iterations before stopping the optimization algorithm if it has not converged. @@ -86,10 +86,10 @@ def emd(a, b, M, max_iter=100000): if len(b) == 0: b = np.ones((M.shape[1], ), dtype=np.float64)/M.shape[1] - return emd_c(a, b, M, max_iter) + return emd_c(a, b, M, numItermax) -def emd2(a, b, M, processes=multiprocessing.cpu_count(), max_iter=100000): +def emd2(a, b, M, processes=multiprocessing.cpu_count(), numItermax=100000): """Solves the Earth Movers distance problem and returns the loss .. math:: @@ -113,7 +113,7 @@ def emd2(a, b, M, processes=multiprocessing.cpu_count(), max_iter=100000): Target histogram (uniform weigth if empty list) M : (ns,nt) ndarray, float64 loss matrix - max_iter : int, optional (default=100000) + numItermax : int, optional (default=100000) The maximum number of iterations before stopping the optimization algorithm if it has not converged. @@ -161,12 +161,12 @@ def emd2(a, b, M, processes=multiprocessing.cpu_count(), max_iter=100000): b = np.ones((M.shape[1], ), dtype=np.float64)/M.shape[1] if len(b.shape) == 1: - return emd2_c(a, b, M, max_iter) + return emd2_c(a, b, M, numItermax) else: nb = b.shape[1] - # res = [emd2_c(a, b[:, i].copy(), M, max_iter) for i in range(nb)] + # 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) + return emd2_c(a, b, M, numItermax) res = parmap(f, [b[:, i] for i in range(nb)], processes) return np.array(res) -- cgit v1.2.3 From 5bbea9c608e66b499f9858af408cc65c07cf4ac2 Mon Sep 17 00:00:00 2001 From: aje Date: Wed, 30 Aug 2017 10:22:48 +0200 Subject: Fix name error --- ot/da.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ot/da.py b/ot/da.py index 5871aba..b4a69b1 100644 --- a/ot/da.py +++ b/ot/da.py @@ -1387,7 +1387,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_, numItermax=self.max_iter ) return self -- cgit v1.2.3 From 0316d552fa6005aaf0f6231eb9ca20441d5a2532 Mon Sep 17 00:00:00 2001 From: aje Date: Wed, 30 Aug 2017 10:53:31 +0200 Subject: Move normalize function in utils.py --- ot/da.py | 52 ++++++---------------------------------------------- ot/utils.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 46 deletions(-) diff --git a/ot/da.py b/ot/da.py index b4a69b1..61a3ba0 100644 --- a/ot/da.py +++ b/ot/da.py @@ -13,7 +13,7 @@ import numpy as np from .bregman import sinkhorn from .lp import emd -from .utils import unif, dist, kernel +from .utils import unif, dist, kernel, cost_normalization from .utils import check_params, deprecated, BaseEstimator from .optim import cg from .optim import gcg @@ -673,7 +673,7 @@ class OTDA(object): self.wt = wt self.M = dist(xs, xt, metric=self.metric) - self.normalizeM(norm) + self.M = cost_normalization(self.M, norm) self.G = emd(ws, wt, self.M, max_iter) self.computed = True @@ -741,26 +741,6 @@ class OTDA(object): # aply the delta to the interpolation return xf[idx, :] + x - x0[idx, :] - def normalizeM(self, norm): - """ Apply normalization to the loss matrix - - - Parameters - ---------- - norm : str - type of normalization from 'median','max','log','loglog' - - """ - - if norm == "median": - self.M /= float(np.median(self.M)) - elif norm == "max": - self.M /= float(np.max(self.M)) - elif norm == "log": - self.M = np.log(1 + self.M) - elif norm == "loglog": - 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.") @@ -787,7 +767,7 @@ class OTDA_sinkhorn(OTDA): self.wt = wt self.M = dist(xs, xt, metric=self.metric) - self.normalizeM(norm) + self.M = cost_normalization(self.M, norm) self.G = sinkhorn(ws, wt, self.M, reg, **kwargs) self.computed = True @@ -816,7 +796,7 @@ class OTDA_lpl1(OTDA): self.wt = wt self.M = dist(xs, xt, metric=self.metric) - self.normalizeM(norm) + self.M = cost_normalization(self.M, norm) self.G = sinkhorn_lpl1_mm(ws, ys, wt, self.M, reg, eta, **kwargs) self.computed = True @@ -845,7 +825,7 @@ class OTDA_l1l2(OTDA): self.wt = wt self.M = dist(xs, xt, metric=self.metric) - self.normalizeM(norm) + self.M = cost_normalization(self.M, norm) self.G = sinkhorn_l1l2_gl(ws, ys, wt, self.M, reg, eta, **kwargs) self.computed = True @@ -1001,7 +981,7 @@ class BaseTransport(BaseEstimator): # pairwise distance self.cost_ = dist(Xs, Xt, metric=self.metric) - self.normalizeCost_(self.norm) + self.cost_ = cost_normalization(self.cost_, self.norm) if (ys is not None) and (yt is not None): @@ -1183,26 +1163,6 @@ class BaseTransport(BaseEstimator): return transp_Xt - def normalizeCost_(self, norm): - """ Apply normalization to the loss matrix - - - Parameters - ---------- - norm : str - type of normalization from 'median','max','log','loglog' - - """ - - if norm == "median": - self.cost_ /= float(np.median(self.cost_)) - elif norm == "max": - self.cost_ /= float(np.max(self.cost_)) - elif norm == "log": - self.cost_ = np.log(1 + self.cost_) - elif norm == "loglog": - self.cost_ = np.log(1 + np.log(1 + self.cost_)) - class SinkhornTransport(BaseTransport): """Domain Adapatation OT method based on Sinkhorn Algorithm diff --git a/ot/utils.py b/ot/utils.py index 01f2a67..31a002b 100644 --- a/ot/utils.py +++ b/ot/utils.py @@ -134,6 +134,39 @@ def dist0(n, method='lin_square'): return res +def cost_normalization(C, norm=None): + """ Apply normalization to the loss matrix + + + Parameters + ---------- + C : np.array (n1, n2) + The cost matrix to normalize. + norm : str + type of normalization from 'median','max','log','loglog'. Any other + value do not normalize. + + + Returns + ------- + + C : np.array (n1, n2) + The input cost matrix normalized according to given norm. + + """ + + if norm == "median": + C /= float(np.median(C)) + elif norm == "max": + C /= float(np.max(C)) + elif norm == "log": + C = np.log(1 + C) + elif norm == "loglog": + C = np.log(1 + np.log(1 + C)) + + return C + + def dots(*args): """ dots function for multiple matrix multiply """ return reduce(np.dot, args) -- cgit v1.2.3 From fadaf2ab3c3844d281b22f8d5c3404c3c4cf7d97 Mon Sep 17 00:00:00 2001 From: aje Date: Wed, 30 Aug 2017 11:25:03 +0200 Subject: Move norm out of fit to init for deprecated OTDA --- ot/da.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/ot/da.py b/ot/da.py index 61a3ba0..564c7b7 100644 --- a/ot/da.py +++ b/ot/da.py @@ -650,15 +650,16 @@ class OTDA(object): """ - def __init__(self, metric='sqeuclidean'): + def __init__(self, metric='sqeuclidean', norm=None): """ Class initialization""" self.xs = 0 self.xt = 0 self.G = 0 self.metric = metric + self.norm = norm self.computed = False - def fit(self, xs, xt, ws=None, wt=None, norm=None, max_iter=100000): + def fit(self, xs, xt, ws=None, wt=None, max_iter=100000): """Fit domain adaptation between samples is xs and xt (with optional weights)""" self.xs = xs @@ -673,7 +674,7 @@ class OTDA(object): self.wt = wt self.M = dist(xs, xt, metric=self.metric) - self.M = cost_normalization(self.M, norm) + self.M = cost_normalization(self.M, self.norm) self.G = emd(ws, wt, self.M, max_iter) self.computed = True @@ -752,7 +753,7 @@ class OTDA_sinkhorn(OTDA): """ - def fit(self, xs, xt, reg=1, ws=None, wt=None, norm=None, **kwargs): + def fit(self, xs, xt, reg=1, ws=None, wt=None, **kwargs): """Fit regularized domain adaptation between samples is xs and xt (with optional weights)""" self.xs = xs @@ -767,7 +768,7 @@ class OTDA_sinkhorn(OTDA): self.wt = wt self.M = dist(xs, xt, metric=self.metric) - self.M = cost_normalization(self.M, norm) + self.M = cost_normalization(self.M, self.norm) self.G = sinkhorn(ws, wt, self.M, reg, **kwargs) self.computed = True @@ -779,8 +780,7 @@ class OTDA_lpl1(OTDA): """Class for domain adaptation with optimal transport with entropic and group regularization""" - def fit(self, xs, ys, xt, reg=1, eta=1, ws=None, wt=None, norm=None, - **kwargs): + def fit(self, xs, ys, xt, reg=1, eta=1, ws=None, wt=None, **kwargs): """Fit regularized domain adaptation between samples is xs and xt (with optional weights), See ot.da.sinkhorn_lpl1_mm for fit parameters""" @@ -796,7 +796,7 @@ class OTDA_lpl1(OTDA): self.wt = wt self.M = dist(xs, xt, metric=self.metric) - self.M = cost_normalization(self.M, norm) + self.M = cost_normalization(self.M, self.norm) self.G = sinkhorn_lpl1_mm(ws, ys, wt, self.M, reg, eta, **kwargs) self.computed = True @@ -808,8 +808,7 @@ class OTDA_l1l2(OTDA): """Class for domain adaptation with optimal transport with entropic and group lasso regularization""" - def fit(self, xs, ys, xt, reg=1, eta=1, ws=None, wt=None, norm=None, - **kwargs): + def fit(self, xs, ys, xt, reg=1, eta=1, ws=None, wt=None, **kwargs): """Fit regularized domain adaptation between samples is xs and xt (with optional weights), See ot.da.sinkhorn_lpl1_gl for fit parameters""" @@ -825,7 +824,7 @@ class OTDA_l1l2(OTDA): self.wt = wt self.M = dist(xs, xt, metric=self.metric) - self.M = cost_normalization(self.M, norm) + self.M = cost_normalization(self.M, self.norm) self.G = sinkhorn_l1l2_gl(ws, ys, wt, self.M, reg, eta, **kwargs) self.computed = True -- cgit v1.2.3