<jupyter_start><jupyter_text>**The Boston Housing Price dataset** Con esta base de datos, nuestro objetivo será el de predecir el precio promedio de casas de un suburbio de Boston de mediados de los 70's, de acuerdo a características tales como la tasa de crímines, impuestos, concentración de óxido nítrico, etcétera.**Un problema de predicción consiste en obtener valores en una escala continua.** Para entender este concepto, imaginemos que nos piden que cada uno de nosotros dé su predicción acerca de la temperatura actual. Algún compañero podría decir: 25°C, alguien más podría estimar que la temperatura es de 19.5°, otro hará una estimación de 22°C, etc. El punto es que las predicciones pueden tener cualquier valor, no nos estamos limitando a un rango o valor particular, puede ser un valor entero o un valor con decimales. Este comportamiento representa una caso de predicción de valores en una escala continua. En el caso del problema del Boston Housing Price dataset, sólo se va a predecir un valor por cada casa, en este caso, el precio de cada casa en cuestión. En machine learning, la tarea de predecir valores en una escala continua se conoce como **regresión**.La base de datos Boston Housing Price se compone únicamente de 506 instancias, de las cuales, 404 corresponden al conjunto de entrenamiento y 102 al conjunto de prueba. Otra propiedad peculiar de esta base de datos, consiste en que los atributos de los datos de entrada están definidos en diferentes escalas. Por ejemplo, algunas características tienes valores entre 0 y 1, otras entre 1 y 12, otras entre 0 y 100, etcétera.Keras tiene precargada esta base de datos:<jupyter_code>from keras.datasets import boston_housing

(train_data, train_targets), (test_data, test_targets) =  boston_housing.load_data()
train_data.shape
test_data.shape<jupyter_output><empty_output><jupyter_text>Las 13 características o atributos de los datos de entrada, se enlistan a continuación: 1. Per capita crime rate.2. Proportion of residential land zoned for lots over 25,000 square feet.3. Proportion of non-retail business acres per town.4. Charles River dummy variable (= 1 if tract bounds river; 0 otherwise).5. Nitric oxides concentration (parts per 10 million).6. Average number of rooms per dwelling.7. Proportion of owner-occupied units built prior to 1940.8. Weighted distances to five Boston employment centres.9. Index of accessibility to radial highways.10. Full-value property-tax rate per $10,000.11. Pupil-teacher ratio by town.12. 1000 * (Bk - 0.63) ** 2 where Bk is the proportion of Black people by town.13. % lower status of the population.<jupyter_code>train_data[9] #Atributos de la casa con el índice 9 del conjunto de entrenamiento
len(train_data[9]) #Número de atributos de la casa con el índice 9 del conjunto de entrenamiento<jupyter_output><empty_output><jupyter_text>Las etiquetas de la base de datos, consisten en precios promedios de casas, fijadas en miles de dólares.<jupyter_code>train_targets[9] #Precio (en miles de dólares) de la casa con el índice 9 del conjunto de entrenamiento<jupyter_output><empty_output><jupyter_text>**Preprocesamiento de los datos** Debido a que los atributos de los datos están expresados en distintas escalas, es conveniente estandarizar estos valores para facilitarle a la red neuronal el proceso de ajuste de los pesos. Para lograrlo, la estandarización se llevará a cabo por atributo, de tal forma que a cada columna de la matriz de entrada, se le restará su promedio y luego se le divirá entre la desviación estándar, generando así atributos centrados en cero y con desviación estándar de 1.<jupyter_code>#sklearn, formalmente conocida como Scikit-learn, es una librería de Python enfocada en machine learning.
#La clase StandardScaler hace que los atributos estén centrados en cero y que tengan una desviación estándar de 1.
from sklearn.preprocessing import StandardScaler 
#Instanciamos un objeto de la clase StandardScaler.
stdsc = StandardScaler() 
#Obtenemos los parámetros para la estandarización con base al conjunto de entrenamiento y luego, procedemos a estandarizar dicho conjunto con los
#parámetros generados.
train_data_std =  stdsc.fit_transform(train_data) #El método fit_transform se aplica solamente al conjunto de entrenamiento
#Estandarizamos el conjunto de prueba con los parámetros que se obtuvieron a partir del conjunto de entrenamiento.
test_data_std = stdsc.transform(test_data)
train_data_std
test_data_std<jupyter_output><empty_output><jupyter_text>**Construcción de la red neuronal**<jupyter_code>from keras import models
from keras import layers

#Para este problema, definiremos el modelo dentro de una función, ya que, como veremos más adelante,
#estaremos utilizando el mismo modelo en múltiples ocasiones.
def build_model():
    model = models.Sequential()
    model.add(layers.Dense(64, activation='relu',
                           input_shape=(train_data.shape[1],)))
    model.add(layers.Dense(64, activation='relu'))
    model.add(layers.Dense(1))
    model.compile(optimizer='rmsprop', loss='mse', metrics=['mae']) #MSE = Mean Square Error / MAE = Mean Absolute Error
    return model<jupyter_output><empty_output><jupyter_text>**Aplicando validación cruzada** A diferencia de la manera en la que implementamos la fase de validación en el problema referente a la base de datos IMDB, en esta ocasión, debido a que la base de datos Boston Housing Price se compone de pocas instancias, emplearemos el enfoque de validación cruzada.<jupyter_code>import numpy as np

k = 4 # En total, el conjunto de entrenamiento se dividirá en 4 lotes o folds, y por lo tanto, se habrán de generar 4 modelos.
num_val_samples = len(train_data_std) // k # Definimos el número de instancias que constituirán cada fold
num_epochs = 100 # El entrenamiento se realizará durante 100 épocas para cada modelo
all_scores = [] # La lista all_scores almacenará el valor del mae de cada unos de los cuatro modelos
for i in range(k):
    print('Procesamiento de fold #', i)
    #i = 0
    # Para cada fold:
    # Generamos su conjunto de validación a partir del conjunto de entrenamiento original
    val_data = train_data_std[i * num_val_samples: (i + 1) * num_val_samples]
    # val_data = train_data_std[0 * 101: (0 + 1) * 101]
    # val_data = train_data_std[0 : 1 *101] 
    # val_data = train_data_std[0: 101] 
    val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples]
    # val_targets = train_targets[0: 101]

    # Generamos su conjunto de entrenamiento a partir del conjunto de entrenamiento original
    partial_train_data = np.concatenate(
        [train_data_std[:i * num_val_samples], # = [train_data_std[:0 * 101] = [train_data_std[:0]
         train_data_std[(i + 1) * num_val_samples:]], # = train_data_std[(0 + 1) * 101:] = train_data_std[1 * 101:] = train_data_std[101:]
        axis=0)                                                         #np.concatenate([train_data_std[:0], train_data_std[101:])                                                      #train_data_std[303:]
    partial_train_targets = np.concatenate(
        [train_targets[:i * num_val_samples],
         train_targets[(i + 1) * num_val_samples:]], #np.concatenate([train_data_std[:0], train_data_std[101:])
        axis=0)
    #-----------------------------------------------------------------------------------

    #Mandamos llamar al modelo que previamente compilamos
    model = build_model()
    # Entrenamos el modelo (el argumento verbose=0, indica que el entrenamiento se realizará en modo silencioso)
    model.fit(partial_train_data, partial_train_targets,
              epochs=num_epochs, batch_size=1, verbose=0)
    # Procedemos a evaluar el desempeño del modelo en el conjunto de validación
    val_mse, val_mae = model.evaluate(val_data, val_targets, verbose=0)
    # Debido a que la evaluación se realizará en k-folds, utilizamos la lista all_scores para
    # almacenar el resultado de la evaluación de cada fold.
    all_scores.append(val_mae)
all_scores # Desplegamos el valor del mae de cada fold<jupyter_output><empty_output><jupyter_text>Observemos que existe cierta discrepancia en los resultados de validación anteriores, y para solventar esta situación, lo más apropiado es obtener un promedio de estos resultados.<jupyter_code>np.mean(all_scores) # Obtenemos el promedio del mae, que es el resultado que nos interesa<jupyter_output><empty_output><jupyter_text>Ahora, procederemos a entrenar el modelo durante 500 épocas. Además, daremos seguimiento al desempeño de cada modelo en las 500 épocas.<jupyter_code>num_epochs = 100
all_mae_histories = []
for i in range(k):
    print('Procesamiento de fold #', i)
    
    # Para cada fold:
    # Generamos su conjunto de validación a partir del conjunto de entrenamiento original
    val_data = train_data_std[i * num_val_samples: (i + 1) * num_val_samples]
    val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples]

    # Generamos su conjunto de entrenamiento a partir del conjunto de entrenamiento original
    partial_train_data = np.concatenate(
        [train_data_std[:i * num_val_samples],
         train_data_std[(i + 1) * num_val_samples:]],
        axis=0)
    partial_train_targets = np.concatenate(
        [train_targets[:i * num_val_samples],
         train_targets[(i + 1) * num_val_samples:]],
        axis=0)
    #-----------------------------------------------------------------------------------
    
    # Mandamos llamar el modelo que previamente compilamos
    model = build_model()
    # Entrenamos el modelo y almacenamos su rendimiento (mae) en el conjunto de validación durante 500 épocas.
    history = model.fit(partial_train_data, partial_train_targets,
                        validation_data=(val_data, val_targets),
                        epochs=num_epochs, batch_size=1, verbose=0)
    mae_history = history.history['val_mae']

    # Debido a que la evaluación se realizará en k-folds, utilizamos la lista all_mae_histories para
    # almacenar el resultado de la evaluación de cada fold durante 500 épocas.
    all_mae_histories.append(mae_history)
all_mae_histories[0]
len(all_mae_histories[3])<jupyter_output><empty_output><jupyter_text>Por cada época, calculamos el promedio del mae de cada fold:<jupyter_code>average_mae_history = [
    np.mean([x[i] for x in all_mae_histories]) for i in range(num_epochs)]
    # i = 0
      # Indica que tenemos que extrar el desempeño de cada uno de los cuatros modelos en la primera época, para luego promediar estos cuatro valores
      # y así obtener un promedio del rendimiento de los cuatro modelos en la primera época.
    # i = 1
      # Indica que tenemos que extrar el desempeño de cada uno de los cuatros modelos en la segunda época, para luego promediar estos cuatro valores
      # y así obtener un promedio del rendimiento de los cuatro modelos en la primera época.
    # i = 2
      # Indica que tenemos que extrar el desempeño de cada uno de los cuatros modelos en la tercera época, para luego promediar estos cuatro valores
      # y así obtener un promedio del rendimiento de los cuatro modelos en la primera época.
    #...<jupyter_output><empty_output><jupyter_text>Grafiquemos el resultado anterior:<jupyter_code>import matplotlib.pyplot as plt

plt.plot(range(1, len(average_mae_history) + 1), average_mae_history, 'g')
plt.xlabel('Epochs')
plt.ylabel('Validation MAE')
plt.show()<jupyter_output><empty_output><jupyter_text>Con base a las gráficas previas, podemos observar que, aproximadamente a partir de la época 50, el modelo deja de mejorar y comienza a presentarse una disminución en su rendimiento.  **Re-entrenamiento y evaluación del modelo final** Una vez que se seleccionó el modelo tomando como base su rendimiento en la validación cruzada, una práctica recomendada es volver a entrar la arquitectura con los mismos parámetros con los que se generó el modelo seleccionado y con el conjunto de entrenamieto original.<jupyter_code># Instanciamos de nueva cuenta el modelo
model = build_model()
# Entrenamos el modelo en el conjunto de entrenamiento original
model.fit(train_data_std, train_targets,
          epochs=45, batch_size=1, verbose=0)
#Evaluamos el modelo final
test_mse_score, test_mae_score = model.evaluate(test_data_std, test_targets)
test_mae_score
test_data_std.shape
model.predict(test_data_std)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.07691686844229217, 'fraction_numerical': 0.01549636803874092, 'mean_word_length': 4.132974316487158, 'pattern_counts': {'":': 0, '<': 45, '<?xml version=': 0, '>': 45, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '134712', 'license': 'MIT', 'n_tokens_mistral': 4082, 'n_tokens_neox': 3898, 'n_words': 1434, 'path': 'BostonHousingPrice_Keras.ipynb', 'repo_name': 'juanalverto/BostonHousingPrice_Keras', 'size': '12390'}