Mundarija (22)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Saqlash va yuklash
- 2.2. Hajm va kechikish
- 2.3. Pipeline ni to'liq saqlash
- 2.4. Drift
- 2.5. Qayta o'qitish siyosati
- 2.6. Nazorat ro'yxati
- 2.7. Tuzoqlar
- 2.8. Ishlab chiqarish — ishning yarmi
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Hajm va kechikish
- Misol 2 — Pipeline ni saqlash
- Misol 3 — Drift ni aniqlash
- Misol 4 — Qayta o'qitish siyosati
- 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
15.13-dars: Ansambllarni ishlab chiqarishga chiqarish
15-QISM — DARAXTLAR VA ANSAMBLLAR · 13-dars
1. Kirish va motivatsiya
Model CV da 0.85 AUC berdi — bu ishning yarmi. Qolgan yarmi: modelni saqlash, yuklash, bashorat kechikishini o'lchash, hajmini cheklash, driftni kuzatish va qachon qayta o'qitishni hal qilish.
Ansambllar bu yerda maxsus muammolar tug'diradi: 500 daraxtli Random Forest yuzlab megabayt bo'lishi mumkin, gradient boosting bashorati ketma-ket hisoblanadi, va model versiyasi bilan kutubxona versiyasi mos kelmasa — yuklash umuman ishlamaydi.
Bu darsda: joblib bilan saqlash, model hajmini kamaytirish, bashorat kechikishi, Pipeline ni to'liq saqlash, drift ni aniqlash, qayta o'qitish siyosati va nazorat ro'yxati.
Real vaziyat. Tavsiya tizimida 1000 daraxtli Random Forest 340 MB joy egalladi va har so'rov 45 ms oldi — SLA 20 ms edi. min_samples_leaf=20 va n_estimators=300 bilan model 18 MB va 9 ms ga tushdi, AUC esa atigi 0.004 ga pasaydi. Sifatning kichik qismini narxning katta qismiga almashtirish — ishlab chiqarishning odatiy savdosi.
Bu darsda ansambllarni ishlab chiqarishga chiqarishni o'rganamiz.
Bu darsda:
- Saqlash va yuklash
- Hajm va kechikish
- Pipeline ni to'liq saqlash
- Drift
- Qayta o'qitish siyosati
- Nazorat ro'yxati
- Tuzoqlar
- Amaliy: to'liq oqim
ℹ Misollar real numpy/pandas/sklearn bilan (Python 3.14).
2. Nazariya — chuqur tushuntirish
2.1. Saqlash va yuklash
import joblib
joblib.dump(quvur, "model.joblib", compress=3)
quvur = joblib.load("model.joblib")
MUHIM: pickle/joblib VERSIYAGA bog'liq
sklearn versiyasi o'zgarsa - ogohlantirish yoki xato
Python versiyasi o'zgarsa - muammo bo'lishi mumkin
Shuning uchun SAQLANG:
- sklearn/numpy/python versiyalari
- o'quv ma'lumotining xesh yoki sanasi
- belgilar ro'yxati va tartibi
- o'quv metrikalari Versiya mosligini yozib qo'ying: joblib.load boshqa sklearn versiyasida InconsistentVersionWarning beradi yoki umuman ishlamaydi. Model faylini metama'lumot bilan birga saqlang.
2.2. Hajm va kechikish
Random Forest hajmi ~ n_estimators * o'rtacha tugunlar soni
Kamaytirish:
1. min_samples_leaf oshirish (eng samarali)
2. n_estimators kamaytirish (to'yinishdan keyin)
3. max_depth cheklash
4. compress=3 (joblib) - 2-4x
5. Boosting ga o'tish (odatda ancha kichik)
Kechikish:
RF: daraxtlar PARALLEL (n_jobs) lekin har biri to'liq
GB: KETMA-KET, lekin daraxtlar sayoz -> ko'pincha tezroq
Bitta so'rov uchun n_jobs FOYDA BERMAYDI (ustama xarajat) Bitta so'rov uchun n_jobs=-1 ko'pincha sekinroq: parallellashtirish ustama xarajati foydadan katta. Paketli (batch) bashoratda esa u foydali.
2.3. Pipeline ni to'liq saqlash
quvur = Pipeline([("tayyor", ColumnTransformer(...)),
("model", RandomForestClassifier(...))])
quvur.fit(Xtr, ytr)
joblib.dump(quvur, "quvur.joblib")
NEGA butun Pipeline:
- tayyorlash va model BIRGA versiyalanadi
- bashoratda bir xil transformatsiya kafolatlanadi
- leakage xavfi kamayadi (12.9)
- kirish - asl DataFrame, tayyorlash kerak emasFaqat modelni saqlash — keng tarqalgan xato: tayyorlash bosqichi (scaler, encoder, imputer) alohida qolib ketadi va ishlab chiqarishda boshqacha qo'llaniladi. Har doim butun Pipeline ni saqlang.
2.4. Drift
Ikki xil drift:
1. KIRISH drifti (covariate shift): P(X) o'zgardi
- belgilar taqsimoti siljidi
- aniqlash: KS-test, PSI, o'rtacha/kvantil kuzatuvi
- model hali ishlashi mumkin
2. TUSHUNCHA drifti (concept drift): P(y|X) o'zgardi
- bog'liqlik o'zgardi
- aniqlash: faqat haqiqiy yorliq kelganda
- model ishlamay qoladi
PSI (Population Stability Index):
< 0.1 - barqaror
0.1-0.25 - kuzatuv kerak
> 0.25 - jiddiy siljishKirish drifti yorliqsiz aniqlanadi — bu uning asosiy qiymati: yorliqlar kechikib kelsa ham (masalan kredit defolti 12 oydan keyin ma'lum bo'ladi), kirish taqsimotini darhol kuzatish mumkin.
2.5. Qayta o'qitish siyosati
Uch yondashuv:
1. JADVAL bo'yicha: har hafta/oy
+ sodda, bashoratli
- keraksiz o'qitish yoki kech qolish
2. DRIFT bo'yicha: PSI yoki metrika chegarasi oshsa
+ resurs tejaladi
- kuzatuv infratuzilmasi kerak
3. ONLINE: har yangi ma'lumotda yangilash
+ tez moslashadi
- daraxt ansambllarida qiyin (qayta o'qitish kerak)
Amalda: jadval + drift signali (ikkalasi)
Har qayta o'qitishda: yangi modelni eski bilan TAQQOSLANG
yomonroq bo'lsa - chiqarmangYangi model avtomatik yaxshiroq emas: har qayta o'qitishdan keyin uni eski model bilan bir xil test to'plamida solishtiring va faqat yaxshiroq bo'lsa almashtiring. Aks holda bitta buzilgan ma'lumot to'plami ishlab chiqarishga tushadi.
2.6. Nazorat ro'yxati
CHIQARISHDAN OLDIN:
[ ] Pipeline to'liq saqlangan (tayyorlash + model)
[ ] Versiyalar yozib qo'yilgan
[ ] Belgilar ro'yxati va tartibi qayd etilgan
[ ] Test to'plamidagi metrika hujjatlashtirilgan
[ ] Kechikish va hajm SLA ga mos
[ ] Yo'qolgan qiymatlar/yangi kategoriyalar sinalgan
[ ] Chegara (threshold) tanlangan va asoslangan
[ ] Kalibrlash tekshirilgan (kerak bo'lsa)
[ ] Orqaga qaytarish (rollback) rejasi bor
CHIQARGANDAN KEYIN:
[ ] Kirish taqsimoti kuzatilmoqda (PSI)
[ ] Bashorat taqsimoti kuzatilmoqda
[ ] Yorliqlar kelganda metrika hisoblanmoqda
[ ] Ogohlantirish chegaralari o'rnatilgan Yangi kategoriya va yo'qolgan qiymat — ishlab chiqarishdagi eng tez-tez uchraydigan nosozlik sababi. OneHotEncoder(handle_unknown="ignore") va imputer ni oldindan sinab ko'ring.
2.7. Tuzoqlar
Asosiy tuzoqlar: faqat modelni saqlash (Pipeline emas); versiyalarni yozmaslik; belgilar tartibini tekshirmaslik; bitta so'rovda n_jobs=-1; drift ni faqat yorliq kelganda ko'rish; yangi modelni eski bilan solishtirmaslik; chegarani ishlab chiqarishda o'zgartirib, hujjatlashtirmaslik; model hajmini oxirida tekshirish (kech).
2.8. Ishlab chiqarish — ishning yarmi
Modelni butun Pipeline sifatida saqlang (joblib.dump(quvur, ..., compress=3)) va versiyalarni yozib qo'ying. Hajm va kechikishni SLA ga moslang: min_samples_leaf oshirish eng samarali usul. Kirish driftini PSI bilan yorliqsiz kuzating, tushuncha driftini esa yorliq kelganda. Qayta o'qitishni jadval + drift signali bo'yicha qiling va yangi modelni eski bilan albatta taqqoslang. Keyingi dars — amaliyot.
3. Tez ma'lumotnoma
import joblib
import sklearn
joblib.dump({"quvur": quvur, "versiyalar": {"sklearn": sklearn.__version__},
"belgilar": list(X.columns), "sana": "2026-09-20",
"metrika": {"test_auc": 0.842}}, "model.joblib", compress=3)
paket = joblib.load("model.joblib")
assert list(Xyangi.columns) == paket["belgilar"] # tartibni tekshiring
# PSI
def psi(eski, yangi, savatlar=10):
kes = np.quantile(eski, np.linspace(0, 1, savatlar + 1))
kes[0], kes[-1] = -np.inf, np.inf
a = np.histogram(eski, kes)[0] / len(eski)
b = np.histogram(yangi, kes)[0] / len(yangi)
a, b = np.clip(a, 1e-6, None), np.clip(b, 1e-6, None)
return float(((b - a) * np.log(b / a)).sum())
QOIDA: Pipeline ni saqla · versiyani yoz · belgilar tartibini tekshir ·
PSI ni kuzatIshlab chiqarish xulosasi
joblib.dump(butun Pipeline, compress=3) + metama'lumot
Hajm: min_samples_leaf > n_estimators > max_depth
Kechikish: bitta so'rovda n_jobs=1
PSI < 0.1 barqaror, > 0.25 jiddiy siljish
Qayta o'qitish: jadval + drift; yangi modelni eski bilan taqqosla4. Batafsil misollar
Misollar real numpy/pandas/sklearn bilan (Python 3.14).
Misol 1 — Hajm va kechikish
"""Model hajmini va bashorat narxini o'lchash (real numpy/sklearn/joblib)."""
import io
import pickle
import numpy as np
from sklearn.ensemble import (HistGradientBoostingClassifier,
RandomForestClassifier)
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import train_test_split
def yarat(seed: int = 4, n: int = 8000, p: int = 12, shovqin: float = 0.12):
rng = np.random.default_rng(seed)
X = rng.normal(0, 1, (n, p))
qoida = (((X[:, 0] > 0.25) & (X[:, 1] < 0.25))
| ((X[:, 2] > 0.4) & (X[:, 3] > 0.0))
| (X[:, 4] < -1.0))
y = qoida.astype(int)
alm = rng.random(n) < shovqin
y[alm] = 1 - y[alm]
return X, y
def hajm_kb(model) -> float:
"""Serializatsiya qilingan model hajmi."""
bufer = io.BytesIO()
pickle.dump(model, bufer, protocol=pickle.HIGHEST_PROTOCOL)
return bufer.tell() / 1024
def main() -> None:
X, y = yarat()
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.3, random_state=0,
stratify=y)
print("=== 1. n_estimators va hajm ===")
print(f" {'n_est':>7} {'hajm KB':>10} {'tugunlar':>11} {'test AUC':>10}")
for ne in [50, 100, 200, 400]:
o = RandomForestClassifier(n_estimators=ne, random_state=0,
n_jobs=1).fit(Xtr, ytr)
tugunlar = sum(e.tree_.node_count for e in o.estimators_)
print(f" {ne:>7} {hajm_kb(o):>10.0f} {tugunlar:>11,} "
f"{roc_auc_score(yte, o.predict_proba(Xte)[:, 1]):>10.4f}")
print("\n=== 2. min_samples_leaf va hajm ===")
print(f" {'msl':>5} {'hajm KB':>10} {'tugunlar':>11} {'test AUC':>10}")
for msl in [1, 5, 20, 50, 200]:
o = RandomForestClassifier(n_estimators=150, min_samples_leaf=msl,
random_state=0, n_jobs=1).fit(Xtr, ytr)
tugunlar = sum(e.tree_.node_count for e in o.estimators_)
print(f" {msl:>5} {hajm_kb(o):>10.0f} {tugunlar:>11,} "
f"{roc_auc_score(yte, o.predict_proba(Xte)[:, 1]):>10.4f}")
print("\n=== 3. RF va boosting hajmi ===")
rf = RandomForestClassifier(n_estimators=150, min_samples_leaf=20,
random_state=0, n_jobs=1).fit(Xtr, ytr)
gb = HistGradientBoostingClassifier(learning_rate=0.1, max_iter=250,
early_stopping=True,
validation_fraction=0.15,
n_iter_no_change=20,
random_state=0).fit(Xtr, ytr)
print(f" RF(150, msl=20): {hajm_kb(rf):>8.0f} KB, AUC "
f"{roc_auc_score(yte, rf.predict_proba(Xte)[:, 1]):.4f}")
print(f" HistGB({gb.n_iter_} qadam): {hajm_kb(gb):>8.0f} KB, AUC "
f"{roc_auc_score(yte, gb.predict_proba(Xte)[:, 1]):.4f}")
print("\n=== 4. Bashorat hisoblash hajmi ===")
print(" (bitta bashorat uchun ko'rib chiqiladigan tugunlar tartibi)")
toliq = RandomForestClassifier(n_estimators=150, random_state=0,
n_jobs=1).fit(Xtr, ytr)
for nom, model in [("RF(150, msl=1)", toliq),
("RF(150, msl=20)", rf)]:
chuqurliklar = [e.get_depth() for e in model.estimators_]
print(f" {nom:<18}: {len(model.estimators_)} daraxt x "
f"o'rtacha chuqurlik {np.mean(chuqurliklar):.1f} = "
f"~{len(model.estimators_) * np.mean(chuqurliklar):.0f} qadam")
print(f" {'HistGB':<18}: {gb.n_iter_} daraxt x max_leaf_nodes "
f"{gb.max_leaf_nodes} -> ancha kam")
print(" ⭐ min_samples_leaf - hajmni kamaytirishning eng samarali yo'li")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. n_estimators va hajm ===
n_est hajm KB tugunlar test AUC
50 4898 62,498 0.8747
100 9776 124,758 0.8731
200 19454 248,272 0.8735
400 39037 498,206 0.8724
=== 2. min_samples_leaf va hajm ===
msl hajm KB tugunlar test AUC
1 14646 186,904 0.8733
5 7848 99,904 0.8726
20 3063 38,664 0.8730
50 1309 16,212 0.8745
200 345 3,880 0.8779
=== 3. RF va boosting hajmi ===
RF(150, msl=20): 3063 KB, AUC 0.8730
HistGB(47 qadam): 189 KB, AUC 0.8712
=== 4. Bashorat hisoblash hajmi ===
(bitta bashorat uchun ko'rib chiqiladigan tugunlar tartibi)
RF(150, msl=1) : 150 daraxt x o'rtacha chuqurlik 25.8 = ~3877 qadam
RF(150, msl=20) : 150 daraxt x o'rtacha chuqurlik 14.7 = ~2210 qadam
HistGB : 47 daraxt x max_leaf_nodes 31 -> ancha kam
⭐ min_samples_leaf - hajmni kamaytirishning eng samarali yo'liNima ko'rsatdi: 2.2-bo'lim.
Misol 2 — Pipeline ni saqlash
"""Butun quvurni metama'lumot bilan saqlash (real pandas/sklearn/joblib)."""
import os
import tempfile
import joblib
import numpy as np
import pandas as pd
import sklearn
from sklearn.compose import ColumnTransformer
from sklearn.ensemble import RandomForestClassifier
from sklearn.impute import SimpleImputer
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder
def yarat(seed: int = 6, n: int = 8000) -> pd.DataFrame:
rng = np.random.default_rng(seed)
hudud = rng.choice(["toshkent", "samarqand", "buxoro"], n,
p=[0.5, 0.3, 0.2])
tur = rng.choice(["oddiy", "tezkor"], n, p=[0.7, 0.3])
masofa = rng.gamma(3, 60, n)
ogirlik = rng.gamma(2, 4, n)
hq = pd.Series(hudud).map({"toshkent": 0.0, "samarqand": 0.5,
"buxoro": 0.9}).to_numpy()
kuch = -2.5 + 0.006 * masofa + 0.05 * ogirlik + hq
y = (rng.random(n) < 1 / (1 + np.exp(-kuch))).astype(int)
df = pd.DataFrame({"hudud": hudud, "tur": tur, "masofa": masofa,
"ogirlik": ogirlik, "kechikdi": y})
df.loc[rng.random(n) < 0.04, "ogirlik"] = np.nan
return df
def main() -> None:
df = yarat()
X = df.drop(columns="kechikdi")
y = df["kechikdi"]
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.3, random_state=0,
stratify=y)
kategoriya = ["hudud", "tur"]
sonli = ["masofa", "ogirlik"]
quvur = Pipeline([
("tayyor", ColumnTransformer([
("k", OneHotEncoder(handle_unknown="ignore"), kategoriya),
("s", SimpleImputer(strategy="median"), sonli)])),
("model", RandomForestClassifier(n_estimators=200, min_samples_leaf=10,
random_state=0, n_jobs=1))])
quvur.fit(Xtr, ytr)
auc = roc_auc_score(yte, quvur.predict_proba(Xte)[:, 1])
print("=== 1. Quvur o'qitildi ===")
print(f" bosqichlar: {[nom for nom, _ in quvur.steps]}")
print(f" test ROC AUC: {auc:.4f}")
print("\n=== 2. Metama'lumot bilan saqlash ===")
paket = {
"quvur": quvur,
"belgilar": list(X.columns),
"versiyalar": {"sklearn": sklearn.__version__,
"numpy": np.__version__,
"pandas": pd.__version__},
"metrika": {"test_roc_auc": round(float(auc), 4),
"o_quv_hajmi": len(Xtr)},
"chegara": 0.5,
}
jild = tempfile.mkdtemp()
yol = os.path.join(jild, "model.joblib")
joblib.dump(paket, yol, compress=3)
print(f" fayl hajmi: {os.path.getsize(yol) / 1024:.0f} KB")
yol2 = os.path.join(jild, "model_siqilmagan.joblib")
joblib.dump(paket, yol2)
print(f" siqilmagan: {os.path.getsize(yol2) / 1024:.0f} KB")
print(f" siqish nisbati: "
f"{os.path.getsize(yol2) / os.path.getsize(yol):.1f}x")
print("\n=== 3. Yuklash va tekshirish ===")
yuklangan = joblib.load(yol)
print(f" belgilar: {yuklangan['belgilar']}")
print(f" versiyalar: {yuklangan['versiyalar']}")
print(f" saqlangan metrika: {yuklangan['metrika']}")
q2 = yuklangan["quvur"]
yangi_auc = roc_auc_score(yte, q2.predict_proba(Xte)[:, 1])
print(f" yuklangandan keyin AUC: {yangi_auc:.4f} "
f"(farq {abs(yangi_auc - auc):.6f})")
print("\n=== 4. Ishlab chiqarishdagi qiyin holatlar ===")
# yangi kategoriya
yangi = Xte.iloc[:3].copy()
yangi.loc[:, "hudud"] = "andijon" # o'quvda yo'q edi
print(f" yangi kategoriya: bashorat "
f"{q2.predict_proba(yangi)[:, 1].round(4).tolist()}")
# yo'qolgan qiymat
yoq = Xte.iloc[:3].copy()
yoq.loc[:, "ogirlik"] = np.nan
print(f" NaN ogirlik: bashorat "
f"{q2.predict_proba(yoq)[:, 1].round(4).tolist()}")
# belgilar tartibi o'zgargan
teskari = Xte.iloc[:3][list(reversed(list(X.columns)))]
print(f" teskari tartib: bashorat "
f"{q2.predict_proba(teskari)[:, 1].round(4).tolist()}")
print(f" (ColumnTransformer nom bo'yicha ishlaydi - tartib muhim emas)")
print(" ⭐ Butun Pipeline saqlansa, bu holatlar boshqariladi")
for f in [yol, yol2]:
os.remove(f)
os.rmdir(jild)
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Quvur o'qitildi ===
bosqichlar: ['tayyor', 'model']
test ROC AUC: 0.6441
=== 2. Metama'lumot bilan saqlash ===
fayl hajmi: 2525 KB
siqilmagan: 8449 KB
siqish nisbati: 3.3x
=== 3. Yuklash va tekshirish ===
belgilar: ['hudud', 'tur', 'masofa', 'ogirlik']
versiyalar: {'sklearn': '1.9.1', 'numpy': '2.5.3', 'pandas': '3.0.6'}
saqlangan metrika: {'test_roc_auc': 0.6441, 'o_quv_hajmi': 5600}
yuklangandan keyin AUC: 0.6441 (farq 0.000000)
=== 4. Ishlab chiqarishdagi qiyin holatlar ===
yangi kategoriya: bashorat [0.3078, 0.3143, 0.5927]
NaN ogirlik: bashorat [0.2621, 0.3326, 0.2462]
teskari tartib: bashorat [0.4844, 0.395, 0.5653]
(ColumnTransformer nom bo'yicha ishlaydi - tartib muhim emas)
⭐ Butun Pipeline saqlansa, bu holatlar boshqariladiNima ko'rsatdi: 2.1, 2.3-bo'limlar.
Misol 3 — Drift ni aniqlash
"""PSI va bashorat taqsimoti kuzatuvi (real numpy/sklearn)."""
import numpy as np
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import train_test_split
def yarat(seed: int, n: int = 4000, siljish: float = 0.0,
tushuncha: float = 0.0):
"""siljish - kirish drifti; tushuncha - bog'liqlik drifti."""
rng = np.random.default_rng(seed)
X = rng.normal(0, 1, (n, 8))
X[:, 0] += siljish
kuch = ((1.4 - tushuncha) * X[:, 0] - 1.0 * X[:, 1]
+ (0.3 + tushuncha) * X[:, 2] + 0.8 * (X[:, 3] > 0.5))
y = (rng.random(n) < 1 / (1 + np.exp(-kuch))).astype(int)
return X, y
def psi(eski, yangi, savatlar: int = 10) -> float:
kes = np.quantile(eski, np.linspace(0, 1, savatlar + 1))
kes[0], kes[-1] = -np.inf, np.inf
a = np.histogram(eski, kes)[0] / len(eski)
b = np.histogram(yangi, kes)[0] / len(yangi)
a = np.clip(a, 1e-6, None)
b = np.clip(b, 1e-6, None)
return float(((b - a) * np.log(b / a)).sum())
def main() -> None:
X, y = yarat(1)
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.3, random_state=0,
stratify=y)
model = HistGradientBoostingClassifier(learning_rate=0.1, max_iter=300,
early_stopping=True,
validation_fraction=0.15,
n_iter_no_change=20,
random_state=0).fit(Xtr, ytr)
asosiy = roc_auc_score(yte, model.predict_proba(Xte)[:, 1])
asosiy_p = model.predict_proba(Xte)[:, 1]
print("=== 1. Bazaviy model ===")
print(f" test AUC: {asosiy:.4f}, o'rtacha bashorat "
f"{asosiy_p.mean():.4f}")
print("\n=== 2. Kirish drifti (covariate shift) ===")
print(f" {'siljish':>8} {'PSI(x0)':>9} {'PSI(bashorat)':>15} "
f"{'AUC':>8} {'o_rt p':>8}")
for s in [0.0, 0.25, 0.5, 1.0, 2.0]:
Xn, yn = yarat(50, siljish=s)
p = model.predict_proba(Xn)[:, 1]
print(f" {s:>8.2f} {psi(Xte[:, 0], Xn[:, 0]):>9.4f} "
f"{psi(asosiy_p, p):>15.4f} "
f"{roc_auc_score(yn, p):>8.4f} {p.mean():>8.4f}")
print("\n=== 3. Tushuncha drifti (concept shift) ===")
print(f" {'daraja':>8} {'PSI(x0)':>9} {'PSI(bashorat)':>15} "
f"{'AUC':>8}")
for t in [0.0, 0.4, 0.8, 1.2]:
Xn, yn = yarat(50, tushuncha=t)
p = model.predict_proba(Xn)[:, 1]
print(f" {t:>8.2f} {psi(Xte[:, 0], Xn[:, 0]):>9.4f} "
f"{psi(asosiy_p, p):>15.4f} {roc_auc_score(yn, p):>8.4f}")
print(" (kirish o'zgarmadi, lekin sifat tushdi - PSI ni ko'rmaydi)")
print("\n=== 4. Barcha belgilar bo'yicha PSI ===")
Xn, yn = yarat(50, siljish=0.8)
print(f" {'belgi':>8} {'PSI':>9} {'holat':<20}")
for i in range(X.shape[1]):
v = psi(Xte[:, i], Xn[:, i])
holat = ("barqaror" if v < 0.1
else "kuzatuv kerak" if v < 0.25 else "JIDDIY SILJISH")
print(f" x{i:<7} {v:>9.4f} {holat:<20}")
print(" ⭐ Kirish drifti yorliqsiz aniqlanadi, tushuncha drifti - yo'q")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Bazaviy model ===
test AUC: 0.8144, o'rtacha bashorat 0.5479
=== 2. Kirish drifti (covariate shift) ===
siljish PSI(x0) PSI(bashorat) AUC o_rt p
0.00 0.0133 0.0068 0.8167 0.5627
0.25 0.0924 0.0633 0.8192 0.6129
0.50 0.3024 0.1800 0.8217 0.6604
1.00 1.0555 0.6127 0.8139 0.7510
2.00 3.5096 2.0615 0.8198 0.8721
=== 3. Tushuncha drifti (concept shift) ===
daraja PSI(x0) PSI(bashorat) AUC
0.00 0.0133 0.0068 0.8167
0.40 0.0133 0.0068 0.7914
0.80 0.0133 0.0068 0.7421
1.20 0.0133 0.0068 0.6801
(kirish o'zgarmadi, lekin sifat tushdi - PSI ni ko'rmaydi)
=== 4. Barcha belgilar bo'yicha PSI ===
belgi PSI holat
x0 0.7015 JIDDIY SILJISH
x1 0.0050 barqaror
x2 0.0189 barqaror
x3 0.0070 barqaror
x4 0.0082 barqaror
x5 0.0078 barqaror
x6 0.0057 barqaror
x7 0.0140 barqaror
⭐ Kirish drifti yorliqsiz aniqlanadi, tushuncha drifti - yo'qNima ko'rsatdi: 2.4-bo'lim.
Misol 4 — Qayta o'qitish siyosati
"""Jadval, drift va taqqoslash (real numpy/sklearn)."""
import numpy as np
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.metrics import roc_auc_score
def davr(nomer: int, n: int = 2500):
"""Vaqt o'tishi bilan sekin o'zgaradigan jarayon."""
rng = np.random.default_rng(100 + nomer)
X = rng.normal(0, 1, (n, 8))
X[:, 0] += 0.12 * nomer # sekin siljish
w0 = 1.4 - 0.05 * nomer # bog'liqlik ham o'zgaradi
w2 = 0.3 + 0.05 * nomer
kuch = w0 * X[:, 0] - 1.0 * X[:, 1] + w2 * X[:, 2] + 0.8 * (X[:, 3] > 0.5)
y = (rng.random(n) < 1 / (1 + np.exp(-kuch))).astype(int)
return X, y
def oqit(X, y):
return HistGradientBoostingClassifier(learning_rate=0.1, max_iter=150,
early_stopping=True,
validation_fraction=0.15,
n_iter_no_change=20,
random_state=0).fit(X, y)
def main() -> None:
X0, y0 = davr(0)
model = oqit(X0, y0)
print("=== 1. Hech qachon qayta o'qitmaslik ===")
print(f" {'davr':>5} {'AUC':>8}")
hech = []
for d in range(1, 9):
Xd, yd = davr(d)
a = roc_auc_score(yd, model.predict_proba(Xd)[:, 1])
hech.append(a)
if d in [1, 3, 5, 8]:
print(f" {d:>5} {a:>8.4f}")
print("\n=== 2. Har davr qayta o'qitish ===")
m = oqit(X0, y0)
har = []
for d in range(1, 9):
Xd, yd = davr(d)
a = roc_auc_score(yd, m.predict_proba(Xd)[:, 1])
har.append(a)
m = oqit(Xd, yd) # keyingi davr uchun yangilash
print(f" o'rtacha AUC: {np.mean(har):.4f} "
f"(qayta o'qitishsiz {np.mean(hech):.4f})")
print(f" oxirgi davr: {har[-1]:.4f} (qayta o'qitishsiz {hech[-1]:.4f})")
print("\n=== 3. Drift signali bo'yicha ===")
def psi(eski, yangi, savatlar: int = 10) -> float:
kes = np.quantile(eski, np.linspace(0, 1, savatlar + 1))
kes[0], kes[-1] = -np.inf, np.inf
a = np.clip(np.histogram(eski, kes)[0] / len(eski), 1e-6, None)
b = np.clip(np.histogram(yangi, kes)[0] / len(yangi), 1e-6, None)
return float(((b - a) * np.log(b / a)).sum())
m = oqit(X0, y0)
asos_X = X0
signal, oqitishlar = [], 0
for d in range(1, 9):
Xd, yd = davr(d)
a = roc_auc_score(yd, m.predict_proba(Xd)[:, 1])
signal.append(a)
eng_psi = max(psi(asos_X[:, i], Xd[:, i]) for i in range(Xd.shape[1]))
if eng_psi > 0.25:
m = oqit(Xd, yd)
asos_X = Xd
oqitishlar += 1
print(f" qayta o'qitishlar soni: {oqitishlar} (8 davrda)")
print(f" o'rtacha AUC: {np.mean(signal):.4f}")
print(f" oxirgi davr: {signal[-1]:.4f}")
print("\n=== 4. Yangi modelni eski bilan taqqoslash ===")
Xs, ys = davr(5)
eski = oqit(X0, y0)
# "buzilgan" yangi ma'lumot: yorliqlarning 40% i tasodifiy
rng = np.random.default_rng(0)
yb = ys.copy()
buzuq = rng.random(len(yb)) < 0.4
yb[buzuq] = rng.integers(0, 2, buzuq.sum())
yangi = oqit(Xs, yb)
Xv, yv = davr(6)
a_eski = roc_auc_score(yv, eski.predict_proba(Xv)[:, 1])
a_yangi = roc_auc_score(yv, yangi.predict_proba(Xv)[:, 1])
print(f" eski model: {a_eski:.4f}")
print(f" yangi model: {a_yangi:.4f}")
print(f" qaror: {'YANGI chiqariladi' if a_yangi > a_eski else 'ESKI qoladi'}")
print(" ⭐ Har qayta o'qitishdan keyin taqqoslash majburiy")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Hech qachon qayta o'qitmaslik ===
davr AUC
1 0.8030
3 0.8052
5 0.7986
8 0.7631
=== 2. Har davr qayta o'qitish ===
o'rtacha AUC: 0.8059 (qayta o'qitishsiz 0.7929)
oxirgi davr: 0.7851 (qayta o'qitishsiz 0.7631)
=== 3. Drift signali bo'yicha ===
qayta o'qitishlar soni: 1 (8 davrda)
o'rtacha AUC: 0.8039
oxirgi davr: 0.8009
=== 4. Yangi modelni eski bilan taqqoslash ===
eski model: 0.7993
yangi model: 0.7528
qaror: ESKI qoladi
⭐ Har qayta o'qitishdan keyin taqqoslash majburiyNima ko'rsatdi: 2.5-bo'lim.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
| "Modelni saqlash yetarli" | Butun Pipeline |
| "joblib versiyaga bog'liq emas" | Juda bog'liq |
| "n_jobs=-1 har doim tezroq" | Bitta so'rovda emas |
| "PSI barcha driftni ko'radi" | Faqat kirish driftini |
| "Yangi model yaxshiroq" | Taqqoslash kerak |
| "Hajm muhim emas" | SLA va xarajat |
| "RF va GB hajmi o'xshash" | GB ancha kichik |
| "Drift sekin keladi" | Birdan ham bo'ladi |
6. Keng tarqalgan xatolar va yechimlari
1. Faqat modelni saqlash
joblib.dump(model, "model.joblib") # scaler qayerda? # ⚠️
joblib.dump(quvur, "quvur.joblib") # butun Pipeline # ✅2. Versiyalarni yozmaslik
joblib.dump(quvur, "model.joblib") # ⚠️
joblib.dump({"quvur": quvur, "versiyalar": {...}}, "model.joblib") # ✅3. Bitta so'rovda n_jobs=-1
model.set_params(n_jobs=-1); model.predict(x_bitta) # ⚠️
model.set_params(n_jobs=1); model.predict(x_bitta) # ✅4. Belgilar tartibini tekshirmaslik
model.predict(yangi_df.values) # ⚠️
assert list(yangi_df.columns) == paket["belgilar"] # ✅5. Faqat yorliq bilan kuzatuv
# metrika yorliq kelguncha hisoblanmaydi (12 oy) # ⚠️
# PSI va bashorat taqsimotini darhol kuzating # ✅6. Taqqoslashsiz almashtirish
yangi = oqit(yangi_malumot); chiqar(yangi) # ⚠️
if baho(yangi) > baho(eski): chiqar(yangi) # ✅7. Hajmni oxirida tekshirish
# sozlash tugagach 400 MB ekanini bilib qolish # ⚠️
# SLA ni sozlash setkasiga cheklov sifatida qo'ying # ✅7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 15.5-dars (o'tilgan): Random Forest
- 15.10-dars (o'tilgan): XGBoost va LightGBM
- 12.9-dars (o'tilgan): Leakage va Pipeline
- 14.10-dars (o'tilgan): Kalibrlash
- 15.14-dars: Amaliyot
8. Eng yaxshi amaliyotlar
Butun Pipeline ni saqlang.
Metama'lumot qo'shing.
Belgilar tartibini tekshiring.
Hajm va kechikishni o'lchang.
PSI ni kuzating.
Jadval + drift siyosati.
Har doim taqqoslang.
Rollback rejasi tayyorlang.
9. Amaliy topshiriq
Vazifa 1: Bashorat qiling
1. # nima saqlanadi?
2. # metama'lumotda nima bo'lsin?
3. # hajmni kamaytirishning eng samarali yo'li?
4. # bitta so'rovda n_jobs?
5. # RF va GB hajmi?
6. # kirish drifti nima?
7. # tushuncha drifti?
8. # PSI chegaralari?
9. # PSI qaysi driftni ko'radi?
10. # qayta o'qitish siyosatlari?
11. # yangi model chiqarilsinmi?
12. # yangi kategoriya uchun?Javoblar
- Butun Pipeline
- Versiyalar, belgilar, metrika
- min_samples_leaf
- 1
- GB ancha kichik
- P(X) o'zgarishi
- P(y|X) o'zgarishi
- 0.1 / 0.25
- Faqat kirish
- Jadval, drift, online
- Faqat taqqoslashdan keyin
- handle_unknown="ignore"
Vazifa 2: Xatolarni tuzating
1. joblib.dump(model, "model.joblib") # scaler alohida
2. joblib.dump(quvur, "model.joblib") # versiya yo'q
3. model.set_params(n_jobs=-1); model.predict(bitta_qator)
4. model.predict(yangi_df.values)
5. yangi = oqit(yangi_malumot); chiqar(yangi)Javoblar
1. joblib.dump(quvur, "quvur.joblib")
2. joblib.dump({"quvur": quvur, "versiyalar": {...}}, "model.joblib")
3. model.set_params(n_jobs=1); model.predict(bitta_qator)
4. assert list(yangi_df.columns) == paket["belgilar"]
5. if baho(yangi) > baho(eski): chiqar(yangi)Vazifa 3: Hajm
Modellang:
- n_estimators
- min_samples_leaf
- RF va GB
- Hisoblash hajmi
Vazifa 4: Saqlash
Modellang:
- Quvur
- Metama'lumot
- Yuklash
- Qiyin holatlar
Vazifa 5: Drift
Modellang:
- Bazaviy
- Kirish drifti
- Tushuncha drifti
- Barcha belgilar
Vazifa 6: Qayta o'qitish
Modellang:
- Hech qachon
- Har davr
- Drift bo'yicha
- Taqqoslash
Vazifa 7: O'ylash
Model ishlab chiqarishda 6 oy muvaffaqiyatli ishladi, keyin metrikalar tusha boshladi. PSI barqaror. Nima bo'lgan va nima qilish kerak?
Javob
Qisqa javob: PSI barqaror bo'lsa-yu metrika tushsa, bu tushuncha drifti (P(y|X) o'zgargan) yoki o'lchov muammosiga ishora qiladi. Birinchi qadam — sabab aniqlash, qayta o'qitish emas.
1. Mumkin sabablar
| Sabab | Tekshiruv |
|---|---|
| Tushuncha drifti | Yangi ma'lumotda qayta o'qitib, sifatni solishtirish |
| Yorliqlar kechikishi | Yorliq yig'ish jarayonini tekshirish |
| Yangi segment | Segmentlar bo'yicha metrikani bo'lib ko'rish |
| Quvurdagi buzilish | Kirish qiymatlarining oraliqlarini tekshirish |
| Fikr-mulohaza sikli | Model qarorlari ma'lumotga ta'sir qilyaptimi? |
| Metrika ta'rifi o'zgardi | Hisoblash kodini solishtirish |
2. Fikr-mulohaza sikli (feedback loop)
Eng nozik sabab: model o'zi kuzatayotgan ma'lumotni o'zgartiradi.
- Model "xavfli" degan arizalar rad etiladi -> ularning natijasi ma'lum bo'lmaydi
- O'quv ma'lumoti faqat tasdiqlanganlardan iborat bo'lib qoladi
- Model asta-sekin tor taqsimotga moslashadi
Yechim: tasodifiy nazorat guruhi (kichik ulushda modelni chetlab o'tish).
3. Segment tahlili
- Metrikani hudud, kanal, mijoz turi bo'yicha bo'ling
- Ko'pincha muammo bitta segmentda bo'ladi
- Umumiy PSI uni yashiradi (segment ulushi kichik)
- Segment bo'yicha PSI ni ham hisoblang
4. Amaliy tartib
- Metrika hisoblash kodini tekshiring (eng arzon)
- Segmentlarga bo'ling
- Yangi ma'lumotda qayta o'qitib, eski bilan solishtiring
- Yaxshilansa — tushuncha drifti tasdiqlandi
- Yaxshilanmasa — muammo ma'lumot quvurida yoki metrikada
5. Xulosa
- PSI faqat kirishni ko'radi
- Tushuncha drifti yorliq talab qiladi
- Avval sabab, keyin qayta o'qitish
- Segment tahlili ko'pincha javobni beradi
Nimani mustahkamlaydi: 2.4, 2.5-bo'limlar.
Xulosa
Bu darsda ansambllarni ishlab chiqarishga chiqarishni o'rgandik.
Eng muhim uch fikr:
Butun Pipeline ni metama'lumot bilan saqlang.
joblib.dump({"quvur": ..., "versiyalar": ..., "belgilar": ..., "metrika": ...}, ..., compress=3). Faqat modelni saqlash — keng tarqalgan xato: tayyorlash bosqichi alohida qolib ketadi.joblibversiyaga bog'liq, shuning uchun sklearn/numpy/Python versiyalarini albatta yozib qo'ying.Hajm va kechikish — SLA cheklovi. Random Forest yuzlab megabayt bo'lishi mumkin. Kamaytirishning eng samarali yo'li —
min_samples_leafni oshirish (sifat deyarli tushmaydi), keyinn_estimators. Gradient boosting odatda ancha kichik. Bitta so'rovdan_jobs=1qo'ying — parallellashtirish ustama xarajati foydadan katta.Drift ni yorliqsiz kuzating. PSI kirish taqsimoti siljishini darhol ko'rsatadi (< 0.1 barqaror, > 0.25 jiddiy), lekin tushuncha driftini (
P(y|X)o'zgarishi) ko'rmaydi — uning uchun haqiqiy yorliq kerak. Qayta o'qitishni jadval + drift signali bo'yicha qiling va yangi modelni eski bilan bir xil to'plamda albatta taqqoslang.
Keyingi darsda amaliyot: butun 15-qism bo'yicha to'liq loyiha.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!