Hyperparameter tuning
Sahna: sukut sozlamalari yaxshimi?
Hozirgacha biz modellarni sukut bo'yicha ishlatdik: RandomForestClassifier(), SVC(), XGBClassifier().
Lekin ularning ichida o'nlab sozlama bor. RandomForest da max_depth, min_samples_leaf, max_features. SVC da C va gamma. XGBoost da yigirmadan ko'p.
Savol: sukut qiymatlar yaxshimi? Va sozlash qancha foyda beradi?
Javob ko'pchilik o'ylaganidan kamroq — buni o'lchab ko'rsataman.
Protokoldagi joyi
Bu — 12-qadam dagi variantlardan biri: "Hyperparameter tuning — model yaxshi, lekin sozlanmagan".
Diqqat: u oxirgi variantlardan. 10-bobda aytganmiz — xato tahlili qilmasdan tuning ga o'tish eng ko'p uchraydigan vaqt yo'qotish.
Parametr va hyperparametr
1-bobdan eslatma, chunki bu yerda hayotiy:
import pandas as pd import numpy as np show(pd.DataFrame([ {"": "Parametr (weight)", "Kim topadi": "MODEL — fit() paytida", "Misol": "Chiziq koeffitsiyenti, daraxt chegarasi"}, {"": "Hyperparametr", "Kim topadi": "SIZ — fit() dan OLDIN", "Misol": "max_depth, C, learning_rate, n_neighbors"}, ]))
| Kim topadi | Misol | |
|---|---|---|
| Parametr (weight) | MODEL — fit() paytida | Chiziq koeffitsiyenti, daraxt chegarasi |
| Hyperparametr | SIZ — fit() dan OLDIN | max_depth, C, learning_rate, n_neighbors |
Sukut qiymatlar qanchalik yaxshi
Avval bazani o'lchaymiz — sozlanmagan modellar.
from sklearn.model_selection import train_test_split, cross_val_score, GridSearchCV, RandomizedSearchCV from sklearn.pipeline import Pipeline from sklearn.compose import ColumnTransformer from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.impute import SimpleImputer from sklearn.ensemble import RandomForestClassifier from sklearn.linear_model import LogisticRegression from sklearn.svm import SVC df = pd.read_csv(DATA / "uy_ijara.csv") son = ["xonalar", "maydon_m2", "qavat", "qavatlar_soni", "metro_km", "mebel", "qurilgan_yil"] matn = ["tuman"] X, y = df[son + matn], 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) def quvur(model): return Pipeline([ ("t", ColumnTransformer([ ("son", Pipeline([("i", SimpleImputer(strategy="median")), ("sc", StandardScaler())]), son), ("matn", OneHotEncoder(handle_unknown="ignore"), matn), ])), ("m", model), ]) print(f"{'model (sukut sozlamalar)':<34} {'CV F1':>9} {'±':>8}") print("-" * 54) sukut = {} for nom, m in [("LogisticRegression", LogisticRegression(max_iter=3000)), ("RandomForest", RandomForestClassifier(random_state=42)), ("SVC", SVC(random_state=42))]: b = cross_val_score(quvur(m), X, y, cv=5, scoring="f1") sukut[nom] = b print(f"{nom:<34} {b.mean():>9.4f} {b.std():>8.4f}")
model (sukut sozlamalar) CV F1 ± ------------------------------------------------------ LogisticRegression 0.7273 0.0118 RandomForest 0.7032 0.0145 SVC 0.7263 0.0134
GridSearchCV: hamma kombinatsiyani sinash
import time panjara = { "m__n_estimators": [100, 300], "m__max_depth": [None, 6, 12], "m__min_samples_leaf": [1, 5, 20], "m__max_features": ["sqrt", 0.5], } jami = 1 for v in panjara.values(): jami *= len(v) print(f"Kombinatsiyalar soni : {jami}") print(f"CV fold lari : 5") print(f"Jami o'qitish : {jami * 5} marta") t0 = time.perf_counter() gs = GridSearchCV(quvur(RandomForestClassifier(random_state=42)), panjara, cv=5, scoring="f1", n_jobs=-1) gs.fit(X_tr, y_tr) gs_vaqt = time.perf_counter() - t0 print() print(f"Vaqt : {gs_vaqt:.1f}s") print(f"Eng yaxshi CV F1 : {gs.best_score_:.4f}") print("Eng yaxshi sozlamalar:") for k, v in gs.best_params_.items(): print(f" {k.replace('m__', ''):20s} = {v}")
Kombinatsiyalar soni : 36 CV fold lari : 5 Jami o'qitish : 180 marta Vaqt : 4.1s Eng yaxshi CV F1 : 0.6997 Eng yaxshi sozlamalar: max_depth = 6 max_features = sqrt min_samples_leaf = 20 n_estimators = 100
print(f"Sukut sozlamalar CV F1 : {sukut['RandomForest'].mean():.4f} ±{sukut['RandomForest'].std():.4f}") print(f"Sozlangandan keyin : {gs.best_score_:.4f}") print(f"Tuning dan foyda : {gs.best_score_ - sukut['RandomForest'].mean():+.4f}") print(f"CV tarqalishi : ±{sukut['RandomForest'].std():.4f}") print() foyda = gs.best_score_ - sukut["RandomForest"].mean() print("Hukm:", "foyda tarqalishdan KATTA — tuning arzigulik" if foyda > sukut["RandomForest"].std() else "foyda tarqalish ICHIDA — tuning deyarli hech narsa bermadi")
Sukut sozlamalar CV F1 : 0.7032 ±0.0145 Sozlangandan keyin : 0.6997 Tuning dan foyda : -0.0034 CV tarqalishi : ±0.0145 Hukm: foyda tarqalish ICHIDA — tuning deyarli hech narsa bermadi
Tuning dan kutiladigan foyda — odatda kichik
Talabalar GridSearchCV ga katta umid bog'laydi. Amalda u odatda 1-3% beradi, ba'zan esa hech narsa.
Taqqoslash uchun tartib:
- Ma'lumot sifati — eng katta ta'sir (4-bob, 43-bob mashqi)
- To'g'ri metrika va chegara — katta ta'sir (7, 8-bob)
- Yangi xususiyat yoki yangi manba — o'rtacha (22-bob)
- Model turini almashtirish — o'rtacha
- Hyperparameter tuning — eng kichik
Shuning uchun tuning oxirida qilinadi. Avval ma'lumotni tuzatasiz.
Eng yaxshi natijalar jadvali
natijalar = pd.DataFrame(gs.cv_results_) ustunlar = ["param_m__n_estimators", "param_m__max_depth", "param_m__min_samples_leaf", "param_m__max_features", "mean_test_score", "std_test_score"] eng_yaxshi = natijalar[ustunlar].sort_values("mean_test_score", ascending=False).head(8).copy() eng_yaxshi.columns = ["n_est", "depth", "min_leaf", "max_feat", "CV F1", "±"] eng_yaxshi["CV F1"] = eng_yaxshi["CV F1"].round(4) eng_yaxshi["±"] = eng_yaxshi["±"].round(4) show(eng_yaxshi) print() eng = natijalar.mean_test_score.max() yaqin = int((natijalar.mean_test_score > eng - natijalar.std_test_score.max()).sum()) print(f"Eng baland balldan tarqalish ichida bo'lgan kombinatsiyalar: {yaqin} / {len(natijalar)}")
Eng baland balldan tarqalish ichida bo'lgan kombinatsiyalar: 36 / 36
| n_est | depth | min_leaf | max_feat | CV F1 | ± |
|---|---|---|---|---|---|
| 100 | 6 | 20 | sqrt | 0.7 | 0.014 |
| 100 | 6 | 1 | sqrt | 0.7 | 0.024 |
| 100 | 12 | 20 | sqrt | 0.698 | 0.013 |
| 100 | None | 20 | sqrt | 0.698 | 0.013 |
| 300 | 6 | 1 | sqrt | 0.698 | 0.023 |
| 300 | 6 | 5 | sqrt | 0.698 | 0.025 |
| 100 | 6 | 5 | sqrt | 0.697 | 0.025 |
| 300 | 6 | 20 | sqrt | 0.696 | 0.013 |
Oxirgi qatorga diqqat qiling, chunki natija keskin: 36 kombinatsiyadan 36 tasi eng baland balldan bir tarqalish ichida.
Ya'ni bu panjarada birorta sozlama ham boshqasidan ishonchli yaxshi emas. GridSearchCV ularning birini "eng yaxshi" deb tanladi, lekin bu tanlov amalda tasodif.
Shuning uchun best_params_ ni muqaddas deb bilmang. Uning yonidagi std_test_score ustuniga qarang: agar farqlar tarqalish ichida bo'lsa, siz eng oddiy yoki eng tez sozlamani tanlashingiz mumkin — natija bir xil bo'ladi.
RandomizedSearchCV: tasodifiy, lekin tezroq
Panjara kattalashganda GridSearchCV portlaydi. Yechim: hamma kombinatsiyani emas, tasodifiy N tasini sinash.
from scipy.stats import randint, uniform katta_panjara = { "m__n_estimators": randint(50, 500), "m__max_depth": [None, 4, 6, 8, 12, 20], "m__min_samples_leaf": randint(1, 40), "m__min_samples_split": randint(2, 40), "m__max_features": ["sqrt", "log2", 0.3, 0.5, 0.8], } print("Agar GridSearch bilan qilsak (taqriban):") print(f" 450 x 6 x 39 x 38 x 5 = {450*6*39*38*5:,} kombinatsiya".replace(",", " ")) print(" -> amalda imkonsiz") print() t0 = time.perf_counter() rs = RandomizedSearchCV(quvur(RandomForestClassifier(random_state=42)), katta_panjara, n_iter=40, cv=5, scoring="f1", random_state=42, n_jobs=-1) rs.fit(X_tr, y_tr) rs_vaqt = time.perf_counter() - t0 print(f"RandomizedSearchCV (40 urinish): {rs_vaqt:.1f}s") print(f" eng yaxshi CV F1: {rs.best_score_:.4f}") print(f"GridSearchCV ({jami} kombinatsiya): {gs_vaqt:.1f}s") print(f" eng yaxshi CV F1: {gs.best_score_:.4f}")
Agar GridSearch bilan qilsak (taqriban): 450 x 6 x 39 x 38 x 5 = 20 007 000 kombinatsiya -> amalda imkonsiz RandomizedSearchCV (40 urinish): 5.3s eng yaxshi CV F1: 0.6985 GridSearchCV (36 kombinatsiya): 4.1s eng yaxshi CV F1: 0.6997
Amalda RandomizedSearchCV dan boshlang
Sababi: ko'p hyperparametr natijaga deyarli ta'sir qilmaydi. Grid search o'sha ahamiyatsiz parametrlarga ham teng vaqt sarflaydi.
Random search esa muhim parametrning ko'proq xil qiymatini ko'rib chiqadi. Shuning uchun bir xil vaqt ichida ko'pincha yaxshiroq natija topadi.
Tartib: RandomizedSearchCV bilan keng qidiring -> eng yaxshi zonani toping -> o'sha zonada kichik GridSearchCV.
HalvingGridSearchCV: tezroq variant
from sklearn.experimental import enable_halving_search_cv # noqa: F401 from sklearn.model_selection import HalvingGridSearchCV t0 = time.perf_counter() hs = HalvingGridSearchCV(quvur(RandomForestClassifier(random_state=42)), panjara, cv=5, scoring="f1", random_state=42, n_jobs=-1, factor=3) hs.fit(X_tr, y_tr) hs_vaqt = time.perf_counter() - t0 print(f"{'usul':<24} {'vaqt':>8} {'eng yaxshi CV F1':>18}") print("-" * 52) print(f"{'GridSearchCV':<24} {gs_vaqt:>7.1f}s {gs.best_score_:>18.4f}") print(f"{'RandomizedSearchCV':<24} {rs_vaqt:>7.1f}s {rs.best_score_:>18.4f}") print(f"{'HalvingGridSearchCV':<24} {hs_vaqt:>7.1f}s {hs.best_score_:>18.4f}")
usul vaqt eng yaxshi CV F1 ---------------------------------------------------- GridSearchCV 4.1s 0.6997 RandomizedSearchCV 5.3s 0.6985 HalvingGridSearchCV 3.8s 0.6948
Halving g'oyasi: birinchi bosqichda hamma kombinatsiyani kichik ma'lumotda sinaydi, yomonlarini tashlaydi, qolganlarini kattaroq ma'lumotda sinaydi. Sport turniri kabi.
Eng muhim xato: test to'plamida tuning
print("NOTO'G'RI yo'l — eng yaxshi sozlamani TEST da tanlash:") from sklearn.metrics import f1_score eng_test, eng_sozlama = -1, None for d in [None, 4, 6, 8, 12, 20]: for msl in [1, 5, 10, 20, 40]: m = quvur(RandomForestClassifier(max_depth=d, min_samples_leaf=msl, random_state=42)) m.fit(X_tr, y_tr) ball = f1_score(y_te, m.predict(X_te)) if ball > eng_test: eng_test, eng_sozlama = ball, (d, msl) print(f" 30 kombinatsiya TEST da sinaldi") print(f" eng yaxshi test F1: {eng_test:.4f} (max_depth={eng_sozlama[0]}, min_samples_leaf={eng_sozlama[1]})") print() print("TO'G'RI yo'l — CV da tanlash, keyin test da BIR MARTA tekshirish:") print(f" CV da tanlangan eng yaxshi: {gs.best_score_:.4f}") print(f" o'sha model test da : {f1_score(y_te, gs.best_estimator_.predict(X_te)):.4f}")
NOTO'G'RI yo'l — eng yaxshi sozlamani TEST da tanlash: 30 kombinatsiya TEST da sinaldi eng yaxshi test F1: 0.7958 (max_depth=None, min_samples_leaf=40) TO'G'RI yo'l — CV da tanlash, keyin test da BIR MARTA tekshirish: CV da tanlangan eng yaxshi: 0.6997 o'sha model test da : 0.7842
30 marta test ga qarasangiz, test endi test emas
Yuqoridagi birinchi usulda men 30 ta sozlamani test to'plamida sinab, eng yaxshisini tanladim.
Natija chiroyli chiqadi — lekin u yolg'on. Men test to'plamiga 30 marta qaradim va unga moslashgan sozlamani tanladim. Bu — 5-bobdagi leakage ning boshqa shakli.
Haqiqiy loyihada bu shunday ko'rinadi: modelni chiqarasiz, serverda natija kutganingizdan past bo'ladi, va nega ekanini tushunmaysiz.
Qoida: sozlama CV da tanlanadi, test faqat yakuniy hisobot uchun bir marta ochiladi.
Nested CV: eng halol baho
Agar siz "tuning dan keyin modelim qanchalik yaxshi?" degan savolga xolis javob bermoqchi bo'lsangiz, bitta CV yetarli emas — chunki u sozlama tanlashda ishlatilgan.
from sklearn.model_selection import KFold, cross_val_score as cvs kichik_panjara = {"m__max_depth": [None, 6, 12], "m__min_samples_leaf": [1, 5, 20]} # oddiy CV — sozlama tanlash uchun ishlatilgan CV ning o'zi oddiy = GridSearchCV(quvur(RandomForestClassifier(random_state=42)), kichik_panjara, cv=5, scoring="f1", n_jobs=-1).fit(X, y) print(f"GridSearchCV ning best_score_ (optimistik): {oddiy.best_score_:.4f}") # nested CV — tashqi halqa sozlash jarayonini BUTUNLAY ko'rmaydi ichki = GridSearchCV(quvur(RandomForestClassifier(random_state=42)), kichik_panjara, cv=3, scoring="f1", n_jobs=-1) nested = cvs(ichki, X, y, cv=KFold(5, shuffle=True, random_state=42), scoring="f1") print(f"Nested CV (xolis baho) : {nested.mean():.4f} ±{nested.std():.4f}") print(f"Farq : {oddiy.best_score_ - nested.mean():+.4f}")
GridSearchCV ning best_score_ (optimistik): 0.7392 Nested CV (xolis baho) : 0.7245 ±0.0219 Farq : +0.0147
best_score_ har doim bir oz optimistik bo'ladi, chunki u eng yaxshi kombinatsiyani o'sha CV da tanlagan. Nested CV esa sozlash jarayonini ham sinovdan o'tkazadi.
Nested CV qachon kerak
U qimmat (CV ichida CV). Shuning uchun:
- Maqola yoki ilmiy ish yozsangiz — nested CV majburiy
- Ishlab chiqarish loyihasida — oddiy usul yetarli: CV da sozlang, keyin alohida test to'plamida bir marta tekshiring
Ikkinchi yo'l amalda keng tarqalgan va yetarlicha halol.
Qaysi parametrlarni sozlash kerak
Hamma parametrni sozlash — vaqtni yo'qotish. Modelga qarab muhim 2-4 tasi bor.
show(pd.DataFrame([ {"Model": "LogisticRegression", "Muhim": "C, penalty", "Sozlamaslik mumkin": "solver, max_iter"}, {"Model": "RandomForest", "Muhim": "max_depth, min_samples_leaf, max_features", "Sozlamaslik mumkin": "n_estimators (katta qo'y, 18-bob)"}, {"Model": "XGBoost / LightGBM", "Muhim": "learning_rate, max_depth, subsample, colsample", "Sozlamaslik mumkin": "n_estimators (early_stopping, 19-bob)"}, {"Model": "SVC", "Muhim": "C, gamma (BIRGA — 21-bob)", "Sozlamaslik mumkin": "kernel (rbf dan boshlang)"}, {"Model": "KNN", "Muhim": "n_neighbors, weights", "Sozlamaslik mumkin": "algorithm"}, ]))
| Model | Muhim | Sozlamaslik mumkin |
|---|---|---|
| LogisticRegression | C, penalty | solver, max_iter |
| RandomForest | max_depth, min_samples_leaf, max_features | n_estimators (katta qo'y, 18-bob) |
| XGBoost / LightGBM | learning_rate, max_depth, subsample, colsample | n_estimators (early_stopping, 19-bob) |
| SVC | C, gamma (BIRGA — 21-bob) | kernel (rbf dan boshlang) |
| KNN | n_neighbors, weights | algorithm |
print("Logistic regression uchun C ni sozlash:") gs_lr = GridSearchCV(quvur(LogisticRegression(max_iter=3000)), {"m__C": [0.001, 0.01, 0.1, 1, 10, 100]}, cv=5, scoring="f1", n_jobs=-1).fit(X, y) print(f" sukut (C=1) : {sukut['LogisticRegression'].mean():.4f}") print(f" eng yaxshi C={gs_lr.best_params_['m__C']:<6}: {gs_lr.best_score_:.4f}") print(f" foyda : {gs_lr.best_score_ - sukut['LogisticRegression'].mean():+.4f}")
Logistic regression uchun C ni sozlash: sukut (C=1) : 0.7273 eng yaxshi C=0.001 : 0.7315 foyda : +0.0042
Yakuniy taqqoslash
yakuniy = pd.DataFrame([ {"Model": "LogisticRegression", "Sukut": round(sukut["LogisticRegression"].mean(), 4), "Sozlangan": round(gs_lr.best_score_, 4), "Foyda": round(gs_lr.best_score_ - sukut["LogisticRegression"].mean(), 4)}, {"Model": "RandomForest", "Sukut": round(sukut["RandomForest"].mean(), 4), "Sozlangan": round(gs.best_score_, 4), "Foyda": round(gs.best_score_ - sukut["RandomForest"].mean(), 4)}, ]) show(yakuniy) print() print(f"Eng katta CV tarqalishi: ±{max(b.std() for b in sukut.values()):.4f}") print("Foydalarni shu tarqalish bilan solishtiring (9-bob qoidasi).")
Eng katta CV tarqalishi: ±0.0145 Foydalarni shu tarqalish bilan solishtiring (9-bob qoidasi).
| Model | Sukut | Sozlangan | Foyda |
|---|---|---|---|
| LogisticRegression | 0.727 | 0.732 | 0.004 |
| RandomForest | 0.703 | 0.7 | -0.003 |
Bugun nima o'rgandik
show(pd.DataFrame([ {"Tushuncha": "Hyperparametr", "Bitta jumla": "fit() dan OLDIN siz qo'yadigan sozlama"}, {"Tushuncha": "GridSearchCV", "Bitta jumla": "Hamma kombinatsiya — kichik panjara uchun"}, {"Tushuncha": "RandomizedSearchCV", "Bitta jumla": "Tasodifiy N ta — katta panjara uchun, amalda birinchi tanlov"}, {"Tushuncha": "HalvingGridSearchCV", "Bitta jumla": "Turnir usuli — yomonlarini erta tashlaydi"}, {"Tushuncha": "Tuning dan foyda", "Bitta jumla": "Odatda 1-3%, ba'zan nol — oxirida qiling"}, {"Tushuncha": "Eng katta xato", "Bitta jumla": "Sozlamani TEST da tanlash — leakage"}, {"Tushuncha": "best_score_", "Bitta jumla": "Bir oz optimistik — nested CV xolisroq"}, {"Tushuncha": "Nechta parametr", "Bitta jumla": "Har model uchun muhim 2-4 tasi bor, qolgani vaqt yo'qotish"}, ]))
| Tushuncha | Bitta jumla |
|---|---|
| Hyperparametr | fit() dan OLDIN siz qo'yadigan sozlama |
| GridSearchCV | Hamma kombinatsiya — kichik panjara uchun |
| RandomizedSearchCV | Tasodifiy N ta — katta panjara uchun, amalda birinchi tanlov |
| HalvingGridSearchCV | Turnir usuli — yomonlarini erta tashlaydi |
| Tuning dan foyda | Odatda 1-3%, ba'zan nol — oxirida qiling |
| Eng katta xato | Sozlamani TEST da tanlash — leakage |
| best_score_ | Bir oz optimistik — nested CV xolisroq |
| Nechta parametr | Har model uchun muhim 2-4 tasi bor, qolgani vaqt yo'qotish |
Keyingi bobda: bitta model o'rniga bir nechtasini birlashtiramiz.
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.