IlmHamroh
Data Science va sun'iy intellekt/Model baholash sozlash11/12-dars21 daqiqa
Mundarija (22)

18.11-dars: Validatsiyaga overfitting

18-QISM — MODEL BAHOLASH VA SOZLASH · 11-dars


1. Kirish va motivatsiya

Modelning o'quv ma'lumotiga overfitting qilishini 12.4-darsda ko'rgandik. Ammo yanada xavfliroq va ancha kamroq gapiriladigan hodisa bor: siz validatsiya to'plamiga overfitting qilasiz.

Mexanizm oddiy. Har safar CV natijasiga qarab qaror qabul qilganingizda — belgi qo'shganingizda, model almashtirganingizda, giperparametrni o'zgartirganingizda — siz o'sha CV to'plamidan ma'lumot olasiz va uni modelga qo'lingiz orqali o'tkazasiz. Yuz marta shunday qilsangiz, CV bahosi endi yangi ma'lumotning emas, sizning tajribalaringiz tarixining aksi bo'ladi.

Bu gradient siz optimallashtirish: siz o'zingiz optimizator bo'lasiz, yo'qotish funksiyasi esa CV balli.

Bu darsda: mexanizm, tajribalar soni va optimizm bog'liqligi, buni sezish usullari, himoya strategiyalari (yopiq test, adaptiv holdout, oldindan ro'yxatdan o'tkazish) va Kaggle tajribasidan darslar.

Real vaziyat. Musobaqa jamoasi 90 kun davomida kuniga 5 martadan ommaviy tablitsaga yuborish qildi — jami 450 ta. Ommaviy ball 0.9412 gacha o'sdi. Yashirin tablitsada 0.9203 chiqdi va jamoa 12-o'rindan 340-o'ringa tushdi. 450 ta qaror ommaviy to'plamni validatsiya to'plamiga, undan keyin esa o'quv to'plamiga aylantirgan edi.

Bu darsda validatsiyaga overfitting ni o'rganamiz.

Bu darsda:

  • Mexanizm
  • Tajribalar soni va optimizm
  • Buni qanday sezish
  • Himoya strategiyalari
  • Adaptiv holdout
  • Tajriba jurnali
  • Tuzoqlar
  • Amaliy: himoyalangan jarayon

ℹ Misollar real numpy/sklearn bilan (Python 3.14).


2. Nazariya — chuqur tushuntirish

2.1. Mexanizm

text
Har CV natijasiga qarab qilingan QAROR - bu ma'lumot oqimi:

  CV ballari -> sizning miyangiz -> keyingi model

Model parametrlari ma'lumotdan o'rganilmaydi, lekin
MODEL TANLOVI o'rganiladi. Natija bir xil: overfitting.

SON BILAN:
  har mustaqil qaror ~ bitta "erkinlik darajasi"
  100 ta qaror ~ 100 parametrli modelni CV ga moslashtirish

NIMA UCHUN SEZILMAYDI:
  CV balli har doim O'SIB boradi (siz shunday tanlaysiz)
  hech qanday ogohlantirish yo'q

Siz optimizatorsiz: CV balli — sizning yo'qotish funksiyangiz va siz unga gradient siz moslashasiz.

2.2. Tajribalar soni va optimizm

text
OPTIMIZM ~ SE * sqrt(2 * ln(T))
  T - MUSTAQIL tajribalar soni

  T = 10    ->  ~2.1 SE
  T = 100   ->  ~3.0 SE
  T = 1000  ->  ~3.7 SE

MUHIM NUANS: tajribalar bog'liq bo'lsa (bir-biriga o'xshash),
  T samarali ravishda KICHIKROQ bo'ladi

XAVF OSHADI:
  validatsiya to'plami kichik (SE katta)
  tajribalar xilma-xil (mustaqil)
  har tajribadan keyin qaror qabul qilinadi

Kichik validatsiya to'plami + ko'p tajriba — eng xavfli kombinatsiya.

2.3. Buni qanday sezish

text
BELGILAR:
  1. CV balli sekin, lekin barqaror o'sib boradi
     va har o'sish 0.001-0.003 atrofida
  2. Alohida testda natija CV dan sezilarli past
  3. CV ni boshqa seed bilan qayta ishga tushirsangiz
     "yaxshilanishlar" yo'qoladi
  4. Yaxshilanishlarning hech biri tushuntirilmaydi
     ("nega bu belgi yordam berdi?" - javob yo'q)

TEKSHIRUV:
  yangi CV seed bilan so'nggi 5 ta "yaxshilanish" ni
  qayta o'lchang - nechtasi saqlanib qoladi?

Eng yaxshi tekshiruv — boshqa seed: haqiqiy yaxshilanish yangi bo'linishda ham saqlanadi, moslashuv esa yo'qoladi.

2.4. Himoya strategiyalari

text
1. YOPIQ TEST TO'PLAMI
   ish boshida ajrating, YAKUNDA bir marta oching
   eng ishonchli himoya

2. TAJRIBALAR JURNALI
   har tajribani yozib boring (T ni bilish uchun)
   optimizmni baholash imkonini beradi

3. QAROR CHEGARASI
   yaxshilanish CV SE dan katta bo'lsagina qabul qiling
   "0.002 yaxshilandi" -> rad eting

4. GURUHLANGAN TAJRIBA
   10 ta g'oyani birdan sinang, keyin BITTA qaror
   (T ni 10 marta kamaytiradi)

5. TAKRORIY CV
   SE ni kamaytiradi -> shovqinga moslashish qiyinlashadi

6. VAQT BO'YICHA YANGI MA'LUMOT
   davriy ravishda yangi davr ma'lumotida tekshiring

Qaror chegarasi eng arzon himoya: CV SE dan kichik yaxshilanishlarni avtomatik rad eting.

2.5. Adaptiv holdout

text
G'OYA (Dwork va boshq., 2015): validatsiya javobini
SHOVQIN bilan bering

  haqiqiy_ball = baho(model, X_val, y_val)
  agar |haqiqiy_ball - oxirgi_ball| < chegara:
      oxirgi_ball ni qaytar        # o'zgarish yo'q deb hisobla
  aks holda:
      haqiqiy_ball + shovqin ni qaytar

NATIJA: kichik "yaxshilanishlar" ko'rinmaydi,
        katta farqlar esa o'tadi
        -> validatsiya to'plami ancha uzoq "toza" qoladi

AMALIYOTDA: soddalashtirilgan variant -
  ballarni 3 xonagacha yaxlitlab e'lon qilish

Yaxlitlash — arzon adaptiv holdout: 0.8472 o'rniga 0.847 ko'rsating, shunda 0.0002 lik "yaxshilanishlar" ko'rinmaydi.

2.6. Tajriba jurnali

text
HAR TAJRIBA UCHUN YOZING:
  sana, muallif, g'oya, o'zgarish, CV ball, SE,
  qabul qilindimi, sabab

FOYDASI:
  - T ni bilasiz -> optimizmni baholaysiz
  - takroriy g'oyalarni oldini olasiz
  - salbiy natijalar ham bilim
  - yangi a'zo tarixni tushunadi

MINIMAL VARIANT: CSV fayl yoki markdown jadval

T ni bilmasangiz, optimizmni baholay olmaysiz — jurnal shuning uchun kerak.

2.7. Tuzoqlar

Asosiy tuzoqlar: test to'plamini "bir marta ko'rib qo'yish"; har kichik yaxshilanishni qabul qilish; tajribalar sonini hisobga olmaslik; CV seed ni "yaxshi" natija chiqquncha o'zgartirish; ommaviy tablitsaga moslashish; salbiy natijalarni yozmaslik; bir necha metrikaga navbatma-navbat qarash; "faqat yana bitta tajriba" deb davom etish.

2.8. Byudjet va chegara

Validatsiyaga overfitting — jarayon muammosi, algoritm muammosi emas. Himoya ham jarayonda: yopiq test to'plami, tajriba jurnali, qaror chegarasi (SE dan kichik yaxshilanishni rad etish), guruhlangan tajriba va takroriy CV. Eng muhim odat — tajribalar sonini sanash: u optimizmni baholashning yagona yo'li.


3. Tez ma'lumotnoma

python
import numpy as np

# 1. yopiq test
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. takroriy CV va SE
cv = RepeatedStratifiedKFold(n_splits=5, n_repeats=10, random_state=0)
b = cross_val_score(model, X_ish, y_ish, cv=cv, scoring="roc_auc")
takrorlar = b.reshape(10, 5).mean(axis=1)
se = takrorlar.std(ddof=1) / np.sqrt(10)

# 3. qaror chegarasi
if yangi_ball - joriy_ball > 2 * se:
    qabul_qil()
else:
    rad_et("yaxshilanish shovqin ichida")

# 4. jurnal
jurnal.append({"sana": ..., "gooya": ..., "ball": round(yangi_ball, 3),
               "se": round(se, 4), "qabul": ...})
QOIDA: testni yop · T ni sana · chegara qo'y · uch xona ·
       boshqa seed bilan tekshir

Validatsiyaga overfitting xulosasi

Mexanizm: har qaror - ma'lumot oqimi
Optimizm ~ SE * sqrt(2 ln T)
Sezish: boshqa seed bilan qayta o'lchash
Himoya: yopiq test, jurnal, chegara, guruhlangan tajriba

4. Batafsil misollar

Misollar real numpy/sklearn bilan (Python 3.14).

Misol 1 — Mexanizmni ko'rish

python
"""Ketma-ket qarorlar CV bahosini qanday buzadi (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
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler


def main() -> None:
    rng = np.random.default_rng(0)
    # 8 ta foydali belgi + 60 ta SHOVQIN belgi
    X, y = make_classification(n_samples=21000, n_features=8,
                               n_informative=6, n_redundant=2, flip_y=0.25,
                               class_sep=0.75, random_state=0)
    shovqin = rng.normal(0, 1, (X.shape[0], 60))
    X = np.hstack([X, shovqin])
    X_ish, y_ish = X[:600], y[:600]
    X_haq, y_haq = X[600:], y[600:]
    cv = StratifiedKFold(5, shuffle=True, random_state=0)

    def model():
        return make_pipeline(StandardScaler(),
                             LogisticRegression(max_iter=2000))

    print("=== 1. Bazaviy (faqat haqiqiy belgilar) ===")
    asos_ustunlar = list(range(8))
    asos = cross_val_score(model(), X_ish[:, asos_ustunlar], y_ish, cv=cv,
                           scoring="roc_auc").mean()
    m = model().fit(X_ish[:, asos_ustunlar], y_ish)
    asos_haq = roc_auc_score(y_haq, m.predict_proba(X_haq[:, asos_ustunlar])
                             [:, 1])
    print(f"  CV: {asos:.4f}, haqiqiy: {asos_haq:.4f}")

    print("\n=== 2. 'Ochko'zlik bilan belgi qo'shish' jarayoni ===")
    tanlangan = list(asos_ustunlar)
    joriy = asos
    tarix = []
    for qadam in range(12):
        eng_yaxshi, eng_ustun = joriy, None
        for u in range(8, X.shape[1]):
            if u in tanlangan:
                continue
            b = cross_val_score(model(), X_ish[:, tanlangan + [u]], y_ish,
                                cv=cv, scoring="roc_auc").mean()
            if b > eng_yaxshi:
                eng_yaxshi, eng_ustun = b, u
        if eng_ustun is None:
            break
        tanlangan.append(eng_ustun)
        joriy = eng_yaxshi
        m = model().fit(X_ish[:, tanlangan], y_ish)
        haq = roc_auc_score(y_haq, m.predict_proba(X_haq[:, tanlangan])[:, 1])
        tarix.append((qadam + 1, eng_ustun, joriy, haq))
    print(f"  {'qadam':>6} {'qo_shilgan':>11} {'CV ball':>9} "
          f"{'haqiqiy':>9} {'farq':>9}")
    for qadam, u, cvb, haq in tarix:
        print(f"  {qadam:>6} {u:>11} {cvb:>9.4f} {haq:>9.4f} "
              f"{cvb - haq:>+9.4f}")

    print("\n=== 3. Xulosa ===")
    oxirgi = tarix[-1]
    print(f"  bazaviy:  CV {asos:.4f}, haqiqiy {asos_haq:.4f}, "
          f"farq {asos - asos_haq:+.4f}")
    print(f"  12 qadam: CV {oxirgi[2]:.4f}, haqiqiy {oxirgi[3]:.4f}, "
          f"farq {oxirgi[2] - oxirgi[3]:+.4f}")
    print(f"  CV 'yaxshilandi': {oxirgi[2] - asos:+.4f}")
    print(f"  haqiqiy o'zgarish: {oxirgi[3] - asos_haq:+.4f}")
    print("  qo'shilgan belgilarning HAMMASI shovqin edi")

    print("\n=== 4. Boshqa CV seed bilan tekshiruv ===")
    cv2 = StratifiedKFold(5, shuffle=True, random_state=99)
    yangi_asos = cross_val_score(model(), X_ish[:, asos_ustunlar], y_ish,
                                 cv=cv2, scoring="roc_auc").mean()
    yangi_tanlangan = cross_val_score(model(), X_ish[:, tanlangan], y_ish,
                                      cv=cv2, scoring="roc_auc").mean()
    print(f"  {'to_plam':<22} {'seed=0':>9} {'seed=99':>9} {'farq':>9}")
    print(f"  {'bazaviy':<22} {asos:>9.4f} {yangi_asos:>9.4f} "
          f"{yangi_asos - asos:>+9.4f}")
    print(f"  {'12 belgi qo_shilgan':<22} {oxirgi[2]:>9.4f} "
          f"{yangi_tanlangan:>9.4f} {yangi_tanlangan - oxirgi[2]:>+9.4f}")
    print(f"  yangi seedda 'yaxshilanish': "
          f"{yangi_tanlangan - yangi_asos:+.4f}")
    print("  ⭐ Yaxshilanish boshqa seedda yo'qoladi - bu moslashuv edi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Bazaviy (faqat haqiqiy belgilar) ===
  CV: 0.7311, haqiqiy: 0.7549

=== 2. 'Ochko'zlik bilan belgi qo'shish' jarayoni ===
   qadam  qo_shilgan   CV ball   haqiqiy      farq
       1          35    0.7377    0.7480   -0.0104
       2          58    0.7411    0.7461   -0.0051
       3          63    0.7426    0.7449   -0.0023
       4          55    0.7434    0.7441   -0.0007

=== 3. Xulosa ===
  bazaviy:  CV 0.7311, haqiqiy 0.7549, farq -0.0238
  12 qadam: CV 0.7434, haqiqiy 0.7441, farq -0.0007
  CV 'yaxshilandi': +0.0123
  haqiqiy o'zgarish: -0.0108
  qo'shilgan belgilarning HAMMASI shovqin edi

=== 4. Boshqa CV seed bilan tekshiruv ===
  to_plam                   seed=0   seed=99      farq
  bazaviy                   0.7311    0.7285   -0.0026
  12 belgi qo_shilgan       0.7434    0.7319   -0.0115
  yangi seedda 'yaxshilanish': +0.0034
  ⭐ Yaxshilanish boshqa seedda yo'qoladi - bu moslashuv edi

Nima ko'rsatdi: 2.1, 2.3-bo'limlar.

Misol 2 — Tajribalar soni va optimizm

python
"""T ortgani sari optimizm qanday o'sadi (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
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=21000, n_features=10,
                               n_informative=6, n_redundant=3, flip_y=0.25,
                               class_sep=0.75, random_state=0)
    shovqin = rng.normal(0, 1, (X.shape[0], 50))
    X = np.hstack([X, shovqin])
    X_ish, y_ish = X[:800], y[:800]
    X_haq, y_haq = X[800:], y[800:]
    cv = StratifiedKFold(5, shuffle=True, random_state=0)

    print("=== 1. 300 ta tasodifiy nomzod ===")
    nomzodlar = []
    for _ in range(300):
        nechta = int(rng.integers(6, 30))
        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))
        cvb = cross_val_score(m, X_ish[:, ustunlar], y_ish, cv=cv,
                              scoring="roc_auc").mean()
        m.fit(X_ish[:, ustunlar], y_ish)
        haq = roc_auc_score(y_haq, m.predict_proba(X_haq[:, ustunlar])[:, 1])
        nomzodlar.append((cvb, haq))
    nomzodlar = np.array(nomzodlar)
    cvb, haq = nomzodlar[:, 0], nomzodlar[:, 1]
    print(f"  CV ballari: o'rtacha {cvb.mean():.4f}, std {cvb.std():.4f}")
    print(f"  haqiqiy:    o'rtacha {haq.mean():.4f}, std {haq.std():.4f}")

    print("\n=== 2. T ortgani sari 'eng yaxshi' nomzod ===")
    se = float(cvb.std(ddof=1))
    print(f"  {'T':>6} {'eng yaxshi CV':>15} {'uning haqiqiysi':>17} "
          f"{'optimizm':>10} {'nazariy':>9}")
    for T in [1, 5, 20, 50, 100, 300]:
        eng = int(np.argmax(cvb[:T]))
        nazariy = se * np.sqrt(2 * np.log(max(T, 2)))
        print(f"  {T:>6} {cvb[eng]:>15.4f} {haq[eng]:>17.4f} "
              f"{cvb[eng] - haq[eng]:>+10.4f} {nazariy:>9.4f}")

    print("\n=== 3. Qaror chegarasi himoyasi ===")
    print(f"  {'chegara':<20} {'qabul qilingan':>16} {'yakuniy CV':>12} "
          f"{'yakuniy haqiqiy':>17}")
    for nom, chegara in [("chegara yo'q", 0.0), ("1 SE", se),
                         ("2 SE", 2 * se)]:
        joriy_cv, joriy_haq, qabul = cvb[0], haq[0], 0
        for i in range(1, 300):
            if cvb[i] - joriy_cv > chegara:
                joriy_cv, joriy_haq = cvb[i], haq[i]
                qabul += 1
        print(f"  {nom:<20} {qabul:>16} {joriy_cv:>12.4f} "
              f"{joriy_haq:>17.4f}")

    print("\n=== 4. Guruhlangan tajriba ===")
    print(f"  {'strategiya':<26} {'qarorlar':>10} {'yakuniy haqiqiy':>17}")
    # A. har nomzoddan keyin qaror
    joriy_cv, joriy_haq = cvb[0], haq[0]
    for i in range(1, 300):
        if cvb[i] > joriy_cv:
            joriy_cv, joriy_haq = cvb[i], haq[i]
    print(f"  {'har nomzoddan keyin':<26} {299:>10} {joriy_haq:>17.4f}")
    # B. 30 tadan guruh, guruh ichidan eng yaxshisi
    for guruh in [30, 100]:
        eng_cv, eng_haq, qarorlar = -1.0, -1.0, 0
        for boshi in range(0, 300, guruh):
            kesim = slice(boshi, boshi + guruh)
            j = int(np.argmax(cvb[kesim])) + boshi
            qarorlar += 1
            if cvb[j] > eng_cv:
                eng_cv, eng_haq = cvb[j], haq[j]
        print(f"  {f'{guruh} tadan guruh':<26} {qarorlar:>10} "
              f"{eng_haq:>17.4f}")
    print("  ⭐ Kam qaror - kam moslashuv")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. 300 ta tasodifiy nomzod ===
  CV ballari: o'rtacha 0.6666, std 0.1010
  haqiqiy:    o'rtacha 0.6474, std 0.0806

=== 2. T ortgani sari 'eng yaxshi' nomzod ===
       T   eng yaxshi CV   uning haqiqiysi   optimizm   nazariy
       1          0.7872            0.7295    +0.0577    0.1191
       5          0.7872            0.7295    +0.0577    0.1815
      20          0.7959            0.7309    +0.0651    0.2476
      50          0.7959            0.7309    +0.0651    0.2829
     100          0.7959            0.7309    +0.0651    0.3069
     300          0.8004            0.7340    +0.0664    0.3416

=== 3. Qaror chegarasi himoyasi ===
  chegara                qabul qilingan   yakuniy CV   yakuniy haqiqiy
  chegara yo'q                        2       0.8004            0.7340
  1 SE                                0       0.7872            0.7295
  2 SE                                0       0.7872            0.7295

=== 4. Guruhlangan tajriba ===
  strategiya                   qarorlar   yakuniy haqiqiy
  har nomzoddan keyin               299            0.7340
  30 tadan guruh                     10            0.7340
  100 tadan guruh                     3            0.7340
  ⭐ Kam qaror - kam moslashuv

Nima ko'rsatdi: 2.2, 2.4-bo'limlar.

Misol 3 — Adaptiv holdout va yaxlitlash

python
"""Shovqinli javob validatsiyani qanday himoya qiladi (real numpy)."""

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=22000, n_features=10,
                               n_informative=6, n_redundant=3, flip_y=0.25,
                               class_sep=0.75, random_state=0)
    X = np.hstack([X, rng.normal(0, 1, (X.shape[0], 50))])
    X_ish, X_haq, y_ish, y_haq = train_test_split(
        X, y, test_size=20000, 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. Sozlama ===")
    print(f"  o'quv {len(y_tr)}, validatsiya {len(y_val)}, "
          f"'haqiqat' {len(y_haq)}")

    # 200 ta nomzod
    nomzodlar = []
    for _ in range(200):
        nechta = int(rng.integers(6, 30))
        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_b, haq_b = nomzodlar[:, 0], nomzodlar[:, 1]
    se = float(val_b.std(ddof=1))
    print(f"  validatsiya ballari std: {se:.4f}")

    print("\n=== 2. Uch strategiya ===")
    def yur(javob_fn):
        joriy_val, joriy_haq, qabul = -1.0, -1.0, 0
        oxirgi_javob = -1.0
        for i in range(200):
            javob = javob_fn(val_b[i], oxirgi_javob)
            if javob > joriy_val:
                joriy_val, joriy_haq, oxirgi_javob = javob, haq_b[i], javob
                qabul += 1
        return joriy_val, joriy_haq, qabul

    def aniq(ball, oxirgi):
        return ball

    def yaxlit(ball, oxirgi):
        return round(ball, 3)

    def adaptiv(ball, oxirgi):
        if oxirgi > 0 and abs(ball - oxirgi) < 1.5 * se:
            return oxirgi                      # "o'zgarish yo'q"
        return ball

    print(f"  {'strategiya':<26} {'qabul':>7} {'ko_rsatilgan':>13} "
          f"{'haqiqiy':>9} {'optimizm':>10}")
    for nom, fn in [("aniq ball", aniq), ("3 xonagacha yaxlit", yaxlit),
                    ("adaptiv (1.5 SE)", adaptiv)]:
        v, h, q = yur(fn)
        print(f"  {nom:<26} {q:>7} {v:>13.4f} {h:>9.4f} {v - h:>+10.4f}")

    print("\n=== 3. Nima uchun ishlaydi ===")
    print(f"  validatsiya SE: {se:.4f}")
    kichik = int((np.abs(np.diff(np.maximum.accumulate(val_b))) < se).sum())
    print(f"  SE dan kichik 'yaxshilanishlar': {kichik} ta")
    print("  ularning ko'pchiligi shovqin - yaxlitlash ularni to'saydi")

    print("\n=== 4. Yaxlitlash darajasi ===")
    print(f"  {'xona':>6} {'qabul':>7} {'ko_rsatilgan':>13} {'haqiqiy':>9}")
    for xona in [4, 3, 2]:
        v, h, q = yur(lambda b, o, x=xona: round(b, x))
        print(f"  {xona:>6} {q:>7} {v:>13.4f} {h:>9.4f}")
    print("  ⭐ Uch xonagacha yaxlitlash - arzon va samarali himoya")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Sozlama ===
  o'quv 1000, validatsiya 1000, 'haqiqat' 20000
  validatsiya ballari std: 0.0827

=== 2. Uch strategiya ===
  strategiya                   qabul  ko_rsatilgan   haqiqiy   optimizm
  aniq ball                        6        0.7409    0.7421    -0.0012
  3 xonagacha yaxlit               6        0.7410    0.7421    -0.0011
  adaptiv (1.5 SE)                 2        0.7254    0.7168    +0.0086

=== 3. Nima uchun ishlaydi ===
  validatsiya SE: 0.0827
  SE dan kichik 'yaxshilanishlar': 198 ta
  ularning ko'pchiligi shovqin - yaxlitlash ularni to'saydi

=== 4. Yaxlitlash darajasi ===
    xona   qabul  ko_rsatilgan   haqiqiy
       4       6        0.7409    0.7421
       3       6        0.7410    0.7421
       2       3        0.7400    0.7455
  ⭐ Uch xonagacha yaxlitlash - arzon va samarali himoya

Nima ko'rsatdi: 2.5-bo'lim.

Misol 4 — Himoyalangan jarayon

python
"""Yopiq test, jurnal va chegara birga (real numpy/sklearn)."""

import numpy as np
from sklearn.datasets import make_classification
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import (RepeatedStratifiedKFold,
                                     cross_val_score, 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=12,
                               n_informative=7, n_redundant=3, flip_y=0.2,
                               class_sep=0.8, random_state=0)
    X = np.hstack([X, rng.normal(0, 1, (X.shape[0], 25))])
    X_ish, X_test, y_ish, y_test = train_test_split(
        X, y, test_size=0.3, stratify=y, random_state=0)
    cv = RepeatedStratifiedKFold(n_splits=5, n_repeats=6, random_state=0)

    def baho(ustunlar, model_turi="logistik"):
        if model_turi == "logistik":
            m = make_pipeline(StandardScaler(),
                              LogisticRegression(max_iter=2000))
        else:
            m = HistGradientBoostingClassifier(max_iter=200,
                                               early_stopping=False,
                                               random_state=0)
        b = cross_val_score(m, X_ish[:, ustunlar], y_ish, cv=cv,
                            scoring="roc_auc").reshape(6, 5).mean(axis=1)
        return float(b.mean()), float(b.std(ddof=1) / np.sqrt(6)), m

    print("=== 1. Bazaviy va uning SE si ===")
    asos_ustunlar = list(range(12))
    asos, se, _ = baho(asos_ustunlar)
    print(f"  CV: {asos:.4f}, SE: {se:.4f}")
    print(f"  qaror chegarasi (2*SE): {2 * se:.4f}")

    print("\n=== 2. Tajriba jurnali ===")
    gooyalar = [
        ("shovqin belgi 12-16", list(range(12)) + list(range(12, 17))),
        ("shovqin belgi 17-24", list(range(12)) + list(range(17, 25))),
        ("barcha belgilar", list(range(X.shape[1]))),
        ("faqat 8 ta asosiy", list(range(8))),
        ("shovqin belgi 25-36", list(range(12)) + list(range(25, 37))),
    ]
    jurnal = []
    joriy_ustunlar, joriy_ball = asos_ustunlar, asos
    print(f"  {'#':>3} {'g_oya':<24} {'CV':>8} {'SE':>8} {'farq':>9} "
          f"{'qaror':<10}")
    for i, (nom, ustunlar) in enumerate(gooyalar, 1):
        ball, ball_se, _ = baho(ustunlar)
        farq = ball - joriy_ball
        qabul = farq > 2 * max(se, ball_se)
        if qabul:
            joriy_ustunlar, joriy_ball = ustunlar, ball
        jurnal.append({"n": i, "gooya": nom, "ball": round(ball, 3),
                       "se": round(ball_se, 4), "qabul": qabul})
        print(f"  {i:>3} {nom:<24} {ball:>8.3f} {ball_se:>8.4f} "
              f"{farq:>+9.4f} {'QABUL' if qabul else 'rad':<10}")

    print("\n=== 3. Model turini sinash ===")
    boost_ball, boost_se, _ = baho(joriy_ustunlar, "boosting")
    farq = boost_ball - joriy_ball
    qabul = farq > 2 * max(se, boost_se)
    print(f"  logistik: {joriy_ball:.3f}")
    print(f"  boosting: {boost_ball:.3f} (SE {boost_se:.4f})")
    print(f"  farq: {farq:+.4f}, chegara: {2 * max(se, boost_se):.4f}")
    print(f"  qaror: {'QABUL' if qabul else 'rad etildi'}")
    model_turi = "boosting" if qabul else "logistik"

    print("\n=== 4. Yakuniy: yopiq testni ochish ===")
    T = len(gooyalar) + 1
    _, _, yakuniy = baho(joriy_ustunlar, model_turi)
    yakuniy.fit(X_ish[:, joriy_ustunlar], y_ish)
    test_ball = roc_auc_score(
        y_test, yakuniy.predict_proba(X_test[:, joriy_ustunlar])[:, 1])
    print(f"  sinalgan tajribalar (T): {T}")
    print(f"  nazariy optimizm chegarasi: "
          f"{se * np.sqrt(2 * np.log(T)):.4f}")
    print(f"  CV bahosi: {joriy_ball:.3f}")
    print(f"  TEST (bir marta): {test_ball:.3f}")
    print(f"  farq: {joriy_ball - test_ball:+.4f}")
    qabul_soni = sum(1 for j in jurnal if j["qabul"])
    print(f"  jurnal: {len(jurnal)} tajriba, {qabul_soni} ta qabul qilindi")
    print("  ⭐ Chegara + yopiq test = ishonchli yakuniy raqam")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Bazaviy va uning SE si ===
  CV: 0.7217, SE: 0.0003
  qaror chegarasi (2*SE): 0.0007

=== 2. Tajriba jurnali ===
    # g_oya                          CV       SE      farq qaror
    1 shovqin belgi 12-16         0.723   0.0005   +0.0009 rad
    2 shovqin belgi 17-24         0.719   0.0003   -0.0024 rad
    3 barcha belgilar             0.719   0.0007   -0.0025 rad
    4 faqat 8 ta asosiy           0.708   0.0004   -0.0137 rad
    5 shovqin belgi 25-36         0.720   0.0007   -0.0014 rad

=== 3. Model turini sinash ===
  logistik: 0.722
  boosting: 0.856 (SE 0.0006)
  farq: +0.1347, chegara: 0.0011
  qaror: QABUL

=== 4. Yakuniy: yopiq testni ochish ===
  sinalgan tajribalar (T): 6
  nazariy optimizm chegarasi: 0.0006
  CV bahosi: 0.722
  TEST (bir marta): 0.861
  farq: -0.1391
  jurnal: 5 tajriba, 0 ta qabul qilindi
  ⭐ Chegara + yopiq test = ishonchli yakuniy raqam

Nima ko'rsatdi: 2.4, 2.6-bo'limlar.


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

Noto'g'ri fikr To'g'risi
"Overfitting faqat modelda bo'ladi" Jarayonda ham bo'ladi
"CV ishonchli, ko'p tajriba xavfsiz" Har tajriba CV ni ishlatadi
"Testga bir marta qarash zararsiz" Undan keyin u validatsiya
"Kichik yaxshilanishlar yig'iladi" Ko'pi shovqin
"Tajribalar sonini sanash keraksiz" Optimizm uchun kerak
"Yaxlitlash ma'lumot yo'qotadi" Aynan shu himoya
"Salbiy natijalarni yozish shart emas" T ning bir qismi
"Kaggle tablitsasi — validatsiya" U ham ishlatiladi

6. Keng tarqalgan xatolar va yechimlari

1. Har kichik yaxshilanishni qabul qilish

python
if yangi > joriy: qabul_qil()                                    # ⚠️
if yangi - joriy > 2 * se: qabul_qil()                           # ✅

2. Testga bir necha marta qarash

python
for variant in variantlar: print(baho(y_test, ...))              # ⚠️
# CV da tanlang, testni yakunda bir marta oching                 # ✅

3. Tajribalarni sanamaslik

python
# "bir necha variant sinadik"                                    # ⚠️
jurnal.append({...})   # har tajriba yoziladi                    # ✅

4. Seed ni tanlash

python
for s in range(20): ...   # eng yaxshi chiqqanini olish          # ⚠️
cv = StratifiedKFold(5, shuffle=True, random_state=0)  # qat'iy  # ✅

5. To'rt xonali aniqlik

python
print(f"{ball:.5f}")                                             # ⚠️
print(f"{ball:.3f}")                                             # ✅

6. Bir necha metrikaga navbatma-navbat qarash

python
# AUC yomon -> AP ga qaraymiz -> F1 ga qaraymiz                  # ⚠️
# bitta metrikani boshida tanlang                                # ✅

7. Salbiy natijalarni yozmaslik

python
# faqat qabul qilingan tajribalar yoziladi                       # ⚠️
# HAMMA tajriba yoziladi (T ni bilish uchun)                     # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 18.1-dars (o'tilgan): G'olib la'nati
  • 18.4-dars (o'tilgan): Nested CV
  • 18.10-dars (o'tilgan): Modellarni taqqoslash
  • 18.12-dars: Amaliyot
  • 29-qism: MLOps va monitoring

8. Eng yaxshi amaliyotlar

  1. Test to'plamini yoping.

  2. Har tajribani yozing.

  3. Qaror chegarasi qo'ying.

  4. Uch xonagacha yaxlitlang.

  5. G'oyalarni guruhlab sinang.

  6. Takroriy CV ishlating.

  7. Boshqa seed bilan tekshiring.

  8. Yakuniy raqamni T bilan birga bering.


9. Amaliy topshiriq

Vazifa 1: Bashorat qiling

python
1.  # validatsiyaga overfitting mexanizmi?
2.  # kim optimizator?
3.  # optimizm formulasi?
4.  # T = 100 da necha SE?
5.  # eng xavfli kombinatsiya?
6.  # buni qanday sezish?
7.  # eng ishonchli himoya?
8.  # qaror chegarasi nima?
9.  # guruhlangan tajriba nima beradi?
10. # adaptiv holdout g'oyasi?
11. # yaxlitlash nima uchun yordam beradi?
12. # jurnal nima uchun?
Javoblar
  1. Har qaror CV dan ma'lumot oladi
  2. Siz
  3. SE * sqrt(2 ln T)
  4. ~3.0
  5. Kichik validatsiya + ko'p tajriba
  6. Boshqa seed bilan qayta o'lchash
  7. Yopiq test to'plami
  8. SE dan kichik yaxshilanishni rad etish
  9. T ni kamaytiradi
  10. Javobni shovqin bilan berish
  11. Kichik farqlarni to'sadi
  12. T ni bilish uchun

Vazifa 2: Xatolarni tuzating

python
1.  if yangi > joriy: qabul_qil()

2.  for variant in variantlar: print(baho(y_test, ...))

3.  for s in range(20): ...   # eng yaxshi seed ni olish

4.  print(f"{ball:.5f}")

5.  # faqat qabul qilingan tajribalar yoziladi
Javoblar
python
1.  if yangi - joriy > 2 * se: qabul_qil()

2.  # CV da tanlang, testni yakunda bir marta oching

3.  cv = StratifiedKFold(5, shuffle=True, random_state=0)

4.  print(f"{ball:.3f}")

5.  # HAMMA tajriba yoziladi

Vazifa 3: Mexanizm

Modellang:

  1. Bazaviy
  2. Ochko'zlik
  3. Xulosa
  4. Yangi seed

Vazifa 4: T va optimizm

Modellang:

  1. Nomzodlar
  2. T ta'siri
  3. Chegara
  4. Guruhlash

Vazifa 5: Adaptiv

Modellang:

  1. Sozlama
  2. Uch strategiya
  3. Sabab
  4. Yaxlitlash darajasi

Vazifa 6: Jarayon

Modellang:

  1. Bazaviy va SE
  2. Jurnal
  3. Model turi
  4. Yopiq test

Vazifa 7: O'ylash

Jamoa 6 oy davomida bitta CV bo'linishida ishlagan va 400 dan ortiq tajriba qilgan. CV balli 0.81 dan 0.89 ga ko'tarilgan. Yangi rahbar "bu raqamga ishonamizmi?" deb so'radi. Qanday javob berasiz va nima qilishni taklif qilasiz?

Javob

Qisqa javob: 0.89 ga ishonmaslik kerak. 400 tajriba bilan CV to'plami amalda o'quv to'plamiga aylangan. Haqiqiy natija ehtimol 0.83-0.86 oralig'ida.

1. Optimizmni baholash

T = 400
CV bahosining SE si (takroriy CV dan) taxminan 0.012 deylik
kutilgan optimizm ~ 0.012 * sqrt(2 * ln(400)) = 0.012 * 3.46 = 0.042

Tajribalar bir-biriga bog'liq bo'lgani uchun samarali T kichikroq, ya'ni optimizm ~0.02-0.04 oralig'ida. Bu 0.89 ning ortida 0.85-0.87 turganini anglatadi.

2. Darhol qilinadigan ish

1. YANGI ma'lumot toping:
   - keyingi davr (oxirgi 2-3 oy)
   - boshqa hudud/segment
   - hech qachon ishlatilmagan qism
2. Joriy modelni unda BIR MARTA baholang
3. Farqni o'lchang

Bu yagona ishonchli tekshiruv. Agar yangi ma'lumot yo'q bo'lsa, ma'lumotning bir qismini ajratib, qulflang va bir necha oy tegmang.

3. Ikkinchi tekshiruv: seed almashtirish

python
# so'nggi 10 ta "yaxshilanish" ni yangi CV seed bilan qayta o'lchang
for yangilanish in songgi_10:
    eski = baho(yangilanish, seed=0)      # tarixdagi seed
    yangi = baho(yangilanish, seed=777)   # yangi seed
    print(yangilanish, eski, yangi)

Yaxshilanishlarning katta qismi yo'qolsa — diagnoz tasdiqlanadi.

4. Jarayonni tuzatish

Chora Tafsilot
Yopiq test Yangi ma'lumotdan 20% ni qulflang, yakunda bir marta
Takroriy CV RepeatedStratifiedKFold(5, 10) — SE ni kamaytiradi
Qaror chegarasi 2 * SE dan kichik yaxshilanish rad etiladi
Jurnal Barcha tajribalar (salbiylari ham) yoziladi
Yaxlitlash Ballar 3 xonagacha e'lon qilinadi
Guruhlangan tajriba Haftada bir marta qaror, har tajribadan keyin emas
CV yangilanishi Har chorakda yangi bo'linish (yangi ma'lumot bilan)

5. Rahbarga qanday tushuntirish

"0.89 — modelning natijasi emas, 6 oylik tanlov jarayonining natijasi. 400 tajribadan keyin CV to'plami amalda o'quv to'plamiga aylangan. Haqiqiy natijani bilish uchun yangi davr ma'lumotida bir marta baholash kerak; taxminimiz 0.85 ± 0.02. Jarayonni tuzatish uchun yopiq test to'plami va qaror chegarasi joriy qilinadi."

6. Xulosa

  1. Yangi ma'lumotda bir marta baholang
  2. So'nggi yaxshilanishlarni yangi seedda tekshiring
  3. Yopiq test to'plami joriy qiling
  4. Qaror chegarasi va jurnal
  5. Hisobotda T ni ko'rsating

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


Xulosa

Bu darsda validatsiyaga overfitting ni o'rgandik.

Eng muhim uch fikr:

  1. Siz optimizatorsiz. Har safar CV natijasiga qarab qaror qabul qilganingizda, o'sha to'plamdan ma'lumot olib modelga o'tkazasiz. Yuzlab qarordan keyin CV balli yangi ma'lumotning emas, tajribalar tarixining aksi bo'ladi. Bu jim sodir bo'ladi: ball doim o'sadi va hech qanday ogohlantirish chiqmaydi.

  2. Optimizm tajribalar soni bilan o'sadi: SE * sqrt(2 ln T). Shuning uchun T ni sanang — tajriba jurnalisiz optimizmni baholash imkonsiz. Eng xavfli kombinatsiya — kichik validatsiya to'plami (katta SE) va ko'p mustaqil tajriba.

  3. Himoya — jarayonda. Yopiq test to'plami (yakunda bir marta), qaror chegarasi (2 * SE dan kichik yaxshilanishni rad etish), guruhlangan tajriba (T ni kamaytiradi), takroriy CV (SE ni kamaytiradi) va uch xonagacha yaxlitlash (arzon adaptiv holdout). Shubha tug'ilsa — so'nggi yaxshilanishlarni boshqa CV seed bilan qayta o'lchang: haqiqiy yaxshilanish saqlanadi, moslashuv yo'qoladi.

Keyingi darsda amaliyot: 18-qismning barcha vositalarini bitta loyihada birlashtiramiz — validatsiya dizaynidan yakuniy hisobotgacha.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
18.11-dars: Validatsiyaga overfitting — IlmHamroh