Em um problema de regressão, o objetivo é prever a saída de um valor contínuo, como um preço ou uma probabilidade. Compare com um problema de classificação, em que o objetivo é selecionar uma classe em uma lista de classes (por exemplo, quando uma imagem contém uma maçã ou uma laranja, reconhecer qual fruta está presente na imagem).
Este tutorial usa o dataset clássico Auto MPG e demonstra como criar modelos para prever a eficiência de combustível dos automóveis do fim da década de 1970 e início da década de 1980. Para fazer isso, você fornecerá aos modelos uma descrição de diversos automóveis desse período. A descrição incluir vários atributos, como cilindros, cilindradas, cavalos e peso.
Este exemplo usa a API do Keras. Para saber mais, confira os tutoriais e guias do Keras.
# 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__)
Primeiro, baixe e importe o dataset usando o pandas:
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()
O dataset contém alguns valores desconhecidos:
dataset.isna().sum()
Elimine essas linhas para simplificar este tutorial inicial:
dataset = dataset.dropna()
A coluna "Origin"
(origem) armazena uma categoria, não um número. Portanto, a próxima etapa é fazer a codificação one-hot dos valores na coluna com pd.get_dummies.
Observação: você pode configurar tf.keras.Model
para fazer esse tipo de transformação, mas isso foge do escopo deste tutorial. Confira exemplos nos tutoriais Classificar dados estruturados usando camadas de pré-processamento do Keras ou Carregar dados em CSV.
dataset['Origin'] = dataset['Origin'].map({1: 'USA', 2: 'Europe', 3: 'Japan'})
dataset = pd.get_dummies(dataset, columns=['Origin'], prefix='', prefix_sep='')
dataset.tail()
Agora, divida o dataset em um conjunto de treinamento e outro de teste. Você usará o conjunto de teste na avaliação final dos modelos.
train_dataset = dataset.sample(frac=0.8, random_state=0)
test_dataset = dataset.drop(train_dataset.index)
Confira a distribuição conjunta de alguns pares de colunas do conjunto de treinamento.
A linha superior indica que a eficiência de combustível (MPG, na sigla em inglês) é uma função de todos os outros parâmetros. As outras linhas indicam que são funções uma da outra.
sns.pairplot(train_dataset[['MPG', 'Cylinders', 'Displacement', 'Weight']], diag_kind='kde')
Confira também as estatísticas gerais. Observe como cada característica abrange um intervalo bem diferente:
train_dataset.describe().transpose()
Separa o valor alvo, o "rótulo", das características. Você treinará o modelo para prever esse rótulo.
train_features = train_dataset.copy()
test_features = test_dataset.copy()
train_labels = train_features.pop('MPG')
test_labels = test_features.pop('MPG')
Na tabela de estatísticas, é fácil ver como os intervalos de cada característica são diferentes:
train_dataset.describe().transpose()[['mean', 'std']]
É uma boa prática normalizar os recursos que usem escalas e intervalos diferentes.
Um motivos dessa importância é porque as características são multiplicadas pelos pesos do modelo. Portanto, a escala das saídas e a escala dos gradientes são afetadas pela escala das entradas.
Embora um modelo talvez possa convergir sem a normalização de características, ela deixa o treinamento muito mais estável.
Observação: não há vantagens em normalizar as características one-hot. Isso é feito aqui por questões de simplicidade. Confira mais detalhes de como usar as camadas de pré-processamento no guia Trabalhando com camadas de pré-processamento e no tutorial Classificar dados estruturados usando camadas de pré-processamento do Keras.
tf.keras.layers.Normalization
é uma forma simples e elegante de acrescentar a normalização de características ao seu modelo.
O primeiro passo é criar a camada:
normalizer = tf.keras.layers.Normalization(axis=-1)
Em seguida, faça a adequação do estado da camada de pré-processamento aos dados chamando Normalization.adapt
:
normalizer.adapt(np.array(train_features))
Calcule a média e a variância., depois armazene-as na camada:
print(normalizer.mean.numpy())
Quando a camada é chamada, retorna os dados de entrada, com cada característica normalizada de forma independente:
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 criar um modelo de rede neural profunda, comece com a regressão linear, usando uma ou várias variáveis.
Comece com uma regressão linear com uma única variável para prever 'MPG'
(milhas por galão) a partir de 'Horsepower'
(cavalos).
Geralmente, fazer o treinamento de um modelo tf.keras
começa pela definição da arquitetura do modelo. Use um modelo tf.keras.Sequential
que representa uma sequência de passos.
Há dois passos no modelo de regressão linear com uma única variável:
'Horsepower'
(cavalos) usando a camada de pré-processamento tf.keras.layers.Normalization
.tf.keras.layers.Dense
).O número de entradas pode ser definido pelo argumento input_shape
ou automaticamente quando o modelo é executado pela primeira vez.
Primeiro, crie uma array NumPy composto pelas características 'Horsepower'
(cavalos). Em seguida, instancie tf.keras.layers.Normalization
e faça a adequação de seu estado aos dados de horsepower
:
horsepower = np.array(train_features['Horsepower'])
horsepower_normalizer = layers.Normalization(input_shape=[1,], axis=None)
horsepower_normalizer.adapt(horsepower)
Crie o modelo Sequential do Keras:
horsepower_model = tf.keras.Sequential([
horsepower_normalizer,
layers.Dense(units=1)
])
horsepower_model.summary()
Esse modelo preverá 'MPG'
a partir de 'Horsepower'
.
Execute o modelo não treinado para os primeiros 10 valores de 'Horsepower'. A saída não será boa, mas observe que tem o formato esperado – (10, 1)
:
horsepower_model.predict(horsepower[:10])
Após criar o modelo, configure o procedimento de treinamento usando o método Model.compile
do Keras. Os argumentos mais importantes a serem compilados são loss
(perda) e optimizer
(otimizador), já que definem o que será otimizado (mean_absolute_error
) e como (usando tf.keras.optimizers.Adam
).
horsepower_model.compile(
optimizer=tf.keras.optimizers.Adam(learning_rate=0.1),
loss='mean_absolute_error')
Use Model.fit
do Keras para executar o treinamento com 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)
Visualize o progresso de treinamento do modelo usando as estatísticas armazenadas no objeto history
(histórico):
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)
Colete os resultados para o conjunto de teste para uso posterior:
test_results = {}
test_results['horsepower_model'] = horsepower_model.evaluate(
test_features['Horsepower'],
test_labels, verbose=0)
Como é uma regressão com uma única variável, é fácil ver as previsões do modelo como uma função da 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)
Você pode usar uma configuração quase idêntica para fazer previsões com base em diversas entradas. Esse modelo ainda faz a mesma transformação linear $y = mx+b$$y = mx+b$, mas $m$$m$ é uma matriz, e $x$$x$ é um vetor.
Crie novamente um modelo Sequential do Keras com dois passos, sendo que a primeira camada é o normalizer
(normalizador), (tf.keras.layers.Normalization(axis=-1)
) que você definiu anteriormente e adaptou para todo o dataset:
linear_model = tf.keras.Sequential([
normalizer,
layers.Dense(units=1)
])
Quando você faz uma chamada a Model.predict
para um lote de entradas, são produzidas saídasunits=1
para cada exemplo:
linear_model.predict(train_features[:10])
Quando você faz uma chamada ao modelo, suas matrizes de peso são construídas. Verifique se os pesos de kernel
(o $m$$m$ em $y=mx+b$$y=mx+b$) têm um formato igual a (9, 1)
:
linear_model.layers[1].kernel
Configure o modelo com Model.compile
do Keras e faça o treinamento com Model.fit
com 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)
Ao usar todas as entradas nesse modelo de regressão, conseguimos um erro de treinamento e validação bem menor do que horsepower_model
, que tinha uma entrada:
plot_loss(history)
Colete os resultados para o conjunto de teste para uso posterior:
test_results['linear_model'] = linear_model.evaluate(
test_features, test_labels, verbose=0)
Na seção anterior, você implementou dois modelos lineares para entrada única ou diversas entradas.
Agora, você implementará modelos de DNN com uma entrada ou diversas entradas.
O código é basicamente o mesmo, exceto que o modelo é expandido, com a inclusão de algumas camadas não lineares "ocultas". O termo "ocultas" significa apenas que elas não estão ligadas diretamente às entradas ou às saídas.
Esses modelos conterão algumas camadas a mais do que o modelo linear:
horsepower_normalizer
para um modelo com uma entrada e normalizer
para um modelo com várias entradas).Dense
não lineares e ocultas, com a função de ativação ReLU (relu
) não linear.Dense
com uma única saída.Os dois modelos usarão o mesmo procedimento de treinamento, então o método compile
é incluído na função build_and_compile_model
abaixo.
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
Crie um modelo de DNN com apenas 'Horsepower'
(cavalos) como entrada e horsepower_normalizer
(definido anteriormente) como a camada de normalização:
dnn_horsepower_model = build_and_compile_model(horsepower_normalizer)
Esse modelo tem bem mais parâmetros que podem ser treinados do que os modelos lineares:
dnn_horsepower_model.summary()
Treine o modelo com Model.fit
do Keras:
%%time
history = dnn_horsepower_model.fit(
train_features['Horsepower'],
train_labels,
validation_split=0.2,
verbose=0, epochs=100)
Esse modelo tem um desempenho um pouco melhor do que o modelo horsepower_model
linear com uma única entrada:
plot_loss(history)
Se você plotar as previsões como função de 'Horsepower'
(cavalos), notará como esse modelo tem vantagens sobre a não linearidade fornecida pelas camadas ocultas:
x = tf.linspace(0.0, 250, 251)
y = dnn_horsepower_model.predict(x)
plot_horsepower(x, y)
Colete os resultados para o conjunto de teste para uso posterior:
test_results['dnn_horsepower_model'] = dnn_horsepower_model.evaluate(
test_features['Horsepower'], test_labels,
verbose=0)
Repita o processo anterior usando todas as entradas. O desempenho do modelo aumenta um pouco para o dataset de validação.
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)
Colete os resultados para o conjunto de teste:
test_results['dnn_model'] = dnn_model.evaluate(test_features, test_labels, verbose=0)
Como todos os modelos foram treinados, você pode conferir o desempenho de seus conjuntos de teste:
pd.DataFrame(test_results, index=['Mean absolute error [MPG]']).T
Esses resultados batem com o erro de validação observado durante o treinamento.
Agora, você pode fazer previsões com dnn_model
para o conjunto de testes usando Model.predict
do Keras e conferindo a perda:
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)
Parece que o modelo faz previsões razoavelmente boas.
Agora, confira a distribuição do erro:
error = test_predictions - test_labels
plt.hist(error, bins=25)
plt.xlabel('Prediction Error [MPG]')
_ = plt.ylabel('Count')
Se você estiver contente com o modelo, salve-o para uso posterior com Model.save
:
dnn_model.save('dnn_model.keras')
Se você recarregar o modelo, ele gerará uma saída 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 notebook apresentou algumas técnicas para lidar com um problema de regressão. Confira mais algumas dicas que podem ajudar:
tf.keras.losses.MeanSquaredError
) e Erro Absoluto Médio (EMA) (tf.keras.losses.MeanAbsoluteError
) são funções de perda comuns usadas para problemas de regressão. O EMA é menos sensível aos pontos fora da curva. Funções de perda diferentes são usadas para problemas de classificação.