Régression de base : prédire l'efficacité énergétique

Voir sur TensorFlow.org Exécuter dans Google Colab Voir la source sur GitHub Télécharger le cahier

Dans un problème de régression , le but est de prédire la sortie d'une valeur continue, comme un prix ou une probabilité. Comparez cela avec un problème de classification , où le but est de sélectionner une classe dans une liste de classes (par exemple, lorsqu'une image contient une pomme ou une orange, en reconnaissant quel fruit est dans l'image).

Ce didacticiel utilise l'ensemble de données Auto MPG classique et montre comment créer des modèles pour prédire l'efficacité énergétique des automobiles de la fin des années 1970 et du début des années 1980. Pour ce faire, vous fournirez aux modèles une description de nombreuses automobiles de cette période. Cette description inclut des attributs tels que les cylindres, la cylindrée, la puissance et le poids.

Cet exemple utilise l'API Keras. (Visitez les didacticiels et guides Keras pour en savoir plus.)

# 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__)
2.8.0-rc1

Le jeu de données Auto MPG

L'ensemble de données est disponible dans le référentiel d'apprentissage automatique de l'UCI .

Obtenir les données

Commencez par télécharger et importer l'ensemble de données à l'aide de 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()

Nettoyer les données

L'ensemble de données contient quelques valeurs inconnues :

dataset.isna().sum()
MPG             0
Cylinders       0
Displacement    0
Horsepower      6
Weight          0
Acceleration    0
Model Year      0
Origin          0
dtype: int64

Supprimez ces lignes pour que ce didacticiel initial reste simple :

dataset = dataset.dropna()

La colonne "Origin" est catégorique et non numérique. L'étape suivante consiste donc à encoder à chaud les valeurs de la colonne avec pd.get_dummies .

dataset['Origin'] = dataset['Origin'].map({1: 'USA', 2: 'Europe', 3: 'Japan'})
dataset = pd.get_dummies(dataset, columns=['Origin'], prefix='', prefix_sep='')
dataset.tail()

Diviser les données en ensembles d'apprentissage et de test

Maintenant, divisez l'ensemble de données en un ensemble d'apprentissage et un ensemble de test. Vous utiliserez l'ensemble de test dans l'évaluation finale de vos modèles.

train_dataset = dataset.sample(frac=0.8, random_state=0)
test_dataset = dataset.drop(train_dataset.index)

Inspectez les données

Examinez la distribution conjointe de quelques paires de colonnes de l'ensemble d'apprentissage.

La rangée du haut suggère que l'efficacité énergétique (MPG) est fonction de tous les autres paramètres. Les autres rangées indiquent qu'elles sont fonction l'une de l'autre.

sns.pairplot(train_dataset[['MPG', 'Cylinders', 'Displacement', 'Weight']], diag_kind='kde')
<seaborn.axisgrid.PairGrid at 0x7f6bfdae9850>

png

Vérifions également les statistiques globales. Notez que chaque fonctionnalité couvre une plage très différente :

train_dataset.describe().transpose()

Séparer les entités des étiquettes

Séparez la valeur cible (le "libellé") des caractéristiques. Cette étiquette est la valeur que vous entraînerez le modèle à prédire.

train_features = train_dataset.copy()
test_features = test_dataset.copy()

train_labels = train_features.pop('MPG')
test_labels = test_features.pop('MPG')

Normalisation

Dans le tableau des statistiques, il est facile de voir à quel point les plages de chaque fonctionnalité sont différentes :

train_dataset.describe().transpose()[['mean', 'std']]

Il est recommandé de normaliser les entités qui utilisent différentes échelles et plages.

L'une des raisons pour lesquelles cela est important est que les caractéristiques sont multipliées par les poids du modèle. Ainsi, l'échelle des sorties et l'échelle des gradients sont affectées par l'échelle des entrées.

Bien qu'un modèle puisse converger sans normalisation des caractéristiques, la normalisation rend la formation beaucoup plus stable.

La couche de normalisation

Le tf.keras.layers.Normalization est un moyen propre et simple d'ajouter la normalisation des fonctionnalités dans votre modèle.

La première étape consiste à créer le calque :

normalizer = tf.keras.layers.Normalization(axis=-1)

Ensuite, adaptez l'état de la couche de prétraitement aux données en appelant Normalization.adapt :

normalizer.adapt(np.array(train_features))

Calculez la moyenne et la variance, et stockez-les dans la couche :

print(normalizer.mean.numpy())
[[   5.478  195.318  104.869 2990.252   15.559   75.898    0.178    0.197
     0.624]]

Lorsque la couche est appelée, elle renvoie les données d'entrée, chaque entité étant normalisée indépendamment :

first = np.array(train_features[:1])

with np.printoptions(precision=2, suppress=True):
  print('First example:', first)
  print()
  print('Normalized:', normalizer(first).numpy())
First example: [[   4.    90.    75.  2125.    14.5   74.     0.     0.     1. ]]

Normalized: [[-0.87 -1.01 -0.79 -1.03 -0.38 -0.52 -0.47 -0.5   0.78]]

Régression linéaire

Avant de construire un modèle de réseau neuronal profond, commencez par une régression linéaire en utilisant une et plusieurs variables.

Régression linéaire à une variable

Commencez par une régression linéaire à variable unique pour prédire 'MPG' à partir de 'Horsepower' .

La formation d'un modèle avec tf.keras commence généralement par la définition de l'architecture du modèle. Utilisez un modèle tf.keras.Sequential , qui représente une séquence d'étapes .

Il y a deux étapes dans votre modèle de régression linéaire à variable unique :

  • Normalisez les caractéristiques d'entrée 'Horsepower' à l'aide de la couche de prétraitement tf.keras.layers.Normalization .
  • Appliquez une transformation linéaire (\(y = mx+b\)) pour produire 1 sortie à l'aide d'un calque linéaire ( tf.keras.layers.Dense ).

Le nombre d' entrées peut être défini soit par l'argument input_shape , soit automatiquement lorsque le modèle est exécuté pour la première fois.

Tout d'abord, créez un tableau NumPy composé des fonctionnalités 'Horsepower' . Ensuite, instanciez tf.keras.layers.Normalization et adaptez son état aux données de horsepower :

horsepower = np.array(train_features['Horsepower'])

horsepower_normalizer = layers.Normalization(input_shape=[1,], axis=None)
horsepower_normalizer.adapt(horsepower)

Construisez le modèle séquentiel Keras :

horsepower_model = tf.keras.Sequential([
    horsepower_normalizer,
    layers.Dense(units=1)
])

horsepower_model.summary()
Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 normalization_1 (Normalizat  (None, 1)                3         
 ion)                                                            
                                                                 
 dense (Dense)               (None, 1)                 2         
                                                                 
=================================================================
Total params: 5
Trainable params: 2
Non-trainable params: 3
_________________________________________________________________

Ce modèle prédira 'MPG' à partir de 'Horsepower' .

Exécutez le modèle non formé sur les 10 premières valeurs de "puissance". La sortie ne sera pas bonne, mais notez qu'elle a la forme attendue de (10, 1) :

horsepower_model.predict(horsepower[:10])
array([[-1.186],
       [-0.67 ],
       [ 2.189],
       [-1.662],
       [-1.504],
       [-0.59 ],
       [-1.782],
       [-1.504],
       [-0.392],
       [-0.67 ]], dtype=float32)

Une fois le modèle construit, configurez la procédure d'entraînement à l'aide de la méthode Keras Model.compile . Les arguments les plus importants à compiler sont le loss et l' optimizer , car ceux-ci définissent ce qui sera optimisé ( mean_absolute_error ) et comment (en utilisant le tf.keras.optimizers.Adam ).

horsepower_model.compile(
    optimizer=tf.optimizers.Adam(learning_rate=0.1),
    loss='mean_absolute_error')

Utilisez Keras Model.fit pour exécuter la formation pendant 100 époques :

%%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)
CPU times: user 4.79 s, sys: 797 ms, total: 5.59 s
Wall time: 3.8 s

Visualisez la progression de l'entraînement du modèle à l'aide des statistiques stockées dans l'objet d' 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)

png

Collectez les résultats sur le jeu de test pour plus tard :

test_results = {}

test_results['horsepower_model'] = horsepower_model.evaluate(
    test_features['Horsepower'],
    test_labels, verbose=0)

Puisqu'il s'agit d'une régression à variable unique, il est facile de visualiser les prédictions du modèle en fonction de l'entrée :

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)

png

Régression linéaire avec plusieurs entrées

Vous pouvez utiliser une configuration presque identique pour effectuer des prédictions basées sur plusieurs entrées. Ce modèle fait toujours le même \(y = mx+b\) sauf que \(m\) est une matrice et \(b\) est un vecteur.

Créez à nouveau un modèle séquentiel Keras en deux étapes avec la première couche étant le normalizer ( tf.keras.layers.Normalization(axis=-1) ) que vous avez défini précédemment et adapté à l'ensemble de données :

linear_model = tf.keras.Sequential([
    normalizer,
    layers.Dense(units=1)
])

Lorsque vous appelez Model.predict sur un lot d'entrées, il produit des sorties units=1 pour chaque exemple :

linear_model.predict(train_features[:10])
array([[ 0.441],
       [ 1.522],
       [ 0.188],
       [ 1.169],
       [ 0.058],
       [ 0.965],
       [ 0.034],
       [-0.674],
       [ 0.437],
       [-0.37 ]], dtype=float32)

Lorsque vous appelez le modèle, ses matrices de poids seront construites - vérifiez que les poids du kernel (le \(m\) dans \(y=mx+b\)) ont la forme (9, 1) :

linear_model.layers[1].kernel
<tf.Variable 'dense_1/kernel:0' shape=(9, 1) dtype=float32, numpy=
array([[-0.702],
       [ 0.307],
       [ 0.114],
       [ 0.233],
       [ 0.244],
       [ 0.322],
       [-0.725],
       [-0.151],
       [ 0.407]], dtype=float32)>

Configurez le modèle avec Keras Model.compile et entraînez-vous avec Model.fit pour 100 époques :

linear_model.compile(
    optimizer=tf.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)
CPU times: user 4.89 s, sys: 740 ms, total: 5.63 s
Wall time: 3.75 s

L'utilisation de toutes les entrées de ce modèle de régression permet d'obtenir une erreur de formation et de validation beaucoup plus faible que le modèle horsepower_model , qui avait une entrée :

plot_loss(history)

png

Collectez les résultats sur le jeu de test pour plus tard :

test_results['linear_model'] = linear_model.evaluate(
    test_features, test_labels, verbose=0)

Régression avec un réseau de neurones profond (DNN)

Dans la section précédente, vous avez implémenté deux modèles linéaires pour des entrées simples et multiples.

Ici, vous implémenterez des modèles DNN à entrée unique et à entrées multiples.

Le code est fondamentalement le même, sauf que le modèle est étendu pour inclure des couches non linéaires "cachées". Le nom « caché » ici signifie simplement qu'il n'est pas directement connecté aux entrées ou aux sorties.

Ces modèles contiendront quelques couches de plus que le modèle linéaire :

  • La couche de normalisation, comme avant (avec horsepower_normalizer pour un modèle à entrée unique et normalizer pour un modèle à entrées multiples).
  • Deux couches cachées, non linéaires et Dense avec la non-linéarité de la fonction d'activation ReLU ( relu ).
  • Une couche Dense linéaire à sortie unique.

Les deux modèles utiliseront la même procédure de formation, de sorte que la méthode de compile est incluse dans la fonction build_and_compile_model ci-dessous.

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

Régression utilisant un DNN et une seule entrée

Créez un modèle DNN avec uniquement 'Horsepower' comme entrée et horsepower_normalizer (défini précédemment) comme couche de normalisation :

dnn_horsepower_model = build_and_compile_model(horsepower_normalizer)

Ce modèle a un peu plus de paramètres entraînables que les modèles linéaires :

dnn_horsepower_model.summary()
Model: "sequential_2"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 normalization_1 (Normalizat  (None, 1)                3         
 ion)                                                            
                                                                 
 dense_2 (Dense)             (None, 64)                128       
                                                                 
 dense_3 (Dense)             (None, 64)                4160      
                                                                 
 dense_4 (Dense)             (None, 1)                 65        
                                                                 
=================================================================
Total params: 4,356
Trainable params: 4,353
Non-trainable params: 3
_________________________________________________________________

Entraînez le modèle avec Keras Model.fit :

%%time
history = dnn_horsepower_model.fit(
    train_features['Horsepower'],
    train_labels,
    validation_split=0.2,
    verbose=0, epochs=100)
CPU times: user 5.07 s, sys: 691 ms, total: 5.76 s
Wall time: 3.92 s

Ce modèle fait légèrement mieux que le modèle linéaire à entrée unique horsepower_model :

plot_loss(history)

png

Si vous tracez les prédictions en fonction de 'Horsepower' , vous devriez remarquer comment ce modèle tire parti de la non-linéarité fournie par les couches cachées :

x = tf.linspace(0.0, 250, 251)
y = dnn_horsepower_model.predict(x)
plot_horsepower(x, y)

png

Collectez les résultats sur le jeu de test pour plus tard :

test_results['dnn_horsepower_model'] = dnn_horsepower_model.evaluate(
    test_features['Horsepower'], test_labels,
    verbose=0)

Régression utilisant un DNN et plusieurs entrées

Répétez le processus précédent en utilisant toutes les entrées. Les performances du modèle s'améliorent légèrement sur l'ensemble de données de validation.

dnn_model = build_and_compile_model(normalizer)
dnn_model.summary()
Model: "sequential_3"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 normalization (Normalizatio  (None, 9)                19        
 n)                                                              
                                                                 
 dense_5 (Dense)             (None, 64)                640       
                                                                 
 dense_6 (Dense)             (None, 64)                4160      
                                                                 
 dense_7 (Dense)             (None, 1)                 65        
                                                                 
=================================================================
Total params: 4,884
Trainable params: 4,865
Non-trainable params: 19
_________________________________________________________________
%%time
history = dnn_model.fit(
    train_features,
    train_labels,
    validation_split=0.2,
    verbose=0, epochs=100)
CPU times: user 5.08 s, sys: 725 ms, total: 5.8 s
Wall time: 3.94 s
plot_loss(history)

png

Recueillez les résultats sur l'ensemble de test :

test_results['dnn_model'] = dnn_model.evaluate(test_features, test_labels, verbose=0)

Performance

Étant donné que tous les modèles ont été entraînés, vous pouvez examiner les performances de leurs ensembles de test :

pd.DataFrame(test_results, index=['Mean absolute error [MPG]']).T

Ces résultats correspondent à l'erreur de validation observée lors de la formation.

Faire des prédictions

Vous pouvez maintenant faire des prédictions avec le dnn_model sur l'ensemble de test à l'aide de Keras Model.predict et examiner la perte :

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)

png

Il semble que le modèle prédit raisonnablement bien.

Maintenant, vérifiez la distribution des erreurs :

error = test_predictions - test_labels
plt.hist(error, bins=25)
plt.xlabel('Prediction Error [MPG]')
_ = plt.ylabel('Count')

png

Si vous êtes satisfait du modèle, enregistrez-le pour une utilisation ultérieure avec Model.save :

dnn_model.save('dnn_model')
2022-01-26 07:26:13.372245: W tensorflow/python/util/util.cc:368] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.
INFO:tensorflow:Assets written to: dnn_model/assets

Si vous rechargez le modèle, il donne une sortie identique :

reloaded = tf.keras.models.load_model('dnn_model')

test_results['reloaded'] = reloaded.evaluate(
    test_features, test_labels, verbose=0)
pd.DataFrame(test_results, index=['Mean absolute error [MPG]']).T

Conclusion

Ce cahier a introduit quelques techniques pour gérer un problème de régression. Voici quelques conseils supplémentaires qui peuvent vous aider :

  • L'erreur quadratique moyenne (MSE) ( tf.losses.MeanSquaredError ) et l'erreur absolue moyenne (MAE) ( tf.losses.MeanAbsoluteError ) sont des fonctions de perte courantes utilisées pour les problèmes de régression. MAE est moins sensible aux valeurs aberrantes. Différentes fonctions de perte sont utilisées pour les problèmes de classification.
  • De même, les mesures d'évaluation utilisées pour la régression diffèrent de la classification.
  • Lorsque les entités de données d'entrée numériques ont des valeurs avec des plages différentes, chaque entité doit être mise à l'échelle indépendamment dans la même plage.
  • Le surajustement est un problème courant pour les modèles DNN, même si ce n'était pas un problème pour ce didacticiel. Consultez le didacticiel de surajustement et de sous-ajustement pour obtenir de l'aide à ce sujet.
# MIT License
#
# Copyright (c) 2017 François Chollet
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.