<jupyter_start><jupyter_text>Sets de entrenamiento y Modelos* Link github: https://github.com/sebastiandres/ia_notebooks/1_error_datasets_y_modelos/1_error_datasets_y_modelos.ipynb* Link mybinder: https://bit.ly/2Vf89oC Sobre jupyter notebookJupyter notebooks es un medio de desarrollo iterativo, que  permite mezclar código con texto, imágenes y video. Su facilidad de uso permite crear y descargar material para el aprendizaje individual y grupal.*Importante*: cada celda se ejecuta con  `Alt + Enter`  Objetivos de Aprendizaje1. Importancia de conocer el negocio y explorar los datos.2. Técnicas para seleccionar un modelo predictivo.3. Conocer el significado y utilidad de:    * Datos de entrenamiento    * Datos de validación (verificación)    * Datos de testeo    * Datos de predicción  0. Verificar disponibilidad de librerías y probar jupyter notebooks<jupyter_code>import pandas as pd
import numpy as np
from matplotlib  import pyplot as plt
import warnings
warnings.filterwarnings("ignore")
print("Versión de pandas: ", pd.__version__)
print("Versión de numpy: ", np.__version__)
%load_ext autoreload
%autoreload 2
# Fix the seed so everyone can reproduce the same results
np.random.seed(42)<jupyter_output><empty_output><jupyter_text>Ejemplos de celdas de jupyter notebook con python:<jupyter_code>a = 1
print(a)
for i in range(10):
    print(i, i**2)<jupyter_output>0 0
1 1
2 4
3 9
4 16
5 25
6 36
7 49
8 64
9 81<jupyter_text>1. Importar funcionalidades pre-existentes<jupyter_code>from secret import get_data<jupyter_output><empty_output><jupyter_text>Los datos pueden estar en un archivo csv o excel, o haberse descargado de intenet, o haberlos obtenido después de un largo proceso de proccesamiento. En esta caso, se obtienen simplemente con una función creada para este objetivo:`def get_data()`<jupyter_code>N_data = 10000
x_all, y_all = get_data(N_data)
len(x_all)
x_all[:10]
y_all[:10]<jupyter_output><empty_output><jupyter_text>Analisis exploratorio<jupyter_code>df_all = pd.DataFrame(columns=["x","y"], data=np.array([x_all, y_all]).T)
df_all
df_all.describe()<jupyter_output><empty_output><jupyter_text>¿Qué cosa le llama la atención de los datos?  ¿Porqué siempre es bueno el análisis gráfico? Existe un ejemplo clásico llamado el Cuarteto de Anscombe. Considere los siguientes 4 conjuntos de datos. ¿Qué puede decir de los datos?<jupyter_code>import pandas as pd
import os
filepath = os.path.join("data","anscombe.csv")
df = pd.read_csv(filepath)
df<jupyter_output><empty_output><jupyter_text>Descripción de los datos, versión numpy:<jupyter_code>import numpy as np
filepath = os.path.join("data","anscombe.csv")
data = np.loadtxt(filepath, delimiter=",", skiprows=1)
for i in range(4):
    x = data[:,2*i]
    y = data[:,2*i+1]
    slope, intercept = np.polyfit(x, y, 1)
    print("Grupo %d:" %(i+1))
    print("\tTiene pendiente m=%.2f e intercepto b=%.2f" %(slope, intercept))<jupyter_output>Grupo 1:
	Tiene pendiente m=0.50 e intercepto b=3.00
Grupo 2:
	Tiene pendiente m=0.50 e intercepto b=3.00
Grupo 3:
	Tiene pendiente m=0.50 e intercepto b=3.00
Grupo 4:
	Tiene pendiente m=0.50 e intercepto b=3.00<jupyter_text>Descripción de los datos, versión pandas:<jupyter_code>import pandas as pd
import os
filepath = os.path.join("data","anscombe.csv")
df = pd.read_csv(filepath)
df[sorted(df.columns)].describe(include="all")<jupyter_output><empty_output><jupyter_text>Veamos ahora que nos puede decir<jupyter_code>from matplotlib import pyplot as plt
import numpy as np

def my_plot():
    filepath = os.path.join("data","anscombe.csv")
    data = np.loadtxt(filepath, delimiter=",", skiprows=1)
    fig = plt.figure(figsize=(16,8))
    for i in range(4):
        x = data[:,2*i]
        y = data[:,2*i+1]
        plt.subplot(2, 2, i+1)
        plt.plot(x,y,'o')
        plt.xlim([2,20])
        plt.ylim([2,20])
        plt.title("Grupo %d" %(i+1))
        m, b = np.polyfit(x, y, 1)
        x_aux = np.linspace(2,16,20)
        plt.plot(x_aux, m*x_aux + b, 'r', lw=2.0)
    plt.suptitle("Cuarteto de Anscombe")
    plt.show()
    
my_plot()<jupyter_output><empty_output><jupyter_text>Análisis gráficoUna de las primeras tareas que debemos hacer es realizar un análisis gráfico de los datos. Para esto existen muchas alternativas. Use su buen juicio.<jupyter_code>plt.figure(figsize=(10,10))
plt.plot(x_all, y_all, "-", label="row data")
plt.xlabel("x", fontsize=16)
plt.ylabel("y", fontsize=16)
plt.legend()
plt.show()<jupyter_output><empty_output><jupyter_text>Lección 1: Los datos no suelen venir ordenados.<jupyter_code>sorting_index = np.argsort(x_all)
x_sorted = np.array(x_all)[sorting_index]
y_sorted = np.array(y_all)[sorting_index]
plt.figure(figsize=(10,10))
plt.plot(x_sorted, y_sorted, "-", label="sorted data")
plt.xlabel("x", fontsize=16)
plt.ylabel("y", fontsize=16)
plt.legend()
plt.show()
plt.figure(figsize=(10,10))
plt.plot(x_sorted[:100], y_sorted[:100], "-", label="sorted data")
plt.xlabel("x", fontsize=16)
plt.ylabel("y", fontsize=16)
plt.legend()
plt.show()<jupyter_output><empty_output><jupyter_text>Preprocesando los datosDespués de ordenar, también es necesario eliminar los datos nulos (y valores fuera de rango)<jupyter_code>np.isnan(x_sorted)
m_nan = np.logical_or(np.isnan(x_sorted), np.isnan(y_sorted))
m_not_nan = np.logical_not(m_nan)
x = x_sorted[m_not_nan]
y = y_sorted[m_not_nan]
len(x), len(y)
x
y<jupyter_output><empty_output><jupyter_text>Ajustando un modelo simpleSi definimos el grado del polinomio, es posible ajustar los coeficientes del polinomio para que "trate de pasar" por los datos.<jupyter_code># Do a polinomial fit
N = 1
z = np.polyfit(x, y, N)
polinomio = np.poly1d(z)
polinomio(np.array([0., 1., 2.0]))
polinomio(2)
plt.figure(figsize=(16,16))
plt.plot(x, y, '-', lw=2.0, label="data")
plt.plot(x, polinomio(x),'-', lw=2.0, label="model")
plt.xlabel("x", fontsize=16)
plt.ylabel("y", fontsize=16)
plt.legend()
plt.show()
# Intentar distintos valores de N: 1, 5, 10, 50, 100
N = 50 
z = np.polyfit(x, y, N)
polinomio = np.poly1d(z)
plt.figure(figsize=(10,10))
plt.plot(x, y, '-', lw=2.0, label="data")
plt.plot(x, polinomio(x),'-', lw=2.0, label="model")
plt.xlabel("x", fontsize=16)
plt.ylabel("y", fontsize=16)
plt.legend()
plt.show()<jupyter_output><empty_output><jupyter_text>¿Qué valor debemos usar para N? ¿Cómo podemmos elegirlo *científicamente*?  Calculando el errorEl valor de error a utilizar depende del contexto del problema. Existen 2 errores habituales para este tipo de problemas de regresión:* Error Absoluto Medio - Mean Absolute Error (MAE): $$\frac{1}{n} \sum_{i=1}^n |y_i - f(x_i)|$$* Error Cuadrático Medio -Mean Squared Error (MSE): $$\frac{1}{n} \sum_{i=1}^n (y_i - f(x_i) )^2 $$<jupyter_code># Compute the error
def mae_from_model(x, y, model):
    m_nan = np.logical_or(np.isnan(x), np.isnan(y))
    m_not_nan = np.logical_not(m_nan)
    x_ = x[m_not_nan]
    y_ = y[m_not_nan]
    y_model_ = model(x_)
    mae = np.sum(np.abs(y_ - y_model_)) / len(y_)
    return mae

def mse_from_model(x, y, model):
    m_nan = np.logical_or(np.isnan(x), np.isnan(y))
    m_not_nan = np.logical_not(m_nan)
    x_ = x[m_not_nan]
    y_ = y[m_not_nan]
    y_model_ = model(x_)
    mse = np.sum((y_ - y_model_)**2) / len(y_)
    return mse<jupyter_output><empty_output><jupyter_text>Veamos cuanto error tienen los modelos anteriores<jupyter_code>N = 10
z = np.polyfit(x, y, N)
model_N = np.poly1d(z)
print("Mean Absolute Error (MAE) for N={}: {}".format(N, mae_from_model(x, y, model_N)))
print("Mean Squared Error (MSE) for N={}: {}".format(N, mse_from_model(x, y, model_N)))<jupyter_output>Mean Absolute Error (MAE) for N=10: 7.959889933575181
Mean Squared Error (MSE) for N=10: 100.09831647386287<jupyter_text>Ambos errores son dos formas válidas de medir el error. No existe una manera correcta de medir el error. Depende del contexto y del problema.En realidad, los coeficientes del polinomio se encuentran minimizando el Mean Squared Error.<jupyter_code>np.polyfit?<jupyter_output><empty_output><jupyter_text>¡Ya estábamos utilizando una forma de medir el error sin saberlo! Recapitulemos: * No sabemos a priori cuál es el grado del polinomio.* Si se fija un grado del polinomio, los coeficientes se encuentran minimizando el error cuadrático medio.Lo anterior es frecuente en todos los modelos de Machine Learning:* Los parámetros de un modelo se llaman **metaparámetros**. Son ciertos parámetros que se definen pero no forman parte de los valores que se ajustarán con los datos.* Una vez definidos los metaparámetros, se buscan los valores de los parámetros. Las librerías proporcionan métodos sencillos para ajustar un modelo específico, pero encontrar los metaparámetros resulta en general un desafío más grande.  Eligiendo el valor de NEn el caso de nuestro problema de juguete, queremos encontrar el metaparámetro $N$: el grado del polinomio.<jupyter_code>degrees = list(range(1,25))
mse = []
for N in degrees:
    model = np.poly1d(np.polyfit(x, y, N))
    mse_error = mse_from_model(x, y, model) 
    mse.append(mse_error)
    print(N, mse_error)
plt.figure(figsize=(16,8))
plt.plot(degrees, mse, 'o-', label="Train error")
plt.xlabel("x", fontsize=16)
plt.ylabel("y", fontsize=16)
plt.legend()
plt.show()<jupyter_output><empty_output><jupyter_text>A partir de lo anterior, sería razonable pensar que tenemos que tomar un polinomio suficientemente grande. Lo anterior es una clásica falacia o error de entrenamiento de modelos.***Lo que buscamos no es un modelo que explique perfectamente el pasado, sino que logre predecir razonablemente bien el futuro.***Todo polinomio o modelo extremandamente complejo logrará reproducir perfectamente los datos conocidos. La simple memorización de los resultados cumple ese objetivo. La tarea de los modelos de Machine Learning es generalizar. Como, a partir de ejemplos, es posible aprender parámetros que lograrán una predicción acertada.  Sets de entrenamiento, validación, verificación, predicciónEn el entrenamiento de modelos de Machine Learning, resulta común dividir los datos en conjuntos con distintas finalidades:* **Set de entrenamiento (Training set)**: Set utilizado para entrenar el modelo, asumiento conocidos los metaparámetros.* **Set de verificación/validación (validation set)**:  Set utilizado para evaluar el modelo y comparar metaparámetros.* **Set de testeo (test set)**: Set para estimar el error de predicción del modelo, una vez seleccionado.La división de los datos conocidos en conjuntos de entrenamiento - validación - testeo se hace en relación 60%-20%-20% o 80%-10%-10%.<jupyter_code>from sklearn.model_selection import train_test_split
x_train, x_vt, y_train, y_vt = train_test_split(x, y, test_size=0.20, random_state=42)
x_val, x_test, y_val, y_test = train_test_split(x_vt, y_vt, test_size=0.50, random_state=42)
print(x_train.shape[0], y_train.shape[0], 100*x_train.shape[0]/x.shape[0])
print(x_val.shape[0], y_val.shape[0], 100*x_val.shape[0]/x.shape[0])
print(x_test.shape[0], y_test.shape[0], 100*x_test.shape[0]/x.shape[0])
degrees = list(range(1,25))
mse_train = []
mse_test = []
values = []
for n in degrees:
    coeffs = np.polyfit(x_train, y_train, n)
    model_n = np.poly1d(coeffs)
    mse_train.append(mse_from_model(x_train, y_train, model_n))
    mse_test.append(mse_from_model(x_test, y_test, model_n))
plt.figure(figsize=(10,10))
plt.plot(degrees, mse_train,'x-', lw=2.0, label="train")
plt.plot(degrees, mse_test,'o-', lw=2.0, label="test")
plt.xlabel("N (grado polinomio)")
plt.ylabel("Mean Squared Error")
plt.ylim([0, 1.1*max(max(mse_train), max(mse_test))])
plt.xlabel("x", fontsize=16)
plt.ylabel("y", fontsize=16)
plt.legend()
plt.show()<jupyter_output><empty_output><jupyter_text>Al combinar un set de entrenamiento con un set de contraste o validación, podemos ver que aumentar el grado del polinomio no es ventajoso. De hecho y como resultaba intuitivo, resulta mejor considerar un modelo más bien simple: una recta o un relación cuadrática.En general, conviene aplicar la navaja de Occam: *En igualdad de condiciones, la explicación más sencilla suele ser la más probable.*  Entre dos modelos que tienen una capacidad predictiva similar, conviene tomar el más simple de ambos (con menos parámetros). El error del modelo podemos indicarlo considerando el conjunto de testeo y el conjunto de testeo:<jupyter_code>N = 10
coeffs_N = np.polyfit(x_train, y_train, N)
model_N = np.poly1d(coeffs_N)
print("Mean Absolute Error (MAE) for N={}: {}".format(N, mae_from_model(x_test, y_test, model_N)))
print("Mean Squared Error (MSE) for N={}: {}".format(N, mse_from_model(x_test, y_test, model_N)))<jupyter_output>Mean Absolute Error (MAE) for N=10: 7.4482684626160065
Mean Squared Error (MSE) for N=10: 86.76869229393691<jupyter_text>¿Como se comportaría el modelo en un conjunto distinto de datos?Uno de los grandes problemas que se tiene en Machine Learning es que a veces no se posee un control perfecto del dataset donde se realizará la predicción. Por ejemplo, si se trata de un modelo que trabaja con fotografías, el modelo puede haberse entrenado en fotografías de buena calidad e iluminación, pero debe trabajar además con fotografías borrosas o con baja iluminación.La única forma que un modelo funcione de la misma manera en el conjunto de datos de entrenamiento y predicción (producción) es que estos sean tan parecidos como sea posible.<jupyter_code># Train the model
N = 5
coeffs_N = np.polyfit(x_train, y_train, N)
model_N = np.poly1d(coeffs_N)
# Get new data
x_new, y_new = get_data(N_data=100, xmin=100, xmax=200)
y_pred = model_N(x_new)
print("Mean Squared Error (MSE) for N={}: {}".format(N, mse_from_model(x_new, y_pred, model_N)))
# Plot
plt.figure(figsize=(10,10))
plt.plot(x_new, y_pred, "x", lw=2.0, label="model")
plt.plot(x_new, y_new, "o", lw=2.0, label="true")
plt.xlabel("x", fontsize=16)
plt.ylabel("y", fontsize=16)
plt.legend()
plt.show()<jupyter_output>Mean Squared Error (MSE) for N=5: 0.0
StarcoderdataJupyterStructuredCleanDedup