Mundarija (23)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Reproduksiya darajalari
- 2.2. Nega natija qaytarilmaydi: olti manba
- 2.3. Urug'lar ierarxiyasi
- 2.4. PYTHONHASHSEED va hash() tuzog'i
- 2.5. Muhitni qayd qilish
- 2.6. Reproduksiya manifesti va uni tekshirish
- 2.7. Urug'ga sezgirlik
- 2.8. Muhit boshqaruvchilari
- 2.9. Tuzoqlar
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Reproduksiya testi: bir xil skript, ikki yangi jarayon
- Misol 2 — Urug'lar ierarxiyasi va PYTHONHASHSEED
- Misol 3 — Reproduksiya manifesti: yaratish, qayta ishga tushirish, buzilishni ushlash
- Misol 4 — Urug'ga sezgirlik: takrorlanadigan natija ham bitta namuna
- 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.2-dars: Reproduksiya va muhit
27-QISM — MLOPS VA DEPLOY · 2-dars
1. Kirish va motivatsiya
27.1-darsda ML hayot siklining har bosqichi iz qoldirishi kerakligini ko'rdik: kichik quvurimiz takroriy yurishda aynan bir xil xeshlar berdi. Lekin bu o'z-o'zidan bo'lmadi — urug'lar qo'yilgan, ma'lumot generatsiyasi deterministik edi va hammasi bitta jarayonda, bitta mashinada ishladi. Haqiqiy hayotda model boshqa kuni, boshqa kompyuterda, boshqa kutubxona versiyasi bilan, ba'zan boshqa odam tomonidan qayta o'qitiladi. Shu savolga javob beramiz: natijani istalgan vaqtda qayta olish uchun nimani qayd qilish kerak?
19.7-darsda sklearn doirasida takrorlanuvchanlikni (random_state shakllari, oqimlar, versiyalarni qulflash), 21.11-darsda esa PyTorch loyihasida seed_everything va konfigni ko'rgan edik. Bu dars ularni tizim darajasiga ko'taradi: bir necha kutubxonaning urug'lari qanday ierarxiya hosil qiladi, interpretator darajasidagi PYTHONHASHSEED qanday qilib "urug' qo'yilgan" skriptni ham buzadi, muhit, ma'lumot va konfigni qanday qilib bitta reproduksiya manifesti ga yig'ish mumkin — va nihoyat, aynan takrorlangan natija ham nega ishonchli natija emas.
Real vaziyat. Jamoa auditorga modelning AUC 0.758 ekanini ko'rsatishi kerak edi. Kodni qayta ishga tushirishdi — 0.741. Yana — 0.749. Urug'lar qo'yilgan, ma'lumot o'zgarmagan, kod o'sha commit dan. Ikki kunlik qidiruvdan keyin topildi: kategoriyalarni kodlash list(set(...)) bilan qilingan va Python har yangi jarayonda satr xeshlarini tasodifiy urug' bilan hisoblaydi — ya'ni "Toshkent" bir ishga tushishda 0, boshqasida 6 bo'ladi. Bu darsning 1-misolida xuddi shu vaziyatni takrorlaymiz va o'lchaymiz.
Bu darsda natijani qayta olish uchun nimani nazorat qilish va nimani qayd qilish kerakligini — va buni avtomatik tekshirishni o'rganamiz.
Bu darsda:
- Reproduksiya darajalari
- Nega natija qaytarilmaydi: olti manba
- Urug'lar ierarxiyasi
PYTHONHASHSEEDvahash()tuzog'i- Muhitni qayd qilish: lock, platforma, ma'lumot va konfig xeshi
- Reproduksiya manifesti va uni tekshirish
- Urug'ga sezgirlik: takrorlanadigan natija ham bitta namuna
- Muhit boshqaruvchilari: venv, conda, uv, poetry, Docker
- Amaliy: jarayonlararo reproduksiya testi, manifest va urug'lar bo'yicha tarqoqlik
ℹ Misollar real numpy/sklearn/torch bilan (Python 3.14). Yangi jarayonlar
subprocessbilan ishga tushiriladi; fayllar faqat vaqtinchalik papkada.
2. Nazariya — chuqur tushuntirish
2.1. Reproduksiya darajalari
Adabiyotda atamalar turlicha ishlatiladi. Bu qismda quyidagi uch darajani farqlaymiz:
1. TAKRORLANUVCHANLIK (bit darajasida)
bir xil kod + bir xil ma'lumot + bir xil muhit + bir xil urug'
-> AYNAN bir xil bashoratlar (xeshlari teng)
kim uchun: debug, audit, regressiya testlari, CI
2. REPRODUKSIYA (statistik)
bir xil kod + bir xil ma'lumot, lekin boshqa mashina/versiya/urug'
-> metrika ishonch oralig'i ichida, XULOSA o'zgarmaydi
kim uchun: hisobot, maqola, qaror
3. REPLIKATSIYA (umumlashish)
yangi ma'lumot (keyingi oy, boshqa hudud)
-> xulosa hali ham to'g'rimi?
kim uchun: ishlab chiqarish, monitoring 27.11-bob
MUHIM:
1-daraja 2-darajaning o'rnini BOSMAYDI:
aynan takrorlanadigan natija ham bitta tasodifiy namuna bo'lishi mumkin
2-daraja 1-darajasiz QIYIN:
natija o'zgarganda sababni topish uchun avval uni takrorlash kerakBit darajasidagi takrorlash — debug va audit uchun; xulosaning ishonchliligi — bir necha urug' va tarqoqlik uchun. Ikkalasi ham kerak.
2.2. Nega natija qaytarilmaydi: olti manba
1. URUG'LAR
o'rnatilmagan yoki faqat bittasi o'rnatilgan (python, lekin numpy emas)
random_state=None -> global holatga bog'liq 19.7-bob
2. TARTIB
set / dict tartibi (satrlar uchun PYTHONHASHSEED ga bog'liq)
fayl ro'yxati tartibi (os.listdir, glob - fayl tizimiga bog'liq)
SQL so'rov natijasi (ORDER BY siz tartib kafolatlanmagan)
qatorlar tartibi -> SGD, bootstrap, bo'lish natijasi o'zgaradi
3. MA'LUMOT
"o'sha jadval" - lekin kecha yangi qatorlar qo'shilgan
kechikib kelgan yozuvlar, tuzatilgan xatolar
-> ma'lumot XESHI yoki snapshot/versiya 27.3-bob
4. KUTUBXONA VERSIYALARI
sukut parametr o'zgaradi (masalan, solver, max_iter)
algoritm ichki tartibi o'zgaradi
pickle/joblib boshqa versiyada ochilmasligi mumkin 19.6-bob
5. FLOAT VA PARALLEL HISOB
(a + b) + c != a + (b + c)
oqimlar soni -> yig'indi tartibi (BLAS, OpenMP, n_jobs)
GPU: atom amallari tartibi aniqlanmagan (cuDNN algoritmlari)
6. MUHIT
muhit o'zgaruvchilari (PYTHONHASHSEED, OMP_NUM_THREADS,
CUBLAS_WORKSPACE_CONFIG, TZ, LANG)
apparat (CPU ko'rsatmalar to'plami, GPU modeli)
operatsion tizim (fayl tizimi, satr oxiri CRLF/LF)Bu manbalarning eng xavflisi — jim ishlaydiganlari. Urug' qo'yilmagani ko'pincha ko'rinib turadi (natija har safar boshqa). set tartibi esa bitta jarayon ichida barqaror: siz noutbukda katakni necha marta ishga tushirmang, natija bir xil — faqat yangi jarayonda o'zgaradi. Shuning uchun reproduksiya testi har doim yangi jarayonda qilinadi (1-misol).
Reproduksiyani "bir xil noutbukda ikki marta" emas, "ikki yangi jarayonda" tekshiring.
2.3. Urug'lar ierarxiyasi
INTERPRETATOR DARAJASI (jarayon boshlanishidan OLDIN):
PYTHONHASHSEED -> str/bytes hash i -> set/dict tartibi
JARAYON DARAJASI (global generatorlar):
random.seed(s) -> Python random moduli
np.random.seed(s) -> numpy GLOBAL holati (eski API)
torch.manual_seed(s) -> torch CPU (+ CUDA) global generatori
torch.cuda.manual_seed_all(s) -> barcha GPU lar
OBYEKT DARAJASI (tavsiya etiladi - mustaqil va aniq):
rng = np.random.default_rng(s) -> o'z Generator i
rng.spawn(k) -> k ta MUSTAQIL oqim
RandomForestClassifier(random_state=s) -> sklearn (int!)
DataLoader(..., generator=torch.Generator().manual_seed(s))
ISHCHI JARAYONLAR (DataLoader num_workers > 0):
har ishchi: torch urug'i = base_seed + worker_id
torch python random va numpy global holatini ham o'zi o'rnatadi
O'Z generatoringiz bo'lsa (Dataset ichida default_rng) -
worker_init_fn ichida get_worker_info().seed dan hosil qiling
DETERMINISTIK ALGORITMLAR (GPU):
torch.use_deterministic_algorithms(True)
torch.backends.cudnn.benchmark = False
CUBLAS_WORKSPACE_CONFIG=:4096:8Qoidalar: (1) har kutubxonaning o'z urug'i bor — random.seed numpy ga ta'sir qilmaydi; (2) global holatga tayanish mo'rt — orada boshqa kod bitta tasodifiy son olsa, hamma narsa siljiydi; (3) eng ishonchlisi — urug'ni obyektga berish (random_state=int, default_rng, generator=).
Urug' — konfigning bir qismi. U konfig faylida turadi, manifestga yoziladi va kod ichida "sehrli son" bo'lmaydi.
2.4. PYTHONHASHSEED va hash() tuzog'i
Python 3.3+ da xavfsizlik uchun (hash-flooding hujumiga qarshi)
str va bytes hash i HAR JARAYONDA tasodifiy "tuz" bilan hisoblanadi.
hash("salom") -> jarayon A: 7213..., jarayon B: -4410...
hash(12345) -> har doim 12345 (int tuzlanmaydi)
OQIBAT:
set(satrlar) tartibi - jarayonga bog'liq
dict tartibi - QO'SHILISH tartibida (3.7+), xeshga emas -> barqaror
lekin dict(set(...)) yoki {k: i for i, k in enumerate(set(...))} -> YO'Q
BOSHQARISH:
PYTHONHASHSEED=0 python orgat.py # interpretator ISHGA TUSHISHIDAN oldin
os.environ["PYTHONHASHSEED"] = "0" # skript ichida - KECH, ta'sir qilmaydi
# (faqat bola jarayonlarga o'tadi)
TO'G'RI YECHIM:
hash() ga tayanmang: sorted(set(...)), barqaror xesh (hashlib)
PYTHONHASHSEED ni qat'iylash - qo'shimcha himoya, asosiy yechim emasYana bir tipik holat: belgini "xeshlash" (hash(foydalanuvchi_id) % 1000 bilan savatga ajratish). Bu o'qitishda bir xil, xizmatda esa boshqa jarayonda boshqa savat beradi — bu training-serving skew 27.1-bob. Barqaror variant: int(hashlib.md5(s.encode()).hexdigest(), 16) % 1000.
hash() — jarayon ichidagi vosita, identifikator emas. Jarayonlar orasida barqaror bo'lishi kerak bo'lgan har narsa uchun hashlib va sorted ishlating.
2.5. Muhitni qayd qilish
NIMA QAYD QILINADI:
python versiyasi platform.python_version()
platforma platform.system(), platform.machine()
paketlar (lock) importlib.metadata.version(nom) -> "numpy==2.5.3"
kod versiyasi git rev-parse HEAD + git status --porcelain (toza?)
ma'lumot sha256(fayl baytlari) yoki snapshot ID
konfig konfig + sha256(kanonik JSON)
urug'lar konfig ichida
muhit o'zgaruvchilari PYTHONHASHSEED, OMP_NUM_THREADS, ... (sirlar EMAS!)
natija metrikalar + bashorat xeshi
KANONIK JSON (konfig xeshi uchun):
json.dumps(k, sort_keys=True, separators=(",", ":"))
-> kalit tartibi ahamiyatsiz, bo'sh joylar ahamiyatsiz
pip freeze VA importlib.metadata:
pip freeze - butun muhit, tashqi buyruq
importlib.metadata - kod ichidan, faqat kerakli paketlarIkki narsaga e'tibor bering. Birinchisi — git holati: commit xeshi yetarli emas, agar ishchi papkada saqlanmagan o'zgarishlar bo'lsa ("dirty"). Manifestda toza: false bo'lsa, natijani aynan qayta olish mumkin emas — bu holatni ogohlantirish yoki taqiqlash kerak. Ikkinchisi — sirlar: muhit o'zgaruvchilarini qayd qilishda faqat ruxsat etilgan ro'yxatdagilarni yozing; DB_PAROL yoki API_KALIT manifestga tushmasligi kerak.
Ma'lumot va konfig — xesh bilan, kod — commit bilan, muhit — lock bilan. To'rttasi birga natijaning "manzili".
2.6. Reproduksiya manifesti va uni tekshirish
manifest.json:
{
"python": "3.14.5",
"platforma": {"tizim": "Windows", "arxitektura": "AMD64"},
"paketlar": {"numpy": "2.5.3", "scikit-learn": "1.9.1", ...},
"git": {"commit": "a1b2c3...", "toza": true},
"muhit": {"PYTHONHASHSEED": "0", "OMP_NUM_THREADS": "1"},
"malumot": {"fayl": "malumot.csv", "sha256": "952c6655a350"},
"konfig": {"urug": 0, "lr": 0.1, "max_iter": 100},
"konfig_xesh": "507dfdb81878",
"natija": {"auc": 0.716766, "bashorat_xesh": "818897a04902"}
}
QAYTA ISHGA TUSHIRISHDAN OLDIN tekshir(manifest):
python/paket versiyalari mosmi? -> farq: OGOHLANTIRISH
ma'lumot xeshi mosmi? -> farq: TO'XTATISH
konfig xeshi mosmi? -> farq: TO'XTATISH
KEYIN:
natija == manifest["natija"]? -> reproduksiya testiManifest ikki yo'nalishda ishlaydi: oldinga (natijani qayta olish uchun retsept) va orqaga (ishlab chiqarishdagi model qaerdan kelganini isbotlash — audit). 27.4 da har yurish manifestini eksperiment kuzatuvchisiga, 27.5 da esa registrga ulaymiz.
Manifest natija bilan birga yoziladi va qayta ishga tushirishdan oldin avtomatik tekshiriladi.
2.7. Urug'ga sezgirlik
BITTA URUG':
A: 0.924, B: 0.926 -> "B yaxshiroq"
aslida: A va B ning urug'lar bo'yicha std ~ 0.01
farq 0.002 - shovqin ichida
TO'G'RI HISOBOT (18-qism):
bir necha urug' (bo'lish + model), o'rtacha +- std
JUFTLASHGAN farq: d_s = A_s - B_s (bir xil urug' - bir xil bo'lish)
SE(d) = std(d) / sqrt(n); |mean(d)| > 2 * SE -> sezilarli
qaror: eng yaxshisidan sezilarli yomon bo'lmagan ENG ARZON variant
TARQOQLIK MANBALARI:
bo'lish urug'i - qaysi qatorlar testga tushdi (ko'pincha KATTA)
model urug'i - bootstrap, belgi tanlash, init
ikkalasini alohida o'zgartirib ko'rish -> qaysi biri ustunBu 18.3 (CV dispersiyasi) va 18.11 (validatsiyaga overfitting) darslarining MLOps dagi davomi: "eng yaxshi urug'" ni tanlash — ko'p variantdan eng yaxshisini tanlashning xuddi o'zi va natija optimistik bo'ladi (4-misol).
Aynan takrorlanadigan natija ham bitta tasodifiy namuna. Qarorlar bir necha urug' bo'yicha juftlashgan farq bilan qabul qilinadi.
2.8. Muhit boshqaruvchilari
# venv + pip (standart kutubxona)
python -m venv .venv
.venv/Scripts/activate # Windows (Linux/macOS: source .venv/bin/activate)
pip install -r requirements.txt # requirements.txt: numpy==2.5.3 ...
pip freeze > requirements.lock # butun muhitning aniq versiyalari
# pip-tools: to'g'ridan-to'g'ri bog'liqliklar -> to'liq lock
pip-compile requirements.in --generate-hashes
# uv (tez, lock fayl bilan)
uv venv
uv pip install -r requirements.txt
uv lock # uv.lock - bog'liqliklar daraxti + xeshlar
# poetry
poetry add scikit-learn==1.9.1 # pyproject.toml + poetry.lock
# conda (python dan tashqari kutubxonalar: CUDA, MKL, GDAL)
conda env create -f environment.yml
conda env export --from-history > environment.yml| Vosita | Kuchli tomoni | E'tibor |
|---|---|---|
venv + pip |
Standart, hamma joyda | Lock uchun pip freeze yoki pip-tools |
uv |
Juda tez, lock fayl, python versiyasini ham boshqaradi | Nisbatan yangi vosita |
poetry |
Paket yaratish + bog'liqliklar bir joyda | Ba'zi ML paketlari bilan sekin hal qilish |
conda |
Python bo'lmagan kutubxonalar (CUDA, MKL) | Kanallar aralashsa, muhit mo'rt |
| Docker | OS darajasigacha muzlatish | 27.9-darsda |
Lock fayl — "qaysi versiyalar" savoliga javob; Docker obrazi esa bundan ham chuqurroq: operatsion tizim kutubxonalari, python ning o'zi, muhit o'zgaruvchilari. 27.9-darsda muhitni konteynerga muzlatamiz; bu darsdagi manifest esa konteynerda ham kerak — chunki ma'lumot, konfig va natija obrazga kirmaydi.
To'g'ridan-to'g'ri bog'liqliklar — bitta faylda, aniq versiyalar — lock faylda, ikkalasi ham git da.
2.9. Tuzoqlar
Asosiy tuzoqlar: faqat bitta kutubxona urug'ini o'rnatish; random_state=None ni qoldirish; list(set(...)) bilan kategoriya kodlash; hash() ni belgi yoki savat uchun ishlatish; PYTHONHASHSEED ni skript ichida o'rnatish; reproduksiyani bitta noutbuk jarayonida tekshirish; fayl ro'yxatini sorted siz o'qish; requirements.txt da versiyasiz paketlar; commit xeshini "dirty" ishchi papkada yozish; manifestga sirlarni yozish; bitta urug' natijasini hisobotga yozish; "eng yaxshi urug'" ni tanlash.
3. Tez ma'lumotnoma
import hashlib
import importlib.metadata as md
import json
import platform
import random
import numpy as np
import torch
def seed_everything(urug):
random.seed(urug)
np.random.seed(urug)
torch.manual_seed(urug) # CUDA ham (bor bo'lsa)
def worker_init_fn(worker_id):
urug = torch.utils.data.get_worker_info().seed % 2**32
np.random.seed(urug) # yoki o'z Generator ingiz
random.seed(urug)
def lock(paketlar):
return {p: md.version(p) for p in sorted(paketlar)}
def konfig_xesh(konfig):
kanonik = json.dumps(konfig, sort_keys=True, separators=(",", ":"))
return hashlib.sha256(kanonik.encode()).hexdigest()[:12]
def fayl_xesh(yol, blok=1 << 20):
h = hashlib.sha256()
with open(yol, "rb") as f:
for bolak in iter(lambda: f.read(blok), b""):
h.update(bolak) # katta fayl - bo'lakma-bo'lak
return h.hexdigest()[:12]
def barqaror_savat(satr, n=1000):
return int(hashlib.md5(satr.encode()).hexdigest(), 16) % n # hash() EMAS
muhit = {"python": platform.python_version(), "tizim": platform.system(),
"paketlar": lock(["numpy", "scikit-learn"])}
kategoriyalar = sorted(set(["Toshkent", "Andijon"])) # list(set()) EMASTuzilma xulosasi
darajalar: takrorlanuvchanlik (bit) / reproduksiya (statistik) / replikatsiya
manbalar: urug', tartib, ma'lumot, versiya, float/parallel, muhit
urug'lar: PYTHONHASHSEED (jarayondan oldin) > global > obyekt (tavsiya)
hash(): jarayonga bog'liq -> sorted() va hashlib
manifest: python, paketlar, git (+toza), ma'lumot xeshi, konfig xeshi, natija
tekshiruv: yangi jarayonda, manifestga qarshi, natija xeshi teng
sezgirlik: bir necha urug', juftlashgan farq, 2*SE4. Batafsil misollar
Misollar real numpy/sklearn/torch bilan (Python 3.14). 1- va 2-misollar yangi Python jarayonlarini ishga tushiradi.
Misol 1 — Reproduksiya testi: bir xil skript, ikki yangi jarayon
Ma'lumot to'rtta CSV bo'lakda. O'qitish skripti — noutbukdan ko'chirilgan tipik kod: fayl nomlarini set bilan dublikatsiz qiladi, hududlarni list(set(...)) bilan kodlaydi va mini-batch SGD bilan logistik regressiya o'qitadi. Skriptni yangi jarayonlarda ishga tushirib, bashorat xeshlarini solishtiramiz. Ikki bayroq: --urug (numpy urug'i) va --sorted (set o'rniga sorted).
"""Reproduksiya testi: bir xil skript, ikki YANGI jarayon, natija tengmi?"""
import os
import subprocess
import sys
import tempfile
from pathlib import Path
import numpy as np
import pandas as pd
# o'qitish skripti - tipik "noutbukdan ko'chirilgan" kod
SKRIPT = r'''
import csv, hashlib, sys
from pathlib import Path
import numpy as np
urug = "--urug" in sys.argv
tartibla = "--sorted" in sys.argv
papka = Path(sys.argv[1])
nomlar = {p.name for p in papka.glob("qism_*.csv")} # dublikatsiz
nomlar = sorted(nomlar) if tartibla else list(nomlar)
qatorlar = []
for n in nomlar:
with open(papka / n, encoding="utf-8") as f:
qatorlar += list(csv.DictReader(f))
hududlar = {q["hudud"] for q in qatorlar}
hududlar = sorted(hududlar) if tartibla else list(hududlar)
kod = {h: i for i, h in enumerate(hududlar)} # label encoding
X = np.array([[float(q["x1"]), float(q["x2"]), kod[q["hudud"]]]
for q in qatorlar])
X = (X - X.mean(0)) / X.std(0)
y = np.array([int(q["y"]) for q in qatorlar])
Xtr, ytr, Xte, yte = X[:3000], y[:3000], X[3000:], y[3000:]
if urug:
np.random.seed(0)
w = np.random.normal(0, 0.1, 3)
b = 0.0
for davr in range(5): # mini-batch SGD
tartib = np.random.permutation(len(ytr))
for i in range(0, len(ytr), 50):
j = tartib[i:i + 50]
g = 1 / (1 + np.exp(-(Xtr[j] @ w + b))) - ytr[j]
w -= 0.1 * Xtr[j].T @ g / len(j)
b -= 0.1 * g.mean()
p = 1 / (1 + np.exp(-(Xte @ w + b)))
r = p.argsort().argsort() + 1 # AUC ranglardan
auc = (r[yte == 1].sum() - (yte == 1).sum() * ((yte == 1).sum() + 1) / 2) / (
(yte == 1).sum() * (yte == 0).sum())
print(hashlib.sha256(np.round(p, 10).tobytes()).hexdigest()[:12], round(auc, 4))
'''
def ishga(skript, papka, bayroqlar, hashseed):
"""Skriptni YANGI jarayonda, berilgan PYTHONHASHSEED bilan ishga tushiradi."""
env = dict(os.environ, PYTHONHASHSEED=str(hashseed))
r = subprocess.run([sys.executable, str(skript), str(papka), *bayroqlar],
capture_output=True, text=True, env=env, timeout=120)
if r.returncode != 0:
raise RuntimeError(r.stderr.strip().splitlines()[-1])
xesh, auc = r.stdout.split()
return xesh, float(auc)
def main() -> None:
with tempfile.TemporaryDirectory() as t:
papka = Path(t)
rng = np.random.default_rng(0)
hudud = ["Andijon", "Buxoro", "Jizzax", "Namangan", "Navoiy",
"Samarqand", "Toshkent", "Xorazm"]
eff = dict(zip(hudud, [0.4, -0.3, 0.8, 0.1, -0.6, 0.0, -0.9, 0.5]))
for i in range(4): # ma'lumot 4 bo'lak faylda
n = 1000
h = rng.choice(hudud, n)
x1, x2 = rng.normal(size=n), rng.normal(size=n)
logit = x1 - 0.5 * x2 + pd.Series(h).map(eff).to_numpy()
y = (rng.random(n) < 1 / (1 + np.exp(-logit))).astype(int)
pd.DataFrame({"x1": x1.round(5), "x2": x2.round(5), "hudud": h,
"y": y}).to_csv(papka / f"qism_{i}.csv", index=False)
skript = papka / "orgat.py"
skript.write_text(SKRIPT, encoding="utf-8")
print("=== 1. Bir xil skript, ikki yangi jarayon (PYTHONHASHSEED 1 va 2) ===")
print(f" {'variant':<26} {'xesh teng':>9} izoh")
variantlar = [("urug' yo'q, set tartibi", []),
("urug' bor, set tartibi", ["--urug"]),
("urug' yo'q, sorted", ["--sorted"]),
("urug' bor, sorted", ["--urug", "--sorted"])]
for nom, bayroq in variantlar:
(x1, a1), (x2, a2) = [ishga(skript, papka, bayroq, hs) for hs in (1, 2)]
izoh = f"AUC {a1:.4f} va {a2:.4f}" if "--urug" in bayroq else ""
print(f" {nom:<26} {str(x1 == x2):>9} {izoh}")
print("\n=== 2. PYTHONHASHSEED qat'iy bo'lsa ===")
(xa, _), (xb, _) = [ishga(skript, papka, ["--urug"], hs) for hs in (5, 5)]
print(f" urug' bor, set tartibi, ikkalasida PYTHONHASHSEED=5: teng {xa == xb}")
print(" -> set tartibi faqat PYTHONHASHSEED ga bog'liq; sukut bo'yicha u")
print(" tasodifiy, ya'ni 'urug' qo'yilgan' skript ham har safar boshqacha")
print("\n=== 3. Olti yangi jarayon (PYTHONHASHSEED 10..15) ===")
for nom, bayroq in [("urug' bor, set tartibi", ["--urug"]),
("urug' bor, sorted", ["--urug", "--sorted"])]:
natijalar = {ishga(skript, papka, bayroq, hs) for hs in range(10, 16)}
auclar = sorted(a for _, a in natijalar)
print(f" {nom:<24} noyob natija: {len(natijalar)}; AUC "
f"{auclar[0]:.4f} - {auclar[-1]:.4f}")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Bir xil skript, ikki yangi jarayon (PYTHONHASHSEED 1 va 2) ===
variant xesh teng izoh
urug' yo'q, set tartibi False
urug' bor, set tartibi False AUC 0.7376 va 0.7456
urug' yo'q, sorted False
urug' bor, sorted True AUC 0.7584 va 0.7584
=== 2. PYTHONHASHSEED qat'iy bo'lsa ===
urug' bor, set tartibi, ikkalasida PYTHONHASHSEED=5: teng True
-> set tartibi faqat PYTHONHASHSEED ga bog'liq; sukut bo'yicha u
tasodifiy, ya'ni 'urug' qo'yilgan' skript ham har safar boshqacha
=== 3. Olti yangi jarayon (PYTHONHASHSEED 10..15) ===
urug' bor, set tartibi noyob natija: 6; AUC 0.7384 - 0.7590
urug' bor, sorted noyob natija: 1; AUC 0.7584 - 0.7584Nima ko'rsatdi: 2.2, 2.4-bo'limlar. Yagona to'liq takrorlanadigan variant — urug' va sorted. Urug' qo'yilgan, lekin set tartibi qolgan skript ikki jarayonda boshqa natija berdi: set fayllar tartibini (qatorlar tartibi → o'quv/test bo'linishi) ham, hudud kodlarini ham o'zgartiradi. Muhimi, bu xato bitta noutbuk jarayonida umuman ko'rinmaydi — 2-qism ko'rsatganidek, PYTHONHASHSEED bir xil bo'lsa natija teng. Olti yangi jarayonda urug'li, lekin set li skript olti xil natija berdi va AUC ancha keng oraliqda "sakradi" — auditordagi vaziyatning o'zi. sorted bilan esa oltitasi ham bitta natija.
Bu misol reproduksiya testining shablonidir: skript yangi jarayonlarda, turli PYTHONHASHSEED bilan ishga tushiriladi va bashorat xeshlari solishtiriladi. CI da 27.8-bob aynan shunday test yoziladi.
Misol 2 — Urug'lar ierarxiyasi va PYTHONHASHSEED
Uch global generator, sklearn ning random_state shakllari, numpy ning mustaqil oqimlari, DataLoader generatori — va interpretator darajasidagi PYTHONHASHSEED, jumladan uni skript ichida o'rnatish tuzog'i.
"""Urug'lar ierarxiyasi va PYTHONHASHSEED tuzog'i."""
import os
import random
import subprocess
import sys
import numpy as np
import torch
from sklearn.datasets import make_classification
from sklearn.ensemble import RandomForestClassifier
from torch.utils.data import DataLoader, TensorDataset
def seed_everything(seed):
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
def jarayon(kod, hashseed):
"""Kodni yangi Python jarayonida ishga tushiradi."""
env = dict(os.environ)
if hashseed is None:
env.pop("PYTHONHASHSEED", None)
else:
env["PYTHONHASHSEED"] = str(hashseed)
r = subprocess.run([sys.executable, "-c", kod], capture_output=True,
text=True, env=env, timeout=60)
return r.stdout.strip()
def main() -> None:
print("=== 1. Uch generator - uch alohida urug' ===")
seed_everything(0)
a = (random.random(), np.random.rand(), torch.rand(1).item())
random.seed(0) # FAQAT python random
b = (random.random(), np.random.rand(), torch.rand(1).item())
print(f" seed_everything(0) dan keyin: {[round(v, 4) for v in a]}")
print(f" faqat random.seed(0) dan keyin: {[round(v, 4) for v in b]}")
print(f" mos keladi: python {a[0] == b[0]}, numpy {a[1] == b[1]}, "
f"torch {a[2] == b[2]}")
print("\n=== 2. sklearn random_state: None va int ===")
X, y = make_classification(n_samples=600, n_features=10, random_state=0)
def rf(rs, oraliq=False):
np.random.seed(0)
if oraliq:
np.random.rand(3) # boshqa kod global holatni ishlatdi
return RandomForestClassifier(n_estimators=20, random_state=rs).fit(
X, y).predict_proba(X[:50])
print(f" None, global seed(0): teng {np.array_equal(rf(None), rf(None))}")
print(f" None, oraliqda boshqa np.random: teng "
f"{np.array_equal(rf(None), rf(None, True))}")
print(f" int 42, oraliqda boshqa np.random: teng "
f"{np.array_equal(rf(42), rf(42, True))}")
print("\n=== 3. numpy Generator: mustaqil oqimlar ===")
g = np.random.default_rng(123)
print(f" default_rng(123) birinchi 3: {np.round(g.random(3), 4).tolist()}")
oqim = np.random.default_rng(123).spawn(2)
print(f" spawn qilingan 2 oqim bir xilmi: "
f"{np.array_equal(oqim[0].random(3), oqim[1].random(3))}")
oqim2 = np.random.default_rng(123).spawn(2)
print(f" qayta spawn - 1-oqim takrorlandimi: "
f"{np.array_equal(np.random.default_rng(123).spawn(2)[0].random(3), oqim2[0].random(3))}")
print("\n=== 4. DataLoader: generator global holatdan mustaqil ===")
ds = TensorDataset(torch.arange(8))
def tartib(oraliq):
torch.manual_seed(0)
if oraliq:
torch.rand(5) # masalan, model init o'zgardi
g = torch.Generator().manual_seed(7)
return next(iter(DataLoader(ds, batch_size=8, shuffle=True,
generator=g)))[0].tolist()
print(f" generator(7): {tartib(False)}")
print(f" generator(7), oraliqda rand: {tartib(True)}")
print("\n=== 5. PYTHONHASHSEED: str hash i jarayonga bog'liq ===")
kod = "print(hash('salom'))"
print(f" =0 va =0 (ikki jarayon) teng: {jarayon(kod, 0) == jarayon(kod, 0)}")
print(f" =0 va =1 teng: {jarayon(kod, 0) == jarayon(kod, 1)}")
tasodifiy = {jarayon(kod, None) for _ in range(5)}
print(f" o'rnatilmagan: 5 jarayonda {len(tasodifiy)} xil qiymat")
ichida = ("import os; os.environ['PYTHONHASHSEED'] = '0'; "
"print(hash('salom'))")
print(f" skript ICHIDA '0' qilingan (tashqarida 1 va 2) teng: "
f"{jarayon(ichida, 1) == jarayon(ichida, 2)}")
kod_int = "print(hash(12345), hash((1, 2)))"
print(f" int va tuple hash i (=0 va =1) teng: "
f"{jarayon(kod_int, 0) == jarayon(kod_int, 1)}")
print("\n=== 6. Oqibat: list(set(...)) bilan kategoriya kodlash ===")
kod = ("s = {'Andijon','Buxoro','Namangan','Samarqand','Toshkent',"
"'Xorazm','Qarshi','Nukus'}; print(list(s).index('Toshkent'))")
print(f" PYTHONHASHSEED 0..5 da 'Toshkent' kodi: "
f"{[int(jarayon(kod, hs)) for hs in range(6)]}")
kod_s = kod.replace("list(s)", "sorted(s)")
print(f" sorted() bilan: "
f"{[int(jarayon(kod_s, hs)) for hs in range(6)]}")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Uch generator - uch alohida urug' ===
seed_everything(0) dan keyin: [0.8444, 0.5488, 0.4963]
faqat random.seed(0) dan keyin: [0.8444, 0.7152, 0.7682]
mos keladi: python True, numpy False, torch False
=== 2. sklearn random_state: None va int ===
None, global seed(0): teng True
None, oraliqda boshqa np.random: teng False
int 42, oraliqda boshqa np.random: teng True
=== 3. numpy Generator: mustaqil oqimlar ===
default_rng(123) birinchi 3: [0.6824, 0.0538, 0.2204]
spawn qilingan 2 oqim bir xilmi: False
qayta spawn - 1-oqim takrorlandimi: True
=== 4. DataLoader: generator global holatdan mustaqil ===
generator(7): [1, 2, 3, 5, 7, 4, 6, 0]
generator(7), oraliqda rand: [1, 2, 3, 5, 7, 4, 6, 0]
=== 5. PYTHONHASHSEED: str hash i jarayonga bog'liq ===
=0 va =0 (ikki jarayon) teng: True
=0 va =1 teng: False
o'rnatilmagan: 5 jarayonda 5 xil qiymat
skript ICHIDA '0' qilingan (tashqarida 1 va 2) teng: False
int va tuple hash i (=0 va =1) teng: True
=== 6. Oqibat: list(set(...)) bilan kategoriya kodlash ===
PYTHONHASHSEED 0..5 da 'Toshkent' kodi: [0, 0, 2, 6, 7, 0]
sorted() bilan: [6, 6, 6, 6, 6, 6]Nima ko'rsatdi: 2.3, 2.4-bo'limlar.
- Uch generator mustaqil. Faqat
random.seed(0)qilinganda Python ning soni mos keldi, numpy va torch niki esa yo'q — ular o'z holatidan davom etdi. random_state=Nonemo'rt. Globalnp.random.seed(0)qo'yilgan bo'lsa ham, orada boshqa kod bittanp.random.randchaqirsa, o'rmon boshqacha bo'ldi.random_state=42esa global holatdan mustaqil.- Mustaqil oqimlar.
spawnbitta urug'dan bir-biriga o'xshamaydigan, lekin qayta hosil qilinadigan oqimlar beradi — parallel ishchilar yoki bir necha tajriba uchun. DataLoadergeneratori global torch holatidan mustaqil: oraliqdatorch.randchaqirilsa ham tartib bir xil.PYTHONHASHSEED. Bir xil qiymat — bir xil hash; turli yoki o'rnatilmagan — turli hash. Uni skript ichida o'rnatish hech narsa bermadi: interpretator allaqachon ishga tushgan.intvatuple(butun sonlardan) hash i tuzlanmaydi. Oxirgi qism oqibatni ko'rsatdi: bir xil kod "Toshkent" ga turli jarayonlarda turli kod beradi —sortedbilan esa har doim bir xil.
Misol 3 — Reproduksiya manifesti: yaratish, qayta ishga tushirish, buzilishni ushlash
Kichik o'qitish skripti natijasi bilan birga manifest yozadi: python va platforma, importlib.metadata dan paket versiyalari (lock), git holati, ma'lumot xeshi, konfig va uning kanonik xeshi. Keyin manifestdan qayta ishga tushiramiz va buzilishlarni simulyatsiya qilamiz.
"""Reproduksiya manifesti: muhit, ma'lumot va konfig xeshi, tekshirish."""
import copy
import hashlib
import importlib.metadata as md
import json
import os
import platform
import subprocess
import tempfile
from pathlib import Path
import numpy as np
import pandas as pd
from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.metrics import roc_auc_score
PAKETLAR = ["numpy", "pandas", "scikit-learn", "scipy", "joblib"]
MUHIT_ROYXAT = ["PYTHONHASHSEED", "OMP_NUM_THREADS", "MKL_NUM_THREADS"] # sirlar EMAS
def xesh(b):
return hashlib.sha256(b).hexdigest()[:12]
def konfig_xesh(konfig):
return xesh(json.dumps(konfig, sort_keys=True, separators=(",", ":")).encode())
def lock(paketlar):
return {p: md.version(p) for p in sorted(paketlar)}
def git_holati(papka):
try:
r = subprocess.run(["git", "rev-parse", "HEAD"], cwd=papka,
capture_output=True, text=True, timeout=10)
except FileNotFoundError:
return {"commit": None, "sabab": "git topilmadi"}
if r.returncode != 0:
return {"commit": None, "sabab": "git repozitoriy emas"}
s = subprocess.run(["git", "status", "--porcelain"], cwd=papka,
capture_output=True, text=True, timeout=10)
return {"commit": r.stdout.strip(), "toza": s.stdout.strip() == ""}
def malumot_yarat(yol, seed=0, n=3000):
rng = np.random.default_rng(seed)
df = pd.DataFrame({"x1": rng.normal(size=n).round(4),
"x2": rng.normal(size=n).round(4),
"x3": rng.integers(0, 5, n)})
logit = 1.2 * df["x1"] - 0.8 * df["x2"] * (df["x3"] > 2) + 0.3
df["y"] = (rng.random(n) < 1 / (1 + np.exp(-logit))).astype(int)
df.to_csv(yol, index=False, lineterminator="\n") # LF - OS dan mustaqil
def orgat(df, konfig):
tr, te = df.iloc[:2000], df.iloc[2000:]
X = ["x1", "x2", "x3"]
m = HistGradientBoostingClassifier(
max_iter=konfig["max_iter"], learning_rate=konfig["lr"],
random_state=konfig["urug"]).fit(tr[X], tr["y"])
p = m.predict_proba(te[X])[:, 1]
return {"auc": round(float(roc_auc_score(te["y"], p)), 6),
"bashorat_xesh": xesh(np.round(p, 8).tobytes())}
def manifest_yarat(papka, malumot_yol, konfig, natija):
return {
"python": platform.python_version(),
"platforma": {"tizim": platform.system(), "arxitektura": platform.machine()},
"paketlar": lock(PAKETLAR),
"git": git_holati(papka),
"muhit": {k: os.environ.get(k) for k in MUHIT_ROYXAT},
"malumot": {"fayl": malumot_yol.name,
"sha256": xesh(malumot_yol.read_bytes())},
"konfig": konfig, "konfig_xesh": konfig_xesh(konfig),
"natija": natija,
}
def tekshir(manifest, papka):
"""Hozirgi muhit va fayllarni manifest bilan solishtiradi."""
farqlar = []
if manifest["python"] != platform.python_version():
farqlar.append(f"python: {manifest['python']} != {platform.python_version()}")
joriy = lock(manifest["paketlar"])
for p, v in manifest["paketlar"].items():
if joriy[p] != v:
farqlar.append(f"OGOHLANTIRISH {p}: manifest {v}, hozir {joriy[p]}")
yol = Path(papka) / manifest["malumot"]["fayl"]
if xesh(yol.read_bytes()) != manifest["malumot"]["sha256"]:
farqlar.append("TO'XTATISH: ma'lumot xeshi mos emas")
if konfig_xesh(manifest["konfig"]) != manifest["konfig_xesh"]:
farqlar.append("TO'XTATISH: konfig o'zgartirilgan")
return farqlar
def main() -> None:
with tempfile.TemporaryDirectory() as t:
papka = Path(t)
yol = papka / "malumot.csv"
malumot_yarat(yol)
konfig = {"urug": 0, "lr": 0.1, "max_iter": 100}
print("=== 1. Konfig xeshi: kalit tartibi ahamiyatsiz, qiymat - muhim ===")
k2 = {"max_iter": 100, "lr": 0.1, "urug": 0}
k3 = dict(konfig, lr=0.05)
print(f" konfig {konfig_xesh(konfig)}")
print(f" tartibi boshqa {konfig_xesh(k2)} teng: "
f"{konfig_xesh(k2) == konfig_xesh(konfig)}")
print(f" lr=0.05 {konfig_xesh(k3)} teng: "
f"{konfig_xesh(k3) == konfig_xesh(konfig)}")
print("\n=== 2. O'qitish va manifest ===")
natija = orgat(pd.read_csv(yol), konfig)
m = manifest_yarat(papka, yol, konfig, natija)
(papka / "manifest.json").write_text(json.dumps(m, indent=2),
encoding="utf-8")
print(f" kalitlar: {list(m)}")
print(f" paketlar (lock): {len(m['paketlar'])} ta, masalan "
f"'scikit-learn=={m['paketlar']['scikit-learn']}'")
print(f" muhit o'zgaruvchilari (faqat ro'yxatdagilar): {sorted(m['muhit'])}")
print(f" git: {m['git']}")
print(f" ma'lumot sha256: {m['malumot']['sha256']}")
print(f" natija: {natija}")
print("\n=== 3. requirements.txt (pin) ===")
req = "\n".join(f"{p}=={v}" for p, v in m["paketlar"].items())
(papka / "requirements.txt").write_text(req + "\n", encoding="utf-8")
for qator in req.splitlines():
print(f" {qator}")
print("\n=== 4. Manifestdan qayta ishga tushirish ===")
m2 = json.loads((papka / "manifest.json").read_text(encoding="utf-8"))
print(f" tekshiruv farqlari: {tekshir(m2, papka) or 'yoq'}")
qayta = orgat(pd.read_csv(yol), m2["konfig"])
print(f" qayta natija: {qayta}")
print(f" aynan teng: {qayta == m2['natija']}")
print("\n=== 5. Buzilishlarni ushlash ===")
df = pd.read_csv(yol)
df.loc[17, "x1"] = df.loc[17, "x1"] + 0.0001 # bitta qiymat
df.to_csv(yol, index=False, lineterminator="\n")
print(f" 1 qiymat o'zgardi: {tekshir(m2, papka)}")
print(f" shu o'zgarishdan natija o'zgardimi: "
f"{orgat(df, m2['konfig']) != m2['natija']}")
malumot_yarat(yol) # tiklash
crlf = yol.read_bytes().replace(b"\n", b"\r\n") # Windows satr oxiri
(papka / "crlf.csv").write_bytes(crlf)
print(f" CRLF nusxa: xesh teng {xesh(crlf) == m2['malumot']['sha256']}, "
f"pandas jadvali teng {pd.read_csv(papka / 'crlf.csv').equals(pd.read_csv(yol))}")
m3 = copy.deepcopy(m2)
m3["paketlar"]["scikit-learn"] = "1.4.2" # boshqa muhitda yozilgan
m3["konfig"]["lr"] = 0.2 # qo'lda o'zgartirilgan
for f in tekshir(m3, papka):
print(f" {f}")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Konfig xeshi: kalit tartibi ahamiyatsiz, qiymat - muhim ===
konfig 507dfdb81878
tartibi boshqa 507dfdb81878 teng: True
lr=0.05 c3db07c8be99 teng: False
=== 2. O'qitish va manifest ===
kalitlar: ['python', 'platforma', 'paketlar', 'git', 'muhit', 'malumot', 'konfig', 'konfig_xesh', 'natija']
paketlar (lock): 5 ta, masalan 'scikit-learn==1.9.1'
muhit o'zgaruvchilari (faqat ro'yxatdagilar): ['MKL_NUM_THREADS', 'OMP_NUM_THREADS', 'PYTHONHASHSEED']
git: {'commit': None, 'sabab': 'git repozitoriy emas'}
ma'lumot sha256: 952c6655a350
natija: {'auc': 0.716766, 'bashorat_xesh': '818897a04902'}
=== 3. requirements.txt (pin) ===
joblib==1.6.0
numpy==2.5.3
pandas==3.0.6
scikit-learn==1.9.1
scipy==1.18.1
=== 4. Manifestdan qayta ishga tushirish ===
tekshiruv farqlari: yoq
qayta natija: {'auc': 0.716766, 'bashorat_xesh': '818897a04902'}
aynan teng: True
=== 5. Buzilishlarni ushlash ===
1 qiymat o'zgardi: ["TO'XTATISH: ma'lumot xeshi mos emas"]
shu o'zgarishdan natija o'zgardimi: False
CRLF nusxa: xesh teng False, pandas jadvali teng True
OGOHLANTIRISH scikit-learn: manifest 1.4.2, hozir 1.9.1
TO'XTATISH: konfig o'zgartirilganNima ko'rsatdi: 2.5, 2.6-bo'limlar. Kanonik JSON tufayli konfig xeshi kalit tartibiga bog'liq emas, lekin bitta qiymat (lr) o'zgarsa, xesh butunlay boshqa. Manifestga python, platforma, beshta paketning aniq versiyasi, faqat ruxsat etilgan muhit o'zgaruvchilari, git holati (vaqtinchalik papka repozitoriy emas — bu ham qayd qilinadi, "noma'lum kod" sifatida), ma'lumot va konfig xeshlari hamda natija yozildi. Manifestdan qayta ishga tushirish aynan bir xil AUC va bashorat xeshini berdi.
Buzilishlar qismida uch muhim kuzatuv bor. Birinchisi: bitta qiymatning 0.0001 ga o'zgarishini xesh ushladi, garchi bu o'zgarish natijani o'zgartirmagan bo'lsa ham (gradient boosting belgini qutilarga ajratadi va kichik o'zgarish qutini almashtirmadi). Xesh natijadan qat'iy — va bu to'g'ri: "natija o'zgarmadi" degan xulosani faqat tekshirib chiqarish mumkin. Ikkinchisi: xuddi shu jadval CRLF satr oxirlari bilan saqlansa, pandas uchun jadval bir xil, lekin fayl xeshi boshqa — shuning uchun biz faylni lineterminator="\n" bilan yozdik. Ma'lumot xeshini fayl baytlaridan olsangiz, yozish formatini ham qat'iylashtiring. Uchinchisi: boshqa sklearn versiyasi ogohlantirish, o'zgartirilgan konfig esa to'xtatish sababi.
Misol 4 — Urug'ga sezgirlik: takrorlanadigan natija ham bitta namuna
Ikki model: A — tasodifiy o'rmon, har bo'linishda sqrt(20) ≈ 4 belgi; B — xuddi shu, lekin 30% (6) belgi, ya'ni sekinroq. Har urug' bo'lishni ham, modelni ham belgilaydi — har bir yurish aynan takrorlanadi. Savol: qaysi model yaxshiroq?
"""Urug'ga sezgirlik: bitta urug'dagi natija - bitta namuna."""
import numpy as np
from sklearn.datasets import make_classification
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import train_test_split
X, Y = make_classification(n_samples=2000, n_features=20, n_informative=5,
n_redundant=5, flip_y=0.08, class_sep=0.8,
random_state=3)
MODELLAR = {"A": {"max_features": "sqrt"}, # 4 belgi har bo'linishda
"B": {"max_features": 0.3}} # 6 belgi - sekinroq
def yurish(nom, bolish_urug, model_urug):
Xtr, Xte, ytr, yte = train_test_split(X, Y, test_size=0.3,
random_state=bolish_urug, stratify=Y)
m = RandomForestClassifier(n_estimators=40, random_state=model_urug,
n_jobs=1, **MODELLAR[nom]).fit(Xtr, ytr)
return roc_auc_score(yte, m.predict_proba(Xte)[:, 1])
def main() -> None:
urug = range(15)
a = np.array([yurish("A", s, s) for s in urug])
b = np.array([yurish("B", s, s) for s in urug])
d = a - b
print("=== 1. Bitta urug' - 'aniq' javob ===")
for s in range(4):
print(f" urug' {s}: A {a[s]:.4f}, B {b[s]:.4f} -> g'olib "
f"{'A' if d[s] > 0 else 'B'} ({d[s]:+.4f})")
print(f" takror: urug' 0 dagi A qayta hisoblandi, teng: "
f"{yurish('A', 0, 0) == a[0]}")
print("\n=== 2. 15 urug' (bo'lish va model urug'i birga) ===")
for nom, v in [("A", a), ("B", b)]:
print(f" {nom}: {v.mean():.4f} +- {v.std(ddof=1):.4f} "
f"[{v.min():.4f}, {v.max():.4f}]")
print(f" A g'olib bo'lgan urug'lar: {int((d > 0).sum())}/15")
se = d.std(ddof=1) / np.sqrt(len(d))
se_juftsiz = np.sqrt(a.var(ddof=1) / len(a) + b.var(ddof=1) / len(b))
print(f" farq A-B: {d.mean():+.4f}; juftlashgan SE {se:.4f}, "
f"juftlashmagan SE {se_juftsiz:.4f}")
if abs(d.mean()) > 2 * se:
print(f" farq SEZILARLI -> {'A' if d.mean() > 0 else 'B'}")
else:
print(" farq sezilarli emas -> qoida: arzonroq A")
print("\n=== 3. Tarqoqlik manbalari: faqat bittasini o'zgartirib ===")
faqat_bolish = np.array([yurish("A", s, 0) for s in urug])
faqat_model = np.array([yurish("A", 0, s) for s in urug])
print(f" faqat bo'lish urug'i: std {faqat_bolish.std(ddof=1):.4f}")
print(f" faqat model urug'i: std {faqat_model.std(ddof=1):.4f}")
katta = "bo'lish" if faqat_bolish.std() > faqat_model.std() else "model"
print(f" asosiy tarqoqlik manbai: {katta} urug'i")
print("\n=== 4. 'Eng yaxshi urug'' ni hisobot qilish ===")
eng = int(b.argmax())
print(f" B ning eng yaxshi urug'i {eng}: {b[eng]:.4f}; o'rtacha {b.mean():.4f}; "
f"optimizm {b[eng] - b.mean():+.4f} "
f"({(b[eng] - b.mean()) / b.std(ddof=1):.1f} std)")
print(f" shu urug'da B ning A dan ustunligi: {-d[eng]:+.4f}; "
f"o'rtacha ustunlik {-d.mean():+.4f}")
print(" hisobotga: o'rtacha +- std (15 urug'), juftlashgan farq va SE")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Bitta urug' - 'aniq' javob ===
urug' 0: A 0.9238, B 0.9264 -> g'olib B (-0.0025)
urug' 1: A 0.9111, B 0.9042 -> g'olib A (+0.0069)
urug' 2: A 0.9355, B 0.9293 -> g'olib A (+0.0062)
urug' 3: A 0.9124, B 0.9175 -> g'olib B (-0.0051)
takror: urug' 0 dagi A qayta hisoblandi, teng: True
=== 2. 15 urug' (bo'lish va model urug'i birga) ===
A: 0.9226 +- 0.0106 [0.9082, 0.9421]
B: 0.9246 +- 0.0100 [0.9042, 0.9411]
A g'olib bo'lgan urug'lar: 6/15
farq A-B: -0.0020; juftlashgan SE 0.0013, juftlashmagan SE 0.0038
farq sezilarli emas -> qoida: arzonroq A
=== 3. Tarqoqlik manbalari: faqat bittasini o'zgartirib ===
faqat bo'lish urug'i: std 0.0097
faqat model urug'i: std 0.0033
asosiy tarqoqlik manbai: bo'lish urug'i
=== 4. 'Eng yaxshi urug'' ni hisobot qilish ===
B ning eng yaxshi urug'i 10: 0.9411; o'rtacha 0.9246; optimizm +0.0165 (1.6 std)
shu urug'da B ning A dan ustunligi: -0.0011; o'rtacha ustunlik +0.0020
hisobotga: o'rtacha +- std (15 urug'), juftlashgan farq va SENima ko'rsatdi: 2.1, 2.7-bo'limlar. Har yurish bit darajasida takrorlanadi (urug' 0 dagi A qayta hisoblanganda aynan teng) — 1-daraja bajarildi. Lekin birinchi to'rtta urug'ning o'zida g'olib almashib turdi: bitta urug' bilan qilingan xulosa tangaga o'xshaydi. 15 urug'da ikkala model ham taxminan 0.01 std ga ega, A 15 tadan 6 tasida g'olib, juftlashgan farq 2*SE dan kichik — sezilarli emas, shuning uchun qoida arzonroq A ni tanlaydi.
E'tibor bering: juftlashgan SE juftlashmaganidan taxminan uch marta kichik — chunki bir xil urug'da ikkala model bir xil bo'linishda baholanadi va bo'linish shovqini farqda qisqaradi. 3-qism buning sababini ko'rsatdi: tarqoqlikning asosiy manbai model urug'i emas, bo'lish urug'i (qaysi qatorlar testga tushgani). Nihoyat, "eng yaxshi urug'" ni hisobot qilish B ni o'rtachasidan +0.0165 ga (1.6 std) optimistik ko'rsatadi — o'sha urug'da esa B A dan hatto biroz yomon (-0.0011).
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
| "Urug' qo'ydim — natija takrorlanadi" | 1-misolda urug'li skript set tartibi tufayli olti jarayonda olti xil natija berdi |
| "Noutbukda ikki marta ishga tushirdim — bir xil" | set tartibi jarayon ichida barqaror; test yangi jarayonda qilinadi |
"random.seed hamma narsani o'rnatadi" |
numpy va torch ning o'z urug'lari bor |
"os.environ['PYTHONHASHSEED'] skript boshida yetarli" |
Interpretator allaqachon ishga tushgan — ta'sir qilmaydi |
"hash() bilan savatga ajratish barqaror" |
Satr hash i jarayonga bog'liq; hashlib ishlating |
| "Ma'lumot o'zgarmagan, chunki natija bir xil" | 3-misolda bitta qiymat o'zgardi, natija bir xil qoldi — faqat xesh ushladi |
"requirements.txt da paket nomi yetarli" |
Aniq versiya (==) va lock fayl kerak |
| "Aynan takrorlanadigan natija — ishonchli natija" | 4-misolda bitta urug'da g'olib almashib turdi |
| "Eng yaxshi urug'ni hisobot qilsa bo'ladi" | Optimistik: +0.0165 (1.6 std) |
6. Keng tarqalgan xatolar va yechimlari
1. Kategoriyani set tartibi bilan kodlash
kod = {h: i for i, h in enumerate(set(df["hudud"]))} # ⚠️ jarayonga bog'liq
kod = {h: i for i, h in enumerate(sorted(set(df["hudud"])))} # ✅2. Fayllarni tartibsiz o'qish
fayllar = os.listdir(papka) # ⚠️ OS ga bog'liq
fayllar = sorted(os.listdir(papka)) # ✅3. Faqat bitta urug'
random.seed(0) # ⚠️ numpy/torch erkin
seed_everything(0); model = RandomForestClassifier(random_state=0) # ✅4. hash() bilan savat
savat = hash(foydalanuvchi_id) % 100 # ⚠️ har jarayonda boshqa
savat = int(hashlib.md5(foydalanuvchi_id.encode()).hexdigest(), 16) % 100 # ✅5. PYTHONHASHSEED ni skript ichida o'rnatish
os.environ["PYTHONHASHSEED"] = "0" # ⚠️ kech
# PYTHONHASHSEED=0 python orgat.py (yoki Dockerfile da ENV) # ✅ jarayondan oldin6. Versiyasiz bog'liqliklar
# requirements.txt: scikit-learn # ⚠️
# requirements.txt: scikit-learn==1.9.1 (+ lock fayl) # ✅7. Manifestga barcha muhit o'zgaruvchilarini yozish
manifest["muhit"] = dict(os.environ) # ⚠️ sirlar tushadi
manifest["muhit"] = {k: os.environ.get(k) for k in RUXSAT_ROYXATI} # ✅7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 19.7-dars (o'tilgan):
random_stateshakllari, oqimlar va suzuvchi nuqta, versiyalarni qulflash - 21.11-dars (o'tilgan):
seed_everything,DataLoadergeneratori, konfig natija bilan birga - 18.3, 18.10, 18.11-darslar (o'tilgan): tarqoqlik, juftlashgan taqqoslash, "eng yaxshisini tanlash" optimizmi
- 27.3-dars: ma'lumot xeshi quvur keshining kaliti bo'ladi
- 27.4-dars: manifest har eksperiment yurishining bir qismi bo'ladi
- 27.8-dars: 1-misoldagi "yangi jarayonda reproduksiya testi" CI testiga aylanadi
- 27.9-dars: Docker muhitni OS darajasigacha muzlatadi;
PYTHONHASHSEEDniENVbilan beradi
8. Eng yaxshi amaliyotlar
Urug'larni obyektlarga bering (
random_state=int,default_rng,generator=), globalga tayanmang.Jarayonlar orasida barqaror bo'lishi kerak bo'lgan hamma narsa uchun
sortedvahashlib.Reproduksiya testini yangi jarayonlarda, turli
PYTHONHASHSEEDbilan qiling.Har yurishda manifest yozing: python, lock, git (+toza), ma'lumot va konfig xeshi, natija.
Konfig xeshini kanonik JSON dan, ma'lumot xeshini qat'iy formatdagi fayldan oling.
Qayta ishga tushirishdan oldin manifestni avtomatik tekshiring: versiya — ogohlantirish, ma'lumot/konfig — to'xtatish.
Manifestga faqat ruxsat etilgan muhit o'zgaruvchilarini yozing — sirlarni hech qachon.
Qarorni bir necha urug' bo'yicha juftlashgan farq bilan qabul qiling; eng yaxshi urug'ni hisobot qilmang.
9. Amaliy topshiriq
Vazifa 1: Bashorat qiling
1. # takrorlanuvchanlik va reproduksiya farqi?
2. # natija qaytarilmasligining olti manbasi?
3. # random.seed(0) numpy ga ta'sir qiladimi?
4. # random_state=None nima uchun mo'rt?
5. # hash("salom") ikki yangi jarayonda tengmi (PYTHONHASHSEED o'rnatilmagan)?
6. # hash(12345) chi?
7. # os.environ["PYTHONHASHSEED"] = "0" skript boshida ishlaydimi?
8. # dict tartibi xeshga bog'liqmi?
9. # manifestda nima bo'ladi?
10. # konfig xeshi uchun JSON qanday yoziladi?
11. # bir xil jadval CRLF va LF da saqlansa, fayl xeshi tengmi?
12. # tarqoqlikning asosiy manbai odatda qaysi urug'?Javoblar
- Takrorlanuvchanlik — bit darajasida bir xil natija; reproduksiya — boshqa sharoitda ham bir xil xulosa
- Urug'lar, tartib, ma'lumot, kutubxona versiyalari, float/parallel hisob, muhit
- Yo'q
- Global holatga bog'liq — orada boshqa kod tasodifiy son olsa, natija o'zgaradi
- Yo'q
- Ha —
inttuzlanmaydi - Yo'q — interpretator allaqachon ishga tushgan
- Yo'q, qo'shilish tartibiga (3.7+); lekin
setdan yasalgan dict — bog'liq - Python, platforma, paketlar, git, ruxsat etilgan muhit o'zgaruvchilari, ma'lumot va konfig xeshi, natija
sort_keys=True, qat'iyseparators- Yo'q
- Bo'lish urug'i (4-misolda)
Vazifa 2: Xatolarni tuzating
1. kod = {h: i for i, h in enumerate(set(df["hudud"]))}
2. for f in os.listdir(papka): qismlar.append(pd.read_csv(f))
3. model = RandomForestClassifier()
4. savat = hash(user_id) % 10
5. manifest["muhit"] = dict(os.environ)Javoblar
1. kod = {h: i for i, h in enumerate(sorted(set(df["hudud"])))}
2. for f in sorted(os.listdir(papka)): qismlar.append(pd.read_csv(papka / f))
3. model = RandomForestClassifier(random_state=konfig["urug"])
4. savat = int(hashlib.md5(str(user_id).encode()).hexdigest(), 16) % 10
5. manifest["muhit"] = {k: os.environ.get(k) for k in ["PYTHONHASHSEED", "OMP_NUM_THREADS"]}Vazifa 3: Reproduksiya testi
1-misolni kengaytiring:
- Yana bir manba qo'shing: skript
n_jobsga o'xshash "bo'laklarda yig'indi" hisoblaydi (masalan,X.mean(0)ninp.array_splitbilan 1 va 7 bo'lakda) — bashorat xeshi o'zgaradimi? - Reproduksiya testini funksiya qiling:
reproduksiya_testi(skript, bayroqlar, urinish=3) -> bool - Test muvaffaqiyatsiz bo'lsa, qaysi manba ekanini aniqlash uchun bayroqlarni bittalab o'zgartiradigan "bisektsiya" yozing
- Natijani JSON hisobot sifatida saqlang
Vazifa 4: Urug'lar
2-misol asosida:
DataLoader(num_workers=2, worker_init_fn=...)bilan (kichik, tezDataset) har ishchi o'z urug'ini olishini tekshiringDataset.__getitem__ichidanp.random.default_rng()(urug'siz) ishlatilsa nima bo'ladi?worker_init_fnbilan tuzatingnp.random.default_rng(s).spawn(4)ni 4 ta tajribaga bering va ularning natijasi urug' tartibiga bog'liq emasligini ko'rsatingPYTHONHASHSEEDni tekshiradigan "himoya" yozing: o'rnatilmagan bo'lsa, skript ogohlantirish chiqarsin (stdout ga)
Vazifa 5: Manifest
3-misoldagi manifestni kuchaytiring:
- Git holatini haqiqiy repozitoriyda sinang (vaqtinchalik papkada
git init, fayl qo'shish, commit) vatozabayrog'ini tekshiring - Katta fayl uchun bo'lakma-bo'lak xesh funksiyasini yozing va kichik fayldagi natija bilan solishtiring
tekshirnatijasini darajalarga ajrating:{"toxtatish": [...], "ogohlantirish": [...]}- Manifestga "bashorat namunasi" qo'shing: 5 ta kirish va ularning ehtimollari — qayta yuklangan modelda ham aynan mos kelishini tekshiring
Vazifa 6: Sezgirlik
4-misolni o'zgartiring:
- Urug'lar sonini 5, 15, 30 qilib, juftlashgan SE qanday kamayishini ko'ring
n_estimatorsni 40 dan 200 ga oshiring — model urug'i tarqoqligi qanday o'zgaradi? Bo'lish urug'iniki-chi?- Takroriy stratifikatsiyalangan K-fold 18.3-bob bilan xuddi shu taqqoslashni qiling
- Qaror funksiyasini yozing:
tanla(natijalar, narx) -> str("eng yaxshisidan sezilarli yomon bo'lmagan eng arzon")
Vazifa 7: O'ylash
Auditor sizdan so'radi: "Ikki yil oldin ishlab chiqarishga chiqarilgan kredit modelining AUC 0.812 ekanini isbotlang." Sizda model fayli (model.joblib) va o'sha paytdagi git commit bor. Nima qila olasiz, nima qila olmaysiz va bundan keyin nimani o'zgartirasiz?
Javob
Qisqa javob: model fayli va commit — yetarli emas. 0.812 ni aynan qayta olish uchun o'sha test ma'lumoti, o'sha kutubxona versiyalari va o'sha konfig kerak. Ular qayd qilinmagan bo'lsa, siz faqat taxminiy dalil bera olasiz.
1. Nima qila olasiz
| Qadam | Natija |
|---|---|
| Commit dan kodni tiklash | O'qitish va baholash mantiqi ma'lum |
model.joblib ni ochish |
Faqat mos sklearn versiyasida ishonchli 19.6-bob; boshqa versiyada ogohlantirish yoki xato |
| Versiyani topish | Commit dagi requirements.txt da pin bo'lsa — yaxshi; bo'lmasa, joblib fayl metama'lumotidan yoki CI loglaridan |
| Test ma'lumotini topish | Eng qiyin qism: ombordagi jadval ikki yilda o'zgargan bo'lishi mumkin |
# eski muhitni tiklash (izolyatsiyada)
# uv venv eski && uv pip install scikit-learn==<o'sha versiya> ...
model = joblib.load("model.joblib")
p = model.predict_proba(X_test_eski)[:, 1]
print(roc_auc_score(y_test_eski, p)) # 0.812 chiqsa - takrorlandiAgar test ma'lumotining aynan o'sha nusxasi yo'q bo'lsa, siz "bit darajasida" isbot bera olmaysiz. Qila oladiganingiz: o'sha davrga oid ma'lumotni qayta yig'ib, AUC ni ishonch oralig'i bilan hisoblash va 0.812 bu oraliqqa tushishini ko'rsatish (reproduksiya, 2.1). Farqni halol yozing.
2. Nima qila olmaysiz
- Test ma'lumoti xeshi yo'q bo'lsa — "bu o'sha ma'lumot" ekanini isbotlay olmaysiz
- Konfig (chegara, belgilar ro'yxati, urug') kodda "sehrli son" sifatida bo'lmagan bo'lsa — qaysi qiymat ishlatilgani noma'lum
list(set(...))kabi jarayonga bog'liq kod bo'lsa — aynan takrorlash mumkin bo'lmasligi mumkin
3. Bundan keyin nimani o'zgartirasiz
manifest = {
"python": ..., "paketlar": lock([...]),
"git": {"commit": ..., "toza": True}, # dirty bo'lsa - deployga ruxsat yo'q
"malumot": {"oquv_sha256": ..., "test_sha256": ...},
"konfig": ..., "konfig_xesh": ...,
"natija": {"auc": 0.812, "bashorat_xesh": ...},
"nazorat_namunasi": {"X": X_test[:5].tolist(), "p": p_test[:5].tolist()},
}- Test ma'lumotining o'zi (yoki o'zgarmas snapshot identifikatori) model bilan birga arxivlanadi — 27.3 va 27.5
- Muhit Docker obrazi sifatida saqlanadi 27.9-bob — ikki yildan keyin ham ishga tushadi
- CI da har deploydan oldin reproduksiya testi (1-misol) va manifest tekshiruvi
- Model registridagi yozuv manifestga ishora qiladi (27.5)
Auditorga javob: "Hozir bizda model va kod bor; o'sha paytdagi test ma'lumotining aynan nusxasini tiklay olsak, natijani qayta olamiz — aks holda o'sha davr ma'lumotida ishonch oralig'ini ko'rsatamiz. Bundan buyon har deployda manifest, ma'lumot snapshoti va muhit obrazi saqlanadi, shuning uchun keyingi auditda javob bir soatda tayyor bo'ladi."
Nimani mustahkamlaydi: 2.4, 2.5, 2.6-bo'limlar.
Xulosa
Bu darsda natijani qayta olish uchun nimani nazorat qilish va nimani qayd qilish kerakligini ko'rdik.
Eng muhim uch fikr:
Reproduksiya yangi jarayonda tekshiriladi —
setvahash()jim buzadi. 1-misolda urug' qo'yilgan skriptlist(set(...))tufayli olti yangi jarayonda olti xil natija berdi;sortedbilan esa hammasi bir xil bo'ldi. 2-misol mexanizmni ko'rsatdi: satr hash iPYTHONHASHSEEDga bog'liq, uni skript ichida o'rnatish kech,inthash i esa tuzlanmaydi. Uch global generator (random,numpy,torch) mustaqil; eng ishonchlisi — urug'ni obyektga berish (random_state=int,default_rng,generator=).Manifest — natijaning manzili. 3-misolda python, lock, git holati, ruxsat etilgan muhit o'zgaruvchilari, ma'lumot va kanonik konfig xeshi natija bilan birga yozildi va manifestdan qayta ishga tushirish aynan bir xil natija berdi. Tekshiruv bitta qiymat o'zgarishini ham ushladi (garchi natija o'zgarmagan bo'lsa ham), CRLF/LF farqi fayl xeshini o'zgartirishini ko'rsatdi va versiya farqini ogohlantirish, konfig farqini to'xtatish sababi sifatida ajratdi.
Aynan takrorlanadigan natija ham bitta namuna. 4-misolda har yurish bit darajasida takrorlandi, lekin bitta urug'da g'olib almashib turdi; 15 urug'da farq sezilarli bo'lmadi va qoida arzonroq modelni tanladi. Juftlashgan SE juftlashmaganidan taxminan uch marta kichik chiqdi, tarqoqlikning asosiy manbai bo'lish urug'i bo'ldi, "eng yaxshi urug'" esa natijani 1.6 std ga optimistik ko'rsatdi.
Keyingi darsda Ma'lumot quvuri: ETL va ELT, bosqichlar va ularning bog'liqlik grafi, qayta ishga tushirilganda dublikat yaratmaydigan idempotent yuklash, inkremental yuklash, ma'lumot sifati tekshiruvlari va buzilgan partiyani karantinga olish — hammasini noldan quramiz.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!