惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
Cyberwarzone
Cyberwarzone
The GitHub Blog
The GitHub Blog
云风的 BLOG
云风的 BLOG
P
Proofpoint News Feed
小众软件
小众软件
Recent Announcements
Recent Announcements
博客园 - 三生石上(FineUI控件)
Security Archives - TechRepublic
Security Archives - TechRepublic
W
WeLiveSecurity
Cloudbric
Cloudbric
博客园 - 司徒正美
美团技术团队
N
News and Events Feed by Topic
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
PCI Perspectives
PCI Perspectives
宝玉的分享
宝玉的分享
H
Help Net Security
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Google DeepMind News
Google DeepMind News
Help Net Security
Help Net Security
Last Week in AI
Last Week in AI
S
Schneier on Security
N
News | PayPal Newsroom
B
Blog RSS Feed
L
LINUX DO - 最新话题
T
Troy Hunt's Blog
S
Secure Thoughts
雷峰网
雷峰网
aimingoo的专栏
aimingoo的专栏
L
Lohrmann on Cybersecurity
G
Google Developers Blog
Microsoft Azure Blog
Microsoft Azure Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
T
Tenable Blog
S
Securelist
L
LangChain Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
I
InfoQ
H
Heimdal Security Blog
Cisco Talos Blog
Cisco Talos Blog
F
Full Disclosure
Y
Y Combinator Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
K
Kaspersky official blog
T
Tailwind CSS Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
阮一峰的网络日志
阮一峰的网络日志
C
Cisco Blogs

暗无天日

读:AI Agent 安全日志——从可见性与隐私的两难说起 - 暗无天日 AI写作的语言指纹——如何让文字不那么像机器 - 暗无天日 读:50 条 Claude Code 技巧——一个工程经理的六个月使用心得 读:AI 辅助开发为什么让 E2E 测试更有价值 - 暗无天日 读:在Emacs中使用Claude Code(Spacemacs适配版) - 暗无天日 Claude Code 背后的工程哲学——读 Agent Harness Engineering 读:Agent Harness Engineering——AI 智能体不只是模型,还有套件 - 暗无天日 browser-harness:让 AI 直接接管你的浏览器 - 暗无天日 读:Security-First CI/CD —— DevSecOps 自动化实践指南 TIL: 数字小键盘的小数点陷阱与行内算术求值 - 暗无天日 读:Immutability 不是万能药,它是一种权衡 - 暗无天日 Conducty:给 Claude Code 加上项目记忆和并行执行能力 - 暗无天日 读 — GitHub Trending 里的 Claude Code 技能包 读 — Prompt Caching 省钱指南 TIL: Emacs 中那些跟鼠标配合的冷门快捷键 - 暗无天日 读:Anvil——把 Emacs 变成 AI 的工具服务器 读:Emacs 代码折叠终极指南 - 暗无天日 读:Clojure 搭车客指南 - 暗无天日 git推送失败后恢复仓库损坏的完整记录 - 暗无天日 多智能体系统的两个有效模式——以及对 Claude Code 用户的启示 - 暗无天日 用 Org Babel 写 Literate 博文:扩展执行 + 定制导出 proced:Emacs 内置的进程查看器 - 暗无天日 从 proced 定制中学到的 Elisp 模式 读:让 Emacs proced 在 macOS 上显示 CPU 和内存 异步编程的函数着色税 - 暗无天日 链式调用的代价:JavaScript 和 Clojure 的共同教训 - 暗无天日 hyperfine:命令行基准测试工具 - 暗无天日 管道中的变量去哪了?——子 shell 作用域陷阱 - 暗无天日 开源包装器的信任陷阱:四个危险信号 - 暗无天日 程序员愿意为 AI 写文档,却不愿为同事写 - 暗无天日 mktemp: Shell 脚本中临时文件的安全陷阱与最佳实践 - 暗无天日 WSL9x —— 在 Windows 9x 里跑 Linux 内核 6.19 用 ox.el 做你想做的事 —— org-export 高级编程指南 读:Hot-wiring the Lisp Machine —— 用纯 Elisp 构建零依赖的 Org 静态站点生成器 Elisp 性能优化的六个实战教训 - 暗无天日 fcitx5 下 Emacs 无法切换输入法的排查 - 暗无天日 ERT 测试交互命令的三种方式 - 暗无天日 SEM Assistant: 当 Elisp 守护进程遇上 LLM 用 dmsg 给 Elisp 加上结构化调试日志 用 org-habit 追踪非每日习惯 - 暗无天日 Clojure X-Men:当编程语言特性变成超能力 - 暗无天日 TIL: 用 diff-hl 在 fringe 中显示 git 变更 读:llm-test —— 用 LLM agent 驱动 Emacs 测试 TIL: AI 时代的橡皮鸭调试 - 暗无天日 fcitx 启动后键盘输入卡顿的排查 - 暗无天日 TIL: 早期网页的图片热区导航 - 暗无天日 读 Seeing the Whole System 用 Emacs 自动生成每周链接推荐 - 暗无天日 读:ASCII control characters in my terminal 读 What to learn - 暗无天日 Lisp 的括号之痛——一个愚人节玩笑揭开的老伤疤 - 暗无天日 一本书该"线性读"还是"并行读" - 暗无天日 读 How to Monetize a Blog:一篇伪装成变现指南的讽刺文 Python Mock 第三方依赖的四种策略 - 暗无天日 Emacs Lisp 热重载实用指南 - 暗无天日 Prot 的 Emacs 配置哲学 - 暗无天日 TIL: 从直播对谈中学到的三个 Emacs 技巧 - 暗无天日 TIL: 自动使用项目虚拟环境的 Python - 暗无天日 TIL: 让 Help buffer 自动获得焦点 一条命令让本地开发用上 HTTPS —— slim 工具介绍 用 fsck 检查和修复 Linux 文件系统 排查Linux进程"卡死"实战:从strace到gdb全流程 - 暗无天日 PostgreSQL 索引:从基础到你可能不知道的高级用法 - 暗无天日 用 .pdbrc 自定义 Python 调试器 ANSI 转义码的标准化现状 - 暗无天日 终端程序的潜规则 - 暗无天日 PARA Org-mode 测试配置 - 暗无天日 AI越强越辣鸡?控制论说这是必然的 - 暗无天日 AI 越强越需要你盯着——反馈循环实操指南 - 暗无天日 你的AI代理正在偷你的密钥——四种你没想到的泄露通道 - 暗无天日 LLM 在 DevOps 中的三种角色 - 暗无天日 写作风格的反建议 - 暗无天日 反驳本质复杂性——Dan Luu 论为什么《没有银弹》错了 - 暗无天日 文件充满了危险——Dan Luu 谈文件系统的可靠性陷阱 - 暗无天日 AI 时代的 PARA 方法:用 Org-mode 和 AI 打造个人知识管理系统 Linux 数据去重学习笔记 - 暗无天日 创建跨平台 ZIP 文件的隐藏陷阱:Extra Field - 暗无天日 X11 Forwarding 排障指南 - 暗无天日 IP欺骗端口扫描:当别人冒充你去扫描别人 - 暗无天日 Linux 输入栈全景解析:从硬件按键到屏幕响应 - 暗无天日 Unix 系统中那些被埋没的配置开关——以 FontConfig 为例 - 暗无天日 在Linux上限制儿童使用电脑 - 暗无天日 GIF不仅仅是一种图片格式——用GIF流做些奇怪的事 - 暗无天日 Leiningen 学习笔记:Clojure 项目构建与管理从入门到实战配置 - 暗无天日 Google SRE Book 读书笔记 - 暗无天日 yes 管道 head 发生了什么 - 暗无天日 为什么 nohup 在 crontab 中不起作用 Bash中的Indirection与Nameref - 暗无天日 Linux PAM 简介 - 暗无天日 从Linux ISO文件启动计算机 - 暗无天日 用 Bash 打造一个Screen Locker 用GitHub Actions自动构建EGO博客 - 暗无天日 blocking I/O 的作用 - 暗无天日 mobileog 手机端同步提示Error:2 No such file 的解决方法 回收 WSL2 VHDX 文件占用空间 使用 org-mode columnview 生成任务列表 - 暗无天日 Emacs 作为 MPD 客户端 - 暗无天日 移动文件路径却不破坏org file link的方法 - 暗无天日 如何合理的导出help link 成HTML - 暗无天日 笑话理解之Biology - 暗无天日
读:为什么 Discord 把实验指标从 50 个砍到 15 个
lujun9972,Claude Code · 2026-06-07 · via 暗无天日

目录

  • 开篇
  • 多重比较问题
    • 为什么指标多了反而坏事
    • BH 校正:控制冤枉率的代价
  • 模拟验证:看见才能相信
    • 实验设计
    • Python 复现
  • 用 PCA 找冗余
    • 第一步:看相关性
    • 第二步:用 PCA 量化冗余
  • 结果

开篇

做 A/B 测试的时候,总觉得多加几个指标没坏处,多看几个维度总归能多发现点什么。

Discord 的数据团队一开始也这么想。他们的默认指标列表从几个膨胀到了约 50 个,每个团队都往里塞自己关心的指标,几乎没人删过。直到他们撞上一个反直觉的事实,指标加得越多,反而越难检测到真实的效果。

这篇博文解读 Discord 怎么用统计模拟和 PCA(主成分分析)验证了这个规律,最后把实验指标从 ~50 砍到 ~15,真阳性检出率(recall)反而提升了约 45%。

多重比较问题

为什么指标多了反而坏事

先说一个基础概念:p 值衡量的是"这个结果有多大概率是碰巧出现的"。p 值越小,说明结果越不像是随机波动造成的,越像是真实效果。通常 p < 0.05 就认为"显著"(只有 5% 的概率是碰巧)。

现在假设你做了一次 A/B 测试,跟踪 100 个指标,每个指标都设 p 值阈值 0.05。就算实验组没有任何真实效果,纯粹因为随机波动,100 个指标里平均会有 5 个"碰巧 p 值小于 0.05"。这就是 *多重比较问题*(multiple comparisons problem),同时检验的东西越多,其中至少有一个碰巧"看起来显著"的概率就越高。

  • *假发现率*(False Discovery Rate,FDR):所有被标记为"有显著效果"的指标中,有多少是冤枉的。FDR 控制在 5% 意味着,每 20 个"显著"的指标中,平均不超过 1 个是假的
  • *recall*(真阳性检出率):所有真实存在的效果中,成功检测到了多少。recall 为 60% 意味着,10 个真实效果里只抓到了 6 个

这两个指标天生矛盾。降低 FDR(少冤枉人)往往意味着降低 recall(多漏掉真效果),BH 校正就是在这两者之间找平衡。

BH 校正:控制冤枉率的代价

Discord 使用 Benjamini-Hochberg(BH)校正来控制 FDR,思路是这样的。

  1. 把所有指标的 p 值从小到大排序
  2. 排名第 i 的指标,阈值设为 =i × 0.05 / n=(n 是指标总数)
  3. 只有 p 值低于对应阈值的指标才被判为"显著"

排名越靠前(i 值越小),指标总数约大(n值越大)面对的门槛越严格, 真正有效果的指标也越容易被漏掉。

一句话总结这个两难,不校正,假阳性太多;校正了,真效果容易被埋没。与其琢磨更精妙的统计方法,不如直接减少指标数量,让校正不那么激进。

模拟验证:看见才能相信

Discord 没有停留在理论分析,他们用模拟实验直接看数字怎么走。

实验设计

他们模拟了 50000 次实验,每次包含 20 个指标。

  • 19 个"噪声指标":从正态分布 N(0, 1) 中随机抽取,代表没有真实效果的指标
  • 1 个"真实效果指标":从 N(2.8, 1) 中抽取,z 值 2.8 对应约 -5.2% 的变化,来自一次真实实验的数据

然后他们在不同的指标总数下重复模拟,观察假阳性率和 recall 的变化。

Python 复现

下面用 Python 复现这个核心模拟(为加快运行速度,这里用 5000 次模拟代替原文的 50000 次)。

import numpy as np
from scipy.stats import norm

def bh_reject(p_values, alpha=0.05):
    """Benjamini-Hochberg 校正,返回哪些假设被拒绝"""
    n = len(p_values)
    sorted_idx = np.argsort(p_values)
    sorted_pvals = p_values[sorted_idx]
    rejected = np.zeros(n, dtype=bool)
        for k in range(n, 0, -1):
        if sorted_pvals[k-1] <= k * alpha / n:
            rejected[sorted_idx[:k]] = True
            break
    return rejected

def simulate(n_metrics, effect_z=2.8, n_sims=5000):
    """模拟 n_metrics 个指标的实验,其中 1 个有真实效果"""
    false_alarms = 0
    detections = 0
    for _ in range(n_sims):
                null_z = np.random.normal(0, 1, n_metrics - 1)
        null_p = 2 * (1 - norm.cdf(np.abs(null_z)))
                effect_z_val = np.random.normal(effect_z, 1)
        effect_p = 2 * (1 - norm.cdf(np.abs(effect_z_val)))
                all_p = np.append(null_p, effect_p)
        rejected = bh_reject(all_p, alpha=0.05)
                if np.any(rejected[:n_metrics-1]):
            false_alarms += 1
                if rejected[-1]:
            detections += 1
    return false_alarms / n_sims, detections / n_sims

print(f"{'指标数':>6s}  {'假阳性率':>8s}  {'recall':>6s}")
print("-" * 30)
for n in [5, 10, 20, 50, 100]:
    far, rec = simulate(n)
    print(f"{n:6d}  {far:8.3f}  {rec:6.3f}")
   指标数      假阳性率  recall
------------------------------
     5     0.062   0.599
    10     0.067   0.504
    20     0.073   0.426
    50     0.067   0.329
   100     0.069   0.258

数字很清楚,从 5 个指标增加到 100 个,recall 从 60% 跌到 26%,腰斩不止。BH 校正面对更多指标时必须加码控制 FDR,结果连真效果也被压下去了。

Discord 面对的正是这个困境,50 个默认指标意味着大量真实效果被 BH 校正埋没。

用 PCA 找冗余

既然"指标越少越好"在统计上站得住,下一个问题就是删哪些。不能瞎删,得先搞清楚哪些指标在测量同一件事。Discord 分两步走。

第一步:看相关性

他们先计算各指标在不同实验中的处理效果相关性。如果两个指标在所有实验中都朝同一个方向变化,说明它们很可能在测量同一件事。

import numpy as np
from itertools import combinations

np.random.seed(42)
n_exp = 200

factor = np.random.normal(0, 1, n_exp)
correlated = np.array([factor + np.random.normal(0, 0.3, n_exp)
                        for _ in range(6)])
independent = np.random.normal(0, 1, (2, n_exp))

metrics = np.vstack([correlated, independent])
labels = [f"metric_{i}" for i in range(8)]

print("高相关指标对(|r| > 0.7):")
for i, j in combinations(range(8), 2):
    r = np.corrcoef(metrics[i], metrics[j])[0, 1]
    if abs(r) > 0.7:
        print(f"  {labels[i]} <-> {labels[j]}: r = {r:.3f}")
高相关指标对(|r| > 0.7):
  metric_0 <-> metric_1: r = 0.905
  metric_0 <-> metric_2: r = 0.900
  metric_0 <-> metric_3: r = 0.901
  metric_0 <-> metric_4: r = 0.902
  metric_0 <-> metric_5: r = 0.909
  metric_1 <-> metric_2: r = 0.915
  metric_1 <-> metric_3: r = 0.908
  metric_1 <-> metric_4: r = 0.891
  metric_1 <-> metric_5: r = 0.917
  metric_2 <-> metric_3: r = 0.925
  metric_2 <-> metric_4: r = 0.902
  metric_2 <-> metric_5: r = 0.911
  metric_3 <-> metric_4: r = 0.914
  metric_3 <-> metric_5: r = 0.909
  metric_4 <-> metric_5: r = 0.900

metric_0 到 metric_5 两两高度相关(r > 0.85),它们测量的是同一个底层概念。留一个就够了,删掉其余 5 个几乎不丢信息。

但相关性只能看两两关系。如果一群指标之间的关系错综复杂(A 和 B 相关,B 和 C 相关,但 A 和 C 相关性弱),两两分析就不够了。需要更全局的工具。

第二步:用 PCA 量化冗余

PCA(主成分分析,Principal Component Analysis)回答一个更本质的问题,这些指标里到底有几个是真正独立的?

直观理解 PCA:如果你的 8 个指标其实都在测量同一件事,PCA 会告诉你"大部分变化可以被 1 个主成分解释"。如果有 3 个独立维度,前 3 个主成分就能覆盖大部分方差。

from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
metrics_scaled = scaler.fit_transform(metrics.T)
pca = PCA()
pca.fit(metrics_scaled)

print("各主成分解释的方差比例:")
cum = 0
for i, v in enumerate(pca.explained_variance_ratio_):
    cum += v
    print(f"  PC{i+1}: {v:.3f} (累计 {cum:.3f})")
各主成分解释的方差比例:
  PC1: 0.696 (累计 0.696)
  PC2: 0.132 (累计 0.828)
  PC3: 0.114 (累计 0.942)
  PC4: 0.014 (累计 0.957)
  PC5: 0.013 (累计 0.970)
  PC6: 0.011 (累计 0.981)
  PC7: 0.010 (累计 0.991)
  PC8: 0.009 (累计 1.000)

第 1 个主成分就解释了约 70% 的方差,前 3 个覆盖了 94%。8 个指标里,真正独立的维度只有 2 到 3 个,其余都是重复测量。

Discord 在真实数据上发现了同样的规律,大量 engagement 相关的指标塌缩到了同一个主成分上。这给了他们信心,去掉冗余指标不会丢多少信号。

需要说明的是,PCA 和相关性分析不能告诉你具体删哪个指标,那是业务判断的事。但它们能告诉你哪些指标在测量同一件事,让你有数据支撑地做减法,别凭感觉。

结果

Discord 最终的操作分两步。

  1. 合并平台拆分,把按平台(iOS/Android/Desktop)拆开的指标合并为父指标
  2. 去掉冗余 engagement 指标,PCA 显示它们其实测量的是同一件事

结果,默认指标从 ~50 个削减到 ~15 个,检测一个中等大小真实效果的 recall 提升了约 45%。

说到底就一句话,做减法之前先量化冗余。相关性分析和 PCA 给你量化工具,告诉你哪些指标在测量同一件事。有了数据,删起来才有底气,不至于拍脑袋。