ادامهٔ ارزیابی OOB
بر اساس ارزیابی Out-of-Bag در بخش قبل، انتظار میرفت BaggingClassifier روی مجموعهٔ آزمون حدود ۸۹٫۶٪ دقت داشته باشد. با ارزیابی واقعی روی آزمون نتیجه چنین است:
>>> from sklearn.metrics import accuracy_score
>>> y_pred = bag_clf.predict(X_test)
>>> accuracy_score(y_test, y_pred)
0.92
دقت واقعی ۹۲٪ است؛ بنابراین ارزیابی OOB در این مثال کمی بدبینانه بوده و بیش از دو درصد کمتر از نتیجهٔ آزمون را برآورد کرده است.
تابع تصمیم OOB برای هر نمونهٔ آموزشی در oob_decision_function_ نیز موجود است. چون Estimator پایه متد predict_proba() دارد، این ویژگی احتمال کلاسها را برای هر نمونه برمیگرداند. برای سه نمونهٔ نخست:
>>> bag_clf.oob_decision_function_[:3]
array([[0.32352941, 0.67647059],
[0.3375 , 0.6625 ],
[1. , 0. ]])
برای نمونهٔ اول، ارزیابی OOB احتمال کلاس مثبت را حدود ۶۷٫۶٪ و کلاس منفی را ۳۲٫۴٪ برآورد کرده است.
Random Patches و Random Subspaces
BaggingClassifier علاوه بر نمونهگیری از سطرهای آموزشی، میتواند از ویژگیها نیز نمونهگیری کند. این کار با دو فراپارامتر max_features و bootstrap_features کنترل میشود؛ عملکرد آنها مشابه max_samples و bootstrap است، با این تفاوت که بهجای نمونههای آموزشی، ویژگیها را انتخاب میکنند. بنابراین هر پیشبینیکننده میتواند فقط روی زیرمجموعهای تصادفی از ویژگیهای ورودی آموزش ببیند.
این تکنیک برای ورودیهای پُربعد مانند تصویر بسیار مفید است، چون میتواند آموزش را بهطور محسوسی سریعتر کند. نمونهگیری همزمان از نمونههای آموزشی و ویژگیها Random Patches نام دارد. اگر تمام نمونهها حفظ شوند ــ با bootstrap=False و max_samples=1.0 ــ ولی فقط ویژگیها نمونهگیری شوند، روش Random Subspaces نام دارد.
نمونهگیری ویژگیها تنوع پیشبینیکنندهها را بیشتر میکند: Bias اندکی بالا میرود اما Variance کاهش مییابد.
جنگلهای تصادفی
جنگل تصادفی مجموعهای از درختهای تصمیم است که معمولاً با Bagging ــ و گاهی Pasting ــ آموزش داده میشوند و اغلب max_samples برابر اندازهٔ کل مجموعهٔ آموزشی است. بهجای ساخت دستی BaggingClassifier با DecisionTreeClassifier، میتوان از RandomForestClassifier استفاده کرد که هم سادهتر است و هم برای درخت تصمیم بهینه شده است. برای رگرسیون نیز RandomForestRegressor وجود دارد.
کد زیر جنگلی شامل ۵۰۰ درخت میسازد که هرکدام حداکثر ۱۶ برگ دارند و از همهٔ هستههای CPU استفاده میکند:
from sklearn.ensemble import RandomForestClassifier
rnd_clf = RandomForestClassifier(
n_estimators=500,
max_leaf_nodes=16,
n_jobs=-1,
random_state=42
)
rnd_clf.fit(X_train, y_train)
y_pred_rf = rnd_clf.predict(X_test)
بهجز چند استثنا، RandomForestClassifier هم فراپارامترهای DecisionTreeClassifier را برای کنترل رشد درختها دارد و هم بسیاری از فراپارامترهای BaggingClassifier را برای کنترل خود Ensemble.
جنگل تصادفی هنگام رشد درختها تصادفیبودن بیشتری وارد میکند: بهجای جستوجوی بهترین ویژگی در میان همهٔ ویژگیها برای هر تقسیم، بهترین ویژگی را فقط از میان یک زیرمجموعهٔ تصادفی از ویژگیها پیدا میکند. این کار تنوع درختها را افزایش میدهد؛ معمولاً Bias کمی بیشتر و Variance کمتر میشود و نتیجهٔ کلی مدل بهبود مییابد.
نمونهٔ زیر تقریباً معادل جنگل تصادفی قبل است:
bag_clf = BaggingClassifier(
DecisionTreeClassifier(
max_features="sqrt",
max_leaf_nodes=16
),
n_estimators=500,
n_jobs=-1,
random_state=42
)
Extra-Trees
در جنگل تصادفی، برای هر گره فقط زیرمجموعهای تصادفی از ویژگیها بررسی میشود، اما هنوز بهترین آستانهٔ تقسیم برای آن ویژگیها جستوجو میشود. میتوان تصادفیبودن را یک گام بیشتر کرد و آستانهها را نیز بهصورت تصادفی انتخاب کرد؛ برای این کار در DecisionTreeClassifier میتوان splitter="random" را به کار برد.
جنگلی از این درختهای بسیار تصادفی Extremely Randomized Trees یا به اختصار Extra-Trees نام دارد. این روش نیز در برابر کاهش Variance، مقدار بیشتری Bias میپذیرد. همچنین آموزش Extra-Trees معمولاً بسیار سریعتر از Random Forest است، چون جستوجوی بهترین آستانه برای هر ویژگی از پرهزینهترین بخشهای رشد درخت است.
در Scikit-Learn کلاسهای ExtraTreesClassifier و ExtraTreesRegressor وجود دارند. رابط آنها تقریباً همان رابط Random Forest است، با این تفاوت که bootstrap بهطور پیشفرض False است.
از قبل نمیتوان با اطمینان گفت Random Forest بهتر است یا Extra-Trees. راه عملی این است که هر دو را امتحان کنید و با Cross-Validation مقایسه کنید.
اهمیت ویژگیها
یکی از مزیتهای مهم جنگل تصادفی امکان اندازهگیری سادهٔ اهمیت نسبی ویژگیها است. Scikit-Learn اهمیت یک ویژگی را بر اساس میزان کاهش ناخالصی در گرههایی که از آن ویژگی استفاده کردهاند محاسبه میکند و سپس این کاهش را در همهٔ درختهای جنگل میانگین میگیرد. این میانگین وزندار است و وزن هر گره با تعداد نمونههای آموزشی مرتبط با آن تعیین میشود.
پس از آموزش، Scikit-Learn این امتیازها را بهصورت خودکار محاسبه و نرمال میکند تا مجموع اهمیت همهٔ ویژگیها برابر ۱ شود. نتیجه در feature_importances_ قرار میگیرد. مثال زیر روی Iris نشان میدهد طول و عرض گلبرگ بهترتیب حدود ۴۴٪ و ۴۲٪ اهمیت دارند، در حالی که طول و عرض کاسبرگ فقط حدود ۱۱٪ و ۲٪ اهمیت دارند:
>>> from sklearn.datasets import load_iris
>>> iris = load_iris(as_frame=True)
>>> rnd_clf = RandomForestClassifier(n_estimators=500, random_state=42)
>>> rnd_clf.fit(iris.data, iris.target)
>>> for score, name in zip(
... rnd_clf.feature_importances_, iris.data.columns):
... print(round(score, 2), name)
...
0.11 sepal length (cm)
0.02 sepal width (cm)
0.44 petal length (cm)
0.42 petal width (cm)
اگر جنگل تصادفی روی MNIST آموزش داده شود و اهمیت هر پیکسل روی تصویر نمایش داده شود، نتیجهٔ شکل ۷-۶ به دست میآید. نواحیای که در شکل رقم نقش بیشتری دارند اهمیت بیشتری پیدا میکنند.
شکل 7-6. اهمیت پیکسلهای MNIST از دید Random Forest
بنابراین Random Forest ابزار مناسبی برای درک سریع ویژگیهای مؤثر و همچنین انتخاب ویژگی است.
Boosting
Boosting به خانوادهای از روشهای Ensemble گفته میشود که چند یادگیرندهٔ ضعیف را به یک یادگیرندهٔ قوی تبدیل میکنند. ایدهٔ کلی بیشتر روشهای Boosting این است که مدلها بهصورت ترتیبی آموزش داده شوند و هر مدل تلاش کند خطاهای مدل قبلی را اصلاح کند. از معروفترین روشها AdaBoost و Gradient Boosting هستند.
AdaBoost
در AdaBoost هر پیشبینیکنندهٔ جدید توجه بیشتری به نمونههایی میکند که مدل قبلی روی آنها خطا داشته یا کمبرازش بوده است. در نتیجه با ادامهٔ فرایند، مدلهای جدید بیشتر روی نمونههای دشوار تمرکز میکنند.
در آموزش یک AdaBoostClassifier، ابتدا یک طبقهبند پایه ــ مثلاً یک درخت تصمیم ــ روی داده آموزش میبیند. پس از پیشبینی روی مجموعهٔ آموزشی، وزن نسبی نمونههای اشتباه افزایش مییابد. مدل دوم با این وزنهای جدید آموزش داده میشود، دوباره خطاها محاسبه و وزنها بهروزرسانی میشوند و این روند ادامه مییابد.
شکل 7-7. آموزش ترتیبی AdaBoost با بهروزرسانی وزن نمونهها
شکل ۷-۸ مرز تصمیم پنج پیشبینیکنندهٔ متوالی را روی دادهٔ moons نشان میدهد. در این نمایش از SVMهای RBF بسیار منظمشده فقط برای توضیح ایده استفاده شده است. مدل اول چند نمونه را اشتباه میکند و وزن آن نمونهها بیشتر میشود؛ مدل دوم بنابراین روی آن موارد تمرکز بیشتری دارد. در نمودار سمت راست نرخ یادگیری نصف شده و وزن خطاها در هر مرحله با شدت کمتری افزایش مییابد.
شکل 7-8. مرزهای تصمیم مدلهای متوالی AdaBoost
این آموزش ترتیبی شباهتی با گرادیان کاهشی دارد؛ با این تفاوت که بهجای تغییر تدریجی پارامترهای یک مدل، AdaBoost مدلهای جدیدی به Ensemble اضافه میکند و مجموعه را مرحلهبهمرحله بهتر میسازد.
محدودیت مهم AdaBoost آن است که آموزش مدلها قابل موازیسازی نیست؛ هر پیشبینیکننده فقط پس از آموزش و ارزیابی مدل قبلی ساخته میشود. از این رو مقیاسپذیری آن از Bagging و Pasting کمتر است.
جزئیات الگوریتم AdaBoost
در ابتدا وزن هر نمونه w⁽ⁱ⁾ برابر 1/m قرار میگیرد. نخستین پیشبینیکننده آموزش میبیند و نرخ خطای وزندار آن محاسبه میشود:
معادلهٔ ۷-۱ ــ نرخ خطای وزندار پیشبینیکنندهٔ j
rⱼ = Σi: ŷⱼ⁽ⁱ⁾ ≠ y⁽ⁱ⁾ w⁽ⁱ⁾
سپس وزن خود پیشبینیکننده با فراپارامتر نرخ یادگیری η محاسبه میشود:
معادلهٔ ۷-۲ ــ وزن پیشبینیکننده
αⱼ = η log((1 − rⱼ) / rⱼ)
هرچه مدل دقیقتر باشد، وزن αⱼ بیشتر است. اگر عملکرد آن تقریباً تصادفی باشد وزن به صفر نزدیک میشود و اگر اغلب اشتباه کند، وزن میتواند منفی شود.
سپس وزن نمونهها بهروزرسانی میشود؛ نمونههایی که مدل اشتباه طبقهبندی کرده است وزن بیشتری میگیرند:
معادلهٔ ۷-۳ ــ قاعدهٔ بهروزرسانی وزن
w⁽ⁱ⁾ ← w⁽ⁱ⁾ اگر ŷⱼ⁽ⁱ⁾ = y⁽ⁱ⁾
w⁽ⁱ⁾ ← w⁽ⁱ⁾ exp(αⱼ) اگر ŷⱼ⁽ⁱ⁾ ≠ y⁽ⁱ⁾
پس از آن همهٔ وزنها با تقسیم بر مجموع وزنها نرمال میشوند. مدل بعدی با وزنهای جدید آموزش میبیند و فرایند تا رسیدن به تعداد موردنظر پیشبینیکنندهها یا پیدا شدن یک مدل کامل ادامه پیدا میکند.
برای پیشبینی، خروجی همهٔ مدلها با وزن αⱼ جمع میشود و کلاسی که بیشترین رأی وزندار را بگیرد انتخاب میشود:
معادلهٔ ۷-۴ ــ پیشبینی AdaBoost
ŷ(x) = argmaxk Σj: ŷⱼ(x)=k αⱼ
Scikit-Learn از نسخهٔ چندکلاسهای با نام SAMME استفاده میکند که مخفف Stagewise Additive Modeling using a Multiclass Exponential loss است. در مسئلهٔ دودویی، SAMME با AdaBoost معادل است. اگر مدلهای پایه احتمال کلاسها را برآورد کنند، نسخهٔ SAMME.R میتواند از احتمالها بهجای خود کلاسهای پیشبینیشده استفاده کند و معمولاً بهتر عمل میکند.
کد زیر AdaBoost با ۳۰ Decision Stump میسازد. Decision Stump درختی با max_depth=1 است، یعنی فقط یک گرهٔ تصمیم و دو برگ دارد و Estimator پایهٔ رایج AdaBoost است:
from sklearn.ensemble import AdaBoostClassifier
ada_clf = AdaBoostClassifier(
DecisionTreeClassifier(max_depth=1),
n_estimators=30,
learning_rate=0.5,
random_state=42
)
ada_clf.fit(X_train, y_train)
اگر AdaBoost روی مجموعهٔ آموزشی بیشبرازش دارد، میتوان تعداد Estimatorها را کاهش داد یا مدل پایه را قویتر منظمسازی کرد.
آغاز Gradient Boosting
Gradient Boosting نیز مانند AdaBoost مدلها را بهصورت ترتیبی به Ensemble اضافه میکند و هر مدل در پی اصلاح مدل قبلی است. تفاوت این است که بهجای تغییر وزن نمونهها، مدل جدید روی خطاهای باقیمانده یا Residualهای مدل قبلی برازش میشود.
در یک مثال رگرسیون با درختهای تصمیم ــ که Gradient Tree Boosting یا GBRT نامیده میشود ــ ابتدا دادهای درجهٔ دوم و دارای نویز میسازیم و نخستین درخت را آموزش میدهیم:
import numpy as np
from sklearn.tree import DecisionTreeRegressor
np.random.seed(42)
X = np.random.rand(100, 1) - 0.5
y = 3 * X[:, 0] ** 2 + 0.05 * np.random.randn(100)
tree_reg1 = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg1.fit(X, y)
در بخش بعد، درختهای بعدی روی Residualهای همین مدل آموزش داده میشوند.