traceot · research, unvalidated

Learn ADOPT: regression when each answer is a whole distribution

A plain-language guide to optimal-transport regression, the additive transport idea behind ADOPT, and what happened when we re-implemented it from the paper.

1. What optimal-transport regression is

Ordinary regression predicts one number per row: an average. Sometimes the thing you care about is a whole distribution: how spread out values are, how heavy the right tail is, not just where the middle sits. Distributional regression predicts the whole shape.

To compare and average distributions you need a distance between them. Optimal transport (OT) gives a natural one: the cheapest way to move the probability mass of one distribution so that it becomes the other. With squared-distance cost this is the Wasserstein-2 distance, written W2.

On a single number line OT is especially simple. The cheapest plan moves the 10th percentile of the first distribution to the 10th percentile of the second, the median to the median, and so on. So the transport map is "percentile to same percentile", and it is always increasing.

image/svg+xml Matplotlib v3.11.2, https://matplotlib.org/
Figure 1. Left: two distributions; arrows move each percentile of the blue one to the same percentile of the red one. Right: the resulting transport map. Simulated data.

This gives a handy representation: describe each distribution by its quantile function (the value at each percentile). Then the W2 distance is just the ordinary distance between quantile functions, and the average distribution (the Fréchet mean) is the percentile-by-percentile average.

OT regression predicts, for each set of inputs x, a whole distribution, and judges errors with W2. Instead of "x raises the average by 2", the answer reads "x pushes the distribution along this transport map".

2. How additive transport works

Additive models (like generalised additive models) are popular because each input gets its own curve you can plot. In ordinary regression the effects simply add: prediction = average + effect of x1 + effect of x2 + ….

Distributions cannot be added. ADOPT's idea is to replace "add an effect" with "apply a transport". Each input x_j gets its own transport map T_j(x_j). Start from the average distribution and apply the maps one after another:

prediction(x) = T_p(x_p) ∘ … ∘ T_2(x_2) ∘ T_1(x_1) applied to the mean distribution

In the paper this chaining is written with a "⊕" sign, which means "do this transport, then that one". In ordinary flat space, chaining shifts is the same as adding them, so ADOPT reduces to the usual additive model there.

Each map is glass-box: you can show how one input value moves, widens or reshapes the outcome distribution while holding the others at their typical level.

image/svg+xml Matplotlib v3.11.2, https://matplotlib.org/
Figure 2. ADOPT fitted to simulated rows whose outcome distribution is shifted by x1, widened by x2 and given a heavier right tail by x3. Each panel shows one estimated transport applied to the mean distribution at three input values.

3. ADOPT's components, one by one

Fréchet mean

The "average" distribution: the one with the smallest total squared W2 distance to all observed responses. For 1-D distributions it is the percentile-wise mean of the quantile functions.

Transport maps T_j(x_j)

For each input there is a curve of distributions g_j(x_j). The map T_j(x_j) is the OT map from the average of that curve to its value at x_j. This is the distribution version of "subtract the mean of each effect" that makes additive models identifiable.

Partial transport residuals

To estimate one map, undo all the others. For row i, take the map from the mean to the observed distribution, then apply the inverse maps of every other input. What remains is "the part of this row explained by input j, plus noise". This mirrors partial residuals in classic backfitting. Order matters because transports need not commute, and the paper fixes the order.

Local Fréchet regression (the smoother)

The residuals for input j are smoothed against x_j with kernel-weighted local linear regression, done in W2 geometry. For 1-D distributions this is a weighted average of quantile functions, followed by a step that makes the result increasing again (the weights can be negative).

Transport centring

After smoothing, each map is re-anchored at the Fréchet mean of its own curve, so that "typical" inputs mean "no change".

Transport backfitting loop

Cycle through the inputs, re-estimating each map with the others held fixed, until the fitted distributions stop changing. The bandwidth of the smoother is chosen by 5-fold cross-validation.

Cost

Each cycle smooths n residual distributions at n points for each of p inputs: about n² · p · q operations for q percentiles (the paper's Supplement C says the same). That is fine for hundreds or thousands of distributions, and too slow for millions of rows. See section 5.

4. Our reproduction of the paper's simulations

There is no public code, so we re-implemented ADOPT from the paper: All computation runs in a Rust core. A separate plain-numpy version, used only by the tests, checks every Rust result (agreement within 1e-9, in practice about 1e-14). We then re-ran the paper's two simulation designs with fixed seeds and B = 200 replicates per setting. Every guess we had to make is listed in REPRODUCTION_NOTES.md.

The metric is MISE: the squared W2 (or log-Cholesky) error of the predicted distribution, averaged over the unit cube of inputs and over replicates. Lower is better. "Paper" columns are copied verbatim from the published tables. "Ours" are our runs.

Table 1: distributional responses, MISE ×10⁻³
settingpaper ADOPTours ADOPTpaper GFours GFpaper ADRours ADRpaper FMours FMours L2-additive*
I/1000.317 (0.010)0.391 (0.014)0.591 (0.008)0.740 (0.011)0.761 (0.008)not run11.019 (0.068)11.691 (0.010)0.636 (0.012)
I/3000.181 (0.004)0.179 (0.005)0.499 (0.003)0.576 (0.005)0.586 (0.004)not run11.119 (0.040)11.602 (0.003)0.436 (0.006)
II/1001.685 (0.057)0.505 (0.010)6.636 (0.057)6.362 (0.022)80.411 (0.702)not run207.205 (1.681)204.700 (0.219)0.579 (0.014)
II/3001.121 (0.041)0.178 (0.003)6.701 (0.037)5.975 (0.008)81.951 (0.455)not run211.297 (1.080)203.277 (0.064)0.194 (0.004)
Table 2: SPD responses, MISE ×10⁻²
settingpaper ADOPTours ADOPTpaper GFours GFpaper FMours FM
I/10/1002.769 (0.025)0.060 (0.000)9.495 (0.060)0.950 (0.003)135.344 (0.647)134.279 (0.231)
I/10/3001.655 (0.012)0.021 (0.000)9.714 (0.037)0.895 (0.001)136.122 (0.366)132.593 (0.066)
I/20/1005.712 (0.070)0.220 (0.001)14.110 (0.102)1.943 (0.005)192.688 (0.92)272.178 (0.516)
I/20/3003.788 (0.048)0.079 (0.000)14.254 (0.063)1.806 (0.002)193.796 (0.521)268.959 (0.191)
II/10/1004.146 (0.088)0.086 (0.001)18.507 (0.090)4.675 (0.013)96.562 (0.490)206.037 (0.112)
II/10/3002.702 (0.036)0.031 (0.000)18.808 (0.052)4.481 (0.004)97.047 (0.303)205.286 (0.033)
II/20/1008.921 (0.167)0.343 (0.003)28.237 (0.14)10.124 (0.029)143.382 (0.706)434.257 (0.264)
II/20/3006.435 (0.100)0.115 (0.000)28.512 (0.078)9.723 (0.008)144.088 (0.437)432.502 (0.092)

Mean (standard error). *L2-additive is an extra comparator of ours (additive model on quantile functions treated as vectors), not part of the paper. We did not re-implement the ADR method of Han et al. (2020), so its column is "not run".

image/svg+xml Matplotlib v3.11.2, https://matplotlib.org/
Figure 3. Paper-reported (pale) versus our (solid) MISE, log scale.

What matched and what did not

Bottom line: the method and its qualitative claims reproduce (ADOPT is best everywhere, and its error falls as n grows). The exact numbers in Table 1 are close for Case I and for the baselines. Our ADOPT in Case II and all of Table 2 do not match. We report this as is and did not tune anything towards the paper's values.

What we found

An earlier arXiv copy (2512.08118) was withdrawn on 15 December 2025. The notice says: "Not all co-authors had agreed in advance to the public release." That is a consent issue, not a stated technical correction. The paper appears in AISTATS 2026.

We checked our code for leaked test points, a wrong noise formula, a wrong error integral, and mixed-up seeds. The evaluation grid does not contain training rows. Responses are noisy in the way the paper writes, and the score compares predictions with the noise-free truth. One bug was real: while choosing the bandwidth, the range of each input was computed on the whole sample, including the fold held out of that check. The fit never saw the held-out responses. The tables above are after that fix. The gap with the paper is still there, so the bug was not the cause.

The method itself has limits the paper's algorithm does not remove. Details and numbers are in ADOPT_AUDIT.md.

5. From ADOPT to TRACE

ADOPT needs one whole distribution per row (for example, one per group or per period), and its smoother compares every row with every other row. TRACE is our own proposed method, not published and not validated. It keeps ADOPT's key idea and changes the setting:

image/svg+xml Matplotlib v3.11.2, https://matplotlib.org/
Figure 4. Wall time against rows: an ADOPT fit (quadratic) versus one pass of the TRACE primitives (linear). Measured on a shared 8-core machine. See BENCHMARKS.md.

ADOPT stays in the package as a faithful baseline for row-level distributional data, and as a segment-level comparator for TRACE.

6. Source and citation

Song, W. and Müller, H.-G. (2026). "ADOPT: Additive Optimal Transport Regression." Proceedings of the 29th International Conference on Artificial Intelligence and Statistics, PMLR 300:1558–1566. proceedings.mlr.press/v300/song26c.html. An arXiv preprint (2512.08118) was withdrawn on 15 December 2025 with the comment "Not all co-authors had agreed in advance to the public release". The conference paper is the version we reproduced.

Other background: Petersen and Müller (2019), Fréchet regression; Lin (2019), log-Cholesky metric; McCann (1997), displacement interpolation.