IlmHamroh
Data Science va sun'iy intellekt/Loyihalar va karyera5/12-dars44 daqiqa
Mundarija (27)

29.5-dars: Kaggle va musobaqalar

29-QISM — LOYIHALAR VA KARYERA · 5-dars


1. Kirish va motivatsiya

Oldingi darsda to'liq loyihaning natijasini taqdim etishni o'rgandik: xulosani birinchi o'ringa qo'yish, noaniqlikni halol ko'rsatish, qarorga aylanadigan tavsiya. O'sha loyihada biz o'zimiz savol qo'ydik, ma'lumot yig'dik va baholash sxemasini o'zimiz tanladik. Endi butunlay boshqa formatga o'tamiz: savol, ma'lumot va metrika tayyor berilgan, minglab odam bir vaqtda bir xil vazifani yechmoqda, va natija jadvalda hammaga ko'rinib turadi. Bu — ma'lumotlar fanidagi musobaqa, eng mashhur platformasi esa Kaggle.

Musobaqa — kuchli mashq maydoni. Bir necha hafta ichida siz o'nlab g'oyani sinaysiz, boshqalarning ochiq notebooklarini o'qiysiz, musobaqa tugagach g'oliblar qanday ishlaganini ko'rasiz. Lekin musobaqaning o'z tuzoqlari bor, va ular real ishga to'g'ridan-to'g'ri ko'chmaydi: jadvaldagi joy uchun kurashda odamlar ko'pincha jadvalning o'ziga moslashib qoladi, ma'lumotdagi tasodifiy sizishlardan foydalanadi, o'nlab modelni bir-biriga qo'shib, amalda hech qachon ishlatib bo'lmaydigan "yirtqich"ni quradi.

Real vaziyat. Talaba birinchi musobaqasida ochiq leaderboard (public LB) da 40-o'rinda edi. Oxirgi haftada u har kuni ruxsat etilgan barcha yuborishlarni ishlatdi: parametrlarni bir oz o'zgartirib, public LB da ball oshsa — saqlab qoldi. Oxirgi kuni u 6-o'ringa chiqdi. Musobaqa tugab, yopiq leaderboard (private LB) ochilganda u 210-o'ringa tushdi. Uning o'z cross-validation (CV) bahosiga ko'ra eng yaxshi bo'lgan, lekin public LB da "zaifroq" ko'ringan yuborishi esa private da 25-o'rinni olgan bo'lardi. Talaba shovqinni signal deb qabul qilgan edi. Bu darsning 1-misolida aynan shu hodisani — shake-up ni — simulyatsiya qilamiz va uning kattaligi nimaga bog'liqligini o'lchaymiz.

Bu darsda musobaqa mexanikasini, leaderboardga emas o'z CV ingizga ishonishni, musobaqa texnikalarining haqiqiy foydasi va chegarasini, hamda Kaggle tajribasidan real ishga nima ko'chishini o'rganamiz.

Bu darsda:

  • Kaggle qanday ishlaydi: musobaqa, notebook, dataset, discussion, darajalar
  • Public va private leaderboard, shake-up va yuborishlar sonining ta'siri
  • "O'z CV ingizga ishoning": CV ni test taqsimotiga moslash
  • Adversarial validation: train va test farqini topish
  • Musobaqa texnikalari: kuchli bazaviy, belgilar, ansambl va uning chegarasi
  • Sizish (leak) — musobaqada g'alaba, real ishda qiymatsiz
  • Kaggle tajribasini real ishga ko'chirish; jamoa va writeup
  • Kaggle API (buyruq satri)
  • Tuzoqlar

ℹ Misollar real numpy/sklearn bilan (Python 3.14). Internet va Kaggle API bu muhitda yo'q — musobaqalar sintetik ma'lumotda simulyatsiya qilinadi, shuning uchun har ishtirokchining "haqiqiy" sifati ma'lum va leaderboard xatosini to'g'ridan-to'g'ri o'lchaymiz.


2. Nazariya — chuqur tushuntirish

2.1. Kaggle qanday ishlaydi

text
KAGGLE EKOTIZIMI
  COMPETITIONS  - musobaqalar: vazifa, ma'lumot, metrika, muddat, qoidalar
  NOTEBOOKS     - bulutda ishlaydigan notebooklar (CPU/GPU, vaqt kvotasi bilan);
                  boshqalarning ochiq yechimlari - eng katta o'quv manbasi
  DATASETS      - foydalanuvchilar joylagan ochiq ma'lumotlar (litsenziyasini o'qing)
  DISCUSSION    - forum: savollar, g'oyalar, sizish haqida xabarlar, writeup lar
  MODELS        - oldindan o'qitilgan modellar katalogi

MUSOBAQA TURLARI (umumiy)
  featured / research  - kompaniya yoki tadqiqot guruhi bergan real vazifa
  playground / getting started - o'quv musobaqalari (sintetik yoki klassik ma'lumot)
  community            - foydalanuvchilar tashkil qilgan
  code competition     - yechim Kaggle notebookida, cheklangan vaqtda ishlashi
                         kerak; yashirin test faqat shu yerda ko'rinadi

MEXANIKA
  1. train (belgilar + nishon) va test (faqat belgilar) yuklab olinadi
  2. test bashoratlari submission.csv ga yoziladi va yuboriladi
  3. kuniga cheklangan miqdorda yuborish (aniq soni musobaqa qoidasida)
  4. oxirida hisobga olinadigan yakuniy yuborishlarni O'ZINGIZ tanlaysiz
     (odatda bir nechta; tanlamasangiz - qoidadagi standart tartib)
  5. musobaqa tugagach private LB ochiladi - o'rinlar shu bo'yicha

Darajalar va medallar. Kaggle da musobaqa, notebook, dataset va discussion yo'nalishlari bo'yicha alohida darajalar bor (Novice dan Grandmaster gacha). Musobaqa medallari (bronza, kumush, oltin) yakuniy private LB dagi o'rinning nisbiy ulushiga qarab beriladi va chegara ishtirokchilar soniga bog'liq. Aniq qoidalar va chegaralar vaqti-vaqti bilan o'zgaradi — joriy talablarni Kaggle ning rasmiy "Progression System" sahifasidan tekshiring; bu darsda ular ataylab raqam bilan keltirilmaydi.

Ish beruvchi uchun daraja o'zi emas, nima o'rganganingiz va buni qanday tushuntira olishingiz muhim (29.7 va 29.8 da bunga qaytamiz). "Kumush medal" dan ko'ra "shake-up ga qarshi CV sxemamni qanday qurdim" degan bir abzas ko'proq ma'lumot beradi.

2.2. Public va private leaderboard

text
            TEST TO'PLAMI (nishonlari tashkilotchida)
  +---------------------------+-------------------------------------+
  |   PUBLIC qism (masalan    |   PRIVATE qism (qolgan qism)        |
  |   20-30%)                 |                                     |
  |   ball DARHOL ko'rinadi   |   ball faqat OXIRIDA ochiladi       |
  +---------------------------+-------------------------------------+
  Siz har doim BUTUN test uchun bashorat yuborasiz; qaysi qator qaysi
  qismga tegishli ekanini bilmaysiz.

PUBLIC LB  - musobaqa davomida: "qanchalik yaxshi ketyapman?"
PRIVATE LB - yakuniy natija: modelning ko'rmagan ma'lumotdagi sifati

Nega ikkita? Agar faqat bitta jadval bo'lsa, ishtirokchilar yuzlab yuborish bilan aynan o'sha test qatorlariga moslashib olardi — 18.11 da ko'rgan validatsiyaga overfitting ning ochiq ko'rinishi. Private qism — hech kim "ko'rmagan" hakam.

Public ball — shovqinli baho. Aniqlik (accuracy) uchun uning standart xatosi:

text
SE(aniqlik) = sqrt( p * (1 - p) / n_public )
  p = 0.65, n_public = 1250  ->  SE ~ 0.0135
  p = 0.65, n_public = 5000  ->  SE ~ 0.0067
AUC, log-loss uchun ham xuddi shunday: n kichik -> shovqin katta

Jadvalda qo'shni o'rinlar orasidagi farq ko'pincha 0.001 atrofida,
ya'ni SE dan 10 barobar kichik -> public o'rinlarning ko'pchiligi TASODIF.

2.3. Public LB ga moslashish va shake-up

Bitta yuborishning public bahosi — haqiqiy sifat + shovqin. Agar siz K ta yuborishdan public bo'yicha eng yaxshisini tanlasangiz, tanlangan baho sistematik ravishda oshirib ko'rsatilgan bo'ladi — siz shovqini eng "omadli" bo'lganini tanladingiz:

text
K ta mustaqil shovqinli baho: s_k = q_k + e_k,  e_k ~ N(0, sigma^2)
max_k s_k  ning kutilgan optimizmi  ~  sigma * sqrt(2 * ln K)   (katta K da)
  K = 1   -> 0
  K = 5   -> ~1.8 * sigma
  K = 40  -> ~2.7 * sigma
Real yuborishlar bir-biriga kuchli korrelyatsiyalangan (bitta model,
kichik o'zgarishlar), shuning uchun amalda optimizm kichikroq -
lekin yo'nalishi har doim bir xil: public "eng yaxshi" > private.

SHAKE-UP: private LB ochilganda o'rinlarning keskin o'zgarishi
  kattaligi: public test kichik, ishtirokchilar sifati bir-biriga yaqin,
             ko'p yuborish, public va private taqsimoti farqli bo'lsa - katta
  o'lchovi:  Spearman(public o'rin, private o'rin), o'rtacha |o'rin farqi|

Qoida: public LB dagi yaxshilanish sizning CV dagi yaxshilanishingiz bilan tasdiqlanmasa — bu shovqin bo'lishi ehtimoli katta.

2.4. O'z CV ingizga ishoning

"Trust your CV" — Kaggle dagi eng mashhur maslahat. Buning ma'nosi: public LB ni yagona hakam qilmang, lekin CV ingiz ishonchga loyiq bo'lishi uchun uni to'g'ri qurishingiz kerak.

text
YAXSHI CV SHARTLARI (18-qism):
  1. test qanday ajratilgan bo'lsa, CV ham shunday ajratiladi
       vaqt bo'yicha test   -> vaqt bo'yicha fold (18.2, 28.3)
       mijoz/guruh bo'yicha -> GroupKFold (bir mijoz ikki tomonda emas)
       tasodifiy            -> StratifiedKFold
  2. CV hajmi katta: butun train (5-fold OOF) - public test dan ko'pincha
     bir necha barobar ko'p qator -> shovqin kichikroq
  3. butun jarayon fold ichida: kodlash, belgi tanlash, sozlash (12.9, 17.8)
  4. CV va public LB ni jadvalda kuzatish:
       versiya | CV     | CV std | public LB | izoh
       v3      | 0.8123 | 0.004  | 0.809     | lgbm baza
       v4      | 0.8151 | 0.004  | 0.806     | + belgi A   <- CV oshdi, LB tushdi
     Ikkalasi bir yo'nalishda harakat qilsa - CV ishonchli;
     tez-tez teskari bo'lsa - yo CV sxemasi xato, yo public test juda kichik

YAKUNIY TANLOV (odatda 2 ta yuborish):
  1) CV bo'yicha eng yaxshi ("xavfsiz")
  2) boshqacha, lekin CV si yaqin variant (masalan, boshqa ansambl) -
     public LB ga qarab emas, xilma-xillik uchun

2.5. Adversarial validation

CV faqat train va test bir xil taqsimotdan kelganda testni yaxshi bashorat qiladi. Farq bormi — 27.12 da drift uchun ko'rgan domen klassifikatori bilan tekshiriladi:

text
ADVERSARIAL VALIDATION:
  train qatorlari -> 0,  test qatorlari -> 1  (nishon ustuni olib tashlanadi)
  klassifikator CV bilan o'qitiladi -> OOF AUC
    AUC ~ 0.5   train va test ajralmaydi -> tasodifiy CV yaroqli
    AUC >> 0.5  farq bor -> qaysi belgilar? (belgi ahamiyati, bitta belgi AUC)

KEYIN NIMA QILINADI:
  a) ID, sana, qator raqami kabi "trivial" ajratuvchilarni olib tashlash
     (ular ajratadi, lekin model uchun foydasiz yoki xavfli)
  b) validatsiyani testga o'xshatish:
       - vaqt bo'yicha oxirgi qism (agar farq vaqt bilan bog'liq bo'lsa)
       - train qatorlarini p(test | x) / (1 - p(test | x)) og'irlik bilan
         TANLAB olish (importance sampling) - val taqsimoti ~ test taqsimoti
       - "eng testga o'xshash" top-k qatorlar - ehtiyot: juda tor bo'lib,
         testdan ham chetroq to'plam hosil qilishi mumkin (2-misol)
  c) siljigan belgini o'zgartirish (nisbiy belgi, masalan summa/daromad)
     yoki olib tashlash - buni CV bilan tekshirib

2.6. Musobaqa texnikalari

text
ISH TARTIBI (tajribali ishtirokchilar amaliyoti)
  1-kun:  qoidalar, metrika, ma'lumot tuzilishi, EDA (8-qism);
          CV sxemasi va KUCHLI BAZAVIY yuborish (masalan, gradient boosting
          standart parametrlar bilan) - "quvur ishlaydi" tekshiruvi
  keyin:  bitta o'zgarish -> CV -> jurnal (27.4 eksperiment kuzatuvi g'oyasi)
          belgilar (17-qism) odatda sozlashdan ko'proq beradi
          xato tahlili 14.13-bob: model qayerda adashyapti?
  oxiri:  ansambl, yakuniy tanlov, kodni tozalash, writeup

METRIKAGA MOSLASH
  metrika - hakam: AUC -> tartib muhim; log-loss -> kalibrlash 14.10-bob;
  RMSE va MAE -> o'rtacha va median; F1 -> chegara tanlash (OOF da!)
  metrikaning "g'alati" joylarini bilish - musobaqa mahoratining bir qismi,
  real ishda esa metrika biznes maqsadidan kelib chiqadi (18.9)

2.7. Ansambl: nega ishlaydi va qayerda to'xtaydi

15.12 da voting va stackingni o'rgandik. Musobaqa kontekstida asosiy fikr:

text
IKKI MODEL O'RTACHASI:  xato dispersiyasi
  Var((e1 + e2) / 2) = (Var e1 + Var e2 + 2 Cov(e1, e2)) / 4
  korrelyatsiya r = 1   -> hech qanday foyda yo'q
  korrelyatsiya r past  -> xatolar bir-birini qoplaydi

USULLAR (soddadan murakkabga)
  o'rtacha ehtimol         - eng sodda, ko'pincha yetarli
  rang o'rtacha            - AUC uchun; modellar shkalasi har xil bo'lsa
  vaznli o'rtacha          - vaznlar OOF da tanlanadi
  stacking                 - meta-model OOF bashoratlarda o'qitiladi
                             (test bashoratida emas! - 15.12 leakage)

CHEGARALAR
  o'xshash modellar (bir xil algoritm, boshqa urug')  -> foyda kichik
  foyda ko'pincha public LB shovqinidan kichik -> uni LB da "ko'rib"
    bo'lmaydi, faqat CV da
  real ishda: har qo'shilgan model - kechikish, xotira, texnik xizmat
    va tushuntirish narxi (27-qism)

2.8. Sizish (leak): musobaqada g'alaba, real ishda nol

17.8 da leakage ni o'rgandik — bashorat paytida mavjud bo'lmaydigan ma'lumotning modelga kirib qolishi. Musobaqada u ko'pincha ma'lumot tayyorlash jarayonidan keladi:

text
MUSOBAQADAGI SIZISH TURLARI
  ID / qator tartibi   - ma'lumot nishon bo'yicha tartiblangan holda
                         raqamlangan (masalan, ijobiylar keyin qo'shilgan)
  kelajak ma'lumoti    - hodisadan KEYIN yozilgan ustun (undiruv qo'ng'iroqlari,
                         "yopilgan sana")
  takror/guruh         - bir obyekt (bemor, mijoz) train va testda
  fayl metama'lumoti   - rasm o'lchami, fayl nomi, yozilgan vaqt
  tashqi manba         - test nishonini ochiq manbadan tiklash

SIZISHDAN FOYDALANISH
  musobaqada: qoidalar va tashkilotchi pozitsiyasiga bog'liq; ko'pincha
              topilgan sizish forumda e'lon qilinadi va tuzatiladi
  real ishda: QIYMATI NOL yoki manfiy - ishlab chiqarishda bu ma'lumot
              bo'lmaydi; model sizishga tayangani uchun halol belgilarni
              kam o'rganadi va real oqimda halol modeldan YOMONROQ bo'ladi
  intervyuda: "leak topdim va undan foydalandim" emas,
              "leak topdim, uni qanday aniqladim va nega real tizimda
              ishlatib bo'lmasligini tushuntirdim" - kuchli javob

2.9. Kaggle tajribasini real ishga ko'chirish

Ko'chadi Ko'chmaydi (yoki teskari)
Baholash sxemasini qurish, CV ga tanqidiy qarash Metrikani tayyor holda olish — real ishda uni o'zingiz tanlaysiz
Tez kuchli bazaviy va iterativ tajriba intizomi Ma'lumot tayyor va toza — real ishda vaqtning katta qismi yig'ish va tozalash
Belgilar yaratish, xato tahlili 0.001 uchun kurash — real ishda bu odatda qiymatsiz
Adversarial validation, drift sezgisi 30 modelli ansambl — kechikish va texnik xizmat narxi
Boshqalarning kodini o'qish va o'rganish Sizishdan foydalanish
Natijani writeup da tushuntirish "Leaderboard hakam" — real ishda hakam biznes natijasi va A/B test (27.13)
Vaqt va resurs cheklovida ishlash (code competition) Model monitoringi, qayta o'qitish — musobaqada umuman yo'q (27.11-27.13)

2.10. Jamoa, yozuvlar va writeup

Jamoa. Qoidada jamoa birlashishi muddati va jamoa hajmi chegarasi bor. Yaxshi jamoa — xilma-xil yondashuvlar (bu ansamblga xilma-xillik beradi, 2.7) va umumiy CV sxemasi: hamma bir xil foldlar bilan OOF bashorat saqlasa, ularni birlashtirish oson. Umumiy CV siz jamoa har xil "haqiqat" ga ishonadi.

Yozuvlar (jurnal). Har yuborish uchun: versiya, o'zgarish, CV o'rtacha va std, public LB, commit xeshi 29.6-bob. Bu — 27.4 dagi eksperiment kuzatuvining soddalashtirilgan shakli.

Writeup — musobaqadan keyin yechimni tushuntiruvchi qisqa maqola (forumda yoki blogda). Portfolio uchun eng qimmatli artefakt 29.7-bob:

text
WRITEUP TUZILISHI
  1. Qisqa xulosa: yakuniy o'rin (private), asosiy g'oya 2-3 gapda
  2. CV sxemasi: qanday va NEGA (test qanday ajratilgan edi)
  3. CV va LB bog'liqligi: jadval, shake-up haqida kuzatuv
  4. Belgilar: nima ishladi, nima ishlamadi (salbiy natijalar ham!)
  5. Modellar va ansambl: har birining CV hissasi
  6. Nima ishlamadi va nega
  7. Kod havolasi (toza repo - 29.6)

2.11. Birinchi musobaqa: qanday boshlash

text
TANLASH
  playground / getting started yoki tugagan musobaqa (ko'plarida kechikkan
  yuborish - "late submission" - hali ham baholanadi) - bosimsiz o'rganish
  tabular vazifa - kurs bilimi (12-19-qismlar) to'g'ridan-to'g'ri ishlaydi
  ma'lumot hajmi noutbukka sig'adigan bo'lsin
  keyin - NLP yoki CV (22-24-qismlar), code competition

BIRINCHI HAFTA REJASI (namuna)
  1-kun  qoidalar, metrika, ma'lumot; EDA notebook
  2-kun  CV sxemasi + bazaviy model + birinchi yuborish (quvur ishlaydimi?)
  3-4    belgilar: har biri alohida CV bilan; jurnal
  5      xato tahlili: qaysi segmentda model adashadi?
  6      2-3 xilma-xil model, OOF saqlash
  7      o'rtacha ansambl, yakuniy tanlov qoidasi, repo ni tozalash

O'QISH TARTIBI (tugagan musobaqada)
  1. o'z yechimingiz -> 2. g'oliblar writeup lari -> 3. farqlar ro'yxati:
     "ular nimani ko'rdi, men nimani ko'rmadim?" - eng tez o'sish usuli

Musobaqani o'quv vositasi sifatida ko'rsangiz, o'rin ikkinchi darajali bo'ladi: bitta musobaqadan olingan "CV sxemam public dan ko'ra private ni yaxshiroq bashorat qildi" degan tajriba o'nta tasodifiy yuqori public o'rindan qimmatroq.

2.12. Kaggle API

Kaggle ning rasmiy buyruq satri vositasi ma'lumotni yuklab olish va yuborishni avtomatlashtiradi. Bu muhitda internet yo'q, shuning uchun buyruqlar faqat ma'lumotnoma sifatida:

bash
pip install kaggle
# API token: Kaggle sozlamalaridan yaratiladi; ~/.kaggle/kaggle.json ga
# yoki muhit o'zgaruvchilariga (KAGGLE_USERNAME, KAGGLE_KEY) joylanadi.
# Token faylini HECH QACHON git ga qo'shmang (.gitignore - 29.6).
chmod 600 ~/.kaggle/kaggle.json

kaggle competitions list -s tabular                      # musobaqalarni qidirish
kaggle competitions download -c <musobaqa-nomi> -p data/raw
kaggle competitions submit -c <musobaqa-nomi> -f submission.csv \
    -m "v4: histgb + belgi A, CV 0.8151 (std 0.004)"     # izohda CV ni yozing
kaggle competitions submissions -c <musobaqa-nomi>       # yuborishlar tarixi
kaggle competitions leaderboard -c <musobaqa-nomi> --show
kaggle datasets download -d <egasi>/<dataset-nomi> -p data/raw --unzip
kaggle kernels pull <egasi>/<notebook-nomi> -p notebooks/

Yuborish izohiga CV ni yozish — oddiy, lekin juda foydali odat: oxirida yakuniy tanlov qilayotganda har yuborishning CV si ko'z oldingizda bo'ladi.

2.13. Tuzoqlar

Asosiy tuzoqlar: public LB ni yagona hakam qilish; ko'p yuborish bilan public testga moslashish; kichik public test dagi 0.001 farqni haqiqiy deb o'ylash; CV sxemasini test ajratilishiga moslamaslik (vaqt, guruh); train va test taqsimoti farqini tekshirmaslik; ID va sana kabi trivial ajratuvchilarni modelda qoldirish; stackingni test bashoratida o'qitish; o'xshash modellardan "ansambl" qilib, katta foyda kutish; ansambl foydasini LB da ko'rishga urinish; sizishga tayangan yechimni real ishga ko'chirish; kodni oxirida takrorlab bo'lmaydigan holda qoldirish; Kaggle tokenini repo ga qo'shib qo'yish.


3. Tez ma'lumotnoma

python
import numpy as np
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import StratifiedKFold, cross_val_predict

# public ball shovqini (aniqlik)
se = np.sqrt(p * (1 - p) / n_public)

# adversarial validation
Xa = np.vstack([X_train, X_test])
d = np.r_[np.zeros(len(X_train)), np.ones(len(X_test))]
p_test = cross_val_predict(HistGradientBoostingClassifier(), Xa, d,
                           cv=StratifiedKFold(5, shuffle=True, random_state=0),
                           method="predict_proba")[:, 1]
adv_auc = roc_auc_score(d, p_test)
w = np.clip(p_test[: len(X_train)], 0.01, 0.99)
w = w / (1 - w)                                   # testga o'xshashlik og'irligi
val = rng.choice(len(X_train), n_val, replace=False, p=w / w.sum())

# OOF bashoratlar va stacking
oof = {k: cross_val_predict(m, X, y, cv=cv, method="predict_proba")[:, 1]
       for k, m in modellar.items()}
Z = np.column_stack([oof[k] for k in modellar])
meta = LogisticRegression().fit(Z, y)             # test bashoratida EMAS

# sizish detektori: bitta belgi AUC
for j, nom in enumerate(ustunlar):
    a = roc_auc_score(y, X[:, j]); a = max(a, 1 - a)
    if a > 0.8:
        print("shubhali:", nom, round(a, 3))

Qaysi vaziyatda nima

Vaziyat Nima qilish kerak
Public LB oshdi, CV tushdi CV ga ishoning; public test kichik — shovqin
CV va LB tez-tez teskari CV sxemasini tekshiring (vaqt/guruh), adversarial validation
Adversarial AUC ~ 1.0 ID/sana kabi trivial ajratuvchini toping va olib tashlang
Adversarial AUC 0.7-0.9 Val ni og'irlik bilan tanlab oling yoki vaqt bo'yicha bo'ling
Bitta belgi yolg'iz AUC > 0.8 Sizish ehtimoli: belgi qachon va qanday yozilgan?
Ansambl o'ylayapsiz Avval OOF korrelyatsiyasi; xilma-xil modellar
Yakuniy tanlov CV bo'yicha eng yaxshi + CV si yaqin xilma-xil variant

Musobaqa xulosasi

kuchli bazaviy + to'g'ri CV -> bitta o'zgarish -> CV -> jurnal
public LB = shovqinli, kichik test; private = hakam; CV = kompas
adversarial AUC: 0.5 -> yaxshi; >> 0.5 -> val ni testga o'xshat
ansambl: xilma-xillik muhim; foyda LB shovqinidan kichik bo'lishi mumkin
sizish: musobaqada ball, real ishda nol yoki zarar

4. Batafsil misollar

Misollar real numpy/sklearn bilan (Python 3.14). Har misol mustaqil ishlaydi; musobaqalar sintetik va har ishtirokchining haqiqiy sifati ma'lum.

Misol 1 — Public va private leaderboard: shake-up, yuborishlar soni va CV

python
"""Public va private leaderboard: ko'p yuborish, shake-up va o'z CV ga ishonish."""

import numpy as np


def rang(x):
    """Kattadan kichikka o'rin: 1 - eng yaxshi."""
    tartib = np.argsort(-x, kind="stable")
    r = np.empty(len(x), dtype=int)
    r[tartib] = np.arange(1, len(x) + 1)
    return r


def spearman(a, b):
    return float(np.corrcoef(rang(a), rang(b))[0, 1])


def haqiqiy_aniqlik(c):
    """p = c*z + shovqin (dispersiya 1.25), y = [z + N(0,1) > 0]: kutilgan aniqlik."""
    rho = c / np.sqrt((c ** 2 + 1.25) * 2.0)
    return 0.5 + np.arcsin(rho) / np.pi


def ustunlar(rng, zz, yy, c):
    """Har yuborish uchun har qatorda to'g'ri/noto'g'ri matritsasi."""
    n = len(zz)
    e = rng.standard_normal(n, dtype=np.float32)            # jamoaning umumiy xatosi
    tw = rng.standard_normal((len(c), n), dtype=np.float32)  # yuborishlar farqi
    p = c[:, None].astype(np.float32) * zz + e + 0.5 * tw
    return (p > 0) == yy


def musobaqa(seed, ulush=0.25, yuborish=20, jamoalar=50, n_test=5000, n_cv=0):
    """Sintetik musobaqa: har jamoa bir necha yuborish qiladi."""
    rng = np.random.default_rng(seed)
    z = rng.standard_normal(n_test, dtype=np.float32)
    y = z + rng.standard_normal(n_test, dtype=np.float32) > 0
    pub = np.zeros(n_test, dtype=bool)
    pub[rng.permutation(n_test)[: int(ulush * n_test)]] = True
    zc = rng.standard_normal(n_cv, dtype=np.float32)
    yc = zc + rng.standard_normal(n_cv, dtype=np.float32) > 0
    q = {k: [] for k in ["pub", "priv", "cv", "haq"]}
    for _ in range(jamoalar):
        a = rng.uniform(0.5, 1.5)                    # jamoa mahorati
        c = a + rng.normal(0, 0.15, yuborish)        # har yuborishning haqiqiy sifati
        togri = ustunlar(rng, z, y, c)
        q["pub"].append(togri[:, pub].mean(axis=1))
        q["priv"].append(togri[:, ~pub].mean(axis=1))
        if n_cv:
            q["cv"].append(ustunlar(rng, zc, yc, c).mean(axis=1))
        else:
            q["cv"].append(np.zeros(yuborish))
        q["haq"].append(haqiqiy_aniqlik(c))
    return {k: np.array(v, dtype=float) for k, v in q.items()}


def tanlov(q, mezon):
    """Har jamoa mezon bo'yicha eng yaxshi yuborishni tanlaydi."""
    j = np.arange(len(q["pub"]))
    if mezon == "birinchi":
        i = np.zeros(len(j), dtype=int)
    else:
        i = np.argmax(q[mezon], axis=1)
    return {k: v[j, i] for k, v in q.items()}


def main() -> None:
    print("=== 1. Bitta musobaqa: 50 jamoa, 5000 test qatori, public 25% ===")
    t = tanlov(musobaqa(seed=0), "pub")
    print(f"  aniqlik SE (p=0.65): public {np.sqrt(0.65 * 0.35 / 1250):.4f}, "
          f"private {np.sqrt(0.65 * 0.35 / 3750):.4f}")
    print(f"  jamoalar haqiqiy aniqligi: min {t['haq'].min():.3f}, "
          f"max {t['haq'].max():.3f}")
    rp, rq = rang(t["pub"]), rang(t["priv"])
    g = int(np.argmin(rp))
    print(f"  public 1-o'rin: public {t['pub'][g]:.4f}, private {t['priv'][g]:.4f}"
          f" -> private o'rni {rq[g]}")
    print(f"  public top-5 dan private top-5 da qolgani: "
          f"{int(np.sum((rp <= 5) & (rq <= 5)))}")
    print(f"  o'rin o'zgarishi (o'rtacha |public - private|): "
          f"{np.mean(np.abs(rp - rq)):.1f}")
    print(f"  Spearman(public, private) {spearman(t['pub'], t['priv']):.3f}, "
          f"Spearman(private, haqiqiy) {spearman(t['priv'], t['haq']):.3f}")

    print("\n=== 2. Yuborishlar soni: public bo'yicha tanlash optimizmi (8 musobaqa) ===")
    print(f"  {'yuborish':>8} {'public':>8} {'private':>8} {'optimizm':>9} {'SE':>7}")
    optimizm = {}
    for k in [1, 5, 20, 40]:
        d = []
        for s in range(8):
            t = tanlov(musobaqa(seed=100 + s, yuborish=k), "pub")
            d.append((t["pub"].mean(), t["priv"].mean()))
        d = np.array(d)
        farq = d[:, 0] - d[:, 1]
        optimizm[k] = (farq.mean(), farq.std(ddof=1) / np.sqrt(len(farq)))
        print(f"  {k:>8} {d[:, 0].mean():>8.4f} {d[:, 1].mean():>8.4f} "
              f"{farq.mean():>+9.4f} {optimizm[k][1]:>7.4f}")
    for k in [1, 40]:
        m, se = optimizm[k]
        print(f"  {k} yuborish: optimizm 2*SE dan katta: {abs(m) > 2 * se}")

    print("\n=== 3. Public ulushi: LB qanchalik ishonchli? (20 yuborish, 6 musobaqa) ===")
    print(f"  {'ulush':>6} {'public n':>9} {'optimizm':>9} {'Spearman':>9} "
          f"{'1-o_rin private da':>19}")
    for u in [0.1, 0.25, 0.5]:
        rhos, orin, opt = [], [], []
        for s in range(6):
            t = tanlov(musobaqa(seed=200 + s, ulush=u), "pub")
            rhos.append(spearman(t["pub"], t["priv"]))
            orin.append(rang(t["priv"])[int(np.argmin(rang(t["pub"])))])
            opt.append(np.mean(t["pub"] - t["priv"]))
        print(f"  {u:>6.2f} {int(u * 5000):>9} {np.mean(opt):>+9.4f} "
              f"{np.mean(rhos):>9.3f} {np.median(orin):>19.1f}")

    print("\n=== 4. Qaysi yuborishni tanlash? (haqiqiy aniqlik, 30 yuborish, 6 musobaqa) ===")
    natija = {m: [] for m in ["birinchi", "pub", "cv"]}
    for s in range(6):
        q = musobaqa(seed=300 + s, yuborish=30, n_cv=5000)
        for m in natija:
            natija[m].append(tanlov(q, m)["haq"])
    natija = {m: np.concatenate(v) for m, v in natija.items()}
    for m, nom in [("birinchi", "birinchi yuborish"), ("pub", "public LB (1250 qator)"),
                   ("cv", "o'z CV (5000 qator)")]:
        print(f"  {nom:<24} o'rtacha haqiqiy aniqlik {natija[m].mean():.4f}")
    d = natija["cv"] - natija["pub"]
    se = d.std(ddof=1) / np.sqrt(len(d))
    print(f"  CV - public: {d.mean():+.4f} (SE {se:.4f}, jamoalar {len(d)})")
    if d.mean() > 2 * se:
        print("  xulosa: kattaroq halol CV kichik public LB dan yaxshiroq tanlaydi")
    elif d.mean() < -2 * se:
        print("  xulosa: public LB bu yerda CV dan yaxshiroq tanladi")
    else:
        print("  xulosa: CV va public LB tanlovi orasida sezilarli farq yo'q")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Bitta musobaqa: 50 jamoa, 5000 test qatori, public 25% ===
  aniqlik SE (p=0.65): public 0.0135, private 0.0078
  jamoalar haqiqiy aniqligi: min 0.614, max 0.703
  public 1-o'rin: public 0.7320, private 0.7099 -> private o'rni 2
  public top-5 dan private top-5 da qolgani: 2
  o'rin o'zgarishi (o'rtacha |public - private|): 4.8
  Spearman(public, private) 0.915, Spearman(private, haqiqiy) 0.967

=== 2. Yuborishlar soni: public bo'yicha tanlash optimizmi (8 musobaqa) ===
  yuborish   public  private  optimizm      SE
         1   0.6484   0.6483   +0.0002  0.0017
         5   0.6696   0.6648   +0.0049  0.0022
        20   0.6787   0.6695   +0.0092  0.0021
        40   0.6821   0.6738   +0.0083  0.0026
  1 yuborish: optimizm 2*SE dan katta: False
  40 yuborish: optimizm 2*SE dan katta: True

=== 3. Public ulushi: LB qanchalik ishonchli? (20 yuborish, 6 musobaqa) ===
   ulush  public n  optimizm  Spearman  1-o_rin private da
    0.10       500   +0.0221     0.770                 4.0
    0.25      1250   +0.0086     0.860                 7.5
    0.50      2500   -0.0002     0.878                 7.5

=== 4. Qaysi yuborishni tanlash? (haqiqiy aniqlik, 30 yuborish, 6 musobaqa) ===
  birinchi yuborish        o'rtacha haqiqiy aniqlik 0.6485
  public LB (1250 qator)   o'rtacha haqiqiy aniqlik 0.6730
  o'z CV (5000 qator)      o'rtacha haqiqiy aniqlik 0.6756
  CV - public: +0.0027 (SE 0.0003, jamoalar 300)
  xulosa: kattaroq halol CV kichik public LB dan yaxshiroq tanlaydi

Natija tahlili.

1-bo'lim — bitta musobaqa. 50 jamoaning haqiqiy aniqligi 0.614 dan 0.703 gacha: o'rtacha qo'shni jamoalar orasidagi farq 0.002 atrofida, public ballning standart xatosi esa 0.0135 — qo'shni farqdan olti barobardan ko'proq. Shu sababli public o'rinlarning katta qismi tasodif. Bu urug'da public 1-o'rin private da 2-o'ringa tushdi (omadli holat), lekin public top-5 dan faqat 2 jamoa private top-5 da qoldi, jamoalar o'rtacha 4.8 o'ringa siljidi. Spearman(public, private) 0.915 — tartib umuman olganda saqlanadi, lekin tepadagi tor guruh ichida emas. Private (3750 qator) haqiqiy sifatni yaxshiroq aks ettiradi: Spearman 0.967.

2-bo'lim — yuborishlar soni. Bitta yuborishda public va private bir xil (+0.0002, 2*SE ichida). Jamoa har yuborishdan public bo'yicha eng yaxshisini tanlasa, optimizm paydo bo'ladi: 5 ta yuborishda +0.0049, 20 tada +0.0092, 40 tada +0.0083 (20 va 40 orasidagi farq shovqin ichida — optimizm sqrt(ln K) kabi sekin o'sadi). Ya'ni public ball tizimli ravishda private dan yuqori, va bu "tushish" private ochilganda hammani kutadi. E'tibor bering: private ham o'sdi (0.6483 dan 0.6738 gacha) — yuborishlar sifati haqiqatan har xil bo'lgani uchun tanlash foydali, lekin public uning foydasini oshirib ko'rsatadi.

3-bo'lim — public ulushi. Public test 500 qator bo'lsa, optimizm +0.0221 va Spearman 0.770; 2500 qatorda optimizm yo'qoldi (-0.0002) va Spearman 0.878 ga chiqdi. Public 1-o'rinning private dagi o'rni (mediana 4.0, 7.5, 7.5) bu 6 ta musobaqada monoton o'zgarmadi — bitta jamoaning o'rni juda shovqinli ko'rsatkich, shuning uchun xulosani o'rtacha kattaliklarga (optimizm, Spearman) qarab qilamiz.

4-bo'lim — asosiy savol: 30 ta yuborishdan qaysi birini yakuniy qilish kerak? Haqiqiy (cheksiz testdagi) aniqlik bo'yicha: birinchi yuborish 0.6485, public LB bo'yicha eng yaxshisi 0.6730, o'z CV (5000 qator) bo'yicha eng yaxshisi 0.6756. CV tanlovi public tanlovidan +0.0027 ga yaxshi (SE 0.0003, 300 jamoa) — sezilarli. Sabab oddiy: CV qatorlari public qatorlaridan to'rt barobar ko'p, shovqin ikki barobar kichik. "O'z CV ingizga ishoning" — shior emas, balki hajm va shovqin haqidagi arifmetika. Lekin shart bor: CV test bilan bir xil taqsimotdan bo'lishi kerak — keyingi misol aynan shu haqda.

Misol 2 — Adversarial validation: train va test farqi va validatsiyani testga moslash

python
"""Adversarial validation: train va test farqini topish va CV ni testga moslash."""

import numpy as np
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import StratifiedKFold, cross_val_predict

BELGILAR = ["kun", "yosh", "daromad", "summa", "muddat", "hudud"]


def yarat(rng, n, kun_lo, kun_hi):
    """Kredit arizalari: vaqt o'tishi bilan mijozlar yoshroq, summa kattaroq."""
    kun = rng.integers(kun_lo, kun_hi, n)
    t = kun / 365
    yosh = np.clip(rng.normal(44 - 14 * t, 11 - 6 * t, n), 18, 75)
    daromad = rng.lognormal(np.log(6.0), 0.5, n)            # mln so'm / oy
    summa = rng.lognormal(np.log(20.0) + 0.9 * t, 0.5, n)   # mln so'm
    muddat = rng.choice([12, 24, 36], n)
    hudud = rng.integers(0, 5, n)
    yuk = summa / muddat / daromad
    logit = (-1.8 + 1.3 * np.log(yuk / 0.14) - 0.07 * (yosh - 40)
             + 0.5 * (hudud == 3))
    y = (rng.random(n) < 1 / (1 + np.exp(-logit))).astype(int)
    X = np.column_stack([kun, yosh, daromad, summa, muddat, hudud]).astype(float)
    return X, y


def model():
    return HistGradientBoostingClassifier(max_iter=60, learning_rate=0.1,
                                          early_stopping=False, random_state=0)


def adv_auc(Xa, Xb, seed=0):
    """Train (0) va test (1) ni ajratuvchi klassifikator: OOF AUC va ehtimollar."""
    X = np.vstack([Xa, Xb])
    d = np.r_[np.zeros(len(Xa)), np.ones(len(Xb))]
    cv = StratifiedKFold(5, shuffle=True, random_state=seed)
    p = cross_val_predict(model(), X, d, cv=cv, method="predict_proba")[:, 1]
    return roc_auc_score(d, p), p[: len(Xa)]


def main() -> None:
    rng = np.random.default_rng(0)
    Xtr, ytr = yarat(rng, 5000, 0, 300)       # train: 1-300 kunlar
    Xte, yte = yarat(rng, 2000, 300, 365)     # test: keyingi 65 kun
    Xtr2, _ = yarat(rng, 2000, 0, 300)        # nazorat: xuddi shu davrdan

    print("=== 1. Adversarial AUC: train va test ajraladimi? ===")
    auc0, _ = adv_auc(Xtr, Xtr2)
    auc1, _ = adv_auc(Xtr, Xte)
    auc2, _ = adv_auc(Xtr[:, 1:], Xte[:, 1:])
    print(f"  nazorat (bir davr ichida tasodifiy)   AUC {auc0:.3f}")
    print(f"  train va test, barcha belgilar        AUC {auc1:.3f}")
    print(f"  train va test, 'kun' siz              AUC {auc2:.3f}")

    print("\n=== 2. Qaysi belgi siljigan? (bitta belgi bilan AUC) ===")
    d = np.r_[np.zeros(len(Xtr)), np.ones(len(Xte))]
    for j, nom in enumerate(BELGILAR):
        a = roc_auc_score(d, np.r_[Xtr[:, j], Xte[:, j]])
        a = max(a, 1 - a)
        belgi = " <- siljigan" if a > 0.6 else ""
        print(f"  {nom:<8} {a:.3f}{belgi}")

    print("\n=== 3. Validatsiya sxemalari: baho va haqiqiy test AUC (4 urug') ===")
    sxemalar = ["tasodifiy", "oxirgi_kun", "eng_oxshash", "adv_tanlanma"]
    xato = {s: [] for s in sxemalar}
    print(f"  {'urug':>4} {'test':>6} " + " ".join(f"{s:>12}" for s in sxemalar))
    for s in range(4):
        r = np.random.default_rng(10 + s)
        Xtr, ytr = yarat(r, 12000, 0, 300)
        Xte, yte = yarat(r, 4000, 300, 365)
        B = slice(1, None)                       # 'kun' modelga kirmaydi
        _, p_test = adv_auc(Xtr[:, B], Xte[:, B], seed=s)
        w = np.clip(p_test, 0.01, 0.99)
        w = w / (1 - w)                          # zichlik nisbati: testga o'xshashlik
        n_val = 3000
        val = {"tasodifiy": r.permutation(len(ytr))[:n_val],
               "oxirgi_kun": np.argsort(Xtr[:, 0], kind="stable")[-n_val:],
               "eng_oxshash": np.argsort(p_test, kind="stable")[-n_val:],
               "adv_tanlanma": r.choice(len(ytr), n_val, replace=False,
                                        p=w / w.sum())}
        test_auc = roc_auc_score(yte, model().fit(Xtr[:, B], ytr)
                                 .predict_proba(Xte[:, B])[:, 1])
        baholar = []
        for sx in sxemalar:
            v = np.zeros(len(ytr), dtype=bool)
            v[val[sx]] = True
            m = model().fit(Xtr[~v][:, B], ytr[~v])
            b = roc_auc_score(ytr[v], m.predict_proba(Xtr[v][:, B])[:, 1])
            baholar.append(b)
            xato[sx].append(b - test_auc)
        print(f"  {s:>4} {test_auc:>6.3f} " + " ".join(f"{b:>12.3f}" for b in baholar))

    print("\n=== 4. Xato = baho - test AUC (4 urug' bo'yicha) ===")
    print(f"  {'sxema':<13} {'o_rtacha':>9} {'SE':>6} {'|xato|':>7}")
    eng = []
    for sx in sxemalar:
        x = np.array(xato[sx])
        se = x.std(ddof=1) / np.sqrt(len(x))
        eng.append((float(np.abs(x).mean()), sx))
        siljish = "siljigan" if abs(x.mean()) > 2 * se else "siljish sezilmadi"
        print(f"  {sx:<13} {x.mean():>+9.3f} {se:>6.3f} {np.abs(x).mean():>7.3f}"
              f"  {siljish}")
    eng.sort()
    print(f"  eng kichik |xato|: {eng[0][1]} ({eng[0][0]:.3f}); "
          f"eng katta: {eng[-1][1]} ({eng[-1][0]:.3f})")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Adversarial AUC: train va test ajraladimi? ===
  nazorat (bir davr ichida tasodifiy)   AUC 0.496
  train va test, barcha belgilar        AUC 1.000
  train va test, 'kun' siz              AUC 0.797

=== 2. Qaysi belgi siljigan? (bitta belgi bilan AUC) ===
  kun      1.000 <- siljigan
  yosh     0.742 <- siljigan
  daromad  0.508
  summa    0.730 <- siljigan
  muddat   0.501
  hudud    0.501

=== 3. Validatsiya sxemalari: baho va haqiqiy test AUC (4 urug') ===
  urug   test    tasodifiy   oxirgi_kun  eng_oxshash adv_tanlanma
     0  0.755        0.783        0.771        0.732        0.760
     1  0.755        0.793        0.760        0.706        0.749
     2  0.748        0.781        0.766        0.721        0.755
     3  0.758        0.782        0.761        0.746        0.774

=== 4. Xato = baho - test AUC (4 urug' bo'yicha) ===
  sxema          o_rtacha     SE  |xato|
  tasodifiy        +0.031  0.003   0.031  siljigan
  oxirgi_kun       +0.010  0.004   0.010  siljigan
  eng_oxshash      -0.027  0.008   0.027  siljigan
  adv_tanlanma     +0.005  0.004   0.008  siljish sezilmadi
  eng kichik |xato|: adv_tanlanma 0.008-bob; eng katta: tasodifiy (0.031)

Natija tahlili.

1-bo'lim — adversarial AUC. Nazorat holatida (ikki to'plam bir davrdan) AUC 0.496 — klassifikator ularni ajrata olmaydi, xuddi shunday bo'lishi kerak. Train va test barcha belgilar bilan 1.000 bilan ajraladi — lekin bu faqat kun ustuni tufayli: train 1-300 kunlar, test 300-365. Bu "trivial" ajratuvchi: u taqsimot farqi haqida hech narsa demaydi, faqat ma'lumot vaqt bo'yicha bo'linganini ko'rsatadi. kun olib tashlangach AUC 0.797 — haqiqiy siljish bor.

2-bo'lim — qaysi belgi siljigan. Bitta belgi AUC: yosh 0.742 (mijozlar yoshroq va bir-biriga o'xshashroq bo'lib qolgan), summa 0.730 (kredit summalari kattalashgan); daromad, muddat, hudud 0.5 atrofida — o'zgarmagan. Bu tahlil ko'p o'lchovli AUC ning "nima uchun" degan savoliga javob beradi.

3-4-bo'limlar — validatsiya sxemalari. Modelni train da o'qitib, testdagi haqiqiy AUC ni (0.748-0.758) to'rt usul bilan oldindan baholaymiz:

  • tasodifiy val testni +0.031 ga oshirib baholaydi (SE 0.003) — siljigan. Test mijozlari yosh bo'yicha bir xilroq, shuning uchun modelga ularni ajratish qiyinroq; tasodifiy val buni ko'rmaydi.
  • oxirgi kunlar (vaqt bo'yicha) — +0.010, hali ham siljigan, lekin uch barobar yaqinroq: siljish vaqt bilan bog'liq bo'lgani uchun oxirgi kunlar testga qisman o'xshaydi.
  • eng testga o'xshash top-3000 — teskari tomonga -0.027 ga siljidi: bu qatorlar testdan ham "chetroq" va bir xilroq — testni ortiqcha pessimistik baholaydi. Bu kutilmagan va muhim natija: adversarial validation ni "eng o'xshashlarni olish" deb tushunish xato.
  • og'irlik bilan tanlab olish (p / (1 - p) ga proporsional) — +0.005 (SE 0.004), siljish sezilmadi, eng kichik |xato| 0.008. Bu importance sampling: val taqsimoti test taqsimotiga yaqinlashadi, na tor, na keng.

Qoida bo'yicha: siljishi sezilmagan yagona sxema — og'irlik bilan tanlash; musobaqada aynan shu val bilan CV qilinsa, public va private bilan kelishuv yaxshilanadi. Real ishda esa bu tahlil 27.12 dagi drift monitoringining o'zi: "yangi mijozlar eskilaridan farq qiladimi va model ularda qanday ishlaydi?"

Misol 3 — Kichik musobaqada ansambl: foyda, xilma-xillik va chegara

python
"""Kichik sintetik musobaqa: ansambl qachon yordam beradi va qachon yo'q."""

import numpy as np
from scipy.stats import rankdata
from sklearn.ensemble import HistGradientBoostingClassifier, RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import StratifiedKFold
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import SplineTransformer, StandardScaler


def yarat(rng, n):
    """Mijoz ketishi: chiziqli qism + nochiziqli qism + shovqinli belgilar."""
    X = rng.normal(size=(n, 10))
    logit = (0.9 * X[:, 0] - 0.7 * X[:, 1] + 0.5 * X[:, 2]
             + 1.2 * np.sin(1.5 * X[:, 3]) + 0.8 * X[:, 4] * X[:, 5]
             + 0.6 * (X[:, 6] > 0.5) - 1.0)
    y = (rng.random(n) < 1 / (1 + np.exp(-logit))).astype(int)
    return X, y


def modellar():
    return {
        "logreg": make_pipeline(SplineTransformer(n_knots=5), LogisticRegression(max_iter=2000)),
        "histgb": HistGradientBoostingClassifier(max_iter=50, learning_rate=0.1, max_leaf_nodes=15,
                                                 early_stopping=False, random_state=0),
        "rf": RandomForestClassifier(n_estimators=40, min_samples_leaf=10,
                                     random_state=0, n_jobs=1),
        "knn": make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighbors=50)),
        "histgb_b": HistGradientBoostingClassifier(max_iter=50, learning_rate=0.1, max_leaf_nodes=15,
                                                   max_features=0.6, early_stopping=False,
                                                   random_state=1),
        "histgb_c": HistGradientBoostingClassifier(max_iter=50, learning_rate=0.1, max_leaf_nodes=15,
                                                   max_features=0.6, early_stopping=False,
                                                   random_state=2),
    }


def oof_va_test(X, y, Xt, seed):
    """Har model uchun OOF (train) va test bashoratlari."""
    cv = StratifiedKFold(3, shuffle=True, random_state=seed)
    oof, test = {}, {}
    for nom, m in modellar().items():
        o = np.zeros(len(y))
        for a, b in cv.split(X, y):
            o[b] = m.fit(X[a], y[a]).predict_proba(X[b])[:, 1]
        oof[nom] = o
        test[nom] = m.fit(X, y).predict_proba(Xt)[:, 1]
    return oof, test


def tez_auc(y, p):
    """Mann-Whitney orqali AUC (bootstrap uchun tez)."""
    r = rankdata(p)
    n1 = y.sum()
    return (r[y == 1].sum() - n1 * (n1 + 1) / 2) / (n1 * (len(y) - n1))


def logit(p):
    p = np.clip(p, 1e-4, 1 - 1e-4)
    return np.log(p / (1 - p))


def ansambllar(oof, test, y):
    """Ansambl bashoratlari (test uchun); vaznlar va stacking faqat OOF da o'rganiladi."""
    xilma = ["logreg", "histgb", "rf", "knn"]
    natija = {}
    natija["o_rtacha (4 xil)"] = np.mean([test[k] for k in xilma], axis=0)
    natija["rang o_rtacha"] = np.mean([rankdata(test[k]) for k in xilma], axis=0)
    natija["3 ta histgb"] = np.mean([test[k] for k in ["histgb", "histgb_b", "histgb_c"]],
                                    axis=0)
    Z = np.column_stack([logit(oof[k]) for k in xilma])
    Zt = np.column_stack([logit(test[k]) for k in xilma])
    st = LogisticRegression(C=1.0, max_iter=1000).fit(Z, y)
    natija["stacking (logreg)"] = st.predict_proba(Zt)[:, 1]
    return natija, st.coef_[0]


def main() -> None:
    urug = range(4)
    yakka = ["logreg", "histgb", "rf", "knn"]
    qator = {}
    print("=== 1. Bitta musobaqa (urug' 0): OOF va private AUC ===")
    for s in urug:
        rng = np.random.default_rng(s)
        X, y = yarat(rng, 2000)
        Xt, yt = yarat(rng, 4000)
        oof, test = oof_va_test(X, y, Xt, s)
        ens, koef = ansambllar(oof, test, y)
        oof_auc = {k: roc_auc_score(y, oof[k]) for k in oof}
        eng = max(yakka, key=lambda k: oof_auc[k])       # OOF bo'yicha tanlangan
        qator.setdefault("eng yaxshi yakka (OOF)", []).append(roc_auc_score(yt, test[eng]))
        for k in yakka + ["histgb_b"]:
            qator.setdefault(k, []).append(roc_auc_score(yt, test[k]))
        for k, p in ens.items():
            qator.setdefault(k, []).append(roc_auc_score(yt, p))
        if s == 0:
            for k in yakka + ["histgb_b", "histgb_c"]:
                print(f"  {k:<10} OOF {oof_auc[k]:.4f}  private "
                      f"{roc_auc_score(yt, test[k]):.4f}")
            print(f"  OOF bo'yicha eng yaxshi yakka model: {eng}")
            print("  OOF korrelyatsiyalari:")
            juft = [("logreg", "histgb"), ("histgb", "rf"), ("rf", "knn"),
                    ("histgb", "histgb_b"), ("histgb_b", "histgb_c")]
            for a, b in juft:
                print(f"    {a:<8} ~ {b:<8} {np.corrcoef(oof[a], oof[b])[0, 1]:.3f}")
            print("  stacking vaznlari: " + ", ".join(
                f"{k} {c:+.2f}" for k, c in zip(yakka, koef)))
            r2 = np.random.default_rng(99)
            pe = ens["o_rtacha (4 xil)"]
            bs = []
            for _ in range(300):
                i = r2.choice(len(yt), 1000, replace=False)
                bs.append(tez_auc(yt[i], pe[i]))
            se_pub = float(np.std(bs, ddof=1))
            print(f"  1000 qatorli public LB da AUC ning SE si: {se_pub:.4f}")

    print("\n=== 2. 4 musobaqa: private AUC o'rtachasi ===")
    for k, v in qator.items():
        print(f"  {k:<24} {np.mean(v):.4f}")

    print("\n=== 3. Juftlashgan farqlar (4 musobaqa) ===")
    juftlar = [("o_rtacha (4 xil)", "eng yaxshi yakka (OOF)"),
               ("rang o_rtacha", "eng yaxshi yakka (OOF)"),
               ("stacking (logreg)", "eng yaxshi yakka (OOF)"),
               ("3 ta histgb", "histgb"),
               ("stacking (logreg)", "o_rtacha (4 xil)")]
    for a, b in juftlar:
        d = np.array(qator[a]) - np.array(qator[b])
        se = d.std(ddof=1) / np.sqrt(len(d))
        holat = ("sezilarli yaxshi" if d.mean() > 2 * se else
                 "sezilarli yomon" if d.mean() < -2 * se else "farq sezilmadi")
        print(f"  {a:<18} - {b:<22} {d.mean():+.4f} (SE {se:.4f})  {holat}")
    d = np.array(qator["stacking (logreg)"]) - np.array(qator["o_rtacha (4 xil)"])
    if abs(d.mean()) <= 2 * d.std(ddof=1) / np.sqrt(len(d)):
        print("  qaror: oddiy o'rtacha - stackingdan sezilarli yomon emas va soddaroq")
    else:
        print("  qaror: stacking o'rtachadan sezilarli yaxshi")
    print(f"  stacking foydasi ({d.mean():.4f}) public LB shovqinidan (SE {se_pub:.4f}) "
          f"kichik: {d.mean() < se_pub}")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Bitta musobaqa (urug' 0): OOF va private AUC ===
  logreg     OOF 0.7809  private 0.7970
  histgb     OOF 0.7750  private 0.7990
  rf         OOF 0.7658  private 0.7898
  knn        OOF 0.7735  private 0.7885
  histgb_b   OOF 0.7704  private 0.7954
  histgb_c   OOF 0.7748  private 0.8019
  OOF bo'yicha eng yaxshi yakka model: logreg
  OOF korrelyatsiyalari:
    logreg   ~ histgb   0.881
    histgb   ~ rf       0.904
    rf       ~ knn      0.840
    histgb   ~ histgb_b 0.960
    histgb_b ~ histgb_c 0.963
  stacking vaznlari: logreg +0.43, histgb +0.32, rf -0.12, knn +0.75
  1000 qatorli public LB da AUC ning SE si: 0.0121

=== 2. 4 musobaqa: private AUC o'rtachasi ===
  eng yaxshi yakka (OOF)   0.7886
  logreg                   0.7886
  histgb                   0.7905
  rf                       0.7806
  knn                      0.7856
  histgb_b                 0.7886
  o_rtacha (4 xil)         0.7993
  rang o_rtacha            0.8000
  3 ta histgb              0.7930
  stacking (logreg)        0.8012

=== 3. Juftlashgan farqlar (4 musobaqa) ===
  o_rtacha (4 xil)   - eng yaxshi yakka (OOF) +0.0107 (SE 0.0012)  sezilarli yaxshi
  rang o_rtacha      - eng yaxshi yakka (OOF) +0.0114 (SE 0.0014)  sezilarli yaxshi
  stacking (logreg)  - eng yaxshi yakka (OOF) +0.0126 (SE 0.0009)  sezilarli yaxshi
  3 ta histgb        - histgb                 +0.0025 (SE 0.0008)  sezilarli yaxshi
  stacking (logreg)  - o_rtacha (4 xil)       +0.0019 (SE 0.0004)  sezilarli yaxshi
  qaror: stacking o'rtachadan sezilarli yaxshi
  stacking foydasi 0.0019-bob public LB shovqinidan (SE 0.0121) kichik: True

Natija tahlili.

1-bo'lim — bitta musobaqa. To'rt xil model OOF da 0.766-0.781 oralig'ida, OOF bo'yicha eng yaxshisi logreg (splayn belgilar bilan). Private AUC OOF dan yuqori (0.797 va 0.781) — chunki yakuniy model butun 2000 qatorda, OOF modellari esa 3-fold da faqat 1333 qatorda o'qitilgan. Bu musobaqada odatiy: OOF biroz pessimistik. OOF korrelyatsiyalari: turli oilalar orasida 0.840-0.904, bir xil histgb ning boshqa urug'li variantlari orasida 0.960-0.963 — ular deyarli bir xil xato qiladi. Stacking vaznlari qiziq: rf manfiy vazn oldi (-0.12) — u histgb bilan kuchli korrelyatsiyalangan va meta-model uni "tuzatuvchi" sifatida ishlatdi; bunday vaznlarni talqin qilish qiyin.

2-3-bo'limlar — 4 musobaqa, juftlashgan. Xilma-xil to'rt modelning oddiy o'rtachasi eng yaxshi yakka modeldan (OOF bo'yicha tanlangan — halol taqqoslash) +0.0107 (SE 0.0012) yaxshi, rang o'rtacha +0.0114, stacking +0.0126. Uchta histgb o'rtachasi esa bitta histgb dan atigi +0.0025 yaxshi — sezilarli, lekin xilma-xil ansambl foydasining to'rtdan biri. Foydani modellar soni emas, xatolar korrelyatsiyasi belgilaydi.

Stacking oddiy o'rtachadan +0.0019 (SE 0.0004) yaxshi — qoida bo'yicha sezilarli, musobaqada uni tanlash asosli. Ammo bu foyda 1000 qatorli public LB dagi AUC shovqinidan (SE 0.0121) olti barobar kichik: public LB da stacking va o'rtacha orasidagi farqni ko'rib bo'lmaydi, uni faqat butun train dagi OOF CV ko'rsatadi. Real ishda esa savol boshqacha: to'rt modelni ishlab chiqarishda saqlash (kechikish, xotira, monitoring, tushuntirish) +0.013 AUC ga arziydimi? Ko'pincha javob — bitta yaxshi model yoki ikki xilma-xil modelning o'rtachasi.

Misol 4 — Sizish: musobaqada g'alaba, real oqimda qulash va detektor

python
"""Sizish (leak): musobaqada g'alaba, real ishda qulash va uni oldindan aniqlash."""

import numpy as np
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import StratifiedKFold, cross_val_score

HALOL = ["daromad", "yosh", "yuk", "kechikish_tarixi", "staj"]
SIZISH = ["ariza_id", "undiruv_qongiroq"]
BARCHA = HALOL + SIZISH


def asosiy(rng, n):
    """Kredit arizasi belgilari va haqiqiy qaytarmaslik (default)."""
    daromad = rng.lognormal(np.log(6), 0.5, n)
    yosh = np.clip(rng.normal(38, 10, n), 18, 70)
    yuk = rng.beta(2, 5, n)
    kech = rng.poisson(0.6, n)
    staj = rng.exponential(5, n)
    logit = (-2.0 + 3.0 * (yuk - 0.3) + 0.6 * kech - 0.3 * np.log(daromad / 6)
             - 0.03 * (yosh - 38) - 0.05 * staj)
    y = (rng.random(n) < 1 / (1 + np.exp(-logit))).astype(int)
    return np.column_stack([daromad, yosh, yuk, kech, staj]), y


def musobaqa_toplami(rng, n):
    """Musobaqa fayli: defaultlar alohida tizimdan keyin qo'shilgan (katta ID),
    undiruv qo'ng'iroqlari esa default BO'LGANDAN KEYIN yozilgan."""
    X, y = asosiy(rng, n)
    kech_kelgan = y * (rng.random(n) < 0.9)          # 90% defaultlar keyin qo'shilgan
    tartib = np.lexsort((rng.random(n), kech_kelgan))
    ariza_id = np.empty(n)
    ariza_id[tartib] = 100000 + 3 * np.arange(n) + rng.integers(0, 3, n)
    qongiroq = rng.poisson(np.where(y == 1, 2.5, 0.3))
    return np.column_stack([X, ariza_id, qongiroq]), y


def real_oqim(rng, n):
    """Ishlab chiqarish: ID vaqt bo'yicha beriladi, qo'ng'iroqlar hali yo'q."""
    X, y = asosiy(rng, n)
    return np.column_stack([X, np.arange(n) * 1.0, np.zeros(n)]), y


def model():
    return HistGradientBoostingClassifier(max_iter=80, max_leaf_nodes=15,
                                          early_stopping=False, random_state=0)


def main() -> None:
    X, y = musobaqa_toplami(np.random.default_rng(0), 6000)
    p = np.random.default_rng(1).permutation(6000)    # train/test tasodifiy bo'linadi
    X, y = X[p], y[p]
    tr, te = np.arange(4000), np.arange(4000, 6000)
    Xp, yp = real_oqim(np.random.default_rng(2), 4000)

    print("=== 1. Sizish detektori: har belgi yolg'iz qanchalik bashorat qiladi? ===")
    print(f"  {'belgi':<18} {'AUC':>6}  izoh")
    for j, nom in enumerate(BARCHA):
        a = roc_auc_score(y[tr], X[tr, j])
        a = max(a, 1 - a)
        noyob = len(np.unique(X[tr, j])) / len(tr)
        izoh = []
        if a > 0.8:
            izoh.append("SHUBHALI: yolg'iz o'zi juda kuchli")
        if noyob > 0.95 and np.allclose(X[tr, j], np.round(X[tr, j])):
            izoh.append("ID ga o'xshash (butun, deyarli noyob)")
        print(f"  {nom:<18} {a:>6.3f}  {'; '.join(izoh)}".rstrip())

    print("\n=== 2. Uch baho: CV, musobaqa testi, real oqim ===")
    cv = StratifiedKFold(5, shuffle=True, random_state=0)
    print(f"  {'model':<24} {'CV AUC':>7} {'test AUC':>9} {'real AUC':>9}")
    natija = {}
    for nom, ust in [("halol", HALOL), ("sizish bilan", BARCHA),
                     ("faqat ariza_id", ["ariza_id"])]:
        j = [BARCHA.index(u) for u in ust]
        c = cross_val_score(model(), X[tr][:, j], y[tr], cv=cv, scoring="roc_auc").mean()
        m = model().fit(X[tr][:, j], y[tr])
        t = roc_auc_score(y[te], m.predict_proba(X[te][:, j])[:, 1])
        r = roc_auc_score(yp, m.predict_proba(Xp[:, j])[:, 1])
        natija[nom] = (c, t, r)
        print(f"  {nom:<24} {c:>7.3f} {t:>9.3f} {r:>9.3f}")

    print("\n=== 3. 5 urug': sizish bilan - halol (juftlashgan) ===")
    dt, dr = [], []
    for s in range(5):
        X, y = musobaqa_toplami(np.random.default_rng(10 + s), 6000)
        p = np.random.default_rng(20 + s).permutation(6000)
        X, y = X[p], y[p]
        Xp, yp = real_oqim(np.random.default_rng(30 + s), 4000)
        jh = [BARCHA.index(u) for u in HALOL]
        mh = model().fit(X[tr][:, jh], y[tr])
        ms = model().fit(X[tr], y[tr])
        dt.append(roc_auc_score(y[te], ms.predict_proba(X[te])[:, 1])
                  - roc_auc_score(y[te], mh.predict_proba(X[te][:, jh])[:, 1]))
        dr.append(roc_auc_score(yp, ms.predict_proba(Xp)[:, 1])
                  - roc_auc_score(yp, mh.predict_proba(Xp[:, jh])[:, 1]))
    for nom, d in [("musobaqa testi", dt), ("real oqim", dr)]:
        d = np.array(d)
        se = d.std(ddof=1) / np.sqrt(len(d))
        yon = ("sizish YUTADI" if d.mean() > 2 * se else
               "sizish YUTQAZADI" if d.mean() < -2 * se else "farq sezilmadi")
        print(f"  {nom:<15} {d.mean():+.3f} (SE {se:.3f})  {yon}")
    h, s_ = natija["halol"], natija["sizish bilan"]
    print(f"  urug' 0: test da sizish +{s_[1] - h[1]:.3f} beradi, real oqimda "
          f"{s_[2] - h[2]:+.3f}")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Sizish detektori: har belgi yolg'iz qanchalik bashorat qiladi? ===
  belgi                 AUC  izoh
  daromad             0.554
  yosh                0.576
  yuk                 0.647
  kechikish_tarixi    0.615
  staj                0.552
  ariza_id            0.952  SHUBHALI: yolg'iz o'zi juda kuchli; ID ga o'xshash (butun, deyarli noyob)
  undiruv_qongiroq    0.921  SHUBHALI: yolg'iz o'zi juda kuchli

=== 2. Uch baho: CV, musobaqa testi, real oqim ===
  model                     CV AUC  test AUC  real AUC
  halol                      0.682     0.678     0.690
  sizish bilan               0.993     0.990     0.585
  faqat ariza_id             0.946     0.949     0.500

=== 3. 5 urug': sizish bilan - halol (juftlashgan) ===
  musobaqa testi  +0.307 (SE 0.006)  sizish YUTADI
  real oqim       -0.101 (SE 0.026)  sizish YUTQAZADI
  urug' 0: test da sizish +0.312 beradi, real oqimda -0.105

Natija tahlili.

1-bo'lim — sizish detektori. Halol belgilar yolg'iz 0.55-0.65 AUC beradi — kredit riskida odatiy. Ikki belgi keskin ajralib turadi: ariza_id 0.952 (va butun, deyarli noyob — ID belgisi) va undiruv_qongiroq 0.921. Ikkalasi ham "juda yaxshi, haqiqat bo'lishi uchun" — birinchi savol: bu qiymat bashorat paytida mavjudmi va nishondan oldin yozilganmi? ID — defaultlar alohida tizimdan keyinroq qo'shilgani uchun katta raqam olgan; undiruv qo'ng'iroqlari — default sodir bo'lgandan keyin yozilgan.

2-bo'lim — uch baho. Halol model: CV 0.682, test 0.678, real oqim 0.690 — uchala baho bir-biriga mos, bu ishonchli modelning belgisi. Sizishli model: CV 0.993, musobaqa testi 0.990 — leaderboard cho'qqisi. Real oqimda (ID vaqt bo'yicha beriladi, qo'ng'iroqlar hali yo'q) esa 0.585 — halol modeldan ancha yomon. Faqat ariza_id dan iborat model real oqimda aniq 0.500 — tanga tashlashdan farqi yo'q.

3-bo'lim — 5 urug' bo'yicha juftlashgan farq: musobaqa testida sizish +0.307 (SE 0.006) yutadi, real oqimda -0.101 (SE 0.026) yutqazadi. Real oqimda sizishli model hatto halol modeldan ham yomon, chunki u o'qitishda halol belgilardan kam o'rgangan — asosiy ishni sizish bajargan edi. Sizish qiymati real ishda nol emas, manfiy. Musobaqada sizishni topish — ma'lumotni chuqur tushunish belgisi, lekin portfolio va intervyu uchun qimmatli bo'lgani — uni aniqlash va tushuntirish, undan foydalanish emas.


5. To'g'ri va noto'g'ri tushunishlar

Noto'g'ri fikr To'g'risi
"Public LB — modelning haqiqiy sifati" Public — kichik testdagi shovqinli baho; 1-misolda public 1-o'rin private da boshqa o'ringa tushdi
"Ko'proq yuborish — yaxshiroq natija" Public bo'yicha tanlash optimizmni oshiradi (1-misol, 2-bo'lim)
"CV — faqat odatiy 5-fold" CV test qanday ajratilgan bo'lsa, shunday quriladi (vaqt, guruh)
"Adversarial AUC yuqori — musobaqa buzilgan" Farq qayerda ekanini ko'rsatadi; ID/sana ni olib tashlab, val ni moslash kerak
"Eng testga o'xshash qatorlar — eng yaxshi val" Top-k juda tor bo'lishi mumkin; og'irlik bilan tanlash yaxshiroq (2-misol)
"Ansambl har doim katta foyda beradi" O'xshash modellarda foyda kichik; foyda xilma-xillikka bog'liq (3-misol)
"Stacking o'rtachadan har doim ancha yaxshi" Farq ko'pincha kichik; murakkablik narxini hisoblang
"Leak topish — mahorat, uni ishlatish kerak" Real ishda sizishga tayangan model halol modeldan yomon (4-misol)
"Kaggle medali — ishga kafolat" Ish beruvchi uchun nima o'rganganingiz va buni tushuntirishingiz muhim

6. Keng tarqalgan xatolar va yechimlari

1. Public LB bo'yicha yakuniy tanlov

python
yakuniy = max(yuborishlar, key=lambda s: s.public)                  # ⚠️
yakuniy = max(yuborishlar, key=lambda s: s.cv)                       # ✅ + xilma-xil ikkinchi

2. CV test ajratilishiga mos emas

python
cv = KFold(5, shuffle=True)            # test - keyingi oylar            # ⚠️
cv = TimeSeriesSplit(5)                # yoki guruh bo'yicha GroupKFold  # ✅

3. Trivial ajratuvchi modelda qoladi

python
X = df[["id", "sana_raqam", "yosh", "summa"]]                         # ⚠️
X = df[["yosh", "summa"]]      # adversarial AUC bilan tekshirilgan    # ✅

4. Stacking test bashoratida o'qitiladi

python
meta.fit(np.column_stack([m.predict_proba(X_test)[:, 1] for m in ms]), y_test)  # ⚠️
meta.fit(np.column_stack([oof[k] for k in ms]), y_train)                        # ✅

5. O'xshash modellar ansambli

python
ens = np.mean([lgbm(seed=s).predict(X) for s in range(10)], axis=0)  # ⚠️ kichik foyda
ens = np.mean([p_logreg, p_histgb, p_knn], axis=0)                   # ✅ OOF korrelyatsiyasi past

6. Sizish real ishga ko'chadi

python
model.fit(df[halol + ["ariza_id", "undiruv_qongiroq"]], y)          # ⚠️
# har belgi uchun: "bashorat paytida bu qiymat mavjudmi?"            # ✅

7. Token repoda

python
# kaggle.json loyiha papkasida va commit qilingan                   # ⚠️
# ~/.kaggle/kaggle.json (chmod 600) yoki muhit o'zgaruvchisi         # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 15.12-dars (o'tilgan): Voting va stacking — OOF bashoratlar, meta-model, leakage
  • 17.8-dars (o'tilgan): Leakage — sizishning umumiy nazariyasi
  • 18.2, 18.11-darslar (o'tilgan): CV turlari va validatsiyaga overfitting — public LB ga moslashishning nazariy asosi
  • 27.4-dars (o'tilgan): Eksperiment kuzatuvi — yuborishlar jurnali
  • 27.12-dars (o'tilgan): Drift aniqlash — domen klassifikatori (adversarial validation)
  • 29.6-dars: GitHub va loyiha tuzilishi — musobaqa kodini toza repo ga aylantirish
  • 29.7-dars: Portfolio — writeup va musobaqa loyihasini portfolio ga qo'shish
  • 29.10-dars: Intervyu — "CV va test natijangiz farq qilsa nima qilasiz?" kabi savollar

8. Eng yaxshi amaliyotlar

  1. Birinchi kuni: qoidalar, metrika, CV sxemasi va kuchli bazaviy yuborish.

  2. CV ni test qanday ajratilgan bo'lsa, shunday quring; adversarial validation bilan tekshiring.

  3. Har yuborish uchun jurnal: o'zgarish, CV o'rtacha va std, public LB.

  4. Public LB dagi o'zgarishni CV tasdiqlamasa — shovqin deb hisoblang.

  5. Ansamblga xilma-xil modellar; vaznlar va meta-model faqat OOF da.

  6. Har belgiga "bashorat paytida mavjudmi?" savoli; bitta belgi AUC ni tekshiring.

  7. Yakuniy tanlov: CV bo'yicha eng yaxshi + xilma-xil ikkinchi variant.

  8. Musobaqadan keyin — writeup va toza repo; g'oliblar yechimini o'qing.


9. Amaliy topshiriq

Vazifa 1: Bashorat qiling

python
1.  # public test 1250 qator, aniqlik ~0.65: public ballning SE si?
2.  # 40 ta yuborishdan public bo'yicha eng yaxshisi: public va private qaysi biri katta?
3.  # public ulushi 10% dan 50% ga oshsa, Spearman(public, private)?
4.  # CV (5000 qator) va public LB (1250 qator) bo'yicha tanlov - qaysi biri yaxshiroq?
5.  # adversarial AUC 1.000 - birinchi nima qilasiz?
6.  # val = train dagi "eng testga o'xshash" top-3000 qator - baho testdan yuqorimi, pastmi?
7.  # 4 xil model o'rtachasi va 3 ta bir xil histgb o'rtachasi - qaysi biri ko'proq foyda?
8.  # stacking o'rtachadan 0.0019 yaxshi, public LB SE 0.012 - buni LB da ko'rasizmi?
9.  # ariza_id yolg'iz AUC 0.95 - bu nimani anglatadi?
10. # sizishli model real oqimda halol modeldan yaxshimi?
Javoblar
  1. sqrt(0.65 * 0.35 / 1250) ~ 0.0135 (1-misol)
  2. Public katta: tanlash optimizmi (1-misolda 40 yuborishda +0.0083)
  3. Oshadi (0.770 dan 0.878 gacha)
  4. CV: haqiqiy aniqlik 0.6756 va 0.6730, farq +0.0027 (SE 0.0003)
  5. Trivial ajratuvchini (ID, sana) topib olib tashlash va AUC ni qayta hisoblash
  6. Pastroq — ortiqcha pessimistik (-0.027); og'irlik bilan tanlash yaxshiroq
  7. Xilma-xil: +0.0107 va +0.0025
  8. Yo'q — farq LB shovqinidan olti barobar kichik; faqat CV da ko'rinadi
  9. Ma'lumot nishon bo'yicha tartiblangan holda raqamlangan — sizish
  10. Yo'q: real oqimda 0.585 va 0.690

Vazifa 2: Xatolarni tuzating

python
1.  eng = max(yuborishlar, key=lambda s: s.public_lb)

2.  cv = KFold(5, shuffle=True, random_state=0)   # test - keyingi chorak

3.  meta = LogisticRegression().fit(P_test, y_test)

4.  X = df.drop(columns=["target"])               # "id", "yaratilgan_sana" ham bor

5.  ens = (p_lgbm_seed1 + p_lgbm_seed2 + p_lgbm_seed3) / 3   # "xilma-xil ansambl"
Javoblar
python
1.  eng = max(yuborishlar, key=lambda s: s.cv)   # + ikkinchi: xilma-xil, CV si yaqin

2.  cv = TimeSeriesSplit(5)                       # vaqt bo'yicha, test kabi

3.  meta = LogisticRegression().fit(OOF, y_train) # OOF bashoratlarda

4.  X = df.drop(columns=["target", "id", "yaratilgan_sana"])
    # + adversarial AUC va bitta belgi AUC tekshiruvi

5.  ens = (p_logreg + p_histgb + p_knn) / 3       # OOF korrelyatsiyasi past modellar

Vazifa 3: Leaderboard simulyatsiyasi

Modellang (1-misol asosida):

  1. Jamoalar mahorati oralig'ini toraytiring (a 0.9-1.1) — shake-up qanday o'zgaradi?
  2. Metrikani aniqlikdan log-loss ga almashtiring (ehtimol sigmoid(p)) — optimizm kattaligi?
  3. Har jamoa ikkita yakuniy yuborish tanlaydi: CV bo'yicha eng yaxshi va public bo'yicha eng yaxshi — private da ikkalasidan yaxshisi hisoblanadi. Bu strategiya faqat CV dan qanchalik yaxshi?
  4. sigma * sqrt(2 ln K) formulasini mustaqil yuborishlar (jamoa xatosi e siz) bilan tekshiring

Vazifa 4: Adversarial validation

Modellang (2-misol asosida):

  1. summa o'rniga summa / daromad belgisini ishlating — adversarial AUC va test AUC qanday o'zgaradi?
  2. adv_tanlanma da og'irlikni (p / (1 - p)) ** 0.5 qiling (yumshatilgan) — xato va dispersiya?
  3. Siljish yo'q holatda (test ham 0-300 kunlardan) to'rt sxemani solishtiring — hammasi teng bo'lishi kerakmi?

Vazifa 5: Ansambl

Modellang (3-misol asosida):

  1. OOF da optimal vaznli o'rtacha (0.1 qadamli to'r) — oddiy o'rtachadan sezilarli yaxshimi?
  2. Juda zaif modelni (masalan, bitta belgili logreg) o'rtachaga qo'shing — ansambl yomonlashadimi?
  3. Stacking meta-modelini test bashoratida o'qiting (xato!) — test AUC qanchaga "oshadi" va bu nega aldov?

Vazifa 6: Sizish

Modellang (4-misol asosida):

  1. Sizishni kuchsizlantiring (defaultlarning 30% i keyin qo'shilgan) — detektor uni hali ham ushlaydimi?
  2. "Takroriy mijoz" sizishi: bir mijozning bir nechta arizasi train va testda — GroupKFold bilan CV qanday o'zgaradi?
  3. Detektorga "belgi nishondan keyin yozilganmi?" degan qo'lda to'ldiriladigan metama'lumot ustunini qo'shing

Vazifa 7: O'ylash

Jamoadoshingiz: "Public LB da 3-o'rindamiz. Mening oxirgi 15 yuborishim har safar public ni oshirdi, CV esa deyarli o'zgarmadi — CV bizning kichik train da noto'g'ri ishlaydi, shekilli. Yakuniy ikkita yuborish sifatida public dagi eng yaxshi ikkitasini tanlaymiz. Yana: forumda kimdir 'sana ustuni test nishonini bashorat qiladi' deb yozibdi — keling, uni ham qo'shamiz, hamma qo'shyapti."

Javob

Qisqa javob: yakuniy tanlovni public LB ga emas, CV ga asoslaymiz; sana ustunini esa qo'shishdan oldin tekshiramiz — u sizish bo'lishi mumkin va shake-up da bizni pastga tortadi.

1. 15 ta yuborish public ni oshirdi, CV o'zgarmadi. Bu klassik public ga moslashish belgisi. 1-misolda 20-40 ta yuborishdan public bo'yicha tanlash optimizmi +0.008-0.009 edi — bu ko'pincha qo'shni o'rinlar orasidagi farqdan o'n barobar katta. Public test kichik (masalan 1250 qator, SE ~0.0135) — undagi har bir "yaxshilanish" asosan shovqin.

2. "CV noto'g'ri ishlaydi" degan da'voni tekshirish kerak.

python
# 1) CV std: foldlar orasidagi tarqoqlik qanchalik katta?
# 2) CV va public jadvali: oldingi versiyalarda ular bir yo'nalishda edimi?
# 3) adversarial validation: train va test farq qiladimi? (2-misol)
#    - farq bo'lsa, val ni og'irlik bilan tanlab olib CV ni qayta quramiz

3. Sana ustuni. Forumdagi "sana nishonni bashorat qiladi" — sizish belgisi bo'lishi mumkin (4-misol: ariza_id musobaqa testida +0.31 berdi). Ikki savol: bu sizish private qismda ham ishlaydimi (ko'pincha ha, lekin ba'zan faqat public da), va qoidalar buni ruxsat etadimi. Hatto ruxsat bo'lsa ham — writeup da buni aniq yozamiz va uni real ishga ko'chirib bo'lmasligini tushuntiramiz.

4. Taklif.

  • Yakuniy 1-yuborish: CV bo'yicha eng yaxshi.
  • Yakuniy 2-yuborish: CV si yaqin, lekin xilma-xil ansambl (3-misol: foyda xilma-xillikdan keladi).
  • Sana ustunini adversarial va bitta belgi AUC bilan tekshiramiz; qoidani o'qiymiz.

Jamoadoshga javob: "Public LB dagi o'sishni CV tasdiqlamayapti — bu kichik testga moslashish. Oxirida private ochilganda bu o'sish yo'qolishi ehtimoli katta. Keling, CV ni adversarial validation bilan tekshiramiz va yakuniy tanlovni unga asoslaymiz. Sana ustunini esa avval tushunaylik: agar u sizish bo'lsa, bu bizga hech narsa o'rgatmaydi va writeup da buni halol yozishimiz kerak."

Nimani mustahkamlaydi: 2.2, 2.3, 2.4, 2.5, 2.7, 2.8-bo'limlar.


Xulosa

Bu darsda musobaqa mexanikasini va undan to'g'ri saboq olishni o'rgandik.

Eng muhim uch fikr:

  1. Public LB — shovqinli va kichik; CV — kompas. 1-misolda public ballning SE si (0.0135) jamoalar orasidagi farqdan ko'p barobar katta edi; public bo'yicha tanlash 20-40 yuborishda +0.008-0.009 optimizm berdi, public 10% bo'lsa +0.022. Katta va halol CV bo'yicha tanlov haqiqiy sifatda public tanlovidan sezilarli yaxshi chiqdi (+0.0027, SE 0.0003).

  2. CV testga o'xshashi kerak — adversarial validation buni tekshiradi. 2-misolda kun trivial ajratuvchisi olib tashlangach AUC 0.797 qoldi; tasodifiy val testni +0.031 ga oshirib baholadi, "eng o'xshash" top-k esa -0.027 ga pasaytirdi, og'irlik bilan tanlash esa siljishsiz edi (+0.005).

  3. Musobaqa texnikalari — foydali, lekin chegarasi bor. 3-misolda xilma-xil ansambl eng yaxshi yakka modeldan +0.011 yaxshi, o'xshash modellar esa atigi +0.0025; stacking ning o'rtachadan ustunligi (+0.0019) public LB shovqinidan olti barobar kichik. 4-misolda sizish musobaqa testida +0.307 berdi, real oqimda esa -0.101 — halol modeldan yomon. Kaggle dan real ishga CV intizomi, belgilar, xato tahlili va drift sezgisi ko'chadi; 0.001 uchun kurash va sizish — yo'q.

Keyingi darsda GitHub va loyiha tuzilishi: ma'lumotlar fani loyihasining repo tuzilishi (data, notebooks, src, tests, configs), git ga nima kiradi va nima kirmaydi, README anatomiyasi, notebookdan modulga o'tish, testlar va CI — va bularning hammasini avtomatik "repo auditori" bilan tekshirish.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
29.5-dars: Kaggle va musobaqalar — IlmHamroh