Zgodnie TensorFlow dokumentacji , to prefetch
i map
metody tf.contrib.data.Dataset
klasy, obie posiadają parametr zwany buffer_size
.
W przypadku prefetch
metody parametr jest znany jako buffer_size
i zgodnie z dokumentacją:
buffer_size: tf.int64 skalarny tf.Tensor, reprezentujący maksymalną liczbę elementów, które będą buforowane podczas wstępnego pobierania.
W map
metodzie parametr jest znany jako output_buffer_size
i zgodnie z dokumentacją:
output_buffer_size: (opcjonalny). Tf.Tensor skalarny tf.int64, reprezentujący maksymalną liczbę przetwarzanych elementów, które będą buforowane.
Podobnie dla shuffle
metody pojawia się ta sama ilość i zgodnie z dokumentacją:
buffer_size: skalarny tf.int64 tf.Tensor, reprezentujący liczbę elementów z tego zbioru danych, z których będzie próbkowany nowy zestaw danych.
Jaka jest zależność między tymi parametrami?
Załóżmy, że utworzę Dataset
obiekt w następujący sposób:
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)
Jaką rolę odgrywają buffer
parametry w powyższym fragmencie?
Odpowiedzi:
TL; DR Pomimo podobnych nazw argumenty te mają całkiem odmienne znaczenie.
buffer_size
WDataset.shuffle()
może wpłynąć na przypadkowość swojego zbioru danych, a tym samym porządku, w którym elementy są produkowane.buffer_size
WDataset.prefetch()
wpływa jedynie na czas potrzebny do wytworzenia następnego elementu.buffer_size
Argumenttf.data.Dataset.prefetch()
ioutput_buffer_size
argumenttf.contrib.data.Dataset.map()
zapewnić sposób dostroić wydajność swojego rurociągu Wejście: oba argumenty powiedzieć TensorFlow stworzyć bufor w większościbuffer_size
pierwiastków, a wątek tła, aby wypełnić tę bufor w tle. (Zauważ, że usunęliśmyoutput_buffer_size
argument z miejsca, wDataset.map()
którym został przeniesiony ztf.contrib.data
dotf.data
. Nowy kod powinien używaćDataset.prefetch()
after,map()
aby uzyskać takie samo zachowanie).Dodanie buforu pobierania wstępnego może poprawić wydajność poprzez nałożenie wstępnego przetwarzania danych na dalsze obliczenia. Zazwyczaj najbardziej przydatne jest dodanie małego buforu pobierania wstępnego (z być może tylko jednym elementem) na samym końcu potoku, ale bardziej złożone potoki mogą skorzystać z dodatkowego pobierania wstępnego, zwłaszcza gdy czas potrzebny na wyprodukowanie pojedynczego elementu może się różnić.
Z kolei
buffer_size
argument przemawiający zatf.data.Dataset.shuffle()
wpływa na losowość transformacji. ZaprojektowaliśmyDataset.shuffle()
transformację (podobnie jaktf.train.shuffle_batch()
funkcję, którą zastępuje), aby obsługiwać zestawy danych, które są zbyt duże, aby zmieścić się w pamięci. Zamiast tasować cały zbiór danych, utrzymuje buforbuffer_size
elementów i losowo wybiera następny element z tego bufora (zastępując go kolejnym elementem wejściowym, jeśli jest dostępny). Zmiana wartościbuffer_size
wpływa na jednolitość tasowania: jeślibuffer_size
jest większa niż liczba elementów w zbiorze danych, otrzymasz jednolite tasowanie; Jeśli to jest1
wtedy nie ma żadnego tasowania. W przypadku bardzo dużych zbiorów danych typowym „wystarczająco dobrym” podejściem jest losowe podzielenie danych na wiele plików jeden raz przed uczeniem, a następnie jednolite przetasowanie nazw plików, a następnie użycie mniejszego buforu do odtwarzania losowego. Jednak właściwy wybór będzie zależał od dokładnego charakteru Twojej pracy szkoleniowej.źródło
tf.data.Dataset.shuffle()
. Chciałbym poznać dokładny proces tasowania. Powiedzmy, że pierwszebatch_size
próbki są wybierane losowo z pierwszychbuffer_size
elementów i tak dalej.buffer_size
równej wielkości pliku (i oczywiście tasowanie plików).dataset.shuffle(buffer_size=1)
tasowaniem nadal występuje. jakieś pomysły?Znaczenie
buffer_size
wshuffle()
Chciałem pójść w górę na poprzedniej odpowiedzi od @mrry podkreślić znaczenie z
buffer_size
wtf.data.Dataset.shuffle()
.Niski poziom
buffer_size
nie tylko w niektórych przypadkach spowoduje gorsze tasowanie : może zepsuć cały trening.Praktyczny przykład: klasyfikator kotów
Załóżmy na przykład, że trenujesz klasyfikator kotów na obrazach, a twoje dane są zorganizowane w następujący sposób (z
10000
obrazami w każdej kategorii):Standardowym sposobem wprowadzania danych za pomocą
tf.data
może być lista nazw plików i lista odpowiadających im etykiet oraz użycietf.data.Dataset.from_tensor_slices()
do utworzenia zestawu danych: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...
Duży problem z kodem powyżej jest to, że zestaw danych nie będzie faktycznie być tasuje we właściwy sposób. Przez mniej więcej pierwszą połowę epoki będziemy widzieć tylko obrazy kotów, a przez drugą połowę tylko obrazy inne niż koty. To bardzo zaszkodzi treningowi.
Na początku uczenia zestaw danych weźmie pierwsze
1000
nazwy plików i umieści je w swoim buforze, a następnie wybierze losowo jedną spośród nich. Ponieważ wszystkie pierwsze1000
obrazy są obrazami kotów, wybierzemy tylko obrazy kotów na początku.Rozwiązaniem jest tutaj upewnienie się, że
buffer_size
jest większy niż20000
lub przetasowanie z wyprzedzeniemfilenames
ilabels
(oczywiście z tymi samymi indeksami).Ponieważ przechowywanie wszystkich nazw plików i etykiet w pamięci nie stanowi problemu, możemy faktycznie użyć
buffer_size = len(filenames)
go, aby upewnić się, że wszystko zostanie przetasowane. Pamiętaj, aby wywołaćtf.data.Dataset.shuffle()
przed zastosowaniem ciężkich przekształceń (takich jak czytanie obrazów, przetwarzanie ich, przetwarzanie wsadowe ...).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...
Na wynos jest zawsze podwójne sprawdzenie, co da tasowanie. Dobrym sposobem na wychwycenie tych błędów może być wykreślenie rozkładu partii w czasie (upewnij się, że partie zawierają mniej więcej taki sam rozkład, jak zbiór uczący, w naszym przykładzie w połowie cat i half non cat).
źródło
filename_01001
) i dodaje je. Druga próbka jest pobierana losowo z tych 1000 nazw plików (1001 pierwszych nazw plików minus pierwsza próbka).tf.summary.histogram
do wykreślenia rozkładu etykiet w czasie.Kod
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()
Wynik
[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]
źródło
W rzeczywistości odpowiedź @ olivier-moindrot jest nieprawidłowa.
Możesz to zweryfikować, tworząc nazwy plików i etykiety, gdy on / ona wspomina, i drukując wartości losowe.
Zobaczysz, że każda procedura tasowania wygeneruje losowo próbkę o rozmiarze równym rozmiarowi bufora z zestawu danych.
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))
źródło
Okazało się, że @ olivier-moindrot jest rzeczywiście poprawne, wypróbowałem kod dostarczony przez @Houtarou Oreki, używając modyfikacji wskazanych przez @max. Kod, którego użyłem, był następujący:
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())
Kod wyjściowy był rzeczywiście liczbą z zakresu od 1 do (rozmiar_bufora + (i * rozmiar_batchu)), gdzie i to liczba uruchomień next_element . Myślę, że sposób, w jaki to działa, jest następujący. Najpierw próbki buffer_size są pobierane w kolejności z fake_data . Następnie próbki batch_size są pobierane jedna po drugiej z bufora. Za każdym razem, gdy próbka partii jest pobierana z bufora, jest ona zastępowana nową, pobraną w kolejności z fake_data . Przetestowałem tę ostatnią rzecz używając następującego kodu:
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)
Maksymalna wartość wygenerowana przez kod wynosiła 109. Dlatego musisz zapewnić zrównoważoną próbkę w ramach swojego batch_size, aby zapewnić jednolite pobieranie próbek podczas uczenia.
Przetestowałem również to, co @mrry powiedział o wydajności, stwierdziłem, że batch_size wstępnie pobierze tę liczbę próbek do pamięci. Przetestowałem to za pomocą następującego kodu:
dataset = dataset.shuffle(buffer_size=20) dataset = dataset.prefetch(10) dataset = dataset.batch(batch_size=5)
Zmiana ilości dataset.prefetch (10) nie spowodowała zmiany w używanej pamięci (RAM). Jest to ważne, gdy dane nie mieszczą się w pamięci RAM. Myślę, że najlepszym sposobem jest shuffle dane / file_names przed wprowadzeniem ich do tf.dataset, a następnie kontrolować wielkość bufora używając BUFFER_SIZE .
źródło