En un problema de regresión, la meta es predecir la salida de un valor continuo, como un precio o una probabilidad. Comparemos esto con un problema de clasificación, donde la meta es seleccionar una clase de una lista de clases (por ejemplo, donde una imagen contiene una manzana o una naranja, reconocer qué fruta se muestra en la imagen).
Este tutorial usa el clásico conjunto de datos Auto MPG y demuestra cómo generar modelos para predecir la eficiencia del combustible de los automóviles de fines de 1970 y principios de 1980. Para ello, deberá brindarles a los modelos una descripción de muchos automóviles de ese período. Esta descripción incluye atributos como cilindros, desplazamiento, potencia y peso.
En este ejemplo se usa la API de Keras. (Visite los tutoriales y las guías de Keras para obtener más información).
# Use seaborn for pairplot.
!pip install -q seaborn
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
# Make NumPy printouts easier to read.
np.set_printoptions(precision=3, suppress=True)
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
print(tf.__version__)
El conjunto de datos está disponible desde el repositorio UCI Machine Learning.
Para empezar, use pandas para descargar e importar el conjunto de datos:
url = 'http://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data'
column_names = ['MPG', 'Cylinders', 'Displacement', 'Horsepower', 'Weight',
'Acceleration', 'Model Year', 'Origin']
raw_dataset = pd.read_csv(url, names=column_names,
na_values='?', comment='\t',
sep=' ', skipinitialspace=True)
dataset = raw_dataset.copy()
dataset.tail()
El conjunto de datos contiene algunos valores desconocidos:
dataset.isna().sum()
Descarte esas filas para simplificar este tutorial inicial:
dataset = dataset.dropna()
La columna "Origin"
corresponde a una categoría, no es numérica. Entonces, el siguiente paso es aplicar la codificación en un solo paso de los valores en la columna con pd.get_dummies.
Nota: Puede configurar tf.keras.Model
para que realice este tipo de transformación por usted, pero eso está fuera del alcance de este tutorial. Consulte los tutoriales Clasificar datos estructurados con las capas de preprocesamiento de Keras o Cargar datos CSV para ver ejemplos.
dataset['Origin'] = dataset['Origin'].map({1: 'USA', 2: 'Europe', 3: 'Japan'})
dataset = pd.get_dummies(dataset, columns=['Origin'], prefix='', prefix_sep='')
dataset.tail()
Ahora, divida los conjuntos de datos en un conjunto de entrenamiento y un conjunto de prueba. Usará el conjunto de prueba para la evaluación final de sus modelos.
train_dataset = dataset.sample(frac=0.8, random_state=0)
test_dataset = dataset.drop(train_dataset.index)
Revise la distribución conjunta de algunos pares de columnas del conjunto de entrenamiento.
La fila superior sugiere que la eficiencia del combustible (MPG) es una función conjunta de todos los demás parámetros. Las otras filas indican que son funciones entre sí.
sns.pairplot(train_dataset[['MPG', 'Cylinders', 'Displacement', 'Weight']], diag_kind='kde')
Comprobemos también las estadísticas generales. Observe cómo cada característica cubre un rango muy distinto:
train_dataset.describe().transpose()
Separe el valor de destino, la "etiqueta", de las características. Esta etiqueta es el valor que deberá predecir el modelo entrenado.
train_features = train_dataset.copy()
test_features = test_dataset.copy()
train_labels = train_features.pop('MPG')
test_labels = test_features.pop('MPG')
En la tabla de estadísticas se puede ver con claridad lo distintos que son los rangos de cada característica:
train_dataset.describe().transpose()[['mean', 'std']]
Resulta muy práctico normalizar características que usan diferentes escalas y rangos.
Uno de los motivos por los que esto resulta importante es que las características se multiplican por los pesos del modelo. Por lo tanto, la escala de las salidas y la escala de los gradientes se ve afectada por la escala de las entradas.
Si bien un modelo podría converger sin implementar la normalización de las características, dicha normalización le aporta más estabilidad al entrenamiento.
Nota: Normalizar las características de codificación en un solo paso no presenta ninguna ventaja, solo se hace aquí para simplificar. Si desea obtener más información sobre cómo se usan las capas de preprocesamiento, consulte la guía Trabajar con capas de preprocesamiento y el tutorial Clasificar datos estructurados con las capas de preprocesamiento de Keras.
tf.keras.layers.Normalization
presenta un método simple y directo de agregar normalización de características a su modelo.
El primer paso consiste en crear la capa:
normalizer = tf.keras.layers.Normalization(axis=-1)
Luego, debe llamar Normalization.adapt
para ajustar el estado de la capa de preprocesamiento a los datos:
normalizer.adapt(np.array(train_features))
Calcule la media y la desviación, y almacénelas en la capa:
print(normalizer.mean.numpy())
Cuando se llama a la capa, esta devuelve los datos de entrada, con cada característica normalizada de forma independiente:
first = np.array(train_features[:1])
with np.printoptions(precision=2, suppress=True):
print('First example:', first)
print()
print('Normalized:', normalizer(first).numpy())
Antes de generar un modelo de red neuronal profunda, comience con la regresión lineal y use una y varias variables.
Comience por aplicar una regresión lineal de una sola variable para predecir 'MPG'
a partir de 'Horsepower'
.
Al entrenar un modelo con tf.keras
generalmente se comienza por definir la arquitectura del modelo. Use un modelo tf.keras.Sequential
, que representa una secuencia de pasos.
Un modelo de regresión lineal de una variable consta de dos pasos:
'Horsepower'
mediante el uso de la capa de preprocesamiento tf.keras.layers.Normalization
.tf.keras.layers.Dense
).La cantidad de entradas se pueden establecer tanto mediante el argumento input_shape
como de forma automática cuando el modelo se ejecute por primera vez.
En primer lugar, cree un arreglo NumPy compuesto por las características de 'Horsepower'
. Luego, cree instancias de tf.keras.layers.Normalization
y ajuste su estado a los datos de horsepower
:
horsepower = np.array(train_features['Horsepower'])
horsepower_normalizer = layers.Normalization(input_shape=[1,], axis=None)
horsepower_normalizer.adapt(horsepower)
Generar el modelo secuencial de Keras:
horsepower_model = tf.keras.Sequential([
horsepower_normalizer,
layers.Dense(units=1)
])
horsepower_model.summary()
Este modelo puede predecir 'MPG'
a partir de 'Horsepower'
.
Ejecute el modelo sin entrenar en los primeros 10 valores de 'Horsepower'. La salida no será buena, pero observe que tiene la forma esperada de (10, 1)
:
horsepower_model.predict(horsepower[:10])
Una vez que se haya generado el modelo, configure el procedimiento de entrenamiento a través del método Model.compile
de Keras. Los argumentos más importantes para compilar son loss
y optimizer
, ya que estos definen qué se optimizará (mean_absolute_error
) y cómo (mediante el uso de tf.keras.optimizers.Adam
).
horsepower_model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=0.1),
loss='mean_absolute_error')
Use Model.fit
de Keras para ejecutar el entrenamiento durante 100 épocas:
%%time
history = horsepower_model.fit(
train_features['Horsepower'],
train_labels,
epochs=100,
# Suppress logging.
verbose=0,
# Calculate validation results on 20% of the training data.
validation_split = 0.2)
Visualice el progreso del entrenamiento del modelo con ayuda de las estadísticas almacenadas en el objeto history
:
hist = pd.DataFrame(history.history)
hist['epoch'] = history.epoch
hist.tail()
def plot_loss(history):
plt.plot(history.history['loss'], label='loss')
plt.plot(history.history['val_loss'], label='val_loss')
plt.ylim([0, 10])
plt.xlabel('Epoch')
plt.ylabel('Error [MPG]')
plt.legend()
plt.grid(True)
plot_loss(history)
Recopile los resultados del conjunto de prueba para después:
test_results = {}
test_results['horsepower_model'] = horsepower_model.evaluate(
test_features['Horsepower'],
test_labels, verbose=0)
Como se trata de una regresión lineal con una sola variable, es fácil ver las predicciones del modelo como una función de la entrada:
x = tf.linspace(0.0, 250, 251)
y = horsepower_model.predict(x)
def plot_horsepower(x, y):
plt.scatter(train_features['Horsepower'], train_labels, label='Data')
plt.plot(x, y, color='k', label='Predictions')
plt.xlabel('Horsepower')
plt.ylabel('MPG')
plt.legend()
plot_horsepower(x, y)
Puede usar una configuración prácticamente idéntica para hacer predicciones basadas en múltiples entradas. Este modelo hace lo mismo que $y = mx+b$$y = mx+b$ con la diferencia de que $m$$m$ es una matriz y $x$$x$ es un vector.
Cree un modelo secuencial de Keras nuevamente donde la primera capa sea normalizer
(tf.keras.layers.Normalization(axis=-1)
) que anteriormente ya definió y adaptó a todo el conjunto de datos:
linear_model = tf.keras.Sequential([
normalizer,
layers.Dense(units=1)
])
Cuando llama Model.predict
en un lote de entradas, produce salidas units=1
para cada ejemplo:
linear_model.predict(train_features[:10])
Cuando llama el modelo, sus matrices de peso se generarán; compruebe que los pesos kernel
(the $m$$m$ in $y=mx+b$$y=mx+b$) tengan la misma forma que (9, 1)
:
linear_model.layers[1].kernel
Configure el modelo con Keras Model.compile
y entrénelo con Model.fit
durante 100 épocas:
linear_model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=0.1),
loss='mean_absolute_error')
%%time
history = linear_model.fit(
train_features,
train_labels,
epochs=100,
# Suppress logging.
verbose=0,
# Calculate validation results on 20% of the training data.
validation_split = 0.2)
Usar todas las entradas en este modelo de regresión consigue un error de entrenamiento y validación mucho más bajo que horsepower_model
, que tenía una sola entrada:
plot_loss(history)
Recopile los resultados del conjunto de prueba para después:
test_results['linear_model'] = linear_model.evaluate(
test_features, test_labels, verbose=0)
En la sección anterior, implementó dos modelos lineales tanto con una entrada como con múltiples entradas.
Aquí, implementará modelos de DNN de una sola entrada y de múltiples entradas.
El código es básicamente el mismo, con la diferencia de que el modelo se amplió para incluir algunas capas no lineales "ocultas". Con el término "ocultas" nos referimos a que no están directamente conectadas con las entradas y las salidas.
Estos modelos contendrán algunas capas más que el modelo lineal:
horsepower_normalizer
para un modelo de una sola entrada y normalizer
para un modelo con múltiples entradas).Dense
ocultas, no lineales con la función de activación no lineal ReLU (relu
).Dense
lineal de una sola salida.Ambos modelos usarán el mismo procedimiento de entrenamiento, por lo que el método compile
se incluye en la función build_and_compile_model
a continuación.
def build_and_compile_model(norm):
model = keras.Sequential([
norm,
layers.Dense(64, activation='relu'),
layers.Dense(64, activation='relu'),
layers.Dense(1)
])
model.compile(loss='mean_absolute_error',
optimizer=tf.keras.optimizers.Adam(0.001))
return model
Cree un modelo de DNN con solo 'Horsepower'
como entrada y horsepower_normalizer
(definido anteriormente) como capa de normalización:
dnn_horsepower_model = build_and_compile_model(horsepower_normalizer)
Este modelo tiene bastantes parámetros entrenables más que el modelo lineal:
dnn_horsepower_model.summary()
Entrene el modelo con Model.fit
de Keras:
%%time
history = dnn_horsepower_model.fit(
train_features['Horsepower'],
train_labels,
validation_split=0.2,
verbose=0, epochs=100)
Este modelo tiene un rendimiento ligeramente superior al modelo lineal horsepower_model
de una sola entrada:
plot_loss(history)
Si traza estas predicciones como una función de 'Horsepower'
, notará que este modelo aprovecha la falta de linealidad que le aportan las capas ocultas:
x = tf.linspace(0.0, 250, 251)
y = dnn_horsepower_model.predict(x)
plot_horsepower(x, y)
Recopile los resultados del conjunto de prueba para después:
test_results['dnn_horsepower_model'] = dnn_horsepower_model.evaluate(
test_features['Horsepower'], test_labels,
verbose=0)
Repita el proceso anterior usando todas las entradas. El rendimiento del modelo mejora levemente con el conjunto de datos de validación.
dnn_model = build_and_compile_model(normalizer)
dnn_model.summary()
%%time
history = dnn_model.fit(
train_features,
train_labels,
validation_split=0.2,
verbose=0, epochs=100)
plot_loss(history)
Recopile los resultados del conjunto de prueba:
test_results['dnn_model'] = dnn_model.evaluate(test_features, test_labels, verbose=0)
Dado que ya ha entrenado todos los modelos, puede consultar el rendimiento del conjunto de prueba:
pd.DataFrame(test_results, index=['Mean absolute error [MPG]']).T
Estos resultados coinciden con el error de validación que se observó durante el entrenamiento.
Ahora puede hacer predicciones con dnn_model
en el conjunto de prueba usando Keras Model.predict
y revisar la pérdida:
test_predictions = dnn_model.predict(test_features).flatten()
a = plt.axes(aspect='equal')
plt.scatter(test_labels, test_predictions)
plt.xlabel('True Values [MPG]')
plt.ylabel('Predictions [MPG]')
lims = [0, 50]
plt.xlim(lims)
plt.ylim(lims)
_ = plt.plot(lims, lims)
Al parecer, el modelo predice bastante bien.
Ahora, compruebe la distribución de los errores:
error = test_predictions - test_labels
plt.hist(error, bins=25)
plt.xlabel('Prediction Error [MPG]')
_ = plt.ylabel('Count')
Si está satisfecho con el modelo, guárdelo para usarlo más tarde con Model.save
:
dnn_model.save('dnn_model.keras')
Si vuelve a cargar el modelo, le da una salida idéntica:
reloaded = tf.keras.models.load_model('dnn_model.keras')
test_results['reloaded'] = reloaded.evaluate(
test_features, test_labels, verbose=0)
pd.DataFrame(test_results, index=['Mean absolute error [MPG]']).T
Este bloc de notas introdujo algunas técnicas para hacer frente a un problema de regresión. Aquí encontrará algunos consejos más que pueden ayudarle:
tf.keras.losses.MeanSquaredError
) y el error absoluto medio (MAE) (tf.keras.losses.MeanAbsoluteError
) son funciones de pérdida comunes que se usan para problemas de regresión. MAE es menos susceptible a los valores atípicos. Para los problemas de clasificación se usan otras funciones de pérdida.