Machine learning desde cero · Lección 4.3 · 22 de 33 lecciones listas · 12 min
Categorías y escalado
En pocas palabras
kNN con vinos acierta 0,695 sin escalado y 0,963 tras estandarizar. Las islas escritas como 0, 1, 2 fingen un orden: one-hot sube el R² de 0,469 a 0,546.
Calentamiento Dos preguntas de lecciones anteriores. Un minuto, y no bloquea la lección.
En la lección 1.4 kNN acertaba el 76,7% de los pingüinos sin escalado y el 96,1% tras estandarizar, porque la masa en gramos ahogaba las demás medidas. Hoy, dos cosas que estropean un modelo antes de que empiece a aprender. La primera son variables en escalas muy distintas; el escalado las lleva a un rango comparable. La segunda son las categorías, nombres en lugar de números, como la isla de la que viene un pingüino. La codificación de categorías las convierte en números, y se puede hacer mal.
Elige una respuesta y tu grado de seguridad.
Responde para desbloquear el experimento.
Experimento interactivo
Este experimento interactivo funciona en un navegador con JavaScript activado. El texto que lo rodea describe lo que muestra.
Ver la respuesta
Respuesta correcta: Mucho más, porque tras estandarizar cuentan en la distancia las 13 medidas por igual
Tras estandarizar 0,963 en vez de 0,695. Sin escalado, la distancia entre vinos es casi solo la diferencia de prolina. Tras estandarizar cada medida tiene desviación típica 1 y vota en igualdad con las demás. Min-max da 0,958; el escalado robusto, 0,946.
Prueba
- Empieza por los vinos sin escalado. ¿Qué medida ocupa casi toda la altura del gráfico?
- Cambia a min-max, estandarización y escalado robusto. ¿Cuánto gana kNN y cuánto se diferencian los tres?
- Ve a los pingüinos. Compara los códigos 0, 1, 2 en orden alfabético y en el orden del archivo. ¿Debería un modelo depender del orden en que escribes las islas?
- Elige one-hot. ¿Dónde quedan ahora las líneas de predicción respecto a los puntos?
Cuando lo hayas comprobado en el lab, pongamos nombre a lo que ves.
La idea en palabras sencillas
kNN considera parecidos dos vinos cuando están cerca en las 13 medidas a la vez. La prolina tiene una desviación típica de 314 y la medida menos variable solo 0,124, así que en la suma de diferencias la prolina tapa todo lo demás. El escalado iguala esos rangos. Min-max lleva cada medida al intervalo de 0 a 1, la estandarización a media 0 y desviación típica 1, el escalado robusto resta la mediana y divide por el rango intercuartílico, así que los valores extremos sueltos lo mueven menos. Con los vinos kNN acierta 0,958, 0,963 y 0,946 en vez de 0,695. Elegir el método importa menos que escalar.
Las categorías tienen otro problema. Una isla es Biscoe, Dream o Torgersen, y una regresión lineal necesita números. La idea más simple son los códigos 0, 1, 2. Pero un número lleva un orden y una distancia: un modelo con un solo peso supondrá que la isla con código 1 está justo a medio camino entre las otras dos. En el lab una regresión predice la masa de un pingüino a partir de su isla y su sexo. Con códigos alfabéticos el R² en validación cruzada es 0,469. Los mismos códigos en el orden del archivo dan 0,193, aunque los datos sean idénticos. One-hot da a cada isla su propia columna 0/1 y su propio peso: R² 0,546, y el error medio baja de 476 a 411 g.
Jak to policzono
- Vinos: Wine (Aeberhard y Forina, 1992; UCI, licencia CC BY 4.0), 178 vinos, 13 medidas químicas, 3 variedades. kNN con k = 5, validación cruzada en 5 partes, repetida 10 veces con otra mezcla. El escalador se ajusta solo con la parte de entrenamiento de cada división.
- Pingüinos: Palmer Penguins (Gorman, Williams y Fraser, 2014; paquete palmerpenguins: Horst, Hill y Gorman, 2020), licencia CC0. 333 de 344 pingüinos: 11 no tienen el sexo anotado. Regresión lineal: masa a partir de isla y sexo (una columna 0/1), validación cruzada en 5 partes, 10 repeticiones. Líneas del gráfico: el modelo ajustado con los 333.
- R²: la parte de la variación de la masa explicada en las partes de validación (1 = toda, 0 = lo mismo que la media). Error medio: la diferencia absoluta media en gramos.
Por qué funciona
La distancia en kNN es la raíz cuadrada de la suma de las diferencias al cuadrado. Una medida con mucha dispersión da diferencias grandes, y son ellas las que mandan en la suma. Estandarizar divide cada medida por su desviación típica, así que una diferencia típica tiene un tamaño parecido en todas las medidas. Las medias, desviaciones, mínimos y máximos del escalador se calculan con datos, igual que los pesos de un modelo. Por eso el escalador se ajusta solo con la parte de entrenamiento: en el código de abajo, un escalador ajustado con todos los vinos antes de la validación cruzada da 0,965 en vez de 0,962. Aquí la diferencia es pequeña, pero ese resultado ha visto la parte de validación, así que no es una estimación honesta.
Con códigos de isla, la regresión lineal tiene un solo peso para toda la columna. Así que del código 0 al 1 y del 1 al 2 la masa predicha cambia lo mismo. Con códigos alfabéticos el modelo predice que los pingüinos de Dream pesan 625 g menos que los de Biscoe, y los de Torgersen otros 625 g menos. En los datos los pingüinos de Dream y de Torgersen pesan casi lo mismo (3719 y 3709 g), y los de Biscoe 4719 g. En el orden del archivo Biscoe recibe el código del medio, y la recta de las tres islas casi se aplana: 243 g por paso. One-hot no supone ningún orden: el modelo predice para Dream y para Torgersen 997 g menos que para Biscoe, igual que en los datos.
Error frecuente
Verdadero o falso: «codifica las categorías como 1, 2, 3, el modelo se las arreglará». Falso. Un número lleva un orden y distancias iguales que no existen entre las islas. En el lab basta con reordenar las islas para que el R² pase de 0,469 a 0,193, aunque los datos sean los mismos. One-hot da 0,546 con cualquier orden. En el código de abajo one-hot gana a los códigos alfabéticos en 49 de 50 pliegues de validación.
La excepción es el sexo: con dos categorías basta una columna 0/1, porque no hay un medio en el que algo pueda quedar. El problema empieza con tres categorías sin un orden natural.
Test
Tres preguntas para comprobar lo aprendido. Elige una respuesta o despliega la solución bajo cada pregunta. Con el teclado: las flechas eligen, Enter o Espacio confirma.
Ideas clave
- Los modelos que miden distancias, como kNN, necesitan variables en una escala parecida: con los vinos 0,695 sin escalado, 0,963 tras estandarizar.
- Min-max, estandarización y escalado robusto dan aquí resultados parecidos; lo que más importa es escalar.
- Ajusta el escalador solo con la parte de entrenamiento, por separado en cada división.
- Codifica con one-hot las categorías sin orden: los códigos 0, 1, 2 imponen un orden y pasos iguales que los datos no tienen.
Más a fondo: fórmula, código, práctica
Las matemáticas
Min-max: x' = (x − min) / (max − min). Estandarización: z = (x − x̄) / s. Escalado robusto: x' = (x − mediana) / IQR, donde IQR = Q3 − Q1 es el rango intercuartílico. Distancia en kNN: d(a, b) = √(Σⱼ (aⱼ − bⱼ)²); una medida con una s grande da (aⱼ − bⱼ)² grandes y tapa el resto.
Códigos de isla en una regresión lineal: ŷ = a + w · código + v · macho. Con códigos alfabéticos ŷ(Dream) − ŷ(Biscoe) = ŷ(Torgersen) − ŷ(Dream) = w: pasos iguales. One-hot: ŷ = a + w_D · [Dream] + w_T · [Torgersen] + v · macho, donde [Dream] vale 1 para un pingüino de Dream y 0 para los demás. Cada isla tiene su propio desplazamiento, con Biscoe como referencia.
En código (Python)
El mismo experimento en scikit-learn: kNN con los vinos y cuatro escalados en validación cruzada 10 × 5 (el escalador va en un pipeline, así que se ajusta en cada división), un escalador ajustado con todos los vinos para comparar y la masa de los pingüinos con la isla codificada de tres maneras.
import numpy as np
import pandas as pd
from sklearn.datasets import load_wine
from sklearn.model_selection import cross_val_score, RepeatedStratifiedKFold, RepeatedKFold
from sklearn.neighbors import KNeighborsClassifier
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
from sklearn.pipeline import make_pipeline
from sklearn.linear_model import LinearRegression
X, y = load_wine(return_X_y=True)
cv = RepeatedStratifiedKFold(n_splits=5, n_repeats=10, random_state=0)
acc = {}
for name, scaler in [("no scaling", None), ("min-max", MinMaxScaler()), ("standardised", StandardScaler()), ("robust", RobustScaler())]:
model = make_pipeline(scaler, KNeighborsClassifier(5)) if scaler else KNeighborsClassifier(5) # el escalador en un pipeline: se ajusta en cada división
acc[name] = cross_val_score(model, X, y, cv=cv)
print("kNN on 178 wines, 10 x 5 CV: " + ", ".join(f"{k} {v.mean():.3f}" for k, v in acc.items()))
print(f"standardised beats no scaling in {(acc['standardised'] > acc['no scaling']).sum()} of 50 folds")
leaky = cross_val_score(KNeighborsClassifier(5), StandardScaler().fit_transform(X), y, cv=cv) # este escalador también ha visto las partes de validación
print(f"scaler fitted on all wines before the CV: {leaky.mean():.3f}")
df = pd.read_csv("data/raw/penguins.csv").dropna(subset=["sex", "body_mass_g"])
male = (df.sex == "MALE").to_numpy(float)[:, None]
codings = {
"codes alphabetical": df.island.map({"Biscoe": 0, "Dream": 1, "Torgersen": 2}).to_numpy(float)[:, None],
"codes in file order": df.island.map({"Torgersen": 0, "Biscoe": 1, "Dream": 2}).to_numpy(float)[:, None],
"one-hot": pd.get_dummies(df.island).to_numpy(float),
}
cv2 = RepeatedKFold(n_splits=5, n_repeats=10, random_state=0)
r2 = {}
for name, isl in codings.items():
Xe = np.hstack([isl, male])
r2[name] = cross_val_score(LinearRegression(), Xe, df.body_mass_g, cv=cv2, scoring="r2")
mae = -cross_val_score(LinearRegression(), Xe, df.body_mass_g, cv=cv2, scoring="neg_mean_absolute_error").mean()
print(f"{len(df)} penguins, body mass from island + sex, {name}: R2 {r2[name].mean():.3f}, mean error {mae:.0f} g")
print(f"one-hot beats the alphabetical codes in {(r2['one-hot'] > r2['codes alphabetical']).sum()} of 50 folds")
Resultado
kNN on 178 wines, 10 x 5 CV: no scaling 0.706, min-max 0.963, standardised 0.962, robust 0.952
standardised beats no scaling in 50 of 50 folds
scaler fitted on all wines before the CV: 0.965
333 penguins, body mass from island + sex, codes alphabetical: R2 0.464, mean error 477 g
333 penguins, body mass from island + sex, codes in file order: R2 0.179, mean error 618 g
333 penguins, body mass from island + sex, one-hot: R2 0.542, mean error 412 g
one-hot beats the alphabetical codes in 49 of 50 folds
Las divisiones no son las del lab, así que los números también cambian un poco. La imagen es la misma: estandarizar gana a no escalar en los 50 pliegues de validación, y one-hot gana a los códigos alfabéticos en 49 de 50. El orden de los códigos vuelve a decidir el resultado: 0,464 en orden alfabético, 0,179 en el orden del archivo.
Descargar el notebook (.ipynb). Ábrelo en Jupyter o súbelo a Google Colab; descarga por sí solo los datos que falten.
Para profesionales
- En scikit-learn:
StandardScaler,MinMaxScaleryRobustScalerpara números,OneHotEncoder(handle_unknown="ignore")para categorías.ColumnTransformerenvía las columnas numéricas y las categóricas a transformaciones distintas, y todo va dentro demake_pipeline, para que la validación cruzada las ajuste en cada división. - Los árboles de decisión y los bosques aleatorios no necesitan escalado: cortan una medida con un umbral, y el umbral se mueve junto con la escala.
- Un orden que existe de verdad (pequeño, mediano, grande; nivel de estudios) se puede escribir con códigos, pero en un orden dado explícitamente (
OrdinalEncoder(categories=...)). - Las categorías con cientos de valores (código postal, número de producto) dan cientos de columnas con one-hot. Entonces se juntan los valores raros en un grupo «otros» o se usa la codificación por la media del objetivo calculada fuera del pliegue (una entrada del Atlas), porque calculada con todos los datos produce fuga.
¿Te ha sido útil?
¡Gracias! Nos ayuda a mejorar.
Profundiza en el Atlas
Entradas de la enciclopedia sobre las ideas de esta lección: