Machine Learning: اختبار A/B وتصميم التجارب

آخر تحديث: 2026-08-26

شحن نموذج دون اختبار A/B مثل المشي على حبل مشدود دون شبكة أمان - ليس لديك أي فكرة عما إذا كان أفضل حقًا أم مجرد محظوظ.

1. ما ستتعلمه


2. قصة حقيقية من مدير منتج

(1) الألم: عدم معرفة ما إذا كان النموذج الجديد أفضل حقًا

أطلقت منصة SaaS لـ Alice نموذج توصية جديد، وأظهرت لوحة المعلومات ارتفاع التحويل من 3.2% إلى 3.5%. لكن الرئيس التنفيذي اعترض: "قد يكون هذا مجرد تقلب طبيعي - كيف تثبت أن النموذج تسبب فيه؟" لم يكن لدى Alice إجابة — بدون تجربة خاضعة للرقابة، لا يمكن أن يُعزى أي تحسن.

(2) حل اختبار A/B

يقسم اختبار A/B المستخدمين عشوائيًا إلى المجموعة A (النموذج القديم) والمجموعة B (النموذج الجديد)، ويشغلهما في نفس الوقت، ويستخدم الإحصاء لتحديد ما إذا كان الفرق ذا دلالة.

PYTHON
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) إطار اختبار الفرضيات

100%
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) المفاهيم الإحصائية الأساسية

▶ مثال: حساب اختبار الفرضيات

PYTHON
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:

TEXT 📖 للعرض فقط
# 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

PYTHON
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:

TEXT 📖 للعرض فقط
# 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 يوم
📌 نقطة رئيسية: كلما كان MDE أصغر (كلما كانت الزيادة التي تريد اكتشافها أصغر)، كلما احتجت إلى حجم عينة أكبر. مع خط أساس 3.2% فقط، فإن اكتشاف زيادة نسبية 5% (أي 3.2%→3.36%) يتطلب أكثر من 300,000 مستخدم.

(2) استراتيجيات تقسيم الزيارات

الاستراتيجية الطريقة المزايا العيوب
مستوى المستخدم تجزئة حسب user_id تجربة مستخدم متسقة تتطلب تسجيل الدخول
مستوى الطلب حسب request_id بسيطة قد يرى نفس المستخدم نتائج مختلفة
مستوى الجهاز حسب device_id يغطي المستخدمين غير المسجلين قد يتعارض مستخدمو الأجهزة المتعددة

▶ مثال: تقسيم التجزئة المتسقة

PYTHON
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:

TEXT 📖 للعرض فقط
# Functions defined successfully

5. اختبار A/B لنماذج ML

(1) مقارنة النماذج عبر الإنترنت

▶ مثال: اختبار A/B لنموذج التوصية لـ Alice

PYTHON
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:

TEXT 📖 للعرض فقط
=
A/B TEST RESULTS: Recommendation Model
=

(2) المزالق الشائعة لاختبار A/B

المأزق الوصف الحل
التطفل إعلان الدلالة قبل الموعد النهائي حدد مدة التجربة؛ حلل فقط في النهاية
الاختبار المتعدد اختبار مقاييس/مجموعات متعددة تصحيح Bonferroni
تأثير الجدة أداء واجهة المستخدم الجديدة جيدًا في البداية مدد مدة التجربة
التلوث مستخدمو المجموعة A يرون محتوى المجموعة B التقسيم على مستوى المستخدم + التحقق من السجلات
حجم عينة غير كافٍ لم يتم الوصول إلى القوة الإحصائية احسب حجم العينة مسبقًا

6. اللصوص متعددو الأذرع (MAB)

(1) الموازنة بين الاستكشاف والاستغلال

يستخدم اختبار A/B التقليدي تقسيم حركة مرور ثابت (50/50)، بينما يعدل MAB ديناميكيًا - النماذج ذات الأداء الأفضل تحصل على حركة مرور أكثر.

▶ مثال: Thompson Sampling

PYTHON
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:

TEXT 📖 للعرض فقط
# Executed successfully
خوارزمية MAB الاستراتيجية طريقة الاستكشاف التعقيد
ε-Greedy استكشف عشوائيًا باحتمالية ε نسبة ثابتة منخفض
UCB اختر الذراع بأعلى حد ثقة عليا مدفوعة بعدم اليقين متوسط
Thompson Sampling عينة من اللاحق لاختيار ذراع مدفوعة بالاحتمالية متوسط
البُعد اختبار A/B MAB
تخصيص الزيارات ثابت (50/50) يتم تعديله ديناميكيًا
تكلفة الاستكشاف عالية (50% تذهب للأسوأ) منخفضة (تكيفية)
الصرامة الإحصائية عالية (اختبار الفرضيات) منخفضة (مقاربة)
حالة الاستخدام عند الحاجة إلى دليل إحصائي تكرار سريع

❓ أسئلة شائعة

س كم يجب أن تستمر تجربة A/B؟
ج على الأقل أسبوعين (لتغطية تأثيرات الدورة الكاملة مثل فروق عطلة نهاية الأسبوع) وحتى تصل إلى حجم العينة المخطط له. خذ أياهما أطول. لا تتوقف مبكرًا (حتى لو p<0.05).
س هل قيمة p < 0.05 كافية؟
ج لا. تحتاج أيضًا إلى — 1) القوة الإحصائية (power ≥ 0.8)؛ 2) حجم عينة كافٍ؛ 3) أهمية تجارية (هل الزيادة تستحق الاستثمار)؛ 4) استبعاد تأثيرات الدورة.
س ماذا لو تعارض اختبار A/B واختبار B/A؟
ج تحقق من — 1) ما إذا كان هناك تأثير فترة زمنية؛ 2) ما إذا كان التقسيم متساويًا؛ 3) ما إذا كان هناك تأثير تفاعل (النموذج الجديد أفضل لبعض المستخدمين وأسوأ لآخرين). استخدم التحليل الطبقي للحفر أعمق.
س هل يمكن لـ MAB أن يحل محل اختبار A/B؟
ج ليس بالكامل. MAB يناسب الاستكشاف السريع (مثل ترتيب التوصيات)، بينما يناسب اختبار A/B القرارات التي تحتاج إلى دليل إحصائي (مثل استراتيجية التسعير، طرح النموذج). يكملون بعضهم البعض.
س كيف تتعامل مع اختبار A/A (كلتا المجموعتين تستخدمان النموذج القديم)؟
ج اختبار A/A يتحقق من نظام التقسيم - إذا أظهر اختبار A/A فرقًا ذا دلالة، فإن التقسيم مكسور. من الجيد تشغيل التحقق من A/A قبل كل اختبار A/B.
س ماذا عن اختبار مقاييس متعددة في وقت واحد؟
ج استخدم تصحيح Bonferroni - مع k مقاييس، استخدم مستوى دلالة α/k. أو خصص مسبقًا مقياسًا رئيسيًا واحدًا واعتبر الباقي مقاييس ثانوية.

📖 ملخص

📝 تمارين

  1. أساسي (الصعوبة ⭐): استخدم proportions_ztest لاختبار ما إذا كان معدلا تحويل (3.0% مقابل 3.5%، 10 آلاف مستخدم لكل منهما) يختلفان بشكل كبير، واحسب قيمة p وفاصل الثقة 95%. تلميح: stats.proportions_ztest.
  2. متوسط (الصعوبة ⭐⭐): احسب حجم العينة لـ MDEs مختلفة وارسم منحنى "MDE مقابل حجم العينة". تلميح: zt_ind_solve_power + matplotlib.
  3. تحدٍّ (الصعوبة ⭐⭐⭐): ابنِ محاكي Thompson Sampling - 3 أذرع بمعدلات تحويل حقيقية مختلفة - وبعد 10,000 جولة قارن Thompson Sampling مع ε-Greedy على إجمالي المكافأة ومعدل اختيار أفضل ذراع. تلميح: أخذ عينات من توزيع Beta + المقارنة مع الاستكشاف العشوائي.

← الدرس السابق: نشر النموذج | الدرس التالي: المراقبة في الإنتاج وانحراف النموذج →

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%