Ja sabem quin tipus de problema tenim (04-02) i com deixar les dades a punt (04-03). Falta obrir la caixa negra de fit: què fa exactament cada algorisme quan "aprèn"? En aquesta lliçó recorrerem els algorismes clàssics que resolen la immensa majoria dels problemes de negoci: la regressió lineal, la regressió logística, els k veïns més propers, els arbres de decisió, els boscos aleatoris (i la idea d'ensamblatge), les màquines de vectors de suport, Naive Bayes i k-means. Per a cadascun donarem la intuïció, el pessic de matemàtica necessari per entendre què s'optimitza (recordant que "aprendre és optimitzar", 03-04) i codi sobre les dades de NovaMarket: la demanda del NovaClean per a la regressió, les comandes retornades per a la classificació i els clients per a l'agrupament. Acabarem amb una taula comparativa per triar algorisme i una comparació en codi de diversos classificadors sobre les mateixes comandes. És important perquè triar i entendre l'algorisme és el que permet interpretar-ne els resultats, explicar-los al Diego, saber quan desconfiar i, més endavant, ajustar-lo (04-06). Un avís: la comparació final fa servir només l'exactitud, mesura provisional que a 04-05 substituirem per altres de millors.

Contingut

  1. Regressió lineal: la recta que menys s'equivoca
  2. Regressió logística: de la suma ponderada a la probabilitat
  3. k veïns més propers: predir per semblança
  4. Arbres de decisió: preguntes encadenades
  5. Boscos aleatoris i la idea d'ensamblatge
  6. Màquines de vectors de suport: el marge més ample
  7. Naive Bayes: probabilitat amb independència
  8. k-means per dins: centroides i mètode del colze
  9. Taula comparativa i com triar
  10. Comparació en codi de diversos classificadors
  11. Errors Comuns i Consells
  12. Exercicis
  13. Conclusió

  1. Regressió lineal: la recta que menys s'equivoca

1.1 Intuïció i matemàtica mínima

La regressió lineal suposa que l'etiqueta és una suma ponderada de les característiques més una constant: amb una sola característica, una recta y = a + b·x; amb diverses, y = a + b₁·x₁ + b₂·x₂ + .... Els paràmetres són a (ordenada) i els coeficients b. Quina recta triar? La que minimitza la suma d'errors al quadrat (mínims quadrats): per a cada exemple es calcula la diferència entre el valor real i el predit, s'eleva al quadrat (perquè els errors per excés i per defecte no es cancel·lin i per penalitzar més els grans) i se sumen.

Exemple numèric petit: quatre setmanes amb vendes 350, 340, 380, 390. Comparem tres rectes candidates:

Recta Prediccions Errors Suma de quadrats
330 + 10·setmana 340, 350, 360, 370 10, −10, 20, 20 1.000
335 + 15·setmana 350, 365, 380, 395 0, −25, 0, −5 650
330 + 15·setmana 345, 360, 375, 390 5, −20, 5, 0 450
325 + 16·setmana (l'òptima) 341, 357, 373, 389 9, −17, 7, 1 420

LinearRegression().fit troba l'última sense provar candidates: per a aquest problema hi ha una fórmula tancada (les anomenades equacions normals) que dona directament els coeficients òptims. És un dels pocs algorismes amb solució exacta; la majoria dels següents optimitzen per cerca iterativa.

La gran virtut de la regressió lineal és la interpretabilitat: cada coeficient diu quant canvia la predicció per cada unitat de la característica, amb les altres fixes.

1.2 Codi: la demanda del NovaClean amb estacionalitat

A 04-02 una recta sobre setmana donava un error mitjà de 47 unitats perquè ignorava l'estacionalitat i el Black Friday. Afegim característiques que els representin (com vam anunciar a 04-03) i el mateix algorisme millora molt:

import numpy as np
from novamarket_ml import generar_demanda_setmanal
from sklearn.linear_model import LinearRegression

d = generar_demanda_setmanal(104, 42)
d["sen"] = np.sin(2 * np.pi * d["setmana"] / 52)     # l'estacionalitat anual com a ona
d["cos"] = np.cos(2 * np.pi * d["setmana"] / 52)
d["black_friday"] = ((d["setmana"] - 1) % 52 == 47).astype(int)

caracteristiques = ["setmana", "sen", "cos", "black_friday"]
train, test = d[d["setmana"] <= 78], d[d["setmana"] > 78]

reg = LinearRegression().fit(train[caracteristiques], train["unitats"])
print("Coeficients:", dict(zip(caracteristiques, reg.coef_.round(2))))
print("Ordenada:", round(reg.intercept_, 1))
previsio = reg.predict(test[caracteristiques])
print("Error mitja en test:", round(np.abs(previsio - test["unitats"]).mean(), 1), "unitats")

Sortida:

Coeficients: {'setmana': 2.47, 'sen': -10.25, 'cos': -61.41, 'black_friday': 255.22}
Ordenada: 399.7
Error mitja en test: 16.8 unitats

Explicació: la recta ara té quatre coeficients. setmana (2,47) recupera la tendència real de 2,5 unitats/setmana; sen i cos capturen l'ona anual (la combinació de tots dos equival a una ona d'amplitud √(10² + 61²) ≈ 62, davant dels 60 reals); black_friday suma 255 unitats aquella setmana (250 reals). L'error mitjà cau de 47 a 17 unitats. L'algorisme és el mateix; el que ha canviat són les característiques. És la lliçó de 04-03 confirmada amb nombres.

  1. Regressió logística: de la suma ponderada a la probabilitat

2.1 Intuïció

Malgrat el nom, és un algorisme de classificació. Calcula la mateixa suma ponderada que la regressió lineal, z = a + b₁·x₁ + ... + bₙ·xₙ, però en comptes de fer servir z com a predicció la passa per la funció sigmoide, σ(z) = 1 / (1 + e^(−z)), que converteix qualsevol nombre en un valor entre 0 i 1 interpretable com a probabilitat de la classe positiva. Si z = 0, la probabilitat és 0,5; valors molt positius donen probabilitat propera a 1 i molt negatius, propera a 0. És exactament la fórmula que vam fer servir al generador de 04-01 per crear la "veritat oculta"; per això la logística és un candidat natural per a aquestes dades.

Després s'aplica un llindar: per defecte, probabilitat ≥ 0,5 → classe 1. Aquest llindar no és sagrat: a 04-05 veurem que convé moure'l segons el cost de cada error.

Els paràmetres (els coeficients) s'aprenen maximitzant la versemblança de les dades, o equivalentment minimitzant la pèrdua logarítmica (04-01), que castiga molt assignar probabilitat baixa al que sí que va passar. No hi ha fórmula tancada: s'optimitza iterativament seguint el gradient, la idea que vam anunciar en tancar 03-04 i que desenvoluparem a 05-03.

2.2 Codi: llegir els coeficients i calcular una probabilitat a mà

from novamarket_ml import generar_comandes_ml
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
import pandas as pd

comandes = generar_comandes_ml(3000, 42)
cols = ["import_comanda", "num_articles", "dies_lliurament", "client_nou"]
X_train, X_test, y_train, y_test = train_test_split(
    comandes[cols], comandes["retornat"], test_size=0.25, random_state=42, stratify=comandes["retornat"])

log = LogisticRegression(max_iter=1000).fit(X_train, y_train)
print("Coeficients:", dict(zip(cols, log.coef_[0].round(4))), " Ordenada:", round(log.intercept_[0], 3))

comanda = pd.DataFrame({"import_comanda": [250.0], "num_articles": [1], "dies_lliurament": [5], "client_nou": [1]})
z = log.intercept_[0] + (log.coef_[0] * comanda.values[0]).sum()
print("z =", round(z, 3), " sigmoide(z) =", round(1 / (1 + np.exp(-z)), 3))
print("predict_proba:", log.predict_proba(comanda).round(3))

Sortida:

Coeficients: {'import_comanda': 0.012, 'num_articles': -0.2012, 'dies_lliurament': 0.3069, 'client_nou': 1.8821}  Ordenada: -4.906
z = 1.302  sigmoide(z) = 0.786
predict_proba: [[0.214 0.786]]

Lectura: cada euro d'import suma 0,012 a z; cada dia de lliurament, 0,31; ser client nou, 1,88; cada article addicional resta 0,20. Per a la comanda de 250 € (1 article, 5 dies, client nou), z = −4,906 + 0,012·250 − 0,201·1 + 0,307·5 + 1,882·1 = 1,30, i la sigmoide dona 0,786: es prediu devolució. La mateixa comanda d'un client habitual tindria z = −0,58 i probabilitat 0,36: no es marcaria. El Diego pot seguir el càlcul amb una calculadora, i aquesta és la força d'aquest model. Nota: sense escalar, els coeficients no són comparables entre columnes (0,012 per euro davant d'1,88 per ser nou); per comparar la importància cal estandarditzar primer (04-03) o mirar el rang de cada variable.

  1. k veïns més propers: predir per semblança

3.1 Intuïció

k-NN no aprèn cap fórmula: memoritza el conjunt d'entrenament i, per predir una comanda nova, busca les k comandes més semblants (les més properes en l'espai de característiques, amb la distància euclidiana habitual) i vota: la classe majoritària entre aquests veïns (o la mitjana dels seus valors, en regressió). És l'algorisme més intuïtiu ("les comandes com aquesta es van retornar") i té dues conseqüències pràctiques:

  • Necessita escalat (04-03): sense això, import_comanda (desenes o centenars) domina la distància i client_nou (0/1) no compta.
  • L'hiperparàmetre k controla la suavitat: k = 1 segueix el veí més proper (memoritza el soroll), k gran fa mitjana de molts (perd detall). A 04-06 veurem que és l'exemple de manual de sobreajust davant de subajust.

3.2 Codi

from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline

for k in (1, 5, 15, 51):
    knn = Pipeline([("escalar", StandardScaler()),
                    ("knn", KNeighborsClassifier(n_neighbors=k))]).fit(X_train, y_train)
    print(f"k={k:2d}  train {knn.score(X_train, y_train):.3f}  test {knn.score(X_test, y_test):.3f}")

sense_escalar = KNeighborsClassifier(n_neighbors=15).fit(X_train, y_train)
print("k=15 sense escalar: test", round(sense_escalar.score(X_test, y_test), 3))

Sortida:

k= 1  train 1.000  test 0.815
k= 5  train 0.888  test 0.864
k=15  train 0.872  test 0.875
k=51  train 0.872  test 0.872
k=15 sense escalar: test 0.837

Amb k = 1 el model encerta el 100 % de l'entrenament (cada comanda és el seu propi veí) i només el 81,5 % del test: memòria pura. Amb k = 15 s'estabilitza en 87,5 %. I sense escalar cau al 83,7 %, gairebé el nivell de "mai no es retorna" (83,6 %): la distància estava dominada per l'import.

  1. Arbres de decisió: preguntes encadenades

4.1 Intuïció i matemàtica mínima

Un arbre de decisió fa el que feia aprendre_llindar a 01-02, però repetidament: tria la columna i el llindar que millor separen les classes, parteix les dades en dues, i repeteix a cada meitat fins que les fulles són prou pures o s'arriba a la profunditat màxima. El resultat és un conjunt de regles llegibles.

Què vol dir "millor separen"? Es mesura la impuresa d'un grup: si totes les seves comandes són de la mateixa classe, impuresa 0; si estan al 50 %, impuresa màxima. Les dues mesures habituals són la impuresa de Gini, 1 − p² − (1−p)² per a dues classes amb proporció p de positius, i l'entropia, −p·log₂p − (1−p)·log₂(1−p). Totes dues es comporten igual a la pràctica. L'arbre prova tots els llindars de totes les columnes i tria el que més redueix la impuresa ponderada dels dos fills respecte al pare.

Amb nombres de les nostres dades: a l'arrel hi ha 2.250 comandes amb un 16,4 % de retornades, Gini = 1 − 0,164² − 0,836² = 0,275. La partició import_comanda ≤ 195,65 deixa a l'esquerra 1.895 comandes amb 11,5 % de retornades (Gini 0,204) i a la dreta 355 amb 42,8 % (Gini 0,490); la impuresa ponderada és (1.895·0,204 + 355·0,490)/2.250 = 0,249, un guany de 0,026. La partició alternativa per client_nou donaria 0,252 (menys guany); per això l'arbre pregunta primer per l'import. Això és tot l'"aprenentatge": una cerca voraç (03-02) de la millor pregunta a cada node.

4.2 Codi: llegir les regles amb export_text

Fem servir les quatre columnes numèriques més la categoria en one-hot (sense escalar: els arbres no ho necessiten):

from sklearn.tree import DecisionTreeClassifier, export_text

Xd = pd.get_dummies(comandes[cols + ["categoria"]], columns=["categoria"], dtype=int)
Xd_train, Xd_test, yd_train, yd_test = train_test_split(
    Xd, comandes["retornat"], test_size=0.25, random_state=42, stratify=comandes["retornat"])

arbre = DecisionTreeClassifier(max_depth=3, random_state=42).fit(Xd_train, yd_train)
print(export_text(arbre, feature_names=list(Xd.columns), show_weights=True))
print("Encert en test:", round(arbre.score(Xd_test, yd_test), 3))

Sortida (resumida):

|--- import_comanda <= 195.65
|   |--- client_nou <= 0.50
|   |   |--- dies_lliurament <= 6.50        -> weights: [1075, 60]  class: 0
|   |   |--- dies_lliurament >  6.50        -> weights: [159, 28]   class: 0
|   |--- client_nou >  0.50
|   |   |--- import_comanda <= 142.31       -> weights: [379, 70]   class: 0
|   |   |--- import_comanda >  142.31       -> weights: [64, 60]    class: 0
|--- import_comanda >  195.65
|   |--- client_nou <= 0.50
|   |   |--- import_comanda <= 358.52       -> weights: [179, 44]   class: 0
|   |   |--- import_comanda >  358.52       -> weights: [8, 15]     class: 1
|   |--- client_nou >  0.50
|   |   |--- categoria_electronica <= 0.50  -> weights: [16, 56]    class: 1
|   |   |--- categoria_electronica >  0.50  -> weights: [0, 37]     class: 1
Encert en test: 0.868

Cada fulla mostra quantes comandes d'entrenament hi van caure [no retornades, retornades] i la classe majoritària. Es llegeix com un manual de regles: "comandes de més de 195 € de clients nous: retornades (56 de 72; i 37 de 37 si és electrònica)"; "clients habituals només per sobre de 358 €". El Diego reconeix la seva intuïció corregida per les dades: el seu llindar de 300 € era raonable per a clients habituals i massa alt per als nous. La fulla [64, 60] (clients nous entre 142 i 196 €) està gairebé al 50 %: és la zona grisa de 02-04.

Si traiem max_depth, l'arbre continua partint fins a fulles pures: en aquestes dades arriba a profunditat 21 amb 414 fulles, encerta el 100 % en entrenament i el 82,5 % en test, pitjor que la profunditat 3. És el sobreajust que estudiarem a 04-06; de moment, queda't que la profunditat és un hiperparàmetre que cal limitar.

  1. Boscos aleatoris i la idea d'ensamblatge

Un arbre sol és inestable: canvia molt si canvien una mica les dades. La solució és un ensamblatge: entrenar molts models i combinar-ne els vots. El bosc aleatori (random forest) entrena centenars d'arbres, cadascun amb una mostra aleatòria amb reemplaçament de les dades (bagging, de bootstrap aggregating) i considerant a cada node només un subconjunt aleatori de columnes, i prediu per majoria (o mitjana en regressió). Cada arbre s'equivoca de manera diferent, i els errors es cancel·len en votar: és la saviesa de les multituds aplicada a models.

from sklearn.ensemble import RandomForestClassifier

bosc = RandomForestClassifier(n_estimators=200, min_samples_leaf=5, random_state=42)
bosc.fit(Xd_train, yd_train)
print("Encert en test:", round(bosc.score(Xd_test, yd_test), 3))
importancies = pd.Series(bosc.feature_importances_, index=Xd.columns).sort_values(ascending=False)
print(importancies.round(3).head(4))

Sortida:

Encert en test: 0.871
import_comanda     0.528
client_nou         0.204
dies_lliurament    0.132
num_articles       0.075

n_estimators és el nombre d'arbres i min_samples_leaf el mínim de comandes per fulla (evita fulles d'un sol exemple). Es perd la lectura de regles d'un arbre únic, però es guanya robustesa i les importàncies de característiques que vam fer servir a 04-03 (aquí, l'import explica més de la meitat de la separació).

L'altra gran família d'ensamblatges és el boosting (gradient boosting: GradientBoostingClassifier, i les llibreries XGBoost, LightGBM i CatBoost): en comptes d'arbres independents en paral·lel, s'entrenen en seqüència, i cada arbre nou es concentra a corregir els errors del conjunt anterior. És, avui, l'algorisme que més competicions i projectes guanya sobre dades tabulars; es presenta a 07-03 i n'hi ha prou que sàpigues que existeix i quan provar-lo (quan el bosc funciona i vols esprémer-ne més).

  1. Màquines de vectors de suport: el marge més ample

La SVM (support vector machine) busca la frontera que separa les dues classes deixant el marge més ample possible a banda i banda; els exemples que toquen el marge són els vectors de suport, i només ells determinen la frontera. Quan les classes no són separables per una recta (o un pla), el truc del kernel projecta les dades a un espai de més dimensions on sí que ho són, sense calcular aquesta projecció explícitament; el kernel RBF (gaussià) és el més usat i permet fronteres corbes. Els hiperparàmetres principals són C (quant es penalitzen els punts mal classificats) i gamma (com de local és la frontera amb RBF). Necessita escalat i escala malament a datasets molt grans (centenars de milers de files), però és molt sòlida en mides mitjanes.

from sklearn.svm import SVC

svm = Pipeline([("escalar", StandardScaler()), ("svm", SVC(kernel="rbf", C=1.0))]).fit(X_train, y_train)
print("SVM (RBF) test:", round(svm.score(X_test, y_test), 3))     # 0.872

Només intuïció: la SVM no retorna probabilitats de manera natural (hi ha una opció probability=True, més lenta) i les seves fronteres no es llegeixen com a regles.

  1. Naive Bayes: probabilitat amb independència

Naive Bayes aplica el teorema de Bayes (que estudiarem amb detall a 06-03) per calcular la probabilitat de cada classe donades les característiques, amb la suposició "ingènua" que les característiques són independents entre si donada la classe. Aquesta suposició gairebé mai no és certa, però l'algorisme funciona sorprenentment bé, és rapidíssim i necessita poques dades; és el clàssic del filtratge de spam i una bona línia base per classificar ressenyes per paraules (cas 4). Sobre les nostres comandes, GaussianNB().fit(X_train, y_train) encerta el 86,0 % en test.

  1. k-means per dins: centroides i mètode del colze

A 04-02 vam fer servir k-means per segmentar clients. Per dins és un algorisme iteratiu de dos passos (l'algorisme de Lloyd):

  1. Triar k centroides inicials (punts a l'espai de característiques; KMeans fa servir una inicialització intel·ligent anomenada k-means++).
  2. Assignar cada client al centroide més proper.
  3. Recalcular cada centroide com la mitjana dels clients assignats.
  4. Repetir 2-3 fins que les assignacions no canviïn.

És un ascens de turó (03-04) sobre la inèrcia, la suma de distàncies al quadrat de cada punt al seu centroide: cada iteració la redueix, i s'atura en un òptim local; per això n_init=10 executa 10 arrencades diferents i es queda amb la millor, exactament com els reinicis aleatoris de 03-04. Als nostres clients convergeix en 4 iteracions.

El nombre de grups k és un hiperparàmetre que cal decidir. El mètode del colze entrena k-means per a diversos valors de k i dibuixa la inèrcia: sempre baixa en augmentar k (amb k = n seria 0), però arriba un punt en què afegir grups aporta poc; aquest "colze" és un bon k.

from sklearn.cluster import KMeans
from novamarket_ml import generar_clients_ml

clients = generar_clients_ml(600, 42)
Xc = StandardScaler().fit_transform(clients)
for k in range(1, 9):
    km = KMeans(n_clusters=k, n_init=10, random_state=42).fit(Xc)
    print(k, round(km.inertia_, 1))

Sortida:

1 1800.0
2 795.7
3 399.9
4 299.2
5 254.5
6 219.3
7 194.4
8 174.1

D'1 a 2 grups la inèrcia cau 1.000; de 2 a 3, 400 més; de 3 a 4, només 100, i després menys de 50 per grup: el colze és a k = 3, que coincideix amb els tres tipus de client. Si ho dibuixes amb Matplotlib (plt.plot(range(1, 9), inercies, marker="o")) ho veuràs com un braç doblegat. El colze no sempre és tan clar; llavors es complementa amb el coeficient de silueta (silhouette_score) i, sobretot, amb la interpretabilitat de negoci dels grups.

  1. Taula comparativa i com triar

Algorisme Problema Interpretabilitat Necessita escalat? Fortaleses Debilitats Quan triar-lo
Regressió lineal Regressió Molt alta (coeficients) Recomanable amb regularització Simple, ràpida, exacta, explica l'efecte de cada variable Només relacions lineals; sensible a atípics Línia base de regressió; quan importa explicar
Regressió logística Classificació Alta (coeficients, probabilitats) Probabilitats ben calibrades, robusta, ràpida Frontera lineal Línia base de classificació; problemes amb requisits d'explicació
k veïns Tots dos Mitjana (es veuen els veïns) Sí, imprescindible Sense suposicions, captura formes complexes Lent en predir amb moltes dades; pateix amb moltes columnes Dades petites o mitjanes i poques columnes rellevants
Arbre de decisió Tots dos Molt alta (regles) No Regles llegibles, gestiona barreges de tipus i interaccions Inestable, sobreajusta si no es limita Quan cal explicar decisions una a una
Bosc aleatori Tots dos Mitjana (importàncies) No Robust, poc ajust necessari, bon rendiment general Menys interpretable, més lent, fitxers grans Primera opció "seriosa" en dades tabulars
Gradient boosting Tots dos Mitjana No Sol donar el millor rendiment en tabulars Més hiperparàmetres, fàcil sobreajustar Quan el bosc funciona i se'n vol més
SVM Classificació (i regressió) Baixa Molt bona en mida mitjana, fronteres flexibles amb kernel Escala malament, sense probabilitats natives Dades mitjanes amb fronteres complexes
Naive Bayes Classificació Mitjana No Rapidíssim, poques dades, text Suposició d'independència Text, línia base ràpida
k-means Agrupament Alta (centroides) Simple, escalable Cal fixar k; grups esfèrics Segmentació amb grups compactes
Xarxes neuronals Tots dos Baixa Imatges, text, senyals, escala massiva Moltes dades i còmput, poc interpretables Mòdul 5

Regla pràctica de la Marta: començar per logística (o lineal) com a línia base interpretable, provar un bosc aleatori, i només si compensa passar a boosting o a xarxes. I triar segons el problema complet, no només l'encert: si el Diego necessita justificar cada denegació de reemborsament, l'arbre o la logística guanyen encara que el bosc encerti una mica més.

  1. Comparació en codi de diversos classificadors

Reutilitzem la preparació de 04-03 tal com la vam empaquetar a la seva secció 9.1 (preparar_comandes i crear_preparacio a novamarket_ml.py) i canviem només l'últim pas del pipeline:

from novamarket_ml import generar_comandes_ml, embrutar_comandes, preparar_comandes, crear_preparacio

X, y = preparar_comandes(embrutar_comandes(generar_comandes_ml(3000, 42), 42))
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.25, random_state=42, stratify=y)

models = {
    "Regressió logística": LogisticRegression(max_iter=1000),
    "k veïns (k=15)":      KNeighborsClassifier(n_neighbors=15),
    "Arbre (prof. 4)":     DecisionTreeClassifier(max_depth=4, random_state=42),
    "Bosc aleatori":       RandomForestClassifier(n_estimators=200, min_samples_leaf=5, random_state=42),
    "SVM (RBF)":           SVC(kernel="rbf"),
}
for nom, m in models.items():
    p = Pipeline([("preparacio", crear_preparacio()), ("model", m)]).fit(Xtr, ytr)
    print(f"{nom:22s} train {p.score(Xtr, ytr):.3f}  test {p.score(Xte, yte):.3f}")
print("Linia base 'mai no es retorna': ", round((yte == 0).mean(), 3))

Sortida:

Regressió logística    train 0.881  test 0.871
k veïns (k=15)         train 0.850  test 0.841
Arbre (prof. 4)        train 0.886  test 0.855
Bosc aleatori          train 0.908  test 0.859
SVM (RBF)              train 0.894  test 0.864
Linia base 'mai no es retorna':  0.836

Observacions (provisionals, perquè l'exactitud enganya amb classes desequilibrades, com veurem a 04-05):

  • La regressió logística és la millor aquí, cosa que no sorprèn: la "veritat oculta" del generador és una sigmoide d'una suma ponderada, justament la seva família. En dades reals, amb interaccions i no linealitats, els boscos solen avançar-la.
  • k veïns empitjora respecte a la secció 3 (0,841 davant 0,875): amb les 21 columnes del pipeline, moltes d'elles soroll, les distàncies perden sentit (la "maledicció de la dimensionalitat"). És l'algorisme més sensible a la selecció de característiques.
  • El bosc té la diferència més gran entre train (0,908) i test (0,859): un principi de sobreajust que 04-06 tractarà amb hiperparàmetres.
  • Cap model no passa del 87 % i la línia base és al 83,6 %. Amb aquesta mesura sembla poc guany; a 04-05 veurem que, en el que importa (detectar devolucions sense molestar clients honrats), la diferència és molt més gran.

Falta un algorisme important en aquesta llista: el perceptró i les xarxes neuronals, que són "un algorisme més" de classificació i regressió (scikit-learn té MLPClassifier), però l'arquitectura i l'entrenament dels quals mereixen un mòdul sencer, el 5.

Errors Comuns i Consells

  • Fer servir k-NN o SVM sense escalar. Els resultats cauen al nivell de la línia base, com vam veure amb k-NN sense StandardScaler.
  • Deixar un arbre sense límit de profunditat. Memoritza (100 % en train, 82,5 % en test). Fixa max_depth o min_samples_leaf, o fes servir un bosc.
  • Interpretar els coeficients de la logística sense mirar les unitats. 0,012 per euro pot pesar més que 1,88 per ser client nou quan l'import varia en centenars d'euros. Estandarditza abans de comparar.
  • Triar l'algorisme per moda. El boosting o les xarxes no són millors per definició; per a 3.000 comandes amb poques columnes, la logística guanya. Prova sempre la línia base simple.
  • Refiar-se de k-means sense el colze ni interpretació. Sempre troba k grups; el colze, la silueta i el sentit de negoci diuen si valen alguna cosa.
  • Comparar models amb una sola xifra d'exactitud. És el que acabem de fer, i per això hem insistit que és provisional. La lliçó següent dona les eines correctes.

Exercicis

Exercici 1. Afegeix a la regressió de la demanda (secció 1.2) la característica unitats_setmana_anterior (d["unitats"].shift(1), eliminant la primera fila amb dropna()). Millora l'error mitjà? Mira el coeficient que rep. Per què aporta poc quan ja hi són la tendència, l'estacionalitat i el Black Friday, i en quina situació real seria més valuosa?

Exercici 2. Calcula a mà (o amb dues línies de pandas) la impuresa de Gini de la partició dies_lliurament <= 4 a l'arrel de les dades d'entrenament de la secció 4, i compara-la amb la d'import_comanda <= 195.65 (0,249). Quina preferiria l'arbre? Comprova després què passa amb la profunditat i el nombre de fulles si entrenes amb min_samples_leaf=50 en comptes de max_depth=3.

Exercici 3. Amb els clients de la secció 8, calcula el coeficient de silueta (from sklearn.metrics import silhouette_score; silhouette_score(Xc, km.labels_)) per a k de 2 a 6. Coincideix el màxim amb el colze? Canvia després la llavor de generar_clients_ml a 7 i repeteix-ho: es manté la conclusió?

Solucions

Solució 1. L'error mitjà queda pràcticament igual (17,0 davant 16,8 unitats) i el coeficient del lag és gairebé zero (0,04): les altres quatre característiques ja expliquen la sèrie, així que les vendes de la setmana anterior no afegeixen informació nova. Seria valuosa en una sèrie real, on la demanda té inèrcia que ni la tendència ni l'estacionalitat fixes capturen (una campanya que arrenca, un producte que es posa de moda, un trencament d'estoc): allà el lag sol ser la característica més important. També és l'única característica que exigeix cura amb la fuita (04-03): sempre shift(1).

Solució 2. Per a dies_lliurament <= 4: la meitat esquerra (dies 1-4, 1.293 comandes) té un 11,7 % de retornades i la dreta (5-7, 957 comandes) un 22,9 %; els Gini són 0,206 i 0,353 i la impuresa ponderada 0,269, un guany de només 0,006 davant dels 0,026 de la partició per import. L'arbre prefereix l'import. Amb min_samples_leaf=50 (sense max_depth) l'arbre creix més que amb profunditat 3 (profunditat 8 i 32 fulles), però cap fulla no té menys de 50 comandes, cosa que limita la memorització: encerta 0,876 en entrenament i 0,864 en test, semblant a l'arbre de profunditat 3. Són dues maneres diferents de frenar el mateix problema, i a 04-06 les compararem amb mètode.

Solució 3. Amb la llavor 42, la silueta és màxima a k = 3 (0,57, davant 0,55 amb k = 2 i 0,53 amb k = 4) i cau a 0,42 amb k = 6: coincideix amb el colze i amb els tres tipus de client. Amb la llavor 7 els valors canvien lleugerament (0,58 a k = 3) però el màxim continua a k = 3, perquè l'estructura oculta (tres grups) és la mateixa; només canvien els clients concrets. Si en un dataset real la silueta fos baixa per a tot k (per sota de 0,25) o el colze no aparegués, la conclusió honesta seria que no hi ha grups clars, i forçar una segmentació seria inventar estructura.

Conclusió

En aquesta lliçó hem obert la caixa de fit per als algorismes clàssics: la regressió lineal minimitza els errors al quadrat i, amb les característiques adequades, va reduir l'error de la demanda del NovaClean de 47 a 17 unitats; la regressió logística passa una suma ponderada per la sigmoide per donar probabilitats llegibles; k veïns prediu per semblança i exigeix escalat; els arbres encadenen preguntes triant a cada node la que més redueix la impuresa de Gini, i produeixen regles que el Diego pot llegir; els boscos aleatoris fan la mitjana de molts arbres (bagging) i el boosting els encadena corregint errors; les SVM busquen el marge més ample amb ajuda de kernels; Naive Bayes aplica Bayes amb independència; i k-means alterna assignar i recalcular centroides, amb el mètode del colze per triar k. La taula comparativa i la comparació en codi ens han deixat una primera classificació de models sobre les comandes de NovaMarket, mesurada només amb l'exactitud.

I aquí hi ha el problema pendent: l'exactitud diu que la logística encerta el 87,1 % i la línia base "mai no es retorna" el 83,6 %, una diferència que al Diego li sembla petita. A la lliçó següent, Avaluació i Validació de Models, veurem per què aquesta xifra enganya amb classes desequilibrades, aprendrem a llegir la matriu de confusió i a traduir cada tipus d'error a euros, mesurarem precisió, sensibilitat, F1 i AUC, triarem el llindar segons el cost, farem servir les mètriques de regressió per a la demanda i validarem amb mètodes que no es deixin enganyar per una única divisió afortunada, inclosa la validació temporal per a les sèries.

Fonaments d'Intel·ligència Artificial (IA)

Mòdul 1: Introducció a la Intel·ligència Artificial

Mòdul 2: Principis Bàsics de la IA

Mòdul 3: Algorismes en IA

Mòdul 4: Aprenentatge Automàtic (Machine Learning)

Mòdul 5: Xarxes Neuronals i Deep Learning

Mòdul 6: Lògica i Sistemes Experts

Mòdul 7: Eines i Llenguatges de Programació en IA

Mòdul 8: Projectes i Casos d'Estudi

Mòdul 9: Exercicis i Pràctiques

Mòdul 10: Recursos Addicionals

© Copyright 2026. Tots els drets reservats