Welford 在线方差 从中心矩到分块合并
先确认变量、单位与假设,再核对手算、图表和解释范围。
章节速览9 节 · 展开选择
文件可以分块读取,传感器记录也会持续到达。如果每来一条数据就重新扫描历史记录,统计量的更新时间会随数据量增长;如果只保留总和与平方和,又可能在“大基数、小波动”的数据中丢失有效数字。
本篇接续NumPy常用统计,研究一个具体任务:不保存全部历史值,持续维护数量、均值和方差,并允许合并互不重复的数据分块。 例子全部是合成记录。本文是普通等权、实数标量的累计统计,不处理权重、缺失值填补、删点或滑动窗口。
明确n、均值μ、离均差平方和M₂的含义,再证明新观测到来后怎样更新。
完整Python标准库代码保留分母口径、空状态和错误处理;测试使用独立高精度两遍参考。
先定义统计量,不急着换算法
对n条记录x₁,…,xₙ,均值μₙ=(Σxᵢ)/n。中心量M₂,ₙ=Σ(xᵢ−μₙ)²不是方差,而是离均差平方和。总体描述方差为M₂/n;在独立同分布、有限方差的抽样条件下,常用的无偏样本方差使用M₂/(n−1)。分母修正不能替代抽样条件,时间相关或选择偏差不会被“减一”自动消除。
样本估计:s² = M₂ / (n − 1),n ≥ 2
M₂、v和s²的单位是观测单位的平方。空数据的均值/方差未定义,单个观测的样本方差也未定义。口径对照文献2。
若记录是延迟毫秒,均值单位为ms,方差单位为ms²,标准差才回到ms。本文代码以None表达样本数不足,不把“尚未观察”伪装成“没有波动”。完整数据集的描述量与总体参数的估计是不同使用目的,报告时要写明采用哪个分母。
为什么总平方和相减可能不可靠
代数恒等式M₂=Σxᵢ²−(Σxᵢ)²/n在精确算术下成立。但当记录围绕很大的基数轻微波动,右边两项都很大、差却很小。浮点数先舍入再相减,可能损失小波动的信息。问题不在减法符号本身,而在使用近似的大量抵消来取得小结果。
例如x=10¹²+[1,2,3,4]。真正均值是10¹²+2.5,M₂=5。直接处理约10²⁴量级的平方项,比维护相对当前均值的增量更容易出现抵消。这个例子不是说所有输入都一定失败,也不保证每种语言、求和次序和浮点环境给出相同的错误值。
Welford的1962年论文是这一在线更新方法的原始出处之一,出版信息见文献1;下面给出独立的代数推导,而不是只把公式当作一个名称来记忆。
从均值变化推导中心矩更新
假设已有n≥1个观测,状态为(n,μ,M₂),新值为x。令δ=x−μ,新的均值μ′=μ+δ/(n+1)。对旧观测,把相对旧均值的偏差重新写成相对新均值的偏差:xᵢ−μ′=(xᵢ−μ)−δ/(n+1)。
展开旧记录平方项之和时,交叉项包含Σ(xᵢ−μ)=0,因此只增加n·δ²/(n+1)²。新记录自身相对新均值的偏差是nδ/(n+1),平方后再相加,新增总量为nδ²/(n+1)。于是得到同一更新的两种写法:
μ′ = μ + δ / n′
M₂′ = M₂ + δ(x − μ′) = M₂ + δ²·n / n′
精确算术下成立。实现使用δ(x−μ′),明确第二项必须用新均值。n=0时直接得到(1,x,0),不需要先定义空集合的真实均值。
flowchart LR A[旧状态 n μ M2] --> B[新值减旧均值得到δ] B --> C[更新数量与均值] C --> D[用新均值计算增量] D --> E[全部检查通过后提交状态]
四条记录的逐步手算
取[2,4,4,6],从空状态开始。第三行使用分数更方便核对,代码只打印最终数值。
| 已纳入的值 | n | μ | M₂ | 总体方差M₂/n |
|---|---|---|---|---|
| 2 | 1 | 2 | 0 | 0 |
| 2,4 | 2 | 3 | 2 | 1 |
| 2,4,4 | 3 | 10/3 | 8/3 | 8/9 |
| 2,4,4,6 | 4 | 4 | 8 | 2 |
最后一次,δ=6−10/3=8/3,新均值为4,增量δ(6−4)=16/3;8/3+16/3=8。直接围绕最终均值4计算,也得到(2−4)²+0+0+(6−4)²=8。样本方差是8/3,不能把表中总体方差2换个名称就报告成样本方差。
自检1:为什么常量数据的M₂保持零?
第一条数据建立均值后,每个新值与当前均值相同,δ=0,因此中心矩不再增加。样本数达到2以前,代码仍不报告样本方差;“恰好没有波动”和“样本数不足”需要区分。
两个分块怎样合并
设A、B是互不重复的观测分块,分别保存(n_A,μ_A,M₂,A)和(n_B,μ_B,M₂,B)。新均值是数量加权均值;两个块内中心矩之外,还要补上各块均值偏离总均值的贡献。展开平方项,块内偏差和为零,得到:
μ = μ_A + Δ·n_B/n
M₂ = M₂,A + M₂,B + Δ²·n_A·n_B/n
任一块为空时直接采用另一块。分块不能重复计入。推广到并行统计的背景见文献3,本式也可由块内中心化展开直接证明。
将A=[2,4]、B=[4,6]分别统计,得到(2,3,2)与(2,5,2)。Δ=2,跨块项为4,合并后(4,4,8)。只把两个M₂相加会得到4,漏掉块均值之间的差异;平均两块方差也不能一般地得到总体方差。
自检2:同一块被合并两次,为什么不是无害的重试?
合并操作没有去重语义。重复合并会重复计数,通常也改变均值与方差。分布式任务需要额外的分块标识和提交记录;不能靠三个统计量判断一批记录是否已经处理过。
代码1:可直接运行的标准库实现
代码1|Python 3 标准库:保存为welford_demo.py运行。只接受有限int/float标量,排除bool;不把字符串、缺失值或无穷大偷偷转换成有效记录。输入先转换为float,因此大整数精度可能在算法开始前就损失。代码中的None表示未定义量。
1 | import math |
预期输出第一行为4 4.0 8.0,第二行为2.0 2.6666666666666665。每次更新或合并是常数次浮点运算,累计n条记录为O(n)时间,额外状态O(1);这里不把任意大整数运算的位复杂度算作真正常数。类属性用于查看,不支持手动改写状态后再继续合并。
状态先在局部变量中计算,检查失败时不会提交半个更新。代码检测非有限状态并拒绝,而不是宣称对任意有限输入都不会溢出。生产代码还应定义错误的记录策略与诊断信息,避免上层捕获异常后无声遗漏数据。
并行计算时,各工作单元可以各自维护独立分块,随后由调用方串行合并。这个类没有锁,不保证多个线程同时写入同一实例的原子性;公式可并行组合,不等于这段可变状态代码已经实现了并发保护。
稳定不等于精确,也不等于完整统计分析
flowchart TD
A[输入数据] --> B{转换后有限吗}
B -->|否| X[拒绝并记录原因]
B -->|是| C[更新或合并局部候选]
C --> D{候选状态有效吗}
D -->|否| X
D -->|是| E[提交并报告数量与分母口径]
浮点加法不满足一般的结合律,分块方式、合并树和记录顺序都可能改变末尾位。因此不能要求并行合并与逐条更新逐位相同。Welford减少一种常见的抵消问题,却没有消除舍入、输入量化和动态范围限制;也不应通过无条件把负方差改成零来隐藏实现错误。
尤其是float(10**16 + 1)可能与float(10**16)相同。两个不同整数已经映射成同一个浮点输入,再好的增量算法也无法恢复丢失的信息。要保留十进制计量值或极大整数的精确关系,应先决定数值表示,再决定更新方法。
自检3:合并结果最后几位不同,能否立即判定代码错误?
不能仅凭末尾位不同判断。先用相同的浮点输入、高精度参考及合理绝对/相对容差核对,再检查分母和分块是否重复。相反,明显漏掉跨块项或数量错误,也不能用“浮点误差”掩盖。
自检4:累计统计能直接表示最近一小时吗?
不能。本文状态累计所有纳入的记录,没有时间窗口或删点操作。最近一小时需要明确过期规则并保存足以撤销旧数据的信息,或使用分桶与重新合并;不能把累计均值改个标签当作窗口均值。
验证时应该比较什么
先核对[2,4,4,6]、空数据、单值和常量;再用高精度两遍计算,独立求均值与离均差平方和,比较随机数据和大基数小波动。合并验证要覆盖空块、不同分块、不同合并次序和重复输入语义,不能只比较两份复用了同一更新式的程序。
需要报告的不只是方差数值,还应包括样本数、单位、分母、输入转换规则和数据范围。缺失与异常值的处理应在统计之前定义;标准差也不是均值置信区间,若要研究抽样不确定性,可衔接配对Bootstrap的采样单位讨论。
参考文献与适用位置
- B. P. Welford. Note on a Method for Calculating Corrected Sums of Squares and Products. Technometrics, 4(3), 419–420, 1962. 出版社原始条目与DOI。用于方法来源和出版信息;本篇推导自行展开,不声称取得受限全文。返回公式2。
- Python官方文档:statistics。 pvariance与variance说明总体/样本分母与数据数量条件;本文返回None的接口约定与标准库抛异常的约定不同。返回公式1。
- Philippe Pébay. Formulas for Robust, One-Pass Parallel Computation of Covariances and Arbitrary-Order Statistical Moments. SAND2008-6212, 2008. Sandia官方报告条目。用于分块与并行统计背景;本篇仅实现标量二阶中心矩,不实现全部高阶公式。返回公式3。