Vam tancar el mòdul anterior amb una pregunta pendent: "puc fer el meu model encara millor?". La primera resposta d'aquest mòdul és la regularització, la tècnica que doma l'overfitting amb precisió quirúrgica en lloc de a cops de martell. A la lliçó 06-05 vam veure que un model sobreajustat memoritza el soroll de les dades d'entrenament; en els models lineals, aquest sobreajustament té una signatura molt recognoscible: coeficients enormes i de signes oposats que es compensen entre si. La regularització ataca directament aquesta signatura afegint a la funció de cost un càstig per complexitat. En aquesta lliçó entendràs Ridge (L2), Lasso (L1) i Elastic Net, el paper de l'hiperparàmetre alpha, i aplicaràs les tres tècniques a la predicció de la despesa mensual dels clients de MercaFresh.

Contingut

  1. L'overfitting en models lineals: coeficients desbocats
  2. La idea central: penalitzar la complexitat
  3. Ridge (L2): encongir sense anul·lar
  4. Lasso (L1): selecció automàtica de features
  5. Elastic Net: el millor de tots dos mons
  6. L'hiperparàmetre alpha i les trajectòries de coeficients
  7. Escalar abans de regularitzar
  8. Regularització a MercaFresh: predicció de la despesa mensual
  9. La mateixa idea en classificació: el paràmetre C
  10. Taula comparativa i criteris d'elecció

L'overfitting en models lineals: coeficients desbocats

A 04-01 vam entrenar regressions lineals minimitzant l'error quadràtic mitjà, i ja vam avisar que la regularització estendria aquella idea. Per què cal? Perquè la regressió lineal ordinària (OLS) té un punt feble: les features correlacionades.

Recorda la lliçó 02-03: a MercaFresh, num_comandes_mes i num_productes_mes estan fortament correlacionades (qui fa més comandes compra més productes). Quan dues columnes contenen gairebé la mateixa informació, el model lineal té infinites maneres gairebé equivalents de repartir el pes entre elles:

  • despesa = 20·num_comandes + 5·num_productes prediu gairebé el mateix que
  • despesa = 500·num_comandes - 155·num_productes

Totes dues s'ajusten igual de bé al conjunt d'entrenament, però la segona és una bomba de rellotgeria: els seus coeficients gegants amplifiquen qualsevol petita variació de la dada d'entrada, i en dades noves l'error es dispara. És exactament el símptoma d'alta variància que vam diagnosticar a 06-05: el model és hipersensible a la mostra concreta amb què es va entrenar.

import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression

rng = np.random.default_rng(42)
n = 60

# Dues features gairebe redundants (correlacio ~0.98)
num_comandes = rng.poisson(4, n).astype(float)
num_productes = num_comandes * 5 + rng.normal(0, 1, n)

# La despesa real depen sobretot del nombre de comandes
despesa = 22 * num_comandes + rng.normal(0, 8, n)

X = np.column_stack([num_comandes, num_productes])
ols = LinearRegression().fit(X, despesa)
print(ols.coef_)   # p. ex. [17.3, 0.95] - o valors molt mes extrems segons la mostra

Amb només 60 clients i features gairebé duplicades, si repeteixes l'experiment canviant la llavor veuràs que els coeficients ballen de manera salvatge entre execucions. Un model els paràmetres del qual depenen tant de l'atzar de la mostra no és de fiar.

La idea central: penalitzar la complexitat

La solució és sorprenentment simple. En lloc de minimitzar només l'error, minimitzem:

$$\text{Cost} = \underbrace{\text{Error d'ajust (MSE)}}{\text{predic bé?}} + \alpha \cdot \underbrace{\text{Penalització(coeficients)}}{\text{soc simple?}}$$

  • El primer terme empeny el model a ajustar-se a les dades (el de sempre).
  • El segon terme castiga els coeficients grans: com més grans siguin els pesos, més gran serà el cost.
  • alpha (α) és l'hiperparàmetre que regula l'equilibri: el "preu" que paga el model per cada unitat de complexitat.

El model ja no es pot permetre coeficients de 500 i -155 "perquè sí": només mantindrà un pes gran si la millora en l'error ho compensa. Això redueix la variància a canvi d'un petit augment del biaix — el compromís que vam formalitzar a 06-05, ara amb un control continu.

Les tres tècniques d'aquesta lliçó es diferencien únicament en com mesuren la mida dels coeficients:

Tècnica Penalització Fórmula del terme
Ridge L2 (quadrats) $\alpha \sum_j w_j^2$
Lasso L1 (valors absoluts) $\alpha \sum_j |w_j|$
Elastic Net Barreja L1 + L2 $\alpha \left( r \sum_j |w_j| + \frac{1-r}{2} \sum_j w_j^2 \right)$

Nota: la penalització no inclou mai el terme independent (intercepte); castigar el nivell base de la predicció no tindria sentit.

Ridge (L2): encongir sense anul·lar

Ridge penalitza la suma de quadrats dels coeficients. Com que elevar al quadrat castiga desproporcionadament els valors grans, Ridge odia els coeficients extrems i prefereix repartir el pes de manera equilibrada entre features correlacionades.

Propietats clau:

  • Encongeix tots els coeficients cap a zero, però no els fa mai exactament zero: reduir un pes petit aporta cada vegada menys estalvi quadràtic, així que mai no compensa anul·lar-lo del tot.
  • És l'opció més estable quan hi ha multicolinealitat: en l'exemple anterior, Ridge repartiria el pes entre num_comandes i num_productes de manera robusta.
  • Té solució analítica tancada, per la qual cosa és ràpid i numèricament estable.
from sklearn.linear_model import Ridge

ridge = Ridge(alpha=1.0).fit(X, despesa)
print(ridge.coef_)   # coeficients moderats i estables entre execucions

Lasso (L1): selecció automàtica de features

Lasso penalitza la suma de valors absoluts. La diferència geomètrica sembla menor, però té una conseqüència enorme: amb L1, reduir un coeficient de 0.1 a 0 estalvia exactament el mateix que reduir-lo de 5.1 a 5. Per això a Lasso sí que li compensa posar coeficients exactament a zero quan una feature aporta poc.

El resultat és que Lasso fa selecció automàtica de features: el model final usa només un subconjunt de columnes, i la resta queden literalment eliminades (coeficient = 0). Això connecta directament amb l'enginyeria de característiques de 03-06: allà vam crear features a mà (RFM, ràtios, agregats) sense saber quines serien útils; Lasso ens dona un mecanisme basat en dades per descartar les que no aporten.

from sklearn.linear_model import Lasso

lasso = Lasso(alpha=1.0).fit(X, despesa)
print(lasso.coef_)   # p. ex. [21.8, 0.0] - num_productes eliminada!

Matís important: quan dues features estan molt correlacionades, Lasso tendeix a quedar-se'n una i anul·lar l'altra, i quina tria pot dependre de l'atzar de la mostra. Ridge, en canvi, reparteix el pes entre totes dues. Cap estratègia no és "la bona" en abstracte: depèn de si vols un model compacte (Lasso) o estable (Ridge).

Elastic Net: el millor de tots dos mons

Elastic Net combina totes dues penalitzacions amb un segon hiperparàmetre, l1_ratio (r en la fórmula anterior):

  • l1_ratio=1 → Lasso pur.
  • l1_ratio=0 → Ridge pur.
  • Valors intermedis → continua anul·lant features irrellevants (herència L1) però tracta les features correlacionades en grup en lloc de triar-ne una arbitràriament (herència L2).
from sklearn.linear_model import ElasticNet

enet = ElasticNet(alpha=1.0, l1_ratio=0.5).fit(X, despesa)
print(enet.coef_)

És l'opció recomanable quan tens moltes features, sospites que en sobren algunes i a més hi ha grups correlacionats — una situació molt habitual després d'una sessió generosa d'enginyeria de característiques.

L'hiperparàmetre alpha i les trajectòries de coeficients

alpha controla la intensitat del càstig:

  • alpha = 0: sense penalització → regressió lineal ordinària (amb tots els seus problemes).
  • alpha petit: penalització suau, coeficients gairebé lliures.
  • alpha gran: penalització dura, coeficients cada vegada més petits; a l'extrem, tots tendeixen a zero i el model prediu gairebé la mitjana (underfitting pur, com el DummyRegressor de 06-02).

La millor manera de visualitzar-ho és el gràfic de trajectòries: com evoluciona cada coeficient en augmentar alpha.

import matplotlib.pyplot as plt

alphas = np.logspace(-2, 3, 100)   # de 0.01 a 1000, en escala logaritmica
coefs_ridge, coefs_lasso = [], []

for a in alphas:
    coefs_ridge.append(Ridge(alpha=a).fit(X, despesa).coef_)
    coefs_lasso.append(Lasso(alpha=a, max_iter=10000).fit(X, despesa).coef_)

fig, axes = plt.subplots(1, 2, figsize=(12, 4), sharey=True)
for ax, coefs, titol in [(axes[0], coefs_ridge, "Ridge"),
                         (axes[1], coefs_lasso, "Lasso")]:
    ax.plot(alphas, coefs)
    ax.set_xscale("log")
    ax.set_xlabel("alpha (escala log)")
    ax.set_title(f"Trajectories de coeficients - {titol}")
    ax.axhline(0, color="gray", lw=0.5)
axes[0].set_ylabel("Valor del coeficient")
plt.show()

El que veuràs al gràfic:

  • A Ridge, les corbes descendeixen suaument cap a zero però no el toquen: s'hi acosten asimptòticament.
  • A Lasso, les corbes xoquen contra zero i s'hi queden: a partir d'un cert alpha, cada feature "mor" i el model es va simplificant per etapes.

I com es tria el millor alpha? Es prova una graella de valors amb validació creuada (06-03). En aquesta lliçó ho farem amb un bucle manual; la cerca sistemàtica i automatitzada (GridSearchCV i companyia) és el tema de la lliçó 07-05.

Escalar abans de regularitzar

Punt crític que reprèn la lliçó 03-05: la regularització castiga la mida numèrica dels coeficients, i aquesta mida depèn de l'escala de cada feature.

Si despesa_total es mesura en euros (valors de milers) i frequencia en comandes/mes (valors d'unitats), la primera necessitarà un coeficient diminut i la segona un de gran per al mateix efecte real. La penalització castigaria injustament frequencia només per la seva escala. Conclusió:

Estandarditza sempre les features (StandardScaler) abans de Ridge, Lasso o Elastic Net. I, com vam aprendre al mòdul 3, dins d'un Pipeline perquè l'escalat s'ajusti només amb l'entrenament i no hi hagi fuites (06-01).

Regularització a MercaFresh: predicció de la despesa mensual

Apliquem-ho tot al problema de regressió de MercaFresh: predir la despesa mensual d'un client a partir de features de comportament, diverses de les quals correlacionades entre si (com vam descobrir a la matriu de correlació de 02-03).

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression, Ridge, Lasso, ElasticNet

# --- Dataset sintetic de MercaFresh (features de comportament) ---
rng = np.random.default_rng(7)
n = 300

frequencia = rng.gamma(3, 1.5, n)                      # comandes/mes
productes = frequencia * 6 + rng.normal(0, 2, n)       # correlacionada amb frequencia
antiguitat = rng.uniform(1, 60, n)                     # mesos com a client
tiquet_mitja = rng.normal(35, 8, n)                    # EUR per comanda
visites_web = frequencia * 4 + rng.normal(0, 3, n)     # correlacionada amb frequencia
soroll_1 = rng.normal(0, 1, n)                         # features irrellevants
soroll_2 = rng.normal(0, 1, n)

# Despesa real: depen de la frequencia i el tiquet; la resta es redundant o soroll
despesa_mensual = frequencia * tiquet_mitja + rng.normal(0, 15, n)

X = pd.DataFrame({
    "frequencia": frequencia, "productes": productes,
    "antiguitat": antiguitat, "tiquet_mitja": tiquet_mitja,
    "visites_web": visites_web, "soroll_1": soroll_1, "soroll_2": soroll_2,
})
y = despesa_mensual

X_entrenament, X_prova, y_entrenament, y_prova = train_test_split(
    X, y, test_size=0.2, random_state=42)

# --- Torneig: OLS vs Ridge vs Lasso vs Elastic Net, sempre amb escalat ---
models = {
    "OLS":         LinearRegression(),
    "Ridge":       Ridge(alpha=10),
    "Lasso":       Lasso(alpha=1.0, max_iter=10000),
    "Elastic Net": ElasticNet(alpha=1.0, l1_ratio=0.5, max_iter=10000),
}

for nom, model in models.items():
    pipe = Pipeline([("scaler", StandardScaler()), ("reg", model)])
    # RMSE per validacio creuada de 5 folds, com a 06-03
    rmse = -cross_val_score(pipe, X_entrenament, y_entrenament, cv=5,
                            scoring="neg_root_mean_squared_error").mean()
    pipe.fit(X_entrenament, y_entrenament)
    coefs = pipe.named_steps["reg"].coef_.round(1)
    print(f"{nom:12s} RMSE-CV: {rmse:6.2f} EUR   coefs: {coefs}")

Un resultat típic d'aquest experiment:

  • OLS reparteix pes entre frequencia, productes i visites_web (redundants) i fins i tot assigna alguna cosa a les features de soroll.
  • Ridge redueix tots els pesos i estabilitza el repartiment entre les correlacionades; sol millorar lleugerament el RMSE de validació.
  • Lasso anul·la soroll_1, soroll_2 i sovint alguna de les redundants: el model resultant és més curt i més llegible per a l'equip de negoci de MercaFresh ("la despesa l'expliquen la freqüència i el tiquet mitjà").
  • Elastic Net queda entre tots dos: elimina el soroll però conserva part del grup correlacionat.

Fixa't que la comparació es fa amb validació creuada sobre l'entrenament, reservant la prova per al veredicte final — la disciplina del mòdul 6 no s'abandona per usar tècniques més avançades.

La mateixa idea en classificació: el paràmetre C

La regularització no és exclusiva de la regressió. De fet, ja l'has estat usant sense saber-ho:

  • A 04-02 vam veure que LogisticRegression de sklearn té un paràmetre C. Doncs bé: LogisticRegression ve regularitzada amb L2 per defecte, i C és exactament l'invers d'alpha: C = 1/α.
  • A 04-04, el paràmetre C de les SVM complia el mateix paper: controlar l'equilibri entre ajustar bé l'entrenament i mantenir un model simple (marge ampli).
Paràmetre Valor petit Valor gran
alpha (Ridge/Lasso/EN) Poca regularització → risc d'overfitting Molta regularització → risc d'underfitting
C (LogisticRegression/SVM) Molta regularització → underfitting Poca regularització → overfitting

Compte amb la inversió: apujar alpha i apujar C tenen efectes oposats. A més, LogisticRegression(penalty="l1", solver="liblinear") et dona un classificador amb selecció de features a l'estil Lasso — útil en el churn de MercaFresh si vols un model que usi poques variables RFM.

from sklearn.linear_model import LogisticRegression

# Churn amb regularitzacio L1: coeficients a zero = features descartades
clf = Pipeline([
    ("scaler", StandardScaler()),
    ("logreg", LogisticRegression(penalty="l1", C=0.1, solver="liblinear")),
])

Taula comparativa i criteris d'elecció

Criteri Ridge (L2) Lasso (L1) Elastic Net
Coeficients a zero exacte No
Selecció de features No Automàtica Automàtica (per grups)
Features molt correlacionades Reparteix el pes (estable) En tria una gairebé a l'atzar Tendeix a conservar el grup
Nre. d'hiperparàmetres 1 (alpha) 1 (alpha) 2 (alpha, l1_ratio)
Quan usar-la Moltes features útils i correlacionades; prioritat: estabilitat Sospites que poques features importen; prioritat: interpretabilitat Moltes features, algunes d'irrellevants i altres en grups correlacionats

Regla pràctica: comença amb Ridge com a opció segura; passa a Lasso si necessites un model compacte i explicable; usa Elastic Net quan Lasso es comporti de manera inestable per la correlació entre features.

Errors Comuns i Consells

  • Regularitzar sense escalar. L'error número u. Sense StandardScaler, alpha castiga les features per la seva unitat de mesura, no per la seva rellevància. Usa sempre un Pipeline.
  • Confondre alpha i C. En regressió (Ridge/Lasso), més alpha = més regularització. En classificació (C), és a l'inrevés. Comprova sempre en quina direcció mous el control.
  • Interpretar el zero de Lasso com "aquesta variable no influeix en la realitat". Lasso diu que la feature no aporta atès que ja hi són les altres; amb features correlacionades, l'eliminada pot ser tan causal com la supervivent. La inferència causal és una altra disciplina.
  • Triar alpha mirant l'error d'entrenament. L'entrenament sempre preferirà alpha = 0. L'alpha es tria amb validació (creuada), mai amb l'error d'entrenament.
  • Oblidar max_iter a Lasso/ElasticNet. Usen optimització iterativa (descens per coordenades) i amb alphas petits poden no convergir; si veus warnings, apuja max_iter.
  • Consell: explora alpha sempre en escala logarítmica (np.logspace), perquè el seu efecte és multiplicatiu: la diferència rellevant és entre 0.01, 0.1, 1 i 10, no entre 1 i 2.

Exercicis

  1. Trajectòries sobre MercaFresh. Amb el dataset de despesa mensual d'aquesta lliçó (les 7 features), dibuixa les trajectòries de coeficients de Lasso per a alphas = np.logspace(-2, 2, 100) (estandarditzant abans). En quin ordre "moren" les features? Coincideix amb el que saps de com es va generar la despesa?
  2. Ridge contra la multicolinealitat. Genera 20 mostres diferents del dataset petit de la secció 1 (canviant la llavor) i ajusta en cadascuna OLS i Ridge amb alpha=10 (amb escalat). Calcula la desviació estàndard del primer coeficient entre les 20 execucions per a cada model. Quin és més estable?
  3. L1 en el churn. Sobre el dataset de churn amb features RFM del mòdul 3, entrena LogisticRegression(penalty="l1", solver="liblinear") amb C a [0.01, 0.1, 1, 10] dins d'un pipeline amb escalat. Per a cada C, compta quants coeficients queden a zero i calcula l'F1 amb validació creuada estratificada (06-03). Quina C triaries i per què?

Solucions

  1. Estandarditza amb StandardScaler i ajusta un Lasso per cada alpha, guardant coef_. En pintar les 7 corbes veuràs que soroll_1 i soroll_2 moren gairebé immediatament (alphas petits), després cauen les redundants (visites_web, productes — Lasso conserva normalment frequencia del grup correlacionat) i les últimes supervivents són frequencia i tiquet_mitja, exactament les dues variables amb què es va generar la despesa. L'ordre de mort de les trajectòries és en si mateix un rànquing de rellevància.
  2. Estructura: bucle for llavor in range(20), regenerar dades amb np.random.default_rng(llavor), ajustar tots dos models sobre features estandarditzades i guardar coef_[0]. Amb OLS la desviació estàndard del coeficient resulta diverses vegades més gran que amb Ridge (amb dades tan correlacionades pot ser un ordre de magnitud). Conclusió: Ridge no només millora la predicció; fa que el model sigui reproduïble, que és el que significa reduir variància.
  3. Amb C=0.01 (regularització molt forta) gairebé tots els coeficients queden a zero i l'F1 cau cap al del baseline DummyClassifier (06-02): underfitting. Amb C=10 no s'anul·la gairebé res i el resultat s'assembla a la logística sense regularitzar. El punt interessant sol ser a C=0.11: F1 comparable al màxim amb 2-4 features RFM eliminades. Triaries la C amb millor F1-CV i, en empat pràctic, la més regularitzada (model més simple). La cerca sistemàtica d'aquest tipus de decisions l'automatitzarem a 07-05.

Conclusió

La regularització converteix el compromís biaix-variància de 06-05 en un control continu: la funció de cost passa a premiar simultàniament l'ajust i la simplicitat, i alpha decideix l'equilibri. Ridge (L2) encongeix coeficients i estabilitza models amb features correlacionades; Lasso (L1) a més posa coeficients a zero exacte, regalant-nos selecció automàtica de features; Elastic Net barreja totes dues. Has vist que la idea no és nova per a tu: el paràmetre C de la regressió logística i de les SVM era regularització disfressada. I has confirmat dues disciplines innegociables: escalar dins d'un Pipeline i triar alpha amb validació creuada, mai amb l'entrenament. Regularitzar millora un model contenint-lo; la lliçó següent explora el camí oposat i complementari: millorar combinant molts models perquè els seus errors es cancel·lin entre si. Benvingut a l'Ensemble Learning.

Curs de Machine Learning

Mòdul 1: Introducció al Machine Learning

Mòdul 2: Fonaments d'Estadística i Probabilitat

Mòdul 3: Preprocessament de Dades

Mòdul 4: Algorismes de Machine Learning Supervisat

Mòdul 5: Algorismes de Machine Learning No Supervisat

Mòdul 6: Avaluació i Validació de Models

Mòdul 7: Tècniques Avançades i Optimització

Mòdul 8: Implementació i Desplegament de Models

Mòdul 9: Projectes Pràctics

Mòdul 10: Recursos Addicionals

© Copyright 2026. Tots els drets reservats