web-dev-qa-db-fra.com

TensorFlow - tf.data.Dataset lecture de gros fichiers HDF5

Je mets en place un pipeline TensorFlow pour la lecture de gros fichiers HDF5 en entrée pour mes modèles d'apprentissage en profondeur. Chaque fichier HDF5 contient 100 vidéos de taille variable stockées sous la forme d'une collection d'images JPG compressées (pour rendre la taille sur le disque gérable). En utilisant tf.data.Dataset Et une carte vers tf.py_func, Lire des exemples du fichier HDF5 en utilisant la logique personnalisée Python est assez facile. Par exemple:

def read_examples_hdf5(filename, label):
    with h5py.File(filename, 'r') as hf:
        # read frames from HDF5 and decode them from JPG
    return frames, label

filenames = glob.glob(os.path.join(hdf5_data_path, "*.h5"))
labels = [0]*len(filenames) # ... can we do this more elegantly?

dataset = tf.data.Dataset.from_tensor_slices((filenames, labels))
dataset = dataset.map(
    lambda filename, label: Tuple(tf.py_func(
        read_examples_hdf5, [filename, label], [tf.uint8, tf.int64]))
)

dataset = dataset.shuffle(1000 + 3 * BATCH_SIZE)
dataset = dataset.batch(BATCH_SIZE)
iterator = dataset.make_one_shot_iterator()
next_batch = iterator.get_next()

Cet exemple fonctionne, mais le problème est qu'il semble que tf.py_func Ne peut gérer qu'un seul exemple à la fois. Comme mon conteneur HDF5 stocke 100 exemples, cette limitation entraîne une surcharge importante car les fichiers doivent constamment être ouverts, lus, fermés et rouverts. Il serait beaucoup plus efficace de lire les 100 exemples de vidéos dans l'objet d'ensemble de données, puis de passer au fichier HDF5 suivant (de préférence dans plusieurs threads, chaque thread traitant de sa propre collection de fichiers HDF5).

Donc, ce que je voudrais, c'est un certain nombre de threads s'exécutant en arrière-plan, lisant des images vidéo à partir des fichiers HDF5, les décodant à partir de JPG, puis les introduisant dans l'objet de l'ensemble de données. Avant l'introduction du pipeline tf.data.Dataset, Cela était assez simple en utilisant les opérations RandomShuffleQueue et enqueue_many, Mais il semble qu'il n'existe actuellement aucun moyen élégant de le faire (ou la documentation manque).

Quelqu'un sait-il quelle serait la meilleure façon d'atteindre mon objectif? J'ai également examiné (et implémenté) le pipeline à l'aide de fichiers tfrecord, mais prendre un échantillon aléatoire d'images vidéo stockées dans un fichier tfrecord semble tout à fait impossible (voir ici =). De plus, j'ai examiné les entrées from_generator() pour tf.data.Dataset Mais cela ne fonctionnera certainement pas dans plusieurs threads, semble-t-il. Toutes les suggestions sont plus que bienvenues.

17
verified.human

Je suis tombé sur cette question tout en traitant d'un problème similaire. J'ai trouvé une solution basée sur l'utilisation d'un générateur Python, avec la méthode de construction de l'ensemble de données TF from_generator . Parce que nous utilisons un générateur, le fichier HDF5 ne doit être ouvert pour la lecture qu'une seule fois et maintenu ouvert tant qu'il y a des entrées à lire. Il ne sera donc pas ouvert, lu, puis fermé pour chaque appel pour obtenir l'élément de données suivant.

Définition du générateur

Pour permettre à l'utilisateur de passer le nom de fichier HDF5 comme argument, j'ai généré une classe qui a un __call__ méthode depuis from_generator spécifie que le générateur doit être appelable. Voici le générateur:

import h5py
import tensorflow as tf

class generator:
    def __init__(self, file):
        self.file = file

    def __call__(self):
        with h5py.File(self.file, 'r') as hf:
            for im in hf["train_img"]:
                yield im

En utilisant un générateur, le code devrait reprendre là où il s'était arrêté à chaque appel depuis la dernière fois qu'il a renvoyé un résultat, au lieu de tout recommencer depuis le début. Dans ce cas, c'est à la prochaine itération de la boucle intérieure for. Donc, cela devrait sauter l'ouverture du fichier à nouveau pour la lecture, en le maintenant ouvert tant qu'il y a des données dans yield. Pour en savoir plus sur les générateurs, voir cet excellent Q&A .

Bien sûr, vous devrez remplacer tout ce qui se trouve dans le bloc with pour correspondre à la façon dont votre ensemble de données est construit et aux sorties que vous souhaitez obtenir.

Exemple d'utilisation

ds = tf.data.Dataset.from_generator(
    generator(hdf5_path), 
    tf.uint8, 
    tf.TensorShape([427,561,3]))

value = ds.make_one_shot_iterator().get_next()

# Example on how to read elements
while True:
    try:
        data = sess.run(value)
        print(data.shape)
    except tf.errors.OutOfRangeError:
        print('done.')
        break

Encore une fois, dans mon cas, j'avais stocké uint8 images de hauteur 427, largeur 561, et 3 canaux de couleur dans mon jeu de données, vous devrez donc les modifier dans l'appel ci-dessus pour qu'ils correspondent à votre cas d'utilisation.

Gérer plusieurs fichiers

J'ai une solution proposée pour gérer plusieurs fichiers HDF5. L'idée de base est de construire un Dataset à partir des noms de fichiers comme d'habitude, puis d'utiliser la méthode interleave pour traiter simultanément de nombreux fichiers d'entrée, en obtenant des échantillons de chacun d'eux pour former un lot, par exemple.

L'idée est la suivante:

ds = tf.data.Dataset.from_tensor_slices(filenames)
# You might want to shuffle() the filenames here depending on the application
ds = ds.interleave(lambda filename: tf.data.Dataset.from_generator(
        generator(filename), 
        tf.uint8, 
        tf.TensorShape([427,561,3])),
       cycle_length, block_length)

Ce que cela fait est ouvert cycle_length fichiers simultanément et produire block_length éléments de chacun avant de passer au fichier suivant - voir la documentation interleave pour plus de détails. Vous pouvez définir les valeurs ici pour qu'elles correspondent à ce qui est approprié pour votre application: par exemple, avez-vous besoin de traiter un fichier à la fois ou plusieurs simultanément, voulez-vous uniquement avoir un seul échantillon à la fois à partir de chaque fichier, etc. .

Edit : pour une version parallèle, jetez un œil à tf.contrib.data.parallel_interleave !

Avertissements possibles

Soyez conscient des particularités de l'utilisation de from_generator si vous décidez de choisir la solution. Pour Tensorflow 1.6.0, la documentation de from_generator mentionne ces deux notes.

Il peut être difficile de l'appliquer dans différents environnements ou avec une formation distribuée:

REMARQUE: l'implémentation actuelle de Dataset.from_generator () utilise tf.py_func et hérite des mêmes contraintes. En particulier, il nécessite que les opérations liées au jeu de données et à l'itérateur soient placées sur un périphérique dans le même processus que le programme Python qui appelait Dataset.from_generator (). Le corps du générateur ne sera pas être sérialisé dans un GraphDef, et vous ne devez pas utiliser cette méthode si vous devez sérialiser votre modèle et le restaurer dans un environnement différent.

Soyez prudent si le générateur dépend de l'état externe:

REMARQUE: si le générateur dépend de variables globales mutables ou d'un autre état externe, sachez que le runtime peut invoquer le générateur plusieurs fois (afin de prendre en charge la répétition de l'ensemble de données) et à tout moment entre l'appel à Dataset.from_generator () et la production de le premier élément du générateur. La mutation des variables globales ou de l'état externe peut provoquer un comportement indéfini, et nous vous recommandons de mettre en cache explicitement tout état externe dans le générateur avant d'appeler Dataset.from_generator ().

16
mikkola

J'ai mis un certain temps à comprendre cela, alors j'ai pensé que je devrais l'enregistrer ici. Sur la base de la réponse de mikkola, voici comment gérer plusieurs fichiers:

import h5py
import tensorflow as tf

class generator:
    def __call__(self, file):
        with h5py.File(file, 'r') as hf:
            for im in hf["train_img"]:
                yield im

ds = tf.data.Dataset.from_tensor_slices(filenames)
ds = ds.interleave(lambda filename: tf.data.Dataset.from_generator(
        generator(), 
        tf.uint8, 
        tf.TensorShape([427,561,3]),
        args=(filename,)),
       cycle_length, block_length)

La clé est que vous ne pouvez pas passer filename directement à generator, car c'est un Tensor. Vous devez le passer par args, que tensorflow évalue et le convertit en une variable régulière python.

4
Rong Ou