Significado de buffer_size en Dataset.map, Dataset.prefetch y Dataset.shuffle

Según la documentación de TensorFlow, los métodos de tf.contrib.data.Dataset y tf.contrib.data.Dataset clase tf.contrib.data.Dataset , ambos tienen un parámetro llamado buffer_size .

Para el método de buffer_size , el parámetro se conoce como buffer_size y de acuerdo con la documentación:

buffer_size: A tf.int64 escalar tf.Tensor, que representa el número máximo de elementos que se almacenarán en el búfer al realizar la captación previa.

Para el método de map , el parámetro se conoce como output_buffer_size y según la documentación:

output_buffer_size: (Opcional) A tf.int64 escalar tf.Tensor, que representa la cantidad máxima de elementos procesados ​​que se almacenarán en el búfer.

De manera similar para el método de shuffle , aparece la misma cantidad y según la documentación:

buffer_size: A tf.int64 escalar tf.Tensor, que representa el número de elementos de este conjunto de datos del que se tomarán muestras del nuevo conjunto de datos.

¿Cuál es la relación entre estos parámetros?

Supongamos que creo un objeto Dataset siguiente manera:

  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) 

¿Qué papel juegan los parámetros del buffer en el fragmento de arriba?

TL; DR A pesar de sus nombres similares, estos argumentos tienen significados bastante diferentes. El buffer_size en Dataset.shuffle() puede afectar la aleatoriedad de su conjunto de datos y, por lo tanto, el orden en que se producen los elementos. El buffer_size en Dataset.prefetch() solo afecta el tiempo que lleva producir el siguiente elemento.


El argumento buffer_size en tf.data.Dataset.prefetch() y el argumento output_buffer_size en tf.contrib.data.Dataset.map() proporcionan una forma de ajustar el rendimiento de su canalización de entrada: ambos argumentos le dicen a TensorFlow que cree un buffer de en la mayoría de los elementos buffer_size , y una buffer_size fondo para llenar ese buffer en el fondo. (Tenga en cuenta que eliminamos el argumento output_buffer_size de Dataset.map() cuando se movió de tf.contrib.data a tf.data . El nuevo código debe usar Dataset.prefetch() después de map() para obtener el mismo comportamiento.)

Agregar un búfer de captación previa puede mejorar el rendimiento superponiendo el preprocesamiento de datos con el cómputo descendente. Normalmente, es más útil agregar un pequeño búfer de captación previa (con quizás solo un elemento) al final de la tubería, pero las tuberías más complejas pueden beneficiarse de la captación previa adicional, especialmente cuando el tiempo para producir un solo elemento puede variar.

Por el contrario, el argumento buffer_size para tf.data.Dataset.shuffle() afecta la aleatoriedad de la transformación. Diseñamos la transformación Dataset.shuffle() (como la función tf.train.shuffle_batch() que reemplaza) para manejar conjuntos de datos que son demasiado grandes para caber en la memoria. En lugar de mezclar todo el conjunto de datos, mantiene un buffer de elementos buffer_size y selecciona aleatoriamente el siguiente elemento de ese búfer (reemplazándolo con el siguiente elemento de entrada, si hay alguno disponible). Cambiar el valor de buffer_size afecta la uniformidad de la buffer_size : si buffer_size es mayor que el número de elementos en el conjunto de datos, se obtiene una mezcla uniforme; si es 1 entonces no se baraja en absoluto. Para conjuntos de datos muy grandes, un enfoque típico “suficientemente bueno” es fragmentar aleatoriamente los datos en múltiples archivos una vez antes del entrenamiento, luego mezclar los nombres de los archivos de manera uniforme y luego usar un buffer de mezcla más pequeño. Sin embargo, la elección adecuada dependerá de la naturaleza exacta de su trabajo de capacitación.


Importancia de buffer_size en shuffle()

Quería dar seguimiento a la respuesta anterior de @mrry para enfatizar la importancia de buffer_size en tf.data.Dataset.shuffle() .

Tener un buffer_size bajo no solo te dará una buffer_size inferior en algunos casos: puede estropear todo tu entrenamiento.


Un ejemplo práctico: clasificador de gatos

Supongamos, por ejemplo, que está entrenando un clasificador de gatos en imágenes, y sus datos están organizados de la siguiente manera (con 10000 imágenes en cada categoría):

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

Una forma estándar de ingresar datos con tf.data puede ser tener una lista de nombres de archivos y una lista de tags correspondientes, y usar tf.data.Dataset.from_tensor_slices() para crear el conjunto de datos:

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

El gran problema con el código anterior es que el conjunto de datos en realidad no se mezclará de la manera correcta. Durante aproximadamente la primera mitad de una época, solo veremos imágenes de gatos, y para la segunda mitad solo imágenes que no sean de gatos. Esto lastimará el entrenamiento mucho.
Al comienzo del entrenamiento, el conjunto de datos tomará los primeros 1000 nombres de archivo y los colocará en su búfer, luego escogerá uno al azar entre ellos. Como todas las primeras 1000 imágenes son imágenes de gato, solo elegiremos las imágenes de gato al principio.

La solución aquí es asegurarse de que buffer_size sea ​​más grande que 20000 , o barajar con anticipación filenames y labels (obviamente con los mismos índices).

Como almacenar todos los nombres y tags de archivos en la memoria no es un problema, podemos usar buffer_size = len(filenames) para asegurarnos de que todo se mezclará. Asegúrese de llamar a tf.data.Dataset.shuffle() antes de aplicar las transformaciones pesadas (como leer las imágenes, procesarlas, procesar por lotes …).

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

Lo más importante es comprobar siempre lo que hará el barajado. Una buena forma de detectar estos errores podría ser trazar la distribución de los lotes a lo largo del tiempo (asegúrese de que los lotes contengan aproximadamente la misma distribución que el conjunto de entrenamiento, medio gato y medio no gato en nuestro ejemplo).