Premier réseau de neurones avec keras

Keras est simple est puissant pour les réseaux de neurones en python. Essayez-le dans cette recette pas à pas

artist view of a neural network

Introduction

Dans mon article Reconnaissance de Chiffres Manuscrits avec scikit-learn, nous avons comment entraîner un réseau de neurones simple avec scikit-learn. Et nous avons pu classer des images de chiffres manuscrits avec une précision supérieure à 90%.

Mais scikit-learn n'est pas vraiment adapté aux réseaux de neurones.

Son but est en fait de fournir une interface pour l'entraînement et le test d'une grande variété d'algorithmes de machine learning : réseaux de neurones, mais aussi support vector machines, plus proches voisins, arbres de décision, etc. En effet, dans vos projets de machine learning, vous passerez le plus clair de votre temps à choisir le bon algorithme, et à le régler pour obtenir les meilleures performances. Scikit-learn a été conçu pour faciliter ces tâches au maximum.

Cependant, pour ce qui est des réseaux de neurones, scikit-learn a deux désavantages :

  • son interface ne permet pas de construire un réseau de neurones complexe, ni de contrôler les détails de son fonctionnement.
  • il n'est pas adapté au deep learning.

Dans cet article, nous allons répéter la classification des chiffres manuscrits avec Keras, une interface de haut niveau vers TensorFlow pour le deep learning. Vous apprendrez à :

  • Installer Keras
  • Créer un réseau de neurones simple
  • Estimer ses performances

Dans un futur article, nous verrons comment faire du deep learning avec Keras.

Prérequis:

Installation

Pour interagir avec ce notebook, le plus simple est de le faire tourner sur Google Colab en cliquant ici.

Si vous préférez le faire tourner sur votre machine, suivez les instructions ci-dessous.

D'abord, Installez Anaconda pour le Machine Learning et la Data Science avec python.

Créez un environnement pour ce tutoriel :

conda create -n hwd_keras

Activez-le :

conda activate hwd_keras

Installez les packages nécessaires dans l'environnement :

conda install keras  scikit-learn jupyter matplotlib

Enfin, téléchargez ce notebook et ouvrez le avec jupyter notebook :

jupyter notebook handwritten_digits_keras_fr.ipynb

Préparation de l'échantillon de données

Comme dans reconnaissance de chiffres manuscrits avec scikit-learn, nous allons utiliser l'échantillon de chiffres fourni avec scikit-learn. Les chiffres sont des images 8x8, et nous allons les traiter avec un réseau de neurones avec:

  • une couche d'entrée avec 8x8 = 64 neurones
  • une couche cachée de 15 neurones
  • une couche de sortie de 10 neurones, correspondant aux dix catégories de chiffres.

D'abord, initialisons nos outils et chargeons l'échantillon:

In [1]:
import numpy as np
import matplotlib.pyplot as plt
import os 
# for some reason, the following 
# is needed to run on mac os X
os.environ['KMP_DUPLICATE_LIB_OK']='True'  

from sklearn import datasets
digits = datasets.load_digits()

La couche d'entrée requiert un tableau 1D de 64 valeurs, mais nos images sont 2D, avec 8x8 pixels. Nous devons donc les sérialiser:

In [2]:
x = digits.images.reshape((len(digits.images), -1))
x.shape
Out[2]:
(1797, 64)

De plus, un peu de travail est nécessaire avant de pouvoir utiliser les étiquettes.

Pour l'instant, digits.target contient le chiffre auquel correspond chaque image dans l'échantillon:

In [3]:
digits.target
Out[3]:
array([0, 1, 2, ..., 8, 9, 8])

Mais avec Keras, nous devons construire un réseau avec 10 neurones de sortie. C'est aussi le cas avec scikit-learn, bien que ce soit caché à l'utilisateur. Au cours de l'entraînement, Keras devra comparer les 10 valeurs de sortie de ces neurones à l'étiquette pour donner un retour au réseau et lui permettre de s'améliorer. Mais comment pouvons nous comparer un tableau de 10 valeurs à une seule valeur, celle de l'étiquette?

La solution est de traduire chaque étiquette en un tableau de taille 10 (une technique appelée one-hot encoding):

  • la valeur 0 donne [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
  • 1 donne [0, 1, 0, 0, 0, 0, 0, 0, 0, 0]
  • ...
  • 9 donne [0, 0, 0, 0, 0, 0, 0, 0, 0, 1]

Après cela, les valeurs des 10 neurones de sortie, qui sont des probabilités entre 0 et 1, peuvent être directement comparées aux 10 valeurs de l'étiquette.

De cette façon, pour un chiffre donné, par exemple 0, le réseau de neurones sera entraîné à donner une probabilité élevée sur le premier neurone de sortie, et une faible probabilité sur les neurones suivants.

Cet encodage se fait facilement avec les outils fournis par Keras:

In [4]:
from keras.utils import np_utils
y = np_utils.to_categorical(digits.target,10)
print(digits.target)
print(y)
Using TensorFlow backend.
[0 1 2 ... 8 9 8]
[[1. 0. 0. ... 0. 0. 0.]
 [0. 1. 0. ... 0. 0. 0.]
 [0. 0. 1. ... 0. 0. 0.]
 ...
 [0. 0. 0. ... 0. 1. 0.]
 [0. 0. 0. ... 0. 0. 1.]
 [0. 0. 0. ... 0. 1. 0.]]

Nous pouvons maintenant partager nos données entre un échantillon d'entraînement et un échantillon de test:

In [5]:
split_limit=1000
x_train = x[:split_limit]
y_train = y[:split_limit]
x_test = x[split_limit:]
y_test = y[split_limit:]

Les 1000 premières images et étiquettes seront utilisées pour l'entraînement, et les suivantes pour l'évaluation des performances de notre réseau.

Création du réseau de neurones avec Keras

Après avoir importé les outils nécessaires, créons le réseau de neurones.

In [6]:
from keras import layers, Model, optimizers, regularizers
In [7]:
# Création de la couche d'entrée
# 
# On spécifie que cette couche doit 
# avoir 64 neurones, un pour chaque 
# pixel dans nos images. 
# Les neurones d'entrée ne font rien, 
# ils se contentent de transférer la 
# valeur sur chaque pixel à la couche 
# suivante.
img_input = layers.Input(shape=(64,))


# Création de la couche cachée
#
# Cette couche est dense, ce qui veut 
# dire que chacun de ses neurones est 
# connecté à tous les neurones de la 
# couche précédente (la couche d'entrée).
# Nous parlerons de l'activation dans un
# futur post.
tmp = layers.Dense(15, 
                   activation='sigmoid')(img_input)

# Création de la couche de sortie
# 
# La couche de sortie est dense également. 
# Elle doit avoir 10 neurones, correspondant
# aux 10 catégories de chiffres. 
output = layers.Dense(10, 
                      activation='softmax')(tmp)

# Création du réseau de neurones 
# à partir des couches
model = Model(img_input, output)

# Impression d'une description du réseau
model.summary()

# =================================================
# Merci de pas prêter attention à cette partie. 
# Nous parlerons de la régularisation plus tard.
# Pour l'instant, il suffit de noter que la 
# régularisation aide le réseau à converger 
# correctement. 
# J'ai rajouté cette régularisation ici car elle 
# est effectuée par défaut dans scikit-learn,  
# et que nous voulons pouvoir comparer les résultats
# de keras et scikit-learn. 
l2_rate = 1e-4
for layer in model.layers:
    if hasattr(layer, 'kernel_regularizer'):
        layer.kernel_regularizer = regularizers.l2(l2_rate)
        layer.bias_regularizer = regularizers.l2(l2_rate)
        layer.activity_regularizer = regularizers.l2(l2_rate)
# =================================================

# Définition de la méthode d'apprentissage, 
# et compilation du modèle.
# 
# Le modèle doit être compilé pour être 
# entraîné et utilisé. 
# Les arguments loss, optimizer, et metric
# seront couverts dans un futur post. 
model.compile(loss='categorical_crossentropy',
              optimizer=optimizers.SGD(lr=0.1, momentum=0.9),
              metrics=['accuracy'])
Model: "model_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 64)                0         
_________________________________________________________________
dense_1 (Dense)              (None, 15)                975       
_________________________________________________________________
dense_2 (Dense)              (None, 10)                160       
=================================================================
Total params: 1,135
Trainable params: 1,135
Non-trainable params: 0
_________________________________________________________________

Finalement, nous pouvons entraîner le réseau:

In [8]:
history = model.fit(x=x_train, y=y_train, validation_data=(x_test,y_test),
                    batch_size=100, epochs=50)
Train on 1000 samples, validate on 797 samples
Epoch 1/50
1000/1000 [==============================] - 0s 397us/step - loss: 2.1134 - accuracy: 0.2210 - val_loss: 1.7801 - val_accuracy: 0.5144
Epoch 2/50
1000/1000 [==============================] - 0s 25us/step - loss: 1.5518 - accuracy: 0.6310 - val_loss: 1.3405 - val_accuracy: 0.6838
Epoch 3/50
1000/1000 [==============================] - 0s 25us/step - loss: 1.0895 - accuracy: 0.8280 - val_loss: 0.9646 - val_accuracy: 0.8369
Epoch 4/50
1000/1000 [==============================] - 0s 25us/step - loss: 0.7730 - accuracy: 0.8940 - val_loss: 0.6991 - val_accuracy: 0.8783
Epoch 5/50
1000/1000 [==============================] - 0s 24us/step - loss: 0.5380 - accuracy: 0.9200 - val_loss: 0.5841 - val_accuracy: 0.8883
Epoch 6/50
1000/1000 [==============================] - 0s 26us/step - loss: 0.4211 - accuracy: 0.9280 - val_loss: 0.5142 - val_accuracy: 0.8846
Epoch 7/50
1000/1000 [==============================] - 0s 25us/step - loss: 0.3359 - accuracy: 0.9490 - val_loss: 0.4503 - val_accuracy: 0.8996
Epoch 8/50
1000/1000 [==============================] - 0s 28us/step - loss: 0.2813 - accuracy: 0.9490 - val_loss: 0.4092 - val_accuracy: 0.8971
Epoch 9/50
1000/1000 [==============================] - 0s 27us/step - loss: 0.2436 - accuracy: 0.9600 - val_loss: 0.3884 - val_accuracy: 0.9046
Epoch 10/50
1000/1000 [==============================] - 0s 25us/step - loss: 0.2001 - accuracy: 0.9720 - val_loss: 0.3862 - val_accuracy: 0.9072
Epoch 11/50
1000/1000 [==============================] - 0s 29us/step - loss: 0.1715 - accuracy: 0.9800 - val_loss: 0.3711 - val_accuracy: 0.8984
Epoch 12/50
1000/1000 [==============================] - 0s 26us/step - loss: 0.1630 - accuracy: 0.9760 - val_loss: 0.3590 - val_accuracy: 0.9021
Epoch 13/50
1000/1000 [==============================] - 0s 25us/step - loss: 0.1493 - accuracy: 0.9790 - val_loss: 0.3427 - val_accuracy: 0.9034
Epoch 14/50
1000/1000 [==============================] - 0s 24us/step - loss: 0.1417 - accuracy: 0.9840 - val_loss: 0.3611 - val_accuracy: 0.9046
Epoch 15/50
1000/1000 [==============================] - 0s 25us/step - loss: 0.1282 - accuracy: 0.9870 - val_loss: 0.3388 - val_accuracy: 0.8996
Epoch 16/50
1000/1000 [==============================] - 0s 25us/step - loss: 0.1126 - accuracy: 0.9890 - val_loss: 0.3430 - val_accuracy: 0.8959
Epoch 17/50
1000/1000 [==============================] - 0s 26us/step - loss: 0.1049 - accuracy: 0.9890 - val_loss: 0.3468 - val_accuracy: 0.8959
Epoch 18/50
1000/1000 [==============================] - 0s 25us/step - loss: 0.0970 - accuracy: 0.9920 - val_loss: 0.3240 - val_accuracy: 0.9122
Epoch 19/50
1000/1000 [==============================] - 0s 25us/step - loss: 0.0937 - accuracy: 0.9890 - val_loss: 0.3059 - val_accuracy: 0.9059
Epoch 20/50
1000/1000 [==============================] - 0s 26us/step - loss: 0.0834 - accuracy: 0.9950 - val_loss: 0.3067 - val_accuracy: 0.9072
Epoch 21/50
1000/1000 [==============================] - 0s 26us/step - loss: 0.0815 - accuracy: 0.9940 - val_loss: 0.3240 - val_accuracy: 0.9072
Epoch 22/50
1000/1000 [==============================] - 0s 31us/step - loss: 0.0866 - accuracy: 0.9930 - val_loss: 0.3348 - val_accuracy: 0.9034
Epoch 23/50
1000/1000 [==============================] - 0s 29us/step - loss: 0.0861 - accuracy: 0.9930 - val_loss: 0.3379 - val_accuracy: 0.8984
Epoch 24/50
1000/1000 [==============================] - 0s 27us/step - loss: 0.0821 - accuracy: 0.9890 - val_loss: 0.3091 - val_accuracy: 0.9059
Epoch 25/50
1000/1000 [==============================] - 0s 25us/step - loss: 0.0710 - accuracy: 0.9940 - val_loss: 0.3208 - val_accuracy: 0.9072
Epoch 26/50
1000/1000 [==============================] - 0s 24us/step - loss: 0.0623 - accuracy: 0.9960 - val_loss: 0.3216 - val_accuracy: 0.9034
Epoch 27/50
1000/1000 [==============================] - 0s 25us/step - loss: 0.0606 - accuracy: 0.9950 - val_loss: 0.3244 - val_accuracy: 0.9021
Epoch 28/50
1000/1000 [==============================] - 0s 25us/step - loss: 0.0563 - accuracy: 0.9960 - val_loss: 0.3217 - val_accuracy: 0.9059
Epoch 29/50
1000/1000 [==============================] - 0s 26us/step - loss: 0.0538 - accuracy: 0.9970 - val_loss: 0.3172 - val_accuracy: 0.9046
Epoch 30/50
1000/1000 [==============================] - 0s 26us/step - loss: 0.0511 - accuracy: 0.9960 - val_loss: 0.3208 - val_accuracy: 0.8996
Epoch 31/50
1000/1000 [==============================] - 0s 27us/step - loss: 0.0493 - accuracy: 0.9970 - val_loss: 0.3155 - val_accuracy: 0.9021
Epoch 32/50
1000/1000 [==============================] - 0s 27us/step - loss: 0.0466 - accuracy: 0.9970 - val_loss: 0.3138 - val_accuracy: 0.9009
Epoch 33/50
1000/1000 [==============================] - 0s 26us/step - loss: 0.0444 - accuracy: 0.9970 - val_loss: 0.3192 - val_accuracy: 0.8971
Epoch 34/50
1000/1000 [==============================] - 0s 29us/step - loss: 0.0432 - accuracy: 0.9970 - val_loss: 0.3185 - val_accuracy: 0.9009
Epoch 35/50
1000/1000 [==============================] - 0s 32us/step - loss: 0.0419 - accuracy: 0.9970 - val_loss: 0.3191 - val_accuracy: 0.9009
Epoch 36/50
1000/1000 [==============================] - 0s 30us/step - loss: 0.0405 - accuracy: 0.9970 - val_loss: 0.3177 - val_accuracy: 0.8971
Epoch 37/50
1000/1000 [==============================] - 0s 26us/step - loss: 0.0396 - accuracy: 0.9970 - val_loss: 0.3198 - val_accuracy: 0.8996
Epoch 38/50
1000/1000 [==============================] - 0s 26us/step - loss: 0.0384 - accuracy: 0.9970 - val_loss: 0.3179 - val_accuracy: 0.8984
Epoch 39/50
1000/1000 [==============================] - 0s 24us/step - loss: 0.0378 - accuracy: 0.9970 - val_loss: 0.3199 - val_accuracy: 0.9009
Epoch 40/50
1000/1000 [==============================] - 0s 24us/step - loss: 0.0369 - accuracy: 0.9970 - val_loss: 0.3224 - val_accuracy: 0.8971
Epoch 41/50
1000/1000 [==============================] - 0s 23us/step - loss: 0.0357 - accuracy: 0.9970 - val_loss: 0.3223 - val_accuracy: 0.8984
Epoch 42/50
1000/1000 [==============================] - 0s 23us/step - loss: 0.0352 - accuracy: 0.9970 - val_loss: 0.3256 - val_accuracy: 0.8971
Epoch 43/50
1000/1000 [==============================] - 0s 22us/step - loss: 0.0346 - accuracy: 0.9970 - val_loss: 0.3218 - val_accuracy: 0.8996
Epoch 44/50
1000/1000 [==============================] - 0s 23us/step - loss: 0.0337 - accuracy: 0.9970 - val_loss: 0.3236 - val_accuracy: 0.8971
Epoch 45/50
1000/1000 [==============================] - 0s 22us/step - loss: 0.0332 - accuracy: 0.9970 - val_loss: 0.3191 - val_accuracy: 0.9009
Epoch 46/50
1000/1000 [==============================] - 0s 23us/step - loss: 0.0324 - accuracy: 0.9970 - val_loss: 0.3235 - val_accuracy: 0.8984
Epoch 47/50
1000/1000 [==============================] - 0s 23us/step - loss: 0.0321 - accuracy: 0.9970 - val_loss: 0.3240 - val_accuracy: 0.8984
Epoch 48/50
1000/1000 [==============================] - 0s 22us/step - loss: 0.0322 - accuracy: 0.9960 - val_loss: 0.3266 - val_accuracy: 0.8984
Epoch 49/50
1000/1000 [==============================] - 0s 23us/step - loss: 0.0312 - accuracy: 0.9970 - val_loss: 0.3223 - val_accuracy: 0.9021
Epoch 50/50
1000/1000 [==============================] - 0s 23us/step - loss: 0.0308 - accuracy: 0.9980 - val_loss: 0.3260 - val_accuracy: 0.8971

Évaluation des performances

Les prédictions du réseau de neurones sont évaluées pour tous les exemples de l'échantillon de test:

In [10]:
predictions = model.predict(x_test)
print(predictions[3])
[4.6237273e-04 2.6946730e-04 7.0270261e-04 1.0361271e-03 6.8260713e-05
 9.9545693e-01 4.7021132e-04 8.3707950e-05 1.3659039e-03 8.4294996e-05]

Pour chaque exemple, la prédiction est un tableau de 10 valeurs. Chaque valeur est la probabilité, estimée par le réseau, que l'image appartienne à une catégorie donnée.

La catégorie prédite est celle avec la probabilité la plus grande.

Écrivons maintenant une petite fonction pour afficher une image donnée, et imprimer la catégorie prédite ainsi que la catégorie réelle:

In [11]:
def plot_prediction(index):
    print('predicted probabilities:')
    print(predictions[index])
    print('predicted category', np.argmax(predictions[index]))
    print('true probabilities:')
    print(y_test[index])
    print('true category', np.argmax(y_test[index]))
    img = x_test[index].reshape(8,8)
    plt.imshow(img)

Dans cette fonction, on obtient la catégorie avec np.argmax qui, pour un tableau, retourne l'index correspondant à la valeur maximum.

Utilisons cette fonction pour regarder quelques exemples (changez juste l'index pour choisir un autre exemple):

In [12]:
plot_prediction(3)
predicted probabilities:
[4.6237273e-04 2.6946730e-04 7.0270261e-04 1.0361271e-03 6.8260713e-05
 9.9545693e-01 4.7021132e-04 8.3707950e-05 1.3659039e-03 8.4294996e-05]
predicted category 5
true probabilities:
[0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]
true category 5

Enfin, calculons la précision, c'est à dire la probabilité de classer les chiffres correctement.

On calcule cette précision sur l'échantillon de test, qui n'a pas été utilisé dans l'entraînement du réseau. À nouveau, on utilise np.argmax pour obtenir les catégories vraies et prédites pour chaque exemple.

In [14]:
# Le deuxième argument de argmax spécifie
# que l'on souhaite conserver la première 
# dimension du tableau. 
# Ainsi, argmax est calculé pour chaque 
# exemple. 
# Sans cet argument, argmax retournerait
# une seule valeur, la probabilité maximum 
# dans le tableau complet, 
# en considérant l'ensemble des exemples 
y_test_best = np.argmax(y_test,1)
print(y_test_best.shape)
predictions_best = np.argmax(predictions,1)

from sklearn.metrics import accuracy_score
accuracy_score(y_test_best, predictions_best)
(797,)
Out[14]:
0.8971141781681304

Vous devriez obtenir une précision autour de 91%, similaire à celle que nous avons obtenue dans les mêmes conditions avec scikit-learn.

Le résultat n'est pas reproductible, et la précision variera à chaque fois que vous ré-entraînerez un nouveau réseau. Personnellement, j'obtiens généralement une précision entre 90 et 93%.

Et pour vous, que se passe-t'il lorsque vous répétez l'exercice?

Et ensuite?

Dans ce post, vous avez entraîné votre premier réseau de neurones avec keras.

Keras est un outil incontournable, et nous l'utiliserons régulièrement sur ce blog.

Pour voir comment utiliser le deep learning pour la reconnaissance d'images avec Keras et TensorFlow, jetez un oeil aux articles suivants:


N'hésitez pas à me donner votre avis dans les commentaires ! Je répondrai à toutes les questions.

Et si vous avez aimé cet article, vous pouvez souscrire à ma newsletter pour être prévenu lorsque j'en sortirai un nouveau. Pas plus d'un mail par semaine, promis!

Retour


Encore plus de data science et de machine learning !

Rejoignez ma mailing list pour plus de posts et du contenu exclusif:

Je ne partagerai jamais vos infos.
Partagez si vous aimez cet article: