Le livre Learn Keras for Deep Neural Networks de Jojo Moolayil, publié chez Apress est un livre de 182 pages qui se veut une introduction au Deep Learning (apprentissage profond) en s’appuyant sur la librairie Keras.

Chapitre 1 : An introduction do Deep Learning and Keras

Le chapitre #1 : An introduction do Deep Learning and Keras satisfera les débutants dans le Deep Learning (DL). Une présentation générale des frameworks est présentée et un premier exemple très simple en Python décrit ce qu’est un logiciel développé avec Keras. Dommage qu’il n’y ait pas le code quelque part sur le Web.

Pour exécuter ce premier exemple, si vous le faites sur votre PC/Mac, alors il est préférable d’utiliser Anaconda, dans un environnement où vous avez installé TensorFlow et Keras. Sinon, utilisez GCP, et mon code disponible sur Github.

Cet exemple n’est pas commenté dans le détail dans ce chapitre. Il a juste vocation à montrer les grandes ligne d’un code Keras.

Chapitre 2 : Keras in action

L’installation de l’environnement est tout d’abord décrite. Personnellement, je pense préférable d’utiliser Anaconda ou GCP et d’installer les packages supplémentaires à partir d’Anaconda.

Ce qui est bien dans ce chapitre, c’est l’explication de ce que sont la fonction d’activation, la fonction sigmoïde, ReLU.

Par ailleurs, la description des couches de neurones est très claire (core layer, dense layer). Idem pour le dropout, la fonction de coût, les optimizers et les métriques.

Ce chapitre permet de comprendre facilement quels sont les points importants dans un réseau de neurones.

Le code décrit au § Model training est ici. Ce code n’est pa très intéressant car il s’appuie sur des données aléatoires. Il est préférable de travailler sur des données réelles, ce qui est fait en fin de ce chapitre avec le Boston House Prices dataset et qui est décrit ci-dessous.

Boston House Prices

Les données de cet exercice ont servi à une compétition Kaggle et sont disponibles sur leur site, tout comme sur celui de Keras.

La description des données est la suivante :

The Boston data frame has 506 rows and 14 columns.
This data frame contains the following columns:
crim
per capita crime rate by town.
zn
proportion of residential land zoned for lots over 25,000 sq.ft.
indus
proportion of non-retail business acres per town.
chas
Charles River dummy variable (= 1 if tract bounds river; 0 otherwise).
nox
nitrogen oxides concentration (parts per 10 million).
rm
average number of rooms per dwelling.
age
proportion of owner-occupied units built prior to 1940.
dis
weighted mean of distances to five Boston employment centres.
rad
index of accessibility to radial highways.
tax
full-value property-tax rate per \$10,000.
ptratio
pupil-teacher ratio by town.
black
1000(Bk – 0.63)^2 where Bk is the proportion of blacks by town.
lstat
lower status of the population (percent).
medv
median value of owner-occupied homes in \$1000s.

https://www.kaggle.com/c/boston-housing/overview

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

Il est utile de consulter la compétition sur Kaggle, d’autant plus qu’elle est terminée depuis longtemps et qu’on connait les performances obtenues par les concurrents.

Dans le cas présent,

The medv variable is the target variable.

medv: median value of owner-occupied homes in \$1000s.

https://www.kaggle.com/c/boston-housing/overview

La méthode d’évaluation est RMSE.

RMSE(y) = \sqrt{\sum_{i=1}^n (y_i - \hat{y}_i)^2}

# Download the data using Keras; this will need an active internet connection
from keras.datasets import boston_housing
(x_train, y_train), (x_test, y_test) = boston_housing.load_data()

Avec Pandas

Pour faciliter la compréhension des données, je montre aussi comment lire les données avec Pandas et les présenter avec matplotlib et seaborn.

import pandas as pd # conventional alias
from sklearn.datasets import load_boston

dataset = load_boston()
df = pd.DataFrame(dataset.data, columns=dataset.feature_names)
df['target'] = dataset.target
print(df.shape)
(506, 14)
df.head()
IdCRIMZNINDUSCHASNOXRMAGEDISRADTAXPTRATIOBLSTATtarget
00.0063218.02.310.00.5386.57565.24.09001.0296.015.3396.904.9824.0
10.027310.07.070.00.4696.42178.94.96712.0242.017.8396.909.1421.6
20.027290.07.070.00.4697.18561.14.96712.0242.017.8392.834.0334.7
30.032370.02.180.00.4586.99845.86.06223.0222.018.7394.632.9433.4
40.069050.02.180.00.4587.14754.26.06223.0222.018.7396.905.3336.2

Il y a 506 enregistrements dans le fichier.

Je vous recommande d’exécuter df.describe() qui vous donnera une bonne image de vos données. medv est renommée target dans le tableau.

df.describe()

et ensuite pd.isnull(df).sum() pour savoir s’il y a des données manquantes (ce qui n’est pas le cas ici).

pd.isnull(df).sum()

Les corrélations entre les variables sont les suivantes :

import matplotlib.pyplot as plt
import seaborn as sns
plt.figure(figsize=(12,10))
sns.heatmap(df.corr().round(2),cmap='coolwarm',annot=True)

Conversion avec Pandas

Pour obtenir des DataFrames, une autre solution est de convertir les données Numpy. Par exemple :

df_x_train = pd.DataFrame.from_records(x_train)
df_x_train.shape
(404, 13)

Training set, test set

Lorsque les données sont lues avec Keras, elles sont scindées automatiquement entre un Training Set et un Test set.

# Explore the data structure using basic python commands

print("Type of the Dataset:",type(y_train))
print("Shape of training data :",x_train.shape)
print("Shape of training labels :",y_train.shape)
print("Shape of testing data :",type(x_test))
print("Shape of testing labels :",y_test.shape)

Le testing set a une taille (approximative) de 25% du training set ou dit autrement (approximativement) 20% du dataset. C’est d’ailleurs ce qu’on lit dans le code de Keras (test_split=0.2)

Type of the Dataset: <class 'numpy.ndarray'>
Shape of training data : (404, 13)
Shape of training labels : (404,)
Shape of testing data : <class 'numpy.ndarray'>
Shape of testing labels : (102,)

Si vous ne connaissez pas la différence entre Training Set, Validation Set et Test Set, rendez-vous ici.

Training Dataset: The sample of data used to fit the model.

Validation Dataset: The sample of data used to provide an unbiased evaluation of a model fit on the training dataset while tuning model hyperparameters. 

Test Dataset: The sample of data used to provide an unbiased evaluation of a final model fit on the training dataset.

https://towardsdatascience.com/train-validation-and-test-sets-72cb40cba9e7

y_train

y_train correspond à la valeur médiane des maisons occupées (en milliers de dollars).

y_train
array([15.2, 42.3, 50. , 21.1, 17.7, 18.5, 11.3, 15.6, 15.6, 14.4, 12.1,
       17.9, 23.1, 19.9, 15.7,  8.8, 50. , 22.5, 24.1, 27.5, 10.9, 30.8,
       32.9, 24. , 18.5, ...

Les prix sont entre 10K$ et 50K$ mais c’était les années 70 !

Imports

import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation

Le modèle utilisé ici est le modèle séquentiel de Keras, un modèle relativement simple à comprendre.

Données de validation

Des données de validation sont créées (pour rappel, nous avons déjà des données de test)

# Extract the last 100 rows from the training data to create the validation datasets.
x_val = x_train[300:,]
y_val = y_train[300:,]

Cette manière de faire est assez étonnante car Keras permet de définir un validation_split, un float qui indique le pourcentage utilisé pour les données de validation.

Architecture

Le modèle est séquentiel. On décrit successivement les différentes couches du réseau.

#Define the model architecture
model = Sequential()
model.add(Dense(13, input_dim=13, kernel_initializer='normal', activation='relu'))
model.add(Dense(6, kernel_initializer='normal', activation='relu'))
model.add(Dense(1, kernel_initializer='normal'))

Première couche

Pour la première couche, il est impératif d’indiquer la shape (les dimensions input_dim=13) car elle ne peut pas être déduite contrairement aux autres couches suivantes pour qui c’est possible dans un modèle séquentiel.

The model needs to know what input shape it should expect. For this reason, the first layer in a Sequential model (and only the first, because following layers can do automatic shape inference) needs to receive information about its input shape. There are several possible ways to do this:
Pass an input_shape argument to the first layer. This is a shape tuple (a tuple of integers or None entries, where None indicates that any positive integer may be expected). In input_shape, the batch dimension is not included.
Some 2D layers, such as Dense, support the specification of their input shape via the argument input_dim, and some 3D temporal layers support the arguments input_dim and input_length.

https://keras.io/getting-started/sequential-model-guide/

Pourquoi avoir précisé kernel_initializer=’normal’ ? je ne sais pas, l’auteur ne l’explique pas. Je doute que ça ait une quelconque utilité ici.

La fonction d’activation est ReLu. C’est la fonction la plus commune aujourd’hui mais ce n’est pas la fonction d’activation par défaut, il est donc nécessaire de le préciser.

Deuxième couche

Rien de particulier. Pourquoi passe t-on de 13 à 6 couches et pas à 5 ou 4 ou … il n’y a pas de règles.

Toutefois, étant donné le faible nombre de données, il est préférable de limiter le nombre de couches. Ce qu’en dit F. Chollet (le créateur de Keras) :

Because so few samples are available, you’ll use a very small network with two hidden layers, each with 64 units. In general, the less training data you have, the worse overfit- ting will be, and using a small network is one way to mitigate overfitting.

Deep Learning with Python

Lui utilise, pour le même exemple : 2 couches de 64 neurones suivies d’un output à 1.

Dernière couche

Il n’y a pas d’autre option que de terminer avec un nombre. Nous sommes dans le cas d’une régression. Notez qu’il n’y a pas de fonction d’activation. Une fonction d’activation nous contraindrait sur les valeurs que peut prendre le nombre.

Compilation du modèle

# Compile model
model.compile(loss='mean_squared_error', optimizer='adam',metrics=['mean_absolute_percentage_error'])

La fonction de perte est MSE, c’est celle qu’on utilise typiquement dans un problème de régression. L’auteur a choisi Adam pour optimizer. Par défaut, on utilise sgd. Quelle est l’influence de ce choix ? Difficile à dire.

Ma métrique est MAPE. Cette métrique n’a été choisie ici que pour sa vertu pédagogique. Elle donne un pourcentage sur l’erreur.

Apprentissage

# Train the model
model.fit(x_train, y_train, batch_size=32, epochs=3, validation_data=(x_val,y_val))

Le nombre d’epochs est très faible (3), par contre le batch_size me semble élevé. Les données de validation ont été discutées précédemment.

Les résultats ne sont pas très bons (et c’est un euphémisme )

Train on 404 samples, validate on 104 samples
Epoch 1/3
404/404 [==============================] - 0s 469us/step - loss: 574.4271 - mean_absolute_percentage_error: 98.2716 - val_loss: 652.3900 - val_mean_absolute_percentage_error: 96.5681
Epoch 2/3
404/404 [==============================] - 0s 50us/step - loss: 533.9519 - mean_absolute_percentage_error: 92.5494 - val_loss: 578.9429 - val_mean_absolute_percentage_error: 88.0311
Epoch 3/3
404/404 [==============================] - 0s 49us/step - loss: 435.1889 - mean_absolute_percentage_error: 78.1117 - val_loss: 422.8921 - val_mean_absolute_percentage_error: 67.6374

67% sur l’erreur !

Sur les données de Test

results = model.evaluate(x_test, y_test)
for i in range(len(model.metrics_names)):
  print(model.metrics_names[i]," : ", results[i])
loss  :  373.1487354951746
mean_absolute_percentage_error  :  66.17471478032131

évidemment le résultat n’est pas meilleur !

En augmentant le nombre d’epochs

# Train the model
model.fit(x_train, y_train, batch_size=32, epochs=30, validation_data=(x_val,y_val))
Train on 404 samples, validate on 104 samples
Epoch 1/30
404/404 [==============================] - 0s 53us/step - loss: 269.7837 - mean_absolute_percentage_error: 54.8461 - val_loss: 223.8510 - val_mean_absolute_percentage_error: 43.6438
Epoch 2/30
404/404 [==============================] - 0s 52us/step - loss: 150.7172 - mean_absolute_percentage_error: 47.1239 - val_loss: 151.9747 - val_mean_absolute_percentage_error: 45.6067
Epoch 3/30
404/404 [==============================] - 0s 55us/step - loss: 138.3203 - mean_absolute_percentage_error: 51.8210 - val_loss: 143.2883 - val_mean_absolute_percentage_error: 41.8812
Epoch 4/30
404/404 [==============================] - 0s 52us/step - loss: 122.8476 - mean_absolute_percentage_error: 44.5627 - val_loss: 140.4394 - val_mean_absolute_percentage_error: 37.6512
Epoch 5/30
404/404 [==============================] - 0s 57us/step - loss: 115.1191 - mean_absolute_percentage_error: 41.3090 - val_loss: 130.3238 - val_mean_absolute_percentage_error: 36.4818
Epoch 6/30
404/404 [==============================] - 0s 53us/step - loss: 106.4456 - mean_absolute_percentage_error: 40.0573 - val_loss: 121.9103 - val_mean_absolute_percentage_error: 35.3038
Epoch 7/30
404/404 [==============================] - 0s 52us/step - loss: 99.5817 - mean_absolute_percentage_error: 38.4672 - val_loss: 114.8539 - val_mean_absolute_percentage_error: 33.8199
Epoch 8/30
404/404 [==============================] - 0s 48us/step - loss: 93.5690 - mean_absolute_percentage_error: 35.9864 - val_loss: 109.3819 - val_mean_absolute_percentage_error: 32.0800
Epoch 9/30
404/404 [==============================] - 0s 47us/step - loss: 87.5985 - mean_absolute_percentage_error: 35.6523 - val_loss: 102.2023 - val_mean_absolute_percentage_error: 31.9997
Epoch 10/30
404/404 [==============================] - 0s 51us/step - loss: 81.9733 - mean_absolute_percentage_error: 33.9350 - val_loss: 99.2321 - val_mean_absolute_percentage_error: 29.6323
Epoch 11/30
404/404 [==============================] - 0s 56us/step - loss: 78.7858 - mean_absolute_percentage_error: 30.8595 - val_loss: 96.6743 - val_mean_absolute_percentage_error: 27.7957
Epoch 12/30
404/404 [==============================] - 0s 50us/step - loss: 74.1199 - mean_absolute_percentage_error: 31.7667 - val_loss: 90.0601 - val_mean_absolute_percentage_error: 28.9264
Epoch 13/30
404/404 [==============================] - 0s 46us/step - loss: 70.4005 - mean_absolute_percentage_error: 30.3918 - val_loss: 90.0534 - val_mean_absolute_percentage_error: 25.9741
Epoch 14/30
404/404 [==============================] - 0s 51us/step - loss: 67.8720 - mean_absolute_percentage_error: 28.8515 - val_loss: 87.2296 - val_mean_absolute_percentage_error: 25.5040
Epoch 15/30
404/404 [==============================] - 0s 49us/step - loss: 65.3452 - mean_absolute_percentage_error: 27.9279 - val_loss: 86.0044 - val_mean_absolute_percentage_error: 24.6537
Epoch 16/30
404/404 [==============================] - 0s 53us/step - loss: 63.9135 - mean_absolute_percentage_error: 27.1884 - val_loss: 84.1267 - val_mean_absolute_percentage_error: 24.5333
Epoch 17/30
404/404 [==============================] - 0s 65us/step - loss: 62.4643 - mean_absolute_percentage_error: 26.7182 - val_loss: 83.9935 - val_mean_absolute_percentage_error: 23.8654
Epoch 18/30
404/404 [==============================] - 0s 53us/step - loss: 61.2815 - mean_absolute_percentage_error: 26.8983 - val_loss: 81.9291 - val_mean_absolute_percentage_error: 24.5295
Epoch 19/30
404/404 [==============================] - 0s 55us/step - loss: 60.7674 - mean_absolute_percentage_error: 27.1701 - val_loss: 81.8467 - val_mean_absolute_percentage_error: 24.0074
Epoch 20/30
404/404 [==============================] - 0s 59us/step - loss: 60.1599 - mean_absolute_percentage_error: 26.1704 - val_loss: 81.8593 - val_mean_absolute_percentage_error: 23.5683
Epoch 21/30
404/404 [==============================] - 0s 56us/step - loss: 59.6977 - mean_absolute_percentage_error: 26.5049 - val_loss: 81.1265 - val_mean_absolute_percentage_error: 23.7069
Epoch 22/30
404/404 [==============================] - 0s 47us/step - loss: 59.4475 - mean_absolute_percentage_error: 25.5855 - val_loss: 81.7738 - val_mean_absolute_percentage_error: 23.0464
Epoch 23/30
404/404 [==============================] - 0s 58us/step - loss: 58.6671 - mean_absolute_percentage_error: 25.7437 - val_loss: 80.0469 - val_mean_absolute_percentage_error: 23.9294
Epoch 24/30
404/404 [==============================] - 0s 57us/step - loss: 58.8535 - mean_absolute_percentage_error: 26.9202 - val_loss: 80.9957 - val_mean_absolute_percentage_error: 22.9954
Epoch 25/30
404/404 [==============================] - 0s 56us/step - loss: 58.4746 - mean_absolute_percentage_error: 24.8414 - val_loss: 80.6377 - val_mean_absolute_percentage_error: 22.9542
Epoch 26/30
404/404 [==============================] - 0s 61us/step - loss: 58.4065 - mean_absolute_percentage_error: 27.0826 - val_loss: 78.9771 - val_mean_absolute_percentage_error: 23.8978
Epoch 27/30
404/404 [==============================] - 0s 62us/step - loss: 57.8777 - mean_absolute_percentage_error: 25.2186 - val_loss: 79.9620 - val_mean_absolute_percentage_error: 22.8801
Epoch 28/30
404/404 [==============================] - 0s 56us/step - loss: 57.4435 - mean_absolute_percentage_error: 25.3085 - val_loss: 78.4486 - val_mean_absolute_percentage_error: 23.6313
Epoch 29/30
404/404 [==============================] - 0s 69us/step - loss: 57.1338 - mean_absolute_percentage_error: 25.4473 - val_loss: 79.1974 - val_mean_absolute_percentage_error: 22.7873
Epoch 30/30
404/404 [==============================] - 0s 55us/step - loss: 56.7096 - mean_absolute_percentage_error: 24.8387 - val_loss: 78.4174 - val_mean_absolute_percentage_error: 23.0010
<keras.callbacks.History at 0x7f57adf2fe10>

Il y a du progrès : 23%

Sur les données de Test

results = model.evaluate(x_test, y_test)
for i in range(len(model.metrics_names)):
  print(model.metrics_names[i]," : ", results[i])
102/102 [==============================] - 0s 66us/step
loss  :  61.81917706657858
mean_absolute_percentage_error  :  29.708916421030082

On est à 29%

Il y a de la place pour s’améliorer ! mais l’explication est claire.

F. Chollet recommande d’utiliser un K-fold pour la validation et de normaliser les données.

Chapitre 3 – Deep Neural Networks for Supervised Learning : Regression

Cette fois, le dataset est le Rossmann Store sales dataset

You are provided with historical sales data for 1,115 Rossmann stores. The task is to forecast the « Sales » column for the test set. Note that some stores in the dataset were temporarily closed for refurbishment.

https://www.kaggle.com/c/rossmann-store-sales/data

Il s’agit encore une fois d’une régression : prévoir les ventes (le CA) pour un magasin, pour un jour donné.

L’auteur nous présente les frameworks SCR et SCQ que je ne connaissais pas et que je vois ici utilisé pour la première fois dans le Machine Learning. Je ne suis pas sûr que ce soit très utile.

Etude des données

train

Le problème avec le dataset Rossman, c’est qu’il n’est pas pris en compte par Keras (ni sckit-kearn, ni Tensorflow, …). Il faut copier les données sur son disque/serveur. C’est ce que j’ai fait.

import pandas as pd
df = pd.read_csv("/Users/...train.csv")

Remplacez : « /Users/… » par votre lien.

df.head()
IdStoreDayOfWeekDateSalesCustomersOpenPromoStateHolidaySchoolHoliday
01531/07/1552635551101
12531/07/1560646251101
23531/07/1583148211101
34531/07/151399514981101
45531/07/1548225591101
df.shape
(1017209, 9)

Le fichier a une taille conséquente, 1 017 209 lignes, et 9 colonnes.

Les champ sont les suivants :

Most of the fields are self-explanatory. The following are descriptions for those that aren’t.
Id – an Id that represents a (Store, Date) duple within the test set
Store – a unique Id for each store
Sales – the turnover for any given day (this is what you are predicting)
Customers – the number of customers on a given day
Open – an indicator for whether the store was open: 0 = closed, 1 = open
StateHoliday – indicates a state holiday. Normally all stores, with few exceptions, are closed on state holidays. Note that all schools are closed on public holidays and weekends. a = public holiday, b = Easter holiday, c = Christmas, 0 = None
SchoolHoliday – indicates if the (Store, Date) was affected by the closure of public schools
StoreType – differentiates between 4 different store models: a, b, c, d
Assortment – describes an assortment level: a = basic, b = extra, c = extended
CompetitionDistance – distance in meters to the nearest competitor store
CompetitionOpenSince[Month/Year] – gives the approximate year and month of the time the nearest competitor was opened
Promo – indicates whether a store is running a promo on that day
Promo2 – Promo2 is a continuing and consecutive promotion for some stores: 0 = store is not participating, 1 = store is participating
Promo2Since[Year/Week] – describes the year and calendar week when the store started participating in Promo2
PromoInterval – describes the consecutive intervals Promo2 is started, naming the months the promotion is started anew. E.g. « Feb,May,Aug,Nov » means each round starts in February, May, August, November of any given year for that store

https://www.kaggle.com/c/rossmann-store-sales/data

store

store = pd.read_csv(".../store.csv") 
print("Shape of the Dataset:",store.shape)

Ce fichier décrit les magasins.

Shape of the Dataset: (1115, 10)
store.head(5)
IdStoreStoreTypeAssortmentCompetitionDistanceCompetitionOpenSinceMonthCompetitionOpenSinceYearPromo2Promo2SinceWeekPromo2SinceYearPromoInterval
01ca1270.09.02008.00NaNNaNNaN
12aa570.011.02007.0113.02010.0Jan,Apr,Jul,Oct
23aa14130.012.02006.0114.02011.0Jan,Apr,Jul,Oct
34cc620.09.02009.00NaNNaNNaN
45aa29910.04.02015.00NaNNaNNaN

merge

Il nous faut travailler sur un seul ensemble de données. Nous allons fusionner les 2 fichiers précédents en un seul, grâce aux possibilités offertes par Pandas.

df_new = df.merge(store,on=["Store"], how="inner")
print(df_new.shape)
(1017209, 18)
print("Distinct number of Stores :", len(df_new["Store"].unique()))
print("Distinct number of Days :", len(df_new["Date"].unique()))
print("Average daily sales of all stores : ",round(df_new["Sales"].mean(),2))
Distinct number of Stores : 1115
Distinct number of Days : 942
Average daily sales of all stores :  5773.82

Il y a 1115 magasins, ce qu’on savait déjà avec le nombre de lignes de store. Il y a 942 jours pour lesquels on a a des données, et le chiffre d’affaires moyen journalier pour un magasin est de 5773,82 $.

types

df_new.dtypes
DayOfWeek                      int64
Date                          object
Sales                          int64
Customers                      int64
Open                           int64
Promo                          int64
StateHoliday                  object
SchoolHoliday                  int64
StoreType                     object
Assortment                    object
CompetitionDistance          float64
CompetitionOpenSinceMonth    float64
CompetitionOpenSinceYear     float64
Promo2                         int64
Promo2SinceWeek              float64
Promo2SinceYear              float64
PromoInterval                 object
dtype: object

Les données sont de types variés. Nous devons mettre un peu d’ordre dans tout ça.

Nettoyage des données

Dates

Comme souvent, lorsqu’il y a des dates, il faut les réarranger pour leur donner du sens. En effet, il est plus important de savoir qu’on a affaire à un WE qu’au 12/03/2019. Si vous utilisez fastai il y a des routines spécialisées pour cela. Ici ce n’est pas le cas.

df_new["DayOfWeek"].value_counts()
5    145845
4    145845
3    145665
2    145664
7    144730
6    144730
1    144730
Name: DayOfWeek, dtype: int64

Ce que nous voyons déjà, c’est que certains enregistrements ne sont pas datés.

# We can extract all date properties from a datetime datatype
import numpy as np
df_new['Date'] = pd.to_datetime(df_new['Date'], infer_datetime_format=True)
df_new["Month"] = df_new["Date"].dt.month
df_new["Quarter"] = df_new["Date"].dt.quarter
df_new["Year"] = df_new["Date"].dt.year
df_new["Day"] = df_new["Date"].dt.day
df_new["Week"] = df_new["Date"].dt.week
df_new["Season"] = np.where(df_new["Month"].isin([3,4,5]),"Spring", np.where(df_new["Month"].isin([6,7,8]),"Summer",np.where(df_new["Month"].isin([9,10,11]),"Fall",np.where(df_new["Month"].isin([12,1,2]),"Winter","None"))))
# Using the head command to view (only) the data and the newly engineered features
print(df_new[["Date","Year","Month","Day","Week","Quarter","Season"]].head())
        Date  Year  Month  Day  Week  Quarter  Season
0 2015-07-31  2015      7   31    31        3  Summer
1 2015-07-30  2015      7   30    31        3  Summer
2 2015-07-29  2015      7   29    31        3  Summer
3 2015-07-28  2015      7   28    31        3  Summer
4 2015-07-27  2015      7   27    31        3  Summer

Je n’insiste pas sur ces conversions qui peuvent être faites automatiquement avec certains outils. Notez que l’auteur pense nécessaire de calculer la saison.

Histogrammes

Histogramme des ventes

# Import matplotlib, python most popular data visualizing library
import matplotlib.pyplot as plt
%matplotlib inline
# Create a histogram to study the Daily Sales for the stores
plt.figure(figsize=(15,8))
plt.hist(df_new["Sales"])
plt.title("Histogramme des Ventes")
plt.xlabel("bins")
plt.xlabel("Fréquence")
plt.show()

Autres histogrammes

# Use the  histogram function provided by the Pandas object
# The function returns a cross-tab histogram plot for all numeric columns in the data
df_new.hist(figsize=(20,10))

Valeurs absentes

df_new.isnull().sum()/df_new.shape[0] * 100
Store                         0.000000
DayOfWeek                     0.000000
Date                          0.000000
Sales                         0.000000
Customers                     0.000000
Open                          0.000000
Promo                         0.000000
StateHoliday                  0.000000
SchoolHoliday                 0.000000
StoreType                     0.000000
Assortment                    0.000000
CompetitionDistance           0.259730
CompetitionOpenSinceMonth    31.787764
CompetitionOpenSinceYear     31.787764
Promo2                        0.000000
Promo2SinceWeek              49.943620
Promo2SinceYear              49.943620
PromoInterval                49.943620
Month                         0.000000
Quarter                       0.000000
Year                          0.000000
Day                           0.000000
Week                          0.000000
Season                        0.000000
dtype: float64

Comment traiter les valeurs absentes. Là aussi, fastai propose une solution technique. Ici il faut le faire soi-même. L’auteur a choisi de remplacer les valeurs absentes pas le mode (plutôt que la moyenne). Pourquoi pas !

# Replace nulls with the mode
df_new["CompetitionDistance"]=df_new["CompetitionDistance"].fillna(df_new["CompetitionDistance"].mode()[0])
# Double check if we still see nulls for the column 
df_new["CompetitionDistance"].isnull().sum()/df_new.shape[0] * 100
0.0

Valeurs qualitatives

import seaborn as sns  # Seaborn is another powerful visualization library for Python
sns.set(style="whitegrid")
# Create the bar plot for Average Sales across different Seasons
ax = sns.barplot(x="Season", y="Sales", data=df_new)
# Create the bar plot for Average Sales across different Assortments
ax = sns.barplot(x="Assortment", y="Sales", data=df_new)
# Create the bar plot for Average Sales across different Store Types
ax = sns.barplot(x="StoreType", y="Sales", data=df_new)

L’auteur continue son analyse des données de façon similaire.

Data engineering

Les catégories (pour lesquelles c’est possible) sont transformées en One Hot Encoding.

print("Shape of Data:",temp.shape)
print("Distinct Datatypes:",temp.dtypes.unique())
Shape of Data: (1017209, 44)
Distinct Datatypes: [dtype('int64') dtype('O') dtype('float64')]

Il faut aussi modifier StateHoliday qui est de type Objet.

print(temp.columns[temp.dtypes=="object"])
Index(['StateHoliday'], dtype='object')
temp["StateHoliday"].unique()
array(['0', 'a', 'b', 'c', 0], dtype=object)

On remplace les a, b, c par 1 et le reste par 0

temp["StateHoliday"]= np.where(temp["StateHoliday"]== '0',0,1)
# One last check of the data type
temp.dtypes.unique()

Apprentissage /Test / Validation

Les données sont scindées en jeu d’apprentissage, de validation et de test. Il y a une petite erreur dans le code du livre. Au lieu de

from sklearn.cross_validation import train_test_split 

il faut :

from sklearn.model_selection import train_test_split
from sklearn.model_selection import train_test_split

# Create train and test dataset with an 80:20 split
x_train, x_test, y_train, y_test = train_test_split(temp,
df_new[target],test_size=0.2,random_state=2018)

# Further divide training dataset into train and validation dataset with an 90:10 split
x_train, x_val, y_train, y_val = train_test_split(x_train,
y_train,test_size=0.1,random_state=2018)

# Check the sizes of all newly created datasets
print("Shape of x_train:",x_train.shape)
print("Shape of x_val:",x_val.shape)
print("Shape of x_test:",x_test.shape)
print("Shape of y_train:",y_train.shape)
print("Shape of y_val:",y_val.shape)
print("Shape of y_test:",y_test.shape)
Shape of x_train: (732390, 44)
Shape of x_val: (81377, 44)
Shape of x_test: (203442, 44)
Shape of y_train: (732390, 1)
Shape of y_val: (81377, 1)
Shape of y_test: (203442, 1)

Modèle

La métrique est MAE (mean absolute error)

# calculate the average score of the train dataset
mean_sales = y_train.mean()
print("Average Sales :",mean_sales)
Average Sales : Sales    5773.099997
dtype: float64
# Calculate the Mean Absolute Error on the test dataset
print("MAE for Test Data:",abs(y_test - mean_sales).mean()[0])
MAE for Test Data: 2883.587604303215

Premier DNN

# Create Deep Neural Network Architecture
from keras.models import Sequential
from keras.layers import Dense, Dropout
model = Sequential()
model.add(Dense(150,input_dim = 44,activation="relu"))

# The input_dim =44, since the width of the training data=44(refer data engg section)
model.add(Dense(1,activation = "linear"))

#Configure the model
model.compile(optimizer='adam',loss="mean_absolute_error", metrics=["mean_absolute_error"])

#Train the model
model.fit(x_train.values,y_train.values, validation_data=(x_val,y_val),epochs=10,batch_size=64)

Le modèle est très classique et fortement inspiré de l’exemple précédent.

Using TensorFlow backend.
WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/tensorflow/python/framework/op_def_library.py:263: colocate_with (from tensorflow.python.framework.ops) is deprecated and will be removed in a future version.
Instructions for updating:
Colocations handled automatically by placer.
WARNING:tensorflow:From /usr/local/lib/python3.6/dist-packages/tensorflow/python/ops/math_ops.py:3066: to_int32 (from tensorflow.python.ops.math_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.cast instead.
Train on 732390 samples, validate on 81377 samples
Epoch 1/10
732390/732390 [==============================] - 30s 41us/step - loss: 944.6557 - mean_absolute_error: 944.6557 - val_loss: 819.7478 - val_mean_absolute_error: 819.7478
Epoch 2/10
732390/732390 [==============================] - 29s 39us/step - loss: 773.2035 - mean_absolute_error: 773.2035 - val_loss: 747.8291 - val_mean_absolute_error: 747.8291
Epoch 3/10
732390/732390 [==============================] - 28s 38us/step - loss: 734.3570 - mean_absolute_error: 734.3570 - val_loss: 723.2145 - val_mean_absolute_error: 723.2145
Epoch 4/10
732390/732390 [==============================] - 27s 37us/step - loss: 718.6157 - mean_absolute_error: 718.6157 - val_loss: 710.5568 - val_mean_absolute_error: 710.5568
Epoch 5/10
732390/732390 [==============================] - 29s 39us/step - loss: 704.8254 - mean_absolute_error: 704.8254 - val_loss: 695.5905 - val_mean_absolute_error: 695.5905
Epoch 6/10
732390/732390 [==============================] - 27s 37us/step - loss: 699.6464 - mean_absolute_error: 699.6464 - val_loss: 691.4862 - val_mean_absolute_error: 691.4862
Epoch 7/10
732390/732390 [==============================] - 29s 40us/step - loss: 694.9001 - mean_absolute_error: 694.9001 - val_loss: 705.1612 - val_mean_absolute_error: 705.1612
Epoch 8/10
732390/732390 [==============================] - 29s 39us/step - loss: 692.2150 - mean_absolute_error: 692.2150 - val_loss: 690.5002 - val_mean_absolute_error: 690.5002
Epoch 9/10
732390/732390 [==============================] - 27s 37us/step - loss: 688.5974 - mean_absolute_error: 688.5974 - val_loss: 689.1487 - val_mean_absolute_error: 689.1487
Epoch 10/10
732390/732390 [==============================] - 27s 37us/step - loss: 683.1414 - mean_absolute_error: 683.1414 - val_loss: 692.4914 - val_mean_absolute_error: 692.4914
<keras.callbacks.History at 0x7f337c7a4898>

Mes résultats sont un peu différents mais somme toute assez voisins de ceux du livre.

Et sur le test dataset :

# Use the model's evaluate method to predict and evaluate the test datasets
result = model.evaluate(x_test.values,y_test.values)

# Print the results
for i in range(len(model.metrics_names)):
  print("Metric ",model.metrics_names[i],":",str(round(result[i],2)))
203442/203442 [==============================] - 5s 26us/step
Metric  loss : 690.1
Metric  mean_absolute_error : 690.1

ça colle aussi !

Amélioration du modèle

Au lieu de mean_absolute_error pour la fonction de coût, utilisons mean_squared_error et augmentons le nombre de couches cachées.

model = Sequential()
model.add(Dense(150,input_dim = 44,activation="relu"))
model.add(Dense(150,activation="relu"))
model.add(Dense(150,activation="relu"))
model.add(Dense(1,activation = "linear"))
model.compile(optimizer='adam',loss="mean_squared_error",metrics=["mean_absolute_error"])

history = model.fit(x_train,y_train, validation_data=(x_val,y_val),epochs=10,batch_size=64)

# result = model.evaluate(x_test,y_test)
for i in range(len(model.metrics_names)):
  print("Metric ",model.metrics_names[i],":",str(round(result[i],2)))
Train on 732390 samples, validate on 81377 samples
Epoch 1/10
732390/732390 [==============================] - 35s 48us/step - loss: 1726005.7325 - mean_absolute_error: 853.7699 - val_loss: 1116257.5049 - val_mean_absolute_error: 710.3434
Epoch 2/10
732390/732390 [==============================] - 36s 49us/step - loss: 1149926.0055 - mean_absolute_error: 718.6125 - val_loss: 1043340.8199 - val_mean_absolute_error: 682.8135
Epoch 3/10
732390/732390 [==============================] - 35s 48us/step - loss: 1078362.3809 - mean_absolute_error: 696.9257 - val_loss: 1030583.2103 - val_mean_absolute_error: 684.6889
Epoch 4/10
732390/732390 [==============================] - 35s 48us/step - loss: 1045838.6690 - mean_absolute_error: 686.2809 - val_loss: 995751.8180 - val_mean_absolute_error: 669.2394
Epoch 5/10
732390/732390 [==============================] - 38s 52us/step - loss: 1027480.0447 - mean_absolute_error: 680.0208 - val_loss: 977277.6982 - val_mean_absolute_error: 667.7428
Epoch 6/10
732390/732390 [==============================] - 35s 48us/step - loss: 998382.9407 - mean_absolute_error: 671.7496 - val_loss: 941391.8789 - val_mean_absolute_error: 660.5804
Epoch 7/10
732390/732390 [==============================] - 36s 49us/step - loss: 982592.7586 - mean_absolute_error: 666.6328 - val_loss: 945146.3742 - val_mean_absolute_error: 659.2310
Epoch 8/10
732390/732390 [==============================] - 35s 47us/step - loss: 958592.3347 - mean_absolute_error: 659.9552 - val_loss: 890162.8889 - val_mean_absolute_error: 643.0346
Epoch 9/10
732390/732390 [==============================] - 36s 49us/step - loss: 938630.9130 - mean_absolute_error: 652.7611 - val_loss: 867834.9076 - val_mean_absolute_error: 637.6027
Epoch 10/10
732390/732390 [==============================] - 35s 47us/step - loss: 920924.4739 - mean_absolute_error: 647.1866 - val_loss: 880487.3983 - val_mean_absolute_error: 633.7560
Metric  loss : 690.1
Metric  mean_absolute_error : 690.1

Les résultats sont meilleurs mais à mon avis pas de façon très significative.

Par la suite, l’auteur essaie différentes options.

Le dernier test se fait avec un réseau plus profond et plus d’epochs.

model = Sequential()
model.add(Dense(350,input_dim = 44,activation="relu"))
model.add(Dense(350,activation="relu"))
model.add(Dense(350,activation="relu"))
model.add(Dense(350,activation="relu"))
model.add(Dense(350,activation="relu"))
model.add(Dense(1,activation = "linear"))
model.compile(optimizer='adam',loss="mean_squared_error",metrics=["mean_absolute_error"])

model.fit(x_train,y_train, validation_data=(x_val,y_val),epochs=15,batch_size=64,callbacks=[history])

result = model.evaluate(x_test,y_test)
for i in range(len(model.metrics_names)):
  print("Metric ",model.metrics_names[i],":",str(round(result[i],2)))
Train on 732390 samples, validate on 81377 samples
Epoch 1/15
732390/732390 [==============================] - 43s 58us/step - loss: 1668036.1558 - mean_absolute_error: 848.7682 - val_loss: 1391763.4342 - val_mean_absolute_error: 822.2929
Epoch 2/15
732390/732390 [==============================] - 43s 59us/step - loss: 1178223.9480 - mean_absolute_error: 727.8970 - val_loss: 1326927.5146 - val_mean_absolute_error: 767.8719
Epoch 3/15
732390/732390 [==============================] - 42s 57us/step - loss: 1103083.9624 - mean_absolute_error: 702.5887 - val_loss: 1103129.3224 - val_mean_absolute_error: 708.5258
Epoch 4/15
732390/732390 [==============================] - 43s 59us/step - loss: 1060283.2808 - mean_absolute_error: 688.9944 - val_loss: 1014199.5701 - val_mean_absolute_error: 679.0442
Epoch 5/15
732390/732390 [==============================] - 41s 56us/step - loss: 1038514.9163 - mean_absolute_error: 682.2779 - val_loss: 1043350.7875 - val_mean_absolute_error: 693.5402
Epoch 6/15
732390/732390 [==============================] - 44s 61us/step - loss: 1019344.1514 - mean_absolute_error: 676.8103 - val_loss: 978472.8194 - val_mean_absolute_error: 662.1369
Epoch 7/15
732390/732390 [==============================] - 41s 56us/step - loss: 999939.9721 - mean_absolute_error: 670.5147 - val_loss: 1120414.9661 - val_mean_absolute_error: 729.1646
Epoch 8/15
732390/732390 [==============================] - 43s 58us/step - loss: 976066.5027 - mean_absolute_error: 663.1325 - val_loss: 972052.0571 - val_mean_absolute_error: 674.5352
Epoch 9/15
732390/732390 [==============================] - 41s 56us/step - loss: 962188.0513 - mean_absolute_error: 659.0306 - val_loss: 884975.0631 - val_mean_absolute_error: 634.9357
Epoch 10/15
732390/732390 [==============================] - 43s 58us/step - loss: 946094.2717 - mean_absolute_error: 653.3623 - val_loss: 988205.5985 - val_mean_absolute_error: 665.5666
Epoch 11/15
732390/732390 [==============================] - 41s 56us/step - loss: 931117.2877 - mean_absolute_error: 648.1744 - val_loss: 873690.6011 - val_mean_absolute_error: 634.2235
Epoch 12/15
732390/732390 [==============================] - 42s 58us/step - loss: 912910.2298 - mean_absolute_error: 642.1214 - val_loss: 910412.7187 - val_mean_absolute_error: 639.3000
Epoch 13/15
732390/732390 [==============================] - 43s 59us/step - loss: 895185.9466 - mean_absolute_error: 635.9180 - val_loss: 838907.5059 - val_mean_absolute_error: 618.2833
Epoch 14/15
732390/732390 [==============================] - 43s 58us/step - loss: 889846.4490 - mean_absolute_error: 633.7508 - val_loss: 851292.2353 - val_mean_absolute_error: 628.7147
Epoch 15/15
732390/732390 [==============================] - 41s 56us/step - loss: 882237.3479 - mean_absolute_error: 631.4782 - val_loss: 985618.9676 - val_mean_absolute_error: 697.8313
203442/203442 [==============================] - 7s 35us/step
Metric  loss : 982963.56
Metric  mean_absolute_error : 694.82

Je ne constate pas les performances décrites dans le livre. J’y reviendrai.

Les chapitres suivants seront présentés dans un prochain article.

(mon code et ici)

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *