Skip to content

Commit 8355c50

Browse files
Circle CICircle CI
authored andcommitted
CircleCI update of dev docs (4016).
1 parent 78371b3 commit 8355c50

882 files changed

Lines changed: 2005286 additions & 1505486 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

_downloads/00efe738b664431523810a0d0baf5949/plot_lowrank_GW.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@
179179
"name": "python",
180180
"nbconvert_exporter": "python",
181181
"pygments_lexer": "ipython3",
182-
"version": "3.10.18"
182+
"version": "3.12.13"
183183
}
184184
},
185185
"nbformat": 4,

_downloads/01f7e78349daa20a31a00f8d86852f5e/plot_partial_1d.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
"name": "python",
4747
"nbconvert_exporter": "python",
4848
"pygments_lexer": "ipython3",
49-
"version": "3.10.18"
49+
"version": "3.12.13"
5050
}
5151
},
5252
"nbformat": 4,

_downloads/059a63fc6cb655dcd2f1c9a61bb9d7ec/plot_OT_2D_samples.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@
125125
"name": "python",
126126
"nbconvert_exporter": "python",
127127
"pygments_lexer": "ipython3",
128-
"version": "3.10.18"
128+
"version": "3.12.13"
129129
}
130130
},
131131
"nbformat": 4,
Binary file not shown.
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
# -*- coding: utf-8 -*-
2+
r"""
3+
===============================================================================
4+
Solve Fused Unbalanced Gromov Wasserstein with Adam
5+
===============================================================================
6+
7+
Since the FUGW loss is differentiable, it can be minimized with first-order optimization.
8+
We show how to do this with the `loss_fugw_batch` function and compare the results with
9+
the dedicated FUGW solver `fused_unbalanced_gromov_wasserstein`.
10+
"""
11+
12+
# Author: Rémi Flamary <remi.flamary@polytechnique.edu>
13+
# Sonia Mazelet <sonia.mazelet@polytechnique.edu>
14+
#
15+
# License: MIT License
16+
17+
# sphinx_gallery_thumbnail_number = 3
18+
19+
import numpy as np
20+
import matplotlib.pylab as pl
21+
import torch
22+
from time import perf_counter
23+
import ot
24+
from ot.batch._quadratic import loss_quadratic_batch, tensor_batch
25+
from ot.gromov import fused_unbalanced_gromov_wasserstein
26+
from sklearn.manifold import MDS
27+
28+
29+
# %%
30+
# Generation of source and target graphs
31+
# ----------------
32+
33+
rng = np.random.RandomState(42)
34+
35+
36+
def get_sbm(n, nc, ratio, P):
37+
nbpc = np.round(n * ratio).astype(int)
38+
n = np.sum(nbpc)
39+
C = np.zeros((n, n))
40+
for c1 in range(nc):
41+
for c2 in range(c1 + 1):
42+
if c1 == c2:
43+
for i in range(np.sum(nbpc[:c1]), np.sum(nbpc[: c1 + 1])):
44+
for j in range(np.sum(nbpc[:c2]), i):
45+
if rng.rand() <= P[c1, c2]:
46+
C[i, j] = 1
47+
else:
48+
for i in range(np.sum(nbpc[:c1]), np.sum(nbpc[: c1 + 1])):
49+
for j in range(np.sum(nbpc[:c2]), np.sum(nbpc[: c2 + 1])):
50+
if rng.rand() <= P[c1, c2]:
51+
C[i, j] = 1
52+
53+
return C + C.T
54+
55+
56+
def plot_graph(x, C, color="C0", s=100):
57+
for j in range(C.shape[0]):
58+
for i in range(j):
59+
if C[i, j] > 0:
60+
pl.plot([x[i, 0], x[j, 0]], [x[i, 1], x[j, 1]], alpha=0.2, color="k")
61+
pl.scatter(x[:, 0], x[:, 1], c=color, s=s, zorder=10, edgecolors="k")
62+
63+
64+
def get_sbm_labels(n, ratio):
65+
nbpc = np.round(n * ratio).astype(int)
66+
return np.concatenate(
67+
[np.full(count, label, dtype=int) for label, count in enumerate(nbpc)]
68+
)
69+
70+
71+
def get_noisy_one_hot(labels, n_classes, noise_level=0.1):
72+
x = np.eye(n_classes)[labels]
73+
x += noise_level * rng.randn(*x.shape)
74+
return x
75+
76+
77+
n1 = 15
78+
n2 = 10
79+
nc1 = 3
80+
nc2 = 2
81+
ratio1 = np.array([0.33, 0.33, 0.33])
82+
ratio2 = np.array([0.5, 0.5])
83+
84+
P1 = np.array([[0.8, 0.03, 0.0], [0.08, 0.8, 0.03], [0.0, 0.08, 0.8]])
85+
P2 = np.array(0.8 * np.eye(2) + 0.01 * np.ones((2, 2)))
86+
C1 = get_sbm(n1, nc1, ratio1, P1)
87+
C2 = get_sbm(n2, nc2, ratio2, P2)
88+
labels1 = get_sbm_labels(n1, ratio1)
89+
labels2 = get_sbm_labels(n2, ratio2)
90+
91+
# Use noisy one-hot encodings of the SBM classes as node features.
92+
feature_dim = max(nc1, nc2)
93+
x1 = get_noisy_one_hot(labels1, feature_dim)
94+
x2 = get_noisy_one_hot(labels2, feature_dim)
95+
all_features = np.vstack([x1, x2])
96+
feature_min = all_features[:, :3].min(axis=0, keepdims=True)
97+
feature_max = all_features[:, :3].max(axis=0, keepdims=True)
98+
99+
# get 2d positions for visualization
100+
pos1 = MDS(dissimilarity="precomputed", random_state=0, n_init=1).fit_transform(1 - C1)
101+
pos2 = MDS(dissimilarity="precomputed", random_state=0, n_init=1).fit_transform(1 - C2)
102+
103+
colors1 = np.clip(
104+
(x1 - feature_min) / np.maximum(feature_max - feature_min, 1e-15), 0.0, 1.0
105+
)
106+
colors2 = np.clip(
107+
(x2 - feature_min) / np.maximum(feature_max - feature_min, 1e-15), 0.0, 1.0
108+
)
109+
110+
111+
pl.figure(1, (10, 5))
112+
pl.clf()
113+
pl.subplot(1, 2, 1)
114+
plot_graph(pos1, C1, color=colors1)
115+
pl.title("SBM source graph")
116+
pl.axis("off")
117+
pl.subplot(1, 2, 2)
118+
plot_graph(pos2, C2, color=colors2)
119+
pl.title("SBM target graph")
120+
_ = pl.axis("off")
121+
122+
123+
# %%
124+
# Solve FUGW with Adam
125+
# ----------------
126+
127+
# Even though `loss_fugw_batch` supports batches of problems, we use a
128+
# batch of size 1 here for clarity.
129+
130+
a = ot.unif(C1.shape[0])
131+
b = ot.unif(C2.shape[0])
132+
M = ot.dist(x1, x2)
133+
M /= M.max()
134+
135+
a_torch = torch.tensor(a[None, :])
136+
b_torch = torch.tensor(b[None, :])
137+
C1_torch = torch.tensor(C1[None, :, :])
138+
C2_torch = torch.tensor(C2[None, :, :])
139+
M_torch = torch.tensor(M[None, :, :])
140+
L = tensor_batch(a_torch, b_torch, C1_torch, C2_torch, loss="sqeuclidean")
141+
142+
alpha = 0.5
143+
reg_marginals = 0.5
144+
lr = 5e-2
145+
nb_iter_max = 1500
146+
tol = 1e-7
147+
148+
T0_torch = a_torch[:, :, None] * b_torch[:, None, :]
149+
T_torch = torch.log(torch.expm1(T0_torch)).clone().requires_grad_(True)
150+
optimizer = torch.optim.Adam([T_torch], lr=lr)
151+
loss_iter = []
152+
mass_iter = []
153+
previous_plan_torch = None
154+
155+
tic = perf_counter()
156+
for i in range(nb_iter_max):
157+
optimizer.zero_grad()
158+
# Positive transport plan parameterized as log(1 + exp(T)).
159+
plan_torch = torch.nn.functional.softplus(T_torch)
160+
loss = loss_quadratic_batch(
161+
a_torch,
162+
b_torch,
163+
C1_torch,
164+
C2_torch,
165+
plan_torch,
166+
M_torch,
167+
alpha=alpha,
168+
unbalanced=reg_marginals,
169+
unbalanced_type="kl",
170+
recompute_const=True,
171+
)[0]
172+
173+
loss_iter.append(float(loss.detach()))
174+
mass_iter.append(float(plan_torch.detach().sum()))
175+
if previous_plan_torch is not None:
176+
err = float(torch.sum(torch.abs(plan_torch.detach() - previous_plan_torch)))
177+
if err < tol:
178+
break
179+
previous_plan_torch = plan_torch.detach().clone()
180+
loss.backward()
181+
optimizer.step()
182+
time_adam = perf_counter() - tic
183+
184+
T_adam = torch.nn.functional.softplus(T_torch).detach().cpu().numpy()[0]
185+
186+
187+
# %%
188+
# Compare with the dedicated FUGW solver
189+
# -------------------------------------
190+
#
191+
# The dedicated solver uses a block coordinate descent (BCD) scheme. We compare
192+
# the coupling it returns with the one obtained by direct Adam minimization of
193+
# `loss_fugw_batch`.
194+
195+
196+
def evaluate_batch_fugw_loss(plan):
197+
plan_torch = torch.tensor(plan[None, :, :], dtype=M_torch.dtype)
198+
loss = loss_quadratic_batch(
199+
a_torch,
200+
b_torch,
201+
C1_torch,
202+
C2_torch,
203+
plan_torch,
204+
M_torch,
205+
alpha=alpha,
206+
unbalanced=reg_marginals,
207+
unbalanced_type="kl",
208+
recompute_const=True,
209+
)[0]
210+
return float(loss.detach())
211+
212+
213+
tic = perf_counter()
214+
result = ot.solve_gromov(
215+
C1, C2, M, a, b, alpha=alpha, reg=0, unbalanced_type="kl", unbalanced=reg_marginals
216+
)
217+
time_bcd = perf_counter() - tic
218+
219+
loss_adam_final = evaluate_batch_fugw_loss(T_adam)
220+
T_bcd = result.plan
221+
loss_bcd_final = evaluate_batch_fugw_loss(T_bcd)
222+
mass_bcd = T_bcd.sum()
223+
224+
pl.figure(2, (10, 4))
225+
pl.clf()
226+
pl.subplot(1, 2, 1)
227+
pl.plot(loss_iter, label="Adam")
228+
pl.axhline(loss_bcd_final, color="C1", linestyle="--", label="BCD solver")
229+
pl.grid()
230+
pl.title("FUGW loss along iterations")
231+
pl.xlabel("Iterations")
232+
pl.legend()
233+
pl.subplot(1, 2, 2)
234+
pl.plot(mass_iter, label="Adam")
235+
pl.axhline(mass_bcd, color="C1", linestyle="--", label="BCD solver")
236+
pl.grid()
237+
pl.title("Transport mass")
238+
pl.xlabel("Iterations")
239+
_ = pl.legend()
240+
241+
242+
# %%
243+
# Visualize the learned couplings
244+
# -------------------------------
245+
# We visualize the couplings obtained by both methods to compare them. On this example, both methods recover similar couplings,
246+
# but direct minimization reaches a lower `loss_fugw_batch` value at the cost
247+
# of a longer runtime.
248+
249+
vmin = min(T_adam.min(), T_bcd.min())
250+
vmax = max(T_adam.max(), T_bcd.max())
251+
pl.figure(3, (10, 4))
252+
pl.clf()
253+
pl.subplot(1, 2, 1)
254+
pl.imshow(T_adam, interpolation="nearest", cmap="Blues", vmin=vmin, vmax=vmax)
255+
pl.title(
256+
f"Coupling from direct minimization\nloss={loss_adam_final:.3f}, time={time_adam:.2f}s"
257+
)
258+
pl.xlabel("Target nodes")
259+
pl.ylabel("Source nodes")
260+
pl.colorbar()
261+
pl.subplot(1, 2, 2)
262+
pl.imshow(T_bcd, interpolation="nearest", cmap="Blues", vmin=vmin, vmax=vmax)
263+
pl.title(f"Coupling from BCD solver\nloss={loss_bcd_final:.3f}, time={time_bcd:.2f}s")
264+
pl.xlabel("Target nodes")
265+
pl.ylabel("Source nodes")
266+
_ = pl.colorbar()

_downloads/0dbd57c6090215001a0a712021c577e5/plot_GMMOT_plan.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
"name": "python",
7272
"nbconvert_exporter": "python",
7373
"pygments_lexer": "ipython3",
74-
"version": "3.10.18"
74+
"version": "3.12.13"
7575
}
7676
},
7777
"nbformat": 4,

_downloads/0e0c970a6374049b6079f4ba3f451631/plot_fgw_solvers.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@
8989
"name": "python",
9090
"nbconvert_exporter": "python",
9191
"pygments_lexer": "ipython3",
92-
"version": "3.10.18"
92+
"version": "3.12.13"
9393
}
9494
},
9595
"nbformat": 4,

_downloads/0e15d87ff8ae3d0dd037bf7ddc80d8a7/plot_variance.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@
118118
"name": "python",
119119
"nbconvert_exporter": "python",
120120
"pygments_lexer": "ipython3",
121-
"version": "3.10.18"
121+
"version": "3.12.13"
122122
}
123123
},
124124
"nbformat": 4,

_downloads/0e77b32207676919c68b7f28c6029b1b/plot_unmix_optim_torch.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@
125125
"name": "python",
126126
"nbconvert_exporter": "python",
127127
"pygments_lexer": "ipython3",
128-
"version": "3.10.18"
128+
"version": "3.12.13"
129129
}
130130
},
131131
"nbformat": 4,

_downloads/0eb518a5cb2af5a1870fc1f120d1483e/plot_partial_wass_and_gromov.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@
107107
"name": "python",
108108
"nbconvert_exporter": "python",
109109
"pygments_lexer": "ipython3",
110-
"version": "3.10.18"
110+
"version": "3.12.13"
111111
}
112112
},
113113
"nbformat": 4,

0 commit comments

Comments
 (0)