Prediciendo precios de casas de Ames: flujo de trabajo secuencial
El propósito de este cuaderno es realizar un análisis de aprendizaje supervisado en el que se modela para predecir un valor continuo -utilizando el conjunto de datos de Ames Housing Prices- mediante la regresión del precio de venta de una casa a diferentes medidas y calificaciones. Luego, se aprovechan los precios históricos que actúan como variables de respuesta para supervisar el proceso de aprendizaje de este modelo.
Para realizar esta tarea de regresión se han desarrollado pipelines -objetos transformadores y estimadores encadenados que conforman un flujo de trabajo- y un conjunto de pipelines. A cada pipeline se le da la misma entrada, sin embargo, los estimadores obtienen diferentes conjuntos de datos según los objetos de preprocesamiento (transformadores en Scikit-learn) que asignamos a cada pipeline.
La idea es evaluar no solo el objeto regresor, sino todas las pipelines mediante validación cruzada para obtener parámetros optimizados para los regresores. Por lo tanto, todo el preprocesamiento a través de objetos -escalado y / o valores imputados con promedios- se realizará para cada lote de datos durante el proceso de evaluación para evitar el uso de información de todo el conjunto en cada etapa de validación, que sería -en cierta medida- sesgar los resultados.
En este artículo
Configuración
Preprocesamiento de datos
En esta sección se explica cómo se realiza el preprocesamiento de los datos antes de pasarlos a las pipelines. Todas las clases que utilizo en las pipelines para preprocesar los datos se pueden encontrar aquí.
Datos faltantes
Como parte del flujo de trabajo, la imputación de los valores faltantes dentro de la cadena en cada pipeline la dará un objeto Imputer
. Sin embargo, la mayoría de los valores faltantes son deducibles simplemente considerando otros valores en la misma observación:
- En el caso de las variables cualitativas, considere el siguiente ejemplo, si una observación mostrase que no hay piscina, es decir,
PoolArea
fuese cero, entonces asignaríamosNA
a la observación pertinente enPoolQC
, y es que la descripción de los datos indica que este es el valor de la variable de calidad de la piscina cuando en una casa no hay piscina. Y si de hecho hubiese una piscina, introduciríamosNone
para no sesgar el resultado. También, en relación con las pipelines que utilizan conjuntos con variables dummy -o indicadoras-, cuando los indicadores se crean, esta observación no será representada por ninguna de las clasesPoolQC
, y en relación con las pipelines que utilizan conjuntos categóricos,None
no formaría parte de las categorías. - En el caso de variables cuantitativas, y siguiendo un ejemplo similar, si faltase un valor en
GarageArea
para una observación determinada yGarageQual
fueseNA
en la misma fila, entonces se imputaría0
enGarageArea
.
El resto de los valores faltantes serán asignados por un objeto Imputer
, el cual toma como uno de los argumentos un número para completar los valores faltantes, o el tipo de promedio (media o mediana). Este promedio también se puede calcular después de agrupar en base a otro predictor, pasando el nombre de la variable al argumento groupby.
Outliers
El conjunto de entrenamiento contiene algunas observaciones con valores poco comunes en algunos de sus espacios de predicción y también en su respuesta. Los efectos de estas observaciones podrían invalidar el ajuste si las introdujéramos en el modelo de aprendizaje.
Por otro lado, eliminarlos podría evitar que el estimador aprenda algunas peculiaridades de los datos y, al final, generalice mejor.
Opté por no eliminar ninguna observación del conjunto, por transformar algunas variables, así como por crear otras para intentar paliar el impacto de estos valores atípicos sobre el ajuste resultante.
En la figura siguiente vemos cuatro de los predictores con algunos puntos que, en diferente medida, no siguen la tendencia general de los datos, o tienen valores que no son comunes en el espacio del predictor:
Podemos ver gráficamente el impacto de algunos puntos en el ajuste de regresión y cómo cambia cada ajuste simplemente eliminando una observación o algunas de ellas.
Entonces, tomemos dos de estas variables que creemos que contienen valores atípicos y transformémoslas. En el caso de GrLivArea
y LotArea
, podemos ver el resultado de transformar tanto el predictor como la respuesta a continuación:
La presencia de las mismas observaciones que estaban alterando la regresión antes ahora prácticamente no afecta el ajuste resultante. Los puntos que estaban fuera de la tendencia general de los datos ahora lo siguen y los puntos con valores en el espacio del predictor que eran demasiado grandes en comparación con el resto de las observaciones ahora tienen valores similares gracias al cambio de escala después de usar una transformación Box Cox y logarítmica.
Creación de variables
Hay algunos otros aspectos que pueden influir en el precio de una casa, he creado nuevas variables a partir de las variables que ya tenemos para intentar capturar esa información, y también he realizado algunas agregaciones combinando algunas variables:
_Baths/Rooms
(cuantitativa): proporción de baños y habitaciones._Baths/SF
(cuantitativa): número de baños por pie cuadrado._BsmtUnfin_ratio
(cuantitativa): proporción de pies cuadrados de sótano sin terminar. Cero cuando no hay sótano._Condition
(cuantitativa): agregación tanto deCondition1
como deCondition2
para crear variables indicadoras para cada condición. Estas son casi la mitad de las variables que resultarían de obtener variables dummy sin agregar (17 frente a 9), que es aún menor que si creásemos variables dummy a partir de la variable agregada (16 frente a 9). También usamos esta variable para crear los siguientes predictores, luego la eliminamos del conjunto de datos:_Connected
(cuantitativa): casas adyacentes a una arteria o una calle principal._Railed
(cuantitativa): casas dentro de 200 pies o adyacentes a una vía férrea.
_Exterior
(cuantitativa): lo mismo que_Condition
pero conExterior1
yExterior2
.Un problema es que cuando se entrena una pipeline con un regresor de bosque aleatorio, las variables Condición y Exterior están dispersas en el conjunto de datos. Se podrían incluir estas agregaciones en la clase para crear dummies y así la pipeline con el bosque no reciba estas dummies. Sin embargo, esto es más cálculo durante la optimización de los parámetros en las otras pipelines.
_Date_Sold
(cuantitativa): es el resultado de combinarYrSold
conMoSold
. Existe un patrón claro en la venta de viviendas a lo largo de los meses durante 5 años. Hay más ventas durante el verano que en cualquier otra estación, y la cantidad de ventas y el precio de las casas vendidas se mantienen similares a lo largo del tiempo. Podemos ver un diagrama de cajas frente aSalePrice
a continuación
_Living_Area
(cuantitativa): número total de pies cuadrados en la casa._LowQualFin_ratio
(quantitative): proporción de pies cuadrados de sótano con acabado de baja calidad en todas las plantas._Rooms/SF
(cuantitativa): número de habitaciones por pie cuadrado._Time2sale
(cuantitativa): años transcurridos entre la fecha de construcción y la fecha de venta._Time2remod
(cuantitativa): años transcurridos entre la fecha de construcción y la fecha de remodelación._Time_remod2sale
(cuantitativa): años transcurridos entre la fecha de remodelación y la fecha de venta._TopZone
(cualitativa): indica las casas ubicadas en FV (Floating Village Residential), RL (Residential Low Density) y RP (Residential Low Density Park). Aunque no hay observaciones en el conjunto de datos con casas en RP, incluimos esta zona en el set de zonas más caras, elaborada a partir del siguiente grupo simple:
_Total_Baths
(cuantitativa): número total de baños, incluidos los medios baños sobre el nivel del suelo, y los baños y medios baños en el sótano._Unfinished
(cuantitativa): casas con cualquiera de sus predictores en el conjunto de datos que describen una parte o área sin terminar.
En algunas pipelines agrego un objeto para crear variables con promedios como parte del flujo de trabajo. Si crease estas variables de antemano y, por lo tanto, las agregase al conjunto con el que alimentamos la pipeline, estaría entrenando y validando modelos con información de todo el conjunto durante el proceso de validación cruzada.
Luego, mi objetivo es prevenir esta captura creando variables con promedios del subconjunto de entrada al modelo que, en el proceso de validación cruzada para optimizar los parámetros, será el set de entrenamiento.
Transformación de variables
He usado la familia de transformaciones Box Cox en scipy que, si no obtiene una lambda fija, encuentra la lambda que maximiza la función de probabilidad logarítmica:
Incluyo otro objeto en la pipeline para transformar algunas variables, pero con una lista de lambdas que ya hemos calculado anteriormente usando el conjunto completo.
Codificación de variables cualitativas
Para poder codificar correctamente los datos, sin importar cuáles sean las observaciones en cada conjunto de validación, se necesitará un conjunto de categorías:
Justo antes de cambiar la escala de las variables con un objeto StandardScaler
de sklearn, incluyo o un objeto Get_Dummies
o un Get_Categorical
para codificar las variables en el conjunto pero con todas las categorías del conjunto completo, calculadas anteriormente.
La razón de pasar esta lista de categorías es evitar que los estimadores reciban diferentes conjuntos de predictores codificados en algunas particiones (folds) durante el proceso de validación cruzada: se espera que en conjuntos pequeños, al realizar la validación cruzada, los sets de datos después de una división no incluyan todo el conjunto de categorías en la variable cualitativa respectiva.
Modelado
He escrito tres clases que se utilizan en esta sección:
PipeTunerCV
, que se utiliza para optimizar los parámetros pasados de los estimadores en las pipelines, prevaleciendo los valores de estos parámetros con RMSE de validación cruzada más bajos sobre los demás. Además, incluyo algunos métodos para dibujar los gráficos de los coeficientes de las pipelines con los regresores Ridge, Lasso y ElasticNet, y la importancia de las variables del regresor de bosque aleatorio.BoostingTunerCV
, que se utiliza para optimizar los parámetros preestablecidos de los estimadores fuera de las pipelines y para trazar la importancia de las características de los predictores y la curva de aprendizaje del modelo.WeightedAverage
: se utiliza para seleccionar la combinación de pipelines que produce el mejor rendimiento.
Para evaluar cómo de bien generalizan los estimadores, utilizo la raíz cuadrada del error cuadrático medio evaluado por validación cruzada usando 5 divisiones o folds:
El conjunto de datos consta de muchas dimensiones en comparación con el número total de observaciones, por lo tanto, modelos capaces de reducir los coeficientes de cada predictor -y bueno con datos dispersos- puede ser un buen punto de partida.
Mientras que la regresión de Ridge con una norma ℓ2 encoge los coeficientes hacia cero y reduce la importancia de variables altamente correlacionadas, Lasso y ElasticNet -con una norma ℓ1 y un compromiso entre las normas ℓ1 y ℓ2 respectivamente- también pueden regularizar ya que cuando el parámetro de control de la penalización ℓ1 se vuelve lo suficientemente grande, obliga a algunos de los coeficientes a ser iguales a cero, descartando posteriormente su predictor asociado de la elaboración de una futura predicción.
Ridge
Lasso
Elastic Net
También se han utilizado algoritmos con kernel trick. Un núcleo polinomial en el caso de Kernel Ridge y un núcleo RBF en caso del regresor de vectores de soporte. Los parámetros de estos estimadores se optimizan mediante una grid search con validación cruzada sobre diferentes grids de parámetros definidos a continuación:
Kernel Ridge
Regresor de soporte vectorial
Regresor de bosque aleatorio
También he creado una pipeline que contiene un regresor de bosque aleatorio. Aumentar el número de árboles mejorará ligeramente el rendimiento, aunque seguirá siendo peor que el rendimiento de los estimadores anteriores para este conjunto de datos.
Asigno 25 a n_iter
para randomizar la búsqueda dado que de esta manera se limitará la longitud de cada grid de parámetros, si el valor de n_iter
es inferior a la longitud del array de combinaciones, y se muestrea desde esta lista aleatoriamente sin reemplazo:
Regresores de potenciación del gradiente
eXtreme Gradient Boosting y Light Gradient Boosting Machine son los regresores de potenciación o boosting que van a ser explorados. El primero utiliza el algoritmo de crecimiento de árboles en profundidad, mientras que el segundo, el algoritmo de crecimiento de árboles en forma de hojas.
Dado que el número de observaciones en nuestro conjunto de entrenamiento no es grande y estos conjuntos tienden a sobreajustarse en conjuntos de datos pequeños, la optimización de los parámetros es una tarea a realizar necesaria, ya que de lo contrario nuestro estimador probablemente seguiría demasiado de cerca los datos de entrenamiento y resultaría en malas predicciones. En este sentido, se ha escrito un enfoque para optimizar -o sintonizar- los parámetros en ambos regresores: BoostingTunerCV
.
La idea principal es automatizar el proceso de búsqueda de cuadrícula, o grid, con validación cruzada de todos los parámetros. Estos parámetros se incluyen luego en diferentes grids: en primer lugar, los parámetros principales para controlar la complejidad del modelo, a posteriori, parámetros para agregar características de submuestreo de aleatoriedad y observaciones -para hacer el entrenamiento robusto al ruido- y, finalmente, parámetros de regularización.
He fijado la tasa de aprendizaje a 0,025 en el regresor XGB y 0,03 en el ensemble LGBM y he utilizado las funciones de validación cruzada nativas xgb.cv
y lgb.cv
para evaluar los modelos correspondientes que permitan rondas early_stopping_round
.
De esta manera, el entrenamiento se detiene en cualquiera de los subconjuntos si la raíz cuadrada del error cuadrático medio (métrica seleccionada) de los datos de validación no mejora en las rondas dadas. El número de la última ronda será el número total de estimadores que se necesitan para realizar una tarea óptima con los parámetros dados.
eXtreme Gradient Boosting
Como se señaló anteriormente y como podemos ver a continuación, el modelo tiende a sobreajustar los datos de entrenamiento usando los parámetros estándar incluso deteniendo el entrenamiento si no hay mejora después de 100 ciclos completos (o iteraciones sobre el conjunto de datos). El rendimiento del conjunto mejora notáblemente a medida que el número de estimadores aumenta a 300. En cambio, cerca de 800 el error de entrenamiento sigue disminuyendo sustancialmente en comparación con el rmse de prueba, es decir, el modelo está corrigiendo errores en cada árbol posterior que no ayudará cuando se trata de predecir sobre nuevos datos.
Por lo tanto, el entrenamiento se puede detener antes estableciendo un valor más bajo de early_stopping_rounds
para evitar el sobreajuste, es decir, para matener una buena generalización, como podemos ver en el gráfico de la derecha:
Cuando se configura early_stopping_rounds = 5
todavía hay una precisión de prueba similar, pero el modelo no sigue tan de cerca los datos de entrenamiento. Aunque todavía hay sobreajuste, no es tan severo:
Dado que los parámetros aún no se han optimizado para el conjunto de datos, esto fue solo una aproximación con el fin de introducir algunas propiedades inherentes de estos conjuntos y tener una idea de la curva de aprendizaje de nuestro modelo y cómo podemos atenuar parcialmente el sobreajuste manteniendo un rmse de prueba bajo.
A continuación, comenzaré a ajustar los hiperparámetros de la máquina de boosting para que el rendimiento pueda mejorarse aún más:
Como podemos ver en la curva de aprendizaje, después de optimizar los parámetros del modelo, el problema de sobreajuste se ha aliviado considerablemente mientras se mantiene un rmse de prueba similar, es decir, el modelo mejora su error de prueba y no sigue tan de cerca los datos de entrenamiento:
Dado que no estandarizo ni introduzco valores con promedios en el conjunto de datos que se usa para los regresores gradient boosting (de hecho, dejamos np.nan
como el valor faltante especificado), el RMSE validado cruzado es exactamente el mismo para el XGB que para el Pipeline donde está encadenado:
Light Gradient Boosting Machine
He seguido un procedimiento similar para optimizar los hiperparámetros del LGBM, sin embargo, early_stopping_rounds
ahora se ha establecido en 10 y la tasa de aprendizaje en 0.03:
LGBM está generalizando ligeramente mejor en este conjunto y también permite un entrenamiento más rápido en comparación con el XGB.
Gráficas de residuos
La siguiente figura muestra los residuos de cada estimador después de dividir el conjunto de entrenamiento, ajustar cada estimador y predecir los precios a partir del conjunto de validación. Las observaciones sobre la línea discontinua naranja oscura, o el origen, se corresponden con predicciones perfectas (residual = 0).
A pesar de que no hay patrones claros en los datos de validación, todavía hay algunas observaciones difíciles de predecir correctamente después de entrenar los modelos con el 70% de los datos (el resto de los datos se deja para la evaluación del modelo). Sin embargo, todos los estimadores, excepto el conjunto de árboles, funcionan bastante bien. El regresor de bosque aleatorio sobreajusta significativamente los datos de entrenamiento y, como resultado, sus predicciones son significativamente peores que las de otros regresores.
$R ^2$ se utiliza para evaluar la precisión del modelo. Esta métrica mide la proporción de varianza que un estimador puede explicar.
Promedio ponderado
La mayoría de las predicciones anteriores son bastante similares, pero algunas difieren, por lo que mi intención ha sido realizar un conjunto de modelos , o ensemble, muy simple que calcule el mejor promedio entre las predicciones de los modelos preseleccionados, es decir, algunas de estas predicciones tendrán más importancia, o peso, que otras.
Para seleccionar la mejor combinación de ponderaciones para todos los estimadores, en cada step se crea un conjunto de porcentajes entre 0 y 1, de modo que todos los pesos de cada fila en el conjunto sumen exactamente uno, es decir, se computa el 100% de una estimación. Finalmente, cada columna contiene todos los pesos que se calcularán para cada estimador.
Por ejemplo, digamos que una de las filas es [0.25, 0.0, 0.35, 0.1, 0.3]
, estos serían los pesos necesarios para calcular una estimación tomando el 25% de las predicciones del primer estimador, cero de la estimación del segundo modelo, 35% del tercer modelo, diez por ciento del cuarto modelo y 30% del quinto. Estos pesos se multiplican, por tanto, por las predicciones de cada modelo. Pero estas predicciones son para un conjunto de reserva, o holdout, ya que todo este proceso se evalúa mediante una validación cruzada de k particiones.
Entonces, una vez que los pesos seleccionados se multiplican por las predicciones de este conjunto reservado, el promedio resultante se puntúa con la función rmse
y luego el resultado de la métrica se almacena en una de las celdas del conjunto de predicciones que, al final de este proceso, contendrá una columna por partición, o fold, de la validación cruzada y una fila por posible combinación de todos los pesos o tasas preespecificados.
Las tasas se calculan una vez que se llama al método de ajuste (
fit
) de un objeto de esta clase. Cuanto mayor sea el número de modelos incluidos en el conjunto para un paso dado, más tiempo llevará calcular la combinación de tasas.
En conclusión, en caso de que creáramos un objeto Weighted_Average
de cinco estimadores con un step de cinco, las ponderaciones utilizadas como ejemplo anteriormente estarían en una de las filas en el marco de datos de predicciones, y cada valor de esta fila sería su correspondiente rmse del respectivo subconjunto reservado, o holdout fold. La media a lo largo de esta fila sería la raíz cuadrada con validación cruzada del error cuadrático medio de una combinación de promedios ponderados. Los pesos seleccionados serían aquellos para cuyo RMSE-CV sea menor, es decir, los pesos que arrojen el RMSE-CV más bajo entre 10626 combinaciones.
En primer lugar, incluimos todos los estimadores -salvo XGBRegressor
y RandomForestRegressor
- con un step de 10, de modo que no se necesite demasiado tiempo para crear la combinación de pesos:
Las pipelines con los estimadores el Lasso
, KernelRidge
, XGBRegressor
y LGBMRegressor
son los que pueden seleccionarse con el fin de crear una media ponderada mejorada, pero ahora con un step de 0,01 de manera que la combinación de pesos se calcula cada 1%:
Finalmente, esta es la clasificación de todas las pipelines y el conjunto de pipelines más preciso en este análisis (el mejor modelo está resaltado en azul):
Weighted_Average
devuelve un conjunto de pipelines como el modelo o estimador con la RMSE CV más baja, a saber 0.28 * Pipeline_Lasso + 0.08 * Pipeline_KernelRidge + 0.24 * Pipeline_XGBRegressor + 0.4 * Pipeline_LGBMRegressor
.
Resumen
El objetivo de este análisis fue desarrollar un estimador o conjunto de estimadores para hacer una regresión de los precios de la vivienda utilizando flujos de trabajo secuenciales, de modo que se pudiera evitar la filtración de información de los conjuntos de datos a los objetos transformadores durante el entrenamiento del estimador.
Al entrenar los modelos se muestra cómo en este conjunto de datos las pipelines con modelos de potenciación del gradiente o con regresores con kernel no lineal tienden a generalizar mejor.
También que un conjunto de estos modelos con uno lineal es el estimador más preciso.
Es decir, cuando se combinan ciertos modelos que por si solos pueden no funcionar tan bien, todavía pueden capturar cierta información válida de los datos para hacer mejores predicciones.