Asosiy qismga o'tish

Model formatlari: pickle xavfi va ONNX

26-bob 11 kod bloki 2124 so'z

Bu bobdagi kod shu fayllar bilan ishlaydio'z kompyuteringizda ishga tushiring

Notanish odamdan kelgan fayl

Elektron pochtangizga xat keldi. Ilovada hisobot.exe fayli bor. Yuboruvchi — notanish.

Ishga tushirasizmi?

Albatta yo'q. Har bir odam biladi: notanish .exe faylini ochish — kompyuteringizni begonaga topshirish demak.

Endi ikkinchi savol. Hamkasbingiz sizga model.pkl yubordi. Yoki internetdan tayyor model yuklab oldingiz. Uni yuklaysizmi?

Ko'pchilik hech o'ylamay yuklaydi. Chunki u "shunchaki ma'lumot fayli" ko'rinadi.

Bu noto'g'ri. pickle fayli — ma'lumot emas, u ko'rsatmalar ro'yxati. Uni yuklash .exe ishga tushirish bilan bir xil xavfga ega.

Shu bobda buni isbotlaymiz: hech qachon chaqirmagan funksiyangiz pickle.loads() paytida o'zi bajariladi. Ekranda uning izi qoladi.

pickle — bu kod, ma'lumot emas

Python da model saqlashning odatiy usuli — pickle yoki joblib. Ikkalasi ham bir xil formatda ishlaydi va ikkalasining ham bir xil xavfi bor.

Asosiy fakt: pickle.load() kod bajaradi. U ma'lumotni o'qimaydi — u obyektni qayta quradi, va qayta qurish jarayoni istalgan funksiyani chaqirishi mumkin.

Buni xavfsiz ko'rsatamiz — zararli emas, shunchaki kutilmagan amal:

python13 qator
import pickle

class ZararsizKorinadi:
    """Fayl ochilganda __reduce__ chaqiriladi va u ISTALGAN narsani qaytarishi mumkin."""
    def __reduce__(self):
        # zararli emas: shunchaki matn qaytaradigan funksiya chaqiriladi
        return (str.upper, ("men yuklanishda bajarildim",))

xom = pickle.dumps(ZararsizKorinadi())
print("fayl hajmi:", len(xom), "bayt")
print("yuklash natijasi:", pickle.loads(xom))
print()
print("=> pickle.loads() FUNKSIYA CHAQIRDI. Biz uni chaqirmadik.")
Chiqish
fayl hajmi: 99 bayt
yuklash natijasi: MEN YUKLANISHDA BAJARILDIM

=> pickle.loads() FUNKSIYA CHAQIRDI. Biz uni chaqirmadik.

str.upper o'rniga os.system yoki subprocess.run bo'lishi mumkin edi. Fayl ochilishi bilanoq.

Shuning uchun qoida qat'iy:

python11 qator
import pandas as pd
show(pd.DataFrame([
    {"holat": "O'z modelingiz, o'z serveringiz", "pickle xavfsizmi": "HA",
     "izoh": "Manbaga ishonasiz"},
    {"holat": "Hamkasb yuborgan model", "pickle xavfsizmi": "EHTIYOT",
     "izoh": "Uning mashinasi buzilgan bo'lishi mumkin"},
    {"holat": "Internetdan yuklangan model", "pickle xavfsizmi": "YO'Q",
     "izoh": "Hech qachon. ONNX yoki safetensors ishlating"},
    {"holat": "Foydalanuvchi yuklagan fayl", "pickle xavfsizmi": "YO'Q",
     "izoh": "To'g'ridan-to'g'ri masofaviy kod bajarish"},
]))
holatpickle xavfsizmiizoh
O'z modelingiz, o'z serveringizHAManbaga ishonasiz
Hamkasb yuborgan modelEHTIYOTUning mashinasi buzilgan bo'lishi mumkin
Internetdan yuklangan modelYO'QHech qachon. ONNX yoki safetensors ishlating
Foydalanuvchi yuklagan faylYO'QTo'g'ridan-to'g'ri masofaviy kod bajarish

22-bobdagi skops_trusted_types talabini eslang — MLflow aynan shu muammoni yumshatish uchun qo'shgan.

Uch xil format

python17 qator
diagramma(
    {
        "joblib / pickle": (0, 0, 2.8, 1.1, "xizmat"),
        "ONNX": (3.6, 0, 2.8, 1.1, "saqlash"),
        "safetensors": (7.2, 0, 2.8, 1.1, "saqlash"),
        "Kod bajaradi\n(XAVF)": (0, 2.2, 2.8, 1.0, "oddiy"),
        "Faqat graf\n(xavfsiz)": (3.6, 2.2, 2.8, 1.0, "oddiy"),
        "Faqat vaznlar\n(xavfsiz)": (7.2, 2.2, 2.8, 1.0, "oddiy"),
    },
    [
        ("joblib / pickle", "Kod bajaradi\n(XAVF)"),
        ("ONNX", "Faqat graf\n(xavfsiz)"),
        ("safetensors", "Faqat vaznlar\n(xavfsiz)"),
    ],
    nom="model_formatlari.png", kenglik=12,
    izoh="ONNX va safetensors kod saqlamaydi — shuning uchun yuklash xavfsiz.",
)

ONNX va safetensors kod saqlamaydi — shuning uchun yuklash xavfsiz.

ONNX va safetensors kod saqlamaydi — shuning uchun yuklash xavfsiz.

Format Nima saqlaydi Xavfsizmi Til
pickle / joblib Python obyekti + kod Yo'q Faqat Python
ONNX Hisoblash grafi Ha C++, Java, Go, JS...
safetensors Faqat tensorlar Ha Asosan chuqur o'rganish
PMML XML modeli Ha Eski, cheklangan

Modelni ONNX ga o'tkazamiz

shu faylni yaratingsrc/quvur/format.py

python31 qator
"""Model formatlari: joblib va ONNX o'rtasida o'tkazish."""
from pathlib import Path
from typing import Any

import numpy as np


def onnx_ga(model, xususiyat_soni: int, yol: str | Path) -> Path:
    """sklearn modelini ONNX ga o'tkazadi."""
    from skl2onnx import to_onnx
    namuna = np.zeros((1, xususiyat_soni), dtype=np.float32)
    onx = to_onnx(model, namuna, options={id(model): {"zipmap": False}})
    p = Path(yol)
    p.write_bytes(onx.SerializeToString())
    return p


def onnx_sessiya(yol: str | Path):
    """ONNX modelini yuklaydi. Hech qanday kod bajarilmaydi."""
    import onnxruntime as ort
    s = ort.InferenceSession(str(yol), providers=["CPUExecutionProvider"])
    return s


def onnx_ehtimol(sessiya, X) -> np.ndarray:
    """Broker ehtimolini qaytaradi."""
    kirish = sessiya.get_inputs()[0].name
    chiqish = [o.name for o in sessiya.get_outputs()]
    natija = sessiya.run(chiqish, {kirish: np.asarray(X, dtype=np.float32)})
    ehtimollar = natija[-1]
    return np.asarray(ehtimollar)[:, 1]

Avval model borligiga ishonch hosil qilamiz (bu bobni alohida o'qiyotgan bo'lsangiz ham ishlashi uchun):

python17 qator
python3 -c "
import sys, pathlib; sys.path.insert(0, 'src')
if not pathlib.Path('sinov_model.joblib').exists():
    import joblib, pandas as pd
    from sklearn.ensemble import RandomForestClassifier
    from sklearn.model_selection import train_test_split
    from quvur.xususiyat import tayyorla
    d = pd.read_csv('../datasets/ijara.csv')
    x, y = tayyorla(d), d['broker']
    Xtr, _, ytr, _ = train_test_split(x, y, test_size=0.25, stratify=y, random_state=42)
    m = RandomForestClassifier(n_estimators=150, min_samples_leaf=3,
                               random_state=42, n_jobs=1).fit(Xtr, ytr)
    joblib.dump(m, 'sinov_model.joblib')
    print('model yaratildi (25-bobdagidek)')
else:
    print('model allaqachon bor')
"
Chiqish (qaytish kodi 0, 2029 ms)
model yaratildi (25-bobdagidek)
python11 qator
python3 -c "
import sys; sys.path.insert(0, 'src')
import joblib, pandas as pd
from quvur.format import onnx_ga
from quvur.xususiyat import XUSUSIYATLAR

m = joblib.load('sinov_model.joblib')
p = onnx_ga(m, len(XUSUSIYATLAR), 'model.onnx')
print('ONNX yozildi:', p.name, p.stat().st_size // 1024, 'KB')
print('joblib       :', __import__('pathlib').Path('sinov_model.joblib').stat().st_size // 1024, 'KB')
"
Chiqish (qaytish kodi 0, 2069 ms)
ONNX yozildi: model.onnx 6970 KB
joblib       : 14368 KB

Natijalar bir xilmi

Format o'zgarganda bashorat o'zgarmasligi kerak. Buni tekshirish majburiy:

python19 qator
python3 -c "
import sys; sys.path.insert(0, 'src')
import joblib, numpy as np, pandas as pd
from quvur.format import onnx_sessiya, onnx_ehtimol
from quvur.xususiyat import tayyorla

d = pd.read_csv('../datasets/ijara.csv').head(500)
x = tayyorla(d)

m = joblib.load('sinov_model.joblib')
sk = m.predict_proba(x)[:, 1]
s = onnx_sessiya('model.onnx')
on = onnx_ehtimol(s, x.values)

farq = np.abs(sk - on)
print(f'eng katta farq : {farq.max():.2e}')
print(f'o\'rtacha farq  : {farq.mean():.2e}')
print(f'bir xil qaror  : {int((( sk>0.5)==(on>0.5)).sum())} / {len(sk)}')
"
Chiqish (qaytish kodi 0, 1179 ms)
eng katta farq : 4.61e-07
o'rtacha farq  : 1.07e-07
bir xil qaror  : 500 / 500
2026-09-29 19:03:26.773363602 [W:onnxruntime:Default, device_discovery.cc:164 DiscoverDevicesForPlatform] GPU device discovery failed: device_discovery.cc:89 ReadFileContents Failed to open file: "/sys/class/drm/card0/device/vendor"

Farq 10⁻⁶ tartibida — bu float32 ga o'tkazish natijasi (2-bobdagi suzuvchi nuqta mavzusi). Qarorlar esa to'liq bir xil.

O'tkazishdan keyin ALBATTA solishtiring

Format o'tkazish jim ravishda buzilishi mumkin: zipmap sozlamasi, tur aylantirish, qo'llab quvvatlanmaydigan operator. Model yuklanadi va javob qaytaradi — lekin boshqa javob.

Har o'tkazishdan keyin yuzlab namunada predict_proba ni solishtiring va qarorlar mosligini tekshiring. Buni testga aylantiring (25-bob).

Tezlik: ONNX nima uchun ishlatiladi

Xavfsizlik — ONNX ga o'tishning bitta sababi. Ikkinchisi tezlik, va u kutilmagan joyda ko'rinadi.

Savol: sklearn modeli bitta qatorni bashorat qilish uchun nima qiladi?

U predict_proba() ni chaqiradi. U esa ichida: kirishni tekshiradi, numpy massivga aylantiradi, 150 ta daraxtni Python sikli bilan aylanib chiqadi, natijalarni yig'adi, pandas ga qaytaradi.

Bu ishlarning ko'pi bitta qator uchun ortiqcha. Ular minglab qator uchun mo'ljallangan — o'shanda tayyorgarlik xarajati butun to'plamga taqsimlanadi.

Lekin veb-servisda sizga bitta qator keladi. Har so'rovda. Va har safar o'sha tayyorgarlik qaytadan bajariladi.

ONNX esa boshqacha ishlaydi: model oldindan oddiy matematik amallar grafigiga aylantirilgan va C++ da bajariladi. Python sikli yo'q, pandas yo'q, tekshiruv yo'q.

Farqni o'lchaymiz — bitta qator uchun va katta to'plam uchun alohida.

python26 qator
python3 -c "
import sys, time; sys.path.insert(0, 'src')
import joblib, numpy as np, pandas as pd
from quvur.format import onnx_sessiya, onnx_ehtimol
from quvur.xususiyat import tayyorla

d = pd.read_csv('../datasets/ijara.csv')
x = tayyorla(d)
m = joblib.load('sinov_model.joblib')
s = onnx_sessiya('model.onnx')
bitta = x.head(1); bitta_np = bitta.values.astype('float32')

def olch(f, n):
    f(); t0 = time.perf_counter()
    for _ in range(n): f()
    return (time.perf_counter() - t0) / n * 1000

print('BITTA qator (xizmat holati):')
print(f'  sklearn : {olch(lambda: m.predict_proba(bitta), 200):.3f} ms')
print(f'  ONNX    : {olch(lambda: onnx_ehtimol(s, bitta_np), 200):.3f} ms')
print()
toplam = x.head(2000); toplam_np = toplam.values.astype('float32')
print('2000 qator (to\'plam holati):')
print(f'  sklearn : {olch(lambda: m.predict_proba(toplam), 20):.2f} ms')
print(f'  ONNX    : {olch(lambda: onnx_ehtimol(s, toplam_np), 20):.2f} ms')
"
Chiqish (qaytish kodi 0, 2403 ms)
BITTA qator (xizmat holati):
  sklearn : 2.721 ms
  ONNX    : 0.006 ms

2000 qator (to'plam holati):
  sklearn : 21.30 ms
  ONNX    : 3.91 ms
2026-09-29 19:03:28.128867557 [W:onnxruntime:Default, device_discovery.cc:164 DiscoverDevicesForPlatform] GPU device discovery failed: device_discovery.cc:89 ReadFileContents Failed to open file: "/sys/class/drm/card0/device/vendor"

Bitta qatorda farq eng katta — va aynan shu xizmat holati (27-bob): foydalanuvchi bitta e'lon yuboradi, javob kerak.

Sababi: sklearn har chaqiruvda pandas/numpy tekshiruvlarini bajaradi. ONNX esa oldindan tuzilgan grafni bajaradi, ortiqcha qatlamsiz.

Yuklash vaqti

Server ishga tushganda model yuklanadi. Bu vaqt ham muhim:

python14 qator
python3 -c "
import sys, time; sys.path.insert(0, 'src')
sys.path.insert(0, 'src')

def olch(f, n=8):
    f(); t0 = time.perf_counter()
    for _ in range(n): f()
    return (time.perf_counter() - t0) / n * 1000

import joblib
from quvur.format import onnx_sessiya
print(f'joblib yuklash : {olch(lambda: joblib.load(\"sinov_model.joblib\")):.1f} ms')
print(f'ONNX yuklash   : {olch(lambda: onnx_sessiya(\"model.onnx\")):.1f} ms')
"
Chiqish (qaytish kodi 0, 4301 ms)
joblib yuklash : 292.9 ms
ONNX yuklash   : 80.9 ms
2026-09-29 19:03:32.858970678 [W:onnxruntime:Default, device_discovery.cc:164 DiscoverDevicesForPlatform] GPU device discovery failed: device_discovery.cc:89 ReadFileContents Failed to open file: "/sys/class/drm/card0/device/vendor"

Qaysi formatni qachon

python9 qator
import pandas as pd
show(pd.DataFrame([
    {"holat": "Tajriba, lokal ish", "format": "joblib", "sabab": "Eng oson, sklearn obyekti saqlanadi"},
    {"holat": "Xizmat (inference)", "format": "ONNX", "sabab": "Tez, xavfsiz, tildan mustaqil"},
    {"holat": "Model boshqa jamoaga beriladi", "format": "ONNX", "sabab": "Ular Python ishlatmasligi mumkin"},
    {"holat": "Chuqur o'rganish vaznlari", "format": "safetensors", "sabab": "Tez va xavfsiz yuklash"},
    {"holat": "Telefon/brauzerda ishlash", "format": "ONNX", "sabab": "onnxruntime-web, mobil qo'llab-quvvatlash"},
    {"holat": "Modelni qayta o'qitish kerak", "format": "joblib", "sabab": "ONNX faqat inference uchun"},
]))
holatformatsabab
Tajriba, lokal ishjoblibEng oson, sklearn obyekti saqlanadi
Xizmat (inference)ONNXTez, xavfsiz, tildan mustaqil
Model boshqa jamoaga beriladiONNXUlar Python ishlatmasligi mumkin
Chuqur o'rganish vaznlarisafetensorsTez va xavfsiz yuklash
Telefon/brauzerda ishlashONNXonnxruntime-web, mobil qo'llab-quvvatlash
Modelni qayta o'qitish kerakjoblibONNX faqat inference uchun

Oxirgi qator muhim: ONNX ni qayta o'qitib bo'lmaydi. U faqat oldinga yurish (inference) grafini saqlaydi. Shuning uchun amalda ikkalasi saqlanadi: joblib — o'qitish uchun, ONNX — xizmat uchun.

Xususiyat kodini birga qadoqlash

23-bobda aytilgan muammo: model xususiyat tayyorlash kodisiz ma'nosiz. ONNX buni qisman yechadi — tayyorlashni Pipeline ga qo'shsangiz, u ham grafga tushadi:

python23 qator
python3 -c "
import sys; sys.path.insert(0, 'src')
import numpy as np, pandas as pd
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from quvur.format import onnx_ga, onnx_sessiya, onnx_ehtimol
from quvur.xususiyat import XUSUSIYATLAR, tayyorla

d = pd.read_csv('../datasets/ijara.csv')
x, y = tayyorla(d), d['broker']
Xtr, Xte, ytr, yte = train_test_split(x, y, test_size=0.25, stratify=y, random_state=42)
kv = make_pipeline(StandardScaler(), LogisticRegression(max_iter=2000, random_state=42)).fit(Xtr, ytr)

onnx_ga(kv, len(XUSUSIYATLAR), 'quvur.onnx')
s = onnx_sessiya('quvur.onnx')
a = kv.predict_proba(Xte.head(200))[:, 1]
b = onnx_ehtimol(s, Xte.head(200).values)
print('Pipeline (scaler + model) ONNX ga tushdi')
print(f'eng katta farq: {np.abs(a-b).max():.2e}')
print('=> StandardScaler ham grafga kirdi — xizmatda alohida kod kerak emas')
"
Chiqish (qaytish kodi 0, 1068 ms)
Pipeline (scaler + model) ONNX ga tushdi
eng katta farq: 7.55e-08
=> StandardScaler ham grafga kirdi — xizmatda alohida kod kerak emas
2026-09-29 19:03:34.657746020 [W:onnxruntime:Default, device_discovery.cc:164 DiscoverDevicesForPlatform] GPU device discovery failed: device_discovery.cc:89 ReadFileContents Failed to open file: "/sys/class/drm/card0/device/vendor"

StandardScaler ning o'rtacha va standart og'ishi ONNX fayl ichida saqlandi. Bu 3-bobdagi o'qitish/xizmat nomuvofiqligini kamaytiradi: normallashtirish kodi endi model bilan birga yuradi.

Xulosa

Tushuncha Asosiy fikr
pickle xavfi loads() kod bajaradi — ishonchsiz fayl ochmang
joblib ham shunday Bir xil format, bir xil xavf
ONNX Hisoblash grafi; kod yo'q, shuning uchun xavfsiz
Tildan mustaqil C++, Java, Go, brauzer — Python shart emas
Solishtirish majburiy O'tkazish jim buzilishi mumkin — yuzlab namunada tekshiring
Tezlik Farq bitta qatorda eng katta — bu aynan xizmat holati
ONNX cheklovi Qayta o'qitib bo'lmaydi; ikkala format saqlanadi
Pipeline Scaler ham grafga tushadi — nomuvofiqlik kamayadi

Talaba ishi

  1. onnx_ga ga zipmap: True bering (sukut qiymat). Chiqish shakli qanday o'zgaradi va nega biz uni False qildik?
  2. ONNX va joblib natijalarini solishtiradigan test yozing (25-bob uslubida). Qancha namuna yetarli deb hisoblaysiz?
  3. RandomForest o'rniga GradientBoosting ni ONNX ga o'tkazing. Hajm va tezlik qanday farq qiladi?
  4. ONNX faylni matn muharririda oching. Nima ko'rinadi? Bu nima uchun pickle dan xavfsizroq ekanini tushuntiradimi?

Bu bobni bepul o'qidingiz.

Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.

Telegramda yozish Narxlar