Deep Learning : Back to Basics

0

On n’a jamais autant entendu parler de l’intelligence artificielle (IA) que ces 10 dernières années. L’IA washing est très répandue, au point où il est devenu facile de confondre IA, Deep Learning et réseaux de neurones.
Dans cet article, nous allons éclaircir ces trois termes. Ensuite, nous allons implémenter le premier réseau de neurones de l’histoire à savoir le perceptron, en utilisant TensorFlow. Nous entraînerons notre perceptron à simuler les problèmes logiques, le OU par exemple. Pour finir, nous allons exposer nos modèles de réseau de neurones via une application Rest développée en Node.JS et interfacée par un client en React.JS pour l’affichage. Le choix de ces technos est motivé par une volonté de décloisonner les univers, en collaborant avec des devs front. Cette dernière partie est écrite en collaboration avec mon collègue Joan Anagbla.

IA – Deep Learning – Réseaux de neurones

L’intelligence artificielle est un ensemble de techniques permettant de simuler l’intelligence humaine. Elle est née dans les années 50.

source

Il y a plusieurs branches dans l’I.A. (liste non exhaustive) :

  • Représentation des connaissances
  • Résolution de problèmes généraux
  • Robotique
  • Vision automatique (Computer Vision aka CV)
  • Traitement de langage  naturel (NLP)Ces branches utilisent le plus souvent des techniques d’apprentissage automatique (Machine Learning) ou d’apprentissage profond (Deep Learning).
    Un système de Deep Learning utilise exclusivement les réseaux de neurones artificiels pour apprendre.Quand on parle de réseaux de neurones artificiels (RNA), on pense à :

    Structure d’un RNA

    Un RNA est composé de :

  • Une couche d’entrée
  • Une couche de sortie
  • Une ou plusieurs couches intermédiaires, dites couches cachées. Plus il y a de couches cachées, plus l’apprentissage est profond (deep).
  • Et chaque couche est composée d’un ou plusieurs neurones

    Neurones artificiels vs Neurones biologiques

    Comme vous pouvez vous en douter, les RNA sont inspirés des réseaux de neurones biologiques (RNB).

    RNB RNA Rôle
    Dendrites Input Point d’entrée de l’information
    Soma Noeud Processing de l’information
    Axones Output Point de sortie de l’information
    Synapses Interconnexion des poids Connexion aux autres neurones

    Zoom sur le fonctionnement d’un neurone

    Neurone biologique (gauche) et son modèle mathématique (droite)

    1. \(x_0, x_1, x_2, \dots x_n\) sont les entrées du neurone. Ce sont soit les données brutes (pour la couche d’entrée), soit les valeurs intermédiaires issues d’une couche cachée : couches comprises entre la couche d’entrée et la couche de sortie.
    2. \(w_0, w_1, w_2, \dots w_n\) sont les poids de chaque entrée (biais compris)
    3. \(x_0\) est le biais du modèle. C’est une constante permettant de décrire un comportement initial du modèle. Il vaut 1.
    4. \(\mathcal{f}\) est la fonction d’activation.

    La sortie d’un neurone est donnée par :

    $${\displaystyle a_i = \mathcal{f}(\sum_{i = 0} ^{n} w_i x_i) }$$

    En choisissant la fonction sigmoid comme fonction d’activation, on construit facilement une couche via TensorFlow:

    def CreateOneLayer(X, n_neurones):
       W = tf.get_variable("W", [X.get_shape()[1].value, n_neurones], tf.float32)
       b = tf.get_variable("b", [n_neurones], tf.float32)
       return tf.nn.sigmoid(tf.add(tf.matmul(X, W), b))
    

    Pour rappel, TensorFlow utilise des Tensors : des vecteurs multidimensionnels. La fonction CreateOneLayer crée ainsi une couche de neurones et non un neurone(cellule).

    L’apprentissage d’un RNA :

    Avant l’étape d’entraînement effective, on définit l’opérateur train_op qui permet de minimiser l’erreur des moindres carrés loss_op.

    loss_op = tf.reduce_mean(tf.square(tf.subtract(predictions, labels)))
    train_op = tf.train.AdamOptimizer(learning_rate).minimize(loss_op)
    

    Entraîner le modèle consiste à trouver les poids \(w_i , i = 1 \dots n\) qui minimise la fonction de coût.

    def train(X_train, y_train, n_iterations, _model_) :
       saver = tf.train.Saver()
       for step in range(n_iterations + 1):
           _, cost = sess.run([training_op, loss_op], feed_dict={X: X_train, 
     y: y_train})
           if step % 100 == 0:
               print("Step : {: <5} - Lost = {: <23}".format(step, cost))
       save_path = saver.save(sess, "./models/"+_model_+".ckpt")
    

    Après la phase d’apprentissage du système, on exporte le modèle dans le répertoire save_path, notamment ./models/or.ckpt pour le modèle du OU logique. Toutefois, pour changer de modèle, il suffit d’assigner à la variable _model_ une des quatre valeurs possibles : or, xor, and et xnor, et d’adapter le y_train.

    Nous avons vu le fonctionnement d’un neurone et comment le paramétrer. Passons maintenant à l’entraînement du Perceptron.

    Le perceptron de Rosenblatt

    Connu comme le premier système artificiel capable d’apprendre par l’expérience, le Perceptron est composé de deux neurones sur sa couche d’entrée et d’un neurone pour la couche de sortie.


    source

    Ses inputs et outputs sont des valeurs binaires. Ce qui fait de ce système un solveur d’opérations logiques, notamment le ET et le OU logique.

    On implémente avec TensorFlow le perceptron:

    X = tf.placeholder(tf.float32, [None, 2], name="X-input")
    y = tf.placeholder(tf.float32, [None, 1], name="y-output")
    
    def model(_model_):
       if 'x' not in _model_ : # Si _model_ == xnor ou xor
           with tf.variable_scope(_model_+"layer1", reuse = tf.AUTO_REUSE):
               model = CreateOneLayer(X, 1)
       [...]
    

    Puis après 500 itérations d’entraînement, on sauvegarde le modèle. Les données d’entraînement pour le OU logique sont:

    X_train = np.array([[0, 0],
                        [0, 1],
                        [1, 0],
                        [1, 1]])
    
    y_train = np.array([[0],
                        [1],
                        [1],
                        [1]])
    

    La logique booléenne nous permet de savoir que :

    x1 x2 OR
    0 0 0
    0 1 1
    1 0 1
    1 1 1

    En restaurant le modèle précédemment sauvegardé, les prédictions issues du modèle entrainé sont :

    Quand on représente en 3D les outputs du modèle, on obtient un graphe sur lequel on peut séparer clairement les \(0\) des \(1\). En d’autres termes, le problème du OU logique est linéairement séparable.

    Celui du OU exclusif ne l’est pas. Sur le plan de base \((x_1,y_1)\): on ne peut pas séparer la zone bleue de la zone rouge avec une droite.

    Le perceptron dans sa version simple est donc incapable de résoudre le problème du XOR. D’où la nécessité d’ajouter des couches cachées (intermédiaires).

    La décomposition du XOR, nous donne :

    $${\displaystyle {\begin{matrix} X1 \oplus X2 &=&X1.{\overline {X2}}+{\overline{X1}}.X2\\[3pt]&=&(X1+X2).({\overline{X1}}+{\overline {X2}})\\[3pt] &=&(X1+X2)({\overline {X1.X2}})\\[3pt]&=&(X1+X2).({\overline{X1} + \overline{X2}})\end{matrix}}}$$

    x1 x2 XOR
    0 0 0
    0 1 1
    1 0 1
    1 1 0

    (Notons que '+' signifie OU et '.', ET)

    Le modèle du XOR est constitué de :

  • une couche d’entrée à deux neurones
  • une couche cachée à deux neurones
  • une couche de sortie à un neurone
  • chaque couche dispose d’un neurone pour le biais (bias en anglais)

def model(_model_):
   [...]
   else :
       with tf.variable_scope(_model_+"layer1", reuse = tf.AUTO_REUSE):
           z0 = CreateOneLayer(X, 2)
       with tf.variable_scope(_model_+"layer2", reuse = tf.AUTO_REUSE):
           model = CreateOneLayer(z0, 1)
   return model

Une fois ce modèle entraîné au bout de 500 itérations :

Mise en production des modèles

TensorFlow vient avec la suite TensorServing qui permet de mettre en production des modèles construits avec TensorFlow. Ce sujet fera l’objet d’un prochain article.

Le back-end de notre application est principalement composé des 4 modèles sauvegardés, ainsi que de la fonction qui permet d’effectuer la prédiction

def predict_single(x1, x2, _model_) :
   z0 = model()
   saver = tf.train.Saver()
   saver.restore(sess, "./models/"+_model_+".ckpt")
   X_in = np.array([x1, x2]).reshape([-1, 2])
   pred = sess.run(z0, feed_dict={X: X_in})
   result = np.int(np.round(np.reshape(pred, 1)[0]))
   return result

Pour pouvoir exploiter notre modèle, nous allons maintenant le déployer sur un serveur et afficher le résultat des prédictions à l’aide d’une page web.

Exposition du modèle

La création d’un serveur Node.JS se fait à l’aide de commandes linux. Dans un terminal, il faut initier un nouveau projet Node.JS (npm init) et renseigner les informations demandées (nom du projet, etc.). On copie le répertoire des modèles à l’intérieur du projet initié. On utilise ensuite

PythonShell pour lancer l’exécution de la prédiction (script python) depuis un script JavaScript:

PythonShell.run("predict.py", options, function (err, result) {
   if (err) {
     res.send(err); //Retourne une erreur en cas d'erreur
   }
   else {
     res.send(result); //Expose le résultat grâce à la fonction callback de express (res.send)
   }
 });

Enfin, on expose le résultat sur le web grâce à une Api Rest (créée avec Express).

Affichage du résultat

Pour créer la page web chargée de contacter le serveur et d’afficher le résultat des prédictions, nous avons fait le choix de React.JS. React est un framework créé par Facebook permettant de développer des applications web. En plus de React nous avons utilisé une bibliothèque de composants Material-UI. Pour que l’application web puisse contacter le serveur nous avons eu recours à la bibliothèque axios.

Félicitation… Vous venez de mettre en production votre premier modèle de Deep Learning.

Tous les codes de cet article sont disponibles sur mon github. PR vivement recommandée (et souhaitée :-P).

Partagez cet article.

A propos de l'auteur

Ajouter un commentaire