Al final de la lliçó anterior ens va quedar una matriu totalment numèrica però amb escales dispars: columnes one-hot que valen 0 o 1 convivint amb despeses de fins a 15.400 €. Per a molts algorismes, aquesta disparitat és un problema seriós: una columna amb nombres grans domina els càlculs i les altres es tornen invisibles, no perquè importin menys, sinó per pur accident d'unitats. Normalitzar i estandarditzar significa posar totes les variables en escales comparables. En aquesta lliçó entendràs per què l'escala importa (i per a quins algorismes), dominaràs els tres escaladors fonamentals de scikit-learn —MinMaxScaler, StandardScaler i RobustScaler—, en veuràs l'efecte abans i després, i aprendràs la regla d'or que evita fuites: ajustar l'escalador només amb dades d'entrenament.
Contingut
- Per què l'escala importa
- Quins algorismes ho necessiten i quins no
- Min-Max scaling: portar-ho tot a [0, 1]
- Estandardització z-score:
StandardScaler RobustScaler: escalar amb outliers a casa- Comparació visual abans i després
- Quin escalador fer servir: taula de decisió
- Fit a train, transform a test
Per què l'escala importa
Prenem dos clients de MercaFresh descrits per dues variables: despesa total en euros i antiguitat en anys.
- Client A: 1.200 € de despesa, 1 any d'antiguitat.
- Client B: 1.150 € de despesa, 9 anys d'antiguitat.
Per a un algorisme basat en distàncies (com el K-NN que veurem a 04-05), la "diferència" entre tots dos és:
import numpy as np
A = np.array([1200, 1])
B = np.array([1150, 9])
print(np.sqrt(((A - B) ** 2).sum())) # 50.64La distància surt 50,64... dels quals 50 vénen de la despesa i només 0,64 de l'antiguitat. Els 8 anys de diferència —enormes en termes de fidelitat del client— queden aixafats per 50 € de diferència de despesa, que és xavalla. L'antiguitat es podria eliminar del dataset i el resultat gairebé no canviaria: la unitat de mesura, no la importància real, està decidint quina variable mana.
El mateix problema apareix, amb una altra mecànica, en els algorismes que aprenen per gradient (regressió logística 04-02, SVM 04-04, xarxes neuronals 04-07): quan les variables tenen escales molt diferents, la superfície d'error es torna una vall allargada i estreta, i el procés d'optimització fa ziga-zagues lentament en lloc de baixar recte. Escalar arrodoneix la vall i accelera —i estabilitza— l'entrenament. Els detalls de cada algorisme pertanyen al mòdul 4; aquí n'hi ha prou amb el missatge: distàncies i gradients exigeixen escales comparables.
Quins algorismes ho necessiten i quins no
| Família | Sensible a l'escala? | Per què |
|---|---|---|
| K-NN (04-05), K-means (05-01) | Sí, crític | Calculen distàncies entre files |
| SVM (04-04) | Sí, crític | Distàncies a l'hiperplà + optimització |
| Regressió lineal/logística amb gradient (04-01, 04-02) | Sí | Convergència del gradient; comparabilitat de coeficients |
| Xarxes neuronals (04-07) | Sí | Gradients estables |
| PCA (05-03) | Sí, crític | La variància gran domina les components |
| Arbres de decisió (04-03), Random Forest, Gradient Boosting (07-02/03) | No | Només fan talls tipus "despesa > 500?"; el tall és el mateix en qualsevol escala |
| Naive Bayes (04-06) | En general no | Treballa amb probabilitats per variable |
Aquesta taula t'acompanyarà tot el curs: abans d'entrenar qualsevol model, pregunta't a quina fila cau. I un recordatori de la lliçó 03-02: el KNNImputer també mesura distàncies, així que també agraeix dades escalades.
Min-Max scaling: portar-ho tot a [0, 1]
La normalització Min-Max transforma cada variable perquè el seu mínim sigui 0 i el seu màxim 1:
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
clients = pd.DataFrame({
"despesa_total": [1250.5, 890.0, 2100.75, 310.2, 15400.0, 670.4, 980.1, 45.9],
"antiguitat_anys": [3, 1, 6, 2, 9, 4, 1, 5],
"num_comandes": [18, 12, 25, 4, 210, 9, 14, 6],
})
mms = MinMaxScaler()
escalat = pd.DataFrame(mms.fit_transform(clients), columns=clients.columns)
print(escalat.round(3))
print(mms.data_min_, mms.data_max_) # allo apres al fitInterpretació directa: 0 és "el client que menys", 1 "el que més", 0,5 "a mig camí". Avantatges i inconvenients:
- A favor: rang acotat i predictible ([0, 1]), interpretació intuïtiva, encaixa on s'exigeix un rang fix (per exemple, algunes xarxes neuronals o imatges amb píxels 0-255).
- En contra: extremadament sensible als outliers. Mira la columna
despesa_total: el client de 15.400 € s'endú l'1, i tots els altres queden comprimits entre 0,00 i 0,13. L'outlier ha segrestat l'escala i la resta de clients són gairebé indistingibles entre si. - A més, una dada futura més gran que el màxim d'entrenament sortirà > 1: el rang [0, 1] només està garantit per a les dades amb què es va fer
fit.
Estandardització z-score: StandardScaler
L'estandardització transforma cada variable restant-ne la mitjana i dividint per la seva desviació estàndard:
Et sona? És exactament el z-score de la lliçó 02-02: cada valor passa a expressar-se en "nombre de desviacions estàndard respecte a la mitjana". Un client amb z = +2 en despesa està dues desviacions per sobre de la despesa mitjana; un amb z = 0 és exactament la mitjana. El que allà fèiem servir com a detector de rareses, aquí es converteix en un idioma comú per a totes les variables: després d'estandarditzar, totes tenen mitjana 0 i desviació 1, i "estar 2 desviacions per sobre" significa el mateix en euros que en anys.
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
estandarditzat = pd.DataFrame(ss.fit_transform(clients), columns=clients.columns)
print(estandarditzat.round(2))
print(estandarditzat.mean().round(2)) # ~0 en totes les columnes
print(estandarditzat.std().round(2)) # ~1 en totes les columnes
print(ss.mean_, ss.scale_) # mitjana i desviacio apreses al fitPropietats:
- No acota el rang: els valors típics cauen entre -3 i +3 (la regla 68-95-99.7 de 02-02), però un outlier pot sortir a z = 6 sense problema.
- No canvia la forma de la distribució: si la despesa era asimètrica, la despesa estandarditzada continua sent asimètrica. Escalar no és transformar (això va ser 03-03); són passos complementaris: primer
log1pcorregeix la forma, desprésStandardScalerajusta l'escala. - És l'escalador per defecte a la pràctica: prou robust per a la majoria de casos i el que assumeixen molts algorismes.
- Continua fent servir mitjana i desviació, així que els outliers el pertorben (menys que a Min-Max, però ho fan): amb el client de 15.400 € a dins, la mitjana i la desviació de la despesa estan inflades.
RobustScaler: escalar amb outliers a casa
Quan els outliers són part legítima del dataset —i a MercaFresh ho són: els clients-restaurant existeixen i no els volem eliminar—, convé escalar amb estadístics que els ignorin. RobustScaler fa servir la mediana i l'IQR (el rang interquartílic de 02-01) en lloc de la mitjana i la desviació:
from sklearn.preprocessing import RobustScaler
rs = RobustScaler()
robust = pd.DataFrame(rs.fit_transform(clients), columns=clients.columns)
print(robust.round(2))
print(rs.center_, rs.scale_) # mediana i IQR apresos al fitCom que mediana i IQR gairebé no es mouen encara que hi hagi valors extrems (ho vas veure a 02-01: són estadístics robustos), el gruix dels clients queda ben repartit i l'outlier simplement surt amb un valor gran, sense comprimir els altres. És la tria natural quan la neteja de 03-01 va concloure "aquests outliers són reals i es queden".
Comparació visual abans i després
Res no convenç més que veure-ho. Comparem els tres escaladors sobre la despesa, amb el seu outlier inclòs:
import matplotlib.pyplot as plt
despesa = clients[["despesa_total"]]
versions = {
"Original (EUR)": despesa.values.ravel(),
"MinMaxScaler": MinMaxScaler().fit_transform(despesa).ravel(),
"StandardScaler": StandardScaler().fit_transform(despesa).ravel(),
"RobustScaler": RobustScaler().fit_transform(despesa).ravel(),
}
fig, axes = plt.subplots(1, 4, figsize=(14, 3))
for ax, (titol, valors) in zip(axes, versions.items()):
ax.boxplot(valors, vert=True)
ax.set_title(titol, fontsize=10)
plt.tight_layout()
plt.show()El que mostren els quatre boxplots (els de 02-01, treballant de nou):
- Original: caixa diminuta a baix, outlier llunyíssim a dalt. Escala en milers.
- MinMaxScaler: la mateixa silueta exacta, però comprimida en [0, 1]: la caixa sencera ocupa un 13 % del rang. L'outlier mana.
- StandardScaler: la mateixa silueta, centrada en 0; l'outlier surt a z ≈ 2,6 i la resta queda apinyada en una franja estreta per sota de la mitjana.
- RobustScaler: la caixa central queda ben desplegada al voltant de 0 (de -0,5 a 0,5 aproximadament, perquè l'IQR passa a valer 1) i l'outlier se'n va lluny sense molestar ningú.
Conclusió visual: escalar mai no canvia la forma de la distribució, només la seva regla de mesurar; el que distingeix els escaladors és quin tram de les dades fan servir com a referència per a aquesta regla.
Quin escalador fer servir: taula de decisió
| Situació | Escalador recomanat | Motiu |
|---|---|---|
| Cas general, sense outliers greus | StandardScaler |
Estàndard de facto; l'assumeixen molts algorismes |
| Outliers legítims presents | RobustScaler |
Mediana i IQR no es deixen arrossegar |
| Es necessita rang acotat [0, 1] | MinMaxScaler |
L'únic que ho garanteix (a train) |
| Variable ja binària (one-hot) | Cap | Ja és a {0, 1}; escalar-la només resta interpretabilitat |
| Model d'arbres (04-03, 07-02/03) | Cap de necessari | Els talls són invariants a l'escala |
| Distribució molt asimètrica | Transformar primer (03-03), escalar després | Escalar no corregeix la forma |
I la connexió amb el flux que anem muntant: l'escalador és un pas més del Pipeline de la branca numèrica del ColumnTransformer — imputació → transformació → escalat, en aquest ordre, mentre les columnes one-hot passen de llarg.
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
branca_numerica = Pipeline(steps=[
("imputar", SimpleImputer(strategy="median")),
("escalar", RobustScaler()),
])Fit a train, transform a test
La regla que tanca la lliçó és la mateixa que va aparèixer a 03-02 amb la imputació, ara aplicada a l'escalat — i amb els escaladors és on més es peca. Tot escalador aprèn paràmetres a fit: mínims i màxims, mitjanes i desviacions, medianes i IQRs. Si aquests paràmetres es calculen amb el dataset complet abans de dividir en entrenament i prova (la divisió que formalitzarem a 06-01), les dades de prova hauran deixat la seva empremta en la mitjana o el màxim: fuita d'informació. L'avaluació sortirà una mica millor del que mereix, i cap error ni avís no t'alertarà.
El protocol correcte:
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# 1) Dividir PRIMER (els detalls, a 06-01)
X_train, X_test = train_test_split(clients, test_size=0.25, random_state=42)
# 2) Ajustar l'escalador NOMES amb entrenament
scaler = StandardScaler()
scaler.fit(X_train)
# 3) Transformar tots dos conjunts amb ALLO APRES A TRAIN
X_train_esc = scaler.transform(X_train)
X_test_esc = scaler.transform(X_test)
# Drecera equivalent per a train: scaler.fit_transform(X_train)Conseqüència que sorprèn la primera vegada: el conjunt de prova escalat no tindrà mitjana exactament 0 ni desviació exactament 1 — s'ha mesurat amb la regla de train, no amb la seva pròpia. I així ha de ser: en producció, cada client nou arribarà d'un en un i s'escalarà amb els paràmetres congelats de l'entrenament. fit_transform a train, transform a seques a test i en producció: grava-t'ho com a reflex. Si a més tot viu dins d'un Pipeline, el reflex ve de sèrie: el pipeline només fa fit quan tu li ho demanes sobre train.
Errors Comuns i Consells
- Fer
fit_transformsobre el conjunt de prova. És l'error de fuita més comú en principiants: a test (i en producció) noméstransform. Si el teu test escalat té mitjana 0 perfecta, sospita. - Escalar amb
MinMaxScalerun dataset amb outliers. La resta de valors queda comprimida en un racó del [0, 1]. Amb outliers legítims,RobustScaler. - Esperar que escalar arregli l'asimetria. Escalar és una operació lineal: la forma de l'histograma no canvia. Per a la forma, les transformacions de 03-03.
- Escalar les columnes one-hot. Un 0/1 estandarditzat es converteix en valors com -0,58/1,72: es perd la llegibilitat sense guanyar res de rellevant. Deixa les binàries fora de la branca d'escalat al
ColumnTransformer. - Escalar la variable objectiu d'un problema de classificació. El churn (0/1) és l'etiqueta, no una característica: no s'escala.
- Oblidar guardar l'escalador. Els paràmetres apresos (
mean_,scale_...) són part del model: en producció necessitaràs el mateix objecte per escalar cada client nou (ho reprendrem al mòdul 8). - Consell: imprimeix el
describe()del resultat escalat. Mitjanes ~0 i desviacions ~1 (o rangs [0, 1]) a train confirmen que tot ha funcionat; valors absurds delaten columnes que no s'havien d'escalar.
Exercicis
Exercici 1
Sense executar codi: els temps de lliurament de MercaFresh són [38, 42, 44, 47, 52, 55, 61, 190] minuts (el 190 va ser una comanda amb la furgoneta avariada). Quin escalador triaries i per què? Què li passaria a la resta de valors amb MinMaxScaler?
Exercici 2
Estandarditza a mà (sense scikit-learn, només NumPy) la variable antiguitat = [3, 1, 6, 2, 9, 4, 1, 5] i comprova que el resultat té mitjana 0 i desviació 1. Després verifica que StandardScaler dona el mateix resultat.
Exercici 3
Aquest codi té un error de fuita d'informació. Troba'l i corregeix-lo:
scaler = StandardScaler()
dades_esc = scaler.fit_transform(dades)
X_train, X_test = train_test_split(dades_esc, test_size=0.3)Solucions
Exercici 1
RobustScaler. El 190 és un outlier amb causa coneguda (avaria) però present a les dades; mitjana i desviació (StandardScaler) quedarien inflades per ell. Amb MinMaxScaler seria pitjor: 190 s'enduria l'1 i els set valors normals quedarien comprimits aproximadament entre 0,00 i 0,15, gairebé indistingibles entre si. RobustScaler, en fer servir la mediana (49,5) i l'IQR, desplega bé els valors normals i deixa que el 190 surti lluny sense distorsionar els altres. (Alternativa legítima de 03-01: tractar el 190 com a error puntual i corregir-lo abans d'escalar.)
Exercici 2
import numpy as np
from sklearn.preprocessing import StandardScaler
x = np.array([3, 1, 6, 2, 9, 4, 1, 5], dtype=float)
z = (x - x.mean()) / x.std() # std() de NumPy divideix per n, com StandardScaler
print(z.round(3))
print(z.mean().round(10), z.std().round(10)) # 0.0 i 1.0
ss = StandardScaler()
z_sk = ss.fit_transform(x.reshape(-1, 1)).ravel()
print(np.allclose(z, z_sk)) # TrueDetall fi: StandardScaler fa servir la desviació poblacional (dividir per n), igual que np.std() per defecte; la mostral de pandas (Series.std(), que divideix per n-1) donaria valors lleugerament diferents.
Exercici 3
El fit_transform s'executa sobre totes les dades abans de dividir: la mitjana i la desviació apreses contenen informació de les files que després seran de test. Correcció:
X_train, X_test = train_test_split(dades, test_size=0.3, random_state=42)
scaler = StandardScaler()
X_train_esc = scaler.fit_transform(X_train) # apren NOMES de train
X_test_esc = scaler.transform(X_test) # aplica allo apresPrimer dividir, després ajustar només amb train, i transformar test amb els paràmetres de train.
Conclusió
Ja domines l'última peça purament mecànica del preprocessament: per què els algorismes de distàncies i gradients necessiten escales comparables, com MinMaxScaler acota a [0, 1] (però es rendeix davant dels outliers), com StandardScaler converteix cada variable als z-scores que vas conèixer a 02-02, com RobustScaler escala amb mediana i IQR quan els outliers són legítims, i la regla innegociable d'ajustar sempre l'escalador només amb dades d'entrenament. El dataset de churn de MercaFresh està net, complet, transformat, codificat i escalat.
Està, doncs, acabat? Tècnicament sí; competitivament no. Fins ara hem reparat i adaptat les columnes que ja existien. El salt de qualitat ve de crear columnes noves que condensin coneixement de negoci: quant fa que el client no compra, quant val, si la seva activitat creix o s'apaga. Aquesta és l'enginyeria de característiques, la lliçó que tanca el mòdul — i on el dataset de MercaFresh assolirà la seva forma definitiva.
Curs de Machine Learning
Mòdul 1: Introducció al Machine Learning
- Què és el Machine Learning?
- Història i evolució del Machine Learning
- Tipus de Machine Learning
- Aplicacions del Machine Learning
- El flux de treball d'un projecte de Machine Learning
Mòdul 2: Fonaments d'Estadística i Probabilitat
- Conceptes bàsics d'estadística
- Distribucions de probabilitat
- Correlació i covariància
- Inferència estadística
- Teorema de Bayes
Mòdul 3: Preprocessament de Dades
- Neteja de dades
- Gestió de dades mancants
- Transformació de dades
- Codificació de variables categòriques
- Normalització i estandardització
- Enginyeria de característiques
Mòdul 4: Algorismes de Machine Learning Supervisat
- Regressió lineal
- Regressió logística
- Arbres de decisió
- Màquines de suport vectorial (SVM)
- K veïns més propers (K-NN)
- Naive Bayes
- Xarxes neuronals
Mòdul 5: Algorismes de Machine Learning No Supervisat
- Clustering: K-means
- Clustering jeràrquic
- Anàlisi de components principals (PCA)
- Anàlisi d'agrupament DBSCAN
- Visualització de dades amb t-SNE i UMAP
Mòdul 6: Avaluació i Validació de Models
- Divisió de dades: entrenament, validació i prova
- Mètriques d'avaluació
- Validació creuada
- Corba ROC i AUC
- Overfitting i underfitting
Mòdul 7: Tècniques Avançades i Optimització
- Regularització: Ridge, Lasso i Elastic Net
- Ensemble Learning
- Gradient Boosting
- Xarxes neuronals profundes (Deep Learning)
- Optimització d'hiperparàmetres
Mòdul 8: Implementació i Desplegament de Models
- Frameworks i biblioteques populars
- Implementació de models en producció
- Manteniment i monitoratge de models
- Consideracions ètiques i de privadesa
Mòdul 9: Projectes Pràctics
- Projecte 1: Predicció de preus d'habitatges
- Projecte 2: Classificació d'imatges
- Projecte 3: Anàlisi de sentiments a les xarxes socials
- Projecte 4: Detecció de fraus
- Projecte 5: Segmentació de clients
