Asosiy qismga o'tish

Drift va qayta o'qitish: taksichini qachon o'qitish kerak

35-bob 7 kod bloki 2013 so'z

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

Grafik siljidi. Endi nima?

34-bobda bashorat taqsimotini o'lchadik. Model ishonchli edi: javoblarning ko'pi chekkalarda, o'rtada kam.

Bir oy o'tdi. Grafik o'zgardi — endi javoblar o'rtaga to'plana boshladi.

Monitoring o'z ishini qildi: u siljishni ko'rsatdi. Lekin u sizga nima qilishni aytmaydi.

Uch savol qoladi:

  1. Bu haqiqiy o'zgarishmi yoki tasodifmi?
  2. Nima o'zgardi — ma'lumotmi yoki dunyoning o'zimi?
  3. Qayta o'qitish yordam beradimi?

Uchinchisi eng muhim va eng ko'p noto'g'ri javob beriladi. Ko'pchilik "model eskirdi — qayta o'qitamiz" deydi. Ba'zan bu foydasiz.

Ikki xil drift

Farqni tushunish uchun taksichiga qaytamiz.

Birinchi holat. Shahar o'zgarmadi, lekin siz uni boshqa mahallalarga yubora boshladingiz. U eski hududni yaxshi biladi, yangisini bilmaydi.

Bu — ma'lumot drifti (data drift). Dunyo o'sha, lekin kiruvchi ma'lumot boshqa.

Yechim oddiy: unga yangi mahallalarni ko'rsating. Ya'ni qayta o'qitish yordam beradi.

Ikkinchi holat. Hudud o'sha, lekin ko'chalar o'zgardi — bir tomonlama bo'ldi, yangi yo'l ochildi.

Bu — tushuncha drifti (concept drift). Kirish bir xil, lekin to'g'ri javob o'zgardi.

Yechim ham qayta o'qitish, lekin yangi ma'lumot bilan — eski yozuvlar endi noto'g'ri javobni o'rgatadi.

python9 qator
import pandas as pd
show(pd.DataFrame([
    {"Turi": "Ma'lumot drifti", "Nima o'zgardi": "Kiruvchi ma'lumot taqsimoti",
     "Yorliqsiz seziladimi": "**Ha**", "Qayta o'qitish yordam beradimi": "Odatda ha"},
    {"Turi": "Tushuncha drifti", "Nima o'zgardi": "Kirish va javob orasidagi bog'liqlik",
     "Yorliqsiz seziladimi": "Qiyin", "Qayta o'qitish yordam beradimi": "Yangi yorliq kerak"},
    {"Turi": "Xususiyat buzilishi", "Nima o'zgardi": "Quvur (3, 6-bob)",
     "Yorliqsiz seziladimi": "Ha — keskin sakrash", "Qayta o'qitish yordam beradimi": "**Yo'q — tuzatish kerak**"},
]))
TuriNima o'zgardiYorliqsiz seziladimiQayta o'qitish yordam beradimi
Ma'lumot driftiKiruvchi ma'lumot taqsimoti**Ha**Odatda ha
Tushuncha driftiKirish va javob orasidagi bog'liqlikQiyinYangi yorliq kerak
Xususiyat buzilishiQuvur (3, 6-bob)Ha — keskin sakrash**Yo'q — tuzatish kerak**

Uchinchi qatorga diqqat. Agar xususiyat quvuri buzilgan bo'lsa (3-bobdagi skew, 6-bobdagi buzuq ma'lumot), qayta o'qitish zarar keltiradi: siz buzuq ma'lumotni modelga o'rgatasiz.

Shuning uchun birinchi savol har doim: bu drift mi yoki nosozlikmi?

Yorliqsiz aniqlash

Eng katta qiyinchilik shu: ishlab chiqarishda to'g'ri javob yo'q (1-bob). Brokerni belgilaganingizdan keyin hech kim kelib "to'g'ri" yoki "xato" demaydi.

Demak F1 ni hisoblab bo'lmaydi. Nimani o'lchash mumkin?

Kirish taqsimotini. U yorliqsiz ham o'lchanadi.

Shu bobda haqiqiy drift bilan ishlaymiz — ijara_yangi.csv faylida brokerlar xulqini o'zgartirgan.

python15 qator
python3 -c "
import pandas as pd
import numpy as np

eski = pd.read_csv('../datasets/ijara.csv')
yangi = pd.read_csv('../datasets/ijara_yangi.csv')
print(f'eski: {len(eski):,} qator | yangi: {len(yangi):,} qator')
print()
USTUN = ['tavsif_uzunligi','rasmlar_soni','telefon_takrorlanishi','kunlik_elonlar','narx_mln']
print(f'{\"ustun\":22s} {\"eski ort\":>10s} {\"yangi ort\":>10s} {\"farq %\":>9s}')
print('-' * 55)
for u in USTUN:
    a, b = eski[u].mean(), yangi[u].mean()
    print(f'{u:22s} {a:>10.2f} {b:>10.2f} {(b-a)/a*100:>8.1f}%')
"
Chiqish (qaytish kodi 0, 304 ms)
eski: 12,000 qator | yangi: 4,000 qator

ustun                    eski ort  yangi ort    farq %
-------------------------------------------------------
tavsif_uzunligi            316.74     364.20     15.0%
rasmlar_soni                 7.20       8.22     14.2%
telefon_takrorlanishi        3.54       3.69      4.2%
kunlik_elonlar               2.34       2.45      4.9%
narx_mln                     5.72       5.67     -0.9%

Ba'zi ustunlar sezilarli o'zgargan. Lekin "o'rtacha o'zgardi" — bu hali dalil emas. Bizga raqam kerak: qanchalik o'zgardi va bu muhimmi?

PSI: siljish ko'rsatkichi

Eng keng tarqalgan o'lchov — PSI (Population Stability Index).

G'oyasi oddiy: ustunni oraliqlarga bo'lamiz va har oraliqda ulush qanchalik o'zgarganini hisoblaymiz.

shu faylni yaratingsrc/quvur/drift.py

python41 qator
"""Taqsimot siljishini o'lchash — yorliqsiz."""
import numpy as np
import pandas as pd

# sanoatda qabul qilingan chegaralar
KICHIK, KATTA = 0.10, 0.25


def psi(asos: pd.Series, yangi: pd.Series, paqir: int = 10) -> float:
    """Population Stability Index.

    < 0.10  - siljish yo'q
    < 0.25  - o'rtacha siljish, kuzating
    >= 0.25 - katta siljish, harakat kerak
    """
    chegara = np.quantile(asos, np.linspace(0, 1, paqir + 1))
    chegara[0], chegara[-1] = -np.inf, np.inf
    a, _ = np.histogram(asos, bins=chegara)
    b, _ = np.histogram(yangi, bins=chegara)
    # nol bo'lmasin - logarifm uchun
    a = np.clip(a / max(a.sum(), 1), 1e-6, None)
    b = np.clip(b / max(b.sum(), 1), 1e-6, None)
    return float(np.sum((b - a) * np.log(b / a)))


def baho(qiymat: float) -> str:
    if qiymat < KICHIK:
        return "barqaror"
    if qiymat < KATTA:
        return "o'rtacha"
    return "KATTA SILJISH"


def hisobot(asos: pd.DataFrame, yangi: pd.DataFrame,
            ustunlar: list[str]) -> pd.DataFrame:
    qatorlar = [
        {"ustun": u, "psi": round(psi(asos[u], yangi[u]), 4),
         "baho": baho(psi(asos[u], yangi[u]))}
        for u in ustunlar
    ]
    return pd.DataFrame(qatorlar).sort_values("psi", ascending=False)
python12 qator
python3 -c "
import sys; sys.path.insert(0, 'src')
import pandas as pd
from quvur.drift import hisobot

eski = pd.read_csv('../datasets/ijara.csv')
yangi = pd.read_csv('../datasets/ijara_yangi.csv')
USTUN = ['tavsif_uzunligi','rasmlar_soni','telefon_takrorlanishi','kunlik_elonlar',
         'narx_mln','maydon','xonalar','egasi_tasdiqlangan']
h = hisobot(eski, yangi, USTUN)
print(h.to_string(index=False))
"
Chiqish (qaytish kodi 0, 405 ms)
                ustun    psi     baho
         rasmlar_soni 0.2251 o'rtacha
      tavsif_uzunligi 0.1262 o'rtacha
telefon_takrorlanishi 0.0089 barqaror
       kunlik_elonlar 0.0075 barqaror
             narx_mln 0.0046 barqaror
               maydon 0.0031 barqaror
              xonalar 0.0012 barqaror
   egasi_tasdiqlangan 0.0009 barqaror

Jadval qaysi ustun siljiganini aniq ko'rsatadi. Bu 34-bobdagi grafikdan ko'ra foydaliroq: grafik "nimadir o'zgardi" deydi, PSI esa nima o'zgarganini aytadi.

PSI chegaralari qonun emas

0.10 va 0.25 — sanoatda keng ishlatiladigan raqamlar, lekin ular hech qanday teoremadan kelib chiqmaydi. Ular kredit skoring sohasidagi tajribadan olingan.

Sizning ma'lumotingizda ular noto'g'ri bo'lishi mumkin:

  • Mavsumiy ma'lumotda (dekabr va iyul) PSI tabiiy ravishda yuqori bo'ladi — bu drift emas.
  • Kichik namunada PSI shovqindan ham oshib ketadi.

To'g'ri yondashuv: bir necha oy davomida normal PSI ni o'lchang va o'z chegarangizni shunga qarab qo'ying. 12-bobdagi partition masalasi bilan bir xil qoida — taxmin qilmang, o'lchang.

Drift natijaga ta'sir qildimi

PSI kirish o'zgarganini ko'rsatdi. Lekin model yomonlashdimi?

Bu bobda bizda maxsus imkoniyat bor: sinov ma'lumotida yorliq bor, shuning uchun haqiqatni ko'rsatib bera olamiz. Ishlab chiqarishda bu bo'lmaydi.

python28 qator
python3 -c "
import sys; sys.path.insert(0, 'src')
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import f1_score, precision_score, recall_score
from sklearn.model_selection import train_test_split
from quvur.xususiyat import tayyorla

eski = pd.read_csv('../datasets/ijara.csv')
yangi = pd.read_csv('../datasets/ijara_yangi.csv')

x, y = tayyorla(eski), eski['broker']
Xtr, Xte, ytr, yte = 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)

def baho(mod, X, Y):
    p = mod.predict(X)
    return f1_score(Y,p), precision_score(Y,p), recall_score(Y,p)

a = baho(m, Xte, yte)
b = baho(m, tayyorla(yangi), yangi['broker'])
print(f'{\"davr\":14s} {\"F1\":>8s} {\"aniqlik\":>9s} {\"toliqlik\":>9s}')
print('-' * 44)
print(f'{\"eski (sinov)\":14s} {a[0]:>8.3f} {a[1]:>9.3f} {a[2]:>9.3f}')
print(f'{\"yangi\":14s} {b[0]:>8.3f} {b[1]:>9.3f} {b[2]:>9.3f}')
print()
print(f'F1 tushishi: {a[0]-b[0]:.3f}')
"
Chiqish (qaytish kodi 0, 1983 ms)
davr                 F1   aniqlik  toliqlik
--------------------------------------------
eski (sinov)      0.860     0.893     0.828
yangi             0.664     0.883     0.533

F1 tushishi: 0.195

1-bobdagi raqam qaytdi. Va endi biz uning sababini bilamiz: PSI jadvalidagi siljigan ustunlar.

Diqqat: aniqlik deyarli o'zgarmadi, to'liqlik qulab tushdi. Ya'ni model "broker" deganda hali ham haq — lekin brokerlarning ko'pini ko'rmayapti.

Bu mantiqiy: brokerlar xulqini o'zgartirib, egalarga o'xshab qolishgan.

Qayta o'qitish yordam beradimi

Endi eng muhim savol. Yangi ma'lumotda qayta o'qitamiz va tekshiramiz.

python36 qator
python3 -c "
import sys; sys.path.insert(0, 'src')
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import f1_score
from sklearn.model_selection import train_test_split
from quvur.xususiyat import tayyorla

eski = pd.read_csv('../datasets/ijara.csv')
yangi = pd.read_csv('../datasets/ijara_yangi.csv')

Xy, yy = tayyorla(yangi), yangi['broker']
Xtr_y, Xte_y, ytr_y, yte_y = train_test_split(Xy, yy, test_size=0.25, stratify=yy, random_state=42)

# 1) ESKI model, yangi sinovda
xe, ye = tayyorla(eski), eski['broker']
Xtr_e, _, ytr_e, _ = train_test_split(xe, ye, test_size=0.25, stratify=ye, random_state=42)
eski_m = RandomForestClassifier(n_estimators=150, min_samples_leaf=3, random_state=42, n_jobs=1).fit(Xtr_e, ytr_e)
f_eski = f1_score(yte_y, eski_m.predict(Xte_y))

# 2) FAQAT yangi malumotda oqitilgan
yangi_m = RandomForestClassifier(n_estimators=150, min_samples_leaf=3, random_state=42, n_jobs=1).fit(Xtr_y, ytr_y)
f_yangi = f1_score(yte_y, yangi_m.predict(Xte_y))

# 3) IKKALASI birga
Xb = pd.concat([Xtr_e, Xtr_y]); yb = pd.concat([ytr_e, ytr_y])
birga_m = RandomForestClassifier(n_estimators=150, min_samples_leaf=3, random_state=42, n_jobs=1).fit(Xb, yb)
f_birga = f1_score(yte_y, birga_m.predict(Xte_y))

print('YANGI sinov toplamida F1:')
print(f'  eski model          : {f_eski:.3f}')
print(f'  faqat yangida oqitildi: {f_yangi:.3f}')
print(f'  ikkalasida oqitildi : {f_birga:.3f}')
print()
print(f'qayta oqitish foydasi: +{f_yangi-f_eski:.3f}')
"
Chiqish (qaytish kodi 0, 3709 ms)
YANGI sinov toplamida F1:
  eski model          : 0.632
  faqat yangida oqitildi: 0.822
  ikkalasida oqitildi : 0.774

qayta oqitish foydasi: +0.189

Qayta o'qitish yordam berdi — va bu ma'lumot drifti ekanini tasdiqlaydi. Agar tushuncha drifti bo'lganda, foyda ancha kam bo'lardi.

Uchinchi qatorga ham qarang: eski va yangi ma'lumotni birga ishlatish har doim ham eng yaxshi emas. Eski yozuvlar endi eskirgan bo'lsa, ular modelni orqaga tortadi.

Jadval bo'yicha qayta o'qitish — odat, strategiya emas

"Har hafta dushanba kuni qayta o'qitamiz" — keng tarqalgan yondashuv va u ikki tomondan noto'g'ri.

Kech qoladi. Chorshanba kuni manba buzilsa, siz olti kun yomon model bilan ishlaysiz.

Erta ham bo'ladi. Hech narsa o'zgarmagan bo'lsa, siz har hafta hisob-kitob qilasiz, yangi model chiqarasiz, xavf olasiz — hech qanday foyda uchun.

To'g'ri yondashuv — sabab bilan qayta o'qitish:

Signal Harakat
PSI > chegara Tekshirish, keyin qayta o'qitish
Bashorat taqsimoti siljidi (34-bob) Tekshirish
Yangi yorliq to'plandi Qayta o'qitishga arziydi
Hech narsa o'zgarmadi Tegmang

Jadval faqat zaxira sifatida qoladi: "kamida oyiga bir marta, agar boshqa sabab bo'lmasa".

Avtomatik qayta o'qitish xavfi

Drift sezildi → quvur ishga tushdi → yangi model chiqdi. Chiroyli ko'rinadi.

Lekin 6 va 31-boblarni eslang: agar drift aslida buzilgan ma'lumot bo'lsa?

Unda siz buzuq ma'lumotda o'qitasiz va yomon modelni avtomatik chiqarasiz. Drift aniqlash tizimi endi zararga ishlayapti.

Shuning uchun avtomatik qayta o'qitishda uch to'siq bo'lishi shart:

  1. Ma'lumot sifati (6-bob) — kirish qoidaga mos bo'lsin;
  2. Sifat darvozasi (24, 31-bob) — yangi model chegaradan past bo'lmasin;
  3. Shadow yoki canary (33-bob) — to'g'ridan 100% ga chiqmasin.

Uchalasi bo'lsa, avtomatik qayta o'qitish xavfsiz. Bittasi yo'q bo'lsa — qo'lda tasdiqlash qoldiring.

To'liq halqa

python19 qator
diagramma(
    {
        "Ishlayotgan\nmodel": (0, 2.0, 2.2, 1.0, "xizmat"),
        "Monitoring\n(34-bob)": (3.0, 2.0, 2.2, 1.0, "kuzatuv"),
        "Drift\nsezildi": (6.0, 2.0, 2.0, 1.0, "kuzatuv"),
        "Sabab?\nsifat yoki drift": (8.8, 2.0, 2.4, 1.0, "qayta"),
        "Quvurni\ntuzatish": (11.9, 3.4, 2.1, 0.9, "manba"),
        "Qayta\no'qitish": (11.9, 0.7, 2.1, 0.9, "qayta"),
    },
    [
        ("Ishlayotgan\nmodel", "Monitoring\n(34-bob)"),
        ("Monitoring\n(34-bob)", "Drift\nsezildi"),
        ("Drift\nsezildi", "Sabab?\nsifat yoki drift"),
        ("Sabab?\nsifat yoki drift", "Quvurni\ntuzatish", "nosozlik"),
        ("Sabab?\nsifat yoki drift", "Qayta\no'qitish", "haqiqiy drift"),
    ],
    nom="drift_halqa.png", kenglik=15,
    izoh="Eng muhim tugun - o'rtadagi savol. Usiz drift har doim 'qayta o'qitish' deb javob oladi.",
)

Eng muhim tugun - o'rtadagi savol. Usiz drift har doim 'qayta o'qitish' deb javob oladi.

Eng muhim tugun - o'rtadagi savol. Usiz drift har doim 'qayta o'qitish' deb javob oladi.

Xulosa

Tushuncha Asosiy fikr
Monitoring siljishni ko'rsatadi Lekin nima qilishni aytmaydi
Ma'lumot drifti Kirish o'zgardi — qayta o'qitish yordam beradi
Tushuncha drifti Bog'liqlik o'zgardi — yangi yorliq kerak
Xususiyat buzilishi Qayta o'qitish zarar keltiradi — tuzatish kerak
Birinchi savol "Drift mi yoki nosozlikmi?"
PSI Yorliqsiz o'lchanadi; qaysi ustun siljiganini aytadi
PSI chegaralari Qonun emas — o'z normangizni o'lchang
Jadval bo'yicha qayta o'qitish Kech ham, erta ham bo'ladi
Sabab bilan qayta o'qitish Signal bo'lsa — harakat; bo'lmasa — tegmang
Avtomatik qayta o'qitish Faqat uch to'siq bilan: sifat, darvoza, canary

Talaba ishi

  1. ijara_yangi.csv dan tasodifiy 500 qator oling va PSI ni qayta hisoblang. Kichik namunada raqam qanday o'zgardi?
  2. psi() da paqir ni 5 va 20 qiling. Natija qanchalik barqaror?
  3. Eski ma'lumotning faqat oxirgi yarmini yangisi bilan qo'shib o'qiting. Natija yaxshilandimi?
  4. Bashorat taqsimotining PSI sini hisoblang (34-bobdagi model_ehtimol_taqsimoti). Kirish PSI sidan oldin yoki keyin siljidi?
  5. Sizning loyihangizda tushuncha driftini qanday sezasiz? Qanday yorliq to'playsiz?

Bu bobni bepul o'qidingiz.

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

Telegramda yozish Narxlar