Por ejemplo supongamos que nos contrata una empresa inmobiliaria.
A la empresa le interesa predecir el precio de mercado de un inmueble.
¿Y cómo lo hacían antes de contratarte? Ellos te dirán que por "intuición". Realmente lo que hacen es identificar patrones en función de casas que hayan visto venderse en el pasado.
Nosotros esencialmente haremos esto de forma automática y refinada.
Por ejemplo, podemos hacer un árbol de decisión (Decision Tree).
¿La casa tiene más de 2 dormitorios?
Por ejemplo, los precios predichos los podemos haber obtenido a partir de la media de precios de cada categoría. La idea de este paso se conoce como ajuste o entrenamiento de los datos. (fitting, training)
Usamos training data para fit al modelo.
Se pueden añadir más variables: ¿La casa tiene más de 2 dormitorios?
Los bloques de final del árbol, donde hacemos las predicciones de precios, se conocen como las hojas del árbol (leaves).
pandas
¶import pandas as pd
import numpy as np
casas = pd.read_csv('melb_data.csv')
casas.head()
Primero vemos todas las columnas:
casas.columns
Podemos hacer el procesamiento típico que hemos hecho en otras ocasiones:
def is_number(s):
try:
float(s)
return True
except ValueError:
return False
def clasifica_columnas(datos):
numericas=[]
categoricas=[]
for i in datos.columns:
if is_number(datos.loc[0,i]):
numericas.append(i)
else:
categoricas.append(i)
return numericas, categoricas
def procesamiento(i,datos):
if i in numericas:
print('Missings: ',datos.shape[0]-sum(datos[i].apply(is_number)))
print('NaNs: ', sum(datos[i].apply(np.isnan)))
print('Ceros: ', sum(datos[i].isin(datos[i]==0)))
print('Tipo: ', type(datos.loc[0,i]))
elif i in categoricas:
print(datos[i].unique())
ax = sns.countplot(datos[i])
ax = ax.set_xticklabels(ax.get_xticklabels(),rotation=90) #Para que rote los títulos
numericas, categoricas = clasifica_columnas(casas)
for i in numericas:
print(i)
procesamiento(i,casas)
print('\n')
Afortunadamente no hay missings. ¿A qué pueden deberse los ceros y los NaNs?
casas['Bedroom2'].head()
Los NaNs pueden deberse a que falte el dato. Pero por ejemplo, los ceros en Bedroom2
probablemente se deban a que no haya segunda habitación.
¿Cuál es el tamaño medio de una casa? ¿Cuántos años tiene la casa más nueva?
casas.describe()
Antes de nada, vamos a quitarnos los NaN.
casas=casas.dropna()
casas.head()
Primero vamos a elegir la variable de la que queremos predecir sus valores, el objetivo de predicción (prediction target). Generalmente esta variable se suele denotar por $y$.
En este caso $y$ va a ser el precio:
y=casas.Price
Las columnas que usamos para hacer predicciones de $y$ son aquellas que consideramos que pueden ser relevantes para el precio. Se llaman características (features) y sus datos se denotan por $X$.
# Seleccionamos unas pocas columnas como features
features=['Rooms','Bathroom','BuildingArea','YearBuilt','Landsize','Lattitude','Longtitude']
X = casas[features]
X.head()
X.describe()
Para constuir el modelo usaremos la librería scikit-learn (sklearn
).
Los pasos para hacer un modelo son:
Veamos un ejemplo:
Importamos la librería para hacer el Decision Tree:
from sklearn.tree import DecisionTreeRegressor
casas_modelo=DecisionTreeRegressor(random_state=1)
(Con random_state
lo que estamos haciendo es fijar un estado del generador de números aleatorios para que cada vez que lo ejecutemos nos salga lo mismo).
casas_modelo.fit(X,y)
prediccioneshead=casas_modelo.predict(X.head())
print('Hacemos predicciones de las siguientes 5 casas: ')
print(X.head())
print('\n') #Salto de línea
print('Los precios predichos son: ')
print(prediccioneshead)
Veamos si coinciden con los precios reales.
casas.Price.head()
Son exactamente los mismos precios.
Para evaluar la precisión del modelo, debemos comparar los valores predichos con los valores reales del target. Como no podemos irlos comparando uno a uno, necesitamos medidores (metrics), esto es, números que estimen la precisión del modelo.
Un ejemplo es el error medio absoluto ("mean absolute error", MAE).
El error de predicción producido en cada casa es simplemente
error = precio_real - precio_predicho
$$
E_i = y_i - \hat{y}_i.
$$El MAE es el promedio de los valores absolutos de los errores de cada casa: $$ MAE = \frac{\sum_{i=1}^n |E_i|}{n}. $$
Otro ejemplo sería el error cuadrático medio (MSE) y su raíz cuadrada (RMSE).
$$ MSE = \frac{\sum_{i=1}^n E_i^2}{n}, $$$$ RMSE = \sqrt{MSE}. $$Nosotros lo calcularemos simplemente con una función de la librería scikit-learn.
from sklearn.metrics import mean_absolute_error
predicciones = casas_modelo.predict(X)
mean_absolute_error(y,predicciones)
El error medio es de 434 dólares y estamos manejando precios del orden del millón. Esto indica que funciona muy bien.
Sin embargo, existe un problema. Estamos evaluando la precisión del modelo en los propios training data, por tanto es lógico que el modelo funcione bien en esos mismos datos. Pero lo importante es que prediga bien los precios de casas que no están en el training data.
La forma más directa de tener este problema en cuenta es seleccionando sólo una parte de los datos como training data y usar el resto para probar el modelo. Estos son los evaluation data.
Esto se implementa fácilmente en Python usando la función train_test_split
de scikit-learn. Esta función nos separa los datos en training data y evaluation data de forma aleatoria. (De nuevo, fijando el random_state
nos aseguramos de obtener lo mismo cada vez que lo ejecutemos).
from sklearn.model_selection import train_test_split
# Separamos los datos en training y validation
train_X, val_X, train_y, val_y = train_test_split(X,y, random_state=1)
# Definimos el modelo
casas_modelo = DecisionTreeRegressor(random_state=1)
# Ajustamos los training data
casas_modelo.fit(train_X,train_y)
# Predecimos los valores de los evaluation data
val_predicciones = casas_modelo.predict(val_X)
# Evaluamos
print('Error: ', mean_absolute_error(val_y,val_predicciones))
¡Corcho! Tengo un error de más de 250K dólares, que es aproximadamente un cuarto del precio medio, eso es mucho. Resulta que el modelo no era tan bueno como parecía.
Meter muy pocas hojas da predicciones poco precisas, ya que no tiene en cuenta la riqueza y todas las posibles características que se pueden dar. Esto se conoce como underfitting.
Para obtener más precisión podríamos meter más divisiones en el árbol. Notemos que en general el número de hojas es $2^n$, con $n$ el número de divisiones.
Sin embargo, si tenemos muchas hojas tendremos menos casas en cada hoja. Las hojas con muy pocas casas harán predicciones muy buenas para esas mismas casas pero como cada predicción se basa en muy pocas casas, las predicciones de datos nuevos serán muy malas. Esto se conoce como overfitting.
En general tenemos una curva:
¿Cómo encontramos el sweet spot?
DecisionTreeRegressor
tiene una opción max_leaf_nodes
que nos permite controlar la complejidad del modelo.
Podemos definir una función que nos diga el MAE que se obtiene para cada número de hojas:
def get_mae(max_leaf_nodes, train_X, val_X, train_y, val_y):
modelo = DecisionTreeRegressor(max_leaf_nodes=max_leaf_nodes, random_state=0) # Definir
modelo.fit(train_X,train_y) # Ajustar
preds_val = modelo.predict(val_X) # Predecir
mae = mean_absolute_error(val_y, preds_val) # Evaluar
return mae
Usando un bucle for
podemos comparar la precisión para distintos valores de max_leaf_nodes
.
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: ", max_leaf_nodes)
print("MAE: ", my_mae)
print("\n")
La opción óptima de las que le hemos dado es 500.
Otra forma más directa:
candidatas_max_leaf_nodes = [5, 25, 50, 100, 250, 500]
my_mae=[]
for i in candidatas_max_leaf_nodes:
my_mae.append(get_mae(i, train_X, val_X, train_y, val_y))
mae_optimo = min(my_mae)
max_leaf_nodes_optimo = candidatas_max_leaf_nodes[my_mae.index(min(my_mae))]
mae_optimo, max_leaf_nodes_optimo
También lo puedo ver gráficamente:
import matplotlib.pyplot as plt
plt.plot(candidatas_max_leaf_nodes,my_mae)
Ahora que ya hemos encontrado el número óptimo de hojas de nuestro modelo, ya no necesitamos separar entre training y evaluation data y podemos crear un modelo final con todos los datos de los que disponemos.
# Definir
casas_modelo_final = DecisionTreeRegressor(max_leaf_nodes=max_leaf_nodes_optimo,random_state=1)
# Ajustar
casas_modelo_final.fit(X,y)
Una forma de evitarse el tener que buscar el número de hojas óptimo es utilizar un algoritmo distinto, el random forest. Un random forest genera muchos árboles y hace una predicción ponderando las predicciones de cada árbol. En general es más preciso que un solo árbol de decisión y funciona bien con los parámetros por defecto.
Para implementarlo en Python usamos la clase RandomForestRegressor
de scikit-learn.
from sklearn.ensemble import RandomForestRegressor
# Definir
casas_modelo_forest = RandomForestRegressor(random_state=1)
# Ajustar
casas_modelo_forest.fit(train_X, train_y)
# Predecir
predicciones = casas_modelo_forest.predict(val_X)
# Evaluar
print('Error: ', mean_absolute_error(val_y,predicciones))
Sigue siendo bastante, pero es una buena mejoría respecto al Decision Tree.
Probar suerte en una competición de Kaggle.