الوحدة 5 — Spark ML: المحوّلات والمقدِّرات
pyspark.ml مكتبة MLlib الحديثة المبنيّة فوق DataFrame (ملحقة pyspark.mllib القديمة تعمل على RDD وستُهجَر). تعرض نموذجًا موحَّدًا مستوحًى من scikit-learn لكنّه مطبَّق على مقياس موزَّع: المحوّل و المقدِّر.
المحوّل والمقدِّر
- المحوّل (
Transformer) شيء يُحوّل DataFrame إلى DataFrame آخر بإضافة أعمدة. لا يتعلّم شيئًا، لديهtransform(df). أمثلة:VectorAssembler،Tokenizer. - المقدِّر (
Estimator) شيء يتعلّم من DataFrame لِيُنتج محوّلًا. لديهfit(df)الذي يُرجعModel(وهو محوّل). أمثلة:StringIndexerالذي يتعلّم قاموس القِيَم،StandardScalerالذي يحسب ا لمتوسّط والانحراف المعياريّ،LogisticRegressionالذي يتعلّم الأوزان.
الاصطلاح الأهمّ للفهم: كلّ ما لدى Spark ML يعمل على عمود واحد اسمه features يحوي متّجهًا لكلّ سطر. فرغم أن DataFrame الأصلي فيه ثلاثون عمودًا، ستتحوّل الأعمدة الرقميّة والتصنيفيّة إلى عمود متّجهيّ واحد. هذا التوحيد ما يُتيح لِخوارزميّة واحدة أن تعمل مع أيّ مجموعة متغيّرات.
المتغيّرات التصنيفيّة: من نصّ إلى رقم
خذ عمود compagnie: قيم نصّيّة ("AF"، "BA"، "LH"، ...). لا يفهم نموذج رياضيّ النصّ. الحلّ في مرحلتين.
أوّلًا StringIndexer يتعلّم من DataFrame قاموسًا يربط كلّ قيمة بعدد صحيح:
from pyspark.ml.feature import StringIndexer
indexer_compagnie = StringIndexer(
inputCol="compagnie",
outputCol="compagnie_idx",
handleInvalid="keep"
)
handleInvalid="keep" هام: يخصّص فهرسًا احتياطيًّا لِلقيم المجهولة التي قد تظهر في التسجيل ولم يرها التدريب.
ثانيًا OneHotEncoder يُحوّل هذا العدد إلى متّجه بترميز واحد ساخن:
from pyspark.ml.feature import OneHotEncoder
encoder_compagnie = OneHotEncoder(
inputCol="compagnie_idx",
outputCol="compagnie_oh"
)
خطأ شائع: تخطّي StringIndexer وتمرير النصّ مباشرة إلى OneHotEncoder. الأخير لا يقبل إلّا الأعداد.
المتغيّرات الرقميّة: التسوية
المتغيّرات الرقميّة (distance، mois، retard_depart) تُمرَّر مباشرة، لكن مقاييسها مختلفة: المسافة بالكيلومترات (0 إلى 12000)، الشهر بين 1 و 12. الخوارزميّات ذات التدرّج (الانحدار اللوجستيّ، الشبكة العصبيّة) تتعلّم أبطأ حين تختلف المقاييس. StandardScaler يعيد كلّ عمود إلى متوسّط صفر وانحراف واحد:
from pyspark.ml.feature import StandardScaler
scaler = StandardScaler(
inputCol="features_brutes",
outputCol="features",
withMean=True,
withStd=True
)
لاحظ أنّه يعمل على عمود متّجهي، لا على عمود قيميّ. ولهذا نحتاج أوّلًا إلى تجميع كلّ متغيّراتنا.
التجميع في متّجه: VectorAssembler
هذا المحوّل يأخذ عدّة أعمدة رقميّة أو متّجهيّة ويجمعها في متّجه واحد:
from pyspark.ml.feature import VectorAssembler
assembleur = VectorAssembler(
inputCols=["compagnie_oh", "distance", "mois", "heure_depart"],
outputCol="features_brutes",
handleInvalid="skip"
)
القيم الفارغة مشكلة كبيرة: handleInvalid="error" (الافتراض) يفشل التحويل عند أوّل null، وهذا كثير على 60 مليون سطر. "skip" يقصي الأسطر، و "keep" يستبدل الأصفار مع علامة. الاختيار قرار مجاليّ لا تقنيّ.
متّجهات كثيفة ومتفرّقة
Spark ML يستعمل نوعَين للمتّجه: DenseVector (مصفوفة عاديّة) و SparseVector (فهرسا الفهارس والقِيَم غير الصفريّة فقط). بعد OneHotEncoder تُنشأ متّجهات متفرّقة تلقائيًّا: على 300 شركة طيران، متّجه من 299 موضعًا فيه واحد فقط، لا يُخزَّن كاملًا. هذه التفاصيل تُدار وحدها في معظم الأحيان، لكنّها تفسّر لِمَ يبدو استعمال الذاكرة لديك أقلّ ممّا كنت تنتظره.
المسائل الحدّيّة
- حين تكون الفئات كثيرة:
StringIndexerثمّOneHotEncoderينفجران على مليون قيمة مميّزة. البدائل:FeatureHasher(تجزئة بموضع ثابت) أو تخفيض الفئات ال نادرة إلى"AUTRE". - حين يكون المتغيّر مرتَّبًا: الشهر ليس تصنيفيًّا فعليًّا، الفرق بين 1 و 2 موجود. رمِّزه رقميًّا مع ترميز دوريّ (جيب وجيب تمام) بدل التسخين الواحد.
- حين يفشل التسوية: عمود لا يتغيّر (انحراف صفر) يُنتج قسمة على صفر. أَقصِ الأعمدة الثابتة قبل التسوية.
StringIndexer بمُدخَل عمود واحد يتعلّم قاموس قيمه فقط. لا تجمع كلّ الأعمدة النصّيّة في مُفَهرِس واحد يُطبَّق تسلسليًّا: كلّ عمود مقدِّر مستقلّ. Spark يعرض إصدارًا يقبل قائمة أعمدة (inputCols) — استعمله، لكن اِفهم أنّه يبني قاموسًا لكلّ عمود، لا قاموسًا مشتركًا.
الخلاصة
- المحوّل يُحوّل بلا تعلُّم؛ المقدِّر يتعلّم من DataFrame لِيُن تج محوّلًا.
- VectorAssembler يجمع أعمدة رقميّة ومتّجهيّة في عمود واحد اسمه
featuresهو ما تتوقّعه الخوارزميّات. - StringIndexer ثمّ OneHotEncoder يُحوّلان النصّ التصنيفيّ إلى متّجه؛
handleInvalid="keep"يحمي التسجيل من القيم الجديدة. - StandardScaler يوحّد المقاييس؛ تفاديه للأشجار الحتميّة، اِستعمله للنماذج ذات التدرّج.
الوحدة التالية: كيف نُغلِّف كلّ هذه المحوّلات والمقدِّر النهائيّ في Pipeline واحد، ثمّ نُشغِّل تحقّقًا متقاطعًا موزَّعًا بلا تسرُّب بيانات.