Asosiy qismga o'tish

Boosting: XGBoost, LightGBM, CatBoost

20-bob 14 kod bloki 2404 so'z

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

Sahna: xatoga qarab o'rganish

18-bobda yuzta daraxtni parallel o'stirdik. Har biri mustaqil ishladi, keyin ovoz berdik.

Endi boshqa g'oya: daraxtlarni ketma-ket o'stiramiz, va har bir yangi daraxt oldingilarning xatosiga qaraydi.

Tasavvur qiling: o'qituvchi test ishini tekshiradi. Birinchi o'quvchi 60% to'g'ri yozdi. Ikkinchi o'quvchiga aytadi: "birinchisi mana shu savollarda adashdi — sen aynan ularga e'tibor ber."

Bu — boosting. Va amalda u tabular ma'lumotda eng yaxshi natija beradigan usul.

Qo'lda boosting: 20 qator kod

G'oyani tushunish uchun uni o'zimiz yozamiz. Regression misolida — u soddaroq.

python31 qator
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.tree import DecisionTreeRegressor
from sklearn.metrics import root_mean_squared_error

df = pd.read_csv(DATA / "uy_ijara.csv")
son = ["xonalar", "maydon_m2", "qavat", "qavatlar_soni", "metro_km", "mebel"]
X, y = df[son], df["narx_usd"]
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.3, random_state=42)

# 1-qadam: oddiy bashorat — hammaga o'rtacha
bashorat_tr = np.full(len(y_tr), y_tr.mean())
bashorat_te = np.full(len(y_te), y_tr.mean())
daraxtlar = []
lr = 0.1

print(f"{'qadam':>6} {'train RMSE':>12} {'test RMSE':>11}")
print("-" * 32)
print(f"{0:>6} {root_mean_squared_error(y_tr, bashorat_tr):>12.1f} "
      f"{root_mean_squared_error(y_te, bashorat_te):>11.1f}")

for qadam in range(1, 101):
    qoldiq = y_tr - bashorat_tr                       # HOZIRGI xato
    d = DecisionTreeRegressor(max_depth=3, random_state=42).fit(X_tr, qoldiq)
    daraxtlar.append(d)
    bashorat_tr = bashorat_tr + lr * d.predict(X_tr)  # xatoni bir oz tuzatamiz
    bashorat_te = bashorat_te + lr * d.predict(X_te)
    if qadam in (1, 5, 20, 50, 100):
        print(f"{qadam:>6} {root_mean_squared_error(y_tr, bashorat_tr):>12.1f} "
              f"{root_mean_squared_error(y_te, bashorat_te):>11.1f}")
Chiqish
 qadam   train RMSE   test RMSE
--------------------------------
     0        229.8       228.6
     1        211.7       210.5
     5        157.4       158.0
    20         85.8        94.0
    50         69.4        84.0
   100         63.4        84.5

Mana butun boosting. Uch qator muhim:

  1. qoldiq = y - bashorat — hozirgi xato
  2. d.fit(X, qoldiq) — yangi daraxt xatoni bashorat qiladi
  3. bashorat += lr * d.predict(X) — xatoni bir oz tuzatamiz

Daraxt target ni emas, XATONI o'rganadi

Bu — boostingning eng muhim va eng ko'p chalkashtiriladigan nuqtasi.

Random Forest da har daraxt y ni bashorat qiladi. Boosting da esa birinchi daraxtdan keyingi hammasi qoldiqni (y - bashorat) bashorat qiladi.

Shuning uchun boosting daraxtlari sayoz (depth 3-6) bo'ladi: ular butun vazifani hal qilmaydi, faqat qolgan xatoni bir oz kamaytiradi.

sklearn bilan tekshiramiz

python6 qator
from sklearn.ensemble import GradientBoostingRegressor

sk = GradientBoostingRegressor(n_estimators=100, learning_rate=0.1,
                               max_depth=3, random_state=42).fit(X_tr, y_tr)
print(f"Bizning qo'lda yozilgan: test RMSE = {root_mean_squared_error(y_te, bashorat_te):.1f}")
print(f"sklearn GradientBoosting: test RMSE = {root_mean_squared_error(y_te, sk.predict(X_te)):.1f}")
Chiqish
Bizning qo'lda yozilgan: test RMSE = 84.5
sklearn GradientBoosting: test RMSE = 84.3

Yaqin natija — demak g'oyani to'g'ri amalga oshirdik. Qolgan farq sklearn ning qo'shimcha optimizatsiyalaridan.

Zamonaviy uchlik

python8 qator
show(pd.DataFrame([
    {"Kutubxona": "XGBoost", "Yili": 2014, "Asosiy kuchi": "Birinchi mashhur; regularizatsiya ichida",
     "Kamchiligi": "Kategoriyani o'zi eplamaydi"},
    {"Kutubxona": "LightGBM", "Yili": 2017, "Asosiy kuchi": "ENG TEZ; katta ma'lumotda ustun",
     "Kamchiligi": "Kichik ma'lumotda overfitting ga moyil"},
    {"Kutubxona": "CatBoost", "Yili": 2017, "Asosiy kuchi": "Kategoriyani O'ZI eplaydi; sukut sozlamalari yaxshi",
     "Kamchiligi": "Sekinroq o'qitadi"},
]))
KutubxonaYiliAsosiy kuchiKamchiligi
XGBoost2 014Birinchi mashhur; regularizatsiya ichidaKategoriyani o'zi eplamaydi
LightGBM2 017ENG TEZ; katta ma'lumotda ustunKichik ma'lumotda overfitting ga moyil
CatBoost2 017Kategoriyani O'ZI eplaydi; sukut sozlamalari yaxshiSekinroq o'qitadi
python19 qator
import time
from xgboost import XGBRegressor
from lightgbm import LGBMRegressor
from catboost import CatBoostRegressor
from sklearn.ensemble import RandomForestRegressor

print(f"{'model':<26} {'CV R2':>9} {'±':>8} {'vaqt':>8}")
print("-" * 54)
for nom, m in [
    ("RandomForest (18-bob)", RandomForestRegressor(n_estimators=200, random_state=42)),
    ("GradientBoosting", GradientBoostingRegressor(random_state=42)),
    ("XGBoost", XGBRegressor(n_estimators=200, verbosity=0, random_state=42)),
    ("LightGBM", LGBMRegressor(n_estimators=200, verbose=-1, random_state=42)),
    ("CatBoost", CatBoostRegressor(iterations=200, verbose=0, random_seed=42)),
]:
    t0 = time.perf_counter()
    b = cross_val_score(m, X, y, cv=5, scoring="r2")
    dt = time.perf_counter() - t0
    print(f"{nom:<26} {b.mean():>9.4f} {b.std():>8.4f} {dt:>7.1f}s")
Chiqish
model                          CV R2        ±     vaqt
------------------------------------------------------
RandomForest (18-bob)         0.8524   0.0105     1.5s
GradientBoosting              0.8656   0.0122     0.3s
XGBoost                       0.8242   0.0108     1.0s
LightGBM                      0.8307   0.0107     0.2s
CatBoost                      0.8577   0.0127     0.8s

Natijalarni 9-bob qoidasi bilan o'qing: farqlar fold tarqalishidan kattami? Bizning ma'lumot kichik va oson, shuning uchun farqlar kichik bo'lishi mumkin.

Kaggle da boosting yutadi — lekin SIZNING ma'lumotingizda ham?

"XGBoost eng yaxshi model" degan gap keng tarqalgan. U Kaggle musobaqalarida haqiqatan ko'p yutgan.

Lekin Kaggle datasetlari katta (yuz minglab qator) va murakkab. Sizning 1200 qatorli ma'lumotingizda chiziqli model ham xuddi shunday natija berishi mumkin — va u tezroq, tushunarli va kam buziladi.

Qoida o'zgarmaydi: o'lchang, 9-bobdagi qoida bilan solishtiring, farq tarqalish ichida bo'lsa oddiyroq modelni oling.

Eng muhim parametr: learning_rate

python7 qator
from xgboost import XGBRegressor
print(f"{'learning_rate':>14} {'n_estimators':>13} {'CV R2':>9}")
print("-" * 40)
for lr_ in [0.01, 0.05, 0.1, 0.3, 1.0]:
    b = cross_val_score(XGBRegressor(n_estimators=200, learning_rate=lr_, verbosity=0,
                                     random_state=42), X, y, cv=5, scoring="r2")
    print(f"{lr_:>14} {200:>13} {b.mean():>9.4f}")
Chiqish
 learning_rate  n_estimators     CV R2
----------------------------------------
          0.01           200    0.8365
          0.05           200    0.8478
           0.1           200    0.8387
           0.3           200    0.8242
           1.0           200    0.7605

learning_rate va n_estimators bir-biriga bog'liq: kichik learning_rate ko'proq daraxt talab qiladi.

python7 qator
print("Kichik learning_rate + ko'p daraxt:")
print(f"{'lr':>6} {'daraxtlar':>11} {'CV R2':>9}")
print("-" * 28)
for lr_, n in [(0.3, 50), (0.1, 150), (0.05, 300), (0.01, 1500)]:
    b = cross_val_score(XGBRegressor(n_estimators=n, learning_rate=lr_, verbosity=0,
                                     random_state=42), X, y, cv=5, scoring="r2")
    print(f"{lr_:>6} {n:>11} {b.mean():>9.4f}")
Chiqish
Kichik learning_rate + ko'p daraxt:
    lr   daraxtlar     CV R2
----------------------------
   0.3          50    0.8358
   0.1         150    0.8424
  0.05         300    0.8441
  0.01        1500    0.8451

Boosting da `n_estimators` ni oshirish OVERFITTING qiladi

18-bobda aytganmiz: Random Forest da daraxt qo'shish xavfsiz.

Boosting da teskari. Har yangi daraxt xatoni yanada kamaytiradi — oxir-oqibat train dagi shovqinni ham "tuzata" boshlaydi.

Shuning uchun boosting da early_stopping ishlatiladi: validation ball yaxshilanishdan to'xtaganda o'qitish tugaydi.

python7 qator
X_tr2, X_val, y_tr2, y_val = train_test_split(X_tr, y_tr, test_size=0.25, random_state=42)
m_es = XGBRegressor(n_estimators=3000, learning_rate=0.05, early_stopping_rounds=50,
                    verbosity=0, random_state=42)
m_es.fit(X_tr2, y_tr2, eval_set=[(X_val, y_val)], verbose=False)
print(f"Ruxsat berilgan daraxtlar : 3000")
print(f"Early stopping to'xtatdi  : {m_es.best_iteration} daraxtda")
print(f"Test R2                   : {m_es.score(X_te, y_te):.4f}")
Chiqish
Ruxsat berilgan daraxtlar : 3000
Early stopping to'xtatdi  : 80 daraxtda
Test R2                   : 0.8395

`early_stopping_rounds` XGBoost 2.0 da KO'CHDI

Eski kodda (va eski darsliklarda) shunday yozilgan:

python1 qator
model.fit(X, y, early_stopping_rounds=50)      # XGBoost 2.0+ da XATO beradi

Hozir u konstruktorga beriladi:

python1 qator
XGBRegressor(early_stopping_rounds=50).fit(X, y, eval_set=[(X_val, y_val)])

Bu — eski ML_GUIDE kitobidagi xatolardan biri edi. API o'zgarishlari shunday jim buzadi.

CatBoost: kategoriyani o'zi eplaydi

python29 qator
from catboost import CatBoostClassifier
yc = df["ijaraga_berildi_30k"]
Xk = df[son + ["tuman"]]

# CatBoost matn ustunni TO'G'RIDAN-TO'G'RI qabul qiladi
cb = CatBoostClassifier(iterations=200, verbose=0, random_seed=42,
                        cat_features=["tuman"]).fit(Xk, yc)
print("CatBoost matn ustunni encoding SIZ qabul qildi — xato yo'q")
print(f"  train accuracy: {cb.score(Xk, yc):.4f}")
print()
# boshqalari uchun OneHot kerak
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
from xgboost import XGBClassifier
q = Pipeline([
    ("t", ColumnTransformer([("m", OneHotEncoder(handle_unknown="ignore"), ["tuman"])],
                            remainder="passthrough")),
    ("m", XGBClassifier(n_estimators=200, verbosity=0, random_state=42)),
])
from sklearn.metrics import f1_score
Xk_tr, Xk_te, yk_tr, yk_te = train_test_split(Xk, yc, test_size=0.3, random_state=42, stratify=yc)

q.fit(Xk_tr, yk_tr)
print(f"XGBoost + OneHot:  test F1 = {f1_score(yk_te, q.predict(Xk_te)):.4f}")

cb2 = CatBoostClassifier(iterations=200, verbose=0, random_seed=42,
                         cat_features=["tuman"]).fit(Xk_tr, yk_tr)
print(f"CatBoost (o'zi)  :  test F1 = {f1_score(yk_te, cb2.predict(Xk_te)):.4f}")
Chiqish
CatBoost matn ustunni encoding SIZ qabul qildi — xato yo'q
  train accuracy: 0.7883

XGBoost + OneHot:  test F1 = 0.6975
CatBoost (o'zi)  :  test F1 = 0.7247

CatBoost ni `cross_val_score` ga bersangiz yiqiladi

Yuqorida CV emas, oddiy train/test ishlatdim. Sababi bor — va bu amalda uchraydigan tuzoq.

python1 qator
cross_val_score(CatBoostClassifier(cat_features=["tuman"]), X, y, cv=5)

Bu kod shunday xato beradi:

yaml2 qator
RuntimeError: Cannot clone object CatBoostClassifier(...), as the constructor
either does not set or modifies parameter cat_features

cross_val_score har fold uchun modelni klonlaydi (sklearn.base.clone). Klonlash konstruktorga berilgan parametrlar o'zgarmasligini talab qiladi, CatBoost esa cat_features ni ichida o'zgartiradi.

Yechimlar: - cat_features ni fit() ga bering: cb.fit(X, y, cat_features=[...]) - yoki ustun indekslarini bering, nomlarini emas - yoki CatBoost ning o'z cv() funksiyasidan foydalaning

Bu — kutubxonalar sklearn interfeysini to'liq bajarmaganda chiqadigan muammo. Shunday holatlarda xato xabarini o'qing: u "clone" so'zini aytsa, muammo shu yerda.

Asosiy parametrlar

python8 qator
show(pd.DataFrame([
    {"Parametr": "learning_rate", "Ma'nosi": "Har daraxt qancha tuzatadi", "Amaliy": "0.01-0.1"},
    {"Parametr": "n_estimators", "Ma'nosi": "Daraxtlar soni", "Amaliy": "early_stopping bilan topiladi"},
    {"Parametr": "max_depth", "Ma'nosi": "Daraxt chuqurligi", "Amaliy": "3-8 (RF dan SAYOZ)"},
    {"Parametr": "subsample", "Ma'nosi": "Har daraxt uchun qatorlar ulushi", "Amaliy": "0.7-1.0"},
    {"Parametr": "colsample_bytree", "Ma'nosi": "Har daraxt uchun ustunlar ulushi", "Amaliy": "0.7-1.0"},
    {"Parametr": "reg_lambda", "Ma'nosi": "L2 jarima (16-bob)", "Amaliy": "1-10"},
]))
ParametrMa'nosiAmaliy
learning_rateHar daraxt qancha tuzatadi0.01-0.1
n_estimatorsDaraxtlar soniearly_stopping bilan topiladi
max_depthDaraxt chuqurligi3-8 (RF dan SAYOZ)
subsampleHar daraxt uchun qatorlar ulushi0.7-1.0
colsample_bytreeHar daraxt uchun ustunlar ulushi0.7-1.0
reg_lambdaL2 jarima (16-bob)1-10

Bugun nima o'rgandik

python10 qator
show(pd.DataFrame([
    {"Tushuncha": "Boosting g'oyasi", "Bitta jumla": "Har daraxt oldingilarning XATOSINI o'rganadi"},
    {"Tushuncha": "Uch qator kod", "Bitta jumla": "qoldiq -> fit(X, qoldiq) -> bashorat += lr * yangi"},
    {"Tushuncha": "Daraxt chuqurligi", "Bitta jumla": "Sayoz (3-6) — RF dagidan farqli"},
    {"Tushuncha": "learning_rate", "Bitta jumla": "Kichik bo'lsa ko'proq daraxt kerak"},
    {"Tushuncha": "n_estimators", "Bitta jumla": "Oshirish OVERFITTING qiladi — RF dan farqli"},
    {"Tushuncha": "early_stopping", "Bitta jumla": "Validation to'xtaganda o'qitishni tugatadi"},
    {"Tushuncha": "XGBoost 2.0", "Bitta jumla": "early_stopping_rounds konstruktorga ko'chdi"},
    {"Tushuncha": "CatBoost", "Bitta jumla": "cat_features bilan matn ustunni o'zi eplaydi"},
]))
TushunchaBitta jumla
Boosting g'oyasiHar daraxt oldingilarning XATOSINI o'rganadi
Uch qator kodqoldiq -> fit(X, qoldiq) -> bashorat += lr * yangi
Daraxt chuqurligiSayoz (3-6) — RF dagidan farqli
learning_rateKichik bo'lsa ko'proq daraxt kerak
n_estimatorsOshirish OVERFITTING qiladi — RF dan farqli
early_stoppingValidation to'xtaganda o'qitishni tugatadi
XGBoost 2.0early_stopping_rounds konstruktorga ko'chdi
CatBoostcat_features bilan matn ustunni o'zi eplaydi

Keyingi bobda ikkita oddiy, lekin hali ham foydali model: qo'shnilarga qarab bashorat qilish va ehtimollikni sanash.

Bu bobni bepul o'qidingiz.

Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.

Telegramda yozish Narxlar