Mundarija (22)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Ikki xavf: dispersiya va optimizm
- 2.2. G'olib la'nati (winner's curse)
- 2.3. Test hajmi va standart xato
- 2.4. Uchta dizayn
- 2.5. Qaysi dizayn qachon
- 2.6. Baholash byudjeti
- 2.7. Tuzoqlar
- 2.8. Dizaynni oldindan tanlash
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Bitta holdout qanchalik beqaror
- Misol 2 — G'olib la'nati
- Misol 3 — Test hajmi va standart xato
- Misol 4 — Uchta dizaynni taqqoslash
- 5. To'g'ri va noto'g'ri tushunishlar
- 6. Keng tarqalgan xatolar va yechimlari
- 7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8. Eng yaxshi amaliyotlar
- 9. Amaliy topshiriq
- Xulosa
18.1-dars: Baholash dizayni
18-QISM — MODEL BAHOLASH VA SOZLASH · 1-dars
1. Kirish va motivatsiya
Model qurish oson, uni to'g'ri baholash qiyin. 12-qismda train_test_split va asosiy metrikalarni ko'rgandik. Endi savol chuqurroq: baholashning o'zi qanchalik ishonchli?
Ikkita xavf bor. Birinchisi — dispersiya: bitta tasodifiy bo'linishda olingan natija keyingi bo'linishda 0.05 ga o'zgarishi mumkin, ya'ni siz "yaxshilanish" deb o'ylagan narsa shunchaki shovqin bo'lishi mumkin. Ikkinchisi — optimizm: bitta to'plamda ko'p variant sinalsa, eng yaxshisi shu to'plamda omadi keldi degani, va yangi ma'lumotda pastroq chiqadi.
Bu ikki xavf birga ishlaydi va natija halokatli bo'ladi: jamoa CV da 0.86 ko'radi, ishlab chiqarishda 0.79 oladi va nima bo'lganini tushunmaydi.
Bu darsda: baholash dizayni nima, dispersiya va optimizm manbalari, g'olib la'nati (winner's curse), test to'plami hajmi va standart xato, hamda uchta asosiy dizayn (holdout, CV, CV + test) taqqoslanishi.
Real vaziyat. Kaggle musobaqasida jamoa ommaviy tablitsada 3-o'rinda edi 0.8412-bob. Yakuniy (yashirin) tablitsada 47-o'ringa tushdi. Sabab: ular ommaviy ballga qarab 900 ta variant sinashgan — bu ballga shunchaki moslashib qolishgan. G'olib jamoa esa faqat o'z CV siga ishongan va 2-o'rinda qolgan.
Bu darsda baholash dizaynini o'rganamiz.
Bu darsda:
- Ikki xavf: dispersiya va optimizm
- G'olib la'nati
- Test hajmi va standart xato
- Uchta dizayn
- Qaysi dizayn qachon
- Baholash byudjeti
- Tuzoqlar
- Amaliy: dizayn tanlash
ℹ Misollar real numpy/sklearn bilan (Python 3.14).
2. Nazariya — chuqur tushuntirish
2.1. Ikki xavf: dispersiya va optimizm
DISPERSIYA (variance) - baho tasodifiy tebranadi
manba: qaysi qatorlar testga tushdi
belgisi: seed ni o'zgartirsangiz natija o'zgaradi
yechim: ko'proq test qatori, CV, takroriy CV
OPTIMIZM (bias) - baho tizimli ravishda YUQORI
manba: baholash to'plamida tanlov/sozlash qilindi
belgisi: CV yaxshi, yangi ma'lumot yomon
yechim: tanlov uchun ALOHIDA to'plam, nested CV
MUHIM FARQ:
dispersiyani ko'proq ma'lumot bilan kamaytirasiz
optimizmni FAQAT dizayn bilan kamaytirasizKo'proq ma'lumot optimizmni tuzatmaydi: agar siz test to'plamiga qarab qaror qabul qilgan bo'lsangiz, u to'plam qanchalik katta bo'lmasin, baho optimistik qoladi.
2.2. G'olib la'nati (winner's curse)
m ta nomzodni bitta validatsiya to'plamida baholadingiz.
Har birining bahosi: haqiqiy qiymat + shovqin
ENG YUQORI ball olgan nomzod:
- haqiqatan yaxshi bo'lishi mumkin
- YOKI shunchaki shovqini ijobiy bo'lgan
Kutilgan optimizm ~ SE * sqrt(2 * ln(m))
m = 10 nomzod -> ~2.1 * SE
m = 100 nomzod -> ~3.0 * SE
m = 1000 nomzod -> ~3.7 * SE
XULOSA: nomzodlar soni ortsa, "eng yaxshi" ball
haqiqatdan shuncha uzoqlashadiTanlovning o'zi optimizm yaratadi: hech qanday xato qilmasangiz ham, ko'p nomzoddan eng yaxshisini tanlash bahoni yuqoriga suradi.
2.3. Test hajmi va standart xato
Aniqlik uchun (binom):
SE = sqrt(p * (1 - p) / n)
n = 100 p = 0.80 -> SE = 0.040 (+-0.08 oraliq!)
n = 1000 p = 0.80 -> SE = 0.013
n = 10000 p = 0.80 -> SE = 0.004
AUC uchun taxminan:
SE ~ 0.5 / sqrt(min(n_musbat, n_manfiy))
QOIDA: 0.01 lik farqni ishonchli o'lchash uchun
kamida bir necha ming test qatori kerakFarqning kattaligini SE bilan solishtiring: 200 qatorli testda "+0.02 yaxshilanish" hech narsani anglatmaydi, chunki SE ning o'zi 0.03.
2.4. Uchta dizayn
1. HOLDOUT (train / test)
+ eng tez, eng sodda
- yuqori dispersiya (kichik ma'lumotda)
- sozlash uchun joy yo'q
2. CROSS-VALIDATION
+ har qator bir marta testda bo'ladi -> kam dispersiya
+ butun ma'lumot ishlatiladi
- k barobar qimmat
- sozlash qilinsa OPTIMISTIK
3. CV + ALOHIDA TEST
+ CV da sozlaysiz, testda BIR MARTA baholaysiz
+ optimizm yo'q
- ma'lumotning bir qismi sozlashda ishlatilmaydiSozlash bo'ladigan joyda uchinchi dizayn kerak: CV tanlov uchun, alohida test yakuniy raqam uchun.
2.5. Qaysi dizayn qachon
MA'LUMOT HAJMI:
< 1 000 takroriy CV (holdout juda beqaror)
1 000-50 000 CV + alohida test
> 50 000 holdout yetarli (train/val/test)
VAZIFA:
bir nechta modelni taqqoslash -> CV
yakuniy raqam kerak -> alohida test
giperparametr sozlash -> nested CV yoki CV + test
ishlab chiqarishga chiqarish -> vaqt bo'yicha test
MUHIM: test to'plamini BIR MARTA ishlatingTest to'plamiga har qarash uni biroz "ishlatib qo'yadi"; uch marta qarasangiz, u endi validatsiya to'plami.
2.6. Baholash byudjeti
BYUDJETNI OLDINDAN TAQSIMLANG:
tadqiqot bosqichi: CV, cheksiz tajriba
tanlov bosqichi: CV, nomzodlar ro'yxati qisqartiriladi
tasdiqlash: alohida test, 1 marta
YOZIB BORING:
- nechta variant sinaldi (m)
- qaysi to'plamda
- yakuniy qaror qaysi raqamga asoslandi
m ni bilish optimizmni baholashga imkon beradiSinalgan variantlar sonini yozib boring: bu raqam keyinchalik "natija qanchalik ishonchli?" degan savolga javob beradi.
2.7. Tuzoqlar
Asosiy tuzoqlar: bitta holdoutga ishonish; test to'plamiga bir necha marta qarash; nomzodlar sonini hisobga olmaslik; farqni SE bilan solishtirmaslik; seed ni "yaxshi" natija chiqquncha o'zgartirish; CV da sozlab, o'sha CV bahosini yakuniy deb e'lon qilish; test hajmini juda kichik olish.
2.8. Dizaynni oldindan tanlash
Baholash dizaynini ish boshlashdan oldin tanlang va yozib qo'ying: qaysi bo'linish, nechta fold, qaysi metrika, test to'plami qachon ochiladi. Dispersiya ko'proq test qatori va CV bilan kamayadi; optimizm esa faqat dizayn bilan — tanlov uchun bir to'plam, yakuniy baho uchun boshqasi. G'olib la'nati nomzodlar soni bilan o'sadi, shuning uchun sinalgan variantlar sonini yozib boring.
3. Tez ma'lumotnoma
import numpy as np
from sklearn.model_selection import (RepeatedStratifiedKFold, cross_val_score,
train_test_split)
# 1. tadqiqot va tanlov uchun ajratish
X_ish, X_test, y_ish, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=0) # test YOPIQ
# 2. tanlov CV da
cv = RepeatedStratifiedKFold(n_splits=5, n_repeats=3, random_state=0)
ball = cross_val_score(model, X_ish, y_ish, cv=cv, scoring="roc_auc")
print(ball.mean(), ball.std())
# 3. yakuniy baho - BIR MARTA
eng_yaxshi.fit(X_ish, y_ish)
yakuniy = roc_auc_score(y_test, eng_yaxshi.predict_proba(X_test)[:, 1])
# standart xato (aniqlik uchun)
se = np.sqrt(p * (1 - p) / len(y_test))
QOIDA: dizaynni oldindan yoz · nomzodlar sonini sana ·
farqni SE bilan solishtir · testni bir marta ochBaholash dizayni xulosasi
Dispersiya -> ko'proq test qatori, CV, takroriy CV
Optimizm -> dizayn (tanlov va baho ALOHIDA to'plamda)
G'olib la'nati ~ SE * sqrt(2 ln m)
Test to'plami: bir marta, yakunda4. Batafsil misollar
Misollar real numpy/sklearn bilan (Python 3.14).
Misol 1 — Bitta holdout qanchalik beqaror
"""Holdout va CV dispersiyasini o'lchash (real numpy/sklearn)."""
import numpy as np
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import (StratifiedKFold, cross_val_score,
train_test_split)
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
def model():
return make_pipeline(StandardScaler(),
LogisticRegression(max_iter=2000))
def main() -> None:
X, y = make_classification(n_samples=600, n_features=20,
n_informative=6, n_redundant=4,
flip_y=0.15, class_sep=0.9, random_state=0)
print("=== 1. Ma'lumot ===")
print(f" {X.shape[0]} qator, {X.shape[1]} belgi, "
f"musbat ulushi {y.mean():.2%}")
print("\n=== 2. 200 ta turli holdout bo'linishi ===")
holdout = []
for seed in range(200):
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.25,
stratify=y, random_state=seed)
m = model().fit(Xtr, ytr)
holdout.append(roc_auc_score(yte, m.predict_proba(Xte)[:, 1]))
holdout = np.array(holdout)
print(f" o'rtacha: {holdout.mean():.4f}")
print(f" std: {holdout.std():.4f}")
print(f" eng past: {holdout.min():.4f} eng yuqori: {holdout.max():.4f}")
print(f" oraliq: {holdout.max() - holdout.min():.4f}")
p5, p95 = np.percentile(holdout, [5, 95])
print(f" 90% oraliq: [{p5:.4f}, {p95:.4f}]")
print("\n=== 3. 40 ta turli 5-fold CV ===")
cv_ballar = []
for seed in range(40):
cv = StratifiedKFold(5, shuffle=True, random_state=seed)
cv_ballar.append(cross_val_score(model(), X, y, cv=cv,
scoring="roc_auc").mean())
cv_ballar = np.array(cv_ballar)
print(f" o'rtacha: {cv_ballar.mean():.4f}")
print(f" std: {cv_ballar.std():.4f}")
print(f" oraliq: {cv_ballar.max() - cv_ballar.min():.4f}")
print("\n=== 4. Taqqoslash ===")
print(f" {'dizayn':<22} {'std':>8} {'oraliq':>9} {'narx':>8}")
print(f" {'holdout (25%)':<22} {holdout.std():>8.4f} "
f"{holdout.max() - holdout.min():>9.4f} {'1x':>8}")
print(f" {'5-fold CV':<22} {cv_ballar.std():>8.4f} "
f"{cv_ballar.max() - cv_ballar.min():>9.4f} {'5x':>8}")
print(f" dispersiya kamayishi: "
f"{holdout.std() / cv_ballar.std():.1f} barobar")
print(" ⭐ Bitta holdout - bitta tasodifiy son")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Ma'lumot ===
600 qator, 20 belgi, musbat ulushi 48.50%
=== 2. 200 ta turli holdout bo'linishi ===
o'rtacha: 0.7821
std: 0.0330
eng past: 0.6992 eng yuqori: 0.8772
oraliq: 0.1781
90% oraliq: [0.7345, 0.8386]
=== 3. 40 ta turli 5-fold CV ===
o'rtacha: 0.7758
std: 0.0056
oraliq: 0.0244
=== 4. Taqqoslash ===
dizayn std oraliq narx
holdout (25%) 0.0330 0.1781 1x
5-fold CV 0.0056 0.0244 5x
dispersiya kamayishi: 5.9 barobar
⭐ Bitta holdout - bitta tasodifiy sonNima ko'rsatdi: 2.1-bo'lim.
Misol 2 — G'olib la'nati
"""Ko'p nomzoddan tanlash bahoni qanday suradi (real numpy/sklearn)."""
import numpy as np
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
def main() -> None:
rng = np.random.default_rng(0)
X, y = make_classification(n_samples=6000, n_features=30,
n_informative=8, n_redundant=6,
flip_y=0.2, class_sep=0.8, random_state=0)
# mavjud ma'lumot (2000) va "haqiqat" to'plami (4000)
X_ish, X_haq, y_ish, y_haq = train_test_split(
X, y, test_size=4000, stratify=y, random_state=0)
X_tr, X_val, y_tr, y_val = train_test_split(
X_ish, y_ish, test_size=0.5, stratify=y_ish, random_state=0)
print("=== 1. Bo'linish ===")
print(f" o'quv {len(y_tr)}, validatsiya {len(y_val)}, "
f"'haqiqat' {len(y_haq)}")
print("\n=== 2. 200 ta nomzod (tasodifiy belgi to'plamlari) ===")
nomzodlar = []
for _ in range(200):
nechta = int(rng.integers(6, 25))
ustunlar = np.sort(rng.choice(X.shape[1], nechta, replace=False))
C = float(10 ** rng.uniform(-2, 1))
m = make_pipeline(StandardScaler(),
LogisticRegression(C=C, max_iter=2000))
m.fit(X_tr[:, ustunlar], y_tr)
val = roc_auc_score(y_val, m.predict_proba(X_val[:, ustunlar])[:, 1])
haq = roc_auc_score(y_haq, m.predict_proba(X_haq[:, ustunlar])[:, 1])
nomzodlar.append((val, haq))
nomzodlar = np.array(nomzodlar)
val_ballar, haq_ballar = nomzodlar[:, 0], nomzodlar[:, 1]
xato = val_ballar - haq_ballar
print(f" validatsiya: o'rtacha {val_ballar.mean():.4f}, "
f"std {val_ballar.std():.4f}")
print(f" 'haqiqat': o'rtacha {haq_ballar.mean():.4f}, "
f"std {haq_ballar.std():.4f}")
print(f" UMUMIY SILJISH (bu bo'linishning xususiyati): "
f"{xato.mean():+.4f}")
se = float(xato.std(ddof=1))
print(f" bitta bahoning shovqini (SE): {se:.4f}")
print(" tanlov optimizmi shu SILJISHDAN ORTIQCHA qismdir")
print("\n=== 3. Nomzodlar soni va ortiqcha optimizm ===")
print(f" {'m':>6} {'eng yaxshi val':>16} {'uning haqiqiysi':>17} "
f"{'xato':>9} {'ortiqcha':>10}")
for m_soni in [1, 5, 20, 50, 100, 200]:
eng = int(np.argmax(val_ballar[:m_soni]))
ortiqcha = xato[eng] - xato.mean()
print(f" {m_soni:>6} {val_ballar[eng]:>16.4f} "
f"{haq_ballar[eng]:>17.4f} {xato[eng]:>+9.4f} "
f"{ortiqcha:>+10.4f}")
print("\n=== 4. Nazariy chegara bilan solishtirish ===")
print(f" {'m':>6} {'ortiqcha optimizm':>19} {'SE*sqrt(2 ln m)':>17}")
for m_soni in [5, 20, 50, 100, 200]:
eng = int(np.argmax(val_ballar[:m_soni]))
ortiqcha = xato[eng] - xato.mean()
nazariy = se * np.sqrt(2 * np.log(m_soni))
print(f" {m_soni:>6} {ortiqcha:>+19.4f} {nazariy:>17.4f}")
print(" nazariy qiymat - MUSTAQIL nomzodlar uchun YUQORI chegara;")
print(" nomzodlar bir-biriga bog'liq bo'lgani uchun kuzatilgan")
print(" optimizm undan pastroq chiqadi")
print(" ⭐ Tanlovning o'zi optimizm yaratadi")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Bo'linish ===
o'quv 1000, validatsiya 1000, 'haqiqat' 4000
=== 2. 200 ta nomzod (tasodifiy belgi to'plamlari) ===
validatsiya: o'rtacha 0.8146, std 0.0690
'haqiqat': o'rtacha 0.8170, std 0.0687
UMUMIY SILJISH (bu bo'linishning xususiyati): -0.0025
bitta bahoning shovqini (SE): 0.0075
tanlov optimizmi shu SILJISHDAN ORTIQCHA qismdir
=== 3. Nomzodlar soni va ortiqcha optimizm ===
m eng yaxshi val uning haqiqiysi xato ortiqcha
1 0.8538 0.8578 -0.0040 -0.0015
5 0.8591 0.8594 -0.0004 +0.0021
20 0.8591 0.8594 -0.0004 +0.0021
50 0.8611 0.8604 +0.0007 +0.0032
100 0.8611 0.8604 +0.0007 +0.0032
200 0.8611 0.8604 +0.0007 +0.0032
=== 4. Nazariy chegara bilan solishtirish ===
m ortiqcha optimizm SE*sqrt(2 ln m)
5 +0.0021 0.0134
20 +0.0021 0.0183
50 +0.0032 0.0210
100 +0.0032 0.0227
200 +0.0032 0.0244
nazariy qiymat - MUSTAQIL nomzodlar uchun YUQORI chegara;
nomzodlar bir-biriga bog'liq bo'lgani uchun kuzatilgan
optimizm undan pastroq chiqadi
⭐ Tanlovning o'zi optimizm yaratadiNima ko'rsatdi: 2.2-bo'lim.
Misol 3 — Test hajmi va standart xato
"""Necha qator test kerak (real numpy/sklearn)."""
import numpy as np
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
def main() -> None:
rng = np.random.default_rng(0)
X, y = make_classification(n_samples=42000, n_features=20,
n_informative=6, n_redundant=4,
flip_y=0.15, class_sep=0.9, random_state=0)
X_tr, X_pul, y_tr, y_pul = train_test_split(
X, y, test_size=40000, stratify=y, random_state=0)
model = make_pipeline(StandardScaler(),
LogisticRegression(max_iter=2000)).fit(X_tr, y_tr)
p_pul = model.predict_proba(X_pul)[:, 1]
haqiqiy_acc = accuracy_score(y_pul, (p_pul >= 0.5).astype(int))
haqiqiy_auc = roc_auc_score(y_pul, p_pul)
print("=== 1. 'Haqiqiy' qiymatlar (40000 qatorda) ===")
print(f" aniqlik: {haqiqiy_acc:.4f}")
print(f" ROC AUC: {haqiqiy_auc:.4f}")
print("\n=== 2. Kichik test to'plamlari nima ko'rsatadi ===")
print(f" {'n_test':>8} {'acc o_rtacha':>13} {'acc std':>9} "
f"{'nazariy SE':>11} {'auc std':>9}")
for n_test in [50, 100, 500, 2000, 10000]:
acc_lar, auc_lar = [], []
for _ in range(300):
idx = rng.choice(len(y_pul), n_test, replace=False)
acc_lar.append(accuracy_score(y_pul[idx],
(p_pul[idx] >= 0.5).astype(int)))
auc_lar.append(roc_auc_score(y_pul[idx], p_pul[idx]))
acc_lar = np.array(acc_lar)
nazariy = np.sqrt(haqiqiy_acc * (1 - haqiqiy_acc) / n_test)
print(f" {n_test:>8} {acc_lar.mean():>13.4f} {acc_lar.std():>9.4f} "
f"{nazariy:>11.4f} {np.std(auc_lar):>9.4f}")
print("\n=== 3. 0.01 lik farqni ko'rish uchun necha qator kerak ===")
print(f" {'farq':>8} {'kerakli n (95% ishonch)':>26}")
for farq in [0.05, 0.02, 0.01, 0.005]:
# ikki mustaqil namuna uchun: farq > 1.96 * sqrt(2) * SE
p = haqiqiy_acc
n = 2 * (1.96 ** 2) * p * (1 - p) / (farq ** 2)
print(f" {farq:>8.3f} {int(np.ceil(n)):>26}")
print("\n=== 4. Amaliy xulosa ===")
for n_test in [100, 1000, 10000]:
se = np.sqrt(haqiqiy_acc * (1 - haqiqiy_acc) / n_test)
print(f" n={n_test:>6}: aniqlik {haqiqiy_acc:.3f} "
f"+- {1.96 * se:.3f} (95%)")
print(" ⭐ Farqni SE bilan solishtirmagan taqqoslash - bekor")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. 'Haqiqiy' qiymatlar (40000 qatorda) ===
aniqlik: 0.7563
ROC AUC: 0.8239
=== 2. Kichik test to'plamlari nima ko'rsatadi ===
n_test acc o_rtacha acc std nazariy SE auc std
50 0.7501 0.0649 0.0607 0.0656
100 0.7532 0.0427 0.0429 0.0433
500 0.7563 0.0192 0.0192 0.0190
2000 0.7562 0.0093 0.0096 0.0088
10000 0.7560 0.0034 0.0043 0.0035
=== 3. 0.01 lik farqni ko'rish uchun necha qator kerak ===
farq kerakli n (95% ishonch)
0.050 567
0.020 3541
0.010 14161
0.005 56644
=== 4. Amaliy xulosa ===
n= 100: aniqlik 0.756 +- 0.084 (95%)
n= 1000: aniqlik 0.756 +- 0.027 (95%)
n= 10000: aniqlik 0.756 +- 0.008 (95%)
⭐ Farqni SE bilan solishtirmagan taqqoslash - bekorNima ko'rsatdi: 2.3-bo'lim.
Misol 4 — Uchta dizaynni taqqoslash
"""Holdout, CV va CV+test dizaynlari (real numpy/sklearn)."""
import numpy as np
from sklearn.datasets import make_classification
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import (StratifiedKFold, cross_val_score,
train_test_split)
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
def nomzodlar():
return {
"logistik C=0.03": make_pipeline(
StandardScaler(), LogisticRegression(C=0.03, max_iter=2000)),
"logistik C=1": make_pipeline(
StandardScaler(), LogisticRegression(C=1.0, max_iter=2000)),
"KNN k=15": make_pipeline(StandardScaler(),
KNeighborsClassifier(n_neighbors=15)),
"RF 120": RandomForestClassifier(n_estimators=120, min_samples_leaf=3,
random_state=0, n_jobs=1),
}
def main() -> None:
X, y = make_classification(n_samples=21000, n_features=25,
n_informative=8, n_redundant=5,
flip_y=0.15, class_sep=0.85, random_state=0)
X_ish, X_haq, y_ish, y_haq = train_test_split(
X, y, test_size=20000, stratify=y, random_state=0)
print("=== 1. Sozlama ===")
print(f" mavjud ma'lumot: {len(y_ish)} qator")
print(f" 'haqiqat' to'plami: {len(y_haq)} qator (faqat tekshirish uchun)")
haqiqiy = {}
for nom, m in nomzodlar().items():
m.fit(X_ish, y_ish)
haqiqiy[nom] = roc_auc_score(y_haq, m.predict_proba(X_haq)[:, 1])
print("\n=== 2. Haqiqiy natijalar (20000 qatorda) ===")
print(f" {'nomzod':<18} {'haqiqiy AUC':>12}")
for nom, b in sorted(haqiqiy.items(), key=lambda kv: -kv[1]):
print(f" {nom:<18} {b:>12.4f}")
eng_yaxshi_haq = max(haqiqiy, key=haqiqiy.get)
print("\n=== 3. Uch dizayn, 12 ta takrorlash ===")
natija = {"holdout": [], "CV": [], "CV+test": []}
togri = {"holdout": 0, "CV": 0, "CV+test": 0}
for seed in range(12):
# A. holdout
Xa, Xb, ya, yb = train_test_split(X_ish, y_ish, test_size=0.3,
stratify=y_ish, random_state=seed)
ballar = {}
for nom, m in nomzodlar().items():
m.fit(Xa, ya)
ballar[nom] = roc_auc_score(yb, m.predict_proba(Xb)[:, 1])
tanlov = max(ballar, key=ballar.get)
natija["holdout"].append(ballar[tanlov] - haqiqiy[tanlov])
togri["holdout"] += int(tanlov == eng_yaxshi_haq)
# B. CV (o'sha CV bahosi yakuniy deb e'lon qilinadi)
cv = StratifiedKFold(5, shuffle=True, random_state=seed)
ballar = {nom: cross_val_score(m, X_ish, y_ish, cv=cv,
scoring="roc_auc").mean()
for nom, m in nomzodlar().items()}
tanlov = max(ballar, key=ballar.get)
natija["CV"].append(ballar[tanlov] - haqiqiy[tanlov])
togri["CV"] += int(tanlov == eng_yaxshi_haq)
# C. CV + alohida test
Xa, Xb, ya, yb = train_test_split(X_ish, y_ish, test_size=0.3,
stratify=y_ish, random_state=seed)
ballar = {nom: cross_val_score(m, Xa, ya, cv=cv,
scoring="roc_auc").mean()
for nom, m in nomzodlar().items()}
tanlov = max(ballar, key=ballar.get)
m = nomzodlar()[tanlov].fit(Xa, ya)
test = roc_auc_score(yb, m.predict_proba(Xb)[:, 1])
natija["CV+test"].append(test - haqiqiy[tanlov])
togri["CV+test"] += int(tanlov == eng_yaxshi_haq)
print(f" {'dizayn':<12} {'o_rtacha xato':>14} {'std':>8} "
f"{'togri tanlov':>14}")
for nom in ["holdout", "CV", "CV+test"]:
a = np.array(natija[nom])
print(f" {nom:<12} {a.mean():>+14.4f} {a.std():>8.4f} "
f"{togri[nom]:>10}/12")
print("\n=== 4. Talqin ===")
print(f" eng yaxshi model (haqiqatda): {eng_yaxshi_haq}")
print(" 'o_rtacha xato' musbat bo'lsa - dizayn OPTIMISTIK")
print(" CV eng yaxshi tanlovni qiladi, lekin o'z bahosi optimistik")
print(" CV+test: tanlov ham yaxshi, baho ham xolis")
print(" ⭐ Tanlov va yakuniy baho boshqa to'plamda bo'lsin")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Sozlama ===
mavjud ma'lumot: 1000 qator
'haqiqat' to'plami: 20000 qator (faqat tekshirish uchun)
=== 2. Haqiqiy natijalar (20000 qatorda) ===
nomzod haqiqiy AUC
RF 120 0.9024
KNN k=15 0.8867
logistik C=1 0.8588
logistik C=0.03 0.8585
=== 3. Uch dizayn, 12 ta takrorlash ===
dizayn o_rtacha xato std togri tanlov
holdout +0.0093 0.0155 5/12
CV +0.0008 0.0020 12/12
CV+test -0.0012 0.0159 10/12
=== 4. Talqin ===
eng yaxshi model (haqiqatda): RF 120
'o_rtacha xato' musbat bo'lsa - dizayn OPTIMISTIK
CV eng yaxshi tanlovni qiladi, lekin o'z bahosi optimistik
CV+test: tanlov ham yaxshi, baho ham xolis
⭐ Tanlov va yakuniy baho boshqa to'plamda bo'lsinNima ko'rsatdi: 2.4, 2.5-bo'limlar.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
| "Bitta test to'plami yetarli" | Kichik bo'lsa — tasodifiy son |
| "Ko'proq ma'lumot optimizmni tuzatadi" | Faqat dispersiyani kamaytiradi |
| "CV bahosi xolis" | Sozlash qilinsa — optimistik |
| "1000 variant sinash zararsiz" | G'olib la'nati kuchayadi |
| "+0.02 — yaxshilanish" | SE bilan solishtiring |
| "Testga bir necha marta qarash mumkin" | Har qarash uni ishlatadi |
| "Seed muhim emas" | Kichik ma'lumotda 0.05 farq beradi |
| "Dizayn keyin o'ylanadi" | Oldindan yoziladi |
6. Keng tarqalgan xatolar va yechimlari
1. Bitta holdoutga ishonish
Xtr, Xte, ytr, yte = train_test_split(X, y, random_state=42) # ⚠️
cross_val_score(model, X, y, cv=RepeatedStratifiedKFold(...)) # ✅2. Testga qayta-qayta qarash
for variant in variantlar: print(baho(yte, ...)) # ⚠️
# CV da tanlang, testni yakunda bir marta oching # ✅3. Nomzodlar sonini e'tiborsiz qoldirish
# 500 ta variantdan eng yaxshisi: 0.87 "aniq" # ⚠️
# m=500 -> optimizm ~ 3.5*SE; alohida testda tasdiqlang # ✅4. Farqni SE siz taqqoslash
print("A 0.842, B 0.836 -> A yaxshi") # ⚠️
print(f"farq {a-b:.4f}, SE {se:.4f} -> {'muhim' if ... }") # ✅5. Seed ni tanlash
for s in range(50): ... # eng yaxshi chiqqanini yozib qo'yish # ⚠️
# seed ni oldindan belgilang, natijani o'rtachalang # ✅6. Kichik test to'plami
train_test_split(X, y, test_size=0.1) # n=80 test qatori # ⚠️
# kamida bir necha ming, yoki takroriy CV # ✅7. CV bahosini yakuniy deb e'lon qilish
print(f"Model AUC: {grid.best_score_}") # ⚠️
print(f"Model AUC: {roc_auc_score(yte, ...)}") # alohida test # ✅7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 12.3-dars (o'tilgan): Train/test/validatsiya
- 12.4-dars (o'tilgan): Overfitting
- 17.10-dars (o'tilgan): Validatsiya strategiyasi
- 18.2-dars: Cross-validation turlari
- 18.4-dars: Nested CV
- 18.11-dars: Validatsiyaga overfitting
8. Eng yaxshi amaliyotlar
Dizaynni oldindan yozing.
Test to'plamini yoping.
Nomzodlar sonini sanang.
Farqni SE bilan solishtiring.
Kichik ma'lumotda takroriy CV.
Seed ni oldindan belgilang.
Yakuniy raqamni alohida testdan oling.
Natijani m bilan birga hisobot qiling.
9. Amaliy topshiriq
Vazifa 1: Bashorat qiling
1. # ikki asosiy xavf?
2. # dispersiya nima bilan kamayadi?
3. # optimizm nima bilan kamayadi?
4. # g'olib la'nati formulasi?
5. # m=100 da optimizm necha SE?
6. # aniqlik SE formulasi?
7. # 0.01 farq uchun taxminan necha qator?
8. # uchta dizayn?
9. # qaysi dizayn sozlash bo'lsa kerak?
10. # test to'plami necha marta ochiladi?
11. # 500 qatorli ma'lumotda qaysi dizayn?
12. # hisobotda m nima uchun kerak?Javoblar
- Dispersiya va optimizm
- Ko'proq test qatori, CV
- Dizayn (alohida to'plam)
SE * sqrt(2 ln m)- ~3.0 SE
sqrt(p(1-p)/n)- O'n minglab
- Holdout, CV, CV+test
- CV + alohida test
- Bir marta
- Takroriy CV
- Optimizmni baholash uchun
Vazifa 2: Xatolarni tuzating
1. Xtr, Xte, ytr, yte = train_test_split(X, y, random_state=42)
2. for variant in variantlar: print(baho(yte, ...))
3. # 500 variantdan eng yaxshisi: 0.87 "aniq"
4. print("A 0.842, B 0.836 -> A yaxshi")
5. print(f"Model AUC: {grid.best_score_}")Javoblar
1. cross_val_score(model, X, y, cv=RepeatedStratifiedKFold(...))
2. # CV da tanlang, testni yakunda bir marta oching
3. # m=500 -> optimizm ~3.5*SE; alohida testda tasdiqlang
4. # farqni SE bilan solishtiring
5. print(f"Model AUC: {roc_auc_score(yte, ...)}")Vazifa 3: Dispersiya
Modellang:
- Ma'lumot
- 200 holdout
- 40 CV
- Taqqoslash
Vazifa 4: G'olib la'nati
Modellang:
- Bo'linish
- Nomzodlar
- m va optimizm
- Nazariya
Vazifa 5: Test hajmi
Modellang:
- Haqiqiy qiymat
- Kichik testlar
- Kerakli n
- Xulosa
Vazifa 6: Dizaynlar
Modellang:
- Sozlama
- Haqiqiy natijalar
- Uch dizayn
- Talqin
Vazifa 7: O'ylash
Jamoada 3 kishi bir xil ma'lumot bilan ishlaydi va har biri o'z modelini o'z CV si bilan baholaydi. Yakunda eng yuqori CV ballini olgan model tanlanadi. Bu dizaynda qanday muammo bor?
Javob
Qisqa javob: bu yashirin g'olib la'nati. Har bir kishi o'z ichida o'nlab variant sinaydi, keyin ular orasidan yana eng yaxshisi tanlanadi. Umumiy nomzodlar soni — 3 × (har birining variantlari), lekin hech kim bu sonni bilmaydi.
1. Muammoning tuzilishi
| Bosqich | Nomzodlar | Optimizm |
|---|---|---|
| Har bir kishi ichida | ~30 | ~2.6 SE |
| Jamoa darajasida | ~90 | ~3.0 SE |
| Hisobotda ko'rsatiladi | 3 | ~1.5 SE deb o'ylanadi |
Ya'ni haqiqiy optimizm hisobot qilinganidan ikki barobar katta.
2. Qo'shimcha muammolar
- Turli CV bo'linishlari: agar har kim o'z
random_stateini ishlatsa, ballar taqqoslanmaydi — birining foldlari osonroq bo'lishi mumkin. - Turli tayyorlash: biri masshtablagan, ikkinchisi yo'q — farq modeldan emas, quvurdan kelishi mumkin.
- Leakage farqi: biri
Pipelineishlatgan, ikkinchisi yo'q — ikkinchisining balli sun'iy yuqori.
3. To'g'ri dizayn
1. Jamoa BITTA bo'linishni kelishadi (bitta random_state, bitta cv obyekti)
2. Har kim o'z nomzodlarini shu CV da baholaydi
3. HAR KIM sinagan variantlar sonini yozib boradi
4. Finalga har kimdan 1-2 nomzod chiqadi
5. Finalistlar ALOHIDA, hech kim ko'rmagan test to'plamida baholanadi
6. Yakuniy raqam - shu testdan4. Amaliy tashkil qilish
- Test to'plami ish boshida ajratiladi va qulflanadi (alohida fayl, kirish huquqi cheklangan).
- CV bo'linishi umumiy modulda:
from baholash import CV— hamma shuni import qiladi. - Har bir tajriba jurnalga yoziladi: kim, qachon, qanday variant, qanday ball.
5. Xulosa
- Bitta CV bo'linishi — hamma uchun
- Sinalgan variantlar soni yozib boriladi
- Finalistlar alohida testda tasdiqlanadi
- Yakuniy raqam CV dan emas, testdan olinadi
Nimani mustahkamlaydi: 2.2, 2.4-bo'limlar.
Xulosa
Bu darsda baholash dizaynini o'rgandik.
Eng muhim uch fikr:
Dispersiya va optimizm — ikki boshqa xavf. Dispersiya tasodifiy bo'linishdan keladi va ko'proq test qatori yoki CV bilan kamayadi. Optimizm esa baholash to'plamida qaror qabul qilishdan keladi va faqat dizayn bilan tuzatiladi — ma'lumot qo'shish yordam bermaydi.
G'olib la'nati nomzodlar soni bilan o'sadi.
mta mustaqil variantdan eng yaxshisini tanlaganda kutilgan optimizmning yuqori chegarasi taxminanSE * sqrt(2 ln m);m = 100bo'lsa bu 3 SE. Amalda nomzodlar bir-biriga bog'liq bo'lgani uchun kuzatilgan optimizm bu chegaradan pastroq chiqadi — misolimizda 200 nomzod uchun +0.003 atrofida. Shuning uchun sinalgan variantlar sonini yozib boring: usiz optimizmni umuman baholab bo'lmaydi.Tanlov va yakuniy baho alohida to'plamda. Amaliy dizayn: ma'lumotdan test ajratib qulflang, qolganida CV bilan sozlang va tanlang, yakunda testni bir marta oching. Farqlarni standart xato bilan solishtiring: 200 qatorli testda "+0.02" hech narsani anglatmaydi.
Keyingi darsda cross-validation turlarini ko'rib chiqamiz: KFold, StratifiedKFold, GroupKFold, TimeSeriesSplit va ularning qachon kerakligi.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!