协作AI功能的产品实验:基于LLM工具的Python集群随机化

TL;DR · AI 摘要
协作AI功能的产品实验面临用户间相互影响问题,传统的用户级A/B测试会因共享工件传播、工作流干扰和网络采用而失效,必须采用集群随机化方法解决协作者污染陷阱。
核心要点
- 协作AI功能的用户级A/B测试违反稳定单位处理值假设(SUTVA),导致控制组结果被污染
- 集群随机化通过在工作区级别分配处理组来解决跨团队溢出效应问题
- 教程提供了完整的50000用户合成数据集上的集群加权最小二乘法实现代码
结构提纲
按章节快速跳转。
传统用户级A/B测试在协作环境中失效,因为用户行为通过共享工件相互影响导致控制组污染。
AI生成内容在共享渠道传播,使得未启用功能的控制组用户也受到处理效应影响。
共享工件传播、共享工作流干扰和网络采用三种机制破坏了A/B测试的基本假设。
在工作区级别进行随机分配并建模跨工作区溢出效应,以正确估计直接和间接处理效果。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- 协作AI功能集群随机化
- 问题识别
- 协作者污染陷阱
- SUTVA违反
- 解决方案
- 集群随机化
- 工作区级别分配
- 实现方法
- 集群加权最小二乘法
- Bootstrap置信区间
金句 / Highlights
值得收藏与分享的关键句。
您的团队向平台上一半的企业账户推送AI会议总结器。部署很干净,一半开启一半关闭,您等待对照组的任务完成率保持平稳,而处理组逐渐上升。两周后,对照组的数据也开始变动。
这就是协作者污染陷阱。它出现在每一个涉及共享工件的生成式AI产品中:队友阅读的AI会议记录、同事编辑的AI起草文档、审阅者评估的AI代码建议、整个团队回复的AI生成邮件线程。
在用户级A/B测试背后运行协作AI功能是一种违反稳定单位处理值假设(SUTVA)的产品实验。解决方案是集群随机化:在工作区级别抛硬币,使整个团队一起进入或退出,然后直接建模跨工作区溢出效应。
标题:协作 AI 功能的产品实验:Python 中基于 LLM 工具的聚类随机化
URL 来源:https://www.freecodecamp.org/news/cluster-randomization-for-llm-based-tools-in-python/
发布时间:2026-05-22T19:15:56.565Z
Markdown 内容:

每个对基于 LLM 的协作功能运行因果推断的产品实验团队最终都会遇到同样的问题:你的用户不是独立的。你的团队将 AI 会议摘要工具发布给平台上一半的企业账户。发布很干净,一半开启一半关闭,你等待对照组的任务完成率保持平稳,而处理组的数据缓慢上升。两周后,对照组的数据也开始变动了。没有那么多,但确实可见。这些账户的功能确认是关闭的,你也检查了两次发布配置。仍然有什么东西在污染你的对照组。
你在深入日志之前就知道是什么原因。AI 会议摘要出现在共享的 Slack 频道中,AI 草拟的文档出现在共享的 Google Drive 文件夹中,AI 代码审查建议出现在处理组和对照组工程师都会阅读的拉取请求中。处理用户的行为了发生变化,其中一部分行为通过协作图渗透回你的对照组。
这就是协作者污染陷阱。它出现在每一个接触共享工件的生成式 AI 产品中:队友阅读的 AI 会议记录、同事编辑的 AI 草拟文档、审阅者评估的 AI 代码建议、整个团队回复的 AI 生成邮件线程。用户级随机化假设一个用户的处理分配不会影响其他任何用户的产出。在协作工作空间中,这个假设从设计上就是错误的,产品实验将功能的真实效果与它在对照组内产生的溢出效应混在一起。
在用户级 A/B 测试后面运行协作 AI 功能是一个违反稳定单位处理值假设(SUTVA)的产品实验。解决方案是聚类随机化:在工作空间级别抛硬币,使整个团队一起进入或退出,然后直接建模跨工作空间的溢出效应。
本教程将通过完整流程(聚类分配、有偏见的朴素用户级 OLS、用于诚实标准误差的聚类加权最小二乘法、分别识别直接效应和溢出效应的双暴露分解,以及聚类自举置信区间),在一个 50,000 用户的合成 SaaS 数据集上进行演示,其中真实因果效应是已知的。你将估计它们,量化不确定性,并看到这种方法在哪里静默地失效。
配套代码: 每个代码块都在配套笔记本中端到端运行,地址为 github.com/RudrenduPaul/product-experimentation-causal-inference-genai-llm/tree/main/05_cluster_randomization。笔记本(
cluster_randomization_demo.ipynb)的所有输出都已预先执行,因此你可以在本地运行之前在 GitHub 上阅读。
目录
为什么用户级 A/B 随机化在协作下会失效
A/B 测试的数学原理很优雅,因为一个用户的处理分配不影响另一个用户的产出。抛硬币;一半用户获得 AI 功能,硬币投掷通过构造打破每一个可能的混淆因素。协作以三种方式破坏这种保证。
共享工件传播。 AI 摘要出现在每个队友阅读的频道中,AI 草拟的文档进入每个队友编辑的文件夹,AI 代码审查建议出现在每个审阅者评估的拉取请求上。对照用户消费这些工件,无论该功能是否为他们开启,阅读 AI 辅助内容的行为效应泄漏到他们的产出中。
共享工作流产生干扰。 依赖 AI 摘要器的处理用户写更短的后续笔记,假设队友已经阅读了摘要。同一团队的对照用户收到这些较短的笔记,花费更少时间阅读,这改变了他们的会话长度。这意味着处理用户的分配改变了对照用户的产出,这正是 SUTVA 禁止的。
网络采用跟随协作。 处理团队中的高级用户首先试验该功能,然后通过跨团队渠道推动其他工作空间的队友。如果你的处理组产生 AI 辅助内容,而你的对照组阅读并复制,那么对照组在从未切换开关的情况下部分接受了处理。
所有三种机制产生相同的症状:原始用户级比较低估了功能的直接影响,因为对照组不再是纯粹的反事实。在本教程的合成数据集中,处理用户会话时间的真实直接影响是 +0.80 分钟,跨工作空间协作的对照用户的真实溢出效应是 +0.20 分钟。朴素的用户级 OLS 恢复 +0.6723,低估了 16% 的直接影响,并报告标准误差大约小了 19 倍,因为它将 50,000 用户视为独立的,即使处理只在 50 个聚类间随机化。这不是一个小错误。这是那种导致错误功能发布决策的错误。
聚类随机化实际做什么
聚类随机化在工作空间级别翻转分配硬币,使整个团队落在同一组中,将大部分干扰限制在应该在的地方,使残余的跨工作空间泄漏成为你可以直接建模的东西。

_图 1(图片 ab:集群随机化针对的 SUTVA 违反示意图。处理工作空间中的每个用户(第一行,红色)都能看到 AI 功能。对照工作空间中的每个用户(第二行)应该什么都看不到,但协作者(橙色)会通过共享的 Slack、文档和代码审查读取 AI 产出物。这些溢出暴露的用户处于部分处理状态。集群随机化并不能消除干扰;它将干扰限制在工作空间边界内,将剩余的跨工作空间泄漏作为可识别的组成部分,双暴露模型可以直接估计。_
如果一个工作空间被处理,其中的每个用户都会获得该功能。如果是对照工作空间,则其中没有人能获得。工作空间内的干扰是可以接受的,因为所有团队成员共享相同的分配,而工作空间级别的均值捕获了完整的处理包。该设计旨在控制跨工作空间的干扰。
估计器在一系列假设下工作,每一个都有值得了解的名称,因为本教程末尾的失败模式直接映射到特定的违反情况。
- 集群级随机分配。 处理由真正的随机机制在集群级别分配。哪些工作空间进入处理组与工作空间级别的潜在结果无关。
- 部分干扰。 干扰发生在集群内部但不在集群之间(Hudgens 等人)。工作空间 A 中的处理用户可以影响她在工作空间 A 中的队友,但不能影响工作空间 B 中的用户。这是集群随机化围绕构建的假设。
- 集群级 SUTVA。 工作空间的处理是一个单一、明确定义的包。功能只有一个版本,集群内的暴露异质性被吸收到集群级效应中。
- 集群的可交换性。 在抛硬币之前,处理和对照工作空间是可交换的。随机化通过构造实现这一点。
- 足够的集群数量。 集群稳健推断依赖于跨集群的中心极限定理。从业者通常使用 K ≥ 30 作为工作门槛,尽管适当的阈值取决于集群大小异质性和检验统计量的选择。较少的集群需要不同的推断工具,例如随机化推断或集群野蛮引导法。
部分干扰是这里的支撑假设。集群随机化的全部要点是跨集群溢出比集群内溢出更小更慢,因此处理整个团队将大部分干扰包含在其应该在的位置(Ugander 等人)。当跨集群溢出有意义时,双暴露模型直接识别并估计这种泄漏。
先决条件
你需要 Python 3.11 或更新版本,熟悉 pandas 和线性回归,以及对普通最小二乘法有大致了解。
安装本教程所需的包:
pip install numpy pandas statsmodels scipy matplotlib以下是正在发生的事情: 五个包涵盖了完整流程。Pandas 加载数据并构建集群分配。NumPy 处理数组运算和引导抽样。Statsmodels 拟合每次回归:朴素 OLS、集群加权最小二乘法和具有集群稳健标准误的双暴露模型。Scipy 支持核密度诊断图,matplotlib 渲染它。
克隆配套仓库以获取合成数据集:
git clone https://github.com/RudrenduPaul/product-experimentation-causal-inference-genai-llm.git
cd product-experimentation-causal-inference-genai-llm
python data/generate_data.py --seed 42 --n-users 50000 --out data/synthetic_llm_logs.csv以下是正在发生的事情: 克隆拉取配套仓库,generate_data.py 生成系列中使用的共享 50,000 用户数据集。种子 42 保持数据可重现。50,000 用户规模为每个工作空间提供了足够多的用户(约每个 1,000 个),使集群级推断表现渐近特性。输出 CSV 位于 data/synthetic_llm_logs.csv。
设置工作示例
合成数据集模拟了一个拥有 50,000 名用户的 SaaS 产品,分布在 50 个工作空间中。协作式 AI 功能以全覆盖方式部署到 25 个随机选择的工作空间,其他 25 个保持关闭。
当控制用户跨工作空间协作时,他们会被溢出暴露。在本教程中,opt_in_agent_mode == 1 作为跨工作空间活动的行为代理:主动选择 AI 工具的用户是那些阅读队友编写的文档、Slack 帖子和拉取请求的用户,其中处理工作空间的 AI 输出会出现。在生产部署中,您需要用观察到的协作图替换此代理,例如共享频道成员、文档共同撰写或审阅者重叠。由于 opt_in_agent_mode 反映了一个没有随机成分的自愿行为选择,真实实验中的溢出系数将吸收选择加入和未选择加入的控制用户之间的选择差异。生产溢出标志应基于观察到的协作图;行为代理引入的选择偏差是双暴露模型无法纠正的。
本教程通过在工作空间级基线上叠加已知的真实效应来从头构建 session_minutes_obs。CSV 的 session_minutes 列被有意保留。这种分离让您验证每个估计器是否都恢复了内置的效应。
场景中嵌入的真实效应包括对处理用户的 +0.80 分钟直接影响和对溢出暴露控制用户 +0.20 分钟的溢出效应。了解这两个值可以让你验证估计器是否能够恢复它们。
步骤 1:构建集群分配和溢出暴露
第一个代码块加载数据,在集群层面为工作区分配处理组,标记溢出暴露用户,并构建一个已知真实情况的观测结果。结果从工作区层面的基础值开始,因此工作区内相关性是真实的。然后为处理用户添加直接影响,为暴露控制用户添加溢出效应,以及高斯噪声。
import numpy as np
import pandas as pd
DIRECT_EFFECT = 0.80
SPILLOVER_EFFECT = 0.20
DATA_SEED = 42
OUTCOME_NOISE_SD = 0.30
df = pd.read_csv("data/synthetic_llm_logs.csv")
rng = np.random.default_rng(DATA_SEED)
df["treated_workspace"] = (df["workspace_id"] < 25).astype(int)
df["treated_user"] = df["treated_workspace"]
df["spillover_exposed"] = (
(df["treated_workspace"] == 0) & (df["opt_in_agent_mode"] == 1)
).astype(int)
ws_baseline = pd.DataFrame({
"workspace_id": np.arange(50),
"ws_baseline": rng.normal(5.0, 0.30, size=50),
})
df = df.merge(ws_baseline, on="workspace_id")
noise = rng.normal(0, OUTCOME_NOISE_SD, size=len(df))
df["session_minutes_obs"] = (
df["ws_baseline"]
+ DIRECT_EFFECT * df["treated_user"]
+ SPILLOVER_EFFECT * df["spillover_exposed"]
+ noise
)
df["exposure"] = np.select(
[df["treated_user"] == 1, df["spillover_exposed"] == 1],
["direct", "spillover"],
default="pure_control",
)
print(f"总用户数: {len(df):,}")
print(f"处理工作区数: {df[df.treated_workspace == 1].workspace_id.nunique()}")
print(f"控制工作区数: {df[df.treated_workspace == 0].workspace_id.nunique()}")
print(f"处理用户数: {df.treated_user.sum():,}")
print(f"纯控制用户数: {(df.exposure == 'pure_control').sum():,}")
print(f"溢出暴露用户数: {(df.exposure == 'spillover').sum():,}")
ws_sizes = df.groupby("workspace_id").size()
print(f"工作区规模: min={ws_sizes.min()} median={int(ws_sizes.median())} max={ws_sizes.max()}")预期输出:
总用户数: 50,000
处理工作区数: 25
控制工作区数: 25
处理用户数: 24,937
纯控制用户数: 18,319
溢出暴露用户数: 6,744
工作区规模: min=923 median=1002 max=1052发生的情况如下: 工作区 ID 0 到 24 成为处理集群,25 到 49 成为控制集群,这样你有 24,937 名处理用户和 25,063 名控制用户。在控制组中,6,744 名被标记为溢出暴露,因为他们选择了代理模式并处于控制工作区中,他们可能通过跨团队渠道阅读到处理工作区的输出。剩余的 18,319 名是纯控制用户,未受到该功能的影响。工作区规模从 923 到 1,052 名用户不等,足够接近平衡,因此集群加权和非加权估计器的行为会相似。观测结果 session_minutes_obs 捕获了已知的真实情况:处理用户为其工作区基础值增加 0.80 分钟,溢出暴露用户增加 0.20 分钟,每个用户都受到标准差为 0.30 分钟的高斯噪声影响。

_图 2(上图):50,000 用户数据集上的三个暴露组。上方面板显示每组的观测结果分布,虚线垂直线位于组均值处(5.06 分钟纯控制,5.27 分钟溢出暴露,5.79 分钟处理)。溢出分布在纯控制和处理分布之间,这是天真用户级估计器会纳入控制基线的污染。下面板将相同组转换为原始计数:18,319 名纯控制用户,6,744 名溢出暴露控制用户,以及 24,937 名处理用户。图 1 示意性地展示了 SUTVA 违反,此图在数据规模上展示它,三组结构正是步骤 4 的双暴露模型将识别的。_
步骤 2:天真的用户级 OLS(有偏且过于自信)
天真的分析完全忽略了聚类,将观测结果对每个用户的处理分配进行回归,报告标准误差时假设每个用户都是独立抽取的。两件事同时出错。
import statsmodels.formula.api as smf
naive = smf.ols("session_minutes_obs ~ treated_user", data=df).fit()
print(f"天真的估计: {naive.params['treated_user']:+.4f} 分钟")
print(f"天真的标准误: {naive.bse['treated_user']:.4f} (报告不足)")
ci = naive.conf_int().loc["treated_user"].tolist()
print(f"天真的 95% 置信区间: [{ci[0]:+.4f}, {ci[1]:+.4f}]")
print(f"真实情况: +0.80")
print(f"偏差: {naive.params['treated_user'] - 0.80:+.4f} 分钟")预期输出:
天真的估计: +0.6723 分钟
天真的标准误: 0.0034 (报告不足)
天真的 95% 置信区间: [+0.6656, +0.6790]
真实情况: +0.80
偏差: -0.1277 分钟正在发生的情况: 点估计值落在 +0.6723,比真实直接效应 +0.80 低了 16%。这种偏差有两个组成部分。首先是溢出污染:6,744 名阅读过处理工作区输出的对照用户高于纯对照基线,提高了对照组均值并压缩了天真的处理组减对照组差距。其次是工作区基线不平衡:只有 50 个集群时,随机分配无法保证处理和对照工作区池具有相等的均值基线。这个数据集的特定种子产生了处理池基线略低于对照池基线的结果,给估计值增加了额外的向下压力。教训是普遍性的:在小 K 值情况下,实验前对可观测工作区特征的平衡检查是防范标准误差校正无法修复的预存臂间差异的唯一防线。
标准误差是更令人担忧的数字。在 0.0034 时,它反映了被视为独立观测的 50,000 名用户的变异,由此产生的 95% 置信区间 [+0.6656, +0.6790] 完全排除了真实值,在大约是设计实际支持宽度的二十分之一范围内。小了 19 倍的标准误差以相同因子放大了 t 统计量,使天真的回归 p 值看起来比设计所证明的显著性高几个数量级。阅读此报告的利益相关者会确信直接效应接近 0.67 分钟。错误的数字,错误的精度。
步骤 3:聚类加权最小二乘法(诚实标准误差)
修正标准误差的方法是聚合到 50 个工作区均值,然后对工作区级别的处理指示器进行加权回归,权重为工作区大小。推断现在基于 K = 50 个观测值。
import statsmodels.api as sm
ws = (
df.groupby("workspace_id")
.agg(ws_mean=("session_minutes_obs", "mean"),
ws_size=("user_id", "count"),
treated=("treated_workspace", "max"))
.reset_index()
)
X_ws = sm.add_constant(ws["treated"])
wls = sm.WLS(ws["ws_mean"], X_ws, weights=ws["ws_size"]).fit()
wls_ci = wls.conf_int().loc["treated"].tolist()
print(f"WLS cluster-mean contrast: {wls.params['treated']:+.4f} min")
print(f"WLS SE: {wls.bse['treated']:.4f} (based on K=50 clusters)")
print(f"WLS 95% CI: [{wls_ci[0]:+.4f}, {wls_ci[1]:+.4f}]")预期输出:
WLS cluster-mean contrast: +0.6723 min
WLS SE: 0.0652 (based on K=50 clusters)
WLS 95% CI: [+0.5412, +0.8035]正在发生的情况: 聚类均值对比与天真的估计值在 +0.6723 处相同,因为加权工作区均值是对相同用户级别数据的不同聚合。改变的是标准误差。在 0.0652 时,它大约是天真估计值 0.0034 的 19 倍,并反映了 50 个聚类均值之间的真正变异(statsmodels WLS 使用 t(48) 临界值代替 z=1.96,这就是为什么置信区间边界与使用 z 的手工计算略有不同的原因)。95% 置信区间扩展到 [+0.5412, +0.8035],勉强覆盖了真实值。WLS 修正了推断问题,因此标准误差现在反映了实际设计,但它没有修正识别问题。对照工作区均值仍包含暴露于溢出的用户,所以这个估计是一个不能解释为干净 ATE 的污染对比。下一步将分离两者。
步骤 4:双暴露分解(无偏直接和溢出效应)
双暴露模型将每个用户的暴露视为三类别变量(直接、溢出或纯对照)并对结果进行回归,针对两个非基线类别(Aronow 等人)。纯对照是省略的参考类别,因此两个系数都可直接解释:一个是功能的直接效应,另一个是跨工作区协作的对照用户溢出效应。
df["is_direct"] = (df["exposure"] == "direct").astype(int)
df["is_spillover"] = (df["exposure"] == "spillover").astype(int)
two_exp = smf.ols(
"session_minutes_obs ~ is_direct + is_spillover",
data=df,
).fit(cov_type="cluster", cov_kwds={"groups": df["workspace_id"]})
direct = two_exp.params["is_direct"]
spillover = two_exp.params["is_spillover"]
direct_ci = two_exp.conf_int().loc["is_direct"].tolist()
spillover_ci = two_exp.conf_int().loc["is_spillover"].tolist()
print(f"Direct effect: {direct:+.4f} min (ground truth = +0.80)")
print(f" SE: {two_exp.bse['is_direct']:.4f}")
print(f" 95% CI: [{direct_ci[0]:+.4f}, {direct_ci[1]:+.4f}]")
print(f"Spillover effect: {spillover:+.4f} min (ground truth = +0.20)")
print(f" SE: {two_exp.bse['is_spillover']:.4f}")
print(f" 95% CI: [{spillover_ci[0]:+.4f}, {spillover_ci[1]:+.4f}]")
spillover_share = (df["exposure"] == "spillover").mean()
projected = direct + spillover_share * spillover
print(f"Spillover share of all users: {spillover_share:.4f}")
print(f"Projected total under full rollout: {projected:+.4f} min")预期输出:
Direct effect: +0.7284 min (ground truth = +0.80)
SE: 0.0647
95% CI: [+0.6016, +0.8552]
Spillover effect: +0.2083 min (ground truth = +0.20)
SE: 0.0038
95% CI: [+0.2008, +0.2158]
Spillover share of all users: 0.1349
Projected total under full rollout: +0.7565 min正在发生的情况: 使用以 workspace_id 为键的聚类稳健标准误对三类别暴露情况进行拟合,得到了两个干净的系数。直接效应为 +0.7284,95% 置信区间为 [+0.6016, +0.8552],包含了真实值 +0.80。溢出效应为 +0.2083,95% 置信区间为 [+0.2008, +0.2158],紧密覆盖了真实值 +0.20。溢出标准误(0.0038)对于聚类稳健推断来说看起来很小,因为模拟的溢出效应在所有 25 个控制聚类中都是均匀的;在具有异质性溢出强度的真实数据中,你会看到聚类稳健标准误显著增大。预测的总计 +0.7565 分钟考虑了溢出效应,基于在给定部署规模下预期会受到溢出暴露的用户比例(此数据集中为 0.1349)。在生产部署中,你需要用协作图预测的在你的推出计划下将受到溢出暴露的份额来替换该比例。预测是你推出中的设计参数,因此在报告数字时明确说明假设的份额。
步骤 5:聚类自举置信区间
聚类自举重新采样整个工作空间,测试步骤 4 的解析置信区间是否在不假设中心极限定理在 K = 50 时完全生效的情况下仍然成立。当 K 很大且工作空间大小大致相等时,聚类设计的解析标准误效果良好;自举确认这在实际中对你的实际数据也成立。重新采样单个用户会低估方差,因为同一工作空间中的用户共享聚类分配和工作空间级别的基线;聚类自举保持了这种相关结构。
def naive_point(d):
return smf.ols(
"session_minutes_obs ~ treated_user", data=d
).fit().params["treated_user"]
def wls_point(d):
w = (d.groupby("workspace_id").agg(
ws_mean=("session_minutes_obs", "mean"),
ws_size=("user_id", "count"),
treated=("treated_workspace", "max")).reset_index())
X = sm.add_constant(w["treated"])
return sm.WLS(w["ws_mean"], X, weights=w["ws_size"]).fit().params["treated"]
def two_exp_point(d):
fit = smf.ols(
"session_minutes_obs ~ is_direct + is_spillover", data=d
).fit(cov_type="cluster", cov_kwds={"groups": d["workspace_id"]})
return fit.params["is_direct"], fit.params["is_spillover"]
rng_boot = np.random.default_rng(7)
ws_ids = df["workspace_id"].unique()
k = len(ws_ids)
reps = {"naive": [], "cluster_wls": [], "direct": [], "spillover": []}
for _ in range(500):
draw = rng_boot.choice(ws_ids, size=k, replace=True)
sample = pd.concat(
[df[df["workspace_id"] == wid] for wid in draw],
ignore_index=True,
)
reps["naive"].append(naive_point(sample))
reps["cluster_wls"].append(wls_point(sample))
d_b, s_b = two_exp_point(sample)
reps["direct"].append(d_b)
reps["spillover"].append(s_b)
for key, truth in [("naive", 0.80), ("cluster_wls", 0.80),
("direct", 0.80), ("spillover", 0.20)]:
arr = np.array(reps[key])
lo, hi = np.percentile(arr, [2.5, 97.5])
covers = "covers" if lo <= truth <= hi else "misses"
print(f"{key:<13} 95% CI: [{lo:+.4f}, {hi:+.4f}] ({covers} {truth:+.2f})")预期输出:
naive 95% CI: [+0.5386, +0.7966] (misses +0.80)
cluster_wls 95% CI: [+0.5386, +0.7966] (misses +0.80)
direct 95% CI: [+0.5931, +0.8519] (covers +0.80)
spillover 95% CI: [+0.2008, +0.2164] (covers +0.20)正在发生的情况: 有放回地抽取 50 个工作空间并重复拟合每个估计量 500 次,为你提供了每个点估计的自举分布。朴素 OLS 和聚类 WLS 估计量产生相同的自举区间,因为它们在工作空间级别重采样下共享相同的点估计,且两个区间都排除了真实值 +0.80,因为两者都受到步骤 2 中确定的两个来源的偏差影响(溢出污染和工作空间基线不平衡)。两暴露模型的直接效应区间为 [0.5931, 0.8519],包含了 0.80。溢出区间为 [+0.2008, +0.2164],紧密覆盖了 +0.20。聚类自举确认了步骤 4 中的解析聚类稳健标准误已经显示的内容:在 K = 50 时不依赖渐近近似推断仍然有效。在笔记本电脑上运行大约需要一分钟。
聚类随机化失败的情况
当其假设成立时,聚类随机化解决了 SUTVA 问题,而当假设不成立时,它会产生看起来干净但有偏的估计。三种失败模式对应于一个命名的识别假设;第四种情况处理聚类大小不等时的估计量效率。
聚类太少(违反足够的聚类数量)。 聚类稳健标准误依赖于跨聚类的中心极限定理,实践者通常使用 K ≥ 30 作为工作底线,尽管适当的阈值取决于聚类大小的异质性和检验统计量的选择(MacKinnon & Webb, 2017)。向四个客户账户推出的协作 AI 功能没有达到该标准。K = 4 时的聚类稳健标准误是反保守的,结果置信区间过窄。当 K 很小时,随机化推断或聚类野自举可以给你有效的 p 值。
集群边界不包含干扰图(违反部分干扰假设)。 集群随机化假设干扰仅限于工作空间内部。如果用户通过 Slack Connect 频道、外部共享文档或客户社区论坛在不同工作空间间大量协作,那么部分干扰就只是虚构的,溢出效应会跨越每个集群边界。双暴露模型可以吸收适度的跨集群泄漏,因为溢出系数捕获了暴露标志所测量的所有溢出效应。当泄漏是结构性的时候,你需要观察到的协作图和基于协作结构本身构建集群的图聚类随机化设计(Ugander 等人)。
异质性集群规模导致聚合偏差(估计效率)。 等权重的集群均值将 50 用户的工作空间与 5000 用户的工作空间同等对待,这是低效的选择,因为工作空间均值的方差取决于其中的用户数量。解决方案是按工作空间规模加权最小二乘法,或使用工作空间随机截距的混合效应模型。这是一个效率问题,不影响识别,这种区别很重要:在任一加权选择下,点估计保持一致。
事后集群构建(违反可交换性)。 在观察结果后构建集群分配是将有效设计转化为 p 值操纵的最干净方式。你必须在随机化之前定义并承诺使用你的集群,理想情况下在预注册分析计划中。任何对集群边界的临时调整(丢弃极端结果的工作空间、将小工作空间合并为复合体、检查数据后重新定义溢出暴露)都会重新引入标准误差校正无法修复的选择偏差。
在实际部署中还有两个额外威胁值得关注。
部分功能采用下集群级 SUTVA 失效。 集群级 SUTVA 假设要求工作空间的处理是一个单一、明确定义的包。当一个功能在单个工作空间内以不同的采用率推出,或当多个功能版本共存时(高级版面向重度用户,基础版面向普通用户),这一假设就会崩溃。在这种情况下,集群级"处理"混淆了多种效应,估计量不再可解释。
随机化非机械时的工作空间级混杂因素。 在企业部署中,工作空间进入处理组通常不是完全随机的。测试项目吸引技术先进的账户;客户成功团队影响哪些客户获得早期访问权限。当抛硬币前就违反了可交换性时,集群稳健标准误无法纠正处理组和对照组工作空间池之间的预先存在的系统性差异。对可观测工作空间特征(规模、行业、基线参与度)的平衡检验和集群级别的回归调整是标准补救措施。
这些失败模式在你的回归系数中是不可见的。它们会在离线估计和生产推广之间的差距中显现出来。集群计数、协作图审计和书面预注册是你唯一的真实防御手段。
下一步做什么
当工作空间内的协作为创建破坏用户级 SUTVA 的溢出效应,且你的集群是自然和可观察的(工作空间、团队、账户、实体店铺)时,集群随机化是正确的工具。如果你关心的干扰跨越地理市场,或发生在司机和乘客整体清算的双边市场中的时间维度上,切换实验(随机化时间段)更适合。如果你的处理在个人层面分配但怀疑存在未观察的跨用户混杂因素,基于设计的工具变量分析提供更清洁的识别策略。当干扰已知且复杂时,带有霍维茨-汤普森加权暴露估计器的图聚类随机化在不强制每个集群边界包含每个干扰路径的情况下给出无偏效应估计。
本教程的配套笔记本位于 github.com/RudrenduPaul/product-experimentation-causal-inference-genai-llm/tree/main/05_cluster_randomization。克隆仓库,生成合成数据集,运行 cluster_randomization_demo.ipynb(或 cluster_randomization_demo.py)来重现本教程的每个代码块、每个数字和每个图表。
当协作 AI 功能发布给分享工作的团队时,用户级 A/B 估计几乎总是错误的。集群随机化加上双暴露模型分别给出直接影响和溢出效应,集群自助法给出你可以辩护的区间,当利益相关者询问提升中有多少来自功能,多少来自队友相互交流时。
- * *
- * *
免费学习编程。freeCodeCamp 的开源课程已经帮助超过 40,000 人获得开发人员工作。开始学习