In [0]:
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

Validación

Objetivos de aprendizaje:

  • usar varios atributos, en lugar de uno solo, para mejorar aún más la eficacia de un modelo
  • depurar problemas en los datos de entrada del modelo
  • usar un conjunto de datos de prueba para comprobar si un modelo está realizando un sobreajuste en los datos de validación

Al igual que en los ejercicios anteriores, trabajamos con el conjunto de datos de viviendas en California para intentar predecir median_house_value a nivel de manzana en la ciudad a partir de los datos del censo de 1990.

Preparación

Primero, carguemos y preparemos nuestros datos. Esta vez, trabajaremos con varios atributos, de manera que usaremos un sistema modular en la lógica para procesar los atributos:


In [0]:
from __future__ import print_function

import math

from IPython import display
from matplotlib import cm
from matplotlib import gridspec
from matplotlib import pyplot as plt
import numpy as np
import pandas as pd
from sklearn import metrics
import tensorflow as tf
from tensorflow.python.data import Dataset

tf.logging.set_verbosity(tf.logging.ERROR)
pd.options.display.max_rows = 10
pd.options.display.float_format = '{:.1f}'.format

california_housing_dataframe = pd.read_csv("https://download.mlcc.google.com/mledu-datasets/california_housing_train.csv", sep=",")

# california_housing_dataframe = california_housing_dataframe.reindex(
#     np.random.permutation(california_housing_dataframe.index))

In [0]:
def preprocess_features(california_housing_dataframe):
  """Prepares input features from California housing data set.

  Args:
    california_housing_dataframe: A Pandas DataFrame expected to contain data
      from the California housing data set.
  Returns:
    A DataFrame that contains the features to be used for the model, including
    synthetic features.
  """
  selected_features = california_housing_dataframe[
    ["latitude",
     "longitude",
     "housing_median_age",
     "total_rooms",
     "total_bedrooms",
     "population",
     "households",
     "median_income"]]
  processed_features = selected_features.copy()
  # Create a synthetic feature.
  processed_features["rooms_per_person"] = (
    california_housing_dataframe["total_rooms"] /
    california_housing_dataframe["population"])
  return processed_features

def preprocess_targets(california_housing_dataframe):
  """Prepares target features (i.e., labels) from California housing data set.

  Args:
    california_housing_dataframe: A Pandas DataFrame expected to contain data
      from the California housing data set.
  Returns:
    A DataFrame that contains the target feature.
  """
  output_targets = pd.DataFrame()
  # Scale the target to be in units of thousands of dollars.
  output_targets["median_house_value"] = (
    california_housing_dataframe["median_house_value"] / 1000.0)
  return output_targets

Para el conjunto de entrenamiento, elegiremos los primeros 12,000 ejemplos del total de 17,000.


In [0]:
training_examples = preprocess_features(california_housing_dataframe.head(12000))
training_examples.describe()

In [0]:
training_targets = preprocess_targets(california_housing_dataframe.head(12000))
training_targets.describe()

Para el conjunto de validación, elegiremos los últimos 5,000 ejemplos del total de 17,000.


In [0]:
validation_examples = preprocess_features(california_housing_dataframe.tail(5000))
validation_examples.describe()

In [0]:
validation_targets = preprocess_targets(california_housing_dataframe.tail(5000))
validation_targets.describe()

Tarea 1: Examina los datos

Observemos los datos que aparecen más arriba. Tenemos 9 atributos de entrada que podemos usar.

Haz una lectura rápida de la tabla de valores. ¿Se ve todo bien? Observa cuántos problemas puedes detectar. No te preocupes si no tienes formación en estadística; el sentido común te llevará lejos.

Una vez que hayas tenido la oportunidad de revisar los datos por tu cuenta, comprueba la solución para obtener otras ideas sobre cómo verificar los datos.

Solución

Haz clic más abajo para conocer la solución.

Comprobemos nuestros datos con algunas explicaciones de punto de referencia:

  • Para algunos valores, como median_house_value, podemos ver si estos valores están dentro de rangos razonables (teniendo en cuenta que se trata de datos de 1990, no actuales).

  • Para otros valores, como latitude y longitude, podemos hacer una comprobación rápida para ver si se alinean con los valores esperados a partir de una búsqueda rápida en Google.

Si observas más detenidamente, es posible que detectes algunas singularidades:

  • median_income está en una escala de aproximadamente 3 a 15. No está claro a qué se refiere esta escala. ¿Tal vez sea una escala logarítmica? No está documentada en ninguna parte; todo lo que podemos suponer es que los valores más altos corresponden a ingresos más altos.

  • El median_house_value máximo es 500,001. Esto parece un límite artificial de algún tipo.

  • Nuestro atributo rooms_per_person generalmente está en una escala razonable, con un valor percentil 75º de aproximadamente 2. Pero hay algunos valores muy altos, como 18 o 55, los cuales pueden indicar cierto grado de daño en los datos.

Por el momento, usaremos estos atributos como están. Pero esperamos que estos tipos de ejemplos puedan ayudarte a desarrollar algo de intuición sobre cómo comprobar los datos que llegan a ti de una fuente desconocida.

Tarea 2: Representa latitud/longitud frente a valor mediano de las casas

Observemos detenidamente dos atributos en particular: latitude y longitude. Estas son las coordenadas geográficas de la manzana en cuestión.

Podría parecer una buena visualización; representemos latitude y longitude, y usemos colores para mostrar el median_house_value.


In [0]:
plt.figure(figsize=(13, 8))

ax = plt.subplot(1, 2, 1)
ax.set_title("Validation Data")

ax.set_autoscaley_on(False)
ax.set_ylim([32, 43])
ax.set_autoscalex_on(False)
ax.set_xlim([-126, -112])
plt.scatter(validation_examples["longitude"],
            validation_examples["latitude"],
            cmap="coolwarm",
            c=validation_targets["median_house_value"] / validation_targets["median_house_value"].max())

ax = plt.subplot(1,2,2)
ax.set_title("Training Data")

ax.set_autoscaley_on(False)
ax.set_ylim([32, 43])
ax.set_autoscalex_on(False)
ax.set_xlim([-126, -112])
plt.scatter(training_examples["longitude"],
            training_examples["latitude"],
            cmap="coolwarm",
            c=training_targets["median_house_value"] / training_targets["median_house_value"].max())
_ = plt.plot()

Espera un momento… Esto nos debería haber dado un hermoso mapa del estado de California, con el color rojo para representar las áreas costosas, como San Francisco y Los Ángeles.

El conjunto de entrenamiento sí lo representa, en comparación con un mapa real, pero claramente el conjunto de validación no.

Regresa y vuelve a observar los datos de la Tarea 1.

¿Puedes ver otras diferencias en las distribuciones de los atributos u objetivos entre los datos de validación y los de entrenamiento?

Solución

Haz clic más abajo para conocer la solución.

Al observar las tablas de las estadísticas de resumen anteriores, fácilmente te preguntarás cómo se puede hacer una comprobación útil de los datos. ¿Cuál es el valor del percentil 75º para total_rooms por manzana?

El punto clave que se debe observar es que, para cualquier atributo o columna específica, la distribución de los valores entre las divisiones de entrenamiento y validación debe ser casi igual.

El hecho de que esto no sea así es una verdadera preocupación y demuestra que es probable que tengamos una falla en la forma en que se creó nuestra división de entrenamiento y validación.

Tarea 3: Regresa al código de procesamiento previo e importación de datos para ver si se detectan errores

Si detectas errores, soluciónalos. No pases más de un minuto o dos observando. Si no pueden encontrar el error, comprueba la solución.

Cuando hayas encontrado y solucionado el problema, vuelve a ejecutar la celda de representación de latitude/longitude anterior y confirma si las comprobaciones de estado dan mejores resultados.

Por cierto, esto representa una lección importante.

Con frecuencia, la depuración en AA es depuración de datos en lugar de depuración de código.

Si los datos no son correctos, incluso el código de AA más avanzado será incapaz de resolver los problemas.

Solución

Haz clic más abajo para conocer la solución.

Observa cómo se aleatorizan los datos cuando se leen.

Si no aleatorizamos los datos correctamente antes de crear las divisiones de entrenamiento y validación, es posible que tengamos problemas si los datos se proporcionan en un cierto orden; ese parece ser el caso aquí.

Tarea 4: Entrena y evalua un modelo

Dedica alrededor de 5 minutos a probar diferentes configuraciones de hiperparámetros. Intenta obtener el mejor rendimiento de validación posible. A continuación, entrenaremos un regresor lineal con todos los atributos del conjunto de datos y veremos qué tan bien se desempeña. Definamos la misma función de entrada que usamos anteriormente para cargar los datos en un modelo de TensorFlow.


In [0]:
def my_input_fn(features, targets, batch_size=1, shuffle=True, num_epochs=None):
    """Trains a linear regression model of multiple features.
  
    Args:
      features: pandas DataFrame of features
      targets: pandas DataFrame of targets
      batch_size: Size of batches to be passed to the model
      shuffle: True or False. Whether to shuffle the data.
      num_epochs: Number of epochs for which data should be repeated. None = repeat indefinitely
    Returns:
      Tuple of (features, labels) for next data batch
    """
    
    # Convert pandas data into a dict of np arrays.
    features = {key:np.array(value) for key,value in dict(features).items()}                                           
 
    # Construct a dataset, and configure batching/repeating.
    ds = Dataset.from_tensor_slices((features,targets)) # warning: 2GB limit
    ds = ds.batch(batch_size).repeat(num_epochs)
    
    # Shuffle the data, if specified.
    if shuffle:
      ds = ds.shuffle(10000)
    
    # Return the next batch of data.
    features, labels = ds.make_one_shot_iterator().get_next()
    return features, labels

Como ahora estamos trabajando con varios atributos de entrada, usemos un sistema modular en nuestro código para configurar columnas de atributos en un atributo independiente. (Por ahora, este código es bastante simple, porque todos nuestros atributos son numéricos. Sin embargo, desarrollaremos mejor este código a medida que usemos otros tipos de atributos en ejercicios futuros).


In [0]:
def construct_feature_columns(input_features):
  """Construct the TensorFlow Feature Columns.

  Args:
    input_features: The names of the numerical input features to use.
  Returns:
    A set of feature columns
  """ 
  return set([tf.feature_column.numeric_column(my_feature)
              for my_feature in input_features])

A continuación, avancemos y completemos el código de train_model() más abajo para configurar las funciones de entrada y calcular las predicciones.

NOTA: Es correcto hacer referencia al código de los ejercicios anteriores, pero debes asegurarte de invocar predict() en los conjuntos de datos adecuados.

Compara las pérdidas en los datos de entrenamiento y los datos de validación. Con un solo atributo sin procesar, nuestro mejor error de la raíz cuadrada de la media (RMSE) fue de alrededor de 180.

Observa cuánto mejora el desempeño ahora que podemos usar atributos múltiples.

Comprueba los datos con alguno de los métodos que observamos antes. Entre estos se incluyen los siguientes:

  • Comparación de distribuciones de predicciones y valores objetivo reales

  • Creación de una representación de dispersión de predicciones frente a valores objetivo

  • Creación de dos representaciones de dispersión de datos de validación con latitude y longitude:

    • Una representación asigna el color al median_house_value objetivo real.
    • Una segunda representación asigna el color al median_house_value predicho para la comparación en paralelo.

In [0]:
def train_model(
    learning_rate,
    steps,
    batch_size,
    training_examples,
    training_targets,
    validation_examples,
    validation_targets):
  """Trains a linear regression model of multiple features.
  
  In addition to training, this function also prints training progress information,
  as well as a plot of the training and validation loss over time.
  
  Args:
    learning_rate: A `float`, the learning rate.
    steps: A non-zero `int`, the total number of training steps. A training step
      consists of a forward and backward pass using a single batch.
    batch_size: A non-zero `int`, the batch size.
    training_examples: A `DataFrame` containing one or more columns from
      `california_housing_dataframe` to use as input features for training.
    training_targets: A `DataFrame` containing exactly one column from
      `california_housing_dataframe` to use as target for training.
    validation_examples: A `DataFrame` containing one or more columns from
      `california_housing_dataframe` to use as input features for validation.
    validation_targets: A `DataFrame` containing exactly one column from
      `california_housing_dataframe` to use as target for validation.
      
  Returns:
    A `LinearRegressor` object trained on the training data.
  """

  periods = 10
  steps_per_period = steps / periods
  
  # Create a linear regressor object.
  my_optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
  my_optimizer = tf.contrib.estimator.clip_gradients_by_norm(my_optimizer, 5.0)
  linear_regressor = tf.estimator.LinearRegressor(
      feature_columns=construct_feature_columns(training_examples),
      optimizer=my_optimizer
  )
  
  # 1. Create input functions.
  training_input_fn = # YOUR CODE HERE
  predict_training_input_fn = # YOUR CODE HERE
  predict_validation_input_fn = # YOUR CODE HERE
  
  # Train the model, but do so inside a loop so that we can periodically assess
  # loss metrics.
  print("Training model...")
  print("RMSE (on training data):")
  training_rmse = []
  validation_rmse = []
  for period in range (0, periods):
    # Train the model, starting from the prior state.
    linear_regressor.train(
        input_fn=training_input_fn,
        steps=steps_per_period,
    )
    # 2. Take a break and compute predictions.
    training_predictions = # YOUR CODE HERE
    validation_predictions = # YOUR CODE HERE
    
    # Compute training and validation loss.
    training_root_mean_squared_error = math.sqrt(
        metrics.mean_squared_error(training_predictions, training_targets))
    validation_root_mean_squared_error = math.sqrt(
        metrics.mean_squared_error(validation_predictions, validation_targets))
    # Occasionally print the current loss.
    print("  period %02d : %0.2f" % (period, training_root_mean_squared_error))
    # Add the loss metrics from this period to our list.
    training_rmse.append(training_root_mean_squared_error)
    validation_rmse.append(validation_root_mean_squared_error)
  print("Model training finished.")

  # Output a graph of loss metrics over periods.
  plt.ylabel("RMSE")
  plt.xlabel("Periods")
  plt.title("Root Mean Squared Error vs. Periods")
  plt.tight_layout()
  plt.plot(training_rmse, label="training")
  plt.plot(validation_rmse, label="validation")
  plt.legend()

  return linear_regressor

In [0]:
linear_regressor = train_model(
    # TWEAK THESE VALUES TO SEE HOW MUCH YOU CAN IMPROVE THE RMSE
    learning_rate=0.00001,
    steps=100,
    batch_size=1,
    training_examples=training_examples,
    training_targets=training_targets,
    validation_examples=validation_examples,
    validation_targets=validation_targets)

Solución

Haz clic más abajo para conocer la solución.


In [0]:
def train_model(
    learning_rate,
    steps,
    batch_size,
    training_examples,
    training_targets,
    validation_examples,
    validation_targets):
  """Trains a linear regression model of multiple features.
  
  In addition to training, this function also prints training progress information,
  as well as a plot of the training and validation loss over time.
  
  Args:
    learning_rate: A `float`, the learning rate.
    steps: A non-zero `int`, the total number of training steps. A training step
      consists of a forward and backward pass using a single batch.
    batch_size: A non-zero `int`, the batch size.
    training_examples: A `DataFrame` containing one or more columns from
      `california_housing_dataframe` to use as input features for training.
    training_targets: A `DataFrame` containing exactly one column from
      `california_housing_dataframe` to use as target for training.
    validation_examples: A `DataFrame` containing one or more columns from
      `california_housing_dataframe` to use as input features for validation.
    validation_targets: A `DataFrame` containing exactly one column from
      `california_housing_dataframe` to use as target for validation.
      
  Returns:
    A `LinearRegressor` object trained on the training data.
  """

  periods = 10
  steps_per_period = steps / periods
  
  # Create a linear regressor object.
  my_optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate)
  my_optimizer = tf.contrib.estimator.clip_gradients_by_norm(my_optimizer, 5.0)
  linear_regressor = tf.estimator.LinearRegressor(
      feature_columns=construct_feature_columns(training_examples),
      optimizer=my_optimizer
  )
  
  # Create input functions.
  training_input_fn = lambda: my_input_fn(
      training_examples, 
      training_targets["median_house_value"], 
      batch_size=batch_size)
  predict_training_input_fn = lambda: my_input_fn(
      training_examples, 
      training_targets["median_house_value"], 
      num_epochs=1, 
      shuffle=False)
  predict_validation_input_fn = lambda: my_input_fn(
      validation_examples, validation_targets["median_house_value"], 
      num_epochs=1, 
      shuffle=False)

  # Train the model, but do so inside a loop so that we can periodically assess
  # loss metrics.
  print("Training model...")
  print("RMSE (on training data):")
  training_rmse = []
  validation_rmse = []
  for period in range (0, periods):
    # Train the model, starting from the prior state.
    linear_regressor.train(
        input_fn=training_input_fn,
        steps=steps_per_period,
    )
    # Take a break and compute predictions.
    training_predictions = linear_regressor.predict(input_fn=predict_training_input_fn)
    training_predictions = np.array([item['predictions'][0] for item in training_predictions])
    
    validation_predictions = linear_regressor.predict(input_fn=predict_validation_input_fn)
    validation_predictions = np.array([item['predictions'][0] for item in validation_predictions])
    
    
    # Compute training and validation loss.
    training_root_mean_squared_error = math.sqrt(
        metrics.mean_squared_error(training_predictions, training_targets))
    validation_root_mean_squared_error = math.sqrt(
        metrics.mean_squared_error(validation_predictions, validation_targets))
    # Occasionally print the current loss.
    print("  period %02d : %0.2f" % (period, training_root_mean_squared_error))
    # Add the loss metrics from this period to our list.
    training_rmse.append(training_root_mean_squared_error)
    validation_rmse.append(validation_root_mean_squared_error)
  print("Model training finished.")

  # Output a graph of loss metrics over periods.
  plt.ylabel("RMSE")
  plt.xlabel("Periods")
  plt.title("Root Mean Squared Error vs. Periods")
  plt.tight_layout()
  plt.plot(training_rmse, label="training")
  plt.plot(validation_rmse, label="validation")
  plt.legend()

  return linear_regressor

In [0]:
linear_regressor = train_model(
    learning_rate=0.00003,
    steps=500,
    batch_size=5,
    training_examples=training_examples,
    training_targets=training_targets,
    validation_examples=validation_examples,
    validation_targets=validation_targets)

Tarea 5: Evalua los datos de prueba

En la celda a continuación, carga los datos de prueba y evalúa tu modelo con ellos.

Hemos realizado mucha iteración en nuestros datos de validación. Asegurémonos de no haber sobreajustado las peculiaridades de esa muestra en particular.

El conjunto de datos de prueba está ubicado aquí.

¿Cómo se compara el rendimiento de la prueba con el rendimiento de la validación? ¿Qué indica esto sobre el rendimiento de la generalización de tu modelo?


In [0]:
california_housing_test_data = pd.read_csv("https://download.mlcc.google.com/mledu-datasets/california_housing_test.csv", sep=",")
#
# YOUR CODE HERE
#

Solución

Haz clic más abajo para conocer la solución.


In [0]:
california_housing_test_data = pd.read_csv("https://download.mlcc.google.com/mledu-datasets/california_housing_test.csv", sep=",")

test_examples = preprocess_features(california_housing_test_data)
test_targets = preprocess_targets(california_housing_test_data)

predict_test_input_fn = lambda: my_input_fn(
      test_examples, 
      test_targets["median_house_value"], 
      num_epochs=1, 
      shuffle=False)

test_predictions = linear_regressor.predict(input_fn=predict_test_input_fn)
test_predictions = np.array([item['predictions'][0] for item in test_predictions])

root_mean_squared_error = math.sqrt(
    metrics.mean_squared_error(test_predictions, test_targets))

print("Final RMSE (on test data): %0.2f" % root_mean_squared_error)