<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://huguesva.github.io/feed.xml" rel="self" type="application/atom+xml"/><link href="https://huguesva.github.io/" rel="alternate" type="text/html" hreflang="en"/><updated>2026-03-04T17:15:47+00:00</updated><id>https://huguesva.github.io/feed.xml</id><title type="html">blank</title><subtitle>A simple, whitespace theme for academics. Based on [*folio](https://github.com/bogoli/-folio) design. </subtitle><entry><title type="html">Joint-Embedding vs Reconstruction: When Should You Use Each?</title><link href="https://huguesva.github.io/blog/2025/je-vs-reconstruction/" rel="alternate" type="text/html" title="Joint-Embedding vs Reconstruction: When Should You Use Each?"/><published>2025-11-20T00:00:00+00:00</published><updated>2025-11-20T00:00:00+00:00</updated><id>https://huguesva.github.io/blog/2025/je-vs-reconstruction</id><content type="html" xml:base="https://huguesva.github.io/blog/2025/je-vs-reconstruction/"><![CDATA[<script type="text/javascript" async="" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"> </script> <p>This blog post presents the key findings from our NeurIPS 2025 paper on comparing two fundamental paradigms in Self-Supervised Learning (SSL): <strong>reconstruction-based</strong> and <strong>joint-embedding</strong> methods.</p> <h2 id="introduction-two-paradigms-of-ssl">Introduction: Two Paradigms of SSL</h2> <p>Self-Supervised Learning has emerged as a powerful alternative to supervised learning, moving away from specialized labels toward specifying which variations should be disregarded. Two primary families of methods have emerged to learn representations using this principle:</p> <h3 id="reconstruction-based-approaches">Reconstruction-Based Approaches</h3> <p><strong>Reconstruction</strong>-based approaches train models by augmenting an input signal (e.g., adding noise or masking) and then training the model to restore the original input. This process encourages the model to learn meaningful internal representations of the data’s underlying structure. However, because the learning signal arises from minimizing reconstruction error in the input space, the model is naturally steered toward subspaces that explain the majority of the input’s variance.</p> <p>In <strong>language</strong>, reconstruction-based learning is highly effective because textual tokens represent compact, semantically meaningful units. Predicting a missing token provides a learning signal that operates directly in semantic space.</p> <p>In <strong>vision</strong>, however, variance-explaining features often emphasize aspects that are statistically dominant but semantically shallow. Pixel-level reconstruction objectives tend to drive models toward capturing local statistics and textures rather than higher-order structures and object-level relationships.</p> <h3 id="joint-embedding-approaches">Joint-Embedding Approaches</h3> <p><strong>Joint-embedding</strong> methods operate entirely in latent space. Their objective is to produce similar representations for different augmented views of the same input while ensuring that representations of distinct samples remain dissimilar. This separation can be enforced explicitly through a contrastive loss, or implicitly via architectural mechanisms such as self-distillation, stop-gradient operations, or predictor heads.</p> <p>Unlike reconstruction-based approaches, joint-embedding methods do not predict in the input space and are therefore <strong>less biased toward capturing high-variance components</strong> of the signal. Empirically, joint-embedding frameworks have shown strong performance across domains where the input signal is high-dimensional and semantically diffuse, including histopathology, Earth observation, and video representation learning.</p> <div style="text-align: center; margin: 2em 0;"> <img src="/assets/img/blog-je-vs-rc/schema_je_vs_reconstruction.png" alt="SSL paradigms comparison" style="width: 95%; max-width: 900px;"/> <p style="font-size: 0.9em; color: #666; margin-top: 0.5em;"> <strong>Figure 1:</strong> Two self-supervised learning paradigms. <em>Left:</em> Reconstruction approach trains an encoder $f_{\mathbf{E}}$ and decoder $f_{\mathbf{D}}$ to recover $\mathbf{x}$ from augmented view $\tau(\mathbf{x})$. <em>Right:</em> Joint-embedding approach maps two independent augmentations $\tau_1(\mathbf{x})$ and $\tau_2(\mathbf{x})$ to nearby representations via $f_{\mathbf{W}}$. </p> </div> <h2 id="the-two-main-problems-and-their-solutions">The Two Main Problems and Their Solutions</h2> <p>Consider \(n\) samples \(\mathbf{X} = (\mathbf{x}_1, \dots, \mathbf{x}_n)^\top \in \mathbb{R}^{n \times d}\) and a data augmentation distribution \(\mathcal{T}\) defined over transformations \(\tau: \mathbb{R}^d \rightarrow \mathbb{R}^d\). For analytical tractability, we focus on linear models: \(f_{\mathbf{E}}: \mathbf{x} \mapsto \mathbf{E} \mathbf{x}\), \(f_{\mathbf{D}}: \mathbf{z} \mapsto \mathbf{D} \mathbf{z}\), and \(f_{\mathbf{W}}: \mathbf{x} \mapsto \mathbf{W} \mathbf{x}\).</p> <h3 id="problem-1-reconstruction-based-ssl">Problem 1: Reconstruction-Based SSL</h3> <p>The reconstruction problem is formulated as:</p> \[\begin{align}\tag{SSL-RC}\label{eq:reconstruction} \min_{\mathbf{E}, \mathbf{D}} \quad \frac{1}{n} \sum_{i=1}^{n} \mathbb{E}_{\tau \sim \mathcal{T}} \left[ \| \mathbf{x}_i - f_\mathbf{D}(f_\mathbf{E}(\tau(\mathbf{x}_i))) \|_2^2 \right] \end{align}\] <p>where \(f_{\mathbf{E}}\) and \(f_{\mathbf{D}}\) are encoder and decoder functions. Each data sample is augmented, encoded, and decoded, with the objective to minimize reconstruction error. This methodology is analogous to Denoising Auto-Encoders and Masked Auto-Encoders (MAE).</p> <details><summary>Closed-Form Solution for Reconstruction</summary> <p><strong>Theorem 1 (Reconstruction-Based SSL).</strong> Let \(\overline{\mathbf{x}}_i := \mathbb{E}_{\tau \sim \mathcal{T}}[\tau(\mathbf{x}_i)]\) denote the expected augmented sample and \(\overline{\mathbf{X}} := (\overline{\mathbf{x}}_1, \dots, \overline{\mathbf{x}}_n)^\top\). Define the covariance of augmented samples:</p> \[\mathbf{\Sigma} := \frac{1}{n} \sum_{i} \mathbb{E}_{\tau \sim \mathcal{T}} \left[ \tau(\mathbf{x}_i) \tau(\mathbf{x}_i)^\top\right] - \mathbb{E}_{\tau \sim \mathcal{T}} \left[\tau(\mathbf{x}_i) \right] \mathbb{E}_{\tau \sim \mathcal{T}} \left[\tau(\mathbf{x}_i) \right]^\top\] <p>Assume that \(\frac{1}{n} \overline{\mathbf{X}}^\top \overline{\mathbf{X}} + \mathbf{\Sigma}\) is positive definite. Consider the singular value decomposition:</p> \[\begin{align} \frac{1}{n} \mathbf{X}^\top \overline{\mathbf{X}} \left(\frac{1}{n} \overline{\mathbf{X}}^\top \overline{\mathbf{X}} + \mathbf{\Sigma} \right)^{-\frac{1}{2}} = \mathbf{R} \mathbf{\Phi} \mathbf{P}^\top \end{align}\] <p>where \(\mathbf{R} \in \mathbb{R}^{d \times d}\) and \(\mathbf{P} \in \mathbb{R}^{d \times d}\) are orthogonal and \(\mathbf{\Phi} := \mathrm{diag}(\phi_1, \dots, \phi_d)\) with \(\phi_1 \geq \dots \geq \phi_d \geq 0\).</p> <p>Solutions of the reconstruction problem \eqref{eq:reconstruction} take the form:</p> \[\begin{align} \mathbf{E}^\star = \mathbf{T} \mathbf{P}_k^\top \left(\frac{1}{n} \overline{\mathbf{X}}^\top \overline{\mathbf{X}} + \mathbf{\Sigma} \right)^{-\frac{1}{2}} \quad \text{and} \quad \mathbf{D}^\star = \mathbf{R}_k \mathbf{\Phi}_k \mathbf{T}^{-1} \end{align}\] <p>where \(\mathbf{T}\) is any invertible matrix in \(\mathbb{R}^{k \times k}\), \(\mathbf{P}_k\) and \(\mathbf{R}_k\) are the first \(k\) columns of \(\mathbf{P}\) and \(\mathbf{R}\), and \(\mathbf{\Phi}_k = \mathrm{diag}(\phi_1, \dots, \phi_k)\).</p> </details> <h3 id="problem-2-joint-embedding-based-ssl">Problem 2: Joint-Embedding-Based SSL</h3> <p>The joint-embedding problem is formulated as:</p> \[\begin{equation}\tag{SSL-JE}\label{eq:ssl} \begin{aligned} \min_{\mathbf{W}} \quad &amp; \frac{1}{n} \sum_{i=1}^{n} \mathbb{E}_{\tau_1, \tau_2 \sim \mathcal{T}} \left[ \| f_\mathbf{W}(\tau_1(\mathbf{x}_i)) - f_\mathbf{W}(\tau_2(\mathbf{x}_i)) \|^2_2 \right] \\ \text{subject to} \quad &amp; \frac{1}{n} \sum_{i=1}^{n} \mathbb{E}_{\tau \sim \mathcal{T}} \left[ f_\mathbf{W}(\tau(\mathbf{x}_i)) f_\mathbf{W}(\tau(\mathbf{x}_i))^\top\right] = \mathbf{I}_k \end{aligned} \end{equation}\] <p>where \(f_{\mathbf{W}}\) is the SSL model. The objective represents the invariance term ensuring consistency between augmented views, while the constraint enforces orthonormality, preventing collapse. This formulation closely resembles methods like SimCLR, VICReg, BYOL, and DINO.</p> <details><summary>Closed-Form Solution for Joint-Embedding</summary> <p><strong>Theorem 2 (Joint-Embedding-Based SSL).</strong> Let \(\mathbf{S} := \frac{1}{n} \sum_{i} \mathbb{E}_{\tau \sim \mathcal{T}} \left[ \tau(\mathbf{x}_i) \tau(\mathbf{x}_i)^\top\right]\) and \(\mathbf{G} := \frac{1}{n} \sum_{i} \mathbb{E}_{\tau \sim \mathcal{T}} \left[ \tau(\mathbf{x}_i)\right] \mathbb{E}_{\tau \sim \mathcal{T}} \left[ \tau(\mathbf{x}_i)\right]^\top\).</p> <p>Assume that \(\mathbf{S}\) is positive definite. Consider the eigendecomposition:</p> \[\begin{align} \mathbf{S}^{-\frac{1}{2}} \mathbf{G} \mathbf{S}^{-\frac{1}{2}} = \mathbf{Q} \mathbf{\Omega} \mathbf{Q}^\top \end{align}\] <p>where \(\mathbf{\Omega} = \mathrm{diag}(\omega_1, \dots, \omega_d)\) with \(\omega_1 \geq \dots \geq \omega_d\).</p> <p>Solutions of the joint-embedding problem \eqref{eq:ssl} take the form:</p> \[\begin{align} \mathbf{W}^\star = \mathbf{U} \mathbf{Q}_k^\top \mathbf{S}^{-\frac{1}{2}} \end{align}\] <p>where \(\mathbf{Q}_k = (\mathbf{q}_1, \dots, \mathbf{q}_k)\) and \(\mathbf{U}\) is any orthogonal matrix of size \(k \times k\).</p> </details> <p>These closed-form solutions are directly parameterized by the augmentation structure, enabling us to analyze precisely how augmentations impact learned representations.</p> <h2 id="key-findings-augmentation-alignment-requirements">Key Findings: Augmentation Alignment Requirements</h2> <p>Using these closed-form solutions, we uncover fundamental differences between the two paradigms. We model data as having \(k\) <strong>important signal components</strong> and \(d-k\) <strong>pure noise components</strong> (irrelevant features that SSL should be invariant to). Optimal performance is achieved when the learned representations discard these irrelevant features and retain only the important, meaningful signal components.</p> <p>We introduce a parameter \(\alpha \geq 0\) that controls the <strong>alignment</strong> between the irrelevant features and the augmentations. Our main theoretical results reveal:</p> <h3 id="finding-1-ssl-requires-aligned-augmentations">Finding 1: SSL Requires Aligned Augmentations</h3> <p>Unlike supervised learning, <strong>both SSL paradigms require aligned augmentations to achieve optimal performance</strong>, even with infinite samples. Simply increasing the sample size cannot overcome misalignment between augmentations and noise.</p> <div style="background-color: #f5f5f5; border-left: 4px solid #9C27B0; padding: 0.8em; margin: 1em 0;"> <strong>Proposition (Supervised Learning).</strong> Supervised models achieve optimal performance either when: <ul style="margin: 0.3em 0; padding-left: 1.5em;"> <li>Augmentations are well aligned with noise ($\alpha$ large), or</li> <li>Sample size is large ($n \to \infty$), <strong>regardless of alignment</strong>.</li> </ul> </div> <div style="background-color: #f5f5f5; border-left: 4px solid #9C27B0; padding: 0.8em; margin: 1em 0;"> <strong>Proposition (Self-Supervised Learning).</strong> SSL models achieve optimal performance when: <ul style="margin: 0.3em 0; padding-left: 1.5em;"> <li>Augmentations are well aligned with noise ($\alpha$ large), or</li> <li>Sample size is large ($n \to \infty$) <strong>AND</strong> alignment satisfies $\alpha &gt; \alpha_{\text{threshold}}$.</li> </ul> </div> <p>This critical difference underscores that <strong>carefully designed augmentations are essential in SSL</strong>.</p> <h3 id="finding-2-joint-embedding-vs-reconstruction-comparison">Finding 2: Joint-Embedding vs Reconstruction Comparison</h3> <p>Having established that SSL requires aligned augmentations, we now compare the two SSL paradigms. Our second major finding reveals when to prefer each paradigm. Recall that both methods require the alignment parameter $\alpha$ to exceed a certain threshold to achieve optimal performance. Crucially, <strong>these thresholds differ</strong> between the two paradigms:</p> <ul> <li><strong>Reconstruction</strong> has threshold $\alpha_{\text{RC}}$</li> <li><strong>Joint-embedding</strong> has threshold $\alpha_{\text{JE}}$</li> </ul> <p>These thresholds depend on noise magnitude, augmentation quality, and data characteristics. <strong>A smaller threshold is preferable</strong> as it succeeds in more scenarios: since we don’t know noise characteristics in advance, lower alignment requirements mean greater robustness.</p> <p>Our analysis reveals:</p> <div style="background-color: #f5f5f5; border-left: 4px solid #4CAF50; padding: 0.8em; margin: 1em 0;"> <strong>Low-Magnitude Irrelevant Features:</strong> When noise/irrelevant features have small variance, reconstruction requires less alignment: $\alpha_{\text{RC}} &lt; \alpha_{\text{JE}}$ <br/><strong>→ Reconstruction is preferable</strong> </div> <div style="background-color: #f5f5f5; border-left: 4px solid #FF5722; padding: 0.8em; margin: 1em 0;"> <strong>High-Magnitude Irrelevant Features:</strong> When noise/irrelevant features have large variance, joint-embedding requires less alignment: $\alpha_{\text{JE}} &lt; \alpha_{\text{RC}}$ <br/><strong>→ Joint-embedding is preferable</strong> </div> <h3 id="interpretation">Interpretation</h3> <p><strong>Reconstruction</strong> methods prioritize high-variance components: with low-magnitude noise, important features dominate naturally. In contrast, <strong>joint-embedding</strong> methods operate in latent space, bypassing the need to reconstruct noisy components. With high-magnitude noise, they require less alignment because they avoid reconstructing irrelevant features.</p> <p>Since data from physical world measurements (images, sounds, sensor recordings) often contain high-magnitude irrelevant features (backgrounds, experimental artifacts), <strong>joint-embedding is typically more robust in practice</strong>. Our experiments on ImageNet-1k confirm this: joint-embedding methods like DINO and BYOL are considerably more robust to severe data corruption than reconstruction-based methods like MAE.</p> <div style="text-align: center; margin: 2em 0;"> <img src="/assets/img/blog-je-vs-rc/mnist_small.png" alt="Linear models validation" style="width: 100%; max-width: 1000px;"/> <p style="font-size: 0.9em; color: #666; margin-top: 0.5em;"> <strong>Figure 2:</strong> Validation on linear models with MNIST corrupted by synthetic Gaussian noise. Each subplot shows how performance varies with sample size $n$ (x-axis) and augmentation alignment $\alpha$ (different lines). <em>Left:</em> Supervised learning achieves optimal performance with either large $n$ or large $\alpha$, regardless of noise magnitude. <em>Middle:</em> Joint-embedding requires minimal alignment but remains robust even with strong noise. <em>Right:</em> Reconstruction is robust to augmentation choice under weak noise but degrades under strong noise. </p> </div> <h2 id="practical-takeaway">Practical Takeaway</h2> <div style="background-color: #e3f2fd; border-left: 4px solid #2196F3; padding: 1em; margin: 2em 0;"> <strong>Key Recommendation:</strong> <ul style="margin: 0.5em 0;"> <li><strong>Use Reconstruction</strong> when irrelevant features have low magnitude and you have limited knowledge about effective augmentations.</li> <li><strong>Use Joint-Embedding</strong> when irrelevant features are non-negligible (common in physical world measurements) or when effective augmentations can be identified.</li> </ul> </div> <p>For more technical details, proofs, and comprehensive experimental results, please refer to our <a href="https://arxiv.org/abs/2505.12477">full paper</a>.</p> <h2 id="citation">Citation</h2> <p>If you found this work useful, please cite our NeurIPS 2025 paper:</p> <div class="language-bibtex highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">@inproceedings</span><span class="p">{</span><span class="nl">vanassel2025je</span><span class="p">,</span>
  <span class="na">title</span><span class="p">=</span><span class="s">{Joint-Embedding vs Reconstruction: Provable Benefits of Latent Space Prediction for Self-Supervised Learning}</span><span class="p">,</span>
  <span class="na">author</span><span class="p">=</span><span class="s">{Van Assel, Hugues and Ibrahim, Mark and Biancalani, Tommaso and Regev, Aviv and Balestriero, Randall}</span><span class="p">,</span>
  <span class="na">booktitle</span><span class="p">=</span><span class="s">{Advances in Neural Information Processing Systems}</span><span class="p">,</span>
  <span class="na">year</span><span class="p">=</span><span class="s">{2025}</span>
<span class="p">}</span>
</code></pre></div></div>]]></content><author><name>Hugues Van Assel</name></author><category term="SSL"/><category term="Theory"/><category term="NeurIPS"/><summary type="html"><![CDATA[A theoretical analysis revealing when joint-embedding methods outperform reconstruction-based SSL, and vice versa.]]></summary></entry><entry><title type="html">Inverse optimal transport does not require unrolling</title><link href="https://huguesva.github.io/blog/2024/inverseOT_mongegap/" rel="alternate" type="text/html" title="Inverse optimal transport does not require unrolling"/><published>2024-02-25T00:00:00+00:00</published><updated>2024-02-25T00:00:00+00:00</updated><id>https://huguesva.github.io/blog/2024/inverseOT_mongegap</id><content type="html" xml:base="https://huguesva.github.io/blog/2024/inverseOT_mongegap/"><![CDATA[<script type="text/javascript" async="" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"> </script> <p>This blog is about an elegant and practical reformulation of inverse Optimal Transport (OT) that enables efficient computations. It is based on a derivation found in <d-cite key="ma2020learning"></d-cite>. In the last part, we apply this trick to efficiently learn low dimensional data representations.</p> <h3 id="background-on-entropic-optimal-transport">Background on (Entropic) Optimal Transport</h3> <p>Entropic OT <d-cite key="peyre2019computational"></d-cite> is a powerful tool with many applications in machine learning, including generative modelling <d-cite key="genevay2018learning"></d-cite>, domain adaptation <d-cite key="courty2017joint"></d-cite> and dimensionality reduction <d-cite key="van2024snekhorn"></d-cite>.</p> <p>We consider two discrete distributions that we wish to compare: \(\sum_i a_i \delta_{\mathbf{x}_i}\) <d-footnote> $\delta_{\mathbf{x}}$ is a dirac distribution with a unit mass in position $\mathbf{x}$ and $0$ elsewhere. </d-footnote> and \(\sum_j b_j \delta_{\mathbf{y}_j}\) where \(\mathbf{a}\) \(= (a_1,...,a_p)\) and \(\mathbf{b}\) \(= (b_1,...,b_m)\) are vectors with positive entries in the probability simplex (<em>ie</em> such that \(\sum_i a_i = \sum_j b_j =1\)). We also consider a cost matrix \(\mathbf{C}\) with entries \(C_{ij} = d(\mathbf{x}_i, \mathbf{y}_j)\) where \(d\) is a dissimilarity function.</p> <p><strong>Primal problem.</strong> The entropic OT problem reads <d-footnote> $\langle \mathbf{C}, \mathbf{P} \rangle = \sum_{ij} C_{ij} P_{ij}$ denotes the Euclidean inner product. </d-footnote></p> \[\DeclareMathOperator*{\argmin}{arg\,min} \begin{align}\label{eq:eot} \min_{\mathbf{P} \in \Pi(\mathbf{a}, \mathbf{b})} \: \: \langle \mathbf{C}, \mathbf{P} \rangle - \varepsilon \mathrm{H}(\mathbf{P}) \end{align}\] <p>where \(\Pi(\mathbf{a}, \mathbf{b})=\left\{\mathbf{P}\mathbf{\geq0},\mathbf{P}\mathbf{1}=\mathbf{a},\mathbf{P}^{\top}\mathbf{1}=\mathbf{b}\right\}\) is the set of couplings with marginals \((\mathbf{a}, \mathbf{b})\) and \(\mathrm{H}(\mathbf{P}) = - \langle \mathbf{P}, \log \mathbf{P} - \mathbf{1} \mathbf{1}^\top \rangle\) <d-footnote> $\mathbf{1}$ is the vector of ones $(1,...,1)$. </d-footnote>. \(\varepsilon &gt; 0\) is a regularizer that sets the entropy of the transport plan.</p> <p><strong>Dual problem.</strong> The above entropic OT problem \eqref{eq:eot} can be solved through the following dual</p> \[\begin{align}\label{eq:dual_eot} \max_{\mathbf{f},\mathbf{g}} \: \: \langle \mathbf{f}, \mathbf{a} \rangle + \langle \mathbf{g}, \mathbf{b} \rangle - \varepsilon \left\langle \exp((\mathbf{f} \oplus \mathbf{g} - \mathbf{C}) / \varepsilon), \mathbf{1} \mathbf{1}^\top \right\rangle \:. \end{align}\] <p>The solution \(\mathbf{P}^\star\) of the primal problem \eqref{eq:eot} can be expressed in terms of the optimal dual variables \((\mathbf{f}^\star, \mathbf{g}^\star)\) solving \eqref{eq:dual_eot} as \(\mathbf{P}^{\star} = \exp((\mathbf{f}^\star \oplus \mathbf{g}^\star - \mathbf{C}) / \varepsilon)\).</p> <details><summary>proof</summary> <p>The Lagrangian of the above problem is as follows, with dual variables \(\mathbf{f}\) and \(\mathbf{g}\)</p> \[\begin{align}\label{eq:lagrangian_eot} \langle \mathbf{C}, \mathbf{P} \rangle - \varepsilon \mathrm{H}(\mathbf{P}) - \langle \mathbf{f}, \mathbf{P} \mathbf{1} - \mathbf{a} \rangle - \langle \mathbf{g}, \mathbf{P}^\top \mathbf{1} - \mathbf{b} \rangle \:. \end{align}\] <p>Strong duality holds for \eqref{eq:eot} and the first order KKT condition gives</p> \[\begin{align} \mathbf{C} - \varepsilon \log(\mathbf{P}^\star) - \mathbf{f}^\star\mathbf{1}^\top - \mathbf{1}(\mathbf{g}^\star)^{\top} \mathbf{=0} \end{align}\] <p>for optimal primal \(\mathbf{P}^\star\) and dual \((\mathbf{f}^\star, \mathbf{g}^\star)\) variables.</p> <p>It gives the primal/dual relation \(\mathbf{P}^\star = \exp((\mathbf{f}^\star \oplus \mathbf{g}^\star - \mathbf{C}) / \varepsilon)\).</p> <p>Plugging it back into the Lagrangian we recover the dual objective of equation \eqref{eq:dual_eot}.</p> </details> <p>Problem \eqref{eq:dual_eot} can be solved using block coordinate ascent, alternatively optimizing with respect to \(\mathbf{f}\) and \(\mathbf{g}\) with the following updates:</p> \[\begin{align} f_i &amp;\leftarrow \varepsilon \log a_i - \varepsilon \log \sum_j e^{(g_j-C_{ij}) / \varepsilon} \label{eq:sinkhorn-f} \\ g_j &amp;\leftarrow \varepsilon \log b_j - \varepsilon \log \sum_i e^{(f_i-C_{ij}) / \varepsilon} \label{eq:sinkhorn-g} \:. \end{align}\] <p>:bulb: The above updates are known as Sinkhorn iterations (in log domain) due to the seminal work of Sinkhorn and Knopp who proved their convergence <d-cite key="sinkhorn1967concerning"></d-cite>.</p> <h3 id="inverse-optimal-transport-arrow_right_hook">Inverse Optimal Transport :arrow_right_hook:</h3> <p>In inverse OT <d-cite key="ma2020learning"></d-cite>, from an OT plan \(\widehat{\mathbf{P}} \in \Pi(\mathbf{a}, \mathbf{b})\), one seeks to reconstruct a cost \(\mathbf{C}\) likely to have generated \(\widehat{\mathbf{P}}\) when solving OT on \(\mathbf{C}\). We will see some applications in what follows.</p> <p>When using entropic OT, the inverse OT problem is usually formulated with a KL divergence \(\mathrm{KL}(\mathbf{P} \| \mathbf{Q}) = \langle \mathbf{P}, \log (\mathbf{P} \oslash \mathbf{Q}) \rangle - \mathbf{P} + \mathbf{Q}\). The problem we consider is as follows</p> \[\DeclareMathOperator*{\argmin}{arg\,min} \begin{align} \min_{\mathbf{C}} \quad &amp;\mathrm{KL}(\widehat{\mathbf{P}} \| \mathbf{P}^{\mathbf{C}}) \label{eq:outer_invot}\\[1em] \text{s.t.} \quad &amp;\mathbf{P}^{\mathbf{C}} = \argmin_{\mathbf{P} \in \Pi(\mathbf{a}, \mathbf{b})} \: \: \langle \mathbf{C}, \mathbf{P} \rangle - \varepsilon \mathrm{H}(\mathbf{P}) \label{eq:inner_invot} \:. \end{align}\] <p><strong>Issue</strong> : the above is a nested problem and we need to unroll the Sinkhorn iterations of the inner problem \eqref{eq:inner_invot} to solve the outer problem \eqref{eq:outer_invot}. Another approach would be to rely on the implicit function theorem but it requires a costly inversion.</p> <p>Hopefully, a computationally simpler formulation can be derived from the above, as shown in the theorem 1 of <d-cite key="ma2020learning"></d-cite>. Indeed, problem \eqref{eq:outer_invot} is equivalent to the following single-level problem</p> \[\begin{align} \min_{\mathbf{C}, \mathbf{f}, \mathbf{g}} \: \: \left\langle \widehat{\mathbf{P}}, \mathbf{C} \right\rangle - \langle \mathbf{f}, \mathbf{a} \rangle - \langle \mathbf{g}, \mathbf{b} \rangle + \varepsilon \left\langle \exp(\left(\mathbf{f} \oplus \mathbf{g} - \mathbf{C}\right) / \varepsilon), \mathbf{1} \mathbf{1}^\top \right\rangle \:. \end{align}\] <p>We detail this derivation in what follows.</p> <h3 id="simplification-of-inverse-ot-rocket">Simplification of inverse OT :rocket:</h3> <p>A first step is to observe that the outer objective \eqref{eq:outer_invot} of inverse OT can be expressed in terms of the optimal dual variables \((\mathbf{f}^\star,\mathbf{g}^\star)\) of the entropic OT inner problem \eqref{eq:inner_invot}. Indeed, it holds</p> \[\begin{align}\label{eq:first_step} \varepsilon \left( \mathrm{KL}(\widehat{\mathbf{P}} \| \mathbf{P}^{\mathbf{C}}) + \operatorname{H}(\widehat{\mathbf{P}}) \right) &amp;= \left\langle \widehat{\mathbf{P}}, \mathbf{C} \right\rangle - \langle \mathbf{f}^\star, \mathbf{a} \rangle - \langle \mathbf{g}^\star, \mathbf{b} \rangle \:. \end{align}\] <details><summary>proof</summary> <p>The KL can be decomposed as</p> \[\begin{align} \operatorname{KL}(\widehat{\mathbf{P}} | \mathbf{P}^{\mathbf{C}}) = - \langle \widehat{\mathbf{P}}, \log \mathbf{P}^{\mathbf{C}} \rangle - \operatorname{H}(\widehat{\mathbf{P}}) \:. \end{align}\] <p>For optimal dual variables \((\mathbf{f}^\star, \mathbf{g}^\star)\), the solution of the primal of entropic OT is given by</p> \[\begin{align} \mathbf{P}^{\mathbf{C}} = \exp((\mathbf{f}^\star \oplus \mathbf{g}^\star - \mathbf{C}) / \varepsilon) \:. \end{align}\] <p>Therefore we have</p> \[\begin{align} \varepsilon \left( \mathrm{KL}(\widehat{\mathbf{P}} \| \mathbf{P}^{\mathbf{C}}) + \operatorname{H}(\widehat{\mathbf{P}}) \right) &amp;= - \left\langle \widehat{\mathbf{P}}, \mathbf{f}^\star \oplus \mathbf{g}^\star - \mathbf{C} \right\rangle \\ &amp;= \left\langle \widehat{\mathbf{P}}, \mathbf{C} \right\rangle - \left\langle \widehat{\mathbf{P}}, \mathbf{f}^\star \oplus \mathbf{g}^\star \right\rangle \:. \end{align}\] <p>Focusing on the last term, using that \(\widehat{\mathbf{P}} \in \Pi(\mathbf{a}, \mathbf{b})\) it holds</p> \[\begin{align} \left\langle \widehat{\mathbf{P}}, \mathbf{f}^\star \oplus \mathbf{g}^\star \right\rangle &amp;= \sum_i f^\star_i \sum_j \widehat{P}_{ij} + \sum_j g^\star_j \sum_i \widehat{P}_{ij} \\ &amp;= \sum_i f^\star_i a_i + \sum_j g^\star_j b_j \\ &amp;= \langle \mathbf{f}^\star, \mathbf{a} \rangle + \langle \mathbf{g}^\star, \mathbf{b} \rangle \:. \end{align}\] <p>Therefore</p> \[\begin{align} \varepsilon \left( \mathrm{KL}(\widehat{\mathbf{P}} \| \mathbf{P}^{\mathbf{C}}) + \operatorname{H}(\widehat{\mathbf{P}}) \right) &amp;= \left\langle \widehat{\mathbf{P}}, \mathbf{C} \right\rangle - \langle \mathbf{f}^\star, \mathbf{a} \rangle - \langle \mathbf{g}^\star, \mathbf{b} \rangle \:. \end{align}\] </details> <p>In equation \eqref{eq:first_step}, \(\mathbf{f}^\star\) and \(\mathbf{g}^\star\) implicitly depend on \(\mathbf{C}\) through problem \eqref{eq:dual_eot}. Thus we are still stuck with the bilevel structure and have’nt made any real progress yet.</p> <p>Recall that we would like to derive a joint single-level objective for both outer variable $\mathbf{C}$ and inner variables $(\mathbf{f}, \mathbf{g})$. To do so, one can notice that equation \eqref{eq:first_step} has terms in common with the dual problem of entropic OT \eqref{eq:dual_eot}. Indeed, in both \eqref{eq:dual_eot} and \eqref{eq:first_step} we find</p> \[\begin{align} \langle\mathbf{f},\mathbf{a}\rangle+\langle\mathbf{g},\mathbf{b}\rangle \:. \end{align}\] <p>The <strong>trick</strong> is to add the missing term of dual entropic OT \eqref{eq:dual_eot} in \eqref{eq:first_step}. Doing so, we define the following joint objective</p> \[\begin{align} \cal{G}(\widehat{\mathbf{P}}, \mathbf{C}, \mathbf{f}, \mathbf{g}) = &amp;\left\langle \widehat{\mathbf{P}}, \mathbf{C} \right\rangle - \langle \mathbf{f}, \mathbf{a} \rangle - \langle \mathbf{g}, \mathbf{b} \rangle \\ + &amp;\varepsilon \left\langle \exp(\left(\mathbf{f} \oplus \mathbf{g} - \mathbf{C}\right) / \varepsilon), \mathbf{1} \mathbf{1}^\top \right\rangle \:. \end{align}\] <p>For any \(\mathbf{C}\), minimizing \(\cal{G}\) with respect to \((\mathbf{f}, \mathbf{g})\) exactly amounts to solving dual entropic OT \eqref{eq:dual_eot}, because \(\left\langle \widehat{\mathbf{P}}, \mathbf{C} \right\rangle\) does not depend on \((\mathbf{f}, \mathbf{g})\). Hence we have:</p> \[\begin{align} \min_{\mathbf{f},\mathbf{g}} \: \cal{G}(\widehat{\mathbf{P}}, \mathbf{C}, \mathbf{f}, \mathbf{g}) = \left\langle \widehat{\mathbf{P}}, \mathbf{C} \right\rangle - \langle \mathbf{f}^\star, \mathbf{a} \rangle - \langle \mathbf{g}^\star, \mathbf{b} \rangle + \varepsilon \left\langle \mathbf{P}^{\mathbf{C}}, \mathbf{1} \mathbf{1}^\top \right\rangle \end{align}\] <p>where \(\mathbf{P}^{\mathbf{C}} = \exp((\mathbf{f}^\star \oplus \mathbf{g}^\star - \mathbf{C}) / \varepsilon)\) as we have seen in the first part.</p> <p>Importantly, because we have \(\mathbf{P}^{\mathbf{C}} \in \Pi(\mathbf{a}, \mathbf{b})\), we can notice that the term we added no longer depends on \(\mathbf{C}\) when evaluted in \((\mathbf{f}^\star,\mathbf{g}^\star)\). Indeed</p> \[\begin{align} \left\langle \mathbf{P}^{\mathbf{C}}, \mathbf{1} \mathbf{1}^\top \right\rangle = \sum_{ij} P^{\mathbf{C}}_{ij} = \sum_i a_i = 1 \:. \end{align}\] <p>Thus, when evaluated in \((\mathbf{f}^\star,\mathbf{g}^\star)\), thanks to equation \eqref{eq:first_step} the objective writes</p> \[\begin{align} \min_{\mathbf{f},\mathbf{g}} \: \cal{G}(\widehat{\mathbf{P}}, \mathbf{C}, \mathbf{f}, \mathbf{g}) &amp;= \left\langle \widehat{\mathbf{P}}, \mathbf{C} \right\rangle - \langle \mathbf{f}^\star, \mathbf{a} \rangle - \langle \mathbf{g}^\star, \mathbf{b} \rangle + \varepsilon \\ &amp;= \varepsilon \left( \mathrm{KL}(\widehat{\mathbf{P}} \| \mathbf{P}^{\mathbf{C}}) + \operatorname{H}(\widehat{\mathbf{P}}) + \textrm{1} \right) \label{eq:final_derivation} \:. \end{align}\] <p>Minimizing the above with respect to \(\mathbf{C}\) then amounts to minimizing \(\mathrm{KL}(\widehat{\mathbf{P}}\|\mathbf{P}^{\mathbf{C}})\) since it is the only term that depends on \(\mathbf{C}\) in equation \eqref{eq:final_derivation}.</p> <p>Therefore solving inverse OT is equivalent to the following jointly convex problem</p> \[\begin{align}\label{eq:new_form_invot} \min_{\mathbf{C}, \mathbf{f}, \mathbf{g}} \: \: \cal{G}(\widehat{\mathbf{P}}, \mathbf{C}, \mathbf{f}, \mathbf{g}) \:. \end{align}\] <p>Concretely, this means that \((\mathbf{C}^\star, \mathbf{f}^\star, \mathbf{g}^\star)\) solves \eqref{eq:new_form_invot} if and only if \(\mathbf{C}^\star\) solves inverse OT \eqref{eq:outer_invot} where \(\mathbf{P}^{\mathbf{C}} = \exp((\mathbf{f}^\star \oplus \mathbf{g}^\star - \mathbf{C}) / \varepsilon)\) solves the inner problem \eqref{eq:inner_invot}.</p> <h3 id="parallel-with-monge-gap">Parallel with Monge gap</h3> <p>Let’s take a moment to decipher this new expression closely.</p> <p>Since strong duality holds for entropic OT, one has the equality between the primal optimal objective and the dual optimal objective <em>ie</em> \eqref{eq:eot} = \eqref{eq:dual_eot}.</p> <p>Therefore we have</p> \[\begin{align}\label{eq:min_formulation_invot} \min_{\mathbf{f},\mathbf{g}} \: \cal{G}(\widehat{\mathbf{P}}, \mathbf{C}, \mathbf{f}, \mathbf{g}) &amp;= \left\langle \widehat{\mathbf{P}}, \mathbf{C} \right\rangle - \left(\min_{\mathbf{P} \in \Pi(\mathbf{a}, \mathbf{b})} \: \: \langle \mathbf{C}, \mathbf{P} \rangle - \varepsilon \mathrm{H}(\mathbf{P}) \right) \:. \end{align}\] <p>Hence \(\cal{G}\) quantifies the difference in the transport cost when using \(\widehat{\mathbf{P}}\) against the solution of the inner problem \(\mathbf{P}^{\mathbf{C}}\). This quantity is known as the Monge gap <d-cite key="pmlr-v202-uscidda23a"></d-cite>.</p> <p>:bulb: As discussed earlier, optimizing <em>w.r.t.</em> \(\mathbf{C}\) an argmin like in \eqref{eq:inner_invot} requires computationally demanding tools such as unrolling or implicit function theorem. On the contrary, optimizing the min as in \eqref{eq:min_formulation_invot} is much simpler. It can be done using Danskin’s theorem (or envelope theorem).</p> <p>In our case, this result simply states that, for each update of \(\mathbf{C}\), we can optimize \(\cal{G}\) in \(\mathbf{C}\) by considering \(\mathbf{f}\) and \(\mathbf{g}\) as constants. Without further constraint on \(\mathbf{C}\), the update reads</p> \[\begin{align}\label{eq:update_C} \mathbf{C} &amp;\leftarrow \mathbf{f} \oplus \mathbf{g} - \varepsilon \log \widehat{\mathbf{P}} \:. \end{align}\] <p>Overall, to efficiently solve inverse OT one can use block coordinate descent alternating between updating \(\mathbf{f}\) and \(\mathbf{g}\) with Sinkhorn iterations \eqref{eq:sinkhorn-f}-\eqref{eq:sinkhorn-g} and updating \(\mathbf{C}\) with \eqref{eq:update_C}.</p> <h3 id="applications-to-learn-embeddings">Applications to learn embeddings</h3> <p>In this last part, we are going to see how inverse OT and the presented trick can be used to learn data representations, as shown in <d-cite key="van2024snekhorn"></d-cite> . We are given a dataset \((\mathbf{x}_1, .., \mathbf{x}_n)\) and the goal is to compute embeddings \((\mathbf{z}_1, .., \mathbf{z}_n)\) such that each \(\mathbf{z}_i\) is a low-dimensional representation of the input data point \(\mathbf{x}_i\).</p> <p>To do so, we are going to look for a cost of the form \(d(\mathbf{z}_i, \mathbf{z}_j)\) which solves inverse OT with an input \(\widehat{\mathbf{P}}\) computed from \((\mathbf{x}_1, .., \mathbf{x}_n)\). To compute \(\widehat{\mathbf{P}}\), one can simply solve the symmetric variant of entropic OT wich is exactly problem \eqref{eq:eot} with symmetric \(\mathbf{C}\) \(=(d(\mathbf{x}_i, \mathbf{x}_j))_{ij}\) and \(\mathbf{a}=\mathbf{b}\). We pick \(\mathbf{a}=\mathbf{b}=\mathbf{1}\) to give the same mass to every data point.</p> <p>In symmetric entropic OT, we only have one dual variable \(\mathbf{f}\) as the primal solution is given by \(\widehat{\mathbf{P}} = \exp((\mathbf{f}^\star \oplus \mathbf{f}^\star - \mathbf{C}) / \varepsilon)\). Moreover \(\mathbf{f}^\star\) can be computed by simply iterating <d-footnote> In the code we use the following well-conditioned variant : $f_i \leftarrow \frac{1}{2} (f_i-\varepsilon \log \sum_j e^{(f_j-C_{ij}) / \varepsilon})$. </d-footnote>.</p> \[\begin{align} f_i &amp;\leftarrow - \varepsilon \log \sum_j e^{(f_j-C_{ij}) / \varepsilon} \label{eq:sinkhorn-sym} \:. \end{align}\] <p>:bulb: In symmetric entropic OT, each point spreads its mass to its closest neighbors thus capturing the geometry of the data. In this context, the regularizer \(\varepsilon\) controls the scale of dependencies that is captured.</p> <p>Once we have computed \(\widehat{\mathbf{P}}\), the goal is to solve the inverse problem of finding the embeddings \((\mathbf{z}_1, .., \mathbf{z}_n)\) that would generate a similar entropic OT plan in low-dimension. In other words, we want the geometry in the low-dimensional space to be similar to the one in input space. This method has strong connections with the t-SNE algorithm as developped in <d-cite key="van2024snekhorn"></d-cite> <d-footnote> This work relies on a more elaborate version of symmetric entropic OT for computing $\widehat{\mathbf{P}}$ but the methodology to update the $(\mathbf{z}_1, .., \mathbf{z}_n)$ is the same as here. </d-footnote>.</p> <p>To do so, we rely on the presented trick for inverse OT and therefore focus on solving</p> \[\begin{align} \min_{(\mathbf{z}_1, .., \mathbf{z}_n), \mathbf{f}, \mathbf{g}} \: \: \cal{G}(\widehat{\mathbf{P}}, \mathbf{C}_{\mathbf{Z}}, \mathbf{f}, \mathbf{g}) \:. \end{align}\] <p>where \(\mathbf{C}_{\mathbf{Z}}\) it the symmetric cost matrix with entries \(d(\mathbf{z}_i, \mathbf{z}_j)\).</p> <p>We consider the common task of embedding the swiss roll (depicted below) from 3d to 2d. <img src="/assets/img/blog-invot/swiss_roll.svg" alt="" style="display:block; margin-left:auto; margin-right:auto; width:50%;"/></p> <p>In the experiments, we take the squared Euclidean distance for \(d\), \(\varepsilon=10\) for the entropic regularizer and independent \(\cal{N}(0,1)\) variables to initialize the embedding coordinates. The code is provided in the box below.</p> <details><summary>Python Code</summary> <figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="n">torch</span>
<span class="kn">import</span> <span class="n">matplotlib.pyplot</span> <span class="k">as</span> <span class="n">plt</span>
<span class="kn">from</span> <span class="n">sklearn</span> <span class="kn">import</span> <span class="n">datasets</span>
<span class="kn">from</span> <span class="n">tqdm</span> <span class="kn">import</span> <span class="n">tqdm</span>
<span class="kn">import</span> <span class="n">time</span>

<span class="k">def</span> <span class="nf">symmetric_sinkhorn</span><span class="p">(</span><span class="n">C</span><span class="p">,</span> <span class="n">eps</span><span class="o">=</span><span class="mf">1e0</span><span class="p">,</span> <span class="n">f0</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">max_iter</span><span class="o">=</span><span class="mi">1000</span><span class="p">,</span> <span class="n">tol</span><span class="o">=</span><span class="mf">1e-6</span><span class="p">,</span> <span class="n">verbose</span><span class="o">=</span><span class="bp">False</span><span class="p">):</span>
<span class="sh">"""</span><span class="s">
Performs Sinkhorn iterations in log domain to solve the entropic symmetric
OT problem with symmetric cost C and entropic regularization eps.
</span><span class="sh">"""</span>
<span class="n">n</span> <span class="o">=</span> <span class="n">C</span><span class="p">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>

    <span class="k">if</span> <span class="n">f0</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
        <span class="n">f</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">zeros</span><span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="n">dtype</span><span class="o">=</span><span class="n">C</span><span class="p">.</span><span class="n">dtype</span><span class="p">,</span> <span class="n">device</span><span class="o">=</span><span class="n">C</span><span class="p">.</span><span class="n">device</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="n">f</span> <span class="o">=</span> <span class="n">f0</span>

    <span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">max_iter</span><span class="p">):</span>
        <span class="c1"># well-conditioned symmetric Sinkhorn update
</span>        <span class="n">f</span> <span class="o">=</span> <span class="mf">0.5</span> <span class="o">*</span> <span class="p">(</span><span class="n">f</span> <span class="o">-</span> <span class="n">eps</span> <span class="o">*</span> <span class="n">torch</span><span class="p">.</span><span class="nf">logsumexp</span><span class="p">((</span><span class="n">f</span> <span class="o">-</span> <span class="n">C</span><span class="p">)</span> <span class="o">/</span> <span class="n">eps</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">))</span>

        <span class="c1"># check convergence every 10 iterations
</span>        <span class="k">if</span> <span class="n">k</span> <span class="o">%</span> <span class="mi">10</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
            <span class="n">log_T</span> <span class="o">=</span> <span class="p">(</span><span class="n">f</span><span class="p">[:,</span> <span class="bp">None</span><span class="p">]</span> <span class="o">+</span> <span class="n">f</span><span class="p">[</span><span class="bp">None</span><span class="p">,</span> <span class="p">:]</span> <span class="o">-</span> <span class="n">C</span><span class="p">)</span> <span class="o">/</span> <span class="n">eps</span>
            <span class="nf">if </span><span class="p">(</span><span class="n">torch</span><span class="p">.</span><span class="nf">abs</span><span class="p">(</span><span class="n">torch</span><span class="p">.</span><span class="nf">exp</span><span class="p">(</span><span class="n">torch</span><span class="p">.</span><span class="nf">logsumexp</span><span class="p">(</span><span class="n">log_T</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">))</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="o">&lt;</span> <span class="n">tol</span><span class="p">).</span><span class="nf">all</span><span class="p">():</span>
                <span class="k">if</span> <span class="n">verbose</span><span class="p">:</span>
                    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">'</span><span class="s">---------- Breaking at iter </span><span class="si">{</span><span class="n">k</span><span class="si">}</span><span class="s"> ----------</span><span class="sh">'</span><span class="p">)</span>
                <span class="k">break</span>

        <span class="k">if</span> <span class="n">k</span> <span class="o">==</span> <span class="n">max_iter</span><span class="o">-</span><span class="mi">1</span><span class="p">:</span>
            <span class="k">if</span> <span class="n">verbose</span><span class="p">:</span>
                <span class="nf">print</span><span class="p">(</span><span class="sh">'</span><span class="s">---------- Max iter attained for Sinkhorn algorithm ----------</span><span class="sh">'</span><span class="p">)</span>

    <span class="nf">return </span><span class="p">(</span><span class="n">f</span><span class="p">[:,</span> <span class="bp">None</span><span class="p">]</span> <span class="o">+</span> <span class="n">f</span><span class="p">[</span><span class="bp">None</span><span class="p">,</span> <span class="p">:]</span> <span class="o">-</span> <span class="n">C</span><span class="p">)</span> <span class="o">/</span> <span class="n">eps</span><span class="p">,</span> <span class="n">f</span>

<span class="k">def</span> <span class="nf">inverse_OT_unrolling</span><span class="p">(</span><span class="n">log_P_hat</span><span class="p">,</span> <span class="n">Z0</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">lr</span><span class="o">=</span><span class="mf">1e0</span><span class="p">,</span> <span class="n">eps</span><span class="o">=</span><span class="mf">1e0</span><span class="p">,</span> <span class="n">verbose</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">max_iter</span><span class="o">=</span><span class="mi">1000</span><span class="p">,</span> <span class="n">tol</span><span class="o">=</span><span class="mf">1e-6</span><span class="p">):</span>
<span class="sh">"""</span><span class="s">
Solves the inverse OT problem for an input P_hat using autodiff.
</span><span class="sh">"""</span>
<span class="n">n</span> <span class="o">=</span> <span class="n">log_P_hat</span><span class="p">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>

    <span class="k">if</span> <span class="n">Z0</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
        <span class="n">Z</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">randn</span><span class="p">((</span><span class="n">n</span><span class="p">,</span><span class="mi">2</span><span class="p">))</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="n">Z</span> <span class="o">=</span> <span class="n">Z0</span>

    <span class="n">Z</span><span class="p">.</span><span class="n">requires_grad</span> <span class="o">=</span> <span class="bp">True</span>
    <span class="n">optimizer</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">optim</span><span class="p">.</span><span class="nc">Adam</span><span class="p">([</span><span class="n">Z</span><span class="p">],</span> <span class="n">lr</span><span class="o">=</span><span class="n">lr</span><span class="p">)</span>
    <span class="n">f</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">zeros</span><span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="n">dtype</span><span class="o">=</span><span class="n">C</span><span class="p">.</span><span class="n">dtype</span><span class="p">,</span> <span class="n">device</span><span class="o">=</span><span class="n">C</span><span class="p">.</span><span class="n">device</span><span class="p">)</span>

    <span class="n">pbar</span> <span class="o">=</span> <span class="nf">tqdm</span><span class="p">(</span><span class="nf">range</span><span class="p">(</span><span class="n">max_iter</span><span class="p">))</span>
    <span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="n">pbar</span><span class="p">:</span>
        <span class="n">Z_prev</span> <span class="o">=</span> <span class="n">Z</span><span class="p">.</span><span class="nf">clone</span><span class="p">().</span><span class="nf">detach</span><span class="p">()</span>
        <span class="n">optimizer</span><span class="p">.</span><span class="nf">zero_grad</span><span class="p">()</span>

        <span class="n">C_z</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">cdist</span><span class="p">(</span><span class="n">Z</span><span class="p">,</span> <span class="n">Z</span><span class="p">,</span> <span class="n">p</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span><span class="o">**</span><span class="mi">2</span>

        <span class="c1"># Run Sinkhorn (with autograd) to update dual variables
</span>        <span class="n">log_Q</span><span class="p">,</span> <span class="n">f</span> <span class="o">=</span> <span class="nf">symmetric_sinkhorn</span><span class="p">(</span><span class="n">C_z</span><span class="p">,</span> <span class="n">eps</span><span class="o">=</span><span class="n">eps</span><span class="p">,</span> <span class="n">max_iter</span><span class="o">=</span><span class="mi">100</span><span class="p">,</span> <span class="n">f0</span><span class="o">=</span><span class="n">f</span><span class="p">.</span><span class="nf">detach</span><span class="p">())</span>

        <span class="c1"># Compute KL loss to update Z
</span>        <span class="n">loss</span> <span class="o">=</span> <span class="p">(</span><span class="n">torch</span><span class="p">.</span><span class="nf">exp</span><span class="p">(</span><span class="n">log_P_hat</span><span class="p">)</span> <span class="o">*</span> <span class="p">(</span><span class="n">log_P_hat</span> <span class="o">-</span> <span class="n">log_Q</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="o">+</span> <span class="n">torch</span><span class="p">.</span><span class="nf">exp</span><span class="p">(</span><span class="n">log_Q</span><span class="p">)).</span><span class="nf">sum</span><span class="p">()</span>
        <span class="n">loss</span><span class="p">.</span><span class="nf">backward</span><span class="p">()</span>
        <span class="n">optimizer</span><span class="p">.</span><span class="nf">step</span><span class="p">()</span>

        <span class="c1"># Check convergence
</span>        <span class="n">delta</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">abs</span><span class="p">(</span><span class="n">Z</span> <span class="o">-</span> <span class="n">Z_prev</span><span class="p">)</span> <span class="o">/</span> <span class="n">torch</span><span class="p">.</span><span class="nf">abs</span><span class="p">(</span><span class="n">Z_prev</span><span class="p">)</span>
        <span class="nf">if </span><span class="p">(</span><span class="n">delta</span> <span class="o">&lt;</span> <span class="n">tol</span><span class="p">).</span><span class="nf">all</span><span class="p">():</span>
            <span class="k">if</span> <span class="n">verbose</span><span class="p">:</span>
                <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">'</span><span class="s">---------- Breaking at iter </span><span class="si">{</span><span class="n">k</span><span class="o">+</span><span class="mi">1</span><span class="si">}</span><span class="s"> ----------</span><span class="sh">'</span><span class="p">)</span>
            <span class="k">break</span>

        <span class="k">if</span> <span class="n">verbose</span><span class="p">:</span>
            <span class="n">pbar</span><span class="p">.</span><span class="nf">set_description</span><span class="p">(</span><span class="sa">f</span><span class="sh">'</span><span class="s">Loss : </span><span class="si">{</span><span class="nf">float</span><span class="p">(</span><span class="n">loss</span><span class="p">.</span><span class="nf">item</span><span class="p">())</span><span class="si">:</span> <span class="p">.</span><span class="mi">3</span><span class="n">e</span><span class="si">}</span><span class="s">, </span><span class="sh">'</span>
                                 <span class="sa">f</span><span class="sh">'</span><span class="s">Delta : </span><span class="si">{</span><span class="nf">float</span><span class="p">(</span><span class="n">delta</span><span class="p">.</span><span class="nf">mean</span><span class="p">().</span><span class="nf">item</span><span class="p">())</span><span class="si">:</span> <span class="p">.</span><span class="mi">3</span><span class="n">e</span><span class="si">}</span><span class="s"> </span><span class="sh">'</span>
                                <span class="p">)</span>

    <span class="k">return</span> <span class="n">Z</span>

<span class="k">def</span> <span class="nf">inverse_OT_gap</span><span class="p">(</span><span class="n">log_P_hat</span><span class="p">,</span> <span class="n">Z0</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span> <span class="n">lr</span><span class="o">=</span><span class="mf">1e0</span><span class="p">,</span> <span class="n">eps</span><span class="o">=</span><span class="mf">1e0</span><span class="p">,</span> <span class="n">verbose</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">max_iter</span><span class="o">=</span><span class="mi">1000</span><span class="p">,</span> <span class="n">tol</span><span class="o">=</span><span class="mf">1e-6</span><span class="p">):</span>
<span class="sh">"""</span><span class="s">
Solves the inverse OT problem for an input P_hat using the trick detailed in the blog.
</span><span class="sh">"""</span>
<span class="n">n</span> <span class="o">=</span> <span class="n">log_P_hat</span><span class="p">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>

    <span class="k">if</span> <span class="n">Z0</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
        <span class="n">Z</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">randn</span><span class="p">((</span><span class="n">n</span><span class="p">,</span><span class="mi">2</span><span class="p">))</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="n">Z</span> <span class="o">=</span> <span class="n">Z0</span>

    <span class="n">Z</span><span class="p">.</span><span class="n">requires_grad</span> <span class="o">=</span> <span class="bp">True</span>
    <span class="n">optimizer</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="n">optim</span><span class="p">.</span><span class="nc">Adam</span><span class="p">([</span><span class="n">Z</span><span class="p">],</span> <span class="n">lr</span><span class="o">=</span><span class="n">lr</span><span class="p">)</span>
    <span class="n">f</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">zeros</span><span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="n">dtype</span><span class="o">=</span><span class="n">C</span><span class="p">.</span><span class="n">dtype</span><span class="p">,</span> <span class="n">device</span><span class="o">=</span><span class="n">C</span><span class="p">.</span><span class="n">device</span><span class="p">)</span>

    <span class="n">pbar</span> <span class="o">=</span> <span class="nf">tqdm</span><span class="p">(</span><span class="nf">range</span><span class="p">(</span><span class="n">max_iter</span><span class="p">))</span>
    <span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="n">pbar</span><span class="p">:</span>
        <span class="n">Z_prev</span> <span class="o">=</span> <span class="n">Z</span><span class="p">.</span><span class="nf">clone</span><span class="p">().</span><span class="nf">detach</span><span class="p">()</span>
        <span class="n">optimizer</span><span class="p">.</span><span class="nf">zero_grad</span><span class="p">()</span>

        <span class="n">C_z</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">cdist</span><span class="p">(</span><span class="n">Z</span><span class="p">,</span> <span class="n">Z</span><span class="p">,</span> <span class="n">p</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span><span class="o">**</span><span class="mi">2</span>

        <span class="c1"># Run Sinkhorn (without autograd) to update dual variables
</span>        <span class="k">with</span> <span class="n">torch</span><span class="p">.</span><span class="nf">no_grad</span><span class="p">():</span>
            <span class="n">_</span><span class="p">,</span> <span class="n">f</span> <span class="o">=</span> <span class="nf">symmetric_sinkhorn</span><span class="p">(</span><span class="n">C_z</span><span class="p">,</span> <span class="n">eps</span><span class="o">=</span><span class="n">eps</span><span class="p">,</span> <span class="n">max_iter</span><span class="o">=</span><span class="mi">100</span><span class="p">,</span> <span class="n">f0</span><span class="o">=</span><span class="n">f</span><span class="p">.</span><span class="nf">detach</span><span class="p">())</span>
        <span class="n">log_Q</span> <span class="o">=</span> <span class="p">(</span><span class="n">f</span><span class="p">[:,</span> <span class="bp">None</span><span class="p">]</span> <span class="o">+</span> <span class="n">f</span><span class="p">[</span><span class="bp">None</span><span class="p">,</span> <span class="p">:]</span> <span class="o">-</span> <span class="n">C_z</span><span class="p">)</span> <span class="o">/</span> <span class="n">eps</span>

        <span class="c1"># Compute Monge gap loss to update Z
</span>        <span class="n">loss</span> <span class="o">=</span> <span class="p">(</span><span class="n">torch</span><span class="p">.</span><span class="nf">exp</span><span class="p">(</span><span class="n">log_P_hat</span><span class="p">)</span><span class="o">*</span><span class="n">C_z</span><span class="p">).</span><span class="nf">sum</span><span class="p">()</span> <span class="o">+</span> <span class="n">eps</span> <span class="o">*</span> <span class="n">torch</span><span class="p">.</span><span class="nf">exp</span><span class="p">(</span><span class="n">torch</span><span class="p">.</span><span class="nf">logsumexp</span><span class="p">(</span><span class="n">log_Q</span><span class="p">,</span> <span class="n">dim</span><span class="o">=</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">)))</span>
        <span class="n">loss</span><span class="p">.</span><span class="nf">backward</span><span class="p">()</span>
        <span class="n">optimizer</span><span class="p">.</span><span class="nf">step</span><span class="p">()</span>

        <span class="c1"># Check convergence
</span>        <span class="n">delta</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">abs</span><span class="p">(</span><span class="n">Z</span> <span class="o">-</span> <span class="n">Z_prev</span><span class="p">)</span> <span class="o">/</span> <span class="n">torch</span><span class="p">.</span><span class="nf">abs</span><span class="p">(</span><span class="n">Z_prev</span><span class="p">)</span>
        <span class="nf">if </span><span class="p">(</span><span class="n">delta</span> <span class="o">&lt;</span> <span class="n">tol</span><span class="p">).</span><span class="nf">all</span><span class="p">():</span>
            <span class="k">if</span> <span class="n">verbose</span><span class="p">:</span>
                <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">'</span><span class="s">---------- Breaking at iter </span><span class="si">{</span><span class="n">k</span><span class="o">+</span><span class="mi">1</span><span class="si">}</span><span class="s"> ----------</span><span class="sh">'</span><span class="p">)</span>
            <span class="k">break</span>

        <span class="k">if</span> <span class="n">verbose</span><span class="p">:</span>
            <span class="n">pbar</span><span class="p">.</span><span class="nf">set_description</span><span class="p">(</span><span class="sa">f</span><span class="sh">'</span><span class="s">Loss : </span><span class="si">{</span><span class="nf">float</span><span class="p">(</span><span class="n">loss</span><span class="p">.</span><span class="nf">item</span><span class="p">())</span><span class="si">:</span> <span class="p">.</span><span class="mi">3</span><span class="n">e</span><span class="si">}</span><span class="s">, </span><span class="sh">'</span>
                                 <span class="sa">f</span><span class="sh">'</span><span class="s">Delta : </span><span class="si">{</span><span class="nf">float</span><span class="p">(</span><span class="n">delta</span><span class="p">.</span><span class="nf">mean</span><span class="p">().</span><span class="nf">item</span><span class="p">())</span><span class="si">:</span> <span class="p">.</span><span class="mi">3</span><span class="n">e</span><span class="si">}</span><span class="s"> </span><span class="sh">'</span>
                                <span class="p">)</span>

    <span class="k">return</span> <span class="n">Z</span>

<span class="c1">### Run the experiments with Swiss Roll
</span>
<span class="c1"># We fix a scale via the regularizer epsilon
</span>
<span class="n">eps</span> <span class="o">=</span> <span class="mf">1e1</span>

<span class="n">N_list</span> <span class="o">=</span> <span class="p">[</span><span class="mi">50</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">200</span><span class="p">,</span> <span class="mi">300</span><span class="p">,</span> <span class="mi">600</span><span class="p">,</span> <span class="mi">1000</span><span class="p">]</span>

<span class="n">list_Z_unrolling</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">list_Z_gap</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">list_color</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">timings_unrolling</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">timings_gap</span> <span class="o">=</span> <span class="p">[]</span>

<span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">N_list</span><span class="p">:</span> <span class="c1"># Load n datapoints of the Swiss roll
</span><span class="n">sr_points</span><span class="p">,</span> <span class="n">sr_color</span> <span class="o">=</span> <span class="n">datasets</span><span class="p">.</span><span class="nf">make_swiss_roll</span><span class="p">(</span><span class="n">n_samples</span><span class="o">=</span><span class="n">n</span><span class="p">,</span> <span class="n">random_state</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
<span class="n">list_color</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">sr_color</span><span class="p">)</span>
<span class="n">sr_points_torch</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">tensor</span><span class="p">(</span><span class="n">sr_points</span><span class="p">,</span> <span class="n">dtype</span><span class="o">=</span><span class="n">torch</span><span class="p">.</span><span class="n">double</span><span class="p">)</span>

    <span class="c1"># Compute the corresponding input P_hat
</span>    <span class="n">C</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">cdist</span><span class="p">(</span><span class="n">sr_points_torch</span><span class="p">,</span> <span class="n">sr_points_torch</span><span class="p">,</span> <span class="n">p</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span><span class="o">**</span><span class="mi">2</span>
    <span class="n">log_P</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="nf">symmetric_sinkhorn</span><span class="p">(</span><span class="n">C</span><span class="p">,</span> <span class="n">eps</span><span class="o">=</span><span class="n">eps</span><span class="p">)</span>

    <span class="c1"># We use the same initialisation for both algorithms
</span>    <span class="n">Z0</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">randn</span><span class="p">((</span><span class="n">n</span><span class="p">,</span><span class="mi">2</span><span class="p">))</span>

    <span class="c1"># Solve inverse OT via unrolling
</span>    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="p">.</span><span class="nf">time</span><span class="p">()</span>
    <span class="n">Z</span> <span class="o">=</span> <span class="nf">inverse_OT_unrolling</span><span class="p">(</span><span class="n">log_P</span><span class="p">,</span> <span class="n">Z0</span><span class="p">.</span><span class="nf">clone</span><span class="p">(),</span> <span class="n">eps</span><span class="o">=</span><span class="n">eps</span><span class="p">)</span>
    <span class="n">end</span> <span class="o">=</span> <span class="n">time</span><span class="p">.</span><span class="nf">time</span><span class="p">()</span>
    <span class="n">timings_unrolling</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">end</span><span class="o">-</span><span class="n">start</span><span class="p">)</span>
    <span class="n">list_Z_unrolling</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">Z</span><span class="p">.</span><span class="nf">detach</span><span class="p">().</span><span class="nf">numpy</span><span class="p">())</span>

    <span class="c1"># Solve inverse OT via Monge gap
</span>    <span class="n">start</span> <span class="o">=</span> <span class="n">time</span><span class="p">.</span><span class="nf">time</span><span class="p">()</span>
    <span class="n">Z_</span> <span class="o">=</span> <span class="nf">inverse_OT_gap</span><span class="p">(</span><span class="n">log_P</span><span class="p">,</span> <span class="n">Z0</span><span class="p">.</span><span class="nf">clone</span><span class="p">(),</span> <span class="n">eps</span><span class="o">=</span><span class="n">eps</span><span class="p">)</span>
    <span class="n">end</span> <span class="o">=</span> <span class="n">time</span><span class="p">.</span><span class="nf">time</span><span class="p">()</span>
    <span class="n">timings_gap</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">end</span><span class="o">-</span><span class="n">start</span><span class="p">)</span>
    <span class="n">list_Z_gap</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">Z_</span><span class="p">.</span><span class="nf">detach</span><span class="p">().</span><span class="nf">numpy</span><span class="p">())</span>

<span class="c1">### Plot the results
</span>
<span class="n">fig</span><span class="p">,</span> <span class="n">axs</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">subplots</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">6</span><span class="p">),</span> <span class="n">layout</span><span class="o">=</span><span class="sh">'</span><span class="s">constrained</span><span class="sh">'</span><span class="p">)</span>

<span class="k">for</span> <span class="n">e</span><span class="p">,</span><span class="n">i</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">5</span><span class="p">]):</span>
<span class="n">axs</span><span class="p">[</span><span class="mi">0</span><span class="p">,</span><span class="n">e</span><span class="p">].</span><span class="nf">set_title</span><span class="p">(</span><span class="sa">f</span><span class="sh">'</span><span class="si">{</span><span class="n">N_list</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="si">}</span><span class="s"> points via unrolling</span><span class="sh">'</span><span class="p">)</span>
<span class="n">axs</span><span class="p">[</span><span class="mi">0</span><span class="p">,</span><span class="n">e</span><span class="p">].</span><span class="nf">scatter</span><span class="p">(</span><span class="n">list_Z_unrolling</span><span class="p">[</span><span class="n">i</span><span class="p">][:,</span><span class="mi">0</span><span class="p">],</span> <span class="n">list_Z_unrolling</span><span class="p">[</span><span class="n">i</span><span class="p">][:,</span><span class="mi">1</span><span class="p">],</span> <span class="n">c</span><span class="o">=</span><span class="n">list_color</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>

    <span class="n">axs</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="n">e</span><span class="p">].</span><span class="nf">set_title</span><span class="p">(</span><span class="sa">f</span><span class="sh">'</span><span class="si">{</span><span class="n">N_list</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="si">}</span><span class="s"> points via Monge gap</span><span class="sh">'</span><span class="p">)</span>
    <span class="n">axs</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="n">e</span><span class="p">].</span><span class="nf">scatter</span><span class="p">(</span><span class="n">list_Z_gap</span><span class="p">[</span><span class="n">i</span><span class="p">][:,</span><span class="mi">0</span><span class="p">],</span> <span class="n">list_Z_gap</span><span class="p">[</span><span class="n">i</span><span class="p">][:,</span><span class="mi">1</span><span class="p">],</span> <span class="n">c</span><span class="o">=</span><span class="n">list_color</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>

<span class="n">plt</span><span class="p">.</span><span class="nf">savefig</span><span class="p">(</span><span class="sh">"</span><span class="s">swiss_roll_inverse_OT.svg</span><span class="sh">"</span><span class="p">,</span> <span class="n">bbox_inches</span><span class="o">=</span><span class="sh">'</span><span class="s">tight</span><span class="sh">'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">show</span><span class="p">()</span>

<span class="n">plt</span><span class="p">.</span><span class="nf">plot</span><span class="p">(</span><span class="n">N_list</span><span class="p">,</span> <span class="n">timings_unrolling</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="sh">'</span><span class="s">Unrolling</span><span class="sh">'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">scatter</span><span class="p">(</span><span class="n">N_list</span><span class="p">,</span> <span class="n">timings_unrolling</span><span class="p">,</span> <span class="n">marker</span><span class="o">=</span><span class="sh">'</span><span class="s">X</span><span class="sh">'</span><span class="p">,</span> <span class="n">s</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">scatter</span><span class="p">(</span><span class="n">N_list</span><span class="p">,</span> <span class="n">timings_gap</span><span class="p">,</span> <span class="n">marker</span><span class="o">=</span><span class="sh">'</span><span class="s">X</span><span class="sh">'</span><span class="p">,</span> <span class="n">s</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">plot</span><span class="p">(</span><span class="n">N_list</span><span class="p">,</span> <span class="n">timings_gap</span><span class="p">,</span> <span class="n">label</span><span class="o">=</span><span class="sh">'</span><span class="s">Monge gap</span><span class="sh">'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">xlabel</span><span class="p">(</span><span class="sh">'</span><span class="s">Number of points</span><span class="sh">'</span><span class="p">,</span> <span class="n">fontsize</span><span class="o">=</span><span class="mi">15</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">ylabel</span><span class="p">(</span><span class="sh">'</span><span class="s">Time (s)</span><span class="sh">'</span><span class="p">,</span> <span class="n">fontsize</span><span class="o">=</span><span class="mi">15</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">legend</span><span class="p">(</span><span class="n">fontsize</span><span class="o">=</span><span class="mi">15</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">title</span><span class="p">(</span><span class="sh">'</span><span class="s">Computation time for inverse OT</span><span class="sh">'</span><span class="p">,</span> <span class="n">fontsize</span><span class="o">=</span><span class="mi">20</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">savefig</span><span class="p">(</span><span class="sh">"</span><span class="s">timings.svg</span><span class="sh">"</span><span class="p">,</span> <span class="n">bbox_inches</span><span class="o">=</span><span class="sh">'</span><span class="s">tight</span><span class="sh">'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">show</span><span class="p">()</span></code></pre></figure> </details> <p>First, as shown in the figure below, we can verify that we obtain exactly the same embeddings \((\mathbf{z}_1, .., \mathbf{z}_n)\) using unrolling and the Monge gap trick presented in this blog. <img src="/assets/img/blog-invot/swiss_roll_inverse_OT.svg" alt="" style="display:block; margin-left:auto; margin-right:auto; width:100%;"/></p> <p>Regarding run-time, the Monge gap approach is faster than unrolling as we can see on the following plot. Hence the trick presented in this blog has a great practical interest, especially for large-scale applications. <img src="/assets/img/blog-invot/timings.svg" alt="" style="display:block; margin-left:auto; margin-right:auto; width:50%;"/></p> <p>:bulb: Inverse OT is also useful for contrastive learning as shown in <d-cite key="pmlr-v202-shi23j"></d-cite>. In contrastive learning, one constructs augmented views \((\mathbf{y}_1, .., \mathbf{y}_r)\) of input data points \((\mathbf{x}_1, .., \mathbf{x}_n)\). The ground truth coupling \(\widehat{\mathbf{P}}\) is taken such that \(\widehat{P}_{ij}=1\) if \(\mathbf{y}_j\) is an augmented view of \(\mathbf{x}_i\) and \(0\) otherwise. Then, inverse OT can be applied to compute latent representations</p> \[\begin{align} (\phi_{\theta}(\mathbf{x}_1), .., \phi_{\theta}(\mathbf{x}_n), \phi_{\theta}(\mathbf{y}_1), ..., \phi_{\theta}(\mathbf{y}_r)) \end{align}\] <p>where \(\phi_{\theta}\) is a neural network. Note that both directed and symmetric inverse OT can be considered <d-footnote> Indeed, directed inverse OT corresponds to treating the $(\mathbf{x}_1, .., \mathbf{x}_n)$ as source points and the $(\mathbf{y}_1, .., \mathbf{y}_r)$ as target points while symmetric inverse OT treats each point indifferently. Both approach use $\widehat{\mathbf{P}}$ as target coupling.</d-footnote>. Interestingly, the trick presented in this blog can be applied in this context thus alleviating the need to perform backpropagation through the Sinkhorn iterations.</p> <p>:pencil2: Feel free to contact me for any question or remark on this blog !</p> <h3 id="citation">Citation</h3> <p>If you found this useful, you can cite this blog post using:</p> <pre><code class="language-{.bibtex}">@article{inverse_ot_unrolling,
  title   = {Inverse optimal transport does not require unrolling},
  author  = {Hugues Van Assel},
  year    = {2024},
  month   = {April},
  url     = {https://huguesva.github.io/blog/2024/inverseOT_mongegap/}
}
</code></pre>]]></content><author><name>Hugues Van Assel</name></author><category term="OT"/><category term="DR"/><summary type="html"><![CDATA[A note on the equivalence between inverse OT and minimizing the Monge gap.]]></summary></entry></feed>