多重检验修正:Bonferroni、Holm 与 Benjamini-Hochberg
三个程序,是同一个问题的三种回答:你愿意为「一个假阳性都不要」付出多少统计功效。以及它们共同的盲点——没有一个会去看你的策略之间有多像。
你测了 100 个策略,其中 5 个的 p 值低于 0.05。常规解释是”5% 的策略在 95% 置信水平上显著”。这个解释是错的:做 100 次检验时,假阳性的期望数量正好就是 。你观察到的现象,和”一个有优势的策略都没有”这个世界完全相容。
本文讲三个用来解决这件事的程序:Bonferroni、Holm、Benjamini-Hochberg。它们其实是同一个问题的三种回答,而那个问题是——你愿意为”一个假阳性都不要”付出多少统计功效?Bonferroni 付得最多,Holm 用同样的假设付得更少,BH 干脆换了个问题问。
推完这三个之后,还有一件更要紧的事:它们有一个共同的盲点,而这个盲点在量化场景里恰好最致命。
假阳性是怎么叠起来的
检验 个原假设,每个都用显著性水平 。在全局原假设成立、检验相互独立时,至少出现一个假阳性的概率是
代入 、:
99.4%。做 100 次 5% 水平的检验,你几乎必然会看到至少一个”显著”结果,哪怕真相是什么都没有。 时这个概率在小数点后好几位都是 9,假阳性成了确定事件。
这个概率有个名字,家族错误率(FWER):
Bonferroni 和 Holm 控制的都是它。注意 FWER 是一个关于”有没有”的陈述,不是关于”有多少”的陈述——这个区别等下讲 BH 时会变成关键。
Bonferroni:把阈值除以次数
、 时阈值是 0.0005,只有 p 值低于它才拒绝。
证明只有两行。设所有原假设都成立:
第二行用的是 Boole 不等式(并集上界)。这里有一个常被忽略的好处:这一步对 p 值之间的依赖结构不作任何要求,正相关、负相关、任意相关都成立。Bonferroni 是无条件有效的。
代价也正是从这里来的。并集上界在各事件互斥时取等号,而在正相关时松得厉害——几个策略同时给出小 p 值这件事高度重叠,被上界重复计了很多次。量化场景恰恰是重叠最严重的场景:两个在 SPY 上做动量的策略共享绝大部分信号,它们不是两次独立下注,是同一次下注的两种写法。Bonferroni 却按两次算。
给个数量感。假设你有 50 个策略,但里面真正独立的信号只有 5 个,其余都是变体。Bonferroni 给你的阈值是 ,而”正确”的阈值应该按 5 次独立检验算,即 。Bonferroni 严了整整 10 倍。
实际后果不只是”少拒绝几个”。能穿过 0.001 这道门的,只有整批结果里最极端的那几个离群值,而在回测里,最极端的结果恰恰是最可能来自过拟合的那些。把门槛抬到极端,筛出来的就是极端。
Bonferroni 唯一的优点是简单,以及那个无条件成立的保证。检验只有五个的时候用它没问题,五十个就该换。
Holm:免费的改进
Holm-Bonferroni 逐步下降程序对 p 值排序后,给每个位置分配不同的阈值:
- 排序:。
- 从 开始逐个检查 。
- 一旦某个 不满足就停下,拒绝所有 的 。
第 3 步的”停下”必须照字面执行。Holm 在第一次失败处终止,即使排在后面的某个 p 值恰好满足它自己那条阈值,也不再拒绝。这一点和下面的 BH 正好相反,混起来会让程序失去 FWER 保证——这是实现 Holm 时最常见的 bug。
阈值的分母从 一路减到 1:最小的 p 值面对的阈值和 Bonferroni 一模一样(),之后每拒绝一个,剩下的假设就少一个,分母跟着变小、阈值跟着放松。所以 Holm 的功效永不低于 Bonferroni——Bonferroni 能拒绝的,Holm 一定也能拒绝,反过来不成立。
而这个改进是免费的:Holm 和 Bonferroni 一样,在任意依赖结构下都把 FWER 控制在 以内,不需要独立性假设。既然假设一样、功效更强、代价只是排个序,实践中就没有理由再用裸的 Bonferroni。
举例。,,p 值为 。
Bonferroni 对所有检验用同一个阈值 ,只有 通过,拒绝 1 个。
Holm 逐个看:
- ,阈值 ,通过,拒绝。
- ,阈值 ,通过,拒绝。
- ,阈值 ,通过,拒绝。
- ,阈值 ,不通过,停。
拒绝 3 个。同样的数据、同样的 FWER 保证,多找出两个候选。
Holm 顺带还给你一个排好序的拒绝列表,最小的 p 值是最强候选、第二小是第二强,做优先级排序时直接可用。
Benjamini-Hochberg:换一个问题
在检验很多、而且你预期里面确实有一批是真的场景下(基因研究、因子发现),FWER 这个目标太苛刻了。“一个假阳性都不能有”意味着几乎什么都拒绝不了,于是真发现和假发现一起被埋掉。
Benjamini 和 Hochberg(1995)的做法是换掉目标函数,改控错误发现率(FDR):
差别在于 FWER 限制的是”出现一个或更多假阳性”的概率,FDR 限制的是假阳性在你的拒绝集合里占的期望比例。FDR 明确允许假阳性存在,只要求它们别占太大份额。这不是把标准放松,是换了一个衡量单位。
程序如下:
- 排序:。
- 找到最大的 ,使得 。
- 拒绝所有 的 。
注意第 2 步是”最大的 “,不是”第一次失败就停”。BH 是逐步上升(step-up)程序:中间某个 越过了自己的阈值不要紧,只要后面还有更大的 满足条件, 照样被拒绝。这和 Holm 的停止规则是相反的,两者不能互换。
用上面那组 p 值(,,)走一遍,阈值序列是 :
- :,满足。
- :,满足。
- :,满足。
- :,满足。
- :,不满足。
最大的满足者是 ,拒绝 4 个。同一组数据上,Bonferroni 1 个、Holm 3 个、BH 4 个——这个 1/3/4 的阶梯就是三种目标函数的价格表。
BH 实际把 FDR 控制在 ,其中 是真原假设的数量。当 ,也就是确实有一部分假设为假时,BH 比名义水平还要保守一点。这既是它在筛选场景里的优势,也留下了改进空间(下面 Storey 那一节就在做这件事)。
有一个前提必须点明,因为量化场景经常踩:BH 的 FDR 保证需要检验之间独立,或者满足一个叫 PRDS(正回归依赖)的条件。策略之间正相关通常落在 PRDS 里,勉强还行;但如果依赖结构复杂或者含负相关,保证就不再成立。Benjamini 和 Yekutieli(2001)给了一个在任意依赖下都成立的版本,代价是把阈值再除以 ( 时约 5.2)。这个代价很重,重到很多人宁可去论证自己的场景满足 PRDS。
三个程序,一份代码
import numpy as np
def bonferroni(pvals, alpha=0.05):
"""Bonferroni correction. Returns a boolean mask of rejections."""
return np.array(pvals) <= (alpha / len(pvals))
def holm(pvals, alpha=0.05):
"""Holm-Bonferroni step-down: stop at the FIRST failure."""
m = len(pvals)
sorted_idx = np.argsort(pvals)
sorted_p = np.array(pvals)[sorted_idx]
thresholds = alpha / (m - np.arange(m)) # alpha/m, alpha/(m-1), ..., alpha/1
rejected = np.zeros(m, dtype=bool)
for i, (p, th) in enumerate(zip(sorted_p, thresholds)):
if p <= th:
rejected[sorted_idx[i]] = True
else:
break # step-down: halt here
return rejected
def benjamini_hochberg(pvals, alpha=0.05):
"""Benjamini-Hochberg step-up: take the LARGEST k that passes."""
m = len(pvals)
sorted_idx = np.argsort(pvals)
sorted_p = np.array(pvals)[sorted_idx]
thresholds = (np.arange(1, m + 1) / m) * alpha
max_k = -1
for k in range(m):
if sorted_p[k] <= thresholds[k]:
max_k = k # step-up: keep scanning
rejected = np.zeros(m, dtype=bool)
if max_k >= 0:
rejected[sorted_idx[:max_k + 1]] = True
return rejected
pvals = [0.003, 0.0055, 0.006, 0.02, 0.03, 0.20, 0.35, 0.50, 0.70, 0.90]
print(f"Bonferroni rejects: {bonferroni(pvals).sum()}")
print(f"Holm rejects: {holm(pvals).sum()}")
print(f"BH rejects: {benjamini_hochberg(pvals).sum()}")
这组 的 p 值输出还是 1、3、4。两个循环之间那个 break 和 max_k 的差别,就是 step-down 和 step-up 的全部实现差异,也是整篇文章里最容易写错的两行。
选哪一个
检验数量少(五个以内)、或者你处在需要向监管方交代的确认性分析里,Bonferroni 够用,它的无条件保证和一行实现在这种场合是优点。检验数量到了几十个,就该换 Holm——它假设一样、功效更高,没有理由不换。真正需要想清楚的是 Holm 和 BH 之间的取舍,而这个取舍不是技术问题,是你打算拿结果干什么。
如果这一批检验的输出会直接决定资金投向,你要的是 FWER:宁可漏掉几个真的,也不要在名单里混进一个假的,因为你只会挑一个去部署,而混进来的那个可能正好是你挑中的。如果这一批检验只是在圈定候选、后面还有独立的确认环节(样本外测试、另一个市场上的复现),那 FDR 更合适:你能容忍名单里有 5% 是假的,因为下一关会把它们筛掉,而 FWER 的严格会让你在第一关就丢掉太多真信号。
一句话:FWER 用于终局决策,FDR 用于漏斗上游。
还有一种情况值得单独说:从一批候选里挑一个策略部署。这时上面三个方法都不完全对路。BH 或 Holm 给你的仍然是一个多元素的名单,而”从名单里挑最好的那个”这个动作本身又引入了新的选择偏差,多重检验修正对此一言不发。去膨胀夏普比率和回测过拟合概率(本模块后面两篇)就是为这个决策设计的。
它们共同的盲点
现在说那件更要紧的事。上面三个程序有一个共同点:它们的输入只有 p 值和检验数量 ,从头到尾没有看过你的检验之间有多像。
Bonferroni 按 平分,Holm 按排序调整分母,BH 按排序缩放阈值——三者用的都是排序位置这一个信息,而策略之间 0.95 的相关性和 0.05 的相关性,在它们眼里长得一模一样。
这在生物统计里问题不大,那里的检验往往接近独立。在量化里这是核心问题:50 个动量变体提供的独立信息可能只相当于三五个检验,而这三个程序会一致地把它当成 50 个。Holm 相对 Bonferroni 的那点改进是从排序里榨出来的,不是从依赖结构里榨出来的,所以面对一批高度雷同的策略时,它同样会过度保守。
要利用依赖结构,就得让程序真的去看数据,而不只是看 p 值。这正是 Romano-Wolf 逐步下降法用 bootstrap 做的事,也是它成为下一篇主题的原因。
Storey 的 估计
顺着 BH 那个” 比名义水平更保守”的观察往下走,Storey(2002)提出把 估出来,再用它把阈值放大回去:
- 估计真原假设占比。常用估计量是 ,其中 通常取 0.5——想法是大 p 值那一段几乎全是真原假设贡献的,据此外推总量。
- 用修正阈值 。
明显小于 1 时(也就是相当一部分假设确实为假),这比 BH 更有功效; 接近 1 时它退化回 BH。
量化金融里很少用它,原因是它把方法的表现押在了 上,而这个估计对 的选择敏感,在 只有几十的时候还很不稳。50 到 100 个策略的规模上,BH 已经够用,多这一层估计带来的不确定性未必划算。
逐步下降、逐步上升,以及闭合检验
前面提过 Holm 是 step-down、BH 是 step-up,这两个词值得单独解释一下,因为它们背后是一个一般性的权衡。
逐步下降从最小 p 值往上走,遇到第一个不满足就停。逐步上升从最大 p 值往下走(或者等价地,扫完全部再取最大的满足者),中间的失败不影响后面的成功。
在 FWER 这一侧,Holm(step-down)和 Hochberg(step-up)是一对。功效上 Hochberg 更强:它拒绝的假设是 Holm 的超集。但这个优势不是白拿的——Hochberg 的有效性建立在 Simes 不等式上,需要独立性或 PRDS,而 Holm 在任意依赖下都成立。所以选择又回到那个老问题:你愿不愿意为多几个拒绝,去承担一个关于依赖结构的假设。在策略高度相关、依赖结构又说不清楚的量化场景里,Holm 那个无条件保证通常更值钱。
在 FDR 这一侧,step-up 的 BH 是标准做法,step-down 的 FDR 版本存在但很少用。
再往上还有一层,叫闭合检验:把 个原假设的全部 个非空子集都拿出来,对每个子集检验它的交集原假设,只有当一个原假设在所有包含它的子集上都被拒绝时才最终拒绝它。这个框架能给出 FWER 控制的一般性证明,Holm 其实就是它的一个特例——对每个交集假设用 Bonferroni 做局部检验,化简之后正好得到 Holm 的阈值序列。换更强的局部检验(比如用 Simes 检验,得到的就是 Hommel 程序)能得到更高的功效,代价是计算量。
代价有多大? 时 个子集,直接枚举是不可能的。所以实践中的甜点区间就是 Holm 和 Romano-Wolf:它们都用了排序结构,又都不用付完整闭合检验的账。
因子研究里的阈值
多重检验最有名的一次实际应用,是 Harvey、Liu 和 Zhu(2016)对金融因子文献的清算。他们的论点是:考虑到几十年里被试过的因子数量,传统的 显著性门槛低得不合理。
把 Bonferroni 直接套上去就能看出问题的量级。双侧检验、 时, 个因子对应的阈值是 :
| 因子数量 | 双侧 Bonferroni 的 t 阈值 |
|---|---|
| 1 | 1.96 |
| 20 | 3.02 |
| 100 | 3.48 |
| 316 | 3.78 |
| 1000 | 4.06 |
单看这张表,只要研究者集体试过一百个以上的因子, 就完全够不上显著。
Harvey、Liu 和 Zhu 最终给出的建议门槛是 。值得注意的是这个数字低于同规模的 Bonferroni 阈值,而不是高于——原因是他们并没有停在 Bonferroni 上,还算了 Holm 和 Benjamini-Yekutieli 型的 FDR 阈值,后者比 FWER 阈值宽松;同时他们也承认自己统计到的因子数量是已发表因子的下界,无从知道有多少因子被试过又没被写出来。 是在这些考量之间取的一个可操作的整数。
无论用哪一档,结论方向是一致的:大量 值落在 2 到 3 之间的已发表因子,撑不过任何一种多重检验修正。这也是他们那篇论文最常被引用的含义——已发表的因子里有相当比例可能是假阳性。
顺着这个思路可以画一条发现曲线:横轴是显著性门槛,纵轴是通过门槛的因子数量。一个典型的因子研究项目可能长这样: 时留下 30 个因子, 时 12 个, 时 5 个,Bonferroni 修正后 2 个。这条曲线把”发现数量”和”严谨程度”之间的取舍画在了同一张图上,比任何单个门槛都更有信息量——它让读者自己看到,你的结论对门槛有多敏感。
验证你的实现
多重检验程序的实现 bug(比如把 Holm 的 break 写成了 BH 的扫描)不会报错,只会悄悄给你更多拒绝。有一个便宜的 sanity check:模拟纯零数据。
生成 1000 组”谁都没有优势”的数据,每组跑一遍你的程序。Bonferroni 和 Holm 应该在大约 5% 的模拟里出现至少一个拒绝,也就是名义 FWER;BH 应该让拒绝集合里的假阳性比例平均在 5% 左右。观测值和名义水平差得明显(尤其是偏高),八成不是运气,是实现有问题。
同一套模拟顺便还能告诉你功效:把真信号加进去,看各程序分别找回多少。这比读文献里的功效比较更贴近你自己的数据结构。
修正之外
多重检验修正是给统计问题的统计解药。它管不了前视偏差、容量限制、状态切换和实现细节——一个干干净净通过 Holm 修正的策略,照样可能因为这四样里的任何一样在实盘失败。
它也不告诉你哪些策略有真实优势。它告诉你的是:在把检验次数计入之后,哪些策略的收益还能和基准区分开。这是部署的必要条件,不是充分条件。真正的流程是拿修正后的候选名单去过样本外测试、容量分析、状态分析和带实现摩擦的回测,最后再由 DSR 或 PBO 处理”从名单里挑一个”这个动作本身带来的偏差。
报告的时候,“我测了 100 个策略,5 个显著”是不完整的。读者需要检验数量 、用的什么检验统计量、原始 p 值、修正方法、修正后的 p 值或调整后的显著性水平、被拒绝的假设列表,以及结果对 的敏感性——把 、、 都算一遍列出来。最后这一项尤其重要,因为 往往不是一个客观事实,而是一个判断:你到底”试过”多少个策略?改一次参数算不算一次试验?调一次特征呢?把这个判断的影响摊开给读者看,比替他们选一个数要诚实得多。
参考文献
- Bonferroni, C. E. (1936). “Teoria Statistica delle Classi e Calcolo delle Probabilità.” Pubblicazioni del R Istituto Superiore di Scienze Economiche e Commerciali di Firenze, 8, 3–62.
- Holm, S. (1979). “A Simple Sequentially Rejective Multiple Test Procedure.” Scandinavian Journal of Statistics, 6(2), 65–70.
- Benjamini, Y. and Hochberg, Y. (1995). “Controlling the False Discovery Rate: A Practical and Powerful Approach to Multiple Testing.” Journal of the Royal Statistical Society B, 57(1), 289–300.
- Benjamini, Y. and Yekutieli, D. (2001). “The Control of the False Discovery Rate in Multiple Testing under Dependency.” Annals of Statistics, 29(4), 1165–1188.
- Storey, J. D. (2002). “A Direct Approach to False Discovery Rates.” Journal of the Royal Statistical Society B, 64(3), 479–498.
- Harvey, C. R., Liu, Y., and Zhu, H. (2016). “…and the Cross-Section of Expected Returns.” Review of Financial Studies, 29(1), 5–68。大多数已发表因子可能是假阳性的实证证据。