من النظرية إلى الكود
بنى الدرسان السابقان النظرية الكاملة لآلات المتجهات الداعمة: المُصنِّف ذو الهامش الأقصى، ومتغيرات الاسترخاء، وخدعة النواة، والصياغة الثنائية. حان الوقت الآن لاستخدام SVM على بيانات حقيقية. في هذا الدرس نمر بالسير العملي الكامل: المعالجة المسبقة، والتدريب، وضبط المعاملات الفائقة عبر البحث الشبكي، والتحقق المتقاطع، والتقييم.
سنستخدم scikit-learn، مكتبة التعلم الآلي القياسية في Python، التي تُنفِّذ SVM عبر صنفَي SVC (SVM بنواة) وLinearSVC (SVM خطي مُحسَّن للبيانات الكبيرة).
لماذا تُهمّ المعالجة المسبقة في SVM
آلات المتجهات الداعمة ليست محايدة تجاه المقياس. تعتمد حسابات الهامش والنواة على المسافات الإقليدية. ميزة بالكيلومتر ستهيمن على ميزة بالمتر، مما يُحيز حدود القرار. دائمًا وحِّد ميزاتك قبل تدريب SVM.
الوصفة القياسية هي التوحيد بمتوسط صفر وتباين واحد باستخدام StandardScaler:
from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline from sklearn.svm import SVC # دائمًا وحِّد داخل Pipeline لتجنب تسرب البيانات pipe = Pipeline([ ('scaler', StandardScaler()), ('svm', SVC(kernel='rbf', C=1.0, gamma='scale')) ])
استخدام Pipeline أمر بالغ الأهمية: يضمن أن المُوحِّد يُدرَّب فقط على بيانات التدريب ويُطبَّق باتساق على طيات التحقق والاختبار. تدريب المُوحِّد على مجموعة البيانات كاملة قبل التقسيم — خطأ شائع — يُسرِّب إحصائيات مجموعة الاختبار إلى التدريب، مما ينتج تقديرات دقة متفائلة بشكل مبالغ فيه.
تسرب البيانات هو أحد أكثر مصادر النتائج المتفائلة زيفًا شيوعًا في التعلم الآلي. عند توحيد البيانات باستخدام مجموعة البيانات الكاملة قبل التقسيم، تؤثر إحصائيات مجموعة الاختبار على المُوحِّد، الذي يؤثر بدوره على التدريب. تجنب Pipeline هذا عبر ضمان إعادة تدريب المعالجة المسبقة داخل كل طية تحقق متقاطع.
واجهة SVC البرمجية
المعاملات الرئيسية لـ SVC هي:
| المعامل | الافتراضي | المعنى |
|---|---|---|
| kernel | 'rbf' |
نوع النواة: 'linear'، 'poly'، 'rbf'، 'sigmoid'، أو دالة مخصصة |
| C | 1.0 |
قوة التنظيم. C أصغر → هامش أوسع، مزيد من الأخطاء المسموحة |
| gamma | 'scale' |
عرض النطاق لـ RBF/poly/sigmoid. 'scale' = 1/(n_features · X.var()) |
| degree | 3 |
درجة النواة متعددة الحدود فقط |
| probability | False |
تفعيل تقديرات الاحتمالية عبر تحجيم Platt (أبطأ) |
| class_weight | None |
اضبط على 'balanced' للفئات غير المتوازنة |
التصنيف متعدد الفئات
إنّ SVM في جوهره مصنِّف ثنائي — فهو يجد مستوًى فائقًا واحدًا يفصل بين فئتين. للتعامل مع عدد فئات K > 2، تُفكَّك المسألة إلى عدة مسائل ثنائية فرعية باستخدام إحدى طريقتين.
واحد-ضد-واحد (one-vs-one, OvO) يُدرِّب مصنِّفًا واحدًا لكل زوج من الفئات — بعددٍ يبلغ K(K−1)/2 منها. عند التنبؤ يُدلي كل مصنِّف بصوت لإحدى فئتيه، وتفوز الفئة الأكثر أصواتًا. هذا ما يفعله SVC داخليًا: فمحلل LIBSVM دائمًا واحد-ضد-واحد، والمعامل decision_function_shape يعيد تشكيل درجات المخرجات فقط — ولا يغيّر طريقة التدريب. يرى كل مسألة فرعية الفئتين المعنيّتين فقط، فتكون الملاءمات الفردية رخيصة، لكن عدد المصنِّفات ينمو تربيعيًا مع K.
واحد-ضد-البقية (one-vs-rest, OvR) — ويُسمّى أيضًا واحد-ضد-الكل — يُدرِّب مصنِّفًا واحدًا لكل فئة، بإجمالي K، يفصل كلٌّ منها فئته عن كل الفئات الأخرى مجتمعةً. تفوز الفئة التي تسجّل دالة قرارها الأعلى. يستخدم LinearSVC طريقة OvR افتراضيًا. يحتاج إلى K مصنِّفات فقط (خطيًا في عدد الفئات)، لكن يُدرَّب كلٌّ منها على مجموعة البيانات كاملةً.
يبني واحد-ضد-واحد عددًا من المصنِّفات من رتبة O(K²)، كلٌّ منها على مجموعة فرعية صغيرة من فئتين؛ بينما يبني واحد-ضد-البقية عددًا من المصنِّفات من رتبة O(K)، كلٌّ منها على البيانات كاملةً. لعددٍ قليل من الفئات يشيع OvO (وهو الافتراضي لـ SVC بنواة)؛ وعندما تكون K كبيرة، يُبقي OvR عدد النماذج قابلًا للإدارة. في كلتا الحالتين تُنفِّذ scikit-learn التفكيك نيابةً عنك — فما زلت تستدعي fit وpredict مرة واحدة.
استراتيجية التحقق المتقاطع
تعطي تقسيمة واحدة للبيانات تقديرًا صاخبًا لأداء التعميم. التحقق المتقاطع k-fold (عادةً k = 5 أو 10) يقسم البيانات إلى k طيات، يُدرِّب على k-1 طية، ويُقيِّم على الطية المحجوزة، مع التدوير عبر جميع الاحتمالات k. الدرجة المُبلَّغ عنها هي المتوسط (وانحراف معياري اختياريًا) عبر الطيات.
from sklearn.model_selection import cross_val_score, StratifiedKFold cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) scores = cross_val_score(pipe, X_train, y_train, cv=cv, scoring='accuracy') print(f"CV accuracy: {scores.mean():.3f} ± {scores.std():.3f}")
يحافظ StratifiedKFold على نسب الفئات في كل طية، وهو أمر مهم للبيانات غير المتوازنة. الخلط قبل التقسيم يمنع تعيينات الطيات التي تعتمد على ترتيب البيانات.
البحث عن المعاملات الفائقة: C وγ
أهم معاملين فائقين لـ SVM بنواة RBF هما C وγ. يتفاعلان: الـ C الأمثل يعتمد على γ والعكس. هذا يعني أنك يجب أن تبحث عبر شبكة من أزواج (C، γ)، ليس ضبط كل منهما بشكل مستقل.
ابحث على مقياس لوغاريتمي: استجابة SVM لـ C وγ خطية لوغاريتميًا تقريبًا، لذا اختبار قيم مثل {0.01، 0.1، 1، 10، 100} أكثر إفادة من شبكة خطية.
from sklearn.model_selection import GridSearchCV import numpy as np param_grid = { 'svm__C': np.logspace(-2, 3, 6), 'svm__gamma': np.logspace(-4, 1, 6), } search = GridSearchCV( pipe, param_grid, cv=5, scoring='f1_weighted', n_jobs=-1, verbose=1 ) search.fit(X_train, y_train) print(search.best_params_)
لاحظ بناء جملة الشرطة السفلية المزدوجة 'svm__C': هكذا يمرر GridSearchCV المعاملات إلى خطوة مُسمَّاة داخل Pipeline. اسم الخطوة هو المفتاح الذي أعطيته في منشئ Pipeline (هنا 'svm')، ويتبع اسم المعامل الشرطتين السفليتين.
إذا كانت شبكتك كبيرة (معاملات كثيرة، نطاقات واسعة)، فإن RandomizedSearchCV أكثر كفاءة من البحث الشبكي الشامل. يُعيِّن عددًا ثابتًا من مجموعات المعاملات من التوزيعات المحددة، ويجد في الغالب حلًا قريبًا من الأمثل في جزء من الوقت. استخدمه عندما يستغرق البحث الشبكي ساعات.
تقييم أداء SVM
الدقة مقياس مُضلِّل عند عدم توازن الفئات. مصنِّف يتنبأ دائمًا بالفئة الأكثر يمكنه تحقيق دقة 90٪ على تقسيمة 90/10 مع كونه عديم الفائدة تمامًا للفئة الأقلية. استخدم الدقة، والاستدعاء، وF1، ومصفوفة الالتباس بدلًا من ذلك.
from sklearn.metrics import classification_report, confusion_matrix best_model = search.best_estimator_ y_pred = best_model.predict(X_test) print(classification_report(y_test, y_pred)) print(confusion_matrix(y_test, y_pred))
يُظهر classification_report الدقة والاستدعاء وF1 لكل فئة مع المتوسطات الماكروية والموزونة. للمشاكل الثنائية، اختر مقياس تسجيل يتوافق مع هدف النشر: الدقة إذا كانت الإيجابيات الكاذبة مكلفة، الاستدعاء إذا كانت السلبيات الكاذبة مكلفة، F1 إذا كنت بحاجة إلى توازن.
درجات القرار والاحتمالات المُعايَرة
بشكل افتراضي، يُرجع SVC.predict() تسمية فئة صارمة. يمكنك الحصول على قيمة دالة القرار الخام (المسافة الموقعة إلى الهامش) عبر decision_function(). لتقديرات الاحتمالية، اضبط probability=True في المنشئ: تستخدم scikit-learn تحجيم Platt — تُدرِّب انحدارًا لوجستيًا فوق مخرجات SVM باستخدام تحقق متقاطع داخلي — لتحويل درجات القرار إلى احتمالات.
LinearSVC للبيانات الكبيرة
للبيانات الكبيرة (>100,000 مثال) بنواة خطية، يكون LinearSVC أسرع بكثير من SVC(kernel='linear'). يستخدم محلل LIBLINEAR (نزول الإحداثيات الأولي) بدلًا من LIBSVM (SMO على الثنائي)، الذي يتوسع كـ O(n) في أمثلة التدريب بدلًا من O(n²) إلى O(n³).
from sklearn.svm import LinearSVC # LinearSVC: SVM خطي سريع، بلا خدعة النواة، بلا predict_proba pipe_linear = Pipeline([ ('scaler', StandardScaler()), ('svm', LinearSVC(C=1.0, max_iter=2000)) ])
انحدار المتجهات الداعمة (SVR)
تحلّ آليةُ الهامش الأقصى نفسها مسألة الانحدار، لا التصنيف فقط. يُلائم انحدار المتجهات الداعمة دالةً تبقى مسطحةً قدر الإمكان مع إبقاء معظم نقاط التدريب ضمن هامش تسامح حولها.
الفكرة الأساسية هي الأنبوب غير الحسّاس بعرض ε: تُتجاهَل الأخطاء الأصغر من ε تمامًا — فأي تنبؤ ضمن ±ε من القيمة الحقيقية يتكبّد خسارة صفرية. النقاط التي تقع على الأنبوب أو خارجه فقط تصبح متجهات دعم وتشكّل الملاءمة. هذه صورة معكوسة للتصنيف، حيث تكون متجهات الدعم هي النقاط الواقعة على الهامش أو داخله.
لأنّ معظم النقاط تقع عادةً داخل الأنبوب، يكون الحل متفرّقًا — محدَّدًا بعدد قليل نسبيًا من متجهات الدعم، تمامًا كما في التصنيف. يعيد SVR استخدام ترسانة النوى نفسها (خطية، متعددة الحدود، RBF)، فيلتقط SVR بنواة العلاقاتِ غير الخطية بالطريقة نفسها التي يلتقط بها SVC الحدودَ غير الخطية. تُتيحه scikit-learn عبر SVR وNuSVR وLinearSVR السريع الخطي فقط، بما يوازي أصناف المصنِّفات. يحكمه مفتاحان: ε يضبط عرض الأنبوب، وC يقايض بين انتهاكات الأنبوب والاستواء — الدور نفسه الذي يؤديه C في SVC.
نصائح عملية وأخطاء شائعة
ابدأ بـ SVM خطي. سريع وقابل للتفسير وغالبًا تنافسي على البيانات عالية الأبعاد. انتقل إلى RBF فقط إذا كان النواة الخطية تُعاني من نقص التخصيص.
دائمًا وحِّد. نسيان StandardScaler هو الخطأ الأكثر شيوعًا في SVM.
البحث الشبكي على مقياس لوغاريتمي. استخدم np.logspace().
انتبه لتحذيرات التقارب. إذا طبعت LinearSVC أو SVC تحذير تقارب، زد max_iter. لا تتجاهل التحذيرات — قد لا يكون النموذج قد تقارب إلى حل صالح.
الفئات غير المتوازنة. اضبط class_weight='balanced' عندما تكون فئة ما أقل كثيرًا من الأخرى.
عدد متجهات الدعم كفحص عقلاني. بعد التدريب، افحص model.n_support_. إذا كانت تقريبًا كل نقطة تدريب متجه دعم، فالنموذج يُعاني من نقص التخصيص. إذا كان عددها قليلًا جدًا، فقد يكون في حالة إفراط التخصيص.
تتألق SVMs على مجموعات البيانات الصغيرة إلى المتوسطة وعالية الأبعاد حيث عدد الميزات مماثل لعدد الأمثلة أو يتجاوزه — تصنيف النصوص، علم الأحياء الجزيئي، متجهات ميزات الصور. تعمل أيضًا جيدًا عندما تكون الفئات قابلة للفصل بالقرب من الخط (SVM خطي) أو عندما تحتاج إلى حد قرار غير معلمي (SVM بـ RBF). للبيانات الكبيرة جدًا، تتفوق التجميعات الشجرية أو الشبكات العصبية عادةً على SVMs.
- دائمًا وحِّد الميزات قبل تدريب SVM — استخدم
StandardScalerداخلPipelineلمنع تسرب البيانات. - المعاملات الرئيسية لـ
SVC:kernel(RBF افتراضيًا)،C(التنظيم)،gamma(عرض نطاق RBF). - استخدم التحقق المتقاطع
StratifiedKFoldللحصول على تقدير موثوق وغير متحيز لأداء التعميم. - اضبط C وγ معًا عبر البحث الشبكي على مقياس لوغاريتمي — يتفاعلان ولا يمكن ضبطهما بشكل مستقل.
- للبيانات غير المتوازنة، استخدم
class_weight='balanced'وقيِّم بـ F1 أو AUC بدلًا من الدقة. - للبيانات الكبيرة بنواة خطية، فضِّل
LinearSVC— يتوسع كـ O(n) بدلًا من O(n²–n³). - SVM ثنائي في جوهره: التصنيف متعدد الفئات يستخدم واحد-ضد-واحد (
SVC، عدد مصنِّفات O(K²)) أو واحد-ضد-البقية (LinearSVC، O(K))؛ ويوسّع SVR فكرة الهامش نفسها إلى الانحدار عبر أنبوب غير حسّاس بعرض ε. - تتفوق SVMs على مجموعات البيانات الصغيرة إلى المتوسطة وعالية الأبعاد والمعالجة جيدًا.