Publicado el: 28 de Febrero del 2021 - Jhonatan Montilla
¿Cómo funciona un modelo?.
Utilizaremos el siguiente caso de ejemplo. Un familiar ha ganado millones de dólares especulando con bienes raíces. Se ha ofrecido a convertirse en socio comercial con nosotros debido a nuestro interés en la ciencia de datos. Él proporcionará el dinero y nosotros proporcionaremos modelos que predicen cuánto valen las distintas casas. Nosotros le preguntamos cómo predijo los valores inmobiliarios en el pasado. él nos responde que es solo intuición. Pero más preguntas revelan que ha identificado patrones de precios de casas que ha visto en el pasado, y usa esos patrones para hacer predicciones para nuevas casas que está considerando. El aprendizaje automático funciona de manera similar.
Comenzaremos con un modelo llamado Árbol de decisiones. Hay modelos más sofisticados que brindan predicciones más precisas. Pero los árboles de decisión son fáciles de entender y son el bloque de construcción básico para algunos de los mejores modelos en ciencia de datos. Comenzaremos con el árbol de decisiones más simple posible en donde el modelo divide las casas en solo dos categorías. El precio previsto para cualquier casa considerada es el precio promedio histórico de las casas de la misma categoría.
Usamos datos para decidir cómo dividir las casas en dos grupos y luego nuevamente para determinar el precio previsto en cada grupo. Este paso de capturar patrones a partir de datos se denomina ajuste o entrenamiento del modelo. Los datos utilizados para ajustar el modelo se denominan datos de entrenamiento. Los detalles de cómo se ajusta el modelo (por ejemplo, cómo dividir los datos) son lo suficientemente complejos como para guardarlos para más adelante. Una vez que se ha ajustado el modelo, puede aplicarlo a nuevos datos para predecir los precios de viviendas adicionales.
import pandas as pd
from sklearn.tree import DecisionTreeRegressor
from sklearn.metrics import mean_absolute_error
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
Como ejemplo, veremos los datos sobre los precios de las viviendas en Melbourne, Australia. En los ejercicios prácticos, aplicará los mismos procesos a un nuevo conjunto de datos, que tiene precios de viviendas en Iowa. Los datos de ejemplo (Melbourne) se encuentran en nuestro repositorio el cual podrá descargar haciendo clic aquí. Cargamos y exploramos los datos con los siguientes comandos:
melbourne_data = pd.read_csv('melb_data.csv')
round(melbourne_data.describe(),2)
Unnamed: 0 | Rooms | Price | Distance | Postcode | Bedroom2 | Bathroom | Car | Landsize | BuildingArea | YearBuilt | Lattitude | Longtitude | Propertycount | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 18396.00 | 18396.00 | 18396.00 | 18395.00 | 18395.00 | 14927.00 | 14925.00 | 14820.00 | 13603.00 | 7762.00 | 8958.00 | 15064.00 | 15064.00 | 18395.00 |
mean | 11826.79 | 2.94 | 1056697.46 | 10.39 | 3107.14 | 2.91 | 1.54 | 1.62 | 558.12 | 151.22 | 1965.88 | -37.81 | 145.00 | 7517.98 |
std | 6800.71 | 0.96 | 641921.67 | 6.01 | 95.00 | 0.96 | 0.69 | 0.96 | 3987.33 | 519.19 | 37.01 | 0.08 | 0.11 | 4488.42 |
min | 1.00 | 1.00 | 85000.00 | 0.00 | 3000.00 | 0.00 | 0.00 | 0.00 | 0.00 | 0.00 | 1196.00 | -38.18 | 144.43 | 249.00 |
25% | 5936.75 | 2.00 | 633000.00 | 6.30 | 3046.00 | 2.00 | 1.00 | 1.00 | 176.50 | 93.00 | 1950.00 | -37.86 | 144.93 | 4294.00 |
50% | 11820.50 | 3.00 | 880000.00 | 9.70 | 3085.00 | 3.00 | 1.00 | 2.00 | 440.00 | 126.00 | 1970.00 | -37.80 | 145.00 | 6567.00 |
75% | 17734.25 | 3.00 | 1302000.00 | 13.30 | 3149.00 | 3.00 | 2.00 | 2.00 | 651.00 | 174.00 | 2000.00 | -37.76 | 145.06 | 10331.00 |
max | 23546.00 | 12.00 | 9000000.00 | 48.10 | 3978.00 | 20.00 | 8.00 | 10.00 | 433014.00 | 44515.00 | 2018.00 | -37.41 | 145.53 | 21650.00 |
Los resultados muestran 8 números para cada columna en su conjunto de datos original. El primer número, el recuento, muestra cuántas filas tienen valores que no faltan. Los valores perdidos surgen por muchas razones. Por ejemplo, el tamaño del segundo dormitorio no se recopilará al inspeccionar una casa de 1 dormitorio. Volveremos al tema de los datos faltantes. El segundo valor es la media, que es la media. Debajo de eso, std es la desviación estándar, que mide qué tan distribuidos numéricamente están los valores.
Para interpretar los valores mínimo, 25%, 50%, 75% y máximo, imagine ordenar cada columna de menor a mayor valor. El primer valor (el más pequeño) es el mínimo. Si recorre un cuarto de la lista, encontrará un número que es mayor que el 25% de los valores y menor que el 75% de los valores. Ese es el valor del 25% (pronunciado "percentil 25"). Los percentiles 50 y 75 se definen de forma análoga, y el máximo es el número más grande.
Un conjunto de datos puede tener demasiadas variables para entenderlo, o incluso para imprimirlo bien. ¿Cómo puede reducir esta abrumadora cantidad de datos a algo que pueda entender?. Comenzaremos eligiendo algunas variables usando nuestra intuición. Los cursos posteriores le mostrarán técnicas estadísticas para priorizar automáticamente las variables. Para elegir variables / columnas, necesitaremos ver una lista de todas las columnas del conjunto de datos. Eso se hace con la propiedad de columnas del DataFrame.
melbourne_data.columns
Index(['Unnamed: 0', 'Suburb', 'Address', 'Rooms', 'Type', 'Price', 'Method', 'SellerG', 'Date', 'Distance', 'Postcode', 'Bedroom2', 'Bathroom', 'Car', 'Landsize', 'BuildingArea', 'YearBuilt', 'CouncilArea', 'Lattitude', 'Longtitude', 'Regionname', 'Propertycount'], dtype='object')
melbourne_data = melbourne_data.dropna(axis = 0)
Hay muchas maneras de seleccionar un subconjunto de sus datos. Nos centraremos en dos enfoques por ahora.
Puede extraer una variable con notación de puntos. Esta única columna se almacena en una serie, que en general es como un DataFrame con una única columna de datos. Usaremos la notación de puntos para seleccionar la columna que queremos predecir, que se llama objetivo de predicción. Por convención, el objetivo de predicción se llama y. Entonces, el código que necesitamos para guardar los precios de la vivienda en los datos de Melbourne es
y = melbourne_data.Price
Las columnas que se ingresan en nuestro modelo (y luego se usan para hacer predicciones) se llaman "características". En nuestro caso, esas serían las columnas utilizadas para determinar el precio de la vivienda. A veces, utilizará todas las columnas excepto el destino como características. Otras veces, estará mejor con menos funciones. Por ahora, crearemos un modelo con solo algunas características. Más adelante verá cómo iterar y comparar modelos creados con diferentes características. Seleccionamos múltiples características proporcionando una lista de nombres de columnas entre corchetes. Cada elemento de esa lista debe ser una cadena (con comillas).
melbourne_features = ['Rooms', 'Bathroom', 'Landsize', 'Lattitude', 'Longtitude']
X = melbourne_data[melbourne_features]
round(X.describe(),2)
Rooms | Bathroom | Landsize | Lattitude | Longtitude | |
---|---|---|---|---|---|
count | 6196.00 | 6196.00 | 6196.00 | 6196.00 | 6196.00 |
mean | 2.93 | 1.58 | 471.01 | -37.81 | 144.99 |
std | 0.97 | 0.71 | 897.45 | 0.08 | 0.10 |
min | 1.00 | 1.00 | 0.00 | -38.16 | 144.54 |
25% | 2.00 | 1.00 | 152.00 | -37.86 | 144.93 |
50% | 3.00 | 1.00 | 373.00 | -37.80 | 145.00 |
75% | 4.00 | 2.00 | 628.00 | -37.76 | 145.05 |
max | 8.00 | 8.00 | 37000.00 | -37.46 | 145.53 |
round(X.head(),2)
Rooms | Bathroom | Landsize | Lattitude | Longtitude | |
---|---|---|---|---|---|
1 | 2 | 1.0 | 156.0 | -37.81 | 144.99 |
2 | 3 | 2.0 | 134.0 | -37.81 | 144.99 |
4 | 4 | 1.0 | 120.0 | -37.81 | 144.99 |
6 | 3 | 2.0 | 245.0 | -37.80 | 145.00 |
7 | 2 | 1.0 | 256.0 | -37.81 | 145.00 |
Utilizará la biblioteca scikit-learn para crear sus modelos. Al codificar, esta biblioteca se escribe como sklearn, como verá en el código de muestra. Scikit-learn es fácilmente la biblioteca más popular para modelar los tipos de datos que normalmente se almacenan en DataFrames.
Los pasos para construir y usar un modelo son:
A continuación, se muestra un ejemplo de cómo definir un modelo de árbol de decisión con scikit-learn y ajustarlo con las características y la variable de destino.
melbourne_model = DecisionTreeRegressor(random_state = 1) # Define el modelo.
melbourne_model.fit(X, y) # Ajusta el modelo
DecisionTreeRegressor(random_state=1)
Muchos modelos de aprendizaje automático permiten cierta aleatoriedad en el entrenamiento de modelos. Especificar un número para random_state asegura que obtendrá los mismos resultados en cada ejecución. Esto se considera una buena práctica. Utiliza cualquier número, y la calidad del modelo no dependerá significativamente del valor exacto que elija.
Ahora tenemos un modelo ajustado que podemos usar para hacer predicciones. En la práctica, querrá hacer predicciones para las casas nuevas que saldrán al mercado en lugar de las casas para las que ya tenemos precios. Pero haremos predicciones para las primeras filas de los datos de entrenamiento para ver cómo funciona la función de predicción.
print("Haciendo predicciones para las siguientes 5 casas:\n")
print(round(X.head(),2))
print("\nLas predicciones son:\n")
print(melbourne_model.predict(X.head()))
Haciendo predicciones para las siguientes 5 casas: Rooms Bathroom Landsize Lattitude Longtitude 1 2 1.0 156.0 -37.81 144.99 2 3 2.0 134.0 -37.81 144.99 4 4 1.0 120.0 -37.81 144.99 6 3 2.0 245.0 -37.80 145.00 7 2 1.0 256.0 -37.81 145.00 Las predicciones son: [1035000. 1465000. 1600000. 1876000. 1636000.]
En esta sección aprenderemos a usar la validación del modelo para medir la calidad del modelo construido anteriormente. Medir la calidad de los modelos es la clave para mejorarlos de forma iterativa. Seguramente querrá evaluar casi todos los modelos que haya construido. En la mayoría de las aplicaciones (aunque no en todas), la medida relevante de la calidad del modelo es la precisión predictiva. En otras palabras, saber si las predicciones del modelo se acercarán a lo que realmente sucede. Mucha gente comete un gran error al medir la precisión predictiva. Hacen predicciones con sus datos de entrenamiento y comparan esas predicciones con los valores objetivo en los datos de entrenamiento.
Hay un problema con este enfoque por lo que será necesario resolverlo en un momento, sin embargo, es necesario primero pensar en cómo lo haríamos primero. Primero tendríamos que resumir la calidad del modelo de una manera comprensible. Si compara los valores de las casas pronosticados y reales para 10,000 casas, probablemente encontrará una combinación de predicciones buenas y malas. No tendría sentido mirar a través de una lista de 10,000 valores predichos y reales. Necesitamos resumir esto en una sola métrica. Hay muchas métricas para resumir la calidad del modelo, pero comenzaremos con una llamada Error absoluto medio (también llamado MAE). Analicemos esta métrica comenzando con la última palabra, error.
Entonces, si una casa cuesta 150.000USD y predice que costará 100.000USD, el error es 50.000USD. Con la métrica MAE, tomamos el valor absoluto de cada error. Esto convierte cada error en un número positivo. Luego tomamos el promedio de esos errores absolutos. Ésta es nuestra medida de la calidad del modelo.
predicted_home_prices = melbourne_model.predict(X)
print('MAE: ',round(mean_absolute_error(y, predicted_home_prices),2))
MAE: 1115.75
La medida que acabamos de calcular puede denominarse puntuación "dentro de la muestra". Usamos una sola "muestra" de casas tanto para construir el modelo como para evaluarlo. He aquí por qué esto es malo. Imagínese que, en el gran mercado inmobiliario, el color de la puerta no está relacionado con el precio de la vivienda. Sin embargo, en la muestra de datos que usó para construir el modelo, todas las casas con puertas verdes eran muy caras. El trabajo del modelo es encontrar patrones que predigan los precios de las viviendas, por lo que verá este patrón y siempre predecirá precios altos para las casas con puertas verdes. Dado que este patrón se derivó de los datos de entrenamiento, el modelo parecerá preciso en los datos de entrenamiento. Pero si este patrón no se cumple cuando el modelo ve nuevos datos, el modelo sería muy inexacto cuando se usa en la práctica. Dado que el valor práctico de los modelos proviene de hacer predicciones sobre nuevos datos, medimos el rendimiento en datos que no se utilizaron para construir el modelo. La forma más sencilla de hacer esto es excluir algunos datos del proceso de construcción del modelo y luego usarlos para probar la precisión del modelo en datos que no ha visto antes. Estos datos se denominan datos de validación.
La biblioteca scikit-learn tiene una función train_test_split para dividir los datos en dos partes. Usaremos algunos de esos datos como datos de entrenamiento para ajustar el modelo, y usaremos los otros datos como datos de validación para calcular mean_absolute_error.
train_X, val_X, train_y, val_y = train_test_split(X, y, random_state = 0)
melbourne_model = DecisionTreeRegressor() # Define el modelo
melbourne_model.fit(train_X, train_y) # Ajusta el modelo
DecisionTreeRegressor()
val_predictions = melbourne_model.predict(val_X)
print(round(mean_absolute_error(val_y, val_predictions),2))
275754.89
En la práctica, no es raro que un árbol tenga 10 divisiones entre el nivel superior (todas las casas) y una hoja. A medida que el árbol se hace más profundo, el conjunto de datos se corta en hojas con menos casas. Si un árbol solo tiene 1 división, divide los datos en 2 grupos. Si cada grupo se vuelve a dividir, obtendríamos 4 grupos de casas. Dividir cada uno de ellos de nuevo crearía 8 grupos. Si seguimos duplicando el número de grupos agregando más divisiones en cada nivel, tendremos 210 grupos de casas para cuando lleguemos al décimo nivel. Eso es 1024 hojas. Cuando dividimos las casas entre muchas hojas, también tenemos menos casas en cada hoja. Las hojas con muy pocas casas harán predicciones que se acercan bastante a los valores reales de esas casas, pero pueden hacer predicciones muy poco fiables para nuevos datos (porque cada predicción se basa solo en unas pocas casas). Este es un fenómeno llamado sobreajuste, donde un modelo coincide con los datos de entrenamiento casi a la perfección, pero lo hace mal en la validación y otros datos nuevos. Por otro lado, si hacemos que nuestro árbol sea muy poco profundo, no divide las casas en grupos muy distintos.
En un extremo, si un árbol divide las casas en solo 2 o 4, cada grupo todavía tiene una amplia variedad de casas. Las predicciones resultantes pueden estar lejanas para la mayoría de las casas, incluso en los datos de entrenamiento (y también serán malas en la validación por la misma razón). Cuando un modelo no logra capturar distinciones y patrones importantes en los datos, por lo que tiene un rendimiento deficiente incluso en los datos de entrenamiento, eso se denomina ajuste insificiente. Dado que nos preocupamos por la precisión de los datos nuevos, que estimamos a partir de nuestros datos de validación, queremos encontrar el punto óptimo entre el ajuste insuficiente y el sobreajuste.
Existen algunas alternativas para controlar la profundidad del árbol, y muchas permiten que algunas rutas a través del árbol tengan mayor profundidad que otras rutas. Pero el argumento max_leaf_nodes proporciona una forma muy sensata de controlar el sobreajuste frente al desajuste. Cuantas más hojas permitamos que haga el modelo, más nos movemos del área de ajuste inferior en el gráfico anterior al área de ajuste superior. Podemos usar una función de utilidad para ayudar a comparar las puntuaciones MAE de diferentes valores para max_leaf_nodes:
def get_mae(max_leaf_nodes, train_X, val_X, train_y, val_y):
model = DecisionTreeRegressor(max_leaf_nodes=max_leaf_nodes, random_state=0)
model.fit(train_X, train_y)
preds_val = model.predict(val_X)
mae = mean_absolute_error(val_y, preds_val)
return(mae)
for max_leaf_nodes in [5, 50, 500, 5000]:
my_mae = get_mae(max_leaf_nodes, train_X, val_X, train_y, val_y)
print("Max leaf nodes: %d \t\t Mean Absolute Error: %d" %(max_leaf_nodes, my_mae))
Max leaf nodes: 5 Mean Absolute Error: 385696 Max leaf nodes: 50 Mean Absolute Error: 279794 Max leaf nodes: 500 Mean Absolute Error: 261718 Max leaf nodes: 5000 Mean Absolute Error: 271996
Aquí está la conclusión: los modelos pueden sufrir de:
Usamos datos de validación, que no se usan en el entrenamiento de modelos, para medir la precisión de un modelo candidato. Esto nos permite probar muchos modelos candidatos y mantener el mejor.
Los árboles de decisión nos dejan con una decisión difícil. Un árbol profundo con muchas hojas se sobreajustará porque cada predicción proviene de datos históricos de solo las pocas casas en su hoja. Pero un árbol poco profundo con pocas hojas tendrá un desempeño deficiente porque no logra capturar tantas distinciones en los datos brutos. Incluso las técnicas de modelado más sofisticadas de la actualidad se enfrentan a esta tensión entre ajuste insificiente y sobreajuste. Sin embargo, muchos modelos tienen ideas inteligentes que pueden conducir a un mejor rendimiento. El bosque aleatorio utiliza muchos árboles y hace una predicción promediando las predicciones de cada árbol componente. Por lo general, tiene una precisión predictiva mucho mejor que un árbol de decisión único y funciona bien con los parámetros predeterminados.
forest_model = RandomForestRegressor(random_state = 1) # Crea el modelo
forest_model.fit(train_X, train_y) # Ajusta el modelo
RandomForestRegressor(random_state=1)
melb_preds = forest_model.predict(val_X)
print(round(mean_absolute_error(val_y, melb_preds),2))
207190.69
Es probable que haya espacio para una mejora adicional, pero esta es una gran mejora con respecto al error del mejor árbol de decisión de 250.000. Hay parámetros que le permiten cambiar el rendimiento del bosque aleatorio tanto como cambiamos la profundidad máxima del árbol de decisión único. Pero una de las mejores características de los modelos de Random Forest es que, por lo general, funcionan de manera razonable incluso sin este ajuste.