【K-S 检验】Kolmogorov–Smirnov计算过程与示例

K-S 检验详解:背景、计算与代码实现


一、背景与用途

1. 什么是 K-S 检验?

Kolmogorov--Smirnov(K--S)检验是一种非参数假设检验方法,用于判断:

  • 单样本 K--S 检验 :一组观测数据是否来自某个特定的理论分布(如正态分布、均匀分布)。
  • 双样本 K--S 检验 :两组独立样本是否来自同一未知连续分布

核心优势:不依赖分布形态假设,适用于任意连续分布,对位置和形状变化敏感。

2. 在本文中的应用

在《思维链与幻觉检测》论文中,作者使用双样本 K--S 检验来评估:

CoT 提示是否显著改变了 LLM 内部状态的分布(如 Hidden Score、Attention Score)。

  • 零假设 H0H_0H0:有/无 CoT 条件下,检测分数来自同一分布。
  • 若拒绝 H0H_0H0(p < 0.01) → 说明 CoT 显著扰动了内部信号,原有检测阈值失效。

二、计算原理与完整示例-单样本

2.1 场景设定

我们用一个单样本检验 案例,检验以下5个数据点是否服从标准均匀分布 U(0,1)

  • 样本数据 (X)[0.38, 0.12, 0.95, 0.70, 0.55]
  • 样本量 (N):5
  • 原假设 H0H_0H0:数据服从 U(0,1)

2.2 计算步骤详解

步骤 1:排序数据

K--S 检验基于累积分布,必须先排序:

复制代码
排序后: [0.12, 0.38, 0.55, 0.70, 0.95]
步骤 2:理解经验累积分布函数 (ECDF)

ECDF 是阶梯函数。对于第 i 个数据点,其"跳跃前"概率为 (i-1)/N,"跳跃后"概率为 i/N

步骤 3:定义理论 CDF

对于标准均匀分布 U(0,1),理论 CDF 为:

Ftheory(x)=x F_{\text{theory}}(x) = x Ftheory(x)=x

步骤 4:逐点计算最大偏差 D

在每个数据点 x_i 处,计算两个距离:

  1. 跳跃前距离|(i-1)/N - F_theory(x_i)|
  2. 跳跃后距离|i/N - F_theory(x_i)|

取所有距离中的最大值作为统计量 D

序号 i 数据点 x_i 理论值 F_theory(x_i) 观测值 (跳前) (i-1)/N 观测值 (跳后) i/N 距离1 (跳前差) 距离2 (跳后差)
1 0.12 0.12 0.0 0.2 0.12 0.08
2 0.38 0.38 0.2 0.4 0.18 ← 最大 0.02
3 0.55 0.55 0.4 0.6 0.15 0.05
4 0.70 0.70 0.6 0.8 0.10 0.10
5 0.95 0.95 0.8 1.0 0.15 0.05

结论 :最大距离出现在第2个点(x=0.38),D = 0.18

步骤 5:查表判决

对于 N=5,α=0.05 的临界值约为 0.563

  • 因为 D = 0.18 < 0.563不能拒绝 H₀ → 数据符合均匀分布。

2.3 判断过程

1. 样本较少时(如 n < 30)
  • 问题 :KS 检验在小样本下功效较低(不容易拒绝错误的原假设)。
  • 判断方式
    • 直接使用 KS 检验的 p 值。
    • 若 p 值 > 显著性水平(如 0.05),不能拒绝原假设(注意:不是"证明成立")。
    • 但即使 p 值较大,也不能强结论"分布匹配",因为可能只是样本太少看不出差异。
    • 建议:结合可视化(如 Q-Q 图或 ECDF vs CDF 图)辅助判断。
2. 样本正常、较多时
  • 问题 :样本正常(如 30 ≤ n < 100)KS 检验表现较稳健,p 值较可靠;KS 检验在大样本下过于敏感,微小偏差也可能导致显著结果(p 值很小)。
  • 判断方式
    • 即使 p 值 < 0.05,也要看 KS 统计量 D(最大偏差)的实际大小
    • 如果 D 很小(如 < 0.05),即使统计显著,实际意义可能不大,可认为"近似成立"。
    • 结合业务或应用场景判断:是否容许微小偏差?
    • 可考虑使用效应量(如 D 值本身)而非仅依赖 p 值。

三、计算原理与完整示例-双样本

3.1 双样本 K-S 检验计算示例

场景背景

假设你在做一个药物实验。

  • A组(对照组) :3只老鼠,药物起效时间为:[10, 15, 20] 分钟。
  • B组(实验组) :3只老鼠,药物起效时间为:[12, 18, 25] 分钟。
  • 目的:我们想知道这两组数据的分布是否有显著差异?

零假设 (H₀):两组数据来自同一个分布(药物没效果,时间分布一样)。

手动计算全过程

步骤 1:混合排序

双样本检验不关心数据是 A 的还是 B 的,我们先要把所有观测到的时间点放在一起,从小到大排列,作为我们的"坐标轴"。

混合坐标点:10, 12, 15, 18, 20, 25

步骤 2:计算各自的累积比例

对于每一个时间点,我们要分别问:

  • A组里有多少小于等于这个时间的?(除以A的总数3)
  • B组里有多少小于等于这个时间的?(除以B的总数3)

让我们逐行扫描(Nₐ = 3, Nᵦ = 3):

markdown 复制代码
| 时间点 ($x$) | 事件来源 | A组累积计数 ($N_a=3$) | A的 CDF ($F_a$) | B组累积计数 ($N_b=3$) | B的 CDF ($F_b$) | 差距 $|F_a - F_b|$ |
|:-------------:|:--------:|:---------------------:|:---------------:|:---------------------:|:---------------:|:------------------:|
| 10            | A组数据  | 1                     | $1/3$           | 0                     | $0$             | $1/3 \approx 0.333$ |
| 12            | B组数据  | 1                     | $1/3$           | 1                     | $1/3$           | $0$                |
| 15            | A组数据  | 2                     | $2/3$           | 1                     | $1/3$           | $1/3 \approx 0.333$ |
| 18            | B组数据  | 2                     | $2/3$           | 2                     | $2/3$           | $0$                |
| 20            | A组数据  | 3                     | $1$             | 2                     | $2/3$           | $1/3 \approx 0.333$ |
| 25            | B组数据  | 3                     | $1$             | 3                     | $1$             | $0$                |

步骤 3:寻找最大距离 D

查看表格最后一列,最大的差距是 0.33(出现在多个点,最大值即为 0.33)。

结论

  • 双样本 K-S 统计量 D = 0.33。
  • 因为样本量太小且 D 值较小,通常无法拒绝零假设。

3.2 当样本量足够大时,如何处理?

当样本量 m,n≥10m, n \geq 10m,n≥10 时,K--S 统计量 DDD 的分布近似服从 渐近分布 (asymptotic distribution),此时不再依赖查表,而是通过p值来判断是否拒绝零假设。

处理步骤:

1. 计算 K--S 统计量 D

与小样本相同:

  • 合并所有观测值,排序去重;
  • 在每个点计算两个 ECDF 的差值 ∣FX(t)−FY(t)∣|F_X(t) - F_Y(t)|∣FX(t)−FY(t)∣;
  • 取最大值作为 DDD。
2. 计算 p 值(关键步骤)

使用以下公式进行近似(由 Smirnov 提出):

λ=(mnm+n+0.12+0.11mn/(m+n))⋅D \lambda = \left( \sqrt{ \frac{mn}{m+n} } + 0.12 + \frac{0.11}{\sqrt{mn/(m+n)}} \right) \cdot D λ=(m+nmn +0.12+mn/(m+n) 0.11)⋅D

然后 p 值为:

p=2∑k=1∞(−1)k−1e−2k2λ2 p = 2 \sum_{k=1}^{\infty} (-1)^{k-1} e^{-2k^2 \lambda^2} p=2k=1∑∞(−1)k−1e−2k2λ2

实际工程中,无需手动计算此级数。推荐直接调用统计库(如 scipy.stats.ks_2samp),它会自动选择精确或渐近方法。

3. 判断显著性
  • p < α (通常 α=0.05),则拒绝零假设 → 两组分布显著不同;
  • p ≥ α ,则不能拒绝零假设 → 无足够证据表明分布不同。

四、Python 代码实现

这里我们将代码整合,分别展示 Scipy 库的标准用法和为了理解原理的手写实现。

1. 库调用版本 (Scipy)

  • 推荐生产环境使用 Scipy 提供了 kstest (单样本) 和 ks_2samp (双样本)。
python 复制代码
import numpy as np
from scipy import stats

def standard_ks_demo():
    print("========== 1. Scipy 库调用演示 ==========")
    
    # --- A. 单样本检验 (One-Sample) ---
    # 场景:检验一组数据是否服从正态分布
    data_1 = np.random.normal(loc=0, scale=1, size=100) # 生成正态分布数据
    # kstest(数据, '理论分布名')
    d1, p1 = stats.kstest(data_1, 'norm') 
    
    print(f"[单样本] 统计量 D: {d1:.4f}, P值: {p1:.4f}")
    if p1 > 0.05:
        print("  -> 结论:数据服从正态分布")
    else:
        print("  -> 结论:数据不服从正态分布")

    print("-" * 30)

    # --- B. 双样本检验 (Two-Sample) ---
    # 场景:比较两组独立数据是否同分布
    # 为了演示差异,我们生成两个均值不同的分布
    group_A = np.random.normal(0, 1, 100)
    group_B = np.random.normal(0.5, 1, 100) # 均值偏移了0.5
    
    d2, p2 = stats.ks_2samp(group_A, group_B)
    
    print(f"[双样本] 统计量 D: {d2:.4f}, P值: {p2:.4f}")
    if p2 > 0.05:
        print("  -> 结论:两组数据分布相同")
    else:
        print("  -> 结论:两组数据分布不同 (拒绝H0)")
    print("\n")

standard_ks_demo()

2. 手写实现版本 (Manual) -

用于理解原理这里我将手写实现两个函数,逻辑完全对应上面的讲解。

python 复制代码
import numpy as np

# --- 手写 1: 单样本 K-S 检验 ---
def manual_ks_one_sample(data, cdf_function):
    """
    data: 观测数据列表
    cdf_function: 理论CDF函数,输入x返回概率
    """
    n = len(data)
    data_sorted = np.sort(data)
    max_d = 0
    
    for i, x in enumerate(data_sorted):
        # 经验分布(ECDF)在 x 处的两个值:跳跃前,跳跃后
        cdf_obs_pre = i / n
        cdf_obs_post = (i + 1) / n
        
        # 理论分布值
        cdf_theo = cdf_function(x)
        
        # 找最大差值
        diff = max(abs(cdf_obs_pre - cdf_theo), abs(cdf_obs_post - cdf_theo))
        if diff > max_d:
            max_d = diff
            
    return max_d

# --- 手写 2: 双样本 K-S 检验 ---
def manual_ks_two_sample(data1, data2):
    """
    data1: 第一组样本
    data2: 第二组样本
    """
    n1 = len(data1)
    n2 = len(data2)
    
    # 1. 混合所有点并排序,作为 x 轴的扫描点
    # (去重是为了提高效率,不去重结果也一样)
    all_points = np.unique(np.concatenate([data1, data2]))
    
    max_d = 0
    
    # 2. 遍历每一个混合后的时间点
    for x in all_points:
        # 计算 data1 中 <= x 的比例
        # np.sum(data1 <= x) 算出个数,除以 n1 得到比例
        cdf_1 = np.sum(data1 <= x) / n1
        
        # 计算 data2 中 <= x 的比例
        cdf_2 = np.sum(data2 <= x) / n2
        
        # 3. 计算差距
        diff = abs(cdf_1 - cdf_2)
        
        if diff > max_d:
            max_d = diff
            
    return max_d

# --- 测试手写代码 ---
def manual_test_demo():
    print("========== 2. 手写代码验证 ==========")
    
    # 测试单样本 (用简单的均匀分布数据)
    # 假设理论是 U(0,1),即 F(x) = x
    sample_data = np.array([0.1, 0.4, 0.35, 0.8])
    d_one = manual_ks_one_sample(sample_data, lambda x: x)
    print(f"[手写单样本] 统计量 D: {d_one:.4f}")
    
    # 测试双样本 (使用我们在第二部分讲解的例子)
    group_A = np.array([10, 15, 20])
    group_B = np.array([12, 18, 25])
    
    d_two = manual_ks_two_sample(group_A, group_B)
    print(f"[手写双样本] 统计量 D: {d_two:.4f} (预期应为 0.3333)")

manual_test_demo()

总结

  1. CDF 是"小于等于某值的比例",是 K-S 检验的标尺。
  2. 双样本检验 通过混合两组数据,逐点比较它们积累速度的差异。
  3. 代码实现 中,核心逻辑始终是 abs(CDF_1 - CDF_2) 的最大值。
相关推荐
菜鸟起航ing3 小时前
Spring AI 全方位指南:从基础入门到高级实战
java·人工智能·spring
Guheyunyi3 小时前
智慧消防管理系统如何重塑安全未来
大数据·运维·服务器·人工智能·安全
ZZY_dl4 小时前
训练数据集(三):真实场景下采集的课堂行为目标检测数据集,可直接用于YOLO各版本训练
人工智能·yolo·目标检测
yiersansiwu123d4 小时前
AI伦理治理:在创新与规范之间寻找动态平衡
人工智能
华清远见成都中心4 小时前
成都理工大学&华清远见成都中心实训,助力电商人才培养
大数据·人工智能·嵌入式
鲨莎分不晴4 小时前
强化学习第五课 —— A2C & A3C:并行化是如何杀死经验回放
网络·算法·机器学习
爱好读书5 小时前
AI生成er图/SQL生成er图在线工具
人工智能
CNRio5 小时前
智能影像:AI视频生成技术的战略布局与产业变革
人工智能
六行神算API-天璇5 小时前
架构思考:大模型作为医疗科研的“智能中间件”
人工智能·中间件·架构·数据挖掘·ar