A Northstar adathalmazon a korábbi klaszterező fejezet csoportokat
írt le; most a ritka, emberi vizsgálatra érdemes rendeléseket keressük.
A megfigyelési egység egy rendelés (orders.csv, 2400 sor).
A bemenet rendelésenként öt, a rendelés pillanatában ismert numerikus
mező:
distance_km: szállítási távolság,priority_flag: 0/1 gyorsított szállítási jelölő,basket_minor: kosárérték EUR-centben,quantity_sum: összes darabszám,shipping_fee_minor: szállítási díj EUR-centben.A kimenet egy rangsorolt vizsgálati lista és az első tétel ellenőrzött találati aránya, azaz precision at k. A laborban 36 szintetikus eltérést ültetünk a tiszta adatba, így a „emberi validáció” a tanulási környezetben ismert ground truth lesz; éles projektben ezt a szerepet üzleti szakértő vagy adatgazda tölti be.
Induló becslés, mielőtt a módszert olvasná: ha 2400 rendelésből 36 valóban vizsgálandó, és véletlenszerűen nézünk meg 48 sort, akkor az első 48 találati arányának várható értéke , és várhatóan mindössze találatunk lenne. A modelltől azt várjuk, hogy ennél lényegesen jobb legyen. A lehetséges maximum , mert összesen csak 36 megerősíthető anomália van.
Az anomália (anomaly vagy outlier) itt nem erkölcsi ítélet: olyan megfigyelés, amely a referenciaeloszlástól feltűnően eltér. A „feltűnően” mindig a választott modell és változókészlet szerint értendő. Az anomáliapontszám tehát vizsgálati feladat, nem automatikus csalás- vagy adathibacímke.
Az IsolationForest (izolációs erdő) véletlen döntési fákból építkezik, de nem célváltozót tanul. Minden fa egy véletlen részmintán, véletlen jellemzővágásokkal növekszik. Egy pont úthossza az a splitmegosztási mélység, ahol a pont egyedül marad a levélben. Az extrém pontot kevés vágás izolálja, mert az értéktartomány szélén egyetlen jó vágásra van szükség. A pontszám az átlagos faúthosszból képzett:
ahol
egy-egy fa úthossza,
az erdő fáira vett átlag,
az
elemhez tartozó normalizáló, elméleti átlagos úthossz. A magas
rövid átlagos utat, tehát eltérő pontot jelent. A scikit-learn
decision_function visszatérési előjele ezzel ellentétes
konvenciót használ, ezért a kódban
score = -decision_function(X) adja a magas értéknél
gyanúsabb pontszámot.
Két használati mód van, és a két angol szó nem felcserélhető:
A mi laborunk outlier mód, mert a vizsgálandó állomány már
tartalmazhat anomáliát. A contamination paraméter az
előzetesen feltételezett anomáliarány; itt 36/2400 = 0.015.
Ez nem címkézett arány, hanem a bináris döntési küszöb eltolása. A
folytonos pontszámból képzett rangsor szempontjából a
contamination csak egy konstans eltolás, ezért az első
tétel kiválasztását nem a predict bináris címkéjére, hanem
a pontszám szerinti rendezésre bízzuk.
A precision at k a vizsgálandó lista első elemének megerősített anomáliaránya:
ahol a pontszám szerinti -edik sor indexe, a ground truth anomáliajelző. A metrika tartománya , a nevező mindig az ellenőrzött sorok száma , nem az összes sor és nem is az összes anomália. Ha valódi anomália van és , akkor .
A ritka esemény bizonytalansága visszaköszön: egy véletlen lista találati arányának szórása közelítőleg
ahol az alapráta. A laborban és mellett , tehát a véletlen összehasonlítási alap alacsony, de egyetlen kis -s mérés zajos.
Mentsük a következő kódot a projektgyökérbe
s095_anomaly_review.py néven, és futtassuk ugyanonnan, ahol
a lab/ mappa található:
# Linux/macOS, projektgyökér
python s095_anomaly_review.pyimport numpy as np
import pandas as pd
from sklearn.ensemble import IsolationForest
rng = np.random.default_rng(20261020)
orders = pd.read_csv("lab/orders.csv")
feature_cols = [
"distance_km", "priority_flag", "basket_minor",
"quantity_sum", "shipping_fee_minor",
]
X = orders[feature_cols].to_numpy(dtype=float)
n, p = X.shape
y_true = np.zeros(n, dtype=int)
# 36 szintetikus, emberi vizsgálatra érdemes eltérés
idx = rng.choice(n, size=36, replace=False)
idx_distance = idx[:12]
idx_basket = idx[12:24]
idx_quantity = idx[24:36]
X[idx_distance, 0] += rng.uniform(500, 900, size=12)
X[idx_basket, 2] *= rng.uniform(6, 10, size=12)
X[idx_quantity, 3] += rng.uniform(40, 80, size=12)
y_true[idx] = 1
model = IsolationForest(
n_estimators=200,
max_samples=256,
contamination=36 / n,
random_state=7,
)
model.fit(X)
score = -model.decision_function(X) # magas = gyanúsabb
rank = np.argsort(-score)
k = 48
top_k = rank[:k]
p_at_k = y_true[top_k].mean()
review = pd.DataFrame({
"rank": np.arange(1, k + 1),
"order_id": orders.iloc[top_k]["order_id"].to_numpy(),
"anomaly_score": score[top_k],
"simulated_human_decision": np.where(
y_true[top_k] == 1, "confirmed", "not_confirmed"
),
})
print(f"X shape={X.shape}, injected={y_true.sum()}, "
f"P@48={p_at_k:.3f}")
print(review.head(10).to_string(index=False))Szemléltető kimenet (a tényleges futtatás ettől eltérhet; e könyvfejezet írásakor a kód helyben nem futott):
X shape=(2400, 5), injected=36, P@48=0.562
rank order_id anomaly_score simulated_human_decision
1 ORD-0187 0.732000 confirmed
2 ORD-1104 0.711000 confirmed
3 ORD-0442 0.708000 not_confirmed
4 ORD-1931 0.701000 confirmed
...
A P@48 a vizsgált 48 sorból megerősített találatok
aránya; a fenti szemléltető érték 0.562, azaz körülbelül 27 valódi
eltérést talált az első 48 helyen. Ez jóval a véletlen 0.015 alap felett
van, és nem lépi át a 0.75 elméleti maximumot. Ha a számításból 0.75-nél
nagyobb érték jönne ki, az a ground truth-összerendelés hibájára utalna;
ha 0.015 körüli, akkor a modell nem tanult rangsorolható jelet, vagy a
rangsor véletlen.
Az invariánsok: X.shape = (2400, 5),
score.shape = (2400,), rank.shape = (2400,),
review 48 sorból áll, és
review["simulated_human_decision"] csak a szimulált
ellenőrző címke. A contamination explicit megadása nem
befolyásolja a rangsort, de a predict kimenetét igen; ezért
a labor a folytonos score-t és az argsort(-score) sorrendet
használja.
A három injektált torzítási mód különböző feature-ökben jelentkezik. A távolság és a kosárérték szélsőségei valószínűleg korán izolálódnak, a darabszám-eltolás kevésbé domináns lehet, mert a normál rendelések darabszáma is változékony. Az emberi vizsgálati lista mellé ezért érdemes a nyers feature-értékeket is kirakni; a pontszám önmagában nem magyarázza meg, mit érdemes nézni.
Felügyelet nélküli anomáliapontszám nem azonos a csalás vagy késés
valószínűségével. A contamination paraméter gyakran a
kijelölés küszöbét befolyásolja; a valódi üzleti pozitív prediktív érték
címkézett auditmintát igényel.
Egy hat soros halmaz anomáliapontszám szerinti sorrendje:
[2, 0, 1, 3, 4, 5]. A teljes y ground truth
vektor: [0, 0, 1, 0, 1, 0], azaz csak a 2. és 4. indexű sor
valódi anomália. Számítsa ki a P@3 és a P@6
értékét, és magyarázza el, miért lehet fontos a P@3 akkor
is, ha a teljes találati halmazt P@6 már megmutatja.
Módosítsa a minimális példát: használjon
rng = np.random.default_rng(99)-ot, injektáljon 24
anomáliát úgy, hogy 8 távolság-, 8 kosár- és 8 darabszám-eltérés legyen,
majd illesszen 100 fás modellt max_samples=128 és
random_state=11 mellett. Válasszon k=24
listát, és írassa ki a P@24 értéket. Mi az elméleti
összehasonlítási alap és mi a metrika felső határa itt? Ellenőrizze le,
hogy y_true.sum()==24.
Egy változat nem a folytonos pontszám szerint rendez, hanem ezt teszi:
flag = model.predict(X) == -1
suspects = np.flatnonzero(flag)[:k]
p_at_k = y_true[suspects].mean()A mérés P@24=0.08 lesz, miközben a pontszám szerinti
rangsor sokkal jobb. Nevezze meg a két lehetséges hibát ebben a
kódrészletben: miért nem elég a predict bináris kimenete,
és milyen sorrendben szerepelnek az így kiválasztott sorok? Írja át a
kiválasztást úgy, hogy a legmagasabb anomáliapontszámú első 24 sor
kerüljön a listába.
Tegyük fel, hogy a rendeléskezelő csapat egy tiszta
referencia-időszakot ad: a lab/orders.csv első 1200 sora
bizonyítottan átnézett, ismert üzleti tartomány. A következő 1200 sorra
viszont új, címkézetlen rendelésekként tekintünk. Injektáljon az új
részbe 5 erős kosár-anomáliát (basket_minor * 10) a
rng = np.random.default_rng(11) használatával. Tanítson
novelty=True, 150 fás modellt a tiszta referencia-részen,
pontozza az új részt, és mérje a P@5 értéket. Fogalmazza
meg, miben más a contamination szerepe ebben a megváltozott
feltételben.
A rangsor első három sora a 2., 0. és 1. index. A ground truth-értékek: , , , ezért
A P@6 valamennyi sorra
.
A P@3 azért fontos, mert a gyakorlatban csak egy kis
ellenőrzési költségkeret áll rendelkezésre; a jó modelltől az első,
azonnal ellenőrzött tételeknél várunk koncentrált találatot.
A minimális példából kiindulva a változtatások:
rng = np.random.default_rng(99)
idx = rng.choice(n, size=24, replace=False)
X[idx[:8], 0] += rng.uniform(500, 900, size=8)
X[idx[8:16], 2] *= rng.uniform(6, 10, size=8)
X[idx[16:24], 3] += rng.uniform(40, 80, size=8)
y_true[idx] = 1
model = IsolationForest(
n_estimators=100,
max_samples=128,
contamination=24 / n,
random_state=11,
)
model.fit(X)
score = -model.decision_function(X)
top_k = np.argsort(-score)[:24]
p_at_k = y_true[top_k].mean()
print(f"P@24={p_at_k:.3f}, injected={y_true.sum()}")A véletlen alap most
,
az ehhez tartozó szórás
.
A felső határ
,
mert 24 valódi anomália van és 24 sort vizsgálunk. A
y_true.sum()==24 assert az injektálás konzisztenciáját
ellenőrzi. Numerikus reprodukálhatósághoz a seedeket rögzítettük, de
platformokon átívelő bitazonos output nem garantálható; az elvárt
viselkedés a véletlen alapnál számottevően magasabb találati arány.
A predict a folytonos pontszámot egy küszöbbel binárissá
vágja: a küszöb feletti sorok mind -1-et kapnak, de a
küszöbön belül nincs további sorrend. Az
np.flatnonzero(flag)[:k] emellett az eredeti sorrend első
megjelölt elemét veszi, nem a legszélsőségesebbeket. Ezért a
válogatásban erős anomáliák maradhatnak ki, és a lista elejére
véletlenszerűen kerülhetnek gyenge gyanús elemek. A helyes
kiválasztás:
score = -model.decision_function(X)
top_k = np.argsort(-score)[:k]
p_at_k = y_true[top_k].mean()A küszöbválasztás akkor jó, ha egy emberi munkafolyamat fix riasztási
szabályt akar (predict == -1), de rangsorolt vizsgálati
listához nem alkalmas.
Egy lehetséges megoldás:
orders = pd.read_csv("lab/orders.csv")
feature_cols = [
"distance_km", "priority_flag", "basket_minor",
"quantity_sum", "shipping_fee_minor",
]
X_train = orders.iloc[:1200][feature_cols].to_numpy(dtype=float)
X_test = orders.iloc[1200:].copy()[feature_cols].to_numpy(dtype=float)
rng = np.random.default_rng(11)
y_test = np.zeros(len(X_test), dtype=int)
idx = rng.choice(len(X_test), size=5, replace=False)
X_test[idx, 2] *= 10.0
y_test[idx] = 1
model = IsolationForest(
n_estimators=150,
novelty=True,
contamination=0.01,
random_state=3,
)
model.fit(X_train)
score = -model.decision_function(X_test)
top5 = np.argsort(-score)[:5]
print(f"P@5={y_test[top5].mean():.3f}, injected={y_test.sum()}")Novelty módban a contamination a bináris küszöböt
hangolja a tiszta referencia adathalmazon; nem a teszt anomáliáinak
ismert aránya. Itt azért használunk folytonos score-rangsort és
P@5-öt, mert a feladat nem küszöbös riasztás, hanem kis
vizsgálati lista. A küszöb beállításához éles környezetben külön
validációs, lehetőleg emberileg ellenőrzött referencia kell. A
P@5 maximuma itt 1, mert 5 valódi anomália van, és ennyi
sort nézünk meg.
A leggyakoribb hiba az a rövidzárlat, hogy „magas anomáliapontszám,
tehát csalás vagy hibás adat”. A Northstar példán: a
priority_flag=1 rendeléseknél a magas
shipping_fee_minor kifejezetten várható lehet, hiszen a
gyorsított szállítás többletköltséget hordozhat. Ha a modell ilyen sort
emel ki, az emberi validáció helyes döntése lehet „legitim üzleti
érték”, nem adathiba. Az anomáliapontszám feladata a ritka, magyarázatra
szoruló eset vizsgálati listára tétele, nem az ok automatikus
megállapítása.
Önellenőrzésre érdemes kérdések: mi a megfigyelési egység, és nem
duplázódik-e a pontszám egy join miatt? A P@k nevezője
mindig az ellenőrzött sorok száma, nem az összes anomália. A
kontaminációt nem a tesztvalidáció után szabad úgy hangolni, hogy a
P@k maximális legyen; az adaptív választás a Hiperparaméter és nested értékelés alfejezetben
tanult optimista becsléshez vezet. A score nem kalibrált valószínűség; a
ritka esemény miatt a kis
mellett a találati arány zajos, ahogy a Várható
érték és Monte Carlo közelítés Monte Carlo-hibája is mutatta. Végül
az IsolationForest csak a numerikus mezőkön lát; a
customer_id érvényességéhez vagy a duplikált sorokhoz külön
adatminőségi szabályok kellenek.
Az anomáliakeresés emberi vizsgálati listát állít elő, nem
automatikus ítéletet. Az IsolationForest rövid faúthosszal pontozza a
nehezen izolálható, eltérő rendeléseket; a folytonos pontszámból képzett
rangsor az ellenőrzési költséget az első
tételre összpontosítja. A laborban szintetikus eltérésekkel mért
P@k a megerősített találatok aránya az ellenőrzött listán,
és mindig a véletlen alaphoz viszonyítjuk. A contamination a bináris
küszöb feltevése, a rangsort lényegében nem változtatja. A következő
alkalmazás a VI. ellenőrző pont lesz, ahol ezt a vizsgálati listát, a
pontszám-rangsort és az emberi döntési naplót szivárgásmentes felügyelt
modellkísérletbe kell illeszteni.