web-dev-qa-db-fra.com

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

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

Pour la méthode prefetch, le paramètre est appelé buffer_size et selon la documentation:

buffer_size: Un scalaire tf.int64, tf.Tensor, représentant le nombre maximal d'éléments pouvant être mis en mémoire tampon lors de la pré-extraction.

Pour la méthode map, le paramètre est appelé output_buffer_size et selon la documentation:

output_buffer_size: (Facultatif.) Un scalaire tf.int64, tf.Tensor, représentant le nombre maximal d'éléments traités qui seront mis en mémoire tampon.

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

buffer_size: un scalaire tf.int64, tf.Tensor, représentant le nombre d'éléments de cet ensemble de données à partir duquel le nouvel ensemble de données sera échantillonné.

Quelle est la relation entre ces paramètres?

Supposons que je crée un objetDataset 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 jouent les paramètres buffer dans l'extrait de code ci-dessus?

67
Ujjwal

TL; DR Malgré leurs noms similaires, ces arguments ont des significations bien différentes. Le buffer_size dans Dataset.shuffle() peut affecter le caractère aléatoire de votre jeu de données, et donc l’ordre dans lequel les éléments sont produits. Le buffer_size dans Dataset.prefetch() n'affecte que le temps nécessaire pour produire l'élément suivant.


L'argument buffer_size dans tf.data.Dataset.prefetch() et l'argument output_buffer_size dans tf.contrib.data.Dataset.map() permettent d'ajuster les performances de votre pipeline d'entrée: les deux arguments indiquent à TensorFlow de créer un tampon d'au plus buffer_size éléments, ainsi qu'un thread d'arrière-plan pour remplir ce tampon en arrière-plan. (Notez que nous avons supprimé l'argument output_buffer_size de Dataset.map() lorsqu'il est passé de tf.contrib.data à tf.data. Le nouveau code doit utiliser Dataset.prefetch() après map() pour obtenir le même comportement.)

L'ajout d'un tampon de prélecture peut améliorer les performances en superposant le prétraitement des données avec le calcul en aval. En général, il est très utile d’ajouter un petit tampon de prélecture (avec peut-être un seul élément) à la toute fin du pipeline, mais des pipelines plus complexes peuvent bénéficier d’un prélecture supplémentaire, en particulier lorsque le temps nécessaire à la production d’un seul élément peut varier.

En revanche, l'argument buffer_size de tf.data.Dataset.shuffle() affecte le caractère aléatoire de la transformation. Nous avons conçu la transformation Dataset.shuffle() (comme la fonction tf.train.shuffle_batch() qu’elle remplace) pour gérer des ensembles de données trop volumineux pour tenir en mémoire. Au lieu de réorganiser l'intégralité du jeu de données, il conserve un tampon d'éléments buffer_size et sélectionne de manière aléatoire l'élément suivant dans ce tampon (en le remplaçant par l'élément d'entrée suivant, le cas échéant). La modification de la valeur de buffer_size affecte l’uniformité du brassage: si buffer_size est supérieur au nombre d’éléments du jeu de données, vous obtenez un brassage uniforme; Si c'est 1 alors vous ne serez pas mélangé du tout. Pour les très grands ensembles de données, une approche "assez efficace" typique consiste à fragmenter aléatoirement les données en plusieurs fichiers une fois avant l'entraînement, puis à mélanger les noms de fichiers de manière uniforme, puis à utiliser un tampon de réorganisation plus petit. Cependant, le choix approprié dépendra de la nature exacte de votre travail de formation.


104
mrry

Importance de buffer_size dans shuffle()

Je voulais donner suite à la réponse précédente de @mrry pour souligner l’importance de importance de buffer_size dans tf.data.Dataset.shuffle() .

Avoir un buffer_size faible ne vous donnera pas simplement un brassage inférieur dans certains cas: cela peut gâcher toute votre formation.


Un exemple pratique: classificateur de chat

Supposons, par exemple, que vous entraîniez un classificateur chat sur des images et que vos données soient organisées 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 d'entrer des données avec tf.data peut être d'avoir une liste de noms de fichiers et une liste d'étiquettes correspondantes, et utiliser tf.data.Dataset.from_tensor_slices() pour créer le jeu de données:

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 ne sera en réalité pas brassé de la bonne manière. Pendant environ la première moitié d'une époque, nous ne verrons que des images de chats et, pour la seconde moitié, des images de non-chats. Cela fera beaucoup de mal à l'entraînement.
Au début de la formation, le jeu de données prendra les premiers noms de fichiers 1000 et les mettra dans sa mémoire tampon, puis en choisira un au hasard. Etant donné que toutes les premières images 1000 sont des images de chat, seules les images de chat seront prises au début.

La solution ici est de s’assurer que buffer_size est supérieur à 20000, ou de mélanger par avance filenames et labels (avec les mêmes indices évidemment).

Étant donné que le stockage de tous les noms de fichiers et étiquettes en mémoire n'est pas un problème, nous pouvons utiliser buffer_size = len(filenames) pour nous assurer que tout sera mélangé. Assurez-vous d'appeler tf.data.Dataset.shuffle() avant d'appliquer les transformations lourdes (comme lire les images, les traiter, les mettre en lot, etc.).

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...

Le point à retenir est de toujours vérifier ce que le brassage fera. Un bon moyen de détecter ces erreurs pourrait consister à tracer la distribution des lots dans le temps (assurez-vous que les lots contiennent environ la même distribution que l'ensemble d'entraînement, moitié chat et moitié non chat dans notre exemple).

94
Olivier Moindrot

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]

5
Vladimir

J'ai trouvé que @ olivier-moindrot est bien correct, j'ai essayé le code fourni par @Houtarou Oreki, en utilisant les modifications indiquées 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())

La sortie du code était en effet un nombre compris entre 1 et (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, les échantillons buffer_size sont sélectionnés dans l’ordre fake_data. Ensuite, un à un, les échantillons batch_size sont sélectionnés dans la mémoire tampon. Chaque fois qu'un échantillon de lot est sélectionné dans la mémoire tampon, il est remplacé par un nouveau, pris dans l'ordre suivant 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 assurer un échantillon équilibré au sein de votre batch_size pour assurer un échantillonnage uniforme pendant la formation.

J'ai également testé les propos de @mrry sur les performances. J'ai constaté que le batch_size prélèverait cette quantité d'échantillons en mémoire. J'ai testé cela en utilisant le code suivant:

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

La modification de la quantité de dataset.prefetch (10) n'a entraîné aucun changement de la mémoire RAM utilisée. Ceci est important lorsque vos données ne rentrent pas dans la RAM. Je pense que le meilleur moyen est de mélanger vos données/noms de fichier avant de les alimenter dans tf.dataset, puis de contrôler la taille de la mémoire tampon à l'aide de buffer_size.

2
Ramiro R.C.

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

Vous pouvez le vérifier en créant des noms de fichiers et des étiquettes au fur et à mesure qu'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))
1
Isaac Cheng