De la résolution d'équations à l'apprentissage en profondeur : un didacticiel Python TensorFlow

Publié: 2022-03-11

Il y a eu des développements remarquables ces derniers temps dans le monde de l'intelligence artificielle, des progrès très médiatisés avec les voitures autonomes aux machines qui composent maintenant des imitations de Chopin ou qui sont simplement très douées pour les jeux vidéo.

Au cœur de ces avancées se trouvent un certain nombre d'outils pour aider à dériver l'apprentissage en profondeur et d'autres modèles d'apprentissage automatique, avec Torch, Caffe et Theano parmi ceux au premier plan. Cependant, depuis que Google Brain est devenu open source en novembre 2015 avec son propre framework, TensorFlow, nous avons vu la popularité de cette bibliothèque de logiciels monter en flèche pour devenir le framework d'apprentissage en profondeur le plus populaire.

TensorFlow

Pourquoi est-ce arrivé? Les raisons incluent la richesse de l'assistance et de la documentation disponibles, sa préparation à la production, la facilité de distribution des calculs sur une gamme d'appareils et un excellent outil de visualisation : TensorBoard.

En fin de compte, TensorFlow parvient à combiner un ensemble complet et flexible de fonctionnalités techniques avec une grande facilité d'utilisation.

Dans cet article, vous allez comprendre la mécanique de cet outil en l'utilisant pour résoudre un problème numérique général, bien en dehors de ce qu'implique habituellement le machine learning, avant d'introduire ses utilisations en deep learning avec une simple implémentation de réseau de neurones.

Avant que tu commences

Une connaissance de base des méthodes d'apprentissage automatique est supposée. Si vous avez besoin de vous rattraper, consultez cet article très utile.

Comme nous allons démontrer l'API Python, une compréhension de Numpy est également bénéfique.

Pour configurer TensorFlow, veuillez suivre les instructions fournies ici.

Si vous utilisez Windows, il convient de noter qu'au moment de la rédaction, vous devez utiliser Python 3.4+, et non 2.7.

Ensuite, lorsque vous êtes prêt, vous devriez pouvoir importer la bibliothèque avec :

 import tensorflow as tf

Étape 1 sur 2 d'une solution TensorFlow : créer un graphique

La construction des programmes TensorFlow consiste généralement en deux étapes principales, dont la première consiste à construire un graphe de calcul, qui décrira les calculs que vous souhaitez effectuer, mais ne les effectuera pas réellement ni ne conservera de valeurs.

Comme pour tout graphe, nous avons des nœuds et des arêtes. Les arêtes représentent des tenseurs, un tenseur représentant un tableau à n dimensions. Par exemple, un tenseur de dimension (ou de rang dans TensorFlow) 0 est un scalaire, le rang 1 un vecteur, le rang 2 une matrice et ainsi de suite.

Les nœuds représentent des opérations qui produisent un tenseur de sortie, en prenant des tenseurs comme entrées si nécessaire. De telles opérations incluent des additions ( tf.add ), des multiplications de matrices ( tf.matmul ), ainsi que la création de constantes ( tf.constant ).

Alors, combinons quelques-uns d'entre eux pour notre premier graphique.

 a = tf.constant([2.5, 2]) b = tf.constant([3, 6], dtype=tf.float32) total = tf.add(a, b)

Ici, nous avons créé trois opérations, dont deux pour créer des tableaux 1-d constants.

Les types de données sont déduits de l'argument values ​​transmis, ou vous pouvez les désigner avec l'argument dtype . Si je n'avais pas fait cela pour b , alors un int32 aurait été déduit et une erreur générée comme tf.add aurait essayé de définir un ajout sur deux types différents.

Étape 2 sur 2 d'une solution TensorFlow : exécuter les opérations

Le graphique est défini, mais pour effectuer des calculs dessus (ou sur une partie de celui-ci), nous devons configurer une session TensorFlow.

 sess = tf.Session()

Alternativement, si nous exécutons une session dans un shell interactif, tel que IPython, nous utilisons :

 sess = tf.InteractiveSession()

La méthode run sur l'objet de session est un moyen d'évaluer un Tensor.

Par conséquent, pour évaluer le calcul d'addition défini ci-dessus, nous passons 'total', le Tensor à récupérer, qui représente la sortie de l'op tf.add .

 print(sess.run(total)) # [ 5.5 8. ]

À ce stade, nous introduisons la classe Variable de TensorFlow. Alors que les constantes font partie intégrante de la définition du graphique, les variables peuvent être mises à jour. Le constructeur de classe nécessite une valeur initiale, mais même dans ce cas, les variables ont besoin d'une opération pour les initialiser explicitement avant que toute autre opération sur celles-ci ne soit effectuée.

Les variables contiennent l'état du graphique dans une session particulière, nous devons donc observer ce qui se passe avec plusieurs sessions utilisant le même graphique pour mieux comprendre les variables.

 # Create a variable with an initial value of 1 some_var = tf.Variable(1) # Create op to run variable initializers init_op = tf.global_variables_initializer() # Create an op to replace the value held by some_var to 3 assign_op = some_var.assign(3) # Set up two instances of a session sess1 = tf.Session() sess2 = tf.Session() # Initialize variables in both sessions sess1.run(init_op) sess2.run(init_op) print(sess1.run(some_var)) # Outputs 1 # Change some_var in session1 sess1.run(assign_op) print(sess1.run(some_var)) # Outputs 3 print(sess2.run(some_var)) # Outputs 1 # Close sessions sess1.close() sess2.close()

Nous avons mis en place le graphique et deux sessions.

Après avoir exécuté l'initialisation sur les deux sessions (si nous ne l'exécutons pas puis évaluons la variable, nous rencontrons une erreur), nous n'exécutons l'opération d'affectation que sur une seule session. Comme on peut le voir, la valeur de la variable persiste, mais pas d'une session à l'autre.

Alimenter le graphe pour résoudre les problèmes numériques

Un autre concept important de TensorFlow est l'espace réservé. Alors que les variables conservent l'état, les espaces réservés sont utilisés pour définir les entrées auxquelles le graphique peut s'attendre et leur type de données (et éventuellement leur forme). Ensuite, nous pouvons introduire des données dans le graphique via ces espaces réservés lorsque nous exécutons le calcul.

Le graphe TensorFlow commence à ressembler aux réseaux de neurones que nous voulons éventuellement former, mais avant cela, utilisons les concepts pour résoudre un problème numérique courant du monde financier.

Supposons que nous voulions trouver y dans une équation comme celle-ci :

v = Ce -0,5y + Ce -y +Ce -1,5y +(C+P)e -2y

pour un v donné (avec C et P constants).

Il s'agit d'une formule pour calculer le rendement à l'échéance ( y ) sur une obligation avec une valeur de marché v , un principal P et un coupon C payés semestriellement mais avec les flux de trésorerie actualisés avec une capitalisation continue.

Nous devons essentiellement résoudre une équation comme celle-ci par essais et erreurs, et nous choisirons la méthode de la bissection pour nous concentrer sur notre valeur finale pour y .

Tout d'abord, nous allons modéliser ce problème sous la forme d'un graphe TensorFlow.

C et P sont des constantes fixes et font partie de la définition de notre graphe. Nous souhaitons avoir un processus qui affine les bornes inférieure et supérieure de y . Par conséquent, ces bornes (notées a et b ) sont de bons candidats pour les variables qui doivent être modifiées après chaque estimation de y (considéré comme le point médian de a et b ).

 # Specify the values our constant ops will output C = tf.constant(5.0) P = tf.constant(100.0) # We specify the initial values that our lower and upper bounds will be when initialised. # Obviously the ultimate success of this algorithm depends on decent start points a = tf.Variable(-10.0) b = tf.Variable(10.0) # We expect a floating number to be inserted into the graph v_target = tf.placeholder("float") # Remember the following operations are definitions, # none are carried out until an operation is evaluated in a session! y = (a+b)/2 v_guess = C*tf.exp(-0.5*y) + C*tf.exp(-y) + C*tf.exp(-1.5*y) + (C + P)*tf.exp(-2*y) # Operations to set temporary values (a_ and b_) intended to be the next values of a and b. # eg if the guess results in av greater than the target v, # we will set a_ to be the current value of y a_ = tf.where(v_guess > v_target, y, a) b_ = tf.where(v_guess < v_target, y, b) # The last stage of our graph is to assign the two temporary vals to our variables step = tf.group( a.assign(a_), b.assign(b_) )

Nous avons donc maintenant une liste d'opérations et de variables, chacune pouvant être évaluée par rapport à une session particulière. Certaines de ces opérations reposent sur d'autres opérations à exécuter, donc exécuter, par exemple, v_guess déclenchera une réaction en chaîne pour que d'autres tenseurs, tels que C et P , soient évalués en premier.

Certaines de ces opérations reposent sur un espace réservé pour lequel une valeur doit être spécifiée, alors comment alimentons-nous réellement cette valeur ?

Cela se fait via l'argument feed_dict dans la fonction run elle-même.

Si nous voulons évaluer a_ , nous insérons la valeur de notre espace réservé v_target , comme ceci :

 sess.run(a_, feed_dict={v_target: 100})

nous donnant 0,0.

Branchez un v_target de 130 et nous obtenons -10.0.

C'est notre opération "étape" qui nécessite en fait que toutes les autres opérations soient effectuées comme prérequis et exécute en fait le graphe entier. C'est aussi une opération qui modifie réellement l'état réel au cours de notre session. Par conséquent, plus nous exécutons l'étape, plus nous poussons progressivement nos variables a et b vers la valeur réelle de y .

Donc, disons que notre valeur pour v dans notre équation est égale à 95. Configurons une session et exécutons notre graphique dessus 100 fois.

 # Set up a session and initialize variables sess = tf.Session() tf.global_variables_initializer().run() # Run the step operation (and therefore whole graph) 100 times for i in range (100): sess.run(step, feed_dict={v_target:95})

Si nous évaluons le tenseur y maintenant, nous obtenons quelque chose qui ressemble à une réponse souhaitable

 print(sess.run(y)) # 0.125163

Les réseaux de neurones

Maintenant que nous comprenons les mécanismes de TensorFlow, nous pouvons les associer à des opérations d'apprentissage automatique supplémentaires intégrées à TensorFlow pour former un réseau de neurones simple.

Ici, nous aimerions classer les points de données sur un système de coordonnées 2d selon qu'ils se situent dans une région particulière - un cercle de rayon 0,5 centré à l'origine.

Bien sûr, cela peut être vérifié concrètement en vérifiant simplement pour un point donné (a,b) si a^2 + b^2 < 0.5 , mais pour les besoins de cette expérience d'apprentissage automatique, nous aimerions plutôt passer dans un ensemble d'entraînement : une série de points aléatoires et s'ils tombent dans notre région prévue. Voici une façon de créer ceci :

 import numpy as np NO_OF_RANDOM_POINTS = 100 CIRCLE_RADIUS = 0.5 random_spots = np.random.rand(NO_OF_RANDOM_POINTS, 2) * 2 - 1 is_inside_circle = (np.power(random_spots[:,0],2) + np.power(random_spots[:,1],2) < CIRCLE_RADIUS).astype(int)

Nous allons créer un réseau de neurones avec les caractéristiques suivantes :

  1. Il se compose d'une couche d'entrée à deux nœuds, dans laquelle nous alimentons notre série de vecteurs bidimensionnels contenus dans des "random_spots". Cela sera représenté par un espace réservé en attente des données d'apprentissage.
  2. La couche de sortie aura également deux nœuds, nous devons donc alimenter notre série d'étiquettes d'apprentissage ("is_inside_circle") dans un espace réservé pour un scalaire, puis convertir ces valeurs en un vecteur bidimensionnel à chaud.
  3. Nous aurons une couche cachée composée de trois nœuds, nous devrons donc utiliser des variables pour nos matrices de poids et nos vecteurs de biais, car ce sont les valeurs qui doivent être affinées lors de l'exécution de la formation.
 INPUT_LAYER_SIZE = 2 HIDDEN_LAYER_SIZE = 3 OUTPUT_LAYER_SIZE = 2 # Starting values for weights and biases are drawn randomly and uniformly from [-1, 1] # For example W1 is a matrix of shape 2x3 W1 = tf.Variable(tf.random_uniform([INPUT_LAYER_SIZE, HIDDEN_LAYER_SIZE], -1, 1)) b1 = tf.Variable(tf.random_uniform([HIDDEN_LAYER_SIZE], -1, 1)) W2 = tf.Variable(tf.random_uniform([HIDDEN_LAYER_SIZE, OUTPUT_LAYER_SIZE], -1, 1)) b2 = tf.Variable(tf.random_uniform([OUTPUT_LAYER_SIZE], -1, 1)) # Specifying that the placeholder X can expect a matrix of 2 columns (but any number of rows) # representing random spots X = tf.placeholder(tf.float32, [None, INPUT_LAYER_SIZE]) # Placeholder Y can expect integers representing whether corresponding point is in the circle # or not (no shape specified) Y = tf.placeholder(tf.uint8) # An op to convert to a one hot vector onehot_output = tf.one_hot(Y, OUTPUT_LAYER_SIZE)

Pour compléter la définition de notre graphe, nous définissons quelques opérations qui nous aideront à entraîner les variables pour atteindre un meilleur classifieur. Ceux-ci incluent les calculs matriciels, les fonctions d'activation et l'optimiseur.

 LEARNING_RATE = 0.01 # Op to perform matrix calculation X*W1 + b1 hidden_layer = tf.add(tf.matmul(X, W1), b1) # Use sigmoid activation function on the outcome activated_hidden_layer = tf.sigmoid(hidden_layer) # Apply next weights and bias (W2, b2) to hidden layer and then apply softmax function # to get our output layer (each vector adding up to 1) output_layer = tf.nn.softmax(tf.add(tf.matmul(activated_hidden_layer, W2), b2)) # Calculate cross entropy for our loss function loss = -tf.reduce_sum(onehot_output * tf.log(output_layer)) # Use gradient descent optimizer at specified learning rate to minimize value given by loss tensor train_step = tf.train.GradientDescentOptimizer(LEARNING_RATE).minimize(loss)

Après avoir configuré notre graphique, il est temps de configurer une session et d'exécuter le "train_step" (qui exécute également toutes les opérations préalables). Certaines de ces opérations utilisent des espaces réservés, il faut donc fournir des valeurs pour ceux-ci. Cette étape de formation représente une époque dans notre algorithme d'apprentissage et, en tant que telle, est bouclée sur le nombre d'époques que nous souhaitons exécuter. Nous pouvons exécuter d'autres parties du graphique, telles que le tenseur de "perte" à des fins d'information.

 EPOCH_COUNT = 1000 sess = tf.Session() tf.global_variables_initializer().run() for i in range(EPOCH_COUNT): if i%100 == 0: print('Loss after %d runs: %f' % (i, sess.run(loss, feed_dict={X: random_spots, Y: is_inside_circle}))) sess.run(train_step, feed_dict={X: random_spots, Y: is_inside_circle}) print('Final loss after %d runs: %f' % (i, sess.run(loss, feed_dict={X: random_spots, Y: is_inside_circle})))

Une fois que nous avons entraîné l'algorithme, nous pouvons alimenter un point et obtenir la sortie du réseau de neurones comme suit :

 sess.run(output_layer, feed_dict={X: [[1, 1]]}) # Hopefully something close to [1, 0] sess.run(output_layer, feed_dict={X: [[0, 0]]}) # Hopefully something close to [0, 1]

Nous pouvons classer le point comme hors du cercle si le premier membre du vecteur de sortie est supérieur à 0,5, à l'intérieur sinon.

En exécutant le tenseur output_layer pour de nombreux points, nous pouvons avoir une idée de la façon dont l'apprenant envisage la région contenant les points classés positivement. Cela vaut la peine de jouer avec la taille de l'ensemble de formation, le taux d'apprentissage et d'autres paramètres pour voir à quel point nous pouvons nous rapprocher du cercle que nous voulions.

Presque triangle
Ensemble d'entraînement : 100 points
Taux d'apprentissage : 0,01
Époques : 1000

Petit triangle
Ensemble d'entraînement : 1 000 points
Taux d'apprentissage : 0,01
Époques : 1000

Grand triangle
Ensemble d'entraînement : 1 000 points
Taux d'apprentissage : 0,01
Époques : 10 000

Presque cercle
Ensemble d'entraînement : 1 000 points
Taux d'apprentissage : 0,001
Époques : 10 000

Emballer

C'est une bonne leçon qu'un ensemble de formation ou un nombre d'époques accru n'est pas une garantie pour un bon apprenant - le taux d'apprentissage doit être ajusté de manière appropriée.

J'espère que ces démonstrations vous ont donné un bon aperçu des principes de base de TensorFlow et fournissent une base solide pour mettre en œuvre des techniques plus complexes.

Nous n'avons pas couvert des concepts tels que le Tensorboard ou la formation de nos modèles sur les GPU, mais ceux-ci sont bien couverts dans la documentation TensorFlow. Un certain nombre de recettes peuvent être trouvées dans la documentation qui peuvent vous aider à vous familiariser rapidement avec des tâches passionnantes d'apprentissage en profondeur à l'aide de ce cadre puissant !

En relation : Les nombreuses applications de la descente de gradient dans TensorFlow