Une introduction douce à l'API tensorflow.data
Lorsque vous créez et entraînez un modèle d'apprentissage profond Keras, vous pouvez fournir les données d'entraînement de plusieurs manières différentes. La présentation des données sous forme de tableau NumPy ou de tenseur TensorFlow est courante. Une autre façon consiste à créer une fonction de générateur Python et à laisser la boucle d'entraînement en lire les données. Une autre façon de fournir des données consiste à utiliser l'ensemble de données tf.data
.
Dans ce didacticiel, vous verrez comment utiliser l'ensemble de données tf.data
pour un modèle Keras. Après avoir terminé ce tutoriel, vous apprendrez :
- Comment créer et utiliser l'ensemble de données
tf.data
? - L'avantage de le faire par rapport à une fonction génératrice
Commençons.
Aperçu
Cet article est divisé en quatre sections ; ils sont:
- Formation d'un modèle Keras avec NumPy Array et la fonction Générateur
- Création d'un ensemble de données à l'aide de
tf.data
- Création d'un ensemble de données à partir d'une fonction de générateur
- Données avec prélecture
Formation d'un modèle Keras avec NumPy Array et la fonction Générateur
Avant de voir comment fonctionne l'API tf.data
, examinons comment vous pouvez habituellement entraîner un modèle Keras.
Tout d’abord, vous avez besoin d’un ensemble de données. Un exemple est l'ensemble de données de mode MNIST fourni avec l'API Keras. Cet ensemble de données contient 60 000 échantillons d'apprentissage et 10 000 échantillons de test de 28 × 28 pixels en niveaux de gris, et l'étiquette de classification correspondante est codée avec des nombres entiers de 0 à 9.
L'ensemble de données est un tableau NumPy. Ensuite, vous pouvez créer un modèle Keras pour la classification, et avec la fonction fit()
du modèle, vous fournissez le tableau NumPy sous forme de données.
Le code complet est le suivant :
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow.keras.datasets.fashion_mnist import load_data
from tensorflow.keras.layers import Dense, Flatten
from tensorflow.keras.models import Sequential
(train_image, train_label), (test_image, test_label) = load_data()
print(train_image.shape)
print(train_label.shape)
print(test_image.shape)
print(test_label.shape)
model = Sequential([
Flatten(input_shape=(28,28)),
Dense(100, activation="relu"),
Dense(100, activation="relu"),
Dense(10, activation="sigmoid")
])
model.compile(optimizer="adam",
loss="sparse_categorical_crossentropy",
metrics="sparse_categorical_accuracy")
history = model.fit(train_image, train_label,
batch_size=32, epochs=50,
validation_data=(test_image, test_label), verbose=0)
print(model.evaluate(test_image, test_label))
plt.plot(history.history['val_sparse_categorical_accuracy'])
plt.show()
L'exécution de ce code affichera ce qui suit :
(60000, 28, 28)
(60000,)
(10000, 28, 28)
(10000,)
313/313 [==============================] - 0s 392us/step - loss: 0.5114 - sparse_categorical_accuracy: 0.8446
[0.5113903284072876, 0.8446000218391418]
Et également, créez le graphique suivant de précision de validation sur les 50 époques pendant lesquelles vous avez entraîné votre modèle :
L'autre façon de former le même réseau consiste à fournir les données d'une fonction génératrice Python au lieu d'un tableau NumPy. Une fonction génératrice est celle avec une instruction yield
pour émettre des données tandis que la fonction s'exécute parallèlement au consommateur de données. Un générateur de l'ensemble de données de mode MNIST peut être créé comme suit :
def batch_generator(image, label, batchsize):
N = len(image)
i = 0
while True:
yield image[i:i+batchsize], label[i:i+batchsize]
i = i + batchsize
if i + batchsize > N:
i = 0
Cette fonction est censée être appelée avec la syntaxe batch_generator(train_image, train_label, 32)
. Il analysera indéfiniment les tableaux d’entrée par lots. Une fois atteint la fin du tableau, il redémarrera depuis le début.
Entraîner un modèle Keras avec un générateur est similaire à l'utilisation de la fonction fit()
:
history = model.fit(batch_generator(train_image, train_label, 32),
steps_per_epoch=len(train_image)//32,
epochs=50, validation_data=(test_image, test_label), verbose=0)
Au lieu de fournir les données et l'étiquette, il vous suffit de fournir le générateur car il fournira les deux. Lorsque les données sont présentées sous forme de tableau NumPy, vous pouvez déterminer le nombre d'échantillons en regardant la longueur du tableau. Keras peut terminer une époque lorsque l'ensemble de données est utilisé une fois. Cependant, votre fonction génératrice émettra des lots indéfiniment, vous devez donc lui indiquer quand une époque est terminée, en utilisant l'argument steps_per_epoch
de la fonction fit()
.
Dans le code ci-dessus, les données de validation ont été fournies sous forme de tableau NumPy, mais vous pouvez utiliser un générateur à la place et spécifier l'argument validation_steps
.
Voici le code complet utilisant une fonction génératrice, dans lequel le résultat est le même que celui de l'exemple précédent :
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow.keras.datasets.fashion_mnist import load_data
from tensorflow.keras.layers import Dense, Flatten
from tensorflow.keras.models import Sequential
(train_image, train_label), (test_image, test_label) = load_data()
print(train_image.shape)
print(train_label.shape)
print(test_image.shape)
print(test_label.shape)
model = Sequential([
Flatten(input_shape=(28,28)),
Dense(100, activation="relu"),
Dense(100, activation="relu"),
Dense(10, activation="sigmoid")
])
def batch_generator(image, label, batchsize):
N = len(image)
i = 0
while True:
yield image[i:i+batchsize], label[i:i+batchsize]
i = i + batchsize
if i + batchsize > N:
i = 0
model.compile(optimizer="adam",
loss="sparse_categorical_crossentropy",
metrics="sparse_categorical_accuracy")
history = model.fit(batch_generator(train_image, train_label, 32),
steps_per_epoch=len(train_image)//32,
epochs=50, validation_data=(test_image, test_label), verbose=0)
print(model.evaluate(test_image, test_label))
plt.plot(history.history['val_sparse_categorical_accuracy'])
plt.show()
Création d'un ensemble de données à l'aide de tf.data
Étant donné que les données de mode MNIST sont chargées, vous pouvez les convertir en un ensemble de données tf.data
, comme suit :
...
dataset = tf.data.Dataset.from_tensor_slices((train_image, train_label))
print(dataset.element_spec)
Cela imprime les spécifications de l'ensemble de données comme suit :
(TensorSpec(shape=(28, 28), dtype=tf.uint8, name=None),
TensorSpec(shape=(), dtype=tf.uint8, name=None))
Vous pouvez voir que les données sont un tuple (car un tuple a été passé en argument à la fonction from_tensor_slices()
), alors que le premier élément est sous la forme (28,28)
tandis que le deuxième élément est un scalaire. Les deux éléments sont stockés sous forme d’entiers non signés de 8 bits.
Si vous ne présentez pas les données sous forme de tuple de deux tableaux NumPy lorsque vous créez l'ensemble de données, vous pouvez également le faire plus tard. Ce qui suit crée le même ensemble de données, mais crée d'abord l'ensemble de données pour les données d'image et l'étiquette séparément avant de les combiner :
...
train_image_data = tf.data.Dataset.from_tensor_slices(train_image)
train_label_data = tf.data.Dataset.from_tensor_slices(train_label)
dataset = tf.data.Dataset.zip((train_image_data, train_label_data))
print(dataset.element_spec)
Cela imprimera la même spécification :
(TensorSpec(shape=(28, 28), dtype=tf.uint8, name=None),
TensorSpec(shape=(), dtype=tf.uint8, name=None))
La fonction zip()
dans l'ensemble de données est comme la fonction zip()
en Python car elle fait correspondre les données une par une à partir de plusieurs ensembles de données dans un tuple.
L'un des avantages de l'utilisation de l'ensemble de données tf.data
est la flexibilité de gestion des données. Vous trouverez ci-dessous le code complet expliquant comment entraîner un modèle Keras à l'aide d'un ensemble de données dans lequel la taille du lot est définie sur l'ensemble de données :
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow.keras.datasets.fashion_mnist import load_data
from tensorflow.keras.layers import Dense, Flatten
from tensorflow.keras.models import Sequential
(train_image, train_label), (test_image, test_label) = load_data()
dataset = tf.data.Dataset.from_tensor_slices((train_image, train_label))
model = Sequential([
Flatten(input_shape=(28,28)),
Dense(100, activation="relu"),
Dense(100, activation="relu"),
Dense(10, activation="sigmoid")
])
history = model.fit(dataset.batch(32),
epochs=50,
validation_data=(test_image, test_label),
verbose=0)
print(model.evaluate(test_image, test_label))
plt.plot(history.history['val_sparse_categorical_accuracy'])
plt.show()
Il s’agit du cas d’utilisation le plus simple d’utilisation d’un ensemble de données. Si vous approfondissez, vous pouvez voir qu’un ensemble de données n’est qu’un itérateur. Par conséquent, vous pouvez imprimer chaque échantillon d'un ensemble de données en utilisant les éléments suivants :
for image, label in dataset:
print(image) # array of shape (28,28) in tf.Tensor
print(label) # integer label in tf.Tensor
L'ensemble de données comporte de nombreuses fonctions intégrées. Le batch()
utilisé auparavant en fait partie. Si vous créez des lots à partir d'un ensemble de données et que vous les imprimez, vous obtenez les éléments suivants :
for image, label in dataset.batch(32):
print(image) # array of shape (32,28,28) in tf.Tensor
print(label) # array of shape (32,) in tf.Tensor
Ici, chaque élément d'un lot n'est pas un échantillon mais un lot d'échantillons. Vous disposez également de fonctions telles que map()
, filter()
et reduce()
pour la transformation de séquence, ou concatendate()
et interleave()
pour combiner avec un autre ensemble de données. Il existe également repeat()
, take()
, take_while()
et skip()
comme notre familier homologue du module itertools
de Python. Une liste complète des fonctions peut être trouvée dans la documentation de l'API.
Création d'un ensemble de données à partir d'une fonction de générateur
Jusqu'à présent, vous avez vu comment un ensemble de données pouvait être utilisé à la place d'un tableau NumPy pour entraîner un modèle Keras. En effet, un jeu de données peut également être créé à partir d’une fonction génératrice. Mais au lieu d'une fonction génératrice qui génère un lot, comme vous l'avez vu dans l'un des exemples ci-dessus, vous créez maintenant une fonction génératrice qui génère un échantillon à la fois. Voici la fonction :
import numpy as np
import tensorflow as tf
def shuffle_generator(image, label, seed):
idx = np.arange(len(image))
np.random.default_rng(seed).shuffle(idx)
for i in idx:
yield image[i], label[i]
dataset = tf.data.Dataset.from_generator(
shuffle_generator,
args=[train_image, train_label, 42],
output_signature=(
tf.TensorSpec(shape=(28,28), dtype=tf.uint8),
tf.TensorSpec(shape=(), dtype=tf.uint8)))
print(dataset.element_spec)
Cette fonction randomise le tableau d'entrée en mélangeant le vecteur d'index. Ensuite, il génère un échantillon à la fois. Contrairement à l’exemple précédent, ce générateur se terminera lorsque les échantillons du tableau seront épuisés.
Vous pouvez créer un ensemble de données à partir de la fonction en utilisant from_generator()
. Vous devez fournir le nom de la fonction génératrice (au lieu d'un générateur instancié) ainsi que la signature de sortie de l'ensemble de données. Ceci est nécessaire car l'API tf.data.Dataset
ne peut pas déduire la spécification de l'ensemble de données avant que le générateur ne soit consommé.
L'exécution du code ci-dessus imprimera la même spécification qu'avant :
(TensorSpec(shape=(28, 28), dtype=tf.uint8, name=None),
TensorSpec(shape=(), dtype=tf.uint8, name=None))
Un tel ensemble de données est fonctionnellement équivalent à l’ensemble de données que vous avez créé précédemment. Vous pouvez donc l’utiliser pour vous entraîner comme avant. Voici le code complet :
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
from tensorflow.keras.datasets.fashion_mnist import load_data
from tensorflow.keras.layers import Dense, Flatten
from tensorflow.keras.models import Sequential
(train_image, train_label), (test_image, test_label) = load_data()
def shuffle_generator(image, label, seed):
idx = np.arange(len(image))
np.random.default_rng(seed).shuffle(idx)
for i in idx:
yield image[i], label[i]
dataset = tf.data.Dataset.from_generator(
shuffle_generator,
args=[train_image, train_label, 42],
output_signature=(
tf.TensorSpec(shape=(28,28), dtype=tf.uint8),
tf.TensorSpec(shape=(), dtype=tf.uint8)))
model = Sequential([
Flatten(input_shape=(28,28)),
Dense(100, activation="relu"),
Dense(100, activation="relu"),
Dense(10, activation="sigmoid")
])
history = model.fit(dataset.batch(32),
epochs=50,
validation_data=(test_image, test_label),
verbose=0)
print(model.evaluate(test_image, test_label))
plt.plot(history.history['val_sparse_categorical_accuracy'])
plt.show()
Ensemble de données avec prélecture
Le véritable avantage de l'utilisation d'un ensemble de données est d'utiliser prefetch()
.
L'utilisation d'un tableau NumPy pour l'entraînement est probablement la meilleure performance. Cependant, cela signifie que vous devez charger toutes les données en mémoire. Utiliser une fonction générateur pour la formation permet de préparer un lot à la fois, dans lequel les données peuvent être chargées depuis le disque à la demande, par exemple. Cependant, l'utilisation d'une fonction génératrice pour entraîner un modèle Keras signifie que la boucle d'entraînement ou la fonction génératrice est exécutée à tout moment. Il n’est pas facile de faire fonctionner en parallèle le générateur et la boucle d’entraînement de Keras.
Dataset est l'API qui permet au générateur et à la boucle de formation de fonctionner en parallèle. Si vous disposez d'un générateur coûteux en termes de calcul (par exemple, pour effectuer une augmentation d'image en temps réel), vous pouvez créer un ensemble de données à partir d'une telle fonction de générateur, puis l'utiliser avec prefetch()
, comme suit :
...
history = model.fit(dataset.batch(32).prefetch(3),
epochs=50,
validation_data=(test_image, test_label),
verbose=0)
L'argument numérique de prefetch()
est la taille du tampon. Ici, il est demandé à l'ensemble de données de conserver trois lots en mémoire, prêts à être consommés par la boucle d'entraînement. Chaque fois qu'un lot est consommé, l'API de l'ensemble de données reprend la fonction de générateur pour remplir le tampon de manière asynchrone en arrière-plan. Par conséquent, vous pouvez permettre à la boucle de formation et à l’algorithme de préparation des données à l’intérieur de la fonction génératrice de s’exécuter en parallèle.
Il convient de mentionner que, dans la section précédente, vous avez créé un générateur de brassage pour l'API de l'ensemble de données. En effet, l'API de l'ensemble de données dispose également d'une fonction shuffle()
pour faire de même, mais vous ne souhaiterez peut-être pas l'utiliser à moins que l'ensemble de données ne soit suffisamment petit pour tenir en mémoire.
La fonction shuffle()
, identique à prefetch()
, prend un argument de la taille du tampon. L'algorithme de lecture aléatoire remplira le tampon avec l'ensemble de données et en tirera un élément au hasard. L'élément consommé sera remplacé par l'élément suivant de l'ensemble de données. Par conséquent, vous avez besoin d'un tampon aussi grand que l'ensemble de données lui-même pour effectuer un mélange véritablement aléatoire. Cette limitation est démontrée par l'extrait suivant :
import tensorflow as tf
import numpy as np
n_dataset = tf.data.Dataset.from_tensor_slices(np.arange(10000))
for n in n_dataset.shuffle(10).take(20):
print(n.numpy())
Le résultat de ce qui précède ressemble à ce qui suit :
9
6
2
7
5
1
4
14
11
17
19
18
3
16
15
22
10
23
21
13
Ici, vous pouvez voir que les nombres sont mélangés dans son voisinage, et vous ne voyez jamais de grands nombres issus de sa sortie.
Lectures complémentaires
Pour en savoir plus sur l'ensemble de données tf.data
, consultez la documentation de son API :
- API
tf.data.Dataset
Résumé
Dans cet article, vous avez vu comment utiliser l'ensemble de données tf.data
et comment il peut être utilisé pour entraîner un modèle Keras.
Concrètement, vous avez appris :
- Comment entraîner un modèle à l'aide des données d'un tableau NumPy, d'un générateur et d'un ensemble de données
- Comment créer un ensemble de données à l'aide d'un tableau NumPy ou d'une fonction génératrice
- Comment utiliser la prélecture avec un ensemble de données pour faire fonctionner le générateur et la boucle d'entraînement en parallèle