Dans cet article, nous décrivons comment construire un modèle d'apprentissage automatique pour la reconnaissance automatique de chiffres manuscrits. Ce modèle est généré à partir d'un script Python distribué, exécuté par le service de Calcul Haute Performance (HPC) Qarnot. La base de chiffres manuscrits que nous souhaitons reconnaître est celle de la base de données MNIST.
Nous disposons d'un jeu de données (Train_Set) d'images, chacune correspondant à un chiffre manuscrit. Ces correspondances sont codées dans un vecteur (labels) qui, étant donné l'indice d'une image, renvoie un chiffre de 0 à 9. L'objectif est d'induire un modèle capable de prédire parfaitement les chiffres sur un autre jeu d'images (Test_Set). Pour le Train_Set et le Test_Set, nous allons utiliser la collection de chiffres manuscrits de la base de données MNIST. Cette collection comprend plus de 70 000 images d'échantillons. Les images sont en niveaux de gris et normalisées pour tenir dans 28×28 pixels. Dans la base de données, chaque chiffre est associé à plusieurs images manuscrites.
Pour construire un modèle d'apprentissage automatique, nous avons besoin d'une représentation numérique (un encodage) des données à partir desquelles nous souhaitons induire un modèle. Dans le cas de MNIST, cette représentation est déjà fournie. En effet, chaque image de la base de données est représentée comme une matrice de 28×28 entiers, capturant le niveau d'obscurité de chaque pixel. Ce niveau varie de 0 (blanc) à 255 (noir). Ci-dessous, par exemple, nous montrons comment une image manuscrite correspondant au chiffre 1 est encodée (normalisée à 1.0). La bibliothèque scikit-learn implémente une fonction intégrée pour charger les données de la base MNIST. Ci-dessous, nous présentons un script pour charger les données de MNIST et créer le Train_Set et le Test_Set.
from sklearn.datasets import fetch_openml
# Fetch MNIST database
mnist = fetch_openml('mnist_784', version=1, cache=True)
X, y = mnist.data / 255., mnist.target
Vous pouvez consulter certaines valeurs des tableaux X
et y
. Chaque élément du tableau X
est un vecteur de 28×28=784 valeurs entre 0 et 255.
>>> y[0]
0.0
>>>X[0].reshape((28,28))
array([[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 159, 253, 159, 50, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 238, 252, 252, 252, 237, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 54, 227, 253, 252, 239, 233, 252, 57, 6, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 60, 224, 252, 253, 252, 202, 84, 252, 253, 122, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 163, 252, 252, 252, 253, 252, 252, 96, 189, 253, 167, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 238, 253, 253, 190, 114, 253, 228, 47, 79, 255, 168, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 238, 252, 252, 179, 12, 75, 121, 21, 0, 0, 253, 243, 50, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 38, 165, 253, 233, 208, 84, 0, 0, 0, 0, 0, 0, 253, 252, 165, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 7, 178, 252, 240, 71, 19, 28, 0, 0, 0, 0, 0, 0, 253, 252, 195, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 57, 252, 252, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 253, 252, 195, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 198, 253, 190, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 253, 196, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 76, 246, 252, 112, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 253, 252, 148, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 85, 252, 230, 25, 0, 0, 0, 0, 0, 0, 0, 0, 7, 135, 253, 186, 12, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 85, 252, 223, 0, 0, 0, 0, 0, 0, 0, 0, 7, 131, 252, 225, 71, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 85, 252, 145, 0, 0, 0, 0, 0, 0, 0, 48, 165, 252, 173, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 86, 253, 225, 0, 0, 0, 0, 0, 0, 114, 238, 253, 162, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 85, 252, 249, 146, 48, 29, 85, 178, 225, 253, 223, 167, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 85, 252, 252, 252, 229, 215, 252, 252, 252, 196, 130, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 28, 199, 252, 252, 253, 252, 252, 233, 145, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 25, 128, 252, 253, 252, 141, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=uint8)
La base de données MNIST est classée comme suit : X[0] à X[5922] sont des '0', X[5923] à X[12664] sont des '1', X[5924] à X[18622] sont des '2', et ainsi de suite.
Dans les scripts suivants, nous allons nous concentrer uniquement sur les chiffres '0', '1' et '2' au sein du sous-ensemble X[:L], y[:L].
La méthode des k plus proches voisins (KNN) est l'une des méthodes les plus intuitives pour construire des modèles d'apprentissage automatique. L'idée est la suivante : supposons que nous ayons une base d'images X_train
, associée aux chiffres correspondants y_train
. Si nous voulons savoir à quel chiffre correspond une nouvelle entrée x, nous calculons simplement la similarité entre x et toutes les autres images dans X_train.
Pour définir la similarité, le moyen le plus simple est d'utiliser une mesure de distance de base. Par défaut, le classifieur utilisera la distance euclidienne entre les vecteurs. Par exemple, si p et q sont deux vecteurs à 784 dimensions, leur distance euclidienne mutuelle est définie par :
d(p,q)=i=1∑n(pi−qi)2
Les K images les plus similaires, c'est-à-dire celles avec la distance la plus petite par rapport à l'image candidate, sont ensuite sélectionnées. Nous prenons alors les chiffres correspondants dans Y_train et renvoyons le chiffre le plus fréquent. Avec scikit-learn, nous pouvons implémenter ce schéma d'apprentissage automatique simplement avec le code suivant.
import sys
from sklearn import neighbors
from sklearn.datasets import fetch_openml
# Fetch MNIST database
mnist = fetch_openml('mnist_784', version=1, cache=True)
# crop to keep only 0's, 1's and 2's and normalize greyscale to 1.0
L = 18622
X, y = mnist.data[:L] / 255., mnist.target[:L]
# split even and odd elements into training and testing subsets
X_train, X_test = X[0::2], X[1::2]
y_train, y_test = y[0::2], y[1::2]
# create a simple KNN classifier with k=2 and uniform weight policy
clf = neighbors.KNeighborsClassifier(2, weights='uniform')
# fit the model using X_train as training data and y_train as target values
clf.fit(X_train, y_train)
# return the mean accuracy on the training and testing sets
print('Training set score: %f' % clf.score(X_train, y_train))
print('Test set score: %f' % clf.score(X_test, y_test))
Les scores mesurent le nombre moyen de bonnes prédictions du modèle. Plus il est élevé, meilleur est le modèle. Dans cet exemple, nous n'avons considéré que les 18 622 premières images de la base MNIST. Sans cette restriction, le processus aurait été plus long. En exécutant le script Python, nous obtenons :
Training set score: 0.994200Test set score: 0.993986
Le score obtenu est élevé. Cependant, il convient de noter que nous n'avons pas considéré l'intégralité de la base de données. De plus, de meilleurs scores sont possibles. En effet, nous avons exécuté la méthode KNN avec K=2 en supposant que tous les points dans chaque voisinage sont pondérés de manière égale (weight='uniform'
). En pratique, cependant, il pourrait être plus intéressant de considérer plus de voisins (une valeur plus grande pour K) ou d'autres définitions du chiffre le plus fréquent. Pour inclure ces possibilités, nous proposons de modifier le script ci-dessus comme suit.
import sys
from sklearn import neighbors
from sklearn.datasets import fetch_openml
# Fetch MNIST database
mnist = fetch_openml('mnist_784', version=1, cache=True)
options = [['2', 'uniform'], ['2', 'distance'], ['3', 'uniform'], ['3', 'distance'] ]
index = int(sys.argv[1])
# crop to keep only 0's, 1's and 2's and normalize greyscale to 1.0
L = 18622
X, y = mnist.data[:L] / 255., mnist.target[:L]
# split even and odd elements into training and testing subsets
X_train, X_test = X[0::2], X[1::2]
y_train, y_test = y[0::2], y[1::2]
# create a simple KNN classifier with the specified k and weight policy
n_neighbors = int(options[index][0])
weights = options[index][1]
print ('(n_neighbors: '+ str(n_neighbors) +' , '+'weigths: '+weights+')')
clf = neighbors.KNeighborsClassifier(n_neighbors=n_neighbors, weights=weights)
# fit the model using X_train as training data and y_train as target values
clf.fit(X_train, y_train)
# return the mean accuracy on the training and testing sets
print('Training set score: %f' % clf.score(X_train, y_train))
print('Test set score: %f' % clf.score(X_test, y_test))
Le nouveau script mnist_knn2.py
propose quatre combinaisons possibles de paramètres pour le modèle KNN. Les deux premières combinaisons génèrent un modèle 2-NN et les deux autres un modèle 3-NN.
smith@wesson python mnist_knn2.py 1 (n_neighbors: 2 , weigths: distance) Training set score: 1.000000 Test set score: 0.999785
On peut remarquer qu'en changeant la politique de pondération de 'uniform'
à 'distance'
, nous obtenons un meilleur score de prédiction.
Ainsi, pour obtenir le meilleur modèle KNN, il faudrait essayer toutes les combinaisons de paramètres possibles. Cependant, cela pourrait prendre beaucoup de temps, surtout si nous ne limitions pas L à 18 622. Une solution pour réduire ce temps est d'utiliser le service HPC Qarnot pour construire les modèles en parallèle.
Avec le SDK Qarnot, nous pouvons lancer la construction des modèles KNN de notre problème de manière concurrente. Pour cela, nous utilisons le script suivant :
import qarnot
conn = qarnot.connection.Connection(client_token='<YOUR_API_TOKEN>')
task = conn.create_task('mnist-digitRecognition', 'docker-network', 4)
d = conn.create_bucket('digit-recognition-bucket')
d.add_file('mnist_knn2.py')
task.resources.append(d)
task.constants['DOCKER_REPO'] = 'huanjason/scikit-learn'
task.constants['DOCKER_CMD'] = 'python mnist_knn2.py ${FRAME_ID}'
task.run()
Dans ce script, nous créons d'abord une tâche avec 4 frames (cadres) : task = conn.create_task("mnist-digitRecognition", "docker-network", 4)
. La tâche sera exécutée avec l'image Docker Hub nommée "huanjason/scikit-learn"
. Cette image inclut Python 3.7, scikit-learn et numpy. Ensuite, chaque frame traitera la commande python mnist_knn2.py ${FRAME_ID}
.
Par définition, les valeurs possibles pour ${FRAME_ID}
sont 0, 1, 2, 3. Ainsi, le frame numéro i générera le modèle KNN dont les paramètres sont définis dans options[i]
.
En exécutant ce script, nous obtenons les résultats. L'exécution peut également être surveillée sur la console Qarnot. Une fois terminée, le résultat est disponible dans l'onglet stdout :
1> (n_neighbors: 2 , weigths: distance)
0> (n_neighbors: 2 , weigths: uniform)
2> (n_neighbors: 3 , weigths: uniform)
3> (n_neighbors: 3 , weigths: distance)
0> Training set score: 0.994200
1> Training set score: 1.000000
2> Training set score: 0.995274
3> Training set score: 1.000000
1> Test set score: 0.999785
0> Test set score: 0.993986
2> Test set score: 0.995060
3> Test set score: 0.999785
Pour plus d'informations, le lecteur intéressé peut consulter les paramètres KNN définis dans [2] et définir une exploration parallèle plus large en redéfinissant le tableau options
. Nous recommandons également de modifier le script ci-dessus pour utiliser les 60 000 premières images de la base de données MNIST pour l'entraînement et le reste pour le test.
Avec les services HPC Qarnot, vous pouvez aller bien au-delà de ce qui a pu être décrit dans cet article. Vous proposerez probablement de nombreux cas d'utilisation plus intéressants. Alors, lancez-vous ! Essayez-le maintenant et partagez vos commentaires avec la communauté, afin que nous puissions continuer à améliorer ce produit.
[1] L'algorithme KNN (Wikipedia)
[2] Classifieur KNN dans Scikit-learn (scikit-learn.org)
[3] Mesures de similarité / distance pour KNN (youtube)