Bedeutung von buffer_size in Dataset.map, Dataset.prefetch und Dataset.shuffle

94

Gemäß der TensorFlow- Dokumentation haben beide prefetchund mapMethoden der tf.contrib.data.DatasetKlasse einen Parameter namens buffer_size.

Für die prefetchMethode ist der Parameter als buffer_sizeund gemäß Dokumentation bekannt:

buffer_size: Ein tf.int64-Skalar tf.Tensor, der die maximale Anzahl von Elementen darstellt, die beim Vorabrufen gepuffert werden.

Für die mapMethode ist der Parameter als output_buffer_sizeund gemäß Dokumentation bekannt:

output_buffer_size: (Optional.) Ein tf.int64-Skalar tf.Tensor, der die maximale Anzahl verarbeiteter Elemente darstellt, die gepuffert werden.

Ähnlich erscheint für die shuffleMethode die gleiche Menge und laut Dokumentation:

buffer_size: Ein tf.int64-Skalar tf.Tensor, der die Anzahl der Elemente aus diesem Datensatz darstellt, aus denen der neue Datensatz abgetastet wird.

Welche Beziehung besteht zwischen diesen Parametern?

Angenommen, ich erstelle ein DatasetObjekt wie folgt:

 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)

Welche Rolle spielen die bufferParameter im obigen Snippet?

Ujjwal
quelle
1
404 Link zu "Dokumentation" nicht gefunden.
Pradeep Singh

Antworten:

144

TL; DR Trotz ihrer ähnlichen Namen haben diese Argumente sehr unterschiedliche Bedeutungen. Das buffer_sizeIn Dataset.shuffle()kann die Zufälligkeit Ihres Datensatzes und damit die Reihenfolge beeinflussen, in der Elemente erstellt werden. Das buffer_sizeIn Dataset.prefetch()wirkt sich nur auf die Zeit aus, die zum Erstellen des nächsten Elements benötigt wird.


Das buffer_sizeArgument in tf.data.Dataset.prefetch()und das output_buffer_sizeArgument in tf.contrib.data.Dataset.map()bieten eine Möglichkeit, die Leistung Ihrer Eingabepipeline zu optimieren: Beide Argumente weisen TensorFlow an, einen Puffer mit höchstens buffer_sizeElementen zu erstellen , und einen Hintergrundthread, um diesen Puffer im Hintergrund zu füllen. (Beachten Sie, dass wir das output_buffer_sizeArgument entfernt haben, Dataset.map()als es von tf.contrib.datanach verschoben wurde tf.data. Neuer Code sollte Dataset.prefetch()after verwenden map(), um das gleiche Verhalten zu erzielen.)

Das Hinzufügen eines Prefetch-Puffers kann die Leistung verbessern, indem die Vorverarbeitung von Daten mit der nachgeschalteten Berechnung überlappt wird. In der Regel ist es am nützlichsten, am Ende der Pipeline einen kleinen Prefetch-Puffer (mit möglicherweise nur einem einzelnen Element) hinzuzufügen. Komplexere Pipelines können jedoch von einem zusätzlichen Prefetch profitieren, insbesondere wenn die Zeit zum Erstellen eines einzelnen Elements variieren kann.

Im Gegensatz buffer_sizedazu tf.data.Dataset.shuffle()beeinflusst das Argument, die Zufälligkeit der Transformation zu beeinflussen. Wir haben die Dataset.shuffle()Transformation (wie die tf.train.shuffle_batch()Funktion, die sie ersetzt) ​​so konzipiert, dass sie Datensätze verarbeitet, die zu groß sind, um in den Speicher zu passen. Anstatt das gesamte Dataset zu mischen, wird ein Puffer mit buffer_sizeElementen verwaltet und das nächste Element zufällig aus diesem Puffer ausgewählt (wobei es durch das nächste Eingabeelement ersetzt wird, sofern eines verfügbar ist). Das Ändern des Werts von buffer_sizebeeinflusst, wie gleichmäßig das Mischen ist: wennbuffer_size es größer als die Anzahl der Elemente im Datensatz ist, erhalten Sie ein einheitliches Mischen. wenn es so ist1dann bekommst du überhaupt kein schlurfen. Bei sehr großen Datenmengen besteht ein typischer "gut genug" -Ansatz darin, die Daten vor dem Training einmal zufällig in mehrere Dateien zu zerlegen, dann die Dateinamen einheitlich zu mischen und dann einen kleineren Mischpuffer zu verwenden. Die richtige Wahl hängt jedoch von der genauen Art Ihres Ausbildungsberufs ab.


mrry
quelle
Für diese Erklärung habe ich noch einige Verwirrungen tf.data.Dataset.shuffle(). Ich würde gerne den genauen Mischvorgang kennen. Angenommen, die ersten batch_sizeStichproben werden zufällig aus den ersten buffer_sizeElementen ausgewählt und so weiter.
Bs He
1
@mrry IIUC Das Mischen von Dateinamen ist wichtig, da sonst in jeder Epoche das gleiche Element in den Stapeln 0 ... 999 angezeigt wird. und in Chargen 1000.1999; usw., wobei ich 1 Datei = 1000 Stapel annehme. Selbst beim Mischen von Dateinamen gibt es immer noch eine gewisse Nicht-Zufälligkeit: Das liegt daran, dass die Beispiele aus Datei #k in jeder Epoche alle nahe beieinander liegen. Das ist vielleicht nicht schlecht, da die Datei #k selbst zufällig ist. In einigen Fällen könnte sogar das das Training durcheinander bringen. Die einzige Möglichkeit, eine perfekte Zufallswiedergabe zu erzielen, besteht darin buffer_size, die Dateigröße gleichzusetzen (und die Dateien natürlich zu mischen).
Max
Tensorflow rc 15.0. Beim dataset.shuffle(buffer_size=1)Mischen tritt immer noch auf. Irgendwelche Gedanken?
Sergey Bushmanov
@SergeyBushmanov Dies kann von der Transformation vor dem Mischen abhängen, z. B. list_files (), bei der die Dateinamen zu Beginn jeder Epoche standardmäßig gemischt werden.
Xiaolong
125

Bedeutung von buffer_sizeinshuffle()

Ich wollte die vorherige Antwort von @mrry weiterverfolgen, um die Wichtigkeit von buffer_sizein hervorzuheben tf.data.Dataset.shuffle().

Ein niedriges Niveau führt in einigen Fällen buffer_sizenicht nur zu einem minderwertigen Mischen, sondern kann auch Ihr gesamtes Training durcheinander bringen.


Ein praktisches Beispiel: Katzenklassifikator

Angenommen, Sie trainieren einen Katzenklassifikator für Bilder und Ihre Daten sind folgendermaßen organisiert (mit 10000Bildern in jeder Kategorie):

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

Eine Standardmethode zur Dateneingabe mit tf.data kann darin bestehen, eine Liste mit Dateinamen und eine Liste der entsprechenden Beschriftungen tf.data.Dataset.from_tensor_slices()zu erstellen und den Datensatz zu erstellen:

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

Das große Problem mit dem obigen Code ist, dass der Datensatz tatsächlich nicht richtig gemischt wird. In der ersten Hälfte einer Epoche werden nur Katzenbilder und in der zweiten Hälfte nur Nicht-Katzenbilder angezeigt. Dies wird das Training sehr verletzen.
Zu Beginn des Trainings nimmt der Datensatz die ersten 1000Dateinamen und legt sie in seinen Puffer. Wählen Sie dann zufällig einen aus. Da alle ersten 1000Bilder Bilder von Katzen sind, werden wir nur zu Beginn Katzenbilder auswählen.

Das Update hier ist, um sicherzustellen, dass buffer_sizegrößer als ist20000 , oder im Voraus zu mischen filenamesund labels(offensichtlich mit denselben Indizes).

Da das Speichern aller Dateinamen und Beschriftungen im Speicher kein Problem darstellt, können wir tatsächlich buffer_size = len(filenames)sicherstellen, dass alles zusammengemischt wird. Stellen Sie sicher, dass Sie anrufen, tf.data.Dataset.shuffle()bevor Sie die umfangreichen Transformationen anwenden (z. B. Lesen der Bilder, Verarbeiten, Stapeln ...).

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

Zum Mitnehmen muss immer überprüft werden, was das Mischen bewirkt. Eine gute Möglichkeit, diese Fehler zu erkennen, besteht darin, die Verteilung der Chargen über die Zeit zu zeichnen (stellen Sie sicher, dass die Chargen ungefähr die gleiche Verteilung wie das Trainingsset enthalten, in unserem Beispiel halb Katze und halb Nichtkatze).

Olivier Moindrot
quelle
1
Das nächste Sample wird immer aus dem Puffer ausgewählt (hier Größe 1000). Das erste Beispiel wird also aus den ersten 1000 Dateinamen entnommen. Der Puffer verringert sich auf die Größe 999, nimmt also die nächste Eingabe ( filename_01001) und fügt sie hinzu. Die zweite Stichprobe wird zufällig aus diesen 1000 Dateinamen entnommen (1001 erste Dateinamen abzüglich der ersten Stichprobe).
Olivier Moindrot
1
Das Problem bei dieser geringen Puffergröße ist, dass Sie nur Katzen in Ihren ersten Chargen haben. Das Modell wird also trivial lernen, nur "Katze" vorherzusagen. Der beste Weg, um das Netzwerk zu trainieren, besteht darin, Chargen mit der gleichen Menge an "Katze" und "Nichtkatze" zu haben.
Olivier Moindrot
1
Sie können tf.summary.histogramdie Verteilung der Etiketten über die Zeit zeichnen.
Olivier Moindrot
3
Kein Tippfehler :) Der Datensatz enthält 10.000 Bilder jeder Klasse, daher sollte die Gesamtpuffergröße über 20.000 liegen. Im obigen Beispiel habe ich jedoch eine Puffergröße von 1 KB verwendet, was zu niedrig ist.
Olivier Moindrot
1
Ja, das Einstellen der Puffergröße auf die Datensatzgröße ist im Allgemeinen in Ordnung. Alles, was über der Datensatzgröße liegt, ist ohnehin nutzlos (und wenn Sie Ihren Datensatz nicht vor dem Mischen wiederholen, kann der Puffer nicht größer als der Datensatz sein).
Olivier Moindrot
7

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()

Ausgabe

[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]

Vladimir
quelle
2
Dies zeigt an, dass für jedes vom Iterator ausgegebene Element der Puffer mit dem jeweiligen nächsten Element des Datensatzes gefüllt wird, das zuvor nicht im Puffer war.
Alex
2

Eigentlich ist die Antwort von @ olivier-moindrot nicht richtig.

Sie können dies überprüfen, indem Sie Dateinamen und Beschriftungen erstellen, während er die Shuffle-Werte erwähnt und druckt.

Sie werden sehen, dass jede Shuffle-Prozedur zufällig eine Stichprobe generiert, deren Größe der Puffergröße aus dem Datensatz entspricht.

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))
Isaac Cheng
quelle
2

Ich fand, dass @ olivier-moindrot tatsächlich korrekt ist. Ich habe den von @Houtarou Oreki bereitgestellten Code unter Verwendung der von @max angegebenen Änderungen ausprobiert. Der Code, den ich verwendet habe, war folgender:

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())

Die Code-Ausgabe war in der Tat eine Zahl zwischen 1 und (buffer_size + (i * batch_size)), wobei i die Häufigkeit ist, mit der Sie next_element ausgeführt haben . Ich denke, die Art und Weise, wie es funktioniert, ist die folgende. Zuerst werden buffer_size- Samples in der Reihenfolge aus den fake_data ausgewählt . Dann werden nacheinander die Batch_Size- Proben aus dem Puffer entnommen . Jedes Mal, wenn eine Chargenprobe aus dem Puffer entnommen wird, wird sie durch eine neue ersetzt, die in der Reihenfolge von fake_data entnommen wird . Ich habe dieses letzte Ding mit dem folgenden Code getestet:

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)

Der vom Code erzeugte Maximalwert betrug 109. Sie müssen also eine ausgewogene Stichprobe innerhalb Ihrer Batch-Größe sicherstellen, um eine einheitliche Stichprobe während des Trainings zu gewährleisten.

Ich habe auch getestet, was @mrry über die Leistung gesagt hat. Ich habe festgestellt, dass batch_size diese Anzahl von Samples vorab in den Speicher abruft . Ich habe dies mit dem folgenden Code getestet:

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

Das Ändern der Menge von dataset.prefetch (10) führte zu keiner Änderung des verwendeten Speichers (RAM). Dies ist wichtig, wenn Ihre Daten nicht in den Arbeitsspeicher passen. Ich denke, der beste Weg ist, Ihre Daten / Dateinamen zu mischen, bevor Sie sie an tf.dataset weiterleiten, und dann die Puffergröße mit buffer_size zu steuern .

Ramiro RC
quelle