22.1 Lineáris és logisztikus prediktív modellek

22.1.1 Cél és konkrét helyzet

A Northstar prediktív feladatban a rendelés pillanatában kell megbecsülnünk annak valószínűségét, hogy a szállítás a vállalt 7 napnál később érkezik. A megfigyelési egység egy rendelés = egy sor az orders.csv állományban. A bemenet jelen laborban két, a rendeléskor ismert feature: distance_km távolság kilométerben és basket_minor kosárérték EUR-centben. A célváltozó a bináris target_late: 1, ha delivery_days > promised_days, egyébként 0. A kimenet predict_proba szerinti P(𝚝𝚊𝚛𝚐𝚎𝚝_𝚕𝚊𝚝𝚎=1)P(\texttt{target\_late}=1) becslés, amely 0 és 1 közé esik, és két osztályra összegezve 1-et ad.

Induló becslés a megoldás előtt. A cutoff 2025-05-01T00:00:00+00:00; a rendelések 2025 első 180 napjára esnek. Becsüld meg nagyságrendileg, hány train és hány teszt sorra számítasz! Második kérdés: a C=0.2 vagy a C=5 jelent erősebb L2 regularizálást a LogisticRegression esetén? A választ a fogalmi rész után ellenőrizzük.

22.1.2 Ridge, Lasso, logisztikus célfüggvény és együttható

A prediktív lineáris modell egy pp dimenziós feature-vektorból indul: 𝐱i∈ℝp\mathbf{x}_i \in \mathbb{R}^p az ii-edik megfigyelés, a cél folytonos yi∈ℝy_i \in \mathbb{R} esetén

ŷi=β0+𝐱i⊤𝛃,𝛃=(β1,…,βp)⊤. \hat y_i = \beta_0 + \mathbf{x}_i^\top \boldsymbol{\beta}, \qquad \boldsymbol{\beta}=(\beta_1,\dots,\beta_p)^\top.

A legkisebb négyzetek (ordinary least squares, OLS) a négyzetes hibát minimalizálja. A Ridge ehhez a 𝛃\boldsymbol{\beta} vektor euklideszi normájának négyzetét büntetésként hozzáadja:

minβ0,𝛃∑i=1n(yi−β0−𝐱i⊤𝛃)2+α∥𝛃∥22,∥𝛃∥22=∑j=1pβj2. \min_{\beta_0,\boldsymbol{\beta}} \sum_{i=1}^n \left(y_i-\beta_0-\mathbf{x}_i^\top\boldsymbol{\beta}\right)^2 +\alpha \|\boldsymbol{\beta}\|_2^2, \qquad \|\boldsymbol{\beta}\|_2^2=\sum_{j=1}^p \beta_j^2.

Itt nn a tanítósorok száma, α>0\alpha>0 a regularizálási erő. A Lasso ugyanezt az ℓ1\ell_1 normával teszi:

minβ0,𝛃12n∑i=1n(yi−β0−𝐱i⊤𝛃)2+α∥𝛃∥1,∥𝛃∥1=∑j=1p|βj|. \min_{\beta_0,\boldsymbol{\beta}} \frac{1}{2n}\sum_{i=1}^n \left(y_i-\beta_0-\mathbf{x}_i^\top\boldsymbol{\beta}\right)^2 +\alpha \|\boldsymbol{\beta}\|_1, \qquad \|\boldsymbol{\beta}\|_1=\sum_{j=1}^p |\beta_j|.

A scikit-learn Lasso a fenti 1/(2n)1/(2n)-es skálázást használja, a Ridge az első képlet szerinti formát; ezért ugyanaz az α\alpha abszolút értéke a két modellnél nem közvetlenül egyenértékű. A fontos irány közös: nagyobb alpha erősebb zsugorítás (shrinkage) nullához. A büntetés a fit_intercept=True alapbeállítás mellett az interceptet nem bünteti.

A ∥⋅∥2\|\cdot\|_2 és ∥⋅∥1\|\cdot\|_1 közötti különbség a Vektor, skalárszorzat, norma és távolság alfejezetből ismert távolságfogalomra épül. A gradiensszámítás szempontjából (Optimalizáló választás és konvergenciaellenőrzés) az L2-büntetés 2αβj2\alpha\beta_j-t ad a gradienshez, ami minden lépésben arányosan húzza az együtthatót 0 felé; az L1-büntetés csak βj\beta_j előjelét adja hozzá, ezért a megoldás egyes koordinátái pontosan 0-vá válhatnak. A Ridge jellemzően minden együtthatót kicsivé, de nem nullává tesz; a Lasso ritka megoldást adhat.

Bináris cél esetén a LogisticRegression a logisztikus függvényen (sigmoid) keresztül köti a lineáris prediktort a valószínűséghez:

pi=P(Yi=1∣𝐱i)=11+e−zi,zi=β0+𝐱i⊤𝛃, p_i = P(Y_i=1 \mid \mathbf{x}_i)=\frac{1}{1+e^{-z_i}}, \qquad z_i=\beta_0+\mathbf{x}_i^\top\boldsymbol{\beta},

ahol Yi∈{0,1}Y_i\in\{0,1\}. A ziz_i a logit, azaz a log odds:

zi=logpi1−pi. z_i=\log\frac{p_i}{1-p_i}.

A nem regularizált változat a negatív log-likelihoodot minimalizálja; a likelihood fogalmát Paraméter, becslő és likelihood vezette be, a GLM-keretet pedig GLM: bináris és számlálási cél. Az sklearn regularizált logisztikus célfüggvénye L2 büntetéssel, fogalmi alakban:

JC(β0,𝛃)=C[−1n∑i=1n(yilogpi+(1−yi)log(1−pi))]+12∥𝛃∥22. J_C(\beta_0,\boldsymbol{\beta})= C\left[-\frac1n \sum_{i=1}^n \left(y_i\log p_i+(1-y_i)\log(1-p_i)\right)\right] +\frac12 \|\boldsymbol{\beta}\|_2^2.

A LogisticRegression C paramétere tehát a negatív likelihood súlya, ezért kisebb C erősebb regularizálást jelent. Ez fordítottja a Ridge/Lasso alpha irányának. A penalty='l2' a fenti négyzetes büntetés, penalty='l1' az ℓ1\ell_1-es ritkító változat; penalty=None kikapcsolja a büntetést. A modern scikit-learn a None értéket használja; ha a telepített verzió hibát ad, a korábbi penalty='none' szöveget kell ellenőrizni.

Modellcsalád scikit-learn paraméter Paraméter növelésének hatása
Ridge / Lasso alpha erősebb büntetés
LogisticRegression C gyengébb büntetés (inverz)

A coef_ a tanult 𝛃\boldsymbol{\beta} együtthatóvektor. Folytonos célú Ridge/Lasso esetén coef_.shape == (p,); bináris LogisticRegression esetén coef_.shape == (1, p). Az utóbbi együttható a standardizált feature egy egységnyi növekedéséhez tartozó log odds változás; exponense, eβje^{\beta_j}, odds ratio (OR). A basket_minor centben való megadása miatt a nyers együttható nem hasonlítható össze a distance_km együtthatójával: a büntetés nagysága mértékegységfüggő lenne. Ezért a labor a StandardScaler-t tanítja a train halmazon, és azt transzformálja a tesztre. A coef_ ezután „egy szórásnyi feature-változásra” jutó hatást jelent.

Kézi kontroll. Legyen egy standardizált megfigyelés x1=1.6x_1=1.6, x2=−0.8x_2=-0.8, β0=−1.2\beta_0=-1.2, β1=0.5\beta_1=0.5, β2=0.1\beta_2=0.1. Ekkor

z=−1.2+0.5⋅1.6+0.1⋅(−0.8)=−0.48,p=11+e0.48≈0.382. z=-1.2+0.5\cdot 1.6+0.1\cdot(-0.8)=-0.48, \qquad p=\frac{1}{1+e^{0.48}}\approx 0.382.

Egy egységnyi x1x_1 növekedés 0.50.5 log odds növekedést, azaz e0.5≈1.65e^{0.5}\approx 1.65-szeres oddsot jelent.

22.1.3 Teljes minimális munkapélda

A példa a lab/ munkakönyvtárból fut, ahol az orders.csv rendelkezésre áll. A felosztás a Predikciós szerződés és hibateszt alfejezetben rögzített szerződést követi: train = order_ts < cutoff és label_available_at < cutoff; teszt = order_ts >= cutoff. A köztes sáv (korábbi rendelés, még nem kézbesített címke) kimarad. Nincs véletlen split, ezért a cutoff maga a reprodukálhatósági rögzítő; a random_state csak a solver jövőbeli konzisztenciáját jelzi.

from pathlib import Path
import numpy as np
import pandas as pd
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score, brier_score_loss

orders = pd.read_csv(
    Path("orders.csv"),
    parse_dates=["order_ts", "actual_delivery_at", "label_available_at"],
)
assert {"distance_km", "basket_minor", "target_late",
        "order_ts", "label_available_at"}.issubset(orders.columns)

CUTOFF = pd.Timestamp("2025-05-01T00:00:00+00:00")
FEATURES = ["distance_km", "basket_minor"]
TARGET = "target_late"

train = orders[(orders["order_ts"] < CUTOFF) &
               (orders["label_available_at"] < CUTOFF)]
test = orders[orders["order_ts"] >= CUTOFF]
purged = orders[(orders["order_ts"] < CUTOFF) &
                (orders["label_available_at"] >= CUTOFF)]
print(f"train={len(train)}, test={len(test)}, purged={len(purged)}")
assert set(train.index).isdisjoint(set(test.index))

X_train, y_train = train[FEATURES], train[TARGET]
X_test, y_test = test[FEATURES], test[TARGET]

def make_pipeline(penalty, C=1.0):
    if penalty is None:
        model = LogisticRegression(penalty=None, solver="lbfgs",
                                   max_iter=1000, random_state=42)
    else:
        model = LogisticRegression(penalty=penalty, C=C, solver="lbfgs",
                                   max_iter=1000, random_state=42)
    return Pipeline([
        ("scaler", StandardScaler()),
        ("model", model),
    ])

pipe_unreg = make_pipeline(None)
pipe_l2 = make_pipeline("l2", 0.2)

results = {}
for name, pipe in [("penalty=None", pipe_unreg),
                   ("penalty=l2, C=0.2", pipe_l2)]:
    pipe.fit(X_train, y_train)
    proba = pipe.predict_proba(X_test)   # shape: (n_test, 2)
    p_test = proba[:, 1]                 # P(target_late=1)
    assert np.allclose(proba.sum(axis=1), 1.0)
    results[name] = (pipe, p_test)
    print(f"{name}: AUC={roc_auc_score(y_test, p_test):.4f}, "
          f"Brier={brier_score_loss(y_test, p_test):.4f}")
    print("  coef (scaled):",
          np.round(pipe.named_steps["model"].coef_[0], 4),
          " intercept:",
          round(pipe.named_steps["model"].intercept_[0], 4))

# Kézi logit-ellenőrzés a regularizált modellen
pipe_reg, p_test_reg = results["penalty=l2, C=0.2"]
X_test_scaled = pipe_reg.named_steps["scaler"].transform(X_test)
reg_model = pipe_reg.named_steps["model"]
z_manual = X_test_scaled @ reg_model.coef_[0] + reg_model.intercept_[0]
p_manual = 1.0 / (1.0 + np.exp(-z_manual))
print("max |p_manual - predict_proba|:",
      np.max(np.abs(p_manual - p_test_reg)))

Futtatás a lab/ mappában:

python s085_demo.py

Verzióérzékeny döntés: ha a telepített scikit-learn a penalty=None értéket nem fogadja el, használd a penalty='none' szöveges formát, és ellenőrizd, hogy a lbfgs solver elérhető.

22.1.4 Eredmény értelmezése

A fenti futásból származó számok szemléltető, elméletileg várt értékek; automatizált futási ellenőrzés még nem történt. Az várható, hogy a train nagyságrendileg 1500 sor, a teszt nagyságrendileg 800 sor; a purged sáv nagyságrendileg a cutoff előtti utolsó 7 nap rendeléseit tartalmazza. Az ellenőrző invariantok: a predict_proba minden sora 1-re összegződik, shape-je (len(X_test), 2); a kézi logit-számítás és a predict_proba eltérése lebegőpontos tolerancián belül, jellemzően 10−1010^{-10} alatt marad.

A két modell összehasonlításában az L2 büntetés a maximum likelihoodhoz képest nullához húzza az együtthatókat. A penalty=None tehát jellemzően nagyobb abszolút értékű együtthatókat ad, míg a C=0.2 erős büntetés kisebb együtthatókat eredményez. A distance_km együtthatója várhatóan pozitív: nagyobb távolság mellett nagyobb késési kockázat. A basket_minor hatása gyengébb lehet, mert a kosárérték nem feltétlenül a logisztikai késés fő mozgatója. A regularizálás nem garantál jobb teszt AUC-t; akkor hasznos, ha csökkenti a maximum likelihood nagy mintavételi varianciáját anélkül, hogy túl nagy torzítást vinne be. Az AUC és a Brier itt a modellváltozatok közötti gyors összehasonlítást szolgálja, nem a kalibráció teljes értékelését.

A logisztikus regresszióból származó predict_proba modellszerinti valószínűségbecslés; nem tekinthető automatikusan jól kalibrált eseményvalószínűségnek. A kalibrációt és az osztályarány eltolódását külön ellenőrizzük.

22.1.5 Feladatok

  1. Értelmezés. Tegyük fel, hogy egy standardizált futásban a LogisticRegression distance_km együtthatója 0.520.52. Mekkora az odds ratio? Ha ugyanez a feature csak 0.50.5 szórásnyival nő, mennyi a log odds változás? Fogalmazd meg, miért nem oksági hatás ez az állítás!

  2. Megvalósítás. Ugyanezen a spliten, ugyanazzal a két feature-rel taníts folytonos célra (delivery_days) három modellt: LinearRegression, Ridge(alpha=1.0) és Lasso(alpha=0.1, max_iter=10000). Mindegyiket StandardScaler-rel egy Pipeline-ben futtasd. Számold a train és teszt RMSE-t, és írd ki a coef_ vektort. Milyen reláció várható a Ridge és a LinearRegression együtthatói között?

  3. Hibakeresés. A következő kód a standardizálást a teljes adaton végzi, mielőtt a Pipeline-t meghívná:

from sklearn.preprocessing import StandardScaler
full = pd.concat([X_train, X_test])
X_all_scaled = StandardScaler().fit_transform(full[FEATURES])
# majd a scaled mátrixot használja a modellekhez

Mi a hiba, és milyen irányban torzíthatja a teszt AUC-t? Írj két ellenőrző számítást: hasonlítsd össze a train-only és a full scaler átlagvektorát!

  1. Megváltoztatott feltétel. Bővítsd a feature-listát a priority_flag és quantity_sum oszlopokkal. A C értékét válaszd 0.01, 0.2 és 10 értékekre. Melyiket választanád a teszteredmény alapján? Miért helytelen ez a döntési sorrend? Hogyan választanál C-t a train halmazon belüli időrendi validációval?

22.1.6 Megoldások és indoklás

1. megoldás. Az odds ratio e0.52≈1.68e^{0.52}\approx 1.68. A 0.50.5 szórásnyi növekedés log odds változása 0.5⋅0.52=0.260.5\cdot 0.52=0.26, az odds ratio e0.26≈1.30e^{0.26}\approx 1.30. Ez prediktív asszociáció: a modell a rendeléskori jellemzők és a késés között tanul összefüggést, de a távolság nem feltétlenül beavatkozás; a zavaró tényezők elemzése az Oksági cél és megfigyelési torzítás alfejezetben szereplő oksági keretre tartozik.

2. megoldás. A kód a következő lehet:

from sklearn.linear_model import LinearRegression, Ridge, Lasso

target_continuous = train["delivery_days"]
ytest_continuous = test["delivery_days"]

for name, model in [
    ("ols", LinearRegression()),
    ("ridge", Ridge(alpha=1.0)),
    ("lasso", Lasso(alpha=0.1, max_iter=10000)),
]:
    pipe = Pipeline([
        ("scaler", StandardScaler()),
        ("model", model),
    ])
    pipe.fit(X_train, target_continuous)
    pred_train = pipe.predict(X_train)
    pred_test = pipe.predict(X_test)
    rmse_train = np.sqrt(np.mean((target_continuous - pred_train) ** 2))
    rmse_test = np.sqrt(np.mean((ytest_continuous - pred_test) ** 2))
    print(f"{name}: train RMSE={rmse_train:.3f}, "
          f"test RMSE={rmse_test:.3f}, "
          f"coef={pipe.named_steps['model'].coef_}")

Az invariánsok: coef_.shape == (2,); a Ridge együtthatói abszolút értékben kisebbek vagy egyenlők, mint a büntetés nélküli OLS megfelelői; a Lasso egyes együtthatója 00-vá válhat. A Ridge train RMSE-e általában nem kisebb az OLS-nél, mert a büntetés a train illeszkedésből vesz el; a teszt RMSE változása az adattól függ.

3. megoldás. A hiba a tesztadatból való szivárgás (leakage): a scaler a teszt átlagát és szórását is felhasználja, tehát a teszt standardizált értékei nem függetlenek a teszt megfigyelésektől. A teszt AUC jellemzően optimista lehet. Ellenőrzés:

full_scaler = StandardScaler().fit(pd.concat([X_train, X_test])[FEATURES])
train_scaler = StandardScaler().fit(X_train)
print(np.max(np.abs(full_scaler.mean_ - train_scaler.mean_)))
print(np.max(np.abs(full_scaler.scale_ - train_scaler.scale_)))

Ha a két vektor gyakorlatilag egyezik, a szivárgás kicsi; normál esetben a különbség nem nulla, és a helyes megoldás a scaler fit-je kizárólag X_train-en, ahogy a Pipeline a fenti fő példában teszi. Ez a Preprocessing tanulás kizárólag a training részen alapelve.

4. megoldás. A teszt AUC alapján választani hibás, mert a teszt addig érintetlen kell maradjon. A C választásához a train halmazt időrendben bontsd tovább: az utolsó 20%-ot validációra hagyva.

val_cutoff = train["order_ts"].quantile(0.8)
early_train = train[train["order_ts"] <= val_cutoff]
early_val = train[train["order_ts"] > val_cutoff]

A C rácson a validációs AUC alapján válassz; a végső teszt AUC-t csak egyszer, a kiválasztott modellel számold. A C=0.01 erős büntetés, alulillesztést okozhat; a C=10 gyenge büntetés, nagyobb varianciát enged. A három érték közötti rangsor nem garantálható előre: az optimális C adatfüggő. A keresztvalidáció és a nested kiértékelés a Hiperparaméter és nested értékelés alfejezetben lesz részletesebb.

22.1.7 Gyakori tévedés ellenpéldával

A statsmodels inferenciális outputja és a scikit-learn prediktív estimator nem ugyanazt a célt szolgálja. A statsmodels Logit összefoglaló táblája standard hibát, z-értéket és p-értéket ad, amelyek akkor értelmesek, ha a modell feltevései és az adatfelvételi folyamat igazak. A scikit-learn LogisticRegression ezzel szemben prediktív illesztő felület: a coef_ a büntetett célfüggvény optimuma, és a C a predikciós kompromisszumot állítja.

Ellenpélda: egy elemző a statsmodels p-értéke alapján elhagyja a basket_minor változót, mert p > 0.05. A predikciós modellben ettől még a változó megtartása csökkentheti a teszt Brier-értékét, mert a p-érték nem predikciós fontossági mutató, és a nem szignifikáns eredmény nem bizonyítja a hatás hiányát (Nullmodell és p-érték). A reverse hiba is gyakori: a sklearn coef_ nagyságából közvetlen oksági vagy inferenciális állítást tenni. A coef_ a modell reprezentációjának része, nem a világ becsült oksági paramétere.

Önellenőrzés. (1) Miért változik meg egy Ridge-együttható, ha a basket_minor-t cent helyett euróban adod meg? (2) A C=0.01 vagy a C=10 ad nagyobb büntetést a logisztikus modellben? (3) Miért kell a standardizáló átlagát és szórását a train halmazon belül tanítani? (4) Mit jelent, ha a LogisticRegression coef_[0, 1] értéke 0.1 standardizált basket_minor esetén? A válaszokhoz térj vissza a fenti képletekhez és a pipeline-művelethez.

22.1.8 Rövid összefoglaló

A Ridge az L2, a Lasso az L1 büntetéssel zsugorítja a lineáris modell együtthatóit; a scikit-learnben nagyobb alpha erősebb büntetést jelent. A logisztikus regresszió a logit linkkel becsüli a bináris kimenet valószínűségét, és a negatív log-likelihoodhoz hozzáadott büntetést a C paraméter súlyozza: kisebb C erősebb regularizálás. A coef_ a standardizált feature-változás log odds hatását adja, ezért a skálázást a train halmazon belül kell tanítani. A következő alkalmazásban ezt a lineáris feltevést fák, random forest és gradiensboosting modellekkel vetjük össze, amelyek nemlineáris határokat és interakciókat is képesek reprezentálni.