Bagging va Random Forest
Sahna: bitta daraxt beqaror
17-bobning oxirida bitta narsani o'lchadik: daraxtning ildiz savoli train bo'linishiga qarab o'zgaradi.
Bu jiddiy muammo. Model har safar boshqa javob bersa, unga ishonib bo'lmaydi.
Yechim esa kutilmaganda oddiy: bitta daraxtga ishonmaymiz. Yuztasini o'stiramiz va ovoz beramiz.
Olomon donoligi
1906 yilda statistik Frensis Galton yarmarkada tajriba o'tkazdi. 787 kishi ho'kizning vaznini taxmin qildi. Hech kim aniq topmadi — lekin 787 taxminning o'rtachasi haqiqiy vazndan atigi 1% farq qildi.
Shu printsip ML da ishlaydi, lekin bitta shart bilan: xatolar bir-biridan mustaqil bo'lishi kerak.
import pandas as pd import numpy as np r = np.random.default_rng(42) n_model, n_namuna = 100, 2000 haqiqat = r.choice([0, 1], n_namuna) # har bir "model" 60% aniqlikda, lekin xatolari MUSTAQIL mustaqil = np.array([np.where(r.random(n_namuna) < 0.60, haqiqat, 1 - haqiqat) for _ in range(n_model)]) ovoz = (mustaqil.mean(axis=0) > 0.5).astype(int) print(f"Bitta modelning aniqligi : {(mustaqil[0] == haqiqat).mean():.4f}") print(f"100 ta modelning o'rtacha ball: {(mustaqil == haqiqat).mean():.4f}") print(f"OVOZ BERISH natijasi : {(ovoz == haqiqat).mean():.4f}")
Bitta modelning aniqligi : 0.6065 100 ta modelning o'rtacha ball: 0.6001 OVOZ BERISH natijasi : 0.9795
60% aniqlikdagi yuzta zaif model birlashib ancha yuqori natija berdi.
Endi xatolar bog'liq bo'lsa nima bo'ladi — o'sha ham muhim:
# hamma model BIR XIL xato qiladi (to'liq bog'liq) bitta_naqsh = np.where(r.random(n_namuna) < 0.60, haqiqat, 1 - haqiqat) bogliq = np.array([bitta_naqsh for _ in range(n_model)]) ovoz_b = (bogliq.mean(axis=0) > 0.5).astype(int) print(f"Xatolar BOG'LIQ bo'lsa, ovoz berish: {(ovoz_b == haqiqat).mean():.4f}") print() print("Xulosa: ensemble faqat modellar BIR-BIRIDAN FARQ qilsa ishlaydi.")
Xatolar BOG'LIQ bo'lsa, ovoz berish: 0.5885 Xulosa: ensemble faqat modellar BIR-BIRIDAN FARQ qilsa ishlaydi.
Ensemble ning yagona sharti
Agar siz yuzta bir xil modelni o'rgatsangiz — natija bitta modeldan farq qilmaydi.
Shuning uchun bagging va Random Forest da asosiy savol: "daraxtlarni qanday bir-biridan farqli qilamiz?"
Bagging: har daraxtga boshqa ma'lumot
Birinchi usul: har daraxtni ma'lumotning boshqa namunasida o'rgatamiz.
Namuna qaytarish bilan olinadi (bootstrap) — ya'ni bitta qator bir necha marta tushishi mumkin.
namuna = np.arange(10) for i in range(3): bs = np.random.default_rng(i).choice(namuna, size=10, replace=True) yoq = sorted(set(namuna) - set(bs)) print(f" {i+1}-bootstrap: {bs} | tushmagan: {yoq}") print() # har bootstrap da taxminan 37% qator tushmaydi tushmagan = [len(set(np.arange(1000)) - set(np.random.default_rng(s).choice(1000, 1000, replace=True)))/1000 for s in range(50)] print(f"O'rtacha necha foiz qator tushmaydi: {np.mean(tushmagan):.1%}") print("Bu — matematik fakt: (1 - 1/n)^n -> 1/e = 0.368")
1-bootstrap: [8 6 5 2 3 0 0 0 1 8] | tushmagan: [np.int64(4), np.int64(7), np.int64(9)] 2-bootstrap: [4 5 7 9 0 1 8 9 2 3] | tushmagan: [np.int64(6)] 3-bootstrap: [8 2 1 2 4 8 4 0 3 6] | tushmagan: [np.int64(5), np.int64(7), np.int64(9)] O'rtacha necha foiz qator tushmaydi: 36.8% Bu — matematik fakt: (1 - 1/n)^n -> 1/e = 0.368
Tushmagan 37% — bu bepul test to'plami. Uning nomi: OOB (out-of-bag).
from sklearn.model_selection import train_test_split, cross_val_score from sklearn.tree import DecisionTreeClassifier from sklearn.ensemble import BaggingClassifier, RandomForestClassifier df = pd.read_csv(DATA / "uy_ijara.csv") son = ["xonalar", "maydon_m2", "qavat", "qavatlar_soni", "metro_km", "mebel"] X, y = df[son], df["ijaraga_berildi_30k"] X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y) print(f"{'model':<40} {'CV F1':>9} {'±':>8}") print("-" * 58) for nom, m in [ ("Bitta daraxt (chegarasiz)", DecisionTreeClassifier(random_state=42)), ("Bitta daraxt (max_depth=6)", DecisionTreeClassifier(max_depth=6, random_state=42)), ("Bagging (100 chegarasiz daraxt)", BaggingClassifier( DecisionTreeClassifier(random_state=42), n_estimators=100, random_state=42)), ]: b = cross_val_score(m, X, y, cv=5, scoring="f1") print(f"{nom:<40} {b.mean():>9.4f} {b.std():>8.4f}")
model CV F1 ± ---------------------------------------------------------- Bitta daraxt (chegarasiz) 0.6504 0.0318 Bitta daraxt (max_depth=6) 0.6794 0.0179 Bagging (100 chegarasiz daraxt) 0.6924 0.0134
Bagging chegarasiz daraxtlardan tuzilgan — ya'ni har biri alohida overfitting qiladi. Lekin ularning xatolari turlicha, shuning uchun o'rtachasi barqaror chiqadi.
Random Forest: bitta aqlli qo'shimcha
Bagging da daraxtlar hali ham o'xshash bo'lib qolishi mumkin — chunki hammasi bir xil kuchli ustunni birinchi savol qilib tanlaydi.
Random Forest shuni tuzatadi: har bo'linishda tasodifiy tanlangan bir qism ustun ko'riladi.
show(pd.DataFrame([ {"Qadam": "1. Bootstrap namuna", "Bagging": "HA", "Random Forest": "HA"}, {"Qadam": "2. Har bo'linishda ustunlarning QISMI", "Bagging": "yo'q (hammasi)", "Random Forest": "HA — max_features"}, ])) print() print("Shu bitta qo'shimcha daraxtlarni ancha farqli qiladi.")
Shu bitta qo'shimcha daraxtlarni ancha farqli qiladi.
| Qadam | Bagging | Random Forest |
|---|---|---|
| 1. Bootstrap namuna | HA | HA |
| 2. Har bo'linishda ustunlarning QISMI | yo'q (hammasi) | HA — max_features |
print(f"{'model':<40} {'CV F1':>9} {'±':>8}") print("-" * 58) for nom, m in [ ("Bagging (hamma ustun)", BaggingClassifier( DecisionTreeClassifier(random_state=42), n_estimators=100, random_state=42)), ("Random Forest (max_features='sqrt')", RandomForestClassifier( n_estimators=100, random_state=42)), ("Random Forest (max_features=None)", RandomForestClassifier( n_estimators=100, max_features=None, random_state=42)), ]: b = cross_val_score(m, X, y, cv=5, scoring="f1") print(f"{nom:<40} {b.mean():>9.4f} {b.std():>8.4f}") print() print("Uchinchi qator — max_features=None bo'lsa RF aslida Bagging ga aylanadi.")
model CV F1 ± ---------------------------------------------------------- Bagging (hamma ustun) 0.6924 0.0134 Random Forest (max_features='sqrt') 0.6788 0.0205 Random Forest (max_features=None) 0.6913 0.0152 Uchinchi qator — max_features=None bo'lsa RF aslida Bagging ga aylanadi.
Beqarorlik tuzaldimi? O'lchaymiz
17-bobda bitta daraxtning ildiz savoli o'zgarardi. Endi ensemble ning bashoratlari qanchalik barqaror ekanini tekshiramiz.
from sklearn.metrics import f1_score print(f"{'random_state':>13} {'bitta daraxt':>14} {'Random Forest':>15}") print("-" * 46) d_ballar, rf_ballar = [], [] for rs in [0, 1, 7, 13, 42, 99, 123, 2026]: Xa, Xb, ya, yb = train_test_split(X, y, test_size=0.3, random_state=rs, stratify=y) d = DecisionTreeClassifier(random_state=42).fit(Xa, ya) rf = RandomForestClassifier(n_estimators=100, random_state=42).fit(Xa, ya) df_ = f1_score(yb, d.predict(Xb)); rf_ = f1_score(yb, rf.predict(Xb)) d_ballar.append(df_); rf_ballar.append(rf_) print(f"{rs:>13} {df_:>14.4f} {rf_:>15.4f}") print("-" * 46) print(f"{'tarqalish':>13} {np.std(d_ballar):>14.4f} {np.std(rf_ballar):>15.4f}")
random_state bitta daraxt Random Forest
----------------------------------------------
0 0.6307 0.6630
1 0.6316 0.6856
7 0.6507 0.7147
13 0.6322 0.6723
42 0.6264 0.7069
99 0.6596 0.6973
123 0.5600 0.6814
2026 0.6364 0.7123
----------------------------------------------
tarqalish 0.0279 0.0179
Oxirgi qator — javob. Random Forest ning natijalari bitta daraxtga qaraganda ancha kam tarqalgan.
OOB score — bepul validation
rf_oob = RandomForestClassifier(n_estimators=300, oob_score=True, random_state=42).fit(X_tr, y_tr) print(f"OOB score (bepul, CV kerak emas): {rf_oob.oob_score_:.4f}") print(f"Test to'plamidagi accuracy : {rf_oob.score(X_te, y_te):.4f}") cv_acc = cross_val_score(RandomForestClassifier(n_estimators=300, random_state=42), X, y, cv=5).mean() print(f"5-fold CV accuracy (qimmat) : {cv_acc:.4f}")
OOB score (bepul, CV kerak emas): 0.6488 Test to'plamidagi accuracy : 0.7167 5-fold CV accuracy (qimmat) : 0.6817
Uchta raqamni solishtiring: OOB 0.6488, CV 0.6817, test 0.7167.
OOB eng past chiqdi — va bu tasodif emas. Har bir qator uchun OOB bahosi faqat o'sha qatorni ko'rmagan daraxtlar bo'yicha hisoblanadi, ya'ni taxminan 37% daraxt bilan. Shuning uchun OOB odatda bir oz pessimistik.
Demak OOB — CV ning aniq o'rnini bosadigan narsa emas. U arzon va ehtiyotkor baho: bitta o'qitish narxiga tushadi, lekin haqiqiy natijani pastroq ko'rsatadi.
Katta ma'lumotda OOB ishlatilsin
Agar cross_val_score juda sekin bo'lsa, oob_score=True qo'ying. U bitta o'qitish narxiga validation bahosini beradi.
Cheklov: faqat bagging asosidagi modellarda bor (RF, BaggingClassifier), boosting da yo'q.
Nechta daraxt kerak
import time print(f"{'n_estimators':>13} {'CV F1':>9} {'±':>8} {'vaqt':>9}") print("-" * 44) for n in [1, 10, 50, 200, 500]: t0 = time.perf_counter() b = cross_val_score(RandomForestClassifier(n_estimators=n, random_state=42, n_jobs=1), X, y, cv=5, scoring="f1") dt = time.perf_counter() - t0 print(f"{n:>13} {b.mean():>9.4f} {b.std():>8.4f} {dt:>8.1f}s")
n_estimators CV F1 ± vaqt
--------------------------------------------
1 0.6138 0.0198 0.0s
10 0.6446 0.0298 0.1s
50 0.6699 0.0184 0.2s
200 0.6824 0.0185 0.8s
500 0.6889 0.0141 2.2s
`n_estimators` ni oshirish overfitting qilmaydi
Bu — muhim farq. Boshqa modellarda parametrni oshirish overfitting ga olib keladi. Random Forest da esa yo'q: daraxt qo'shsangiz natija yaxshilanadi yoki to'xtaydi, lekin yomonlashmaydi.
Shuning uchun n_estimators ni "yetarlicha katta" qo'yasiz (100-500) va uni sozlamaysiz. Yagona narx — vaqt.
Sozlanadigan parametrlar boshqa: max_depth, min_samples_leaf, max_features.
Bagging va Boosting — intervyu juftligi
show(pd.DataFrame([ {"": "Bagging / RF", "Daraxtlar qanday o'stiriladi": "PARALLEL — bir-biridan mustaqil", "Har daraxt nima qiladi": "Butun vazifani mustaqil hal qiladi", "Nimani kamaytiradi": "VARIANCE (beqarorlik)", "Daraxt turi": "Chuqur, chegarasiz"}, {"": "Boosting", "Daraxtlar qanday o'stiriladi": "KETMA-KET — har biri oldingisining xatosiga qaraydi", "Har daraxt nima qiladi": "Oldingi xatoni tuzatadi", "Nimani kamaytiradi": "BIAS (aniqlik yetishmasligi)", "Daraxt turi": "Sayoz (depth 3-6)"}, ]))
| Daraxtlar qanday o'stiriladi | Har daraxt nima qiladi | Nimani kamaytiradi | Daraxt turi | |
|---|---|---|---|---|
| Bagging / RF | PARALLEL — bir-biridan mustaqil | Butun vazifani mustaqil hal qiladi | VARIANCE (beqarorlik) | Chuqur, chegarasiz |
| Boosting | KETMA-KET — har biri oldingisining xatosiga qaraydi | Oldingi xatoni tuzatadi | BIAS (aniqlik yetishmasligi) | Sayoz (depth 3-6) |
Bu jadval intervyuda eng ko'p so'raladigan savollardan biri. Asosiy farq: parallel va mustaqil vs ketma-ket va bog'liq.
19-bob boosting ga bag'ishlangan.
Xususiyat muhimligi: RF ishonchliroq
from sklearn.inspection import permutation_importance rf = RandomForestClassifier(n_estimators=300, random_state=42).fit(X_tr, y_tr) d1 = DecisionTreeClassifier(max_depth=6, random_state=42).fit(X_tr, y_tr) pi = permutation_importance(rf, X_te, y_te, n_repeats=20, random_state=42, scoring="f1") show(pd.DataFrame({ "xususiyat": son, "bitta daraxt": d1.feature_importances_.round(4), "Random Forest": rf.feature_importances_.round(4), "RF permutation": pi.importances_mean.round(4), }).sort_values("RF permutation", ascending=False))
| xususiyat | bitta daraxt | Random Forest | RF permutation |
|---|---|---|---|
| maydon_m2 | 0.725 | 0.362 | 0.126 |
| xonalar | 0 | 0.108 | 0.07 |
| metro_km | 0.13 | 0.238 | 0.001 |
| mebel | 0.009 | 0.03 | -0.001 |
| qavatlar_soni | 0.068 | 0.139 | -0.007 |
| qavat | 0.068 | 0.123 | -0.026 |
RF ning muhimligi ko'p daraxt bo'yicha o'rtachalangani uchun bitta daraxtdan barqarorroq. Lekin 17-bobdagi ogohlantirish kuchda qoladi: bog'liq ustunlar orasida muhimlik bo'linib ketadi.
Regression uchun
from sklearn.ensemble import RandomForestRegressor from sklearn.linear_model import LinearRegression from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler yn = df["narx_usd"] print(f"{'model':<34} {'CV R2':>9} {'±':>8}") print("-" * 52) for nom, m in [ ("LinearRegression (14-bob)", Pipeline([("sc", StandardScaler()), ("m", LinearRegression())])), ("DecisionTreeRegressor", __import__("sklearn.tree", fromlist=["DecisionTreeRegressor"]).DecisionTreeRegressor(max_depth=8, random_state=42)), ("RandomForestRegressor", RandomForestRegressor(n_estimators=200, random_state=42)), ]: b = cross_val_score(m, X, yn, cv=5, scoring="r2") print(f"{nom:<34} {b.mean():>9.4f} {b.std():>8.4f}")
model CV R2 ± ---------------------------------------------------- LinearRegression (14-bob) 0.8759 0.0091 DecisionTreeRegressor 0.7869 0.0159 RandomForestRegressor 0.8524 0.0105
Natijaga qarang va 9-bob qoidasini qo'llang: farq fold tarqalishidan kattami?
Daraxt regression da ekstrapolyatsiya QILMAYDI
Bu muhim cheklov. Daraxt bargdagi o'rtachani qaytaradi, shuning uchun u hech qachon train da ko'rgan diapazondan chiqmaydi.
Chiziqli model 500 m2 kvartira uchun narx hisoblab beradi (to'g'ri yoki noto'g'ri). Daraxt esa eng katta ko'rgan kvartiraning narxini qaytaradi.
katta = pd.DataFrame([{"xonalar": 10, "maydon_m2": 500.0, "qavat": 3, "qavatlar_soni": 5, "metro_km": 1.0, "mebel": 1}]) lin = Pipeline([("sc", StandardScaler()), ("m", LinearRegression())]).fit(X, yn) rfr = RandomForestRegressor(n_estimators=200, random_state=42).fit(X, yn) print(f"Train dagi eng katta narx : {yn.max()}") print(f"500 m2 kvartira uchun bashorat:") print(f" LinearRegression : {lin.predict(katta)[0]:.0f} <- diapazondan CHIQDI") print(f" RandomForest : {rfr.predict(katta)[0]:.0f} <- diapazonda QOLDI")
Train dagi eng katta narx : 1515 500 m2 kvartira uchun bashorat: LinearRegression : 4538 <- diapazondan CHIQDI RandomForest : 1370 <- diapazonda QOLDI
Bugun nima o'rgandik
show(pd.DataFrame([ {"Tushuncha": "Ensemble sharti", "Bitta jumla": "Modellar bir-biridan FARQ qilishi kerak"}, {"Tushuncha": "Bootstrap", "Bitta jumla": "Qaytarish bilan namuna; ~37% qator tushmaydi"}, {"Tushuncha": "Bagging", "Bitta jumla": "Har daraxtga boshqa namuna"}, {"Tushuncha": "Random Forest", "Bitta jumla": "Bagging + har bo'linishda ustunlarning qismi"}, {"Tushuncha": "Nimani tuzatadi", "Bitta jumla": "Variance — bitta daraxtning beqarorligi"}, {"Tushuncha": "OOB score", "Bitta jumla": "Bepul validation, CV dan 5 marta arzon"}, {"Tushuncha": "n_estimators", "Bitta jumla": "Oshirish overfitting QILMAYDI — faqat vaqt"}, {"Tushuncha": "Bagging vs Boosting", "Bitta jumla": "Parallel+mustaqil vs ketma-ket+bog'liq"}, {"Tushuncha": "Regression cheklovi", "Bitta jumla": "Daraxt ekstrapolyatsiya qilmaydi"}, ]))
| Tushuncha | Bitta jumla |
|---|---|
| Ensemble sharti | Modellar bir-biridan FARQ qilishi kerak |
| Bootstrap | Qaytarish bilan namuna; ~37% qator tushmaydi |
| Bagging | Har daraxtga boshqa namuna |
| Random Forest | Bagging + har bo'linishda ustunlarning qismi |
| Nimani tuzatadi | Variance — bitta daraxtning beqarorligi |
| OOB score | Bepul validation, CV dan 5 marta arzon |
| n_estimators | Oshirish overfitting QILMAYDI — faqat vaqt |
| Bagging vs Boosting | Parallel+mustaqil vs ketma-ket+bog'liq |
| Regression cheklovi | Daraxt ekstrapolyatsiya qilmaydi |
Keyingi bobda ikkinchi yo'l: daraxtlarni parallel emas, ketma-ket o'stiramiz — va har biri oldingisining xatosini tuzatadi.
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.