Retour d'expérience sur l'étude de la base MNIST pour la reconnaissance de chiffres manuscrits

Magazine
Marque
GNU/Linux Magazine
HS n°
Numéro
102
|
Mois de parution
mai 2019
|
Domaines


Résumé

Parmi les premières recherches effectuées sur l'Intelligence Artificielle figure la reconnaissance des chiffres manuscrits. La base d'étude utilisée par l'expert en IA Yann Le Cun pour ses travaux, et maintenant devenue une référence, est appelée base MNIST. Nous allons découvrir comment la récupérer, la manipuler et l'étudier.


Body

Nous vivons actuellement une nouvelle période de développement de l'Intelligence Artificielle (IA). Le sujet est de plus en plus fréquemment évoqué à la télévision, à la radio, dans les journaux et sur Internet. Depuis les années 2010, des progrès très importants, réalisés par Geoffrey Hinton, ont permis de relancer la recherche et de créer des applications qu'il aurait été impossible de produire sans disposer de l'IA. Mais nous ne sommes toutefois qu'au tout début de cette discipline, car si l'on en juge par l'émergence de composants spécialisés  dans l'accélération des traitements (ASIC, FPGA, puces spécialisées comme celles de Nervana, IBM, NVIDIA, Intel ou Apple) et par les travaux encourageants sur les ordinateurs quantiques d'IBM et de Google, nous pouvons nous attendre à une véritable révolution industrielle dans les années qui viennent. Dans cet article, nous nous contenterons modestement de remonter un peu dans le passé en abordant une étape, particulièrement importante dans l'évolution de l'IA, qui s'est déroulée dans les années 1980 et qui a permis de déboucher sur un moyen de reconnaître automatiquement des chiffres manuscrits. Les premières applications concrètes ont été la reconnaissance du montant des chèques bancaires et la lecture des codes postaux sur les enveloppes.

Mais commençons tout d'abord par quelques brefs rappels sur l'IA.

Le programme présenté dans cet article a été testé sur les systèmes suivants :

  • Debian 7.8 (x86_64 Intel(R) Core(TM) i7-3770K CPU @ 3.50 GHz) ;
  • Devuan ascii/ceres (x86_64 Intel(R) Core(TM) i5-2405S CPU @ 2.50 GHz) ;
  • Devuan jessie (x86_64 Intel(R) Core(TM)2 Duo CPU     T7250  @ 2.00 GHz) ;
  • Raspbian GNU/Linux 7 (wheezy) (armv7l ARMv7 Processor rev 5 (v7l) @ 600 MHz).

Le code source est disponible sur [1].

Quelques rappels sur l'IA

L'Intelligence Artificielle a commencé son existence dans les années 1940 grâce à des personnalités comme John Mc Carthy, Marvin L. Minsky, Claude E. Shannon, Alan M. Turing, Donald O. Hebb, et Frank Rosenblatt, parmi les plus célèbres initiateurs. D'autres personnalités ont ensuite émergé et sont maintenant considérées comme des experts mondiaux de l'IA, les principales étant Yann Le Cun, Yoshua Bengio et Geoffrey Hinton. Le développement de l'IA jusqu'à nos jours a été très irrégulier, avec une alternance de succès, d'échecs, d'enthousiasmes, de doutes, de découvertes et plus récemment, d'inquiétudes. Les récents progrès de l'informatique et de la technologie, relatifs à la puissance de calcul (CPU et GPU), à la capacité mémoire (RAM et disques) et aux logiciels (algorithmes), ont permis des avancées très importantes dans les domaines de la reconnaissance d'images (photographique, radiographique), de la traduction automatique des langues, de la robotique, pour ne citer que ceux-là, et ont relancé les recherches liées à l'Intelligence Artificielle. Les technologies actuelles utilisent généralement des réseaux de neurones artificiels (ANN pour Artificial Neural Networks) de différents types : perceptrons multicouches (MLP pour MultiLayer Perceptrons) permettant l'apprentissage profond (deep learning), réseaux convolutifs (CNN pour Convolutional Neural Network), réseaux récurrents (RNN pour Recurrent Neural Network), ainsi que des procédés de mémorisation tels que LSTM (Long Short-Term Memory), entre autres.

L'importance grandissante de l'IA et la nécessité pour la France de la maîtriser ont été soulignées dans le rapport que Cédric Villani, mathématicien lauréat de la médaille Fields en 2010 et député de l'Essonne, a remis au gouvernement au premier trimestre 2018. Des sommes considérables sont actuellement investies par les grandes puissances comme les États-Unis et la Chine pour intensifier la recherche et développer les applications basées sur l'IA. La France, elle aussi, a décidé de se lancer dans cette course à la maîtrise de l'IA, mais avec des moyens malheureusement bien plus faibles que ceux cités précédemment. L'intérêt du public et des scientifiques pour l'IA se développe toutefois, grâce aux efforts importants de communication, de persuasion et de vulgarisation de Cédric Villani, et on peut encore espérer aujourd'hui que la France, malgré ses faibles moyens comparés à ceux des superpuissances, puisse tout de même réussir à faire progresser les résultats dans ce domaine et ne perde pas toute son indépendance.

Venons-en maintenant au sujet qui nous intéresse : une étape, ancienne, mais importante, du développement de l'IA, à savoir les travaux débutés par Yann Le Cun dans les années 1980 aux Bell Labs sur les réseaux de neurones et la reconnaissance des chiffres manuscrits.

Description de la base MNIST

Lorsque l'on apprend un nouveau langage informatique, on commence généralement par créer un premier programme, extrêmement simple, que l'on appelle « hello world », car il se limite à l'affichage du message Hello, World. Cela permet de vérifier que le compilateur ou l'interpréteur fonctionne correctement, et que l'on peut commencer à faire ses premiers pas avec ce langage. En IA, l'équivalent du hello world est un peu plus compliqué, puisqu'il consiste à créer un réseau de neurones capable de reconnaître les chiffres manuscrits de la base MNIST. Mais quelle est donc cette base MNIST qui semble si fondamentale dans la compréhension des bases de l'IA ?

La base MNIST (Modified National Institute of Standards and Technology database) est un ensemble de données constitué d'un jeu d'apprentissage de 60.000 chiffres décimaux manuscrits et d'un jeu de test de 10.000 chiffres de même nature, mais différents de ceux contenus dans le jeu d'apprentissage. Le jeu d'apprentissage sert à entraîner un réseau de neurones à reconnaître les chiffres manuscrits et le jeu de test sert, quant à lui, à vérifier que ce réseau est capable, après apprentissage, de reconnaître des chiffres qu'il n'a jamais vus. Cette base est issue d'un mélange des bases SD-1 et SD-3 du NIST qui contiennent des chiffres écrits respectivement par des lycéens et des employés du United States Census Bureau (voir [2]). Les images sources, sélectionnées par Chris Burges et Corinna Cortes, ont été initialement codées dans des matrices 20 x 20, puis converties en matrices de 28 lignes par 28 colonnes, chaque composante de la matrice étant codée sur un octet (donc avec 256 valeurs possibles, de 0 à 255) et représentant une quantité d'encre donnée, la valeur 0 indiquant qu'il n'y a pas d'encre dans la case (case blanche) et la valeur 255 indiquant que la case est complètement remplie d'encre (case noire). Les chiffres manuscrits ont été positionnés dans les matrices de façon à placer leur centre de masse au centre de la matrice. Bien évidemment, la suite d'octets constituée par cet ensemble de chiffres manuscrits ne peut pas être visualisée sans l'aide d'outils appropriés. Ceux que nous allons découvrir dans ce qui suit ont été développés spécifiquement pour la manipulation de cette base. Ils ont été intégrés à un programme multi-usage dont nous avons parlé dans [3] et [4] : l'interpréteur d'expressions en notation polonaise inversée nommé RPN.

Cet interpréteur peut être vu comme un programme à tout faire, capable de manipuler tout type d'objets, pourvu qu'il dispose d'opérateurs sachant identifier et effectuer des traitements sur ces objets. Les principales mécaniques de ce programme sont un analyseur lexical capable de lire des données et de reconnaître les mots-clés que sont les noms des opérateurs, ainsi qu'un gestionnaire de la pile opérationnelle contenant les objets et les résultats de l'exécution des opérateurs. Ces derniers seront introduits au fur et à mesure des besoins de traitement.

Récupération de la base MNIST

Le format des données de la base MNIST est défini dans [5], au paragraphe « THE IDX FILE FORMAT ».

Pour manipuler la base MNIST, nous définirons les types d'éléments suivants :

  • TRAINING_ELT (identifiant un élément d'apprentissage, constitué d'une image de chiffre manuscrit et de son label associé) ;
  • TEST_ELT (identifiant un élément de test, constitué d'une image de chiffre manuscrit et de son label associé).

Rien ne nous obligeait à créer deux types distincts pour décrire les éléments des deux jeux de la base MNIST, mais la présence de deux jeux de données ayant des fonctionnalités différentes, et donc des traitements potentiellement différents, nous a incités à les distinguer bien qu'ils aient des formats identiques. Ces types sont utilisés pour identifier les données associées à un chiffre manuscrit d'un jeu particulier. Pour des besoins de simplicité de manipulation de la base, nous allons définir deux autres types d'objets permettant de manipuler les ensembles de façon globale :

  • TRAINING_SET (un ensemble d'éléments de type TRAINING_ELT) ;
  • TEST_SET (un ensemble d'éléments de type TEST_ELT).

La première opération que nous allons avoir besoin d'effectuer est de placer dans la pile opérationnelle les jeux de chiffres manuscrits. Pour cela, nous avons créé l'opérateur read_mnist (voir la fonction rpn_op_read_mnist(), définie via la macro RPN_DEF_OP(rpn_op_read_mnist), dans le fichier rpn_custom.c)  qui effectue les tâches suivantes :

  • création, si nécessaire, des répertoires de stockage des informations ;
  • téléchargement, si nécessaire, des fichiers de la base MNIST à partir du site de Yann Le Cun (voir [5]) ;
  • décompression, si nécessaire, des fichiers téléchargés ;
  • lecture du contenu de la base MNIST ;
  • création des éléments de types TRAINING_ELT et TEST_ELT ;
  • création des éléments de type TRAINING_SET et TEST_SET.

Voyons dès à présent comment utiliser cet opérateur. Le programme rpn, après avoir été généré à partir des sources disponibles sur [1] (voir éventuellement [3] pour la méthode de génération) doit être lancé par la commande suivante :

$ ./rpn

L'opérateur read_mnist peut ensuite être exécuté par simple saisie de son nom sur l'entrée standard (le clavier dans le cas présent) de rpn :

RPN> read_mnist

La première fois, le résultat ressemblera à ce qui suit :

$ ./rpn

MAIN                 STACK EMPTY

RPN> read_mnist

                read_mnist

rpn_mnist_install_file (5602) : file "train-images-idx3-ubyte" does not exist => uncompress ...

rpn_mnist_uncompress_file (5628) : file "train-images-idx3-ubyte.gz" does not exist => download ...

converted 'http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz' (ANSI_X3.4-1968) -> 'http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz' (UTF-8)

--2019-01-12 20:44:40--  http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz

Resolving yann.lecun.com (yann.lecun.com)... 216.165.22.6

Connecting to yann.lecun.com (yann.lecun.com)|216.165.22.6|:80... connected.

HTTP request sent, awaiting response... 200 OK

Length: 9912422 (9.5M) [application/x-gzip]

Saving to: 'train-images-idx3-ubyte.gz'

train-images-idx3-ubyte.gz      100%[===============================>]   9.45M   250KB/s   in 27s    

2019-01-12 20:45:07 (363 KB/s) - 'train-images-idx3-ubyte.gz' saved [9912422/9912422]

[...]

2019-01-12 20:45:15 (237 KB/s) - 't10k-images-idx3-ubyte.gz' saved [1648877/1648877]

rpn_mnist_uncompress_file (5636) : uncompress (cmd = "t10k-images-idx3-ubyte.gz" ...

rpn_mnist_install_file (5602) : file "t10k-labels-idx1-ubyte" does not exist => uncompress ...

rpn_mnist_uncompress_file (5628) : file "t10k-labels-idx1-ubyte.gz" does not exist => download ...

converted 'http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz' (ANSI_X3.4-1968) -> 'http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz' (UTF-8)

--2019-01-12 20:45:15--  http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz

Resolving yann.lecun.com (yann.lecun.com)... 216.165.22.6

Connecting to yann.lecun.com (yann.lecun.com)|216.165.22.6|:80... connected.

HTTP request sent, awaiting response... 200 OK

Length: 4542 (4.4K) [application/x-gzip]

Saving to: 't10k-labels-idx1-ubyte.gz'

t10k-labels-idx1-ubyte.gz       100%[===============================>]   4.44K  --.-KB/s   in 0.002s

 

2019-01-12 20:45:16 (1.84 MB/s) - 't10k-labels-idx1-ubyte.gz' saved [4542/4542]

rpn_mnist_uncompress_file (5636) : uncompress (cmd = "t10k-labels-idx1-ubyte.gz" ...

Magic number  = 0x00000803

Images number =      10000  (0x00002710)

Dimension 1   =         28  (0x0000001c)

Dimension 2   =         28  (0x0000001c)

Magic number  = 0x00000801

Labels number =      10000  (0x00002710)

Magic number  = 0x00000803

Images number =      60000  (0x0000ea60)

Dimension 1   =         28  (0x0000001c)

Dimension 2   =         28  (0x0000001c)

Magic number  = 0x00000801

Labels number =      60000  (0x0000ea60)

 <MNIST_TRAIN>             ***

Lors de la première exécution, cet opérateur crée les répertoires MNIST, TEXTS, IMAGES et TOOLS, puis télécharge avec la commande wget les fichiers constituant la base MNIST. Ces derniers sont déposés dans le répertoire MNIST, puis décompressés.

On peut voir l'appel à la commande wget dans le code de la fonction rpn_mnist_download_file() (dans le fichier rpn_custom.c) :

void rpn_mnist_download_file(char *url)

{

     char                      _cmd[RPN_SYS_CMD_SIZE];

     int                       _ret;

     /* Prepare download command

~~~~~~~~~~~~~~~~~~~~~~~~ */

     sprintf(_cmd, "wget %s", url);

         

     /* Execute the download command

~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */

     if ((_ret = system(_cmd) < 0)) {

          fprintf(stderr, "%s: download command (%s) returned an error (%d)!\n",

                  G.progname, _cmd, _ret);

          perror("system");

          exit(RPN_EXIT_SYSTEM_ERROR);

     }    

}

L'opérateur read_mnist télécharge les fichiers de la base MNIST à partir du site de Yann Le Cun. Les commandes utilisées pour le téléchargement sont les suivantes :

$ wget http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz

$ wget http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz

$ wget http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz

$ wget http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz

Dans le cas où les fichiers sont déjà présents dans le répertoire MNIST, cette étape n'est pas effectuée. Les fichiers sont ensuite décompressés avec les commandes :

$ gunzip train-images-idx3-ubyte.gz

$ gunzip train-labels-idx1-ubyte.gz

$ gunzip t10k-images-idx3-ubyte.gz

$ gunzip t10k-labels-idx1-ubyte.gz

Ils sont ensuite convertis en une suite d'éléments de type TRAINING_ELT (respectivement TEST_ELT) et regroupés sous la forme d'un unique élément TRAINING_SET (respectivement TEST_SET).

Pour des besoins de connaissance des durées de traitement des opérateurs, nous avons créé les opérateurs suivants :

  • sw_on (Stop Watch ON : activation du mode chronométrage, voir la fonction rpn_op_sw_on() dans le fichier rpn_utils.c) ;
  • sw_off (Stop Watch OFF : désactivation du mode chronométrage, voir la fonction rpn_op_sw_off() dans le fichier rpn_utils.c).

Le chronométrage peut, à tout moment, être activé par appel à l'opérateur sw_on et désactivé par appel à  sw_off.

Une deuxième exécution de l'opérateur read_mnist affiche, sur une machine équipée d'un CPU core i7 3770K, les résultats suivants :

$ ./rpn

MAIN                 STACK EMPTY

RPN> sw_on

                     sw_on

RPN> read_mnist

                read_mnist

Magic number  = 0x00000803

Images number =      10000  (0x00002710)

Dimension 1   =         28  (0x0000001c)

Dimension 2   =         28  (0x0000001c)

Magic number  = 0x00000801

Labels number =      10000  (0x00002710)

Magic number  = 0x00000803

Images number =      60000  (0x0000ea60)

Dimension 1   =         28  (0x0000001c)

Dimension 2   =         28  (0x0000001c)

Magic number  = 0x00000801

Labels number =      60000  (0x0000ea60)

read_mnist :      0.057369 s

 <MNIST_TRAIN>             ***  

Ceci nous permet de voir que la lecture de la base et sa préparation pour les manipulations ne prend que 6 centièmes de secondes environ, une fois que les fichiers ont été téléchargés et décompressés.

Sur un Raspberry Pi 2, la même opération sera effectuée un peu moins vite, comme on peut le voir dans la capture épurée suivante :

$ ./rpn

MAIN                 STACK EMPTY

RPN> sw_on

                     sw_on

RPN> read_mnist

                read_mnist

[...]

read_mnist :      0.941503 s

 <MNIST_TRAIN>             ***

Cela dit, cette durée d'initialisation reste quasiment négligeable.

Examinons le contenu de la pile après exécution de read_mnist grâce à l'opérateur stk (voir la fonction rpn_op_stk() dans le fichier rpn_utils.c) qui en affiche un contenu synthétique :

RPN> stk

 <MNIST_TRAIN>             stk

MAIN                 STACK   2   Y : TEST_SET     'MNIST_TESTS' 10000 elts, current idx = 1

MAIN                 STACK   1   X : TRAINING_SET 'MNIST_TRAIN' 60000 elts, current idx = 1

stk :      0.000029 s  

Nous voyons que la pile opérationnelle contient maintenant les deux jeux de données, le jeu de test de 10 000 éléments étant dans le registre Y, sous forme d'un élément de type TEST_SET nommé MNIST_TESTS et le jeu d'apprentissage de 60 000 éléments étant dans le registre X, sous forme d'un élément de type TRAINING_SET nommé MNIST_TRAIN. Nous allons voir, dans la suite de cet article, comment exploiter ces deux éléments.

Manipulation de la base MNIST

Maintenant que les données sont disponibles dans la pile opérationnelle, nous allons pouvoir commencer à les utiliser. Notre première opération sera la récupération d'un élément donné du jeu d'apprentissage. L'opérateur get permet de placer, dans le registre X de la pile, une copie d'un élément (d'index spécifié) du jeu d'éléments stocké dans le registre Y. L'élément contenu dans le registre X étant le jeu TRAINING_SET, l'introduction d'un index, par exemple 1234, dans le registre X a pour effet de décaler l'élément TRAINING_SET dans le registre Y. L'exécution de l'opérateur get, quant à elle, permet de récupérer une copie du 1234e chiffre manuscrit et de remplacer dans le registre X l'index 1234 par l'élément possédant cet index. Comme généralement, la valeur contenue dans le registre X avant opération est placée dans le registre LASTX après opération. L'opérateur stk nous montre le contenu courant de la pile :

RPN> 1234 get

      1234            get

get :      0.000014 s

 <3>             ***

RPN> stk

                       stk

MAIN                 STACK   3   Z : TEST_SET     'MNIST_TESTS' 10000 elts, current idx = 1

MAIN                 STACK   2   Y : TRAINING_SET 'MNIST_TRAIN' 60000 elts, current idx = 1235

MAIN                 STACK   1   X : TRAINING_ELT [idx =    1234, orig =    1234] '3'    784 -- [ 28 x  28] USED   

stk :      0.000044 s  

Nous avons donc bien un élément de type TRAINING_ELT dans le registre X, et son index est bien celui qui a été spécifié (1234). Cet élément reste toutefois relativement mystérieux. Nous pouvons avoir une meilleure idée de son contenu en exécutant l'opérateur mat qui, comme nous l'avons vu dans [4] permet de créer une matrice de composantes et de dimensions spécifiées, mais qui ici a été surchargé pour réaliser une nouvelle opération, à savoir la création d'une matrice de dimensions 28 x 28 issue de celle constituant l'élément récupéré précédemment. La matrice résultante, remplaçant dans le registre X l'élément d'où elle est issue, est de type MATRIX, et peut de ce fait être manipulée comme n'importe quelle autre matrice. On peut notamment afficher ses dimensions avec l'opérateur dim (voir la fonction rpn_op_dim() dans le fichier rpn_custom.c) qui place respectivement dans les registres Y et X le nombre de lignes et le nombre de colonnes :

RPN> mat

                       mat

MATRIX    [28 x 28]

[...]

RPN> stk

                       stk

MAIN                 STACK   3   Z : TEST_SET     'MNIST_TESTS' 10000 elts, current idx = 1

MAIN                 STACK   2   Y : TRAINING_SET 'MNIST_TRAIN' 60000 elts, current idx = 1235

MAIN                 STACK   1   X : MATRIX [28 x 28]

RPN> dim

                       dim

        28            ***

RPN> stk

                       stk

MAIN                 STACK   5     : TEST_SET     'MNIST_TESTS' 10000 elts, current idx = 1

MAIN                 STACK   4   T : TRAINING_SET 'MNIST_TRAIN' 60000 elts, current idx = 1235

MAIN                 STACK   3   Z : MATRIX    [28 x 28]

MAIN                 STACK   2   Y : INT                28

MAIN                 STACK   1   X : INT                28

L'opérateur get (voir la fonction rpn_op_get() dans le fichier rpn_custom.c) permet de copier en sommet de pile (X) l'élément dont le numéro a été placé en X, ou, si X contient un élément de type TRAINING_SET (c'est-à-dire que l'index de l'élément à récupérer n'est pas spécifié), de placer en X l'élément dont l'index est spécifié par la variable current_index interne à l'élément TRAINING_SET, et visible à l'aide de l'opérateur stk. L'élément copié en X est de type TRAINING_ELT.

L'élément TRAINING_SET, qui est un élément important et coûteux à produire (en nombre de cycles CPU), est implémenté en tant qu'élément persistent, c'est-à-dire qu'il ne disparaît pas de la pile opérationnelle (il n'est pas consommé) après utilisation par un opérateur (tel que get, dispatch ou mnist_pics par exemple, voir les fonctions rpn_op_nn_dispatch() et rpn_op_mnist_pics() dans le fichier rpn_custom.c).

Voyons cela à l'aide d'un exemple concret :

$ ./rpn

MAIN                 STACK EMPTY

RPN> 'STACK_BASE' 2 3 4 stk

         4            stk

MAIN                 STACK   4   T : LITTERAL 'STACK_BASE'

MAIN                 STACK   3   Z : INT                 2

MAIN                 STACK   2   Y : INT                 3

MAIN                 STACK   1   X : INT                 4

RPN> +

                         +

         7            ***

RPN> stk

                       stk

MAIN                 STACK   3   Z : LITTERAL 'STACK_BASE'

MAIN                 STACK   2   Y : INT                 2

MAIN                 STACK   1   X : INT                 7

RPN> *

                         *

        14            ***

RPN> stk

                       stk

MAIN                 STACK   2   Y : LITTERAL 'STACK_BASE'

MAIN                 STACK   1   X : INT 14

Le littéral STACK_BASE est, dans l'exemple précédent, entré dans une pile vide, et matérialise la base de la pile (représentée ici au sommet de l'affichage, puisque nous avons choisi de présenter systématiquement le dernier élément placé dans la pile en dernier, lors de l'affichage). Lors de l'opération +, les chiffres 3 et 4 sont consommés et remplacés par le résultat de l'opération 3 + 4, soit 7. Lors de l'opération *, les chiffres 2 et 7 sont également consommés et remplacés par le résultat de l'opération 2 * 7, soit 14.

Regardons maintenant ce qu'il se passe avec un élément de type TRAINING_SET :

$ ./rpn

MAIN                 STACK EMPTY

RPN> 'STACK_BASE' read_mnist

 'STACK_BASE'      read_mnist

[...]

 <MNIST_TRAIN>             ***

RPN> stk

 <MNIST_TRAIN>             stk

MAIN                 STACK   3   Z : LITTERAL   'STACK_BASE'

MAIN                 STACK   2   Y : TEST_SET     'MNIST_TESTS' 10000 elts, current idx = 1

MAIN                 STACK   1   X : TRAINING_SET 'MNIST_TRAIN' 60000 elts, current idx = 1

RPN> 2019

RPN> stk

      2019            stk

MAIN                 STACK   4   T : LITTERAL   'STACK_BASE'

MAIN                 STACK   3   Z : TEST_SET     'MNIST_TESTS' 10000 elts, current idx = 1

MAIN                 STACK   2   Y : TRAINING_SET 'MNIST_TRAIN' 60000 elts, current idx = 1

MAIN                 STACK   1   X : INT              2019

RPN> get

                       get

 <7>             ***

RPN> stk

                       stk

MAIN                 STACK   4   T : LITTERAL   'STACK_BASE'

MAIN                 STACK   3   Z : TEST_SET     'MNIST_TESTS' 10000 elts, current idx = 1

MAIN                 STACK   2   Y : TRAINING_SET 'MNIST_TRAIN' 60000 elts, current idx = 2020

MAIN                 STACK   1   X : TRAINING_ELT [idx =    2019, orig =    2019] '7'    784 -- [ 28 x  28] USED

Dans cet exemple, nous utilisons le même littéral que dans l'exemple précédent pour matérialiser la base de la pile. L'élément de type TRAINING_SET est produit par l'opérateur read_mnist, qui lit la base MNIST et produit l'élément associé. Si nous voulons récupérer le 2019e élément de la base d'apprentissage, il suffit d'entrer le nombre 2019 puis l'opérateur get. Après exécution de celui-ci, le 2019e élément de la base MNIST (en l'occurrence un 7 manuscrit) remplace le nombre 2019 dans la pile, mais contrairement à l'exemple précédent, l'élément TRAINING_SET est encore présent dans la pile afin que nous puissions le réutiliser, sans avoir à relire la base. De la même façon, l'opérateur get exécuté sans spécifier d'index récupère l'élément suivant (2020) et maintient l'élément TRAINING_SET dans la pile :

RPN> stk

                       stk

[...]

MAIN                 STACK   1   X : TRAINING_SET 'MNIST_TRAIN' 60000 elts, current idx = 2020

RPN> get

                       get

 <2>             ***

RPN> stk

                       stk

[...]

MAIN                 STACK   2   Y : TRAINING_SET 'MNIST_TRAIN' 60000 elts, current idx = 2021

MAIN                 STACK   1   X : TRAINING_ELT [idx =    2020, orig =    2020] '2'    784 -- [ 28 x  28] USED   

Mais revenons à notre élément TRAINING_ELT : il serait intéressant de le visualiser de façon à avoir une idée de l'aspect du chiffre manuscrit. C'est là que la commande gnuplot va nous être d'un grand secours, car elle permet de générer tout type de graphe, et notamment des heatmaps, c'est-à-dire des graphes dans lesquels chaque cellule est colorisée avec une couleur indiquant sa valeur en fonction d'une échelle de couleurs spécifiées. Dans notre cas, les couleurs seront remplacées par des niveaux de gris, le 0 étant représenté en blanc et le 255 en noir, chaque point de la matrice étant représenté par une cellule dont la teinte va varier du blanc au noir, en fonction de sa valeur numérique.

Sur Devuan et Debian, l'installation de la commande gnuplot est effectuée à l'aide de la commande suivante :

$ apt-get install gnuplot

Sur Raspbian, nous utiliserons en revanche une commande légèrement différente :

$ apt-get install gnuplot-x11

Pour générer l'image d'un chiffre manuscrit, nous allons devoir enregistrer sa matrice dans un fichier de données, puis utiliser gnuplot pour produire le graphe. Effectuons ces opérations avec le script suivant :

#!/bin/bash

cat <<- EOF | ./rpn > /dev/null

 read_mnist

 6 get

 mat

 'label_2_01.txt' filename

 write

EOF

mv 'label_2_01.txt' TEXTS

gnuplot -e 'RADIX="label_2_01"' TOOLS/plot_digit_640

Le fichier image est généré dans le répertoire IMAGES et s'appelle label_2_01.png : c'est la première version d'une image du 6e élément de la base MNIST qui représente le chiffre 2. Quand on visualise le contenu du fichier texte label_2_01.txt, on obtient un résultat identique à celui de la figure 1 sur lequel on voit clairement se dessiner le chiffre manuscrit. Et lorsque l'on regarde le contenu du fichier image (figure 2), on voit que le chiffre est à l'envers, tête en bas. La raison de cette inversion est que la commande gnuplot considère que la première ligne de données du fichier correspond aux cellules qui sont les plus proches de l'axe des abscisses. Nous devons donc inverser les lignes de la matrice pour avoir un affichage dans le bon sens, c'est ce que fait le script suivant, qui utilise l'opérateur hrev (voir la fonction rpn_op_h_rev() dans le fichier rpn_custom.c) spécialement conçu pour inverser les lignes d'une matrice :

#!/bin/bash

cat <<- EOF | ./rpn > /dev/null

 read_mnist

 6 get

 mat

 hrev

 'label_2_02.txt' filename

 write

EOF

mv 'label_2_02.txt' TEXTS

gnuplot -e 'RADIX="label_2_02"' TOOLS/plot_digit_640

 

MNIST_figure_01

 

Fig. 1: Chiffre manuscrit dans fichier texte.

 

MNIST_figure_02

 

Fig. 2: Fichier image associé au fichier texte de la figure 1.

Le contenu du fichier texte (figure 3) montre sous forme numérique un chiffre 2 tête en bas, mais l'image générée par gnuplot (figure 4) montre bien un chiffre 2 à l'endroit.

 

MNIST_figure_03

 

Fig. 3: Chiffre manuscrit à l'envers dans fichier texte.

 

MNIST_figure_04

 

Fig. 4: Fichier image associé au fichier texte de la figure 3.

 Contrôle de la base MNIST

Maintenant que nous avons vu à quoi ressemble un chiffre de la base MNIST, une autre question se pose à nous : comment pouvons-nous examiner cette base de chiffres et nous assurer que les valeurs d'apprentissage sont correctes, c'est-à-dire que les labels correspondent bien aux chiffres manuscrits ? Et comment pouvons-nous faire cela dans un temps raisonnable ?

Les éléments de test seront analysés, à terme, par le réseau entraîné, qui pourra nous indiquer les éléments mal catégorisés. Ceux-ci devraient, on l'espère, être peu nombreux, donc le temps à consacrer à leur contrôle devrait être plutôt faible. Le jeu d'apprentissage, quant à lui, est constitué de 60 000 éléments que nous devons contrôler pour nous assurer qu'il ne comporte pas d'erreur ! Si nous mettions en moyenne 1s (une seconde) pour chaque chiffre à contrôler, la durée du contrôle complet serait de 16 h 40, soit plus dedeux journées de travail de 8 h. Or, la comparaison du chiffre manuscrit sous forme d'image avec sa valeur sous forme de texte, de taille différente (le petit label généré par gnuplot en bas de l'image), risque de nous prendre plus d'une seconde, et d'occasionner un grand nombre de mouvements oculaires, et donc une fatigue visuelle rapide, l’œil passant d'un chiffre représenté par un graphe de taille moyenne à un texte de petite taille, aboutissant à une dérive importante du temps d'examen, et peut-être à un découragement qui serait la conséquence d'un travail répétitif et d'une prise de conscience d'une durée de contrôle largement sous-estimée. Il nous faut donc trouver un moyen plus efficace de faire ce contrôle. Pour aller au plus vite avec moins de fatigue oculaire, il faudrait que le chiffre manuscrit et sa valeur soient de mêmes tailles et côte à côte, mais sans aucune confusion possible lors d'un balayage visuel rapide. Nous choisirons donc pour la valeur (label) une représentation sous la forme classique des chiffres 7 segments (figure 5).

 

MNIST_figure_05

 

Fig. 5: Représentation sous forme de chiffre 7 segments.

L'opérateur mnist_pic utilise les opérateurs suivant :

  • hcat (concaténation de deux matrices, voir la fonction my_op_generic2() dans le fichier rpn_custom.c) ;
  • mat7seg (génération d'une matrice de chiffre 7 segments à partir de sa valeur, voir la fonction rpn_op_mat_7_seg() dans le fichier rpn_custom.c) ;
  • hrev (inversion des lignes d'une matrice).

Il permet la génération d'une image conforme à ce que nous souhaitons. Nous pouvons l'utiliser de la façon suivante :

cat <<- EOF | ./rpn > /dev/null

 read_mnist

 6 get

 mnist_pic

EOF

Le résultat est contenu dans le fichier IMAGES/00/mnist_2_00006.png visible figure 6.

 

MNIST_figure_06

 

Fig. 6: Génération d'un chiffre 7 segments à partir d'une image contenant un chiffre manuscrit.

Avec une telle représentation, et en faisant défiler rapidement les images, on peut espérer mettre environ 1s par chiffre, tout du moins au début. Mais à la longue, rien de garanti que nous pourrons maintenir ce rythme de contrôle. Il faudrait donc trouver une méthode plus efficace...

Si nous groupions ces images dans des tableaux, l’œil pourrait les traiter rapidement, à la manière de la lecture d'un livre. On pourrait donc espérer des gains de temps significatifs pour le contrôle de ces 60 000 éléments.

Voyons ce que cela peut donner avec le script de génération suivant :

#!/bin/bash

gen_page()

{

     cat <<- EOF

          read_mnist

     EOF

     for j in  {1..10}

     do  

          cat <<- EOF

               get

               label

               lastx

               mat

               x<>y

               mat7seg

               hcat

               hrev

               rdn

          EOF

          for i in {1..9}

          do

               cat <<- EOF

                    get

                    label

                    lastx

                    mat

                    x<>y

                    mat7seg

                    hcat

                    hrev

                    rup

                    x<>y

                    hcat

                    rdn

               EOF

          done

          if [ $j != 1 ]; then

               cat <<- EOF

                    rup

                    rup

                    stk

                    vcat

                    rdn

               EOF

          fi

     done

     cat <<- EOF

          rup

          stk

          'mnist_with_labels_page_01.txt'

          filename

          write

     EOF

}

gen_page | ./rpn > /dev/null

mv 'mnist_with_labels_page_01.txt' TEXTS/GROUPS

gnuplot -e 'RADIX="mnist_with_labels_page_01"' TOOLS/plot_digits_1920

Le but de ce script est de produire une image PNG constituée de dix lignes de dix chiffres manuscrits, chacun d'entre eux étant accolé à sa version en chiffre 7 segments. Le résultat de son exécution est visible sur la figure 7.

 

MNIST_figure_07

 

Fig. 7: Tableau de chiffres associant les versions manuscrites et 7 segments.

Un premier essai de balayage rapide nous permet de constater une augmentation significative de la vitesse de traitement, mais la perspective de passer presque deux jours à faire ces contrôles reste peu motivante.

Nous constatons également lors des premiers contrôles qu'il y a encore un frein qui nous empêche d'aller très vite : c'est l'alternance de chiffres distincts, et la nécessité à chaque chiffre rencontré de changer mentalement la valeur du chiffre auquel on compare le chiffre manuscrit. Si nous comparions des groupes de chiffres identiques (labels identiques), nous irions sans aucun doute beaucoup plus vite. Et dans ce cas, inutile d'afficher la valeur du label en chiffre 7 segments, car elle serait constante dans un groupe donné. De plus, on récupérerait pour chaque chiffre un espace dans lequel on pourrait placer un autre chiffre manuscrit. On pourrait ainsi doubler la densité de chiffres à contrôler par ligne, et augmenter de façon significative la vitesse de traitement.

Pour afficher des groupes de chiffres de valeur identique, nous allons avoir besoin de grouper par valeur les chiffres du jeu MNIST. Une façon rapide d'effectuer ce classement est d'utiliser un tableau de 10 pointeurs de listes chaînées (une liste pour chaque chiffre de 0 à 9). L'opérateur dispatch, implémenté selon ce principe, va rapidement effectuer ce travail pour nous :

RPN> stk

 <MNIST_TRAIN>             stk

MAIN                 STACK   2   Y : TEST_SET     'MNIST_TESTS' 10000 elts, current idx = 1

MAIN                 STACK   1   X : TRAINING_SET 'MNIST_TRAIN' 60000 elts, current idx = 1

RPN> dispatch

                  dispatch

 <MNIST_TRAIN-0>             ***

RPN> stk

                       stk

MAIN                 STACK  12     : TEST_SET     'MNIST_TESTS' 10000 elts, current idx = 1

MAIN                 STACK  11     : TRAINING_SET 'MNIST_TRAIN' 60000 elts, current idx = 1

MAIN                 STACK  10     : TRAINING_SET 'MNIST_TRAIN-9'  5949 elts, current idx = 1

MAIN                 STACK   9     : TRAINING_SET 'MNIST_TRAIN-8'  5851 elts, current idx = 1

MAIN                 STACK   8     : TRAINING_SET 'MNIST_TRAIN-7'  6265 elts, current idx = 1

MAIN                 STACK   7     : TRAINING_SET 'MNIST_TRAIN-6'  5918 elts, current idx = 1

MAIN                 STACK   6     : TRAINING_SET 'MNIST_TRAIN-5'  5421 elts, current idx = 1

MAIN                 STACK   5     : TRAINING_SET 'MNIST_TRAIN-4'  5842 elts, current idx = 1

MAIN                 STACK   4   T : TRAINING_SET 'MNIST_TRAIN-3'  6131 elts, current idx = 1

MAIN                 STACK   3   Z : TRAINING_SET 'MNIST_TRAIN-2'  5958 elts, current idx = 1

MAIN                 STACK   2   Y : TRAINING_SET 'MNIST_TRAIN-1'  6742 elts, current idx = 1

MAIN                 STACK   1   X : TRAINING_SET 'MNIST_TRAIN-0'  5923 elts, current idx = 1

RPN> rup

                       rup

 <MNIST_TESTS>             prx

RPN> stk

                       stk

MAIN                 STACK  12     : TRAINING_SET 'MNIST_TRAIN' 60000 elts, current idx = 1

MAIN                 STACK  11     : TRAINING_SET 'MNIST_TRAIN-9'  5949 elts, current idx = 1

MAIN                 STACK  10     : TRAINING_SET 'MNIST_TRAIN-8'  5851 elts, current idx = 1

MAIN                 STACK   9     : TRAINING_SET 'MNIST_TRAIN-7'  6265 elts, current idx = 1

MAIN                 STACK   8     : TRAINING_SET 'MNIST_TRAIN-6'  5918 elts, current idx = 1

MAIN                 STACK   7     : TRAINING_SET 'MNIST_TRAIN-5'  5421 elts, current idx = 1

MAIN                 STACK   6     : TRAINING_SET 'MNIST_TRAIN-4'  5842 elts, current idx = 1

MAIN                 STACK   5     : TRAINING_SET 'MNIST_TRAIN-3'  6131 elts, current idx = 1

MAIN                 STACK   4   T : TRAINING_SET 'MNIST_TRAIN-2'  5958 elts, current idx = 1

MAIN                 STACK   3   Z : TRAINING_SET 'MNIST_TRAIN-1'  6742 elts, current idx = 1

MAIN                 STACK   2   Y : TRAINING_SET 'MNIST_TRAIN-0'  5923 elts, current idx = 1

MAIN                 STACK   1   X : TEST_SET     'MNIST_TESTS' 10000 elts, current idx = 1

RPN> dispatch

                  dispatch

 <MNIST_TESTS-0>             ***

RPN> stk

                       stk

MAIN                 STACK  22     : TRAINING_SET 'MNIST_TRAIN' 60000 elts, current idx = 1

MAIN                 STACK  21     : TRAINING_SET 'MNIST_TRAIN-9'  5949 elts, current idx = 1

MAIN                 STACK  20     : TRAINING_SET 'MNIST_TRAIN-8'  5851 elts, current idx = 1

MAIN                 STACK  19     : TRAINING_SET 'MNIST_TRAIN-7'  6265 elts, current idx = 1

MAIN                 STACK  18     : TRAINING_SET 'MNIST_TRAIN-6'  5918 elts, current idx = 1

MAIN                 STACK  17     : TRAINING_SET 'MNIST_TRAIN-5'  5421 elts, current idx = 1

MAIN                 STACK  16     : TRAINING_SET 'MNIST_TRAIN-4'  5842 elts, current idx = 1

MAIN                 STACK  15     : TRAINING_SET 'MNIST_TRAIN-3'  6131 elts, current idx = 1

MAIN                 STACK  14     : TRAINING_SET 'MNIST_TRAIN-2'  5958 elts, current idx = 1

MAIN                 STACK  13     : TRAINING_SET 'MNIST_TRAIN-1'  6742 elts, current idx = 1

MAIN                 STACK  12     : TRAINING_SET 'MNIST_TRAIN-0'  5923 elts, current idx = 1

MAIN                 STACK  11     : TEST_SET     'MNIST_TESTS' 10000 elts, current idx = 1

MAIN                 STACK  10     : TEST_SET     'MNIST_TESTS-9'  1009 elts, current idx = 1

MAIN                 STACK   9     : TEST_SET     'MNIST_TESTS-8'   974 elts, current idx = 1

MAIN                 STACK   8     : TEST_SET     'MNIST_TESTS-7'  1028 elts, current idx = 1

MAIN                 STACK   7     : TEST_SET     'MNIST_TESTS-6'   958 elts, current idx = 1

MAIN                 STACK   6     : TEST_SET     'MNIST_TESTS-5'   892 elts, current idx = 1

MAIN                 STACK   5     : TEST_SET     'MNIST_TESTS-4'   982 elts, current idx = 1

MAIN                 STACK   4   T : TEST_SET     'MNIST_TESTS-3'  1010 elts, current idx = 1

MAIN                 STACK   3   Z : TEST_SET     'MNIST_TESTS-2'  1032 elts, current idx = 1

MAIN                 STACK   2   Y : TEST_SET     'MNIST_TESTS-1'  1135 elts, current idx = 1

MAIN                 STACK   1   X : TEST_SET     'MNIST_TESTS-0'   980 elts, current idx = 1

Les deux jeux ont chacun été éclatés en dix jeux de mêmes types (TRAINING_SET ou TEST_SET) ne comprenant que des chiffres manuscrits de même valeur. Le nom de ces jeux est dérivé des noms d'origine, et les suffixes ajoutés identifient la valeur numérique des chiffres contenus dans les jeux.

L'opérateur mnist_pics a été conçu pour générer les images PNG de tableaux de 1 000 chiffresmanuscrits (25 lignes de 40 chiffres), avec des règles (sur le côté gauche et au-dessus) permettant de calculer l'index d'un chiffre dans un tableau. La génération des 60 pages de 1 000 chiffres peut être effectuée de la façon suivante (l'affichage a été épuré, de façon à ne montrer que la génération de la première et de la dernière page) :

$ ./rpn

MAIN                 STACK EMPTY

RPN> sw_on

                     sw_on

RPN> read_mnist

                read_mnist

[...]

read_mnist :      0.066197 s

 <MNIST_TRAIN>             ***

RPN> 1 60000 mnist_pics

     60000     mnist_pics

    1 -  1000

TXT FILE : TEXTS/GROUPS/mnist_v5_MNIST_TRAIN_00001-01000_25x40.txt

PNG FILE : IMAGES/GROUPS/mnist_v5_MNIST_TRAIN_00001-01000_25x40.png

PLOT COMMAND [gnuplot -e RADIX='"mnist_v5_MNIST_TRAIN_00001-01000_25x40"' TOOLS/plot_digits_1920]

RADIX  = mnist_v5_MNIST_TRAIN_00001-01000_25x40

INPUT  = TEXTS/GROUPS/mnist_v5_MNIST_TRAIN_00001-01000_25x40.txt

OUTPUT = IMAGES/GROUPS/mnist_v5_MNIST_TRAIN_00001-01000_25x40.png

[...]

59001 - 60000

TXT FILE : TEXTS/GROUPS/mnist_v5_MNIST_TRAIN_59001-60000_25x40.txt

PNG FILE : IMAGES/GROUPS/mnist_v5_MNIST_TRAIN_59001-60000_25x40.png

PLOT COMMAND [gnuplot -e RADIX='"mnist_v5_MNIST_TRAIN_59001-60000_25x40"' TOOLS/plot_digits_1920]

RADIX  = mnist_v5_MNIST_TRAIN_59001-60000_25x40

INPUT  = TEXTS/GROUPS/mnist_v5_MNIST_TRAIN_59001-60000_25x40.txt

OUTPUT = IMAGES/GROUPS/mnist_v5_MNIST_TRAIN_59001-60000_25x40.png

mnist_pics :    109.333351 s

 <MNIST_TRAIN>             ***

RPN>

Les soixante images ont été générées en moins de deux minutes sur une machine équipée un CPU Intel i7 3770K. La première page est visible sur la figure 8.

 

MNIST_figure_08

 

Fig. 8: Page de chiffres.

Enfin, le même traitement peut être effectué sur les jeux générés par l'opérateur dispatch de la façon suivante :

$ ./rpn

MAIN                 STACK EMPTY

RPN> read_mnist

                read_mnist

[...]

 <MNIST_TRAIN>             ***

RPN> dispatch

 <MNIST_TRAIN>        dispatch

 <MNIST_TRAIN-0>             ***

RPN> stk

                       stk

MAIN                 STACK  12     : TEST_SET     'MNIST_TESTS' 10000 elts, current idx = 1

MAIN                 STACK  11     : TRAINING_SET 'MNIST_TRAIN' 60000 elts, current idx = 1

[...]

MAIN                 STACK   2   Y : TRAINING_SET 'MNIST_TRAIN-1'  6742 elts, current idx = 1

MAIN                 STACK   1   X : TRAINING_SET 'MNIST_TRAIN-0'  5923 elts, current idx = 1

RPN> 1 6000 mnist_pics

      6000     mnist_pics

    1 -  1000

[...]

OUTPUT = IMAGES/GROUPS/mnist_v5_MNIST_TRAIN-0_00001-01000_25x40.png

[...]

 5001 - 5923

[...]

OUTPUT = IMAGES/GROUPS/mnist_v5_MNIST_TRAIN-0_05001-05923_25x40.png

 <MNIST_TRAIN-0>             ***

On notera que l'on peut spécifier un index de fin (6 000) supérieur au nombre d'éléments du jeu (5 923 pour l'ensemble des 0 du jeu d'apprentissage).

Voyons enfin ce que donne une page de chiffres identiques, disons par exemple la deuxième page des 3 (figure 9). Au premier coup d’œil, on se rend compte qu'avec une telle présentation, le contrôle sera maintenant bien plus rapide.

 

MNIST_figure_09

 

Fig. 9: Page de chiffres ne contenant que des 3.

Lors du contrôle visuel, on découvre par exemple un chiffre qui ressemble plus à un 9 qu'à un 3. Grâce aux règles libellées en chiffres 7 segments sur les bords du tableau, on peut localiser ce chiffre : il est situé en 4e ligne et 14e colonne de la 2e page des chiffres 3. Son index peut donc être calculé à l'aide de rpn, qui est évidemment capable d'effectuer des opérations arithmétiques. Puisque le nombre est situé sur la deuxième page de 1 000 chiffres 3, sur la 4e ligne de 40 chiffres, en 14e colonne, son index sera donné par :

RPN> 2 1 - 1000 * 4 1 - 40 * 14 + +

         1              -

         1            ***

      1000              *

      1000            ***

         1              -

         3            ***

        40              *

       120            ***

        14              +

       134            ***

                         +

      1134            ***

Et on peut donc le récupérer à l'aide de l'opérateur get en ayant le jeu des 3 en Y et l'index 1134 en X :

RPN> stk

                       stk

[...]

MAIN                 STACK   2   Y : TRAINING_SET 'MNIST_TRAIN-3'  6131 elts, current idx = 1

MAIN                 STACK   1   X : INT              1134

RPN> get

                       get

 <3>             ***

RPN> stk

                       stk

[...]

MAIN                 STACK   2   Y : TRAINING_SET 'MNIST_TRAIN-3'  6131 elts, current idx = 1135

MAIN                 STACK   1   X : TRAINING_ELT [idx =    1134, orig =   10995] '3'    784 -- [ 28 x  28] USED   

Le label du chiffre récupéré est bien un 3 et non un 9, son index relatif dans le jeu des 3 est 1134 et son index original dans le jeu d'apprentissage MNIST_TRAIN est 10.995.

En poursuivant le contrôle des données, on peut trouver d'autres anomalies, comme :

  • l'élément 26 561 : un 1 labellisé comme un 7 (index 2758 dans MNIST_TRAIN-7) ;
  • l'élément 32 343 : un 7 labellisé comme un 9 (index 3253 dans MNIST_TRAIN-9) ;
  • l'élément 54 265 : un 1 labellisé comme un 4 (index 5269 dans MNIST_TRAIN-4) ;
  • l'élément 59 916 : un 7 labellisé comme un 4 (index 5835 dans MNIST_TRAIN-4).

Il en existe encore quelques-unes que vous avez maintenant les moyens de trouver rapidement grâce aux outils que nous venons d'utiliser.

Le mot clé USED, affiché au bout de la ligne de description des éléments, indique qu'il est utilisable lors d'un apprentissage par un réseau de neurones de type MLP (que nous aborderons dans un futur article). La génération de l'image à l'aide de mnist_pic nous montre bien un 9 identifié comme un 3 (figure 10).

 

MNIST_figure_10

 

Fig. 10: Chiffre 9 identifié comme un 3.

Si nous souhaitons l'ignorer lors de la soumission du jeu d'apprentissage à un MLP, il suffit d'utiliser l'opérateur ignore (voir la fonction rpn_op_nn_ignore() dans le fichier rpn_custom.c) de la façon suivante :

RPN> stk

                       stk

[...]

MAIN                 STACK   1   X : TRAINING_SET 'MNIST_TRAIN' 60000 elts, current idx = 1

RPN> 10995 ignore

     10995         ignore

 <MNIST_TRAIN>             ***

RPN> stk

                       stk

[...]

MAIN                 STACK   1   X : TRAINING_SET 'MNIST_TRAIN' 60000 elts, current idx = 1

RPN> 10995 get

     10995            get

 <3>             ***

RPN> stk

                       stk

[...]

MAIN                 STACK   2   Y : TRAINING_SET 'MNIST_TRAIN' 60000 elts, current idx = 10996

MAIN                 STACK   1   X : TRAINING_ELT [idx =   10995, orig =   10995] '3'    784 -- [ 28 x  28] IGNORED

Le statut du chiffre d'index original 10 995 dans le jeu MNIST_TRAIN est passé de USED à IGNORED. Lors des prochaines utilisations de ce jeu, comme dans un apprentissage supervisé, cet élément sera identifié comme un élément à ignorer. Si l'on souhaite le rendre utilisable à nouveau, on utilisera l'opérateur use (voir à nouveau la fonction rpn_op_nn_ignore() dans le fichier rpn_custom.c) de la façon suivante :

RPN> stk

                       stk

[...]

MAIN                 STACK   1   X : TRAINING_SET 'MNIST_TRAIN' 60000 elts, current idx = 10996

RPN> 10995 use

     10995            use

 <MNIST_TRAIN>             ***

RPN> 10995 get

     10995            get

 <3>             ***

RPN> stk

                       stk

[...]

MAIN                 STACK   2   Y : TRAINING_SET 'MNIST_TRAIN' 60000 elts, current idx = 10996

MAIN                 STACK   1   X : TRAINING_ELT [idx =   10995, orig =   10995] '3'    784 -- [ 28 x  28] USED   

Conclusion

Nous avons découvert dans cet article différentes façons de manipuler la base MNIST permettant de :

  • visualiser son contenu en affichant des chiffres individuellement ou par groupes ;
  • générer des sous-ensembles de jeux de chiffres triés par valeur ;
  • générer la matrice associée à un élément, et l'enregistrer dans un fichier ;
  • localiser rapidement un élément erroné ;
  • désactiver et réactiver un élément dans un jeu.

Dans un futur article consacré aux perceptrons multicouches (MLP), nous utiliserons l'activation et la désactivation d'éléments pour étudier l'impact des valeurs erronées d'un jeu d'apprentissage sur la fiabilité de reconnaissance des éléments. En attendant, vous avez le temps de vous entraîner à manipuler vos nouveaux outils.

Références

[1] Site GitHub officiel de RPNhttps://github.com/mbornet-hl/rpn

[2] Wikipedia anglophone sur la base MNIST : https://en.wikipedia.org/wiki/MNIST_database

[3] M. BORNET,« RPN : interpréteur de notation polonaise inversée en langage C », GNU/Linux magazine n°217, p. 34 à 47 : https://connect.ed-diamond.com/GNU-Linux-Magazine/GLMF-217/RPN-interpreteur-de-notation-polonaise-inversee-en-langage-C

[4] M. BORNET,« RPN : extension de la syntaxe grâce à lex », GNU Linux magazine n°221, p. 82 à 90 : https://connect.ed-diamond.com/GNU-Linux-Magazine/GLMF-221/RPN-extension-de-la-syntaxe-grace-a-lex

[5] Site de Yann Le Cun sur MNIST : « THE MNIST DATABASE of handwritten digits  » : http://yann.lecun.com/exdb/mnist/

Pour aller plus loin

Si vous souhaitez continuer à explorer l'histoire de l'IA, la reconnaissance des chiffres manuscrits, les perceptrons multicouches et autres réseaux de neurones, et prendre un peu d'avance sur les futurs articles traitant du sujet, voici quelques références qui sont susceptibles de vous intéresser et vous aider à progresser plus rapidement :

  • Vidéo de Claude Shannon présentant sa souris intelligente, « Where did digital communication begin? » : https://www.youtube.com/watch?v=nS0luYZd4fs
  • Vidéo de Yann Le Cun faisant une démonstration de la reconnaissance des chiffres manuscrits, « Convolutional Network Demo from 1993 » : https://www.youtube.com/watch?v=FwFduRA_L6Q
  • Vidéo « Mais qu'est-ce qu'un réseau de neurones ? Apprentissage profond, chapitre 1 » : https://www.youtube.com/watch?v=aircAruvnKk
  • Cours de Stanford University sur la reconnaissance visuelle, « CS231n: Convolutional Neural Networks for Visual Recognition » : http://cs231n.stanford.edu/ (explorer les différents liens de la page)
  • Des vidéos de Geoffrey Hinton sur les réseaux de neurones, « Geoffrey Hinton Lectures »http://www.cs.toronto.edu/~hinton/coursera_lectures.html
  • Livre de Ian Goodfellow, Yoshua Bengio and Aaron Courville, « Deep Learning », The MIT Press, 2017
  • Publications de Yann Le Cun sur arXiv : https://arxiv.org/find/all/1/all:+AND+yann+lecun/0/1/0/all/0/1

 

Sur le même sujet

Intelligence artificielle : la grande méprise

Magazine
Marque
Linux Pratique
Numéro
118
|
Mois de parution
mars 2020
|
Domaines
Résumé

Que ce soit dans les salons professionnels, la presse spécialisée ou même les publicités adressées au grand public, on n’échappe pas à l’intelligence artificielle. Pourtant, derrière ce terme, finalement très fourre-tout, il existe une véritable science. Mais entre l’état de l’art et ce qui existe réellement, il y a une différence qui change la donne.

AlphaFold, la réponse au problème le plus complexe de l'univers ?

Magazine
Marque
GNU/Linux Magazine
Numéro
234
|
Mois de parution
février 2020
|
Domaines
Résumé

Avec l’avènement de la biologie moléculaire au XXe siècle, l’homme a pris conscience qu’il pouvait utiliser le vivant pour l’étudier, puis pour le modifier. Le défi du XXIe siècle va porter sur l’utilisation intelligente de ce savoir pour accélérer l’évolution, et produire des nano machines, les protéines, capables de corriger tous les problèmes que nous avons engendrés. Mais pour cela, il faut répondre au problème le plus complexe de l’univers...

Réalisez vos deepfakes avec les réseaux génératifs antagonistes

Magazine
Marque
GNU/Linux Magazine
Numéro
234
|
Mois de parution
février 2020
|
Domaines
Résumé

Parmi les applications du moment les plus décriées de l'intelligence artificielle figurent celles qui consistent à falsifier des supports multimédias (vidéos, images ou bandes sons...) dont il est de plus en plus difficile de savoir s'il s'agit d'un support orignal ou corrompu. Vous avez pu vous en rendre compte par vous-même en regardant la vidéo dans laquelle Barack Obama parle comme Donald Trump [1]. Nous allons découvrir dans cet article comment utiliser les réseaux génératifs antagonistes, pour créer des images ou vidéos truquées, communément appelées deepfakes.

Utiliser simplement un réseau de neurones sur Raspberry Pi grâce à ONNX et Go

Magazine
Marque
GNU/Linux Magazine
HS n°
Numéro
106
|
Mois de parution
janvier 2020
|
Domaines
Résumé

« Les data scientists sont partis et ont laissé un fichier au format HDF5. C'est magique, ça détecte les voitures. Tu crois qu'on peut en faire un outil ? Ça tournerait sur mon Raspberry Pi ? »Dans cet article, nous allons découvrir le format Open Neural Network eXchange (ONNX). Nous allons ensuite coder une petite application en Go dans le but d’exécuter un réseau de neurones sur Raspberry Pi, simplement.

Utilisation d'un accélérateur matériel : test du TPU Coral USB Accelerator

Magazine
Marque
GNU/Linux Magazine
HS n°
Numéro
106
|
Mois de parution
janvier 2020
|
Domaines
Résumé

Des cartes et des clés USB permettant d'accélérer les calculs pour effectuer du Machine Learning ont fait leur apparition ces dernières années. Google, ne pouvant rester en retrait, propose ses solutions par le biais de la société Coral. Étudions dans cet article l'une d'entre elles, la clé USB Coral Accelerator.

Passez vos réseaux de neurones à la vitesse supérieure avec l’Intel Neural Compute Stick 2

Magazine
Marque
GNU/Linux Magazine
Numéro
233
|
Mois de parution
janvier 2020
|
Domaines
Résumé

Avec sa Neural Compute Stick 2, Intel ouvre la porte du Machine Learning au commun des mortels, en fournissant à la fois un périphérique de calcul dédié à un prix abordable et à la fois une suite logicielle, qui ne nécessite pas d’être un expert pour l’utiliser avec les modèles de Deep Learning, dont on entend tant parler. Mais qu'en est-il vraiment ?

Par le même auteur

Les arbres binaires équilibrés en C

Magazine
Marque
GNU/Linux Magazine
Numéro
235
|
Mois de parution
mars 2020
|
Domaines
Résumé

Nous allons dans cet article revenir sur une structure de données très importante en informatique : les arbres binaires. Après quelques rappels sur l’ordre des algorithmes, les algorithmes de tri, les algorithmes de recherche d’éléments et le rééquilibrage des arbres, nous examinerons le code d’une implémentation en C d’une gestion d’arbres binaires équilibrés.

Tirez parti de la colorisation pour faciliter la lecture de vos données

Magazine
Marque
Linux Pratique
HS n°
Numéro
46
|
Mois de parution
octobre 2019
|
Domaines
Résumé
Dans le numéro 93 de Linux Pratique [1], nous vous avions présenté la commande de colorisation hl (disponible sur GitHub [2]) qui permet de coloriser très simplement des fichiers texte ou résultats de commande. Dans cet article, nous allons étudier les nouvelles fonctionnalités, récemment ajoutées, qui rendent cette commande encore plus puissante.

Gestion de timers en langage C

Magazine
Marque
GNU/Linux Magazine
Numéro
228
|
Mois de parution
juillet 2019
|
Domaines
Résumé
On a parfois besoin de programmer l'exécution d'une tâche dans un programme écrit en langage C, par exemple pour envoyer un signal à un processus qui ne répond pas comme prévu : il existe plusieurs façons de répondre à ce besoin, ainsi que nous allons le voir dans cet article.

Retour d'expérience sur l'étude de la base MNIST pour la reconnaissance de chiffres manuscrits

Magazine
Marque
GNU/Linux Magazine
HS n°
Numéro
102
|
Mois de parution
mai 2019
|
Domaines
Résumé

Parmi les premières recherches effectuées sur l'Intelligence Artificielle figure la reconnaissance des chiffres manuscrits. La base d'étude utilisée par l'expert en IA Yann Le Cun pour ses travaux, et maintenant devenue une référence, est appelée base MNIST. Nous allons découvrir comment la récupérer, la manipuler et l'étudier.

Implémentation et exploitation de la fonction backtrace() en C

Magazine
Marque
GNU/Linux Magazine
Numéro
223
|
Mois de parution
février 2019
|
Domaines
Résumé
Vous avez créé un binaire (issu d'un source C) que vous avez livré à un client, et, de temps en temps, le programme se plante suite à une violation mémoire. Vous n'avez pas la possibilité de lancer votre programme avec un debugger, ni d'examiner un fichier core pour connaître l'origine du plantage ... La fonction backtrace(), associée à quelques outils, va vous sortir de cette situation embarrassante.