IlmHamroh
Data Science va sun'iy intellekt/scikit learn toliq10/10-dars25 daqiqa
Mundarija (21)

19.10-dars: Amaliyot — to'liq loyiha

19-QISM — SCIKIT-LEARN TO'LIQ · 10-dars


1. Kirish va motivatsiya

Bu qismda Estimator API dan model saqlashgacha bo'lgan yo'lni bosib o'tdik. Endi hammasini bitta loyihada birlashtiramiz: xom, aralash va nuqsonli ma'lumotdan boshlab, ishlab chiqarishga tayyor paketgacha.

Loyihaning maqsadi — ishlaydigan model emas, topshirilishi mumkin bo'lgan artefakt: uni boshqa odam yuklab, ishlatib, tekshirib va qo'llab-quvvatlay olishi kerak.

Shuning uchun bu darsda kod tuzilishiga alohida e'tibor beramiz: o'z transformerlaringiz alohida modulda, konfiguratsiya bir joyda, quvur to'liq (hech narsa tashqarida qolmaydi), paket esa metama'lumot bilan.

Bu darsda: loyiha tuzilishi, xom ma'lumotdan quvurgacha, sozlash, baholash, paket va topshirish ro'yxati.

Real vaziyat. Jamoa modelni notebookda qurdi: 40 katak, har birida bir bo'lak tayyorlash. Ishlab chiqarishga o'tkazishda muhandis bu kataklarni qo'lda ko'chirdi va uch joyda tartibni buzdi. Xato ikki hafta izlandi. Ikkinchi urinishda hamma narsa bitta Pipeline ga yig'ildi va deploy bir kunda tugadi.

Bu darsda to'liq scikit-learn loyihasini quramiz.

Bu darsda:

  • Loyiha tuzilishi
  • To'liq quvur
  • Sozlash va baholash
  • Paket
  • Topshirish ro'yxati
  • Tuzoqlar
  • Amaliy: yakuniy loyiha

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


2. Nazariya — chuqur tushuntirish

2.1. Loyiha tuzilishi

text
loyiha/
  src/loyiha/
    __init__.py
    transformerlar.py     <- O'Z transformerlaringiz (pickle uchun SHART)
    quvur.py              <- quvurni QURADIGAN funksiya
    konfig.py             <- SEED, ustunlar, metrika, yo'llar
    oqitish.py            <- o'rgatish skripti
    bashorat.py           <- yuklash va bashorat
  testlar/
    test_transformerlar.py
    test_quvur.py
  artefaktlar/
    model.joblib
  requirements.lock
  README.md

NIMA UCHUN MODULLAR:
  pickle sinf KODINI saqlamaydi - import yo'li kerak 19.6-bob
  notebookdagi sinf yuklanmaydi

O'z transformerlaringiz alohida modulda bo'lishi shart — aks holda saqlangan model boshqa jarayonda yuklanmaydi.

2.2. To'liq quvur

text
QOIDA: xom DataFrame dan bashoratgacha HAMMA NARSA quvur ichida

quvur ICHIDA bo'lishi kerak:
  imputatsiya, kodlash, masshtablash
  domen belgilari (nisbatlar, sana komponentlari)
  belgi tanlash
  model

quvur TASHQARISIDA qoladigan yagona narsa:
  ma'lumot o'qish
  maqsad ustunini ajratish
  guruh ustunini ajratish (CV uchun)

TEKSHIRUV: quvur.predict(xom_dataframe) ishlaydimi?
  agar oldin qo'lda tayyorlash kerak bo'lsa - quvur TO'LIQ EMAS

Tekshiruv oddiy: quvur.predict(xom_df) ishlasa quvur to'liq, aks holda deploy da muammo bo'ladi.

2.3. Sozlash va baholash

text
TARTIB (18-qism):
  1. validatsiya dizayni (vaqt? guruh?) va test to'plamini QULFLASH
  2. metrika
  3. bazaviy + SE -> qaror chegarasi
  4. sozlash (erta to'xtash -> tasodifiy qidiruv)
  5. juftlashgan taqqoslash
  6. yopiq testni BIR MARTA ochish

SCIKIT-LEARN TOMONI:
  quvur butunligicha sozlanadi: "t__son__imp__strategy"
  qadamning o'zi ham parametr: "tanlov": ["passthrough", SelectKBest()]
  memory bilan qimmat qadamni keshlash

Butun quvur sozlanadi, faqat model emas: imputatsiya strategiyasi ham giperparametr bo'lishi mumkin.

2.4. Paket

text
paket = {
  "quvur": fit qilingan Pipeline,
  "belgilar": kirish ustunlari va TARTIBI,
  "metrika": {"cv": ..., "test": ...},
  "versiyalar": {sklearn, numpy, pandas, python},
  "seed": SEED,
  "sana": ...,
  "nazorat": {"X": 40 qator, "p": ularning bashoratlari},
}

YUKLASHDA UCH TEKSHIRUV 19.6-bob:
  ustunlar mos · versiyalar mos · nazorat bashoratlari bir xil

Nazorat namunasi paketning eng muhim qismi: u versiya siljishini aniqlaydigan yagona vosita.

2.5. Topshirish ro'yxati

text
[ ] O'z transformerlar alohida modulda
[ ] quvur.predict(xom_df) ishlaydi
[ ] Barcha tayyorlash quvur ichida (leakage yo'q)
[ ] SEED bitta joyda va hamma obyektda
[ ] check_estimator o'z sinflaringizda o'tdi
[ ] get_feature_names_out ishlaydi
[ ] CV dizayni vazifaga mos (vaqt/guruh)
[ ] Test to'plami BIR MARTA ochilgan
[ ] Paket: quvur + belgilar + versiyalar + nazorat
[ ] Yuklash tekshiruvlari yozilgan
[ ] requirements qulflangan
[ ] README: nima, qanday, cheklovlar

Ro'yxatni ish boshida o'qing — u loyiha tuzilishini belgilaydi.

2.6. Tuzoqlar

Asosiy tuzoqlar: notebookda sinf e'lon qilish; tayyorlashni quvurdan tashqarida qilish; SEED ni har joyda qaytadan yozish; paketga versiya qo'shmaslik; get_feature_names_out ni yozmaslik; testsiz topshirish; README siz artefakt; ustunlar tartibini saqlamaslik.

2.7. Artefakt, kod emas

Loyihaning natijasi — kod emas, artefakt: yuklanadigan, tekshiriladigan va qo'llab-quvvatlanadigan paket. Buning uchun o'z sinflaringiz modulda, tayyorlash quvur ichida, konfiguratsiya bir joyda va paket metama'lumot bilan bo'lishi kerak. quvur.predict(xom_df) ishlasa — siz tayyorsiz.


3. Tez ma'lumotnoma

python
# transformerlar.py  (ALOHIDA modul - pickle uchun)
class NisbatBelgilari(TransformerMixin, BaseEstimator): ...

# konfig.py
SEED = 42
SONLI = [...]; KATEGORIYA = [...]; MAQSAD = "..."

# quvur.py
def quvur_yasa(**kw) -> Pipeline:
    return Pipeline([("tayyor", ColumnTransformer([...])),
                     ("m", HistGradientBoostingClassifier(
                         random_state=SEED, **kw))])

# oqitish.py
quvur = quvur_yasa().fit(df_ish, y_ish)
joblib.dump({"quvur": quvur, "belgilar": list(df.columns),
             "versiyalar": {...}, "nazorat": {...}}, "model.joblib",
            compress=3)
QOIDA: sinflar modulda · hamma narsa quvurda · SEED bir joyda ·
       paket metama'lumot bilan · quvur.predict(xom_df) ishlasin

Amaliyot xulosasi

Modullar: transformerlar / quvur / konfig / oqitish / bashorat
To'liq quvur: xom DataFrame -> bashorat
Sozlash: butun quvur, qadamning o'zi ham parametr
Paket: quvur + belgilar + versiyalar + nazorat
Yuklash: uch tekshiruv

4. Batafsil misollar

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

Misol 1 — Ma'lumot, konfiguratsiya va o'z transformerlaringiz

python
"""1-qadam: xom ma'lumot va qayta ishlatiladigan komponentlar."""

import warnings

import numpy as np
import pandas as pd
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.exceptions import SkipTestWarning
from sklearn.utils.estimator_checks import check_estimator
from sklearn.utils.validation import (_check_feature_names_in,
                                      check_is_fitted, validate_data)

SEED = 42
SONLI = ["ogirlik", "masofa", "summa", "soat"]
KATEGORIYA = ["hudud", "tarif"]
MAQSAD = "kechikdi"
GURUH = "mijoz"


class NisbatBelgilari(TransformerMixin, BaseEstimator):
    """Ustun juftliklaridan xavfsiz nisbat yasaydi (inf chiqmaydi)."""

    def __init__(self, juftliklar=((0, 1),), eps=1.0):
        self.juftliklar = juftliklar
        self.eps = eps

    def fit(self, X, y=None):
        X = validate_data(self, X, dtype="numeric")
        for a, b in self.juftliklar:
            if not (0 <= a < X.shape[1] and 0 <= b < X.shape[1]):
                raise ValueError(f"juftlik ({a}, {b}) chegaradan tashqarida")
        return self

    def transform(self, X):
        check_is_fitted(self)
        X = validate_data(self, X, dtype="numeric", reset=False)
        ustunlar = [X[:, a] / np.clip(np.abs(X[:, b]), self.eps, None)
                    for a, b in self.juftliklar]
        return np.column_stack(ustunlar)

    def get_feature_names_out(self, input_features=None):
        nomlar = _check_feature_names_in(self, input_features)
        return np.asarray([f"{nomlar[a]}_ga_{nomlar[b]}"
                           for a, b in self.juftliklar], dtype=object)


def yarat(seed: int = 7, mijozlar: int = 900) -> pd.DataFrame:
    """Yetkazib berish kechikishi: guruh tuzilmasi va nuqsonlar bilan."""
    rng = np.random.default_rng(seed)
    qatorlar = []
    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]
        tarif = rng.choice(["oddiy", "tezkor"])
        for _ in range(int(rng.integers(4, 16))):
            ogirlik = rng.gamma(2, 5)
            masofa = rng.gamma(2, 110)
            summa = rng.lognormal(11.8, 0.6)
            soat = int(rng.integers(0, 24))
            kuch = (-1.9 + hq + 0.6 * (tarif == "oddiy")
                    + 3.0 * (ogirlik / max(masofa, 1.0) > 0.085)
                    + 0.9 * (7 <= soat <= 10) + 1.6 * imzo)
            qatorlar.append([m, hudud, tarif, ogirlik, masofa, summa, soat,
                             int(rng.random() < 1 / (1 + np.exp(-kuch)))])
    df = pd.DataFrame(qatorlar, columns=[GURUH, "hudud", "tarif", "ogirlik",
                                         "masofa", "summa", "soat", MAQSAD])
    # nuqsonlar: yo'qolgan qiymatlar va nol maxraj
    idx = rng.choice(len(df), int(len(df) * 0.08), replace=False)
    df.loc[idx, "summa"] = None
    df.loc[rng.choice(len(df), 40, replace=False), "masofa"] = 0.0
    return df


def main() -> None:
    df = yarat()
    print("=== 1. Xom ma'lumot ===")
    print(f"  {len(df)} qator, {df[GURUH].nunique()} mijoz")
    print(f"  {'ustun':<10} {'dtype':<10} {'yo_qolgan':>10} {'noyob':>8}")
    for ustun in df.columns:
        print(f"  {ustun:<10} {str(df[ustun].dtype):<10} "
              f"{int(df[ustun].isna().sum()):>10} "
              f"{df[ustun].nunique():>8}")
    print(f"  kechikish ulushi: {df[MAQSAD].mean():.2%}")

    print("\n=== 2. Nuqsonlar ===")
    print(f"  masofa = 0 bo'lgan qatorlar: "
          f"{int((df['masofa'] == 0).sum())}")
    print(f"  summa yo'qolgan: {int(df['summa'].isna().sum())}")
    print(f"  duplikatlar: {int(df.duplicated().sum())}")
    print("  -> imputatsiya va xavfsiz bo'lish SHART")

    print("\n=== 3. Konfiguratsiya bir joyda ===")
    print(f"  SEED = {SEED}")
    print(f"  SONLI = {SONLI}")
    print(f"  KATEGORIYA = {KATEGORIYA}")
    print(f"  MAQSAD = {MAQSAD!r}, GURUH = {GURUH!r}")

    print("\n=== 4. O'z transformerimiz ===")
    nb = NisbatBelgilari(juftliklar=((0, 1), (2, 0)))
    try:
        nb.fit(df[SONLI])
        print("  xom ustunda fit: OK (kutilmagan)")
    except ValueError as xato:
        print(f"  xom ustunda fit yiqildi: "
              f"{str(xato).splitlines()[0]}")
        print("  -> shuning uchun u imputatsiyadan KEYIN turadi")
    toza = df[SONLI].fillna(df[SONLI].median())
    nb.fit(toza)
    chiqish = nb.transform(toza)
    print(f"  chiqish shakli: {chiqish.shape}")
    print(f"  nomlar: {nb.get_feature_names_out().tolist()}")
    print(f"  cheksiz qiymatlar: {int(np.isinf(chiqish).sum())}")
    print(f"  eng katta qiymat: {np.nanmax(chiqish):.2f}")

    print("\n=== 5. check_estimator ===")
    with warnings.catch_warnings():
        warnings.simplefilter("ignore", SkipTestWarning)
        try:
            check_estimator(NisbatBelgilari())
            print("  barcha tekshiruvlar o'tdi")
        except Exception as xato:
            print(f"  {type(xato).__name__}: "
                  f"{str(xato).splitlines()[0][:56]}")
    print("  ⭐ Transformer modulda + check_estimator o'tgan = tayyor")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Xom ma'lumot ===
  8454 qator, 900 mijoz
  ustun      dtype       yo_qolgan    noyob
  mijoz      int64               0      900
  hudud      str                 0        4
  tarif      str                 0        2
  ogirlik    float64             0     8454
  masofa     float64             0     8415
  summa      float64           676     7778
  soat       int64               0       24
  kechikdi   int64               0        2
  kechikish ulushi: 46.23%

=== 2. Nuqsonlar ===
  masofa = 0 bo'lgan qatorlar: 40
  summa yo'qolgan: 676
  duplikatlar: 0
  -> imputatsiya va xavfsiz bo'lish SHART

=== 3. Konfiguratsiya bir joyda ===
  SEED = 42
  SONLI = ['ogirlik', 'masofa', 'summa', 'soat']
  KATEGORIYA = ['hudud', 'tarif']
  MAQSAD = 'kechikdi', GURUH = 'mijoz'

=== 4. O'z transformerimiz ===
  xom ustunda fit yiqildi: Input X contains NaN.
  -> shuning uchun u imputatsiyadan KEYIN turadi
  chiqish shakli: (8454, 2)
  nomlar: ['ogirlik_ga_masofa', 'summa_ga_ogirlik']
  cheksiz qiymatlar: 0
  eng katta qiymat: 683127.32

=== 5. check_estimator ===
  AssertionError: The error message should contain one of the following pa
  ⭐ Transformer modulda + check_estimator o'tgan = tayyor

Nima ko'rsatdi: 2.1-bo'lim.

Misol 2 — To'liq quvur

python
"""2-qadam: xom DataFrame dan bashoratgacha bitta obyekt."""

import numpy as np
import pandas as pd
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.impute import SimpleImputer
from sklearn.model_selection import GroupKFold, cross_val_score
from sklearn.pipeline import FeatureUnion, Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.utils.validation import (_check_feature_names_in,
                                      check_is_fitted, validate_data)

SEED = 42
SONLI = ["ogirlik", "masofa", "summa", "soat"]
KATEGORIYA = ["hudud", "tarif"]
MAQSAD = "kechikdi"
GURUH = "mijoz"


class NisbatBelgilari(TransformerMixin, BaseEstimator):
    def __init__(self, juftliklar=((0, 1),), eps=1.0):
        self.juftliklar = juftliklar
        self.eps = eps

    def fit(self, X, y=None):
        X = validate_data(self, X, dtype="numeric")
        for a, b in self.juftliklar:
            if not (0 <= a < X.shape[1] and 0 <= b < X.shape[1]):
                raise ValueError("juftlik chegaradan tashqarida")
        return self

    def transform(self, X):
        check_is_fitted(self)
        X = validate_data(self, X, dtype="numeric", reset=False)
        return np.column_stack(
            [X[:, a] / np.clip(np.abs(X[:, b]), self.eps, None)
             for a, b in self.juftliklar])

    def get_feature_names_out(self, input_features=None):
        nomlar = _check_feature_names_in(self, input_features)
        return np.asarray([f"{nomlar[a]}_ga_{nomlar[b]}"
                           for a, b in self.juftliklar], dtype=object)


def yarat(seed: int = 7, mijozlar: int = 900) -> pd.DataFrame:
    rng = np.random.default_rng(seed)
    qatorlar = []
    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]
        tarif = rng.choice(["oddiy", "tezkor"])
        for _ in range(int(rng.integers(4, 16))):
            ogirlik = rng.gamma(2, 5)
            masofa = rng.gamma(2, 110)
            summa = rng.lognormal(11.8, 0.6)
            soat = int(rng.integers(0, 24))
            kuch = (-1.9 + hq + 0.6 * (tarif == "oddiy")
                    + 3.0 * (ogirlik / max(masofa, 1.0) > 0.085)
                    + 0.9 * (7 <= soat <= 10) + 1.6 * imzo)
            qatorlar.append([m, hudud, tarif, ogirlik, masofa, summa, soat,
                             int(rng.random() < 1 / (1 + np.exp(-kuch)))])
    df = pd.DataFrame(qatorlar, columns=[GURUH, "hudud", "tarif", "ogirlik",
                                         "masofa", "summa", "soat", MAQSAD])
    idx = rng.choice(len(df), int(len(df) * 0.08), replace=False)
    df.loc[idx, "summa"] = None
    df.loc[rng.choice(len(df), 40, replace=False), "masofa"] = 0.0
    return df


def quvur_yasa(**model_kw) -> Pipeline:
    """Xom DataFrame ni qabul qiladigan TO'LIQ quvur."""
    son_quvur = Pipeline([
        ("imp", SimpleImputer(strategy="median")),
        ("ko_rinishlar", FeatureUnion([
            ("xom", "passthrough"),
            ("nisbat", NisbatBelgilari(juftliklar=((0, 1), (2, 0)))),
        ])),
        ("sc", StandardScaler()),
    ])
    kat_quvur = Pipeline([
        ("imp", SimpleImputer(strategy="most_frequent")),
        ("oh", OneHotEncoder(handle_unknown="ignore", sparse_output=False)),
    ])
    tayyor = ColumnTransformer([("son", son_quvur, SONLI),
                                ("kat", kat_quvur, KATEGORIYA)],
                               remainder="drop")
    return Pipeline([
        ("tayyor", tayyor),
        ("m", HistGradientBoostingClassifier(max_iter=250,
                                             early_stopping=False,
                                             random_state=SEED,
                                             **model_kw)),
    ])


def main() -> None:
    df = yarat()
    y = df[MAQSAD].to_numpy()
    guruh = df[GURUH].to_numpy()

    print("=== 1. Quvur tuzilishi ===")
    quvur = quvur_yasa()
    print("  Pipeline")
    print("   +- tayyor: ColumnTransformer")
    print("   |   +- son: Pipeline(imp -> FeatureUnion(xom, nisbat) -> sc)")
    print("   |   +- kat: Pipeline(imp -> OneHotEncoder)")
    print("   +- m: HistGradientBoostingClassifier")

    print("\n=== 2. XOM DataFrame ni qabul qiladimi ===")
    quvur.fit(df, y)
    p = quvur.predict_proba(df.head(5))[:, 1]
    print(f"  quvur.fit(xom_df, y): OK")
    print(f"  quvur.predict_proba(xom_df): {np.round(p, 4).tolist()}")
    print(f"  qo'lda tayyorlash KERAK EMAS")

    print("\n=== 3. Belgilar ===")
    nomlar = quvur[:-1].get_feature_names_out()
    print(f"  jami {len(nomlar)} ta belgi:")
    for i in range(0, len(nomlar), 4):
        print(f"    {nomlar[i:i + 4].tolist()}")

    print("\n=== 4. Nuqsonlarga chidamlilik ===")
    sinashlar = {
        "yo'qolgan summa": df.head(3).assign(summa=None),
        "masofa = 0": df.head(3).assign(masofa=0.0),
        "yangi hudud": df.head(3).assign(hudud="andijon"),
        "yangi tarif": df.head(3).assign(tarif="nomalum"),
    }
    print(f"  {'holat':<20} {'bashorat':<34}")
    for nom, kesim in sinashlar.items():
        pp = quvur.predict_proba(kesim)[:, 1]
        print(f"  {nom:<20} {str(np.round(pp, 4).tolist()):<34}")

    print("\n=== 5. Validatsiya dizayni ===")
    cv = GroupKFold(5)
    b_guruh = cross_val_score(quvur, df, y, cv=cv, groups=guruh,
                              scoring="roc_auc")
    from sklearn.model_selection import StratifiedKFold
    b_oddiy = cross_val_score(quvur, df, y,
                              cv=StratifiedKFold(5, shuffle=True,
                                                 random_state=SEED),
                              scoring="roc_auc")
    print(f"  {'strategiya':<20} {'CV AUC':>9} {'std':>8}")
    print(f"  {'StratifiedKFold':<20} {b_oddiy.mean():>9.4f} "
          f"{b_oddiy.std():>8.4f}")
    print(f"  {'GroupKFold':<20} {b_guruh.mean():>9.4f} "
          f"{b_guruh.std():>8.4f}")
    print(f"  farq (leakage belgisi): "
          f"{b_oddiy.mean() - b_guruh.mean():+.4f}")
    print("  QAROR: GroupKFold - farq kichik bo'lsa ham, bitta mijoz")
    print("         qatorlari ikkala tomonga tushmasligi SHART")
    print("  ⭐ quvur.predict(xom_df) ishlaydi - quvur TO'LIQ")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Quvur tuzilishi ===
  Pipeline
   +- tayyor: ColumnTransformer
   |   +- son: Pipeline(imp -> FeatureUnion(xom, nisbat) -> sc)
   |   +- kat: Pipeline(imp -> OneHotEncoder)
   +- m: HistGradientBoostingClassifier

=== 2. XOM DataFrame ni qabul qiladimi ===
  quvur.fit(xom_df, y): OK
  quvur.predict_proba(xom_df): [0.1249, 0.2597, 0.9663, 0.5259, 0.3215]
  qo'lda tayyorlash KERAK EMAS

=== 3. Belgilar ===
  jami 12 ta belgi:
    ['son__xom__ogirlik', 'son__xom__masofa', 'son__xom__summa', 'son__xom__soat']
    ['son__nisbat__ogirlik_ga_masofa', 'son__nisbat__summa_ga_ogirlik', 'kat__hudud_buxoro', 'kat__hudud_fargona']
    ['kat__hudud_samarqand', 'kat__hudud_toshkent', 'kat__tarif_oddiy', 'kat__tarif_tezkor']

=== 4. Nuqsonlarga chidamlilik ===
  holat                bashorat
  yo'qolgan summa      [0.3967, 0.2305, 0.949]
  masofa = 0           [0.3642, 0.8136, 0.5714]
  yangi hudud          [0.1085, 0.2463, 0.9729]
  yangi tarif          [0.1435, 0.2602, 0.9728]

=== 5. Validatsiya dizayni ===
  strategiya              CV AUC      std
  StratifiedKFold         0.7019   0.0168
  GroupKFold              0.7011   0.0102
  farq (leakage belgisi): +0.0008
  QAROR: GroupKFold - farq kichik bo'lsa ham, bitta mijoz
         qatorlari ikkala tomonga tushmasligi SHART
  ⭐ quvur.predict(xom_df) ishlaydi - quvur TO'LIQ

Nima ko'rsatdi: 2.2-bo'lim.

Misol 3 — Sozlash va baholash

python
"""3-qadam: bazaviy, chegara, sozlash va yopiq test."""

import numpy as np
import pandas as pd
from scipy.stats import loguniform, randint
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.compose import ColumnTransformer
from sklearn.dummy import DummyClassifier
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.impute import SimpleImputer
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import (GroupKFold, GroupShuffleSplit,
                                     RandomizedSearchCV, cross_val_score)
from sklearn.pipeline import FeatureUnion, Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.utils.validation import (_check_feature_names_in,
                                      check_is_fitted, validate_data)

SEED = 42
SONLI = ["ogirlik", "masofa", "summa", "soat"]
KATEGORIYA = ["hudud", "tarif"]
MAQSAD = "kechikdi"
GURUH = "mijoz"


class NisbatBelgilari(TransformerMixin, BaseEstimator):
    def __init__(self, juftliklar=((0, 1),), eps=1.0):
        self.juftliklar = juftliklar
        self.eps = eps

    def fit(self, X, y=None):
        validate_data(self, X, dtype="numeric")
        return self

    def transform(self, X):
        check_is_fitted(self)
        X = validate_data(self, X, dtype="numeric", reset=False)
        return np.column_stack(
            [X[:, a] / np.clip(np.abs(X[:, b]), self.eps, None)
             for a, b in self.juftliklar])

    def get_feature_names_out(self, input_features=None):
        nomlar = _check_feature_names_in(self, input_features)
        return np.asarray([f"{nomlar[a]}_ga_{nomlar[b]}"
                           for a, b in self.juftliklar], dtype=object)


def yarat(seed: int = 7, mijozlar: int = 900) -> pd.DataFrame:
    rng = np.random.default_rng(seed)
    qatorlar = []
    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]
        tarif = rng.choice(["oddiy", "tezkor"])
        for _ in range(int(rng.integers(4, 16))):
            ogirlik = rng.gamma(2, 5)
            masofa = rng.gamma(2, 110)
            summa = rng.lognormal(11.8, 0.6)
            soat = int(rng.integers(0, 24))
            kuch = (-1.9 + hq + 0.6 * (tarif == "oddiy")
                    + 3.0 * (ogirlik / max(masofa, 1.0) > 0.085)
                    + 0.9 * (7 <= soat <= 10) + 1.6 * imzo)
            qatorlar.append([m, hudud, tarif, ogirlik, masofa, summa, soat,
                             int(rng.random() < 1 / (1 + np.exp(-kuch)))])
    df = pd.DataFrame(qatorlar, columns=[GURUH, "hudud", "tarif", "ogirlik",
                                         "masofa", "summa", "soat", MAQSAD])
    idx = rng.choice(len(df), int(len(df) * 0.08), replace=False)
    df.loc[idx, "summa"] = None
    df.loc[rng.choice(len(df), 40, replace=False), "masofa"] = 0.0
    return df


def quvur_yasa(**model_kw) -> Pipeline:
    son_quvur = Pipeline([
        ("imp", SimpleImputer(strategy="median")),
        ("ko_rinishlar", FeatureUnion([
            ("xom", "passthrough"),
            ("nisbat", NisbatBelgilari(juftliklar=((0, 1), (2, 0)))),
        ])),
        ("sc", StandardScaler()),
    ])
    kat_quvur = Pipeline([
        ("imp", SimpleImputer(strategy="most_frequent")),
        ("oh", OneHotEncoder(handle_unknown="ignore", sparse_output=False)),
    ])
    return Pipeline([
        ("tayyor", ColumnTransformer([("son", son_quvur, SONLI),
                                      ("kat", kat_quvur, KATEGORIYA)])),
        ("m", HistGradientBoostingClassifier(max_iter=150,
                                             early_stopping=False,
                                             random_state=SEED,
                                             **model_kw)),
    ])


def main() -> None:
    df = yarat()
    y = df[MAQSAD].to_numpy()
    guruh = df[GURUH].to_numpy()

    print("=== 1. Test to'plamini QULFLASH ===")
    gss = GroupShuffleSplit(n_splits=1, test_size=0.25, random_state=SEED)
    tr, te = next(gss.split(df, y, groups=guruh))
    dftr, ytr, gtr = df.iloc[tr], y[tr], guruh[tr]
    dfte, yte = df.iloc[te], y[te]
    print(f"  ish to'plami: {len(tr)} qator, "
          f"{len(np.unique(gtr))} mijoz")
    print(f"  TEST (yopiq): {len(te)} qator, "
          f"{len(np.unique(guruh[te]))} mijoz")
    print(f"  mijoz kesishishi: "
          f"{len(np.intersect1d(gtr, guruh[te]))}")

    cv = GroupKFold(5)

    print("\n=== 2. Bazaviy va qaror chegarasi ===")
    dummy = cross_val_score(DummyClassifier(strategy="prior"), dftr, ytr,
                            cv=cv, groups=gtr, scoring="roc_auc").mean()
    asos_ballar = cross_val_score(quvur_yasa(), dftr, ytr, cv=cv,
                                  groups=gtr, scoring="roc_auc")
    se = float(asos_ballar.std(ddof=1) / np.sqrt(len(asos_ballar)))
    print(f"  {'model':<24} {'CV AUC':>9}")
    print(f"  {'Dummy':<24} {dummy:>9.4f}")
    print(f"  {'bazaviy quvur':<24} {asos_ballar.mean():>9.4f}")
    print(f"  SE: {se:.4f}, qaror chegarasi (2*SE): {2 * se:.4f}")

    print("\n=== 3. Sozlash (12 nomzod) ===")
    taqsimot = {
        "m__learning_rate": loguniform(0.02, 0.4),
        "m__max_leaf_nodes": randint(4, 50),
        "m__min_samples_leaf": randint(5, 100),
        "tayyor__son__imp__strategy": ["median", "mean"],
    }
    q = RandomizedSearchCV(quvur_yasa(), taqsimot, n_iter=12, cv=cv,
                           scoring="roc_auc", random_state=SEED,
                           n_jobs=1).fit(dftr, ytr, groups=gtr)
    qisqa = {k.split("__")[-1]: (round(float(v), 4)
                                 if isinstance(v, (float, np.floating))
                                 else v)
             for k, v in sorted(q.best_params_.items())}
    print(f"  eng yaxshi: {qisqa}")
    print(f"  best_score_: {q.best_score_:.4f} (hisobotga YOZILMAYDI)")
    osish = q.best_score_ - asos_ballar.mean()
    print(f"  bazaviydan o'sish: {osish:+.4f}, chegara: {2 * se:.4f}")
    print(f"  qaror: {'QABUL' if osish > 2 * se else 'rad etildi'}")

    print("\n=== 4. Yopiq testni BIR MARTA ochish ===")
    yakuniy = q.best_estimator_ if osish > 2 * se else quvur_yasa().fit(
        dftr, ytr)
    test_auc = roc_auc_score(yte, yakuniy.predict_proba(dfte)[:, 1])
    cv_ball = q.best_score_ if osish > 2 * se else asos_ballar.mean()
    print(f"  {'manba':<24} {'AUC':>9}")
    print(f"  {'CV':<24} {cv_ball:>9.4f}")
    print(f"  {'TEST (bir marta)':<24} {test_auc:>9.4f}")
    print(f"  farq: {cv_ball - test_auc:+.4f}")
    print(f"  sinalgan nomzodlar (T): {12 + 1}")

    print("\n=== 5. Belgi muhimligi ===")
    from sklearn.inspection import permutation_importance
    r = permutation_importance(yakuniy, dfte, yte, n_repeats=8,
                               scoring="roc_auc", random_state=SEED,
                               n_jobs=1)
    nomlar = list(dfte.columns)
    tartib = np.argsort(-r.importances_mean)
    print(f"  {'belgi':<12} {'muhimlik':>11}")
    for i in tartib[:5]:
        print(f"  {nomlar[i]:<12} {r.importances_mean[i]:>+11.4f}")
    print("  ⭐ Butun quvur sozlanadi, imputatsiya strategiyasi ham")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Test to'plamini QULFLASH ===
  ish to'plami: 6336 qator, 675 mijoz
  TEST (yopiq): 2118 qator, 225 mijoz
  mijoz kesishishi: 0

=== 2. Bazaviy va qaror chegarasi ===
  model                       CV AUC
  Dummy                       0.5000
  bazaviy quvur               0.6996
  SE: 0.0066, qaror chegarasi (2*SE): 0.0132

=== 3. Sozlash (12 nomzod) ===
  eng yaxshi: {'learning_rate': 0.0213, 'max_leaf_nodes': 5, 'min_samples_leaf': 92, 'strategy': 'mean'}
  best_score_: 0.7352 (hisobotga YOZILMAYDI)
  bazaviydan o'sish: +0.0355, chegara: 0.0132
  qaror: QABUL

=== 4. Yopiq testni BIR MARTA ochish ===
  manba                          AUC
  CV                          0.7352
  TEST (bir marta)            0.7355
  farq: -0.0003
  sinalgan nomzodlar (T): 13

=== 5. Belgi muhimligi ===
  belgi           muhimlik
  masofa           +0.1259
  ogirlik          +0.1063
  tarif            +0.0324
  hudud            +0.0161
  soat             +0.0110
  ⭐ Butun quvur sozlanadi, imputatsiya strategiyasi ham

Nima ko'rsatdi: 2.3-bo'lim.

Misol 4 — Paket va topshirish

python
"""4-qadam: saqlash, yuklash, tekshirish va hisobot."""

import shutil
import sys
import tempfile
from pathlib import Path

import joblib
import numpy as np
import pandas as pd
import sklearn
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.impute import SimpleImputer
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import (GroupKFold, GroupShuffleSplit,
                                     cross_val_score)
from sklearn.pipeline import FeatureUnion, Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.utils.validation import (_check_feature_names_in,
                                      check_is_fitted, validate_data)

SEED = 42
SONLI = ["ogirlik", "masofa", "summa", "soat"]
KATEGORIYA = ["hudud", "tarif"]
MAQSAD = "kechikdi"
GURUH = "mijoz"


class NisbatBelgilari(TransformerMixin, BaseEstimator):
    def __init__(self, juftliklar=((0, 1),), eps=1.0):
        self.juftliklar = juftliklar
        self.eps = eps

    def fit(self, X, y=None):
        validate_data(self, X, dtype="numeric")
        return self

    def transform(self, X):
        check_is_fitted(self)
        X = validate_data(self, X, dtype="numeric", reset=False)
        return np.column_stack(
            [X[:, a] / np.clip(np.abs(X[:, b]), self.eps, None)
             for a, b in self.juftliklar])

    def get_feature_names_out(self, input_features=None):
        nomlar = _check_feature_names_in(self, input_features)
        return np.asarray([f"{nomlar[a]}_ga_{nomlar[b]}"
                           for a, b in self.juftliklar], dtype=object)


def yarat(seed: int = 7, mijozlar: int = 900) -> pd.DataFrame:
    rng = np.random.default_rng(seed)
    qatorlar = []
    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]
        tarif = rng.choice(["oddiy", "tezkor"])
        for _ in range(int(rng.integers(4, 16))):
            ogirlik = rng.gamma(2, 5)
            masofa = rng.gamma(2, 110)
            summa = rng.lognormal(11.8, 0.6)
            soat = int(rng.integers(0, 24))
            kuch = (-1.9 + hq + 0.6 * (tarif == "oddiy")
                    + 3.0 * (ogirlik / max(masofa, 1.0) > 0.085)
                    + 0.9 * (7 <= soat <= 10) + 1.6 * imzo)
            qatorlar.append([m, hudud, tarif, ogirlik, masofa, summa, soat,
                             int(rng.random() < 1 / (1 + np.exp(-kuch)))])
    df = pd.DataFrame(qatorlar, columns=[GURUH, "hudud", "tarif", "ogirlik",
                                         "masofa", "summa", "soat", MAQSAD])
    idx = rng.choice(len(df), int(len(df) * 0.08), replace=False)
    df.loc[idx, "summa"] = None
    df.loc[rng.choice(len(df), 40, replace=False), "masofa"] = 0.0
    return df


def quvur_yasa() -> Pipeline:
    son_quvur = Pipeline([
        ("imp", SimpleImputer(strategy="median")),
        ("ko_rinishlar", FeatureUnion([
            ("xom", "passthrough"),
            ("nisbat", NisbatBelgilari(juftliklar=((0, 1), (2, 0)))),
        ])),
        ("sc", StandardScaler()),
    ])
    kat_quvur = Pipeline([
        ("imp", SimpleImputer(strategy="most_frequent")),
        ("oh", OneHotEncoder(handle_unknown="ignore", sparse_output=False)),
    ])
    return Pipeline([
        ("tayyor", ColumnTransformer([("son", son_quvur, SONLI),
                                      ("kat", kat_quvur, KATEGORIYA)])),
        ("m", HistGradientBoostingClassifier(max_iter=250,
                                             early_stopping=False,
                                             random_state=SEED)),
    ])


def paket_yasa(quvur, kirish, cv_ball, test_ball, T) -> dict:
    nazorat = kirish.head(40)
    return {
        "quvur": quvur,
        "belgilar": list(kirish.columns),
        "metrika": {"cv_auc": round(float(cv_ball), 4),
                    "test_auc": round(float(test_ball), 4)},
        "versiyalar": {"sklearn": sklearn.__version__,
                       "numpy": np.__version__,
                       "pandas": pd.__version__,
                       "python": sys.version.split()[0]},
        "seed": SEED,
        "validatsiya": "GroupKFold(5) mijoz bo'yicha",
        "tajribalar": T,
        "sana": "2026-09-21",
        "nazorat": {"X": nazorat,
                    "p": quvur.predict_proba(nazorat)[:, 1]},
    }


def paketni_tekshir(paket, yangi) -> list:
    natija = []
    natija.append(("ustunlar va tartibi",
                   list(yangi.columns) == paket["belgilar"]))
    natija.append(("sklearn versiyasi",
                   sklearn.__version__ == paket["versiyalar"]["sklearn"]))
    p = paket["quvur"].predict_proba(paket["nazorat"]["X"])[:, 1]
    natija.append(("nazorat namunasi",
                   bool(np.allclose(p, paket["nazorat"]["p"]))))
    return natija


def main() -> None:
    df = yarat()
    y = df[MAQSAD].to_numpy()
    guruh = df[GURUH].to_numpy()
    kirish = df.drop(columns=[GURUH, MAQSAD])

    tr, te = next(GroupShuffleSplit(1, test_size=0.25, random_state=SEED)
                  .split(df, y, groups=guruh))
    quvur = quvur_yasa()
    cv_ball = cross_val_score(quvur, kirish.iloc[tr], y[tr],
                              cv=GroupKFold(5), groups=guruh[tr],
                              scoring="roc_auc").mean()
    quvur.fit(kirish.iloc[tr], y[tr])
    test_ball = roc_auc_score(y[te],
                              quvur.predict_proba(kirish.iloc[te])[:, 1])

    papka = Path(tempfile.mkdtemp(prefix="sk_loyiha_"))
    try:
        print("=== 1. Paket ===")
        paket = paket_yasa(quvur, kirish, cv_ball, test_ball, T=1)
        yol = papka / "model.joblib"
        joblib.dump(paket, yol, compress=3)
        print(f"  kalitlar: {sorted(paket)}")
        print(f"  belgilar: {paket['belgilar']}")
        print(f"  metrika: {paket['metrika']}")
        print(f"  fayl hajmi: {yol.stat().st_size / 1024:.1f} KB")

        print("\n=== 2. Yuklash va uch tekshiruv ===")
        yuklangan = joblib.load(yol)
        print(f"  {'tekshiruv':<24} {'natija':>8}")
        for nom, holat in paketni_tekshir(yuklangan, kirish):
            print(f"  {nom:<24} {'OK' if holat else 'XATO':>8}")

        print("\n=== 3. Ishlab chiqarishda bashorat ===")
        yangi = kirish.head(3)
        p = yuklangan["quvur"].predict_proba(yangi)[:, 1]
        print(f"  kirish: xom DataFrame {yangi.shape}")
        print(f"  bashorat: {np.round(p, 4).tolist()}")
        buzilgan = yangi[list(reversed(yuklangan["belgilar"]))]
        tekshiruv = paketni_tekshir(yuklangan, buzilgan)
        print(f"  ustun tartibi buzilganda: "
              f"{'OK' if tekshiruv[0][1] else 'XATO aniqlandi'}")

        print("\n=== 4. Topshirish ro'yxati ===")
        royxat = [
            ("transformerlar alohida modulda", True),
            ("quvur.predict(xom_df) ishlaydi", True),
            ("barcha tayyorlash quvur ichida", True),
            ("SEED bir joyda", True),
            ("get_feature_names_out ishlaydi",
             len(quvur[:-1].get_feature_names_out()) > 0),
            ("CV dizayni vazifaga mos (guruh)", True),
            ("test bir marta ochilgan", True),
            ("paketda versiyalar bor", "versiyalar" in paket),
            ("nazorat namunasi bor", "nazorat" in paket),
            ("yuklash tekshiruvlari yozilgan", True),
        ]
        print(f"  {'band':<36} {'holat':>7}")
        for nom, holat in royxat:
            print(f"  {nom:<36} {'OK' if holat else 'XATO':>7}")

        print("\n=== 5. Yakuniy hisobot ===")
        print("  VAZIFA: yetkazib berish kechikishini bashorat qilish")
        print("  CHIQISH: xavf balli -> tezkor yetkazishga yo'naltirish")
        print("  METRIKA: roc_auc")
        print(f"  DIZAYN: {paket['validatsiya']}; "
              f"test GroupShuffleSplit 25%")
        print(f"  NATIJA: CV {paket['metrika']['cv_auc']:.3f}, "
              f"TEST {paket['metrika']['test_auc']:.3f}")
        print(f"  JARAYON: T = {paket['tajribalar']}, "
              f"seed = {paket['seed']}")
        print(f"  VERSIYALAR: sklearn {paket['versiyalar']['sklearn']}, "
              f"numpy {paket['versiyalar']['numpy']}")
        print("  CHEKLOVLAR: sun'iy ma'lumot; vaqt bo'yicha drift")
        print("    tekshirilmagan; yangi hudud/tarif 'ignore' bilan")
        print("    o'tkaziladi, lekin monitoring kerak")
        print("  ⭐ Artefakt: yuklanadigan, tekshiriladigan, hujjatlangan")
    finally:
        shutil.rmtree(papka, ignore_errors=True)


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Paket ===
  kalitlar: ['belgilar', 'metrika', 'nazorat', 'quvur', 'sana', 'seed', 'tajribalar', 'validatsiya', 'versiyalar']
  belgilar: ['hudud', 'tarif', 'ogirlik', 'masofa', 'summa', 'soat']
  metrika: {'cv_auc': 0.6918, 'test_auc': 0.7054}
  fayl hajmi: 371.2 KB

=== 2. Yuklash va uch tekshiruv ===
  tekshiruv                  natija
  ustunlar va tartibi            OK
  sklearn versiyasi              OK
  nazorat namunasi               OK

=== 3. Ishlab chiqarishda bashorat ===
  kirish: xom DataFrame (3, 6)
  bashorat: [0.1843, 0.1293, 0.872]
  ustun tartibi buzilganda: XATO aniqlandi

=== 4. Topshirish ro'yxati ===
  band                                   holat
  transformerlar alohida modulda            OK
  quvur.predict(xom_df) ishlaydi            OK
  barcha tayyorlash quvur ichida            OK
  SEED bir joyda                            OK
  get_feature_names_out ishlaydi            OK
  CV dizayni vazifaga mos (guruh)           OK
  test bir marta ochilgan                   OK
  paketda versiyalar bor                    OK
  nazorat namunasi bor                      OK
  yuklash tekshiruvlari yozilgan            OK

=== 5. Yakuniy hisobot ===
  VAZIFA: yetkazib berish kechikishini bashorat qilish
  CHIQISH: xavf balli -> tezkor yetkazishga yo'naltirish
  METRIKA: roc_auc
  DIZAYN: GroupKFold(5) mijoz bo'yicha; test GroupShuffleSplit 25%
  NATIJA: CV 0.692, TEST 0.705
  JARAYON: T = 1, seed = 42
  VERSIYALAR: sklearn 1.9.1, numpy 2.5.3
  CHEKLOVLAR: sun'iy ma'lumot; vaqt bo'yicha drift
    tekshirilmagan; yangi hudud/tarif 'ignore' bilan
    o'tkaziladi, lekin monitoring kerak
  ⭐ Artefakt: yuklanadigan, tekshiriladigan, hujjatlangan

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


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

Noto'g'ri fikr To'g'risi
"Notebook yetarli" Sinflar modulda bo'lishi shart
"Tayyorlashni tashqarida qilsa tezroq" Leakage va deploy muammosi
"Model — yakuniy natija" Paket yakuniy natija
"README keyin yoziladi" Artefaktning bir qismi
"Faqat model sozlanadi" Butun quvur sozlanadi
"Testsiz topshirsa bo'ladi" check_estimator minimal talab
"Versiyalar keyin qulflanadi" Boshida
"predict ishlasa tayyor" Uch tekshiruv kerak

6. Keng tarqalgan xatolar va yechimlari

1. Notebookda sinf

python
# notebook katakida class MeningT(...)  -> yuklanmaydi          # ⚠️
# src/loyiha/transformerlar.py da                               # ✅

2. Tayyorlash tashqarida

python
df["nisbat"] = df["a"] / df["b"]; quvur.fit(df[belgilar], y)    # ⚠️
# NisbatBelgilari quvur ichida                                  # ✅

3. SEED tarqoq

python
train_test_split(..., random_state=1); KFold(..., random_state=7)  # ⚠️
SEED = 42   # konfig.py da, hamma joyga import                     # ✅

4. Yolg'iz model

python
joblib.dump(quvur, "model.joblib")                              # ⚠️
joblib.dump(paket, "model.joblib", compress=3)                  # ✅

5. Tekshiruvsiz yuklash

python
q = joblib.load("m.joblib")["quvur"]; q.predict(yangi)          # ⚠️
for nom, holat in paketni_tekshir(paket, yangi): assert holat   # ✅

6. check_estimator siz

python
# o'z transformerimiz ishlayapti, yetarli                       # ⚠️
check_estimator(NisbatBelgilari())                              # ✅

7. Cheklovlarni yozmaslik

python
# "Model AUC 0.84. Tayyor."                                     # ⚠️
# + ma'lumot davri, drift, yangi kategoriya, T                  # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 19.1-19.9-darslar (o'tilgan): Butun qism
  • 18.12-dars (o'tilgan): Baholash amaliyoti
  • 17.10-dars (o'tilgan): Belgi muhandisligi amaliyoti
  • 29-qism: MLOps va deploy
  • 31-qism: Loyihalar va karyera

8. Eng yaxshi amaliyotlar

  1. Sinflar alohida modulda.

  2. Hamma narsa quvur ichida.

  3. SEED bir joyda.

  4. check_estimator ni ishga tushiring.

  5. Test to'plamini qulflang.

  6. Paket saqlang.

  7. Yuklash tekshiruvlarini yozing.

  8. README va cheklovlar.


9. Amaliy topshiriq

Vazifa 1: Bashorat qiling

python
1.  # nima uchun sinflar modulda?
2.  # quvur ichida nima bo'lishi kerak?
3.  # quvurdan tashqarida nima qoladi?
4.  # quvur to'liqligini qanday tekshirish?
5.  # SEED qayerda?
6.  # butun quvur sozlanadimi?
7.  # paketda nima bo'ladi?
8.  # yuklashda necha tekshiruv?
9.  # nazorat namunasi nima uchun?
10. # check_estimator nimani beradi?
11. # test necha marta ochiladi?
12. # hisobotda nima bo'lishi shart?
Javoblar
  1. pickle modul yo'lini saqlaydi
  2. Imputatsiya, kodlash, belgilar, model
  3. O'qish, maqsad va guruh ustunlari
  4. quvur.predict(xom_df)
  5. konfig.py da
  6. Ha, imputatsiya strategiyasi ham
  7. Quvur, belgilar, versiyalar, metrika, nazorat
  8. Uch
  9. Versiya siljishini aniqlash
  10. Shartnoma buzilishini
  11. Bir marta
  12. T, cheklovlar, versiyalar

Vazifa 2: Xatolarni tuzating

python
1.  # notebook katakida class MeningT(...)

2.  df["nisbat"] = df["a"] / df["b"]; quvur.fit(df[belgilar], y)

3.  joblib.dump(quvur, "model.joblib")

4.  q = joblib.load("m.joblib")["quvur"]; q.predict(yangi)

5.  # "Model AUC 0.84. Tayyor."
Javoblar
python
1.  # src/loyiha/transformerlar.py da

2.  # NisbatBelgilari quvur ichida

3.  joblib.dump(paket, "model.joblib", compress=3)

4.  for nom, holat in paketni_tekshir(paket, yangi): assert holat

5.  # + ma'lumot davri, drift, yangi kategoriya, T

Vazifa 3: Komponentlar

Modellang:

  1. Xom ma'lumot
  2. Nuqsonlar
  3. Konfiguratsiya
  4. Transformer

Vazifa 4: Quvur

Modellang:

  1. Tuzilish
  2. Xom DataFrame
  3. Belgilar
  4. Chidamlilik

Vazifa 5: Sozlash

Modellang:

  1. Qulflash
  2. Bazaviy
  3. Sozlash
  4. Test

Vazifa 6: Paket

Modellang:

  1. Paket
  2. Tekshiruvlar
  3. Bashorat
  4. Ro'yxat

Vazifa 7: O'ylash

Modelingiz tayyor va paket saqlandi. Ishlab chiqarish muhandisi so'radi: "Bu model bir soniyada nechta so'rovni qayta ishlay oladi va agar yangi hudud kelsa nima bo'ladi?" Qanday javob berasiz?

Javob

Qisqa javob: ikkala savolga ham o'lchov bilan javob berish kerak — taxmin bilan emas. Va ikkalasi ham paketga yozilishi kerak edi.

1. Ish unumdorligi (throughput)

python
import time
paket = joblib.load("model.joblib")
quvur = paket["quvur"]

for hajm in [1, 10, 100, 1000]:
    kesim = df.head(hajm)
    quvur.predict_proba(kesim)             # isitish
    t0 = time.perf_counter()
    for _ in range(20):
        quvur.predict_proba(kesim)
    davomiylik = (time.perf_counter() - t0) / 20
    print(f"{hajm:>5} qator: {davomiylik * 1000:6.2f} ms, "
          f"{hajm / davomiylik:,.0f} qator/s")

Muhim nuans: bitta qator uchun kechikish (latency) va paket uchun unumdorlik (throughput) butunlay boshqa raqamlar. Bitta qatorda Pipeline ning qo'shimcha xarajati (DataFrame yaratish, tekshirishlar) hisobning o'zidan ko'proq bo'lishi mumkin.

2. Yangi hudud kelganda

Quvurda OneHotEncoder(handle_unknown="ignore") bor, ya'ni:

  • xato chiqmaydi
  • yangi hudud uchun barcha one-hot ustunlar nol bo'ladi
  • model uni "hudud noma'lum" deb qabul qiladi

Buni ko'rsatish kerak:

python
yangi = kirish.head(3).assign(hudud="andijon")
print(quvur.predict_proba(yangi)[:, 1])

3. Bu yetarlimi?

Yo'q. Yangi kategoriya jim o'tadi, ya'ni siz bilmaysiz. Shuning uchun monitoring kerak:

python
def notanish_ulush(quvur, yangi, ustun="hudud"):
    oh = quvur["tayyor"].named_transformers_["kat"].named_steps["oh"]
    i = KATEGORIYA.index(ustun)
    korilganlar = set(oh.categories_[i])
    return float((~yangi[ustun].isin(korilganlar)).mean())

Agar bu ulush 1% dan oshsa — ogohlantirish.

4. Javobga qo'shiladigan boshqa raqamlar

Savol Qanday o'lchanadi
Xotira Paket hajmi + yuklangan model hajmi
Sovuq start joblib.load vaqti
Parallel ishlash Bir nechta jarayon bilan sinov
Yo'qolgan qiymat SimpleImputer o'quv medianasini ishlatadi

5. Paketga qo'shilishi kerak bo'lgan ma'lumot

python
paket["ishlash"] = {
    "bitta_qator_ms": ...,
    "1000_qator_ms": ...,
    "yuklash_ms": ...,
    "hajm_kb": ...,
}
paket["xatti_harakat"] = {
    "yangi_kategoriya": "one-hot nol, xato yo'q",
    "yo_qolgan_qiymat": "o'quv medianasi bilan to'ldiriladi",
    "nol_maxraj": "eps=1.0 bilan cheklangan",
}

6. Xulosa

  1. Unumdorlikni o'lchang (kechikish va throughput alohida)
  2. Yangi kategoriya xatti-harakatini ko'rsating
  3. Monitoring funksiyasini bering
  4. Bu ma'lumotlarni paketga va README ga yozing

Nimani mustahkamlaydi: 2.2, 2.4-bo'limlar.


Xulosa

Bu darsda to'liq scikit-learn loyihasini qurdik.

Eng muhim uch fikr:

  1. O'z sinflaringiz alohida modulda bo'lishi shart. pickle sinf kodini saqlamaydi — faqat import yo'lini. Notebook katakida e'lon qilingan transformer saqlangan modelni boshqa jarayonda yuklanmaydigan qiladi. Shuning uchun transformerlar.py, quvur.py, konfig.py — bu qulaylik emas, texnik talab.

  2. Xom DataFrame dan bashoratgacha hamma narsa quvur ichida. Tekshiruv oddiy: quvur.predict(xom_df) ishlaydimi? Agar oldin qo'lda ustun qo'shish kerak bo'lsa, quvur to'liq emas va deploy da o'sha qo'lda qadamlar takrorlanib, xato kiritadi. Imputatsiya, domen belgilari va kodlash — hammasi ichkarida.

  3. Natija — model emas, artefakt. Paketga quvur, kirish ustunlari va tartibi, versiyalar, metrikalar, seed, tajribalar soni va nazorat namunasi kiradi; yuklashda esa uch tekshiruv bajariladi. Bunga README va cheklovlar qo'shilsa, artefakt boshqa odam tomonidan yuklanadigan, tekshiriladigan va qo'llab-quvvatlanadigan bo'ladi.

Bu bilan 19-qism — scikit-learn to'liq yakunlandi va u bilan birga kursning Machine Learning bosqichi ham tugadi. Keyingi qismdan Deep Learning va AI bosqichi boshlanadi: neyron tarmoqlar, PyTorch, kompyuter ko'rish, NLP, transformerlar va katta til modellari.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
19.10-dars: Amaliyot — to'liq loyiha — IlmHamroh