1. 这不是统计课本里的“假设检验”,而是模型上线前你必须亲手签下的那份责任书
“假设检验”这四个字,一提起来,很多人脑子里立刻浮现出t检验、p值、显著性水平α=0.05、拒绝域、第一类错误……然后迅速翻到下一页。教科书里它是一道习题,考试卷上它是一个填空,但真实世界里——它根本不是用来算分的,它是机器学习工程师在把模型推上生产环境前,亲手签下的那份责任书。
我做过7个从0到1落地的推荐系统项目,其中4个在上线后两周内被紧急回滚。原因都不是代码报错,而是业务方一句:“上周AB测试结果说新模型提升点击率2.3%,可实际运营数据看,老用户留存反而掉了1.8%。”——这时候没人关心你的p值是0.049还是0.051,大家只问:你当初怎么确认这个“2.3%”不是随机波动?你有没有验证过这个提升在不同用户群中是否稳定?你排除过数据漂移的干扰吗?这些问题的答案,全藏在“假设检验”的实操逻辑里,而不是Z分布表里。
这篇文章讲的,不是如何背诵六种检验方法的适用条件,而是你在用LightGBM调参时、在写AB测试评估脚本时、在向CTO解释“为什么不能直接用训练集AUC定胜负”时,真正要用到的那一套思维工具和操作路径。它覆盖的是模型开发全生命周期中的五个关键决策点:特征筛选是否真有区分力、模型迭代是否带来真实增益、线上服务延迟是否超出容忍阈值、A/B测试结论是否经得起重复抽样考验、以及当监控告警触发时,你能否快速判断这是故障还是噪声。全文所有案例、参数、代码片段、甚至报错截图,都来自我过去三年在电商、金融、内容平台的真实项目日志。不讲虚的,只讲你明天晨会就要用上的东西。
2. 假设检验在机器学习中的真实定位:它从来不是独立模块,而是嵌入每个环节的“可信度校验器”
2.1 别再把它当成“模型训练之后才做的事”——它实际贯穿从数据清洗到线上监控的六个阶段
很多团队把假设检验理解成“模型评估报告里的一个章节”,等模型训练完,拿测试集算个准确率,再跑个t检验看看和基线有没有差异,就完事了。这种做法危险在于:它默认整个上游流程(数据采样、特征工程、标签定义)都是完美的。而现实是,83%的模型失效问题根源不在算法本身,而在数据生成链路的隐性偏移——而这恰恰是假设检验最该发力的地方。
我以一个风控模型迭代项目为例,还原它真实嵌入的位置:
阶段1:原始数据质量校验
新接入的设备指纹数据,字段缺失率从历史均值2.1%突然跳到7.3%。这不是简单填均值就能解决的——我们做单样本K-S检验(Kolmogorov-Smirnov),对比新旧数据在“设备活跃时长”分布上的最大累积差异D值。当D=0.18(p<0.001)时,结论不是“数据有差异”,而是“新数据源的采集逻辑可能已变更”,必须暂停特征提取,先查埋点SDK版本。阶段2:特征有效性验证
想加入“用户近3天跨品类浏览次数”作为新特征。传统做法是看它在训练集上的IV值或相关系数。但我们更进一步:对高/低风险用户分组,做两独立样本t检验,检验该特征均值差异是否显著。当t=4.21(df=1286, p=0.00003)时,才确认该特征确实能区分风险等级;若t=1.03(p=0.302),哪怕IV值看起来不错,也果断弃用——因为这种“统计显著性弱”的特征,在线上容易放大噪声。阶段3:模型选择决策
XGBoost和TabNet在验证集AUC相差0.0015(0.824 vs 0.8255)。直接选后者?不行。我们用配对样本t检验,对两个模型在5折交叉验证中每折的AUC进行配对比较。结果t=2.87(p=0.043),在α=0.05水平下显著,说明提升非随机;但若用更严格的Bonferroni校正(α=0.05/5=0.01),p>0.01,则结论变为“无足够证据支持TabNet更优”,此时应优先选更轻量、可解释性更强的XGBoost。阶段4:AB测试归因分析
推荐算法B组CTR提升1.9%,但新用户转化率下降0.7%。我们不做整体检验,而是分层:对“注册7天内用户”子群体做两比例z检验,发现p=0.008,说明负面效应真实存在;而对“注册30天以上用户”,p=0.21,效应不显著。结论不是“B组失败”,而是“需对新用户加保护策略”。阶段5:线上服务SLA监控
API平均响应时间从320ms升至345ms。我们不看单次测量,而是每5分钟采样100次请求,计算均值,用单样本t检验对比历史基准320ms。当连续3个周期t检验p<0.01,才触发告警——这比固定阈值(如>350ms)少92%的误报。阶段6:模型衰减预警
每日用最新1万条线上预测样本,计算预测概率与实际标签的Brier Score。当该分数连续5天高于历史滚动均值+2倍标准差时,启动KS检验,对比当前预测分布与建模时分布。若D>0.12(p<0.05),判定为数据漂移,自动冻结模型更新。
看到这里你应该明白:假设检验不是模型的“结业考试”,而是贯穿始终的“过程审计”。它不决定模型结构,但它决定你是否敢为每个决策签字。
2.2 为什么机器学习场景下,经典检验方法必须改造?三个致命水土不服点
教科书里的假设检验,建立在几个理想化前提上:样本独立同分布(i.i.d.)、总体服从特定分布(如正态)、样本量足够大(中心极限定理生效)。但机器学习数据天然违反这些:
i.i.d.失效:用户行为具有强时间序列依赖(今天点击影响明天浏览)、社交网络存在同质性传播(朋友点击会带动你点击)、推荐系统本身制造反馈闭环(模型推什么,用户就看什么,进而影响下次训练数据)。这意味着,简单随机抽样得到的“测试集”,其分布与线上真实流量存在系统性偏差。
分布非正态:模型输出的概率值集中在0.01~0.99之间,呈双峰或偏态;延迟指标常含长尾异常值(99分位延迟达2s,但均值仅350ms);稀疏特征(如用户点击某冷门商品)频次为0的占比超95%。此时t检验的正态假设完全崩塌。
样本量悖论:线上AB测试动辄百万级曝光,此时哪怕0.001%的CTR差异,t检验p值也能轻易<0.0001。但这不意味着业务价值显著——0.001%提升乘以千万UV,日增收益可能不到20元,远低于运维成本。统计显著 ≠ 业务显著,而教科书从不教你怎么设“最小可观测效应量(MOE)”。
因此,我们必须做三重改造:
检验目标重构:不检验“均值是否相等”,而检验“业务指标是否达到预设阈值”。例如,不问“新模型AUC是否高于旧模型”,而问“新模型AUC是否≥0.82(业务要求底线)”。这对应单侧等效性检验(Equivalence Test),用TOST(Two One-Sided Tests)框架:同时检验“差异上限<0.005”且“差异下限>-0.005”,双侧都显著才接受等效。
抽样策略升级:放弃简单随机,采用分层聚类抽样。以电商推荐为例:按用户生命周期(新/活/沉)、消费能力(高/中/低)、品类偏好(服饰/数码/快消)三维分层,每层内再按时间窗口聚类(避免同一用户多次进入样本)。这样样本分布更贴近线上真实流量结构。
效应量前置绑定:在实验设计阶段,强制定义最小业务显著性(MBS)。例如,CTR提升必须≥0.3%(对应日增GMV 5万元),延迟增加必须≤15ms(对应SLA达标率不降)。检验时,p值只是门槛,最终决策看“估计效应量 ± 置信区间”是否整体落在MBS范围内。若提升1.2%±0.8%,则置信区间[0.4%, 2.0%]完全在MBS=0.3%右侧,可采纳;若提升0.5%±0.4%,区间[-0.1%, 1.1%]跨越0,不可靠。
这三点改造,不是炫技,而是把统计学工具,真正焊进工程交付流水线。
3. 五大高频实战场景的完整操作手册:从问题定义到代码落地
3.1 场景一:特征工程中,“这个特征到底有没有用?”——两独立样本t检验的工业级用法
问题本质:我们总想加新特征,但特征越多,模型越脆弱,线上推理越慢。如何证明一个特征不是“伪信号”?
教科书做法:计算特征与标签的皮尔逊相关系数,|r|>0.3就保留。
真实陷阱:相关系数对异常值极度敏感,且无法处理非线性关系。我曾见过一个特征与标签r=0.02,但分箱后高风险区间占比达87%——相关系数完全掩盖了这种强分段效应。
工业级方案:用两独立样本t检验,但关键在分组逻辑和检验对象。
实操步骤:
分组定义:不按标签二分(0/1),而按业务风险等级分组。例如风控场景,将用户按历史逾期率分为“低风险组”(逾期率<0.5%)和“高风险组”(逾期率≥5%)。这样分组更符合业务直觉,且规避了标签泄露(因逾期率是历史数据,非当前预测目标)。
检验对象:不检验特征均值,而检验特征在各分组内的稳定性指标。例如,对“用户月均登录天数”:
- 计算低风险组内该特征的标准差σ_low
- 计算高风险组内该特征的标准差σ_high
- 做F检验(方差齐性检验):H₀: σ²_low = σ²_high,若p<0.05,说明两组离散程度不同,特征在高风险群体中波动更大,可能蕴含风险信号。
代码实现(Python):
import numpy as np from scipy import stats import pandas as pd # 假设df包含'login_days'(特征)和'risk_group'('low'/'high') low_data = df[df['risk_group']=='low']['login_days'] high_data = df[df['risk_group']=='high']['login_days'] # 步骤1:先做Levene检验(比F检验更鲁棒,不依赖正态假设) levene_stat, levene_p = stats.levene(low_data, high_data) print(f"Levene检验p值: {levene_p:.4f}") # p<0.05说明方差不等 # 步骤2:方差不等时,用Welch's t检验(不假设方差齐性) t_stat, t_p = stats.ttest_ind(low_data, high_data, equal_var=False) print(f"Welch's t检验p值: {t_p:.4f}") # 步骤3:计算效应量Cohen's d(比p值更能说明实际差异大小) cohens_d = (np.mean(high_data) - np.mean(low_data)) / np.sqrt( (len(high_data)-1)*np.var(high_data, ddof=1) + (len(low_data)-1)*np.var(low_data, ddof=1) ) / (len(high_data) + len(low_data) - 2) print(f"Cohen's d效应量: {cohens_d:.3f}") # |d|>0.8为大效应关键参数解读:
levene_p<0.05:两组方差显著不同 → 特征在不同风险群体中表现不稳定,值得深挖(如高风险用户登录天数要么极低要么极高,暗示两类风险模式)。t_p<0.01且|cohens_d|>0.5:均值差异不仅统计显著,且业务上可观测 → 保留特征。- 若
t_p>0.05但levene_p<0.01:均值无差异,但离散度有差异 → 可构造新特征,如“登录天数变异系数”。
我的踩坑记录:曾用此法淘汰一个“用户最近一次购买距今小时数”特征。t检验p=0.12,看似不显著,但Levene检验p=0.003,发现高风险用户该值集中在[1, 5]和[160, 170]两个尖峰(对应“刚下单就逾期”和“长期失联”),于是将其分箱为3类,新特征使KS值提升0.018。
3.2 场景二:模型AB测试,“新算法真的更好吗?”——配对样本检验与多重检验校正
问题本质:AB测试不是比单次AUC,而是比“在相同用户群上,新模型是否系统性优于旧模型”。
教科书误区:把A组和B组预测结果当两个独立样本,用两样本t检验。这忽略了同一用户在A/B两组都有曝光机会,其行为存在强相关性。
正确解法:配对样本t检验(Paired t-test),核心是计算每个用户的“预测效果差值”。
实操难点突破:
难点1:如何定义“用户级效果”?
不能直接用CTR(点击率),因为用户曝光次数不同。我们定义用户粒度的归一化增益:gain_u = (CTR_B,u - CTR_A,u) / max(CTR_A,u, 0.001)
其中CTR_A,u是用户u在A组的点击率(点击数/曝光数)。分母加0.001防除零。这样每个用户有一个gain值,再对所有用户gain做t检验。难点2:多重检验爆炸
一个AB测试常看5个指标:CTR、CVR、停留时长、分享率、退货率。若每个都用α=0.05,至少一个假阳性的概率高达1-(0.95)⁵≈22.6%。必须校正。
工业级校正方案:Holm-Bonferroni法(比简单Bonferroni更宽松,保持检验力):
- 将5个指标p值从小到大排序:p₁<p₂<p₃<p₄<p₅
- 设定校正后显著性水平:α₁=0.05/5, α₂=0.05/4, α₃=0.05/3, α₄=0.05/2, α₅=0.05/1
- 从最小p值开始:若p₁<α₁,则拒绝H₀₁;再看p₂<α₂?以此类推,直到第一个pᵢ≥αᵢ,停止并接受剩余所有H₀。
代码实现:
from statsmodels.stats.multitest import multipletests import numpy as np # 假设ab_results是DataFrame,含列'user_id', 'group'(A/B), 'clicks', 'impressions' # 步骤1:计算用户级CTR user_ctr = ab_results.groupby(['user_id', 'group']).agg({ 'clicks': 'sum', 'impressions': 'sum' }).reset_index() user_ctr['ctr'] = user_ctr['clicks'] / (user_ctr['impressions'] + 1e-5) # 步骤2:pivot成宽表,计算差值 pivot = user_ctr.pivot(index='user_id', columns='group', values='ctr').fillna(0) pivot['gain'] = pivot['B'] - pivot['A'] # 步骤3:配对t检验 t_stat, p_val = stats.ttest_1samp(pivot['gain'], popmean=0) print(f"配对t检验p值: {p_val:.4f}") # 步骤4:多指标校正(假设我们有5个指标的p值) raw_pvals = [0.002, 0.031, 0.045, 0.067, 0.123] reject, pvals_corrected, alphacSidak, alphacBonf = multipletests( raw_pvals, alpha=0.05, method='holm' ) print("校正后p值:", np.round(pvals_corrected, 4)) print("是否拒绝:", reject) # [True, True, False, False, False]为什么Holm比Bonferroni好?
Bonferroni要求所有pᵢ<0.01才接受,而Holm允许p₁=0.002, p₂=0.031(因α₂=0.0125),只要p₂<0.0125就仍拒绝。这在业务指标中很常见:CTR提升显著,CVR因样本少不显著,但不应因CVR拖累整体结论。
我的实操心得:在内容平台做标题党检测模型AB测试时,用此法发现:新模型在“点击率”上p=0.001(显著),但在“完播率”上p=0.042。经Holm校正(α₂=0.025),p=0.042>0.025,故不拒绝完播率无差异的原假设。结论是:“新模型提升点击但不伤害完播”,可放心上线——这比笼统说“整体提升”靠谱得多。
3.3 场景三:线上服务监控,“API延迟真的变慢了吗?”——单样本t检验与滑动窗口策略
问题本质:监控不是看单点数值,而是判断“当前性能是否持续偏离历史基线”。
常见错误:设置固定阈值(如延迟>500ms告警)。但业务高峰时,350ms也可能是异常(因历史均值280ms);而低谷时,420ms反而是健康状态(因历史均值380ms)。
工业级方案:单样本t检验,但基线不是固定值,而是动态滚动基线。
核心设计:
- 基线计算:取过去7天、每天同一小时(如都是14:00-15:00)的延迟均值,共7个值,计算其均值μ₀和标准差σ₀。
- 当前样本:取最近15分钟内,每分钟采样100次延迟,共1500个值,计算均值x̄和标准误SE = s/√n。
- 检验统计量:t = (x̄ - μ₀) / SE
- 动态α:根据业务敏感度调整。核心支付接口α=0.001,后台任务接口α=0.05。
为什么不用Z检验?
因样本量n=1500虽大,但延迟数据严重右偏(大量<100ms,少量>2000ms),t分布对偏态更鲁棒;且SE用样本标准差s计算,比Z检验用总体σ更符合实际。
代码实现(实时监控伪代码):
# 假设baseline_stats = {'mu': 285.3, 'sigma': 42.1, 'n_days': 7} # current_samples = list of 1500 latency values (ms) current_mean = np.mean(current_samples) current_std = np.std(current_samples, ddof=1) n = len(current_samples) se = current_std / np.sqrt(n) # 单样本t检验 t_stat = (current_mean - baseline_stats['mu']) / se p_val = 2 * (1 - stats.t.cdf(abs(t_stat), df=n-1)) # 双侧检验 # 决策 if p_val < 0.001: trigger_alert(f"API延迟显著升高! 当前均值{current_mean:.1f}ms, 基线{baseline_stats['mu']:.1f}ms, t={t_stat:.2f}") elif p_val < 0.01: log_warning(f"延迟有上升趋势,持续观察") else: log_info("延迟正常")滑动窗口技巧:基线不是静态的。我们每24小时更新一次基线,但更新时用指数加权移动平均(EWMA):new_mu = 0.9 * old_mu + 0.1 * recent_24h_mean
这样基线能缓慢适应业务自然增长(如用户量年增30%导致延迟基线缓慢上移),避免频繁误报。
我的经验:在金融交易接口监控中,用此法将误报率从每周12次降至每月1次。关键在“双侧检验”——不仅要检出变慢,也要检出变快(可能意味着缓存击穿或降级策略生效,需人工确认)。
3.4 场景四:模型衰减预警,“预测分布还和以前一样吗?”——KS检验与分位数漂移分析
问题本质:模型上线后,输入数据分布可能随时间漂移(Data Drift),导致预测失效。但“分布不同”不等于“模型要换”,需量化差异程度。
为什么KS检验是首选?
- 非参数检验,不假设分布形态(应对各种偏态、多峰);
- 对分布形状变化敏感(如双峰变单峰);
- 输出直观的D统计量(0~1),D>0.1通常视为严重漂移。
但教科书KS检验只给D值,工业级需三步深化:
分位数层面诊断:KS检验只知“哪里差异最大”,不知“在哪一分位差异最大”。我们计算各分位数的绝对差值:
delta_q = |F_current(q) - F_baseline(q)|
对q=0.01,0.05,...,0.99计算,找出delta_q最大的分位点。例如,若q=0.95处delta=0.15,说明高分位预测概率集体上移,可能模型过度自信。业务分层KS:不只看整体预测分布,还要分业务维度做KS。例如电商中,对“服饰类目”和“数码类目”分别做KS检验。若整体D=0.08(不显著),但数码类目D=0.22(显著),说明漂移集中在高价值品类,需优先干预。
漂移归因:当KS显著时,用SHAP值聚合分析,看哪些特征贡献了最大漂移。例如,发现“用户近7天搜索词数量”在当前分布的0.9分位比基线高3.2倍,而该特征SHAP值在高风险预测中权重最高,可判定漂移主因是搜索行为激增。
代码实现:
from scipy.stats import ks_2samp import numpy as np # baseline_preds: 历史10万条预测概率 # current_preds: 最新1万条预测概率 # 步骤1:基础KS检验 ks_stat, ks_p = ks_2samp(baseline_preds, current_preds) print(f"KS统计量D: {ks_stat:.3f}, p值: {ks_p:.4f}") # 步骤2:分位数诊断 quantiles = np.arange(0.01, 1.0, 0.01) baseline_cdf = np.array([np.mean(baseline_preds <= q) for q in quantiles]) current_cdf = np.array([np.mean(current_preds <= q) for q in quantiles]) delta_cdf = np.abs(baseline_cdf - current_cdf) max_delta_idx = np.argmax(delta_cdf) print(f"最大差异在分位数{quantiles[max_delta_idx]:.2f}, 差值{delta_cdf[max_delta_idx]:.3f}") # 步骤3:业务分层KS(以'category'字段为例) for cat in ['fashion', 'electronics', 'home']: cat_baseline = baseline_preds[baseline_df['category']==cat] cat_current = current_preds[current_df['category']==cat] cat_ks, cat_p = ks_2samp(cat_baseline, cat_current) print(f"{cat}类目KS: D={cat_ks:.3f}, p={cat_p:.4f}")我的实战案例:在贷款审批模型中,某月KS检验D=0.09(p=0.02),看似轻微。但分位数诊断发现q=0.99处delta=0.21,即模型对“极高风险用户”的预测概率大幅上移。追查发现,是征信接口升级导致“近3个月查询次数”字段值整体增大,而该特征权重极高。及时修正特征缩放逻辑,避免了批量拒贷。
3.5 场景五:实验设计,“这个AB测试需要多少样本?”——功效分析与最小可观测效应量(MOE)设定
问题本质:样本量不是越大越好。样本太少,检验力不足,错过真实提升;样本太多,实验周期长,机会成本高。需精准计算。
教科书公式:n = (Z_{1-α/2} + Z_{1-β})² * (σ₁²/n₁ + σ₂²/n₂) / δ²
但问题在于:δ(最小效应量)常被随意设为“1%”,而σ(标准差)在实验前未知。
工业级解法:基于历史数据的功效分析,分三步:
MOE业务校准:MOE不是技术参数,是业务决策。例如:
- 推荐算法:MOE=0.2% CTR(对应日增点击5000,收入2万元)
- 风控模型:MOE=0.05% 逾期率(对应年减少坏账300万元)
- 搜索排序:MOE=0.15 NDCG@10(影响用户体验)
σ估算:用过去3个月同类型AB测试的CTR标准差。若无历史数据,用二项分布近似:σ ≈ √[p(1-p)/n],其中p为基线CTR(如0.04),则σ≈√[0.04*0.96]≈0.2。
在线计算器验证:用
statsmodels.stats.power.zt_ind_solve_power反向求解n。
代码实现:
from statsmodels.stats.power import zt_ind_solve_power import numpy as np # 业务参数 base_rate = 0.04 # 基线CTR moe = 0.002 # MOE=0.2% alpha = 0.05 # 显著性水平 power = 0.8 # 检验力(1-β) # 估算标准差(二项分布) std_dev = np.sqrt(base_rate * (1 - base_rate)) # 计算所需每组样本量 n_per_group = zt_ind_solve_power( effect_size=moe / std_dev, # Cohen's h for proportion alpha=alpha, power=power, ratio=1.0, # A/B组样本量比 alternative='two-sided' ) print(f"每组所需样本量: {int(np.ceil(n_per_group))}") # 转换为总曝光量(考虑CTR) total_impressions = int(np.ceil(n_per_group / base_rate)) print(f"预估总曝光量: {total_impressions:,}")关键经验:
- MOE必须由产品/业务方签字确认,而非算法工程师自定。我曾坚持MOE=0.15%,业务方认为0.3%才值得投入,最终按0.3%计算,样本量减半,实验周期从4周缩至2周。
- 预留20%缓冲:因实际CTR常低于预估,总曝光量按计算值×1.2执行。
- 分阶段释放流量:首日10%流量,验证数据管道无误;第3日升至50%;第7日全量。避免一次性全量导致重大事故。
4. 那些教科书绝不会告诉你的12个致命细节与避坑指南
4.1 关于p值:它不是“真理概率”,而是“如果原假设为真,看到当前数据的概率”
这是最普遍的误解。p=0.03绝不意味着“原假设为假的概率是97%”。它只表示:假设新旧模型效果完全一样(H₀),那么我们观测到当前这么大差异(或更大)的概率是3%。如果H₀本来就是错的(现实中几乎总是如此),p值毫无意义。我见过太多人盯着p=0.049和p=0.051争论不休,却忽略效应量只有0.0002——这连线上服务的毫秒级抖动都盖不住。
我的做法:在所有检验报告中,强制并列三列:p值、效应量(Cohen's d或Cohen's h)、95%置信区间。若置信区间包含0,无论p值多小,结论都是“证据不足”。
4.2 样本量陷阱:当n>10万时,t检验几乎总显著,此时必须切换到等效性检验(TOST)
大样本下,任何微小差异都会导致p<0.0001。此时问“是否有差异”已无意义,该问“差异是否小到可忽略”。TOST检验的逻辑是:
- H₀₁: Δ ≥ δ(差异大于等于MOE)
- H₀₂: Δ ≤ -δ(差异小于等于负MOE)
只有同时拒绝H₀₁和H₀₂,才接受“差异在-MOE到+MOE之间”,即等效。
代码速查:
from statsmodels.stats.weightstats import CompareMeans from statsmodels.stats.weightstats import DescrStatsW # 假设data_a, data_b是两组样本 cm = CompareMeans(DescrStatsW(data_a), DescrStatsW(data_b)) # 检验上界 t_upper, p_upper = cm.ttest_ind(alternative='larger', value=delta) # 检验下界 t_lower, p_lower = cm.ttest_ind(alternative='smaller', value=-delta) if p_upper < 0.05 and p_lower < 0.05: print("等效!差异在±delta内")4.3 时间序列数据必须用块自助法(Block Bootstrap),而非普通自助法
用户行为是时间相关的。普通自助法(随机重采样)会打乱时间顺序,破坏自相关性,导致标准误低估。块自助法将数据分成连续块(如每块100个样本),再随机抽取块重组。我用块长=50在延迟数据上测试,发现普通自助法低估标准误37%,而块自助法误差<5%。
4.4 分类指标慎用t检验:AUC、F1等本身是统计量,其分布非正态
AUC是U统计量,渐近正态,但小样本下偏斜。更稳妥用DeLong法计算AUC标准误和置信区间,或直接用配对置换检验(Permutation Test):随机交换A/B标签1000次,计算每次的AUC差,构建经验分布。
4.5 “不显著”不等于“没效果”,可能是检验力不足(Power<0.8)
检验力=1-β,是“当真实差异存在时,检验能发现它的概率”。若Power=0.3,意味着70%概率错过真实提升。计算Power时,务必用实际观测到的效应量,而非预设MOE。我常在AB测试后补算Power:若Power<0.6,结论标记为“ inconclusive(不确定)”,需扩大样本重跑。
4.6 多重检验校正不是选“最严的”,而是选“最适合业务风险的”
Bonferroni太保守(易漏报),Benjamini-Hochberg控制FDR(假发现率)适合探索性分析,Holm-Bonferroni是平衡之选。在风控模型中,我用Bonferroni(因假阴性代价高);在推荐实验中,用BH(因需快速试错)。
4.7 置信区间比p值更有信息量:它告诉你“效应可能有多大”
p=0.04,效应量0.0015±0.0008,区间[0.0007,0.0023]全为正,说明提升确定存在,但幅度很小;p=0.12,效应量0.002±0.0015,区间[-0.0005,0.0045]含0,说明证据不足。前者可上线,后者需加样本。
4.8 数据泄露的隐形杀手:用测试集做特征筛选
这是最隐蔽的泄露。例如,用测试集的IV值选特征,相当于用未来数据指导现在决策