Mundarija (25)
- 1. Kirish va motivatsiya
- 2. Nazariya — chuqur tushuntirish
- 2.1. Qisqa eslatma va nima yangi
- 2.2. Vaqt qatoridagi anomaliya turlari
- 2.3. Detektorlar
- 2.4. Vaqt qatorida baholash
- 2.5. Firibgarlik: juda nomutanosib ma'lumot
- 2.6. Belgilar: shaxsiy bazaviy
- 2.7. Nazoratsiz, nazoratli, yarim nazoratli
- 2.8. Xarajatga asoslangan chegara
- 2.9. Vaqt bo'yicha bo'lish va belgi kechikishi
- 2.10. Avtoenkoder bilan anomaliya
- 2.11. Tuzoqlar
- 3. Tez ma'lumotnoma
- 4. Batafsil misollar
- Misol 1 — Vaqt qatorida anomaliyalar: besh detektor
- Misol 2 — Firibgarlik: shaxsiy bazaviy, nazoratsiz va nazoratli usullar
- Misol 3 — Amaliyotda: belgi kechikishi, kunlik sig'im va xarajatli chegara
- Misol 4 — Avtoenkoder va IsolationForest: halol taqqoslash
- 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
28.6-dars: Ilg'or anomaliya aniqlash
28-QISM — MAXSUS MAVZULAR · 6-dars
1. Kirish va motivatsiya
Oldingi ikki darsda tavsiya tizimlarini qurdik — ko'p foydalanuvchilarning odatiy xulqidan o'rganib, har biriga mos narsani topdik. Bu darsda teskari savolga qaytamiz: odatiy bo'lmagan narsani qanday topish mumkin?
Anomaliya aniqlash bilan ikki marta tanishganmiz. 08.6-darsda EDA doirasida IQR, z-score va "imkonsiz kombinatsiyalar" bilan g'ayrioddiy qatorlarni qidirdik. 16.10-darsda nazoratsiz usullarni — IsolationForest, LOF, One-Class SVM, PCA qayta tiklash xatosini — o'rgandik va anomaliyaning uch turini (nuqtali, kontekstli, jamoaviy) ajratdik; contamination faqat chegarani belgilashini, baholashda PR-AUC va precision@k kerakligini ko'rdik. Bu asoslarni qayta tushuntirmaymiz — ular ustiga uchta amaliy yo'nalishni quramiz:
- Vaqt qatoridagi anomaliya. Qiymat faqat o'zi emas, vaqt konteksti bilan baholanadi: juma kechqurun 40 MW — past, tunda 40 MW — baland. Sensor "qotib qolsa", har bir qiymat normal, lekin ketma-ketlik g'ayrioddiy.
- Firibgarlik. Tranzaksiyalarning 0.2-0.5% i firib; ROC-AUC 0.97 bo'lsa ham signallarning aksariyati yolg'on bo'lishi mumkin. Tekshiruvchilar kuniga faqat bir necha o'nta holatni ko'ra oladi, yorliqlar (chargeback) haftalar keyin keladi, xatoning narxi esa har xil.
- Avtoenkoder. Neyron tarmoq bilan qayta tiklash xatosi 26.2-bob — lekin u haqiqatan IsolationForest dan yaxshimi? Halol taqqoslaymiz.
Real vaziyat. Toshkentdagi bank firibgarlikka qarshi tizim uchun IsolationForest qurdi va hisobotda "ROC-AUC 0.97" deb yozdi. Ishga tushirilgach, operatorlar kuniga 600 dan ortiq signal oldi, ulardan faqat 20% ga yaqini haqiqiy firib edi. Operatorlar faqat birinchi 30 tasini ko'rishga ulgurdi va qolganini yopib qo'ydi. Keyingi oyda model qayta o'qitildi — lekin so'nggi ikki haftadagi firiblar hali tasdiqlanmagan edi va "normal" deb o'qitishga kirdi. Ikki xato: noto'g'ri metrika (ROC-AUC nomutanosiblikda aldaydi) va noto'g'ri yorliqlar (belgi kechikishi). Bu darsda ikkalasini ham raqam bilan ko'rsatamiz.
Bu darsda anomaliya aniqlashni "ball hisoblash" dan operatsion tizim ga aylantiramiz: kontekst, chegara, sig'im, narx va kechikish bilan.
Bu darsda:
- Vaqt qatorida: nuqtali, kontekstli va kollektiv anomaliyalar
- Rolling z-score, robust median/MAD, STL qoldig'i, bashorat qoldig'i
- Hodisa darajasida baholash: recall, yolg'on signal/hafta, aniqlash kechikishi
- Firibgarlik: shaxsiy bazaviy belgilar, nazoratsiz vs nazoratli vs yarim nazoratli
- PR-AUC va ROC-AUC, kunlik sig'im (top-k), xarajatga asoslangan chegara
- Vaqt bo'yicha bo'lish va belgi kechikishi
- Avtoenkoder va IsolationForest — halol taqqoslash
- Tuzoqlar
ℹ Misollar real numpy/pandas/statsmodels/sklearn/torch bilan (Python 3.14). Ma'lumot sintetik: tuman elektr iste'moli (soatlik, sintetik taqvimdagi bayram kunlari bilan), bank kartasi tranzaksiyalari (O'zbekiston shaharlari, akkaunt egallash epizodlari) va uskuna sensorlari.
2. Nazariya — chuqur tushuntirish
2.1. Qisqa eslatma va nima yangi
16.10 DA O'RGANILGAN (qayta tushuntirilmaydi):
IsolationForest - tasodifiy bo'linishlarda tez izolyatsiya = anomaliya
LOF - mahalliy zichlik; One-Class SVM; PCA qayta tiklash xatosi
contamination - faqat chegara; baholash - PR-AUC, precision@k
kontekstual anomaliya - belgi muhandisligi bilan nuqtaviyga aylanadi
BU DARSDA YANGI:
vaqt konteksti (soat, hafta kuni, bayram) va KETMA-KETLIK
sababiylik: detektor faqat O'TMISHNI ko'ra oladimi?
hodisa darajasida baholash va aniqlash kechikishi
juda nomutanosib yorliqli ma'lumot: nazoratli usullar, sig'im, narx
yorliqlar kechikib keladi2.2. Vaqt qatoridagi anomaliya turlari
NUQTALI (point): bitta soatda keskin sakrash
---/\--- global darajada ham g'ayrioddiy bo'lishi MUMKIN,
lekin kun ichidagi tebranish katta bo'lsa - yo'q
KONTEKSTLI (contextual): qiymat o'zi normal, VAQTIGA nisbatan g'ayrioddiy
juma 20:00 da 39 MW - kunduzgi daraja; shu soatda odatda 57 MW
global statistika uni KO'RMAYDI (39 - umumiy o'rtachadan past ham emas)
kontekst = soat, hafta kuni, bayram, harorat
KOLLEKTIV (collective): har nuqta normal, KETMA-KETLIK g'ayrioddiy
sensor qotib qoldi: 10 soat bir xil qiymat
---_____________--- darajasi normal, lekin kunlik profil yo'qoldi
aniqlash uchun bir necha nuqta kerak -> KECHIKISH muqarrar
ANOMALIYA EMAS (lekin detektorni aldaydi):
bayram kunlari, dam olish kunlari, mavsum - KUTILGAN o'zgarish
ular modelda kontekst sifatida bo'lishi kerak, aks holda yolg'on signal2.3. Detektorlar
1. GLOBAL z: |x - mean| / std (tarixdan)
kontekstsiz; kun ichidagi katta tebranish std ni shishiradi
2. ROLLING z: oxirgi w soatning o'rtachasi va std si bilan (sababiy)
o'tgan 24 soat - kun profilining HAMMASI -> "kechki cho'qqi" normal
anomaliyaning o'zi oynaga kirib, std ni shishiradi (niqoblanish)
3. ROBUST: median va MAD (median absolute deviation)
z_robust = |x - median| / (1.4826 * MAD) 1.4826 - normal uchun std ga moslash
bitta sakrash median va MAD ni deyarli o'zgartirmaydi
4. STL QOLDIG'I: x = trend + mavsum + qoldiq (statsmodels STL)
qoldiqda robust z; mavsumiy kontekstni o'zi hisobga oladi
⚠️ STL BUTUN qatorni ko'radi (oldingi va KEYINGI qiymatlar) -
oflayn tahlil uchun zo'r, real vaqtda bu shaklda ishlamaydi
5. BASHORAT QOLDIG'I: e_t = x_t - x_hat_t, x_hat - faqat o'tmishdan
eng sodda: o'tgan 4 haftaning SHU SOATI mediana (mavsumiy naive, 28.1-28.2)
yaxshiroq: 28.2-28.3 dagi modellar (bayram belgisi bilan)
qoldiqda robust z; sababiy -> real vaqtda ishlaydistatsmodels ba'zi hollarda (chastotasiz indeks, yaqinlashish) ogohlantirishlarni stderr ga chiqaradi. Misollarda indeksga freq="h" beramiz va STL chaqiruvini warnings.catch_warnings() + simplefilter("ignore") bilan o'raymiz — bu faqat ekranni toza saqlash uchun, natijaga ta'sir qilmaydi.
Kontekstli anomaliya uchun "normal" ning o'zi kontekstga bog'liq bo'lishi kerak. Global va rolling statistikalar "odatda qancha" degan savolga soatni hisobga olmay javob beradi — shuning uchun kontekstli anomaliyani deyarli ko'rmaydi (1-misol).
2.4. Vaqt qatorida baholash
NUQTA darajasi emas, HODISA darajasi:
hodisa topildi = hodisa oynasida (+2 soat) kamida bitta signal
recall = topilgan hodisalar / hamma hodisalar (tur bo'yicha alohida)
yolg'on signal / hafta = hech bir hodisaga tegishli bo'lmagan signallar
KECHIKISH = birinchi signal soati - hodisa boshlanishi
CHEGARA - oldindan belgilangan byudjet bilan (27.11 alerting):
"haftasiga ~1 yolg'on signal" -> toza KALIBRLASH davrida
ball taqsimotining 1 - 1/168 kvantili
har detektorga O'Z chegarasi, lekin BIR XIL yolg'on signal byudjeti
-> detektorlarni teng sharoitda solishtirish mumkin
KALIBRLASH davri toza bo'lishi kerak; bo'lmasa (real hayotda) -
robust kvantil yoki qo'lda tozalangan tarix2.5. Firibgarlik: juda nomutanosib ma'lumot
ulush 0.3%: 60 000 tranzaksiyada ~200 firib
ROC-AUC: "tasodifiy firib tasodifiy normaldan yuqorimi" - normal
ko'p bo'lgani uchun FPR = 1% ham 600 yolg'on signal!
PR-AUC: signallar ichida firib ulushi (precision) - recall bo'yicha
tasodifiy model: PR-AUC = ulush 0.003-bob, ROC-AUC = 0.5
AMALIY METRIKALAR:
kunlik sig'im n: tekshiruvchilar kuniga n ta ko'radi
-> har kuni eng shubhali n ta: kunlik precision, topilgan firib ulushi
xarajat: FN narxi (yo'qolgan summa) va FP narxi (tekshirish, mijoz
noqulayligi) - chegara shu yig'indini minimallashtiradi2.6. Belgilar: shaxsiy bazaviy
XOM BELGILAR: summa, soat, kategoriya
500 ming so'm - bir mijoz uchun oddiy, boshqasi uchun g'ayrioddiy
SHAXSIY BAZAVIY (har mijoz O'Z O'TMISHI bilan):
summa_z = (log summa - o'tmish o'rtachasi) / o'tmish std
soat_farq = |soat - o'tmishdagi o'rtacha soat|
yangi_shahar, yangi_qurilma - o'tmishda ko'rilmaganmi
soni_24s = oxirgi 24 soatdagi tranzaksiyalar soni
oraliq = oldingi tranzaksiyadan beri o'tgan vaqt
tarix_soni - o'tmish qancha uzun (bazaviy qanchalik ishonchli)
⚠️ FAQAT O'TMISH: cumsum - joriy qiymat; groupby().shift()
16.10-4-misoldagi groupby().transform("median") butun davrni
(KELAJAKNI ham) ishlatadi - tahlil uchun mayli, ishlab chiqarishda
bunday belgi yo'q (17.8 leakage)2.7. Nazoratsiz, nazoratli, yarim nazoratli
NAZORATSIZ (IsolationForest, LOF): yorliq kerak emas
"g'ayrioddiy" != "firib": sayohatchi, katta xarid ham g'ayrioddiy
yangi turdagi firibni ham ushlashi mumkin
NAZORATLI (HistGB, logistik regressiya): tarixiy yorliqlar bilan
"firib qanday ko'rinadi" ni o'rganadi - odatda ANCHA aniqroq
nomutanosiblik: class_weight="balanced" (sinf og'irligi)
faqat MA'LUM turdagi firiblarni yaxshi taniydi
YARIM NAZORATLI:
a) faqat tasdiqlangan NORMAL misollarda o'qitilgan nazoratsiz model
(novelty detection, 16.10 dagi novelty=True g'oyasi)
b) nazoratsiz ball -> nazoratli modelga belgi sifatida
c) kam yorliq + ko'p yorliqsiz ma'lumot (self-training)
AMALDA: ikkalasi birga - nazoratli model asosiy ball, nazoratsiz model
"yangi narsa" uchun qo'shimcha signal2.8. Xarajatga asoslangan chegara
signal (tekshirish yoki bloklash): narx FP (tekshirish, mijoz noqulayligi)
o'tkazib yuborilgan firib: narx = firib summasi
jami(c) = sum_{firib, ball < c} summa + (ball >= c lar soni) * narx_FP
c ni VALIDATSIYA davrida tanlaymiz (test - bir marta)
0.5 - ehtimol uchun "tabiiy" chegara, lekin narxlar teng bo'lmasa
optimal chegara undan ancha farq qilishi mumkin
narxlar biznes bilan kelishiladi; bu darsdagi raqamlar FARAZIY2.9. Vaqt bo'yicha bo'lish va belgi kechikishi
FIRIB YORLIG'I qachon ma'lum bo'ladi?
mijoz shikoyati / chargeback - kunlar yoki HAFTALAR keyin
o'qitish kuni T da oxirgi haftalardagi firiblarning ko'pi hali "normal"
SODDA YO'L (xato): noma'lum = 0 -> firiblar "normal" deb o'qitiladi
YETILGAN YO'L: faqat T - L gacha bo'lgan davr (L - odatiy kechikish)
yorliqlar ishonchli, lekin ma'lumot eskiroq va kamroq
BAHOLASH: test davri yorliqlari ham yetilishi kerak -
aks holda precision past ko'rinadi
BO'LISH: har doim vaqt bo'yicha (train - o'tmish, test - kelajak);
tasodifiy bo'lish bitta epizodning firiblarini ikkala tomonga
taqsimlaydi va natijani oshirib ko'rsatadi2.10. Avtoenkoder bilan anomaliya
26.2 dagi avtoenkoder: x -> enkoder -> z (kichik) -> dekoder -> x_hat
faqat (asosan) normal ma'lumotda o'qitiladi
anomaliya bali = |x - x_hat|^2 (qayta tiklash xatosi)
PCA qayta tiklash xatosining NOCHIZIQLI umumlashmasi
QACHON FOYDALI:
ustunlar orasida murakkab (nochiziqli) bog'liqlik bor
anomaliya har ustunda alohida normal, lekin BIRGALIKDA mos emas
yuqori o'lchamli ma'lumot (rasm, spektr, ko'p sensor)
QACHON ORTIQCHA:
kichik jadval, anomaliya bitta-ikkita ustunda chetga chiqishda
-> IsolationForest oddiyroq, tezroq, masshtablash kerak emas
AE: masshtablash, arxitektura, epoxalar - sozlanadigan narsa ko'p2.11. Tuzoqlar
Asosiy tuzoqlar: kontekstsiz (global) statistika bilan kontekstli anomaliyani qidirish; STL kabi kelajakni ko'radigan usulni real vaqt detektori deb baholash; nuqta darajasida baholab, kechikish va hodisa recall ini o'lchamaslik; detektorlarni turli yolg'on signal byudjetida solishtirish; bayram va dam olish kunlarini kontekstga qo'shmaslik; nomutanosib ma'lumotda ROC-AUC ga tayanish; shaxsiy bazaviyni butun davrdan (kelajakdan) hisoblash; tasodifiy bo'lish; yetilmagan yorliqlarni "normal" deb olish; chegarani 0.5 yoki test ga qarab tanlash; tekshiruvchilar sig'imini hisobga olmaslik; "chuqur o'rganish yaxshiroq" deb IsolationForest bilan solishtirmaslik.
3. Tez ma'lumotnoma
import numpy as np
import pandas as pd
# vaqt qatori: rolling va robust (faqat o'tmish)
oldin = x.shift(1)
z_roll = (x - oldin.rolling(24).mean()) / oldin.rolling(24).std()
med = oldin.rolling(24).median()
mad = (oldin - med).abs().rolling(24).median()
z_rob = (x - med) / (1.4826 * mad)
# mavsumiy naive bashorat qoldig'i: o'tgan 4 haftaning shu soati
bashorat = np.median([x.shift(168 * k) for k in range(1, 5)], axis=0)
qoldiq = x - bashorat
# chegara: toza kalibrlash davrida haftasiga ~1 yolg'on signal
chegara = np.nanquantile(ball[kalibrlash], 1 - 1 / 168)
# firibgarlik: shaxsiy bazaviy (o'tmish o'rtachasi, joriy qator kirmaydi)
n_old = df.groupby("mijoz").cumcount()
yig = df["ls"].groupby(df["mijoz"]).cumsum() - df["ls"]
df["summa_z"] = (df["ls"] - yig / n_old) / std_otmish
# nazoratli, nomutanosib
m = HistGradientBoostingClassifier(class_weight="balanced", early_stopping=False)
pr_auc = average_precision_score(y_te, m.fit(X_tr, y_tr).predict_proba(X_te)[:, 1])
# kunlik sig'im: har kuni eng shubhali n ta
top = te.assign(b=ball).groupby("kun", group_keys=False).apply(
lambda g: g.nlargest(20, "b"))
# xarajatli chegara (validatsiyada)
jami = summa[(y == 1) & (ball < c)].sum() + (ball >= c).sum() * narx_fpQaysi vaziyatda nima
| Vaziyat | Yondashuv |
|---|---|
| Vaqt qatori, real vaqt | bashorat qoldig'i (mavsum + bayram konteksti) + robust z |
| Vaqt qatori, oflayn tekshiruv | STL qoldig'i |
| Sensor qotishi, darajaning siljishi | ketma-ketlik belgilari (o'zgarmaslik, CUSUM), kechikishni o'lchang |
| Firib, yorliqlar bor | HistGB + class_weight, shaxsiy bazaviy belgilar |
| Yorliq yo'q yoki yangi tur | IsolationForest (shaxsiy belgilar bilan) |
| Baholash | PR-AUC, kunlik top-k, xarajat; vaqt bo'yicha bo'lish |
| Murakkab ko'p o'lchovli bog'liqlik | avtoenkoder (IF va PCA bilan solishtirib) |
Anomaliya tizimi xulosasi
kontekst: soat/kun/bayram - "normal" ning o'zi kontekstga bog'liq
sababiylik: real vaqt detektori faqat o'tmishni ko'radi
baholash: hodisa recall, yolg'on signal/hafta, kechikish - bir xil byudjetda
firib: shaxsiy bazaviy, PR-AUC, top-k sig'im, narx bo'yicha chegara
yorliqlar kechikadi: yetilmagan davrni ehtiyot bilan
AE - murakkab bog'liqlikda; oddiy jadvalda IF ko'pincha yetarli4. Batafsil misollar
Misollar real numpy/pandas/statsmodels/sklearn/torch bilan (Python 3.14). Har misol mustaqil ishlaydi. 2-4-misollar bir xil tranzaksiya generatorini (
tranzaksiyalar()) va shaxsiy belgilarni (belgilar()) ishlatadi.
Misol 1 — Vaqt qatorida anomaliyalar: besh detektor
Tuman elektr iste'moli, soatlik, 20 hafta: kunlik profil (ertalabki va kechki cho'qqi), dam olish kunlari past, sekin trend va AR(1) shovqin. 13-haftada sintetik taqvimdagi ikki bayram kuni (iste'mol 20% past — anomaliya emas). 9-20-haftalarga 8 ta nuqtali, 6 ta kontekstli (ish kuni 19-21 soatda kunduzgi daraja) va 4 ta kollektiv (sensor 10 soat qotib qoladi) anomaliya qo'shilgan. 1-4-haftalar — tarix, 5-8 — toza kalibrlash.
"""Vaqt qatorida anomaliyalar: nuqtali, kontekstli, kollektiv - besh detektor."""
import warnings
import numpy as np
import pandas as pd
from statsmodels.tsa.seasonal import STL
HAFTA = 168
def qator(seed):
"""Tuman elektr iste'moli (MW), soatlik, 20 hafta; 9-20-haftalarga anomaliyalar."""
rng = np.random.default_rng(seed)
n = 20 * HAFTA
t = np.arange(n)
soat, kun = t % 24, (t // 24) % 7
profil = (40 + 12 * np.exp(-(soat - 9) ** 2 / 8) + 20 * np.exp(-(soat - 20) ** 2 / 6)
- 8 * (soat < 6))
x = profil * np.where(kun >= 5, 0.85, 1.0) + 0.02 * t / 24 \
+ 3 * np.sin(2 * np.pi * t / (24 * 30))
shovqin = np.zeros(n)
e = rng.normal(0, 1.5, n)
for i in range(1, n):
shovqin[i] = 0.6 * shovqin[i - 1] + e[i]
x = x + shovqin
bayram = np.zeros(n, dtype=bool) # sintetik taqvimda bayram kunlari
b0 = 12 * HAFTA + 24 # 13-hafta seshanba-chorshanba
bayram[b0:b0 + 48] = True
x[bayram] *= 0.8
hodisalar, band = [], np.zeros(n, dtype=bool)
def joy(uzunlik, shart):
while True:
s = int(rng.integers(8 * HAFTA + 24, n - uzunlik - 24))
if shart(s) and not band[s - 24:s + uzunlik + 24].any() and not bayram[s - 24:s + uzunlik + 24].any():
band[s:s + uzunlik] = True
return s
for _ in range(8): # nuqtali: bitta soatda sakrash
s = joy(1, lambda s: True)
x[s] += rng.choice([-1, 1]) * rng.uniform(12, 20)
hodisalar.append(("nuqtali", s, s + 1))
for _ in range(6): # kontekstli: kechki cho'qqida kunduzgi daraja
s = joy(3, lambda s: s % 24 == 19 and (s // 24) % 7 < 5)
x[s:s + 3] = x[s - 5:s - 2].mean() - 3 # 14-16 soatdagi daraja - umumiy normal
hodisalar.append(("kontekstli", s, s + 3))
for _ in range(4): # kollektiv: sensor qotib qoldi
s = joy(10, lambda s: True)
x[s:s + 10] = x[s]
hodisalar.append(("kollektiv", s, s + 10))
return pd.Series(x, index=pd.date_range("2025-01-06", periods=n, freq="h")), hodisalar, bayram
def ballar(x):
"""Har detektor: har soat uchun 'g'ayrioddiylik' bali (katta = shubhali)."""
v = x.to_numpy()
tarix = v[:8 * HAFTA]
b = {"global z": np.abs(v - tarix.mean()) / tarix.std()}
oldin = x.shift(1)
o, s = oldin.rolling(24).mean(), oldin.rolling(24).std()
b["rolling z (24s)"] = (np.abs(x - o) / s).to_numpy()
med = oldin.rolling(24).median()
mad = (oldin - med).abs().rolling(24).median()
b["robust (median/MAD)"] = (np.abs(x - med) / (1.4826 * mad)).to_numpy()
with warnings.catch_warnings():
warnings.simplefilter("ignore")
q = STL(x, period=24, robust=True).fit().resid.to_numpy()
b["STL qoldig'i"] = np.abs(q - np.median(q[:8 * HAFTA])) / (
1.4826 * np.median(np.abs(q[:8 * HAFTA] - np.median(q[:8 * HAFTA]))))
haftalar = np.stack([x.shift(HAFTA * k).to_numpy() for k in range(1, 5)])
bashorat = np.median(haftalar, axis=0) # o'tgan 4 haftaning shu soati
qold = pd.Series(v - bashorat, index=x.index)
qmad = (qold - qold.shift(1).rolling(HAFTA).median()).abs().shift(1).rolling(HAFTA).median()
b["bashorat qoldig'i"] = (np.abs(qold - qold.shift(1).rolling(HAFTA).median())
/ (1.4826 * qmad)).to_numpy()
return b
def baholash(ball, hodisalar, bayram, n):
"""Chegara: toza kalibrlash haftalarida (5-8) haftasiga ~1 yolg'on signal."""
kal = ball[4 * HAFTA:8 * HAFTA]
chegara = np.nanquantile(kal, 1 - 1 / HAFTA)
signal = np.nan_to_num(ball[8 * HAFTA:], nan=0.0) > chegara
ind = np.flatnonzero(signal) + 8 * HAFTA
oyna = np.zeros(n, dtype=bool)
topildi, kechikish = {}, []
for tur, s, e in hodisalar:
oyna[s:e + 3] = True
hit = ind[(ind >= s) & (ind < e + 3)]
topildi.setdefault(tur, []).append(len(hit) > 0)
if tur == "kollektiv" and len(hit):
kechikish.append(hit[0] - s)
yolgon = [i for i in ind if not oyna[i] and not bayram[i]]
return ({k: np.mean(v) for k, v in topildi.items()},
np.mean([t for v in topildi.values() for t in v]),
len(yolgon) / 12, int(bayram[ind].sum()), kechikish)
def main() -> None:
x, hodisalar, bayram = qator(0)
print("=== 1. Qator va anomaliyalar (urug' 0) ===")
print(f" {len(x)} soat (20 hafta): 1-4 tarix, 5-8 kalibrlash (toza), 9-20 baholash")
print(f" o'rtacha {x.mean():.1f} MW, std {x.std():.1f}; kun ichida 3-soat "
f"{x[x.index.hour == 3].mean():.1f}, 20-soat {x[x.index.hour == 20].mean():.1f}")
for tur in ["nuqtali", "kontekstli", "kollektiv"]:
s = [h for h in hodisalar if h[0] == tur]
print(f" {tur:<11} {len(s)} ta hodisa, masalan soat {s[0][1]}: "
f"qiymat {x.iloc[s[0][1]]:.1f} (shu soatda odatda "
f"{np.median([x.iloc[s[0][1] - HAFTA * k] for k in (1, 2, 3)]):.1f})")
print(f" bayram kunlari (anomaliya EMAS, 48 soat): o'rtacha "
f"{x[bayram].mean():.1f}, oldingi haftaning shu soatlari "
f"{x.shift(HAFTA)[bayram].mean():.1f}")
print("\n=== 2. Besh detektor, 6 ta qator (urug'lar) - o'rtacha ===")
jadval = {}
for seed in range(6):
x, hodisalar, bayram = qator(seed)
for nom, b in ballar(x).items():
jadval.setdefault(nom, []).append(baholash(b, hodisalar, bayram, len(x)))
print(f" {'detektor':<20} {'nuqtali':>8} {'kontekst':>9} {'kollektiv':>10} "
f"{'hammasi':>8} {'yolg_on/hafta':>14} {'bayramda':>9}")
for nom, r in jadval.items():
tur = {k: np.mean([q[0][k] for q in r]) for k in ["nuqtali", "kontekstli", "kollektiv"]}
print(f" {nom:<20} {tur['nuqtali']:>8.2f} {tur['kontekstli']:>9.2f} "
f"{tur['kollektiv']:>10.2f} {np.mean([q[1] for q in r]):>8.2f} "
f"{np.mean([q[2] for q in r]):>14.2f} {np.mean([q[3] for q in r]):>9.1f}")
print("\n=== 3. Kollektiv anomaliya: aniqlash kechikishi (soat) ===")
for nom, r in jadval.items():
k = [d for q in r for d in q[4]]
print(f" {nom:<20} topildi {len(k):>2}/24, kechikish mediana "
f"{np.median(k) if k else float('nan'):>4.1f}")
print("\n=== 4. Qaror: faqat REAL VAQTDA ishlay oladigan detektorlar ichida ===")
rec = {nom: np.array([q[1] for q in r]) for nom, r in jadval.items()}
tartib = ["global z", "rolling z (24s)", "robust (median/MAD)",
"bashorat qoldig'i"] # soddadan murakkabga, sababiy
eng = max(tartib, key=lambda n: rec[n].mean())
tanlov = None
for nom in tartib:
d = rec[nom] - rec[eng]
se = d.std(ddof=1) / np.sqrt(len(d))
yomon = d.mean() < -2 * se
if nom != eng:
print(f" {nom:<20} - {eng}: {d.mean():+.3f}, SE {se:.3f}, "
f"sezilarli yomon: {yomon}")
if tanlov is None and not yomon:
tanlov = nom
print(f" qaror (eng sodda munosib): {tanlov}")
d = rec["STL qoldig'i"] - rec[eng]
print(f" STL (butun qatorni, ya'ni KELAJAKNI ham ko'radi) - {eng}: {d.mean():+.3f}, "
f"SE {d.std(ddof=1) / np.sqrt(len(d)):.3f} - faqat oflayn tahlil uchun")
print(" ⭐ Kontekst (soat, hafta kuni) bilan solishtirmaguncha kontekstli anomaliya ko'rinmaydi")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Qator va anomaliyalar (urug' 0) ===
3360 soat (20 hafta): 1-4 tarix, 5-8 kalibrlash (toza), 9-20 baholash
o'rtacha 43.4 MW, std 9.1; kun ichida 3-soat 32.5, 20-soat 57.9
nuqtali 8 ta hodisa, masalan soat 1485: qiymat 34.5 (shu soatda odatda 48.6)
kontekstli 6 ta hodisa, masalan soat 2803: qiymat 39.1 (shu soatda odatda 57.2)
kollektiv 4 ta hodisa, masalan soat 2471: qiymat 47.4 (shu soatda odatda 45.1)
bayram kunlari (anomaliya EMAS, 48 soat): o'rtacha 34.9, oldingi haftaning shu soatlari 42.3
=== 2. Besh detektor, 6 ta qator (urug'lar) - o'rtacha ===
detektor nuqtali kontekst kollektiv hammasi yolg_on/hafta bayramda
global z 0.33 0.00 0.08 0.17 2.06 0.5
rolling z (24s) 0.33 0.06 0.04 0.18 2.36 0.2
robust (median/MAD) 0.27 0.08 0.29 0.21 2.33 0.5
STL qoldig'i 0.98 1.00 0.96 0.98 1.28 1.5
bashorat qoldig'i 0.88 0.94 0.92 0.91 1.35 8.5
=== 3. Kollektiv anomaliya: aniqlash kechikishi (soat) ===
global z topildi 2/24, kechikish mediana 5.0
rolling z (24s) topildi 1/24, kechikish mediana 10.0
robust (median/MAD) topildi 7/24, kechikish mediana 10.0
STL qoldig'i topildi 23/24, kechikish mediana 4.0
bashorat qoldig'i topildi 22/24, kechikish mediana 4.0
=== 4. Qaror: faqat REAL VAQTDA ishlay oladigan detektorlar ichida ===
global z - bashorat qoldig'i: -0.741, SE 0.062, sezilarli yomon: True
rolling z (24s) - bashorat qoldig'i: -0.731, SE 0.056, sezilarli yomon: True
robust (median/MAD) - bashorat qoldig'i: -0.694, SE 0.059, sezilarli yomon: True
qaror (eng sodda munosib): bashorat qoldig'i
STL (butun qatorni, ya'ni KELAJAKNI ham ko'radi) - bashorat qoldig'i: +0.074, SE 0.031 - faqat oflayn tahlil uchun
⭐ Kontekst (soat, hafta kuni) bilan solishtirmaguncha kontekstli anomaliya ko'rinmaydiNatija tahlili.
1-bo'lim — kontekst raqamda: kun ichida 3-soatda o'rtacha 32.5 MW, 20-soatda 57.9 MW, umumiy std 9.1. Kontekstli anomaliya misolida qiymat 39.1 — umumiy o'rtachadan (43.4) ham past emas, lekin shu soatda odatda 57.2. Bayram kunlari iste'mol 34.9, oldingi haftaning shu soatlarida 42.3 — sezilarli farq, lekin bu kutilgan hodisa.
2-bo'lim — 6 ta qator, har detektorga bir xil yolg'on signal byudjeti (kalibrlashda haftasiga ~1). Kontekstsiz detektorlar deyarli ko'r: global z kontekstli anomaliyalarning birortasini topmadi (0.00), nuqtali sakrashlarning faqat 0.33 qismini — kun ichidagi tebranish (std 9.1) 12-20 MW lik sakrashni "yashiradi". Rolling va robust variantlar ham oxirgi 24 soat bilan solishtiradi — bu oynada kechki cho'qqi ham, tungi pastlik ham bor, shuning uchun ular ham kontekstni bilmaydi (hammasi 0.21 gacha). Kontekstni biladigan ikki detektor boshqa sinfda: bashorat qoldig'i 0.91, STL qoldig'i 0.98. Bayram ustuni muhim: bashorat qoldig'i bayram kunlarida o'rtacha 8.5 ta signal berdi — "o'tgan 4 hafta" bayramni bilmaydi. Real tizimda bayram taqvimi modelga belgi sifatida kiradi (28.2-28.3).
3-bo'lim — kollektiv anomaliya (sensor qotishi). Qotgan qiymat dastlab normal — farq faqat kunlik profil qiymatni "tark etganda" paydo bo'ladi, shuning uchun eng yaxshi detektorlar ham mediana 4 soat kechikish bilan topadi. Kontekstsiz detektorlar 24 hodisadan faqat 1-7 tasini topdi. Kechikish — kollektiv anomaliyaning tabiiy narxi; uni ogohlantirish qoidasini loyihalashda 27.11-bob hisobga olish kerak.
4-bo'lim — qaror. STL butun qatorni — ya'ni kelajakni ham — ko'radi, shuning uchun real vaqt detektori sifatida solishtirilmaydi. Sababiy detektorlar ichida bashorat qoldig'i qolgan uchtasidan 0.69-0.74 ga yaxshi (SE ~0.06) va qaror qoidasi bo'yicha tanlandi. STL undan yana +0.074 (SE 0.031) yaxshi — kelajakni ko'rishning "bonusi"; u oflayn tahlil (masalan, o'tgan oy hisobotida anomaliyalarni belgilash) uchun ajoyib vosita.
Misol 2 — Firibgarlik: shaxsiy bazaviy, nazoratsiz va nazoratli usullar
Generator 1200 mijoz uchun 180 kunlik karta tranzaksiyalarini yaratadi: har mijozning odatiy summasi, soati, shahri, kategoriyalari; 3% sayohat, 1.5% yangi qurilma — normal xulqda ham. Firibgarlik — akkaunt egallash epizodlari: bir necha soat ichida 1-5 ta tranzaksiya, ko'pincha katta summa, tunda, yangi qurilma yoki boshqa shahardan, lekin har doim emas. Har firibga tasdiqlanish kechikishi (chargeback, o'rtacha ~23 kun) ham beriladi.
"""Firibgarlik: shaxsiy bazaviy belgilar, vaqt bo'yicha bo'lish, nazoratsiz vs nazoratli."""
import numpy as np
import pandas as pd
from sklearn.ensemble import HistGradientBoostingClassifier, IsolationForest
from sklearn.metrics import average_precision_score, roc_auc_score, roc_curve
KATEG = ["oziq", "transport", "restoran", "kiyim", "elektronika", "kommunal",
"otkazma", "onlayn"]
def tranzaksiyalar(seed=0, n_mijoz=1200, kunlar=180):
"""Bank kartasi tranzaksiyalari; firibgarlik - akkaunt egallash epizodlari (~0.3%)."""
rng = np.random.default_rng(seed)
tezlik = rng.lognormal(np.log(0.45), 0.5, n_mijoz) # kuniga tranzaksiya
odat_summa = rng.lognormal(11.3, 0.6, n_mijoz) # ~80 ming so'm
odat_soat = rng.uniform(9, 20, n_mijoz)
uy = rng.integers(0, 6, n_mijoz)
kat_pref = rng.dirichlet(np.full(len(KATEG), 0.7), n_mijoz)
kat_pref[:, 4] *= 0.3 # elektronika kam
kat_pref /= kat_pref.sum(1, keepdims=True)
soni = rng.poisson(tezlik * kunlar)
m = np.repeat(np.arange(n_mijoz), soni)
n = len(m)
vaqt = rng.uniform(0, kunlar, n)
kat = (rng.random(n)[:, None] > np.cumsum(kat_pref[m], 1)).sum(1)
kat_koef = np.array([0.6, 0.3, 1.0, 2.0, 4.0, 1.2, 2.5, 1.5])
summa = odat_summa[m] * kat_koef[kat] * rng.lognormal(0, 0.6, n)
soat = np.mod(odat_soat[m] + rng.normal(0, 3, n), 24)
safar = rng.random(n) < 0.03
shahar = np.where(safar, (uy[m] + rng.integers(1, 6, n)) % 6, uy[m])
qurilma = np.where(rng.random(n) < 0.015, rng.integers(1, 1000, n), 0)
firib = np.zeros(n, dtype=bool)
# firibgarlik epizodlari: bir necha soat ichida 1-5 ta tranzaksiya
ep = int(n * 0.003 / 2.6)
em = rng.integers(0, n_mijoz, ep)
et = rng.uniform(0, kunlar - 1, ep)
ek = rng.integers(1, 6, ep)
fm = np.repeat(em, ek)
fv = np.repeat(et, ek) + rng.uniform(0, 0.25, ek.sum())
nf = len(fm)
fk = rng.choice(len(KATEG), nf, p=[0.05, 0.02, 0.05, 0.1, 0.35, 0.03, 0.25, 0.15])
fs = odat_summa[fm] * kat_koef[fk] * rng.lognormal(np.log(1.8), 0.8, nf)
fsoat = np.where(rng.random(nf) < 0.45, rng.uniform(0, 6, nf),
np.mod(odat_soat[fm] + rng.normal(0, 5, nf), 24))
fsh = np.where(rng.random(nf) < 0.5, (uy[fm] + rng.integers(1, 6, nf)) % 6, uy[fm])
fq = np.where(rng.random(nf) < 0.7, rng.integers(1000, 2000, nf), 0)
df = pd.DataFrame({
"mijoz": np.r_[m, fm], "vaqt": np.r_[vaqt, fv], "summa": np.r_[summa, fs],
"soat": np.r_[soat, fsoat], "kategoriya": np.r_[kat, fk],
"shahar": np.r_[shahar, fsh], "qurilma": np.r_[qurilma, fq],
"firib": np.r_[firib, np.ones(nf, dtype=bool)].astype(int)})
kechikish = 3 + rng.exponential(20, len(df)) # chargeback kelishi (kun)
df["belgi_kuni"] = np.where(df["firib"] == 1, df["vaqt"] + kechikish, df["vaqt"])
return df.sort_values(["mijoz", "vaqt"]).reset_index(drop=True)
def belgilar(df, sizish=False):
"""Shaxsiy bazaviy: har tranzaksiya mijozning FAQAT O'TMISHI bilan solishtiriladi."""
df = df.copy()
g = df.groupby("mijoz")
ls = np.log(df["summa"])
df["log_summa"] = ls
if sizish: # XATO: butun davr (kelajak ham) ishlatiladi
o = ls.groupby(df["mijoz"]).transform("mean")
s = ls.groupby(df["mijoz"]).transform("std")
os_ = g["soat"].transform("mean")
else:
n_old = g.cumcount()
yig = ls.groupby(df["mijoz"]).cumsum() - ls
yig2 = (ls ** 2).groupby(df["mijoz"]).cumsum() - ls ** 2
o = yig / n_old.replace(0, np.nan)
s = np.sqrt((yig2 / n_old.replace(0, np.nan) - o ** 2).clip(lower=0.01))
os_ = (df["soat"].groupby(df["mijoz"]).cumsum() - df["soat"]) / n_old.replace(0, np.nan)
df["summa_z"] = ((ls - o) / s).fillna(0)
df["soat_farq"] = (df["soat"] - os_).abs().fillna(0)
df["yangi_shahar"] = (df.groupby(["mijoz", "shahar"]).cumcount() == 0).astype(int)
df["yangi_qurilma"] = (df.groupby(["mijoz", "qurilma"]).cumcount() == 0).astype(int)
birinchi = g.cumcount() == 0
df.loc[birinchi, ["yangi_shahar", "yangi_qurilma"]] = 0
kalit = df["mijoz"].to_numpy() * 1000.0 + df["vaqt"].to_numpy()
df["soni_24s"] = np.arange(len(df)) - np.searchsorted(kalit, kalit - 1.0)
df["oraliq"] = np.log1p(g["vaqt"].diff().fillna(30).to_numpy() * 24)
df["tarix_soni"] = g.cumcount()
return df
XOM = ["log_summa", "soat", "kategoriya"]
SHAXSIY = ["log_summa", "soat", "kategoriya", "summa_z", "soat_farq", "yangi_shahar",
"yangi_qurilma", "soni_24s", "oraliq", "tarix_soni"]
def ap_vazn(y, w, tartib):
"""Og'irlikli average precision: bootstrap da kun necha marta tanlangan bo'lsa."""
y, w = y[tartib], w[tartib].astype(float)
tp = np.cumsum(w * y)
precision = tp / np.maximum(np.cumsum(w), 1e-12)
return float(np.sum(w * y * precision) / max(tp[-1], 1e-12))
def main() -> None:
xom = tranzaksiyalar()
df = belgilar(xom)
print("=== 1. Ma'lumot ===")
print(f" {len(df)} tranzaksiya, {df['mijoz'].nunique()} mijoz, 180 kun; "
f"firibgarlik {int(df['firib'].sum())} ta ({df['firib'].mean():.2%})")
f, n = df[df["firib"] == 1], df[df["firib"] == 0]
print(f" summa mediana: normal {n['summa'].median():,.0f}, firib {f['summa'].median():,.0f} so'm")
print(f" firib ichida: tungi (0-6) {np.mean(f['soat'] < 6):.0%}, yangi qurilma "
f"{f['yangi_qurilma'].mean():.0%}, yangi shahar {f['yangi_shahar'].mean():.0%}")
print(f" normal ichida: tungi {np.mean(n['soat'] < 6):.0%}, yangi qurilma "
f"{n['yangi_qurilma'].mean():.1%}, yangi shahar {n['yangi_shahar'].mean():.1%}")
tr, te = df[df["vaqt"] < 120], df[df["vaqt"] >= 120]
y_tr, y_te = tr["firib"].to_numpy(), te["firib"].to_numpy()
print(f" vaqt bo'yicha: train 0-119 kun ({len(tr)}, firib {y_tr.sum()}), "
f"test 120-179 ({len(te)}, firib {y_te.sum()})")
print("\n=== 2. Usullar (test, 60 kun) ===")
ballar = {}
for nom, cols in [("IF xom belgilar", XOM), ("IF shaxsiy belgilar", SHAXSIY)]:
m = IsolationForest(n_estimators=100, random_state=0, n_jobs=1).fit(tr[cols])
ballar[nom] = -m.score_samples(te[cols])
m = IsolationForest(n_estimators=100, random_state=0, n_jobs=1).fit(
tr.loc[tr["firib"] == 0, SHAXSIY])
ballar["IF faqat normal (yarim)"] = -m.score_samples(te[SHAXSIY])
for nom, cols, vazn in [("HistGB xom, balanced", XOM, "balanced"),
("HistGB", SHAXSIY, None),
("HistGB balanced", SHAXSIY, "balanced")]:
m = HistGradientBoostingClassifier(max_iter=80, learning_rate=0.1, max_leaf_nodes=15,
early_stopping=False, class_weight=vazn,
random_state=0)
ballar[nom] = m.fit(tr[cols], y_tr).predict_proba(te[cols])[:, 1]
print(f" {'usul':<24} {'ROC-AUC':>8} {'PR-AUC':>7} {'P@100':>6}")
print(f" {'tasodifiy (nazariy)':<24} {0.5:>8.4f} {y_te.mean():>7.4f} {y_te.mean():>6.3f}")
for nom, b in ballar.items():
top = np.argsort(-b, kind="stable")[:100]
print(f" {nom:<24} {roc_auc_score(y_te, b):>8.4f} "
f"{average_precision_score(y_te, b):>7.4f} {y_te[top].mean():>6.3f}")
print("\n=== 3. ROC-AUC nega aldaydi (IF shaxsiy belgilar) ===")
b = ballar["IF shaxsiy belgilar"]
fpr, tpr, chegara = roc_curve(y_te, b)
for maqsad in [0.01, 0.05]:
i = np.searchsorted(fpr, maqsad)
tp, fp = tpr[i] * y_te.sum(), fpr[i] * (len(y_te) - y_te.sum())
print(f" FPR {maqsad:.0%}: firiblarning {tpr[i]:.0%} i topildi ({tp:.0f} ta), "
f"yolg'on signal {fp:.0f} ta -> precision {tp / (tp + fp):.1%}")
print(f" ROC-AUC {roc_auc_score(y_te, b):.3f} 'yaxshi' ko'rinadi, lekin signallarning "
f"aksariyati yolg'on - PR-AUC {average_precision_score(y_te, b):.3f}")
print("\n=== 4. Juftlashgan bootstrap (kunlar bo'yicha, 200 marta), PR-AUC ===")
kun = te["vaqt"].astype(int).to_numpy() - 120
rng = np.random.default_rng(0)
juftlar = [("HistGB balanced", "HistGB"), ("HistGB balanced", "IF shaxsiy belgilar"),
("IF shaxsiy belgilar", "IF faqat normal (yarim)")]
tartib = {nom: np.argsort(-b, kind="stable") for nom, b in ballar.items()}
for a, c in juftlar:
d = []
for _ in range(200):
w = np.bincount(rng.integers(0, 60, 60), minlength=60)[kun] # kun vazni
d.append(ap_vazn(y_te, w, tartib[a]) - ap_vazn(y_te, w, tartib[c]))
nuqta = (average_precision_score(y_te, ballar[a])
- average_precision_score(y_te, ballar[c]))
se = np.std(d, ddof=1)
print(f" {a} - {c}: {nuqta:+.4f}, SE {se:.4f}, sezilarli: {abs(nuqta) > 2 * se}")
print(" ⭐ Yorliq bo'lsa - nazoratli model; baholash - PR-AUC va top-k")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Ma'lumot ===
107685 tranzaksiya, 1200 mijoz, 180 kun; firibgarlik 388 ta (0.36%)
summa mediana: normal 93,548, firib 335,491 so'm
firib ichida: tungi (0-6) 48%, yangi qurilma 68%, yangi shahar 36%
normal ichida: tungi 3%, yangi qurilma 1.5%, yangi shahar 2.2%
vaqt bo'yicha: train 0-119 kun (71885, firib 244), test 120-179 (35800, firib 144)
=== 2. Usullar (test, 60 kun) ===
usul ROC-AUC PR-AUC P@100
tasodifiy (nazariy) 0.5000 0.0040 0.004
IF xom belgilar 0.7779 0.0707 0.150
IF shaxsiy belgilar 0.9670 0.4908 0.620
IF faqat normal (yarim) 0.9661 0.4877 0.590
HistGB xom, balanced 0.8503 0.1761 0.300
HistGB 0.8597 0.2162 0.310
HistGB balanced 0.9847 0.7152 0.830
=== 3. ROC-AUC nega aldaydi (IF shaxsiy belgilar) ===
FPR 1%: firiblarning 68% i topildi (98 ta), yolg'on signal 367 ta -> precision 21.1%
FPR 5%: firiblarning 85% i topildi (123 ta), yolg'on signal 1787 ta -> precision 6.4%
ROC-AUC 0.967 'yaxshi' ko'rinadi, lekin signallarning aksariyati yolg'on - PR-AUC 0.491
=== 4. Juftlashgan bootstrap (kunlar bo'yicha, 200 marta), PR-AUC ===
HistGB balanced - HistGB: +0.4990, SE 0.0375, sezilarli: True
HistGB balanced - IF shaxsiy belgilar: +0.2245, SE 0.0347, sezilarli: True
IF shaxsiy belgilar - IF faqat normal (yarim): +0.0031, SE 0.0138, sezilarli: False
⭐ Yorliq bo'lsa - nazoratli model; baholash - PR-AUC va top-kNatija tahlili.
1-bo'lim — 107685 tranzaksiya, firib 388 ta (0.36%). Firiblar o'rtacha kattaroq (mediana 335,491 va 93,548 so'm), ko'pincha tunda (48% va 3%) va yangi qurilmadan (68% va 1.5%) — lekin har bir belgi alohida olinganda aniq ajratmaydi: normal tranzaksiyalar orasida ham tungi, sayohatdagi va katta xaridlar bor. Bo'lish vaqt bo'yicha: 0-119 kun train, 120-179 test.
2-bo'lim — belgilar sifati hal qiladi. IsolationForest xom belgilarda (summa, soat, kategoriya) PR-AUC 0.0707 — "g'ayrioddiy summa" ko'p mijoz uchun oddiy. Shaxsiy bazaviy belgilar bilan xuddi shu model 0.4908 — yetti barobar yaxshi. Nazoratli model ham xuddi shunday: xom belgilarda 0.1761, shaxsiy belgilarda 0.7152. Sinf og'irligi muhim chiqdi: og'irliksiz HistGB atigi 0.2162 — 0.36% musbat sinfda boosting beqaror va noto'g'ri tomonga moslashadi; class_weight="balanced" bilan 0.7152 va top-100 da 0.83 precision. Yarim nazoratli variant (IF faqat normal tranzaksiyalarda o'qitilgan) oddiy IF bilan deyarli bir xil (0.4877 va 0.4908): train dagi firiblar ulushi shunchalik kichikki, ular IF ni "ifloslantirmaydi".
3-bo'lim — ROC-AUC nega aldaydi. IF (shaxsiy) ROC-AUC 0.967 — "a'lo" ko'rinadi. Lekin FPR 1% da, ya'ni normal tranzaksiyalarning atigi 1% ida signal bersa ham: 98 ta firib topiladi, yolg'on signal esa 367 ta — precision 21.1%. FPR 5% da precision 6.4%. ROC-AUC normal sinfning kattaligini "ko'rmaydi"; PR-AUC (0.491) haqiqiy manzarani beradi.
4-bo'lim — juftlashgan bootstrap kunlar bo'yicha (bir kun ichidagi tranzaksiyalar bog'liq — bitta epizod bir kunda). HistGB balanced og'irliksiz variantdan +0.4990 (SE 0.0375) va IF dan +0.2245 (SE 0.0347) yaxshi — ikkalasi ham sezilarli. IF va yarim nazoratli IF farqi +0.0031 (SE 0.0138) — sezilarli emas. Xulosa: yorliqlar bo'lsa, nazoratli model; nazoratsiz model esa yorliq yo'q joyda va "yangi tur" signali sifatida.
Misol 3 — Amaliyotda: belgi kechikishi, kunlik sig'im va xarajatli chegara
"""Firibgarlik amaliyotda: belgi kechikishi, kunlik sig'im (top-k) va xarajatli chegara."""
import numpy as np
import pandas as pd
from sklearn.ensemble import HistGradientBoostingClassifier, IsolationForest
from sklearn.metrics import average_precision_score
KATEG = ["oziq", "transport", "restoran", "kiyim", "elektronika", "kommunal",
"otkazma", "onlayn"]
def tranzaksiyalar(seed=0, n_mijoz=1200, kunlar=180):
"""Bank kartasi tranzaksiyalari; firibgarlik - akkaunt egallash epizodlari (~0.3%)."""
rng = np.random.default_rng(seed)
tezlik = rng.lognormal(np.log(0.45), 0.5, n_mijoz) # kuniga tranzaksiya
odat_summa = rng.lognormal(11.3, 0.6, n_mijoz) # ~80 ming so'm
odat_soat = rng.uniform(9, 20, n_mijoz)
uy = rng.integers(0, 6, n_mijoz)
kat_pref = rng.dirichlet(np.full(len(KATEG), 0.7), n_mijoz)
kat_pref[:, 4] *= 0.3 # elektronika kam
kat_pref /= kat_pref.sum(1, keepdims=True)
soni = rng.poisson(tezlik * kunlar)
m = np.repeat(np.arange(n_mijoz), soni)
n = len(m)
vaqt = rng.uniform(0, kunlar, n)
kat = (rng.random(n)[:, None] > np.cumsum(kat_pref[m], 1)).sum(1)
kat_koef = np.array([0.6, 0.3, 1.0, 2.0, 4.0, 1.2, 2.5, 1.5])
summa = odat_summa[m] * kat_koef[kat] * rng.lognormal(0, 0.6, n)
soat = np.mod(odat_soat[m] + rng.normal(0, 3, n), 24)
safar = rng.random(n) < 0.03
shahar = np.where(safar, (uy[m] + rng.integers(1, 6, n)) % 6, uy[m])
qurilma = np.where(rng.random(n) < 0.015, rng.integers(1, 1000, n), 0)
firib = np.zeros(n, dtype=bool)
# firibgarlik epizodlari: bir necha soat ichida 1-5 ta tranzaksiya
ep = int(n * 0.003 / 2.6)
em = rng.integers(0, n_mijoz, ep)
et = rng.uniform(0, kunlar - 1, ep)
ek = rng.integers(1, 6, ep)
fm = np.repeat(em, ek)
fv = np.repeat(et, ek) + rng.uniform(0, 0.25, ek.sum())
nf = len(fm)
fk = rng.choice(len(KATEG), nf, p=[0.05, 0.02, 0.05, 0.1, 0.35, 0.03, 0.25, 0.15])
fs = odat_summa[fm] * kat_koef[fk] * rng.lognormal(np.log(1.8), 0.8, nf)
fsoat = np.where(rng.random(nf) < 0.45, rng.uniform(0, 6, nf),
np.mod(odat_soat[fm] + rng.normal(0, 5, nf), 24))
fsh = np.where(rng.random(nf) < 0.5, (uy[fm] + rng.integers(1, 6, nf)) % 6, uy[fm])
fq = np.where(rng.random(nf) < 0.7, rng.integers(1000, 2000, nf), 0)
df = pd.DataFrame({
"mijoz": np.r_[m, fm], "vaqt": np.r_[vaqt, fv], "summa": np.r_[summa, fs],
"soat": np.r_[soat, fsoat], "kategoriya": np.r_[kat, fk],
"shahar": np.r_[shahar, fsh], "qurilma": np.r_[qurilma, fq],
"firib": np.r_[firib, np.ones(nf, dtype=bool)].astype(int)})
kechikish = 3 + rng.exponential(20, len(df)) # chargeback kelishi (kun)
df["belgi_kuni"] = np.where(df["firib"] == 1, df["vaqt"] + kechikish, df["vaqt"])
return df.sort_values(["mijoz", "vaqt"]).reset_index(drop=True)
def belgilar(df, sizish=False):
"""Shaxsiy bazaviy: har tranzaksiya mijozning FAQAT O'TMISHI bilan solishtiriladi."""
df = df.copy()
g = df.groupby("mijoz")
ls = np.log(df["summa"])
df["log_summa"] = ls
if sizish: # XATO: butun davr (kelajak ham) ishlatiladi
o = ls.groupby(df["mijoz"]).transform("mean")
s = ls.groupby(df["mijoz"]).transform("std")
os_ = g["soat"].transform("mean")
else:
n_old = g.cumcount()
yig = ls.groupby(df["mijoz"]).cumsum() - ls
yig2 = (ls ** 2).groupby(df["mijoz"]).cumsum() - ls ** 2
o = yig / n_old.replace(0, np.nan)
s = np.sqrt((yig2 / n_old.replace(0, np.nan) - o ** 2).clip(lower=0.01))
os_ = (df["soat"].groupby(df["mijoz"]).cumsum() - df["soat"]) / n_old.replace(0, np.nan)
df["summa_z"] = ((ls - o) / s).fillna(0)
df["soat_farq"] = (df["soat"] - os_).abs().fillna(0)
df["yangi_shahar"] = (df.groupby(["mijoz", "shahar"]).cumcount() == 0).astype(int)
df["yangi_qurilma"] = (df.groupby(["mijoz", "qurilma"]).cumcount() == 0).astype(int)
birinchi = g.cumcount() == 0
df.loc[birinchi, ["yangi_shahar", "yangi_qurilma"]] = 0
kalit = df["mijoz"].to_numpy() * 1000.0 + df["vaqt"].to_numpy()
df["soni_24s"] = np.arange(len(df)) - np.searchsorted(kalit, kalit - 1.0)
df["oraliq"] = np.log1p(g["vaqt"].diff().fillna(30).to_numpy() * 24)
df["tarix_soni"] = g.cumcount()
return df
XOM = ["log_summa", "soat", "kategoriya"]
SHAXSIY = ["log_summa", "soat", "kategoriya", "summa_z", "soat_farq", "yangi_shahar",
"yangi_qurilma", "soni_24s", "oraliq", "tarix_soni"]
def hgb(X, y):
return HistGradientBoostingClassifier(max_iter=80, learning_rate=0.1, max_leaf_nodes=15,
early_stopping=False, class_weight="balanced",
random_state=0).fit(X, y)
def ap_vazn(y, w, tartib):
"""Og'irlikli average precision (kunlar bo'yicha bootstrap uchun)."""
y, w = y[tartib], w[tartib].astype(float)
tp = np.cumsum(w * y)
return float(np.sum(w * y * tp / np.maximum(np.cumsum(w), 1e-12)) / max(tp[-1], 1e-12))
def kunlik_top(ball, kun, y, n):
"""Har kuni eng shubhali n ta tekshiriladi: kunlik precision va topilganlar."""
prec, top_soni = [], []
for d in np.unique(kun):
i = np.flatnonzero(kun == d)
tanlandi = i[np.argsort(-ball[i], kind="stable")[:n]]
prec.append(y[tanlandi].mean())
top_soni.append(y[tanlandi].sum())
return np.array(prec), np.array(top_soni)
def xarajat(ball, chegara, y, summa, fp_narx=20_000):
"""FN - firib summasi yo'qotiladi; har signal (TP yoki FP) - tekshirish narxi."""
signal = ball >= chegara
return float(summa[(y == 1) & ~signal].sum() + signal.sum() * fp_narx)
def main() -> None:
df = belgilar(tranzaksiyalar())
T = 120 # o'qitish kuni
tr, te = df[df["vaqt"] < T], df[df["vaqt"] >= T]
y_te = te["firib"].to_numpy()
kun = te["vaqt"].astype(int).to_numpy()
print("=== 1. Belgi kechikishi: 120-kunda qaysi firiblar MA'LUM? ===")
malum = tr["belgi_kuni"] < T
f = tr["firib"] == 1
print(f" train dagi firib: {int(f.sum())}, 120-kungacha tasdiqlangani: "
f"{int((f & malum).sum())}")
for a, b in [(0, 60), (60, 90), (90, 105), (105, 120)]:
m = f & (tr["vaqt"] >= a) & (tr["vaqt"] < b)
print(f" {a:>3}-{b:<3} kun: firib {int(m.sum()):>3}, ma'lum {(m & malum).sum() / m.sum():.0%}")
strategiyalar = {
"ideal (hamma yorliq)": (tr, tr["firib"]),
"sodda (noma'lum = 0)": (tr, (tr["firib"] == 1) & malum),
"faqat yetilgan (<75)": (tr[tr["vaqt"] < 75], tr.loc[tr["vaqt"] < 75, "firib"]),
}
ballar = {}
for nom, (qism, y) in strategiyalar.items():
ballar[nom] = hgb(qism[SHAXSIY], y.astype(int)).predict_proba(te[SHAXSIY])[:, 1]
print(f" {nom:<22} o'qitish firiblari {int(y.sum()):>3}, test PR-AUC "
f"{average_precision_score(y_te, ballar[nom]):.4f}")
rng = np.random.default_rng(0)
tartib = {n: np.argsort(-b, kind="stable") for n, b in ballar.items()}
nomlar = list(ballar)
for a, c in [(nomlar[1], nomlar[0]), (nomlar[2], nomlar[1])]:
d = []
for _ in range(200):
w = np.bincount(rng.integers(0, 60, 60), minlength=60)[kun - T]
d.append(ap_vazn(y_te, w, tartib[a]) - ap_vazn(y_te, w, tartib[c]))
nuqta = average_precision_score(y_te, ballar[a]) - average_precision_score(y_te, ballar[c])
print(f" {a} - {c}: {nuqta:+.4f}, SE {np.std(d, ddof=1):.4f}")
print("\n=== 2. Kunlik sig'im: tekshiruvchilar kuniga n ta ko'radi ===")
ifm = IsolationForest(n_estimators=100, random_state=0, n_jobs=1).fit(tr[SHAXSIY])
usul = {"qoida (summa_z>2 va yangi qurilma)":
(te["summa_z"] > 2).to_numpy() * 2.0 + te["yangi_qurilma"].to_numpy()
+ 0.01 * te["summa_z"].to_numpy(),
"IF shaxsiy": -ifm.score_samples(te[SHAXSIY]),
"HistGB (yetilgan)": ballar["faqat yetilgan (<75)"]}
print(f" test: 60 kun, kuniga o'rtacha {len(te) / 60:.0f} tranzaksiya, "
f"{y_te.sum() / 60:.1f} firib")
print(f" {'usul':<36}" + "".join(f"{f'n={n}':>14}" for n in [5, 10, 20]))
kunlik = {}
for nom, b in usul.items():
qator = []
for n in [5, 10, 20]:
p, s = kunlik_top(b, kun, y_te, n)
kunlik[(nom, n)] = s
qator.append(f"{p.mean():.2f}/{s.sum() / y_te.sum():.0%}")
print(f" {nom:<36}" + "".join(f"{q:>14}" for q in qator))
print(" (kunlik precision / topilgan firiblar ulushi)")
for a in ["qoida (summa_z>2 va yangi qurilma)", "IF shaxsiy"]:
d = kunlik[("HistGB (yetilgan)", 10)] - kunlik[(a, 10)]
se = d.std(ddof=1) / np.sqrt(len(d))
print(f" n=10: HistGB - {a}: kuniga {d.mean():+.2f} firib, SE {se:.2f}, "
f"sezilarli: {abs(d.mean()) > 2 * se}")
print("\n=== 3. Xarajatga asoslangan chegara (faraziy narxlar) ===")
print(" FN: firib summasi yo'qoladi; har signal: 20 000 so'm (tekshirish, mijoz noqulayligi)")
trv, va = df[df["vaqt"] < 90], df[(df["vaqt"] >= 90) & (df["vaqt"] < T)]
b_va = hgb(trv[SHAXSIY], trv["firib"]).predict_proba(va[SHAXSIY])[:, 1]
y_va, s_va = va["firib"].to_numpy(), va["summa"].to_numpy()
nomzod = np.quantile(b_va, [0.9, 0.97, 0.99, 0.995, 0.998, 0.999])
xv = [xarajat(b_va, c, y_va, s_va) for c in nomzod]
eng = nomzod[int(np.argmin(xv))]
print(" validatsiya (90-119 kun): " + ", ".join(
f"{c:.3f}: {x / 1e6:.1f}" for c, x in zip(nomzod, xv)) + " (mln so'm)")
print(f" tanlangan chegara: {eng:.3f}")
b = ballar["faqat yetilgan (<75)"]
s_te = te["summa"].to_numpy()
variantlar = {"hech narsa tekshirmaslik": np.inf, "chegara 0.5": 0.5,
"xarajat bo'yicha (val)": eng}
for nom, c in variantlar.items():
signal = b >= c
print(f" TEST {nom:<24} signal {int(signal.sum()):>5}, topildi "
f"{int(y_te[signal].sum()):>3}/{int(y_te.sum())}, xarajat "
f"{xarajat(b, c, y_te, s_te) / 1e6:>6.1f} mln")
print(" ⭐ Chegara - biznes qarori: narxlar va sig'im bilan, validatsiyada")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Belgi kechikishi: 120-kunda qaysi firiblar MA'LUM? ===
train dagi firib: 244, 120-kungacha tasdiqlangani: 178
0-60 kun: firib 116, ma'lum 99%
60-90 kun: firib 62, ma'lum 79%
90-105 kun: firib 13, ma'lum 46%
105-120 kun: firib 53, ma'lum 15%
ideal (hamma yorliq) o'qitish firiblari 244, test PR-AUC 0.7152
sodda (noma'lum = 0) o'qitish firiblari 178, test PR-AUC 0.7002
faqat yetilgan (<75) o'qitish firiblari 148, test PR-AUC 0.6931
sodda (noma'lum = 0) - ideal (hamma yorliq): -0.0150, SE 0.0108
faqat yetilgan (<75) - sodda (noma'lum = 0): -0.0071, SE 0.0156
=== 2. Kunlik sig'im: tekshiruvchilar kuniga n ta ko'radi ===
test: 60 kun, kuniga o'rtacha 597 tranzaksiya, 2.4 firib
usul n=5 n=10 n=20
qoida (summa_z>2 va yangi qurilma) 0.13/27% 0.08/34% 0.07/62%
IF shaxsiy 0.25/52% 0.17/69% 0.09/78%
HistGB (yetilgan) 0.30/62% 0.20/83% 0.10/87%
(kunlik precision / topilgan firiblar ulushi)
n=10: HistGB - qoida (summa_z>2 va yangi qurilma): kuniga +1.18 firib, SE 0.21, sezilarli: True
n=10: HistGB - IF shaxsiy: kuniga +0.33 firib, SE 0.10, sezilarli: True
=== 3. Xarajatga asoslangan chegara (faraziy narxlar) ===
FN: firib summasi yo'qoladi; har signal: 20 000 so'm (tekshirish, mijoz noqulayligi)
validatsiya (90-119 kun): 0.011: 38.0, 0.081: 15.8, 0.334: 9.6, 0.697: 8.7, 0.983: 15.6, 0.994: 24.1 (mln so'm)
tanlangan chegara: 0.697
TEST hech narsa tekshirmaslik signal 0, topildi 0/144, xarajat 81.2 mln
TEST chegara 0.5 signal 206, topildi 103/144, xarajat 25.9 mln
TEST xarajat bo'yicha (val) signal 169, topildi 100/144, xarajat 26.6 mln
⭐ Chegara - biznes qarori: narxlar va sig'im bilan, validatsiyadaNatija tahlili.
1-bo'lim — 120-kunda train dagi 244 firibdan faqat 178 tasi tasdiqlangan. Kechikish oxirgi haftalarda eng og'ir: 105-120 kunlardagi firiblarning faqat 15% i ma'lum. Uch strategiya: "ideal" (hamma yorliq — amalda imkonsiz, faqat taqqoslash uchun) PR-AUC 0.7152; "sodda" (noma'lum = normal) 0.7002; "faqat yetilgan" (75-kungacha) 0.6931. Farqlar kichik va bootstrap SE ichida (-0.0150, SE 0.0108; -0.0071, SE 0.0156). Bu ma'lumotda belgi kechikishi kichik zarar berdi: yetilmagan 66 ta firib class_weight bilan og'irligi oshirilgan 178 ta haqiqiy firib oldida kam. Lekin bu kafolat emas — firib turi tez o'zgaradigan davrda aynan eng yangi firiblar eng qimmatli va aynan ular yetilmagan. Qaysi strategiya yaxshi ekanini har safar o'lchash kerak; biz keyingi bo'limlarda xavfsizroq "faqat yetilgan" modelni ishlatamiz.
2-bo'lim — kunlik sig'im. Testda kuniga ~`597tranzaksiya va2.4firib. Tekshiruvchilar kunigan = 10ta ko'rsa: HistGB firiblarning83%ini topadi (kunlik precision0.20), IF — 69%, oddiy qoida ("summa_z > 2 va yangi qurilma") — 34%. Juftlashgan farq kunlar bo'yicha: HistGB qoidadan kuniga +1.18 firib (SE 0.21), IF dan +0.33 (SE 0.10) ko'proq topadi. nni 20 ga oshirish HistGB da qamrovni83%dan87%` ga oshiradi, precision esa yarmiga tushadi — sig'im va samaradorlik o'rtasidagi murosa.
3-bo'lim — xarajat (faraziy narxlar: o'tkazib yuborilgan firib — uning summasi, har signal — 20 000 so'm). Validatsiyada (90-119 kun; soddalik uchun yorliqlar yetilgan deb olingan) jami xarajat chegara 0.697 da eng kichik (8.7 mln). Testda hech narsa tekshirmaslik 81.2 mln yo'qotish beradi; chegara 0.5 — 25.9 mln, validatsiyada tanlangan 0.697 — 26.6 mln. Halol natija: tanlangan chegara testda 0.5 dan biroz yomon chiqdi — xarajat egri chizig'i minimum atrofida tekis, va val/test farqi shovqin ichida. Asosiy yutuq — chegaraning o'zida emas, tizimda: xarajat 81.2 mln dan ~26 mln ga tushdi. Chegarani narxlar asosida tanlash "0.5" ni tasodifan to'g'ri bo'lib chiqqanini ham tekshirish imkonini beradi.
Misol 4 — Avtoenkoder va IsolationForest: halol taqqoslash
Ikki ma'lumot: (1) uskuna sensorlari — 8 ustun 2 ta yashirin holatdan nochiziqli hosil qilingan, anomaliyada 2 ta ustun boshqa qatordan olinadi (har biri alohida normal, birgalikda mos emas); (2) 2-misoldagi tranzaksiyalar (shaxsiy belgilar, train dan 4000 qator). Har birida 4 urug', uchta nazoratsiz usul: IsolationForest, PCA (3 komponent) qayta tiklash xatosi va avtoenkoder (8 → 16 → 3 → 16 → 8, torch).
"""Avtoenkoder bilan anomaliya: IsolationForest va PCA bilan halol taqqoslash."""
import numpy as np
import pandas as pd
import torch
from sklearn.decomposition import PCA
from sklearn.ensemble import IsolationForest
from sklearn.metrics import average_precision_score
from sklearn.preprocessing import StandardScaler
KATEG = ["oziq", "transport", "restoran", "kiyim", "elektronika", "kommunal",
"otkazma", "onlayn"]
def tranzaksiyalar(seed=0, n_mijoz=1200, kunlar=180):
"""Bank kartasi tranzaksiyalari; firibgarlik - akkaunt egallash epizodlari (~0.3%)."""
rng = np.random.default_rng(seed)
tezlik = rng.lognormal(np.log(0.45), 0.5, n_mijoz) # kuniga tranzaksiya
odat_summa = rng.lognormal(11.3, 0.6, n_mijoz) # ~80 ming so'm
odat_soat = rng.uniform(9, 20, n_mijoz)
uy = rng.integers(0, 6, n_mijoz)
kat_pref = rng.dirichlet(np.full(len(KATEG), 0.7), n_mijoz)
kat_pref[:, 4] *= 0.3 # elektronika kam
kat_pref /= kat_pref.sum(1, keepdims=True)
soni = rng.poisson(tezlik * kunlar)
m = np.repeat(np.arange(n_mijoz), soni)
n = len(m)
vaqt = rng.uniform(0, kunlar, n)
kat = (rng.random(n)[:, None] > np.cumsum(kat_pref[m], 1)).sum(1)
kat_koef = np.array([0.6, 0.3, 1.0, 2.0, 4.0, 1.2, 2.5, 1.5])
summa = odat_summa[m] * kat_koef[kat] * rng.lognormal(0, 0.6, n)
soat = np.mod(odat_soat[m] + rng.normal(0, 3, n), 24)
safar = rng.random(n) < 0.03
shahar = np.where(safar, (uy[m] + rng.integers(1, 6, n)) % 6, uy[m])
qurilma = np.where(rng.random(n) < 0.015, rng.integers(1, 1000, n), 0)
firib = np.zeros(n, dtype=bool)
# firibgarlik epizodlari: bir necha soat ichida 1-5 ta tranzaksiya
ep = int(n * 0.003 / 2.6)
em = rng.integers(0, n_mijoz, ep)
et = rng.uniform(0, kunlar - 1, ep)
ek = rng.integers(1, 6, ep)
fm = np.repeat(em, ek)
fv = np.repeat(et, ek) + rng.uniform(0, 0.25, ek.sum())
nf = len(fm)
fk = rng.choice(len(KATEG), nf, p=[0.05, 0.02, 0.05, 0.1, 0.35, 0.03, 0.25, 0.15])
fs = odat_summa[fm] * kat_koef[fk] * rng.lognormal(np.log(1.8), 0.8, nf)
fsoat = np.where(rng.random(nf) < 0.45, rng.uniform(0, 6, nf),
np.mod(odat_soat[fm] + rng.normal(0, 5, nf), 24))
fsh = np.where(rng.random(nf) < 0.5, (uy[fm] + rng.integers(1, 6, nf)) % 6, uy[fm])
fq = np.where(rng.random(nf) < 0.7, rng.integers(1000, 2000, nf), 0)
df = pd.DataFrame({
"mijoz": np.r_[m, fm], "vaqt": np.r_[vaqt, fv], "summa": np.r_[summa, fs],
"soat": np.r_[soat, fsoat], "kategoriya": np.r_[kat, fk],
"shahar": np.r_[shahar, fsh], "qurilma": np.r_[qurilma, fq],
"firib": np.r_[firib, np.ones(nf, dtype=bool)].astype(int)})
kechikish = 3 + rng.exponential(20, len(df)) # chargeback kelishi (kun)
df["belgi_kuni"] = np.where(df["firib"] == 1, df["vaqt"] + kechikish, df["vaqt"])
return df.sort_values(["mijoz", "vaqt"]).reset_index(drop=True)
def belgilar(df, sizish=False):
"""Shaxsiy bazaviy: har tranzaksiya mijozning FAQAT O'TMISHI bilan solishtiriladi."""
df = df.copy()
g = df.groupby("mijoz")
ls = np.log(df["summa"])
df["log_summa"] = ls
if sizish: # XATO: butun davr (kelajak ham) ishlatiladi
o = ls.groupby(df["mijoz"]).transform("mean")
s = ls.groupby(df["mijoz"]).transform("std")
os_ = g["soat"].transform("mean")
else:
n_old = g.cumcount()
yig = ls.groupby(df["mijoz"]).cumsum() - ls
yig2 = (ls ** 2).groupby(df["mijoz"]).cumsum() - ls ** 2
o = yig / n_old.replace(0, np.nan)
s = np.sqrt((yig2 / n_old.replace(0, np.nan) - o ** 2).clip(lower=0.01))
os_ = (df["soat"].groupby(df["mijoz"]).cumsum() - df["soat"]) / n_old.replace(0, np.nan)
df["summa_z"] = ((ls - o) / s).fillna(0)
df["soat_farq"] = (df["soat"] - os_).abs().fillna(0)
df["yangi_shahar"] = (df.groupby(["mijoz", "shahar"]).cumcount() == 0).astype(int)
df["yangi_qurilma"] = (df.groupby(["mijoz", "qurilma"]).cumcount() == 0).astype(int)
birinchi = g.cumcount() == 0
df.loc[birinchi, ["yangi_shahar", "yangi_qurilma"]] = 0
kalit = df["mijoz"].to_numpy() * 1000.0 + df["vaqt"].to_numpy()
df["soni_24s"] = np.arange(len(df)) - np.searchsorted(kalit, kalit - 1.0)
df["oraliq"] = np.log1p(g["vaqt"].diff().fillna(30).to_numpy() * 24)
df["tarix_soni"] = g.cumcount()
return df
XOM = ["log_summa", "soat", "kategoriya"]
SHAXSIY = ["log_summa", "soat", "kategoriya", "summa_z", "soat_farq", "yangi_shahar",
"yangi_qurilma", "soni_24s", "oraliq", "tarix_soni"]
def sensor(seed, n=5000, ulush=0.02):
"""Uskuna sensorlari: 8 ustun 2 ta yashirin holatdan (burchak t, yuklama s) NOCHIZIQLI."""
rng = np.random.default_rng(seed)
t, s = rng.uniform(0, 2 * np.pi, n), rng.normal(0, 1, n)
X = np.column_stack([np.cos(t), np.sin(t), np.cos(2 * t), np.sin(t) * s,
s, np.tanh(2 * s), 0.5 * np.cos(t) + 0.5 * s, np.exp(0.5 * s)])
X += rng.normal(0, 0.05, X.shape)
y = np.zeros(n, dtype=int)
a = rng.choice(n, int(n * ulush), replace=False)
for i in a: # 2 ta ustun boshqa qatordan: har biri
j = rng.choice(8, 2, replace=False) # alohida normal, birga - g'ayrioddiy
X[i, j] = X[rng.integers(0, n), j]
y[a] = 1
return X, y
def avtoenkoder(Xtr, Xte, seed, yashirin=3, qadam=600):
torch.manual_seed(seed)
torch.set_flush_denormal(True)
d = Xtr.shape[1]
model = torch.nn.Sequential(torch.nn.Linear(d, 16), torch.nn.Tanh(),
torch.nn.Linear(16, yashirin), torch.nn.Tanh(),
torch.nn.Linear(yashirin, 16), torch.nn.Tanh(),
torch.nn.Linear(16, d))
opt = torch.optim.Adam(model.parameters(), lr=0.01)
A = torch.tensor(Xtr, dtype=torch.float32)
for _ in range(qadam): # to'liq partiya, MSE
loss = ((model(A) - A) ** 2).mean()
opt.zero_grad()
loss.backward()
opt.step()
with torch.no_grad():
B = torch.tensor(Xte, dtype=torch.float32)
return ((model(B) - B) ** 2).mean(1).numpy(), float(loss)
def uch_usul(Xtr, Xte, seed):
sk = StandardScaler().fit(Xtr)
A, B = sk.transform(Xtr), sk.transform(Xte)
b = {"IsolationForest": -IsolationForest(n_estimators=100, random_state=seed, n_jobs=1)
.fit(Xtr).score_samples(Xte)}
p = PCA(3, random_state=seed).fit(A)
b["PCA (3) qayta tiklash"] = ((B - p.inverse_transform(p.transform(B))) ** 2).mean(1)
b["avtoenkoder (3)"], _ = avtoenkoder(A, B, seed)
return b
def main() -> None:
print("=== 1. Avtoenkoder ichkarisi: qayta tiklash xatosi (sensor, urug' 0) ===")
X, y = sensor(0)
Xtr, Xte, yte = X[:3000], X[3000:], y[3000:]
sk = StandardScaler().fit(Xtr)
xato, loss = avtoenkoder(sk.transform(Xtr), sk.transform(Xte), 0)
print(f" 8 ustun -> 3 yashirin -> 8 ustun; oxirgi train MSE {loss:.4f}")
print(f" test qayta tiklash xatosi: normal mediana {np.median(xato[yte == 0]):.4f}, "
f"anomaliya mediana {np.median(xato[yte == 1]):.4f}")
z = np.abs(StandardScaler().fit(Xtr).transform(Xte))
print(f" alohida ustunlarda |z| > 3 bo'lgan anomaliyalar: "
f"{np.mean(z[yte == 1].max(1) > 3):.0%} (normal: {np.mean(z[yte == 0].max(1) > 3):.0%})")
print("\n=== 2. Ikki ma'lumot, 4 urug', PR-AUC ===")
natija = {}
for seed in range(4):
X, y = sensor(seed)
natija.setdefault("sensor (nochiziqli bog'liqlik)", []).append(
(uch_usul(X[:3000], X[3000:], seed), y[3000:]))
df = belgilar(tranzaksiyalar(seed=seed, n_mijoz=400))
tr, te = df[df["vaqt"] < 120], df[df["vaqt"] >= 120]
tr = tr.sample(4000, random_state=seed) # kichik jadval: 4000 qator
natija.setdefault("tranzaksiyalar (shaxsiy belgilar)", []).append(
(uch_usul(tr[SHAXSIY].to_numpy(float), te[SHAXSIY].to_numpy(float), seed),
te["firib"].to_numpy()))
tartib = ["IsolationForest", "PCA (3) qayta tiklash", "avtoenkoder (3)"]
for nom, qatorlar in natija.items():
pr = {u: np.array([average_precision_score(yy, b[u]) for b, yy in qatorlar])
for u in tartib}
print(f" {nom}: anomaliya ulushi {np.mean([yy.mean() for _, yy in qatorlar]):.2%}")
for u in tartib:
print(f" {u:<24} PR-AUC {pr[u].mean():.4f} (urug'lar: "
f"{', '.join(f'{v:.3f}' for v in pr[u])})")
eng = max(tartib, key=lambda u: pr[u].mean())
tanlov = None
for u in tartib:
d = pr[u] - pr[eng]
se = d.std(ddof=1) / np.sqrt(len(d))
if u != eng:
print(f" {u} - {eng}: {d.mean():+.4f}, SE {se:.4f}")
if tanlov is None and d.mean() >= -2 * se:
tanlov = u
print(f" qaror (eng sodda munosib): {tanlov}")
print(" ⭐ Avtoenkoder - murakkab bog'liqlik bo'lsa; oddiy jadvalda IF ko'pincha yetarli")
if __name__ == "__main__":
main()Natijaning muhim qismi:
=== 1. Avtoenkoder ichkarisi: qayta tiklash xatosi (sensor, urug' 0) ===
8 ustun -> 3 yashirin -> 8 ustun; oxirgi train MSE 0.0226
test qayta tiklash xatosi: normal mediana 0.0123, anomaliya mediana 0.2606
alohida ustunlarda |z| > 3 bo'lgan anomaliyalar: 3% (normal: 2%)
=== 2. Ikki ma'lumot, 4 urug', PR-AUC ===
sensor (nochiziqli bog'liqlik): anomaliya ulushi 1.98%
IsolationForest PR-AUC 0.0365 (urug'lar: 0.031, 0.034, 0.047, 0.035)
PCA (3) qayta tiklash PR-AUC 0.0487 (urug'lar: 0.028, 0.047, 0.083, 0.035)
avtoenkoder (3) PR-AUC 0.6414 (urug'lar: 0.647, 0.699, 0.488, 0.732)
IsolationForest - avtoenkoder (3): -0.6049, SE 0.0570
PCA (3) qayta tiklash - avtoenkoder (3): -0.5928, SE 0.0647
qaror (eng sodda munosib): avtoenkoder (3)
tranzaksiyalar (shaxsiy belgilar): anomaliya ulushi 0.34%
IsolationForest PR-AUC 0.5288 (urug'lar: 0.620, 0.641, 0.355, 0.499)
PCA (3) qayta tiklash PR-AUC 0.1339 (urug'lar: 0.040, 0.060, 0.237, 0.199)
avtoenkoder (3) PR-AUC 0.1514 (urug'lar: 0.175, 0.021, 0.094, 0.315)
PCA (3) qayta tiklash - IsolationForest: -0.3949, SE 0.1135
avtoenkoder (3) - IsolationForest: -0.3774, SE 0.0978
qaror (eng sodda munosib): IsolationForest
⭐ Avtoenkoder - murakkab bog'liqlik bo'lsa; oddiy jadvalda IF ko'pincha yetarliNatija tahlili.
1-bo'lim — avtoenkoder sensor ma'lumotining nochiziqli tuzilmasini 3 o'lchamli yashirin fazoga siqdi: test da normal nuqtalarning qayta tiklash xatosi mediana 0.0123, anomaliyalarniki 0.2606 — yigirma barobardan ko'proq. Holbuki alohida ustunlar bo'yicha anomaliyalar deyarli ko'rinmaydi: |z| > 3 anomaliyalarning 3% ida, normallarning 2% ida.
2-bo'lim, sensor — aynan avtoenkoder uchun yaratilgan vazifa. IF (0.0365) va PCA (0.0487) deyarli tasodifiy darajada (anomaliya ulushi 1.98%): IF har ustunni alohida bo'ladi va marginal taqsimotlar normal; PCA chiziqli — aylana (cos t, sin t) va eksponenta kabi nochiziqli bog'liqlikni tiklay olmaydi. Avtoenkoder 0.6414 — IF dan +0.60 (SE 0.057). Qaror qoidasi avtoenkoderni tanladi — bu yerda murakkablik o'zini oqlaydi.
2-bo'lim, tranzaksiyalar — manzara teskari. IF 0.5288, PCA 0.1339, avtoenkoder 0.1514; avtoenkoder IF dan -0.3774 (SE 0.0978) — sezilarli yomon. Sabab: firib bir-ikki belgida kuchli chetga chiqish (katta summa_z, yangi qurilma, tungi soat) — IF buni bir necha bo'linishda ajratadi. Qayta tiklash xatosi esa hamma ustunlarning xatosini qo'shadi va tarix_soni, oraliq kabi "shovqinli", lekin firib bilan bog'liq bo'lmagan ustunlar xatoni to'ldiradi. Urug'lar orasidagi tarqoqlik ham avtoenkoderda kattaroq (0.021 dan 0.315 gacha) — u sozlamalarga (masshtab, epoxa, arxitektura) sezgir.
Darsning halol xulosasi: avtoenkoder "IF ning yangi, yaxshiroq versiyasi" emas. U ko'p o'lchovli nochiziqli bog'liqlik buzilganda kuchli; oddiy jadvalda, anomaliya alohida belgilarda ko'rinsa, IF sodda, tez va yaxshiroq. Ikkalasini har doim bir xil bo'lish va bir xil metrikada solishtiring.
5. To'g'ri va noto'g'ri tushunishlar
| Noto'g'ri fikr | To'g'risi |
|---|---|
| "z-score > 3 — anomaliya" | Kontekstsiz z kontekstli anomaliyani ko'rmaydi (1-misol: 0.00) |
| "STL eng yaxshi natija berdi — real vaqtda ishlatamiz" | STL kelajakni ko'radi; real vaqtda sababiy bashorat qoldig'i |
| "Bayramdagi pasayish — anomaliya" | Kutilgan o'zgarish; kontekst sifatida modelga kiradi |
| "Nuqta bo'yicha precision/recall yetarli" | Hodisa recall, yolg'on signal/hafta va kechikish kerak |
| "ROC-AUC 0.97 — a'lo detektor" | FPR 1% da precision 21% (2-misol); PR-AUC qarang |
| "Summa katta — firib" | Shaxsiy bazaviysiz IF PR-AUC 0.07, bilan — 0.49 |
| "Nomutanosiblikda nazoratli model ham avtomatik ishlaydi" | Og'irliksiz HistGB 0.22, balanced bilan 0.72 |
| "Chegara — 0.5" | Chegara narxlar va sig'imdan kelib chiqadi |
| "Hozirgacha tasdiqlanmagan — demak normal" | Belgi kechikishi: oxirgi haftadagi firiblarning 85% i hali noma'lum |
| "Avtoenkoder — IF dan zamonaviyroq, demak yaxshiroq" | Oddiy jadvalda IF sezilarli yaxshi chiqdi (4-misol) |
6. Keng tarqalgan xatolar va yechimlari
1. Kontekstsiz statistika
z = (x - x.mean()) / x.std() # ⚠️
qoldiq = x - np.median([x.shift(168 * k) for k in range(1, 5)], axis=0) # ✅ shu soat2. Kelajakni ko'radigan detektor
q = STL(x, period=24).fit().resid # ⚠️ real vaqt detektori sifatida
# real vaqtda: faqat o'tmishdan bashorat qoldig'i # ✅3. Chegara "ko'z bilan"
signal = z > 3 # ⚠️
chegara = np.nanquantile(ball[kalibrlash], 1 - 1 / 168) # ✅ byudjet4. Kelajakdan shaxsiy bazaviy
df["nisbat"] = df["summa"] / df.groupby("mijoz")["summa"].transform("median") # ⚠️
yig = ls.groupby(df["mijoz"]).cumsum() - ls # ✅ faqat o'tmish5. ROC-AUC bilan tanlash
eng = max(modellar, key=lambda m: roc_auc_score(y, m)) # ⚠️
eng = max(modellar, key=lambda m: average_precision_score(y, m)) # ✅ + top-k6. Yetilmagan yorliqlar
y = df["firib_malum"].fillna(0) # ⚠️ firib = 0
tr = df[df["vaqt"] < T - L] # ✅ yetilgan davr7. Sinf og'irligisiz nomutanosib boosting
HistGradientBoostingClassifier() # ⚠️
HistGradientBoostingClassifier(class_weight="balanced") # ✅ va PR-AUC da tekshiring7. Integratsiya — bu bilim qayerda kerak bo'ladi
- 08.6, 16.10-darslar (o'tilgan): Anomaliya turlari, IsolationForest, LOF, PCA qayta tiklash, PR-AUC va precision@k
- 14-qism, 18-qism (o'tilgan): Nomutanosib klassifikatsiya, PR egri chizig'i, chegara tanlash
- 17.8-dars (o'tilgan): Leakage — shaxsiy bazaviy faqat o'tmishdan
- 26.2-dars (o'tilgan): Avtoenkoder — qayta tiklash xatosi
- 27.11-27.12-darslar (o'tilgan): Alerting byudjeti, drift va Page-Hinkley — oqimdagi o'zgarish aniqlash
- 28.1-28.3-darslar (o'tilgan): Mavsum, bayram belgilari va bashorat modellari — bashorat qoldig'i shular ustida
- 28.11-dars: Sababiy xulosa — "bu choralar firibni kamaytirdimi?" savoliga javob
8. Eng yaxshi amaliyotlar
"Normal" ni kontekst bilan aniqlang: soat, kun, bayram, mijozning o'z tarixi.
Real vaqt detektori faqat o'tmishni ko'rsin; oflayn tahlil bilan aralashtirmang.
Hodisa darajasida baholang: recall, yolg'on signal/hafta, kechikish.
Detektorlarni bir xil yolg'on signal byudjetida solishtiring; chegarani toza davrda kalibrlang.
Nomutanosib ma'lumotda PR-AUC, kunlik top-k va xarajat; ROC-AUC — faqat qo'shimcha.
Yorliqlar bo'lsa — nazoratli model (
class_weight); nazoratsiz — yangi tur signali.Vaqt bo'yicha bo'ling va belgi kechikishini hisobga oling.
Murakkab usulni (avtoenkoder) har doim IsolationForest bilan juftlashgan taqqoslang.
9. Amaliy topshiriq
Vazifa 1: Bashorat qiling
1. # 20:00 da 39 MW (odatda 57), umumiy o'rtacha 43 - global z qanday?
2. # rolling 24 soatlik oyna kontekstli anomaliyani nega ko'rmaydi?
3. # MAD ni std ga aylantirish koeffitsienti?
4. # STL qoldig'i real vaqt detektori bo'la oladimi?
5. # sensor qotib qolsa, detektor nega kechikadi?
6. # firib ulushi 0.4% - tasodifiy modelning PR-AUC i?
7. # 36000 normal, FPR 1% - nechta yolg'on signal?
8. # 16.10 dagi groupby().transform("median") nima uchun xavfli?
9. # nomutanosib boosting ga nima qo'shish kerak?
10. # o'qitish kunida oxirgi 2 haftadagi firiblar qanday belgilangan bo'ladi?
11. # FN narxi katta, FP narxi kichik - chegara qaysi tomonga siljiydi?
12. # anomaliya bitta ustunda katta chetlanish - IF yoki avtoenkoder?Javoblar
- Kichik (~0.4) — globalda normal, faqat soat kontekstida g'ayrioddiy
- Oynada kun profilining hammasi bor — "odatiy" daraja soatni bilmaydi
1.4826(normal taqsimotda std = 1.4826 * MAD)- Yo'q — u keyingi qiymatlarni ham ishlatadi; faqat oflayn tahlil uchun
- Qotgan qiymat dastlab normal; farq profil undan uzoqlashganda paydo bo'ladi
0.004(ulushga teng)360ta — firiblar esa bor-yo'g'i yuzga yaqin- Kelajakdagi tranzaksiyalarni ham ishlatadi — ishlab chiqarishda bunday belgi yo'q
class_weight="balanced"(yokisample_weight) va PR-AUC bilan tekshirish- Ko'pchiligi hali "normal" — chargeback hali kelmagan
- Pastga — ko'proq signal, kamroq o'tkazib yuborish
- IsolationForest — sodda va odatda yaxshiroq (4-misol, tranzaksiyalar)
Vazifa 2: Xatolarni tuzating
1. signal = np.abs(x - x.mean()) / x.std() > 3 # soatlik elektr
2. df["nisbat"] = df["summa"] / df.groupby("mijoz")["summa"].transform("mean")
3. eng = max(modellar, key=lambda m: roc_auc_score(y_te, ball[m]))
4. y_train = df_train["firib_tasdiqlangan"].fillna(0) # oxirgi haftalar ham
5. chegara = 0.5 # FN = summa, FP = 20 000Javoblar
1. bashorat = np.median([x.shift(168 * k) for k in range(1, 5)], axis=0)
ball = np.abs(x - bashorat) / (1.4826 * mad_qoldiq)
signal = ball > np.nanquantile(ball[kalibrlash], 1 - 1 / 168)
2. n_old = df.groupby("mijoz").cumcount()
df["nisbat"] = df["summa"] / ((df.groupby("mijoz")["summa"].cumsum()
- df["summa"]) / n_old)
3. eng = max(modellar, key=lambda m: average_precision_score(y_te, ball[m]))
4. df_train = df_train[df_train["vaqt"] < T - L] # faqat yetilgan davr
5. chegara = min(nomzodlar, key=lambda c: xarajat(ball_val, c, y_val, summa_val))Vazifa 3: Vaqt qatori
Modellang (1-misol asosida):
- Bashorat qoldig'iga bayram belgisini qo'shing (bayram kuni — o'tgan haftaning yakshanbasi bilan solishtirish) va bayramdagi yolg'on signallarni sanang
- Kollektiv anomaliya uchun "o'zgarmaslik" detektorini yozing: oxirgi 4 soatda qiymat o'zgarishlari std si juda kichik — kechikish qanchaga qisqaradi?
- Yolg'on signal byudjetini haftasiga 0.5, 1, 3 qilib, har detektorning hodisa recall ini jadval qiling
STL(period=168)ni sinab ko'ring — dam olish kunlari effekti qoldiqdan chiqadimi?
Vazifa 4: Firibgarlik belgilari
Modellang (2-misol asosida):
- Tasodifiy bo'lish bilan xuddi shu modellarni baholang — PR-AUC qanchaga oshib ketadi va nega?
- Har bir shaxsiy belgini navbat bilan olib tashlab (ablation), HistGB PR-AUC ga hissasini o'lchang
- IF ballini HistGB ga belgi sifatida qo'shing (yarim nazoratli b) — sezilarli yutuq bormi?
- Firibning yangi turini yarating (masalan, kichik summalar seriyasi) va faqat test ga qo'shing — HistGB va IF qanday ushlaydi?
Vazifa 5: Amaliyot
Modellang (3-misol asosida):
- Kechikish o'rtachasini 20 kundan 45 kunga oshiring — "sodda" va "yetilgan" strategiyalar farqi qanday o'zgaradi?
- FP narxini 5 000, 20 000, 100 000 so'm qiling — optimal chegara va signal soni
- Kunlik sig'im
nni xarajat funksiyasiga qo'shing:ndan ortiq signal tekshirilmaydi - Test davrini ikki qismga bo'lib, validatsiyada tanlangan chegaraning barqarorligini tekshiring
Vazifa 6: Avtoenkoder
Modellang (4-misol asosida):
- Yashirin o'lchamni 2, 3, 6 qiling — sensor ma'lumotida PR-AUC qanday o'zgaradi?
- Avtoenkoderni faqat normal train qatorlarida o'qiting — ifloslangan train bilan farq bormi?
- Tranzaksiyalarda avtoenkoderga faqat 4 ta eng informativ belgini bering — IF ga yetib oladimi?
- PCA komponentlar sonini 2-6 qilib, sensor ma'lumotida qaysi nuqtada AE ga yaqinlashishini toping
Vazifa 7: O'ylash
Bankning firibgarlikka qarshi bo'limi boshlig'i: "Bizda IsolationForest bor, ROC-AUC 0.97. Operatorlar esa kuniga yuzlab yolg'on signaldan charchagan va ishonmay qo'ygan. Chuqur o'rganish — avtoenkoderga o'tsak, muammo hal bo'ladimi?" Nima deysiz?
Javob
Qisqa javob: yo'q — muammo algoritmda emas, metrika, chegara va yorliqlardan foydalanmaslikda. Avtoenkoder bu turdagi jadval ma'lumotida IF dan yaxshi bo'lishi kutilmaydi (4-misol: tranzaksiyalarda sezilarli yomon).
1. Metrika. ROC-AUC 0.97 nomutanosiblikda "a'lo" ko'rinadi, lekin 2-misolda xuddi shunday ROC-AUC (0.967) FPR 1% da ham precision atigi 21% berdi — 36 ming normalning 1% i 360 ta yolg'on signal. Asosiy metrika — PR-AUC va kunlik sig'imdagi topilgan firiblar.
2. Sig'im va chegara. Operatorlar kuniga ko'ra oladigan sonni (masalan, 10) chegara sifatida belgilang: har kuni faqat eng shubhali 10 ta. 3-misolda HistGB bilan bu kunlik precision 0.20 va firiblarning 83% ini beradi — "yuzlab" o'rniga 10 ta, har beshinchisi haqiqiy. Narxlar ma'lum bo'lsa, chegara xarajat bo'yicha tanlanadi.
3. Yorliqlardan foydalanish. Bankda tasdiqlangan firiblar tarixi bor — bu nazoratli model uchun oltin. 2-misolda HistGB (class_weight="balanced") IF dan PR-AUC da +0.22 sezilarli yaxshi. IF ni tashlamaymiz — u yangi turdagi firiblar uchun qo'shimcha signal.
4. Belgilar. Shaxsiy bazaviy belgilar (mijozning o'z o'tmishiga nisbatan) PR-AUC ni yetti barobar oshirdi (0.07 → 0.49) — algoritm almashtirishdan ko'ra ancha katta yutuq.
5. Yorliqlar kechikishi. Qayta o'qitishda oxirgi haftalardagi hali tasdiqlanmagan firiblar "normal" bo'lib qolmasligi kerak — yetilgan davr yoki buni o'lchab tanlangan strategiya.
Boshliqqa javob: "Avtoenkoder bu muammoni hal qilmaydi — bizning sinovimizda oddiy jadvalda u IsolationForest dan yomon chiqdi. Muammo shundaki, biz signallarni ROC-AUC bo'yicha baholab, chegarani operatorlar sig'imidan qat'i nazar qo'yganmiz va tasdiqlangan firiblar tarixidan foydalanmayapmiz. Taklif: shaxsiy belgilar bilan nazoratli model, har kuni faqat top-10 signal, IF — yangi turlar uchun qo'shimcha. Kutilgan natija: kuniga 10 ta signal, har beshinchisi haqiqiy firib, firiblarning ~80% i topiladi — buni test davrida raqam bilan ko'rsataman."
Nimani mustahkamlaydi: 2.2, 2.4, 2.5, 2.8, 2.9-bo'limlar.
Xulosa
Bu darsda anomaliya aniqlashni kontekst, vaqt, narx va halol taqqoslash bilan kengaytirdik.
Eng muhim uch fikr:
"Normal" kontekstga bog'liq. 1-misolda global, rolling va robust z-score kontekstli anomaliyalarni deyarli ko'rmadi (global z —
0.00), mavsumiy kontekstni biladigan bashorat qoldig'i esa bir xil yolg'on signal byudjetida hodisalarning0.91qismini topdi. STL undan ham yaxshi (0.98), lekin kelajakni ko'radi — faqat oflayn tahlil uchun. Kollektiv anomaliya kechikish bilan (~4soat) topiladi, bayram esa kontekstsiz modelni aldaydi (bayramda8.5yolg'on signal).Firibgarlik — nomutanosiblik, sig'im va narx masalasi. ROC-AUC
0.967bo'lgan detektor FPR 1% da21%precision berdi. Shaxsiy bazaviy belgilar IF ning PR-AUC ini0.07dan0.49ga oshirdi; tasdiqlangan yorliqlar bilan sinf og'irlikli HistGB0.72ga yetdi va kuniga 10 ta tekshiruvda firiblarning83%ini topdi. Belgi kechikishi oxirgi haftalardagi firiblarning85%ini yashirdi — bu ma'lumotda zarari kichik chiqdi, lekin uni har safar o'lchash kerak. Chegara narxlardan kelib chiqadi va validatsiyada tanlanadi.Murakkab usul — faqat o'lchab. Avtoenkoder nochiziqli bog'liqlik buzilgan sensor ma'lumotida IF va PCA ni katta farq bilan yengdi (
0.64va0.04), oddiy tranzaksiya jadvalida esa IF dan sezilarli yomon chiqdi (0.15va0.53). Qoida o'zgarmaydi: bir xil bo'lish, bir xil metrika, juftlashgan taqqoslash va eng sodda munosib usul.
Keyingi darsda Reinforcement learning asoslari: model endi faqat "bu g'alati" demaydi, balki o'zi qaror qabul qiladi, uning oqibatini ko'radi va ketma-ket qarorlardan o'rganadi — agent, muhit, mukofot, Markov qaror jarayoni, qiymat funksiyalari va Q-learning.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!