129 votes

Signification de buffer_size dans Dataset.map, Dataset.prefetch et Dataset.shuffle

Comme par TensorFlow de la documentation , l' prefetch et map méthodes tf.contrib.data.Dataset classe, les deux ont un paramètre appelé buffer_size.

Pour prefetch méthode, le paramètre est connu comme l' buffer_size et selon la documentation :

buffer_size: Un tf.int64 scalaire tf.Tenseur, représentant le maximum nombre d'éléments qui vont être mis en mémoire tampon lors de la pré-chargement.

Pour l' map méthode, le paramètre est connu comme l' output_buffer_size et selon la documentation :

output_buffer_size: (Facultatif.) Une tf.int64 scalaire tf.Tenseur, représentant le nombre maximal de transformation des éléments qui seront mise en mémoire tampon.

De même pour l' shuffle méthode, la même quantité apparaît et selon la documentation :

buffer_size: Un tf.int64 scalaire tf.Tenseur, représentant le nombre de les éléments de ce jeu de données à partir de laquelle le nouveau jeu de données sera de l'échantillon.

Quelle est la relation entre ces paramètres ?

Supposons que je créer unDataset objet comme suit :

 tr_data = TFRecordDataset(trainfilenames)
    tr_data = tr_data.map(providefortraining, output_buffer_size=10 * trainbatchsize, num_parallel_calls\
=5)
    tr_data = tr_data.shuffle(buffer_size= 100 * trainbatchsize)
    tr_data = tr_data.prefetch(buffer_size = 10 * trainbatchsize)
    tr_data = tr_data.batch(trainbatchsize)

Quel rôle est joué par l' buffer paramètres dans l'extrait ci-dessus ?

185voto

mrry Points 1

TL;DR en Dépit de leurs noms similaires, ces arguments ont assez de différence de sens. L' buffer_size en Dataset.shuffle() peuvent affecter le caractère aléatoire de votre jeu de données, et donc l'ordre dans lequel les éléments sont produits. L' buffer_size en Dataset.prefetch() seulement affecte le temps qu'il faut pour produire l'élément suivant.


L' buffer_size argument en tf.data.Dataset.prefetch() et de la output_buffer_size argument en tf.contrib.data.Dataset.map() fournir un moyen de régler la performance de votre entrée de pipeline: les deux arguments dire TensorFlow pour créer une zone tampon d'au plus buffer_size - éléments, et un thread d'arrière-plan pour remplir la mémoire tampon à l'arrière-plan. (Notez que nous avons retiré de l' output_buffer_size argument de Dataset.map() quand il a quitté tf.contrib.data de tf.data. Le nouveau code doit utiliser Dataset.prefetch() après map() pour obtenir le même comportement.)

L'ajout d'une récupération de la mémoire tampon peut améliorer les performances en chevauchant le prétraitement des données avec l'aval de calcul. En général, il est plus utile d'ajouter un petit tampon de prélecture (avec peut-être un seul élément) à la fin de la canalisation, mais plus complexe pipelines peuvent bénéficier d'un complément de pré-chargement, en particulier lorsque le temps de produire un seul élément peut varier.

En revanche, l' buffer_size argument tf.data.Dataset.shuffle() affecte le caractère aléatoire de la transformation. Nous avons conçu l' Dataset.shuffle() transformation (comme l' tf.train.shuffle_batch() de la fonction qu'il remplace) pour gérer des ensembles de données qui sont trop volumineux pour tenir dans la mémoire. Au lieu de brassage de l'ensemble du jeu de données, il gère une mémoire tampon de l' buffer_size - éléments, et de façon aléatoire sélectionne l'élément suivant de la mémoire tampon (en le remplaçant par le prochain élément d'entrée, si un seul est disponible). La modification de la valeur de buffer_size affecte de façon uniforme le brassage est: si buffer_size est supérieur au nombre d'éléments dans le jeu de données, vous obtenez un uniforme aléatoire; si c'est 1 , puis vous obtenez pas de brassage à tous. Pour de très grands ensembles de données, un typique "assez bonne" approche est de façon aléatoire en éclat les données dans plusieurs fichiers en une fois avant l'entraînement, puis mélangez les noms de fichiers de manière uniforme, et ensuite utiliser un petit shuffle tampon. Cependant, le choix dépendra de la nature exacte de votre travail de la formation.


161voto

Olivier Moindrot Points 17630

Importance de l' buffer_size en shuffle()

J'ai voulu suivre à la précédente réponse de @mrry de souligner l' importance de l' buffer_size en tf.data.Dataset.shuffle().

Ayant un faible buffer_size ne sera pas seulement vous donner inférieure traînant dans certains cas: il peut gâcher toute votre formation.


Un exemple pratique: chat classificateur

Supposons par exemple que vous êtes à la formation d'un chat classificateur sur les images, et de vos données est organisée de la manière suivante (avec 10000 images dans chaque catégorie):

train/
    cat/
        filename_00001.jpg
        filename_00002.jpg
        ...
    not_cat/
        filename_10001.jpg
        filename_10002.jpg
        ...

Un moyen standard pour les données d'entrée avec tf.data peut être d'avoir une liste de noms de fichiers et une liste d'étiquettes, et d'utiliser tf.data.Dataset.from_tensor_slices() pour créer le dataset:

filenames = ["filename_00001.jpg", "filename_00002.jpg", ..., 
             "filename_10001.jpg", "filename_10002.jpg", ...]
labels = [1, 1, ..., 0, 0...]  # 1 for cat, 0 for not_cat

dataset = tf.data.Dataset.from_tensor_slices((filenames, labels))
dataset = dataset.shuffle(buffer_size=1000)  # 1000 should be enough right?
dataset = dataset.map(...)  # transform to images, preprocess, repeat, batch...

Le gros problème avec le code ci-dessus est que le jeu de données sera de fait pas être mélangées dans le droit chemin. Pour environ la première moitié de l'époque, nous ne voyons chat images, et pour la deuxième moitié seulement non chat images. Cela nuira à la formation d'un lot.
Au début de la formation, le jeu de données va prendre la première 1000 des noms de fichiers et les mettre dans son tampon, puis choisissez-en un au hasard parmi eux. Depuis, tous les premiers 1000 des images sont des images de chat, nous choisissons seulement les chats d'images au début.

Le correctif est de faire en sorte qu' buffer_size est plus grand que 20000, ou à mélanger à l'avance filenames et labels (avec les mêmes indices évidemment).

Depuis le stockage de tous les noms et les étiquettes dans la mémoire n'est pas un problème, nous pouvons utiliser buffer_size = len(filenames) , pour vérifier que tout va être mélangés ensemble. Assurez-vous d'appeler tf.data.Dataset.shuffle() avant l'application de l'lourdes transformations (comme la lecture, les images, leur traitement, le dosage,...).

dataset = tf.data.Dataset.from_tensor_slices((filenames, labels))
dataset = dataset.shuffle(buffer_size=len(filenames)) 
dataset = dataset.map(...)  # transform to images, preprocess, repeat, batch...

La vente à emporter est toujours le double de vérifier ce que le brassage va faire. Un bon moyen de rattraper ces erreurs peuvent être de tracer la distribution des lots au fil du temps (assurez-vous que les lots contiennent à peu près la même distribution que l'ensemble de la formation, de la moitié chat et demi non chat dans notre exemple).

7voto

Vladimir Points 41

Code

import tensorflow as tf
def shuffle():
    ds = list(range(0,1000))
    dataset = tf.data.Dataset.from_tensor_slices(ds)
    dataset=dataset.shuffle(buffer_size=500)
    dataset = dataset.batch(batch_size=1)
    iterator = dataset.make_initializable_iterator()
    next_element=iterator.get_next()
    init_op = iterator.initializer
    with tf.Session() as sess:
        sess.run(init_op)
        for i in range(100):
            print(sess.run(next_element), end='')

shuffle()

Sortie

[298][326][2][351][92][398][72][134][404][378][238][131][369][324][35][182][441][370][372][144][77][11][199][65][346][418][493][343][444][470][222][83][61][81][366][49][295][399][177][507][288][524][401][386][89][371][181][489][172][159][195][232][160][352][495][241][435][127][268][429][382][479][519][116][395][165][233][37][486][553][111][525][170][571][215][530][47][291][558][21][245][514][103][45][545][219][468][338][392][54][139][339][448][471][589][321][223][311][234][314]

3voto

Ramiro R.C. Points 61

J'ai trouvé que @olivier-moindrot est tout à fait exact, j'ai essayé le code fourni par @Oreki Houtarou, en utilisant les modifications signalé par @max. Le code que j'ai utilisé était le suivant:

fake_data = np.concatenate((np.arange(1,500,1),np.zeros(500)))

dataset = tf.data.Dataset.from_tensor_slices(fake_data)
dataset=dataset.shuffle(buffer_size=100)
dataset = dataset.batch(batch_size=10)
iterator = dataset.make_initializable_iterator()
next_element=iterator.get_next()

init_op = iterator.initializer

with tf.Session() as sess:
    sess.run(init_op)
    for i in range(50):
        print(i)
        salida = np.array(sess.run(next_element))
        print(salida)
        print(salida.max())

Le code de sortie a été, en effet, un nombre allant de 1 à (buffer_size+(i*batch_size)), où i est le nombre de fois que vous avez exécuté next_element. Je pense que la façon dont cela fonctionne est la suivante. Tout d'abord, buffer_size les échantillons sont repris dans l'ordre de la fake_data. Puis, un par un, les batch_size échantillons sont prélevés à partir de la mémoire tampon. Chaque fois qu'un lot de l'échantillon est choisi à partir de la mémoire tampon, il est remplacé par un nouveau, pris dans l'ordre de fake_data. J'ai testé cette dernière chose en utilisant le code suivant:

aux = 0
for j in range (10000):
    with tf.Session() as sess:
        sess.run(init_op)
        salida = np.array(sess.run(next_element))
        if salida.max() > aux:
            aux = salida.max()

print(aux)

La valeur maximale produite par le code était de 109. Vous devez donc vous assurer d'un échantillon équilibré au sein de votre batch_size pour assurer un échantillonnage uniforme au cours de la formation.

J'ai aussi testé ce que @mrry dit à propos de la performance, j'ai trouvé que le batch_size sera prefetch que la quantité d'échantillons dans la mémoire. J'ai testé cela, utilisez le code suivant:

dataset = dataset.shuffle(buffer_size=20)
dataset = dataset.prefetch(10)
dataset = dataset.batch(batch_size=5)

Changer le jeu de données.prefetch(10) montant a pas entraîné de modification dans la mémoire vive (RAM) utilisée. C'est important quand vos données ne sont pas d'ajustement dans la RAM. Je pense que le mieux est de mélanger vos données/file_names avant de nourrir tf.jeu de données, puis de contrôler la taille de la mémoire tampon à l'aide de buffer_size.

1voto

Isaac Cheng Points 11

En réalité, la réponse de @ olivier-moindrot n'est pas correcte.

Vous pouvez le vérifier en créant des noms de fichier et des étiquettes lorsqu'il mentionne et imprime les valeurs de lecture aléatoire.

Vous verrez que chaque procédure aléatoire générera un échantillon de manière aléatoire avec une taille égale à la taille de la mémoire tampon à partir du jeu de données.

 dataset = dataset.shuffle(buffer_size=1000)
iterator = dataset.make_one_shot_iterator()
next_element = iterator.get_next()
with tf.Session() as sess:
    for i in range(1000):
        print(sess.run(next_element))
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X