IlmHamroh
Data Science va sun'iy intellekt/PyTorch12/12-dars29 daqiqa
Mundarija (21)

21.12-dars: Amaliyot — to'liq PyTorch loyihasi

21-QISM — PYTORCH · 12-dars


1. Kirish va motivatsiya

21-qismda PyTorch ning barcha asosiy qurilish bloklarini ko'rdik: nn.Module, bloklar, Dataset va DataLoader, optimizator va jadvallar, Trainer va callback lar, qurilmalar, checkpoint, regularizatsiya, diagnostika, embedding va loyiha tuzilishi. Endi ularni bitta ishlaydigan loyihaga yig'amiz.

Vazifa — telekom mijozlarining ketishini (churn) bashorat qilish. Ma'lumotda son belgilar (oylik to'lov, xizmat muddati, qo'ng'iroqlar soni) va kategoriyali belgilar (tarif, shahar, qurilma) bor. Shahar — 40 darajali, ya'ni embedding uchun tabiiy nomzod.

Loyiha 18- va 20-qismdagi tartibni saqlaydi: dizayn va testni qulflash, tez tekshiruvlar, o'rgatish, bazaviy bilan juftlashgan taqqoslash, testni bir marta ochish va topshirish paketi. Farq shundaki, endi har bosqich 21-qismda yozilgan qayta ishlatiladigan komponent bilan bajariladi.

Muhim natija oldindan emas, oxirida ma'lum bo'ladi: tarmoq HistGradientBoosting dan yaxshimi yoki yo'qmi — buni juftlashgan taqqoslash hal qiladi. Loyihaning qiymati tarmoqning g'alabasida emas, javobning ishonchliligida.

Real vaziyat. Jamoa birinchi haftada ishlaydigan modelni yozdi. Ikkinchi haftada uni hech kim qayta ola olmadi — konfig yo'q edi, eng yaxshi holat havola bilan saqlangan, masshtablovchi paketga kirmagan. Ikkinchi urinishda xuddi shu model ushbu darsdagi tuzilma bilan qayta yozildi va o'sha kuni ishlab chiqarishga topshirildi.

Bu darsda to'liq PyTorch loyihasini quramiz.

Bu darsda:

  • Konfigdan boshlanadigan quvur
  • Trainer, callback va eng yaxshi holat
  • Tez tekshiruvlar va bitta batch testi
  • Bazaviy bilan juftlashgan taqqoslash
  • Topshirish paketi va hisobot
  • Tuzoqlar
  • Amaliy: yakuniy loyiha

ℹ Misollar real torch/sklearn bilan (Python 3.14, torch 2.14 CPU).


2. Nazariya — chuqur tushuntirish

2.1. Loyiha xaritasi

text
Konfig (frozen dataclass)
  |
  v
Ma'lumot: yarat -> bo'lish (test QULFLANADI) -> lug'at + masshtab (o'quvda)
  |
  v
Dataset (son + kat) -> DataLoader (generator bilan)
  |
  v
Model: Embedding lar + son -> blok -> bosh
  |
  v
Tez tekshiruvlar + bitta batch testi          21.9-bob
  |
  v
Trainer + EngYaxshisi (deepcopy) + ErtaToxtash 21.5-bob
  |
  v
Juftlashgan CV: Tarmoq vs HistGB               (18, 20.12)
  |
  v
Test BIR MARTA -> paket (weights_only) -> Bashoratchi -> jurnal

Har bosqich — alohida, sinaladigan komponent; kirish nuqtasi faqat ularni yig'adi.

2.2. Ma'lumot quvuri

text
1. BO'LISH:  o'quv / validatsiya / test (test QULFLANADI)
2. O'QUVDA QURILADI:
     lug'atlar (kategoriya -> 1..n, 0 noma'lum)
     masshtab (mean, scale)
3. HAMMA TO'PLAMGA QO'LLANADI:
     kodla(qiymatlar, lug'at), (x - mean) / scale
4. Dataset: {"son": ..., "kat": ..., "y": ...}

TEKSHIRUV:
  val/test da noma'lum kategoriya ulushi
  masshtablangan son belgilar ~ N(0, 1)

Lug'at va masshtab faqat o'quvda — ular paketga ham aynan shu ko'rinishda kiradi.

2.3. O'rgatish

text
Trainer:
  forward_batch(batch) -> model(batch["son"], batch["kat"]), batch["y"]
  metrika: AUC - butun davr bashoratlarida 21.5-bob
  callback: EngYaxshisi(monitor="val_auc", rejim="max", sabr=8)

OPTIMIZATOR:
  AdamW, bias/norm decay siz 21.4-bob
  CosineAnnealingLR, davr birligida

BIR NECHA SEED:
  3-5 seed -> o'rtacha +- std

Eng yaxshi holat deepcopy bilan va oxirida tiklanadi — aks holda hisobot oxirgi davrniki bo'ladi.

2.4. Taqqoslash va qaror

text
BIR XIL FOLDLAR:
  LogisticRegression (one-hot)        - eng sodda bazaviy
  HistGradientBoosting(categorical_features=...)
  TabularTarmoq

JUFTLASHGAN FARQ:
  d_k = AUC_tarmoq,k - AUC_hgb,k
  qaror: |mean(d)| > 2 * SE(d) bo'lsa - sezilarli

QOIDA: eng yaxshisidan SEZILARLI yomon bo'lmagan ENG SODDA model
  soddalik tartibi: LogReg < HistGB < Tarmoq

Qarorni juftlashgan farq va uning SE si belgilaydi — bitta AUC emas.

2.5. Topshirish paketi

text
paket = {
  "format": 1,
  "konfig": asdict(konfig),
  "holat": model.state_dict(),
  "kirish": {"son": [...], "kat": [...],
             "mean": [...], "scale": [...],
             "lugatlar": {"tarif": {...}, ...}},
  "metrika": {"val_auc": ..., "test_auc": ...},
  "versiyalar": {"torch": str(torch.__version__), ...},
  "nazorat": {"son": tensor, "kat": tensor, "p": tensor},
}
torch.save(paket, yol)
torch.load(yol, weights_only=True)      # HAMMASI xavfsiz turlar

Paketda pickle qilinadigan obyekt yo'q — lug'at, masshtab, versiya — hammasi son va satr.

2.6. Hisobot

text
VAZIFA, METRIKA, DIZAYN (bo'lish, CV)
NATIJA: tarmoq o'rtacha +- std (seedlar), HistGB, juftlashgan farq +- SE
QAROR va uning asosi
TEST: bir marta ochilgan natija
PAKET: format, hajm, nazorat tekshiruvi
CHEKLOVLAR: sun'iy ma'lumot, vaqt drift i tekshirilmagan,
            yangi kategoriyalar 0 ga tushadi

Hisobotda qaror va uning dalili — faqat eng yaxshi raqam emas.

2.7. Tuzoqlar

Asosiy tuzoqlar: testni tarmoq tanlashda ishlatish; lug'at va masshtabni butun ma'lumotda qurish; eng yaxshi holatni havola bilan saqlash; bitta seed natijasini hisobot qilish; bazaviy bilan boshqa foldlarda taqqoslash; paketga sklearn obyekti yoki TorchVersion qo'yish; yuklashdan keyin eval() ni unutish; tekshiruvlarsiz uzoq o'rgatish.


3. Tez ma'lumotnoma

python
k = Konfig()
seed_everything(k.seed)
tayyor = Tayyorlovchi(SON, KAT).fit(df.iloc[tr])
dl_tr = DataLoader(tayyor.dataset(df.iloc[tr], y[tr]), k.batch,
                   shuffle=True, generator=torch.Generator().manual_seed(k.seed))
model = TabularTarmoq(tayyor.hajmlar(), k)
tekshir(model, dl_tr)                          # 21.9
tr_ = Trainer(model, opt, kriteriy, {"auc": auc},
              callbacklar=[EngYaxshisi("val_auc", "max", k.sabr)])
tarix = tr_.fit(dl_tr, dl_va, k.davrlar)
paket = paket_yasa(model, k, tayyor, metrika, nazorat)
torch.save(paket, yol); Bashoratchi(yol)

Amaliyot xulosasi

konfig -> ma'lumot (o'quvda lug'at/masshtab) -> Dataset
model: embedding + son -> blok -> bosh
tekshiruv -> Trainer + EngYaxshisi -> seedlar
juftlashgan CV vs HistGB -> qaror
test bir marta -> paket (weights_only) -> hisobot

4. Batafsil misollar

Misollar real torch/sklearn bilan (Python 3.14, torch 2.14 CPU).

Misol 1 — Konfig, ma'lumot va tekshiruvlar

python
"""1-qadam: quvurni qurish va o'rgatishdan oldin tekshirish."""

import math
import random
from dataclasses import dataclass

import numpy as np
import pandas as pd
import torch
import torch.nn as nn
from sklearn.model_selection import train_test_split
from torch.utils.data import DataLoader, Dataset

SON = ["tolov", "muddat", "qongiroq", "internet_gb", "shikoyat"]
KAT = ["tarif", "shahar", "qurilma"]


@dataclass(frozen=True)
class Konfig:
    seed: int = 42
    lr: float = 3e-3
    batch: int = 128
    davrlar: int = 40
    yashirin: int = 64
    dropout: float = 0.1
    weight_decay: float = 1e-4
    sabr: int = 8


def seed_everything(seed):
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)


def yarat(n=7000, seed=7):
    rng = np.random.default_rng(seed)
    tarif = rng.integers(0, 5, n)
    shahar = rng.integers(0, 40, n)
    qurilma = rng.integers(0, 8, n)
    shahar_t = rng.normal(0, 0.7, 40)
    tarif_t = np.array([0.6, 0.2, 0.0, -0.4, -0.8])
    tolov = rng.lognormal(4.0, 0.4, n)
    muddat = rng.gamma(2.0, 12.0, n)
    qongiroq = rng.poisson(30, n).astype(float)
    internet = rng.lognormal(2.5, 0.8, n)
    shikoyat = rng.poisson(0.6, n).astype(float)
    z = (np.log(tolov) - 4.0) / 0.4
    kuch = (-1.4 + tarif_t[tarif] + shahar_t[shahar]
            + 0.6 * z ** 2                       # U-shakl: juda arzon va
            - 1.4 * (muddat > 24)                #   juda qimmat tarif
            + 1.0 * (muddat < 3)                 # yangi mijoz
            + 0.9 * shikoyat * (tarif >= 3)      # o'zaro ta'sir
            + 0.8 * (internet > 30) * (qurilma < 2))
    y = (kuch + rng.logistic(0, 1, n) > 0).astype(np.int64)
    df = pd.DataFrame({"tolov": tolov, "muddat": muddat,
                       "qongiroq": qongiroq, "internet_gb": internet,
                       "shikoyat": shikoyat, "tarif": tarif,
                       "shahar": shahar, "qurilma": qurilma})
    return df, y


class Tayyorlovchi:
    def fit(self, df):
        self.mean = df[SON].mean().to_numpy()
        self.scale = df[SON].std(ddof=0).to_numpy()
        self.lugatlar = {c: {int(k): i + 1 for i, k in
                             enumerate(np.unique(df[c]))} for c in KAT}
        return self

    def hajmlar(self):
        return [len(self.lugatlar[c]) + 1 for c in KAT]

    def dataset(self, df, y):
        son = (df[SON].to_numpy() - self.mean) / self.scale
        kat = np.column_stack([[self.lugatlar[c].get(int(v), 0)
                                for v in df[c]] for c in KAT])
        return JadvalDataset(son, kat, y)


class JadvalDataset(Dataset):
    def __init__(self, son, kat, y):
        self.son = torch.as_tensor(son, dtype=torch.float32)
        self.kat = torch.as_tensor(kat, dtype=torch.int64)
        self.y = torch.as_tensor(y, dtype=torch.int64)

    def __len__(self):
        return len(self.y)

    def __getitem__(self, i):
        return {"son": self.son[i], "kat": self.kat[i], "y": self.y[i]}


class TabularTarmoq(nn.Module):
    def __init__(self, hajmlar, k):
        super().__init__()
        d = [min(16, (n + 1) // 2) for n in hajmlar]
        self.embler = nn.ModuleList(
            [nn.Embedding(n, di) for n, di in zip(hajmlar, d)])
        self.tarmoq = nn.Sequential(
            nn.Linear(len(SON) + sum(d), k.yashirin), nn.ReLU(),
            nn.Dropout(k.dropout), nn.Linear(k.yashirin, k.yashirin),
            nn.ReLU(), nn.Linear(k.yashirin, 2))

    def forward(self, son, kat):
        e = [emb(kat[:, i]) for i, emb in enumerate(self.embler)]
        return self.tarmoq(torch.cat([son] + e, dim=1))


def main() -> None:
    k = Konfig()
    seed_everything(k.seed)
    df, y = yarat()

    print("=== 1. Ma'lumot va dizayn ===")
    idx = np.arange(len(df))
    ish, te = train_test_split(idx, test_size=0.2, random_state=k.seed,
                               stratify=y)
    tr, va = train_test_split(ish, test_size=0.25, random_state=k.seed,
                              stratify=y[ish])
    print(f"  jami {len(df)}, ketish ulushi {y.mean():.3f}")
    print(f"  o'quv {len(tr)}, validatsiya {len(va)}, TEST (qulf) {len(te)}")
    print(f"  kategoriyalar: "
          f"{ {c: int(df[c].nunique()) for c in KAT} }")

    print("\n=== 2. O'quvda qurilgan tayyorlash ===")
    tayyor = Tayyorlovchi().fit(df.iloc[tr])
    ds_tr = tayyor.dataset(df.iloc[tr], y[tr])
    ds_va = tayyor.dataset(df.iloc[va], y[va])
    print(f"  lug'at hajmlari (+0 noma'lum): {tayyor.hajmlar()}")
    print(f"  o'quv son belgilar: o'rtacha "
          f"{ds_tr.son.mean(0).abs().max().item():.4f} (maks |.|), "
          f"std {ds_tr.son.std(0).mean().item():.4f}")
    nomalum = (ds_va.kat == 0).float().mean(0)
    print(f"  val da noma'lum kategoriya ulushi: "
          f"{[round(v, 4) for v in nomalum.tolist()]}")

    print("\n=== 3. Tez tekshiruvlar 21.9-bob ===")
    g = torch.Generator().manual_seed(k.seed)
    dl = DataLoader(ds_tr, batch_size=k.batch, shuffle=True, generator=g)
    model = TabularTarmoq(tayyor.hajmlar(), k)
    b = next(iter(dl))
    model.eval()
    with torch.no_grad():
        chiqish = model(b["son"], b["kat"])
        boshl = nn.functional.cross_entropy(chiqish, b["y"]).item()
    param = sum(p.numel() for p in model.parameters())
    tekshiruvlar = [
        ("chiqish shakli", tuple(chiqish.shape),
         tuple(chiqish.shape) == (k.batch, 2)),
        ("yorliqlar", sorted(set(b["y"].tolist())),
         set(b["y"].tolist()) <= {0, 1}),
        ("boshlang'ich loss", round(boshl, 3),
         abs(boshl - math.log(2)) < 0.3),
        ("parametrlar", param, param > 0),
    ]
    print(f"  {'tekshiruv':<20} {'qiymat':>16} {'holat':>7}")
    for nom, q, ok in tekshiruvlar:
        print(f"  {nom:<20} {str(q):>16} {'OK' if ok else 'XATO':>7}")

    print("\n=== 4. Bitta batch testi ===")
    model.train()
    for m in model.modules():
        if isinstance(m, nn.Dropout):
            m.p = 0.0
    opt = torch.optim.Adam(model.parameters(), lr=1e-2)
    kichik = {kk: v[:32] for kk, v in b.items()}
    for _ in range(300):
        opt.zero_grad()
        loss = nn.functional.cross_entropy(
            model(kichik["son"], kichik["kat"]), kichik["y"])
        loss.backward()
        opt.step()
    print(f"  32 namuna, 300 qadam: loss {loss.item():.5f}")
    print(f"  natija: {'OTDI' if loss.item() < 0.01 else 'OTMADI'}")
    print("  ⭐ Quvur tayyor - o'rgatishga o'tish mumkin")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Ma'lumot va dizayn ===
  jami 7000, ketish ulushi 0.279
  o'quv 4200, validatsiya 1400, TEST (qulf) 1400
  kategoriyalar: {'tarif': 5, 'shahar': 40, 'qurilma': 8}

=== 2. O'quvda qurilgan tayyorlash ===
  lug'at hajmlari (+0 noma'lum): [6, 41, 9]
  o'quv son belgilar: o'rtacha 0.0000 (maks |.|), std 1.0001
  val da noma'lum kategoriya ulushi: [0.0, 0.0, 0.0]

=== 3. Tez tekshiruvlar 21.9-bob ===
  tekshiruv                      qiymat   holat
  chiqish shakli               (128, 2)      OK
  yorliqlar                      [0, 1]      OK
  boshlang'ich loss               0.743      OK
  parametrlar                      6929      OK

=== 4. Bitta batch testi ===
  32 namuna, 300 qadam: loss 0.00000
  natija: OTDI
  ⭐ Quvur tayyor - o'rgatishga o'tish mumkin

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

Misol 2 — Trainer, callback va seedlar

python
"""2-qadam: o'rgatish, eng yaxshi holat va bir necha seed."""

import copy
import random
from dataclasses import dataclass, replace

import numpy as np
import pandas as pd
import torch
import torch.nn as nn
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import train_test_split
from torch.utils.data import DataLoader, Dataset

SON = ["tolov", "muddat", "qongiroq", "internet_gb", "shikoyat"]
KAT = ["tarif", "shahar", "qurilma"]


@dataclass(frozen=True)
class Konfig:
    seed: int = 42
    lr: float = 3e-3
    batch: int = 128
    davrlar: int = 40
    yashirin: int = 64
    dropout: float = 0.1
    weight_decay: float = 1e-4
    sabr: int = 8


def seed_everything(seed):
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)


def yarat(n=7000, seed=7):
    rng = np.random.default_rng(seed)
    tarif = rng.integers(0, 5, n)
    shahar = rng.integers(0, 40, n)
    qurilma = rng.integers(0, 8, n)
    shahar_t = rng.normal(0, 0.7, 40)
    tarif_t = np.array([0.6, 0.2, 0.0, -0.4, -0.8])
    tolov = rng.lognormal(4.0, 0.4, n)
    muddat = rng.gamma(2.0, 12.0, n)
    qongiroq = rng.poisson(30, n).astype(float)
    internet = rng.lognormal(2.5, 0.8, n)
    shikoyat = rng.poisson(0.6, n).astype(float)
    z = (np.log(tolov) - 4.0) / 0.4
    kuch = (-1.4 + tarif_t[tarif] + shahar_t[shahar]
            + 0.6 * z ** 2                       # U-shakl: juda arzon va
            - 1.4 * (muddat > 24)                #   juda qimmat tarif
            + 1.0 * (muddat < 3)                 # yangi mijoz
            + 0.9 * shikoyat * (tarif >= 3)      # o'zaro ta'sir
            + 0.8 * (internet > 30) * (qurilma < 2))
    y = (kuch + rng.logistic(0, 1, n) > 0).astype(np.int64)
    df = pd.DataFrame({"tolov": tolov, "muddat": muddat,
                       "qongiroq": qongiroq, "internet_gb": internet,
                       "shikoyat": shikoyat, "tarif": tarif,
                       "shahar": shahar, "qurilma": qurilma})
    return df, y


class Tayyorlovchi:
    def fit(self, df):
        self.mean = df[SON].mean().to_numpy()
        self.scale = df[SON].std(ddof=0).to_numpy()
        self.lugatlar = {c: {int(k): i + 1 for i, k in
                             enumerate(np.unique(df[c]))} for c in KAT}
        return self

    def hajmlar(self):
        return [len(self.lugatlar[c]) + 1 for c in KAT]

    def dataset(self, df, y):
        son = (df[SON].to_numpy() - self.mean) / self.scale
        kat = np.column_stack([[self.lugatlar[c].get(int(v), 0)
                                for v in df[c]] for c in KAT])
        return JadvalDataset(son, kat, y)


class JadvalDataset(Dataset):
    def __init__(self, son, kat, y):
        self.son = torch.as_tensor(son, dtype=torch.float32)
        self.kat = torch.as_tensor(kat, dtype=torch.int64)
        self.y = torch.as_tensor(y, dtype=torch.int64)

    def __len__(self):
        return len(self.y)

    def __getitem__(self, i):
        return {"son": self.son[i], "kat": self.kat[i], "y": self.y[i]}


class TabularTarmoq(nn.Module):
    def __init__(self, hajmlar, k):
        super().__init__()
        d = [min(16, (n + 1) // 2) for n in hajmlar]
        self.embler = nn.ModuleList(
            [nn.Embedding(n, di) for n, di in zip(hajmlar, d)])
        self.tarmoq = nn.Sequential(
            nn.Linear(len(SON) + sum(d), k.yashirin), nn.ReLU(),
            nn.Dropout(k.dropout), nn.Linear(k.yashirin, k.yashirin),
            nn.ReLU(), nn.Linear(k.yashirin, 2))

    def forward(self, son, kat):
        e = [emb(kat[:, i]) for i, emb in enumerate(self.embler)]
        return self.tarmoq(torch.cat([son] + e, dim=1))


class EngYaxshisi:
    def __init__(self, monitor, sabr):
        self.monitor, self.sabr = monitor, sabr

    def fit_boshi(self, tr):
        self.eng, self.holat, self.davr, self.hisob = -np.inf, None, 0, 0

    def davr_oxiri(self, tr, davr, log):
        if log[self.monitor] > self.eng:
            self.eng, self.davr, self.hisob = log[self.monitor], davr, 0
            self.holat = copy.deepcopy(tr.model.state_dict())
        else:
            self.hisob += 1
            tr.toxtash = self.hisob >= self.sabr

    def fit_oxiri(self, tr):
        tr.model.load_state_dict(self.holat)


class Trainer:
    def __init__(self, model, opt, sched, callbacklar):
        self.model, self.opt, self.sched = model, opt, sched
        self.callbacklar, self.tarix, self.toxtash = callbacklar, [], False

    def _davr(self, dl, orgatish):
        self.model.train(orgatish)
        jami, n, plar, ylar = 0.0, 0, [], []
        with torch.set_grad_enabled(orgatish):
            for b in dl:
                if orgatish:
                    self.opt.zero_grad()
                ch = self.model(b["son"], b["kat"])
                loss = nn.functional.cross_entropy(ch, b["y"])
                if orgatish:
                    loss.backward()
                    self.opt.step()
                jami += loss.item() * len(b["y"])
                n += len(b["y"])
                plar.append(torch.softmax(ch.detach(), 1)[:, 1])
                ylar.append(b["y"])
        return {"loss": jami / n,
                "auc": roc_auc_score(torch.cat(ylar).numpy(),
                                     torch.cat(plar).numpy())}

    def fit(self, dl_tr, dl_va, davrlar):
        for cb in self.callbacklar:
            cb.fit_boshi(self)
        for davr in range(1, davrlar + 1):
            t, v = self._davr(dl_tr, True), self._davr(dl_va, False)
            log = {"train_loss": t["loss"], "val_loss": v["loss"],
                   "val_auc": v["auc"]}
            self.sched.step()
            self.tarix.append({"davr": davr, **log})
            for cb in self.callbacklar:
                cb.davr_oxiri(self, davr, log)
            if self.toxtash:
                break
        for cb in self.callbacklar:
            cb.fit_oxiri(self)
        return pd.DataFrame(self.tarix)


def guruhlar(model, wd):
    decay = [p for p in model.parameters() if p.ndim >= 2]
    siz = [p for p in model.parameters() if p.ndim < 2]
    return [{"params": decay, "weight_decay": wd},
            {"params": siz, "weight_decay": 0.0}]


def bitta_yurish(k, tayyor, ds_tr, ds_va):
    seed_everything(k.seed)
    model = TabularTarmoq(tayyor.hajmlar(), k)
    opt = torch.optim.AdamW(guruhlar(model, k.weight_decay), lr=k.lr)
    sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, k.davrlar)
    g = torch.Generator().manual_seed(k.seed)
    dl_tr = DataLoader(ds_tr, batch_size=k.batch, shuffle=True,
                       generator=g)
    dl_va = DataLoader(ds_va, batch_size=1024)
    eyx = EngYaxshisi("val_auc", k.sabr)
    tarix = Trainer(model, opt, sched, [eyx]).fit(dl_tr, dl_va, k.davrlar)
    return model, tarix, eyx


def main() -> None:
    k = Konfig()
    df, y = yarat()
    idx = np.arange(len(df))
    ish, te = train_test_split(idx, test_size=0.2, random_state=k.seed,
                               stratify=y)
    tr, va = train_test_split(ish, test_size=0.25, random_state=k.seed,
                              stratify=y[ish])
    tayyor = Tayyorlovchi().fit(df.iloc[tr])
    ds_tr = tayyor.dataset(df.iloc[tr], y[tr])
    ds_va = tayyor.dataset(df.iloc[va], y[va])

    print("=== 1. Bitta yurish ===")
    model, tarix, eyx = bitta_yurish(k, tayyor, ds_tr, ds_va)
    print(f"  {len(tarix)} davr, eng yaxshi val_auc {eyx.eng:.4f} "
          f"({eyx.davr}-davr)")
    korsat = tarix[tarix["davr"].isin([1, 5, 10, eyx.davr,
                                       len(tarix)])]
    print(korsat.round(4).to_string(index=False))

    print("\n=== 2. Tiklangan holat tekshiruvi ===")
    model.eval()
    with torch.no_grad():
        p = torch.softmax(model(ds_va.son, ds_va.kat), 1)[:, 1].numpy()
    tiklangan = roc_auc_score(y[va], p)
    print(f"  tiklangan model val_auc: {tiklangan:.4f}")
    print(f"  callback eslagan:        {eyx.eng:.4f}")
    print(f"  mos: {abs(tiklangan - eyx.eng) < 1e-9}")

    print("\n=== 3. Bir necha seed ===")
    ballar = []
    for s in range(4):
        _, _, e = bitta_yurish(replace(k, seed=s), tayyor, ds_tr, ds_va)
        ballar.append(e.eng)
    ballar = np.array(ballar)
    print(f"  4 seed val_auc: {ballar.round(4).tolist()}")
    print(f"  o'rtacha {ballar.mean():.4f} +- {ballar.std(ddof=1):.4f}")
    print("  ⭐ Hisobotga o'rtacha va std yoziladi")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Bitta yurish ===
  17 davr, eng yaxshi val_auc 0.7765 (9-davr)
 davr  train_loss  val_loss  val_auc
    1      0.6052    0.5670   0.6571
    5      0.4939    0.4973   0.7615
    9      0.4496    0.4856   0.7765
   10      0.4423    0.4934   0.7724
   17      0.4076    0.5146   0.7603

=== 2. Tiklangan holat tekshiruvi ===
  tiklangan model val_auc: 0.7765
  callback eslagan:        0.7765
  mos: True

=== 3. Bir necha seed ===
  4 seed val_auc: [0.7814, 0.7707, 0.7668, 0.7717]
  o'rtacha 0.7727 +- 0.0062
  ⭐ Hisobotga o'rtacha va std yoziladi

Nima ko'rsatdi: 2.3-bo'lim.

Misol 3 — HistGradientBoosting bilan juftlashgan taqqoslash

python
"""3-qadam: bir xil foldlarda tarmoq va boosting (real torch/sklearn)."""

import random
from dataclasses import dataclass

import numpy as np
import pandas as pd
import torch
import torch.nn as nn
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import StratifiedKFold, train_test_split
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from torch.utils.data import DataLoader, Dataset

SON = ["tolov", "muddat", "qongiroq", "internet_gb", "shikoyat"]
KAT = ["tarif", "shahar", "qurilma"]


@dataclass(frozen=True)
class Konfig:
    seed: int = 42
    lr: float = 3e-3
    batch: int = 128
    davrlar: int = 25
    yashirin: int = 64
    dropout: float = 0.1
    weight_decay: float = 1e-4


def seed_everything(seed):
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)


def yarat(n=7000, seed=7):
    rng = np.random.default_rng(seed)
    tarif = rng.integers(0, 5, n)
    shahar = rng.integers(0, 40, n)
    qurilma = rng.integers(0, 8, n)
    shahar_t = rng.normal(0, 0.7, 40)
    tarif_t = np.array([0.6, 0.2, 0.0, -0.4, -0.8])
    tolov = rng.lognormal(4.0, 0.4, n)
    muddat = rng.gamma(2.0, 12.0, n)
    qongiroq = rng.poisson(30, n).astype(float)
    internet = rng.lognormal(2.5, 0.8, n)
    shikoyat = rng.poisson(0.6, n).astype(float)
    z = (np.log(tolov) - 4.0) / 0.4
    kuch = (-1.4 + tarif_t[tarif] + shahar_t[shahar]
            + 0.6 * z ** 2                       # U-shakl: juda arzon va
            - 1.4 * (muddat > 24)                #   juda qimmat tarif
            + 1.0 * (muddat < 3)                 # yangi mijoz
            + 0.9 * shikoyat * (tarif >= 3)      # o'zaro ta'sir
            + 0.8 * (internet > 30) * (qurilma < 2))
    y = (kuch + rng.logistic(0, 1, n) > 0).astype(np.int64)
    df = pd.DataFrame({"tolov": tolov, "muddat": muddat,
                       "qongiroq": qongiroq, "internet_gb": internet,
                       "shikoyat": shikoyat, "tarif": tarif,
                       "shahar": shahar, "qurilma": qurilma})
    return df, y


class JadvalDataset(Dataset):
    def __init__(self, son, kat, y):
        self.son = torch.as_tensor(son, dtype=torch.float32)
        self.kat = torch.as_tensor(kat, dtype=torch.int64)
        self.y = torch.as_tensor(y, dtype=torch.int64)

    def __len__(self):
        return len(self.y)

    def __getitem__(self, i):
        return {"son": self.son[i], "kat": self.kat[i], "y": self.y[i]}


class TabularTarmoq(nn.Module):
    def __init__(self, hajmlar, k):
        super().__init__()
        d = [min(16, (n + 1) // 2) for n in hajmlar]
        self.embler = nn.ModuleList(
            [nn.Embedding(n, di) for n, di in zip(hajmlar, d)])
        self.tarmoq = nn.Sequential(
            nn.Linear(len(SON) + sum(d), k.yashirin), nn.ReLU(),
            nn.Dropout(k.dropout), nn.Linear(k.yashirin, k.yashirin),
            nn.ReLU(), nn.Linear(k.yashirin, 2))

    def forward(self, son, kat):
        e = [emb(kat[:, i]) for i, emb in enumerate(self.embler)]
        return self.tarmoq(torch.cat([son] + e, dim=1))


def tarmoq_auc(df, y, tr, va, k):
    mean = df.iloc[tr][SON].mean().to_numpy()
    scale = df.iloc[tr][SON].std(ddof=0).to_numpy()
    lug = {c: {int(v): i + 1 for i, v in enumerate(np.unique(df.iloc[tr][c]))}
           for c in KAT}

    def ds(idx):
        son = (df.iloc[idx][SON].to_numpy() - mean) / scale
        kat = np.column_stack([[lug[c].get(int(v), 0)
                                for v in df.iloc[idx][c]] for c in KAT])
        return JadvalDataset(son, kat, y[idx])

    d_tr, d_va = ds(tr), ds(va)
    seed_everything(k.seed)
    model = TabularTarmoq([len(lug[c]) + 1 for c in KAT], k)
    opt = torch.optim.AdamW(model.parameters(), lr=k.lr,
                            weight_decay=k.weight_decay)
    sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, k.davrlar)
    g = torch.Generator().manual_seed(k.seed)
    dl = DataLoader(d_tr, batch_size=k.batch, shuffle=True, generator=g)
    for _ in range(k.davrlar):
        model.train()
        for b in dl:
            opt.zero_grad()
            nn.functional.cross_entropy(model(b["son"], b["kat"]),
                                        b["y"]).backward()
            opt.step()
        sched.step()
    model.eval()
    with torch.no_grad():
        p = torch.softmax(model(d_va.son, d_va.kat), 1)[:, 1].numpy()
    return roc_auc_score(y[va], p)


def main() -> None:
    k = Konfig()
    df, y = yarat()
    idx = np.arange(len(df))
    ish, _ = train_test_split(idx, test_size=0.2, random_state=k.seed,
                              stratify=y)
    df_ish, y_ish = df.iloc[ish].reset_index(drop=True), y[ish]
    cv = StratifiedKFold(4, shuffle=True, random_state=0)

    print("=== 1. Uch model, bir xil foldlar ===")
    natija = {"LogReg": [], "HistGB": [], "Tarmoq": []}
    for f, (tr, va) in enumerate(cv.split(df_ish, y_ish)):
        lr = make_pipeline(
            ColumnTransformer([("son", StandardScaler(), SON),
                               ("kat", OneHotEncoder(
                                   handle_unknown="ignore"), KAT)]),
            LogisticRegression(max_iter=2000))
        lr.fit(df_ish.iloc[tr], y_ish[tr])
        natija["LogReg"].append(roc_auc_score(
            y_ish[va], lr.predict_proba(df_ish.iloc[va])[:, 1]))
        gb = HistGradientBoostingClassifier(
            max_iter=200, learning_rate=0.05, categorical_features=KAT,
            early_stopping=False, random_state=0)
        gb.fit(df_ish.iloc[tr], y_ish[tr])
        natija["HistGB"].append(roc_auc_score(
            y_ish[va], gb.predict_proba(df_ish.iloc[va])[:, 1]))
        natija["Tarmoq"].append(tarmoq_auc(df_ish, y_ish, tr, va, k))
    print(f"  {'fold':>5} {'LogReg':>9} {'HistGB':>9} {'Tarmoq':>9}")
    for f in range(4):
        print(f"  {f + 1:>5} {natija['LogReg'][f]:>9.4f} "
              f"{natija['HistGB'][f]:>9.4f} {natija['Tarmoq'][f]:>9.4f}")

    print("\n=== 2. O'rtachalar ===")
    for nom, b in natija.items():
        b = np.array(b)
        print(f"  {nom:<8} {b.mean():.4f} +- {b.std(ddof=1):.4f}")

    print("\n=== 3. Juftlashgan farqlar ===")
    print(f"  {'taqqoslash':<20} {'o_rt farq':>10} {'SE':>8} "
          f"{'sezilarli':>10}")
    for a, b_ in [("Tarmoq", "HistGB"), ("Tarmoq", "LogReg"),
                  ("HistGB", "LogReg")]:
        d = np.array(natija[a]) - np.array(natija[b_])
        se = d.std(ddof=1) / np.sqrt(len(d))
        print(f"  {a + ' - ' + b_:<20} {d.mean():>+10.4f} {se:>8.4f} "
              f"{str(abs(d.mean()) > 2 * se):>10}")

    print("\n=== 4. Qaror: eng yaxshisidan sezilarli yomon bo'lmagan")
    print("    ENG SODDA model ===")
    oddiydan = ["LogReg", "HistGB", "Tarmoq"]       # soddalik tartibi
    eng = max(natija, key=lambda m: np.mean(natija[m]))
    print(f"  o'rtacha bo'yicha eng yaxshi: {eng}")
    for m in oddiydan:
        if m == eng:
            tanlov, sabab = m, "eng yaxshisining o'zi"
            break
        d = np.array(natija[eng]) - np.array(natija[m])
        se = d.std(ddof=1) / np.sqrt(len(d))
        print(f"  {m:<7}: {eng} dan {d.mean():+.4f} past, "
              f"2*SE = {2 * se:.4f}")
        if d.mean() <= 2 * se:
            tanlov, sabab = m, f"{eng} dan sezilarli yomon emas"
            break
    print(f"  TANLOV: {tanlov} ({sabab})")
    print("  ⭐ Eng sodda bazaviyni ham taqqoslashga qo'shing")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Uch model, bir xil foldlar ===
   fold    LogReg    HistGB    Tarmoq
      1    0.7091    0.7806    0.7486
      2    0.7084    0.7679    0.7484
      3    0.6688    0.7335    0.7290
      4    0.7076    0.7710    0.7352

=== 2. O'rtachalar ===
  LogReg   0.6984 +- 0.0198
  HistGB   0.7633 +- 0.0205
  Tarmoq   0.7403 +- 0.0098

=== 3. Juftlashgan farqlar ===
  taqqoslash            o_rt farq       SE  sezilarli
  Tarmoq - HistGB         -0.0229   0.0071       True
  Tarmoq - LogReg         +0.0419   0.0068       True
  HistGB - LogReg         +0.0648   0.0025       True

=== 4. Qaror: eng yaxshisidan sezilarli yomon bo'lmagan
    ENG SODDA model ===
  o'rtacha bo'yicha eng yaxshi: HistGB
  LogReg : HistGB dan +0.0648 past, 2*SE = 0.0050
  TANLOV: HistGB (eng yaxshisining o'zi)
  ⭐ Eng sodda bazaviyni ham taqqoslashga qo'shing

Nima ko'rsatdi: 2.4-bo'lim.

Misol 4 — Test, topshirish paketi va hisobot

python
"""4-qadam: testni bir marta ochish, paket, Bashoratchi, hisobot."""

import copy
import json
import random
import shutil
import sys
import tempfile
from dataclasses import asdict, dataclass
from pathlib import Path

import numpy as np
import pandas as pd
import torch
import torch.nn as nn
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler

SON = ["tolov", "muddat", "qongiroq", "internet_gb", "shikoyat"]
KAT = ["tarif", "shahar", "qurilma"]
FORMAT = 1


@dataclass(frozen=True)
class Konfig:
    seed: int = 42
    lr: float = 3e-3
    batch: int = 128
    davrlar: int = 40
    yashirin: int = 64
    dropout: float = 0.1
    weight_decay: float = 1e-4
    sabr: int = 8


def seed_everything(seed):
    random.seed(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)


def yarat(n=7000, seed=7):
    rng = np.random.default_rng(seed)
    tarif = rng.integers(0, 5, n)
    shahar = rng.integers(0, 40, n)
    qurilma = rng.integers(0, 8, n)
    shahar_t = rng.normal(0, 0.7, 40)
    tarif_t = np.array([0.6, 0.2, 0.0, -0.4, -0.8])
    tolov = rng.lognormal(4.0, 0.4, n)
    muddat = rng.gamma(2.0, 12.0, n)
    qongiroq = rng.poisson(30, n).astype(float)
    internet = rng.lognormal(2.5, 0.8, n)
    shikoyat = rng.poisson(0.6, n).astype(float)
    z = (np.log(tolov) - 4.0) / 0.4
    kuch = (-1.4 + tarif_t[tarif] + shahar_t[shahar]
            + 0.6 * z ** 2                       # U-shakl: juda arzon va
            - 1.4 * (muddat > 24)                #   juda qimmat tarif
            + 1.0 * (muddat < 3)                 # yangi mijoz
            + 0.9 * shikoyat * (tarif >= 3)      # o'zaro ta'sir
            + 0.8 * (internet > 30) * (qurilma < 2))
    y = (kuch + rng.logistic(0, 1, n) > 0).astype(np.int64)
    df = pd.DataFrame({"tolov": tolov, "muddat": muddat,
                       "qongiroq": qongiroq, "internet_gb": internet,
                       "shikoyat": shikoyat, "tarif": tarif,
                       "shahar": shahar, "qurilma": qurilma})
    return df, y


class TabularTarmoq(nn.Module):
    def __init__(self, hajmlar, yashirin, dropout):
        super().__init__()
        d = [min(16, (n + 1) // 2) for n in hajmlar]
        self.embler = nn.ModuleList(
            [nn.Embedding(n, di) for n, di in zip(hajmlar, d)])
        self.tarmoq = nn.Sequential(
            nn.Linear(len(SON) + sum(d), yashirin), nn.ReLU(),
            nn.Dropout(dropout), nn.Linear(yashirin, yashirin),
            nn.ReLU(), nn.Linear(yashirin, 2))

    def forward(self, son, kat):
        e = [emb(kat[:, i]) for i, emb in enumerate(self.embler)]
        return self.tarmoq(torch.cat([son] + e, dim=1))


class Kirish:
    """Lug'at va masshtab - faqat son va lug'atlar (weights_only)."""

    def __init__(self, mean, scale, lugatlar):
        self.mean = np.asarray(mean)
        self.scale = np.asarray(scale)
        self.lugatlar = lugatlar

    @classmethod
    def fit(cls, df):
        return cls(df[SON].mean().to_numpy(),
                   df[SON].std(ddof=0).to_numpy(),
                   {c: {str(int(k)): i + 1 for i, k in
                        enumerate(np.unique(df[c]))} for c in KAT})

    def hajmlar(self):
        return [len(self.lugatlar[c]) + 1 for c in KAT]

    def tensorlar(self, df):
        son = (df[SON].to_numpy(dtype=float) - self.mean) / self.scale
        kat = np.column_stack([[self.lugatlar[c].get(str(int(v)), 0)
                                for v in df[c]] for c in KAT])
        return (torch.tensor(son, dtype=torch.float32),
                torch.tensor(kat, dtype=torch.int64))

    def lugat(self):
        return {"son": SON, "kat": KAT, "mean": self.mean.tolist(),
                "scale": self.scale.tolist(), "lugatlar": self.lugatlar}


def orgat(k, kirish, df_tr, y_tr, df_va, y_va):
    seed_everything(k.seed)
    model = TabularTarmoq(kirish.hajmlar(), k.yashirin, k.dropout)
    opt = torch.optim.AdamW(model.parameters(), lr=k.lr,
                            weight_decay=k.weight_decay)
    sched = torch.optim.lr_scheduler.CosineAnnealingLR(opt, k.davrlar)
    s_tr, c_tr = kirish.tensorlar(df_tr)
    s_va, c_va = kirish.tensorlar(df_va)
    yt = torch.tensor(y_tr)
    g = torch.Generator().manual_seed(k.seed)
    eng, holat, hisob = -1.0, None, 0
    for _ in range(k.davrlar):
        model.train()
        tartib = torch.randperm(len(yt), generator=g)
        for i in range(0, len(yt), k.batch):
            idx = tartib[i:i + k.batch]
            opt.zero_grad()
            nn.functional.cross_entropy(model(s_tr[idx], c_tr[idx]),
                                        yt[idx]).backward()
            opt.step()
        sched.step()
        model.eval()
        with torch.no_grad():
            p = torch.softmax(model(s_va, c_va), 1)[:, 1].numpy()
        auc = roc_auc_score(y_va, p)
        if auc > eng:
            eng, hisob = auc, 0
            holat = copy.deepcopy(model.state_dict())
        else:
            hisob += 1
            if hisob >= k.sabr:
                break
    model.load_state_dict(holat)
    return model, eng


class Bashoratchi:
    def __init__(self, yol):
        p = torch.load(yol, weights_only=True)
        if p["format"] != FORMAT:
            raise ValueError("format mos emas")
        k = p["konfig"]
        ki = p["kirish"]
        self.kirish = Kirish(ki["mean"], ki["scale"], ki["lugatlar"])
        self.model = TabularTarmoq(self.kirish.hajmlar(), k["yashirin"],
                                   k["dropout"])
        self.model.load_state_dict(p["holat"])
        self.model.eval()
        with torch.no_grad():
            q = torch.softmax(self.model(p["nazorat"]["son"],
                                         p["nazorat"]["kat"]), 1)[:, 1]
        self.nazorat_ok = bool(torch.allclose(q, p["nazorat"]["p"],
                                              atol=1e-6))

    def ehtimollik(self, df):
        s, c = self.kirish.tensorlar(df)
        with torch.no_grad():
            return torch.softmax(self.model(s, c), 1)[:, 1].numpy()


def main() -> None:
    k = Konfig()
    df, y = yarat()
    idx = np.arange(len(df))
    ish, te = train_test_split(idx, test_size=0.2, random_state=k.seed,
                               stratify=y)
    tr, va = train_test_split(ish, test_size=0.25, random_state=k.seed,
                              stratify=y[ish])
    kirish = Kirish.fit(df.iloc[tr])

    print("=== 1. Yakuniy modellar ===")
    model, val_auc = orgat(k, kirish, df.iloc[tr], y[tr], df.iloc[va],
                           y[va])
    gb = HistGradientBoostingClassifier(
        max_iter=200, learning_rate=0.05, categorical_features=KAT,
        early_stopping=False, random_state=0).fit(df.iloc[ish], y[ish])
    lr_ = make_pipeline(
        ColumnTransformer([("son", StandardScaler(), SON),
                           ("kat", OneHotEncoder(handle_unknown="ignore"),
                            KAT)]),
        LogisticRegression(max_iter=2000)).fit(df.iloc[ish], y[ish])
    print(f"  tarmoq val_auc (eng yaxshi davr): {val_auc:.4f}")

    print("\n=== 2. Testni BIR MARTA ochamiz ===")
    s_te, c_te = kirish.tensorlar(df.iloc[te])
    model.eval()
    with torch.no_grad():
        p_nn = torch.softmax(model(s_te, c_te), 1)[:, 1].numpy()
    p_gb = gb.predict_proba(df.iloc[te])[:, 1]
    p_lr = lr_.predict_proba(df.iloc[te])[:, 1]
    test_nn = roc_auc_score(y[te], p_nn)
    test_gb = roc_auc_score(y[te], p_gb)
    test_lr = roc_auc_score(y[te], p_lr)
    print(f"  {'model':<10} {'test AUC':>9}")
    print(f"  {'LogReg':<10} {test_lr:>9.4f}")
    print(f"  {'HistGB':<10} {test_gb:>9.4f}")
    print(f"  {'Tarmoq':<10} {test_nn:>9.4f}")
    print("  test QARORNI o'zgartirmaydi - u faqat tasdiqlaydi")

    papka = Path(tempfile.mkdtemp(prefix="churn_"))
    try:
        print("\n=== 3. Topshirish paketi ===")
        nazorat_df = df.iloc[te[:30]]
        ns, nk = kirish.tensorlar(nazorat_df)
        with torch.no_grad():
            np_ = torch.softmax(model(ns, nk), 1)[:, 1]
        paket = {
            "format": FORMAT,
            "konfig": asdict(k),
            "holat": model.state_dict(),
            "kirish": kirish.lugat(),
            "metrika": {"val_auc": round(float(val_auc), 4),
                        "test_auc": round(float(test_nn), 4)},
            "versiyalar": {"torch": str(torch.__version__),
                           "python": sys.version.split()[0]},
            "nazorat": {"son": ns, "kat": nk, "p": np_},
        }
        yol = papka / "churn_model.pt"
        torch.save(paket, yol)
        print(f"  kalitlar: {sorted(paket)}")
        print(f"  hajm: {yol.stat().st_size / 1024:.1f} KB")

        print("\n=== 4. Bashoratchi (weights_only=True) ===")
        b = Bashoratchi(yol)
        print(f"  nazorat tekshiruvi: {'OK' if b.nazorat_ok else 'XATO'}")
        yangi = df.iloc[te[:3]].copy()
        yangi.loc[yangi.index[0], "shahar"] = 999        # noma'lum
        p = b.ehtimollik(yangi)
        for (_, q), pp in zip(yangi.iterrows(), p):
            print(f"  tarif={int(q.tarif)}, shahar={int(q.shahar):>3}, "
                  f"muddat={q.muddat:5.1f} -> ketish {pp:.3f}")
        print("  shahar=999 o'quvda yo'q -> 0 (noma'lum) indeksiga tushdi")

        print("\n=== 5. Jurnal yozuvi ===")
        jurnal = papka / "jurnal.jsonl"
        with open(jurnal, "a", encoding="utf-8") as f:
            f.write(json.dumps({"konfig": asdict(k),
                                "metrika": paket["metrika"]}) + "\n")
        print(f"  {jurnal.read_text(encoding='utf-8').strip()[:76]}...")

        print("\n=== 6. Yakuniy hisobot ===")
        print("  VAZIFA: telekom mijozining ketishini bashorat qilish")
        print("  METRIKA: ROC AUC")
        print("  DIZAYN: stratifikatsiyalangan 60/20/20; test bir marta")
        print(f"  TEST: LogReg {test_lr:.4f}, HistGB {test_gb:.4f}, "
              f"tarmoq {test_nn:.4f}")
        print("  QAROR: 3-qadamdagi juftlashgan CV asosida (test emas)")
        print("  ESLATMA: paketlash bu yerda tarmoq uchun ko'rsatildi;")
        print("    qaror bo'yicha ishlab chiqarishga tanlangan model")
        print("    shu tartibda (konfig + kirish + nazorat) paketlanadi")
        print(f"  PAKET: format {FORMAT}, weights_only=True, nazorat "
              f"{'OK' if b.nazorat_ok else 'XATO'}")
        print("  CHEKLOVLAR: sun'iy ma'lumot; vaqt drift i tekshirilmagan;")
        print("    yangi kategoriyalar umumiy 0-vektorga tushadi")
        print("  ⭐ 21-qism yakunlandi: bo'laklardan ishlaydigan loyihagacha")
    finally:
        shutil.rmtree(papka, ignore_errors=True)


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Yakuniy modellar ===
  tarmoq val_auc (eng yaxshi davr): 0.7737

=== 2. Testni BIR MARTA ochamiz ===
  model       test AUC
  LogReg        0.7101
  HistGB        0.7692
  Tarmoq        0.7538
  test QARORNI o'zgartirmaydi - u faqat tasdiqlaydi

=== 3. Topshirish paketi ===
  kalitlar: ['format', 'holat', 'kirish', 'konfig', 'metrika', 'nazorat', 'versiyalar']
  hajm: 34.4 KB

=== 4. Bashoratchi (weights_only=True) ===
  nazorat tekshiruvi: OK
  tarif=0, shahar=999, muddat= 16.0 -> ketish 0.651
  tarif=0, shahar= 38, muddat=  4.9 -> ketish 0.238
  tarif=1, shahar= 28, muddat= 61.8 -> ketish 0.047
  shahar=999 o'quvda yo'q -> 0 (noma'lum) indeksiga tushdi

=== 5. Jurnal yozuvi ===
  {"konfig": {"seed": 42, "lr": 0.003, "batch": 128, "davrlar": 40, "yashirin"...

=== 6. Yakuniy hisobot ===
  VAZIFA: telekom mijozining ketishini bashorat qilish
  METRIKA: ROC AUC
  DIZAYN: stratifikatsiyalangan 60/20/20; test bir marta
  TEST: LogReg 0.7101, HistGB 0.7692, tarmoq 0.7538
  QAROR: 3-qadamdagi juftlashgan CV asosida (test emas)
  ESLATMA: paketlash bu yerda tarmoq uchun ko'rsatildi;
    qaror bo'yicha ishlab chiqarishga tanlangan model
    shu tartibda (konfig + kirish + nazorat) paketlanadi
  PAKET: format 1, weights_only=True, nazorat OK
  CHEKLOVLAR: sun'iy ma'lumot; vaqt drift i tekshirilmagan;
    yangi kategoriyalar umumiy 0-vektorga tushadi
  ⭐ 21-qism yakunlandi: bo'laklardan ishlaydigan loyihagacha

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


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

Noto'g'ri fikr To'g'risi
"Loyiha = model" Konfig, quvur, tekshiruv, paket, hisobot
"Tekshiruvlar uzoq" Bir necha soniya
"Eng yaxshi holat avtomatik saqlanadi" deepcopy va tiklash kerak
"Tarmoq zamonaviy — tanlaymiz" Juftlashgan farq hal qiladi
"Test bilan model tanlash mumkin" Test bir marta, oxirida
"Paketga sklearn obyektini qo'yish qulay" weights_only rad etadi
"Noma'lum kategoriya kam uchraydi" Ishlab chiqarishda albatta
"Bitta seed yetarli" O'rtacha va std

6. Keng tarqalgan xatolar va yechimlari

1. Test bilan tanlash

python
for k in konfiglar: print(test_auc(k))             # ⚠️
for k in konfiglar: print(val_auc(k))              # ✅ test oxirida

2. Butun ma'lumotda tayyorlash

python
Kirish.fit(df)                                     # ⚠️
Kirish.fit(df.iloc[tr])                            # ✅

3. Havola bilan saqlash

python
holat = model.state_dict()                         # ⚠️
holat = copy.deepcopy(model.state_dict())          # ✅

4. Boshqa foldlarda taqqoslash

python
cross_val_score(gb, ...); cross_val_score(net, ...)  # ⚠️ har xil bo'linish
for tr, va in cv.split(X, y): ...                    # ✅ bir xil foldlar

5. Paketda obyekt

python
{"masshtablovchi": StandardScaler()}               # ⚠️
{"mean": [...], "scale": [...]}                    # ✅

6. Lug'at kalitlari turi

python
{np.int64(3): 1}                                   # ⚠️ weights_only muammo
{"3": 1}                                           # ✅ satr kalit

7. Yuklashdan keyin eval() yo'q

python
model.load_state_dict(p["holat"]); model(x)        # ⚠️
model.load_state_dict(p["holat"]); model.eval()    # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 21.1-21.11-darslar (o'tilgan): Butun qism
  • 20.12-dars (o'tilgan): Tarmoq va boosting
  • 18-qism (o'tilgan): Baholash dizayni
  • 22-qism: Kompyuter ko'rish — xuddi shu skelet, rasm bilan
  • 29-qism: Deploy va monitoring

8. Eng yaxshi amaliyotlar

  1. Konfigdan boshlang.

  2. Testni birinchi qadamda qulflang.

  3. Tayyorlashni o'quvda quring.

  4. Tekshiruv va bitta batch testi.

  5. Eng yaxshi holatni deepcopy.

  6. Bir xil foldlarda taqqoslang.

  7. Paketni xavfsiz turlardan tuzing.

  8. Hisobotda qaror va dalil.


9. Amaliy topshiriq

Vazifa 1: Bashorat qiling

python
1.  # loyiha qaysi komponentdan boshlanadi?
2.  # test qachon qulflanadi?
3.  # lug'at qayerda quriladi?
4.  # bitta batch testi nimani ko'rsatadi?
5.  # eng yaxshi holat qanday saqlanadi?
6.  # tarmoq va boosting qanday taqqoslanadi?
7.  # qaror mezoni?
8.  # teng natijada qaysi model?
9.  # paketda sklearn obyekti bo'ladimi?
10. # lug'at kalitlari qanday turda?
11. # noma'lum kategoriya qayerga tushadi?
12. # hisobotda nima bo'lishi shart?
Javoblar
  1. Konfig
  2. Birinchi qadamda
  3. O'quv to'plamida
  4. Model/loss/optimizator ishlashini
  5. copy.deepcopy(state_dict())
  6. Bir xil foldlarda, juftlashgan
  7. |farq| > 2*SE
  8. Soddaroq (HistGB)
  9. Yo'q
  10. Satr
  11. 0-indeks
  12. Qaror, dalil, cheklovlar

Vazifa 2: Xatolarni tuzating

python
1.  Kirish.fit(df)

2.  holat = model.state_dict()

3.  {"masshtablovchi": StandardScaler()}

4.  {np.int64(3): 1}

5.  for k in konfiglar: print(test_auc(k))
Javoblar
python
1.  Kirish.fit(df.iloc[tr])

2.  holat = copy.deepcopy(model.state_dict())

3.  {"mean": [...], "scale": [...]}

4.  {"3": 1}

5.  for k in konfiglar: print(val_auc(k))   # test faqat oxirida

Vazifa 3: Quvur

Modellang:

  1. Dizayn
  2. Tayyorlash
  3. Tekshiruvlar
  4. Bitta batch

Vazifa 4: O'rgatish

Modellang:

  1. Bitta yurish
  2. Tiklash
  3. Seedlar
  4. Jurnal

Vazifa 5: Taqqoslash

Modellang:

  1. Foldlar
  2. O'rtachalar
  3. Farqlar
  4. Qaror

Vazifa 6: Topshirish

Modellang:

  1. Test
  2. Paket
  3. Bashoratchi
  4. Hisobot

Vazifa 7: O'ylash

Juftlashgan CV da tarmoq HistGB dan ustun chiqmadi, lekin testda tarmoq 0.01 yuqori ball oldi. Menejer "demak tarmoqni tanlaymiz" dedi. Nima deysiz?

Javob

Qisqa javob: yo'q — qaror CV da qabul qilingan, test uni o'zgartirmaydi.

Nima uchun:

1. Test — bitta namuna. Test to'plami ~1400 qator. Bunday hajmda AUC ning standart xatosi taxminan 0.01 atrofida. Ya'ni 0.01 farq — tasodif chegarasida. CV esa 4 ta fold bo'yicha juftlashgan farqni va uning SE sini berdi — bu ancha ishonchliroq dalil.

2. Testni qaror uchun ishlatish — uni validatsiyaga aylantirish. Agar test natijasiga qarab model tanlasak, test endi "ko'rilmagan ma'lumot" emas. Keyingi hisobotdagi test balli optimistik bo'ladi (18-qism, "g'olib la'nati").

3. Qaror mezoni oldindan belgilangan edi: |farq| > 2*SE. CV da bu shart bajarilmadi → soddaroq model. Mezonni natijani ko'rgandan keyin o'zgartirish — p-hacking ning bir ko'rinishi.

Menejerga javob:

"Test farqi 0.01 — bu test to'plami hajmida tasodifiy tebranish bilan bir xil kattalikda. Biz qarorni oldindan belgilangan mezon bo'yicha, 4 ta foldda juftlashgan taqqoslash bilan qabul qildik — u yerda farq sezilarli emas edi. Shuning uchun HistGB ni tanlaymiz: u tezroq, sozlash va tushuntirish osonroq. Agar tarmoqning ustunligini tekshirmoqchi bo'lsak — ko'proq fold yoki ko'proq ma'lumot bilan yangi taqqoslash o'tkazamiz, testga qarab emas."

Qachon fikrni o'zgartirish mumkin:

  • Yangi, mustaqil ma'lumot to'plamida tarmoq izchil ustun chiqsa
  • CV foldlari soni oshirilganda farq 2*SE dan oshsa
  • Tarmoqning boshqa foydasi bo'lsa: masalan, shahar embeddinglari boshqa vazifada kerak bo'lsa (21.10)

Muhim nuans: testda tarmoq yomonroq chiqqanda ham xuddi shu mantiq ishlaydi — biz qarorni testga qarab o'zgartirmaymiz. Bu bir tomonlama qoida emas.

Nimani mustahkamlaydi: 2.4-bo'lim.


Xulosa

Bu darsda to'liq PyTorch loyihasini qurdik.

Eng muhim uch fikr:

  1. Loyiha — komponentlar zanjiri, har biri alohida sinaladi. Konfig → ma'lumot (lug'at va masshtab faqat o'quvda) → Dataset → embedding li model → tez tekshiruvlar va bitta batch testi → Trainer va eng yaxshi holat → juftlashgan taqqoslash → test → paket. 1-misolda to'rtta tez tekshiruv va bitta batch testi o'rgatishdan oldin quvurni tasdiqladi.

  2. Qaror juftlashgan farq bilan, test esa faqat tasdiqlash uchun. Logistik regressiya, HistGradientBoosting va tarmoq bir xil foldlarda baholandi. Ma'lumotda nochiziqliliklar bo'lgani uchun logistik regressiya ikkalasidan sezilarli ortda qoldi, HistGradientBoosting esa tarmoqdan ham 2*SE dan ortiq yaxshi chiqdi — qoida bo'yicha u tanlandi. Test oxirida bir marta ochildi va xuddi shu tartibni tasdiqladi; uning vazifasi qarorni o'zgartirish emas.

  3. Paket — xavfsiz turlardan, o'zini tekshiradigan. Lug'atlar satr kalitli, masshtab ro'yxat, versiya str — shuning uchun paket weights_only=True bilan to'liq yuklandi. Bashoratchi yuklanganda nazorat namunasini qayta hisoblab o'zini tekshirdi, o'quvda bo'lmagan shahar esa xatosiz 0-indeksga tushdi. Paketlash mexanizmi bu darsda tarmoq uchun ko'rsatildi; ishlab chiqarishga esa 3-qadam qarori bo'yicha HistGradientBoosting topshiriladi — va bu ham halol natija.

Bu bilan 21-qism — PyTorch yakunlandi. Endi bizda har qanday chuqur o'rganish loyihasi uchun tayyor skelet bor. Keyingi qismda uni kompyuter ko'rishga qo'llaymiz: rasm ma'lumoti, konvolyutsiya, CNN arxitekturalari, augmentatsiya va transfer learning.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
21.12-dars: Amaliyot — to'liq PyTorch loyihasi — IlmHamroh