الوحدة 4 — الترميز: one-hot والترتيبي وبالهدف
لا تستهلك النماذج إلّا الأرقام. والبيانات الواقعية تغصّ بالفئات: المدينة والعلامة والرمز البريدي ونوع العقد. وتحويلها إلى أرقام يبدو أمرًا هيّنًا — وهو في الحقيقة من أثقل قرارات هندسة المتغيّرات، والاختيار الخاطئ فيه يُدخل معلومةً زائفة في صمت.
فخّ الترميز الساذج
الإغراء المباشر: ترقيم الفئات. باريس = 1، ليون = 2، مرسيليا = 3. وتظهر المشكلة بمجرّد التفكير: فالنموذج يقرأ أرقامًا مرتّبة، فيستنتج أنّ مرسيليا > ليون > باريس، وأنّ الفارق بين باريس ومرسيليا ضِعف الفارق بين باريس وليون، وأنّ متوسّط باريس ومرسيليا يساوي ليون.
ولا معنى لأيّ من هذه العبارات، لكنّها مكتوبة رياضيًّا في البيانات. والنموذج الخطّي، الذي يضرب المتغيّر في معامل، سيستند إليها. فـترقيم فئات غير مرتّبة ترقيمًا تعسّفيًّا خطأٌ لا اختصار.
one-hot: المرجع عند قلّة الفئات
ينشئ ترميز one-hot عمودًا ثنائيًّا لكلّ فئة: عمود الفئة الحاضرة يساوي 1 والبقية 0. فلا يُقترَح أيّ ترتيب بعد ذلك، ولكلّ فئة معاملها الخاصّ.
from sklearn.preprocessing import OneHotEncoder
OneHotEncoder(handle_unknown="ignore", drop="first", sparse_output=False)
ويهمّ وسيطان فعلًا. فـ handle_unknown="ignore" يتجنّب الانهيار في الإنتاج عندما تظهر فئة غائبة عن التدريب — وهو وضع عاديّ وقاتلٌ لولا ذلك. وdrop="first" يحذف عمودًا صار زائدًا (فإن لم تكن أيًّا من البقية فأنت بالضرورة الأولى)؛ وهو نافع للنماذج الخطّية حيث يضرّ الخطّية التامّة، وغير نافع للأشجار.
وحدُّه بنيويّ: عدد الفئات. فمتغيّر رمز بريدي بستّة آلاف قيمة يُنتج ستّة آلاف عمود. فتتضخّم المصفوفة، وتفقد المسافات معناها (لعنة الأبعاد)، ولا يحتوي كلّ عمود إلّا على الأصفار تقريبًا، وتُرى الفئات النادرة مرّاتٍ أقلّ من أن تُتعلَّم. وفي الممارسة يصلح one-hot حتّى بضع عشرات من الفئات.
الترميز الترتيبي: عندما يوجد الترتيب فعلًا
بعض الفئات مرتّبة فعلًا: «منخفض < متوسّط < مرتفع»، و«ابتدائي < ثانوي < عالٍ». فالترقيم هنا ليس مشروعًا فحسب بل مرغوبًا، لأنّ الترتيب معلومة صحيحة تنقلها إلى النموذج.
from sklearn.preprocessing import OrdinalEncoder
OrdinalEncoder(categories=[["منخفض", "متوسط", "مرتفع"]])
ونقطة منهجية: حدّد الترتيب صراحةً. فإن تُرك حرًّا، رتّب المُرمِّز أبجديًّا فأنتج ترتيبًا زائفًا يُعيد المشكلة التي ظننتَ أنّك تجنّبتها.
عدد الفئات المرتفع: الترميز بالهدف وبالتكرار
في المتغيّرات كثيرة الفئات، يحلّ أسلوبان محلّ one-hot حلولًا نافعًا.
الترميز بالتكرار يستبدل الفئة بعدد ورودها. عمود واحد، ولا تسرّب، ومعلومة وجيهة في الغالب — فرمز منتَج ظهر عشرة آلاف مرّة ليس في منزلة رمزٍ ظهر ثلاث مرّات.
الترميز بالهدف يستبدل الفئة بمتوسّط الهدف لهذه الفئة. فمدينةٌ يهجر 8 % من عملائها الخدمة تصير 0,08. وهذا قويّ جدًّا: عمود واحد، ومعلومة مرتبطة مباشرةً بما تتنبّأ به، وقدرة على معالجة آلاف الفئات.
ولهذه القوّة وجهٌ آخر لا بدّ من تسميته بوضوح: الترميز بالهدف يستخدم الهدف، فهو يتسرّب بالبناء. ففئةٌ لا تظهر إلّا ثلاث مرّات تُمنَح متوسّط هذه الأرصاد الثلاثة — فيحفظ النموذج هذه الصفوف بدل أن يتعلّم. وثمّة علاجان لا غنى عنهما:
- التنعيم: مزج متوسّط الفئة بالمتوسّط العامّ بترجيح حسب العدد، حتّى تميل الفئات النادرة إلى المتوسّط الكلّي؛
- الحساب خارج العيّنة: حساب المتوسّط بالتحقّق المتقاطع، بحيث تُرمَّز كلّ ثنية بإحصاءات مستقاة من الثنيات الأخرى.
وتوفّر مكتبة category_encoders صنف TargetEncoder بتنعيم مدمج، ويوفّر scikit-learn اليوم صنفه الخاصّ TargetEncoder بتحقّق متقاطع داخليّ. أمّا كتابة ذلك بيدك فوسيلة ممتازة لصناعة تسرّبٍ لنفسك.
حتّى نحو عشر فئات، one-hot دون تردّد. ومن عشر إلى خمسين يبقى one-hot ممكنًا، ربّما بعد تجميع الفئات النادرة في «أخرى». وبعد ذلك، التكرار أو الهدف مع التنعيم. وإن كان النموذج gradient boosting، فاعلم أنّ LightGBM و CatBoost يعالجان المتغيّرات الفئوية معالجةً أصيلة — وأفضل من الترميز اليدوي غالبًا، خاصّةً عند ارتفاع عدد الفئات.
الخلاصة
- ترقيم فئات غير مرتّبة يُدخل ترتيبًا زائفًا ستستغلّه النماذج الخطّية.
- one-hot هو المرجع عند قلّة الفئات؛ و
handle_unknown="ignore"يحمي الإنتاج، لكنّ الأسلوب يتضخّم بعد بضع عشرات من الفئات. - الترميز الترتيبي لا يصلح إلّا للترتيبات الحقيقية، ويجب التصريح بالترتيب.
- عند ارتفاع عدد الفئات، استخدِم التكرار أو الهدف؛ والأخير يتسرّب بالبناء ويشترط التنعيم والحساب خارج العيّنة.
الوحدة التالية: التواريخ والمتغيّرات الدورية، حيث تشترط معلومةٌ بسيطة في الظاهر ترميزًا خاصًّا.