Există o scenă recurentă în munca cu date: un inginer junior e rugat să modeleze un set de date tabulare, întinde mâna după XGBoost, ajustează o după-amiază, modelul obține 0.847 AUC și îl livrează. Apoi un senior aruncă o privire la problemă, antrenează o logistic regression cu câteva feature-uri bine alese, obține 0.831 AUC și întreabă: merită cele șaisprezece miimi în plus de AUC un model ale cărui decizii nu le poți explica echipei de compliance?
Răspunsul e uneori da. Adesea e nu. Și singurul mod în care vei ști e dacă ai efectiv baseline-ul liniar cu care să compari. Lecția asta e despre familia de modele - linear regression, logistic regression și verii lor regularizați - care ar trebui să fie primul lucru pe care îl antrenezi pe orice problemă tabulară și care, surprinzător de des, ajunge să fie ceea ce livrezi.
Forma unui model liniar
Modelele liniare prezic luând o sumă ponderată de feature-uri. Atât. Pentru o țintă continuă y, linear regression spune:
y_hat = w0 + w1*x1 + w2*x2 + ... + wn*xn
Antrenarea înseamnă găsirea ponderilor w0..wn care minimizează eroarea pătratică pe datele de antrenare. Există o soluție în formă închisă care implică inversarea unei matrice și, dacă matricea se comportă bine, LinearRegression din scikit-learn o returnează aproape instantaneu. Fără iterații, fără learning rate.
Pentru clasificare binară, logistic regression înfășoară aceeași sumă ponderată într-un sigmoid:
p(y=1 | x) = sigmoid(w0 + w1*x1 + ... + wn*xn)
Antrenarea minimizează log-loss (cross-entropy). Fără formă închisă; iterează, dar pe date tabulare de orice dimensiune rezonabilă converge în câteva secunde. În ciuda numelui, logistic regression e un algoritm de clasificare, „regression”-ul se referă la potrivirea probabilităților, nu la prezicerea de valori continue.
Ambele modele împărtășesc aceeași savoare: un vector de coeficienți pe care îi poți citi, unul pe feature.
Avantajul interpretabilității
Iată ce nu îți oferă nimic altceva. După antrenare, poți citi model.coef_ și spune:
O creștere de 1 unitate în feature-ul X este asociată cu o schimbare de
coef_Xunități în țintă (linear regression) sau cu o schimbare decoef_Xlog-odds în probabilitatea clasei pozitive (logistic regression), păstrând toate celelalte feature-uri constante.
Acea propoziție - „păstrând toate celelalte feature-uri constante” - face multă muncă, dar rămâne o afirmație semnificativă. Încearcă să spui ceva atât de direct despre un random forest. Sau despre o rețea neuronală. Poți calcula valori SHAP, sigur, dar acelea sunt explicații post-hoc ale unui model ale cărui decizii interne rămân opace.
În industriile reglementate - credit scoring, risc medical, oriunde un reglementator ar putea întreba „de ce ți-a refuzat modelul această persoană” - interpretabilitatea nu e un nice-to-have. E condiția. Logistic regression rămâne modelul dominant în underwriting-ul de credit în 2026, și asta nu pentru că data scientists nu au auzit de XGBoost.
Un exemplu de regresie, în trei moduri
Să configurăm o problemă mică de regresie și să antrenăm trei variante de model liniar:
import numpy as np
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LinearRegression, Ridge, Lasso
from sklearn.metrics import mean_squared_error, r2_score
X, y = fetch_california_housing(return_X_y=True, as_frame=True)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
def fit_and_report(name, model):
pipe = Pipeline([("scale", StandardScaler()), ("model", model)])
pipe.fit(X_train, y_train)
pred = pipe.predict(X_test)
rmse = np.sqrt(mean_squared_error(y_test, pred))
r2 = r2_score(y_test, pred)
coefs = dict(zip(X.columns, pipe.named_steps["model"].coef_.round(3)))
print(f"{name:8s} RMSE={rmse:.3f} R2={r2:.3f}")
print(f" coefs: {coefs}")
fit_and_report("Linear", LinearRegression())
fit_and_report("Ridge", Ridge(alpha=1.0))
fit_and_report("Lasso", Lasso(alpha=0.1))
Observă StandardScaler la început. Regularizarea penalizează magnitudinile coeficienților, ceea ce are sens doar dacă feature-urile sunt pe scale comparabile. Uitarea scalării înainte de Ridge sau Lasso e una dintre primele trei greșeli de începător.
Când rulezi asta, vei vedea RMSE-uri la câteva procente unul de altul în cele trei modele, dar coeficienții arată diferit. LinearRegression folosește orice ponderi minimizează MSE-ul de antrenare, oricât de mari. Ridge îi micșorează pe toți spre zero. Lasso îi împinge pe câțiva exact la zero, ți-a făcut feature selection.
Regularizarea, într-un paragraf
Linear regression simplă pe feature-uri zgomotoase sau corelate face overfitting. Coeficienții cresc mari ca să urmărească zgomotul. Regularizarea adaugă o penalizare pe magnitudinile coeficienților la funcția de pierdere. Două variante contează:
- L2 (Ridge): penalizarea e suma coeficienților la pătrat. Micșorează toți coeficienții lin. Bună când crezi că multe feature-uri contează puțin. Soluție în formă închisă; foarte rapidă.
- L1 (Lasso): penalizarea e suma coeficienților în valoare absolută. Împinge unii coeficienți exact la zero, făcând feature selection. Bună când crezi că majoritatea feature-urilor sunt zgomot. Fără formă închisă; coordinate descent.
- ElasticNet: un mix ponderat de L1 și L2. Alegerea sănătoasă implicită când nu știi care familie ți se potrivește datelor.
Tăria e controlată de hiperparametrul alpha. alpha mai mare = mai multă micșorare = model mai simplu. Îl tunezi, de obicei cu cross-validation. scikit-learn vine cu RidgeCV, LassoCV și ElasticNetCV care fac CV-ul în interiorul fit-ului:
from sklearn.linear_model import RidgeCV, LassoCV
ridge_cv = RidgeCV(alphas=np.logspace(-3, 3, 50))
ridge_cv.fit(X_train, y_train)
print(f"best alpha = {ridge_cv.alpha_}")
Compromisul bias-varianță într-un paragraf
Modelele underfit au bias mare: sunt prea simple pentru a captura semnalul, deci sunt sistematic greșite în aceeași direcție pe diferite seturi de date. Modelele overfit au varianță mare: sunt atât de flexibile încât se agață de zgomot, așa că același model antrenat pe un eșantion diferit arată foarte diferit. Regularizarea mută un model din zona low-bias-high-variance spre higher-bias-lower-variance. Punctul dulce, alpha-ul care minimizează eroarea totală pe date păstrate deoparte, e ceea ce vânează RidgeCV.
Când câștigă liniarul
Câteva situații în care ar trebui să te aștepți cu adevărat ca un model liniar regularizat să fie competitiv sau mai bun decât un ansamblu de arbori:
- Seturi de date mici. Cu câteva sute de rânduri, arborii fac overfit și nu poți tuna ca să ieși din asta. Modelele liniare, cu o regularizare puternică pe post de prior, sunt stabile.
- Nevoie de interpretabilitate. Reglementări medicale, juridice, financiare, oriunde decizia modelului trebuie să fie apărabilă. Coeficienții sunt cea mai curată unealtă de explicație pe care o avem.
- Relații aproximativ liniare. Rare, dar nu dispărute. Unele procese fizice, unele relații economice, unele răspunsuri de senzori. Dacă scatter plot-urile tale arată ca pete în jurul unei linii, un model liniar e potrivirea onestă.
- Numărul de feature-uri >> numărul de eșantioane. Genomica e exemplul canonic: 30.000 de feature-uri, 200 de eșantioane. Arborii nu pot supraviețui așa ceva. Lasso poate, penalizarea L1 e construită aproape special pentru cazul „majoritatea feature-urilor sunt zgomot”.
- Servire sensibilă la latență. Inferența unui model liniar e un singur dot product. Microsecunde. Arborii și ansamblurile sunt mai lente, rețelele neuronale și mai lente.
Când pierde liniarul
- Date tabulare cu interacțiuni puternice între feature-uri. „Venitul contează mai mult dacă ai sub 30 de ani” - un singur coeficient pe venit nu poate captura asta. Arborii fac split mai întâi pe vârstă, apoi pe venit, și obțin interacțiunea pe gratis. De-asta XGBoost domină competițiile Kaggle pe date tabulare.
- Neliniarități puternice. Relații în formă de sigmoid, dinți de fierăstrău sau bimodale. Uneori poți rezolva asta cu polynomial features (
PolynomialFeatures(degree=2)în scikit-learn) sau metode de kernel, dar la momentul ăla muncești mai mult decât dacă ai antrena pur și simplu un arbore. - Date brute de imagine, audio, text. Nici nu încerca modele liniare pe pixeli bruți sau pe word counts dincolo de un baseline. Pentru asta sunt rețelele neuronale și embedding-urile de transformer. Deși modele liniare deasupra unor embedding-uri pre-calculate? Asta e încă vie și sănătoasă în 2026.
Generalized linear models pentru ținte non-Gaussiene
Linear regression presupune că reziduurile sunt aproximativ normale, cu varianță constantă. Pentru anumite tipuri de țintă asta e greșit, iar forțarea produce predicții absurde (numărători negative, probabilități în afara [0,1]). Generalized linear models (GLM) extind suma liniar-ponderată la alte distribuții printr-o link function:
- Logistic regression - țintă Bernoulli, link logit. (Deja acoperit.)
- Poisson regression - țintă tip count (număr de evenimente), link log. Folosește
PoissonRegressorîn scikit-learn. - Gamma regression - țintă continuă strict pozitivă cu erori asimetrice spre dreapta (mărimi de daune de asigurare, time-to-event), link log.
GammaRegressor. - Tweedie regression - pentru ținte care amestecă o masă punctuală la zero cu o coadă continuă (prima pură de asigurare).
TweedieRegressor.
from sklearn.linear_model import PoissonRegressor
# Tinta este numarul de vizite pe site pe zi
model = PoissonRegressor(alpha=0.1)
model.fit(X_train, visit_counts_train)
Acestea păstrează interpretabilitatea modelelor liniare în timp ce respectă statistica reală a țintei tale. Dacă ținta ta e un count, folosește Poisson, nu least-squares. Predicțiile vor fi non-negative prin construcție, pierderea va respecta faptul că varianța crește cu media, iar coeficienții vor fi pe scală logaritmică (o creștere de 1 unitate în feature multiplică count-ul prezis cu exp(coef)).
Un exemplu de clasificare, pe scurt
Pentru clasificare binară același pattern se aplică cu LogisticRegression. Parametrul C e inversul tăriei regularizării, C mic înseamnă regularizare grea. Implicit, scikit-learn folosește regularizare L2; pasează penalty="l1" (cu solver="liblinear" sau "saga") pentru logistic în stil Lasso, sau penalty="elasticnet" (cu solver="saga") pentru versiunea mixtă.
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression, LogisticRegressionCV
X, y = load_breast_cancer(return_X_y=True, as_frame=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, random_state=42)
clf = Pipeline([
("scale", StandardScaler()),
("model", LogisticRegressionCV(Cs=20, cv=5, max_iter=5000)),
])
clf.fit(X_train, y_train)
print(f"Test accuracy = {clf.score(X_test, y_test):.3f}")
LogisticRegressionCV face automat tuning cu cross-validation pentru C. Pe Wisconsin breast cancer - 30 de feature-uri, 569 de eșantioane - asta scoate de obicei 0.97+ accuracy și e complet interpretabil. Arborii nu îl bat semnificativ pe acest set de date, iar un model liniar cu coeficienți expliciți e ceea ce ai vrea de fapt să inspecteze un clinician.
Multiclass cu aceeași mașinărie
LogisticRegression se extinde la multiclass prin antrenarea câte unui clasificator binar pe clasă (one-vs-rest) sau prin antrenarea directă a unui model în stil softmax (multi_class="multinomial", default-ul în scikit-learn modern). Oricum, API-ul e identic cu cazul binar, pasezi un y cu mai mult de două valori unice și îl lași să-și dea seama de restul. Povestea interpretabilității se degradează puțin: acum ai un coeficient pe feature, pe clasă, deci explicația devine „feature-ul X împinge clasa A în sus, B în jos, C în sus” în loc de un singur număr. Tot lizibil, doar mai dens.
Pattern-ul practic
Iată workflow-ul pentru care aș pleda pe orice problemă tabulară nouă:
- Antrenează un baseline liniar regularizat (RidgeCV pentru regresie, LogisticRegressionCV pentru clasificare) pe feature-uri scalate corect. Notează scorul cross-validat.
- Antrenează un ansamblu de arbori cu setări implicite (XGBoost, LightGBM sau RandomForest) pe aceleași date. Notează scorul.
- Compară. Dacă arborele bate modelul liniar cu 1-2%, livrează modelul liniar. Interpretabilitatea, viteza de inferență, suprafața de eșec mai mică - acestea valorează două puncte de acuratețe în aproape orice context de business în afara Kaggle.
- Dacă arborele bate modelul liniar cu 10%+, problema chiar are neliniarități sau interacțiuni și ar trebui să investești pe drumul arborelui: tunează-l, evaluează-l atent, planifică pentru costul operațional.
Bias-ul de aici e intenționat. Default-ul pe modelul mai simplu e o asigurare împotriva modurilor de eșec ale modelului pe care nu le poți vedea în evaluarea offline: train/serve skew, drift de distribuție, bug-uri de pipeline de feature-uri care schimbă comportamentul în moduri pe care modelele complexe le ascund. O logistic regression care se înșală se înșală în moduri pe care le poți citi din coeficienți. Un ansamblu gradient-boosted cu 1000 de arbori care se înșală e o sesiune de debugging care îți strică săptămâna.
Lecția următoare: cum să cauți sistematic în spațiul de hiperparametri când chiar ajungi la modelul mai greu.
Referințe: documentația scikit-learn pentru linear models (scikit-learn.org/stable/modules/linear_model.html), Consultat 2026-05-01.