IlmHamroh
Data Science va sun'iy intellekt/MLOps va deploy4/14-dars37 daqiqa
Mundarija (22)

27.4-dars: Eksperiment kuzatuvi

27-QISM — MLOPS VA DEPLOY · 4-dars


1. Kirish va motivatsiya

Oldingi darslarda uchta g'isht qo'ydik: quvur har bosqichda iz qoldiradi 27.1-bob, natijani qayta olish uchun manifest kerak 27.2-bob, ma'lumot esa tekshirilgan va xeshlangan holda keladi 27.3-bob. Endi modelning o'zi bilan ishlaydigan bosqichga o'tamiz: tajribalar. Data scientist bir kunda o'nlab, bir haftada yuzlab yurish qiladi — turli modellar, giperparametrlar, belgilar to'plamlari. Ularning qaysi biri eng yaxshi edi, qaysi sozlama bilan, qaysi ma'lumotda va qaysi kod bilan?

Ko'pchilik bu savolga fayl nomlari bilan javob berishga urinadi: model_final.pkl, model_final_v2.pkl, model_final_v3_haqiqiy.pkl, yonida esa README da "v3 — eng yaxshi, AUC 0.94". Bir oydan keyin bu tizim qulaydi: fayl ustiga yozilgan, AUC boshqa validatsiya to'plamida hisoblangan, ma'lumot o'sha paytda boshqacha edi, kod esa noutbukning qaysi katagida ekani noma'lum. 21.11-darsda JSON Lines jurnal bilan bu muammoning bir qismini hal qilgan edik. Bu darsda uni to'liq eksperiment kuzatuvchisi ga aylantiramiz — MLflow ning kichik nusxasini sqlite ustida noldan quramiz.

Kuzatuvchi faqat "daftar" emas. U so'rovlarga javob beradi ("depth=3 bo'lgan yurishlar ichida eng yaxshisi"), ikki yurishni aniq solishtiradi, yurish yozuvidan modelni qayta quradi — va bir xavfni ham ko'rinadigan qiladi: yuzlab yurishdan validatsiyada eng yaxshisini tanlaganda natija optimistik bo'ladi. Buni "g'olib la'nati" deymiz va uni o'lchaymiz.

Real vaziyat. Jamoa rahbariyatga "yangi model eskisidan AUC bo'yicha 0.16 ga yaxshi" deb hisobot berdi — README dagi ikki raqam farqi. Ishlab chiqarishda yaxshilanish sezilmadi. Keyin ma'lum bo'ldi: "eski" model shovqinli belgilangan ma'lumotda baholangan, "yangi" esa tozalangan ma'lumotda — ya'ni farqning deyarli hammasi ma'lumot tozalanganidan, modeldan emas. Kuzatuvchida ikkala yurishning ma'lumot xeshi yozilganida, bu ikki raqam hech qachon bir jadvalda solishtirilmasdi. 1-misolda shu vaziyatni takrorlaymiz.

Bu darsda har yurishni to'liq qayd qiladigan, so'rov bilan eng yaxshisini topadigan va tanlov optimizmini halol ko'rsatadigan kuzatuv tizimini quramiz.

Bu darsda:

  • Nega fayl nomlari va README ishlamaydi
  • Har yurishda nimani qayd qilish kerak
  • Kuzatuvchi arxitekturasi: eksperiment, yurish, param, metrika, teg, artefakt
  • So'rovlar va ikki yurishni solishtirish
  • G'olib la'nati
  • Autolog va yurishdan qayta tiklash
  • MLflow API (ma'lumotnoma)
  • Amaliy: sqlite va fayl asosidagi kuzatuvchilar noldan

ℹ Misollar real sklearn/sqlite3 bilan (Python 3.14). MLflow mashinada o'rnatilmagan — uning API si ma'lumotnoma blokida; misollarda xuddi shu g'oyani noldan quramiz.


2. Nazariya — chuqur tushuntirish

2.1. Nega fayl nomlari va README ishlamaydi

text
model_final.pkl               "LogReg, AUC 0.745"
model_final_v2.pkl            "GB, AUC 0.780"
model_final_v3_haqiqiy.pkl    "GB lr=0.3, AUC 0.944 - ENG YAXSHI!"

MUAMMOLAR:
  1. USTIGA YOZISH  katak qayta ishga tushdi -> model_final.pkl endi BOSHQA model
  2. SOLISHTIRIB BO'LMAYDI  har AUC boshqa validatsiyada / boshqa ma'lumotda
  3. KONTEKST YO'Q  qaysi kod? qaysi belgilar? qaysi urug'? qaysi versiya?
  4. SO'ROV YO'Q  "depth=3 dagi eng yaxshisi" - qo'lda qidirish
  5. MUVAFFAQIYATSIZLAR YO'Q  faqat "yaxshi" natijalar yoziladi
     -> nechta variant sinalgani noma'lum -> optimizmni baholab bo'lmaydi
  6. NOM - MA'LUMOT EMAS  "haqiqiy", "final", "yangi" - hech narsa demaydi

Oxirgi nuqta alohida muhim: 18.11-darsda ko'rganimizdek, nechta variant sinalgani (T) validatsiya natijasining qanchalik optimistik ekanini belgilaydi. Faqat "g'oliblar" yozilsa, T yo'qoladi.

Model fayli — natija emas, yurishning bitta artefakti. Natija — to'liq yozuv: nima, qanday, nimada va qaysi kod bilan.

2.2. Har yurishda nimani qayd qilish kerak

Guruh Nima Nega
Parametrlar Barcha giperparametrlar, belgilar ro'yxati, urug' Qayta qurish uchun
Metrikalar Asosiy va yordamchi metrikalar, qadamlar bilan (davr, daraxt soni) Taqqoslash, o'rganish egri chizig'i
Kod versiyasi Git commit + "toza"mi (27.2) Qaysi mantiq
Ma'lumot O'quv va validatsiya xeshlari yoki versiyasi (27.3) Solishtirish mumkinmi
Muhit Python, paketlar (lock), apparat Reproduksiya
Artefaktlar Model fayli (+ sha256), bashoratlar, grafiklar, konfig Qayta ishlatish, audit
Teglar Muallif, maqsad, model turi, bosqich Filtrlash
Holat RUNNING / FINISHED / FAILED + xato matni Muvaffaqiyatsizlar ham hisobda
text
QOIDA: yozuv shunchalik to'liq bo'lsinki, boshqa odam UNDAN
  1) modelni qayta qura olsin
  2) natijani qaysi sharoitda olinganini bilsin
  3) uni boshqa yurish bilan solishtirish mumkinmi - aniqlay olsin

Ma'lumot xeshi — solishtirishning sharti. Ikki yurishning validatsiya xeshi boshqa bo'lsa, ularning metrikalarini bir jadvalda taqqoslash ma'nosiz.

2.3. Kuzatuvchi arxitekturasi

text
EKSPERIMENT  (masalan, "churn_gb_qidiruv")
  +-- YURISH (run)  id, nom, holat, ma'lumot xeshi, kod xeshi
        +-- PARAM     kalit -> qiymat (bir marta, o'zgarmas)
        +-- METRIKA   kalit, QADAM -> qiymat (ko'p marta)
        +-- TEG       kalit -> qiymat (o'zgarishi mumkin)
        +-- ARTEFAKT  fayl yo'li + sha256

SAQLASH (MLflow kabi tizimlarda):
  metama'lumot omborchasi (backend store): sqlite / PostgreSQL
    -> param, metrika, teg: kichik, ko'p, SO'ROV qilinadi
  artefakt omborchasi (artifact store): fayl tizimi / S3 / GCS
    -> model, grafik: katta, kam, faqat yuklab olinadi

MLflow FAYL REJIMI (mlruns/):
  mlruns/<eksperiment_id>/<run_id>/
      meta.yaml, params/<kalit>, metrics/<kalit>, tags/<kalit>, artifacts/

Param va metrika farqi muhim: parametr yurish boshida bir marta yoziladi va o'zgarmaydi (MLflow uni qayta yozishga ruxsat bermaydi — bu tasodifiy ustiga yozishdan himoya); metrika esa qadamlar bilan ko'p marta yoziladi va o'rganish egri chizig'ini hosil qiladi.

Metama'lumot — so'rov uchun bazada, katta fayllar — artefakt omborida, orasida esa sha256.

2.4. So'rovlar va ikki yurishni solishtirish

text
SO'ROVLAR:
  eng yaxshi K yurish:  WHERE holat='FINISHED' AND kalit='val_auc'
                        ORDER BY qiymat DESC LIMIT K
  filtr:                param max_depth = 3, teg model = 'GB'
  erta to'xtash nuqtasi: metrika qadamlari ichida maksimum

IKKI YURISHNI SOLISHTIRISH:
  1. SHART: bir xil validatsiya xeshi (aks holda - to'xtash)
  2. parametr farqi: faqat farq qiladigan kalitlar
  3. metrikalar qadamma-qadam (egri chiziqlar)
  4. farqning NOANIQLIGI: bir xil validatsiya qatorlarida juftlashgan
     bootstrap -> farq va SE; |farq| > 2*SE -> sezilarli
     (buning uchun artefakt - model yoki bashoratlar - kerak!)

Solishtirishning oxirgi qadami ko'pincha unutiladi: val AUC 0.9445 va 0.9429 — "birinchisi yaxshiroq" degan xulosa uchun farqning standart xatosi kerak. U faqat ikkala modelning bir xil qatorlardagi bashoratlaridan hisoblanadi — shuning uchun artefakt saqlanishi shart (2-misol).

2.5. G'olib la'nati

text
K ta yurish, har birining validatsiya bahosi = haqiqiy sifat + shovqin
  val_k = haqiqiy_k + e_k,   e_k ~ validatsiya to'plami tasodifi

max_k(val_k) ni tanlaymiz:
  haqiqiy sifatlar yaqin bo'lsa -> tanlov ASOSAN shovqinni tanlaydi
  tanlanganning val bahosi: haqiqiy + (eng katta e_k) -> OPTIMISTIK
  K oshgani sari optimizm oshadi (ko'p "lotereya chiptasi")

O'LCHASH: tanlangan yurishni YOPIQ testda baholash
  optimizm = val(tanlangan) - test(tanlangan)

YECHIMLAR (18.4, 18.11):
  yopiq test - faqat yakunda, BIR marta
  nested CV - tanlov jarayonining o'zini baholash
  hisobotga: K (nechta yurish) va tanlanganning TEST natijasi
  katta validatsiya -> kichik shovqin -> kichik optimizm

Kuzatuvchi bu yerda ikki yo'l bilan yordam beradi: (1) hamma yurish (muvaffaqiyatsizlari ham) yoziladi, shuning uchun K ma'lum; (2) yopiq test natijasi alohida metrika sifatida faqat tanlangan yurishga yoziladi va hisobotda aynan u keltiriladi.

Ko'p yurishdan eng yaxshisining validatsiya bahosi — hisobot raqami emas. Hisobot raqami — tanlangan yurishning yopiq testdagi natijasi.

2.6. Autolog va yurishdan qayta tiklash

text
MUAMMO: qo'lda log_param(...) - unutiladi, ba'zi parametrlar yozilmaydi
AUTOLOG: model.get_params() dan HAMMA parametr, ma'lumot xeshi,
         kod xeshi, versiya, metrika, artefakt - avtomatik
         (mlflow.autolog() - sklearn, xgboost, pytorch uchun)

QAYTA TIKLASH (yozuvdan):
  1. ma'lumot xeshi mosmi? -> yo'q bo'lsa RAD ETISH
  2. model turi + parametrlar -> model qayta quriladi
  3. qayta o'qitish -> metrika yozuvdagiga TENGmi
  4. artefakt sha256 mosmi; saqlangan va qayta qurilgan bashoratlar tengmi

Qayta tiklash — kuzatuvchi yozuvining eng yaxshi testi. Yozuvdan modelni qayta qurib bo'lmasa, yozuv to'liq emas.

2.7. MLflow API (ma'lumotnoma)

Quyidagi kod ishga tushirilmaydi — mashinada mlflow o'rnatilmagan. U bu darsdagi noldan qurilgan kuzatuvchining "haqiqiy" ekvivalentini ko'rsatadi.

python
import mlflow
import mlflow.sklearn
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import roc_auc_score

mlflow.set_tracking_uri("sqlite:///mlflow.db")      # yoki "http://server:5000"
mlflow.set_experiment("churn_gb_qidiruv")

with mlflow.start_run(run_name="lr0.1_d3") as run:
    params = {"learning_rate": 0.1, "max_depth": 3, "n_estimators": 100}
    mlflow.log_params(params)                        # yoki log_param(k, v)
    mlflow.set_tag("model", "GradientBoosting")
    mlflow.set_tag("malumot_xesh", "952c6655a350")

    model = GradientBoostingClassifier(random_state=0, **params).fit(X_tr, y_tr)
    for qadam, p in enumerate(model.staged_predict_proba(X_va), 1):
        if qadam % 20 == 0:
            mlflow.log_metric("val_auc", roc_auc_score(y_va, p[:, 1]), step=qadam)

    mlflow.log_artifact("konfig.json")               # istalgan fayl
    mlflow.sklearn.log_model(model, name="model")    # model + muhit + imzo

# so'rov: eng yaxshi yurishlar (pandas DataFrame qaytaradi)
eng = mlflow.search_runs(
    experiment_names=["churn_gb_qidiruv"],
    filter_string="params.max_depth = '3' and attributes.status = 'FINISHED'",
    order_by=["metrics.val_auc DESC"], max_results=5)

# yurishdan modelni yuklash
model = mlflow.sklearn.load_model(f"runs:/{run.info.run_id}/model")

# avtomatik qayd qilish
mlflow.sklearn.autolog()                             # fit() da hammasi yoziladi
Bu darsda MLflow
tr.start_run(eksperiment, nom) mlflow.start_run(run_name=...)
r.log_param(k, v) mlflow.log_param(k, v)
r.log_metric(k, v, qadam) mlflow.log_metric(k, v, step=...)
r.set_tag(k, v) mlflow.set_tag(k, v)
r.log_artifact(yol, nom) mlflow.log_artifact(yol), mlflow.sklearn.log_model
SQL so'rov mlflow.search_runs(filter_string=..., order_by=...)
autolog(...) mlflow.sklearn.autolog()
holat = 'FAILED' yurish istisno bilan tugasa — avtomatik

Boshqa vositalar (Weights & Biases, Neptune, Comet, TensorBoard) xuddi shu tushunchalar ustida qurilgan: yurish, parametr, qadamli metrika, artefakt. Bulut xizmatlarining narxi va limitlari o'zgarib turadi — ularni rasmiy sahifalardan tekshiring.

2.8. Tuzoqlar

Asosiy tuzoqlar: model faylini natija deb hisoblash; faqat muvaffaqiyatli yurishlarni yozish; turli validatsiya to'plamlaridagi metrikalarni solishtirish; ma'lumot va kod xeshisiz yozuv; parametrlarning bir qismini qo'lda yozish (qolganini unutish); metrikani qadamsiz yozish (egri chiziq yo'qoladi); artefaktni sha256 siz saqlash; ikki yurish farqini SE siz talqin qilish; ko'p yurishdan eng yaxshisining validatsiya bahosini hisobot qilish; yopiq testni tanlovda ishlatish; kuzatuvchiga sirlar (parol, token) ni param sifatida yozish.


3. Tez ma'lumotnoma

python
import hashlib
import json
import sqlite3
from contextlib import contextmanager

SXEMA = """
CREATE TABLE IF NOT EXISTS yurish (id INTEGER PRIMARY KEY, eksperiment TEXT,
    nom TEXT, holat TEXT, malumot_xesh TEXT, kod_xesh TEXT);
CREATE TABLE IF NOT EXISTS param (yurish_id INTEGER, kalit TEXT, qiymat TEXT,
    PRIMARY KEY (yurish_id, kalit));
CREATE TABLE IF NOT EXISTS metrika (yurish_id INTEGER, kalit TEXT, qadam INTEGER,
    qiymat REAL, PRIMARY KEY (yurish_id, kalit, qadam));
"""


@contextmanager
def start_run(db, eksperiment, nom):
    with db:
        yid = db.execute("INSERT INTO yurish (eksperiment, nom, holat) "
                         "VALUES (?, ?, 'RUNNING')", (eksperiment, nom)).lastrowid
    try:
        yield yid
    except Exception:
        with db:
            db.execute("UPDATE yurish SET holat='FAILED' WHERE id=?", (yid,))
        raise
    with db:
        db.execute("UPDATE yurish SET holat='FINISHED' WHERE id=?", (yid,))


def log_param(db, yid, k, v):
    with db:
        db.execute("INSERT INTO param VALUES (?, ?, ?)", (yid, k, json.dumps(v)))


def log_metric(db, yid, k, v, qadam=0):
    with db:
        db.execute("INSERT OR REPLACE INTO metrika VALUES (?, ?, ?, ?)",
                   (yid, k, qadam, float(v)))


ENG_YAXSHI = """
SELECT y.nom, m.qiymat FROM yurish y JOIN metrika m ON m.yurish_id = y.id
WHERE y.holat = 'FINISHED' AND m.kalit = 'val_auc' AND m.qadam = ?
ORDER BY m.qiymat DESC LIMIT ?"""


def artefakt_xesh(yol):
    return hashlib.sha256(open(yol, "rb").read()).hexdigest()[:12]

Tuzilma xulosasi

text
yurish = param + metrika(qadam) + teg + artefakt(sha256) + holat
       + ma'lumot xeshi + kod xeshi + muhit
solishtirish sharti: bir xil validatsiya xeshi
farq: juftlashgan bootstrap SE (artefakt kerak)
g'olib la'nati: max(val) optimistik, K bilan oshadi -> yopiq test
autolog: get_params() dan hammasi; qayta tiklash - yozuv to'liqligining testi
MLflow: start_run, log_param, log_metric(step), log_artifact, log_model, search_runs

4. Batafsil misollar

Misollar real sklearn/sqlite3 bilan (Python 3.14). Kuzatuvchilar noldan; MLflow ishlatilmaydi.

Misol 1 — model_final_v3_haqiqiy.pkl: fayl nomlari va README nima uchun yetmaydi

Noutbuk sessiyasi: to'rt marta o'qitish, uch fayl va README eslatmalari. Orada ma'lumot "jim" yangilangan — belgilash shovqini 20% dan 3% ga tushgan (v1 → v2). Keyin fayllardan savollarga javob berishga urinamiz, ularni bir xil yangi testda qayta baholaymiz va nihoyat har yurish uchun to'liq yozuv yaratamiz.

python
"""Nega model_final_v3_haqiqiy.pkl ishlamaydi va yurish yozuvi nima beradi."""

import hashlib
import inspect
import json
import tempfile
from importlib.metadata import version
from pathlib import Path

import joblib
import numpy as np
from sklearn.datasets import make_classification
from sklearn.ensemble import GradientBoostingClassifier, RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import train_test_split

X_ALL, Y_ALL = make_classification(n_samples=6000, n_features=10, n_informative=4,
                                   flip_y=0.0, random_state=1)


def shovqin(y, ulush, seed):
    """Belgilash xatolari: berilgan ulushdagi belgilar teskari."""
    rng = np.random.default_rng(seed)
    y = y.copy()
    i = rng.random(len(y)) < ulush
    y[i] = 1 - y[i]
    return y


def malumot(versiya):
    """v1: belgilash shovqinli (20%); v2: ma'lumot 'jim' tozalangan (3%)."""
    ulush = {"v1": 0.20, "v2": 0.03}[versiya]
    X, y = X_ALL[:3000], shovqin(Y_ALL[:3000], ulush, seed=5)
    return train_test_split(X, y, test_size=0.3, random_state=0)


def orgat(model, versiya):
    Xtr, Xva, ytr, yva = malumot(versiya)
    model.fit(Xtr, ytr)
    return model, roc_auc_score(yva, model.predict_proba(Xva)[:, 1])


def xesh(b):
    return hashlib.sha256(b).hexdigest()[:12]


def auc_farq_se(y, p1, p2, n_boot=300, seed=0):
    """Bir xil test qatorlarida juftlashgan bootstrap: AUC farqi va SE."""
    rng = np.random.default_rng(seed)
    farqlar = []
    for _ in range(n_boot):
        i = rng.integers(0, len(y), len(y))
        farqlar.append(roc_auc_score(y[i], p1[i]) - roc_auc_score(y[i], p2[i]))
    return roc_auc_score(y, p1) - roc_auc_score(y, p2), float(np.std(farqlar, ddof=1))


def main() -> None:
    with tempfile.TemporaryDirectory() as t:
        papka = Path(t)
        eslatma, eslatma_auc = {}, {}

        print("=== 1. Noutbuk sessiyasi: fayllar va README eslatmalari ===")
        m, a = orgat(LogisticRegression(max_iter=1000), "v1")
        joblib.dump(m, papka / "model_final.pkl")
        eslatma["model_final.pkl"], eslatma_auc["model_final.pkl"] = "LogReg", a
        m, a = orgat(GradientBoostingClassifier(random_state=0), "v1")
        joblib.dump(m, papka / "model_final_v2.pkl")
        eslatma["model_final_v2.pkl"], eslatma_auc["model_final_v2.pkl"] = "GB", a
        m, a = orgat(RandomForestClassifier(n_estimators=100, random_state=0), "v2")
        joblib.dump(m, papka / "model_final.pkl")          # katak qayta ishga tushdi!
        m, a = orgat(GradientBoostingClassifier(learning_rate=0.3, random_state=0), "v2")
        joblib.dump(m, papka / "model_final_v3_haqiqiy.pkl")
        eslatma["model_final_v3_haqiqiy.pkl"] = "GB lr=0.3 - ENG YAXSHI!"
        eslatma_auc["model_final_v3_haqiqiy.pkl"] = a
        for f in sorted(papka.glob("*.pkl")):
            print(f"  {f.name:<28} README: {eslatma[f.name]}, "
                  f"AUC {eslatma_auc[f.name]:.3f}")

        print("\n=== 2. Savollarga javob berishga urinish ===")
        for f in sorted(papka.glob("*.pkl")):
            print(f"  {f.name:<28} haqiqiy turi: {type(joblib.load(f)).__name__}")
        print("  model_final.pkl: README da LogReg, faylda RandomForest (ustiga yozilgan)")
        print("  qaysi ma'lumot? qaysi kod? qaysi validatsiya? - NOMA'LUM")

        print("\n=== 3. Bir xil yangi test to'plamida qayta baholash ===")
        Xt, yt = X_ALL[3000:], shovqin(Y_ALL[3000:], 0.03, seed=6)
        pr = {}
        for f in sorted(papka.glob("*.pkl")):
            pr[f.name] = joblib.load(f).predict_proba(Xt)[:, 1]
            print(f"  {f.name:<28} test AUC {roc_auc_score(yt, pr[f.name]):.4f}")
        v3, v2 = "model_final_v3_haqiqiy.pkl", "model_final_v2.pkl"
        d, se = auc_farq_se(yt, pr[v3], pr[v2])
        print(f"  v3 - v2: README da {eslatma_auc[v3] - eslatma_auc[v2]:+.3f}, "
              f"bir xil testda {d:+.4f} (SE {se:.4f})")

        print("\n=== 4. To'g'ri yo'l: har yurish - to'liq yozuv ===")
        kod_xesh = xesh(inspect.getsource(orgat).encode())
        yozuvlar = []
        for nom, model, v in [
                ("logreg_v1", LogisticRegression(max_iter=1000), "v1"),
                ("gb_v1", GradientBoostingClassifier(random_state=0), "v1"),
                ("gb_v2", GradientBoostingClassifier(random_state=0), "v2"),
                ("gb_lr03_v2", GradientBoostingClassifier(learning_rate=0.3,
                                                          random_state=0), "v2")]:
            m, a = orgat(model, v)
            Xtr, Xva, ytr, yva = malumot(v)
            yol = papka / f"{nom}.joblib"
            joblib.dump(m, yol)
            yozuvlar.append({
                "yurish": nom, "model": type(m).__name__,
                "param": {k: m.get_params()[k] for k in sorted(m.get_params())
                          if k in ("C", "learning_rate", "n_estimators", "max_depth")},
                "metrika": {"val_auc": round(a, 4)},
                "malumot": {"versiya": v, "xesh": xesh(Xtr.tobytes() + ytr.tobytes()),
                            "val_xesh": xesh(Xva.tobytes() + yva.tobytes())},
                "kod_xesh": kod_xesh, "sklearn": version("scikit-learn"),
                "artefakt": {"yol": yol.name, "sha256": xesh(yol.read_bytes())}})
        (papka / "yurishlar.jsonl").write_text(
            "\n".join(json.dumps(y) for y in yozuvlar) + "\n", encoding="utf-8")
        print(f"  bitta yozuv kalitlari: {list(yozuvlar[0])}")
        for y in yozuvlar:
            print(f"  {y['yurish']:<11} val_auc {y['metrika']['val_auc']:.4f}  "
                  f"val_xesh {y['malumot']['val_xesh']}")
        guruh = {}
        for y in yozuvlar:
            guruh.setdefault(y["malumot"]["val_xesh"], []).append(y)
        print("  faqat bir xil validatsiya ichida solishtiramiz:")
        for g in sorted(guruh.values(), key=lambda g: g[0]["yurish"]):
            a, b = g[0], g[-1]
            print(f"    {b['yurish']} - {a['yurish']}: "
                  f"{b['metrika']['val_auc'] - a['metrika']['val_auc']:+.4f}")
        print(f"  gb_v2 - gb_v1 (bir xil model, faqat MA'LUMOT boshqa): "
              f"{yozuvlar[2]['metrika']['val_auc'] - yozuvlar[1]['metrika']['val_auc']:+.4f}"
              f" - bu model yutug'i EMAS")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Noutbuk sessiyasi: fayllar va README eslatmalari ===
  model_final.pkl              README: LogReg, AUC 0.745
  model_final_v2.pkl           README: GB, AUC 0.780
  model_final_v3_haqiqiy.pkl   README: GB lr=0.3 - ENG YAXSHI!, AUC 0.944

=== 2. Savollarga javob berishga urinish ===
  model_final.pkl              haqiqiy turi: RandomForestClassifier
  model_final_v2.pkl           haqiqiy turi: GradientBoostingClassifier
  model_final_v3_haqiqiy.pkl   haqiqiy turi: GradientBoostingClassifier
  model_final.pkl: README da LogReg, faylda RandomForest (ustiga yozilgan)
  qaysi ma'lumot? qaysi kod? qaysi validatsiya? - NOMA'LUM

=== 3. Bir xil yangi test to'plamida qayta baholash ===
  model_final.pkl              test AUC 0.9516
  model_final_v2.pkl           test AUC 0.9274
  model_final_v3_haqiqiy.pkl   test AUC 0.9466
  v3 - v2: README da +0.164, bir xil testda +0.0192 (SE 0.0034)

=== 4. To'g'ri yo'l: har yurish - to'liq yozuv ===
  bitta yozuv kalitlari: ['yurish', 'model', 'param', 'metrika', 'malumot', 'kod_xesh', 'sklearn', 'artefakt']
  logreg_v1   val_auc 0.7448  val_xesh 7935fc4f1ad8
  gb_v1       val_auc 0.7805  val_xesh 7935fc4f1ad8
  gb_v2       val_auc 0.9502  val_xesh 92a7aea2fafe
  gb_lr03_v2  val_auc 0.9441  val_xesh 92a7aea2fafe
  faqat bir xil validatsiya ichida solishtiramiz:
    gb_lr03_v2 - gb_v2: -0.0061
    gb_v1 - logreg_v1: +0.0357
  gb_v2 - gb_v1 (bir xil model, faqat MA'LUMOT boshqa): +0.1697 - bu model yutug'i EMAS

Nima ko'rsatdi: 2.1, 2.2-bo'limlar. Fayl nomlari bilan boshqarishning uchta nuqsoni bitta sessiyada paydo bo'ldi. Birinchidan, model_final.pkl ustiga yozilgan: README LogReg deydi, faylda esa RandomForest. Ikkinchidan, README dagi AUC lar turli ma'lumotda hisoblangan: v3 ning "ustunligi" README bo'yicha +0.164, lekin ikkala faylni bir xil yangi testda baholaganda farq ancha kichik — sezilarli, lekin taxminan sakkiz baravar kam. Uchinchidan, fayllardan qaysi ma'lumot va qaysi kod ishlatilganini bilishning iloji yo'q. Qizig'i, README da eng past AUC bilan turgan model_final.pkl (aslida v2 da o'qitilgan RandomForest) bir xil testda uchalasining eng yaxshisi bo'lib chiqdi — README ga qarab uni hech kim tanlamasdi.

To'liq yozuvlar bu savollarning hammasiga javob beradi. Eng muhimi — val_xesh: yozuvlar o'z-o'zidan ikki guruhga bo'lindi va solishtirish faqat guruh ichida qilinadi. Bir xil model (GB, sukut sozlamalar) faqat ma'lumot versiyasi o'zgargani uchun val AUC da katta sakrash qildi — README dagi "yaxshilanish" ning deyarli hammasi aynan shu. learning_rate=0.3 ning o'z hissasi esa v2 guruhi ichida hatto biroz manfiy — ya'ni "ENG YAXSHI!" belgisi butunlay ma'lumotdan kelgan. Kirishdagi real vaziyatning o'zi.

Misol 2 — sqlite ustida kichik kuzatuvchi: qidiruv, so'rovlar va ikki yurishni solishtirish

MLflow ga o'xshash API: start_run (kontekst menejeri — istisno bo'lsa holat FAILED), log_param, qadamli log_metric, set_tag, sha256 bilan log_artifact. Gradient boosting uchun 9 kombinatsiyali qidiruv (+1 ta ataylab noto'g'ri parametr), har 20 daraxtda val AUC. Keyin SQL so'rovlar va eng yaxshi ikki yurishning artefaktlari orqali juftlashgan solishtirish.

python
"""sqlite ustida MLflow ga o'xshash kichik eksperiment kuzatuvchisi."""

import hashlib
import json
import sqlite3
import tempfile
from contextlib import closing, contextmanager
from pathlib import Path

import joblib
import numpy as np
from sklearn.datasets import make_classification
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import train_test_split

SXEMA = """
CREATE TABLE IF NOT EXISTS yurish (id INTEGER PRIMARY KEY, eksperiment TEXT,
    nom TEXT, holat TEXT, malumot_xesh TEXT, kod_xesh TEXT);
CREATE TABLE IF NOT EXISTS param (yurish_id INTEGER, kalit TEXT, qiymat TEXT,
    PRIMARY KEY (yurish_id, kalit));
CREATE TABLE IF NOT EXISTS metrika (yurish_id INTEGER, kalit TEXT, qadam INTEGER,
    qiymat REAL, PRIMARY KEY (yurish_id, kalit, qadam));
CREATE TABLE IF NOT EXISTS teg (yurish_id INTEGER, kalit TEXT, qiymat TEXT,
    PRIMARY KEY (yurish_id, kalit));
CREATE TABLE IF NOT EXISTS artefakt (yurish_id INTEGER, yol TEXT, sha256 TEXT);
"""


class Yurish:
    def __init__(self, tracker, yid):
        self.t, self.id = tracker, yid

    def log_param(self, kalit, qiymat):          # PRIMARY KEY: qayta yozib bo'lmaydi
        with self.t.db:
            self.t.db.execute("INSERT INTO param VALUES (?, ?, ?)",
                              (self.id, kalit, json.dumps(qiymat)))

    def log_metric(self, kalit, qiymat, qadam=0):
        with self.t.db:
            self.t.db.execute("INSERT OR REPLACE INTO metrika VALUES (?, ?, ?, ?)",
                              (self.id, kalit, qadam, float(qiymat)))

    def set_tag(self, kalit, qiymat):
        with self.t.db:
            self.t.db.execute("INSERT OR REPLACE INTO teg VALUES (?, ?, ?)",
                              (self.id, kalit, str(qiymat)))

    def log_artifact(self, manba, nom):
        yol = self.t.papka / "artefaktlar" / str(self.id) / nom
        yol.parent.mkdir(parents=True, exist_ok=True)
        yol.write_bytes(Path(manba).read_bytes())
        sha = hashlib.sha256(yol.read_bytes()).hexdigest()[:12]
        with self.t.db:
            self.t.db.execute("INSERT INTO artefakt VALUES (?, ?, ?)",
                              (self.id, yol.relative_to(self.t.papka).as_posix(), sha))


class Tracker:
    def __init__(self, papka):
        self.papka = Path(papka)
        self.db = sqlite3.connect(self.papka / "kuzatuv.db")
        self.db.executescript(SXEMA)

    def close(self):
        self.db.close()

    @contextmanager
    def start_run(self, eksperiment, nom, malumot_xesh="", kod_xesh=""):
        with self.db:
            cur = self.db.execute(
                "INSERT INTO yurish (eksperiment, nom, holat, malumot_xesh, kod_xesh) "
                "VALUES (?, ?, 'RUNNING', ?, ?)",
                (eksperiment, nom, malumot_xesh, kod_xesh))
        y = Yurish(self, cur.lastrowid)
        try:
            yield y
        except Exception as xato:
            y.set_tag("xato", f"{type(xato).__name__}: {xato}")
            self._holat(y.id, "FAILED")
            raise
        self._holat(y.id, "FINISHED")

    def _holat(self, yid, holat):
        with self.db:
            self.db.execute("UPDATE yurish SET holat=? WHERE id=?", (holat, yid))

    def yurish_ol(self, nom):
        yid, mx, kx = self.db.execute("SELECT id, malumot_xesh, kod_xesh FROM yurish "
                                      "WHERE nom=?", (nom,)).fetchone()
        return {"id": yid, "malumot": mx, "kod": kx,
                "param": dict(self.db.execute(
                    "SELECT kalit, qiymat FROM param WHERE yurish_id=?", (yid,))),
                "val_auc": dict(self.db.execute(
                    "SELECT qadam, qiymat FROM metrika WHERE yurish_id=? "
                    "AND kalit='val_auc'", (yid,))),
                "artefakt": self.db.execute(
                    "SELECT yol, sha256 FROM artefakt WHERE yurish_id=?",
                    (yid,)).fetchone()}


def main() -> None:
    X, y = make_classification(n_samples=2500, n_features=12, n_informative=5,
                               flip_y=0.05, random_state=0)
    Xtr, Xva, ytr, yva = train_test_split(X, y, test_size=0.3, random_state=0,
                                          stratify=y)
    mxesh = hashlib.sha256(Xva.tobytes() + yva.tobytes()).hexdigest()[:12]
    kxesh = "a1b2c3d"                          # haqiqiy loyihada: git rev-parse HEAD

    with tempfile.TemporaryDirectory() as t, closing(Tracker(t)) as tr:
        print("=== 1. Giperparametr qidiruvi - har kombinatsiya bitta yurish ===")
        tarmoq = [{"learning_rate": lr, "max_depth": d, "n_estimators": 100}
                  for lr in [0.03, 0.1, 0.3] for d in [2, 3, 5]]
        tarmoq.append({"learning_rate": -0.1, "max_depth": 3, "n_estimators": 100})
        for i, p in enumerate(tarmoq, 1):
            try:
                with tr.start_run("gb_qidiruv", f"yurish_{i:02d}", mxesh, kxesh) as r:
                    for k, v in p.items():
                        r.log_param(k, v)
                    r.set_tag("model", "GradientBoosting")
                    m = GradientBoostingClassifier(random_state=0, **p).fit(Xtr, ytr)
                    for qadam, pr in enumerate(m.staged_predict_proba(Xva), 1):
                        if qadam % 20 == 0:
                            r.log_metric("val_auc", roc_auc_score(yva, pr[:, 1]), qadam)
                    joblib.dump(m, Path(t) / "model.joblib")
                    r.log_artifact(Path(t) / "model.joblib", "model.joblib")
            except Exception:
                pass                           # yurish FAILED sifatida yozildi
        holatlar = tr.db.execute("SELECT holat, COUNT(*) FROM yurish GROUP BY holat "
                                 "ORDER BY holat").fetchall()
        print(f"  yurishlar: {holatlar}")
        nom, xato = tr.db.execute("SELECT y.nom, t.qiymat FROM yurish y JOIN teg t "
                                  "ON t.yurish_id = y.id WHERE t.kalit='xato'").fetchone()
        print(f"  muvaffaqiyatsiz: {nom} -> {xato[:58]}")
        for jadval in ["param", "metrika", "teg", "artefakt"]:
            n = tr.db.execute(f"SELECT COUNT(*) FROM {jadval}").fetchone()[0]
            print(f"  {jadval:<9} {n} qator")

        print("\n=== 2. So'rov: eng yaxshi 3 yurish (100-qadamdagi val_auc) ===")
        sorov = """
            SELECT y.nom, m.qiymat,
                   (SELECT qiymat FROM param WHERE yurish_id=y.id AND kalit='learning_rate'),
                   (SELECT qiymat FROM param WHERE yurish_id=y.id AND kalit='max_depth')
            FROM yurish y JOIN metrika m ON m.yurish_id = y.id
            WHERE y.holat='FINISHED' AND m.kalit='val_auc' AND m.qadam=100
            ORDER BY m.qiymat DESC LIMIT 3"""
        eng3 = tr.db.execute(sorov).fetchall()
        for nom, auc, lr, d in eng3:
            print(f"  {nom}: val_auc {auc:.4f}  lr={lr} depth={d}")
        filtr = tr.db.execute("""
            SELECT y.nom, m.qiymat FROM yurish y
            JOIN param p ON p.yurish_id = y.id AND p.kalit='max_depth' AND p.qiymat='2'
            JOIN metrika m ON m.yurish_id = y.id AND m.kalit='val_auc' AND m.qadam=100
            ORDER BY m.qiymat DESC""").fetchall()
        print(f"  filtr max_depth=2: {[(n, round(v, 4)) for n, v in filtr]}")
        nom, qadam, auc = tr.db.execute("""
            SELECT y.nom, m.qadam, m.qiymat FROM metrika m JOIN yurish y
            ON y.id = m.yurish_id WHERE m.kalit='val_auc'
            ORDER BY m.qiymat DESC LIMIT 1""").fetchone()
        print(f"  barcha qadamlar ichida eng yuqori: {nom}, qadam {qadam}: {auc:.4f}")

        print("\n=== 3. Eng yaxshi ikki yurishni solishtirish ===")
        a, b = tr.yurish_ol(eng3[0][0]), tr.yurish_ol(eng3[1][0])
        print(f"  bir xil ma'lumot va kod: "
              f"{a['malumot'] == b['malumot'] and a['kod'] == b['kod']}")
        farq = {k: (a["param"][k], b["param"][k]) for k in sorted(a["param"])
                if a["param"][k] != b["param"][k]}
        print(f"  parametr farqi ({eng3[0][0]}, {eng3[1][0]}): {farq}")
        print(f"  {'qadam':>5} {eng3[0][0]:>10} {eng3[1][0]:>10}")
        for q in sorted(a["val_auc"]):
            print(f"  {q:>5} {a['val_auc'][q]:>10.4f} {b['val_auc'][q]:>10.4f}")
        pa, pb = [joblib.load(Path(t) / r["artefakt"][0]).predict_proba(Xva)[:, 1]
                  for r in (a, b)]
        rng = np.random.default_rng(0)
        boot = []
        for _ in range(300):
            i = rng.integers(0, len(yva), len(yva))
            boot.append(roc_auc_score(yva[i], pa[i]) - roc_auc_score(yva[i], pb[i]))
        d = roc_auc_score(yva, pa) - roc_auc_score(yva, pb)
        se = float(np.std(boot, ddof=1))
        print(f"  farq {d:+.4f}, juftlashgan bootstrap SE {se:.4f} -> "
              f"{'sezilarli' if abs(d) > 2 * se else 'sezilarli emas'}")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Giperparametr qidiruvi - har kombinatsiya bitta yurish ===
  yurishlar: [('FAILED', 1), ('FINISHED', 9)]
  muvaffaqiyatsiz: yurish_10 -> InvalidParameterError: The 'learning_rate' parameter of Gr
  param     30 qator
  metrika   45 qator
  teg       11 qator
  artefakt  9 qator

=== 2. So'rov: eng yaxshi 3 yurish (100-qadamdagi val_auc) ===
  yurish_05: val_auc 0.9445  lr=0.1 depth=3
  yurish_06: val_auc 0.9429  lr=0.1 depth=5
  yurish_03: val_auc 0.9418  lr=0.03 depth=5
  filtr max_depth=2: [('yurish_07', 0.934), ('yurish_04', 0.9303), ('yurish_01', 0.9136)]
  barcha qadamlar ichida eng yuqori: yurish_05, qadam 80: 0.9448

=== 3. Eng yaxshi ikki yurishni solishtirish ===
  bir xil ma'lumot va kod: True
  parametr farqi (yurish_05, yurish_06): {'max_depth': ('3', '5')}
  qadam  yurish_05  yurish_06
     20     0.9272     0.9368
     40     0.9365     0.9426
     60     0.9420     0.9430
     80     0.9448     0.9427
    100     0.9445     0.9429
  farq +0.0016, juftlashgan bootstrap SE 0.0031 -> sezilarli emas

Nima ko'rsatdi: 2.3, 2.4-bo'limlar. O'n yurishning hammasi yozildi — to'qqiztasi FINISHED, ataylab noto'g'ri learning_rate li yurish esa FAILED va xato matni bilan. Bu muhim: muvaffaqiyatsiz yurish ham "sinalgan variant" va K ni oshiradi 2.5-bob. Har yurishda 3 parametr, 5 qadamli metrika, teg va sha256 li artefakt bor.

So'rovlar README da imkonsiz bo'lgan narsani bir necha qatorda qildi: eng yaxshi uchtasi, parametr bo'yicha filtr va barcha qadamlar ichidagi maksimum (erta to'xtash nuqtasi — u 100-qadamdan oldinroq bo'lishi mumkin). Solishtirishda avval shart tekshirildi (bir xil ma'lumot va kod), keyin faqat farq qiladigan parametr ko'rsatildi va egri chiziqlar qadamma-qadam yonma-yon qo'yildi.

Oxirgi qator — eng muhimi. Eng yaxshi ikki yurishning val AUC farqi kichik, va saqlangan artefaktlar (modellar) orqali hisoblangan juftlashgan bootstrap SE bu farq shovqin ichida ekanini ko'rsatdi. "1-o'rin" va "2-o'rin" amalda teng — tanlovni soddalik yoki narx hal qilishi kerak. Artefakt saqlanmaganida bu savolga javob berib bo'lmasdi.

Misol 3 — Fayl asosidagi kuzatuvchi (mlruns uslubi), autolog va yurishdan qayta tiklash

MLflow ning fayl rejimiga o'xshash tuzilma: har yurish — papka, ichida params/, metrics/, tags/, artifacts/ va meta.json. autolog funksiyasi get_params() dan barcha parametrlarni, ma'lumot va kod xeshini, versiyani, metrikani va modelni o'zi yozadi. Keyin eng yaxshi yurishni faqat yozuvdan qayta quramiz va natija aynan mos kelishini tekshiramiz.

python
"""Fayl asosidagi kuzatuvchi (mlruns uslubi), autolog va yurishdan qayta tiklash."""

import hashlib
import inspect
import json
import tempfile
from contextlib import contextmanager
from importlib.metadata import version
from pathlib import Path

import joblib
import numpy as np
from sklearn.ensemble import HistGradientBoostingClassifier, RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score

MODEL_TURLARI = {c.__name__: c for c in [LogisticRegression, RandomForestClassifier,
                                          HistGradientBoostingClassifier]}


def xesh(b):
    return hashlib.sha256(b).hexdigest()[:12]


class Yurish:
    def __init__(self, papka):
        self.papka = papka

    def log_param(self, k, v):
        (self.papka / "params" / k).write_text(json.dumps(v), encoding="utf-8")

    def log_metric(self, k, v, qadam=0):
        with open(self.papka / "metrics" / k, "a", encoding="utf-8") as f:
            f.write(f"{qadam} {float(v)!r}\n")          # MLflow: "vaqt qiymat qadam"

    def set_tag(self, k, v):
        (self.papka / "tags" / k).write_text(str(v), encoding="utf-8")

    def log_artifact(self, obj, nom):
        yol = self.papka / "artifacts" / nom
        joblib.dump(obj, yol)
        self.set_tag(f"sha256.{nom}", xesh(yol.read_bytes()))


class FaylTracker:
    """mlruns/<eksperiment>/<run_id>/{meta.json, params/, metrics/, tags/, artifacts/}"""

    def __init__(self, ildiz):
        self.ildiz = Path(ildiz) / "mlruns"
        self.hisob = 0

    @contextmanager
    def start_run(self, eksperiment, nom):
        self.hisob += 1
        rid = xesh(f"{eksperiment}/{nom}/{self.hisob}".encode())[:8]
        papka = self.ildiz / eksperiment / rid
        for p in ["params", "metrics", "tags", "artifacts"]:
            (papka / p).mkdir(parents=True)
        meta = {"run_id": rid, "nom": nom, "holat": "RUNNING"}
        try:
            yield Yurish(papka)
            meta["holat"] = "FINISHED"
        except Exception:
            meta["holat"] = "FAILED"
            raise
        finally:
            (papka / "meta.json").write_text(json.dumps(meta), encoding="utf-8")

    def search_runs(self, eksperiment, shart=lambda r: True, kalit="val_auc"):
        yurishlar = []
        for papka in sorted((self.ildiz / eksperiment).iterdir()):
            r = json.loads((papka / "meta.json").read_text(encoding="utf-8"))
            r["params"] = {f.name: json.loads(f.read_text(encoding="utf-8"))
                           for f in sorted((papka / "params").iterdir())}
            r["tags"] = {f.name: f.read_text(encoding="utf-8")
                         for f in sorted((papka / "tags").iterdir())}
            r["metrics"] = {f.name: float(f.read_text(encoding="utf-8").split()[-1])
                            for f in sorted((papka / "metrics").iterdir())}
            r["papka"] = papka
            if r["holat"] == "FINISHED" and shart(r):
                yurishlar.append(r)
        return sorted(yurishlar, key=lambda r: -r["metrics"].get(kalit, -1))


def autolog(yurish, model, Xtr, ytr, Xva, yva):
    """Qo'lda unutib qo'ymaslik uchun: hammasini avtomatik yozadi."""
    yurish.set_tag("model_turi", type(model).__name__)
    for k, v in sorted(model.get_params().items()):
        if isinstance(v, (int, float, str, bool)) or v is None:
            yurish.log_param(k, v)
    yurish.set_tag("malumot.oquv", xesh(Xtr.tobytes() + ytr.tobytes()))
    yurish.set_tag("malumot.val", xesh(Xva.tobytes() + yva.tobytes()))
    yurish.set_tag("kod", xesh(inspect.getsource(autolog).encode()))
    yurish.set_tag("sklearn", version("scikit-learn"))
    model.fit(Xtr, ytr)
    yurish.log_metric("val_auc", roc_auc_score(yva, model.predict_proba(Xva)[:, 1]))
    yurish.log_artifact(model, "model.joblib")
    return model


def qayta_tiklash(r, Xtr, ytr, Xva, yva):
    """Yurish yozuvidan modelni qayta qurish va natijani tekshirish."""
    if xesh(Xtr.tobytes() + ytr.tobytes()) != r["tags"]["malumot.oquv"]:
        raise ValueError("o'quv ma'lumoti xeshi mos emas - qayta tiklash mumkin emas")
    model = MODEL_TURLARI[r["tags"]["model_turi"]](**r["params"])
    model.fit(Xtr, ytr)
    auc = roc_auc_score(yva, model.predict_proba(Xva)[:, 1])
    yol = r["papka"] / "artifacts" / "model.joblib"
    sha_mos = xesh(yol.read_bytes()) == r["tags"]["sha256.model.joblib"]
    saqlangan = joblib.load(yol)
    bir_xil = np.array_equal(saqlangan.predict_proba(Xva), model.predict_proba(Xva))
    return auc, sha_mos, bir_xil


def main() -> None:
    rng = np.random.default_rng(0)
    X = rng.normal(size=(3000, 8))
    y = (rng.random(3000) < 1 / (1 + np.exp(-(X[:, 0] - X[:, 1] * X[:, 2]
                                               + 0.5 * X[:, 3])))).astype(int)
    Xtr, ytr, Xva, yva = X[:2000], y[:2000], X[2000:], y[2000:]

    with tempfile.TemporaryDirectory() as t:
        tr = FaylTracker(t)
        print("=== 1. Autolog bilan yurishlar ===")
        nomzodlar = [("logreg", LogisticRegression(C=1.0, max_iter=1000)),
                     ("rf_d4", RandomForestClassifier(n_estimators=100, max_depth=4,
                                                      random_state=0)),
                     ("rf_d8", RandomForestClassifier(n_estimators=100, max_depth=8,
                                                      random_state=0)),
                     ("hgb", HistGradientBoostingClassifier(max_iter=100,
                                                            random_state=0))]
        for nom, model in nomzodlar:
            with tr.start_run("churn", nom) as r:
                autolog(r, model, Xtr, ytr, Xva, yva)
        try:
            with tr.start_run("churn", "rf_xato") as r:
                autolog(r, RandomForestClassifier(max_depth=-1), Xtr, ytr, Xva, yva)
        except Exception as x:
            print(f"  rf_xato: {type(x).__name__} -> holat FAILED")
        papka = tr.ildiz / "churn"
        print(f"  mlruns/churn ichida: {len(list(papka.iterdir()))} yurish papkasi")
        bitta = next(p for p in sorted(papka.iterdir())
                     if json.loads((p / "meta.json").read_text())["nom"] == "hgb")
        print(f"  hgb yurishi: {sorted(p.name for p in bitta.iterdir())}")
        print(f"  hgb params: {len(list((bitta / 'params').iterdir()))} ta "
              f"(get_params dan avtomatik), teglar: "
              f"{sorted(p.name for p in (bitta / 'tags').iterdir())}")

        print("\n=== 2. search_runs: filtr va tartib ===")
        for r in tr.search_runs("churn"):
            print(f"  {r['nom']:<7} {r['run_id']}  val_auc {r['metrics']['val_auc']:.4f}"
                  f"  {r['tags']['model_turi']}")
        rf = tr.search_runs("churn", lambda r: r["tags"]["model_turi"] ==
                            "RandomForestClassifier")
        print(f"  faqat RandomForest: {[(r['nom'], r['params']['max_depth']) for r in rf]}")

        print("\n=== 3. Eng yaxshi yurishni faqat yozuvdan qayta tiklash ===")
        eng = tr.search_runs("churn")[0]
        auc, sha_mos, bir_xil = qayta_tiklash(eng, Xtr, ytr, Xva, yva)
        print(f"  {eng['nom']}: yozuvda {eng['metrics']['val_auc']:.6f}, "
              f"qayta {auc:.6f}, teng: {auc == eng['metrics']['val_auc']}")
        print(f"  artefakt sha256 mos: {sha_mos}; saqlangan va qayta qurilgan "
              f"bashoratlar bir xil: {bir_xil}")

        print("\n=== 4. Ma'lumot o'zgargan bo'lsa - rad etish ===")
        Xtr2 = Xtr.copy()
        Xtr2[0, 0] += 1e-9
        try:
            qayta_tiklash(eng, Xtr2, ytr, Xva, yva)
        except ValueError as x:
            print(f"  ValueError: {x}")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Autolog bilan yurishlar ===
  rf_xato: InvalidParameterError -> holat FAILED
  mlruns/churn ichida: 5 yurish papkasi
  hgb yurishi: ['artifacts', 'meta.json', 'metrics', 'params', 'tags']
  hgb params: 21 ta (get_params dan avtomatik), teglar: ['kod', 'malumot.oquv', 'malumot.val', 'model_turi', 'sha256.model.joblib', 'sklearn']

=== 2. search_runs: filtr va tartib ===
  hgb     8574a368  val_auc 0.7459  HistGradientBoostingClassifier
  rf_d8   94f45615  val_auc 0.7302  RandomForestClassifier
  logreg  1696e759  val_auc 0.7246  LogisticRegression
  rf_d4   e00299b5  val_auc 0.7163  RandomForestClassifier
  faqat RandomForest: [('rf_d8', 8), ('rf_d4', 4)]

=== 3. Eng yaxshi yurishni faqat yozuvdan qayta tiklash ===
  hgb: yozuvda 0.745891, qayta 0.745891, teng: True
  artefakt sha256 mos: True; saqlangan va qayta qurilgan bashoratlar bir xil: True

=== 4. Ma'lumot o'zgargan bo'lsa - rad etish ===
  ValueError: o'quv ma'lumoti xeshi mos emas - qayta tiklash mumkin emas

Nima ko'rsatdi: 2.3, 2.6-bo'limlar. Fayl asosidagi kuzatuvchi sqlite siz ham ishlaydi: har yurish — o'z papkasi, muvaffaqiyatsiz yurish ham papka va FAILED holati bilan qoladi, lekin search_runs uni natijalar ro'yxatiga qo'shmaydi. autolog o'nlab parametrni get_params() dan o'zi yozdi — qo'lda yozganda ularning ko'pchiligi (masalan, l2_regularization, max_bins) unutilardi, holbuki model aynan ular bilan o'qitilgan.

Qayta tiklash yozuvning to'liqligini sinadi: faqat yozuvdagi model turi va parametrlardan qurilgan model aynan bir xil val AUC berdi, artefakt sha256 si mos keldi, saqlangan va qayta qurilgan modellarning bashoratlari bit darajasida teng. O'quv ma'lumotida 1e-9 lik o'zgarish esa qayta tiklashni to'xtatdi — xesh natija o'zgaradimi-yo'qmi deb so'ramaydi, u faqat "bu o'sha ma'lumot emas" deydi (27.2 dagi kabi).

Misol 4 — G'olib la'nati: ko'p yurishdan eng yaxshisi optimistik

Tasodifiy qidiruv: har nomzod — 20 belgidan tasodifiy 10 tasi va tasodifiy C bilan logistik regressiya. Belgilar teng foydali qilib yaratilgan, ya'ni nomzodlarning haqiqiy sifati deyarli bir xil. Validatsiya — 300 qator, yopiq test — 5000 qator. K = 1, 5, 20, 60 yurishdan validatsiya bo'yicha eng yaxshisini tanlaymiz va uning testdagi natijasini o'lchaymiz; 12 takror.

python
"""G'olib la'nati: ko'p yurishdan validatsiyada eng yaxshisi - optimistik."""

import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score

_rng = np.random.default_rng(2)
X = _rng.normal(size=(7000, 20))
# har belgi TENG va kuchsiz foydali -> nomzodlarning haqiqiy sifati deyarli bir xil
Y = (_rng.random(7000) < 1 / (1 + np.exp(-0.18 * X.sum(1)))).astype(int)
X_TEST, Y_TEST = X[2000:], Y[2000:]            # katta yopiq test (5000)


def nomzodlar(k, rng):
    """Tasodifiy qidiruv: C va 10 ta belgi."""
    return [{"C": float(10 ** rng.uniform(-3, 1)),
             "belgilar": sorted(rng.choice(20, size=10, replace=False).tolist())}
            for _ in range(k)]


def yurish(p, Xtr, ytr, Xva, yva):
    m = LogisticRegression(C=p["C"], max_iter=500).fit(Xtr[:, p["belgilar"]], ytr)
    val = roc_auc_score(yva, m.predict_proba(Xva[:, p["belgilar"]])[:, 1])
    test = roc_auc_score(Y_TEST, m.predict_proba(X_TEST[:, p["belgilar"]])[:, 1])
    return val, test


def main() -> None:
    K_MAX, TAKROR = 60, 12
    natija = {k: {"val": [], "test": []} for k in (1, 5, 20, 60)}
    birinchi = None
    for r in range(TAKROR):
        rng = np.random.default_rng(100 + r)
        idx = rng.permutation(2000)
        tr, va = idx[:1000], idx[1000:1300]           # validatsiya: 300 qator
        yurishlar = [yurish(p, X[tr], Y[tr], X[va], Y[va])
                     for p in nomzodlar(K_MAX, rng)]
        if birinchi is None:
            birinchi = yurishlar
        for k in natija:
            eng = max(yurishlar[:k], key=lambda v: v[0])    # validatsiya bo'yicha
            natija[k]["val"].append(eng[0])
            natija[k]["test"].append(eng[1])

    print("=== 1. Bitta qidiruv (60 yurish), validatsiya 300 qator ===")
    val = np.array([v for v, _ in birinchi])
    test = np.array([t for _, t in birinchi])
    i = int(val.argmax())
    print(f"  val AUC oralig'i:  {val.min():.4f} - {val.max():.4f}")
    print(f"  test AUC oralig'i: {test.min():.4f} - {test.max():.4f}")
    print(f"  eng yaxshi (val): {val[i]:.4f}; uning test AUC: {test[i]:.4f}")
    print(f"  test bo'yicha haqiqiy eng yaxshisi: {test.max():.4f}")
    print(f"  val va test korrelyatsiyasi (60 yurish): "
          f"{np.corrcoef(val, test)[0, 1]:.2f}")

    print(f"\n=== 2. {TAKROR} takror: yurishlar soni K va optimizm ===")
    print(f"  {'K':>3} {'val (eng yaxshi)':>16} {'test':>8} {'optimizm':>9} {'SE':>7}")
    for k, v in natija.items():
        o = np.array(v["val"]) - np.array(v["test"])
        print(f"  {k:>3} {np.mean(v['val']):>16.4f} {np.mean(v['test']):>8.4f} "
              f"{o.mean():>+9.4f} {o.std(ddof=1) / np.sqrt(len(o)):>7.4f}")

    print("\n=== 3. K=60 va K=1: juftlashgan farqlar (bir xil takrorlar) ===")
    for nom, kalit in [("val bahosi", "val"), ("test (haqiqiy)", "test")]:
        d = np.array(natija[60][kalit]) - np.array(natija[1][kalit])
        se = d.std(ddof=1) / np.sqrt(len(d))
        baho = "SEZILARLI" if abs(d.mean()) > 2 * se else "sezilarli emas"
        print(f"  {nom:<15} {d.mean():+.4f} (SE {se:.4f}) - {baho}")
    print("  hisobotga: K ni va tanlangan yurishning YOPIQ testdagi natijasini")


if __name__ == "__main__":
    main()

Natijaning muhim qismi:

text
=== 1. Bitta qidiruv (60 yurish), validatsiya 300 qator ===
  val AUC oralig'i:  0.5888 - 0.6863
  test AUC oralig'i: 0.6235 - 0.6486
  eng yaxshi (val): 0.6863; uning test AUC: 0.6434
  test bo'yicha haqiqiy eng yaxshisi: 0.6486
  val va test korrelyatsiyasi (60 yurish): 0.31

=== 2. 12 takror: yurishlar soni K va optimizm ===
    K val (eng yaxshi)     test  optimizm      SE
    1           0.6443   0.6383   +0.0060  0.0062
    5           0.6646   0.6358   +0.0288  0.0061
   20           0.6746   0.6357   +0.0389  0.0052
   60           0.6836   0.6417   +0.0419  0.0049

=== 3. K=60 va K=1: juftlashgan farqlar (bir xil takrorlar) ===
  val bahosi      +0.0393 (SE 0.0059) - SEZILARLI
  test (haqiqiy)  +0.0034 (SE 0.0017) - sezilarli emas
  hisobotga: K ni va tanlangan yurishning YOPIQ testdagi natijasini

Nima ko'rsatdi: 2.5-bo'lim. Bitta qidiruvda 60 nomzodning val AUC lari keng oraliqqa sochildi, test AUC lari esa ancha tor oraliqda — haqiqiy sifatlar yaqin, validatsiyadagi farqning katta qismi shovqin. Val va test o'rtasidagi korrelyatsiya kuchsiz, va validatsiya bo'yicha "g'olib" ning testdagi AUC si uning val bahosidan taxminan 0.04 ga past.

12 takrordagi jadval la'natning mexanizmini ko'rsatdi: K oshgani sari tanlanganning val bahosi o'sib boradi, test bahosi esa deyarli joyida turadi — optimizm (val - test) K=1 da nolga yaqin, K=60 da esa bir necha yuzdan birga yetdi. Juftlashgan taqqoslash buni tasdiqladi: K=60 val bahoni sezilarli oshirdi, testdagi haqiqiy yutuq esa o'n baravarga yaqin kichik va sezilarli emas. Ya'ni 60 yurish "yaxshilanish" ning katta qismini validatsiya shovqinidan "sotib oldi".

Bu natija nomzodlar haqiqatan teng bo'lganda eng kuchli. Agar nomzodlar sifati keskin farq qilsa (masalan, 4 va 20 belgili modellar aralash), qidiruv haqiqiy yaxshisini topadi va optimizm kichikroq bo'ladi — lekin qaysi holatda ekaningizni oldindan bilmaysiz. Shuning uchun qoida o'zgarmaydi: tanlov validatsiyada, hisobot — yopiq testda, K bilan birga.


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

Noto'g'ri fikr To'g'risi
"Fayl nomida versiya bor — yetarli" 1-misolda model_final.pkl ustiga yozildi va README boshqa modelni ta'rifladi
"README dagi ikki AUC ni solishtirish mumkin" Turli ma'lumotda: +0.164 "yaxshilanish" bir xil testda ancha kichik chiqdi
"Faqat muvaffaqiyatli yurishlarni yozish kifoya" Muvaffaqiyatsizlar ham K ga kiradi; optimizm K ga bog'liq
"Parametrlarning asosiylarini qo'lda yozaman" autolog o'nlab parametrni yozdi — qo'lda ular unutiladi
"1-o'rin 2-o'rindan yaxshi" 2-misolda farq juftlashgan SE ichida — amalda teng
"Eng yaxshi val AUC — kutilgan ishlab chiqarish natijasi" K=60 da val baho sezilarli optimistik; hisobot — yopiq test
"Yozuvdan qayta qurish shart emas" Qayta tiklash — yozuv to'liqligining testi (3-misol)
"MLflow bo'lsa, hammasi avtomatik to'g'ri" Ma'lumot xeshi, yopiq test va K ni baribir siz qayd qilasiz

6. Keng tarqalgan xatolar va yechimlari

1. Fayl nomi bilan versiyalash

python
joblib.dump(model, "model_final_v3_haqiqiy.pkl")             # ⚠️ kontekst yo'q
with tr.start_run("churn", "gb_lr03") as r: autolog(r, model, ...)   # ✅ to'liq yozuv

2. Turli ma'lumotdagi metrikalarni solishtirish

python
if auc_yangi > auc_eski: ...                                 # ⚠️ val to'plamlari boshqa
assert a["malumot"] == b["malumot"]; farq, se = juft_farq(...)   # ✅

3. Faqat g'oliblarni yozish

python
if auc > eng_yaxshi: log_run(...)                            # ⚠️ K yo'qoladi
with start_run(...): ...   # har yurish, FAILED ham           # ✅

4. Qadamsiz metrika

python
r.log_metric("val_auc", yakuniy_auc)                         # ⚠️ egri chiziq yo'q
r.log_metric("val_auc", auc, qadam=davr)                     # ✅ har qadamda

5. Artefaktni xeshsiz saqlash

python
shutil.copy("model.joblib", "artefaktlar/")                  # ⚠️ almashtirilsa - sezilmaydi
r.log_artifact("model.joblib", "model.joblib")  # + sha256    # ✅

6. Ko'p yurishdan eng yaxshisining val bahosini hisobot qilish

python
print("Model AUC:", max(val_auclar))                         # ⚠️ g'olib la'nati
print("Model AUC (yopiq test):", test_auc, "K =", len(val_auclar))   # ✅

7. Sirlarni param sifatida yozish

python
r.log_param("db_parol", os.environ["DB_PAROL"])              # ⚠️ kuzatuvchida hamma ko'radi
r.log_param("db_host", konfig.db_host)  # parol - faqat muhitda   # ✅

7. Integratsiya — bu bilim qayerda kerak bo'ladi

  • 18.4, 18.11-darslar (o'tilgan): nested CV va validatsiyaga moslashish — g'olib la'natining nazariyasi
  • 21.11-dars (o'tilgan): JSON Lines jurnal — bu darsdagi kuzatuvchining oddiy ajdodi
  • 27.2-dars (o'tilgan): manifest — har yurish yozuvining "muhit" qismi
  • 27.3-dars (o'tilgan): ma'lumot xeshi — solishtirish sharti
  • 27.5-dars: eng yaxshi yurish registrga "versiya" sifatida ko'tariladi
  • 27.13-dars: qayta o'qitish va A/B test — yangi va eski model yurishlarini solishtirish

8. Eng yaxshi amaliyotlar

  1. Har yurishni yozing — muvaffaqiyatsizlarini ham; holat va xato matni bilan.

  2. Parametrlarni avtomatik (get_params, autolog) yozing; qo'lda tanlab yozmang.

  3. Metrikani qadamlar bilan yozing; eng yaxshi qadamni so'rov bilan toping.

  4. Ma'lumot va kod xeshini har yurishga yozing; solishtirishni faqat bir xil validatsiya xeshi ichida qiling.

  5. Artefaktni sha256 bilan saqlang — keyingi tahlil (juftlashgan SE) va audit uchun.

  6. Ikki yurish farqini juftlashgan SE bilan talqin qiling; farq shovqin ichida bo'lsa, sodda/arzonini tanlang.

  7. Tanlov — validatsiyada, hisobot — yopiq testda; hisobotda K ni ko'rsating.

  8. Yozuvdan qayta tiklashni muntazam sinab turing.


9. Amaliy topshiriq

Vazifa 1: Bashorat qiling

python
1.  # fayl nomlari bilan versiyalashning uch asosiy nuqsoni?
2.  # har yurishda qanday guruh ma'lumotlar yoziladi?
3.  # param va metrika farqi?
4.  # nega metrika qadamlar bilan yoziladi?
5.  # ikki yurishni solishtirish sharti?
6.  # ikki yurish farqining SE si uchun nima kerak?
7.  # muvaffaqiyatsiz yurishlar nega yoziladi?
8.  # g'olib la'nati nima?
9.  # K oshsa optimizm qanday o'zgaradi?
10. # hisobotga qaysi raqam yoziladi?
11. # autolog nima beradi?
12. # yozuvdan qayta tiklash nimani tekshiradi?
Javoblar
  1. Ustiga yozish, turli ma'lumotdagi metrikalar solishtiriladi, kontekst (ma'lumot, kod) yo'q
  2. Parametrlar, metrikalar, kod versiyasi, ma'lumot xeshi, muhit, artefaktlar, teglar, holat
  3. Param — bir marta, o'zgarmas; metrika — qadamlar bilan ko'p marta
  4. O'rganish egri chizig'i va eng yaxshi (erta to'xtash) qadamni topish uchun
  5. Bir xil validatsiya (ma'lumot) xeshi
  6. Ikkala modelning bir xil qatorlardagi bashoratlari — ya'ni artefakt
  7. Ular ham sinalgan variant — K ni to'g'ri bilish uchun
  8. Ko'p variantdan validatsiyada eng yaxshisining bahosi optimistik bo'lishi
  9. Oshadi
  10. Tanlangan yurishning yopiq testdagi natijasi (K bilan birga)
  11. Barcha parametrlar, xeshlar, versiya, metrika va artefakt — unutilmaydi
  12. Yozuv to'liqmi: model qayta quriladimi, metrika va bashoratlar mosmi

Vazifa 2: Xatolarni tuzating

python
1.  joblib.dump(model, f"model_{auc:.3f}_final.pkl")

2.  if yangi["val_auc"] > eski["val_auc"]:
        print("yangi yaxshiroq")

3.  for p in tarmoq:
        auc = orgat(p)
        if auc > eng:
            eng = auc
            yozuv.append({"param": p, "auc": auc})

4.  r.log_param("learning_rate", 0.1)
    # ... max_depth, subsample va boshqalar unutildi

5.  print(f"Yakuniy model AUC: {max(r['val_auc'] for r in yurishlar):.3f}")
Javoblar
python
1.  with tr.start_run("churn", nom) as r:
        autolog(r, model, Xtr, ytr, Xva, yva)       # model artefakt + sha256

2.  assert yangi["malumot"]["val_xesh"] == eski["malumot"]["val_xesh"]
    farq, se = auc_farq_se(yva, p_yangi, p_eski)
    print("yangi yaxshiroq" if farq > 2 * se else "farq sezilarli emas")

3.  for p in tarmoq:
        with tr.start_run("qidiruv", nom(p)) as r:  # HAR yurish, FAILED ham
            ...

4.  for k, v in model.get_params().items():
        r.log_param(k, v)                           # yoki autolog

5.  eng = max(yurishlar, key=lambda r: r["val_auc"])
    print(f"Yopiq test AUC: {test_auc(eng):.3f} (K = {len(yurishlar)} yurish)")

Vazifa 3: Kuzatuvchini kengaytirish

2-misoldagi Tracker ga:

  1. search_runs(eksperiment, filtr_param, tartib_metrika, limit) metodi
  2. Ota-bola yurishlar (parent_id): qidiruv — ota, har kombinatsiya — bola
  3. log_param ni qayta chaqirish boshqa qiymat bilan xato bersin (PRIMARY KEY ni ushlab, tushunarli xabar bilan)
  4. Ikki yurishning parametr va metrika farqini jadval sifatida qaytaradigan solishtir(a, b)

Vazifa 4: Solishtirish

  1. 1-misolda gb_v2 va gb_lr03_v2 ning test AUC farqini juftlashgan bootstrap SE bilan hisoblang
  2. 2-misolda barcha 9 yurishni eng yaxshisi bilan juftlashgan SE bo'yicha solishtiring: nechtasi "sezilarli yomon emas"?
  3. Ulardan eng soddasini tanlang (masalan, max_depth kichik, n_estimators kam)
  4. Natijani jadval sifatida kuzatuvchiga "qaror" yurishi sifatida yozing

Vazifa 5: Fayl kuzatuvchisi

3-misolni o'zgartiring:

  1. meta.json ga ota-yurish va muallif tegini qo'shing
  2. search_runs ga "metrika > chegara" filtrini qo'shing
  3. Artefaktni o'zgartiring (bir baytini) va qayta tiklash sha256 farqini ushlashini tekshiring
  4. Ikki fayl kuzatuvchisini (ikki mashina) bitta sqlite bazaga birlashtiradigan skript yozing — run_id to'qnashmasligini tekshiring

Vazifa 6: G'olib la'nati

4-misolni o'zgartiring:

  1. Validatsiya hajmini 300 dan 1000 ga oshiring — optimizm qanday o'zgaradi?
  2. Nomzodlarni haqiqatan turli sifatli qiling (belgilar soni 2 dan 20 gacha) — optimizm kichrayadimi?
  3. K=60 uchun "tanlovni o'zi" 5-fold CV bilan qiling — optimizm qanchalik kamayadi?
  4. Hisobot shablonini yozing: "K ta yurish, tanlangan: ..., val: ..., yopiq test: ..."

Vazifa 7: O'ylash

Jamoa uch oy davomida 1 400 ta yurish qildi (ularning 300 tasi xato bilan tugagan va hech qayerda yozilmagan). Eng yaxshi yurishning val AUC 0.874, hozirgi ishlab chiqarish modeliniki 0.861 (bir yil oldin boshqa validatsiya to'plamida o'lchangan). Mahsulot egasi: "0.013 yaxshilanish — darhol deploy qilamiz" deydi. Sizning javobingiz?

Javob

Qisqa javob: 0.874 va 0.861 ni solishtirib bo'lmaydi va 0.874 ning o'zi optimistik. Deploydan oldin ikkita ish kerak: ikkala modelni bir xil yangi yopiq to'plamda juftlashgan baholash va natijani K ni hisobga olib talqin qilish.

1. Nima noto'g'ri

Muammo Oqibat
Turli validatsiya to'plamlari 0.013 farqning qancha qismi ma'lumotdan — noma'lum (1-misol)
1 400 (aslida 1 700) yurishdan eng yaxshisi Val baho optimistik — g'olib la'nati (4-misol)
300 xato yurish yozilmagan Haqiqiy K noma'lum
Farqning SE si yo'q Shovqin ichidami — noma'lum (2-misol)

2. To'g'ri tartib

python
# 1. yangi, hech bir yurishda ishlatilmagan yopiq to'plam (masalan, oxirgi oy)
X_yopiq, y_yopiq = oxirgi_oy()

# 2. IKKALA model - bir xil qatorlarda
p_eski = ishlab_chiqarish_modeli.predict_proba(X_yopiq)[:, 1]
p_yangi = eng_yaxshi_yurish_modeli.predict_proba(X_yopiq)[:, 1]
farq, se = auc_farq_se(y_yopiq, p_yangi, p_eski)

# 3. qaror
if farq > 2 * se:
    # sezilarli - lekin baribir ehtiyotkor deploy (kanareyka, A/B - 27.10, 27.13)
    ...
else:
    # sezilarli emas - joriy modelni saqlash yoki soddarog'ini tanlash
    ...

3. Kutiladigan natija

G'olib la'nati tufayli yangi modelning yopiq to'plamdagi AUC si 0.874 dan past bo'lishi deyarli aniq. Eski model ham yangi ma'lumotda boshqa natija beradi (drift — 27.12). Haqiqiy farq 0.013 dan kichik bo'lishi, hatto nolga yaqin bo'lishi ham mumkin — faqat o'lchash ko'rsatadi.

4. Jarayon uchun o'zgarishlar

  • Har yurish kuzatuvchiga yoziladi, FAILED lari ham — K ma'lum bo'ladi
  • Har yurishda validatsiya xeshi — solishtirib bo'lmaydigan raqamlar bir jadvalga tushmaydi
  • Yopiq to'plam faqat yakuniy qaror uchun, bir marta
  • Deploy qarori hujjati: K, tanlangan yurish, yopiq to'plamdagi juftlashgan farq va SE

Mahsulot egasiga javob: "Ikki raqam turli ma'lumotda o'lchangan va yangi raqam 1 700 ga yaqin urinishning eng yaxshisi, shuning uchun u oshirilgan. Ikki kun ichida ikkala modelni bir xil yangi ma'lumotda solishtiramiz. Farq sezilarli chiqsa — bosqichma-bosqich deploy qilamiz, aks holda joriy model qoladi."

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


Xulosa

Bu darsda har yurishni to'liq qayd qiladigan, so'rovga javob beradigan va tanlov optimizmini ko'rsatadigan eksperiment kuzatuvini noldan qurdik.

Eng muhim uch fikr:

  1. Model fayli — natija emas; natija — to'liq yurish yozuvi. 1-misolda fayl nomlari va README bilan boshqarish bitta sessiyada buzildi: fayl ustiga yozildi, README dagi +0.164 "yaxshilanish" bir xil testda ancha kichik bo'lib chiqdi, chunki u deyarli butunlay ma'lumot tozalanganidan kelgan edi — learning_rate ning o'zi validatsiyada hatto biroz yomonroq bo'ldi. Yozuvlardagi validatsiya xeshi solishtirib bo'lmaydigan yurishlarni o'z-o'zidan ajratdi.

  2. Kuzatuvchi — so'rov, solishtirish va qayta tiklash vositasi. 2-misolda sqlite ustidagi kichik kuzatuvchi o'n yurishni (bittasi FAILED) qadamli metrikalar va sha256 li artefaktlar bilan yozdi; eng yaxshisi, filtr va erta to'xtash nuqtasi SQL bilan topildi, eng yaxshi ikki yurishning farqi esa saqlangan modellar orqali juftlashgan SE bilan shovqin ichida ekani ko'rsatildi. 3-misolda fayl asosidagi (mlruns uslubidagi) kuzatuvchi va autolog bilan eng yaxshi yurish faqat yozuvdan aynan qayta qurildi, ma'lumot o'zgarganda esa qayta tiklash rad etildi.

  3. G'olib la'nati — o'lchanadigan xavf. 4-misolda haqiqiy sifati yaqin nomzodlar orasida yurishlar soni oshgani sari tanlanganning validatsiya bahosi sezilarli o'sdi, yopiq testdagisi esa deyarli o'zgarmadi. Tanlov validatsiyada, hisobot — yopiq testda va K bilan birga.

Keyingi darsda Model versiyalash va registr: eng yaxshi yurish qanday qilib registrdagi versiyaga aylanadi, bosqichlar (staging, production) va ular orasidagi o'tish qoidalari, kim va nima uchun modelni ko'targani, va muammo chiqqanda bir buyruq bilan orqaga qaytish.

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
27.4-dars: Eksperiment kuzatuvi — IlmHamroh