1. Présentation
Dans cet atelier, vous allez découvrir l'architecture convolutive moderne et mettre à profit vos connaissances pour implémenter un réseau de neurones convolutif simple, mais efficace, appelé "squeezenet".
Cet atelier comprend les explications théoriques nécessaires concernant les réseaux de neurones convolutifs. Il constitue un bon point de départ pour les développeurs qui se familiarisent avec le deep learning.
Cet atelier fait partie 4 de la série "Keras sur TPU" de la série. Vous pouvez les effectuer dans l'ordre suivant ou indépendamment.
- Pipelines de données à la vitesse du TPU: tf.data.Dataset et TFRecords
- Votre premier modèle Keras, avec apprentissage par transfert
- Réseaux de neurones convolutifs, avec Keras et des TPU
- [THIS LAB] Convnets modernes, Squeezenet et Xception, avec Keras et des TPU
Points abordés
- Maîtriser le style fonctionnel Keras
- Créer un modèle à l'aide de l'architecture Squeezenet
- Utiliser des TPU afin d'effectuer un entraînement rapide et d'effectuer des itérations sur votre architecture
- Pour implémenter l'augmentation des données avec tf.data.dataset
- Affiner un grand modèle pré-entraîné (Xception) sur TPU
Commentaires
Si vous constatez une anomalie dans cet atelier de programmation, veuillez nous en informer. Vous pouvez nous faire part de vos commentaires via GitHub [lien de commentaires].
2. Guide de démarrage rapide de Google Colaboratory
Cet atelier utilise Google Colaboratory et ne nécessite aucune configuration de votre part. Colaboratory est une plate-forme de notebooks en ligne destinée à l'enseignement. Il propose un entraînement sans frais sur les processeurs, les GPU et les TPU.
Vous pouvez ouvrir cet exemple de notebook et l'exécuter sur quelques cellules pour vous familiariser avec Colaboratory.
Sélectionner un backend TPU
Dans le menu Colab, sélectionnez Environnement d'exécution > Modifiez le type d'environnement d'exécution, puis sélectionnez "TPU". Dans cet atelier de programmation, vous allez utiliser un TPU (Tensor Processing Unit) puissant sauvegardé pour l'entraînement avec accélération matérielle. La connexion à l'environnement d'exécution se fera automatiquement lors de la première exécution. Vous pouvez également utiliser la commande dans le coin supérieur droit.
Exécution du notebook
Pour exécuter les cellules une par une, cliquez dessus et utilisez Maj + ENTRÉE. Vous pouvez également exécuter l'intégralité du notebook avec Environnement d'exécution > Tout exécuter
Sommaire
Tous les notebooks comportent une table des matières. Vous pouvez l'ouvrir à l'aide de la flèche noire située à gauche.
Cellules masquées
Certaines cellules n'affichent que leur titre. Cette fonctionnalité de notebook spécifique à Colab. Vous pouvez double-cliquer dessus pour voir le code à l'intérieur, mais ce n'est généralement pas très intéressant. Elles sont généralement compatibles avec les fonctions de visualisation ou de compatibilité. Vous devez quand même exécuter ces cellules pour que les fonctions qu'elles contiennent soient définies.
Authentification
Colab peut accéder à vos buckets Google Cloud Storage privés à condition que vous vous authentifiiez avec un compte autorisé. L'extrait de code ci-dessus déclenche un processus d'authentification.
3. [INFO] Que sont les Tensor Processing Units (TPU) ?
En résumé
Le code pour entraîner un modèle sur TPU dans Keras (et utiliser le GPU ou le processeur si aucun TPU n'est disponible):
try: # detect TPUs
tpu = tf.distribute.cluster_resolver.TPUClusterResolver.connect()
strategy = tf.distribute.TPUStrategy(tpu)
except ValueError: # detect GPUs
strategy = tf.distribute.MirroredStrategy() # for CPU/GPU or multi-GPU machines
# use TPUStrategy scope to define model
with strategy.scope():
model = tf.keras.Sequential( ... )
model.compile( ... )
# train model normally on a tf.data.Dataset
model.fit(training_dataset, epochs=EPOCHS, steps_per_epoch=...)
Aujourd'hui, nous allons utiliser des TPU pour créer et optimiser un classificateur de fleurs à des vitesses interactives (minutes par session d'entraînement).
Pourquoi les TPU ?
Les GPU modernes sont organisés autour de "cœurs" programmables, une architecture très flexible qui leur permet de gérer diverses tâches telles que le rendu 3D, le deep learning, les simulations physiques, etc. À l'inverse, les TPU associent un processeur vectoriel classique à une unité de multiplication matricielle dédiée et excellent dans toute tâche où de grandes multiplications de matrices dominent, comme les réseaux de neurones.
Illustration: couche de réseau de neurones dense sous forme de multiplication matricielle, avec un lot de huit images traitées simultanément via le réseau de neurones. Veuillez effectuer une multiplication sur une ligne par colonne pour vérifier qu'il s'agit bien d'une somme pondérée de toutes les valeurs en pixels d'une image. Les couches convolutives peuvent également être représentées par des multiplications matricielles, bien que cela soit un peu plus compliqué ( explication ici, section 1).
Matériel
MXU et VPU
Un cœur de TPU v2 est composé d'une unité matricielle (MXU, Matrix Multiply Unit) qui exécute les multiplications matricielles et d'une unité de traitement vectoriel (VPU) pour toutes les autres tâches telles que les activations, softmax, etc. Le VPU gère les calculs float32 et int32. Les unités matricielles, quant à elles, fonctionnent dans un format à virgule flottante 16-32 bits de précision mixte.
Valeurs à virgule flottante de précision mixte et bfloat16
L'unité matricielle calcule les multiplications matricielles à l'aide des entrées bfloat16 et des sorties float32. Les accumulations intermédiaires sont effectuées avec une précision de type float32.
L'entraînement des réseaux de neurones est généralement résistant au bruit introduit par une précision réduite à virgule flottante. Dans certains cas, le bruit contribue même à la convergence de l'optimiseur. La précision à virgule flottante 16 bits est traditionnellement utilisée pour accélérer les calculs, mais les formats float16 et float32 ont des plages très différentes. Réduire la précision de float32 à float16 entraîne généralement des sur-débits et des dépassements de capacité insuffisants. Des solutions existent, mais un travail supplémentaire est généralement nécessaire pour faire fonctionner float16.
C'est pourquoi Google a introduit le format bfloat16 dans les TPU. bfloat16 est une valeur float32 tronquée avec exactement les mêmes bits d'exposant et la même plage que float32. En plus du fait que les TPU calculent les multiplications matricielles en précision mixte avec des entrées bfloat16 mais en sorties float32, aucune modification du code n'est généralement nécessaire pour bénéficier des gains de performances liés à une précision réduite.
Tableau systolique
L'unité matricielle implémente les multiplications matricielles dans le matériel à l'aide d'un "tableau systolique" architecture dans laquelle les éléments de données circulent à travers un tableau d'unités de calcul matérielles. (En médecine, le terme « systolique » fait référence aux contractions cardiaques et au flux sanguin, ici au flux de données.)
L'élément de base d'une multiplication matricielle est un produit scalaire entre une ligne d'une matrice et une colonne de l'autre matrice (voir l'illustration en haut de cette section). Pour une multiplication matricielle Y=X*W, un élément du résultat serait:
Y[2,0] = X[2,0]*W[0,0] + X[2,1]*W[1,0] + X[2,2]*W[2,0] + ... + X[2,n]*W[n,0]
Sur un GPU, il est possible de programmer ce produit scalaire dans un "cœur" de GPU. et l'exécuter sur autant de "cœurs" disponibles en parallèle pour essayer de calculer
chaque valeur de la matrice résultante en une seule fois. Si la matrice résultante fait 128 x 128, il faudrait 128 x 128=16 000 cœurs ce qui n'est généralement pas possible. Les GPU les plus volumineux possèdent environ 4 000 cœurs. En revanche, un TPU utilise le strict minimum de matériel pour les unités de calcul de l'unité matricielle: seulement bfloat16 x bfloat16 => float32
accumulateurs de multiplication, rien d'autre. Elles sont si petites qu'un TPU peut en implémenter 16K dans une unité matricielle de 128 x 128 et traiter cette multiplication matricielle en une seule fois.
Illustration: tableau systolique MXU. Les éléments de calcul sont des accumulateurs. Les valeurs d'une matrice sont chargées dans le tableau (points rouges). Les valeurs de l'autre matrice circulent dans le tableau (points gris). Les lignes verticales propagent les valeurs vers le haut. Les lignes horizontales propagent des sommes partielles. Il revient à l'utilisateur de vérifier qu'au fur et à mesure que les données circulent dans le tableau, vous obtenez le résultat de la multiplication matricielle depuis le côté droit.
De plus, alors que les produits scalaires sont calculés dans une unité matricielle, les sommes intermédiaires sont simplement transférées entre des unités de calcul adjacentes. Ils n'ont pas besoin d'être stockés et récupérés vers/depuis la mémoire ou même un fichier d'enregistrement. Au final, l'architecture de tableau systolique des TPU présente un avantage significatif en termes de densité et de puissance, ainsi qu'un avantage non négligeable en termes de vitesse par rapport à un GPU lors du calcul des multiplications matricielles.
Cloud TPU
Lorsque vous demandez un " Cloud TPU v2" sur Google Cloud Platform, vous disposez d'une machine virtuelle (VM) dotée d'une carte TPU PCI. La carte TPU est équipée de quatre puces TPU double cœur. Chaque cœur de TPU comporte un VPU (Vector Processing Unit) et une unité de multiplication matriX de 128 x 128 MXU. Ce "Cloud TPU" est généralement connectée via le réseau à la VM à l'origine de la demande. La vue d'ensemble ressemble donc à ceci:
Illustration: votre VM avec un Cloud TPU connecté au réseau accélérateur. "Cloud TPU" elle-même est composée d'une VM dotée d'une carte TPU PCI équipée de quatre puces TPU double cœur.
Pods TPU
Dans les centres de données de Google, les TPU sont connectés à une interconnexion de calcul hautes performances (HPC, High Performance Computing), qui peut les faire apparaître comme un accélérateur très important. Ils sont appelés "pods", et peuvent englober jusqu'à 512 cœurs de TPU v2 ou 2 048 cœurs de TPU v3.
Illustration: un pod TPU v3. Racks et cartes TPU connectés via une interconnexion HPC.
Pendant l'entraînement, les gradients sont échangés entre les cœurs de TPU à l'aide de l'algorithme all-reduce ( une bonne explication ici). Le modèle en cours d'entraînement peut tirer parti du matériel en s'entraînant sur des lots de grande taille.
Illustration: synchronisation des gradients pendant l'entraînement à l'aide de l'algorithme all-reduce sur le réseau HPC de maillage toroïdal 2D de Google TPU.
Logiciel
Entraînement de lots de grande taille
La taille de lot idéale pour les TPU est de 128 éléments de données par cœur de TPU, mais le matériel peut déjà présenter une bonne utilisation à partir de 8 éléments de données par cœur de TPU. Rappelez-vous qu'un Cloud TPU possède huit cœurs.
Dans cet atelier de programmation, nous allons utiliser l'API Keras. Dans Keras, le lot que vous spécifiez correspond à la taille de lot globale pour l'ensemble du TPU. Vos lots seront automatiquement divisés en huit et exécutés sur les huit cœurs du TPU.
Pour obtenir d'autres conseils sur les performances, consultez le Guide sur les performances des TPU. Pour les très grandes tailles de lot, une attention particulière peut être nécessaire dans certains modèles. Pour en savoir plus, consultez la section LARSOptimizer.
Sous le capot: XLA
Les programmes TensorFlow définissent des graphiques de calcul. Le TPU n'exécute pas directement du code Python, mais le graphe de calcul défini par votre programme TensorFlow. En arrière-plan, un compilateur appelé XLA (Acceleated Linear Algebra compiler) transforme le graphe TensorFlow des nœuds de calcul en code de machine TPU. Ce compilateur effectue également de nombreuses optimisations avancées sur votre code et la disposition de votre mémoire. La compilation s'effectue automatiquement à mesure que le travail est envoyé au TPU. Vous n'avez pas besoin d'inclure explicitement XLA dans votre chaîne de compilation.
Illustration: Pour s'exécuter sur TPU, le graphe de calcul défini par votre programme TensorFlow est d'abord traduit en représentation XLA (acceleated Linear Algebra compiler), puis compilé par XLA en code machine TPU.
Utiliser des TPU dans Keras
Les TPU sont compatibles avec l'API Keras depuis TensorFlow 2.1. La compatibilité avec Keras fonctionne sur les TPU et les pods TPU. Voici un exemple qui fonctionne sur les TPU, les GPU et les processeurs:
try: # detect TPUs
tpu = tf.distribute.cluster_resolver.TPUClusterResolver.connect()
strategy = tf.distribute.TPUStrategy(tpu)
except ValueError: # detect GPUs
strategy = tf.distribute.MirroredStrategy() # for CPU/GPU or multi-GPU machines
# use TPUStrategy scope to define model
with strategy.scope():
model = tf.keras.Sequential( ... )
model.compile( ... )
# train model normally on a tf.data.Dataset
model.fit(training_dataset, epochs=EPOCHS, steps_per_epoch=...)
Dans cet extrait de code:
TPUClusterResolver().connect()
trouve le TPU sur le réseau. Elle fonctionne sans paramètres sur la plupart des systèmes Google Cloud (tâches AI Platform, Colaboratory, Kubeflow, VM de deep learning créées via l'utilitaire "gsutil up"). Ces systèmes savent où se trouve leur TPU grâce à une variable d'environnement TPU_NAME. Si vous créez un TPU manuellement, définissez l'environnement TPU_NAME. "var." sur la VM à partir de laquelle vous l'utilisez, ou appelezTPUClusterResolver
avec des paramètres explicites:TPUClusterResolver(tp_uname, zone, project)
TPUStrategy
est la partie qui implémente la distribution et la fonction "all-reduce" ; l'algorithme de synchronisation de gradient.- La stratégie est appliquée via un champ d'application. Le modèle doit être défini dans le champ d'application de la stratégie.
- La fonction
tpu_model.fit
attend un objet tf.data.Dataset en entrée pour l'entraînement TPU.
Tâches de portage TPU courantes
- Bien qu'il existe de nombreuses façons de charger des données dans un modèle TensorFlow, pour les TPU, l'utilisation de l'API
tf.data.Dataset
est requise. - Les TPU sont très rapides et l'ingestion de données devient souvent le goulot d'étranglement lors de leur exécution. Le guide sur les performances TPU propose des outils permettant de détecter les goulots d'étranglement des données, ainsi que d'autres conseils de performances.
- Les nombres int8 ou int16 sont traités comme des int32. Le TPU n'a pas de matériel entier fonctionnant sur moins de 32 bits.
- Certaines opérations TensorFlow ne sont pas compatibles. Cliquez ici pour consulter la liste. Heureusement, cette limitation ne s'applique qu'au code d'entraînement, c'est-à-dire aux propagations avant et arrière dans votre modèle. Vous pouvez toujours utiliser toutes les opérations Tensorflow dans votre pipeline d'entrée de données, car elles seront exécutées sur le processeur.
tf.py_func
n'est pas compatible avec TPU.
4. [INFO] Principes de base du classificateur de réseaux de neurones
En résumé
Si vous connaissez déjà tous les termes en gras dans le paragraphe suivant, vous pouvez passer à l'exercice suivant. Si vous débutez dans le deep learning, bienvenue et poursuivez votre lecture.
Pour les modèles créés sous la forme d'une séquence de couches, Keras propose l'API Sequential. Par exemple, un classificateur d'images utilisant trois couches denses peut être écrit dans Keras sous la forme suivante:
model = tf.keras.Sequential([
tf.keras.layers.Flatten(input_shape=[192, 192, 3]),
tf.keras.layers.Dense(500, activation="relu"),
tf.keras.layers.Dense(50, activation="relu"),
tf.keras.layers.Dense(5, activation='softmax') # classifying into 5 classes
])
# this configures the training of the model. Keras calls it "compiling" the model.
model.compile(
optimizer='adam',
loss= 'categorical_crossentropy',
metrics=['accuracy']) # % of correct answers
# train the model
model.fit(dataset, ... )
Réseau de neurones dense
Il s'agit du réseau de neurones le plus simple pour classer des images. Elle est composée de "neurones" organisées en couches. La première couche traite les données d'entrée et transmet ses sorties à d'autres couches. Elle est appelée "dense" car chaque neurone est connecté à tous les neurones de la couche précédente.
Vous pouvez alimenter un tel réseau en aplatissant les valeurs RVB de tous ses pixels dans un vecteur long et en les utilisant comme entrées. Ce n'est pas la meilleure technique de reconnaissance d'image, mais nous l'améliorerons plus tard.
Neurones, activations, RELU
Un "neurone" calcule la somme pondérée de toutes ses entrées, ajoute une valeur appelée "biais" et transmet le résultat via une "fonction d'activation". Dans un premier temps, les pondérations et les biais sont inconnus. Elles seront initialisées de manière aléatoire et "apprises" en entraînant le réseau de neurones sur de nombreuses données connues.
La fonction d'activation la plus populaire est appelée RELU pour l'unité de rectification linéaire (rectified Linear Unit). Comme vous pouvez le voir sur le graphique ci-dessus, il s'agit d'une fonction très simple.
Activation Softmax
Le réseau ci-dessus se termine par une couche à cinq neurones, car nous classons les fleurs en cinq catégories (rose, tulipe, pissenlit, marguerite, tournesol). Les neurones des couches intermédiaires sont activés à l'aide de la fonction d'activation RELU classique. Dans la dernière couche, nous souhaitons calculer des nombres compris entre 0 et 1 représentant la probabilité que cette fleur soit une rose, une tulipe, etc. Pour cela, nous allons utiliser une fonction d'activation appelée "softmax".
L'application de la fonction softmax à un vecteur s'effectue en prenant l'exposant exponentiel de chaque élément, puis en normalisant le vecteur, généralement à l'aide de la norme L1 (somme des valeurs absolues) de sorte que les valeurs s'additionnent pour être égales à 1 et puissent être interprétées comme des probabilités.
Perte d'entropie croisée
Maintenant que notre réseau de neurones produit des prédictions à partir d'images d'entrée, nous devons mesurer leur qualité, c'est-à-dire la distance entre ce que le réseau nous dit et les bonnes réponses, souvent appelées "étiquettes". N'oubliez pas que nous disposons d'étiquettes correctes pour toutes les images de l'ensemble de données.
N'importe quelle distance conviendrait, mais pour les problèmes de classification, la "distance d'entropie croisée" est la plus efficace. Nous appellerons cela notre erreur ou « perte » :
Descente de gradient
« Entraînement » le réseau de neurones consiste en fait à utiliser des images et des étiquettes d'entraînement pour ajuster les pondérations et les biais afin de minimiser la fonction de perte d'entropie croisée. Voici comment cela fonctionne.
L'entropie croisée est une fonction des pondérations, des biais, des pixels de l'image d'entraînement et de sa classe connue.
Si nous calculons les dérivées partielles de l'entropie croisée par rapport à l'ensemble des pondérations et des biais, nous obtenons un "gradient" calculé pour une image, une étiquette et une valeur actuelle donnée de pondérations et de biais. N'oubliez pas que nous pouvons avoir des millions de pondérations et de biais. Calculer le gradient représente beaucoup de travail. Heureusement, Tensorflow s'en charge pour nous. La propriété mathématique d'un dégradé est qu'il pointe "vers le haut". Puisque nous voulons aller là où l'entropie croisée est faible, nous allons dans la direction opposée. Nous mettons à jour les pondérations et les biais selon une fraction du gradient. Ensuite, nous faisons la même chose encore et encore en utilisant les lots suivants d'images et d'étiquettes d'entraînement dans une boucle d'entraînement. Espérons que cela aboutit à un point où l'entropie croisée est minimale, bien que rien ne garantit que ce minimum soit unique.
Mini-lot et momentum
Vous pouvez calculer votre gradient à partir d'un exemple d'image et mettre immédiatement à jour les pondérations et les biais. Toutefois, avec un lot de 128 images, par exemple, vous obtenez un gradient qui représente mieux les contraintes imposées par les différentes images d'exemple. Il est donc susceptible de converger plus rapidement vers la solution. La taille du mini-lot est un paramètre ajustable.
Cette technique, parfois appelée "descente de gradient stochastique", présente un autre avantage, plus pragmatique: le traitement par lot implique également de travailler avec des matrices plus volumineuses, qui sont généralement plus faciles à optimiser sur les GPU et les TPU.
La convergence peut encore être un peu chaotique et peut même s'arrêter si le vecteur de gradient n'est que des zéros. Cela signifie-t-il que nous avons trouvé un minimum ? Non. Un composant de dégradé peut avoir une valeur minimale ou maximale égale à zéro. Avec un vecteur de gradient comportant des millions d'éléments, s'ils sont tous des zéros, la probabilité que chaque zéro corresponde à un minimum et aucun d'entre eux à un point maximal est assez faible. Dans un espace comportant de nombreuses dimensions, les points d'arrêt sont assez courants et nous ne voulons pas nous en arrêter là.
Illustration: un point de selle. Le dégradé est de 0, mais ce n'est pas un minimum dans toutes les directions. (Attribution de l'image Wikimedia: By Nicoguaro - Own work, CC BY 3.0)
La solution consiste à donner une dynamique à l'algorithme d'optimisation afin qu'il puisse passer en selle sans s'arrêter.
Glossaire
lot ou mini-lot: l'entraînement est toujours effectué sur des lots de données d'entraînement et d'étiquettes. Cela contribue à la convergence de l'algorithme. Le "lot" est généralement la première dimension des Tensors de données. Par exemple, un Tensor de forme [100, 192, 192, 3] contient 100 images de 192 x 192 pixels avec trois valeurs par pixel (RVB).
perte d'entropie croisée: fonction de perte spéciale, souvent utilisée dans les classificateurs.
couche dense: couche de neurones où chaque neurone est connecté à tous les neurones de la couche précédente.
features: les entrées d'un réseau de neurones sont parfois appelées "caractéristiques". L'ingénierie des caractéristiques consiste à déterminer quelles parties d'un ensemble de données (ou combinaisons de parties) alimenter un réseau de neurones afin d'obtenir de bonnes prédictions.
labels: autre nom pour "classes". ou les réponses correctes d'un problème de classification supervisée
Taux d'apprentissage: fraction du gradient par laquelle les pondérations et les biais sont mis à jour à chaque itération de la boucle d'entraînement.
logits: les sorties d'une couche de neurones avant l'application de la fonction d'activation sont appelées "logits". Le terme vient de la « fonction logistique » autrement dit la "fonction sigmoïde" qui était autrefois la fonction d'activation la plus populaire. "Sorties de neurones avant la fonction logistique" a été abrégé en "logits".
loss: fonction d'erreur comparant les sorties du réseau de neurones aux bonnes réponses
neurone: calcule la somme pondérée de ses entrées, ajoute un biais et alimente le résultat via une fonction d'activation.
Encodage one-hot: la classe 3 sur 5 est encodée sous la forme d'un vecteur de cinq éléments, tous des zéros à l'exception du troisième, qui est égal à 1.
relu: unité de rectification linéaire. Fonction d'activation populaire utilisée pour les neurones.
sigmoïde: autre fonction d'activation populaire et qui reste utile dans des cas particuliers.
softmax: fonction d'activation spéciale qui agit sur un vecteur, augmente la différence entre la composante la plus grande et les autres, et normalise également le vecteur pour obtenir une somme égale à 1, de sorte qu'il puisse être interprété comme un vecteur de probabilités. Il s'agit de la dernière étape des classificateurs.
tensor: un "Tensor" est semblable à une matrice mais avec un nombre arbitraire de dimensions. Un Tensor unidimensionnel est un vecteur. Un Tensor à deux dimensions est une matrice. Vous pouvez aussi avoir des Tensors ayant au moins 3, 4, 5 dimensions ou plus.
5. [INFO] Réseaux de neurones convolutifs
En résumé
Si vous connaissez déjà tous les termes en gras dans le paragraphe suivant, vous pouvez passer à l'exercice suivant. Si vous débutez avec les réseaux de neurones convolutifs, poursuivez votre lecture.
Illustration: Filtrer une image avec deux filtres successifs composés chacun de 4x4x3=48 pondérations apprises.
Voici à quoi ressemble un simple réseau de neurones convolutif dans Keras:
model = tf.keras.Sequential([
# input: images of size 192x192x3 pixels (the three stands for RGB channels)
tf.keras.layers.Conv2D(kernel_size=3, filters=24, padding='same', activation='relu', input_shape=[192, 192, 3]),
tf.keras.layers.Conv2D(kernel_size=3, filters=24, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=12, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=6, padding='same', activation='relu'),
tf.keras.layers.Flatten(),
# classifying into 5 categories
tf.keras.layers.Dense(5, activation='softmax')
])
model.compile(
optimizer='adam',
loss= 'categorical_crossentropy',
metrics=['accuracy'])
Introduction aux réseaux de neurones convolutifs
Dans une couche d'un réseau convolutif, un "neurone" fait une somme pondérée des pixels juste au-dessus, sur une petite zone de l'image uniquement. Elle ajoute un biais et alimente la somme via une fonction d'activation, tout comme le ferait un neurone d'une couche dense standard. Cette opération est ensuite répétée sur toute l'image avec les mêmes pondérations. Souvenez-vous que dans les couches denses, chaque neurone avait ses propres pondérations. Ici, un seul "patch" de pondérations glisse sur l'image dans les deux sens (une "convolution"). La sortie comporte autant de valeurs qu'il y a de pixels dans l'image (un remplissage est toutefois nécessaire au niveau des bords). Il s'agit d'une opération de filtrage utilisant un filtre de 4x4x3=48 pondérations.
En revanche, 48 pondérations ne seront pas suffisantes. Pour ajouter davantage de degrés de liberté, nous répétons la même opération avec un nouvel ensemble de pondérations. Vous obtenez ainsi un nouvel ensemble de sorties de filtre. Appelons-le un "canal" de sorties par analogie avec les canaux R, V et B de l'image d'entrée.
Les deux (ou plusieurs) ensembles de pondérations peuvent être additionnés sous la forme d'un seul Tensor en ajoutant une nouvelle dimension. Cela nous donne la forme générique du Tensor des pondérations pour une couche convolutive. Le nombre de canaux d'entrée et de sortie étant des paramètres, nous pouvons commencer à empiler et à chaîner des couches convolutives.
Illustration: Un réseau de neurones convolutif transforme des "cubes" de données dans d'autres "cubes" de données.
Convolutions fractionnées, pooling maximal
En effectuant les convolutions avec un pas de 2 ou 3, nous pouvons également réduire le cube de données résultant dans ses dimensions horizontales. Il existe deux façons courantes de procéder:
- Convolution échelonnée: un filtre glissant comme ci-dessus, mais avec un pas supérieur à 1
- Pooling maximal: fenêtre glissante appliquant l'opération MAX (généralement sur 2x2 patchs, répétés tous les 2 pixels)
Illustration: le fait de faire glisser la fenêtre de calcul de 3 pixels réduit le nombre de valeurs de sortie. Les convolutions stylisées ou le pooling maximal (max. sur une fenêtre de 2x2 glissant d'un pas de 2) sont un moyen de réduire le cube de données dans les dimensions horizontales.
Classificateur cvolutif
Enfin, nous associons une tête de classification en aplatissant le dernier cube de données et en l'alimentant via une couche dense activée par softmax. Un classificateur convolutif typique peut se présenter comme suit:
Illustration: un classificateur d'images utilisant des couches convolutives et softmax. Elle utilise les filtres 3x3 et 1x1. Les couches "maxpool" prennent le nombre maximal de groupes de 2 x 2 points de données. La tête de classification est implémentée avec une couche dense avec activation softmax.
Dans Keras
La pile convolutive illustrée ci-dessus peut être écrite dans Keras comme suit:
model = tf.keras.Sequential([
# input: images of size 192x192x3 pixels (the three stands for RGB channels)
tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu', input_shape=[192, 192, 3]),
tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu'),
tf.keras.layers.Conv2D(kernel_size=1, filters=32, padding='same', activation='relu'),
tf.keras.layers.MaxPooling2D(pool_size=2),
tf.keras.layers.Conv2D(kernel_size=3, filters=16, padding='same', activation='relu'),
tf.keras.layers.Conv2D(kernel_size=1, filters=8, padding='same', activation='relu'),
tf.keras.layers.Flatten(),
# classifying into 5 categories
tf.keras.layers.Dense(5, activation='softmax')
])
model.compile(
optimizer='adam',
loss= 'categorical_crossentropy',
metrics=['accuracy'])
6. [NOUVELLE INFO] Architectures convolutives modernes
En résumé
Illustration : "module" convolutif Quelle est la meilleure option à ce stade ? Une couche "max-pool" suivie d'une couche convolutive 1x1 ou d'une autre combinaison de couches ? Essayez-les toutes, concaténez les résultats et laissez le réseau décider. À droite : " inception. une architecture convolutive utilisant ces modules.
Dans Keras, pour créer des modèles dont le flux de données peut déboucher sur des ramifications, vous devez utiliser le modèle "fonctionnel" du style du modèle. Voici un exemple :
l = tf.keras.layers # syntax shortcut
y = l.Conv2D(filters=32, kernel_size=3, padding='same',
activation='relu', input_shape=[192, 192, 3])(x) # x=input image
# module start: branch out
y1 = l.Conv2D(filters=32, kernel_size=1, padding='same', activation='relu')(y)
y3 = l.Conv2D(filters=32, kernel_size=3, padding='same', activation='relu')(y)
y = l.concatenate([y1, y3]) # output now has 64 channels
# module end: concatenation
# many more layers ...
# Create the model by specifying the input and output tensors.
# Keras layers track their connections automatically so that's all that's needed.
z = l.Dense(5, activation='softmax')(y)
model = tf.keras.Model(x, z)
Autres astuces à petit prix
Petits filtres 3x3
Dans cette illustration, vous voyez le résultat de deux filtres 3 x 3 consécutifs. Essayez de déterminer les points de données qui ont contribué au résultat: ces deux filtres 3 x 3 consécutifs calculent une combinaison d'une région 5 x 5. Ce n'est pas exactement la même combinaison qu'un filtre 5x5 calculerait, mais cela vaut la peine d'essayer, car deux filtres 3x3 consécutifs sont moins chers qu'un filtre 5x5 unique.
Des convolutions 1x1 ?
En termes mathématiques, un « 1x1 » la convolution est une multiplication par une constante, ce n'est pas un concept très utile. Toutefois, dans les réseaux de neurones convolutifs, rappelez-vous que le filtre est appliqué à un cube de données, et pas seulement à une image 2D. Par conséquent, une image de 1 x 1 pixel filter calcule la somme pondérée d'une colonne de données 1x1 (voir l'illustration) et, lorsque vous la faites glisser sur les données, vous obtenez une combinaison linéaire des canaux de l'entrée. C'est vraiment utile. Si vous considérez les canaux comme le résultat d'opérations de filtrage individuelles, par exemple un filtre pour "oreilles pointues", un autre pour "moustaches" et une troisième pour "yeux fendus" puis un "1 x 1" la couche convolutive calculera plusieurs combinaisons linéaires possibles de ces caractéristiques, ce qui peut être utile lorsque vous recherchez un "chat". En plus de cela, les couches 1x1 utilisent moins de pondérations.
7. Pression
Le "Squeezenet" vous présente un moyen simple de rassembler ces idées. article. Les auteurs suggèrent une conception de module convolutif très simple, utilisant uniquement des couches convolutives 1x1 et 3x3.
Illustration: architecture squeezenet basée sur des modules de déclenchement. Elles alternent une couche 1 x 1 qui "compresse" les données entrantes dans la dimension verticale suivies de deux couches convolutives parallèles de 1 x 1 et 3 x 3 qui se "développent" la profondeur des données.
Pratique
Continuez dans votre notebook précédent et créez un réseau de neurones convolutif inspiré de la compression. Vous devrez remplacer le code du modèle par le "style fonctionnel Keras".
Keras_Flowers_TPU (playground).ipynb
Informations supplémentaires
Dans cet exercice, il sera utile de définir une fonction d'assistance pour un module de compression:
def fire(x, squeeze, expand):
y = l.Conv2D(filters=squeeze, kernel_size=1, padding='same', activation='relu')(x)
y1 = l.Conv2D(filters=expand//2, kernel_size=1, padding='same', activation='relu')(y)
y3 = l.Conv2D(filters=expand//2, kernel_size=3, padding='same', activation='relu')(y)
return tf.keras.layers.concatenate([y1, y3])
# this is to make it behave similarly to other Keras layers
def fire_module(squeeze, expand):
return lambda x: fire(x, squeeze, expand)
# usage:
x = l.Input(shape=[192, 192, 3])
y = fire_module(squeeze=24, expand=48)(x) # typically, squeeze is less than expand
y = fire_module(squeeze=32, expand=64)(y)
...
model = tf.keras.Model(x, y)
L'objectif cette fois est d'atteindre une précision de 80 %.
À essayer
Commencez par une seule couche convolutive, puis ajoutez "fire_modules
" en alternant avec des couches MaxPooling2D(pool_size=2)
. Vous pouvez tester entre deux et quatre couches de pooling maximum dans le réseau, ainsi qu'un, deux ou trois modules Fire consécutifs entre les couches de pooling maximales.
Dans les modules Fire, la commande "presser" doit généralement être plus petit que le paramètre "expand" . Ces paramètres sont en fait des nombres de filtres. Elles peuvent être comprises entre 8 et 196, généralement. Vous pouvez tester des architectures où le nombre de filtres augmente progressivement dans le réseau, ou des architectures simples dans lesquelles tous les modules Fire disposent du même nombre de filtres.
Voici un exemple :
x = tf.keras.layers.Input(shape=[*IMAGE_SIZE, 3]) # input is 192x192 pixels RGB
y = tf.keras.layers.Conv2D(kernel_size=3, filters=32, padding='same', activation='relu')(x)
y = fire_module(24, 48)(y)
y = tf.keras.layers.MaxPooling2D(pool_size=2)(y)
y = fire_module(24, 48)(y)
y = tf.keras.layers.MaxPooling2D(pool_size=2)(y)
y = fire_module(24, 48)(y)
y = tf.keras.layers.GlobalAveragePooling2D()(y)
y = tf.keras.layers.Dense(5, activation='softmax')(y)
model = tf.keras.Model(x, y)
À ce stade, vous remarquerez peut-être que vos tests ne se déroulent pas très bien et que l'objectif de précision de 80% semble lointain. Il est temps de faire d'autres astuces à moindre coût.
Normalisation par lot
La norme de traitement par lot vous aidera à résoudre les problèmes de convergence que vous rencontrez. Vous trouverez des explications détaillées sur cette technique dans le prochain atelier. Pour l'instant, veuillez l'utiliser comme une "magie" de boîte noire. en ajoutant la ligne suivante après chaque couche convolutive de votre réseau, y compris les couches contenues dans la fonction "fire_module" :
y = tf.keras.layers.BatchNormalization(momentum=0.9)(y)
# please adapt the input and output "y"s to whatever is appropriate in your context
Le paramètre momentum doit être réduit de sa valeur par défaut de 0,99 à 0,9, car notre ensemble de données est petit. Peu importe ce détail pour l'instant.
Augmentation des données
Vous obtiendrez encore quelques points de pourcentage en augmentant les données avec des transformations simples, comme des changements de saturation à gauche et à droite:
Cette opération est très simple dans TensorFlow avec l'API tf.data.Dataset. Définissez une nouvelle fonction de transformation pour vos données:
def data_augment(image, label):
image = tf.image.random_flip_left_right(image)
image = tf.image.random_saturation(image, lower=0, upper=2)
return image, label
Utilisez-les ensuite dans votre transformation finale des données (cellule "ensembles de données d'entraînement et de validation", fonction "get_batched_dataset"):
dataset = dataset.repeat() # existing line
# insert this
if augment_data:
dataset = dataset.map(data_augment, num_parallel_calls=AUTO)
dataset = dataset.shuffle(2048) # existing line
N'oubliez pas de rendre l'augmentation des données facultative et d'ajouter le code nécessaire pour vous assurer que seul l'ensemble de données d'entraînement est augmenté. Il n'est pas judicieux d'enrichir l'ensemble de données de validation.
Une précision de 80% en 35 époques devrait désormais être à votre portée.
Solution
Voici le notebook de la solution. Vous pouvez l'utiliser si vous êtes bloqué.
Keras_Flowers_TPU_squeezenet.ipynb
Points abordés
- 🤔 "Style fonctionnel" Keras modèles
- 🤓 Architecture de Squeezenet
- 🤓 Augmentation des données avec tf.data.datset
Veuillez prendre un moment pour passer en revue cette liste de contrôle.
8. Xception affiné
Convolutions séparables
Une autre méthode d'implémentation des couches convolutives s'est récemment fait connaître: les convolutions séparables en profondeur. Je sais, c'est une bouchée, mais le concept est assez simple. Elles sont implémentées dans TensorFlow et Keras en tant que tf.keras.layers.SeparableConv2D
.
Une convolution séparable exécute également un filtre sur l'image, mais utilise un ensemble distinct de pondérations pour chaque canal de l'image d'entrée. On observe ensuite une "convolution 1 x 1", une série de produits scalaires qui aboutissent à une somme pondérée des canaux filtrés. Avec de nouvelles pondérations à chaque fois, le nombre de recombinaisons pondérées des canaux est calculé autant que nécessaire.
Illustration: convolutions séparables. Phase 1: convolutions avec un filtre distinct pour chaque canal. Phase 2: recombinaisons linéaires de canaux. Répété avec un nouvel ensemble de pondérations jusqu'à ce que le nombre souhaité de canaux de sortie soit atteint. La phase 1 peut également être répétée, avec de nouvelles pondérations à chaque fois, mais dans la pratique, c'est rarement le cas.
Les convolutions séparables sont utilisées dans les architectures de réseaux convolutifs les plus récentes: MobileNetV2, Xception et EffectiveNet. À ce propos, MobileNetV2 est celui que vous avez utilisé précédemment pour l'apprentissage par transfert.
Elles sont moins chères que les convolutions standards et se sont avérées tout aussi efficaces dans la pratique. Voici le poids correspondant à l'exemple illustré ci-dessus:
Couche convolutive: 4 x 4 x 3 x 5 = 240
Couche convolutive séparable: 4 x 4 x 3 + 3 x 5 = 48 + 15 = 63
Il s'agit d'un exercice permettant au lecteur de calculer le nombre de multiplications requis pour appliquer chaque style de couche convolutive de la même manière. Les convolutions séparables sont plus petites et beaucoup plus efficaces en termes de calcul.
Pratique
Recommencer à partir de l'apprentissage par transfert pour Playground, mais cette fois, sélectionnez Xception comme modèle pré-entraîné. Xception n'utilise que des convolutions séparables. Laissez toutes les pondérations pouvant être entraînées. Nous allons affiner les pondérations pré-entraînées sur nos données au lieu d'utiliser les couches pré-entraînées comme telles.
Keras Flowers transfer learning (playground).ipynb
Objectif: précision > 95% (Non, sérieusement, c'est possible !)
Il s'agit du dernier exercice, qui nécessite un peu plus de travail en codage et en data science.
Informations supplémentaires sur l'affinage
Xception est disponible dans les modèles pré-entraînés standards de tf.keras.application*. N'oubliez pas de laisser tous les poids pouvant être entraînés cette fois-ci.
pretrained_model = tf.keras.applications.Xception(input_shape=[*IMAGE_SIZE, 3],
include_top=False)
pretrained_model.trainable = True
Pour obtenir de bons résultats lorsque vous affinez un modèle, vous devez prêter attention au taux d'apprentissage et utiliser une programmation de taux d'apprentissage avec une période d'optimisation. Exemple :
Commencer avec un taux d'apprentissage standard perturberait les pondérations pré-entraînées du modèle. Elles sont conservées progressivement jusqu'à ce que le modèle s'appuie sur vos données pour pouvoir les modifier de façon raisonnable. Après la montée en puissance, vous pouvez continuer avec un taux d'apprentissage constant ou exponentiel.
Dans Keras, le taux d'apprentissage est spécifié via un rappel, dans lequel vous pouvez calculer le taux d'apprentissage approprié pour chaque époque. Keras transmet le taux d'apprentissage approprié à l'optimiseur pour chaque époque.
def lr_fn(epoch):
lr = ...
return lr
lr_callback = tf.keras.callbacks.LearningRateScheduler(lr_fn, verbose=True)
model.fit(..., callbacks=[lr_callback])
Solution
Voici le notebook de la solution. Vous pouvez l'utiliser si vous êtes bloqué.
07_Keras_Flowers_TPU_xception_fine_tuned_best.ipynb
Points abordés
- 🤔 Convolution séparable en profondeur
- 🤓 Planifications des taux d'apprentissage
- 🇦 Affiner un modèle pré-entraîné
Veuillez prendre un moment pour passer en revue cette liste de contrôle.
9. Félicitations !
Vous avez créé votre premier réseau de neurones convolutif moderne et l'avez entraîné à une justesse supérieure à 90 %. Vous avez itéré l'entraînement successif en quelques minutes seulement grâce aux TPU. Voici qui conclut les quatre ateliers de programmation Keras sur TPU :
- Pipelines de données à la vitesse du TPU: tf.data.Dataset et TFRecords
- Votre premier modèle Keras, avec apprentissage par transfert
- Réseaux de neurones convolutifs, avec Keras et des TPU
- [THIS LAB] Convnets modernes, Squeezenet et Xception, avec Keras et des TPU
Les TPU en pratique
Les TPU et les GPU sont disponibles sur Cloud AI Platform:
- Sur les VM de deep learning
- Dans AI Platform Notebooks
- Dans les tâches AI Platform Training
Enfin, les commentaires sont les bienvenus. Veuillez nous indiquer si vous constatez une anomalie dans cet atelier ou si vous pensez qu'elle doit être améliorée. Vous pouvez nous faire part de vos commentaires via GitHub [lien de commentaires].
|