Skip to content

Commit 11ba834

Browse files
Circle CICircle CI
authored andcommitted
CircleCI update of dev docs (4084).
1 parent f5a9375 commit 11ba834

323 files changed

Lines changed: 94286 additions & 92751 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.
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
=========================================================
4+
Quasi-Monte Carlo Sliced Wasserstein in 3D
5+
=========================================================
6+
7+
This example illustrates the Quasi-Sliced Wasserstein (QSW) and Randomized
8+
Quasi-Sliced Wasserstein (RQSW) sampling schemes introduced in [95], and
9+
compares them to the default uniform (Monte Carlo) sampling of slicing
10+
directions.
11+
12+
Sliced Wasserstein (SWD) approximates the Wasserstein distance by averaging
13+
1D Wasserstein distances over projections onto random directions
14+
:math:`\\theta` drawn uniformly on the sphere. By default these directions
15+
are sampled purely at random (Monte Carlo), which introduces some variance
16+
in the estimate for a given number of projections.
17+
18+
QSW replaces the random directions with a deterministic, low-discrepancy
19+
point set on the sphere (generalized spiral points), which covers the
20+
sphere more evenly than random sampling and reduces the approximation
21+
error, especially in 3D. Since QSW is deterministic it cannot directly be
22+
used as an unbiased estimator in stochastic settings (e.g. gradient-based
23+
optimization) -- RQSW addresses this by applying a random rotation to the
24+
same point set, which preserves both its low discrepancy and its
25+
unbiasedness.
26+
27+
We first visualize the three sampling schemes on the sphere, then measure
28+
how fast each one converges to the true Sliced Wasserstein distance
29+
between two point clouds -- known here in closed form, with no
30+
approximation error left except from the number of projections itself.
31+
32+
.. [95] Nguyen, K., Bariletto, N., & Ho, N. (2024). Quasi-Monte Carlo for
33+
3D Sliced Wasserstein. International Conference on Learning
34+
Representations (ICLR).
35+
.. [96] Rakhmanov, E. A., Saff, E. B., & Zhou, Y. M. (1994). Minimal
36+
Discrete Energy on the Sphere. Mathematical Research Letters, 1(6),
37+
647-662.
38+
"""
39+
40+
# Author: Samuel Vangu <samuelvangu0@gmail.com>
41+
#
42+
# License: MIT License
43+
44+
# sphinx_gallery_thumbnail_number = 1
45+
46+
import numpy as np
47+
import matplotlib.pylab as pl
48+
from mpl_toolkits.mplot3d import Axes3D # noqa: F401 (registers the 3D projection)
49+
50+
import ot
51+
from ot.sliced import get_random_projections, get_projections_spiral
52+
53+
##############################################################################
54+
# Visualize the three sampling schemes on the sphere
55+
# ----------------------------------------------------
56+
# We draw a few hundred directions on :math:`S^2` with each scheme:
57+
#
58+
# - ``uniform``: directions are Gaussian vectors normalized to unit norm
59+
# (standard Monte Carlo sampling of the sphere).
60+
# - ``spiral_qmc``: deterministic generalized spiral points -- a simple,
61+
# closed-form low-discrepancy point set (Rakhmanov, Saff & Zhou, 1994)
62+
# [96]. The same call always returns the same points.
63+
# - ``randomized_spiral_qmc``: the same spiral point set, rotated by a
64+
# random (3, 3) rotation matrix (drawn via QR decomposition of a
65+
# Gaussian matrix). The rotation makes the estimator unbiased while
66+
# keeping the points as evenly spread out as the deterministic spiral
67+
# set.
68+
69+
n_projections = 500
70+
d = 3
71+
seed = 42
72+
73+
theta_uniform = get_random_projections(d, n_projections, seed=seed)
74+
theta_qsw = get_projections_spiral(d, n_projections, randomized=False)
75+
theta_rqsw = get_projections_spiral(d, n_projections, randomized=True, seed=seed)
76+
77+
fig = pl.figure(1, figsize=(15, 5))
78+
79+
schemes = [
80+
(theta_uniform, "Uniform (Monte Carlo)"),
81+
(theta_qsw, "QSW (deterministic spiral)"),
82+
(theta_rqsw, "RQSW (randomly rotated spiral)"),
83+
]
84+
85+
for i, (theta, title) in enumerate(schemes):
86+
ax = fig.add_subplot(1, 3, i + 1, projection="3d")
87+
ax.scatter(theta[0], theta[1], theta[2], c=theta[2], cmap="viridis", s=4, alpha=0.8)
88+
ax.set_title(title)
89+
ax.set_box_aspect([1, 1, 1])
90+
ax.view_init(elev=20, azim=45)
91+
ax.set_xticks([])
92+
ax.set_yticks([])
93+
ax.set_zticks([])
94+
95+
pl.tight_layout()
96+
pl.show()
97+
98+
# Notice how the uniform sample leaves visible gaps and clusters, while QSW
99+
# and RQSW spread the points much more evenly over the sphere -- this is
100+
# exactly the low-discrepancy property that reduces the error of the Sliced
101+
# Wasserstein estimate.
102+
103+
##############################################################################
104+
# Convergence to the true Sliced Wasserstein distance
105+
# ------------------------------------------------------
106+
# We now compare how fast each sampling scheme converges to the *true*
107+
# SWD as the number of projections grows. To get a reference value with
108+
# **zero** approximation error -- not even from a finite number of
109+
# samples -- we build ``Xt`` as a pure translation of ``Xs`` by a fixed
110+
# vector :math:`\delta`: ``Xt = Xs + delta``.
111+
#
112+
# For a rigid translation, the classical 1D Wasserstein identity
113+
# :math:`W_2(\mu, \mu + c) = |c|` holds *exactly*, for any distribution
114+
# shape and any (even very small) sample size -- no law-of-large-numbers
115+
# argument, no Gaussian assumption, just an algebraic identity of optimal
116+
# transport on the line. Projected onto any direction :math:`\theta`, this
117+
# gives :math:`W_2(\theta_\# \mu, \theta_\# \nu) = |\theta^T \delta|`
118+
# exactly, and averaging the square over :math:`\theta` uniform on
119+
# :math:`S^{d-1}` gives the closed-form identity
120+
#
121+
# .. math::
122+
# \mathcal{SWD}_2(\mu, \nu) = \frac{\|\delta\|}{\sqrt{d}}
123+
#
124+
# Because this holds regardless of ``Xs``'s shape or size, the *only*
125+
# remaining source of error in the experiment below is the number of
126+
# projections -- exactly the quantity we want to study.
127+
128+
rng = np.random.RandomState(0)
129+
130+
n_samples = 200
131+
delta = np.array([1.5, 1.0, -0.5])
132+
Xs = rng.uniform(-2, 2, (n_samples, d))
133+
Xt = Xs + delta
134+
135+
# Exact reference: no approximation at all, at any cost.
136+
sw_true = np.linalg.norm(delta) / np.sqrt(d)
137+
138+
n_proj_list = [10, 20, 50, 100, 200, 500]
139+
n_trials = 8
140+
141+
errors_uniform = np.zeros((n_trials, len(n_proj_list)))
142+
errors_rqsw = np.zeros((n_trials, len(n_proj_list)))
143+
errors_qsw = np.zeros(len(n_proj_list))
144+
145+
for j, n_proj in enumerate(n_proj_list):
146+
for t in range(n_trials):
147+
sw_uniform = ot.sliced_wasserstein_distance(
148+
Xs, Xt, n_projections=n_proj, sampling_slices="uniform", seed=t
149+
)
150+
sw_rqsw = ot.sliced_wasserstein_distance(
151+
Xs,
152+
Xt,
153+
n_projections=n_proj,
154+
sampling_slices="randomized_spiral_qmc",
155+
seed=t,
156+
)
157+
errors_uniform[t, j] = np.abs(sw_uniform - sw_true)
158+
errors_rqsw[t, j] = np.abs(sw_rqsw - sw_true)
159+
160+
sw_qsw = ot.sliced_wasserstein_distance(
161+
Xs, Xt, n_projections=n_proj, sampling_slices="spiral_qmc"
162+
)
163+
errors_qsw[j] = np.abs(sw_qsw - sw_true)
164+
165+
mean_err_uniform = errors_uniform.mean(axis=0)
166+
std_err_uniform = errors_uniform.std(axis=0)
167+
mean_err_rqsw = errors_rqsw.mean(axis=0)
168+
std_err_rqsw = errors_rqsw.std(axis=0)
169+
170+
pl.figure(2, figsize=(6, 5))
171+
pl.plot(n_proj_list, mean_err_uniform, "o-", label="Uniform (MC)")
172+
pl.fill_between(
173+
n_proj_list,
174+
mean_err_uniform - std_err_uniform,
175+
mean_err_uniform + std_err_uniform,
176+
alpha=0.3,
177+
)
178+
pl.plot(n_proj_list, mean_err_rqsw, "s-", label="RQSW")
179+
pl.fill_between(
180+
n_proj_list,
181+
mean_err_rqsw - std_err_rqsw,
182+
mean_err_rqsw + std_err_rqsw,
183+
alpha=0.3,
184+
)
185+
pl.plot(n_proj_list, errors_qsw, "^-", label="QSW (deterministic)")
186+
pl.xscale("log")
187+
pl.yscale("log")
188+
pl.xlabel("Number of projections")
189+
pl.ylabel("Absolute error to the true SWD")
190+
pl.title("Convergence of the Sliced Wasserstein estimate (3D)")
191+
pl.legend()
192+
pl.show()
193+
194+
# QSW and RQSW reach a given accuracy with fewer projections than uniform
195+
# sampling, and RQSW keeps the estimator unbiased -- so it is a drop-in
196+
# replacement for uniform sampling in stochastic optimization settings
197+
# (e.g. Sliced Wasserstein gradient flows) where a deterministic QSW
198+
# estimate would not be appropriate.
199+
200+
# %%
Binary file not shown.
Binary file not shown.
Binary file not shown.
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"\n# Quasi-Monte Carlo Sliced Wasserstein in 3D\n\nThis example illustrates the Quasi-Sliced Wasserstein (QSW) and Randomized\nQuasi-Sliced Wasserstein (RQSW) sampling schemes introduced in [95], and\ncompares them to the default uniform (Monte Carlo) sampling of slicing\ndirections.\n\nSliced Wasserstein (SWD) approximates the Wasserstein distance by averaging\n1D Wasserstein distances over projections onto random directions\n$\\theta$ drawn uniformly on the sphere. By default these directions\nare sampled purely at random (Monte Carlo), which introduces some variance\nin the estimate for a given number of projections.\n\nQSW replaces the random directions with a deterministic, low-discrepancy\npoint set on the sphere (generalized spiral points), which covers the\nsphere more evenly than random sampling and reduces the approximation\nerror, especially in 3D. Since QSW is deterministic it cannot directly be\nused as an unbiased estimator in stochastic settings (e.g. gradient-based\noptimization) -- RQSW addresses this by applying a random rotation to the\nsame point set, which preserves both its low discrepancy and its\nunbiasedness.\n\nWe first visualize the three sampling schemes on the sphere, then measure\nhow fast each one converges to the true Sliced Wasserstein distance\nbetween two point clouds -- known here in closed form, with no\napproximation error left except from the number of projections itself.\n\n.. [95] Nguyen, K., Bariletto, N., & Ho, N. (2024). Quasi-Monte Carlo for\n 3D Sliced Wasserstein. International Conference on Learning\n Representations (ICLR).\n.. [96] Rakhmanov, E. A., Saff, E. B., & Zhou, Y. M. (1994). Minimal\n Discrete Energy on the Sphere. Mathematical Research Letters, 1(6),\n 647-662.\n"
8+
]
9+
},
10+
{
11+
"cell_type": "code",
12+
"execution_count": null,
13+
"metadata": {
14+
"collapsed": false
15+
},
16+
"outputs": [],
17+
"source": [
18+
"# Author: Samuel Vangu <samuelvangu0@gmail.com>\n#\n# License: MIT License\n\n# sphinx_gallery_thumbnail_number = 1\n\nimport numpy as np\nimport matplotlib.pylab as pl\nfrom mpl_toolkits.mplot3d import Axes3D # noqa: F401 (registers the 3D projection)\n\nimport ot\nfrom ot.sliced import get_random_projections, get_projections_spiral"
19+
]
20+
},
21+
{
22+
"cell_type": "markdown",
23+
"metadata": {},
24+
"source": [
25+
"## Visualize the three sampling schemes on the sphere\nWe draw a few hundred directions on $S^2$ with each scheme:\n\n- ``uniform``: directions are Gaussian vectors normalized to unit norm\n (standard Monte Carlo sampling of the sphere).\n- ``spiral_qmc``: deterministic generalized spiral points -- a simple,\n closed-form low-discrepancy point set (Rakhmanov, Saff & Zhou, 1994)\n [96]. The same call always returns the same points.\n- ``randomized_spiral_qmc``: the same spiral point set, rotated by a\n random (3, 3) rotation matrix (drawn via QR decomposition of a\n Gaussian matrix). The rotation makes the estimator unbiased while\n keeping the points as evenly spread out as the deterministic spiral\n set.\n\n"
26+
]
27+
},
28+
{
29+
"cell_type": "code",
30+
"execution_count": null,
31+
"metadata": {
32+
"collapsed": false
33+
},
34+
"outputs": [],
35+
"source": [
36+
"n_projections = 500\nd = 3\nseed = 42\n\ntheta_uniform = get_random_projections(d, n_projections, seed=seed)\ntheta_qsw = get_projections_spiral(d, n_projections, randomized=False)\ntheta_rqsw = get_projections_spiral(d, n_projections, randomized=True, seed=seed)\n\nfig = pl.figure(1, figsize=(15, 5))\n\nschemes = [\n (theta_uniform, \"Uniform (Monte Carlo)\"),\n (theta_qsw, \"QSW (deterministic spiral)\"),\n (theta_rqsw, \"RQSW (randomly rotated spiral)\"),\n]\n\nfor i, (theta, title) in enumerate(schemes):\n ax = fig.add_subplot(1, 3, i + 1, projection=\"3d\")\n ax.scatter(theta[0], theta[1], theta[2], c=theta[2], cmap=\"viridis\", s=4, alpha=0.8)\n ax.set_title(title)\n ax.set_box_aspect([1, 1, 1])\n ax.view_init(elev=20, azim=45)\n ax.set_xticks([])\n ax.set_yticks([])\n ax.set_zticks([])\n\npl.tight_layout()\npl.show()\n\n# Notice how the uniform sample leaves visible gaps and clusters, while QSW\n# and RQSW spread the points much more evenly over the sphere -- this is\n# exactly the low-discrepancy property that reduces the error of the Sliced\n# Wasserstein estimate."
37+
]
38+
},
39+
{
40+
"cell_type": "markdown",
41+
"metadata": {},
42+
"source": [
43+
"## Convergence to the true Sliced Wasserstein distance\nWe now compare how fast each sampling scheme converges to the *true*\nSWD as the number of projections grows. To get a reference value with\n**zero** approximation error -- not even from a finite number of\nsamples -- we build ``Xt`` as a pure translation of ``Xs`` by a fixed\nvector $\\delta$: ``Xt = Xs + delta``.\n\nFor a rigid translation, the classical 1D Wasserstein identity\n$W_2(\\mu, \\mu + c) = |c|$ holds *exactly*, for any distribution\nshape and any (even very small) sample size -- no law-of-large-numbers\nargument, no Gaussian assumption, just an algebraic identity of optimal\ntransport on the line. Projected onto any direction $\\theta$, this\ngives $W_2(\\theta_\\# \\mu, \\theta_\\# \\nu) = |\\theta^T \\delta|$\nexactly, and averaging the square over $\\theta$ uniform on\n$S^{d-1}$ gives the closed-form identity\n\n\\begin{align}\\mathcal{SWD}_2(\\mu, \\nu) = \\frac{\\|\\delta\\|}{\\sqrt{d}}\\end{align}\n\nBecause this holds regardless of ``Xs``'s shape or size, the *only*\nremaining source of error in the experiment below is the number of\nprojections -- exactly the quantity we want to study.\n\n"
44+
]
45+
},
46+
{
47+
"cell_type": "code",
48+
"execution_count": null,
49+
"metadata": {
50+
"collapsed": false
51+
},
52+
"outputs": [],
53+
"source": [
54+
"rng = np.random.RandomState(0)\n\nn_samples = 200\ndelta = np.array([1.5, 1.0, -0.5])\nXs = rng.uniform(-2, 2, (n_samples, d))\nXt = Xs + delta\n\n# Exact reference: no approximation at all, at any cost.\nsw_true = np.linalg.norm(delta) / np.sqrt(d)\n\nn_proj_list = [10, 20, 50, 100, 200, 500]\nn_trials = 8\n\nerrors_uniform = np.zeros((n_trials, len(n_proj_list)))\nerrors_rqsw = np.zeros((n_trials, len(n_proj_list)))\nerrors_qsw = np.zeros(len(n_proj_list))\n\nfor j, n_proj in enumerate(n_proj_list):\n for t in range(n_trials):\n sw_uniform = ot.sliced_wasserstein_distance(\n Xs, Xt, n_projections=n_proj, sampling_slices=\"uniform\", seed=t\n )\n sw_rqsw = ot.sliced_wasserstein_distance(\n Xs,\n Xt,\n n_projections=n_proj,\n sampling_slices=\"randomized_spiral_qmc\",\n seed=t,\n )\n errors_uniform[t, j] = np.abs(sw_uniform - sw_true)\n errors_rqsw[t, j] = np.abs(sw_rqsw - sw_true)\n\n sw_qsw = ot.sliced_wasserstein_distance(\n Xs, Xt, n_projections=n_proj, sampling_slices=\"spiral_qmc\"\n )\n errors_qsw[j] = np.abs(sw_qsw - sw_true)\n\nmean_err_uniform = errors_uniform.mean(axis=0)\nstd_err_uniform = errors_uniform.std(axis=0)\nmean_err_rqsw = errors_rqsw.mean(axis=0)\nstd_err_rqsw = errors_rqsw.std(axis=0)\n\npl.figure(2, figsize=(6, 5))\npl.plot(n_proj_list, mean_err_uniform, \"o-\", label=\"Uniform (MC)\")\npl.fill_between(\n n_proj_list,\n mean_err_uniform - std_err_uniform,\n mean_err_uniform + std_err_uniform,\n alpha=0.3,\n)\npl.plot(n_proj_list, mean_err_rqsw, \"s-\", label=\"RQSW\")\npl.fill_between(\n n_proj_list,\n mean_err_rqsw - std_err_rqsw,\n mean_err_rqsw + std_err_rqsw,\n alpha=0.3,\n)\npl.plot(n_proj_list, errors_qsw, \"^-\", label=\"QSW (deterministic)\")\npl.xscale(\"log\")\npl.yscale(\"log\")\npl.xlabel(\"Number of projections\")\npl.ylabel(\"Absolute error to the true SWD\")\npl.title(\"Convergence of the Sliced Wasserstein estimate (3D)\")\npl.legend()\npl.show()\n\n# QSW and RQSW reach a given accuracy with fewer projections than uniform\n# sampling, and RQSW keeps the estimator unbiased -- so it is a drop-in\n# replacement for uniform sampling in stochastic optimization settings\n# (e.g. Sliced Wasserstein gradient flows) where a deterministic QSW\n# estimate would not be appropriate."
55+
]
56+
}
57+
],
58+
"metadata": {
59+
"kernelspec": {
60+
"display_name": "Python 3",
61+
"language": "python",
62+
"name": "python3"
63+
},
64+
"language_info": {
65+
"codemirror_mode": {
66+
"name": "ipython",
67+
"version": 3
68+
},
69+
"file_extension": ".py",
70+
"mimetype": "text/x-python",
71+
"name": "python",
72+
"nbconvert_exporter": "python",
73+
"pygments_lexer": "ipython3",
74+
"version": "3.12.14"
75+
}
76+
},
77+
"nbformat": 4,
78+
"nbformat_minor": 0
79+
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)