Introducción al analisis exploratorio de datos
Modelo (primer significado):
Mecanismo capaz de codificar conocimiento para solución de problemas. Tipicamente es una función compuesta de parametros, ej.: modelo de regresión linear ($y = \beta X + \epsilon$)
Learner (algoritmo):
Algoritmo o metodo que toma como insumo datos y genera una función o programa capaz de emitir predicciones.
Aprendizaje (learning) o entrenamiento (training):
La ejecución concreta o combinación de un learner con datos aplicables.
Classifiers, regressors & models (segundo significado):
Implementación concreta de una función o programa que toma como insumo datos y genera predicciones. Aquí y en muchas ocasiones encontrarán que el término model se refiere a este concepto, el del resultado del aprendizaje o entrenamiento
Cost/Utility/Loss function:
Función objetivo o de costo que juzga basado en los datos que tan bien se han ajustado los parameters al problema especifico.
Parameters:
Conjunto de elementos configurables/variables que un learner ajusta durante su proceso de aprendizaje para producir un clasificador o regresor
HYPERparameters:
Conjunto de elementos configurables del learner que no se ajustan durante el proceso de aprendizaje, sino que deben ser determinados "manualmente" por nosotros.
Predictores (features):
Subconjunto vertical de los datos (columnas) que caracterizan cada ejemplo/registro y que son utilizados como insumo para predecir/clasificar.
Etiquetas (labels):
Subconjunto vertical de los datos (columnas) que queremos predecir.
Generalization:
El objetivo último de que el modelo aprendido tenga un buen rendimiento con datos desconocidos, es decir en la vida real. El "buen rendimiento" puede definirse con la misma función de utilidad que se uso durante aprendizaje o con otras metricas.
Metricas:
Función que tipicamente toma como insumo los datos (predictores y etiquetas) y valora el rendimiento del modelo, es decir que tanto se acercan las predicciones a la realidad.
Ruído (Noise):
Término genérico que agrupa varias fuentes de distorción no deseada en los datos. Puede ser error de medición (humano o del instrumento), error de recolección, y variabilidad en los datos debido a las simplificaciones que son parte del modelo (1era definicion) usado, entre otras posibles fuentes.
Señal:
Todo lo que no es ruido. Estos dos términos se toman prestados del área de procesamiento de señales, pero su uso es menos estricto.
Outlier (valor atipico):
Es una observación que es muy distinta a las demás. Podría ser ruido o señal, es dificil saber de antemano. Su presencia debe ser considerada puesto que puede tener impacto en cualquier análisis.
Underfitting:
Cuando un modelo no aprende como esperado de los datos y su rendimiento con los mismos datos con los que fue entrenado es "decepcionante".
Overfitting:
Cuando un modelo presenta muy buen rendimiento con los datos con los que fue entrenado (o validado) pero no así con datos no vistos previamente. Podrían escuchar decir que se aprendío del ruido (algo indeseable).
El protocolo más sencillo de todos
Dividimos los datos en dos partes. Entrenamos con una y estimamos la generalizacion con la restante.
Es la base de todos los demás y nos enseña el principio FUNDAMENTAL:
Debemos estimar la generalización siempre con datos que no hayan sido usados en lo absoluto
Cons:
Si nuestro learner tiene hyperparameters y queremos optimizarlos debemos explorar diferentes valores/combinaciones.
Dividir los datos en tres partes:
Empezamos a complicarnos para atacar la primera debilidad del protocolo Train & Test: que sólo tenemos un punto/valor estimado de la generalización (ej.: 95% casos bien clasificados). No sabemos qué tan preciso podría ser ese estimado.
Quisieramos poder decir algo como: "Clasificaremos correctamente entre 92% y 98% de los casos"
Para eso necesitamos una distribución para el estimado de la generalización, es decir varios valores con los cuales podamos calcular promedio, desviación estándar, etc.
Así que:
Después de reenfocar la vista del mareo anterior, recordemos lo que estamos buscando:
Una estimación honesta de la generalización del modelo que finalmente usaremos en la práctica
Con lo que hemos considerado hasta ahora, para lograrlo debemos:
El nivel exterior de este protocolo nos dará una muestra de la distribución de la generalización, lo mismo que teníamos con el protocolo anterior.
El nivel interior de CV en este protocolo nos permitirá probar múltiples combinaciones de hyperparameters y elegir la que tenga el mejor rendimiento.
In [6]:
import pandas as pd
import numpy as np
from scipy import stats
from scipy.stats import norm, skew
from sklearn.preprocessing import LabelEncoder
from scipy.special import boxcox1p
In [7]:
def transform(X):
"""Encapsular las transformaciones y limpiezas realizadas en charla previa
Asumiendo que X es un DataFrame y sin validación alguna
"""
X.drop("Id", axis = 1, inplace = True)
# Eliminamos los outliers
X = X.drop(X[(X['GrLivArea']>4000) & (X['SalePrice']<300000)].index)
# Empleamos la función log1p de numpy para obtener el log(1+x) de todos los elementos de la variable objetivo
y = np.log1p(X["SalePrice"])
# Eliminamos la variable a predecir
X.drop(['SalePrice'], axis=1, inplace=True)
#
X["PoolQC"] = X["PoolQC"].fillna("None")
#
X["MiscFeature"] = X["MiscFeature"].fillna("None")
X["Alley"] = X["Alley"].fillna("None")
X["Fence"] = X["Fence"].fillna("None")
X["FireplaceQu"] = X["FireplaceQu"].fillna("None")
#
for col in ('GarageType', 'GarageFinish', 'GarageQual', 'GarageCond'):
X[col] = X[col].fillna('None')
#
# Agrupamos por vecindario y completamos los valores perdidos con la mediana de LotFrontage para todos los vecindarios
X["LotFrontage"] = X.groupby("Neighborhood")["LotFrontage"].transform(
lambda x: x.fillna(x.median()))
#
for col in ('GarageYrBlt', 'GarageArea', 'GarageCars'):
X[col] = X[col].fillna(0)
#
for col in ('BsmtFinSF1', 'BsmtFinSF2', 'BsmtUnfSF','TotalBsmtSF', 'BsmtFullBath', 'BsmtHalfBath'):
X[col] = X[col].fillna(0)
#
for col in ('BsmtQual', 'BsmtCond', 'BsmtExposure', 'BsmtFinType1', 'BsmtFinType2'):
X[col] = X[col].fillna('None')
#
X["MasVnrType"] = X["MasVnrType"].fillna("None")
X["MasVnrArea"] = X["MasVnrArea"].fillna(0)
#
X['MSZoning'] = X['MSZoning'].fillna(X['MSZoning'].mode()[0])
X = X.drop(['Utilities'], axis=1)
X["Functional"] = X["Functional"].fillna("Typ")
X['Electrical'] = X['Electrical'].fillna(X['Electrical'].mode()[0])
X['KitchenQual'] = X['KitchenQual'].fillna(X['KitchenQual'].mode()[0])
X['Exterior1st'] = X['Exterior1st'].fillna(X['Exterior1st'].mode()[0])
X['Exterior2nd'] = X['Exterior2nd'].fillna(X['Exterior2nd'].mode()[0])
X['SaleType'] = X['SaleType'].fillna(X['SaleType'].mode()[0])
X['MSSubClass'] = X['MSSubClass'].fillna("None")
#
# MSSubClass = El tipo de edificios
X['MSSubClass'] = X['MSSubClass'].apply(str)
# OverallCond
X['OverallCond'] = X['OverallCond'].astype(str)
# Año y mes de venta.
X['YrSold'] = X['YrSold'].astype(str)
X['MoSold'] = X['MoSold'].astype(str)
#
cols = ('FireplaceQu', 'BsmtQual', 'BsmtCond', 'GarageQual', 'GarageCond',
'ExterQual', 'ExterCond','HeatingQC', 'PoolQC', 'KitchenQual', 'BsmtFinType1',
'BsmtFinType2', 'Functional', 'Fence', 'BsmtExposure', 'GarageFinish', 'LandSlope',
'LotShape', 'PavedDrive', 'Street', 'Alley', 'CentralAir', 'MSSubClass', 'OverallCond',
'YrSold', 'MoSold')
# procesa columnas, applicando LabelEncoder a los atributos categóricos
for c in cols:
lbl = LabelEncoder()
lbl.fit(list(X[c].values))
X[c] = lbl.transform(list(X[c].values))
#
# Adicionamos el total de pies cuadrados (TotalSF) de la vivienda
X['TotalSF'] = X['TotalBsmtSF'] + X['1stFlrSF'] + X['2ndFlrSF']
#
numeric_feats = X.dtypes[X.dtypes != "object"].index
# Verificamos el sesgo de todos los atributos numéricos
skewed_feats = X[numeric_feats].apply(lambda x: skew(x.dropna())).sort_values(ascending=False)
skewness = pd.DataFrame({'Skew' :skewed_feats})
#
skewness = skewness[abs(skewness) > 0.75]
skewed_features = skewness.index
lam = 0.15
for feat in skewed_features:
X[feat] = boxcox1p(X[feat], lam)
#
X = pd.get_dummies(X)
#
return X.values, y.values
In [8]:
train = pd.read_csv("../data/train.csv")
train.head()
Out[8]:
In [9]:
X, y = transform(train.copy())
In [10]:
from sklearn.model_selection import GridSearchCV, cross_val_score, KFold
from sklearn.linear_model import ElasticNet
#
np.random.seed(42)
#
enet = ElasticNet()
p_grid = {"alpha": np.logspace(-3,1,10),
"l1_ratio": [0.001, .01, .1, 0.25, 0.5],
"max_iter": [1000,5000],
"normalize": [True, False]}
#
inner_cv = KFold(n_splits=4, shuffle=True)
outer_cv = KFold(n_splits=4, shuffle=True)
#
clf = GridSearchCV(estimator=enet, param_grid=p_grid, cv=inner_cv, n_jobs=-1)
#
nested_score = cross_val_score(clf, X=X, y=y, cv=outer_cv)
#
print('Avg Score {:4f} +/- {:4f}'.format(nested_score.mean(),nested_score.std()*2))