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.