التعامل مع التجميع والتكديس

 التجميع والتكديس (ensembling and stacking) عندما نسمع هاتين الكلمتين ، فإن أول ما يتبادر إلى أذهاننا هو أن الأمر كله يتعلق بمسابقات تعلم الألة. كان هذا هو الحال منذ بضع سنوات ، ولكن الآن مع التقدم في قوة الحوسبة و رخص الحوسبة السحابية ، بدأ الناس في استخدام نماذج التجميع حتى في الصناعات. 

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

إذا كانت هذه هي الحالة ، فما النموذج (النماذج) التي ستختارها؟ شخصياً أفضل نماذج صغيرة متعددة ، والتي تكون أسرع وتعطي نفس الأداء مثل نموذج أكبر وأبطأ بكثير. يرجى تذكر أن النماذج الأصغر هي أيضًا أسهل وأسرع في ضبطها.

التجميع 

التجميع ليس سوى مزيج من نماذج مختلفة. يمكن الجمع بين النماذج من خلال توقعاتهم / احتمالاتهم. إن أبسط طريقة لدمج النماذج هي بحساب المتوسط. 

Ensemble Probabilities = (M1_proba + M2_proba + … + Mn_Proba) / n

هذه هي الطريقة الأبسط والأكثر فعالية لدمج النماذج. في المتوسط ​​البسيط ، تكون الأوزان متساوية لجميع النماذج. شيء واحد يجب أن تضعه في اعتبارك لأي طريقة للجمع هو أنه يجب عليك دائمًا الجمع بين التنبؤات / الاحتمالات للنماذج التي تختلف عن بعضها البعض. 

بعبارة بسيطة ، فإن مجموعة النماذج غير المترابطة بشكل كبير تعمل بشكل أفضل من مجموعة النماذج التي ترتبط ارتباطًا وثيقًا ببعضها البعض. إذا لم يكن لديك احتمالات ، يمكنك الجمع بين التنبؤات أيضًا. إن أبسط طريقة للقيام بذلك هي إجراء تصويت. لنفترض أننا نقوم بتصنيف متعدد الفئات يحتوي ثلاث فئات: 0 و 1 و 2

[0 ، 0 ، 1]: أعلى فئة تم التصويت عليها: 0

[0 ، 1 ، 2]: أعلى فئة تم التصويت عليها: لا شيء (اختر واحدًا عشوائيًا)

[2 ، 2 ، 2]: أعلى فئة تم التصويت عليها: 2

يمكن للوظائف البسيطة التالية إنجاز هذه العمليات البسيطة.

import numpy as np

def mean_predictions(probas):
    """
    قم بإنشاء تنبؤات المتوسط
    """
    return np.mean(probas, axis = 1)

def max_voting(preds):
    """
    قم بإنشاء تنبؤات المتوسط
    
    """
    
    idxs = np.argmax(preds, axis=1)
    return np.take_along_axis(preds, idxs[:, None], axis =1)
    

يرجى ملاحظة أن probas لها احتمال واحد (أي التصنيف الثنائي ، عادةً الفئة 1) في كل عمود. وبالتالي فإن كل عمود هو نموذج جديد. وبالمثل ، بالنسبة للتنبؤات ، يمثل كل عمود تنبؤًا من نماذج مختلفة. كل من هاتين الوظيفتين تفترض مصفوفة متعددة الأبعاد ثنائية الأبعاد. يمكنك تعديله وفقًا لمتطلباتك. 

على سبيل المثال ، قد يكون لديك مصفوفة احتمالات ثنائية الأبعاد لكل نموذج. في هذه الحالة ، ستتغير الوظيفة قليلاً. طريقة أخرى للجمع بين عدة نماذج هي ترتيب احتمالاتها. يعمل هذا النوع من الدمج جيدًا عندما يكون المقياس المعني هو المنطقة الواقعة تحت المنحنى (the area under curve) حيث أن  AUC تدور حول ترتيب العينات.

def rank_mean(probas):
    """
    قم بإنشاء تنبؤات المتوسط بإستخدام الرتب
    
    """
    
    ranked = []
    
    for i in range(probas.shape[1]):
        rank_data = stats.rankdata(probas[:, i])
        rank.append(rank_data)
    
    ranked = np.column_stack(ranked)
    return np.mean(ranked, axis=1)

يرجى ملاحظة أنه في rankdata من scipy ، تبدأ الرتب من 1. 

لماذا تعمل هذه الأنواع من التجميع؟ دعونا نلقي نظرة على الشكل أدناه

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

 يمكن أيضًا الجمع بين الاحتمالات بواسطة الأوزان.

Final Probabilities = w1*M1_proba + w2*M2_proba + … + wn*Mn_proba

حيث

(w1 + w2 + w3 + … + wn) = 1.0

على سبيل المثال ، إذا كان لديك نموذج غابة عشوائي يعطي AUC مرتفعًا جدًا ونموذج انحدار لوجستي مع AUC أقل قليلاً ، فيمكنك دمجها مع 70٪ للغابة العشوائية و 30٪ للانحدار اللوجستي. 

إذن ، كيف توصلت إلى هذه الأرقام؟ دعنا نضيف نموذجًا آخر ، دعنا نقول الآن أن لدينا أيضًا نموذج xgboost الذي يعطي AUC أعلى من الغابة العشوائية. سأقوم الآن بدمجها مع نسبة 3: 2: 1 لـ xgboost: الغابة العشوائية: الانحدار اللوجستي. الوصول إلى هذه الأرقام بسيط. دعونا نرى كيف. 

افترض أن لدينا ثلاثة قرود بثلاثة مقابض بقيم تتراوح بين 0 و 1. هذه القرود تدير المقابض ، ونحسب درجة AUC عند كل قيمة يديرون المقبض إليها. في النهاية ، ستجد القرود تركيب يمنح أفضل AUC.

 نعم ، إنه بحث عشوائي! قبل القيام بهذه الأنواع من عمليات البحث ، يجب أن تتذكر أهم قاعدتين للتجميع.

القاعدة الأولى: هي أن تقوم دائمًا بإنشاء طيات قبل البدء في التجميع.

القاعدة الثانية: هي أن تقوم دائمًا بإنشاء طيات قبل البدء في التجميع.

 نعم. هذان هما أهم قاعدتين ، ولا ، لا خطأ فيما كتبته. الخطوة الأولى هي عمل طيات. للتبسيط ، لنفترض أننا قسمنا البيانات إلى جزأين: طية 1 و  طية 2. يرجى ملاحظة أن هذا يتم فقط من أجل التبسيط في الشرح. في سيناريو العالم الحقيقي ، يجب إنشاء المزيد من الطيات.

 الآن ، نقوم بتدريب نموذج الغابة العشوائية الخاص بنا ونموذج الانحدار اللوجستي ونموذج xgboost الخاص بنا على الطية 1 وعمل تنبؤات في الطية 2. 

بعد ذلك ، نقوم بتدريب النماذج من البداية في الطي 2 و نقوم بعمل تنبؤات في الطية 1. وبالتالي ، فقد أنشأنا تنبؤات لجميع بيانات التدريب. الآن لدمج هذه النماذج ، نأخذ الطية 1 وجميع التوقعات للطية 1 وننشئ وظيفة تحسين تحاول العثور على أفضل الأوزان لتقليل الخطأ أو تعظيم AUC مقابل أهداف الطية 2. 

لذلك ، نحن نوعاً ما ندرب نموذج تحسين على الطية 1 مع الاحتمالات المتوقعة للنماذج الثلاثة وتقييمها في الطية الثانية. لنلقِ نظرة أولاً على الفئة الذي يمكننا استخدامها للعثور على أفضل أوزان لنماذج المتعددة لتحسين   AUC (أو أي نوع من تراكيب مقاييس التنبؤ بشكل عام).

import numpy as np

from functools import  partial
from scipy.optimize import fmin
from sklearn import  metrics

class OptimizeAUC:
    """
    AUC فئة لتحسين

    هذا كل ما تحتاجه للعثور على أفضل أوزان 
    لأي نموذج ولأي مقياس ولأي نوع من التنبؤات
    مع تغييرات صغيرة جدًا ، يمكن استخدام هذه الفئة لتحسين
    الأوزان في نماذج التجميع لأي نوع من التنبؤات

    """

    def __init__(self):
        self.coef_ = 0

    def _auc(self, coef, X, y):
        """
        AUC هذه الوظيفة تقوم بحساب و إرجاه

        : param coef: قائمة المعاملات ، بنفس طول عدد نماذج
        : param X: التنبؤات ، في هذه الحالة مصفوفة ثنائي الأبعاد
        : param y: أهداف ، في حالتنا مجموعة ثنائية في بعد واحد
        """

        # اضرب المعاملات في كل عمود من المصفوفة
        # مع التوقعات.
        # مضروب في العمود 1 coef هذا يعني: العنصر 1 من 
        #coef من مصفوفة التنبؤ ، يتم ضرب العنصر 2 من 
        # بالعمود 2 من مصفوفة التنبؤ وهكذا!

        x_coef = X * coef

        # إنشاء تنبؤات بأخذ مجموع الصف  
        predictions = np.sum(x_coef, axis=1)

        # AUC  حساب نتيجة
        auc_score = metrics.roc_auc_score(y, predictions)

        #AUC إرجاع القيمة السالبة 
        return -1.0 * auc_score
    
    def fit(self, X, y):
        loss_partial = partial(self._auc, X=X, y=y)

        # يمكنك إستخدام أي توزيع dirichlet  سنستخدم توزيع
        # نرغب أن تكون مجموع المعلمات تساوي 1 
        initial_coef = np.random.dirichlet(np.ones(X.shape[1]), size=1)

        #لتقليل دالة الخسارة fmin إستخدام 
        self.coef_ = fmin(loss_partial, initial_coef, disp=True)

    def predict(self, X):
        x_coef = X * self.coef_
        predictions = np.sum(x_coef, axis=1)
        return predictions


دعونا نرى كيفية استخدام هذا ومقارنته بمتوسط ​​بسيط.

import xgboost as xgb
from sklearn.datasets import make_classification
from sklearn import  ensemble
from sklearn import linear_model
from sklearn import metrics
from sklearn import model_selection

# قم بعمل مجموعة بيانات تصنيف ثنائية من 10 آلاف عينة
# و 25 سمة

X, y = make_classification(n_samples=1000, n_features=25)


# القسم إلى طيتين (على سبيل المثال)
xfold1, xfold2 , yfold1, yfold2 = model_selection.train_test_split(
    X,
    y,
    test_size=0.5,
    stratify=y
)

# مناسبة النموذج في الطية 1 وقم بعمل تنبؤات في الطية 2
# لدينا 3 نماذج:
# xgboost الانحدار اللوجستي والغابات العشوائية و 
logreg = linear_model.LogisticRegression()
rf = ensemble.RandomForestClassifier()
xgbc = xgb.XGBClassifier()

# مناسبة جميع النماذج على الطية 1
logreg.fit(xfold1, yfold1)
rf.fit(xfold1, yfold1)
xgbc.fit(xfold1, yfold1)

# توقع جميع النماذج في الطية 2
pred_logpred = logreg.predict_proba(xfold2)[:, 1]
pred_rf = rf.predict_proba(xfold2)[:, 1]
pred_xgbc = xgbc.predict_proba(xfold2)[:, 1]

# إنشاء متوسط لجميع التوقعات
# هذا هو أبسط مجموعة
avg_pred = (pred_logpred + pred_rf + pred_xgbc)

## مجموعة ثنائية الأبعاد لجميع التوقعات
fold2_preds = np.column_stack((
    pred_logpred,
    pred_rf,
    pred_xgbc,
    avg_pred
))

#الفردية AUC حساب وتخزين قيم  

aucs_fold2 = []
for i in range(fold2_preds.shape[1]):
    auc = metrics.roc_auc_score(yfold2, fold2_preds[:, i])
    aucs_fold2.append(auc)

print(f"Fold-2: LR AUC = {aucs_fold2[0]}")
print(f"Fold-2: RF AUC = {aucs_fold2[1]}")
print(f"Fold-2: XGB AUC = {aucs_fold2[2]}")
print(f"Fold-2: Average Pred AUC = {aucs_fold2[3]}")

# الآن نكرر نفس الشيء مع الطية الأخرى
# هذه ليست الطريقة المثالية ، إذا اضطررت إلى تكرار الكود ،
# أنشئ وظيفة!
# مناسبة النماذج في الطية 2 وقم بعمل تنبؤات في الطية 1

logreg = linear_model.LogisticRegression()
rf = ensemble.RandomForestClassifier()
xgbc = xgb.XGBClassifier()

# مناسبة جميع النماذج على الطية 2
logreg.fit(xfold2, yfold2)
rf.fit(xfold2, yfold2)
xgbc.fit(xfold2, yfold2)

# توقع جميع النماذج في الطية 1
pred_logpred = logreg.predict_proba(xfold1)[:, 1]
pred_rf = rf.predict_proba(xfold1)[:, 1]
pred_xgbc = xgbc.predict_proba(xfold1)[:, 1]
avg_pred = (pred_logpred + pred_rf + pred_xgbc)

## مجموعة ثنائية الأبعاد لجميع التوقعات
fold1_preds = np.column_stack((
    pred_logpred,
    pred_rf,
    pred_xgbc,
    avg_pred
))

#الفردية AUC حساب وتخزين قيم  

aucs_fold1 = []
for i in range(fold2_preds.shape[1]):
    auc = metrics.roc_auc_score(yfold1, fold1_preds[:, i])
    aucs_fold1.append(auc)

print(f"Fold-1: LR AUC = {aucs_fold1[0]}")
print(f"Fold-1: RF AUC = {aucs_fold1[1]}")
print(f"Fold-1: XGB AUC = {aucs_fold1[2]}")
print(f"Fold-1: Average Pred AUC = {aucs_fold1[3]}")

# إيجاد أفضل الأوزان بإستخدام المحسن
opt = OptimizeAUC()
# لا ننسى إزالة عامود المتوسط
opt.fit(fold1_preds[:, :-1], yfold1)
opt_preds_fold2 = opt.predict(fold2_preds[:, :-1])
auc = metrics.roc_auc_score(yfold2, opt_preds_fold2)
print(f"Optimized AUC, FOLD 2 = {auc}")
print(f"Coefficients = {opt.coef_}")

opt = OptimizeAUC()
opt.fit(fold2_preds[:, :-1], yfold2)
opt_preds_fold1 = opt.predict(fold1_preds[:, :-1])
auc = metrics.roc_auc_score(yfold1, opt_preds_fold1)
print(f"Optimized AUC, FOLD 1 = {auc}")
print(f"Coefficients = {opt.coef_}")


دعونا نلقي نظرة على الإخراج.

Fold-2: LR AUC = 0.9237747803964863
Fold-2: RF AUC = 0.9604553672858765
Fold-2: XGB AUC = 0.9598393574297188
Fold-2: Average Pred AUC = 0.9573113169810717
Fold-1: LR AUC = 0.91272
Fold-1: RF AUC = 0.950528
Fold-1: XGB AUC = 0.955056
Fold-1: Average Pred AUC = 0.950832
Optimization terminated successfully.
         Current function value: -0.956400
         Iterations: 42
         Function evaluations: 92
Optimized AUC, FOLD 2 = 0.9617113873821982
Coefficients = [-0.01659648  0.27789628  0.58303742]
Optimization terminated successfully.
         Current function value: -0.960255
         Iterations: 13
         Function evaluations: 51
Optimized AUC, FOLD 1 = 0.953712
Coefficients = [0.08534901 0.68633857 0.23972804]

نرى أن المتوسط ​​أفضل ولكن استخدام المُحسِّن للعثور على الحد الأدنى هو أفضل! في بعض الأحيان ، يكون المتوسط ​​هو الخيار الأفضل. كما ترى ، مجموع المعاملات ليس 1.0 ، ولكن هذا جيد لأننا نتعامل مع AUC و  AUC يهتم فقط بالترتيب. 

حتى الغابة العشوائية هي نموذج جماعي. الغابة العشوائية هي مجرد مزيج من العديد من أشجار القرار البسيطة. تأتي الغابة العشوائية في فئة من نماذج التجميع المعروفة باسم التعبئة (bagging). في التعبئة ، نقوم بإنشاء مجموعات فرعية صغيرة من البيانات وتدريب عدة نماذج بسيطة. يتم الحصول على النتيجة النهائية من خلال مجموعة من التنبؤات ، مثل المتوسط ​​، لجميع هذه النماذج الصغيرة. 

ونموذج xgboost الذي استخدمناه هو أيضًا نموذج تجميع. جميع نماذج تعزيز الإشتقاق (gradient boosting) هي نماذج تجميع وتندرج تحت الاسم الشامل: التعزيز (boosting).

 تعمل نماذج التعزيز بشكل مشابه لنماذج التعبئة ، باستثناء حقيقة أن النماذج المتتالية في التعزيز يتم تدريبها على الخطأ المتبقي (error residuals) وتميل إلى تقليل أخطاء النماذج السابقة. بهذه الطريقة ، يمكن للنماذج المعززة أن تتعلم البيانات بشكل مثالي وبالتالي تكون عرضة لفرط التخصيص.

 ما رأيناه في الكود حتى الآن يعتبر عمودًا واحدًا فقط. ليس هذا هو الحال دائمًا ، وستكون هناك عدة مرات يتعين عليك فيها التعامل مع عدة أعمدة للتنبؤات. على سبيل المثال ، قد تواجه مشكلة حيث تتوقع فئة واحدة من فئات متعددة ، أي مشكلة التصنيف متعدد الفئات. 

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

 باستخدام فئات متعددة ، يمكنك محاولة التحسين من أجل خسارة السجل  (log-loss) بدلاً من ذلك . للدمج ، يمكنك استخدام قائمة من مصفوفات نمباي بدلاً من مصفوفة نمباي في دالة الملاءمة (X) وبعد ذلك ، ستحتاج أيضًا إلى تغيير المحسن ووظيفة التنبؤ. 

التكديس

والآن يمكننا الانتقال إلى الموضوع التالي المثير للاهتمام والذي يحظى بشعبية كبيرة ويعرف باسم التكديس. يوضح الشكل كيف يمكنك تكديس النماذج.

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

اسمحوا لي أن أصف الفكرة لك في نقاط بسيطة.

– قسّم بيانات التدريب إلى طيات.

– تدريب مجموعة من النماذج: M1، M2… ..Mn.

– إنشاء تنبؤات تدريب كاملة (باستخدام التدريب خارج الطي) واختبار التنبؤات باستخدام كل هذه النماذج.

– حتى هنا المستوى – 1 (L1).

– استخدم تنبؤات الطيات من هذه النماذج كسمات لنموذج آخر. هذا هو نموذج المستوى 2 (L2) الآن.

– استخدم نفس الطيات السابقة لتدريب نموذج L2 هذا.

– الآن قم بإنشاء تنبؤات OOF (خارج الطي) على مجموعة التدريب ومجموعة الاختبار.

– الآن لديك تنبؤات المستوى الثاني لبيانات التدريب وأيضًا توقعات مجموعة الاختبار النهائية.

يمكنك الاستمرار في تكرار الجزء L1 ويمكنك إنشاء أي عدد تريده من المستويات. وتجدر الإشارة إلى أن ما وصفته في هذا المقال يمكن تطبيقه على أي نوع من المشاكل: التصنيف ، والانحدار ، والتصنيف متعدد العلامات ، إلخ.

إضافة تعليق