Skip to content

Commit ea92ff2

Browse files
committed
WIP: skeleton for normalize parameter in sliced_wasserstein_distance
1 parent 41a4d57 commit ea92ff2

1 file changed

Lines changed: 110 additions & 0 deletions

File tree

ot/sliced.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,72 @@
2020
)
2121

2222

23+
def _normalize_inputs(X_s, X_t, normalize, normalize_mode, nx):
24+
"""Normalize input distributions before computing sliced Wasserstein distance.
25+
26+
Parameters
27+
----------
28+
X_s : array-like, shape (n_s, d)
29+
Source samples
30+
X_t : array-like, shape (n_t, d)
31+
Target samples
32+
normalize : str or None
33+
Normalization method. One of {None, 'standard', 'minmax', 'l2'}.
34+
normalize_mode : str
35+
Reference for computing statistics. One of {'joint', 'source', 'target'}.
36+
Ignored when normalize is None or 'l2'.
37+
nx : backend
38+
POT backend instance (from ot.backend.get_backend)
39+
40+
Returns
41+
-------
42+
X_s_out : array-like, shape (n_s, d)
43+
Normalized source samples
44+
X_t_out : array-like, shape (n_t, d)
45+
Normalized target samples
46+
"""
47+
if normalize is None:
48+
return X_s, X_t
49+
50+
if normalize_mode not in ("joint", "source", "target"):
51+
raise ValueError(
52+
f"Invalid normalize_mode '{normalize_mode}'. "
53+
"Expected one of: 'joint', 'source', 'target'."
54+
)
55+
56+
if normalize == "standard":
57+
# TODO: full implementation
58+
# - compute mean/std using nx ops based on normalize_mode
59+
# - apply to both X_s and X_t
60+
# - handle zero-variance columns with warnings.warn
61+
raise NotImplementedError(
62+
"normalize='standard' will be implemented in a follow-up commit."
63+
)
64+
65+
elif normalize == "minmax":
66+
# TODO: full implementation
67+
# - compute min/max using nx ops based on normalize_mode
68+
# - apply to both X_s and X_t
69+
# - handle zero-range columns with warnings.warn
70+
raise NotImplementedError(
71+
"normalize='minmax' will be implemented in a follow-up commit."
72+
)
73+
74+
elif normalize == "l2":
75+
# TODO: full implementation
76+
# - row-wise L2 normalization (normalize_mode is ignored)
77+
# - handle zero-norm rows with warnings.warn
78+
raise NotImplementedError(
79+
"normalize='l2' will be implemented in a follow-up commit."
80+
)
81+
82+
else:
83+
raise ValueError(
84+
f"Invalid normalize value '{normalize}'. "
85+
"Expected one of: None, 'standard', 'minmax', 'l2'."
86+
)
87+
88+
2389
def get_random_projections(d, n_projections, seed=None, backend=None, type_as=None):
2490
r"""
2591
Generates n_projections samples from the uniform on the unit sphere of dimension :math:`d-1`: :math:`\mathcal{U}(\mathcal{S}^{d-1})`
@@ -76,6 +142,8 @@ def sliced_wasserstein_distance(
76142
projections=None,
77143
seed=None,
78144
log=False,
145+
normalize=None,
146+
normalize_mode="joint",
79147
):
80148
r"""
81149
Computes a Monte-Carlo approximation of the p-Sliced Wasserstein distance
@@ -109,6 +177,24 @@ def sliced_wasserstein_distance(
109177
Seed used for random number generator
110178
log: bool, optional
111179
if True, sliced_wasserstein_distance returns the projections used and their associated EMD.
180+
normalize : str or None, optional
181+
Normalization applied to X_s and X_t before computing the distance.
182+
Useful when features have different scales. Options:
183+
184+
- ``None`` : no normalization (default, preserves existing behavior)
185+
- ``'standard'`` : zero mean, unit variance per feature dimension
186+
- ``'minmax'`` : scale each feature to [0, 1]
187+
- ``'l2'`` : normalize each sample to unit L2 norm (row-wise)
188+
189+
normalize_mode : str, optional
190+
Determines which samples are used to compute normalization statistics.
191+
Ignored when ``normalize`` is ``None`` or ``'l2'``. Options:
192+
193+
- ``'joint'`` : statistics from ``concat(X_s, X_t)`` (default).
194+
Preserves symmetry: SWD(X_s, X_t) == SWD(X_t, X_s).
195+
- ``'source'`` : statistics from ``X_s`` only. Useful for drift
196+
detection where X_s is the reference distribution.
197+
- ``'target'`` : statistics from ``X_t`` only.
112198
113199
Returns
114200
-------
@@ -136,6 +222,8 @@ def sliced_wasserstein_distance(
136222

137223
nx = get_backend(X_s, X_t, a, b, projections)
138224

225+
X_s, X_t = _normalize_inputs(X_s, X_t, normalize, normalize_mode, nx)
226+
139227
n = X_s.shape[0]
140228
m = X_t.shape[0]
141229

@@ -181,6 +269,8 @@ def max_sliced_wasserstein_distance(
181269
projections=None,
182270
seed=None,
183271
log=False,
272+
normalize=None,
273+
normalize_mode="joint",
184274
):
185275
r"""
186276
Computes a Monte-Carlo approximation of the max p-Sliced Wasserstein distance
@@ -215,6 +305,24 @@ def max_sliced_wasserstein_distance(
215305
Seed used for random number generator
216306
log: bool, optional
217307
if True, sliced_wasserstein_distance returns the projections used and their associated EMD.
308+
normalize : str or None, optional
309+
Normalization applied to X_s and X_t before computing the distance.
310+
Useful when features have different scales. Options:
311+
312+
- ``None`` : no normalization (default, preserves existing behavior)
313+
- ``'standard'`` : zero mean, unit variance per feature dimension
314+
- ``'minmax'`` : scale each feature to [0, 1]
315+
- ``'l2'`` : normalize each sample to unit L2 norm (row-wise)
316+
317+
normalize_mode : str, optional
318+
Determines which samples are used to compute normalization statistics.
319+
Ignored when ``normalize`` is ``None`` or ``'l2'``. Options:
320+
321+
- ``'joint'`` : statistics from ``concat(X_s, X_t)`` (default).
322+
Preserves symmetry: SWD(X_s, X_t) == SWD(X_t, X_s).
323+
- ``'source'`` : statistics from ``X_s`` only. Useful for drift
324+
detection where X_s is the reference distribution.
325+
- ``'target'`` : statistics from ``X_t`` only.
218326
219327
Returns
220328
-------
@@ -242,6 +350,8 @@ def max_sliced_wasserstein_distance(
242350

243351
nx = get_backend(X_s, X_t, a, b, projections)
244352

353+
X_s, X_t = _normalize_inputs(X_s, X_t, normalize, normalize_mode, nx)
354+
245355
n = X_s.shape[0]
246356
m = X_t.shape[0]
247357

0 commit comments

Comments
 (0)