qinghuan
← 回到知识探索

AI 直觉 / 可下载小项目

分布差异的平方,为什么会算出负数?

枚举 16 种抽样结果,核对 MMD² 无偏估计、含对角线的估计,以及把负数截成零产生的偏差。

展开本文目录
  1. 下载完整练习包
  2. 先把两点的接近程度写成一个数
  3. 关键是同组能不能与自己配对
  4. 用全部 16 种情况验证无偏
  5. 差异数值还不是显著性结论
  6. 运行完整实验并检查报告
  7. 记住这些要点
  8. 别把例子用过头
  9. 自己试一小步
  10. 查原始资料
开始前,会这些就够了
遇到不熟悉的词,先看这 2 个解释
无偏估计
对所有可能抽样结果按概率求平均,恰好等于想估计的总体量。
核宽度
决定多远的两点仍会得到较高相似度的尺度。
打开完整术语小抄 ↗

你比较两组向量,程序返回 −0.393469。变量明明叫 MMD 平方,负数似乎证明代码错了。真正需要先查的是:它返回总体差异本身,还是用有限样本计算的一种估计?

本例用两个只取 0 或 1 的小样本,把每一种可能抽样都列出来。这样无须依赖“多跑几次应该差不多”,就能精确验证平均意义上的无偏。

把例子带到自己的电脑上

下载完整练习包

需要 Python 3.10 或更新版本,无第三方依赖;制作时实测 Windows / Python 3.14.0。使用标准库,不需要安装第三方包。下载 ZIP 后选择“全部提取”,进入能看到 README.md 的文件夹。

下载 ZIP · 5 个文件
包里有什么
  • mmd_estimators.py:完整可运行示例
  • README.md:运行说明与独立练习
  • observed.stdout.txt:本次实际输出
  • verification.json:源码和产物验证记录
  • SHA256SUMS.txt:内容摘要

在这个文件夹打开 PowerShell,逐行运行

python --version
python -B mmd_estimators.py

解压到独立练习文件夹,在含 mmd_estimators.py 的目录打开终端运行。已运行 16 种等概率独立样本的精确枚举、核宽度配套缩放及非法输入检查;未执行统计假设检验、采集真实嵌入或评测模型质量。

找不到命令或文件时,先按 README 检查环境和所在目录。文中标出的阅读时间只估算文章篇幅,安装环境和动手练习可按自己的节奏进行。

先把两点的接近程度写成一个数

使用 k(a,b)=exp(−(a−b)²/(2σ²))。a、b 是一维观测,σ>0 控制多远的点仍被看作接近;同一点 k(a,a)=1,距离越大,结果越接近零。本文取 σ=1,因此 k(0,1)=exp(−0.5)≈0.606531。

总体 MMD²=E[k(X,X′)]+E[k(Y,Y′)]−2E[k(X,Y)]。X、X′ 是从第一分布独立抽取的点,Y、Y′ 来自第二分布,四者彼此独立。它比较组内接近程度与组间接近程度,使用合适的正定核时总体值非负。

关键是同组能不能与自己配对

令第一组有 m 个点,第二组有 n 个点。无偏版本 U 的第一项是 Σᵢ≠ⱼk(xᵢ,xⱼ)/[m(m−1)],第二项对 y 同样计算;跨组项为 −2Σᵢⱼk(xᵢ,yⱼ)/(mn)。同组排除自己,跨组保留所有配对,因为两组样本独立。

含对角线的版本 V 把第一项改为 Σᵢⱼk(xᵢ,xⱼ)/m²,第二项类似。它是两组经验核均值差的平方,适当核下非负,但有限样本一般有偏。两个版本估计目标相关,不能仅靠名字 MMD 判断返回哪一个。

取 x=y=[0,1]。U 的两项组内均值都是 k=0.606531,跨组均值是 (1+k)/2,因此 U=2k−(1+k)=k−1≈−0.393469。V 则为零。这里相同列表只是一次数值输入,不是在证明同一批随机变量可被当成两份独立样本。

用全部 16 种情况验证无偏

让两组都独立来自公平的 0/1 分布,每组抽两个点。四个位置各有两种取值,共 16 种等概率结果。真实分布相同,总体 MMD²=0。程序逐一计算再平均,E[U] 为零,而 E[V]≈0.196735。

这解释了为什么 U 可以偶尔为负:无偏约束的是所有可能样本的期望,不是要求每次结果都非负。把所有负结果截成零后,正结果保留、负结果消失,平均数就变成正值;代码直接打印这个变化。

如果下游需要可开平方的展示值,可以另定义显示规则,但原始 U、规则和检验依据都应保留。不能把 max(0,U) 继续称为同一个无偏估计。

差异数值还不是显著性结论

统计检验还需要明确零假设、抽样假设、核参数以及检验阈值。作者项目列出了重采样等阈值求法;本文不执行检验,因此不会由某个正值宣布“已检测到漂移”,也不会由负值宣布“两分布相同”。

σ 也带有输入单位。把数据整体乘以 10,同时把 σ 乘以 10,核值与统计量不变;只改变单位而忘记调整 σ,则可能改变结果。程序检查这种配套缩放及组内置换不变性。

运行完整实验并检查报告

保存为 mmd_estimators.py,运行 python -B mmd_estimators.py。预期输出来自本次实际运行;完整结果写入 report.json。

已运行 16 种等概率独立样本的精确枚举、核宽度配套缩放及非法输入检查;未执行统计假设检验、采集真实嵌入或评测模型质量。

展开收起代码 · 小实验python · 55 行

长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。

小实验 · python
"""Exact finite enumeration for biased and unbiased MMD squared."""
import itertools
import json
import math
from pathlib import Path


def mmd2(x,y,sigma=1.0,unbiased=True):
    if not math.isfinite(sigma) or sigma <= 0:
        raise ValueError('positive finite bandwidth required')
    if len(x)<(2 if unbiased else 1) or len(y)<(2 if unbiased else 1):
        raise ValueError('not enough observations')
    if not all(math.isfinite(v) for v in list(x)+list(y)):
        raise ValueError('finite observations required')
    def k(a,b): return math.exp(-0.5*((a-b)/sigma)**2)
    def within(values):
        n=len(values)
        return sum(k(a,b) for i,a in enumerate(values) for j,b in enumerate(values)
                   if not unbiased or i!=j)/(n*(n-1) if unbiased else n*n)
    return within(x)+within(y)-2*sum(k(a,b) for a in x for b in y)/(len(x)*len(y))


def main():
    x=y=[0.0,1.0]
    u=mmd2(x,y); v=mmd2(x,y,unbiased=False)
    k=math.exp(-0.5)
    assert abs(u-(k-1))<1e-12 and abs(v)<1e-12
    # Each four-bit sequence has probability 1/16: independent iid Bernoulli samples.
    outcomes=[]
    for bits in itertools.product([0.0,1.0],repeat=4):
        a,b=bits[:2],bits[2:]
        outcomes.append(dict(x=a,y=b,u=mmd2(a,b),v=mmd2(a,b,unbiased=False)))
    mean_u=sum(r['u'] for r in outcomes)/16
    mean_v=sum(r['v'] for r in outcomes)/16
    clipped_mean=sum(max(0,r['u']) for r in outcomes)/16
    assert abs(mean_u)<1e-12 and abs(mean_v-(1-k)/2)<1e-12
    assert clipped_mean > 0
    assert all(r['v'] >= -1e-12 for r in outcomes)
    assert abs(mmd2([0,2],[1,3],2)-mmd2([0,20],[10,30],20))<1e-12
    assert abs(mmd2([0,2],[1,3])-mmd2([2,0],[3,1]))<1e-12
    rejected=0
    for a,b,s in [([0],[1],1),([0,1],[0,1],0),([0,1],[0,1],float('nan'))]:
        try: mmd2(a,b,s)
        except ValueError: rejected+=1
    assert rejected==3
    report=dict(example_u=u,example_v=v,population_mmd2=0,mean_u=mean_u,mean_v=mean_v,
        clipped_mean=clipped_mean,exact_outcomes=outcomes,invalid_rejections=rejected,checks_passed=True)
    Path('report.json').write_text(json.dumps(report,indent=2)+'\n',encoding='utf-8')
    print(f'same observed lists: U={u:.6f}; V={v:.6f}')
    print(f'16 equally likely iid outcomes: E[U]={mean_u:.6f}; E[V]={mean_v:.6f}')
    print(f'E[max(0,U)]={clipped_mean:.6f}')
    print('checks=PASS; report.json written')


if __name__=='__main__': main()

怎样运行:在独立练习文件夹中执行 python -B mmd_estimators.py。2026-10-18 已在 Windows、Python 3.14.0 运行此份源码并通过自带检查。已运行 16 种等概率独立样本的精确枚举、核宽度配套缩放及非法输入检查;未执行统计假设检验、采集真实嵌入或评测模型质量。

运行后对照结果
same observed lists: U=-0.393469; V=0.000000
16 equally likely iid outcomes: E[U]=0.000000; E[V]=0.196735
E[max(0,U)]=0.098367
checks=PASS; report.json written

记住这些要点

  • 非负的是总体 MMD²;某些无偏有限样本估计可以为负。
  • 同组去对角线、跨组保留配对,是本文 U 版本的合同。
  • 把负数截零会改变估计的期望。
  • MMD 数值不自动给出检验结论或模型质量结论。

别把例子用过头

  • 对 U 直接开平方,然后把报错归咎于浮点精度。
  • 把同组去对角线也照搬到独立跨组项。
  • 忽略核宽度与输入单位的关系。

自己试一小步

每组抽三个点时,截零后的偏差会怎样变化?

  1. 枚举六个 0/1 位置的全部结果,分别平均 U、V 与截零后的 U。
  2. 把 σ 改成 0.5 和 2,观察结果。
  3. 单独保留一个负值样本,手工列出它的同组和跨组求和。

做到这些,就算完成

  • 说明为何每组至少要有两个点。
  • 报告原始估计与显示规则。
  • 不把这次枚举写成实际分布漂移检测。

查原始资料

想确认一个细节,先看这份资料对应的部分,不必从头读完所有文档。

  • A Kernel Two-Sample Test (新标签页)↗Gretton 等 / JMLR · 英文

    MMD² 的 U/V 统计估计与负值边界

    2026-10-18 已打开并阅读第 2.2 节式 (3)、(5) 及第 6 页负值说明;本文只转述相关定义,实验数字由随文程序产生。

  • MaximumMeanDiscrepancy (新标签页)↗PyTorch-Ignite · 英文

    RBF 核、排除同组对角项与批次约定

    2026-10-18 已打开并阅读定义公式、Gaussian RBF 以及 batch 说明;本文只转述相关定义,实验数字由随文程序产生。

  • A Kernel Two-Sample Test 作者项目 (新标签页)↗Arthur Gretton / UCL · 英文

    统计量与检验阈值不能混为一谈

    2026-10-18 已打开并阅读About 与计算检验阈值的四种策略;本文只转述相关定义,实验数字由随文程序产生。