Ten dokument zawiera wskazówki dotyczące wydajności specyficzne dla zestawów danych TensorFlow (TFDS). Należy pamiętać, że TFDS udostępnia zbiory danych jako obiekty tf.data.Dataset
, więc porady z przewodnika tf.data
nadal mają zastosowanie.
Zestawy danych porównawczych
Użyj tfds.benchmark(ds)
do porównania dowolnego obiektu tf.data.Dataset
.
Upewnij się, że podano batch_size=
aby znormalizować wyniki (np. 100 iter/s -> 3200 ex/s). Działa to z dowolną iteracją (np. tfds.benchmark(tfds.as_numpy(ds))
).
ds = tfds.load('mnist', split='train').batch(32).prefetch()
# Display some benchmark statistics
tfds.benchmark(ds, batch_size=32)
# Second iteration is much faster, due to auto-caching
tfds.benchmark(ds, batch_size=32)
Małe zbiory danych (mniej niż 1 GB)
Wszystkie zestawy danych TFDS przechowują dane na dysku w formacie TFRecord
. W przypadku małych zbiorów danych (np. MNIST, CIFAR-10/-100) odczyt z pliku .tfrecord
może spowodować znaczny narzut.
Ponieważ te zbiory danych mieszczą się w pamięci, można znacznie poprawić wydajność poprzez buforowanie lub wstępne ładowanie zbioru danych. Należy pamiętać, że TFDS automatycznie buforuje małe zbiory danych (szczegóły w poniższej sekcji).
Buforowanie zbioru danych
Oto przykład potoku danych, który jawnie buforuje zbiór danych po normalizacji obrazów.
def normalize_img(image, label):
"""Normalizes images: `uint8` -> `float32`."""
return tf.cast(image, tf.float32) / 255., label
ds, ds_info = tfds.load(
'mnist',
split='train',
as_supervised=True, # returns `(img, label)` instead of dict(image=, ...)
with_info=True,
)
# Applying normalization before `ds.cache()` to re-use it.
# Note: Random transformations (e.g. images augmentations) should be applied
# after both `ds.cache()` (to avoid caching randomness) and `ds.batch()` (for
# vectorization [1]).
ds = ds.map(normalize_img, num_parallel_calls=tf.data.AUTOTUNE)
ds = ds.cache()
# For true randomness, we set the shuffle buffer to the full dataset size.
ds = ds.shuffle(ds_info.splits['train'].num_examples)
# Batch after shuffling to get unique batches at each epoch.
ds = ds.batch(128)
ds = ds.prefetch(tf.data.experimental.AUTOTUNE)
Podczas iteracji po tym zbiorze danych druga iteracja będzie znacznie szybsza niż pierwsza dzięki buforowaniu.
Automatyczne buforowanie
Domyślnie TFDS automatycznie buforuje (za pomocą ds.cache()
) zestawy danych, które spełniają następujące ograniczenia:
- Zdefiniowano całkowity rozmiar zbioru danych (wszystkie podziały) i < 250 MiB
-
shuffle_files
jest wyłączone lub odczytywany jest tylko pojedynczy fragment
Można zrezygnować z automatycznego buforowania, przekazując try_autocaching=False
do tfds.ReadConfig
w tfds.load
. Zajrzyj do dokumentacji katalogu zbiorów danych, aby sprawdzić, czy konkretny zbiór danych będzie korzystał z automatycznej pamięci podręcznej.
Ładowanie pełnych danych jako pojedynczego Tensora
Jeśli Twój zestaw danych mieści się w pamięci, możesz także załadować pełny zestaw danych jako pojedynczą tablicę Tensor lub NumPy. Można to zrobić, ustawiając batch_size=-1
aby wsadowo wszystkie przykłady umieścić w jednym tf.Tensor
. Następnie użyj tfds.as_numpy
do konwersji z tf.Tensor
na np.array
.
(img_train, label_train), (img_test, label_test) = tfds.as_numpy(tfds.load(
'mnist',
split=['train', 'test'],
batch_size=-1,
as_supervised=True,
))
Duże zbiory danych
Duże zbiory danych są dzielone na fragmenty (podzielone na wiele plików) i zazwyczaj nie mieszczą się w pamięci, dlatego nie należy ich buforować.
Mieszanie i trening
Podczas treningu ważne jest, aby dobrze przetasować dane – źle przetasowane dane mogą skutkować niższą dokładnością treningu.
Oprócz używania ds.shuffle
do mieszania rekordów należy także ustawić shuffle_files=True
aby uzyskać dobre zachowanie podczas tasowania w przypadku większych zestawów danych podzielonych na wiele plików. W przeciwnym razie epoki będą czytać fragmenty w tej samej kolejności, więc dane nie będą naprawdę losowe.
ds = tfds.load('imagenet2012', split='train', shuffle_files=True)
Dodatkowo, gdy shuffle_files=True
, TFDS wyłącza options.deterministic
, co może dać niewielki wzrost wydajności. Aby uzyskać deterministyczne tasowanie, można zrezygnować z tej funkcji za pomocą tfds.ReadConfig
: albo ustawiając read_config.shuffle_seed
, albo nadpisując read_config.options.deterministic
.
Automatyczne fragmentowanie danych pomiędzy procesami roboczymi (TF)
Podczas szkolenia wielu procesów roboczych można użyć argumentu input_context
tfds.ReadConfig
, aby każdy proces roboczy odczytał podzbiór danych.
input_context = tf.distribute.InputContext(
input_pipeline_id=1, # Worker id
num_input_pipelines=4, # Total number of workers
)
read_config = tfds.ReadConfig(
input_context=input_context,
)
ds = tfds.load('dataset', split='train', read_config=read_config)
Jest to uzupełnienie interfejsu API subsplit. Najpierw stosowane jest subplit API: train[:50%]
jest konwertowane na listę plików do odczytania. Następnie na tych plikach stosowana jest operacja ds.shard()
. Na przykład, używając train[:50%]
z num_input_pipelines=2
, każdy z 2 procesów roboczych odczyta 1/4 danych.
Gdy shuffle_files=True
pliki są tasowane w ramach jednego procesu roboczego, ale nie pomiędzy procesami roboczymi. Każdy proces roboczy będzie czytał ten sam podzbiór plików pomiędzy epokami.
Automatyczne fragmentowanie danych pomiędzy procesami roboczymi (Jax)
Dzięki Jaxowi możesz używać API tfds.split_for_jax_process
lub tfds.even_splits
do dystrybucji danych pomiędzy pracownikami. Zobacz przewodnik po podzielonym interfejsie API .
split = tfds.split_for_jax_process('train', drop_remainder=True)
ds = tfds.load('my_dataset', split=split)
tfds.split_for_jax_process
to prosty alias dla:
# The current `process_index` loads only `1 / process_count` of the data.
splits = tfds.even_splits('train', n=jax.process_count(), drop_remainder=True)
split = splits[jax.process_index()]
Szybsze dekodowanie obrazu
Domyślnie TFDS automatycznie dekoduje obrazy. Są jednak przypadki, w których bardziej wydajne może być pominięcie dekodowania obrazu za pomocą tfds.decode.SkipDecoding
i ręczne zastosowanie opcji tf.io.decode_image
:
- Podczas filtrowania przykładów (za pomocą
tf.data.Dataset.filter
), aby zdekodować obrazy po przefiltrowaniu przykładów. - Podczas przycinania obrazów, aby użyć połączonej opcji
tf.image.decode_and_crop_jpeg
op.
Kod obu przykładów jest dostępny w przewodniku dekodowania .
Pomiń nieużywane funkcje
Jeśli używasz tylko podzbioru funkcji, możliwe jest całkowite pominięcie niektórych funkcji. Jeśli Twój zbiór danych zawiera wiele nieużywanych funkcji, ich dekodowanie może znacząco poprawić wydajność. Zobacz https://www.tensorflow.org/datasets/decode#only_decode_a_sub-set_of_the_features
tf.data wykorzystuje całą moją pamięć RAM!
Jeśli masz ograniczoną ilość pamięci RAM lub jeśli ładujesz wiele zestawów danych równolegle podczas korzystania z tf.data
, oto kilka opcji, które mogą pomóc:
Zastąp rozmiar bufora
builder.as_dataset(
read_config=tfds.ReadConfig(
...
override_buffer_size=1024, # Save quite a bit of RAM.
),
...
)
Zastępuje to buffer_size
przekazany do TFRecordDataset
(lub odpowiednika): https://www.tensorflow.org/api_docs/python/tf/data/TFRecordDataset#args
Użyj tf.data.Dataset.with_options, aby zatrzymać magiczne zachowania
https://www.tensorflow.org/api_docs/python/tf/data/Dataset#with_options
options = tf.data.Options()
# Stop magic stuff that eats up RAM:
options.autotune.enabled = False
options.experimental_distribute.auto_shard_policy = (
tf.data.experimental.AutoShardPolicy.OFF)
options.experimental_optimization.inject_prefetch = False
data = data.with_options(options)