Caso de estudio · Machine learning

Lo que el modelo aprendió,
y lo que no se puede afirmar

Un pipeline completo sobre California Housing — 20,640 viviendas, nueve modelos, 180 experimentos registrados. El resultado interesante no fue el error más bajo, sino descubrir cuáles de las mejoras eran reales y cuáles solo lo parecían.

Modelo desplegadoXGBoost300 árboles · 2.4 MB
RMSE en test0.429± USD 42,910
0.8634,128 casas
Latencia2.3 mspor predicción
El encargo

Predecir el precio medio de una vivienda por distrito

El dataset trae ocho variables por distrito censal de California — ingreso medio, antigüedad, habitaciones, población, coordenadas — y el precio medio de la vivienda. El objetivo es predecir ese precio. Es el problema con el que arranca el libro de Géron, y su virtud es que todas las trampas del oficio caben en él.

Lo que sigue no es un recorrido por los modelos. Es lo que cambió de opinión durante el proceso: cuatro hallazgos que contradijeron lo que parecía obvio al empezar.

Explorador

Dónde se equivoca el modelo

Cada punto es uno de los 4,128 distritos del conjunto de prueba — casas que el modelo nunca vio al entrenar. El color indica si subestimó o sobreestimó el precio. Filtra y las métricas de abajo se recalculan sobre el subconjunto.

Proximidad al océano
Categoría de ingreso
Colorear por
Predicciones reales del modelo XGBoost sobre el conjunto de prueba, calculadas una sola vez y embebidas en la página. Los filtros y las métricas se computan en tu navegador — no hay servidor detrás.
Hallazgos

Seis cosas que resultaron al revés de lo esperado

La librería estándar entregaba datos recortados

+1 variable · 207 nulos

El proyecto usaba fetch_california_housing de scikit-learn. Al comparar contra el CSV original del libro apareció que la versión de la librería descarta la columna ocean_proximity e imputa en silencio los 207 valores faltantes de total_bedrooms.

Justo las dos cosas que el capítulo enseña a tratar — codificación de categóricas e imputación — eran las que la versión conveniente escondía. Cambiar a la fuente cruda también reveló que trae las filas en otro orden, así que con la misma semilla el conjunto de prueba cae en casas distintas: las métricas anteriores dejaron de ser comparables.

La validación cruzada se estaba mintiendo a sí misma

fuga eliminada

El escalado se aplicaba una vez sobre todo el conjunto de entrenamiento, antes de partirlo en pliegues. Cada pliegue de validación se evaluaba entonces con estadísticas calculadas incluyéndolo a él mismo. El cv_rmse salía optimista.

La corrección fue meter el preprocesamiento dentro del Pipeline, donde scikit-learn lo reajusta en cada pliegue. La señal de que funcionó es sutil pero clara: antes la validación cruzada reportaba un error menor que el del conjunto de prueba; ahora reporta uno ligeramente mayor, que es lo que debe pasar.

Beneficio colateral: el artefacto guardado contiene el preprocesamiento. Quien lo carga le pasa datos crudos y funciona — no hay que replicar los pasos en producción, que es donde esto se rompe en la vida real.

La variable “más importante” no mejoraba nada

+0.004 RMSE en XGBoost

Recuperada ocean_proximity, XGBoost la reportó como su variable número uno por importancia — 0.61 contra 0.14 del ingreso medio. Parecía la mejora grande del proyecto.

Midiendo con una ablación — mismo split, misma semilla, única diferencia la columna — resultó que mejora seis de siete modelos por márgenes mínimos, y que a XGBoost lo empeora.

La explicación: latitud y longitud ya estaban ahí. “Cerca del mar” era deducible de las coordenadas, solo que costaba varios cortes. Al recibirlo servido, el modelo lo usa de inmediato y reordena su ranking de importancia — pero la información no era nueva. Importancia alta no significa mejora predictiva cuando hay redundancia.

Lo que sí funcionó fue darle la geografía masticada

−0.036 RMSE

Un árbol solo puede partir el espacio con cortes rectangulares sobre latitud y longitud, así que aproximar “cerca de San Francisco” le cuesta muchos cortes y nunca queda fino. La solución del libro es un transformador propio: agrupar las coordenadas con K-means y reemplazar cada casa por su similitud a cada centro.

Fue el cambio que más rindió de todo el proyecto — más que cualquier ajuste de hiperparámetros. Y reordenó por completo la tabla: los métodos que promedian árboles independientes, que eran los más penalizados por tener que aproximar la geografía a mano, pasaron a ganarle a los de boosting.

Una variable que se veía excelente estaba haciendo trampa

correlación 0.85 → 0.99

Siguiendo la misma idea, se añadió otra variable geográfica: un k-vecinos sobre las coordenadas que responde “¿cuánto cuestan las casas de al lado?”. Su respuesta entra al modelo como una columna más.

En la primera versión su correlación con el precio era de 0.993. Una variable casi perfecta — y ahí estaba el problema: era demasiado buena.

El k-vecinos se había entrenado con las mismas casas sobre las que después predecía, así que cada casa aparecía entre sus propios vecinos. Estaba copiando la respuesta. Calculando la predicción de cada fila con un modelo que nunca la vio, la correlación cae a 0.845 — ese es su valor real.

Aun corregida, sigue siendo la segunda variable más útil del proyecto. Pero sin ese ajuste habría lucido espléndida durante el entrenamiento y se habría derrumbado en producción.

La explicación intuitiva del modelo lento era falsa

n1.33, no n²

Un modelo del conjunto —SVR— tardaba más de veinte minutos y se excluyó de la búsqueda. La razón anotada fue la que todo el mundo repite: “escala con el cuadrado del número de filas”. Suena correcta y está en los libros.

Al medirla resultó falsa. El exponente real es n1.33 y un ajuste sobre el conjunto completo toma unos seis segundos. El tamaño de los datos nunca fue el problema.

El costo lo dominaba un solo hiperparámetro. Con el kernel polinómico y el parámetro de regularización en su valor más alto, el ajuste es 135 veces más lento que con la configuración barata. El espacio de búsqueda incluía justo esa esquina.

La decisión de excluirlo se sostiene —incluso ajustado se queda en 0.531 de error frente a 0.429— pero durante un tiempo el proyecto documentó una explicación inventada. La diferencia entre creer algo razonable y haberlo medido.

Cuánto aportó cada decisión
Cambio en RMSE al añadir cada elemento, medido con ablación sobre el mismo split. Hacia la izquierda es mejor. La selección de variables aparece del lado malo: descarta la mitad de las columnas y cuesta precisión. La partición estratificada no aparece porque no mueve el error — hace que el número sea confiable, que es otra cosa.
El hallazgo incómodo

Tres modelos empatados, aunque la tabla diga otra cosa

Con todo afinado, extra_trees quedó primero con 0.4063 de RMSE y XGBoost tercero con 0.4291. Ordenar por esa columna y declarar un ganador es el reflejo natural. Es también un error.

El RMSE sale de una muestra concreta de 4,128 casas. Con otras 4,128 habría dado distinto. El intervalo de confianza acota cuánto puede moverse — y cuando dos intervalos se traslapan, la diferencia cabe dentro del ruido del muestreo.

RMSE con intervalo de confianza al 95%
Calculado con la distribución t sobre los errores cuadrados del conjunto de prueba. Los tres primeros se solapan entre sí: no hay evidencia para ordenarlos.
La decisión

Entonces gana el que se puede desplegar

Si la precisión no distingue a los tres primeros, el desempate lo pone lo que sí se puede medir sin ambigüedad. Y ahí la diferencia no es sutil.

Costo de servir cada modelo
Medido cargando cada artefacto y cronometrando 20 predicciones individuales. extra_trees crece 300 árboles sin límite de profundidad; XGBoost los poda a 7 niveles.

extra_trees pide 1.3 GB de memoria — no entra en ningún plan gratuito ni en un servidor pequeño — y tarda trece veces más por petición. XGBoost ocupa 2.4 MB, cabe en cualquier lado y responde en 2.3 milisegundos.

Renunciar a 0.023 de RMSE que no se puede demostrar, a cambio de un modelo 293 veces más pequeño, no es una concesión. Es la decisión correcta, y es la que se toma en producción todos los días.

Resultados

Tabla completa

Nueve modelos, todos con búsqueda aleatoria de hiperparámetros sobre el modelo y el preprocesamiento. Cada combinación probada quedó registrada en MLflow como ejecución anidada: 137 candidatos en total.