数学重学 - 05 基础统计思维

这是 数学重学路线图 阶段一的子页面

05 - 基础统计思维

为什么要学这个

后端开发天天看监控面板,P99延迟、QPS均值、错误率——全是统计指标

安全领域:异常检测的本质就是”偏离正常分布多远”

大数据方向:数据质量检查、离群值识别、A/B测试——统计是基本功

不懂统计,看数据就是在看热闹;懂了统计,看数据就是在看门道

“我和马云平均资产500亿”——如果你觉得这句话哪里不对,恭喜你已经有统计直觉了

核心概念:三个”中心”指标

先讲故事

假设一个5人团队的月薪是:8K、10K、12K、15K、200K(最后一个是老板)

平均薪资 = (8+10+12+15+200)/5 = 49K

但其中4个人的薪资都远低于49K!

这就是平均数的陷阱:一个极端值就能拉飞整个均值

均值(Mean)

公式:mean = Σx_i / n(所有值加起来除以个数)

人话:把蛋糕平均切,每人分到多少

优点:计算简单,用到了每一个数据点

缺点:对极端值敏感,一个异常值就能把均值带偏

适用场景:数据分布比较均匀时

中位数(Median)

定义:把数据从小到大排列,取最中间那个值

人话:排队站中间的那个人

上面的例子:8、10、12、15、200 → 中位数 = 12K

优点:不受极端值影响,反映”典型”水平

缺点:没有用到全部数据的信息

适用场景:数据有偏斜时(收入、房价等)

小知识:国家统计局公布的”居民收入”更多用中位数而非均值

众数(Mode)

定义:出现次数最多的值

人话:最流行的选项

例子:鞋码分布中,42码最多人穿 → 众数是42

适用场景:分类数据(最受欢迎的商品、最常见的错误码)

注意:可能有多个众数,也可能没有(每个值出现次数都一样)

三者的关系

正态分布(对称):均值 ≈ 中位数 ≈ 众数

右偏分布(如收入):众数 < 中位数 < 均值

左偏分布(如考试分数偏高):均值 < 中位数 < 众数

一条经验规则:如果均值和中位数差很多,说明数据分布有偏斜

分位数与百分位

概念

P50 = 中位数:50%的数据在此值以下

P90:90%的数据在此值以下

P95:95%的数据在此值以下

P99:99%的数据在此值以下

P99.9:99.9%的数据在此值以下(千分位)

直觉比喻

想象100个人排队:

P50 = 第50个人

P90 = 第90个人

P99 = 第99个人

P99的人”等得最久”,但不是最倒霉的那个(P100)

为什么后端监控看P99而不是看均值

场景:一个API的响应时间

均值 50ms(看起来很快)

P99 = 2000ms(1%的用户要等2秒!)

如果日请求量1000万次,1% = 10万次慢请求

这10万用户的体验是灾难性的

SLA/SLO通常用P99定义:例如”P99延迟 < 200ms”

P99 vs P99.9 vs P99.99

| 指标 | 含义 | 1000万请求中的慢请求数 |
| P99 | 99%请求在X ms内 | 10万次超标 |
| P99.9 | 99.9%请求在X ms内 | 1万次超标 |
| P99.99 | 99.99%请求在X ms内 | 1000次超标 |

每多一个9,难度和成本指数级上升

从99.9%到99.99%,可能要花10倍的钱

方差与标准差

先讲故事

两个班级,平均分都是70分:

A班:69, 70, 71, 70, 70(大家差不多)

B班:30, 50, 70, 90, 110(差距巨大)

均值一样,但分布天差地别

我们需要一个指标来衡量”数据有多分散”

方差(Variance)

公式:σ² = Σ(x_i - μ)² / n

人话翻译:

第一步:算出每个值和均值的差距

第二步:差距取平方(消除正负号,放大大的偏差)

第三步:所有平方差取平均

单位问题:如果原数据单位是”分”,方差的单位是”分²”,不直观

标准差(Standard Deviation)

公式:σ = √(σ²),也就是方差开根号

和原数据同单位,可以直接比较

直觉:标准差越大 = 数据越分散 = 越不稳定

A班标准差 ≈ 0.6,B班标准差 ≈ 28.3

样本方差 vs 总体方差

总体方差:除以 n(你有全部数据)

样本方差:除以 n-1(你只有部分数据,叫 “贝塞尔校正”)

为什么要 n-1:用样本估计总体时,除以 n 会系统性偏小

Python 中:np.var(data) 是总体方差,np.var(data, ddof=1) 是样本方差

正态分布直觉

什么是正态分布

又叫高斯分布、钟形曲线

自然界最常见的分布:身高、体重、测量误差…

两个参数完全确定:均值 μ(中心位置)和标准差 σ(胖瘦程度)

68-95-99.7 法则(记住这个就够了)

μ ± 1σ 范围内:包含 68% 的数据

μ ± 2σ 范围内:包含 95% 的数据

μ ± 3σ 范围内:包含 99.7% 的数据

1
2
3
4
5
6
7
8
   ___
/ \ 68% 在 1σ 内
/ \
/ | | | \ 95% 在 2σ 内
/ | | | \
/ | | | \ 99.7% 在 3σ 内
_/____|_|_|____\_
-3σ -2σ -1σ μ 1σ 2σ 3σ

3σ原则:超过3σ就是异常

正常数据落在 3σ 之外的概率只有 0.3%

也就是说,每1000个数据点,只有3个”应该”在3σ之外

如果你看到很多数据超过了3σ,要么数据不正态,要么出了异常

这就是异常检测的最基础方法

Z-Score

z = (x - μ) / σ

人话:这个值离均值有几个标准差

z = 0:恰好在均值处

z = 2:比均值高2个标准差(比约97.5%的值大)

z = -1.5:比均值低1.5个标准差

把任意正态分布转换成标准正态分布(μ=0, σ=1),方便比较

公式与推导(附人话翻译)

均值公式

$\bar{x} = \frac{1}{n}\sum_{i=1}^{n}x_i$

人话:全加起来除以个数

方差公式

$\sigma^2 = \frac{1}{n}\sum_{i=1}^{n}(x_i - \bar{x})^2$

人话:每个值与均值的差的平方,取平均

标准差公式

$\sigma = \sqrt{\sigma^2}$

人话:方差开根号,回到原来的单位

分位数计算

把 n 个数据排序,第 k 百分位数的位置 = k/100 × (n+1)

如果位置不是整数,取两侧的加权平均

Python 代码

基础统计指标计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
"""
用 numpy 计算基础统计指标
对比均值和中位数在有极端值时的表现
"""
import numpy as np

# 模拟一个后端 API 的响应时间(单位:ms)
np.random.seed(42)

# 大部分请求很快(50ms左右),但有少量慢请求
normal_latencies = np.random.normal(loc=50, scale=10, size=9900)
# 1% 的慢请求(500-2000ms)
slow_latencies = np.random.uniform(500, 2000, size=100)

latencies = np.concatenate([normal_latencies, slow_latencies])
latencies = np.clip(latencies, 1, None) # 延迟最小1ms

# 基础指标
print("=== API 响应时间统计 ===")
print(f"数据量: {len(latencies)}")
print(f"均值: {np.mean(latencies):.1f} ms")
print(f"中位数: {np.median(latencies):.1f} ms")
print(f"标准差: {np.std(latencies):.1f} ms")
print()

# 分位数
percentiles = [50, 90, 95, 99, 99.9]
print("=== 分位数 ===")
for p in percentiles:
value = np.percentile(latencies, p)
print(f"P{p:<5} = {value:>8.1f} ms")
print()

# 均值 vs 中位数差异
diff = np.mean(latencies) - np.median(latencies)
print(f"均值 - 中位数 = {diff:.1f} ms")
print("均值被慢请求拉高了!这就是为什么监控要看 P99 而非均值")

正态分布与 68-95-99.7 法则可视化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
"""
画出正态分布,标注 1σ/2σ/3σ 区域
"""
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats

mu, sigma = 100, 15 # 例如:某API延迟均值100ms,标准差15ms
x = np.linspace(mu - 4*sigma, mu + 4*sigma, 1000)
y = stats.norm.pdf(x, mu, sigma)

fig, ax = plt.subplots(figsize=(12, 5))
ax.plot(x, y, 'k-', linewidth=2)

# 填充 1σ, 2σ, 3σ 区域
colors = ['#2196F3', '#64B5F6', '#BBDEFB']
labels = ['1σ (68.3%)', '2σ (95.4%)', '3σ (99.7%)']

for i, (n_sigma, color, label) in enumerate(
zip([1, 2, 3], colors, labels)
):
x_fill = np.linspace(mu - n_sigma*sigma, mu + n_sigma*sigma, 500)
y_fill = stats.norm.pdf(x_fill, mu, sigma)
ax.fill_between(x_fill, y_fill, alpha=0.3, color=color, label=label)

ax.set_xlabel('延迟 (ms)', fontsize=12)
ax.set_ylabel('概率密度', fontsize=12)
ax.set_title('正态分布与 68-95-99.7 法则', fontsize=14)
ax.legend(fontsize=11)

# 标注均值和 σ 的位置
for n in range(-3, 4):
pos = mu + n * sigma
ax.axvline(x=pos, color='gray', linestyle='--', alpha=0.3)
label_text = f'μ{"+" if n > 0 else ""}{n}σ' if n != 0 else 'μ'
ax.text(pos, -0.002, label_text, ha='center', fontsize=9)

plt.tight_layout()
plt.savefig("normal_distribution_rule.png", dpi=150)
plt.show()

异常检测:3σ 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
"""
用 3σ 原则检测异常流量(简化版 DDoS 检测思路)
"""
import numpy as np

# 模拟一周的每分钟请求数
np.random.seed(42)
# 正常流量:均值1000 QPS,标准差100
normal_traffic = np.random.normal(1000, 100, size=7*24*60)

# 在第5000分钟开始注入攻击流量(持续30分钟)
attack_traffic = normal_traffic.copy()
attack_start = 5000
attack_duration = 30
attack_traffic[attack_start:attack_start+attack_duration] += 800 # 流量突增

# 用历史数据计算基线(前4000分钟)
baseline = attack_traffic[:4000]
mu = np.mean(baseline)
sigma = np.std(baseline)

print(f"基线均值: {mu:.0f} QPS")
print(f"基线标准差: {sigma:.0f} QPS")
print(f"3σ 上界: {mu + 3*sigma:.0f} QPS")
print(f"3σ 下界: {mu - 3*sigma:.0f} QPS")
print()

# 检测异常
threshold_upper = mu + 3 * sigma
threshold_lower = mu - 3 * sigma

anomalies = []
for i, qps in enumerate(attack_traffic):
if qps > threshold_upper or qps < threshold_lower:
anomalies.append((i, qps))

# 统计
true_attacks = [a for a in anomalies
if attack_start <= a[0] < attack_start + attack_duration]
false_positives = [a for a in anomalies
if not (attack_start <= a[0] < attack_start + attack_duration)]

print(f"检测到的异常点总数: {len(anomalies)}")
print(f"其中真正攻击: {len(true_attacks)}")
print(f"误报: {len(false_positives)}")
print(f"\n注意: 3σ 方法简单但粗糙")
print("实际生产中会用滑动窗口、指数加权移动平均(EWMA)等更精细的方法")

分位数分布图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
"""
画出 API 延迟的分位数分布图(CDF)
直观理解 P50/P90/P95/P99
"""
import numpy as np
import matplotlib.pyplot as plt

np.random.seed(42)
# 模拟真实延迟:大部分快,少量慢
fast = np.random.lognormal(mean=3.5, sigma=0.5, size=9500)
slow = np.random.uniform(200, 1000, size=500)
latencies = np.concatenate([fast, slow])

# 排序后画 CDF
sorted_lat = np.sort(latencies)
cdf = np.arange(1, len(sorted_lat)+1) / len(sorted_lat) * 100

fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(sorted_lat, cdf, linewidth=2, color='steelblue')

# 标注关键分位数
key_percentiles = [50, 90, 95, 99]
colors = ['green', 'orange', 'darkorange', 'red']
for p, c in zip(key_percentiles, colors):
val = np.percentile(latencies, p)
ax.axhline(y=p, color=c, linestyle='--', alpha=0.5)
ax.axvline(x=val, color=c, linestyle='--', alpha=0.5)
ax.plot(val, p, 'o', color=c, markersize=8)
ax.annotate(f'P{p} = {val:.0f}ms',
xy=(val, p), xytext=(val+50, p-5),
fontsize=10, color=c)

ax.set_xlabel('延迟 (ms)', fontsize=12)
ax.set_ylabel('百分位 (%)', fontsize=12)
ax.set_title('API 延迟的 CDF(累积分布函数)', fontsize=14)
ax.set_xlim(0, 1200)
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig("latency_cdf.png", dpi=150)
plt.show()

实际应用场景

安全方向

DDoS 检测:监控流量的均值和标准差,突然偏离 3σ → 可能在被攻击

异常登录检测:用户登录时间/地点的分布,偏离历史模式 → 告警

WAF 规则调优:分析请求参数长度的分布,超过 P99.9 的 → 可疑

大数据方向

数据质量检查:某列数据的均值/标准差突然变化 → 数据源可能出问题

离群值检测:Z-Score 绝对值 > 3 的数据点 → 标记为异常

A/B 测试:对比实验组和对照组的指标是否有统计显著差异

后端方向

SLA/SLO 定义:P99 延迟 < 200ms、可用性 > 99.95%

告警阈值设定:基于历史分布的 P99 + 缓冲区

容量规划:根据流量的均值和峰值(P99)来预留资源

性能回归检测:新版本上线后,P99 延迟是否变差了

常见误区

误区1:只看均值不看分布

“平均延迟50ms”听起来很好,但P99可能是2秒

永远要问:分布长什么样?有没有长尾?

误区2:样本量太小就下结论

3个用户中2个喜欢新界面 → “66%用户喜欢” → 这不靠谱

样本量太小,随机波动很大

经验法则:至少需要30个样本才有基本的统计意义

误区3:假设所有数据都是正态分布

收入分布、延迟分布、文件大小分布——这些通常是长尾分布(对数正态、幂律)

对非正态数据用3σ检测,会漏掉很多异常

误区4:混淆标准差和标准误

标准差:衡量数据的分散程度

标准误:衡量均值估计的不确定性 = 标准差 / √n

随着样本量增大,标准误变小但标准差不变

误区5:以为P99就是最差的那1%的均值

P99是一个阈值:99%的数据在这个值以下

不是”最差的1%的平均值”

练习题

题目1:某API一天100万次请求,P95=100ms,P99=500ms,P99.9=2000ms。问:有多少次请求延迟超过500ms?有多少次超过2秒?

答案提示:超过500ms的 = 100万 × 1% = 1万次;超过2秒的 = 100万 × 0.1% = 1000次

题目2:以下数据集:[1, 2, 3, 4, 5, 100],分别计算均值和中位数,体会极端值的影响

答案提示:均值 = 19.17,中位数 = 3.5

题目3:某安全系统监控网络流量,历史均值1000 QPS,标准差100。今天突然出现1500 QPS,这个值的 Z-Score 是多少?是否应该告警?

答案提示:z = (1500-1000)/100 = 5,远超3σ,应该立刻告警

题目4:为什么从P99优化到P99.9的成本远大于从P50优化到P99?用实际工程经验思考

提示:P99之后的长尾通常是GC、网络抖动、磁盘IO等不可控因素,需要从架构层面解决

题目5(编程题):生成一组模拟数据(正态分布 + 5%的离群值),用Z-Score方法检测离群值,计算检测的准确率和误报率

小结

均值会骗人,多看中位数和分位数

P99 是后端工程师的核心指标

标准差衡量”稳不稳”,3σ之外就是异常

正态分布的 68-95-99.7 法则,记住这一个就够用了

下一节:06-生活中的数学


上一章 目录 下一章
04-数感与估算 数学重学路线图 06-生活中的数学