Asosiy qismga o'tish

Bagging va Random Forest

19-bob 14 kod bloki 2959 so'z

Bu bobdagi kod shu fayllar bilan ishlaydio'z kompyuteringizda ishga tushiring

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.

python15 qator
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}")
Chiqish
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:

python7 qator
# 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.")
Chiqish
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.

python11 qator
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")
Chiqish
  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).

python19 qator
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}")
Chiqish
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.

python7 qator
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.")
Chiqish
Shu bitta qo'shimcha daraxtlarni ancha farqli qiladi.
QadamBaggingRandom Forest
1. Bootstrap namunaHAHA
2. Har bo'linishda ustunlarning QISMIyo'q (hammasi)HA — max_features
python14 qator
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.")
Chiqish
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.

python14 qator
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}")
Chiqish
 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

python5 qator
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}")
Chiqish
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

python9 qator
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")
Chiqish
 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

python10 qator
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'stiriladiHar daraxt nima qiladiNimani kamaytiradiDaraxt turi
Bagging / RFPARALLEL — bir-biridan mustaqilButun vazifani mustaqil hal qiladiVARIANCE (beqarorlik)Chuqur, chegarasiz
BoostingKETMA-KET — har biri oldingisining xatosiga qaraydiOldingi xatoni tuzatadiBIAS (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

python12 qator
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))
xususiyatbitta daraxtRandom ForestRF permutation
maydon_m20.7250.3620.126
xonalar00.1080.07
metro_km0.130.2380.001
mebel0.0090.03-0.001
qavatlar_soni0.0680.139-0.007
qavat0.0680.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

python15 qator
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}")
Chiqish
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.

python8 qator
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")
Chiqish
Train dagi eng katta narx : 1515
500 m2 kvartira uchun bashorat:
  LinearRegression : 4538   <- diapazondan CHIQDI
  RandomForest     : 1370   <- diapazonda QOLDI

Bugun nima o'rgandik

python11 qator
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"},
]))
TushunchaBitta jumla
Ensemble shartiModellar bir-biridan FARQ qilishi kerak
BootstrapQaytarish bilan namuna; ~37% qator tushmaydi
BaggingHar daraxtga boshqa namuna
Random ForestBagging + har bo'linishda ustunlarning qismi
Nimani tuzatadiVariance — bitta daraxtning beqarorligi
OOB scoreBepul validation, CV dan 5 marta arzon
n_estimatorsOshirish overfitting QILMAYDI — faqat vaqt
Bagging vs BoostingParallel+mustaqil vs ketma-ket+bog'liq
Regression chekloviDaraxt 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.

Telegramda yozish Narxlar