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
- Regressió lineal: la recta que menys s'equivoca
- Regressió logística: de la suma ponderada a la probabilitat
- k veïns més propers: predir per semblança
- Arbres de decisió: preguntes encadenades
- Boscos aleatoris i la idea d'ensamblatge
- Màquines de vectors de suport: el marge més ample
- Naive Bayes: probabilitat amb independència
- k-means per dins: centroides i mètode del colze
- Taula comparativa i com triar
- Comparació en codi de diversos classificadors
- Errors Comuns i Consells
- Exercicis
- Conclusió
- 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 unitatsExplicació: 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.
- 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.
- 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 iclient_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.
- 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.
- 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).
- 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.872Nomé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.
- 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.
- 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):
- Triar k centroides inicials (punts a l'espai de característiques;
KMeansfa servir una inicialització intel·ligent anomenada k-means++). - Assignar cada client al centroide més proper.
- Recalcular cada centroide com la mitjana dels clients assignats.
- 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:
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.
- 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) | Sí | 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 | Sí | 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) | Sí | Simple, escalable | Cal fixar k; grups esfèrics | Segmentació amb grups compactes |
| Xarxes neuronals | Tots dos | Baixa | Sí | 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.
- 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_depthomin_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
- Conceptes Fonamentals: Agents, Entorns i Racionalitat
- Tipus d'Intel·ligència Artificial
- Les Dades com a Matèria Primera de la IA
- Ètica i Consideracions en IA
Mòdul 3: Algorismes en IA
- Introducció als Algorismes
- Algorismes de Cerca
- Cerca amb Adversari: Jocs i Minimax
- Algorismes d'Optimització
Mòdul 4: Aprenentatge Automàtic (Machine Learning)
- Conceptes Bàsics de Machine Learning
- Tipus d'Aprenentatge Automàtic
- Preparació de Dades i Característiques
- Algorismes de Machine Learning
- Avaluació i Validació de Models
- Sobreajust, Regularització i Ajust d'Hiperparàmetres
Mòdul 5: Xarxes Neuronals i Deep Learning
- Introducció a les Xarxes Neuronals
- Arquitectura de Xarxes Neuronals
- Com Aprèn una Xarxa: Descens del Gradient i Retropropagació
- Deep Learning i les seves Aplicacions
- Transformers, Grans Models de Llenguatge i IA Generativa
Mòdul 6: Lògica i Sistemes Experts
- Lògica en IA
- Sistemes Experts
- Raonament amb Incertesa: Probabilitat i Xarxes Bayesianes
- Aplicacions dels Sistemes Experts
Mòdul 7: Eines i Llenguatges de Programació en IA
- Llenguatges de Programació per a IA
- Python Científic: NumPy, pandas i Matplotlib
- Eines i Llibreries Populars
- Entorns de Desenvolupament
Mòdul 8: Projectes i Casos d'Estudi
Mòdul 9: Exercicis i Pràctiques
- Exercicis d'Algorismes
- Pràctiques de Machine Learning
- Projectes de Xarxes Neuronals
- Projecte Integrador: de la Idea al Prototip
