学习路径的 A/B 实验设计:怎么证明 AI 推荐比固定路线更有效 学习路径的 A/B 实验设计怎么证明 AI 推荐比固定路线更有效一、我觉得 AI 推荐有效不叫验证构建了一个 AI 驱动的个性化学习路径推荐系统。当别人问你它比固定路径好多少时你的回答不能是感觉有效。工程验证需要定量实验——A/B 测试是最直接的方案。但教育领域的 A/B 测试有其特殊性学习效果的反馈周期长几周甚至几个月、用户群体的异质性大、以及存在伦理问题不能让对照组用户接受明显更差的学习方案。二、A/B 实验设计框架flowchart TD A[实验设计] -- B[随机分流] B -- C[实验组: AI 推荐路径] B -- D[对照组: 固定难度阶梯] C -- E[观察期: 4 周] D -- E E -- F[指标采集] F -- F1[主要指标: 正确率提升] F -- F2[次要指标: 完成题目数] F -- F3[体验指标: 放弃率/满意度] F -- F4[长期指标: 间隔后正确率] F1 -- G[统计分析] F2 -- G F3 -- G F4 -- G G -- H{显著性检验} H --|p 0.05| I[AI 推荐显著优于固定路径] H --|p ≥ 0.05| J[无显著差异或需要更多数据]三、实现A/B 实验引擎import random import hashlib import math from dataclasses import dataclass, field from datetime import datetime, timedelta from typing import Optional from scipy import stats dataclass class ExperimentConfig: A/B 实验配置 experiment_id: str description: str # 分流配置 traffic_split: float 0.5 # 实验组流量占比 # 实验周期 duration_days: int 28 # 实验观察期 # 评估指标 primary_metric: str correctness_improvement secondary_metrics: list[str] field( default_factorylambda: [ problems_completed, dropout_rate, retention_accuracy, ] ) dataclass class UserGroup: 用户分组信息 user_id: str group: str # control | treatment assigned_at: datetime class ABTestEngine: A/B 实验引擎 负责分流、指标采集、统计分析。 def __init__(self, config: ExperimentConfig): self.config config self._user_assignments: dict[str, UserGroup] {} def assign_group(self, user_id: str) - UserGroup: 为用户分配实验组 使用哈希分流保证同一用户始终在同一组。 哈希分流的优势 1. 确定性同一用户多次请求结果一致 2. 不需要维护用户-分组映射表但这里仍维护用于监控 if user_id in self._user_assignments: return self._user_assignments[user_id] # 使用哈希 取模实现分流 hash_val int(hashlib.md5(user_id.encode()).hexdigest(), 16) bucket hash_val % 1000 # 1000 个桶 # 前 50% 桶为实验组 if bucket int(self.config.traffic_split * 1000): group treatment else: group control assignment UserGroup( user_iduser_id, groupgroup, assigned_atdatetime.now(), ) self._user_assignments[user_id] assignment return assignment def analyze_results( self, control_metrics: list[float], treatment_metrics: list[float], metric_name: str, ) - dict: 统计分析比较两组指标是否有显著差异 使用 Welchs t-test不假设方差相等。 # 描述性统计 control_mean sum(control_metrics) / len(control_metrics) if control_metrics else 0 treatment_mean sum(treatment_metrics) / len(treatment_metrics) if treatment_metrics else 0 relative_lift ( (treatment_mean - control_mean) / control_mean * 100 if control_mean 0 else 0 ) # Welchs t-test t_stat, p_value stats.ttest_ind( treatment_metrics, control_metrics, equal_varFalse ) # 效应量Cohens d衡量差异的实际幅度 cohens_d self._cohens_d(treatment_metrics, control_metrics) # 置信区间95% ci_lower, ci_upper self._confidence_interval( treatment_metrics, control_metrics ) significant p_value 0.05 return { metric: metric_name, control_size: len(control_metrics), treatment_size: len(treatment_metrics), control_mean: round(control_mean, 4), treatment_mean: round(treatment_mean, 4), relative_lift_pct: round(relative_lift, 2), cohens_d: round(cohens_d, 3), p_value: round(p_value, 4), significant: significant, ci_95: (round(ci_lower, 4), round(ci_upper, 4)), verdict: ( fAI 推荐显著{优于 if relative_lift 0 else 不如}固定路径 f提升 {abs(relative_lift):.1f}%p{p_value:.3f} if significant else 两组无显著差异 ), } staticmethod def _cohens_d(a: list[float], b: list[float]) - float: 计算 Cohens d n1, n2 len(a), len(b) if n1 2 or n2 2: return 0.0 mean1, mean2 sum(a) / n1, sum(b) / n2 var1 sum((x - mean1) ** 2 for x in a) / (n1 - 1) var2 sum((x - mean2) ** 2 for x in b) / (n2 - 1) # 合并标准差 pooled_std math.sqrt(((n1 - 1) * var1 (n2 - 1) * var2) / (n1 n2 - 2)) if pooled_std 0: return 0.0 return (mean1 - mean2) / pooled_std staticmethod def _confidence_interval(a: list[float], b: list[float]) - tuple[float, float]: 计算两组均值差的 95% 置信区间 n1, n2 len(a), len(b) if n1 2 or n2 2: return (float(-inf), float(inf)) mean1, mean2 sum(a) / n1, sum(b) / n2 var1 sum((x - mean1) ** 2 for x in a) / (n1 - 1) var2 sum((x - mean2) ** 2 for x in b) / (n2 - 1) diff mean1 - mean2 se math.sqrt(var1 / n1 var2 / n2) # 使用正态近似大样本时 t 分布趋近正态 z_95 1.96 return (diff - z_95 * se, diff z_95 * se) # ---- 实验示例 ---- def simulate_experiment(): 模拟一次 A/B 实验 config ExperimentConfig( experiment_idai_path_vs_fixed_v1, description验证 AI 推荐路径 vs 固定难度阶梯, traffic_split0.5, duration_days28, ) engine ABTestEngine(config) # 模拟 200 个用户的数据 random.seed(42) control_data [ random.gauss(0.10, 0.15) # 对照组正确率提升均值 10% for _ in range(100) ] treatment_data [ random.gauss(0.18, 0.15) # 实验组正确率提升均值 18% for _ in range(100) ] result engine.analyze_results( control_data, treatment_data, correctness_improvement ) print(A/B 实验结果) for key, value in result.items(): print(f {key}: {value}) # simulate_experiment()四、实验设计的工程考量4.1 样本量估算在实验开始前需要估算需要多少样本量才能检测到预期的效应。规则是效应越小需要的样本越多标准差越大需要的样本越多可以使用功效分析Power Analysis来估算最小样本量4.2 辛普森悖论按总体统计AI 推荐组优于固定路径组。但按难度分组后可能每个难度子集内 AI 推荐都不如固定路径。这就是辛普森悖论——聚合统计可能欺骗你。补救方案是分层分析对不同用户群新手/进阶/高级分别评估。4.3 新奇效应新用户可能因为新鲜感而在短期内表现更好随着新鲜感消退回归真实水平。实验周期应该足够长至少 4 周来排除新奇效应。五、总结A/B 实验不是简单地比较两组的平均值。它涉及分流机制的设计哈希分流保证一致性、统计显著性检验t-test 置信区间、效应量评估Cohens d、以及避免实验偏差辛普森悖论、新奇效应。在教育类产品的实验设计中还需要额外关注伦理边界——不能让任何一组用户接受明显有害的学习方案。