The Boston Housing Price dataset

Partant du principe qu’il vaut mieux s’adresser à Dieu au’à ses Saints, après avoir étudié The Boston Housing Price dataset avec (JM) Jojo Moolayil, présentons ici la version de F. Chollet (FC), l’auteur de Keras. Si le code de JM est très intéressant d’un point de vue pédagogique, il ne l’est pas en termes d’efficacité et d’efficience.

Le code est ici. C’est celui du livre de F. Chollet : Deep Learning with Python.

Pour une description des données, voir Kaggle ici, ou ici.

Le but de l’exercice est de prédire le prix moyen d’une maison à Boston, dans les années 70. Nous sommes dans un cas typique de régression.

Lecture des données

from keras.datasets import boston_housing
(train_data, train_targets), (test_data, test_targets) = boston_housing.load_data()
train_data.shape
(404, 13)
test_data.shape
(102, 13)
train_targets[0:5]
array([15.2, 42.3, 50. , 21.1, 17.7])

Normalisation

Si les données sont toutes de type float, elles ne sont pas toutes dans le même intervalle. C »est pourquoi il est préférable de les normaliser. Notez que la moyenne et l’écart-type sont calculés sur les données d’apprentissage et utilisées sur celles-ci ainsi que sur les données de test.

# Normalizing the data
mean = train_data.mean(axis=0)
train_data -= mean
std = train_data.std(axis=0)
train_data /= std
test_data -= mean
test_data /= std

Autre façon de normaliser (plus simple à mon avis)

from sklearn import preprocessing
n_train_data = preprocessing.scale(train_data)
n_train_data

Si on veut ensuite utiliser, le scaler calculé avec sckit-learn pour les données de test, c’est tout à fait possible. Voir ici.

scaler = preprocessing.StandardScaler().fit(train_data)
scaler.transform(test_data) 

Construction du réseau

Les différentes couches

Le modèle proposé par FC est différent de celui de JM puisque au lieu de 13/6/1 on a 64/64/1.

Par contre tous deux utilisent un ReLU.

Concernant l’optimizer, FC choisit rmsprop alors que JM préfère Adam.

La fonction de coût choisie par FC et JM est MSE, ce qui est presque systématique dans le cas d’une régression.

La métrique de FC est MAE (mean_absolute_error) et celle de JM MAPE (mean_absolute_percentage_error).

Pour rappel, la compétition Kaggle existe RMSE. Avec Keras, il est permis d’utiliser n’importe quelle fonction de coût comme métrique mais RMSE n’est pas implémenté. Il faut créer sa propre fonction.

La définition de RMSE est :

[latex]RMSE(y) = \sqrt{\sum_{i=1}^n (y_i – \hat{y}_i)^2}[/latex]
def root_mean_squared_error(y_true, y_pred):
        return K.sqrt(K.mean(K.square(y_pred - y_true)))

Pour rappel, la métrique n’est pas utilisée pour les calculs du modèle.


# Building your network
from keras import models
from keras import layers
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'])
  return model

K-fold

FC utilise cette technique pour tester différents paramètres car il n’y a pas beaucoup de données dans le dataset.

Il faut tester ici différentes valeurs pour num_epochs mais aussi différentes couches (nombre de neurones et nombre de couches)

# K-fold validation
import numpy as np
k=4
num_val_samples = len(train_data) // k
num_epochs = 100
all_scores = []
for i in range(k):
  print('processing fold #', i)
  val_data = train_data[i * num_val_samples: (i + 1) * num_val_samples]
  val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples]
  partial_train_data = np.concatenate(
      [train_data[:i * num_val_samples], train_data[(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)
  model = build_model()
  model.fit(partial_train_data, partial_train_targets, epochs=num_epochs, batch_size=1, verbose=0)
  val_mse, val_mae = model.evaluate(val_data, val_targets, verbose=0)
  all_scores.append(val_mae)

evaluate :

Returns the loss value & metrics values for the model in test mode.

https://keras.io/models/model/#evaluate
processing fold # 0
processing fold # 1
processing fold # 2
processing fold # 3
all_scores
[2.095678972725821, 2.220593815982932, 2.859968412040484, 2.4053568893139907]
np.mean(all_scores)
2.395399522515807
# Saving the validation logs at each fold
num_epochs = 500
all_mae_histories = []
for i in range(k):
  print('processing fold #', i)
  val_data = train_data[i * num_val_samples: (i + 1) * num_val_samples]
  val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples]
  partial_train_data = np.concatenate([train_data[:i * num_val_samples],train_data[(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)
  model = build_model()
  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_mean_absolute_error']
  all_mae_histories.append(mae_history)
processing fold # 0
processing fold # 1
processing fold # 2
processing fold # 3
# Building the history of successive mean K-fold validation scores
average_mae_history = [np.mean([x[i] for x in all_mae_histories]) for i in range(num_epochs)]

affichage des données

# Plotting validation scores
import matplotlib.pyplot as plt
plt.plot(range(1, len(average_mae_history) + 1), average_mae_history)
plt.xlabel('Epochs')
plt.ylabel('Validation MAE')
plt.show()
# Plotting validation scores, excluding the first 10 data points
def smooth_curve(points, factor=0.9):
  smoothed_points = []
  for point in points:
    if smoothed_points:
      previous = smoothed_points[-1]
      smoothed_points.append(previous * factor + point * (1 - factor))
    else:
      smoothed_points.append(point)
  return smoothed_points
smooth_mae_history = smooth_curve(average_mae_history[10:])
plt.plot(range(1, len(smooth_mae_history) + 1), smooth_mae_history)
plt.xlabel('Epochs')
plt.ylabel('Validation MAE')
plt.show()

Version finale du réseau

# Training the final model
model = build_model()
model.fit(train_data, train_targets,epochs=80, batch_size=16, verbose=0)
test_mse_score, test_mae_score = model.evaluate(test_data, test_targets)
102/102 [==============================] - 0s 1ms/step
test_mae_score
2.675027146058924

J’ai modifié la métrique afin de comparer les résultats avec la compétition Kaggle.

# On remplace la métrique par RMSE
import keras.backend as K
​
def root_mean_squared_error(y_true, y_pred):
  return K.sqrt(K.mean(K.square(y_pred - y_true)))

def build_model_rmse():
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=[root_mean_squared_error])
return model

# Training the final model
model_rmse = build_model_rmse()
model_rmse.fit(train_data, train_targets,epochs=80, batch_size=16, verbose=0)
# Returns the loss value & metrics values for the model in test mode.
test_mse_score, test_rmse_score = model_rmse.evaluate(test_data, test_targets)
102/102 [==============================] - 0s 516us/step
test_rmse_score
5.667209728091371

Avec un tel résultat on est 69ème dans la compétition, ce qui n’est pas terrible.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.