A Northstar predikciós projektben a logisztikus regresszió egyetlen
hangolandó hiperparamétere a regularizáció erősségét szabályozó
C. A kérdés: hogyan becsüljük meg a kiválasztott modell
jövőbeli AUC-ját úgy, hogy a hiperparaméter-keresés ne tegye optimistává
a becslést? Ebben az alfejezetben egy 400 rendelésből álló szintetikus
mintát használunk; a megfigyelési egység egy rendelés, a cél bináris
(late = 1, ha a kézbesítés 7 napnál későbbi), a prediktorok
8 rendeléskori jellemző. Az elvárt ellenőrzés: a külső értékelés
AUC-átlaga és a teljes modellillesztésszám dokumentálása.
Induló feladat a módszer megismerése előtt: becsüld meg, hány modellillesztés történik, ha 5 külső foldot, belső foldonként 3-foldos keresést, 10 véletlen jelöltet és a legjobb jelölt újraillesztését használjuk? A pontos számítást a példában ellenőrizzük.
A modellnek tanult paraméterei (például a
logisztikus regresszió súlyvektora
)
és hiperparaméterei (hyperparameters) vannak. A
hiperparamétert a tanulási algoritmus elindítása előtt rögzítjük;
például a logisztikus regresszió C paramétere a súlyok
büntetésének inverz erőssége. Kis C erős regularizációt,
nagy C gyenge regularizációt jelent, ahogy azt a Távolság, kernel és méretkorlát alfejezetben az SVM
margójánál is láttuk.
A keresési tér (search space) a kipróbálandó hiperparaméter-értékek halmaza. Két gyakori forma:
C ∈ {0.01, 0.1, 1.0}. A
kombinációk száma a rács méretének szorzata.n_iter darab véletlen kombinációt. Ez
akkor előnyös, ha a keresési tér nagy, és a költségvetés
korlátozott.A költségvetés (budget) a megengedett
modellillesztések vagy futási idő felső korlátja. Minden egyes illesztés
iteratív optimalizálást futtat – a gradiens alapú lépéseket a Láncszabály és számítási gráf alfejezetben ismertük
meg –, ezért a futási idő nem elhanyagolható. A teljes illesztésszám az
összes belső foldon végzett jelöltillesztések összege, valamint minden
külső fold egy újraillesztése:
,
ha az alapértelmezett refit=True.
A belső és külső keresztvalidáció (inner/outer CV) célja a kiválasztás és az értékelés szétválasztása. A belső CV a modellválasztás becslője: minden jelöltet a belső foldokon értékelünk, és a legjobb átlagpontszámút választjuk. A külső CV a teljes kiválasztási eljárás értékelője: minden külső fold tanítórészén belül újra lefuttatjuk a teljes belső keresést, majd a kiválasztott modellt a külső teszt-részen egyszer értékeljük. Így a külső teszt soha nem vesz részt a hiperparaméter-választásban.
A kiválasztási torzítás (selection bias) a legfontosabb statisztikai veszély. Tegyük fel, hogy 20 jelölt valódi AUC-je egyaránt , de egy-egy belső CV becslés szórása . A belső CV-pontszámok tehát véletlen zajjal terheltek. Ha kiválasztjuk a legjobbat, annak várható értéke nem , hanem
ahol a belső CV AUC-becslések, standard normális változók, , és a jelöltek száma. Húsz jelöltnél a standardizált maximum várható értéke körülbelül , így a várható nyertes pontszám . A nyertes pontszám tehát akkor is felfelé torzított, ha egyetlen jelölt sem jobb a többinél. Ezt a szimulációt a gyakori tévedésnél ellenőrizzük.
Az alábbi kód szintetikus adatot generál, majd öt külső foldon belül tíz jelöltes véletlen keresést futtat három belső folddal. A külső tesztet a keresés soha nem látja. A példa a lab/pyproject.toml alap numerikus csomagjait használja; a kód a projektgyökérből futtatható.
from sklearn.datasets import make_classification
from sklearn.model_selection import StratifiedKFold, RandomizedSearchCV
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
import numpy as np
from scipy.stats import loguniform
# 1. Bemenet: 400 független szintetikus rendelés, 8 feature, bináris cél.
rng = np.random.default_rng(42)
X, y = make_classification(
n_samples=400, n_features=8, n_informative=5, n_redundant=2,
random_state=42, flip_y=0.12
)
# 2. Külső split: 5 rétegzett fold. A shuffle és a seed rögzített.
outer = StratifiedKFold(n_splits=5, shuffle=True, random_state=123)
# 3. A modell pipeline: skálázás + logisztikus regresszió.
pipe = Pipeline([
('scaler', StandardScaler()),
('clf', LogisticRegression(max_iter=1000, solver='liblinear'))
])
# 4. Keresési tér: C log-uniform eloszlásból, 10 jelölt.
param_dist = {'clf__C': loguniform(1e-3, 1e1)}
outer_scores = []
inner_best_scores = []
best_params_per_fold = []
n_fits = 0
# 5. Nested CV: minden külső foldban új keresést indítunk.
for train_idx, test_idx in outer.split(X, y):
X_train, X_test = X[train_idx], X[test_idx]
y_train, y_test = y[train_idx], y[test_idx]
search = RandomizedSearchCV(
pipe, param_dist, n_iter=10, cv=3, scoring='roc_auc',
random_state=7, refit=True, n_jobs=1
)
search.fit(X_train, y_train)
# Illesztésszám: 10 jelölt * 3 belső fold + 1 refit = 31.
n_fits += search.n_iter * 3 + 1
y_score = search.predict_proba(X_test)[:, 1]
outer_scores.append(roc_auc_score(y_test, y_score))
inner_best_scores.append(search.best_score_)
best_params_per_fold.append(search.best_params_)
print("Külső fold AUC-k:", np.round(outer_scores, 3))
print("Külső AUC átlag:", np.mean(outer_scores).round(3))
print("Külső AUC szórás (ddof=1):", np.std(outer_scores, ddof=1).round(3))
print("Belső best_score-k:", np.round(inner_best_scores, 3))
print("Teljes illesztésszám:", n_fits)
print("Kiválasztott C-k:", best_params_per_fold)Szemléltető kimenet (a pontos számok a véletlen generálás miatt enyhén eltérhetnek; a kód nem futtatott):
Külső fold AUC-k: [0.712 0.689 0.731 0.704 0.718]
Külső AUC átlag: 0.711
Külső AUC szórás (ddof=1): 0.016
Belső best_score-k: [0.752 0.741 0.768 0.755 0.749]
Teljes illesztésszám: 155
Kiválasztott C-k: [{'clf__C': 0.047...}, ...]
Ha nagyobb keresést futtatnánk, a n_jobs=-1 beállítás a
joblib segítségével párhuzamosíthatná a belső foldokat, de ez nem
változtatja meg az illesztések számát, csak a falidőt. A példában
n_jobs=1 a tiszta költségelszámolás miatt szerepel.
A külső fold AUC-k átlaga 0.711, ami alacsonyabb, mint a belső
best_score értékek átlaga (0.753). Ez a különbség a
kiválasztási torzítás gyakorlati jele: a belső CV-pontszám a keresési
folyamat részeként optimista, míg a külső foldok a kiválasztott eljárást
olyan részeken értékelik, amelyeket a keresés nem látott.
Kézi kontroll: a StratifiedKFold garantálja, hogy minden
külső teszt halmaz diszjunkt a hozzá tartozó tanítóhalmaztól, és a
foldok lefedik a teljes 400 elemű mintát. A teljes illesztésszámot a kód
expliciten számolja:
.
Ez a képlet minden nested CV futásnál ellenőrizhető invariáns.
A statisztikai értelmezésnél fontos korlát: a külső foldok AUC-i nem függetlenek, mert a tanítóhalmazok átfednek. A 0.016-os szórást ezért nem szabad konfidenciaintervallumként értelmezni. A külső CV átlaga jobb becslés, mint a belső nyertes pontszám, de a bizonytalanság pontos számszerűsítéséhez ismételt külső splitre vagy további statisztikai módszerre lenne szükség.
1. Értelmezés. Miért magasabb a belső
best_score értékek átlaga, mint a külső fold AUC-k átlaga?
Magyarázd meg a kiválasztási torzítás fogalmával, és nevezz meg legalább
egy helyzetet, amikor a különbség nagyobb lehet.
2. Implementálás. Cseréld a
RandomizedSearchCV-t GridSearchCV-re, és
használd a {'clf__C': [0.01, 0.1, 1.0]} rácsot. Mennyi lesz
a teljes illesztésszám? Módosítsd a kódot, és add meg az új külső AUC
átlagot.
3. Hibakeresés. Egy kolléga az egész adaton futtat
egy RandomizedSearchCV-t, majd a
search.best_score_ értéket közli mint „külső validációs AUC
0.76”. Mi a hiba ebben az állításban? Milyen egyszerű ellenőrzést
javasolnál a torzítás kimutatására?
4. Transzfer. Tegyük fel, hogy egy modellillesztés átlagos futási ideje a te gépeden 0.8 másodperc (ez becsült érték, nem mért). A jelenlegi 155 illesztés hány másodperc? Ha a jelöltek számát 20-ra, a belső foldok számát pedig 5-re növeled, mennyi lesz az illesztésszám és a várható futási idő? Ha a teljes futási időnek 5 perc alatt kell maradnia, milyen költségvetési döntést hoznál?
5. Bónusz értelmezés. Miért nem értelmezhető a külső foldok AUC-inak szórása konfidenciaintervallumként? Milyen további lépéssel lehetne a bizonytalanságot jobban becsülni?
1. A belső best_score minden foldban a
10 jelölt közül a legjobb CV-pontszám. Mivel minden pontszám zajjal
terhelt, a maximum várhatóan magasabb, mint a jelöltek valódi
teljesítménye. A külső AUC viszont a kiválasztott modellt olyan
tesztrészen méri, amelyet a keresés nem használt, ezért nem tartalmazza
ezt a kiválasztási optimizmust. A különbség nagyobb, ha sok jelölt van,
a belső foldok kicsik (nagyobb zaj), vagy a valódi hatás gyenge.
2. A GridSearchCV 3 jelöltet próbál, mindegyiket 3
belső foldon:
illesztés belső ciklusonként. Öt külső folddal a teljes illesztésszám
.
A kódban a RandomizedSearchCV helyett:
from sklearn.model_selection import GridSearchCV
param_grid = {'clf__C': [0.01, 0.1, 1.0]}
search = GridSearchCV(pipe, param_grid, cv=3, scoring='roc_auc', refit=True)A külső AUC átlag szemléltető értéke 0.705 körül alakulhat; a pontos szám függ a seedtől és a környezettől. Az ellenőrzés: a teljes illesztésszámnak 50-nek kell lennie, és a külső AUC átlagnak közelebb kell lennie a valódi teljesítményhez, mint a belső best_score átlagnak.
3. A kolléga kétszeresen használja ugyanazt az
adatot: a kereséshez és a „validációhoz” is. Az adaptív kiválasztás után
a best_score_ már nem torzítatlan becslés; ez a selection
bias klasszikus esete. Az ellenőrzés: osszuk az adatot egy friss, soha
nem látott teszt részre, és mérjük ott a legjobb modell AUC-ját. Ha a
friss AUC számottevően alacsonyabb, mint a best_score_, a
torzítás kimutatható.
4. A jelenlegi futási idő: másodperc, azaz körülbelül 2 perc. Az új beállítás: illesztés, ami másodperc, azaz körülbelül 6 perc 44 másodperc. Ez meghaladja az 5 perces keretet. Egy lehetséges döntés: 20 jelöltet tartunk, de a belső foldok számát 3-ra csökkentjük; ekkor illesztés, másodperc, azaz körülbelül 4 perc. Másik lehetőség: 15 jelölt 5 belső folddal: illesztés, 304 másodperc, szintén 5 perc alatt. A döntés a belső foldok stabilitása és a keresési tér lefedettsége közötti kompromisszum.
5. A külső foldok tanítóhalmazai átfednek: minden fold a többi 4 fold adatának nagy részét tartalmazza. Emiatt a foldok AUC-i korreláltak, és a szórásuk alábecsüli a valódi mintavételi bizonytalanságot. A jobb becsléshez ismételt nested CV-t futtathatunk több különböző külső split véletlen maggal, vagy a teljes adaton bootstrap mintákat használhatunk, de ez utóbbi is feltételezi a megfigyelések függetlenségét. A pontosabb módszerek meghaladják ezen alfejezet keretét.
A leggyakoribb hiba: a legjobb CV-pontszámot a keresés után torzítatlan külső becslésként közölni. A következő szimuláció megmutatja, hogy ez akkor is félrevezető, ha minden jelölt azonos valódi teljesítményű.
import numpy as np
rng = np.random.default_rng(20261020)
n_candidates = 20
n_repeats = 2000
mu = 0.70
sigma = 0.03
maxima = np.empty(n_repeats)
for i in range(n_repeats):
scores = rng.normal(mu, sigma, size=n_candidates)
maxima[i] = scores.max()
print("Átlagos legjobb CV-pontszám:", maxima.mean().round(3))
print("Valódi közös AUC:", mu)
print("Optimizmus (bias):", (maxima.mean() - mu).round(3))Szemléltető kimenet (a kód nem futtatott, a várható érték a normális közelítésből):
Átlagos legjobb CV-pontszám: 0.757
Valódi közös AUC: 0.7
Optimizmus (bias): 0.057
A szimuláció független normális eloszlást feltételez, ami a valóságban ritkán teljesül pontosan, de az intuíciót jól mutatja: 20 jelölt közül a legjobb pontszám várhatóan 0.76 körül lesz, pedig egyetlen jelölt sem jobb 0.70-nél. Ez a nested CV motivációja: a külső foldok átlagával a kiválasztási optimizmus nagy részét elkerüljük.
A hiperparaméter-keresés adaptív adathasználat, ezért a nyertes belső CV-pontszám felfelé torzított. A nested CV szétválasztja a kiválasztást és az értékelést: minden külső fold tanítórészén belül új teljes keresést futtatunk, majd a kiválasztott modellt a külső teszten egyszer értékeljük. A teljes költséget a foldok, a jelöltek és a refit szorzata adja; a példában 155 illesztés történt. A külső fold AUC-k átlaga közelebb áll a valódi általánosításhoz, de a foldok átfedése miatt a szórásuk nem konfidenciaintervallum. A következő alfejezetben a küszöb, kalibráció és ritka esemény problémáit vizsgáljuk ugyanezzel a korrektségi szemlélettel.