batch tensorflow tensorflow-gpu tensorflow-datasets

batch - dataset shuffle tensorflow



Significado de buffer_size en Dataset.map, Dataset.prefetch y Dataset.shuffle (5)

Según la documentation 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: un tf.int64 scalar tf.Tensor, que representa el número máximo de elementos que se almacenarán en el búfer cuando se recupere previamente.

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

output_buffer_size: (Opcional). Un tf.int64 scalar tf.Tensor, que representa el número máximo de elementos procesados ​​que se almacenarán en el búfer.

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

buffer_size: Un tf.int64 scalar tf.Tensor, que representa el número de elementos de este conjunto de datos de los cuales se muestreará el nuevo conjunto de datos.

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

Supongamos que creo un objeto de 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 anterior?


Importancia de buffer_size en shuffle()

Quería seguir 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á un barajado inferior en algunos casos: puede arruinar todo tu entrenamiento.

Un ejemplo práctico: clasificador de gatos

Supongamos, por ejemplo, que está entrenando a un clasificador de gatos en imágenes, y sus datos se organizan 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 archivo y una lista de etiquetas 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 barajará 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á mucho el entrenamiento.
Al comienzo del entrenamiento, el conjunto de datos tomará los primeros 1000 nombres de archivo y los colocará en su búfer, luego elegirá uno al azar entre ellos. Como las primeras 1000 imágenes son imágenes de gato, solo elegiremos imágenes de gato al principio.

La solución aquí es asegurarse de que buffer_size sea ​​mayor que 20000 , o mezclar de antemano filenames y labels (obviamente con los mismos índices).

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

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 conclusión es verificar siempre lo que hará el barajado. Una buena manera de detectar estos errores podría ser trazar la distribución de lotes a lo largo del tiempo (asegúrese de que los lotes contengan aproximadamente la misma distribución que el conjunto de entrenamiento, mitad gato y mitad no gato en nuestro ejemplo).


Código

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

Salida

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


Descubrí que @ olivier-moindrot es correcto, probé el código proporcionado por @Houtarou Oreki, utilizando las modificaciones señaladas por @max. El código que usé fue el siguiente:

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 salida del código fue de hecho un número que va de 1 a (buffer_size + (i * batch_size)), donde i es la cantidad de veces que ejecutó next_element . Creo que la forma en que funciona es la siguiente. Primero, las muestras de tamaño de búfer se seleccionan en orden de los datos falsos . Luego, una por una, las muestras de tamaño de lote se recogen del búfer. Cada vez que se toma una muestra de lote del búfer, se reemplaza por una nueva, tomada en orden de fake_data . Probé esto último usando el siguiente código:

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)

El valor máximo producido por el código fue 109. Por lo tanto, debe asegurar una muestra equilibrada dentro de su tamaño de lote para garantizar un muestreo uniforme durante el entrenamiento.

También probé lo que @mrry dijo sobre el rendimiento, descubrí que batch_size capturará previamente esa cantidad de muestras en la memoria. Probé esto usando el siguiente código:

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

Cambiar la cantidad de dataset.prefetch (10) no resultó en ningún cambio en la memoria (RAM) utilizada. Esto es importante cuando sus datos no caben en la RAM. Creo que la mejor manera es barajar sus datos / nombres de archivo antes de alimentarlos a tf.dataset, y luego controlar el tamaño del búfer usando buffer_size .


En realidad, la respuesta de @ olivier-moindrot no es correcta.

Puede verificarlo creando nombres de archivo y etiquetas como él / ella menciona e imprime los valores aleatorios.

Verá que cada procedimiento aleatorio generará una muestra al azar con un tamaño igual al tamaño del búfer del conjunto de datos.

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


TL; DR A pesar de sus nombres similares, estos argumentos tienen significados muy 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. 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 tf.contrib.data.Dataset.map() de entrada: ambos argumentos le indican a TensorFlow que cree un búfer de a lo buffer_size elementos buffer_size , y un hilo de fondo para llenar ese búfer 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 al superponer el preprocesamiento de datos con el cálculo posterior. Por lo general, 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 barajar todo el conjunto de datos, mantiene un búfer de elementos buffer_size y selecciona aleatoriamente el siguiente elemento de ese búfer (reemplazándolo con el siguiente elemento de entrada, si hay uno 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 combinación aleatoria uniforme; si es 1 entonces no obtienes barajado 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 búfer aleatorio más pequeño. Sin embargo, la elección adecuada dependerá de la naturaleza exacta de su trabajo de capacitación.