bobofamilyNIU CHUN
15/36

数学补课 · 方差可加,标准差不可加:√n 是从哪来的

2026-09-0812 分钟—阅读

上一篇拿 σ_z = √n · σ_W · σ_x 定了 W1 该乘多少,但公式本身是借来就用的。 这一篇把它从期望的定义推出来,顺带回答一个更值钱的问题: 为什么诊断神经网络盯的是方差,而不是均值或最大值?


期望:一切的起点

期望就是平均值,记作 E[X] 或 μ。

X = [2, 4, 4, 4, 5, 5, 7, 9]
E[X] = (2+4+4+4+5+5+7+9) / 8 = 5

E[X²] 是「先每个数平方,再取平均」—— 顺序不能调,E[X²] ≠ (E[X])²。 这两个量的差正好是方差,下面就会看到。

A、B 是什么

下面公式里的 A、B、X 都是随机变量 —— 值不确定、但知道它服从什么分布的量。 它们是占位符,就像代数里的 x、y。在这个场景里它们具体代的是:

zⱼ = x₁w₁ + x₂w₂ + … + x₃₀w₃₀

符号代什么一次采样的值
X单个权重 wᵢ,或单个输入 xᵢ比如 −0.722
A和式里的第 1 项 x₁w₁比如 −0.330
B第 2 项 x₂w₂比如 −0.230

于是三条规则各自在回答一个具体问题:

规则回答什么
Var(cX)把权重乘 0.2,方差变多少?
Var(AB)单项 xᵢwᵢ 的方差是多少?
Var(A+B)30 项加起来之后方差是多少?

用字母而不用具体符号,是为了强调「只要独立且均值为 0,谁都行」。 写成 x₁w₁ 反而会让人以为只对这一项成立。

「随机变量」不意味着反复生成

这里容易搞混:随机变量像掷骰子 —— 掷之前不知道是几,但知道每面 1/6。 但这不是说训练时要反复生成 W。

W1 = torch.randn((30, 200)) # 只执行一次,6000 个数
W1.std() # ≈ 1.0,直接就是 σ_W

W1 只生成一次,之后一直用它、被梯度更新 —— 这就是正常的训练流程。 概率论用得上,是因为那一次生成里就已经有 6000 个来自同一分布的样本。 前向之后 z 有 32×200 = 6400 个值,天然就是「不同样本 × 不同神经元」的随机组合。 公式预测的就是这 6400 个数的散布程度:

z.std() # 4.916 ← 公式算的就是它

一次生成、一次前向,公式就对上了。

σ_W 是分布参数,不是某个权重

是什么例子
w₁一次采样的结果−0.722
σ_W它所来自的分布的标准差1.0

单个确定的数没有方差 —— −0.722 就是 −0.722。而 σ_W 描述的是「这类数典型有多大」。 所以 σ_W 不带下标:30 个权重来自同一分布,共享同一个 σ_W。 这也是为什么后面求和号里的项没有下标。

两条性质后面反复要用:

E[A + B] = E[A] + E[B] 总是成立
E[AB] = E[A] · E[B] 仅当 A、B 独立

第二条的前提很重要,后面所有推导都靠它。


方差:散得多开

两组数,平均值都是 100:

A 组: 99, 100, 101, 100, 100 ← 挤在一起
B 组: 40, 160, 100, 20, 180 ← 散得很开

平均值一样,但明显不是一回事。方差就是量化这个差别的。

Var(X) = E[(X − μ)²]

三步:每个数减平均值 → 平方 → 取平均。用 A 组算(μ = 100):

数偏离偏离²
99−11
10000
101+11
10000
10000
Var = (1+0+1+0+0) / 5 = 0.4 σ = √0.4 = 0.632

为什么要平方

因为直接把偏离量相加会正负抵消:

E[X − μ] = E[X] − μ = 0 永远是 0

平方让所有偏离变正数。取绝对值也行,但不可导,也没有下面那些好性质。

一个好用的等价形式

Var(X) = E[X²] − (E[X])²

推导:E[(X−μ)²] = E[X² − 2μX + μ²] = E[X²] − 2μ² + μ² = E[X²] − μ²。

关键在于 μ 是个确定的数(平均值)而不是随机变量,所以能自由进出期望符号: E[2μX] = 2μ·E[X] = 2μ²,E[μ²] = μ²。

当 μ = 0 时,Var(X) = E[X²]。 randn 生成的数均值就是 0,后面的推导因此大幅简化 —— 这不是巧合,而是初始化故意选均值 0 的原因之一。

用 A 组对一遍:

E[X²] = (99² + 100² + 101² + 100² + 100²) / 5 = 10000.4
(E[X])² = 100² = 10000
Var = 10000.4 − 10000 = 0.4 ✅ 与定义式一致

「先平方后平均」总是 ≥「先平均后平方」,所以方差永远非负;相等只在所有数都相同时成立。

标准差:开方回到同单位

σ = √Var(X)

方差的单位是「原单位的平方」,没法直接跟数据比。标准差和原数据同单位,能直接读。

推导用方差,报告用标准差。 因为只有方差可加(下面第四节),标准差不可加。 这是两者分工的根本原因。


三条运算规则

规则 1:乘常数

Var(cX) = c² · Var(X) σ_cX = |c| · σ_X

推导(设 μ = 0):E[(cX)²] = c²E[X²]。

这就是 W1 = randn(...) * 0.2 为什么能把标准差正好变成 0.2 —— 常数直接乘在标准差上, 不用试。上一篇那个「手调 0.2」不是试出来的,是算出来的。

规则 2:相加(需独立)

Var(A + B) = Var(A) + Var(B)

推导(均值都为 0):

E[(A+B)²] = E[A²] + 2·E[AB] + E[B²]
↑
独立时 = E[A]E[B] = 0 × 0 = 0

交叉项消失是全部关键。 反例:A 跟自己完全相关时

Var(A + A) = Var(2A) = 4·Var(A) ≠ 2·Var(A)

实测 Var(a+a) = 3.999,不是 2。

交叉项为什么会消失

E[AB] = 0 需要两个条件同时成立,各管一步:

步骤用到的条件得到
① 拆开乘积独立E[AB] = E[A]E[B]
② 两个因子都为 0均值 00 × 0 = 0

直观上,E[AB] 就是协方差,衡量「A 和 B 有没有同涨同跌的倾向」。 独立时四种符号组合等概率,正负抵消:

ABAB 的符号概率
+++1/4
+−−1/4
−+−1/4
−−+1/4

E[A] = 0 但 E[A²] ≠ 0,不矛盾

容易混的一点:既然 E[A] = 0,为什么展开式里的 E[A²] 不也是 0?

因为 A 有正有负,但 A² 永远非负。

量值为什么
E[A]0正负抵消
E[AB]0独立,四种符号等概率抵消
E[A²]σ² > 0平方后全正,无法抵消

一次方能抵消,平方不能。 而这正是方差存在的原因 —— 如果 E[A²] 也是 0, 那方差就是 0,整个推导也就没意义了。展开式里消失的只有中间那项, 两边的平方项恰恰就是方差本身。

规则 3:相乘(需独立 + 均值 0)

Var(AB) = σ_A² · σ_B²

推导:

Var(AB) = E[A²B²] − (E[AB])² = E[A²]E[B²] − (E[A]E[B])²

均值为 0 时第二项为 0,而 E[A²] = σ_A²,所以结果是 σ_A²σ_B²。

均值不为 0 时公式复杂得多:Var(AB) = σ_A²σ_B² + σ_A²μ_B² + σ_B²μ_A²。 取 μ_A = 2、μ_B = 3、σ = 1 实测:理论 1 + 9 + 4 = 14,实测 13.991 ✅

三条规则一起验

a, b = torch.randn(2_000_000), torch.randn(2_000_000)
Var(a) = 0.9997 理论 1
Var(3a) = 8.9969 理论 9 ← 规则 1
Var(a+b) = 1.9985 理论 2 ← 规则 2
Var(ab) = 1.0007 理论 1 ← 规则 3
Var(a+a) = 3.9986 ← 不独立,规则 2 失效

方差可加,标准差不可加

这是整套推导的枢纽。

n 项相加后
方差n σ²(线性)
标准差√n σ(开方)
±√n 只是 1σ,所以有的路径会跑到包络外 —— 但没有一条接近线性那条。30 个随机数相加不会都同号,总幅度就落在 5.5 倍附近。

正负会抵消,所以 30 个随机数相加的总幅度不是 30 倍而是 5.5 倍。 随机游走是同一回事:抛 100 次硬币,最后离原点大约 √100 = 10 步。

所以推导必须在方差空间做,最后再开方回到标准差。 上一篇那句「√n 来自方差相加、标准差开方」,全部内容就在这一节。


回到神经网络

一个神经元:zⱼ = Σᵢ xᵢwᵢⱼ,i 从 1 到 n。

第 1 步 —— 单项(规则 3)

Var(xᵢwᵢⱼ) = σ_x² σ_W²

第 2 步 —— n 项相加(规则 2)

Var(zⱼ) = n · σ_x² σ_W²

这一步里有两个等号,性质完全不同:

① Var(Σ…) = ΣVar(…) —— Var 穿过求和号,是规则 2 反复用了 29 次,必须独立才成立 ② ΣVar(xᵢwᵢ) = Σσ_x²σ_W² —— 纯代数替换,把符号换成第 1 步的结果。 右边没有下标 i,因为 30 项同分布、方差全相同

最后「同一个常数加 n 遍 = 乘 n」。等号 ① 是全部推导里唯一有实质内容的一步。

验证等号 ①:

x = torch.randn(100000, 30) * 0.911
W = torch.randn(30)
terms = x * W # (100000, 30) 每一项
print("左边 Var(∑):", terms.sum(1).var().item())
print("右边 ∑Var :", terms.var(0).sum().item()) # 两者几乎相同 ✅
# 不独立的反例
a = torch.randn(100000)
st = torch.stack([a, a], dim=1) # 两列完全相同
print("左边:", st.sum(1).var().item()) # ≈ 4
print("右边:", st.var(0).sum().item()) # ≈ 2 ❌ 不相等

第 3 步 —— 开方

σ_z = √n · σ_W · σ_x

实测对账:σ_x = 0.909,理论 √30 × 1 × 0.911 = 4.99,实测 4.958 ✅

两个假设

假设在哪里用的真的成立吗
xᵢ 与 wᵢⱼ 独立规则 2、3初始化时 ✅(W 是新抽的随机数)
均值为 0规则 3randn ✅

训练开始后两个假设都会逐渐失效:W 被梯度推动后不再独立于数据,均值也不再是 0。 所以这套公式只管第 0 步,管不了第 1000 步 —— 这正是 BatchNorm 存在的理由。


正态分布与预测饱和

大量独立的小因素叠加,结果趋向正态分布(中心极限定理)。 zⱼ 是 30 项相加,所以它必然近似正态 —— 不管 x 和 W 原本是什么分布。

范围理论实测(200 万样本)
±1σ68.27%68.22%
±2σ95.45%95.42%
±3σ99.73%99.73%

这条性质直接换来一个能力:知道 σ_z 就能算出多少神经元会死,不用跑模型。

三条曲线面积都是 1,变的只有胖瘦。σ_z = 4.99 那条太扁,一多半的 z 落在饱和线外;σ_z = 1.0 那条的尾巴细到几乎看不见 —— 那就是 0.3% 的样子。

预测 54.8%,上一篇实测 62.3%,同一量级。差值有两个来源: |z| > 3 和 |h| > 0.99 不是同一条线,以及单次采样的波动。

反过来用更有意思:想让饱和低于 5%,就是在要求 3 至少有 2 个 σ 那么远, 也就是 σ_z ≲ 1.5。上一篇手调出来的 0.2 给出 σ_z ≈ 1.0 —— 落在这个区间里。


为什么诊断盯方差

指标能不能用为什么
均值❌初始化时永远是 0,没有区分度
最大值❌极值噪声大,一个异常值就带偏
饱和比例⚠️只对 tanh / sigmoid 有意义,ReLU 用不了
方差 / 标准差✅可逐层传递、有解析公式、不依赖激活函数

三个不可替代的性质:

① 可预测 —— 有解析公式,不用跑模型就能算出下一层会怎样。上面那张图就是它。

② 可逐层累乘 —— 每层的比值乘起来就是总效应。每层 1.5,十层后 57 倍(爆炸); 每层 0.5,十层后 0.001(消失)。这是上一篇「逐层 σ 比值 ≈ 1」那个指标的全部依据。

③ 反向也适用 —— 梯度反向传递遵循同一套规则,因为 ∂L/∂x = ∂L/∂z · Wᵀ 也是矩阵乘。 一个指标同时监控两个方向。

方差是唯一能把「初始化选多少」和「网络能堆多深」连起来的量。

回头看上一篇那几个指标,其实都是方差的不同面孔:

指标本质上在看
初始 loss = ln|V|logits 的方差够不够小
饱和 < 5%z 的方差合不合适
逐层 σ 比值 ≈ 1方差有没有逐层漂移
grad/data ≈ 1e-3梯度方差与参数方差的比

两个实用细节

n 还是 n−1

除以叫什么什么时候用
n总体方差手上就是全部数据
n−1样本方差(无偏)用样本估计总体
t.std() # 默认 correction=1,除 n−1
t.std(correction=0) # 除 n

元素成千上万时两者差别可忽略,不必纠结。

「矩阵的方差」是什么

W1.shape # (30, 200)
W1.std() # 把 6000 个元素当一堆数算,≈ 1.0
W1[:, 0].std() # 第 0 列,也 ≈ 1.0
W1[0, :].std() # 第 0 行,也 ≈ 1.0

因为 randn 是逐元素独立生成的,怎么切都一样。 「矩阵的标准差」是个偷懒的说法,真正的意思是**「每个元素所服从的分布的标准差」**。


自己验证

import torch
a, b = torch.randn(1_000_000), torch.randn(1_000_000)
# 三条规则
print((3*a).var(), ' 理论 9')
print((a+b).var(), ' 理论 2')
print((a*b).var(), ' 理论 1')
print((a+a).var(), ' ← 不独立,是 4 不是 2')
# √n 增长
for n in [1, 4, 30, 200]:
s = torch.randn(200000, n).sum(1)
print(f'n={n:<4} std={s.std():.3f} √n={n**0.5:.3f}')
# 神经网络场景
x = torch.randn(5000, 30) * 0.911
W = torch.randn(30, 200)
print(f'σ_z 实测 {(x@W).std():.3f} 理论 {(30**0.5)*0.911:.3f}')
# 68/95/99.7
u = torch.randn(1_000_000)
for k in [1, 2, 3]:
print(f'±{k}σ 内 {100*(u.abs()<k).float().mean():.2f}%')

这一篇没有引入任何新机制,只是把上一篇借来就用的那个公式还清: √n 来自「方差可加、标准差不可加」,而方差可加又来自「独立时交叉项消失」。 一路追到底,全部内容就是 E[AB] = E[A]E[B] 那一个条件。

也正因为它靠这个条件,训练一开始它就开始失效 —— W 被梯度推动之后不再独立于数据。 下一段讲 BatchNorm,它的思路是与其算准,不如强制:每层输出直接减均值除标准差, 不管前面发生了什么。