من النظرية للـ code
الدرسين اللي فاتوا بنوا النظرية الكاملة للـ support vector machines (آلات المتجهات الداعمة): الـ maximum margin classifier، والـ slack variables (متغيرات الاسترخاء)، والـ kernel trick، والصياغة الـ dual. ودلوقتي جه وقت نستخدم الـ SVM على بيانات حقيقية. في الدرس ده هنمشي في خطوات الشغل العملي كاملة: الـ preprocessing، والتدريب، وظبط الـ hyperparameters بالـ grid search، والـ cross-validation، والتقييم.
هنستخدم scikit-learn، الـ library المعتادة للـ Machine Learning في Python، اللي بتنفّذ الـ SVM عن طريق الـ classes SVC (SVM بـ kernel) وLinearSVC (SVM خطي متحسّن للبيانات الكبيرة).
ليه الـ preprocessing مهم في الـ SVM
الـ SVMs مش محايدة ناحية المقياس. حسابات الـ margin والـ kernel بتعتمد على المسافات الإقليدية. feature بالكيلومتر هتسيطر على feature بالمتر، وده بيميّل الـ decision boundaries. اعمل standardization للـ features بتاعتك دايمًا قبل ما تدرّب SVM.
الوصفة المعتادة هي الـ standardization لمتوسط صفر وvariance واحد باستخدام StandardScaler:
from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline from sklearn.svm import SVC # اعمل الـ scaling دايمًا جوه Pipeline عشان تتجنّب الـ data leakage pipe = Pipeline([ ('scaler', StandardScaler()), ('svm', SVC(kernel='rbf', C=1.0, gamma='scale')) ])
استخدام Pipeline مهم جدًا: بيضمن إن الـ scaler بيتدرّب على بيانات التدريب بس ويتطبّق بنفس الشكل على folds الـ validation والـ test. وإنك تدرّب الـ scaler على الـ dataset كلها قبل التقسيم — غلطة مشهورة — بيسرّب إحصائيات الـ test set للتدريب، فبيطلّع تقديرات accuracy متفائلة زيادة عن اللزوم.
الـ data leakage (تسرب البيانات) من أشهر مصادر النتايج المتفائلة كدب في الـ Machine Learning. لما تعمل scaling للبيانات باستخدام الـ dataset كلها قبل التقسيم، إحصائيات الـ test set بتأثّر على الـ scaler، اللي بدوره بيأثّر على التدريب. والـ Pipeline بيتجنّب ده بإنه يضمن إن الـ preprocessing بيتدرّب من جديد جوه كل fold في الـ cross-validation.
الـ API بتاع SVC
أهم الـ parameters بتاعة SVC:
| الـ parameter | الافتراضي | المعنى |
|---|---|---|
| kernel | 'rbf' |
نوع الـ kernel: 'linear'، 'poly'، 'rbf'، 'sigmoid'، أو function خاصة |
| C | 1.0 |
قوة الـ regularization. C أصغر → margin أوسع، وأخطاء مسموحة أكتر |
| gamma | 'scale' |
الـ bandwidth بتاع RBF/poly/sigmoid. 'scale' = 1/(n_features · X.var()) |
| degree | 3 |
درجة الـ polynomial kernel بس |
| probability | False |
يشغّل تقديرات الاحتمال عن طريق Platt scaling (أبطأ) |
| class_weight | None |
خليه 'balanced' للفئات اللي مش متوازنة |
الـ multi-class classification
الـ SVM في الأساس classifier ثنائي — بيلاقي hyperplane واحد بيفصل بين فئتين. وعشان يتعامل مع عدد فئات K > 2، المسألة بتتفكّ لكذا مسألة ثنائية صغيرة بطريقة من طريقتين.
الـ one-vs-one (واحد-ضد-واحد) (one-vs-one, OvO) بيدرّب classifier واحد لكل جوز فئات — بعدد K(K−1)/2 منهم. ووقت التنبؤ كل classifier بيدّي صوته لواحدة من فئتيه، والفئة اللي ليها أصوات أكتر بتكسب. وده اللي SVC بيعمله من جوه: الـ solver بتاع LIBSVM دايمًا one-vs-one، والـ parameter decision_function_shape بيعيد تشكيل درجات الـ output بس — ومش بيغيّر طريقة التدريب. كل مسألة صغيرة بتشوف الفئتين بتوعها بس، فالـ fits الفردية رخيصة، بس عدد الـ classifiers بيكبر تربيعيًا مع K.
الـ one-vs-rest (واحد-ضد-البقية) (one-vs-rest, OvR) — واسمه كمان one-vs-all — بيدرّب classifier واحد لكل فئة، الإجمالي K، كل واحد بيفصل فئته عن كل الفئات التانية مع بعض. والفئة اللي الـ decision function بتاعتها أعلى بتكسب. وLinearSVC بيستخدم OvR افتراضيًا. محتاج K classifiers بس (خطي في عدد الفئات)، بس كل واحد بيتدرّب على الـ dataset كلها.
الـ one-vs-one بيبني عدد classifiers من رتبة O(K²)، كل واحد على subset صغيرة من فئتين؛ والـ one-vs-rest بيبني عدد classifiers من رتبة O(K)، كل واحد على البيانات كلها. ومع عدد فئات قليل OvO هو المشهور (وهو الافتراضي لـ SVC بـ kernel)؛ ولما K تبقى كبيرة، OvR بيخلّي عدد الـ models معقول. وفي الحالتين scikit-learn بتعمل التفكيك بدالك — فإنت لسه بتنادي fit وpredict مرة واحدة.
استراتيجية الـ cross-validation
تقسيمة واحدة للبيانات بتدّي تقدير فيه noise لأداء الـ generalization. الـ k-fold cross-validation (التحقق المتقاطع) (عادةً k = 5 أو 10) بيقسم البيانات لـ k fold، يدرّب على k-1 fold، ويقيّم على الـ fold اللي على جنب، ويلف على كل الـ k احتمالات. والدرجة اللي بتتبلّغ هي المتوسط (ومعاه الـ standard deviation لو حبيت) على الـ folds.
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 بيحافظ على نسب الفئات في كل fold، وده مهم للبيانات اللي مش متوازنة. والـ shuffle قبل التقسيم بيمنع توزيع folds يعتمد على ترتيب البيانات.
البحث عن الـ hyperparameters: C وγ
أهم اتنين hyperparameters للـ SVM بـ RBF kernel هما C وγ. وهما بيتفاعلوا: الـ C الأمثل بيعتمد على γ والعكس. ومعنى ده إنك لازم تدوّر على grid من أزواج (C، γ)، مش تظبط كل واحد لوحده.
دوّر على مقياس لوغاريتمي: استجابة الـ SVM لـ C وγ تقريبًا خطية لوغاريتميًا، فاختبار قيم زي {0.01، 0.1، 1، 10، 100} بيفيد أكتر من grid خطي.
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_)
خد بالك من الـ syntax بتاع الـ underscore المزدوجة 'svm__C': كده GridSearchCV بيعدّي الـ parameters لخطوة ليها اسم جوه الـ Pipeline. اسم الخطوة هو المفتاح اللي اديته في الـ constructor بتاع الـ Pipeline (هنا 'svm')، واسم الـ parameter بييجي بعد الـ underscoreين.
لو الـ grid بتاعك كبير (parameters كتير، مدى واسع)، RandomizedSearchCV أكفأ من الـ grid search الشامل. بياخد عدد ثابت من تركيبات الـ parameters من التوزيعات اللي حدّدتها، وغالبًا بيلاقي حل قريب من الأمثل في جزء من الوقت. استخدمه لما الـ grid search ياخد ساعات.
تقييم أداء الـ SVM
الـ accuracy (الدقة) metric مضلّل لما الفئات تبقى مش متوازنة. classifier بيتنبأ دايمًا بالفئة الأكتر ممكن يجيب accuracy 90% على تقسيمة 90/10 وهو مالوش أي لازمة للفئة الأقل. استخدم الـ precision، والـ recall، والـ F1، والـ confusion matrix (مصفوفة الالتباس) بدلها.
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 بيوري الـ precision والـ recall والـ F1 لكل فئة مع المتوسطات الـ macro والـ weighted. وفي المسائل الثنائية، اختار scoring metric ماشي مع هدف النشر: الـ precision لو الـ false positives مكلّفة، والـ recall لو الـ false negatives مكلّفة، والـ F1 لو محتاج توازن.
درجات القرار والاحتمالات المتعايرة
افتراضيًا، SVC.predict() بيرجّع class label قاطع. وتقدر تاخد القيمة الخام للـ decision function (المسافة بالإشارة للـ margin) عن طريق decision_function(). ولتقديرات الاحتمال، خلّي probability=True في الـ constructor: scikit-learn بتستخدم الـ Platt scaling (تحجيم Platt) — بتدرّب logistic regression فوق outputs الـ SVM باستخدام cross-validation داخلي — عشان تحوّل درجات القرار لاحتمالات.
LinearSVC للبيانات الكبيرة
للبيانات الكبيرة (>100,000 مثال) بـ kernel خطي، LinearSVC أسرع بكتير من SVC(kernel='linear'). بيستخدم الـ solver بتاع LIBLINEAR (primal coordinate descent) بدل LIBSVM (SMO على الـ dual)، اللي بيكبر زي O(n) في أمثلة التدريب بدل O(n²) لـ O(n³).
from sklearn.svm import LinearSVC # LinearSVC: SVM خطي سريع، من غير kernel trick، ومن غير predict_proba pipe_linear = Pipeline([ ('scaler', StandardScaler()), ('svm', LinearSVC(C=1.0, max_iter=2000)) ])
الـ support vector regression (SVR)
نفس آلية الـ maximum margin بتحل مسألة الـ regression (الانحدار)، مش الـ classification بس. الـ support vector regression (انحدار المتجهات الداعمة) بيناسب function بتفضل مسطّحة قد ما يمكن وفي نفس الوقت بيخلّي أغلب نقط التدريب جوه هامش سماح حواليها.
الفكرة الأساسية هي الأنبوبة الـ ε-insensitive (غير الحسّاسة): الأخطاء الأصغر من ε بتتجاهل خالص — أي prediction جوه ±ε من القيمة الحقيقية الـ loss بتاعه صفر. والنقط اللي واقعة على الأنبوبة أو بره بس هي اللي بتبقى support vectors وبتشكّل الـ fit. وده عكس الـ classification، اللي فيه الـ support vectors هي النقط اللي على الـ margin أو جواه.
ولأن أغلب النقط عادةً بتقع جوه الأنبوبة، الحل بيبقى sparse (متفرّق) — متحدّد بعدد قليل نسبيًا من الـ support vectors، بالظبط زي الـ classification. والـ SVR بيعيد استخدام نفس مجموعة الـ kernels (linear، polynomial، RBF)، فالـ SVR بـ kernel بيمسك العلاقات اللي مش خطية بنفس الطريقة اللي SVC بيمسك بيها الحدود اللي مش خطية. وscikit-learn بتوفّره عن طريق SVR وNuSVR وLinearSVR السريع الخطي بس، زي classes الـ classifiers بالظبط. وبيتحكم فيه زرارين: ε بيظبط عرض الأنبوبة، وC بيوازن بين كسر الأنبوبة والاستواء — نفس الدور اللي C بيعمله في SVC.
نصايح عملية وغلطات مشهورة
ابدأ بـ SVM خطي. سريع وسهل تفسّره وغالبًا بينافس على البيانات اللي أبعادها عالية. وروح للـ RBF بس لو الـ linear kernel عنده underfitting.
اعمل scaling دايمًا. إنك تنسى StandardScaler هي أشهر غلطة في الـ SVM.
الـ grid search على مقياس لوغاريتمي. استخدم np.logspace().
خد بالك من تحذيرات التقارب. لو LinearSVC أو SVC طبع تحذير تقارب، زوّد max_iter. ومتتجاهلش التحذيرات — ممكن الـ model ميكونش اتقارب لحل صالح.
الفئات اللي مش متوازنة. خلّي class_weight='balanced' لما فئة تبقى أقل بكتير من التانية.
عدد الـ support vectors كاختبار منطق. بعد التدريب، بص على model.n_support_. لو تقريبًا كل نقطة تدريب support vector، يبقى الـ model عنده underfitting. ولو عددهم قليل أوي، ممكن يكون عنده overfitting.
الـ SVMs بتلمع على الـ datasets الصغيرة لحد المتوسطة واللي أبعادها عالية، اللي عدد الـ features فيها قريب من عدد الأمثلة أو أكتر — text classification، والبيولوجيا الجزيئية، وfeature vectors الصور. وبتشتغل كويس كمان لما الفئات تبقى بتتفصل تقريبًا بخط (linear SVM) أو لما تحتاج decision boundary غير parametric (SVM بـ RBF). وللبيانات الكبيرة جدًا، الـ tree ensembles أو الـ neural networks عادةً بتغلب الـ SVMs.
- اعمل scaling للـ features دايمًا قبل تدريب الـ SVM — استخدم
StandardScalerجوهPipelineعشان تمنع الـ data leakage. - أهم parameters في
SVC:kernel(RBF افتراضيًا)، وC(الـ regularization)، وgamma(الـ bandwidth بتاع RBF). - استخدم الـ cross-validation بـ
StratifiedKFoldعشان تاخد تقدير موثوق ومش منحاز لأداء الـ generalization. - ظبط C وγ مع بعض بالـ grid search على مقياس لوغاريتمي — هما بيتفاعلوا ومينفعش يتظبطوا كل واحد لوحده.
- للبيانات اللي مش متوازنة، استخدم
class_weight='balanced'وقيّم بالـ F1 أو الـ AUC بدل الـ accuracy. - للبيانات الكبيرة بـ kernel خطي، فضّل
LinearSVC— بيكبر زي O(n) بدل O(n²–n³). - الـ SVM ثنائي في الأساس: الـ multi-class classification بيستخدم one-vs-one (
SVC، عدد classifiers O(K²)) أو one-vs-rest (LinearSVC، O(K))؛ والـ SVR بيوسّع نفس فكرة الـ margin للـ regression عن طريق أنبوبة ε-insensitive. - الـ SVMs بتلمع على الـ datasets الصغيرة لحد المتوسطة، اللي أبعادها عالية، واللي اتعملها processing كويس.