Les nombreuses applications de la descente de gradient dans TensorFlow
Publié: 2022-03-11TensorFlow de Google est l'un des principaux outils de formation et de déploiement de modèles d'apprentissage en profondeur. Il est capable d'optimiser des architectures de réseaux de neurones extrêmement complexes avec des centaines de millions de paramètres, et il est livré avec un large éventail d'outils pour l'accélération matérielle, la formation distribuée et les workflows de production. Ces fonctionnalités puissantes peuvent le rendre intimidant et inutile en dehors du domaine de l'apprentissage en profondeur.
Mais TensorFlow peut être à la fois accessible et utilisable pour des problèmes plus simples qui ne sont pas directement liés à la formation de modèles d'apprentissage en profondeur. À la base, TensorFlow n'est qu'une bibliothèque optimisée pour les opérations tensorielles (vecteurs, matrices, etc.) et les opérations de calcul utilisées pour effectuer une descente de gradient sur des séquences arbitraires de calculs. Les spécialistes des données expérimentés reconnaîtront la « descente de gradient » comme un outil fondamental pour les mathématiques computationnelles, mais cela nécessite généralement la mise en œuvre d'un code et d'équations spécifiques à l'application. Comme nous le verrons, c'est là qu'intervient l'architecture moderne de "différenciation automatique" de TensorFlow.
Cas d'utilisation de TensorFlow
- Exemple 1 : Régression linéaire avec descente de gradient dans TensorFlow 2.0
- Qu'est-ce que la descente de gradient ?
- Exemple 2 : vecteurs unitaires à propagation maximale
- Exemple 3 : Génération d'entrées d'IA contradictoires
- Réflexions finales : optimisation de la descente de gradient
- Descente de gradient dans TensorFlow : de la recherche des minimums à l'attaque des systèmes d'IA
Exemple 1 : Régression linéaire avec descente de gradient dans TensorFlow 2.0
Exemple 1 Cahier
Avant d'aborder le code TensorFlow, il est important de se familiariser avec la descente de gradient et la régression linéaire.
Qu'est-ce que la descente de gradient ?
Dans les termes les plus simples, il s'agit d'une technique numérique pour trouver les entrées d'un système d'équations qui minimisent sa sortie. Dans le contexte de l'apprentissage automatique, ce système d'équations est notre modèle , les entrées sont les paramètres inconnus du modèle et la sortie est une fonction de perte à minimiser, qui représente l'erreur entre le modèle et nos données. Pour certains problèmes (comme la régression linéaire), il existe des équations pour calculer directement les paramètres qui minimisent notre erreur, mais pour la plupart des applications pratiques, nous avons besoin de techniques numériques comme la descente de gradient pour arriver à une solution satisfaisante.
Le point le plus important de cet article est que la descente de gradient nécessite généralement de disposer nos équations et d'utiliser le calcul pour dériver la relation entre notre fonction de perte et nos paramètres. Avec TensorFlow (et tout outil d'auto-différenciation moderne), le calcul est géré pour nous, nous pouvons donc nous concentrer sur la conception de la solution et ne pas perdre de temps sur sa mise en œuvre.
Voici à quoi cela ressemble sur un problème de régression linéaire simple. Nous avons un échantillon des tailles (h) et des poids (w) de 150 mâles adultes, et commençons par une estimation imparfaite de la pente et de l'écart type de cette ligne. Après environ 15 itérations de descente de gradient, nous arrivons à une solution quasi-optimale.
Voyons comment nous avons produit la solution ci-dessus en utilisant TensorFlow 2.0.
Pour la régression linéaire, nous disons que les poids peuvent être prédits par une équation linéaire des hauteurs.
Nous voulons trouver les paramètres α et β (pente et interception) qui minimisent l'erreur quadratique moyenne (perte) entre les prédictions et les valeurs réelles. Ainsi, notre fonction de perte (dans ce cas, « l'erreur quadratique moyenne » ou MSE) ressemble à ceci :
Nous pouvons voir à quoi ressemble l'erreur quadratique moyenne pour quelques lignes imparfaites, puis avec la solution exacte (α = 6,04, β = -230,5).
Mettons cette idée en action avec TensorFlow. La première chose à faire est de coder la fonction de perte en utilisant des tenseurs et des fonctions tf.*
.
def calc_mean_sq_error(heights, weights, slope, intercept): predicted_wgts = slope * heights + intercept errors = predicted_wgts - weights mse = tf.reduce_mean(errors**2) return mse
Cela semble assez simple. Tous les opérateurs algébriques standard sont surchargés pour les tenseurs, nous devons donc seulement nous assurer que les variables que nous optimisons sont des tenseurs, et nous utilisons les méthodes tf.*
pour tout le reste.
Ensuite, tout ce que nous avons à faire est de mettre cela dans une boucle de descente de gradient :
def run_gradient_descent(heights, weights, init_slope, init_icept, learning_rate): # Any values to be part of gradient calcs need to be vars/tensors tf_slope = tf.Variable(init_slope, dtype='float32') tf_icept = tf.Variable(init_icept, dtype='float32') # Hardcoding 25 iterations of gradient descent for i in range(25): # Do all calculations under a "GradientTape" which tracks all gradients with tf.GradientTape() as tape: tape.watch((tf_slope, tf_icept)) # This is the same mean-squared-error calculation as before predictions = tf_slope * heights + tf_icept errors = predictions - weights loss = tf.reduce_mean(errors**2) # Auto-diff magic! Calcs gradients between loss calc and params dloss_dparams = tape.gradient(loss, [tf_slope, tf_icept]) # Gradients point towards +loss, so subtract to "descend" tf_slope = tf_slope - learning_rate * dloss_dparams[0] tf_icept = tf_icept - learning_rate * dloss_dparams[1]
Prenons un moment pour apprécier à quel point c'est soigné. La descente de gradient nécessite le calcul des dérivées de la fonction de perte par rapport à toutes les variables que nous essayons d'optimiser. Le calcul est censé être impliqué, mais nous n'en avons en fait rien fait. La magie réside dans le fait que :
- TensorFlow construit un graphique de calcul de chaque calcul effectué sous un
tf.GradientTape()
. - TensorFlow sait comment calculer les dérivées (gradients) de chaque opération, de sorte qu'il peut déterminer comment n'importe quelle variable du graphique de calcul affecte n'importe quelle autre variable.
À quoi ressemble le processus à partir de différents points de départ ?
La descente de gradient se rapproche remarquablement de la MSE optimale, mais converge en fait vers une pente et une interception sensiblement différentes de celles de l'optimum dans les deux exemples. Dans certains cas, il s'agit simplement d'une descente de gradient convergeant vers le minimum local, ce qui est un défi inhérent aux algorithmes de descente de gradient. Mais il est prouvé que la régression linéaire n'a qu'un seul minimum global. Alors, comment s'est-on retrouvé sur la mauvaise pente et intercepté ?
Dans ce cas, le problème est que nous avons trop simplifié le code pour des raisons de démonstration. Nous n'avons pas normalisé nos données et le paramètre de pente a une caractéristique différente de celle du paramètre d'interception. De minuscules changements de pente peuvent produire des changements massifs de perte, tandis que de minuscules changements d'interception ont très peu d'effet. Cette énorme différence d'échelle des paramètres entraînables conduit à la pente dominant les calculs de gradient, le paramètre d'interception étant presque ignoré.
Ainsi, la descente de gradient trouve efficacement la meilleure pente très proche de l'estimation d'interception initiale. Et comme l'erreur est si proche de l'optimum, les gradients qui l'entourent sont minuscules, de sorte que chaque itération successive ne se déplace que d'un tout petit peu. Normaliser d'abord nos données aurait considérablement amélioré ce phénomène, mais cela ne l'aurait pas éliminé.
C'était un exemple relativement simple, mais nous verrons dans les sections suivantes que cette capacité « d'auto-différenciation » peut gérer des choses assez complexes.
Exemple 2 : vecteurs unitaires à propagation maximale
Exemple 2 Cahier
Cet exemple suivant est basé sur un exercice d'apprentissage en profondeur amusant dans un cours d'apprentissage en profondeur que j'ai suivi l'année dernière.
L'essentiel du problème est que nous avons un "auto-encodeur variationnel" (VAE) qui peut produire des visages réalistes à partir d'un ensemble de 32 nombres normalement distribués. Pour l'identification des suspects, nous voulons utiliser le VAE pour produire un ensemble diversifié de visages (théoriques) parmi lesquels un témoin peut choisir, puis affiner la recherche en produisant plus de visages similaires à ceux qui ont été choisis. Pour cet exercice, il a été suggéré de randomiser l'ensemble initial de vecteurs, mais je voulais trouver un état initial optimal.
Nous pouvons formuler le problème comme ceci : étant donné un espace à 32 dimensions, trouvez un ensemble de vecteurs unitaires X qui sont au maximum écartés. En deux dimensions, c'est facile à calculer exactement. Mais pour trois dimensions (ou 32 dimensions !), il n'y a pas de réponse simple. Cependant, si nous pouvons définir une fonction de perte appropriée qui est à son minimum lorsque nous avons atteint notre état cible, peut-être que la descente de gradient peut nous aider à y parvenir.
Nous allons commencer avec un ensemble aléatoire de 20 vecteurs comme indiqué ci-dessus et expérimenter avec trois fonctions de perte différentes, chacune avec une complexité croissante, pour démontrer les capacités de TensorFlow.
Définissons d'abord notre boucle d'entraînement. Nous placerons toute la logique TensorFlow sous la méthode self.calc_loss()
, puis nous pourrons simplement remplacer cette méthode pour chaque technique, en recyclant cette boucle.
# Define the framework for trying different loss functions # Base class implements loop, sub classes override self.calc_loss() class VectorSpreadAlgorithm: # ... def calc_loss(self, tensor2d): raise NotImplementedError("Define this in your derived class") def one_iter(self, i, learning_rate): # self.vecs is an 20x2 tensor, representing twenty 2D vectors tfvecs = tf.convert_to_tensor(self.vecs, dtype=tf.float32) with tf.GradientTape() as tape: tape.watch(tfvecs) loss = self.calc_loss(tfvecs) # Here's the magic again. Derivative of spread with respect to # input vectors gradients = tape.gradient(loss, tfvecs) self.vecs = self.vecs - learning_rate * gradients
La première technique à essayer est la plus simple. Nous définissons une métrique de propagation qui est l'angle des vecteurs les plus proches les uns des autres. On veut maximiser la propagation, mais il est classique d'en faire un problème de minimisation. Nous prenons donc simplement le négatif de la métrique de propagation :
class VectorSpread_Maximize_Min_Angle(VectorSpreadAlgorithm): def calc_loss(self, tensor2d): angle_pairs = tf.acos(tensor2d @ tf.transpose(tensor2d)) disable_diag = tf.eye(tensor2d.numpy().shape[0]) * 2 * np.pi spread_metric = tf.reduce_min(angle_pairs + disable_diag) # Convention is to return a quantity to be minimized, but we want # to maximize spread. So return negative spread return -spread_metric
Un peu de magie Matplotlib donnera une visualisation.
C'est maladroit (littéralement !) mais ça marche. Seuls deux des 20 vecteurs sont mis à jour à la fois, augmentant l'espace entre eux jusqu'à ce qu'ils ne soient plus les plus proches, puis passant à l'augmentation de l'angle entre les deux nouveaux vecteurs les plus proches. La chose importante à noter est que cela fonctionne . Nous voyons que TensorFlow a pu passer des gradients via la méthode tf.reduce_min()
et la méthode tf.acos()
pour faire la bonne chose.
Essayons quelque chose d'un peu plus élaboré. Nous savons qu'à la solution optimale, tous les vecteurs doivent avoir le même angle par rapport à leurs voisins les plus proches. Ajoutons donc la "variance des angles minimaux" à la fonction de perte.
class VectorSpread_MaxMinAngle_w_Variance(VectorSpreadAlgorithm): def spread_metric(self, tensor2d): """ Assumes all rows already normalized """ angle_pairs = tf.acos(tensor2d @ tf.transpose(tensor2d)) disable_diag = tf.eye(tensor2d.numpy().shape[0]) * 2 * np.pi all_mins = tf.reduce_min(angle_pairs + disable_diag, axis=1) # Same calculation as before: find the min-min angle min_min = tf.reduce_min(all_mins) # But now also calculate the variance of the min angles vector avg_min = tf.reduce_mean(all_mins) var_min = tf.reduce_sum(tf.square(all_mins - avg_min)) # Our spread metric now includes a term to minimize variance spread_metric = min_min - 0.4 * var_min # As before, want negative spread to keep it a minimization problem return -spread_metric
Ce vecteur solitaire vers le nord rejoint maintenant rapidement ses pairs, car l'angle avec son voisin le plus proche est énorme et augmente le terme de variance qui est maintenant minimisé. Mais il est toujours motivé par l'angle global minimum qui reste lent à monter. Les idées que j'ai pour améliorer cela fonctionnent généralement dans ce cas 2D, mais pas dans des dimensions supérieures.
Mais se concentrer trop sur la qualité de cette tentative mathématique passe à côté de l'essentiel. Regardez combien d'opérations de tenseur sont impliquées dans les calculs de moyenne et de variance, et comment TensorFlow suit et différencie avec succès chaque calcul pour chaque composant de la matrice d'entrée. Et nous n'avons pas eu à faire de calcul manuel. Nous venons de jeter quelques calculs simples ensemble, et TensorFlow a fait le calcul pour nous.
Enfin, essayons encore une chose : une solution basée sur la force. Imaginez que chaque vecteur soit une petite planète attachée à un point central. Chaque planète émet une force qui la repousse des autres planètes. Si nous devions exécuter une simulation physique de ce modèle, nous devrions aboutir à la solution souhaitée.
Mon hypothèse est que la descente de gradient devrait également fonctionner. À la solution optimale, la force tangente sur chaque planète de toutes les autres planètes devrait s'annuler en une force nette nulle (si elle n'était pas nulle, les planètes se déplaceraient). Calculons donc l'amplitude de la force sur chaque vecteur et utilisons la descente de gradient pour la pousser vers zéro.
Tout d'abord, nous devons définir la méthode qui calcule la force à l'aide des méthodes tf.*
:
class VectorSpread_Force(VectorSpreadAlgorithm): def force_a_onto_b(self, vec_a, vec_b): # Calc force assuming vec_b is constrained to the unit sphere diff = vec_b - vec_a norm = tf.sqrt(tf.reduce_sum(diff**2)) unit_force_dir = diff / norm force_magnitude = 1 / norm**2 force_vec = unit_force_dir * force_magnitude # Project force onto this vec, calculate how much is radial b_dot_f = tf.tensordot(vec_b, force_vec, axes=1) b_dot_b = tf.tensordot(vec_b, vec_b, axes=1) radial_component = (b_dot_f / b_dot_b) * vec_b # Subtract radial component and return result return force_vec - radial_component
Ensuite, nous définissons notre fonction de perte en utilisant la fonction de force ci-dessus. Nous accumulons la force nette sur chaque vecteur et calculons sa magnitude. À notre solution optimale, toutes les forces devraient s'annuler et nous devrions avoir une force nulle.

def calc_loss(self, tensor2d): n_vec = tensor2d.numpy().shape[0] all_force_list = [] for this_idx in range(n_vec): # Accumulate force of all other vecs onto this one this_force_list = [] for other_idx in range(n_vec): if this_idx == other_idx: continue this_vec = tensor2d[this_idx, :] other_vec = tensor2d[other_idx, :] tangent_force_vec = self.force_a_onto_b(other_vec, this_vec) this_force_list.append(tangent_force_vec) # Use list of all N-dimensional force vecs. Stack and sum. sum_tangent_forces = tf.reduce_sum(tf.stack(this_force_list)) this_force_mag = tf.sqrt(tf.reduce_sum(sum_tangent_forces**2)) # Accumulate all magnitudes, should all be zero at optimal solution all_force_list.append(this_force_mag) # We want to minimize total force sum, so simply stack, sum, return return tf.reduce_sum(tf.stack(all_force_list))
Non seulement la solution fonctionne à merveille (en plus du chaos dans les premières images), mais le vrai mérite revient à TensorFlow. Cette solution impliquait plusieurs boucles for
, une instruction if
et un énorme réseau de calculs, et TensorFlow a réussi à tracer des gradients à travers tout cela pour nous.
Exemple 3 : Génération d'entrées d'IA contradictoires
Exemple 3 Cahier
À ce stade, les lecteurs peuvent penser : "Hé ! Ce message n'était pas censé parler d'apprentissage en profondeur !" Mais techniquement, l'introduction fait référence à aller au-delà de "l' entraînement de modèles d'apprentissage en profondeur". Dans ce cas, nous ne formons pas , mais exploitons plutôt certaines propriétés mathématiques d'un réseau de neurones profonds pré-formé pour le tromper en nous donnant les mauvais résultats. Cela s'est avéré beaucoup plus facile et plus efficace qu'on ne l'imaginait. Et tout ce qu'il a fallu, c'est une autre petite goutte de code TensorFlow 2.0.
Nous commençons par trouver un classifieur d'images à attaquer. Nous utiliserons l'une des meilleures solutions pour le concours Dogs vs Cats Kaggle ; plus précisément, la solution présentée par Kaggler "uysimty". Tout leur mérite d'avoir fourni un modèle chat contre chien efficace et d'avoir fourni une excellente documentation. Il s'agit d'un modèle puissant composé de 13 millions de paramètres répartis sur 18 couches de réseaux de neurones. (Les lecteurs sont invités à en savoir plus à ce sujet dans le cahier correspondant.)
Veuillez noter que le but ici n'est pas de mettre en évidence une lacune dans ce réseau particulier, mais de montrer comment tout réseau de neurones standard avec un grand nombre d'entrées est vulnérable.
Avec un peu de bricolage, j'ai pu comprendre comment charger le modèle et pré-traiter les images à classer par celui-ci.
Cela ressemble à un classificateur vraiment solide ! Toutes les classifications d'échantillons sont correctes et supérieures à 95 % de confiance. Attaquons-le !
Nous voulons produire une image qui est évidemment un chat, mais demander au classificateur de décider qu'il s'agit d'un chien avec une grande confiance. Comment pouvons-nous faire cela?
Commençons par une image de chat qu'il classe correctement, puis découvrons comment de minuscules modifications dans chaque canal de couleur (valeurs 0-255) d'un pixel d'entrée donné affectent la sortie finale du classificateur. La modification d'un pixel ne fera probablement pas grand-chose, mais peut-être que les ajustements cumulatifs de toutes les valeurs de 128x128x3 = 49 152 pixels permettront d'atteindre notre objectif.
Comment savons-nous dans quel sens pousser chaque pixel ? Pendant l'entraînement normal du réseau neuronal, nous essayons de minimiser la perte entre l'étiquette cible et l'étiquette prédite, en utilisant la descente de gradient dans TensorFlow pour mettre à jour simultanément les 13 millions de paramètres libres. Dans ce cas, nous allons plutôt laisser les 13 millions de paramètres fixes et ajuster les valeurs de pixel de l'entrée elle-même.
Quelle est notre fonction de perte ? Eh bien, c'est à quel point l'image ressemble à un chat ! Si nous calculons la dérivée de la valeur du chat par rapport à chaque pixel d'entrée, nous savons dans quelle direction pousser chacun pour minimiser la probabilité de classification du chat.
def adversarial_modify(victim_img, to_dog=False, to_cat=False): # We only need four gradient descent steps for i in range(4): tf_victim_img = tf.convert_to_tensor(victim_img, dtype='float32') with tf.GradientTape() as tape: tape.watch(tf_victim_img) # Run the image through the model model_output = model(tf_victim_img) # Minimize cat confidence and maximize dog confidence loss = (model_output[0] - model_output[1]) dloss_dimg = tape.gradient(loss, tf_victim_img) # Ignore gradient magnitudes, only care about sign, +1/255 or -1/255 pixels_w_pos_grad = tf.cast(dloss_dimg > 0.0, 'float32') / 255. pixels_w_neg_grad = tf.cast(dloss_dimg < 0.0, 'float32') / 255. victim_img = victim_img - pixels_w_pos_grad + pixels_w_neg_grad
La magie Matplotlib aide à nouveau à visualiser les résultats.
Wow! Pour l'œil humain, chacune de ces images est identique. Pourtant, après quatre itérations, nous avons convaincu le classificateur qu'il s'agissait d'un chien, avec une confiance de 99,4 % !
Assurons-nous que ce n'est pas un hasard et que cela fonctionne aussi dans l'autre sens.
Succès! Le classificateur avait initialement prédit cela correctement en tant que chien avec une confiance de 98,4 %, et pense maintenant qu'il s'agit d'un chat avec une confiance de 99,8 %.
Enfin, regardons un exemple de patch d'image et voyons comment il a changé.
Comme prévu, le patch final est très similaire à l'original, chaque pixel ne se déplaçant que de -4 à +4 dans la valeur d'intensité du canal rouge. Ce décalage n'est pas suffisant pour qu'un humain distingue la différence, mais modifie complètement la sortie du classificateur.
Réflexions finales : optimisation de la descente de gradient
Tout au long de cet article, nous avons envisagé d'appliquer manuellement des gradients à nos paramètres entraînables dans un souci de simplicité et de transparence. Cependant, dans le monde réel, les scientifiques des données devraient se lancer directement dans l'utilisation d' optimiseurs , car ils ont tendance à être beaucoup plus efficaces, sans ajouter de gonflement du code.
Il existe de nombreux optimiseurs populaires, notamment RMSprop, Adagrad et Adadelta, mais le plus courant est probablement Adam . Parfois, elles sont appelées « méthodes de taux d'apprentissage adaptatif » car elles maintiennent dynamiquement un taux d'apprentissage différent pour chaque paramètre. Beaucoup d'entre eux utilisent des termes de quantité de mouvement et approximent des dérivés d'ordre supérieur, dans le but d'échapper aux minima locaux et d'obtenir une convergence plus rapide.
Dans une animation empruntée à Sebastian Ruder, on peut voir le parcours de divers optimiseurs descendant une surface de perte. Les techniques manuelles que nous avons démontrées sont les plus comparables à "SGD". L'optimiseur le plus performant ne sera pas le même pour chaque surface de perte ; cependant, les optimiseurs plus avancés fonctionnent généralement mieux que les plus simples.
Cependant, il est rarement utile d'être un expert des optimiseurs, même pour ceux qui souhaitent fournir des services de développement d'intelligence artificielle. C'est une meilleure utilisation du temps des développeurs pour se familiariser avec un couple, juste pour comprendre comment ils améliorent la descente de gradient dans TensorFlow. Après cela, ils peuvent simplement utiliser Adam par défaut et en essayer d'autres uniquement si leurs modèles ne convergent pas.
Pour les lecteurs qui sont vraiment intéressés par comment et pourquoi ces optimiseurs fonctionnent, la vue d'ensemble de Ruder - dans laquelle l'animation apparaît - est l'une des ressources les meilleures et les plus exhaustives sur le sujet.
Mettons à jour notre solution de régression linéaire de la première section pour utiliser des optimiseurs. Ce qui suit est le code de descente de gradient original utilisant des gradients manuels.
# Manual gradient descent operations def run_gradient_descent(heights, weights, init_slope, init_icept, learning_rate): tf_slope = tf.Variable(init_slope, dtype='float32') tf_icept = tf.Variable(init_icept, dtype='float32') for i in range(25): with tf.GradientTape() as tape: tape.watch((tf_slope, tf_icept)) predictions = tf_slope * heights + tf_icept errors = predictions - weights loss = tf.reduce_mean(errors**2) gradients = tape.gradient(loss, [tf_slope, tf_icept]) tf_slope = tf_slope - learning_rate * gradients[0] tf_icept = tf_icept - learning_rate * gradients[1]
Maintenant, voici le même code utilisant un optimiseur à la place. Vous verrez qu'il n'y a pratiquement pas de code supplémentaire (les lignes modifiées sont surlignées en bleu) :
# Gradient descent with Optimizer (RMSprop) def run_gradient_descent (heights, weights, init_slope, init_icept, learning_rate) : tf_slope = tf.Variable(init_slope, dtype= 'float32' ) tf_icept = tf.Variable(init_icept, dtype= 'float32' ) # Group trainable parameters into a list trainable_params = [tf_slope, tf_icept] # Define your optimizer (RMSprop) outside of the training loop optimizer = keras.optimizers.RMSprop(learning_rate) for i in range( 25 ): # GradientTape loop is the same with tf.GradientTape() as tape: tape.watch( trainable_params ) predictions = tf_slope * heights + tf_icept errors = predictions - weights loss = tf.reduce_mean(errors** 2 ) # We can use the trainable parameters list directly in gradient calcs gradients = tape.gradient(loss, trainable_params ) # Optimizers always aim to *minimize* the loss function optimizer.apply_gradients(zip(gradients, trainable_params))
C'est ça! Nous avons défini un optimiseur RMSprop
en dehors de la boucle de descente de gradient, puis nous avons utilisé la méthode optimizer.apply_gradients()
après chaque calcul de gradient pour mettre à jour les paramètres entraînables. L'optimiseur est défini en dehors de la boucle car il gardera une trace des gradients historiques pour calculer des termes supplémentaires comme le momentum et les dérivés d'ordre supérieur.
Voyons à quoi cela ressemble avec l'optimiseur RMSprop .
Ça a l'air génial ! Essayons maintenant avec l'optimiseur Adam .
Waouh, que s'est-il passé ici ? Il semble que la mécanique de l'élan dans Adam l'amène à dépasser la solution optimale et à inverser le cours plusieurs fois. Normalement, ce mécanisme de quantité de mouvement aide avec les surfaces de perte complexes, mais cela nous fait mal dans ce cas simple. Cela met l'accent sur le conseil de faire du choix de l'optimiseur l'un des hyperparamètres à régler lors de l'entraînement de votre modèle.
Quiconque souhaite explorer l'apprentissage en profondeur voudra se familiariser avec ce modèle, car il est largement utilisé dans les architectures TensorFlow personnalisées, où il est nécessaire d'avoir des mécanismes de perte complexes qui ne sont pas facilement intégrés dans le flux de travail standard. Dans cet exemple simple de descente de gradient TensorFlow, il n'y avait que deux paramètres pouvant être entraînés, mais il est nécessaire lorsque vous travaillez avec des architectures contenant des centaines de millions de paramètres à optimiser.
Descente de gradient dans TensorFlow : de la recherche des minimums à l'attaque des systèmes d'IA
Tous les extraits de code et les images ont été produits à partir des blocs-notes du référentiel GitHub correspondant. Il contient également un résumé de toutes les sections, avec des liens vers les cahiers individuels, pour les lecteurs qui souhaitent voir le code complet. Dans un souci de simplification du message, de nombreux détails ont été omis qui peuvent être trouvés dans la documentation en ligne complète.
J'espère que cet article était perspicace et qu'il vous a fait réfléchir à des façons d'utiliser la descente de gradient dans TensorFlow. Même si vous ne l'utilisez pas vous-même, cela clarifie, espérons-le, le fonctionnement de toutes les architectures de réseaux de neurones modernes : créez un modèle, définissez une fonction de perte et utilisez la descente de gradient pour adapter le modèle à votre ensemble de données.
En tant que partenaire Google Cloud, les experts certifiés Google de Toptal sont à la disposition des entreprises à la demande pour leurs projets les plus importants.