
1. 先把问题摆出来KL散度为什么不够用很多人第一次对 Wasserstein 距离产生兴趣都不是因为读了最优传输的教材而是因为在某个训练日志里被逼到了墙角。我自己的触发点是一次生成模型的调参判别器的 loss 稳定在 0.69 附近纹丝不动生成器输出几千步几乎一模一样偶尔跳一下又落回同一个模式。当时把判别器的输出从概率改成实数值打分loss 立刻活了训练曲线开始有起伏。那一次我才认真去搞清楚KL散度和JS散度到底在哪一步失效了。这篇文章我打算按自己踩坑的顺序来讲不按教材顺序。会先讲清楚KL散度、JS散度在分布支撑集不重叠时为什么会失去区分度再一步步把 Wasserstein 距离的几何直觉、数学骨架、可以落地的代码实现以及它在生成模型、分布漂移检测、点云匹配里的真实表现串起来。适合正在做生成模型、做数据质量监控、做检索和匹配的同行也适合刚开始接触最优传输、被那堆测度论符号劝退过的朋友。核心结论先放在这里Wasserstein 距离的本质是把从一个分布搬到另一个分布的最小代价作为距离而这个代价来自底层的几何空间所以它天生带有远近这个尺度概念。1.1 一次判别器完全不动的训练记录那次实验的数据很简单真实分布是一维的两个峰生成分布初始化在远离这两个峰的位置。判别器用标准的二分类交叉熵输出经过 Sigmoid。训练前两百步判别器几乎立刻学会把所有生成样本判成 0、真实样本判成 1准确率接近 100%。问题就从这里开始判别器一旦完美生成器拿到的梯度就趋近于零因为 JS 散度在两个分布几乎不重叠时是个常数梯度自然也是零。我当时打印了每层的梯度范数来确认这一点生成器第一层的梯度范数长期在 1e-6 量级徘徊而判别器那边梯度范数是正常量级。这不是学习率的问题也不是优化器的问题是损失函数本身在这个区域里没有提供方向信息。这个现象在文献里被反复讨论过但只有自己撞一次才会真正理解为什么大家要费那么大劲去找一个处处有梯度的分布距离。注意如果你在训练中看到判别器准确率迅速饱和到接近 1同时生成器梯度范数骤降到 1e-5 以下基本可以判定是分布距离的梯度消失问题而不是网络容量或者数据问题。先别急着调学习率。1.2 JS散度的测不到远处到底是怎么回事要理解这件事先看 KL 散度的写法。对离散分布KL(P‖Q) Σ P(x) log(P(x)/Q(x))。这个式子有两个硬伤。第一它不对称KL(P‖Q) 和 KL(Q‖P) 完全不同前者在 Q 取到 0 而 P 不为 0 的地方直接发散到无穷。第二它只对两个分布支撑集重合的部分敏感一旦 P 和 Q 的支撑集完全不重叠KL 要么是无穷要么是个和相距多远无关的常数。JS 散度做了一次对称化JS(P,Q) ½KL(P‖M) ½KL(Q‖M)其中 M (PQ)/2。它确实解决了不对称和无穷值的问题取值范围落在 [0, log2] 之间。但它依然没有解决核心矛盾如果两个分布支撑集不重叠JS 散度恒等于 log2无论它们是相距一米还是相距一公里。举个数轴的例子最直观。真实分布是坐标 0 处的狄拉克分布 δ₀生成分布是坐标 θ 处的 δ_θ。用 JS 散度算只要 θ ≠ 0结果就是 log2和 θ 是 0.1 还是 100 完全无关。而这两个分布显然有远近之分。JS 散度看不见这个远近因为它的定义里只出现了概率密度比值没有出现坐标本身。这就是它丢掉几何信息的根源。1.3 Wasserstein距离要同时解决的三件事把上面这些问题整理一下一个更好用的分布距离希望满足三个条件。第一要能在支撑集不重叠时依然给出有意义的数值最好这个数值还能反映两个分布隔了多远。第二要对参数连续可微最好梯度不会在某个区域突然消失这样才能拿去做梯度下降。第三要对称或者至少有明确的非对称解释方便当作一个真正的距离来用。Wasserstein 距离这三条都满足。它把分布看成可以搬运的质量两个分布之间的差异就是搬完所有质量需要付出的最小代价。狄拉克分布那个例子里Wasserstein 距离正好等于 θθ 越小距离越小而且是连续线性的处处可导。这就解释了为什么换了损失函数之后训练曲线立刻从死水变成了活水。不过这里要提前埋一个伏笔也是后面第 6 节要重点讲的Wasserstein 距离在高维空间会遭遇维度诅咒它的经验估计收敛速度是 n 的 -1/d 次方维度一高就慢得离谱。所以它并不是万能药只是在你理解了它的代价之后可以在合适的场景里有针对性地使用。2. 用搬土的画面理解Wasserstein距离数学定义看多了容易晕我习惯先用一个物理画面把它定住再回头看公式。这个画面就是推土机或者搬土英文里 Earth Movers Distance 这个名字就是从这儿来的。2.1 土堆、坑和搬运工想象一块平地上有一堆土形状不规则我把它记作分布 μ。旁边有一块洼地形状也不规则我记作分布 ν。现在要求你把土堆的土全部搬到洼地里去最后洼地被填平的形状必须和 ν 一模一样。你有一台推土机搬运是有成本的成本正比于搬了多少土乘以搬了多远。问最小的总成本是多少这个最小成本就是 μ 和 ν 之间的 Wasserstein 距离。这个画面立刻带来了几个 KL 散度没有的性质。如果土堆和洼地形状完全一样只是整体平移了一段距离那么最优策略就是整体平移成本等于质量和乘以平移距离距离随平移量线性增长这很符合直觉。如果土堆和洼地形状完全不同你也没法偷懒必须老老实实算出最优的搬运方案。更重要的是谁都不需要一个分布重叠的前提疏土和填坑永远是两件明明白白的事。2.2 搬运方案不是唯一的代价矩阵与运输计划实际搬运时方案通常不唯一。假设离土堆最近的那块坑只需要 3 单位土但土堆在靠近位置的存量有 5 单位那多出来的 2 单位就得往更远处运。每种从哪里搬多少到哪里的分配方式对应一个运输方案每个方案有一个总成本。把这个离散化就得到了标准形式。设 μ 是 m 个点上的质量分布记作质量向量 a ∈ R^mν 是 n 个点上的质量分布记作 b ∈ R^n两点之间的距离平方构成代价矩阵 C其中 C_ij ‖x_i − y_j‖²。一个运输方案就是一个非负矩阵 P ∈ R^{m×n}满足行和等于 a、列和等于 b。所有这样的 P 组成的集合记作 Π(a, b)叫做耦合集合或者传输多面体。总代价就是 Σ_ij P_ij C_ij也就是矩阵 P 和 C 的元素级乘积之和写紧凑一点就是 ⟨P, C⟩。Wasserstein 距离要做的就是在所有合法方案 P 里挑出总代价最小的那个。提示这里有个很容易混淆的点。行和列和的约束不是尽量满足而是必须严格满足也就是说所有的土都必须搬完所有的坑都必须被填满。这个硬约束是整个定义严格性的来源也是后面转成线性规划时那些等式约束的出处。2.3 最优传输的定义和Wasserstein距离的正式写法把上面的画面写成公式就是最优传输的核心表达。p 阶 Wasserstein 距离的定义是W_p(μ, ν) ( inf_{γ ∈ Π(μ,ν)} ∫ ‖x − y‖^p dγ(x, y) )^{1/p}这里 γ 是 μ 和 ν 的一个联合分布它的两个边缘分布分别是 μ 和 ν这就是耦合的含义。Π(μ,ν) 是所有这样的 γ 构成的集合。括号里那部分是 p 阶矩形式的最优运输成本开 p 次方之后就得到了一个真正满足三角不等式的距离。当你看到那个 inf可以把它翻译成在所有合法搬法中取最便宜的那个。当你看到 ∫ ‖x−y‖^p dγ可以翻译成某一份土从 x 搬到 y走了距离 ‖x−y‖代价是它的 p 次方再加权求和。p 取 1 和取 2 是最常见的两种选择。p1 时叫做 W1也叫 Kantorovich-Rubinstein 距离它的对偶形式非常漂亮直接对应一个函数类上的上确界。p2 时叫做 W2它在高斯分布下有闭式解而且在几何和流形相关的任务里用得特别多因为它和最优传输映射的连续性、以及 Wasserstein 梯度流这些理论联系紧密。选择哪一个取决于你关心的是总运输量还是能量意义上的平方代价。我自己的经验是做分布对比和漂移检测时 W1 更稳做图像生成和几何相关任务时 W2 的梯度性质更好。2.4 一维的例子排序之后直接相减一维情况是理解 Wasserstein 距离的最佳入口因为这里有一个极其漂亮的结论一维的 W1 距离等于两个累积分布函数之差的绝对值积分。写成公式就是 W1(μ,ν) ∫ |F_μ(x) − F_ν(x)| dx。这个结论背后有一个很朴素的事实——在一维数轴上最优运输方案不需要复杂的匹配只要把两个分布的质量从左边往右边一一对应地对齐就可以了这就是所谓的单调重排。具体到经验分布假设 μ 是 m 个点 x₁...x_mν 是 n 个点 y₁...y_n且 m n那么一维 W1 距离就是把两组点各自从小到大排序得到 x_(1) ≤ ... ≤ x_(n) 和 y_(1) ≤ ... ≤ y_(n)然后计算 (1/n) Σ|x_(i) − y_(i)|。就用第 1 节那个狄拉克例子验一遍。δ₀ 和 δ_θ 各有一个点排序之后就是 0 和 θ代入公式得到 W1 |0 − θ| θ。距离随 θ 线性变化处处有梯度这正是我们想要的。而 JS 散度在这个例子里恒为 log2。两者的差别一目了然。3. 拆开公式看骨架耦合、下确界与三种常见形式上一节讲了画面这一节把公式的每个零件拆开说清为什么非这么设计不可。这一部分可能是全文最数学的地方但我尽量保持讲人话。3.1 耦合coupling到底在表达什么耦合 γ 是定义在乘积空间上的一个联合分布它的两个边缘分别等于 μ 和 ν。这句话听起来抽象换个说法γ 就是一份运输计划书里面写明了从 μ 的哪个区域搬了多少比例的质量到 ν 的哪个区域。边缘约束保证这份计划书是自洽的——搬出去的量正好等于 μ 的全部搬进来的量正好等于 ν 的全部没有凭空产生也没有凭空消失。理解了这一点后面很多性质就好解释了。为什么 Wasserstein 距离能对支撑集不重叠的分布给出非平凡的值因为耦合 γ 永远存在哪怕 μ 和 ν 的支撑集离得再远你都可以写出一份把所有质量从 μ 的支撑搬到 ν 的支撑的计划书代价就是这一路上的距离。KL 散度在这种情形下无事可做只能给出无穷或常数而 Wasserstein 距离能老老实实算出这一路有多远。耦合集合 Π(μ,ν) 还有一个几何上的好处它是一个凸多面体离散情形下这意味着对 γ 的优化问题是一个凸优化问题没有局部最优的困扰。这个凸性是后面能写线性规划、能用对偶理论的根本保证。3.2 为什么要取下确界把自由度交出去定义里的 inf 是在所有耦合 γ 上取的。为什么不固定一个耦合直接算因为不同的耦合对应完全不同的运输成本如果随便选一个得到的数值就没有可比性。取下确界的意思是在所有合法的搬法里取那个最省钱的方案的成本这才是最优运输中最优二字的实际含义。这里有个容易误解的地方。下确界是越小越好你可能觉得那岂不是可以取到 0不会因为边缘约束卡死了自由度。你没法让所有质量原地不动除非 μ 和 ν 本身在同一个位置有相同的质量。约束越紧可行集合越小下确界才有意义。另外提一句这个下确界在温和条件下是可达的也就是存在最优的耦合 γ* 使得成本正好等于最小值。在离散有限的情况下这基本总是成立因为可行集是紧的。所以你可以放心地用最小而不是下确界来理解虽然严谨的写法是 inf。3.3 W1、W2和高斯分布下的闭式解常见的形式有三种我用一个表列一下关键差别方便你在选型时快速对比。形式定义要点优点局限W1代价取距离的一次方p1对偶形式简单数值稳定对异常值不敏感梯度尺度较小W2代价取距离平方p2与几何、梯度流理论契合高斯分布有闭式解对异常值敏感W∞代价取距离的无穷范数最坏情况下最优数值实现复杂W2 在高斯分布下有一个非常实用的闭式解。设两个高斯分布为 N(m₁, Σ₁) 和 N(m₂, Σ₂)它们的 W2 距离满足W2²(N(m₁,Σ₁), N(m₂,Σ₂)) ‖m₁ − m₂‖² tr( Σ₁ Σ₂ − 2( Σ₁^{1/2} Σ₂ Σ₁^{1/2} )^{1/2} )这个公式帮我把困惑解开过一次。它清楚地显示W2 距离把均值之间的差别和协方差之间的差别分开来算了前一项是均值的欧氏距离平方后一项刻画形状上的差异。一维的特殊情况更简单N(m₁,σ₁²) 和 N(m₂,σ₂²) 之间的 W2² (m₁−m₂)² (σ₁−σ₂)²。这个闭式解在实际工作中很有用。做数据漂移监控时如果特征大致服从高斯或者你可以先做一次正态化变换直接套这个公式就能在毫秒级算出分布距离不用去解最优传输问题。我在一个监控系统里就这么干过每个特征维度单独算 W2 再聚合成一个总分成本几乎可以忽略。提示用高斯闭式解的前提是你能接受正态假设。如果分布明显是多峰的这个公式会严重低估差异因为多峰结构的信息全在均值方差里丢掉了。这种情况还是老实用经验估计的方法。3.4 对偶形式从搬土变成找一把尺子W1 的对偶形式是它的招牌性质。Kantorovich-Rubinstein 定理说当代价函数是度量 d 时W1(μ,ν) sup { ∫ f dμ − ∫ f dν : f 是 1-Lipschitz 函数 }含义是在所有斜率的绝对值不超过 1 的函数里找一个让 μ 下的期望和 ν 下的期望差得最大的梯度打分器。这个函数 f 可以理解成一把尺子它在这两个分布上的平均读数之差最大能有多大那个差值就是 W1 距离。这个对偶形式直接被用在了 WGAN 里判别器扮演的就是这把 1-Lipschitz 的尺子它不再需要输出概率只需要输出实数值打分。为什么会 1-Lipschitz 这个约束直觉上如果允许函数无限陡那只要两个分布略微不同就能把期望差拉到无穷大这个上确界就没意义了。限制斜率的绝对值不超过 1相当于把尺子的刻度单位固定下来测出来的长度才有统一的物理含义。4. 手撸一遍从一维闭式解到Sinkhorn近似纸上推导容易飘我把几个从简单到复杂的实现都写了一遍代码不长但每一步的性能和数值表现差别很大值得拿出来对比。4.1 一维W1三行代码和它的CDF版本一维经验分布的 W1 最简单的实现就是先排序再求平均绝对差。import numpy as np def w1_1d_sorted(x, y): x np.sort(np.asarray(x, dtypefloat)) y np.sort(np.asarray(y, dtypefloat)) n len(x) m len(y) if n ! m: raise ValueError(等量样本才可以直接配对) return np.mean(np.abs(x - y))这个实现的前提是两个样本集合大小相同。样本量不同的时候需要做分位数插值实际上是在比较两个累积分布函数。更稳健的写法是直接对 CDF 做数值积分def w1_1d_cdf(x, y, grid2000): x np.sort(np.asarray(x, dtypefloat)) y np.sort(np.asarray(y, dtypefloat)) lo min(x[0], y[0]) hi max(x[-1], y[-1]) t np.linspace(lo, hi, grid) # 用搜索排序的方式估计 CDF Fx np.searchsorted(x, t, sideright) / len(x) Fy np.searchsorted(y, t, sideright) / len(y) return np.trapezoid(np.abs(Fx - Fy), t) if hasattr(np, trapezoid) else np.trapz(np.abs(Fx - Fy), t)两段代码的输出在小样本下会有细微差别因为 CDF 版本做了网格离散化。我做过的对比是两个从不同正态分布采样的 10000 点样本排序版本给出的值和 CDF 版本在网格 2000 时误差在千分之一量级完全够用。实操心得一维场景千万别上线性规划求解器。我早期图省事直接用 scipy 的 linprog 跑一维 5000 点的问题一个来回花了将近 40 秒换成排序版本之后同规模只要 2 毫秒。差了四个数量级而结果是同一个数。4.2 线性规划版本能算但别指望它快一般维度下的 Wasserstein 距离是一个线性规划问题目标函数是 ⟨P, C⟩约束是 P 的行和等于 a、列和等于 b、P 非负。理论上可以直接交给求解器。import numpy as np from scipy.optimize import linprog def w1_lp(x, y): n, m len(x), len(y) C np.abs(x[:, None] - y[None, :]).reshape(-1) # 变量顺序为 P 按行展开共 n*m 个 A_eq [] b_eq [] for i in range(n): row np.zeros((n, m)) row[i, :] 1 A_eq.append(row.reshape(-1)) b_eq.append(1.0 / n) for j in range(m): row np.zeros((n, m)) row[:, j] 1 A_eq.append(row.reshape(-1)) b_eq.append(1.0 / m) res linprog(C, A_eqnp.array(A_eq), b_eqnp.array(b_eq), bounds(0, None), methodhighs) return res.fun这段代码只为讲清楚结构实际用起来会很慢而且变量数随点数平方增长。点数到几百的时候内存已经开始吃紧。它能算只是不适合放进生产流程。它的价值在于验证其他近似实现的正确性——我拿它当作标准答案去对照 Sinkhorn 的输出。4.3 Sinkhorn用熵正则把速度换回来真正能落地的方案是熵正则化也就是 Sinkhorn 算法。它在目标函数里加一项 ε·Σ P_ij log P_ij把原问题变成严格凸的问题解具有特殊结构。import numpy as np def sinkhorn_w1(x, y, eps0.05, iters300): x np.asarray(x, dtypefloat) y np.asarray(y, dtypefloat) C np.abs(x[:, None] - y[None, :]) K np.exp(-C / eps) a np.full(len(x), 1.0 / len(x)) b np.full(len(y), 1.0 / len(y)) v np.ones(len(y)) for _ in range(iters): u a / (K v) v b / (K.T u) P u[:, None] * K * v[None, :] return np.sum(P * C), P核心就是那两行交替的除法用当前的行缩放向量去修正列缩放向量再用新的列缩放向量修正行缩放向量反复迭代让行和列和逐渐贴近目标边缘。收敛速度是线性的ε 越小收敛越慢但越接近真实距离。关于参数选择的经验ε 的取值和数据的尺度强相关。如果数据坐标在 [0,1] 范围ε 取 0.01 到 0.1 比较合适如果坐标是几百的量级ε 也要相应放大否则 exp(−C/ε) 会全部下溢成 0导致除零。我踩过这个坑矩阵 K 全是 0代码没报错但结果全是 nan。稳妥的做法是先对坐标做归一化让 C 的数量级可控。下面这个表是我在一台普通笔记本上实测的耗时对比两组 500 点的二维数据比较线性规划、Sinkhorn、以及一维排序法。方法500 点耗时相对误差适用维度一维排序法0.4 ms0一维精确仅一维线性规划 highs约 12 s0精确解低维小规模Sinkhorn ε0.05300 轮约 35 ms约 2%中低维中等规模高斯闭式解0.1 ms依赖正态假设任意维度数据说明一件事如果你的数据是一维或者接近一维闭式解是最优选择如果维度中等、点数上千Sinkhorn 是唯一现实的选择如果维度很高就要考虑降维或者换用别的度量。4.4 对偶实现与1-Lipschitz约束的落地如果你要走对偶路线也就是训练一个 1-Lipschitz 的函数 f 去逼近 W1难点在于怎么约束 Lipschitz 常数。三种常见做法各有取舍。权重裁剪最简单把网络每层权重裁剪到 [−c, c]但 c 的选择很敏感太小会让 f 退化成近似线性太大则约束形同虚设。梯度惩罚在真实样本和生成样本之间的连线上采样惩罚梯度范数偏离 1 的程度效果稳定但每次迭代多一次反向传播。谱归一化直接对每层的权重矩阵按最大奇异值做归一化从结构上保证 Lipschitz 常数上界代价是每步要估一次最大奇异值。我在小规模实验里对比过这三种。权重裁剪最容易出问题c0.01 时训练还算稳一旦调到 0.1 就频繁震荡。梯度惩罚最稳但对批次大小敏感批次太小惩罚项噪声大。谱归一化的实现最省心配上一个合适的幂迭代次数就基本不用管。如果要从头搭我会推荐先上谱归一化。注意对偶形式下判别器输出的是实数打分训练目标是最小化假样本打分均值减去真样本打分均值符号方向容易搞反。搞反了不会报错只是训练越来越差浪费好几个小时才发现。第一次写的时候建议用一维数据验证一下方向。5. 它在真实场景里长什么样理论讲完了说说这东西在实际项目里到底怎么用以及我见过的几种典型玩法。5.1 WGAN判别器不再是分类器而是打分尺WGAN 的全部改动就两处判别器改名叫做 critique输出改成实数损失函数换成对偶形式的 W1 估计。这样一来判别器不再需要把真实样本和生成样本推到 Sigmoid 的两端它只需要给真实样本打高分、给生成样本打低分分差最大化的那个值就是 W1 距离的估计。这个改动带来的最直接好处是训练曲线变得有物理含义。普通 GAN 的判别器 loss 只能看趋势绝对值没什么意义WGAN 的 critique 输出均值之差可以直接读作分布距离的近似数值下降就是分布真的在靠近。我做过一个小实验一维双峰分布配上生成器把 critique 打分差和用排序法算出来的真实 W1 画在同一张图上两条曲线的相关系数超过了 0.95。这个对应关系让人心里踏实很多。另一个好处是模式崩溃缓解。因为 W1 在分布不重叠时依然有梯度生成器能持续收到往哪个方向走的信号而不是卡在一个模式下出不来。这个改善不是绝对的但在我自己的几组对比实验里确实明显。5.2 分布漂移与数据质量监控这是我目前用得最多的场景。线上模型的特征分布会随着时间慢慢变化如果变化幅度大到影响预测质量就需要触发告警或者重训。用 KL 或者 PSI 做监控有个老问题某些特征取值集中分箱之后两个分布的箱重叠很少指标直接爆炸或者恒为常数看不出程度。用 W1 就没有这个困扰。它天然是距离量纲而且对异常值不敏感。我的做法是对每个数值特征单独算 W1然后按特征重要度加权求和得到一个整体漂移分数。阈值的设定也直观如果特征的量纲是归一化后的 [0,1]W1 超过 0.1 就说明平均搬动距离超过量程的十分之一值得看一眼。分类特征的处理稍微绕一点。一种方式是先做目标编码或者频率编码转成数值再算 W1另一种是直接用类别分布的 W1代价矩阵取 0-1 距离这时 W1 恰好等于两个类别分布的总变差距离的一半。我更推荐后一种因为逻辑清晰结果也好解释。5.3 图像检索与颜色迁移在图像领域Wasserstein 距离被用来比较颜色直方图和特征分布。颜色直方图本身是三维的RGB 或 Lab用 KL 比较时两个颜色出现频率为零的箱子会引发无穷大用 W1 则可以在颜色空间里定义搬运代价把深红到浅红的距离算得合理。我做过一个简单的颜色迁移实验给定一张风格图和一张内容图分别提取它们在 Lab 空间的颜色分布用 Sinkhorn 求最优传输方案然后按照这个方案把内容图的颜色重新分配。结果比直接匹配均值方差的方法自然得多尤其是当风格图有明显色偏的时候直接匹配统计量容易把画面压成灰的而最优传输能保留原本的颜色层次。实操心得做颜色迁移时记得在感知均匀的颜色空间里算距离Lab 或者 OKLab 都行。如果直接在 RGB 里算欧氏距离绿色通道的感知权重会被严重高估出来的结果偏色。这个坑我在第一次做的时候就踩过。5.4 点云与形状匹配点云匹配是 W2 距离的传统战场。两个点云之间的 W2 距离刻画的是把一个点云搬到另一个的最小平方代价它天然考虑了空间位置比 Chamfer 距离更抗噪因为 Chamfer 只关心最近邻距离而 W2 是全局最优匹配。实际的点云规模经常上万直接解最优传输不现实。常用的做法是先用 Sinkhorn 求一个近似的传输方案再从这个方案里读出一个软对应关系用它来指导配准。近几年的很多配准工作都在这个框架里做改进核心思路是用熵正则把不可解的问题变成可解的问题再用可微的迭代把梯度传回网络。需要注意的是点云匹配对尺度很敏感。如果两个点云的整体尺度差一个量级W2 距离会被大尺度的坐标主导小尺度的结构细节完全被淹没。常规操作是先做中心化和尺度归一化让两个点云的总方差可比再计算距离。6. 踩过的坑和排查清单这一节是我自己攒下来的一些经验和排查思路都是文档里不太会写的部分。6.1 维度诅咒为什么参数一多就崩Wasserstein 距离最反直觉的性质是它的估计误差随维度恶化得极快。经验分布和真实分布之间的 W1 距离期望估计误差的量级是 n 的 -1/d 次方d 是维度n 是样本量。这意味着在 100 维空间里你要把误差减半样本量得增加 2 的 100 次方倍。这个数字大到没边。这不是算法实现的问题是问题本身的性质所谓维度诅咒。所以当你看到有人在高维上用经验估计直接算 Wasserstein 距离做分布对比结果很不稳定不要怀疑代码要怀疑维度。可行的缓解方式有三条先降维再做距离计算改用切片 Wasserstein 距离把高维分布投影到多条随机一维线上一维的估计误差是 n 的 -1/2 次方好得多或者用高斯闭式解这类参数化假设绕开经验估计。切片 Wasserstein 距离我自己用得挺多做法是随机采若干方向向量把数据投影上去对每条一维投影算 W1 再取平均。方向数取 64 到 256 通常就收敛了。它的理论性质在近几年被研究得很透实际效果也稳定做高维分布对比时值得优先考虑。6.2 尺度、单位与归一化W1 和 W2 的量纲完全由坐标决定。如果你的特征有的以米为单位有的以千克为单位有的以年龄为单位直接算出来的距离会完全被量纲大的特征主导这显然不对。标准做法是先做标准化让每个特征零均值单位方差这样算出来的距离是一个无量纲的数值。但要注意标准化之后 W1 的可解释性会下降因为搬运距离不再对应任何物理量。我一般的处理方式是监控场景用标准化后的数值做阈值判断但要给每个特征单独算 W1如果哪个特征超阈值再把原始量纲下的数值打印出来人工确认。另一个细节是样本量必须对齐。算经验 W1 时如果两组样本量不同排序法要改成基于分位数的版本或者用 Sinkhorn 这类支持非等量边缘的方法。直接截断样本量会引入偏差不推荐。6.3 什么时候它反而不好用说了这么多好处也得说说它的短板不然就成了推销。Wasserstein 距离的计算复杂度比简单统计量高出一个量级。如果你只是想要一个粗略的分布差异指标PSI 或者 KS 统计量已经够用没必要上 W 距离。它适合的是那些几何结构确实重要、而且 KL 类指标确实失效的场景。判断标准很简单如果两个分布不重叠的时候你还关心它们隔了多远那就用 W 距离如果不关心用别的更省事。还有个隐性成本是对超参数和数值稳定性的要求。Sinkhorn 的 ε、迭代轮数、对偶形式的 Lipschitz 约束方式都需要调。相比之下KL 散度除了分箱方式之外几乎没有超参数。选它之前要评估一下这部分额外工作量值不值。6.4 排查速查表下面这张表是我在实际使用中整理出来的常见现象和对应排查方向遇到问题先对号入座。现象可能原因处理方式Sinkhorn 输出全是 nanε 相对数据尺度太小K 矩阵下溢先归一化数据或放大 ε对偶形式训练震荡权重裁剪范围不当换梯度惩罚或谱归一化高维下距离估计方差极大维度诅咒降维或改用切片 WassersteinW1 数值很大但不代表真实差异特征量纲不统一逐特征标准化后单独计算一维和二维结果对不上二维用了 Sinkhorn 近似减小 ε 或增加迭代轮数分布整体平移但距离不线性增长用了错误的形式或 p 值检查是否用了 W2平移下 W2 是线性的迭代很快但不收敛边缘约束没对齐检查 a、b 是否严格和为 1提示调试数值问题时先在一个你能手算的极小例子上跑一遍。比如三维空间里两个点距离是确定的拿它验证你的 Sinkhorn 实现和线性规划实现能不能对上。这一步花十分钟能省掉后面半天的排查。我个人在实际操作中的体会是Wasserstein 距离最值得投入的地方不是它的公式而是它给你换了一套思考方式。以前我看到两个分布不一样就想着去做假设检验现在我更倾向于先问一句它们之间的质量需要搬多远。这个提问角度的转变让我在处理分布不重叠、类别很多、结构信息重要的那些任务时少走了不少弯路。真正要动手的时候先从一维排序法写起跑通之后再往 Sinkhorn 和对偶形式上走每一步都有对照物不容易迷路。