Skip to content

Commit e2cf853

Browse files
author
Leo gautheron
committed
add GPU implementation sinkhorn lpl1
1 parent 8fc74ed commit e2cf853

File tree

3 files changed

+146
-3
lines changed

3 files changed

+146
-3
lines changed

ot/gpu/bregman.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99

1010
def sinkhorn(a, b, M_GPU, reg, numItermax=1000, stopThr=1e-9, verbose=False,
11-
log=False):
11+
log=False, returnAsGPU=False):
1212
# init data
1313
Nini = len(a)
1414
Nfin = len(b)
@@ -77,7 +77,13 @@ def sinkhorn(a, b, M_GPU, reg, numItermax=1000, stopThr=1e-9, verbose=False,
7777

7878
K_GPU.mult_by_col(u_GPU, target=K_GPU)
7979
K_GPU.mult_by_row(v_GPU.transpose(), target=K_GPU)
80+
81+
if returnAsGPU:
82+
res = K_GPU
83+
else:
84+
res = K_GPU.asarray()
85+
8086
if log:
81-
return K_GPU.asarray(), log
87+
return res, log
8288
else:
83-
return K_GPU.asarray()
89+
return res

ot/gpu/da.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,27 @@
1111

1212

1313
def pairwiseEuclideanGPU(a, b, returnAsGPU=False, squared=False):
14+
"""
15+
Compute the pairwise euclidean distance between matrices a and b.
16+
17+
18+
Parameters
19+
----------
20+
a : np.ndarray (n, f)
21+
first matrice
22+
b : np.ndarray (m, f)
23+
second matrice
24+
returnAsGPU : boolean, optional (default False)
25+
if True, returns cudamat matrix still on GPU, else return np.ndarray
26+
squared : boolean, optional (default False)
27+
if True, return squared euclidean distance matrice
28+
29+
30+
Returns
31+
-------
32+
c : (n x m) np.ndarray or cudamat.CUDAMatrix
33+
pairwise euclidean distance distance matrix
34+
"""
1435
# a is shape (n, f) and b shape (m, f). Return matrix c of shape (n, m).
1536
# First compute in c_GPU the squared euclidean distance. And return its
1637
# square root. At each cell [i,j] of c, we want to have
@@ -46,6 +67,69 @@ def pairwiseEuclideanGPU(a, b, returnAsGPU=False, squared=False):
4667
return c_GPU.asarray()
4768

4869

70+
def sinkhorn_lpl1_mm(a, labels_a, b, M_GPU, reg, eta=0.1, numItermax=10,
71+
numInnerItermax=200, stopInnerThr=1e-9,
72+
unlabelledValue=-99, verbose=False, log=False):
73+
p = 0.5
74+
epsilon = 1e-3
75+
76+
# init data
77+
Nfin = len(b)
78+
79+
indices_labels = []
80+
classes = np.unique(labels_a)
81+
for c in classes:
82+
idxc, = np.where(labels_a == c)
83+
indices_labels.append(cudamat.CUDAMatrix(idxc.reshape(1, -1)))
84+
85+
Mreg_GPU = cudamat.empty(M_GPU.shape)
86+
W_GPU = cudamat.empty(M_GPU.shape).assign(0)
87+
88+
for cpt in range(numItermax):
89+
Mreg_GPU.assign(M_GPU)
90+
Mreg_GPU.add_mult(W_GPU, eta)
91+
transp_GPU = sinkhorn(a, b, Mreg_GPU, reg, numItermax=numInnerItermax,
92+
stopThr=stopInnerThr, returnAsGPU=True)
93+
# the transport has been computed. Check if classes are really
94+
# separated
95+
W_GPU.assign(1)
96+
W_GPU = W_GPU.transpose()
97+
all_majs_GPU = []
98+
idx_unlabelled = -1
99+
for (i, c) in enumerate(classes):
100+
if c != unlabelledValue:
101+
(_, nbRow) = indices_labels[i].shape
102+
tmpC_GPU = cudamat.empty((Nfin, nbRow)).assign(0)
103+
transp_GPU.transpose().select_columns(indices_labels[i],
104+
tmpC_GPU)
105+
majs_GPU = tmpC_GPU.sum(axis=1).add(epsilon)
106+
cudamat.pow(majs_GPU, (p-1))
107+
majs_GPU.mult(p)
108+
all_majs_GPU.append(majs_GPU)
109+
110+
tmpC_GPU.assign(0)
111+
tmpC_GPU.add_col_vec(majs_GPU)
112+
W_GPU.set_selected_columns(indices_labels[i], tmpC_GPU)
113+
else:
114+
idx_unlabelled = i
115+
116+
# now we majorize the unlabelled (if there are any) by the min of
117+
# the majorizations. do it only for unlabbled data
118+
if idx_unlabelled != -1:
119+
all_majs = np.array([m_GPU.asarray() for m_GPU in all_majs_GPU])
120+
minMaj_GPU = (cudamat.CUDAMatrix(all_majs).min(axis=0)
121+
.transpose())
122+
(_, nbRow) = indices_labels[idx_unlabelled].shape
123+
tmpC_GPU = cudamat.empty((Nfin, nbRow)).assign(0)
124+
125+
tmpC_GPU.add_col_vec(minMaj_GPU)
126+
W_GPU.set_selected_columns(indices_labels[idx_unlabelled],
127+
tmpC_GPU)
128+
W_GPU = W_GPU.transpose()
129+
130+
return transp_GPU.asarray()
131+
132+
49133
class OTDA_GPU(OTDA):
50134
def normalizeM(self, norm):
51135
if norm == "median":
@@ -84,3 +168,28 @@ def fit(self, xs, xt, reg=1, ws=None, wt=None, norm=None, **kwargs):
84168
self.normalizeM(norm)
85169
self.G = sinkhorn(ws, wt, self.M_GPU, reg, **kwargs)
86170
self.computed = True
171+
172+
173+
class OTDA_lpl1(OTDA_GPU):
174+
def fit(self, xs, ys, xt, reg=1, eta=1, ws=None, wt=None, norm=None,
175+
**kwargs):
176+
cudamat.init()
177+
xs = np.asarray(xs, dtype=np.float64)
178+
xt = np.asarray(xt, dtype=np.float64)
179+
180+
self.xs = xs
181+
self.xt = xt
182+
183+
if wt is None:
184+
wt = unif(xt.shape[0])
185+
if ws is None:
186+
ws = unif(xs.shape[0])
187+
188+
self.ws = ws
189+
self.wt = wt
190+
191+
self.M_GPU = pairwiseEuclideanGPU(xs, xt, returnAsGPU=True,
192+
squared=True)
193+
self.normalizeM(norm)
194+
self.G = sinkhorn_lpl1_mm(ws, ys, wt, self.M_GPU, reg, eta, **kwargs)
195+
self.computed = True

test/test_gpu_sinkhorn_lpl1.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import ot
2+
import numpy as np
3+
import time
4+
import ot.gpu
5+
6+
7+
def describeRes(r):
8+
print("min:{:.3E}, max:{:.3E}, mean:{:.3E}, std:{:.3E}"
9+
.format(np.min(r), np.max(r), np.mean(r), np.std(r)))
10+
11+
for n in [5000, 10000, 15000, 20000]:
12+
print(n)
13+
a = np.random.rand(n // 4, 100)
14+
labels_a = np.random.randint(10, size=(n // 4))
15+
b = np.random.rand(n, 100)
16+
time1 = time.time()
17+
transport = ot.da.OTDA_lpl1()
18+
transport.fit(a, labels_a, b)
19+
G1 = transport.G
20+
time2 = time.time()
21+
transport = ot.gpu.da.OTDA_lpl1()
22+
transport.fit(a, labels_a, b)
23+
G2 = transport.G
24+
time3 = time.time()
25+
print("Normal sinkhorn lpl1, time: {:6.2f} sec ".format(time2 - time1))
26+
describeRes(G1)
27+
print(" GPU sinkhorn lpl1, time: {:6.2f} sec ".format(time3 - time2))
28+
describeRes(G2)

0 commit comments

Comments
 (0)