Boosting: XGBoost, LightGBM, CatBoost
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.
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}")
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:
qoldiq = y - bashorat— hozirgi xatod.fit(X, qoldiq)— yangi daraxt xatoni bashorat qiladibashorat += 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
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}")
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
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"}, ]))
| Kutubxona | Yili | Asosiy kuchi | Kamchiligi |
|---|---|---|---|
| XGBoost | 2 014 | Birinchi mashhur; regularizatsiya ichida | Kategoriyani o'zi eplamaydi |
| LightGBM | 2 017 | ENG TEZ; katta ma'lumotda ustun | Kichik ma'lumotda overfitting ga moyil |
| CatBoost | 2 017 | Kategoriyani O'ZI eplaydi; sukut sozlamalari yaxshi | Sekinroq o'qitadi |
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")
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
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}")
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.
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}")
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.
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}")
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:
model.fit(X, y, early_stopping_rounds=50) # XGBoost 2.0+ da XATO beradi
Hozir u konstruktorga beriladi:
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
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}")
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.
cross_val_score(CatBoostClassifier(cat_features=["tuman"]), X, y, cv=5)
Bu kod shunday xato beradi:
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
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"}, ]))
| Parametr | Ma'nosi | Amaliy |
|---|---|---|
| learning_rate | Har daraxt qancha tuzatadi | 0.01-0.1 |
| n_estimators | Daraxtlar soni | early_stopping bilan topiladi |
| max_depth | Daraxt chuqurligi | 3-8 (RF dan SAYOZ) |
| subsample | Har daraxt uchun qatorlar ulushi | 0.7-1.0 |
| colsample_bytree | Har daraxt uchun ustunlar ulushi | 0.7-1.0 |
| reg_lambda | L2 jarima (16-bob) | 1-10 |
Bugun nima o'rgandik
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"}, ]))
| Tushuncha | Bitta jumla |
|---|---|
| Boosting g'oyasi | Har daraxt oldingilarning XATOSINI o'rganadi |
| Uch qator kod | qoldiq -> fit(X, qoldiq) -> bashorat += lr * yangi |
| Daraxt chuqurligi | Sayoz (3-6) — RF dagidan farqli |
| learning_rate | Kichik bo'lsa ko'proq daraxt kerak |
| n_estimators | Oshirish OVERFITTING qiladi — RF dan farqli |
| early_stopping | Validation to'xtaganda o'qitishni tugatadi |
| XGBoost 2.0 | early_stopping_rounds konstruktorga ko'chdi |
| CatBoost | cat_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.