La journée communautaire ML est le 9 novembre ! Rejoignez - nous pour les mises à jour de tensorflow, JAX et plus En savoir plus

tf.data : Créer des pipelines d'entrée TensorFlow

Voir sur TensorFlow.org Exécuter dans Google Colab Voir la source sur GitHubTélécharger le cahier

L' tf.data API vous permet de construire des pipelines d'entrée complexes à partir de pièces simples et réutilisables. Par exemple, le pipeline d'un modèle d'image peut agréger des données à partir de fichiers dans un système de fichiers distribué, appliquer des perturbations aléatoires à chaque image et fusionner des images sélectionnées au hasard dans un lot pour l'apprentissage. Le pipeline d'un modèle de texte peut impliquer l'extraction de symboles à partir de données de texte brutes, leur conversion en identifiants intégrés avec une table de recherche et le regroupement de séquences de différentes longueurs. Le tf.data API permet de gérer de grandes quantités de données, lues à partir de différents formats de données et effectuer des transformations complexes.

Le tf.data API introduit un tf.data.Dataset abstraction qui représente une séquence d'éléments, dans lequel chaque élément est constitué d'un ou plusieurs composants. Par exemple, dans un pipeline d'images, un élément peut être un exemple d'apprentissage unique, avec une paire de composants tenseurs représentant l'image et son étiquette.

Il existe deux manières distinctes de créer un jeu de données :

  • Une source de données construit un Dataset de Dataset à partir des données stockées dans la mémoire ou dans un ou plusieurs fichiers.

  • Une transformation de données construit un ensemble de données d'un ou plusieurs tf.data.Dataset objets.

import tensorflow as tf
import pathlib
import os
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

np.set_printoptions(precision=4)

Mécanique de base

Pour créer un pipeline d'entrée, vous devez commencer par une source de données. Par exemple, pour construire un Dataset à partir des données en mémoire, vous pouvez utiliser tf.data.Dataset.from_tensors() ou tf.data.Dataset.from_tensor_slices() . Vous pouvez également, si vos données d'entrée sont stockées dans un fichier au format TFRecord recommandé, vous pouvez utiliser tf.data.TFRecordDataset() .

Une fois que vous avez un Dataset objet, vous pouvez le transformer en un nouveau Dataset en enchaînant les appels de méthode sur l' tf.data.Dataset objet. Par exemple, vous pouvez appliquer par élément des transformations telles que Dataset.map() et transformations multi-éléments tels que Dataset.batch() . Consultez la documentation de tf.data.Dataset pour une liste complète des transformations.

Le Dataset objet est un Python itérable. Cela permet de consommer ses éléments à l'aide d'une boucle for :

dataset = tf.data.Dataset.from_tensor_slices([8, 3, 0, 8, 2, 1])
dataset
<TensorSliceDataset shapes: (), types: tf.int32>
for elem in dataset:
  print(elem.numpy())
8
3
0
8
2
1

Ou en créant explicitement un Python en utilisant iterator iter et consommer ses éléments à l' aide next :

it = iter(dataset)

print(next(it).numpy())
8

Sinon, l' ensemble de données éléments peuvent être consommés à l' aide de la reduce la transformation, ce qui réduit tous les éléments pour produire un seul résultat. L'exemple suivant illustre l'utilisation de la reduce de transformation pour calculer la somme d'un ensemble de données de nombres entiers.

print(dataset.reduce(0, lambda state, value: state + value).numpy())
22

Structure du jeu de données

Un ensemble de données produit une séquence d'éléments, où chaque élément est la même structure (imbriqués) de composants. Les composants individuels de la structure peuvent être de tout type représentable par tf.TypeSpec , y compris tf.Tensor , tf.sparse.SparseTensor , tf.RaggedTensor , tf.TensorArray ou tf.data.Dataset .

Les constructions python qui peuvent être utilisés pour exprimer la structure (imbriquée) d'éléments comprennent tuple , dict , NamedTuple et OrderedDict . En particulier, la list n'est pas une construction valide pour exprimer la structure de l' ensemble de données éléments. En effet , les premiers utilisateurs de tf.data étaient très sensibles list des entrées (par exemple passé à tf.data.Dataset.from_tensors ) étant automatiquement emballés comme tenseurs et list des sorties (par exemple des valeurs de retour des fonctions définies par l' utilisateur) étant sous la contrainte dans un tuple . Par conséquent, si vous souhaitez une list d' entrée à traiter comme une structure, vous devez le convertir en tuple et si vous souhaitez une list de sortie pour être un composant unique, alors vous devez emballer explicitement à l'aide tf.stack .

La Dataset.element_spec propriété vous permet d'inspecter le type de chaque composant de l' élément. La propriété renvoie une structure imbriquée de tf.TypeSpec objets, correspondant à la structure de l'élément, qui peut être un composant unique, un tuple de composants, ou un tuple imbriqué de composants. Par exemple:

dataset1 = tf.data.Dataset.from_tensor_slices(tf.random.uniform([4, 10]))

dataset1.element_spec
TensorSpec(shape=(10,), dtype=tf.float32, name=None)
dataset2 = tf.data.Dataset.from_tensor_slices(
   (tf.random.uniform([4]),
    tf.random.uniform([4, 100], maxval=100, dtype=tf.int32)))

dataset2.element_spec
(TensorSpec(shape=(), dtype=tf.float32, name=None),
 TensorSpec(shape=(100,), dtype=tf.int32, name=None))
dataset3 = tf.data.Dataset.zip((dataset1, dataset2))

dataset3.element_spec
(TensorSpec(shape=(10,), dtype=tf.float32, name=None),
 (TensorSpec(shape=(), dtype=tf.float32, name=None),
  TensorSpec(shape=(100,), dtype=tf.int32, name=None)))
# Dataset containing a sparse tensor.
dataset4 = tf.data.Dataset.from_tensors(tf.SparseTensor(indices=[[0, 0], [1, 2]], values=[1, 2], dense_shape=[3, 4]))

dataset4.element_spec
SparseTensorSpec(TensorShape([3, 4]), tf.int32)
# Use value_type to see the type of value represented by the element spec
dataset4.element_spec.value_type
tensorflow.python.framework.sparse_tensor.SparseTensor

Les Dataset de Dataset transformations prennent en charge les jeux de données de toute structure. Lors de l' utilisation du Dataset.map() , et Dataset.filter() transformations, qui appliquent une fonction à chaque élément, la structure d'élément détermine les arguments de la fonction:

dataset1 = tf.data.Dataset.from_tensor_slices(
    tf.random.uniform([4, 10], minval=1, maxval=10, dtype=tf.int32))

dataset1
<TensorSliceDataset shapes: (10,), types: tf.int32>
for z in dataset1:
  print(z.numpy())
[1 9 8 8 1 8 6 9 6 2]
[6 6 2 5 7 2 8 6 6 2]
[7 6 5 1 5 8 6 1 6 9]
[1 5 1 4 3 6 4 1 1 3]
dataset2 = tf.data.Dataset.from_tensor_slices(
   (tf.random.uniform([4]),
    tf.random.uniform([4, 100], maxval=100, dtype=tf.int32)))

dataset2
<TensorSliceDataset shapes: ((), (100,)), types: (tf.float32, tf.int32)>
dataset3 = tf.data.Dataset.zip((dataset1, dataset2))

dataset3
<ZipDataset shapes: ((10,), ((), (100,))), types: (tf.int32, (tf.float32, tf.int32))>
for a, (b,c) in dataset3:
  print('shapes: {a.shape}, {b.shape}, {c.shape}'.format(a=a, b=b, c=c))
shapes: (10,), (), (100,)
shapes: (10,), (), (100,)
shapes: (10,), (), (100,)
shapes: (10,), (), (100,)

Lecture des données d'entrée

Consommer des tableaux NumPy

Voir Chargement des tableaux numpy pour plus d' exemples.

Si toutes vos données d' entrée unique dans la mémoire, la façon la plus simple de créer un Dataset d'eux est de les convertir en tf.Tensor objets et utilisation Dataset.from_tensor_slices() .

train, test = tf.keras.datasets.fashion_mnist.load_data()
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-labels-idx1-ubyte.gz
32768/29515 [=================================] - 0s 0us/step
40960/29515 [=========================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-images-idx3-ubyte.gz
26427392/26421880 [==============================] - 0s 0us/step
26435584/26421880 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz
16384/5148 [===============================================================================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-images-idx3-ubyte.gz
4423680/4422102 [==============================] - 0s 0us/step
4431872/4422102 [==============================] - 0s 0us/step
images, labels = train
images = images/255

dataset = tf.data.Dataset.from_tensor_slices((images, labels))
dataset
<TensorSliceDataset shapes: ((28, 28), ()), types: (tf.float64, tf.uint8)>

Consommer des générateurs Python

Une autre source de données communes qui peuvent facilement être ingérée comme tf.data.Dataset est le générateur de python.

def count(stop):
  i = 0
  while i<stop:
    yield i
    i += 1
for n in count(5):
  print(n)
0
1
2
3
4

Le Dataset.from_generator constructeur convertit le générateur de python pour un entièrement fonctionnel tf.data.Dataset .

Le constructeur prend un callable en entrée, pas un itérateur. Cela lui permet de redémarrer le générateur lorsqu'il atteint la fin. Il faut une option args argument, qui est passé comme les arguments du appelables.

Le output_types argument est nécessaire parce que tf.data construit un tf.Graph interne, et les bords du graphique nécessitent une tf.dtype .

ds_counter = tf.data.Dataset.from_generator(count, args=[25], output_types=tf.int32, output_shapes = (), )
for count_batch in ds_counter.repeat().batch(10).take(10):
  print(count_batch.numpy())
[0 1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18 19]
[20 21 22 23 24  0  1  2  3  4]
[ 5  6  7  8  9 10 11 12 13 14]
[15 16 17 18 19 20 21 22 23 24]
[0 1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18 19]
[20 21 22 23 24  0  1  2  3  4]
[ 5  6  7  8  9 10 11 12 13 14]
[15 16 17 18 19 20 21 22 23 24]

Le output_shapes argument n'est pas nécessaire , mais est fortement recommandé que de nombreuses opérations de tensorflow ne prennent pas en charge tenseurs de rang inconnu. Si la longueur d'un axe particulier est inconnue ou variable, définir comme None dans les output_shapes .

Il est également important de noter que les output_shapes et output_types suivent les mêmes règles que les autres méthodes de nidification ensemble de données.

Voici un exemple de générateur qui illustre les deux aspects, il renvoie des tuples de tableaux, où le deuxième tableau est un vecteur de longueur inconnue.

def gen_series():
  i = 0
  while True:
    size = np.random.randint(0, 10)
    yield i, np.random.normal(size=(size,))
    i += 1
for i, series in gen_series():
  print(i, ":", str(series))
  if i > 5:
    break
0 : [ 0.814   0.7245 -0.8296  0.106   2.6089  0.799 ]
1 : [0.6854]
2 : [-0.1255 -0.5735]
3 : [ 0.1669 -1.1696  0.0912 -0.4514  2.5346 -0.7017 -1.1124]
4 : [-0.9659]
5 : [-0.5551 -0.8024  0.264  -0.5541 -0.6733  1.6715  0.4508 -0.7317 -2.3218]
6 : [ 0.4648 -0.0541 -0.8733 -1.4034  0.29    0.669   1.2556]

La première sortie est un int32 le second est un float32 .

Le premier élément est un scalaire, la forme () , et le second est un vecteur de longueur inconnue, la forme (None,)

ds_series = tf.data.Dataset.from_generator(
    gen_series, 
    output_types=(tf.int32, tf.float32), 
    output_shapes=((), (None,)))

ds_series
<FlatMapDataset shapes: ((), (None,)), types: (tf.int32, tf.float32)>

Maintenant , il peut être utilisé comme un régulier tf.data.Dataset . Notez que lorsque Batching un ensemble de données avec une forme variable, vous devez utiliser Dataset.padded_batch .

ds_series_batch = ds_series.shuffle(20).padded_batch(10)

ids, sequence_batch = next(iter(ds_series_batch))
print(ids.numpy())
print()
print(sequence_batch.numpy())
[16  4  6  8 23  1 10  2 15 13]

[[ 0.3357 -0.3727  0.7409 -0.8574 -0.2802  0.2744 -0.2841 -1.3277]
 [ 0.7422  0.251   1.1745 -0.2416  0.7332  0.8136 -1.7633  0.    ]
 [ 0.3777 -2.2714  0.      0.      0.      0.      0.      0.    ]
 [ 0.7962 -0.3259 -0.9036  0.7381  0.      0.      0.      0.    ]
 [ 0.3902 -1.2637 -0.6834  0.5808 -0.3917  0.      0.      0.    ]
 [-1.32    0.      0.      0.      0.      0.      0.      0.    ]
 [ 1.9602  0.4305 -0.1827  0.0427 -0.7381 -0.0948 -0.0964  0.1041]
 [ 1.2071  0.0665 -0.4292  0.      0.      0.      0.      0.    ]
 [-0.7277  1.3547 -1.0543  1.9235  0.1442 -1.0691  0.2263  0.    ]
 [ 1.2949  1.5027  0.5522  0.083   0.665  -0.9897  0.0659  0.    ]]

Pour un exemple plus réaliste, essayez d' emballage preprocessing.image.ImageDataGenerator comme tf.data.Dataset .

Téléchargez d'abord les données :

flowers = tf.keras.utils.get_file(
    'flower_photos',
    'https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',
    untar=True)
Downloading data from https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz
228818944/228813984 [==============================] - 10s 0us/step
228827136/228813984 [==============================] - 10s 0us/step

Créer le image.ImageDataGenerator

img_gen = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1./255, rotation_range=20)
images, labels = next(img_gen.flow_from_directory(flowers))
Found 3670 images belonging to 5 classes.
print(images.dtype, images.shape)
print(labels.dtype, labels.shape)
float32 (32, 256, 256, 3)
float32 (32, 5)
ds = tf.data.Dataset.from_generator(
    lambda: img_gen.flow_from_directory(flowers), 
    output_types=(tf.float32, tf.float32), 
    output_shapes=([32,256,256,3], [32,5])
)

ds.element_spec
(TensorSpec(shape=(32, 256, 256, 3), dtype=tf.float32, name=None),
 TensorSpec(shape=(32, 5), dtype=tf.float32, name=None))
for images, label in ds.take(1):
  print('images.shape: ', images.shape)
  print('labels.shape: ', labels.shape)
Found 3670 images belonging to 5 classes.
images.shape:  (32, 256, 256, 3)
labels.shape:  (32, 5)

Consommation des données TFRecord

Voir TFRecords Chargement pour un exemple de bout en bout.

L' tf.data API prend en charge une variété de formats de fichiers afin que vous puissiez traiter grands ensembles de données qui ne correspondent pas à la mémoire. Par exemple, le format de fichier TFRecord est un format binaire simple orienté enregistrement que de nombreuses applications TensorFlow utilisent pour les données d'entraînement. La tf.data.TFRecordDataset classe vous permet de diffuser sur le contenu d'un ou plusieurs fichiers TFRecord dans le cadre d'un pipeline d'entrée.

Voici un exemple utilisant le fichier test de la Signalisation des Rues Françaises (FSNS).

# Creates a dataset that reads all of the examples from two files.
fsns_test_file = tf.keras.utils.get_file("fsns.tfrec", "https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001")
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001
7905280/7904079 [==============================] - 1s 0us/step
7913472/7904079 [==============================] - 1s 0us/step

Le filenames de TFRecordDataset tf.Tensor filenames argument à la TFRecordDataset initialiseur peut être soit une chaîne, une liste de chaînes, ou une tf.Tensor de chaînes. Par conséquent, si vous disposez de deux ensembles de fichiers à des fins d'entraînement et de validation, vous pouvez créer une méthode d'usine qui produit l'ensemble de données, en prenant les noms de fichiers comme argument d'entrée :

dataset = tf.data.TFRecordDataset(filenames = [fsns_test_file])
dataset
<TFRecordDatasetV2 shapes: (), types: tf.string>

De nombreux projets de tensorflow utilisent un numéro de série tf.train.Example enregistrements dans leurs fichiers TFRecord. Ceux-ci doivent être décodés avant de pouvoir être inspectés :

raw_example = next(iter(dataset))
parsed = tf.train.Example.FromString(raw_example.numpy())

parsed.features.feature['image/text']
bytes_list {
  value: "Rue Perreyon"
}

Consommation de données textuelles

Voir texte de chargement pour une fin à l' exemple de fin.

De nombreux ensembles de données sont distribués sous forme d'un ou plusieurs fichiers texte. Le tf.data.TextLineDataset fournit un moyen facile d'extraire des lignes d'un ou plusieurs fichiers texte. Étant donné un ou plusieurs noms de fichiers, un TextLineDataset produira un élément de type chaîne par ligne de ces fichiers.

directory_url = 'https://storage.googleapis.com/download.tensorflow.org/data/illiad/'
file_names = ['cowper.txt', 'derby.txt', 'butler.txt']

file_paths = [
    tf.keras.utils.get_file(file_name, directory_url + file_name)
    for file_name in file_names
]
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/cowper.txt
819200/815980 [==============================] - 0s 0us/step
827392/815980 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/derby.txt
811008/809730 [==============================] - 0s 0us/step
819200/809730 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/illiad/butler.txt
811008/807992 [==============================] - 0s 0us/step
819200/807992 [==============================] - 0s 0us/step
dataset = tf.data.TextLineDataset(file_paths)

Voici les premières lignes du premier fichier :

for line in dataset.take(5):
  print(line.numpy())
b"\xef\xbb\xbfAchilles sing, O Goddess! Peleus' son;"
b'His wrath pernicious, who ten thousand woes'
b"Caused to Achaia's host, sent many a soul"
b'Illustrious into Ades premature,'
b'And Heroes gave (so stood the will of Jove)'

Pour des lignes alternées entre les fichiers utiliser Dataset.interleave . Cela facilite le brassage des fichiers ensemble. Voici les première, deuxième et troisième lignes de chaque traduction :

files_ds = tf.data.Dataset.from_tensor_slices(file_paths)
lines_ds = files_ds.interleave(tf.data.TextLineDataset, cycle_length=3)

for i, line in enumerate(lines_ds.take(9)):
  if i % 3 == 0:
    print()
  print(line.numpy())
b"\xef\xbb\xbfAchilles sing, O Goddess! Peleus' son;"
b"\xef\xbb\xbfOf Peleus' son, Achilles, sing, O Muse,"
b'\xef\xbb\xbfSing, O goddess, the anger of Achilles son of Peleus, that brought'

b'His wrath pernicious, who ten thousand woes'
b'The vengeance, deep and deadly; whence to Greece'
b'countless ills upon the Achaeans. Many a brave soul did it send'

b"Caused to Achaia's host, sent many a soul"
b'Unnumbered ills arose; which many a soul'
b'hurrying down to Hades, and many a hero did it yield a prey to dogs and'

Par défaut, un TextLineDataset donne chaque ligne de chaque fichier, qui ne peut être souhaitable, par exemple, si le fichier commence par une ligne d' en- tête, ou contient des commentaires. Ces lignes peuvent être supprimées à l' aide des Dataset.skip() ou Dataset.filter() transformations. Ici, vous sautez la première ligne, puis filtrez pour ne trouver que les survivants.

titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
titanic_lines = tf.data.TextLineDataset(titanic_file)
for line in titanic_lines.take(10):
  print(line.numpy())
b'survived,sex,age,n_siblings_spouses,parch,fare,class,deck,embark_town,alone'
b'0,male,22.0,1,0,7.25,Third,unknown,Southampton,n'
b'1,female,38.0,1,0,71.2833,First,C,Cherbourg,n'
b'1,female,26.0,0,0,7.925,Third,unknown,Southampton,y'
b'1,female,35.0,1,0,53.1,First,C,Southampton,n'
b'0,male,28.0,0,0,8.4583,Third,unknown,Queenstown,y'
b'0,male,2.0,3,1,21.075,Third,unknown,Southampton,n'
b'1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n'
b'1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n'
b'1,female,4.0,1,1,16.7,Third,G,Southampton,n'
def survived(line):
  return tf.not_equal(tf.strings.substr(line, 0, 1), "0")

survivors = titanic_lines.skip(1).filter(survived)
for line in survivors.take(10):
  print(line.numpy())
b'1,female,38.0,1,0,71.2833,First,C,Cherbourg,n'
b'1,female,26.0,0,0,7.925,Third,unknown,Southampton,y'
b'1,female,35.0,1,0,53.1,First,C,Southampton,n'
b'1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n'
b'1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n'
b'1,female,4.0,1,1,16.7,Third,G,Southampton,n'
b'1,male,28.0,0,0,13.0,Second,unknown,Southampton,y'
b'1,female,28.0,0,0,7.225,Third,unknown,Cherbourg,y'
b'1,male,28.0,0,0,35.5,First,A,Southampton,y'
b'1,female,38.0,1,5,31.3875,Third,unknown,Southampton,n'

Consommation de données CSV

Voir Chargement des fichiers CSV , et Loading Pandas DataFrames pour plus d' exemples.

Le format de fichier CSV est un format populaire pour stocker des données tabulaires en texte brut.

Par exemple:

titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
df = pd.read_csv(titanic_file)
df.head()

Si vos crises de données en mémoire les mêmes Dataset.from_tensor_slices méthode fonctionne sur les dictionnaires, permettant que ces données soient facilement importées:

titanic_slices = tf.data.Dataset.from_tensor_slices(dict(df))

for feature_batch in titanic_slices.take(1):
  for key, value in feature_batch.items():
    print("  {!r:20s}: {}".format(key, value))
'survived'          : 0
  'sex'               : b'male'
  'age'               : 22.0
  'n_siblings_spouses': 1
  'parch'             : 0
  'fare'              : 7.25
  'class'             : b'Third'
  'deck'              : b'unknown'
  'embark_town'       : b'Southampton'
  'alone'             : b'n'

Une approche plus évolutive consiste à charger à partir du disque si nécessaire.

Le tf.data module fournit des méthodes pour extraire les enregistrements d'un ou plusieurs fichiers CSV qui sont conformes à la RFC 4180 .

La experimental.make_csv_dataset fonction est l'interface de haut niveau pour la lecture des ensembles de fichiers csv. Il prend en charge l'inférence de type de colonne et de nombreuses autres fonctionnalités, telles que le traitement par lots et la lecture aléatoire, pour simplifier l'utilisation.

titanic_batches = tf.data.experimental.make_csv_dataset(
    titanic_file, batch_size=4,
    label_name="survived")
for feature_batch, label_batch in titanic_batches.take(1):
  print("'survived': {}".format(label_batch))
  print("features:")
  for key, value in feature_batch.items():
    print("  {!r:20s}: {}".format(key, value))
'survived': [1 0 0 1]
features:
  'sex'               : [b'male' b'male' b'male' b'female']
  'age'               : [17. 18. 28.  5.]
  'n_siblings_spouses': [0 1 0 0]
  'parch'             : [2 1 0 0]
  'fare'              : [110.8833   7.8542   7.725   12.475 ]
  'class'             : [b'First' b'Third' b'Third' b'Third']
  'deck'              : [b'C' b'unknown' b'unknown' b'unknown']
  'embark_town'       : [b'Cherbourg' b'Southampton' b'Queenstown' b'Southampton']
  'alone'             : [b'n' b'n' b'y' b'y']

Vous pouvez utiliser le select_columns argument si vous avez seulement besoin d' un sous - ensemble de colonnes.

titanic_batches = tf.data.experimental.make_csv_dataset(
    titanic_file, batch_size=4,
    label_name="survived", select_columns=['class', 'fare', 'survived'])
for feature_batch, label_batch in titanic_batches.take(1):
  print("'survived': {}".format(label_batch))
  for key, value in feature_batch.items():
    print("  {!r:20s}: {}".format(key, value))
'survived': [0 1 0 0]
  'fare'              : [ 7.25   19.2583  7.8542  7.8958]
  'class'             : [b'Third' b'Third' b'Third' b'Third']

Il y a aussi un niveau inférieur experimental.CsvDataset classe qui offre un contrôle plus précis à grain fin. Il ne prend pas en charge l'inférence de type de colonne. Au lieu de cela, vous devez spécifier le type de chaque colonne.

titanic_types  = [tf.int32, tf.string, tf.float32, tf.int32, tf.int32, tf.float32, tf.string, tf.string, tf.string, tf.string] 
dataset = tf.data.experimental.CsvDataset(titanic_file, titanic_types , header=True)

for line in dataset.take(10):
  print([item.numpy() for item in line])
[0, b'male', 22.0, 1, 0, 7.25, b'Third', b'unknown', b'Southampton', b'n']
[1, b'female', 38.0, 1, 0, 71.2833, b'First', b'C', b'Cherbourg', b'n']
[1, b'female', 26.0, 0, 0, 7.925, b'Third', b'unknown', b'Southampton', b'y']
[1, b'female', 35.0, 1, 0, 53.1, b'First', b'C', b'Southampton', b'n']
[0, b'male', 28.0, 0, 0, 8.4583, b'Third', b'unknown', b'Queenstown', b'y']
[0, b'male', 2.0, 3, 1, 21.075, b'Third', b'unknown', b'Southampton', b'n']
[1, b'female', 27.0, 0, 2, 11.1333, b'Third', b'unknown', b'Southampton', b'n']
[1, b'female', 14.0, 1, 0, 30.0708, b'Second', b'unknown', b'Cherbourg', b'n']
[1, b'female', 4.0, 1, 1, 16.7, b'Third', b'G', b'Southampton', b'n']
[0, b'male', 20.0, 0, 0, 8.05, b'Third', b'unknown', b'Southampton', b'y']

Si certaines colonnes sont vides, cette interface de bas niveau vous permet de fournir des valeurs par défaut au lieu des types de colonnes.

%%writefile missing.csv
1,2,3,4
,2,3,4
1,,3,4
1,2,,4
1,2,3,
,,,
Writing missing.csv
# Creates a dataset that reads all of the records from two CSV files, each with
# four float columns which may have missing values.

record_defaults = [999,999,999,999]
dataset = tf.data.experimental.CsvDataset("missing.csv", record_defaults)
dataset = dataset.map(lambda *items: tf.stack(items))
dataset
<MapDataset shapes: (4,), types: tf.int32>
for line in dataset:
  print(line.numpy())
[1 2 3 4]
[999   2   3   4]
[  1 999   3   4]
[  1   2 999   4]
[  1   2   3 999]
[999 999 999 999]

Par défaut, un CsvDataset donne chaque colonne de chaque ligne du fichier, qui ne peut être souhaitable, par exemple si le fichier commence par une ligne d' en- tête qui doit être ignorée, ou si certaines colonnes ne sont pas nécessaires dans l'entrée. Ces lignes et les champs peuvent être enlevés avec les en- header et select_cols arguments respectivement.

# Creates a dataset that reads all of the records from two CSV files with
# headers, extracting float data from columns 2 and 4.
record_defaults = [999, 999] # Only provide defaults for the selected columns
dataset = tf.data.experimental.CsvDataset("missing.csv", record_defaults, select_cols=[1, 3])
dataset = dataset.map(lambda *items: tf.stack(items))
dataset
<MapDataset shapes: (2,), types: tf.int32>
for line in dataset:
  print(line.numpy())
[2 4]
[2 4]
[999   4]
[2 4]
[  2 999]
[999 999]

Consommer des ensembles de fichiers

Il existe de nombreux ensembles de données distribués sous forme d'ensemble de fichiers, chaque fichier étant un exemple.

flowers_root = tf.keras.utils.get_file(
    'flower_photos',
    'https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',
    untar=True)
flowers_root = pathlib.Path(flowers_root)

Le répertoire racine contient un répertoire pour chaque classe :

for item in flowers_root.glob("*"):
  print(item.name)
sunflowers
daisy
LICENSE.txt
roses
tulips
dandelion

Les fichiers de chaque répertoire de classe sont des exemples :

list_ds = tf.data.Dataset.list_files(str(flowers_root/'*/*'))

for f in list_ds.take(5):
  print(f.numpy())
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/18687587599_3dd4fdf255.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/16863587471_cc3a6ffb29_m.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/sunflowers/9431890901_cd11bda584_n.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/dandelion/3562861685_8b8d747b4d.jpg'
b'/home/kbuilder/.keras/datasets/flower_photos/tulips/14087326141_1906d5a373_n.jpg'

Lire les données en utilisant la tf.io.read_file fonction et extraire l'étiquette de la trajectoire, retournant (image, label) paires:

def process_path(file_path):
  label = tf.strings.split(file_path, os.sep)[-2]
  return tf.io.read_file(file_path), label

labeled_ds = list_ds.map(process_path)
for image_raw, label_text in labeled_ds.take(1):
  print(repr(image_raw.numpy()[:100]))
  print()
  print(label_text.numpy())
b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00H\x00H\x00\x00\xff\xed\x00\x9aPhotoshop 3.0\x008BIM\x04\x04\x00\x00\x00\x00\x00b\x1c\x01Z\x00\x03\x1b%G\x1c\x02\x00\x00\x02\x00\x02\x1c\x027\x00\x0820100808\x1c\x02t\x006                 '

b'sunflowers'

Regroupement des éléments du jeu de données

Dosage simple

La forme la plus simple de piles dosage n éléments consécutifs d'un ensemble de données en un seul élément. Le Dataset.batch() transformation fait exactement cela, avec les mêmes contraintes que l' tf.stack() opérateur, appliqué à chaque composante des éléments: à savoir pour chaque composant i, tous les éléments doivent avoir un tenseur de la même forme exacte.

inc_dataset = tf.data.Dataset.range(100)
dec_dataset = tf.data.Dataset.range(0, -100, -1)
dataset = tf.data.Dataset.zip((inc_dataset, dec_dataset))
batched_dataset = dataset.batch(4)

for batch in batched_dataset.take(4):
  print([arr.numpy() for arr in batch])
[array([0, 1, 2, 3]), array([ 0, -1, -2, -3])]
[array([4, 5, 6, 7]), array([-4, -5, -6, -7])]
[array([ 8,  9, 10, 11]), array([ -8,  -9, -10, -11])]
[array([12, 13, 14, 15]), array([-12, -13, -14, -15])]

Alors que tf.data essaie de propager des informations de forme, les paramètres par défaut de Dataset.batch résultat dans une taille de lot inconnue parce que le dernier lot ne peut pas être complète. Notez les None s dans la forme:

batched_dataset
<BatchDataset shapes: ((None,), (None,)), types: (tf.int64, tf.int64)>

Utilisez le drop_remainder argument pour ignorer ce dernier lot, et obtenir la propagation pleine forme:

batched_dataset = dataset.batch(7, drop_remainder=True)
batched_dataset
<BatchDataset shapes: ((7,), (7,)), types: (tf.int64, tf.int64)>

Tenseurs de dosage avec rembourrage

La recette ci-dessus fonctionne pour les tenseurs qui ont tous la même taille. Cependant, de nombreux modèles (par exemple les modèles de séquence) fonctionnent avec des données d'entrée qui peuvent avoir des tailles variables (par exemple des séquences de différentes longueurs). Pour traiter ce cas, la Dataset.padded_batch transformation vous permet de tenseurs de lots de différentes formes en spécifiant une ou plusieurs dimensions dans lesquelles ils peuvent être rembourrés.

dataset = tf.data.Dataset.range(100)
dataset = dataset.map(lambda x: tf.fill([tf.cast(x, tf.int32)], x))
dataset = dataset.padded_batch(4, padded_shapes=(None,))

for batch in dataset.take(2):
  print(batch.numpy())
  print()
[[0 0 0]
 [1 0 0]
 [2 2 0]
 [3 3 3]]

[[4 4 4 4 0 0 0]
 [5 5 5 5 5 0 0]
 [6 6 6 6 6 6 0]
 [7 7 7 7 7 7 7]]

La Dataset.padded_batch transformation permet de définir différents rembourrage pour chaque dimension de chaque composant, et il peut être de longueur variable (signifié par None dans l'exemple ci - dessus) ou de longueur constante. Il est également possible de remplacer la valeur de remplissage, qui par défaut est 0.

Workflows de formation

Traitement de plusieurs époques

Les tf.data API offre deux façons de traiter plusieurs époques des mêmes données.

Le plus simple pour effectuer une itération sur un ensemble de données dans de multiples époques est d'utiliser le Dataset.repeat() transformation. Tout d'abord, créez un ensemble de données titanesques :

titanic_file = tf.keras.utils.get_file("train.csv", "https://storage.googleapis.com/tf-datasets/titanic/train.csv")
titanic_lines = tf.data.TextLineDataset(titanic_file)
def plot_batch_sizes(ds):
  batch_sizes = [batch.shape[0] for batch in ds]
  plt.bar(range(len(batch_sizes)), batch_sizes)
  plt.xlabel('Batch number')
  plt.ylabel('Batch size')

L' application de la Dataset.repeat() transformation sans argument se répète indéfiniment l'entrée.

La Dataset.repeat transformation concaténer ses arguments sans signaler la fin d'une époque et le début de la prochaine époque. Pour cette raison un Dataset.batch appliqué après Dataset.repeat donnera des lots que les limites d'époque enjambeur:

titanic_batches = titanic_lines.repeat(3).batch(128)
plot_batch_sizes(titanic_batches)

png

Si vous avez besoin d' une séparation claire époque, mis Dataset.batch avant la répétition:

titanic_batches = titanic_lines.batch(128).repeat(3)

plot_batch_sizes(titanic_batches)

png

Si vous souhaitez effectuer un calcul personnalisé (par exemple pour collecter des statistiques) à la fin de chaque époque, il est plus simple de redémarrer l'itération de l'ensemble de données à chaque époque :

epochs = 3
dataset = titanic_lines.batch(128)

for epoch in range(epochs):
  for batch in dataset:
    print(batch.shape)
  print("End of epoch: ", epoch)
(128,)
(128,)
(128,)
(128,)
(116,)
End of epoch:  0
(128,)
(128,)
(128,)
(128,)
(116,)
End of epoch:  1
(128,)
(128,)
(128,)
(128,)
(116,)
End of epoch:  2

Mélanger aléatoirement les données d'entrée

Le Dataset.shuffle() transformation maintient un tampon de taille fixe et choisit l'élément suivant uniformément au hasard à partir de ce tampon.

Ajoutez un index à l'ensemble de données pour voir l'effet :

lines = tf.data.TextLineDataset(titanic_file)
counter = tf.data.experimental.Counter()

dataset = tf.data.Dataset.zip((counter, lines))
dataset = dataset.shuffle(buffer_size=100)
dataset = dataset.batch(20)
dataset
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/data/experimental/ops/counter.py:66: scan (from tensorflow.python.data.experimental.ops.scan_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.data.Dataset.scan(...) instead
<BatchDataset shapes: ((None,), (None,)), types: (tf.int64, tf.string)>

Etant donné que le buffer_size est de 100, et la taille du lot est de 20, le premier lot ne contient pas d' éléments ayant un indice de plus de 120.

n,line_batch = next(iter(dataset))
print(n.numpy())
[ 79   8  60  24  11  56  65  58  67 108  59  81  92  70  94  57  27  73
  17  75]

Comme avec Dataset.batch l'ordre par rapport à Dataset.repeat questions.

Dataset.shuffle ne signale pas la fin d'une époque jusqu'à ce que la mémoire tampon de lecture aléatoire est vide. Ainsi, un shuffle placé avant une répétition affichera chaque élément d'une époque avant de passer à la suivante :

dataset = tf.data.Dataset.zip((counter, lines))
shuffled = dataset.shuffle(buffer_size=100).batch(10).repeat(2)

print("Here are the item ID's near the epoch boundary:\n")
for n, line_batch in shuffled.skip(60).take(5):
  print(n.numpy())
Here are the item ID's near the epoch boundary:

[518 434 421 561 588 600 523 616 404 557]
[524 595 532 411 500 621 558 544 625 620]
[546 297 599 597 465 591 581 374]
[15  4  3  8 49 37 48 42 23 68]
[55 11 36 34 75 26 32 77 94 91]
shuffle_repeat = [n.numpy().mean() for n, line_batch in shuffled]
plt.plot(shuffle_repeat, label="shuffle().repeat()")
plt.ylabel("Mean item ID")
plt.legend()
<matplotlib.legend.Legend at 0x7fbce04a2d10>

png

Mais une répétition avant un shuffle mélange les limites de l'époque :

dataset = tf.data.Dataset.zip((counter, lines))
shuffled = dataset.repeat(2).shuffle(buffer_size=100).batch(10)

print("Here are the item ID's near the epoch boundary:\n")
for n, line_batch in shuffled.skip(55).take(15):
  print(n.numpy())
Here are the item ID's near the epoch boundary:

[614  15 484 606 517 618  17  16 593 218]
[620 575 604 535 496 449  23 605   2  38]
[  3 543 409 480 624  36 430 607 465 531]
[373 422 608 497   4 387 623 440 591 600]
[586 609 506  35  56  13  60 603 625  55]
[ 29  58 572 279 468  37  62  46  77 545]
[  7  68  45 622 563 528  18 499  66  84]
[ 31 568  26 358 613 393 557 511 583  89]
[81 59 96 47 92 57 93 49 67 72]
[ 90 111  27  74 565  65  98  75  80 110]
[610 339  63  69  48 104 112 114 471  61]
[102  19 113  88 611  95 117  10 118  73]
[510 379 592  40  54 571  64 503 144 509]
[101  20  41 479  30 137  34  50 374 433]
[538  51  43  24 617  86 153 140 143 124]
repeat_shuffle = [n.numpy().mean() for n, line_batch in shuffled]

plt.plot(shuffle_repeat, label="shuffle().repeat()")
plt.plot(repeat_shuffle, label="repeat().shuffle()")
plt.ylabel("Mean item ID")
plt.legend()
<matplotlib.legend.Legend at 0x7fbce04eb4d0>

png

Prétraitement des données

Le Dataset.map(f) transformation produit un nouvel ensemble de données par application d' une fonction donnée f à chaque élément de l'ensemble de données d'entrée. Il est basé sur la map() fonction qui est généralement appliquée aux listes (et d' autres structures) dans les langages de programmation fonctionnels. La fonction f prend les tf.Tensor objets qui représentent un seul élément dans l'entrée, et renvoie le tf.Tensor objets qui représentent un seul élément dans le nouvel ensemble de données. Sa mise en œuvre utilise des opérations TensorFlow standard pour transformer un élément en un autre.

Cette section traite des exemples courants de l'utilisation Dataset.map() .

Décodage des données d'image et redimensionnement

Lors de l'apprentissage d'un réseau de neurones sur des données d'images du monde réel, il est souvent nécessaire de convertir des images de différentes tailles en une taille commune, afin qu'elles puissent être regroupées en une taille fixe.

Reconstruisez l'ensemble de données des noms de fichiers Flower :

list_ds = tf.data.Dataset.list_files(str(flowers_root/'*/*'))

Écrivez une fonction qui manipule les éléments de l'ensemble de données.

# Reads an image from a file, decodes it into a dense tensor, and resizes it
# to a fixed shape.
def parse_image(filename):
  parts = tf.strings.split(filename, os.sep)
  label = parts[-2]

  image = tf.io.read_file(filename)
  image = tf.image.decode_jpeg(image)
  image = tf.image.convert_image_dtype(image, tf.float32)
  image = tf.image.resize(image, [128, 128])
  return image, label

Testez que cela fonctionne.

file_path = next(iter(list_ds))
image, label = parse_image(file_path)

def show(image, label):
  plt.figure()
  plt.imshow(image)
  plt.title(label.numpy().decode('utf-8'))
  plt.axis('off')

show(image, label)

png

Mappez-le sur l'ensemble de données.

images_ds = list_ds.map(parse_image)

for image, label in images_ds.take(2):
  show(image, label)

png

png

Appliquer une logique Python arbitraire

Pour des raisons de performances, utilisez les opérations TensorFlow pour prétraiter vos données dans la mesure du possible. Cependant, il est parfois utile d'appeler des bibliothèques Python externes lors de l'analyse de vos données d'entrée. Vous pouvez utiliser le tf.py_function() opération dans un Dataset.map() transformation.

Par exemple, si vous souhaitez appliquer une rotation aléatoire, le tf.image module a seulement tf.image.rot90 , ce qui est très utile pour l' augmentation de l' image.

Pour démontrer tf.py_function , essayez d' utiliser la scipy.ndimage.rotate fonction à la place:

import scipy.ndimage as ndimage

def random_rotate_image(image):
  image = ndimage.rotate(image, np.random.uniform(-30, 30), reshape=False)
  return image
image, label = next(iter(images_ds))
image = random_rotate_image(image)
show(image, label)
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

png

Pour utiliser cette fonction avec Dataset.map les mêmes mises en garde s'appliquent avec Dataset.from_generator , vous devez décrire les formes de retour et types lorsque vous appliquez la fonction:

def tf_random_rotate_image(image, label):
  im_shape = image.shape
  [image,] = tf.py_function(random_rotate_image, [image], [tf.float32])
  image.set_shape(im_shape)
  return image, label
rot_ds = images_ds.map(tf_random_rotate_image)

for image, label in rot_ds.take(2):
  show(image, label)
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).
Clipping input data to the valid range for imshow with RGB data ([0..1] for floats or [0..255] for integers).

png

png

Parsing tf.Example messages de tampon de protocole

De nombreux pipelines d'entrée extraire tf.train.Example messages tampons de protocole d'un format TFRecord. Chaque tf.train.Example enregistrement contient un ou plusieurs « caractéristiques », et le pipeline d'entrée convertit généralement ces caractéristiques en tenseurs.

fsns_test_file = tf.keras.utils.get_file("fsns.tfrec", "https://storage.googleapis.com/download.tensorflow.org/data/fsns-20160927/testdata/fsns-00000-of-00001")
dataset = tf.data.TFRecordDataset(filenames = [fsns_test_file])
dataset
<TFRecordDatasetV2 shapes: (), types: tf.string>

Vous pouvez travailler avec tf.train.Example de l'extérieur d'un tf.data.Dataset pour comprendre les données:

raw_example = next(iter(dataset))
parsed = tf.train.Example.FromString(raw_example.numpy())

feature = parsed.features.feature
raw_img = feature['image/encoded'].bytes_list.value[0]
img = tf.image.decode_png(raw_img)
plt.imshow(img)
plt.axis('off')
_ = plt.title(feature["image/text"].bytes_list.value[0])

png

raw_example = next(iter(dataset))
def tf_parse(eg):
  example = tf.io.parse_example(
      eg[tf.newaxis], {
          'image/encoded': tf.io.FixedLenFeature(shape=(), dtype=tf.string),
          'image/text': tf.io.FixedLenFeature(shape=(), dtype=tf.string)
      })
  return example['image/encoded'][0], example['image/text'][0]
img, txt = tf_parse(raw_example)
print(txt.numpy())
print(repr(img.numpy()[:20]), "...")
b'Rue Perreyon'
b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x02X' ...
decoded = dataset.map(tf_parse)
decoded
<MapDataset shapes: ((), ()), types: (tf.string, tf.string)>
image_batch, text_batch = next(iter(decoded.batch(10)))
image_batch.shape
TensorShape([10])

Fenêtrage de séries temporelles

Pour une fin à l' exemple des séries chronologiques de fin voir: Prévision des séries chronologiques .

Les données de séries chronologiques sont souvent organisées avec l'axe temporel intact.

Utilisez simple Dataset.range pour démontrer:

range_ds = tf.data.Dataset.range(100000)

En règle générale, les modèles basés sur ce type de données voudront une tranche de temps contiguë.

L'approche la plus simple serait de regrouper les données :

En utilisant batch

batches = range_ds.batch(10, drop_remainder=True)

for batch in batches.take(5):
  print(batch.numpy())
[0 1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18 19]
[20 21 22 23 24 25 26 27 28 29]
[30 31 32 33 34 35 36 37 38 39]
[40 41 42 43 44 45 46 47 48 49]

Ou pour faire des prédictions denses un pas dans le futur, vous pouvez décaler les caractéristiques et les étiquettes d'un pas les unes par rapport aux autres :

def dense_1_step(batch):
  # Shift features and labels one step relative to each other.
  return batch[:-1], batch[1:]

predict_dense_1_step = batches.map(dense_1_step)

for features, label in predict_dense_1_step.take(3):
  print(features.numpy(), " => ", label.numpy())
[0 1 2 3 4 5 6 7 8]  =>  [1 2 3 4 5 6 7 8 9]
[10 11 12 13 14 15 16 17 18]  =>  [11 12 13 14 15 16 17 18 19]
[20 21 22 23 24 25 26 27 28]  =>  [21 22 23 24 25 26 27 28 29]

Pour prédire une fenêtre entière au lieu d'un décalage fixe, vous pouvez diviser les lots en deux parties :

batches = range_ds.batch(15, drop_remainder=True)

def label_next_5_steps(batch):
  return (batch[:-5],   # Inputs: All except the last 5 steps
          batch[-5:])   # Labels: The last 5 steps

predict_5_steps = batches.map(label_next_5_steps)

for features, label in predict_5_steps.take(3):
  print(features.numpy(), " => ", label.numpy())
[0 1 2 3 4 5 6 7 8 9]  =>  [10 11 12 13 14]
[15 16 17 18 19 20 21 22 23 24]  =>  [25 26 27 28 29]
[30 31 32 33 34 35 36 37 38 39]  =>  [40 41 42 43 44]

Pour permettre un certain chevauchement entre les caractéristiques d'un lot et les étiquettes d' une autre, utilisez Dataset.zip :

feature_length = 10
label_length = 3

features = range_ds.batch(feature_length, drop_remainder=True)
labels = range_ds.batch(feature_length).skip(1).map(lambda labels: labels[:label_length])

predicted_steps = tf.data.Dataset.zip((features, labels))

for features, label in predicted_steps.take(5):
  print(features.numpy(), " => ", label.numpy())
[0 1 2 3 4 5 6 7 8 9]  =>  [10 11 12]
[10 11 12 13 14 15 16 17 18 19]  =>  [20 21 22]
[20 21 22 23 24 25 26 27 28 29]  =>  [30 31 32]
[30 31 32 33 34 35 36 37 38 39]  =>  [40 41 42]
[40 41 42 43 44 45 46 47 48 49]  =>  [50 51 52]

En utilisant la window

Lors de l' utilisation Dataset.batch travaux, il y a des situations où vous pourriez avoir besoin un contrôle plus précis. La Dataset.window méthode vous donne un contrôle complet, mais nécessite des soins: elle retourne un Dataset de Datasets . Voir la structure Dataset pour plus de détails.

window_size = 5

windows = range_ds.window(window_size, shift=1)
for sub_ds in windows.take(5):
  print(sub_ds)
<_VariantDataset shapes: (), types: tf.int64>
<_VariantDataset shapes: (), types: tf.int64>
<_VariantDataset shapes: (), types: tf.int64>
<_VariantDataset shapes: (), types: tf.int64>
<_VariantDataset shapes: (), types: tf.int64>

La Dataset.flat_map méthode peut prendre un ensemble de données d'ensembles de données et l' aplatir en un seul jeu de données:

for x in windows.flat_map(lambda x: x).take(30):
   print(x.numpy(), end=' ')
0 1 2 3 4 1 2 3 4 5 2 3 4 5 6 3 4 5 6 7 4 5 6 7 8 5 6 7 8 9

Dans presque tous les cas, vous voulez .batch l'ensemble de données d' abord:

def sub_to_batch(sub):
  return sub.batch(window_size, drop_remainder=True)

for example in windows.flat_map(sub_to_batch).take(5):
  print(example.numpy())
[0 1 2 3 4]
[1 2 3 4 5]
[2 3 4 5 6]
[3 4 5 6 7]
[4 5 6 7 8]

Maintenant, vous pouvez voir que les shift de contrôle argument combien chaque fenêtre se déplace sur.

En rassemblant tout cela, vous pourriez écrire cette fonction :

def make_window_dataset(ds, window_size=5, shift=1, stride=1):
  windows = ds.window(window_size, shift=shift, stride=stride)

  def sub_to_batch(sub):
    return sub.batch(window_size, drop_remainder=True)

  windows = windows.flat_map(sub_to_batch)
  return windows
ds = make_window_dataset(range_ds, window_size=10, shift = 5, stride=3)

for example in ds.take(10):
  print(example.numpy())
[ 0  3  6  9 12 15 18 21 24 27]
[ 5  8 11 14 17 20 23 26 29 32]
[10 13 16 19 22 25 28 31 34 37]
[15 18 21 24 27 30 33 36 39 42]
[20 23 26 29 32 35 38 41 44 47]
[25 28 31 34 37 40 43 46 49 52]
[30 33 36 39 42 45 48 51 54 57]
[35 38 41 44 47 50 53 56 59 62]
[40 43 46 49 52 55 58 61 64 67]
[45 48 51 54 57 60 63 66 69 72]

Ensuite, il est facile d'extraire les étiquettes, comme précédemment :

dense_labels_ds = ds.map(dense_1_step)

for inputs,labels in dense_labels_ds.take(3):
  print(inputs.numpy(), "=>", labels.numpy())
[ 0  3  6  9 12 15 18 21 24] => [ 3  6  9 12 15 18 21 24 27]
[ 5  8 11 14 17 20 23 26 29] => [ 8 11 14 17 20 23 26 29 32]
[10 13 16 19 22 25 28 31 34] => [13 16 19 22 25 28 31 34 37]

Rééchantillonnage

Lorsque vous travaillez avec un ensemble de données très déséquilibré en classe, vous souhaiterez peut-être rééchantillonner l'ensemble de données. tf.data fournit deux méthodes pour le faire. L'ensemble de données sur la fraude par carte de crédit est un bon exemple de ce type de problème.

zip_path = tf.keras.utils.get_file(
    origin='https://storage.googleapis.com/download.tensorflow.org/data/creditcard.zip',
    fname='creditcard.zip',
    extract=True)

csv_path = zip_path.replace('.zip', '.csv')
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/creditcard.zip
69156864/69155632 [==============================] - 3s 0us/step
69165056/69155632 [==============================] - 3s 0us/step
creditcard_ds = tf.data.experimental.make_csv_dataset(
    csv_path, batch_size=1024, label_name="Class",
    # Set the column types: 30 floats and an int.
    column_defaults=[float()]*30+[int()])

Maintenant, vérifiez la distribution des classes, elle est très asymétrique :

def count(counts, batch):
  features, labels = batch
  class_1 = labels == 1
  class_1 = tf.cast(class_1, tf.int32)

  class_0 = labels == 0
  class_0 = tf.cast(class_0, tf.int32)

  counts['class_0'] += tf.reduce_sum(class_0)
  counts['class_1'] += tf.reduce_sum(class_1)

  return counts
counts = creditcard_ds.take(10).reduce(
    initial_state={'class_0': 0, 'class_1': 0},
    reduce_func = count)

counts = np.array([counts['class_0'].numpy(),
                   counts['class_1'].numpy()]).astype(np.float32)

fractions = counts/counts.sum()
print(fractions)
[0.9964 0.0036]

Une approche courante de l'entraînement avec un ensemble de données déséquilibré consiste à l'équilibrer. tf.data comprend quelques méthodes qui permettent ce flux de travail:

Échantillonnage des jeux de données

Une approche de rééchantillonnage un ensemble de données consiste à utiliser sample_from_datasets . Ceci est plus applicable lorsque vous avez un séparé data.Dataset pour chaque classe.

Ici, utilisez simplement le filtre pour les générer à partir des données de fraude par carte de crédit :

negative_ds = (
  creditcard_ds
    .unbatch()
    .filter(lambda features, label: label==0)
    .repeat())
positive_ds = (
  creditcard_ds
    .unbatch()
    .filter(lambda features, label: label==1)
    .repeat())
for features, label in positive_ds.batch(10).take(1):
  print(label.numpy())
[1 1 1 1 1 1 1 1 1 1]

À l' utilisation tf.data.experimental.sample_from_datasets passent les ensembles de données, et le poids de chaque:

balanced_ds = tf.data.experimental.sample_from_datasets(
    [negative_ds, positive_ds], [0.5, 0.5]).batch(10)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/data/experimental/ops/interleave_ops.py:260: RandomDataset.__init__ (from tensorflow.python.data.ops.dataset_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.data.Dataset.random(...)`.

Maintenant, l'ensemble de données produit des exemples de chaque classe avec une probabilité de 50/50 :

for features, labels in balanced_ds.take(10):
  print(labels.numpy())
[1 0 1 0 0 1 1 1 0 0]
[1 1 1 1 0 0 0 0 1 0]
[1 1 1 1 0 0 1 1 1 1]
[0 1 0 0 0 1 1 1 0 1]
[0 1 0 0 0 1 0 0 1 1]
[0 1 0 0 1 1 1 0 1 0]
[1 0 1 0 0 0 0 1 1 1]
[0 1 0 1 0 0 1 1 0 0]
[0 0 1 0 0 1 0 0 0 1]
[1 0 1 0 0 0 0 1 0 1]

Rejet du rééchantillonnage

Un problème avec ce qui précède experimental.sample_from_datasets approche est qu'elle a besoin d' une distincte tf.data.Dataset par classe. L' utilisation Dataset.filter fonctionne, mais les résultats dans toutes les données en cours de chargement deux fois.

La data.experimental.rejection_resample fonction peut être appliquée à un ensemble de données pour rééquilibrer, tout en ne chargeant qu'une seule fois. Les éléments seront supprimés de l'ensemble de données pour atteindre l'équilibre.

data.experimental.rejection_resample prend un class_func argument. Cette class_func est appliquée à chaque élément de jeu de données, et est utilisé pour déterminer quelle classe appartient un exemple à des fins d'équilibrage.

Les éléments de creditcard_ds sont déjà (features, label) paires. Ainsi , le class_func a juste besoin de retourner les étiquettes:

def class_func(features, label):
  return label

Le rééchantillonneur a également besoin d'une distribution cible, et éventuellement d'une estimation de distribution initiale :

resampler = tf.data.experimental.rejection_resample(
    class_func, target_dist=[0.5, 0.5], initial_dist=fractions)

Les prix rééchantillonnage avec des exemples individuels, vous devez donc unbatch l'ensemble de données avant d' appliquer la resampler:

resample_ds = creditcard_ds.unbatch().apply(resampler).batch(10)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/data/experimental/ops/resampling.py:159: Print (from tensorflow.python.ops.logging_ops) is deprecated and will be removed after 2018-08-20.
Instructions for updating:
Use tf.print instead of tf.Print. Note that tf.print returns a no-output operator that directly prints the output. Outside of defuns or eager mode, this operator will not be executed unless it is directly specified in session.run or used as a control dependency for other operators. This is only a concern in graph mode. Below is an example of how to ensure tf.print executes in graph mode:

Les rendements rééchantillonnage crée (class, example) par class_func (class, example) des paires de la sortie du class_func . Dans ce cas, l' example était déjà (feature, label) paire, donc utiliser la map pour laisser tomber la copie supplémentaire des étiquettes:

balanced_ds = resample_ds.map(lambda extra_label, features_and_label: features_and_label)

Maintenant, l'ensemble de données produit des exemples de chaque classe avec une probabilité de 50/50 :

for features, labels in balanced_ds.take(10):
  print(labels.numpy())
Proportion of examples rejected by sampler is high: [0.996386707][0.996386707 0.0036132813][0 1]
Proportion of examples rejected by sampler is high: [0.996386707][0.996386707 0.0036132813][0 1]
Proportion of examples rejected by sampler is high: [0.996386707][0.996386707 0.0036132813][0 1]
Proportion of examples rejected by sampler is high: [0.996386707][0.996386707 0.0036132813][0 1]
Proportion of examples rejected by sampler is high: [0.996386707][0.996386707 0.0036132813][0 1]
Proportion of examples rejected by sampler is high: [0.996386707][0.996386707 0.0036132813][0 1]
Proportion of examples rejected by sampler is high: [0.996386707][0.996386707 0.0036132813][0 1]
Proportion of examples rejected by sampler is high: [0.996386707][0.996386707 0.0036132813][0 1]
Proportion of examples rejected by sampler is high: [0.996386707][0.996386707 0.0036132813][0 1]
Proportion of examples rejected by sampler is high: [0.996386707][0.996386707 0.0036132813][0 1]
[1 1 0 1 1 1 1 1 0 1]
[0 0 0 1 1 0 0 1 0 1]
[0 0 0 1 0 0 1 1 0 1]
[0 0 0 1 0 0 1 1 0 0]
[0 1 1 0 1 0 1 0 0 0]
[0 0 1 0 1 1 0 0 0 0]
[0 0 0 1 1 1 0 1 1 1]
[0 0 0 1 1 0 0 0 1 1]
[0 0 1 1 0 1 0 0 0 0]
[1 0 0 0 1 1 1 0 0 0]

Point de contrôle de l'itérateur

Supports tensorflow prenant des points de contrôle de sorte que lorsque votre processus de formation redémarre , il peut restaurer le dernier point de contrôle pour récupérer la majeure partie de ses progrès. En plus de pointer les variables du modèle, vous pouvez également pointer la progression de l'itérateur de jeu de données. Cela peut être utile si vous disposez d'un ensemble de données volumineux et que vous ne souhaitez pas recommencer l'ensemble de données depuis le début à chaque redémarrage. Notez cependant que iterator points de contrôle peuvent être importantes, car les transformations telles que shuffle et prefetch nécessitent des éléments tampons dans l'itérateur.

Pour inclure votre iterator dans un poste de contrôle, passez le iterator au tf.train.Checkpoint constructeur.

range_ds = tf.data.Dataset.range(20)

iterator = iter(range_ds)
ckpt = tf.train.Checkpoint(step=tf.Variable(0), iterator=iterator)
manager = tf.train.CheckpointManager(ckpt, '/tmp/my_ckpt', max_to_keep=3)

print([next(iterator).numpy() for _ in range(5)])

save_path = manager.save()

print([next(iterator).numpy() for _ in range(5)])

ckpt.restore(manager.latest_checkpoint)

print([next(iterator).numpy() for _ in range(5)])
[0, 1, 2, 3, 4]
[5, 6, 7, 8, 9]
[5, 6, 7, 8, 9]

Utiliser tf.data avec tf.keras

L' tf.keras API de nombreux aspects de la création et l' exécution des modèles d'apprentissage de la machine. Son .fit() et .evaluate() et .predict() API supportent des ensembles de données comme entrées. Voici un ensemble de données rapide et une configuration de modèle :

train, test = tf.keras.datasets.fashion_mnist.load_data()

images, labels = train
images = images/255.0
labels = labels.astype(np.int32)
fmnist_train_ds = tf.data.Dataset.from_tensor_slices((images, labels))
fmnist_train_ds = fmnist_train_ds.shuffle(5000).batch(32)

model = tf.keras.Sequential([
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(10)
])

model.compile(optimizer='adam',
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), 
              metrics=['accuracy'])

Le passage d' un ensemble de données (feature, label) paires est tout ce qui est nécessaire pour Model.fit et Model.evaluate :

model.fit(fmnist_train_ds, epochs=2)
Epoch 1/2
1875/1875 [==============================] - 4s 2ms/step - loss: 0.5993 - accuracy: 0.7991
Epoch 2/2
1875/1875 [==============================] - 3s 2ms/step - loss: 0.4618 - accuracy: 0.8410
<keras.callbacks.History at 0x7fbce01a3810>

Si vous passez un ensemble de données infini, par exemple en appelant Dataset.repeat() , il vous suffit de passer aussi le steps_per_epoch argument:

model.fit(fmnist_train_ds.repeat(), epochs=2, steps_per_epoch=20)
Epoch 1/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4410 - accuracy: 0.8484
Epoch 2/2
20/20 [==============================] - 0s 2ms/step - loss: 0.4588 - accuracy: 0.8547
<keras.callbacks.History at 0x7fb900392250>

Pour l'évaluation, vous pouvez passer le nombre d'étapes d'évaluation :

loss, accuracy = model.evaluate(fmnist_train_ds)
print("Loss :", loss)
print("Accuracy :", accuracy)
1875/1875 [==============================] - 4s 2ms/step - loss: 0.4328 - accuracy: 0.8520
Loss : 0.4328066110610962
Accuracy : 0.8520166873931885

Pour les ensembles de données longs, définissez le nombre d'étapes à évaluer :

loss, accuracy = model.evaluate(fmnist_train_ds.repeat(), steps=10)
print("Loss :", loss)
print("Accuracy :", accuracy)
10/10 [==============================] - 0s 2ms/step - loss: 0.5152 - accuracy: 0.8313
Loss : 0.5151785612106323
Accuracy : 0.831250011920929

Les étiquettes ne sont pas nécessaires lors de l' appel Model.predict .

predict_ds = tf.data.Dataset.from_tensor_slices(images).batch(32)
result = model.predict(predict_ds, steps = 10)
print(result.shape)
(320, 10)

Mais les étiquettes sont ignorées si vous transmettez un ensemble de données les contenant :

result = model.predict(fmnist_train_ds, steps = 10)
print(result.shape)
(320, 10)