IlmHamroh
Data Science va sun'iy intellekt/Feature engineering10/10-dars26 daqiqa
Mundarija (22)

17.10-dars: Amaliyot — to'liq belgi muhandisligi

17-QISM — FEATURE ENGINEERING · 10-dars


1. Kirish va motivatsiya

Bu qismda belgi turlaridan boshlab Pipeline gacha bo'lgan yo'lni bosib o'tdik. Endi hammasini bitta loyihada birlashtiramiz: xom, aralash turdagi ma'lumotdan boshlab, leakage dan tozalangan va ishlab chiqarishga tayyor quvurgacha.

Feature engineering loyihasining asosiy qiyinligi — tartib: belgilarni tasodifiy qo'shish oson, lekin qaysi guruh qancha foyda berganini bilish qiyin. Yechim — bosqichma-bosqich qo'shish va har bosqichda CV bilan o'lchash.

Ikkinchi qiyinlik — to'xtash: yangi belgi o'ylab topish har doim mumkin, lekin qaysi nuqtada to'xtash kerak? Javob — CV standart og'ishi va narx-foyda.

Bu darsda: to'liq oqim (audit → bazaviy → belgilar guruhlari → tanlash → sozlash → yakuniy baho → saqlash), har bosqichda qaror qabul qilish va natijani hujjatlashtirish.

Real vaziyat. Loyihada 3 hafta belgi muhandisligi qilindi va 180 ta belgi yaratildi. Yakuniy audit ko'rsatdiki: 12 ta belgi AUC ning 95% ini beradi, qolganlari esa shovqin. Model 180 dan 12 ga tushirildi — aniqlik bir xil, tezlik 14 barobar, va risk bo'limi uni tasdiqladi.

Bu darsda to'liq belgi muhandisligi loyihasini quramiz.

Bu darsda:

  • To'liq oqim
  • Bosqichma-bosqich baholash
  • Qachon to'xtash
  • Yakuniy audit
  • Hujjatlashtirish
  • Ishlab chiqarishga tayyorlash
  • Tuzoqlar
  • Amaliy: yakuniy loyiha

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


2. Nazariya — chuqur tushuntirish

2.1. To'liq oqim

text
1. AUDIT: ustunlar, turlar, yo'qolgan qiymatlar, duplikatlar
2. LEAKAGE TEKSHIRUVI: "bu qiymat qachon yoziladi?" har ustun uchun
3. VALIDATSIYA STRATEGIYASI: vaqt? guruh? oddiy CV?
4. BAZAVIY: eng sodda belgilar + sodda model
5. BELGILAR GURUHLARI: bosqichma-bosqich qo'shish va o'lchash
6. TANLASH: keraksizlarini olib tashlash
7. SOZLASH: tayyorlash + model parametrlari birga
8. YAKUNIY BAHO: alohida test to'plamida BIR MARTA
9. AUDIT: leakage, barqarorlik, ishlab chiqarishda mavjudlik
10. SAQLASH: Pipeline + metama'lumot + hujjat

3-qadam (validatsiya strategiyasi) eng erta qabul qilinadigan va eng muhim qaror: noto'g'ri CV bilan qolgan to'qqiz qadamning hammasi ma'nosiz bo'ladi.

2.2. Bosqichma-bosqich baholash

text
Belgilarni GURUH bo'lib qo'shing:

  bazaviy         xom sonli belgilar
  + kategoriya    kodlangan kategoriyali belgilar
  + nisbatlar     a/b naqshlari
  + agregatsiya   guruh statistikasi
  + vaqt          komponentlar, lag, oyna
  + matn          statistika va TF-IDF

Har guruh uchun:
  CV natijasi, std, belgilar soni, hisoblash narxi

QAROR: yaxshilanish CV STD dan katta bo'lsa - qoldiring
       aks holda - tashlang (murakkablik bepul emas)

Guruh bo'lib qo'shish natijani tushunarli qiladi: "nisbatlar +0.011, vaqt +0.015, agregatsiya +0.010" degan jadval qaysi yo'nalishda davom etish kerakligini aniq ko'rsatadi.

2.3. Qachon to'xtash

text
TO'XTASH SIGNALLARI:

1. So'nggi 2-3 guruh CV std dan kam foyda berdi
2. Yaxshilanish bor, lekin ishlab chiqarish narxi oqlamaydi
3. Belgilar soni talqin chegarasidan oshdi
4. Yangi belgilar uchun ma'lumot manbai yo'q
5. Vaqt byudjeti tugadi

ODATIY TAQSIMOT:
  birinchi 3-5 belgi guruhi -> foydaning 80%
  keyingi 10 guruh          -> foydaning 20%

QOIDA: to'xtash nuqtasini OLDINDAN belgilang
  "CV std dan kam foyda beradigan 2 guruh ketma-ket kelsa - to'xtayman"

To'xtash qoidasini oldindan belgilang: aks holda "yana bitta belgi" jarayoni cheksiz davom etadi va siz validatsiyaga overfitting qilasiz.

2.4. Yakuniy audit

text
TEKSHIRUV RO'YXATI:
  [ ] Duplikatlar tozalanganmi (bo'lishdan OLDIN)
  [ ] Har belgining yakka CV natijasi < 0.85
  [ ] Validatsiya strategiyasi vazifaga mos
  [ ] Barcha tayyorlash Pipeline ichida
  [ ] Har belgi uchun "qachon ma'lum?" javobi bor
  [ ] Belgilar ishlab chiqarishda hisoblanadi
  [ ] CV va test natijasi yaqin
  [ ] Permutation importance mantiqli

SHUBHALI BELGILAR:
  - yakka AUC > 0.85
  - permutation muhimligi hukmron (boshqalardan 5x katta)
  - nomi "natija", "yopilish", "qaytarish" kabi so'zlarni o'z ichiga oladi

Belgi nomlari ham signal beradi: yopilish_sababi, qaytarish_summasi, yakuniy_holat kabi nomlar deyarli har doim leakage ni anglatadi.

2.5. Hujjatlashtirish

text
BELGILAR LUG'ATI (feature dictionary):

  nom | ta'rif | manba | qachon ma'lum | tur | yaratilgan sana | egasi

Misol:
  qarz_nisbati | qarz / daromad | ariza formasi | ariza vaqtida |
  sonli | 2026-09 | risk jamoasi

QO'SHIMCHA:
  - har belgi guruhining CV hissasi
  - tashlangan belgilar va sabablari
  - leakage tekshiruvi natijasi
  - ishlab chiqarishda hisoblash kodi/so'rovi

Belgilar lug'ati — jamoaviy ishning asosi: usiz olti oydan keyin hech kim x_47 belgisining nima ekanini eslay olmaydi.

2.6. Ishlab chiqarishga tayyorlash

python
import joblib

paket = {
    "quvur": quvur,                       # butun tayyorlash + model
    "belgilar": list(X.columns),          # kirish ustunlari va TARTIBI
    "belgilar_lugati": lugat,             # ta'riflar
    "metrika": {"cv_auc": 0.812, "test_auc": 0.807},
    "versiyalar": {"sklearn": sklearn.__version__},
    "sana": "2026-09-21",
}
joblib.dump(paket, "model.joblib", compress=3)
text
ISHLAB CHIQARISHDA TEKSHIRISH:
  assert list(yangi.columns) == paket["belgilar"]
  yangi kategoriyalar ulushi
  NaN ulushi o'quvdagidan farq qiladimi
  bashorat taqsimoti o'quvdagiga o'xshashmi

Kirish ustunlari tartibini saqlang va tekshiring: ColumnTransformer nom bo'yicha ishlaydi, lekin massiv berilganda tartib muhim bo'ladi va jim xato paydo bo'lishi mumkin.

2.7. Tuzoqlar

Asosiy tuzoqlar: validatsiya strategiyasini kech tanlash; belgilarni bitta-bitta qo'shib vaqt yo'qotish; to'xtash qoidasisiz ishlash; yakuniy auditni o'tkazib yuborish; belgilar lug'atini yozmaslik; test to'plamini bir necha marta ishlatish; ishlab chiqarishda hisoblanmaydigan belgi yaratish; murakkablikni "bepul" deb hisoblash.

2.8. Tartib va to'xtash qoidasi

Feature engineering loyihasi tartib bilan olib boriladi: audit → leakage tekshiruvi → validatsiya strategiyasi → bazaviy → guruh bo'lib belgilar qo'shish → tanlash → sozlash → bir marta test → audit → saqlash. Har guruhning hissasini CV std bilan solishtiring va to'xtash qoidasini oldindan belgilang. Yakunda belgilar lug'ati va Pipeline paketi topshiriladi. Bu bilan 17-qism yakunlanadi.


3. Tez ma'lumotnoma

python
import joblib
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.model_selection import GroupKFold, cross_val_score, train_test_split
from sklearn.pipeline import Pipeline

# 1. audit
df.duplicated().sum(); df.isna().mean(); df.nunique()

# 2. guruh bo'lib baholash
for nom, ustunlar in guruhlar.items():
    b = cross_val_score(quvur, X[ustunlar], y, cv=cv, scoring="roc_auc")
    print(nom, b.mean(), b.std())

# 3. yakuniy
joblib.dump({"quvur": quvur, "belgilar": list(X.columns),
             "lugat": lugat, "metrika": {...}}, "model.joblib", compress=3)
QOIDA: validatsiyani birinchi tanla · guruh bo'lib o'lcha ·
       to'xtash qoidasini oldindan belgila · testni bir marta ishlat

Amaliyot xulosasi

1 audit -> 2 leakage -> 3 validatsiya -> 4 bazaviy -> 5 guruhlar
-> 6 tanlash -> 7 sozlash -> 8 test -> 9 audit -> 10 saqlash
Har guruh CV std bilan solishtiriladi
Topshirishda: belgilar lug'ati + Pipeline paketi

4. Batafsil misollar

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

Misol 1 — Audit va validatsiya strategiyasi

python
"""1-4 qadamlar (real pandas/sklearn)."""

import numpy as np
import pandas as pd
from sklearn.dummy import DummyClassifier
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.model_selection import (GroupKFold, StratifiedKFold,
                                     cross_val_score)


def yarat(seed: int = 42, mijozlar: int = 1400) -> pd.DataFrame:
    """Yetkazib berish: har mijozning bir necha buyurtmasi (guruh tuzilmasi)."""
    rng = np.random.default_rng(seed)
    qatorlar = []
    boshlanish = pd.Timestamp("2025-01-01")
    for m in range(mijozlar):
        imzo = rng.normal(0, 1)                     # mijozning xulqi
        kod = round(float(rng.random()), 6)         # CRM kodi - signal YO'Q
        hudud = rng.choice(["toshkent", "samarqand", "buxoro", "fargona"])
        hq = {"toshkent": 0.0, "samarqand": 0.5, "buxoro": 1.0,
              "fargona": 0.3}[hudud]
        odatiy = rng.lognormal(12.0, 0.45)          # mijozning odatiy summasi
        for _ in range(int(rng.integers(8, 24))):
            sana = boshlanish + pd.Timedelta(days=int(rng.integers(0, 400)))
            summa = odatiy * rng.lognormal(0, 0.5)
            masofa = rng.gamma(2, 110)
            ogirlik = rng.gamma(2, 5)
            soat = int(rng.integers(0, 24))
            kuch = (-1.9 + hq
                    + 3.0 * (ogirlik / masofa > 0.085)
                    + 0.9 * (7 <= soat <= 10)
                    + 1.0 * (sana.dayofweek >= 5)
                    + 1.6 * (summa > 1.15 * odatiy)
                    + 1.8 * imzo)
            kechikdi = int(rng.random() < 1 / (1 + np.exp(-kuch)))
            # LEAKAGE belgisi: qaytarish faqat kechikkanda yoziladi
            qaytarish = summa * rng.uniform(0.3, 1.0) if kechikdi else 0.0
            qatorlar.append([m, kod, sana, hudud, summa, masofa, ogirlik,
                             soat, qaytarish, kechikdi])
    df = pd.DataFrame(qatorlar, columns=["mijoz", "kod", "sana", "hudud",
                                         "summa", "masofa", "ogirlik", "soat",
                                         "qaytarish", "kechikdi"])
    dup = df.sample(frac=0.05, random_state=0)      # duplikatlar
    return pd.concat([df, dup], ignore_index=True)


def main() -> None:
    df = yarat()

    print("=== 1. Audit ===")
    print(f"  {len(df)} qator, {df['mijoz'].nunique()} mijoz")
    print(f"  {'ustun':<12} {'dtype':<16} {'noyob':>7} {'yo_qolgan':>10}")
    for ustun in df.columns:
        print(f"  {ustun:<12} {str(df[ustun].dtype):<16} "
              f"{df[ustun].nunique():>7} {int(df[ustun].isna().sum()):>10}")
    dup = df.duplicated()
    print(f"  duplikatlar: {int(dup.sum())} ({dup.mean():.1%}) - TOZALANADI")
    df = df.drop_duplicates().reset_index(drop=True)
    print(f"  tozalangandan keyin: {len(df)} qator")

    y = df["kechikdi"].to_numpy()
    guruh = df["mijoz"].to_numpy()

    def model(iter_soni: int = 200):
        return HistGradientBoostingClassifier(learning_rate=0.1,
                                              max_iter=iter_soni,
                                              random_state=0)

    print("\n=== 2. Leakage tekshiruvi (yakka belgi) ===")
    cv_guruh = GroupKFold(5)
    print(f"  {'belgi':<12} {'yakka CV AUC':>14} {'baho':<12}")
    for ustun in ["summa", "masofa", "ogirlik", "soat", "kod", "qaytarish"]:
        b = cross_val_score(model(100), df[[ustun]], y, cv=cv_guruh,
                            groups=guruh, scoring="roc_auc").mean()
        baho = "SHUBHALI" if b > 0.85 else "OK"
        print(f"  {ustun:<12} {b:>14.4f} {baho:<12}")
    print("  'qaytarish' kechikkandan KEYIN yoziladi -> olib tashlanadi")

    print("\n=== 3. Validatsiya strategiyasi ===")
    belgilar = ["summa", "masofa", "ogirlik", "soat", "kod"]
    oddiy = cross_val_score(model(), df[belgilar], y,
                            cv=StratifiedKFold(5, shuffle=True,
                                               random_state=0),
                            scoring="roc_auc")
    guruhli = cross_val_score(model(), df[belgilar], y, cv=cv_guruh,
                              groups=guruh, scoring="roc_auc")
    print(f"  StratifiedKFold: {oddiy.mean():.4f} (+-{oddiy.std():.4f})")
    print(f"  GroupKFold:      {guruhli.mean():.4f} (+-{guruhli.std():.4f})")
    print(f"  farq: {oddiy.mean() - guruhli.mean():+.4f}")
    print("  sabab: 'kod' mijozga xos - tasodifiy bo'linishda model")
    print("  mijozning o'quvdagi qatorlaridan uning testdagi qatorlarini")
    print("  taniydi (guruh leakage i)")
    print("  QAROR: GroupKFold (bir mijozning ko'p buyurtmasi bor)")

    print("\n=== 4. Bazaviy natija ===")
    dummy = cross_val_score(DummyClassifier(strategy="prior"), df[belgilar], y,
                            cv=cv_guruh, groups=guruh,
                            scoring="roc_auc").mean()
    print(f"  {'model':<26} {'CV ROC AUC':>12}")
    print(f"  {'Dummy':<26} {dummy:>12.4f}")
    print(f"  {'HistGB (xom belgilar)':<26} {guruhli.mean():>12.4f}")
    print(f"  kechikish ulushi: {y.mean():.2%}")
    print("  ⭐ Validatsiya strategiyasi - eng erta va eng muhim qaror")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Audit ===
  22865 qator, 1400 mijoz
  ustun        dtype              noyob  yo_qolgan
  mijoz        int64               1400          0
  kod          float64             1399          0
  sana         datetime64[us]       400          0
  hudud        str                    4          0
  summa        float64            21776          0
  masofa       float64            21776          0
  ogirlik      float64            21776          0
  soat         int64                 24          0
  qaytarish    float64            12186          0
  kechikdi     int64                  2          0
  duplikatlar: 1089 (4.8%) - TOZALANADI
  tozalangandan keyin: 21776 qator

=== 2. Leakage tekshiruvi (yakka belgi) ===
  belgi          yakka CV AUC baho
  summa                0.5656 OK
  masofa               0.6159 OK
  ogirlik              0.5866 OK
  soat                 0.5353 OK
  kod                  0.5170 OK
  qaytarish            1.0000 SHUBHALI
  'qaytarish' kechikkandan KEYIN yoziladi -> olib tashlanadi

=== 3. Validatsiya strategiyasi ===
  StratifiedKFold: 0.7163 (+-0.0045)
  GroupKFold:      0.6937 (+-0.0067)
  farq: +0.0227
  sabab: 'kod' mijozga xos - tasodifiy bo'linishda model
  mijozning o'quvdagi qatorlaridan uning testdagi qatorlarini
  taniydi (guruh leakage i)
  QAROR: GroupKFold (bir mijozning ko'p buyurtmasi bor)

=== 4. Bazaviy natija ===
  model                        CV ROC AUC
  Dummy                            0.5000
  HistGB (xom belgilar)            0.6937
  kechikish ulushi: 55.96%
  ⭐ Validatsiya strategiyasi - eng erta va eng muhim qaror

Nima ko'rsatdi: 2.1-bo'lim.

Misol 2 — Belgilar guruhlarini bosqichma-bosqich qo'shish

python
"""5-qadam: har guruhning hissasini o'lchash (real pandas/sklearn)."""

import numpy as np
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.model_selection import GroupKFold, cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder


def yarat(seed: int = 42, mijozlar: int = 1400) -> pd.DataFrame:
    rng = np.random.default_rng(seed)
    qatorlar = []
    boshlanish = pd.Timestamp("2025-01-01")
    for m in range(mijozlar):
        imzo = rng.normal(0, 1)
        kod = round(float(rng.random()), 6)
        hudud = rng.choice(["toshkent", "samarqand", "buxoro", "fargona"])
        hq = {"toshkent": 0.0, "samarqand": 0.5, "buxoro": 1.0,
              "fargona": 0.3}[hudud]
        odatiy = rng.lognormal(12.0, 0.45)
        for _ in range(int(rng.integers(8, 24))):
            sana = boshlanish + pd.Timedelta(days=int(rng.integers(0, 400)))
            summa = odatiy * rng.lognormal(0, 0.5)
            masofa = rng.gamma(2, 110)
            ogirlik = rng.gamma(2, 5)
            soat = int(rng.integers(0, 24))
            kuch = (-1.9 + hq
                    + 3.0 * (ogirlik / masofa > 0.085)
                    + 0.9 * (7 <= soat <= 10)
                    + 1.0 * (sana.dayofweek >= 5)
                    + 1.6 * (summa > 1.15 * odatiy)
                    + 1.8 * imzo)
            qatorlar.append([m, kod, sana, hudud, summa, masofa, ogirlik,
                             soat, int(rng.random() < 1 / (1 + np.exp(-kuch)))])
    return pd.DataFrame(qatorlar, columns=["mijoz", "kod", "sana", "hudud",
                                           "summa", "masofa", "ogirlik",
                                           "soat", "kechikdi"]).sort_values(
        ["mijoz", "sana"]).reset_index(drop=True)


def belgilar_qosh(df: pd.DataFrame) -> pd.DataFrame:
    df = df.copy()
    # nisbatlar
    df["ogirlik_masofaga"] = df["ogirlik"] / df["masofa"].clip(lower=1)
    df["summa_ogirlikka"] = df["summa"] / df["ogirlik"].clip(lower=0.1)
    # vaqt
    d = df["sana"].dt
    df["hafta_kuni"] = d.dayofweek
    df["oy"] = d.month
    df["hafta_oxiri"] = (d.dayofweek >= 5).astype(int)
    df["tigiz_soat"] = (((df["soat"] >= 7) & (df["soat"] <= 10))
                        | ((df["soat"] >= 17) & (df["soat"] <= 19))).astype(int)
    df["soat_sin"] = np.sin(2 * np.pi * df["soat"] / 24)
    df["soat_cos"] = np.cos(2 * np.pi * df["soat"] / 24)
    # agregatsiya: FAQAT o'tmishdan (shift bilan) - leakage yo'q
    g = df.groupby("mijoz")
    df["summa_oynasi"] = g["summa"].transform(
        lambda s: s.shift(1).rolling(10, min_periods=2).mean())
    df["summa_nisbati"] = df["summa"] / df["summa_oynasi"]
    df["mijoz_buyurtmalari"] = g["summa"].transform("count")
    return df


XOM = ["summa", "masofa", "ogirlik", "soat", "kod"]
NISBATLAR = ["ogirlik_masofaga", "summa_ogirlikka"]
VAQT = ["hafta_kuni", "oy", "hafta_oxiri", "tigiz_soat", "soat_sin",
        "soat_cos"]
AGREGAT = ["summa_oynasi", "summa_nisbati", "mijoz_buyurtmalari"]


def main() -> None:
    df = belgilar_qosh(yarat())
    y = df["kechikdi"].to_numpy()
    guruh = df["mijoz"].to_numpy()
    cv = GroupKFold(5)

    def baho(sonli, kategoriya=()):
        tayyor = ColumnTransformer(
            [("s", "passthrough", list(sonli))]
            + ([("k", OneHotEncoder(handle_unknown="ignore",
                                    sparse_output=False), list(kategoriya))]
               if kategoriya else []))
        quvur = Pipeline([("t", tayyor),
                          ("m", HistGradientBoostingClassifier(
                              learning_rate=0.1, max_iter=200,
                              random_state=0))])
        b = cross_val_score(quvur, df, y, cv=cv, groups=guruh,
                            scoring="roc_auc")
        return b.mean(), b.std(), len(sonli) + len(kategoriya)

    print("=== 1. Belgilar guruhlarini ketma-ket qo'shish ===")
    guruhlar = {
        "bazaviy (xom)": (XOM, ()),
        "+ kategoriya": (XOM, ["hudud"]),
        "+ nisbatlar": (XOM + NISBATLAR, ["hudud"]),
        "+ vaqt": (XOM + NISBATLAR + VAQT, ["hudud"]),
        "+ agregatsiya": (XOM + NISBATLAR + VAQT + AGREGAT, ["hudud"]),
    }
    print(f"  {'guruh':<18} {'belgilar':>9} {'CV AUC':>9} {'std':>8} "
          f"{'o_sish':>9}")
    oldingi = None
    natijalar = {}
    for nom, (sonli, kat) in guruhlar.items():
        o, s, n = baho(sonli, kat)
        natijalar[nom] = (o, s, n)
        osish = "-" if oldingi is None else f"{o - oldingi:+.4f}"
        print(f"  {nom:<18} {n:>9} {o:>9.4f} {s:>8.4f} {osish:>9}")
        oldingi = o

    print("\n=== 2. Har guruhning hissasi CV std bilan ===")
    nomlar = list(guruhlar)
    print(f"  {'guruh':<18} {'o_sish':>9} {'std':>8} {'qaror':<22}")
    qarorlar = []
    for i in range(1, len(nomlar)):
        oldin = natijalar[nomlar[i - 1]][0]
        hozir, std, _ = natijalar[nomlar[i]]
        osish = hozir - oldin
        otdi = osish > std
        qarorlar.append(otdi)
        qaror = "QOLDIRISH" if otdi else "shovqin (std dan kam)"
        print(f"  {nomlar[i]:<18} {osish:>+9.4f} {std:>8.4f} {qaror:<22}")

    print("\n=== 3. Har guruh ALOHIDA (xom bilan birga) ===")
    alohida = {
        "xom": (XOM, ()),
        "xom + kategoriya": (XOM, ["hudud"]),
        "xom + nisbatlar": (XOM + NISBATLAR, ()),
        "xom + vaqt": (XOM + VAQT, ()),
        "xom + agregatsiya": (XOM + AGREGAT, ()),
    }
    asos = natijalar["bazaviy (xom)"][0]
    print(f"  {'variant':<20} {'CV AUC':>9} {'xomdan o_sish':>16}")
    for nom, (sonli, kat) in alohida.items():
        o, _, _ = baho(sonli, kat)
        print(f"  {nom:<20} {o:>9.4f} {o - asos:>+16.4f}")

    print("\n=== 4. To'xtash qarori ===")
    eng_yaxshi = max(natijalar, key=lambda k: natijalar[k][0])
    o, s, n = natijalar[eng_yaxshi]
    print(f"  eng yaxshi to'plam: {eng_yaxshi} ({n} belgi)")
    print(f"  CV AUC {o:.4f} (+-{s:.4f})")
    print(f"  bazaviydan o'sish: {o - asos:+.4f}")
    print(f"  std dan yuqori foyda bergan guruhlar: {sum(qarorlar)}/"
          f"{len(qarorlar)}")
    if not qarorlar[-1]:
        print("  so'nggi guruh std dan kam berdi -> TO'XTASH signali")
    print("  ⭐ Guruh bo'lib qo'shish natijani tushunarli qiladi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Belgilar guruhlarini ketma-ket qo'shish ===
  guruh               belgilar    CV AUC      std    o_sish
  bazaviy (xom)              5    0.6988   0.0057         -
  + kategoriya               6    0.6982   0.0063   -0.0005
  + nisbatlar                8    0.7088   0.0067   +0.0106
  + vaqt                    14    0.7238   0.0061   +0.0150
  + agregatsiya             17    0.7339   0.0048   +0.0101

=== 2. Har guruhning hissasi CV std bilan ===
  guruh                 o_sish      std qaror
  + kategoriya         -0.0005   0.0063 shovqin (std dan kam)
  + nisbatlar          +0.0106   0.0067 QOLDIRISH
  + vaqt               +0.0150   0.0061 QOLDIRISH
  + agregatsiya        +0.0101   0.0048 QOLDIRISH

=== 3. Har guruh ALOHIDA (xom bilan birga) ===
  variant                 CV AUC    xomdan o_sish
  xom                     0.6988          +0.0000
  xom + kategoriya        0.6982          -0.0005
  xom + nisbatlar         0.7036          +0.0049
  xom + vaqt              0.7127          +0.0139
  xom + agregatsiya       0.7038          +0.0050

=== 4. To'xtash qarori ===
  eng yaxshi to'plam: + agregatsiya (17 belgi)
  CV AUC 0.7339 (+-0.0048)
  bazaviydan o'sish: +0.0351
  std dan yuqori foyda bergan guruhlar: 3/4
  ⭐ Guruh bo'lib qo'shish natijani tushunarli qiladi

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

Misol 3 — Tanlash, sozlash va yakuniy baho

python
"""6-8 qadamlar (real pandas/sklearn)."""

import numpy as np
import pandas as pd
from scipy.stats import randint, uniform
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.inspection import permutation_importance
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import (GroupKFold, GroupShuffleSplit,
                                     RandomizedSearchCV, cross_val_score)
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder


def yarat(seed: int = 42, mijozlar: int = 1100) -> pd.DataFrame:
    rng = np.random.default_rng(seed)
    qatorlar = []
    boshlanish = pd.Timestamp("2025-01-01")
    for m in range(mijozlar):
        imzo = rng.normal(0, 1)
        kod = round(float(rng.random()), 6)
        hudud = rng.choice(["toshkent", "samarqand", "buxoro", "fargona"])
        hq = {"toshkent": 0.0, "samarqand": 0.5, "buxoro": 1.0,
              "fargona": 0.3}[hudud]
        odatiy = rng.lognormal(12.0, 0.45)
        for _ in range(int(rng.integers(8, 24))):
            sana = boshlanish + pd.Timedelta(days=int(rng.integers(0, 400)))
            summa = odatiy * rng.lognormal(0, 0.5)
            masofa = rng.gamma(2, 110)
            ogirlik = rng.gamma(2, 5)
            soat = int(rng.integers(0, 24))
            kuch = (-1.9 + hq
                    + 3.0 * (ogirlik / masofa > 0.085)
                    + 0.9 * (7 <= soat <= 10)
                    + 1.0 * (sana.dayofweek >= 5)
                    + 1.6 * (summa > 1.15 * odatiy)
                    + 1.8 * imzo)
            qatorlar.append([m, kod, sana, hudud, summa, masofa, ogirlik,
                             soat, int(rng.random() < 1 / (1 + np.exp(-kuch)))])
    return pd.DataFrame(qatorlar, columns=["mijoz", "kod", "sana", "hudud",
                                           "summa", "masofa", "ogirlik",
                                           "soat", "kechikdi"]).sort_values(
        ["mijoz", "sana"]).reset_index(drop=True)


def belgilar_qosh(df: pd.DataFrame) -> pd.DataFrame:
    df = df.copy()
    df["ogirlik_masofaga"] = df["ogirlik"] / df["masofa"].clip(lower=1)
    df["summa_ogirlikka"] = df["summa"] / df["ogirlik"].clip(lower=0.1)
    d = df["sana"].dt
    df["hafta_kuni"] = d.dayofweek
    df["oy"] = d.month
    df["hafta_oxiri"] = (d.dayofweek >= 5).astype(int)
    df["tigiz_soat"] = (((df["soat"] >= 7) & (df["soat"] <= 10))
                        | ((df["soat"] >= 17) & (df["soat"] <= 19))).astype(int)
    df["soat_sin"] = np.sin(2 * np.pi * df["soat"] / 24)
    df["soat_cos"] = np.cos(2 * np.pi * df["soat"] / 24)
    g = df.groupby("mijoz")
    df["summa_oynasi"] = g["summa"].transform(
        lambda s: s.shift(1).rolling(10, min_periods=2).mean())
    df["summa_nisbati"] = df["summa"] / df["summa_oynasi"]
    df["mijoz_buyurtmalari"] = g["summa"].transform("count")
    return df


SONLI = ["summa", "masofa", "ogirlik", "soat", "kod", "ogirlik_masofaga",
         "summa_ogirlikka", "hafta_kuni", "oy", "hafta_oxiri", "tigiz_soat",
         "soat_sin", "soat_cos", "summa_oynasi", "summa_nisbati",
         "mijoz_buyurtmalari"]
KATEGORIYA = ["hudud"]


def quvur_yarat(sonli_ustunlar, **model_kw):
    tayyor = ColumnTransformer([
        ("s", "passthrough", list(sonli_ustunlar)),
        ("k", OneHotEncoder(handle_unknown="ignore", sparse_output=False),
         KATEGORIYA)])
    return Pipeline([("t", tayyor),
                     ("m", HistGradientBoostingClassifier(
                         learning_rate=0.1, max_iter=250, random_state=0,
                         **model_kw))])


def main() -> None:
    df = belgilar_qosh(yarat())
    y = df["kechikdi"].to_numpy()
    guruh = df["mijoz"].to_numpy()
    cv = GroupKFold(4)

    print("=== 1. O'quv va test ajratish (guruh bo'yicha) ===")
    gss = GroupShuffleSplit(n_splits=1, test_size=0.25, random_state=0)
    tr, te = next(gss.split(df, y, groups=guruh))
    print(f"  o'quv {len(tr)} qator ({len(np.unique(guruh[tr]))} mijoz)")
    print(f"  test  {len(te)} qator ({len(np.unique(guruh[te]))} mijoz)")
    print(f"  kesishgan mijozlar: "
          f"{len(np.intersect1d(guruh[tr], guruh[te]))}")
    dftr, ytr, gtr = df.iloc[tr], y[tr], guruh[tr]
    dfte, yte = df.iloc[te], y[te]

    print("\n=== 2. Belgi tanlash (permutation, test qismida EMAS) ===")
    ichki = GroupShuffleSplit(n_splits=1, test_size=0.25, random_state=1)
    i_tr, i_val = next(ichki.split(dftr, ytr, groups=gtr))
    nomlar = SONLI + KATEGORIYA
    ustunlar = dftr[nomlar]
    quvur = quvur_yarat(SONLI).fit(dftr.iloc[i_tr], ytr[i_tr])
    r = permutation_importance(quvur, ustunlar.iloc[i_val], ytr[i_val],
                               n_repeats=8, scoring="roc_auc",
                               random_state=0, n_jobs=1)
    tartib = np.argsort(-r.importances_mean)
    print(f"  {'belgi':<20} {'muhimlik':>11}")
    for i in tartib[:6]:
        print(f"  {nomlar[i]:<20} {r.importances_mean[i]:>+11.4f}")
    print("  eng past uchtasi:")
    for i in tartib[-3:]:
        print(f"  {nomlar[i]:<20} {r.importances_mean[i]:>+11.4f}")
    print(f"  muhimligi <= 0 bo'lganlar: "
          f"{int((r.importances_mean <= 0).sum())} ta")

    print("\n=== 3. Tanlangan to'plamlarni CV da solishtirish ===")
    to_plamlar = {}
    for chegara, nom in [(-1.0, "hammasi"), (0.0, "muhimlik > 0"),
                         (0.002, "muhimlik > 0.002")]:
        tanlangan = [nomlar[i] for i in range(len(nomlar))
                     if r.importances_mean[i] > chegara
                     and nomlar[i] != "hudud"]
        if len(tanlangan) < 3:
            continue
        to_plamlar[nom] = tanlangan
        b = cross_val_score(quvur_yarat(tanlangan), dftr, ytr, cv=cv,
                            groups=gtr, scoring="roc_auc")
        print(f"  {nom:<20} {len(tanlangan) + 1:>3} belgi, "
              f"CV {b.mean():.4f} (+-{b.std():.4f})")

    print("\n=== 4. Sozlash va yakuniy baho ===")
    tanlangan = to_plamlar["muhimlik > 0"]
    taqsimot = {"m__max_depth": randint(2, 9),
                "m__min_samples_leaf": randint(10, 120),
                "m__l2_regularization": uniform(0, 5)}
    q = RandomizedSearchCV(quvur_yarat(tanlangan), taqsimot, n_iter=12, cv=cv,
                           scoring="roc_auc", random_state=0,
                           n_jobs=1).fit(dftr, ytr, groups=gtr)
    qisqa = {k.replace("m__", ""): (round(float(v), 3)
                                    if isinstance(v, (float, np.floating))
                                    else int(v))
             for k, v in sorted(q.best_params_.items())}
    print(f"  eng yaxshi parametrlar: {qisqa}")
    print(f"  ichki CV: {q.best_score_:.4f}")
    test = roc_auc_score(yte, q.predict_proba(dfte)[:, 1])
    print(f"  TEST (bir marta): {test:.4f}")
    print(f"  optimizm: {q.best_score_ - test:+.4f}")
    print("  ⭐ Test to'plami faqat BIR MARTA ishlatiladi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. O'quv va test ajratish (guruh bo'yicha) ===
  o'quv 12820 qator (825 mijoz)
  test  4215 qator (275 mijoz)
  kesishgan mijozlar: 0

=== 2. Belgi tanlash (permutation, test qismida EMAS) ===
  belgi                   muhimlik
  ogirlik_masofaga         +0.1435
  hafta_kuni               +0.0116
  summa_nisbati            +0.0116
  summa                    +0.0059
  soat                     +0.0051
  hudud                    +0.0041
  eng past uchtasi:
  ogirlik                  -0.0041
  mijoz_buyurtmalari       -0.0044
  kod                      -0.0062
  muhimligi <= 0 bo'lganlar: 6 ta

=== 3. Tanlangan to'plamlarni CV da solishtirish ===
  hammasi               17 belgi, CV 0.7072 (+-0.0169)
  muhimlik > 0          11 belgi, CV 0.7128 (+-0.0141)
  muhimlik > 0.002       9 belgi, CV 0.7136 (+-0.0107)

=== 4. Sozlash va yakuniy baho ===
  eng yaxshi parametrlar: {'l2_regularization': 1.488, 'max_depth': 2, 'min_samples_leaf': 98}
  ichki CV: 0.7386
  TEST (bir marta): 0.7347
  optimizm: +0.0039
  ⭐ Test to'plami faqat BIR MARTA ishlatiladi

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

Misol 4 — Yakuniy audit va paket

python
"""9-10 qadamlar: audit, lug'at va saqlash (real pandas/sklearn)."""

import io
import pickle

import numpy as np
import pandas as pd
import sklearn
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import (GroupKFold, GroupShuffleSplit,
                                     cross_val_score)
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder


def yarat(seed: int = 42, mijozlar: int = 900) -> pd.DataFrame:
    rng = np.random.default_rng(seed)
    qatorlar = []
    boshlanish = pd.Timestamp("2025-01-01")
    for m in range(mijozlar):
        imzo = rng.normal(0, 1)
        hudud = rng.choice(["toshkent", "samarqand", "buxoro", "fargona"])
        hq = {"toshkent": 0.0, "samarqand": 0.5, "buxoro": 1.0,
              "fargona": 0.3}[hudud]
        odatiy = rng.lognormal(12.0, 0.45)
        for _ in range(int(rng.integers(8, 24))):
            sana = boshlanish + pd.Timedelta(days=int(rng.integers(0, 400)))
            summa = odatiy * rng.lognormal(0, 0.5)
            masofa = rng.gamma(2, 110)
            ogirlik = rng.gamma(2, 5)
            soat = int(rng.integers(0, 24))
            kuch = (-1.9 + hq
                    + 3.0 * (ogirlik / masofa > 0.085)
                    + 0.9 * (7 <= soat <= 10)
                    + 1.0 * (sana.dayofweek >= 5)
                    + 1.6 * (summa > 1.15 * odatiy)
                    + 1.8 * imzo)
            qatorlar.append([m, sana, hudud, summa, masofa, ogirlik, soat,
                             int(rng.random() < 1 / (1 + np.exp(-kuch)))])
    return pd.DataFrame(qatorlar, columns=["mijoz", "sana", "hudud", "summa",
                                           "masofa", "ogirlik", "soat",
                                           "kechikdi"]).sort_values(
        ["mijoz", "sana"]).reset_index(drop=True)


def belgilar_qosh(df: pd.DataFrame) -> pd.DataFrame:
    df = df.copy()
    df["ogirlik_masofaga"] = df["ogirlik"] / df["masofa"].clip(lower=1)
    df["tigiz_soat"] = (((df["soat"] >= 7) & (df["soat"] <= 10))
                        | ((df["soat"] >= 17) & (df["soat"] <= 19))).astype(int)
    df["hafta_oxiri"] = (df["sana"].dt.dayofweek >= 5).astype(int)
    g = df.groupby("mijoz")
    df["summa_oynasi"] = g["summa"].transform(
        lambda s: s.shift(1).rolling(10, min_periods=2).mean())
    df["summa_nisbati"] = df["summa"] / df["summa_oynasi"]
    return df


def hajm_kb(obyekt) -> float:
    b = io.BytesIO()
    pickle.dump(obyekt, b, protocol=pickle.HIGHEST_PROTOCOL)
    return b.tell() / 1024


def main() -> None:
    df = belgilar_qosh(yarat())
    y = df["kechikdi"].to_numpy()
    guruh = df["mijoz"].to_numpy()
    sonli = ["summa", "masofa", "ogirlik", "soat", "ogirlik_masofaga",
             "tigiz_soat", "hafta_oxiri", "summa_oynasi", "summa_nisbati"]
    kategoriya = ["hudud"]

    tayyor = ColumnTransformer([
        ("s", "passthrough", sonli),
        ("k", OneHotEncoder(handle_unknown="ignore", sparse_output=False),
         kategoriya)], verbose_feature_names_out=False)
    quvur = Pipeline([("t", tayyor),
                      ("m", HistGradientBoostingClassifier(
                          learning_rate=0.1, max_iter=250, max_depth=4,
                          min_samples_leaf=40, random_state=0))])

    gss = GroupShuffleSplit(n_splits=1, test_size=0.25, random_state=0)
    tr, te = next(gss.split(df, y, groups=guruh))
    quvur.fit(df.iloc[tr], y[tr])
    cv_ball = cross_val_score(quvur, df.iloc[tr], y[tr], cv=GroupKFold(4),
                              groups=guruh[tr], scoring="roc_auc")
    test_ball = roc_auc_score(y[te], quvur.predict_proba(df.iloc[te])[:, 1])

    print("=== 1. Yakuniy audit ===")
    tekshiruvlar = []
    tekshiruvlar.append(("Duplikatlar", f"{int(df.duplicated().sum())}",
                         "OK" if df.duplicated().sum() == 0 else "TOZALANSIN"))
    eng_kuchli, eng_nom = 0.0, ""
    for ustun in sonli:
        b = cross_val_score(HistGradientBoostingClassifier(
            learning_rate=0.1, max_iter=100, random_state=0),
            df.iloc[tr][[ustun]], y[tr], cv=GroupKFold(4), groups=guruh[tr],
            scoring="roc_auc").mean()
        if b > eng_kuchli:
            eng_kuchli, eng_nom = b, ustun
    tekshiruvlar.append(("Eng kuchli yakka belgi", f"{eng_kuchli:.4f}",
                         "SHUBHALI" if eng_kuchli > 0.85 else "OK"))
    tekshiruvlar.append(("Validatsiya strategiyasi", "GroupKFold", "OK"))
    tekshiruvlar.append(("Tayyorlash Pipeline ichida", "ha", "OK"))
    farq = cv_ball.mean() - test_ball
    tekshiruvlar.append(("CV va test farqi", f"{farq:+.4f}",
                         "SHUBHALI" if abs(farq) > 0.05 else "OK"))
    print(f"  {'tekshiruv':<28} {'qiymat':>12} {'holat':<12}")
    for nom, qiymat, holat in tekshiruvlar:
        print(f"  {nom:<28} {qiymat:>12} {holat:<12}")
    print(f"  (eng kuchli yakka belgi: {eng_nom})")

    print("\n=== 2. Belgilar lug'ati ===")
    lugat = {
        "summa": ("buyurtma summasi", "buyurtma vaqtida"),
        "masofa": ("yetkazish masofasi (km)", "buyurtma vaqtida"),
        "ogirlik": ("yuk og'irligi (kg)", "buyurtma vaqtida"),
        "soat": ("buyurtma soati", "buyurtma vaqtida"),
        "ogirlik_masofaga": ("ogirlik / masofa", "buyurtma vaqtida"),
        "tigiz_soat": ("tig'iz soatlar bayrog'i", "buyurtma vaqtida"),
        "hafta_oxiri": ("shanba/yakshanba", "buyurtma vaqtida"),
        "summa_oynasi": ("oldingi 10 buyurtma o'rtachasi (shift)",
                         "buyurtma vaqtida"),
        "summa_nisbati": ("summa / oyna o'rtachasi", "buyurtma vaqtida"),
        "hudud": ("yetkazish hududi", "buyurtma vaqtida"),
    }
    print(f"  {'belgi':<18} {'ta_rif':<40} {'qachon ma_lum':<18}")
    for nom, (tarif, qachon) in lugat.items():
        print(f"  {nom:<18} {tarif:<40} {qachon:<18}")

    print("\n=== 3. Ishlab chiqarish paketi ===")
    paket = {
        "quvur": quvur,
        "belgilar": sonli + kategoriya,
        "lugat": {k: v[0] for k, v in lugat.items()},
        "metrika": {"cv_auc": round(float(cv_ball.mean()), 4),
                    "cv_std": round(float(cv_ball.std()), 4),
                    "test_auc": round(float(test_ball), 4)},
        "validatsiya": "GroupKFold(4) mijoz bo'yicha",
        "versiyalar": {"sklearn": sklearn.__version__,
                       "numpy": np.__version__, "pandas": pd.__version__},
        "sana": "2026-09-21",
    }
    print(f"  kalitlar: {sorted(paket)}")
    print(f"  metrika: {paket['metrika']}")
    print(f"  paket hajmi: {hajm_kb(paket):.1f} KB")

    print("\n=== 4. Ishlab chiqarishda tekshirish ===")
    yangi = df.iloc[te].head(3).copy()
    print(f"  ustunlar mos: "
          f"{all(c in yangi.columns for c in paket['belgilar'])}")
    p = quvur.predict_proba(yangi)[:, 1]
    print(f"  bashoratlar: {np.round(p, 4).tolist()}")
    yangi2 = yangi.copy()
    yangi2["hudud"] = "andijon"                    # o'quvda yo'q kategoriya
    print(f"  yangi hudud bilan: "
          f"{np.round(quvur.predict_proba(yangi2)[:, 1], 4).tolist()}")
    yangi3 = yangi.copy()
    yangi3.loc[:, "summa_oynasi"] = np.nan         # birinchi buyurtma
    yangi3.loc[:, "summa_nisbati"] = np.nan
    print(f"  NaN bilan (birinchi buyurtma): "
          f"{np.round(quvur.predict_proba(yangi3)[:, 1], 4).tolist()}")
    print("  ⭐ Lug'at + paket + audit = topshirishga tayyor natija")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Yakuniy audit ===
  tekshiruv                          qiymat holat
  Duplikatlar                             0 OK
  Eng kuchli yakka belgi             0.6583 OK
  Validatsiya strategiyasi       GroupKFold OK
  Tayyorlash Pipeline ichida             ha OK
  CV va test farqi                  +0.0015 OK
  (eng kuchli yakka belgi: ogirlik_masofaga)

=== 2. Belgilar lug'ati ===
  belgi              ta_rif                                   qachon ma_lum
  summa              buyurtma summasi                         buyurtma vaqtida
  masofa             yetkazish masofasi (km)                  buyurtma vaqtida
  ogirlik            yuk og'irligi (kg)                       buyurtma vaqtida
  soat               buyurtma soati                           buyurtma vaqtida
  ogirlik_masofaga   ogirlik / masofa                         buyurtma vaqtida
  tigiz_soat         tig'iz soatlar bayrog'i                  buyurtma vaqtida
  hafta_oxiri        shanba/yakshanba                         buyurtma vaqtida
  summa_oynasi       oldingi 10 buyurtma o'rtachasi (shift)   buyurtma vaqtida
  summa_nisbati      summa / oyna o'rtachasi                  buyurtma vaqtida
  hudud              yetkazish hududi                         buyurtma vaqtida

=== 3. Ishlab chiqarish paketi ===
  kalitlar: ['belgilar', 'lugat', 'metrika', 'quvur', 'sana', 'validatsiya', 'versiyalar']
  metrika: {'cv_auc': 0.7165, 'cv_std': 0.0038, 'test_auc': 0.715}
  paket hajmi: 94.5 KB

=== 4. Ishlab chiqarishda tekshirish ===
  ustunlar mos: True
  bashoratlar: [0.4132, 0.5269, 0.9326]
  yangi hudud bilan: [0.394, 0.5077, 0.9326]
  NaN bilan (birinchi buyurtma): [0.4132, 0.5269, 0.8611]
  ⭐ Lug'at + paket + audit = topshirishga tayyor natija

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


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

Noto'g'ri fikr To'g'risi
"Ko'proq belgi — yaxshiroq" Murakkablik bepul emas
"Belgilarni bitta-bitta sinash kerak" Guruh bo'lib samaraliroq
"Validatsiyani keyinroq tanlaymiz" Birinchi qaror
"To'xtash o'z-o'zidan bo'ladi" Qoidani oldindan belgilang
"Audit ixtiyoriy" Majburiy qadam
"Lug'at keraksiz byurokratiya" Jamoaviy ishning asosi
"Test to'plamini bir necha marta" Bir marta
"Yaxshilanish har doim foyda" CV std bilan solishtiring

6. Keng tarqalgan xatolar va yechimlari

1. Validatsiyani kech tanlash

python
# belgilar yaratib bo'lgandan keyin GroupKFold kerakligini bilib qolish # ⚠️
# 3-qadamda strategiyani aniqlang                                       # ✅

2. Belgilarni bitta-bitta qo'shish

python
for belgi in 40_ta_belgi: cross_val_score(...)    # 40 ta CV            # ⚠️
for guruh in 5_ta_guruh: cross_val_score(...)     # 5 ta CV             # ✅

3. To'xtash qoidasisiz

python
# "yana bitta belgi sinab ko'raman" (30-marta)                          # ⚠️
# "2 guruh ketma-ket std dan kam bersa - to'xtayman"                    # ✅

4. Auditni o'tkazib yuborish

python
# "CV 0.84, tayyor"                                                     # ⚠️
# yakka belgi AUC, CV/test farqi, leakage ro'yxati                      # ✅

5. Lug'atsiz topshirish

python
# 40 ta belgi, nomlari x_1..x_40                                        # ⚠️
# har belgi uchun ta'rif, manba, "qachon ma'lum"                        # ✅

6. Testni ko'p marta ishlatish

python
for variant in variantlar: print(roc_auc_score(yte, ...))               # ⚠️
# CV da tanlang, testda bir marta                                       # ✅

7. Ishlab chiqarishda hisoblanmaydigan belgi

python
df["30_kunlik_oyna"] = ...    # real vaqtda hisoblanadimi?              # ⚠️
# har belgi uchun hisoblash kodini/so'rovini yozing                     # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 17.1-17.9-darslar (o'tilgan): Butun qism
  • 12.9-dars (o'tilgan): Leakage
  • 15.13-dars (o'tilgan): Ishlab chiqarish
  • 18-qism: Model baholash va sozlash
  • 19-qism: scikit-learn to'liq

8. Eng yaxshi amaliyotlar

  1. Auditdan boshlang.

  2. Validatsiyani birinchi tanlang.

  3. Guruh bo'lib qo'shing.

  4. CV std bilan solishtiring.

  5. To'xtash qoidasini belgilang.

  6. Yakuniy auditni o'tkazing.

  7. Belgilar lug'atini yozing.

  8. Testni bir marta ishlating.


9. Amaliy topshiriq

Vazifa 1: Bashorat qiling

python
1.  # oqimning birinchi qadami?
2.  # eng muhim erta qaror?
3.  # belgilar qanday qo'shiladi?
4.  # yaxshilanish nima bilan solishtiriladi?
5.  # to'xtash qoidasi qachon belgilanadi?
6.  # odatiy foyda taqsimoti?
7.  # shubhali belgi signali?
8.  # belgi nomlari nima aytadi?
9.  # lug'atda nima bo'ladi?
10. # test necha marta?
11. # paketda nima saqlanadi?
12. # ishlab chiqarishda nima tekshiriladi?
Javoblar
  1. Audit
  2. Validatsiya strategiyasi
  3. Guruh bo'lib
  4. CV standart og'ishi
  5. Oldindan
  6. Birinchi 3-5 guruh — 80%
  7. Yakka AUC > 0.85
  8. "yopilish", "qaytarish" — leakage
  9. Ta'rif, manba, "qachon ma'lum"
  10. Bir marta
  11. Pipeline, belgilar, lug'at, metrika, versiyalar
  12. Ustunlar, yangi kategoriya, NaN ulushi

Vazifa 2: Xatolarni tuzating

python
1.  # belgilar yaratilgandan keyin GroupKFold kerakligini bilish

2.  for belgi in 40_ta: cross_val_score(...)

3.  # "yana bitta belgi sinayman" (30-marta)

4.  # "CV 0.84, tayyor"

5.  for variant in variantlar: print(roc_auc_score(yte, ...))
Javoblar
python
1.  # validatsiya strategiyasini 3-qadamda aniqlang

2.  for guruh in 5_ta_guruh: cross_val_score(...)

3.  # to'xtash qoidasini oldindan belgilang

4.  # yakuniy audit: yakka AUC, CV/test farqi, leakage

5.  # CV da tanlang, testda bir marta

Vazifa 3: Audit

Modellang:

  1. Ustunlar
  2. Leakage
  3. Validatsiya
  4. Bazaviy

Vazifa 4: Guruhlar

Modellang:

  1. Bosqichma-bosqich
  2. CV std bilan
  3. Alohida hissa
  4. To'xtash

Vazifa 5: Tanlash

Modellang:

  1. Ajratish
  2. Permutation
  3. To'plamlar
  4. Sozlash va test

Vazifa 6: Paket

Modellang:

  1. Audit
  2. Lug'at
  3. Paket
  4. Ishlab chiqarish

Vazifa 7: O'ylash

Jamoa 180 ta belgi yaratdi, lekin 12 tasi natijaning 95% ini beradi. Qolgan 168 tasi bilan nima qilish kerak?

Javob

Qisqa javob: ularni o'chirmang, lekin modelga qo'shmang. Ular ikki qiymatga ega: kelajakdagi tajribalar uchun material va hujjatlashtirilgan salbiy natija.

1. Nega modelga qo'shmaslik kerak

Narx Izoh
Hisoblash Har belgi ishlab chiqarishda hisoblanadi
Kechikish 180 belgi 12 tadan sekinroq
Drift xavfi Har belgi buzilishi mumkin (15.13)
Talqin 180 belgili modelni tekshirib bo'lmaydi
Xatolar Ko'proq kod — ko'proq xato

Teng aniqlikda 12 ta belgi har doim afzal.

2. Nega o'chirmaslik kerak

  1. Kelajakdagi tajribalar: ma'lumot o'zgarganda ular foydali bo'lishi mumkin
  2. Boshqa vazifalar: bu loyihada foydasiz belgi boshqasida ishlashi mumkin
  3. Hujjatlashtirilgan bilim: "biz buni sinadik, ishlamadi" — qimmatli ma'lumot
  4. Kod bazasi: belgi yaratish funksiyalari qayta ishlatiladi

3. To'g'ri saqlash usuli

belgilar/
  asosiy.py         - 12 ta ishlatiladigan belgi (ishlab chiqarishda)
  tajriba.py        - 168 ta sinalgan belgi (ishlatilmaydi)
  NATIJALAR.md      - har guruhning CV hissasi va qaror

NATIJALAR.md da: guruh nomi, belgilar soni, CV o'sishi, std, qaror va sabab.

4. Qayta ko'rib chiqish vaqti

  • Ma'lumot manbai o'zgarganda
  • Yangi ma'lumot turi qo'shilganda (masalan matn ustuni)
  • Model natijasi tushganda
  • Har 6-12 oyda bir marta

5. Xulosa

  1. Modelga faqat 12 tasini qo'ying
  2. Qolganlarini kod bazasida saqlang
  3. Salbiy natijalarni hujjatlashtiring
  4. Davriy ravishda qayta ko'rib chiqing

Nimani mustahkamlaydi: 2.3, 2.5-bo'limlar.


Xulosa

Bu darsda to'liq belgi muhandisligi loyihasini qurdik.

Eng muhim uch fikr:

  1. Validatsiya strategiyasi — eng erta va eng muhim qaror. Guruh tuzilmasi bor bo'lsa GroupKFold, vaqt bo'lsa TimeSeriesSplit. Noto'g'ri CV bilan qolgan barcha o'lchovlar ma'nosiz bo'ladi: bir mijozning buyurtmalari tasodifiy bo'linsa, model mijozni eslab qoladi va CV optimistik chiqadi — mijozga xos belgi qancha ko’p bo’lsa, farq shuncha katta (bitta CRM kodi ham 0.02-0.03 qo’shadi).

  2. Belgilarni guruh bo'lib qo'shing va CV std bilan solishtiring. "nisbatlar +0.011, vaqt +0.015, agregatsiya +0.010" degan jadval qaysi yo'nalishda davom etish kerakligini aniq ko'rsatadi. Yaxshilanish CV standart og'ishidan kichik bo'lsa — bu shovqin, va belgini qo'shish murakkablikni bepulga oshiradi.

  3. To'xtash qoidasi va audit — majburiy. To'xtash nuqtasini oldindan belgilang ("2 guruh ketma-ket std dan kam bersa"), aks holda siz validatsiyaga overfitting qilasiz. Yakunda audit (duplikatlar, yakka belgi AUC, CV/test farqi), belgilar lug'ati ("qachon ma'lum bo'ladi?" javobi bilan) va Pipeline paketi topshiriladi.

Bu bilan 17-qism — Feature engineering yakunlandi. Keyingi qismda model baholash va sozlashni o'rganamiz: cross-validation strategiyalari, giperparametr qidiruvi va model taqqoslash.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
17.10-dars: Amaliyot — to'liq belgi muhandisligi — IlmHamroh