Mundarija (25)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Versiyalash uchligi: kod, ma'lumot, model
- 2.2. Ma'lumot xeshi va kanonik shakl
- 2.3. Semantik versiya modellar uchun
- 2.4. Model registr: nima saqlanadi
- 2.5. Bosqichlar va o'tish qoidalari
- 2.6. Darvoza: Production ga qachon chiqariladi
- 2.7. Orqaga qaytarish va audit
- 2.8. DVC g'oyasi: kontent-manzilli saqlash
- 2.9. MLflow Model Registry bilan bog'lash
- 2.10. Model kartasi — registr yozuvining bir qismi
- 2.11. Tuzoqlar
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Kod, ma'lumot va modelni birga versiyalash
- Misol 2 — Model registr noldan (sqlite)
- Misol 3 — Darvoza, orqaga qaytarish va audit
- Misol 4 — DVC g'oyasi: kontent-manzilli saqlash noldan
- 5. To'g'ri va noto'g'ri tushunishlar
- 6. Keng tarqalgan xatolar va yechimlari
- 7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 8. Eng yaxshi amaliyotlar
- 9. Amaliy topshiriq
- Xulosa
27.5-dars: Model versiyalash va registr
27-QISM — MLOPS VA DEPLOY · 5-dars
1. Kirish va motivatsiya
Oldingi darsda 27.4-bob har o'rgatish yurishini eksperiment kuzatuvi jurnaliga yozdik: run_id, parametrlar, metrikalar, artefaktlar. Bu savolga javob beradi: "qaysi tajriba eng yaxshi edi?". Lekin ishlab chiqarishda boshqa savollar tug'iladi:
- Hozir mijozlarga qaysi model javob beryapti?
- U qaysi ma'lumotda, qaysi kod bilan o'rgatilgan?
- Uni kim, qachon va nima asosida ishlab chiqarishga chiqargan?
- Yangi model muammo chiqarsa, bir buyruq bilan oldingisiga qaytish mumkinmi?
Bu savollarga javob beradigan joy — model registr (model registry). Eksperiment kuzatuvi — laboratoriya daftari: yuzlab tajriba, ko'pi tashlab yuboriladi. Registr — omborxona: faqat nomlangan, versiyalangan, tekshirilgan modellar, har biri aniq bosqichda (Staging, Production, Archived) va har bir o'tish jurnalda.
Registrning poydevori — versiyalash. Model uchta narsaning mahsuli: kod, ma'lumot va konfiguratsiya (muhit bilan birga — 27.2). Ulardan birortasi versiyalanmagan bo'lsa, model versiyasi yarim haqiqat: "v3" deymiz, lekin v3 ni qayta qura olmaymiz.
Real vaziyat. Kredit skoringi jamoasiga regulyator xat yubordi: "Uch oy oldin mijoz X ga kredit rad etilgan. Qaror qaysi model bilan chiqarilgan va u qanday ma'lumotda o'rgatilgan?". Jamoada s3://modellar/kredit/model_final.pkl bor edi — lekin u har oy ustiga yozilgan. Git da kod bor, lekin qaysi commit ishlab chiqarishda bo'lgani noma'lum; o'rgatish ma'lumoti esa ma'lumotlar omborida o'zgarib ketgan (kechikib kelgan to'lovlar qo'shilgan). Javob tayyorlash uchun ikki hafta kerak bo'ldi va natija baribir taxmin edi. Registr va ma'lumot xeshi bo'lganida bu — bitta SQL so'rov.
Bu darsda modelni kod + ma'lumot + model uchligi sifatida versiyalaymiz va noldan kichik, lekin to'liq model registr quramiz: bosqichlar, darvoza, orqaga qaytarish va audit.
Bu darsda:
- Kod, ma'lumot va modelni birga versiyalash (xeshlar va manifest)
- Ma'lumot xeshi va kanonik shakl
- Semantik versiya modellar uchun
- Model registr: nima saqlanadi, bosqichlar va o'tish qoidalari
- Ishlab chiqarishga chiqarish darvozasi (promotion gate)
- Orqaga qaytarish (rollback) va audit tarixi
- DVC g'oyasi: kontent-manzilli saqlash
- Model kartasi registr yozuvining bir qismi sifatida
- Tuzoqlar
ℹ Misollar real numpy/pandas/sklearn va standart
sqlite3,hashlib,yamlbilan (Python 3.14). MLflow va DVC mashinada yo'q — ularning asosiy g'oyasini noldan quramiz va har joyda "haqiqiy loyihada MLflow/DVC shu ishni qiladi" deb bog'laymiz.
2. Nazariya — chuqur tushuntirish
2.1. Versiyalash uchligi: kod, ma'lumot, model
+-----------+
| KOD | git commit (a1b2c3d), toza ishchi daraxt
+-----+-----+
|
+-----------+ v +-------------+
| MA'LUMOT | ---> O'RGATISH <--- | KONFIG+MUHIT| seed, giperparametrlar,
| xesh 9f3e | | | requirements.lock 27.2-bob
+-----------+ v +-------------+
+-----------+
| MODEL | artefakt xeshi, metrikalar
+-----------+
MODEL VERSIYASI = (kod versiyasi, ma'lumot xeshi, konfig, muhit) -> artefakt
QOIDA: uchlikdan bittasi noma'lum bo'lsa - model QAYTA QURILMAYDIGit kodni yaxshi versiyalaydi, lekin ma'lumot va model uchun yaramaydi: fayllar katta (gigabaytlar), ikkilik (diff ma'noga ega emas), tez-tez o'zgaradi. Shuning uchun ma'lumot va model uchun xesh ishlatiladi: faylning mazmunidan hisoblangan qisqa "barmoq izi". Git esa faqat shu kichik barmoq izini saqlaydi.
Manifest — bitta model versiyasining pasporti:
{
"model": "mijoz_ketishi",
"versiya": "1.2.0",
"kod_versiyasi": "a1b2c3d", <- git commit
"malumot_xeshi": "9f3e41c07b2a", <- o'rgatish ma'lumoti
"konfig": {"C": 1.0, "seed": 7},
"muhit": "requirements.lock xeshi",
"model_xeshi": "5d0c8e1a44f2", <- artefakt fayli
"metrikalar": {"auc": 0.81},
"run_id": "run-0042" <- eksperiment kuzatuvi 27.4-bob
}Model versiyasi — bitta fayl emas, uchlik: kod, ma'lumot va konfig versiyalari plus natija artefakti. Manifest shularni bog'laydi.
2.2. Ma'lumot xeshi va kanonik shakl
XOM XESH: sha256(fayl baytlari)
+ oddiy, tez
- qatorlar tartibi o'zgarsa -> boshqa xesh (mazmun o'sha!)
- float yozilishi 0.1 vs 0.10000000001 -> boshqa xesh
- CSV da \r\n vs \n (Windows!) -> boshqa xesh
KANONIK XESH:
1. ustunlar nomi bo'yicha tartiblanadi
2. qatorlar kalit (id) bo'yicha tartiblanadi
3. sonlar bir xil formatda (%.6f)
4. qator oxiri bir xil (\n)
5. sha256 -> birinchi 12 belgi (ko'rsatish uchun)
QACHON QAYSI:
fayl ombori (DVC) -> xom xesh (fayl aynan saqlanadi)
"mazmun bir xilmi?" -> kanonik xeshKanonik xeshda "mazmun" ni o'zingiz belgilaysiz. Masalan, qatorlar tartibi model uchun ahamiyatsiz bo'lsa, tartib bo'yicha normallashtiring. Lekin qiymat o'zgarishi, hatto +0.01, albatta xeshni o'zgartirishi kerak — aks holda xesh o'z vazifasini bajarmaydi.
Ma'lumot xeshi — ma'lumot versiyasining nomi. "Iyun ma'lumoti" emas, 9f3e41c07b2a: kim qachon so'rasa ham aynan bir xil narsani bildiradi.
2.3. Semantik versiya modellar uchun
Dasturiy ta'minotdagi MAJOR.MINOR.PATCH qoidasini modelga moslaymiz. Mezon — iste'molchi (API mijozi, boshqa jamoa) uchun nima o'zgaradi:
MAJOR (1.x.x -> 2.0.0) - SHARTNOMA buzildi, iste'molchi kodini o'zgartirishi kerak:
yangi MAJBURIY kirish ustuni, ustun olib tashlandi
chiqish ma'nosi o'zgardi (ehtimol -> sinf, yangi sinflar)
birlik o'zgardi (so'm -> ming so'm)
MINOR (1.2.x -> 1.3.0) - shartnoma o'sha, xatti-harakat o'zgardi:
yangi ma'lumot bilan qayta o'qitish
giperparametr yoki algoritm almashdi
ixtiyoriy (sukut qiymatli) yangi ustun
PATCH (1.2.3 -> 1.2.4) - bashoratlar o'zgarmaydi:
metadata, model kartasi, hujjat tuzatildi
paketlash tuzatildi (bir xil bashorat)Registrlarda (MLflow ham) odatda yana bitta raqam bor — ro'yxat raqami: 1, 2, 3, …, har yangi yozuvga avtomatik beriladi. Ikkalasi birga yashaydi: registr raqami — "nechanchi yozuv", semantik versiya — "iste'molchi uchun nima o'zgardi".
MAJOR — shartnoma buzilganda. Iste'molchi sezmaydigan qayta o'qitish — MINOR; u kodini o'zgartirishi kerak bo'lsa — MAJOR.
2.4. Model registr: nima saqlanadi
REGISTR YOZUVI (nom, versiya):
nom "mijoz_ketishi"
versiya 3 (avtomatik, o'sib boradi)
bosqich None | Staging | Production | Archived
metrikalar {"auc": 0.84, "log_loss": 0.41}
kelib chiqish (lineage):
malumot_xeshi "9f3e41c07b2a"
kod_versiyasi "a1b2c3d"
run_id "run-0042" -> eksperiment kuzatuvidagi to'liq yozuv
artefakt ombordagi fayl + uning xeshi
model kartasi maqsad, cheklovlar, adolatlilik 26.10-bob
AUDIT JADVALI (hech qachon o'chirilmaydi, faqat qo'shiladi):
soat | nom | versiya | eski bosqich | yangi bosqich | kim | sababRegistrning ikki muhim xossasi:
- Artefakt o'chirilmaydi —
Archiveddegani "ishlatilmaydi", "yo'q qilindi" emas. Aynan shuning uchun orqaga qaytarish bir necha soniya. - Audit faqat qo'shiladi (append-only). Kim nimani qachon va nima uchun o'zgartirgani — regulyator, incident tahlili va jamoa xotirasi uchun.
2.5. Bosqichlar va o'tish qoidalari
ro'yxatga olish
|
v
[None] ---------------------+
| |
v v
[Staging] ---- rad ----> [Archived]
| ^ |
DARVOZA | | | rollback
v | | (maxsus yo'l)
[Production] --- almashdi ---+ |
^ |
+--------------------------+
RUXSAT ETILGAN O'TISHLAR:
None -> Staging, None -> Archived
Staging -> Production (faqat DARVOZA orqali)
Staging -> Archived
Production -> Archived (yangisi chiqqanda avtomatik)
Archived -> Staging (qayta ko'rib chiqish)
Archived -> Production - FAQAT rollback, audit sababi bilan
TAQIQLANGAN:
None -> Production ("to'g'ridan-to'g'ri prodga")
bitta nomda ikkita ProductionQoidalar kodda (registrda) yashashi kerak, jamoa kelishuvida emas. "Iltimos, to'g'ridan-to'g'ri prodga qo'ymang" degan kelishuv birinchi shoshilinch kechada buziladi; ValueError: ruxsat yo'q: None -> Production esa buzilmaydi.
2.6. Darvoza: Production ga qachon chiqariladi
Darvoza (promotion gate) — Staging -> Production o'tishidan oldin avtomatik tekshiruvlar. Ular natijani ko'rishdan oldin belgilanadi (18-qismdagi qoida: mezonni natijaga moslashtirmaslik).
DARVOZA (misoldagi):
1. MUTLAQ CHEGARA: AUC(nomzod) >= 0.70 (biznes bilan kelishilgan)
2. NISBIY SHART: nomzod joriy Production dan SEZILARLI YOMON EMAS
2-shartni tekshirish - JUFTLASHGAN taqqoslash, bir xil validatsiya to'plamida:
har namuna i uchun: d_i = logloss_nomzod(i) - logloss_prod(i)
o'rtacha d, SE = std(d) / sqrt(n)
o'rtacha d > 2*SE -> nomzod sezilarli YOMON -> RAD
aks holda -> o'tadi
QO'SHIMCHA (haqiqiy loyihalarda):
sxema mosligi 27.6-bob, kechikish byudjeti 27.8-bob, adolatlilik metrikalari,
segmentlar bo'yicha (yangi mijozlar, hududlar) regressiya yo'qligiNega "sezilarli yaxshi" emas, "sezilarli yomon emas"? Qayta o'qitish ko'pincha yangi ma'lumotga moslashish uchun qilinadi — yangi model eskisidan metrikada sezilarli yaxshi bo'lmasligi mumkin, lekin u yangi taqsimotni ko'rgan. Darvoza regressiyani to'xtatadi; "arziydimi" savoli esa eng sodda munosib model qoidasi va biznes qarori.
Ikki shartning har biri o'z xatosini ushlaydi. Mutlaq chegara — "umuman yaroqsiz" modelni (buzilgan ma'lumot, xato join). Nisbiy shart — "o'zicha yomon emas, lekin hozirgisidan yomon" modelni. 3-misolda ikkalasi ham ishlaydi.
Darvoza — oldindan yozilgan, avtomatik, juftlashgan. Chegara natijadan oldin belgilanadi, taqqoslash bir xil to'plamda va shovqin (SE) hisobga olinadi.
2.7. Orqaga qaytarish va audit
ROLLBACK:
1. joriy Production -> Archived (sabab: "rollback: <nima bo'ldi>")
2. OLDINGI Production -> Production
"oldingi" = auditda oxirgi marta Production -> Archived bo'lgan
boshqa versiya
3. ikkala qadam BITTA tranzaksiyada (yarim holat bo'lmaydi)
NEGA TEZ: artefakt registrda turibdi, qayta o'qitish shart emas
NEGA DARVOZASIZ: oldingi versiya allaqachon ishlab chiqarishda sinalgan
(lekin sabab va kim - auditga MAJBURIY)
AUDIT BERADIGAN JAVOBLAR:
"hozir qaysi versiya?" -> versiyalar jadvali
"5-mart kuni qaysi versiya edi?" -> audit, shu vaqtgacha oxirgi Production
"bu ma'lumotdan qaysi modellar?" -> lineage bo'yicha so'rov
"kim prodga chiqargan va nega?" -> audit.kim, audit.sababOrqaga qaytarish tugmasi faqat oldindan sinalgan bo'lsa ishonchli. Ko'p jamoalarda rollback birinchi marta haqiqiy incident paytida ishlatiladi — va ishlamaydi (artefakt o'chirilgan, konfig mos emas). Rollback ni oddiy jarayonning bir qismi sifatida muntazam sinang.
2.8. DVC g'oyasi: kontent-manzilli saqlash
Kontent-manzilli saqlash (content-addressable storage): fayl nomi bilan emas, mazmunining xeshi bilan saqlanadi.
ISHCHI PAPKA OMBOR (kesh / masofaviy)
data/mijozlar.csv ---> ab/12cd34... (fayl xeshi = manzil)
data/mijozlar.csv.dvc 5f/9e01aa...
outs:
- path: mijozlar.csv
sha256: ab12cd34... <- GIT FAQAT SHU KICHIK FAYLNI SAQLAYDI
size: 81234
XOSSALAR:
bir xil mazmun -> bir xil xesh -> BIR marta saqlanadi (dedup)
mazmun o'zgarsa -> yangi xesh -> yangi obyekt, eskisi joyida
"checkout" = ko'rsatkichdagi xesh bo'yicha fayl ombordan tiklanadi
butunlik: ombordagi obyekt xeshi nomiga mos emas -> buzilgan
gc: hech bir ko'rsatkich ishora qilmagan obyektlar o'chiriladiHaqiqiy DVC bilan ish shunday ko'rinadi (bu mashinada DVC yo'q — faqat nazariya):
dvc init
dvc add data/mijozlar.csv # data/mijozlar.csv.dvc yaratiladi
git add data/mijozlar.csv.dvc data/.gitignore
git commit -m "ma'lumot v1"
dvc push # obyektlar masofaviy omborga
git checkout v1 && dvc checkout # ma'lumot v1 holatiga qaytadi
dvc repro # dvc.yaml dagi quvurni kerak joyidan qayta ishlatadidvc.yaml quvur bosqichlarini 27.3-bob ularning kirish-chiqishi bilan tasvirlaydi — DVC xeshlar bo'yicha qaysi bosqich eskirganini aniqlaydi:
stages:
tayyorla:
cmd: python tayyorla.py
deps: [tayyorla.py, data/mijozlar.csv]
outs: [data/tayyor.csv]
orgat:
cmd: python orgat.py
deps: [orgat.py, data/tayyor.csv]
params: [orgat.C, orgat.seed]
outs: [model/model.joblib]
metrics: [natija/metrikalar.json] Git — ko'rsatkichni, ombor — mazmunni saqlaydi. Kod commit i va ma'lumot ko'rsatkichi bir commit da bo'lsa, git checkout + dvc checkout ikkalasini birga qaytaradi.
2.9. MLflow Model Registry bilan bog'lash
Bu darsda noldan quradigan registr MLflow Model Registry ning soddalashtirilgan nusxasi. Haqiqiy loyihada kod taxminan shunday bo'ladi (mashinada mlflow yo'q — faqat tanishish uchun):
import mlflow
from mlflow import MlflowClient
natija = mlflow.register_model("runs:/run-0042/model", "mijoz_ketishi")
mijoz = MlflowClient()
mijoz.set_model_version_tag("mijoz_ketishi", natija.version,
"malumot_xeshi", "9f3e41c07b2a")
mijoz.set_registered_model_alias("mijoz_ketishi", "champion", natija.version)
model = mlflow.pyfunc.load_model("models:/mijoz_ketishi@champion")MLflow ning yangi versiyalarida qat'iy bosqichlar (Staging/Production) o'rniga taxalluslar (alias: champion, challenger) tavsiya qilinadi — g'oya o'sha: nom bo'yicha "hozirgi ishlab chiqarish modeli" ga ishora, versiya esa almashtiriladi. Bosqichlar bilan ishlash tushunarliroq bo'lgani uchun darsda ular ishlatiladi; aniq API ni MLflow rasmiy hujjatidan tekshiring.
2.10. Model kartasi — registr yozuvining bir qismi
26.10-darsda generativ model uchun model kartasini yozdik. Registrda karta har versiyaga biriktiriladi, chunki u versiyaga xos:
MODEL KARTASI (qisqa):
maqsad va iste'molchi "ketish ehtimoli, marketing uchun, kredit qarori UCHUN EMAS"
o'rgatish ma'lumoti malumot_xeshi, davr, hajm, segmentlar
metrikalar umumiy + segmentlar bo'yicha
cheklovlar "yangi mijozlarda (oylar < 3) sinalmagan"
adolatlilik guruhlar bo'yicha farqlar
egasi va aloqa jamoa, navbatchiKarta tuzatilsa (imlo, aniqlashtirish) — bu PATCH; kartadagi "cheklovlar" o'zgargan bo'lsa (masalan, yangi segmentda sinaldi) — odatda yangi o'qitish bilan birga MINOR.
2.11. Tuzoqlar
Asosiy tuzoqlar: model_final.pkl ni ustiga yozish (tarix yo'qoladi); ma'lumotni sana yoki fayl nomi bilan "versiyalash" (iyun.csv keyin o'zgaradi); xom fayl xeshini "mazmun" deb o'ylash (qator tartibi, \r\n); git holati toza bo'lmaganda o'rgatish (commit xeshi kodni to'liq ifodalamaydi); None -> Production ga ruxsat; bir nomda ikkita Production; darvoza chegarasini natijani ko'rgandan keyin tanlash; darvozada juftlashmagan yoki shovqinsiz taqqoslash; Archived modellarni o'chirish (rollback imkonsiz); auditni tahrirlash yoki o'chirish; rollback ni hech qachon sinamaslik.
3. Tez ma'lumotnoma
import hashlib
import json
import sqlite3
def malumot_xeshi(df, kalit="mijoz_id"):
kanonik = df[sorted(df.columns)].sort_values(kalit)
matn = kanonik.to_csv(index=False, float_format="%.6f", lineterminator="\n")
return hashlib.sha256(matn.encode("utf-8")).hexdigest()[:12]
def keyingi_versiya(joriy, ozgarish):
katta, orta, kichik = map(int, joriy.split("."))
if ozgarish == "MAJOR":
return f"{katta + 1}.0.0"
if ozgarish == "MINOR":
return f"{katta}.{orta + 1}.0"
return f"{katta}.{orta}.{kichik + 1}"
RUXSAT = {("None", "Staging"), ("None", "Archived"), ("Staging", "Production"),
("Staging", "Archived"), ("Production", "Archived"),
("Archived", "Staging")}
db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE audit (id INTEGER PRIMARY KEY, nom TEXT, versiya INT,"
" eski TEXT, yangi TEXT, kim TEXT, sabab TEXT)")
with db: # tranzaksiya: hammasi yoki hech narsa
db.execute("INSERT INTO audit (nom, versiya, eski, yangi, kim, sabab)"
" VALUES (?, ?, ?, ?, ?, ?)",
("mijoz_ketishi", 1, "Staging", "Production", "ali", "darvoza"))Tuzilma xulosasi
model versiyasi = kod (git) + ma'lumot (xesh) + konfig/muhit -> artefakt (xesh)
semver: MAJOR shartnoma / MINOR xatti-harakat / PATCH bashorat o'zgarmaydi
registr: nom, versiya, bosqich, metrikalar, lineage, karta + audit (append-only)
None -> Staging -> [darvoza] -> Production -> Archived; rollback - maxsus yo'l
darvoza: mutlaq chegara + juftlashgan "sezilarli yomon emas" (2*SE)
DVC: fayl xeshi -> ombor manzili, git da faqat kichik ko'rsatkich4. Batafsil misollar
Misollar real numpy/pandas/sklearn va
sqlite3/hashlib/yamlbilan (Python 3.14). Hamma fayllar vaqtinchalik papkada.
Misol 1 — Kod, ma'lumot va modelni birga versiyalash
"""Kod + ma'lumot + model: xeshlar, manifest, qayta tiklash, semantik versiya."""
import hashlib
import inspect
import io
import json
import joblib
import numpy as np
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler
SONLI = ["oylar", "tolov", "shikoyat"]
KATEGORIAL = ["tarif"]
def malumot(n, seed):
rng = np.random.default_rng(seed)
df = pd.DataFrame({
"mijoz_id": np.arange(n),
"oylar": rng.integers(1, 72, n),
"tolov": rng.normal(60, 20, n).clip(10, 150).round(2),
"shikoyat": rng.poisson(0.8, n),
"tarif": rng.choice(["oylik", "yillik", "ikki_yillik"], n,
p=[0.55, 0.3, 0.15]),
})
logit = (-1.0 - 0.04 * df["oylar"] + 0.02 * (df["tolov"] - 60)
+ 0.5 * df["shikoyat"]
+ np.where(df["tarif"] == "oylik", 1.0, -0.5))
df["ketdi"] = (rng.random(n) < 1 / (1 + np.exp(-logit))).astype(int)
return df
def xesh(baytlar):
return hashlib.sha256(baytlar).hexdigest()[:12]
def malumot_xeshi(df, kanonik=True):
if kanonik:
df = df[sorted(df.columns)].sort_values("mijoz_id")
matn = df.to_csv(index=False, float_format="%.6f", lineterminator="\n")
return xesh(matn.encode("utf-8"))
def orgat(df, konfig):
pre = ColumnTransformer([
("son", StandardScaler(), SONLI),
("kat", OneHotEncoder(handle_unknown="ignore"), KATEGORIAL),
])
model = Pipeline([("pre", pre),
("clf", LogisticRegression(C=konfig["C"], max_iter=1000))])
return model.fit(df[SONLI + KATEGORIAL], df["ketdi"])
def orgat_muvozanatli(df, konfig):
pre = ColumnTransformer([
("son", StandardScaler(), SONLI),
("kat", OneHotEncoder(handle_unknown="ignore"), KATEGORIAL),
])
model = Pipeline([("pre", pre),
("clf", LogisticRegression(C=konfig["C"], max_iter=1000,
class_weight="balanced"))])
return model.fit(df[SONLI + KATEGORIAL], df["ketdi"])
def kod_versiyasi(funksiya):
# haqiqiy loyihada: git commit xeshi (toza ishchi daraxt bilan)
return xesh(inspect.getsource(funksiya).encode("utf-8"))
def konfig_xeshi(konfig):
return xesh(json.dumps(konfig, sort_keys=True).encode("utf-8"))
def model_xeshi(model):
bufer = io.BytesIO()
joblib.dump(model, bufer)
return xesh(bufer.getvalue())
def keyingi_versiya(joriy, ozgarish):
katta, orta, kichik = map(int, joriy.split("."))
if ozgarish == "MAJOR":
return f"{katta + 1}.0.0"
if ozgarish == "MINOR":
return f"{katta}.{orta + 1}.0"
return f"{katta}.{orta}.{kichik + 1}"
def main() -> None:
konfig = {"C": 1.0, "seed": 7}
df = malumot(4000, seed=konfig["seed"])
orgatish, sinov = df.iloc[:3000], df.iloc[3000:]
print("=== 1. Ma'lumot xeshi ===")
h1 = malumot_xeshi(orgatish)
h2 = malumot_xeshi(malumot(4000, seed=7).iloc[:3000])
aralash = orgatish.sample(frac=1.0, random_state=0)
bitta = orgatish.copy()
bitta.loc[bitta.index[5], "tolov"] += 0.01
print(f" asl ma'lumot: {h1}")
print(f" qayta generatsiya: {h2} teng: {h1 == h2}")
print(f" qatorlar aralashgan, xom: "
f"{malumot_xeshi(aralash, kanonik=False)}")
print(f" qatorlar aralashgan, kanonik: {malumot_xeshi(aralash)} "
f"teng: {malumot_xeshi(aralash) == h1}")
print(f" bitta qiymat +0.01: {malumot_xeshi(bitta)} "
f"teng: {malumot_xeshi(bitta) == h1}")
print("\n=== 2. Manifest: uchlik bitta joyda ===")
model = orgat(orgatish, konfig)
p = model.predict_proba(sinov[SONLI + KATEGORIAL])[:, 1]
manifest = {
"model": "mijoz_ketishi",
"versiya": "1.0.0",
"malumot_xeshi": h1,
"qatorlar": len(orgatish),
"kod_versiyasi": kod_versiyasi(orgat),
"konfig": konfig,
"konfig_xeshi": konfig_xeshi(konfig),
"model_xeshi": model_xeshi(model),
"metrikalar": {"auc": round(float(roc_auc_score(sinov["ketdi"], p)), 4)},
}
for kalit, qiymat in manifest.items():
print(f" {kalit:<14} {qiymat}")
print("\n=== 3. Manifestdan qayta qurish ===")
qayta_df = malumot(4000, seed=manifest["konfig"]["seed"]).iloc[:3000]
print(f" ma'lumot xeshi mos: "
f"{malumot_xeshi(qayta_df) == manifest['malumot_xeshi']}")
qayta = orgat(qayta_df, manifest["konfig"])
print(f" kod versiyasi mos: "
f"{kod_versiyasi(orgat) == manifest['kod_versiyasi']}")
print(f" konfig xeshi mos: "
f"{konfig_xeshi(manifest['konfig']) == manifest['konfig_xeshi']}")
print(f" model xeshi mos: "
f"{model_xeshi(qayta) == manifest['model_xeshi']}")
farq = np.abs(qayta.predict_proba(sinov[SONLI + KATEGORIAL])[:, 1] - p).max()
print(f" bashoratlar max farqi: {farq:.1e}")
print("\n=== 4. Qaysi o'zgarish qaysi xeshni o'zgartiradi ===")
yangi_df = malumot(3500, seed=8)
variantlar = [
("hech narsa", orgat, orgatish, konfig),
("yangi ma'lumot (3500)", orgat, yangi_df, konfig),
("konfig: C=0.1", orgat, orgatish, {**konfig, "C": 0.1}),
("kod: class_weight", orgat_muvozanatli, orgatish, konfig),
]
asos = (h1, manifest["kod_versiyasi"], manifest["konfig_xeshi"],
manifest["model_xeshi"])
sarlavha = "o'zgarish"
print(f" {sarlavha:<22} {'malumot':<9} {'kod':<9} {'konfig':<9} model")
for nom, funksiya, d, k in variantlar:
joriy = (malumot_xeshi(d), kod_versiyasi(funksiya), konfig_xeshi(k),
model_xeshi(funksiya(d, k)))
b = ["o'zgardi" if x != y else "-" for x, y in zip(joriy, asos)]
print(f" {nom:<22} {b[0]:<9} {b[1]:<9} {b[2]:<9} {b[3]}")
print("\n=== 5. Semantik versiya ===")
stsenariylar = [
("MINOR", "yangi oy ma'lumoti bilan qayta o'qitish"),
("PATCH", "model kartasidagi imlo tuzatildi"),
("MINOR", "C giperparametri o'zgardi"),
("MAJOR", "yangi MAJBURIY kirish ustuni: hudud"),
("PATCH", "paket metadata si tuzatildi"),
("MAJOR", "chiqish: ehtimol o'rniga sinf"),
]
versiya = manifest["versiya"]
for ozgarish, tavsif in stsenariylar:
yangi = keyingi_versiya(versiya, ozgarish)
print(f" {versiya:>6} -> {yangi:<6} {ozgarish:<6} {tavsif}")
versiya = yangi
print(" ⭐ Versiya = kod + ma'lumot + konfig; manifest ularni bog'laydi")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Ma'lumot xeshi ===
asl ma'lumot: 390d116cced9
qayta generatsiya: 390d116cced9 teng: True
qatorlar aralashgan, xom: 7e81c712e469
qatorlar aralashgan, kanonik: 390d116cced9 teng: True
bitta qiymat +0.01: e9a117d03277 teng: False
=== 2. Manifest: uchlik bitta joyda ===
model mijoz_ketishi
versiya 1.0.0
malumot_xeshi 390d116cced9
qatorlar 3000
kod_versiyasi 2a9d7b89de90
konfig {'C': 1.0, 'seed': 7}
konfig_xeshi df1e7446ded1
model_xeshi 23453b260206
metrikalar {'auc': 0.7721}
=== 3. Manifestdan qayta qurish ===
ma'lumot xeshi mos: True
kod versiyasi mos: True
konfig xeshi mos: True
model xeshi mos: True
bashoratlar max farqi: 0.0e+00
=== 4. Qaysi o'zgarish qaysi xeshni o'zgartiradi ===
o'zgarish malumot kod konfig model
hech narsa - - - -
yangi ma'lumot (3500) o'zgardi - - o'zgardi
konfig: C=0.1 - - o'zgardi o'zgardi
kod: class_weight - o'zgardi - o'zgardi
=== 5. Semantik versiya ===
1.0.0 -> 1.1.0 MINOR yangi oy ma'lumoti bilan qayta o'qitish
1.1.0 -> 1.1.1 PATCH model kartasidagi imlo tuzatildi
1.1.1 -> 1.2.0 MINOR C giperparametri o'zgardi
1.2.0 -> 2.0.0 MAJOR yangi MAJBURIY kirish ustuni: hudud
2.0.0 -> 2.0.1 PATCH paket metadata si tuzatildi
2.0.1 -> 3.0.0 MAJOR chiqish: ehtimol o'rniga sinf
⭐ Versiya = kod + ma'lumot + konfig; manifest ularni bog'laydiNatija tahlili. 1-bo'limda ma'lumotni urug' bilan qayta generatsiya qilish aynan o'sha xeshni berdi — ma'lumot xeshi uning barqaror nomi. Qatorlar aralashtirilganda xom xesh butunlay boshqacha bo'ldi, kanonik xesh esa asl bilan teng qoldi: mazmun o'sha, faqat tartib boshqa. Bitta qiymatga +0.01 qo'shilganda esa kanonik xesh ham o'zgardi — shunday bo'lishi shart. 2-bo'limdagi manifest modelning pasporti: undagi ma'lumot xeshi, kod versiyasi va konfig bo'yicha 3-bo'limda model qayta qurildi va artefakt xeshi bitma-bit mos keldi. 4-bo'lim jadvali uchlik mantiqini ko'rsatadi: ma'lumot, konfig yoki kod — qaysi biri o'zgarsa, model xeshi ham o'zgaradi, lekin qaysi sababdan o'zgargani faqat qolgan ustunlarga qarab aniqlanadi. Model xeshining o'zi "nimadir o'zgardi" deydi, xolos; manifestdagi boshqa xeshlar "nima o'zgardi" ni aytadi. Misolda kod versiyasi o'rgatish funksiyasining matnidan hisoblandi; haqiqiy loyihada bu git commit xeshi, konfig esa alohida fayl (params.yaml). 5-bo'limda semantik versiya iste'molchi nuqtai nazaridan o'sdi: ikki MAJOR o'zgarish (yangi majburiy ustun, chiqish ma'nosi) API mijozlarini kod o'zgartirishga majbur qiladi.
Misol 2 — Model registr noldan (sqlite)
"""sqlite ustida model registr: versiyalar, bosqichlar, lineage, audit."""
import json
import sqlite3
import tempfile
from pathlib import Path
RUXSAT = {
("None", "Staging"), ("None", "Archived"),
("Staging", "Production"), ("Staging", "Archived"),
("Production", "Archived"), ("Archived", "Staging"),
}
SXEMA = """
CREATE TABLE IF NOT EXISTS versiyalar (
nom TEXT NOT NULL,
versiya INTEGER NOT NULL,
bosqich TEXT NOT NULL,
metrikalar TEXT NOT NULL,
malumot_xeshi TEXT NOT NULL,
kod_versiyasi TEXT NOT NULL,
run_id TEXT NOT NULL,
PRIMARY KEY (nom, versiya)
);
CREATE TABLE IF NOT EXISTS audit (
id INTEGER PRIMARY KEY AUTOINCREMENT,
soat INTEGER NOT NULL,
nom TEXT NOT NULL,
versiya INTEGER NOT NULL,
eski TEXT,
yangi TEXT NOT NULL,
kim TEXT NOT NULL,
sabab TEXT NOT NULL
);
"""
class Registr:
def __init__(self, yol):
self.db = sqlite3.connect(yol)
self.db.executescript(SXEMA)
# virtual soat: haqiqiy vaqt o'rniga o'sib boruvchi hisoblagich
self.soat = self.db.execute(
"SELECT COALESCE(MAX(soat), 0) FROM audit").fetchone()[0]
def _audit(self, nom, versiya, eski, yangi, kim, sabab):
self.soat += 1
self.db.execute(
"INSERT INTO audit (soat, nom, versiya, eski, yangi, kim, sabab)"
" VALUES (?, ?, ?, ?, ?, ?, ?)",
(self.soat, nom, versiya, eski, yangi, kim, sabab))
def royxatga_ol(self, nom, metrikalar, malumot_xeshi, kod_versiyasi,
run_id, kim):
with self.db:
versiya = self.db.execute(
"SELECT COALESCE(MAX(versiya), 0) + 1 FROM versiyalar"
" WHERE nom = ?", (nom,)).fetchone()[0]
self.db.execute(
"INSERT INTO versiyalar VALUES (?, ?, ?, ?, ?, ?, ?)",
(nom, versiya, "None", json.dumps(metrikalar, sort_keys=True),
malumot_xeshi, kod_versiyasi, run_id))
self._audit(nom, versiya, None, "None", kim, "ro'yxatga olindi")
return versiya
def bosqich(self, nom, versiya):
return self.db.execute(
"SELECT bosqich FROM versiyalar WHERE nom = ? AND versiya = ?",
(nom, versiya)).fetchone()[0]
def otkaz(self, nom, versiya, yangi, kim, sabab):
eski = self.bosqich(nom, versiya)
if (eski, yangi) not in RUXSAT:
raise ValueError(f"ruxsat yo'q: {eski} -> {yangi}")
with self.db:
if yangi == "Production":
joriylar = self.db.execute(
"SELECT versiya FROM versiyalar WHERE nom = ?"
" AND bosqich = 'Production'", (nom,)).fetchall()
for (joriy,) in joriylar:
self.db.execute(
"UPDATE versiyalar SET bosqich = 'Archived'"
" WHERE nom = ? AND versiya = ?", (nom, joriy))
self._audit(nom, joriy, "Production", "Archived", kim,
f"v{versiya} bilan almashtirildi")
self.db.execute(
"UPDATE versiyalar SET bosqich = ? WHERE nom = ? AND versiya = ?",
(yangi, nom, versiya))
self._audit(nom, versiya, eski, yangi, kim, sabab)
def jadval(self, nom):
return self.db.execute(
"SELECT versiya, bosqich, metrikalar, malumot_xeshi, run_id"
" FROM versiyalar WHERE nom = ? ORDER BY versiya", (nom,)).fetchall()
def main() -> None:
with tempfile.TemporaryDirectory() as papka:
yol = Path(papka) / "registr.db"
reg = Registr(yol)
print("=== 1. Ro'yxatga olish ===")
yozuvlar = [
({"auc": 0.781, "log_loss": 0.482}, "9f3e41c07b2a", "a1b2c3d",
"run-0007"),
({"auc": 0.804, "log_loss": 0.463}, "4c7d20e9b815", "a1b2c3d",
"run-0012"),
({"auc": 0.809, "log_loss": 0.458}, "4c7d20e9b815", "e5f6a7b",
"run-0019"),
]
for metrikalar, mx, kv, run in yozuvlar:
v = reg.royxatga_ol("mijoz_ketishi", metrikalar, mx, kv, run, "ali")
print(f" v{v}: run {run}, ma'lumot {mx}, kod {kv}")
print("\n=== 2. Bosqich o'tishlari ===")
reg.otkaz("mijoz_ketishi", 1, "Staging", "ali", "sinov uchun")
reg.otkaz("mijoz_ketishi", 1, "Production", "vali", "birinchi chiqish")
reg.otkaz("mijoz_ketishi", 2, "Staging", "ali", "yangi ma'lumot")
reg.otkaz("mijoz_ketishi", 2, "Production", "vali", "auc yaxshiroq")
for v in (1, 2, 3):
print(f" v{v}: {reg.bosqich('mijoz_ketishi', v)}")
print("\n=== 3. Taqiqlangan o'tish ===")
for v, yangi in [(3, "Production"), (1, "Production")]:
try:
reg.otkaz("mijoz_ketishi", v, yangi, "shoshqaloq", "tezroq")
print(f" v{v} -> {yangi}: bajarildi (kutilmagan)")
except ValueError as xato:
print(f" v{v} -> {yangi}: ValueError - {xato}")
reg.otkaz("mijoz_ketishi", 3, "Staging", "ali", "kod yangilandi")
print("\n=== 4. Diskdan qayta ochish ===")
reg.db.close()
reg2 = Registr(yol)
for v, bosq, metr, mx, run in reg2.jadval("mijoz_ketishi"):
auc = json.loads(metr)["auc"]
print(f" v{v:<2} {bosq:<11} auc {auc:.3f} {mx} {run}")
prod_soni = reg2.db.execute(
"SELECT COUNT(*) FROM versiyalar WHERE bosqich = 'Production'"
).fetchone()[0]
print(f" Production soni: {prod_soni} (invariant: 1 dan oshmaydi)")
print("\n=== 5. Lineage so'rovlari ===")
v, mx, kv, run = reg2.db.execute(
"SELECT versiya, malumot_xeshi, kod_versiyasi, run_id"
" FROM versiyalar WHERE nom = 'mijoz_ketishi'"
" AND bosqich = 'Production'").fetchone()
print(f" Production: v{v}, ma'lumot {mx}, kod {kv}, run {run}")
tasirlangan = reg2.db.execute(
"SELECT versiya, bosqich FROM versiyalar WHERE malumot_xeshi = ?"
" ORDER BY versiya", ("4c7d20e9b815",)).fetchall()
print(f" ma'lumot 4c7d20e9b815 da muammo -> ta'sirlangan: "
f"{[f'v{a} ({b})' for a, b in tasirlangan]}")
print("\n=== 6. Audit tarixi ===")
for soat, v, eski, yangi, kim, sabab in reg2.db.execute(
"SELECT soat, versiya, eski, yangi, kim, sabab FROM audit"
" ORDER BY id"):
print(f" t={soat:<2} v{v} {str(eski):<10} -> {yangi:<10} "
f"{kim:<5} {sabab}")
reg2.db.close()
print(" ⭐ Qoidalar registr kodida; audit faqat qo'shiladi")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Ro'yxatga olish ===
v1: run run-0007, ma'lumot 9f3e41c07b2a, kod a1b2c3d
v2: run run-0012, ma'lumot 4c7d20e9b815, kod a1b2c3d
v3: run run-0019, ma'lumot 4c7d20e9b815, kod e5f6a7b
=== 2. Bosqich o'tishlari ===
v1: Archived
v2: Production
v3: None
=== 3. Taqiqlangan o'tish ===
v3 -> Production: ValueError - ruxsat yo'q: None -> Production
v1 -> Production: ValueError - ruxsat yo'q: Archived -> Production
=== 4. Diskdan qayta ochish ===
v1 Archived auc 0.781 9f3e41c07b2a run-0007
v2 Production auc 0.804 4c7d20e9b815 run-0012
v3 Staging auc 0.809 4c7d20e9b815 run-0019
Production soni: 1 (invariant: 1 dan oshmaydi)
=== 5. Lineage so'rovlari ===
Production: v2, ma'lumot 4c7d20e9b815, kod a1b2c3d, run run-0012
ma'lumot 4c7d20e9b815 da muammo -> ta'sirlangan: ['v2 (Production)', 'v3 (Staging)']
=== 6. Audit tarixi ===
t=1 v1 None -> None ali ro'yxatga olindi
t=2 v2 None -> None ali ro'yxatga olindi
t=3 v3 None -> None ali ro'yxatga olindi
t=4 v1 None -> Staging ali sinov uchun
t=5 v1 Staging -> Production vali birinchi chiqish
t=6 v2 None -> Staging ali yangi ma'lumot
t=7 v1 Production -> Archived vali v2 bilan almashtirildi
t=8 v2 Staging -> Production vali auc yaxshiroq
t=9 v3 None -> Staging ali kod yangilandi
⭐ Qoidalar registr kodida; audit faqat qo'shiladiNatija tahlili. Registr uchta versiyani avtomatik raqamladi va har biriga lineage — ma'lumot xeshi, kod versiyasi va eksperiment run_id si — yozildi (metrikalar 27.4 dagi kuzatuvdan olingan deb faraz qilingan). 2-bo'limda v2 Production ga chiqqanda v1 avtomatik Archived bo'ldi — bitta nomda ikkita Production bo'lishi mumkin emas, va bu qoida jamoa xotirasida emas, otkaz metodida. 3-bo'limda ikki shoshqaloq urinish rad etildi: None -> Production ("to'g'ridan-to'g'ri prodga") va Archived -> Production (bu faqat rollback orqali, 3-misolda). 4-bo'limda ulanish yopilib, registr diskdan qayta ochildi — holat to'liq saqlangan, virtual soat ham auditdan tiklangan. 5-bo'lim registrning asosiy qadrini ko'rsatadi: "hozir prodda nima va u qayerdan?" — bitta so'rov; 4c7d20e9b815 ma'lumotida muammo (masalan, shaxsiy ma'lumot sizib chiqqan) topilsa, ta'sirlangan versiyalar ham bitta so'rov. 6-bo'limdagi audit har o'tishni kim va nima uchun qilganini saqlaydi — v1 ning arxivga o'tishi ham, garchi uni hech kim qo'lda qilmagan bo'lsa ham, sababi bilan yozilgan.
Misol 3 — Darvoza, orqaga qaytarish va audit
"""Production darvozasi (juftlashgan taqqoslash), rollback va audit."""
import sqlite3
import numpy as np
import pandas as pd
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
USTUNLAR = ["oylar", "tolov", "shikoyat", "oylik_tarif"]
def malumot(n, seed):
rng = np.random.default_rng(seed)
df = pd.DataFrame({
"oylar": rng.integers(1, 72, n),
"tolov": rng.normal(60, 20, n).clip(10, 150),
"shikoyat": rng.poisson(0.8, n),
"oylik_tarif": (rng.random(n) < 0.55).astype(int),
})
yangi_qimmat = (df["tolov"] > 75) & (df["oylar"] < 18)
norozi_oylik = (df["shikoyat"] >= 3) & (df["oylik_tarif"] == 1)
logit = (-1.5 - 0.02 * df["oylar"] + 0.5 * df["shikoyat"]
+ 1.0 * df["oylik_tarif"] + 3.0 * yangi_qimmat
+ 1.5 * norozi_oylik)
df["ketdi"] = (rng.random(n) < 1 / (1 + np.exp(-logit))).astype(int)
return df
def log_loss_har(y, p):
p = np.clip(p, 1e-6, 1 - 1e-6)
return -(y * np.log(p) + (1 - y) * np.log(1 - p))
def darvoza_yasa(X, y, min_auc):
"""Chegaralar natijadan OLDIN belgilanadi."""
def darvoza(nomzod, prod):
p = nomzod.predict_proba(X)[:, 1]
auc = roc_auc_score(y, p)
if auc < min_auc:
return False, f"AUC {auc:.4f} < {min_auc}"
if prod is None:
return True, f"AUC {auc:.4f} >= {min_auc}, Production yo'q"
d = log_loss_har(y, p) - log_loss_har(y, prod.predict_proba(X)[:, 1])
ort, se = d.mean(), d.std(ddof=1) / np.sqrt(len(d))
if ort > 2 * se:
return False, f"log loss farqi {ort:+.4f} > 2*SE {2 * se:.4f}"
return True, f"AUC {auc:.4f}, log loss farqi {ort:+.4f} (2*SE {2 * se:.4f})"
return darvoza
class Registr:
def __init__(self, darvoza):
self.db = sqlite3.connect(":memory:")
self.db.executescript(
"CREATE TABLE versiyalar (nom TEXT, versiya INT, bosqich TEXT,"
" izoh TEXT, PRIMARY KEY (nom, versiya));"
"CREATE TABLE audit (id INTEGER PRIMARY KEY AUTOINCREMENT,"
" soat INT, nom TEXT, versiya INT, eski TEXT, yangi TEXT,"
" kim TEXT, sabab TEXT);")
self.soat = 0
self.artefakt = {} # haqiqiyda: fayl ombori (4-misol)
self.darvoza = darvoza
def _audit(self, nom, v, eski, yangi, kim, sabab):
self.soat += 1
self.db.execute("INSERT INTO audit (soat, nom, versiya, eski, yangi,"
" kim, sabab) VALUES (?, ?, ?, ?, ?, ?, ?)",
(self.soat, nom, v, eski, yangi, kim, sabab))
def _qoy(self, nom, v, yangi, kim, sabab):
eski = self.bosqich(nom, v)
self.db.execute("UPDATE versiyalar SET bosqich = ? WHERE nom = ?"
" AND versiya = ?", (yangi, nom, v))
self._audit(nom, v, eski, yangi, kim, sabab)
def bosqich(self, nom, v):
return self.db.execute("SELECT bosqich FROM versiyalar WHERE nom = ?"
" AND versiya = ?", (nom, v)).fetchone()[0]
def production(self, nom):
r = self.db.execute("SELECT versiya FROM versiyalar WHERE nom = ?"
" AND bosqich = 'Production'", (nom,)).fetchone()
return r[0] if r else None
def royxatga_ol(self, nom, model, izoh, kim):
with self.db:
v = self.db.execute("SELECT COALESCE(MAX(versiya), 0) + 1 FROM"
" versiyalar WHERE nom = ?", (nom,)).fetchone()[0]
self.db.execute("INSERT INTO versiyalar VALUES (?, ?, 'None', ?)",
(nom, v, izoh))
self._audit(nom, v, None, "None", kim, izoh)
self._qoy(nom, v, "Staging", kim, "sinovga")
self.artefakt[(nom, v)] = model
return v
def kotar(self, nom, v, kim):
joriy = self.production(nom)
otdi, hisobot = self.darvoza(self.artefakt[(nom, v)],
self.artefakt.get((nom, joriy)))
with self.db:
if not otdi:
self._qoy(nom, v, "Archived", kim, "darvoza RAD: " + hisobot)
return False, hisobot
if joriy is not None:
self._qoy(nom, joriy, "Archived", kim, f"v{v} bilan almashdi")
self._qoy(nom, v, "Production", kim, "darvoza: " + hisobot)
return True, hisobot
def rollback(self, nom, kim, sabab):
joriy = self.production(nom)
r = self.db.execute(
"SELECT versiya FROM audit WHERE nom = ? AND eski = 'Production'"
" AND yangi = 'Archived' AND versiya != ? ORDER BY id DESC LIMIT 1",
(nom, joriy)).fetchone()
if r is None:
raise RuntimeError("qaytish uchun oldingi Production yo'q")
with self.db: # ikkala qadam - bitta tranzaksiya
self._qoy(nom, joriy, "Archived", kim, "rollback: " + sabab)
self._qoy(nom, r[0], "Production", kim, f"rollback: v{joriy} dan")
return r[0]
def main() -> None:
orgatish = malumot(4000, seed=1)
val = malumot(3000, seed=2)
Xo, yo = orgatish[USTUNLAR], orgatish["ketdi"]
Xv, yv = val[USTUNLAR], val["ketdi"]
buzilgan = orgatish.copy()
buzilgan["oylar"] = buzilgan["oylar"].sample(
frac=1.0, random_state=3).to_numpy() # noto'g'ri join
yangi = malumot(4000, seed=4) # keyingi oy ma'lumoti
nomzodlar = {
"logistik": make_pipeline(StandardScaler(),
LogisticRegression()).fit(Xo, yo),
"gradient boosting": HistGradientBoostingClassifier(
max_iter=100, learning_rate=0.05, max_depth=3,
random_state=0).fit(Xo, yo),
"logistik (buzilgan join)": make_pipeline(
StandardScaler(), LogisticRegression()).fit(buzilgan[USTUNLAR],
buzilgan["ketdi"]),
"logistik (yangi oy)": make_pipeline(
StandardScaler(), LogisticRegression()).fit(yangi[USTUNLAR],
yangi["ketdi"]),
}
print("=== 1. Nomzodlar validatsiyada ===")
for nom, m in nomzodlar.items():
p = m.predict_proba(Xv)[:, 1]
print(f" {nom:<26} AUC {roc_auc_score(yv, p):.4f} "
f"log loss {log_loss_har(yv.to_numpy(), p).mean():.4f}")
reg = Registr(darvoza_yasa(Xv, yv.to_numpy(), min_auc=0.70))
nom = "mijoz_ketishi"
print("\n=== 2. Darvoza orqali chiqarish ===")
for model_nomi, m in nomzodlar.items():
v = reg.royxatga_ol(nom, m, model_nomi, "ali")
otdi, hisobot = reg.kotar(nom, v, "ci")
print(f" v{v} {model_nomi:<26} {'OTDI' if otdi else 'RAD '} {hisobot}")
print(f" hozirgi Production: v{reg.production(nom)}")
print("\n=== 3. Incident va orqaga qaytarish ===")
print(f" v{reg.production(nom)} da muammo: yangi segmentda shikoyatlar")
qaytgan = reg.rollback(nom, "navbatchi", "segment xatosi, tahlil kerak")
print(f" rollback -> v{qaytgan}, hozirgi Production: v{reg.production(nom)}")
print(f" qayta o'qitish kerak bo'ldimi: yo'q (artefakt registrda edi)")
print("\n=== 4. Registr holati ===")
for v, bosq, izoh in reg.db.execute(
"SELECT versiya, bosqich, izoh FROM versiyalar ORDER BY versiya"):
print(f" v{v} {bosq:<11} {izoh}")
print("\n=== 5. Audit tarixi ===")
for soat, v, eski, yangi, kim, sabab in reg.db.execute(
"SELECT soat, versiya, eski, yangi, kim, sabab FROM audit"
" ORDER BY id"):
print(f" t={soat:<2} v{v} {str(eski):<10} -> {yangi:<10} {kim:<9} "
f"{sabab[:44]}")
print("\n=== 6. 'Soat t da qaysi versiya prodda edi?' ===")
for soat in (4, 8, 15):
r = reg.db.execute(
"SELECT versiya FROM audit WHERE yangi = 'Production'"
" AND soat <= ? ORDER BY soat DESC LIMIT 1", (soat,)).fetchone()
print(f" t={soat:<2} -> v{r[0]}")
print(" javob auditdan, bitta so'rov bilan")
print(" ⭐ Darvoza regressiyani to'xtatadi, rollback - bir qadam")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Nomzodlar validatsiyada ===
logistik AUC 0.7334 log loss 0.5269
gradient boosting AUC 0.7568 log loss 0.5003
logistik (buzilgan join) AUC 0.6654 log loss 0.5623
logistik (yangi oy) AUC 0.7332 log loss 0.5265
=== 2. Darvoza orqali chiqarish ===
v1 logistik OTDI AUC 0.7334 >= 0.7, Production yo'q
v2 gradient boosting OTDI AUC 0.7568, log loss farqi -0.0265 (2*SE 0.0080)
v3 logistik (buzilgan join) RAD AUC 0.6654 < 0.7
v4 logistik (yangi oy) RAD log loss farqi +0.0262 > 2*SE 0.0080
hozirgi Production: v2
=== 3. Incident va orqaga qaytarish ===
v2 da muammo: yangi segmentda shikoyatlar
rollback -> v1, hozirgi Production: v1
qayta o'qitish kerak bo'ldimi: yo'q (artefakt registrda edi)
=== 4. Registr holati ===
v1 Production logistik
v2 Archived gradient boosting
v3 Archived logistik (buzilgan join)
v4 Archived logistik (yangi oy)
=== 5. Audit tarixi ===
t=1 v1 None -> None ali logistik
t=2 v1 None -> Staging ali sinovga
t=3 v1 Staging -> Production ci darvoza: AUC 0.7334 >= 0.7, Production yo'q
t=4 v2 None -> None ali gradient boosting
t=5 v2 None -> Staging ali sinovga
t=6 v1 Production -> Archived ci v2 bilan almashdi
t=7 v2 Staging -> Production ci darvoza: AUC 0.7568, log loss farqi -0.0265
t=8 v3 None -> None ali logistik (buzilgan join)
t=9 v3 None -> Staging ali sinovga
t=10 v3 Staging -> Archived ci darvoza RAD: AUC 0.6654 < 0.7
t=11 v4 None -> None ali logistik (yangi oy)
t=12 v4 None -> Staging ali sinovga
t=13 v4 Staging -> Archived ci darvoza RAD: log loss farqi +0.0262 > 2*SE 0
t=14 v2 Production -> Archived navbatchi rollback: segment xatosi, tahlil kerak
t=15 v1 Archived -> Production navbatchi rollback: v2 dan
=== 6. 'Soat t da qaysi versiya prodda edi?' ===
t=4 -> v1
t=8 -> v2
t=15 -> v1
javob auditdan, bitta so'rov bilan
⭐ Darvoza regressiyani to'xtatadi, rollback - bir qadamNatija tahlili. 1-bo'limda to'rt nomzod bir xil validatsiya to'plamida baholandi: gradient boosting (AUC 0.7568) logistik regressiyadan (0.7334) yaxshi — ma'lumotda o'zaro ta'sirlar bor ("yangi va qimmat to'lovli" mijozlar), chiziqli model ularni ko'rmaydi. "Buzilgan join" — oylar ustuni boshqa mijozlarniki bilan aralashib ketgan — AUC ni 0.6654 gacha tushirdi. 2-bo'limda darvoza ikkala shartini ham ishlatdi. v1 birinchi model — faqat mutlaq chegara tekshirildi. v2 juftlashgan taqqoslashda v1 dan log loss bo'yicha 0.0265 ga yaxshi (manfiy farq) — o'tdi va v1 avtomatik arxivga ketdi. v3 mutlaq chegarada (0.6654 < 0.7) to'xtadi. v4 eng qiziq holat: u mutlaq chegaradan bemalol o'tadi (AUC 0.7332), lekin joriy Production (v2) dan log loss bo'yicha +0.0262 ga yomon, bu esa 2*SE = 0.0080 dan ancha katta — nisbiy shart uni to'xtatdi. Faqat mutlaq chegara bo'lganida, "yangi oy ma'lumoti bilan qayta o'qitilgan" oddiy model yaxshiroq modelni jimgina almashtirib yuborardi. 3-bo'limda incident simulyatsiya qilindi: v2 da oflayn metrikada ko'rinmaydigan muammo chiqdi, rollback bitta tranzaksiyada v2 ni arxivga, oldingi Production (v1) ni qaytardi — auditdan "oldingi Production kim edi" ni o'zi topdi. E'tibor bering: v1 ni darvoza orqali qaytarib bo'lmasdi (u v2 dan sezilarli yomon), shuning uchun rollback maxsus yo'l: darvozasiz, lekin sababi va bajaruvchisi auditga majburiy. 5-bo'limdagi audit to'liq tarixni saqlaydi, rad etilgan urinishlar ham sababi bilan; 6-bo'lim esa regulyator savoliga — "t paytda qaysi versiya ishlagan?" — bitta so'rov bilan javob beradi.
Misol 4 — DVC g'oyasi: kontent-manzilli saqlash noldan
"""Fayl xeshi -> ombor manzili: add, dedup, checkout, butunlik, gc."""
import hashlib
import tempfile
from pathlib import Path
import numpy as np
import pandas as pd
import yaml
class Ombor:
def __init__(self, ildiz):
self.ildiz = ildiz
def _yol(self, xesh):
return self.ildiz / xesh[:2] / xesh[2:]
def qosh(self, fayl):
"""dvc add: faylni omborga, yoniga .dvc ko'rsatkich."""
baytlar = fayl.read_bytes()
xesh = hashlib.sha256(baytlar).hexdigest()
yol = self._yol(xesh)
yangi = not yol.exists()
if yangi:
yol.parent.mkdir(parents=True, exist_ok=True)
yol.write_bytes(baytlar)
korsatkich = {"outs": [{"path": fayl.name, "sha256": xesh,
"size": len(baytlar)}]}
matn = yaml.safe_dump(korsatkich, sort_keys=False)
(fayl.parent / (fayl.name + ".dvc")).write_text(matn, encoding="utf-8")
return matn, yangi
def chiqar(self, korsatkich_matni, papka):
"""dvc checkout: ko'rsatkich bo'yicha faylni tiklash (tekshirib)."""
chiqish = yaml.safe_load(korsatkich_matni)["outs"][0]
baytlar = self._yol(chiqish["sha256"]).read_bytes()
if hashlib.sha256(baytlar).hexdigest() != chiqish["sha256"]:
raise ValueError("ombordagi obyekt buzilgan")
(papka / chiqish["path"]).write_bytes(baytlar)
return chiqish["path"]
def obyektlar(self):
return sorted(p for p in self.ildiz.rglob("*") if p.is_file())
def tekshir(self):
buzilgan = []
for p in self.obyektlar():
kutilgan = p.parent.name + p.name
if hashlib.sha256(p.read_bytes()).hexdigest() != kutilgan:
buzilgan.append(kutilgan[:12])
return buzilgan
def gc(self, korsatkichlar):
kerak = {yaml.safe_load(k)["outs"][0]["sha256"] for k in korsatkichlar}
ochirildi = 0
for p in self.obyektlar():
if p.parent.name + p.name not in kerak:
p.unlink()
ochirildi += 1
return ochirildi
def malumot(n, seed):
rng = np.random.default_rng(seed)
return pd.DataFrame({
"mijoz_id": np.arange(n),
"oylar": rng.integers(1, 72, n),
"tolov": rng.normal(60, 20, n).round(2),
"ketdi": rng.integers(0, 2, n),
})
def holat(ombor):
obyektlar = ombor.obyektlar()
return len(obyektlar), sum(p.stat().st_size for p in obyektlar)
def main() -> None:
with tempfile.TemporaryDirectory() as t:
ish = Path(t) / "ish"
ish.mkdir()
ombor = Ombor(Path(t) / "ombor")
fayl = ish / "mijozlar.csv"
git_tarixi = [] # (commit xabari, ko'rsatkich matni)
print("=== 1. dvc add: birinchi versiya ===")
v1 = malumot(2000, seed=0)
v1.to_csv(fayl, index=False, lineterminator="\n")
matn, yangi = ombor.qosh(fayl)
git_tarixi.append(("ma'lumot v1", matn))
print(" mijozlar.csv.dvc:")
for qator in matn.splitlines():
if "sha256" in qator:
qator = f"{qator[:22]}(+{len(qator) - 22} belgi)"
print(f" {qator}")
n, hajm = holat(ombor)
korsatkich_hajmi = (ish / "mijozlar.csv.dvc").stat().st_size
print(f" omborda: {n} obyekt, {hajm} bayt; git dagi ko'rsatkich: "
f"{korsatkich_hajmi} bayt")
print("\n=== 2. Dedup: bir xil mazmun - bitta obyekt ===")
nusxa = ish / "mijozlar_nusxa.csv"
nusxa.write_bytes(fayl.read_bytes())
_, yangi = ombor.qosh(nusxa)
print(f" nusxa qo'shildi, yangi obyekt yaratildimi: {yangi}")
print(f" omborda: {holat(ombor)[0]} obyekt")
nusxa.unlink()
(ish / "mijozlar_nusxa.csv.dvc").unlink()
print("\n=== 3. Ma'lumot o'zgardi: ikkinchi versiya ===")
v2 = pd.concat([v1, malumot(500, seed=1).assign(
mijoz_id=lambda d: d["mijoz_id"] + 2000)], ignore_index=True)
v2.to_csv(fayl, index=False, lineterminator="\n")
matn2, yangi = ombor.qosh(fayl)
git_tarixi.append(("ma'lumot v2: +500 mijoz", matn2))
n, hajm = holat(ombor)
print(f" yangi obyekt: {yangi}; omborda: {n} obyekt, {hajm} bayt")
for xabar, k in git_tarixi:
x = yaml.safe_load(k)["outs"][0]
print(f" commit '{xabar}': sha256 {x['sha256'][:12]}, "
f"{x['size']} bayt")
print("\n=== 4. Checkout: v1 ga qaytish ===")
ombor.chiqar(git_tarixi[0][1], ish)
tiklangan = pd.read_csv(fayl)
print(f" ishchi fayl qatorlari: {len(tiklangan)}")
print(f" v1 bilan aynan teng: {tiklangan.equals(v1)}")
ombor.chiqar(git_tarixi[1][1], ish)
print(f" yana v2 ga: {len(pd.read_csv(fayl))} qator")
print("\n=== 5. gc: ishora qilinmagan obyektlar ===")
(ish / "sinov.csv").write_text("a,b\n1,2\n", encoding="utf-8")
ombor.qosh(ish / "sinov.csv") # commit qilinmagan tajriba
print(f" gc dan oldin: {holat(ombor)[0]} obyekt")
ochdi = ombor.gc([k for _, k in git_tarixi])
print(f" o'chirildi: {ochdi}; qoldi: {holat(ombor)[0]} obyekt")
print("\n=== 6. Butunlik tekshiruvi ===")
print(f" buzilgan obyektlar: {ombor.tekshir()}")
birinchi = ombor.obyektlar()[0]
baytlar = bytearray(birinchi.read_bytes())
baytlar[100] ^= 1 # bitta bit o'zgardi
birinchi.write_bytes(bytes(baytlar))
print(f" bitta bit o'zgargandan keyin: {ombor.tekshir()}")
buzilgan_xesh = birinchi.parent.name + birinchi.name
for xabar, k in git_tarixi:
if yaml.safe_load(k)["outs"][0]["sha256"] == buzilgan_xesh:
try:
ombor.chiqar(k, ish)
except ValueError as xato:
print(f" checkout '{xabar}': ValueError - {xato}")
print(" ⭐ Manzil = mazmun xeshi: dedup, tiklash va butunlik bepul")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. dvc add: birinchi versiya ===
mijozlar.csv.dvc:
outs:
- path: mijozlar.csv
sha256: 525cd44cd353(+52 belgi)
size: 30524
omborda: 1 obyekt, 30524 bayt; git dagi ko'rsatkich: 120 bayt
=== 2. Dedup: bir xil mazmun - bitta obyekt ===
nusxa qo'shildi, yangi obyekt yaratildimi: False
omborda: 1 obyekt
=== 3. Ma'lumot o'zgardi: ikkinchi versiya ===
yangi obyekt: True; omborda: 2 obyekt, 68945 bayt
commit 'ma'lumot v1': sha256 525cd44cd353, 30524 bayt
commit 'ma'lumot v2: +500 mijoz': sha256 90707916b0aa, 38421 bayt
=== 4. Checkout: v1 ga qaytish ===
ishchi fayl qatorlari: 2000
v1 bilan aynan teng: True
yana v2 ga: 2500 qator
=== 5. gc: ishora qilinmagan obyektlar ===
gc dan oldin: 3 obyekt
o'chirildi: 1; qoldi: 2 obyekt
=== 6. Butunlik tekshiruvi ===
buzilgan obyektlar: []
bitta bit o'zgargandan keyin: ['525cd44cd353']
checkout 'ma'lumot v1': ValueError - ombordagi obyekt buzilgan
⭐ Manzil = mazmun xeshi: dedup, tiklash va butunlik bepulNatija tahlili. 1-bo'limda dvc add ning mohiyati ko'rindi: 30524 baytli CSV omborga fayl xeshi nomi bilan joylandi, git ga esa faqat 120 baytli ko'rsatkich (path, sha256, size) tushadi. Ma'lumot 100 marta katta bo'lsa ham ko'rsatkich o'sha hajmda qoladi. 2-bo'limda aynan bir xil mazmunli nusxa qo'shildi — xesh bir xil, demak yangi obyekt yaratilmadi (dedup). 3-bo'limda ma'lumotga 500 mijoz qo'shildi — yangi xesh, yangi obyekt, eski obyekt esa joyida: ombor endi 2 obyekt, 68945 bayt, har commit o'z ko'rsatkichiga ega. 4-bo'limdagi "checkout" ko'rsatkichdagi xesh bo'yicha faylni tikladi va u v1 bilan aynan teng chiqdi — git checkout + dvc checkout juftligi shu. 5-bo'limda commit qilinmagan tajriba fayli ombordagi ortiqcha obyekt bo'lib qoldi; gc faqat git tarixidagi ko'rsatkichlar ishora qilgan obyektlarni saqladi va bittasini o'chirdi. 6-bo'lim kontent-manzilli saqlashning bepul bonusini ko'rsatadi: obyekt nomi — uning xeshi, shuning uchun bitta bit o'zgarishi ham darhol aniqlanadi va buzilgan ma'lumot ishchi papkaga tiklanmaydi. Haqiqiy DVC xuddi shu g'oyani katta hajmda, masofaviy omborlar (S3, GCS, SSH) va quvur keshi (dvc repro) bilan amalga oshiradi.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
"Model versiyasi — bu .pkl fayl" |
Versiya — kod, ma'lumot, konfig va muhit uchligi; fayl — natija |
| "Ma'lumotni sana bilan nomlash yetarli" | iyun.csv keyin o'zgaradi; mazmun xeshi o'zgarmas nom |
| "Xom fayl xeshi mazmunni aniqlaydi" | Qator tartibi, \r\n, float formati — boshqa xesh; kanonik shakl kerak |
| "Eksperiment kuzatuvi registr o'rnini bosadi" | Kuzatuv — hamma tajriba; registr — nomlangan, bosqichli, auditli modellar |
| "Arxivdagi modellarni o'chirsa bo'ladi" | Rollback va regulyator savollari uchun ular kerak |
| "Darvoza — yangi model yaxshiroqmi degan savol" | Darvoza — regressiya yo'qligi; "arziydimi" — alohida qaror |
| "AUC 0.002 ga oshdi — demak yaxshiroq" | Juftlashgan farq va SE ni ko'rmasdan bu shovqin bo'lishi mumkin |
| "Rollback — favqulodda holat, sinash shart emas" | Sinalmagan rollback kerakli paytda ishlamaydi |
| "DVC ma'lumotni git ga joylaydi" | Git da faqat kichik ko'rsatkich; mazmun alohida omborda |
6. Keng tarqalgan xatolar va yechimlari
1. Artefaktni ustiga yozish
joblib.dump(model, "modellar/model_final.pkl") # ⚠️
joblib.dump(model, f"modellar/{nom}/{versiya}/model.joblib") # ✅ + registr2. Ma'lumotni nom bilan "versiyalash"
manifest = {"malumot": "data/iyun.csv"} # ⚠️
manifest = {"malumot": "data/iyun.csv",
"malumot_xeshi": malumot_xeshi(df)} # ✅3. Kodni git holatisiz yozish
kod_versiyasi = "main" # ⚠️ o'zgaradi
kod_versiyasi = git_commit + ("-dirty" if ozgargan_fayllar else "") # ✅4. To'g'ridan-to'g'ri Production
reg.otkaz(nom, v, "Production", kim, "shoshilinch") # ⚠️ None -> Production
reg.otkaz(nom, v, "Staging", kim, "sinov"); reg.kotar(nom, v, kim) # ✅ darvoza orqali5. Darvozada juftlashmagan taqqoslash
otdi = auc_nomzod > auc_prod # ⚠️ shovqin
d = ll_nomzod - ll_prod; otdi = d.mean() <= 2 * d.std(ddof=1) / np.sqrt(len(d)) # ✅6. Rollback uchun qayta o'qitish
model = orgat(eski_malumot, eski_konfig) # ⚠️ soatlar, boshqa natija
reg.rollback(nom, "navbatchi", "incident #123") # ✅ soniyalar, aynan o'sha7. Auditni tahrirlash
db.execute("DELETE FROM audit WHERE kim = 'men'") # ⚠️
# audit faqat INSERT; tuzatish - yangi yozuv bilan # ✅7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 19-qism (o'tilgan):
Pipelinevajoblib— registrga yoziladigan artefakt - 18-qism (o'tilgan): juftlashgan taqqoslash va SE — darvozaning asosi
- 26.10-dars (o'tilgan): model kartasi — registr yozuviga biriktiriladi
- 27.2-dars (o'tilgan): muhit va
requirements.lock— uchlikning to'rtinchi a'zosi - 27.3-dars (o'tilgan): ma'lumot quvuri — ma'lumot xeshi shu yerda hisoblanadi
- 27.4-dars (o'tilgan): eksperiment kuzatuvi —
run_idregistrdagi lineage ga ulanadi - 27.6-dars: Modelni paketlash — registrdagi artefakt aslida nima ekanini aniqlaymiz
- 27.10-dars: Bulut va deploy strategiyalari — canary va rollback amalda
- 27.13-dars: Qayta o'qitish va A/B test — yangi versiyalar registrga shu jarayon orqali keladi
8. Eng yaxshi amaliyotlar
Har model versiyasi uchun manifest: kod versiyasi, ma'lumot xeshi, konfig, muhit, artefakt xeshi,
run_id.Ma'lumot xeshini kanonik shaklda hisoblang va qaysi normallashtirish qilinganini hujjatlang.
Semantik versiyani iste'molchi nuqtai nazaridan qo'ying: shartnoma buzilsa — MAJOR.
Bosqich qoidalari registr kodida —
None -> Productionva ikkitaProductionimkonsiz.Darvoza oldindan yozilgan va avtomatik: mutlaq chegara + juftlashgan "sezilarli yomon emas".
Artefaktlarni o'chirmang,
Archived— faqat bosqich.Audit append-only, har o'tishda
kimvasababmajburiy.Rollback ni muntazam sinang — xuddi yong'in mashqi kabi.
Katta fayllar git da emas — kontent-manzilli omborda, git da ko'rsatkich.
9. Amaliy topshiriq
Vazifa 1: Bashorat qiling
1. # model versiyasining uchligi nimalardan iborat?
2. # qatorlar aralashtirilsa, xom xesh o'zgaradimi? kanonik-chi?
3. # yangi majburiy kirish ustuni - qaysi semver qismi?
4. # yangi ma'lumot bilan qayta o'qitish - qaysi qismi?
5. # model kartasidagi imlo tuzatilsa?
6. # None -> Production ruxsatmi?
7. # yangi versiya Production ga chiqsa, eskisi nima bo'ladi?
8. # darvozaning ikki sharti?
9. # nega darvozada juftlashgan farq?
10. # rollback nega tez?
11. # DVC da git nimani saqlaydi?
12. # bir xil fayl ikki marta qo'shilsa, omborda nechta obyekt?Javoblar
- Kod versiyasi, ma'lumot xeshi, konfig (va muhit) — natijada artefakt
- Xom — ha; kanonik — yo'q (tartib normallashtiriladi)
- MAJOR
- MINOR
- PATCH
- Yo'q — faqat
Stagingorqali va darvoza bilan - Avtomatik
Archived(audit bilan) - Mutlaq metrika chegarasi va joriy Production dan sezilarli yomon emaslik
- Bir xil namunalarda farq shovqini kichik; SE bilan sezilarlilik baholanadi
- Artefakt registrda turibdi — qayta o'qitish shart emas
- Faqat kichik ko'rsatkich fayli (xesh va hajm)
- Bitta
Vazifa 2: Xatolarni tuzating
1. joblib.dump(model, "model_final.pkl")
2. manifest = {"malumot": "data/iyun.csv"}
3. reg.otkaz("churn", 5, "Production", "men", "tez kerak")
4. otdi = auc_yangi > auc_eski
5. db.execute("DELETE FROM audit WHERE versiya = 3")Javoblar
1. joblib.dump(model, f"modellar/{nom}/{versiya}/model.joblib") # + registrga yozuv
2. manifest = {"malumot": "data/iyun.csv", "malumot_xeshi": malumot_xeshi(df)}
3. reg.otkaz("churn", 5, "Staging", "men", "sinov"); reg.kotar("churn", 5, "men")
4. d = ll_yangi - ll_eski; otdi = d.mean() <= 2 * d.std(ddof=1) / np.sqrt(len(d))
5. # audit o'chirilmaydi; xato yozuv bo'lsa - tuzatuvchi yangi yozuv qo'shiladiVazifa 3: Kanonik xesh
malumot_xeshi ga ikkita parametr qo'shing: kalit (tartiblash ustuni) va onlik (float aniqligi). Tekshiring:
- Ustunlar tartibi o'zgarsa xesh o'zgarmaydi
onlik=2da+0.001o'zgarish xeshni o'zgartirmaydi,onlik=6da o'zgartiradi- Bu xossa qachon foydali, qachon xavfli?
Vazifa 4: Registrga model kartasi
2-misoldagi registrga kartalar jadvalini qo'shing (nom, versiya, maqsad, cheklovlar, egasi). Qoidalar:
- Kartasiz versiya
Staging -> Productionga o'tolmaydi - Karta tahrirlansa — auditga yozuv (
sabab = "karta PATCH") Productionmodelning kartasini bitta so'rov bilan chiqaring
Vazifa 5: Vaqt bo'yicha so'rov
3-misol auditidan foydalanib qaysi_versiya(nom, soat) funksiyasini yozing: berilgan virtual soatda qaysi versiya Production da edi (rollback ni ham hisobga oling). Har soat uchun jadval chiqaring.
Vazifa 6: Kontent-manzilli ombor kengaytmasi
4-misoldagi Ombor ni kengaytiring:
- Papka qo'shish: har fayl alohida obyekt, papka uchun esa fayllar ro'yxatidan iborat "daraxt" obyekti (uning xeshi — ro'yxat xeshi)
- Bitta fayl o'zgarsa — nechta yangi obyekt paydo bo'ladi?
push: ikkinchi ombor (masofaviy o'rnida) ga faqat unda yo'q obyektlarni ko'chiring va nechta ko'chganini chiqaring
Vazifa 7: O'ylash
Jamoangiz har hafta modelni avtomatik qayta o'qitadi va darvozadan o'tsa, avtomatik Production ga chiqaradi. Bir kun biznes bo'limi shikoyat qildi: "so'nggi ikki oyda model sekin-asta yomonlashdi, lekin har hafta darvozadan o'tgan". Qanday bo'lishi mumkin va darvozani qanday tuzatasiz?
Javob
Qisqa javob: darvoza har safar yangi modelni joriy Production bilan solishtiradi. Har hafta yangi model oldingisidan "sezilarli yomon emas" — masalan, farq 0.3*SE — lekin sakkiz hafta ketma-ket shunday kichik pasayishlar yig'ilib, sezilarli yomonlashuvga aylanadi. Bu "qaynayotgan qurbaqa" effekti: har qadam chegaradan past, yig'indi esa katta.
1. Mutlaq chegara ham bor edi — nega ushlamadi?
Mutlaq chegara (masalan, AUC >= 0.75) odatda "minimal qabul qilinadigan" darajada qo'yiladi. Model 0.82 dan boshlab 0.77 gacha tushsa ham chegaradan yuqorida qoladi.
2. Tuzatishlar
| Muammo | Tuzatish |
|---|---|
| Faqat joriy bilan taqqoslash | Langar (anchor) model bilan ham taqqoslash — masalan, oxirgi chorakdagi eng yaxshi Production |
| Validatsiya to'plami har hafta yangi | Qat'iy golden validatsiya to'plami ham saqlanadi — farqlar bir xil o'lchov bilan |
| Chegara past | Mutlaq chegarani vaqti-vaqti bilan qayta ko'rib chiqish (lekin natijadan oldin, hujjatlangan holda) |
| Faqat umumiy metrika | Segmentlar bo'yicha darvoza: yomonlashuv ko'pincha bitta segmentdan boshlanadi |
3. Kuzatuv
Registr auditi va metrikalar tarixidan har bir Production versiyasining langar to'plamidagi metrikasini vaqt qatori sifatida chizing. Sekin trend ko'z bilan darhol ko'rinadi — bu 27.11 (Monitoring) va 27.12 (Drift aniqlash) darslarining mavzusi.
4. Halol chegara
Ba'zan metrika pasayishi modelning aybi emas: dunyo o'zgargan va vazifa qiyinlashgan (masalan, raqobatchi aksiya boshlagan). Buni ajratish uchun langar model ham yangi ma'lumotda baholanadi: agar langar ham xuddi shunday pasaygan bo'lsa — muammo ma'lumotda; agar faqat yangi modellar pasaygan bo'lsa — muammo o'qitish jarayonida.
Xulosa: darvoza bitta qadamni tekshiradi, trendni emas. Joriy modeldan tashqari, qat'iy langar model va qat'iy golden to'plam bilan ham solishtiring — shunda kichik qadamlar yig'indisi ham ko'rinadi.
Nimani mustahkamlaydi: 2.5, 2.6, 2.7-bo'limlar.
Xulosa
Bu darsda modelni kod, ma'lumot va konfig uchligi sifatida versiyaladik va noldan model registr qurdik.
Eng muhim uch fikr:
Model versiyasi — uchlik, fayl emas. 1-misolda manifest (ma'lumot xeshi, kod versiyasi, konfig xeshi, artefakt xeshi) bo'yicha model qayta qurildi va artefakt bitma-bit mos keldi. Kanonik ma'lumot xeshi qatorlar tartibiga befarq, lekin bitta qiymatning
+0.01o'zgarishini ushlaydi. Ma'lumot, kod yoki konfig — qaysi biri o'zgarsa, model xeshi o'zgaradi; sababni esa uchlikning qolgan a'zolari ko'rsatadi. Semantik versiya iste'molchi nuqtai nazaridan: shartnoma buzilsa — MAJOR.Registr — qoidalar kodda, tarix auditda. 2-misolda
None -> ProductionvaArchived -> Productionrad etildi, yangiProductionchiqqanda eskisi avtomatik arxivga o'tdi va bitta nomda ikkitaProductionbo'lmadi. Lineage "prodda nima va qayerdan?" va "bu ma'lumotdan qaysi modellar?" savollariga bitta so'rov bilan javob beradi. 3-misolda darvozaning ikkala sharti ishladi: buzilgan join mutlaq chegarada (AUC 0.6654 < 0.7) to'xtadi, "yangi oy" logistik modeli esa mutlaq chegaradan o'tsa ham joriy Production dan log loss bo'yicha+0.0262ga yomon edi (2*SE = 0.0080) — nisbiy shart uni to'xtatdi. Rollback bitta tranzaksiyada oldingi Production ni qaytardi — qayta o'qitishsiz.DVC g'oyasi — manzil = mazmun xeshi. 4-misolda git ga 120 baytli ko'rsatkich tushdi, 30 KB li ma'lumot esa omborda; bir xil mazmun bir marta saqlandi, eski versiya aynan tiklandi,
gcortiqcha obyektni tozaladi, bitta bitlik buzilish esa darhol aniqlandi.
Keyingi darsda Modelni paketlash: registrdagi artefakt aslida nima bo'lishi kerakligini aniqlaymiz — serializatsiya xavflari, versiya mosligi, sxema, nazorat namunalari va bitta Predictor interfeysi.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!