Machine Learning: اختبار A/B وتصميم التجارب
آخر تحديث: 2026-08-26
شحن نموذج دون اختبار A/B مثل المشي على حبل مشدود دون شبكة أمان - ليس لديك أي فكرة عما إذا كان أفضل حقًا أم مجرد محظوظ.
1. ما ستتعلمه
- أساسيات اختبار A/B: اختبار الفرضيات، قيمة p، فترات الثقة، القوة الإحصائية
- تصميم التجربة: حساب حجم العينة، مدة التجربة، استراتيجيات تقسيم الزيارات
- اختبار A/B لنماذج ML: مقارنة النماذج الجديدة بالقديمة عبر الإنترنت وتقييم المقاييس التجارية
- اللصوص متعددو الأذرع (MAB): Thompson Sampling / UCB والمقايضة بين الاستكشاف والاستغلال
- سيناريو SaaS لـ Alice: التحقق من زيادة معدل التحويل لنموذج توصية جديد باستخدام اختبار A/B
2. قصة حقيقية من مدير منتج
(1) الألم: عدم معرفة ما إذا كان النموذج الجديد أفضل حقًا
أطلقت منصة SaaS لـ Alice نموذج توصية جديد، وأظهرت لوحة المعلومات ارتفاع التحويل من 3.2% إلى 3.5%. لكن الرئيس التنفيذي اعترض: "قد يكون هذا مجرد تقلب طبيعي - كيف تثبت أن النموذج تسبب فيه؟" لم يكن لدى Alice إجابة — بدون تجربة خاضعة للرقابة، لا يمكن أن يُعزى أي تحسن.
(2) حل اختبار A/B
يقسم اختبار A/B المستخدمين عشوائيًا إلى المجموعة A (النموذج القديم) والمجموعة B (النموذج الجديد)، ويشغلهما في نفس الوقت، ويستخدم الإحصاء لتحديد ما إذا كان الفرق ذا دلالة.
from scipy import stats
# المجموعة A: النموذج القديم، المجموعة B: النموذج الجديد
conversions_a, n_a = 320, 10000 # 3.2%
conversions_b, n_b = 350, 10000 # 3.5%
z_stat, p_value = stats.proportions_ztest(
[conversions_a, conversions_b], [n_a, n_b]
)
print(f"p-value: {p_value:.4f}")
print(f"Significant: {p_value < 0.05}")
(3) النتيجة: تجربة استمرت 3 أسابيع أثبتت زيادة تحويل بنسبة 2.3%، مضيفة 500 ألف دولار سنويًا
استخدمت Alice اختبار A/B لمدة 3 أسابيع للتحقق علميًا من تحسن النموذج الجديد — p=0.03، ذو دلالة إحصائية. بهذه الثقة، طرحته للجميع، مضيفًا ما يقرب من 500 ألف دولار في الإيرادات السنوية.
3. الإحصاء وراء اختبار A/B
(1) إطار اختبار الفرضيات
sequenceDiagram
participant Design as تصميم التجربة
participant Split as تقسيم عشوائي
participant Collect as جمع البيانات
participant Test as اختبار إحصائي
participant Decision as قرار
Design->>Split: تحديد H0/H1، حجم العينة
Split->>Collect: المجموعة A (التحكم) مقابل المجموعة B (العلاج)
Collect->>Test: بعد فترة التجربة
Test->>Decision: حساب قيمة p
Decision->>Decision: p < α → رفض H0 (B أفضل)
Decision->>Decision: p ≥ α → لا يمكن رفض H0
(2) المفاهيم الإحصائية الأساسية
▶ مثال: حساب اختبار الفرضيات
from scipy import stats
import numpy as np
# اختبار z لنسبتين
def ab_test_proportions(conversions_a, n_a, conversions_b, n_b, alpha=0.05):
p_a = conversions_a / n_a
p_b = conversions_b / n_b
p_pool = (conversions_a + conversions_b) / (n_a + n_b)
se = np.sqrt(p_pool * (1 - p_pool) * (1/n_a + 1/n_b))
z_stat = (p_b - p_a) / se
p_value = 1 - stats.norm.cdf(z_stat)
ci_low = (p_b - p_a) - 1.96 * se
ci_high = (p_b - p_a) + 1.96 * se
return {
"conversion_a": f"{p_a:.4f}",
"conversion_b": f"{p_b:.4f}",
"lift": f"{(p_b - p_a) / p_a * 100:.2f}%",
"z_stat": f"{z_stat:.3f}",
"p_value": f"{p_value:.4f}",
"ci_95": f"({ci_low:.4f}, {ci_high:.4f})",
"significant": p_value < alpha,
}
result = ab_test_proportions(320, 10000, 350, 10000)
for k, v in result.items():
print(f"{k:15s}: {v}")
Output:
# Functions defined successfully
| المفهوم | المعنى | العتبة النموذجية |
|---|---|---|
| H0 (الفرضية الصفرية) | A و B لا يختلفان | — |
| H1 (الفرضية البديلة) | B أفضل من A | — |
| α (مستوى الدلالة) | احتمال خطأ النوع الأول (إيجابي كاذب) | 0.05 |
| قيمة p | احتمال ملاحظة النتيجة الحالية أو أكثر تطرفًا تحت H0 | <0.05 |
| القوة | احتمال رفض H0 بشكل صحيح (1-β) | 0.8 |
| فترة الثقة | النطاق المعقول للفرق الحقيقي | 95% CI |
4. تصميم التجربة
(1) حساب حجم العينة
▶ مثال: حساب حجم العينة المطلوب لاختبار A/B
from statsmodels.stats.power import zt_ind_solve_power
from statsmodels.stats.proportion import proportion_effectsize
import numpy as np
def calculate_sample_size(p_baseline, mde, alpha=0.05, power=0.8):
"""احسب حجم العينة المطلوب لكل مجموعة.
Args:
p_baseline: معدل التحويل الأساسي
mde: الحد الأدنى للتأثير القابل للكشف (زيادة نسبية)
alpha: مستوى الدلالة
power: القوة الإحصائية
"""
p_new = p_baseline * (1 + mde)
effect_size = proportion_effectsize(p_new, p_baseline)
n = zt_ind_solve_power(effect_size=effect_size, alpha=alpha, power=power)
return int(np.ceil(n))
# SaaS لـ Alice: خط الأساس 3.2%، تريد اكتشاف زيادة نسبية 10%
baseline = 0.032
for mde in [0.05, 0.10, 0.15, 0.20]:
n = calculate_sample_size(baseline, mde)
print(f"MDE={mde*100:.0f}%: Need {n:,} users per group "
f"(total {n*2:,}, ~{n*2/baseline/1000:.0f}k visitors)")
Output:
# Functions defined successfully
| MDE (الحد الأدنى للتأثير القابل للكشف) | حجم العينة لكل مجموعة | إجمالي حجم العينة | مدة التجربة (50k DAU) |
|---|---|---|---|
| زيادة نسبية 5% | 152,000 | 304,000 | 6 أيام |
| زيادة نسبية 10% | 38,000 | 76,000 | 1.5 يوم |
| زيادة نسبية 15% | 17,000 | 34,000 | <1 يوم |
| زيادة نسبية 20% | 9,600 | 19,200 | <1 يوم |
(2) استراتيجيات تقسيم الزيارات
| الاستراتيجية | الطريقة | المزايا | العيوب |
|---|---|---|---|
| مستوى المستخدم | تجزئة حسب user_id | تجربة مستخدم متسقة | تتطلب تسجيل الدخول |
| مستوى الطلب | حسب request_id | بسيطة | قد يرى نفس المستخدم نتائج مختلفة |
| مستوى الجهاز | حسب device_id | يغطي المستخدمين غير المسجلين | قد يتعارض مستخدمو الأجهزة المتعددة |
▶ مثال: تقسيم التجزئة المتسقة
import hashlib
def assign_group(user_id, salt="experiment_1", num_groups=2):
"""تعيين تجزئة متسقة لاختبار A/B."""
hash_input = f"{salt}_{user_id}".encode()
hash_val = int(hashlib.md5(hash_input).hexdigest(), 16)
return hash_val % num_groups # 0=A, 1=B
# تحقق من التقسيم المتساوي
user_ids = range(100000)
groups = [assign_group(uid) for uid in user_ids]
print(f"Group A: {groups.count(0)} ({groups.count(0)/len(groups)*100:.1f}%)")
print(f"Group B: {groups.count(1)} ({groups.count(1)/len(groups)*100:.1f}%)")
Output:
# Functions defined successfully
5. اختبار A/B لنماذج ML
(1) مقارنة النماذج عبر الإنترنت
▶ مثال: اختبار A/B لنموذج التوصية لـ Alice
import numpy as np
from scipy import stats
rng = np.random.default_rng(42)
# محاكاة اختبار A/B لمدة أسبوعين لنموذج التوصية
n_users = 20000 # لكل مجموعة
# المجموعة A: النموذج القديم (معدل التحويل الأساسي 3.2%)
conversions_a = rng.binomial(1, 0.032, n_users)
revenue_a = conversions_a * rng.exponential(200, n_users) # متوسط 200 دولار لكل تحويل
# المجموعة B: النموذج الجديد (معدل التحويل الحقيقي 3.5%، زيادة 9.4%)
conversions_b = rng.binomial(1, 0.035, n_users)
revenue_b = conversions_b * rng.exponential(200, n_users)
# تحليل فرق معدل التحويل
conv_rate_a = conversions_a.mean()
conv_rate_b = conversions_b.mean()
z_stat, p_value = stats.proportions_ztest(
[conversions_a.sum(), conversions_b.sum()],
[n_users, n_users]
)
# تحليل فرق الإيرادات
rev_per_user_a = revenue_a.mean()
rev_per_user_b = revenue_b.mean()
t_stat, t_pvalue = stats.ttest_ind(revenue_a, revenue_b)
print("=" * 50)
print("A/B TEST RESULTS: Recommendation Model")
print("=" * 50)
print(f"Conversion: A={conv_rate_a:.3%} vs B={conv_rate_b:.3%}")
print(f" Lift: {(conv_rate_b - conv_rate_a) / conv_rate_a * 100:.1f}%")
print(f" p-value: {p_value:.4f} {'✅ Significant' if p_value < 0.05 else '❌ Not significant'}")
print(f"\nRevenue/User: A={rev_per_user_a:.2f} vs B={rev_per_user_b:.2f} USD")
print(f" Lift: {(rev_per_user_b - rev_per_user_a) / rev_per_user_a * 100:.1f}%")
print(f" p-value: {t_pvalue:.4f}")
# التأثير التجاري
annual_lift = (rev_per_user_b - rev_per_user_a) * 50000 * 12 # 50k مستخدم * 12 شهر
print(f"\nProjected annual revenue lift: {annual_lift:,.0f} USD")
Output:
=
A/B TEST RESULTS: Recommendation Model
=
(2) المزالق الشائعة لاختبار A/B
| المأزق | الوصف | الحل |
|---|---|---|
| التطفل | إعلان الدلالة قبل الموعد النهائي | حدد مدة التجربة؛ حلل فقط في النهاية |
| الاختبار المتعدد | اختبار مقاييس/مجموعات متعددة | تصحيح Bonferroni |
| تأثير الجدة | أداء واجهة المستخدم الجديدة جيدًا في البداية | مدد مدة التجربة |
| التلوث | مستخدمو المجموعة A يرون محتوى المجموعة B | التقسيم على مستوى المستخدم + التحقق من السجلات |
| حجم عينة غير كافٍ | لم يتم الوصول إلى القوة الإحصائية | احسب حجم العينة مسبقًا |
6. اللصوص متعددو الأذرع (MAB)
(1) الموازنة بين الاستكشاف والاستغلال
يستخدم اختبار A/B التقليدي تقسيم حركة مرور ثابت (50/50)، بينما يعدل MAB ديناميكيًا - النماذج ذات الأداء الأفضل تحصل على حركة مرور أكثر.
▶ مثال: Thompson Sampling
import numpy as np
rng = np.random.default_rng(42)
# 3 متغيرات نموذج بمعدلات تحويل حقيقية
true_rates = [0.030, 0.035, 0.028] # النموذج B هو الأفضل
n_arms = len(true_rates)
n_rounds = 10000
# Thompson Sampling
successes = np.zeros(n_arms)
trials = np.zeros(n_arms)
total_reward = 0
for t in range(n_rounds):
# عينة من توزيع Beta لكل ذراع
samples = [rng.beta(successes[i] + 1, trials[i] - successes[i] + 1) for i in range(n_arms)]
chosen = np.argmax(samples)
# محاكاة تفاعل المستخدم
reward = rng.binomial(1, true_rates[chosen])
successes[chosen] += reward
trials[chosen] += 1
total_reward += reward
print(f"Thompson Sampling after {n_rounds} rounds:")
for i in range(n_arms):
print(f" Model {i}: chosen {int(trials[i]):5d} times, "
f"observed rate={successes[i]/max(trials[i],1):.4f} (true: {true_rates[i]:.4f})")
print(f"Total conversions: {int(total_reward)}")
Output:
# Executed successfully
| خوارزمية MAB | الاستراتيجية | طريقة الاستكشاف | التعقيد |
|---|---|---|---|
| ε-Greedy | استكشف عشوائيًا باحتمالية ε | نسبة ثابتة | منخفض |
| UCB | اختر الذراع بأعلى حد ثقة عليا | مدفوعة بعدم اليقين | متوسط |
| Thompson Sampling | عينة من اللاحق لاختيار ذراع | مدفوعة بالاحتمالية | متوسط |
| البُعد | اختبار A/B | MAB |
|---|---|---|
| تخصيص الزيارات | ثابت (50/50) | يتم تعديله ديناميكيًا |
| تكلفة الاستكشاف | عالية (50% تذهب للأسوأ) | منخفضة (تكيفية) |
| الصرامة الإحصائية | عالية (اختبار الفرضيات) | منخفضة (مقاربة) |
| حالة الاستخدام | عند الحاجة إلى دليل إحصائي | تكرار سريع |
❓ أسئلة شائعة
📖 ملخص
- جوهر اختبار A/B: تقسيم عشوائي + تشغيل متزامن + اختبار إحصائي لإسناد التحسينات علميًا
- ثلاث أساسيات إحصائية: α (معدل الإيجابي الكاذب) < 0.05، القوة ≥ 0.8، وحجم عينة كافٍ
- يعتمد حجم العينة على معدل التحويل الأساسي و MDE (الحد الأدنى للتأثير القابل للكشف) - اكتشاف زيادة أصغر يتطلب عينات أكثر
- استراتيجيات التقسيم: مستوى المستخدم (موصى به) > مستوى الجهاز > مستوى الطلب؛ التجزئة المتسقة تضمن الاستقرار
- اختبار A/B لنماذج ML: قارن المقاييس التجارية مثل التحويل / الإيرادات، وليس مقاييس النموذج (AUC / R²)
- MAB يخصص الزيارات ديناميكيًا، مما يقلل من تكلفة الاستكشاف ويناسب سيناريوهات التكرار السريع
📝 تمارين
- أساسي (الصعوبة ⭐): استخدم
proportions_ztestلاختبار ما إذا كان معدلا تحويل (3.0% مقابل 3.5%، 10 آلاف مستخدم لكل منهما) يختلفان بشكل كبير، واحسب قيمة p وفاصل الثقة 95%. تلميح:stats.proportions_ztest. - متوسط (الصعوبة ⭐⭐): احسب حجم العينة لـ MDEs مختلفة وارسم منحنى "MDE مقابل حجم العينة". تلميح:
zt_ind_solve_power+ matplotlib. - تحدٍّ (الصعوبة ⭐⭐⭐): ابنِ محاكي Thompson Sampling - 3 أذرع بمعدلات تحويل حقيقية مختلفة - وبعد 10,000 جولة قارن Thompson Sampling مع ε-Greedy على إجمالي المكافأة ومعدل اختيار أفضل ذراع. تلميح: أخذ عينات من توزيع Beta + المقارنة مع الاستكشاف العشوائي.
← الدرس السابق: نشر النموذج | الدرس التالي: المراقبة في الإنتاج وانحراف النموذج →