0%
تعلّم الآلة: الأساسيات

تعلّم الآلة

كيف تضبط الآلة قاعدةً ما انطلاقًا من أمثلة.

10-15 دقيقة

تعلّم الآلة: الأساسيات

قد يحقق نموذجٌ ما 99% من الإجابات الصحيحة ومع ذلك يكون عديم الفائدة تمامًا. وقد يتعرّف نموذجٌ آخر بإتقان على جميع الأمثلة التي بُني عليها ويكون مع ذلك أسوأ النماذج المرشَّحة. يبدو هذان القولان متناقضين، لكنهما من صميم الواقع اليومي لتعلّم الآلة، وتجاهلهما هو أكثر الطرق شيوعًا لتسليم نظام يفشل منذ لحظة تشغيله.

والسبب أن تعلّم الآلة لا يسعى إلى حفظ الأمثلة، بل إلى التعميم على حالات لم يرها من قبل. ولا يمكن قياس هذه القدرة مباشرةً، فالحالات المستقبلية بحكم تعريفها غير متاحة. لذلك يكمن الفن كله في تنظيم قياس غير مباشر نزيه، ثم اختيار المؤشر المناسب لقراءته.

يبني هذا الدرس وسائل الحماية هذه بالترتيب الذي نحتاج إليه فيه. سنرى أولًا ما يعنيه «التعلّم» عمليًا بالنسبة إلى الآلة، وهو يؤول إلى ضبط أعداد. ثم الفئات الثلاث الكبرى من المسائل بحسب المعلومات التي نملكها. ثم البروتوكول الذي يتيح قياس التعميم، والظاهرة التي يكشفها: فرط التعلّم. وأخيرًا اختيار المقاييس، وهو الموضع الذي ينشأ فيه معظم سوء الفهم بين الفرق التقنية وفرق الأعمال.

تستخدم جميع الأمثلة مكتبة scikit-learn ومجموعات بيانات مرفقة بها: يمكن تشغيلها كما هي دون تنزيل أي شيء.

التعلّم هو ضبط المعاملات

النموذج دالة ذات معاملات. والتعلّم هو البحث عن قيم المعاملات التي تقلّل الفرق بين التنبؤات والإجابات المعروفة. لا غموض في الأمر أكثر من ذلك.

import numpy as np
from sklearn.linear_model import LinearRegression

# خمس شقق: المساحة بالمتر المربع، والإيجار باليورو.
surface = np.array([[30], [45], [60], [75], [90]])
loyer = np.array([620, 780, 1010, 1150, 1400])

modele = LinearRegression().fit(surface, loyer)

print(round(float(modele.coef_[0]), 2))      # 12.87
print(round(float(modele.intercept_), 2))    # 220.0
print(modele.predict([[50]]).round(1))       # [863.3]

وجد النموذج عددين. وتُقرأ القاعدة المتعلَّمة بلغة بشرية: نحو 12.87 يورو للمتر المربع، مضافًا إليها 220 يورو رسومًا ثابتة. لم تُكتب هذه القاعدة، بل استُنتجت من خمس ملاحظات بتقليل مجموع مربعات الفروق.

المهم هنا هو طبيعة التنبؤ لمساحة 50 م²: 863.3 يورو. لا توجد في البيانات أي شقة مساحتها 50 م². لقد قام النموذج بـالاستيفاء بين 45 م² و60 م²، وهذا مشروع. أما أن نسأله عن إيجار شقة مساحتها 400 م² فهو استقراء يتجاوز بكثير نطاق ما شوهد: ستجيب الصيغة دون تردد، ولن يكون هناك أي سبب لتصديقها.

ثلاثة مفردات ينبغي تثبيتها لأنها تتكرر في كل مكان:

المصطلحالمعنىفي المثال
المتغير التفسيري (feature)ما نقدّمه مُدخلًاالمساحة
الهدف (target، label)ما نريد التنبؤ بهالإيجار
المعاملاتالأعداد التي يضبطها التعلّم12.87 و220
المعاملات الفائقة (Hyperparameters)الإعدادات التي يحددها الإنسان قبل التعلّملا شيء هنا؛ وفي موضع آخر عمق الشجرة

الفئات الثلاث للتعلّم

تعتمد الفئة على المعلومات المتاحة، لا على الخوارزمية.

التعلّم الخاضع للإشراف

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

التعلّم غير الخاضع للإشراف

لا نملك سوى المُدخلات. تبحث الخوارزمية عن بنية: مجموعات، أو محاور تباين، أو حالات شاذة. ويصبح التقييم دقيقًا لغياب مرجع صحيح نقارن به.

import numpy as np
from sklearn.cluster import KMeans
from sklearn.datasets import load_iris

iris = load_iris()
# لا نستخدم iris.target: فالخوارزمية لا ترى أي وسم.
groupes = KMeans(n_clusters=3, n_init=10, random_state=0).fit_predict(iris.data)

print(np.bincount(groupes))   # [62 50 38]

# مقارنة لاحقة مع الأنواع الحقيقية للحكم على النتيجة.
import collections
print(collections.Counter(zip(groupes.tolist(), iris.target.tolist())))

يُظهر العدّ الأخير بالضبط ما ينبغي فهمه عن التعلّم غير الخاضع للإشراف. مجموعة واحدة تضم الأزهار الخمسين من نوع setosa ولا شيء غيرها: هذا النوع منفصل بوضوح عن النوعين الآخرين. أما المجموعتان المتبقيتان فتخلطان versicolor وvirginica: إذ وقعت 14 زهرة virginica مع versicolor، وزهرتان من versicolor مع virginica. ست عشرة زهرة من أصل مئة وخمسين جُمّعت خطأً، أي ما يزيد قليلًا على 10%.

لم تفشل الخوارزمية: لقد وجدت المجموعات الثلاث الأكثر تراصًّا بمقياس المسافة الإقليدية. ولا تتطابق هذه المجموعات تمامًا مع الأنواع لأن شيئًا لم يُلزمها بذلك. وهذا هو الحد الجوهري للتعلّم غير الخاضع للإشراف: فهو يكشف بنيةً ما، لا البنية التي كانت في ذهنك.

التعلّم المعزَّز

لا مُدخلات موسومة ولا مجرد ملاحظة: وكيلٌ (agent) يتصرف، ويتلقى مكافأة رقمية، وعليه أن يكتشف الاستراتيجية التي تعظّم مجموع هذه المكافآت. والصعوبة الخاصة بهذا الإطار هي الموازنة بين استغلال ما يبدو جيدًا واستكشاف ما لم يُجرَّب بما يكفي.

import numpy as np

# ثلاث رافعات، لكل منها احتمال ربح يجهله الوكيل.
rng = np.random.default_rng(0)
vraies = np.array([0.2, 0.5, 0.75])

estimations = np.zeros(3)
tirages = np.zeros(3)

for t in range(2000):
    if rng.random() < 0.1:                    # 10% من الوقت: نستكشف
        bras = rng.integers(3)
    else:                                     # وإلا: نستغل أفضل خيار معروف
        bras = int(np.argmax(estimations))

    recompense = 1.0 if rng.random() < vraies[bras] else 0.0
    tirages[bras] += 1
    # المتوسط يُحدَّث بصورة تزايدية.
    estimations[bras] += (recompense - estimations[bras]) / tirages[bras]

print(estimations.round(2))     # [0.19 0.46 0.75]
print(tirages.astype(int))      # [  81   72 1847]

تقترب التقديرات النهائية من الاحتمالات الحقيقية، والأهم أن توزيع المحاولات يروي قصة التعلّم: 1847 محاولة من أصل 2000 خُصّصت لأفضل رافعة، مقابل نحو 75 لكل من الرافعتين الأخريين. لم يكتفِ الوكيل بالتقدير، بل ركّز ميزانيته. ويُسمّى الثمن المدفوع الندم (regret): فالمحاولات الـ153 على الرافعات المتواضعة كانت ضرورية للتأكد من أنها متواضعة فعلًا.

التدريب والاختبار: لماذا نُخفي جزءًا من البيانات

إن تقييم نموذج على الأمثلة نفسها التي استُخدمت في ضبطه يقيس ذاكرته لا قدرته على التعميم. لذلك تقضي القاعدة بتنحية جزء من البيانات قبل أي تعلّم، وعدم الرجوع إليه إلا في النهاية.

from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier

donnees = load_breast_cancer()      # 569 ورمًا، 30 قياسًا، فئتان
X_train, X_test, y_train, y_test = train_test_split(
    donnees.data, donnees.target, test_size=0.3, random_state=42
)
print(X_train.shape, X_test.shape)  # (398, 30) (171, 30)

for profondeur in (1, 3, None):
    arbre = DecisionTreeClassifier(max_depth=profondeur, random_state=0)
    arbre.fit(X_train, y_train)
    print(profondeur,
          round(arbre.score(X_train, y_train), 3),
          round(arbre.score(X_test, y_test), 3))

يثبّت المعامل random_state عملية السحب العشوائي: فبدونه ستعطي عمليتا تنفيذ تقسيمين مختلفين وأرقامًا غير قابلة للمقارنة.

فرط التعلّم، مقيسًا

الجدول الناتج عن الحلقة السابقة هو أكثر نتائج هذا الدرس إفادة:

أقصى عمقالدقة الإجمالية على التدريبالدقة الإجمالية على الاختبارالفارق
10.9250.8950.030
30.9700.9650.005
غير محدود1.0000.9240.076

تصنّف الشجرة غير المحدودة العمق جميع أمثلة التدريب الـ398 تصنيفًا مثاليًا. ومع ذلك فهي أسوأ في الاختبار من الشجرة ذات العمق 3. فقد بنت فروعًا تصف خصوصيات هؤلاء المرضى الـ398 بعينهم، أي ضجيجًا لا انتظامًا حقيقيًا. هذا هو فرط التعلّم (overfitting).

وعلى الطرف الآخر، لا تطرح الشجرة ذات العمق 1 سوى سؤال واحد: فهي شديدة الجمود بحيث لا تلتقط ما هو موجود فعلًا. هذا هو نقص التعلّم (underfitting). والضبط الجيد يقع بين الطرفين، ويُعثر عليه بالقياس لا بالحدس.

الخطأ
  │
  │╲                                    ╱  ← خطأ الاختبار
  │ ╲                                 ╱
  │  ╲                             ╱
  │   ╲__________________________╱        ← المنطقة المفيدة
  │                ╲
  │                  ╲___________________ ← خطأ التدريب
  └──────────────────────────────────────► تعقيد النموذج
     نقص التعلّم             فرط التعلّم

يُعدّ الفارق 0.076 بين التدريب والاختبار إشارة الإنذار التي ينبغي مراقبتها. لكن تقسيمًا واحدًا يظل مجرد سحب عشوائي: فالقياس على 171 مثالًا يترك هامش شك معتبرًا. ويكرّر التحقق المتقاطع العملية على عدة تقسيمات:

from sklearn.model_selection import cross_val_score

scores = cross_val_score(DecisionTreeClassifier(max_depth=3, random_state=0),
                         donnees.data, donnees.target, cv=5)
print(scores.round(3))              # [0.912 0.895 0.939 0.947 0.894]
print(scores.mean().round(3))       # 0.917
print(scores.std().round(3))        # 0.022

المتوسط (0.917) تقدير أكثر اتزانًا من 0.965 المحصَّل على تقسيم مواتٍ، ويُقدّر الانحراف المعياري (0.022) هامش الشك. إن إعلان «96.5%» استنادًا إلى تقسيم واحد ليس كذبًا، لكنه قياس هشّ أكثر من أن يُبنى عليه قرار.

اختيار المقياس

هذا هو الفخ الذي أشرنا إليه في المقدمة. في مرض يصيب 1% من السكان، يحقق نموذج يجيب «سليم» لكل الناس دقة إجمالية قدرها 99%.

import numpy as np
from sklearn.metrics import confusion_matrix, precision_score, recall_score, f1_score

# 1000 مريض، 10 منهم مصابون.
y_vrai = np.array([0] * 990 + [1] * 10)

# نموذج كسول: «لا أحد مريض».
y_paresseux = np.zeros(1000, dtype=int)
print((y_vrai == y_paresseux).mean())                          # 0.99
print(recall_score(y_vrai, y_paresseux, zero_division=0))      # 0.0

دقة إجمالية 99%، واستدعاء 0%: النموذج لا يكشف أي مريض. وفي الفحص الاستقصائي هذا نظام عديم القيمة تمامًا. فالدقة الإجمالية مضلِّلة متى كانت الفئات غير متوازنة، وهذا حال أغلب المسائل المهمة تقريبًا: الاحتيال، والأعطال، والأمراض النادرة، وعيوب التصنيع.

لنقارن بنموذج غير كامل لكنه مفيد:

y_predit = np.zeros(1000, dtype=int)
y_predit[:20] = 1        # 20 شخصًا سليمًا أُبلغ عنهم خطأً
y_predit[990:998] = 1    # 8 مرضى من أصل 10 كُشفوا بشكل صحيح

print(confusion_matrix(y_vrai, y_predit))
# [[970  20]
#  [  2   8]]
print(round(precision_score(y_vrai, y_predit), 3))   # 0.286
print(round(recall_score(y_vrai, y_predit), 3))      # 0.8
print(round(f1_score(y_vrai, y_predit), 3))          # 0.421

تُقرأ مصفوفة الارتباك على النحو الآتي: 970 سليمًا صُنّفوا تصنيفًا صحيحًا، و20 إنذارًا كاذبًا، ومريضان فاتا، و8 مرضى كُشفوا.

المقياسالتعريفالقيمةالسؤال الذي يجيب عنه
الدقة الإجمالية (Accuracy)الإجابات الصحيحة / المجموع0.978هل يخطئ النموذج كثيرًا، بصرف النظر عن الفئة؟
الضبط (Precision)8 / (8 + 20)0.286حين يُصدر إنذارًا، هل يكون محقًّا؟
الاستدعاء (Recall)8 / (8 + 2)0.800كم حالة حقيقية يجد؟
F1المتوسط التوافقي0.421حل وسط وحيد بين المقياسين السابقين

دقة هذا النموذج الإجمالية أقل من دقة النموذج الكسول (0.978 مقابل 0.990)، ومع ذلك فهو أنفع منه بما لا يُقاس: إذ يجد 8 مرضى من أصل 10. والثمن 20 إنذارًا كاذبًا.

ليس لهذا الحل الوسط جواب عام. بل يُحسم خارج الرياضيات:

  • فحص استقصائي لمرض خطير وقابل للعلاج: تُفضَّل الاستدعاء. فالإنذار الكاذب يكلّف فحصًا للتأكد، أما الحالة الفائتة فقد تكلّف حياة.
  • الحظر الآلي لحساب مصرفي: يُفضَّل الضبط. فكل إيجابية كاذبة تعني عميلًا مشروعًا حُظر حسابه.

لهذا يجب اختيار المقياس مع فريق الأعمال قبل التدريب. فالتحسين أولًا ثم النقاش بعد ذلك يؤدي إلى إعادة العمل.

الخلاصة

  • التعلّم يعني ضبط المعاملات لتقليل فارق مقيس على أمثلة. وما ليس موجودًا في البيانات لن يكون موجودًا في النموذج.
  • تتميّز الفئات الثلاث بالمعلومات المتاحة: وسوم (خاضع للإشراف)، مُدخلات فقط (غير خاضع للإشراف)، مكافآت (معزَّز). ويكشف التعلّم غير الخاضع للإشراف بنيةً ما، وليس بالضرورة تلك التي كنت تبحث عنها، وتذكّرنا بذلك 16 زهرة إيريس جُمّعت خطأً من أصل 150.
  • لا تقيس الدرجة على بيانات التدريب شيئًا مفيدًا. فالشجرة التي حققت 1.000 في التدريب و0.924 في الاختبار أسوأ من تلك التي حققت 0.970 / 0.965: هذا فرط تعلّم.
  • يستبدل التحقق المتقاطع بدرجةٍ هشّة متوسطًا وانحرافًا معياريًا. وهذا هو الحد الأدنى قبل إعلان أي رقم لأي شخص.
  • الدقة الإجمالية مضلِّلة في الفئات غير المتوازنة: 99% دون كشف أي مريض. ويُوازَن بين الضبط والاستدعاء بحسب الكلفة الفعلية للإنذار الكاذب وللحالة الفائتة، وهو قرار يعود إلى فريق الأعمال.

التعليقات

التعليقات مدعومة عبر GitHub Discussions

سجّل الدخول باستخدام GitHub للمشاركة في النقاش

تم نسخ الرابط!