Arti buffer_size di Dataset.map, Dataset.prefetch, dan Dataset.shuffle

102

Sesuai dokumentasi TensorFlow , metode prefetchdan kelas, keduanya memiliki parameter yang dipanggil .maptf.contrib.data.Datasetbuffer_size

Untuk prefetchmetode, parameternya dikenal sebagai buffer_sizedan menurut dokumentasi:

buffer_size: Skalar tf.int64 tf.Tensor, mewakili jumlah elemen maksimum yang akan di-buffer ketika melakukan prefetching.

Untuk mapmetode ini, parameternya dikenal sebagai output_buffer_sizedan menurut dokumentasi:

output_buffer_size: (Opsional.) Skalar tf.int64 tf.Tensor, mewakili jumlah maksimum elemen yang diproses yang akan di-buffer.

Demikian pula untuk shufflemetode, kuantitas yang sama muncul dan menurut dokumentasi:

buffer_size: Skalar tf.int64 tf.Tensor, yang mewakili jumlah elemen dari kumpulan data ini yang akan dijadikan sampel untuk kumpulan data baru.

Apa hubungan antara parameter ini?

Misalkan saya membuat Datasetobjek sebagai berikut:

 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)

Peran apa yang dimainkan oleh bufferparameter dalam cuplikan di atas?

Ujjwal
sumber
1
404 link ke "dokumentasi" tidak ditemukan.
Pradeep Singh

Jawaban:

153

TL; DR Meskipun namanya mirip, argumen ini memiliki arti yang cukup berbeda. The buffer_sizedi Dataset.shuffle()dapat mempengaruhi keacakan dataset Anda, dan karenanya urutan elemen diproduksi. The buffer_sizedalam Dataset.prefetch()hanya mempengaruhi waktu yang dibutuhkan untuk menghasilkan elemen berikutnya.


The buffer_sizeargumen dalam tf.data.Dataset.prefetch()dan output_buffer_sizeargumen dalam tf.contrib.data.Dataset.map()memberikan cara untuk menyesuaikan kinerja dari pipa masukan Anda: kedua argumen memberitahu TensorFlow untuk membuat penyangga paling buffer_sizeunsur, dan latar belakang thread untuk mengisi buffer di latar belakang. (Perhatikan bahwa kami menghapus output_buffer_sizeargumen dari Dataset.map()saat dipindahkan dari tf.contrib.datake tf.data. Kode baru harus digunakan Dataset.prefetch()setelahmap() untuk mendapatkan perilaku yang sama.)

Menambahkan buffer prefetch dapat meningkatkan kinerja dengan tumpang tindih preprocessing data dengan komputasi hilir. Biasanya akan sangat berguna untuk menambahkan buffer prefetch kecil (mungkin hanya dengan satu elemen) di bagian paling akhir dari pipeline, tetapi pipeline yang lebih kompleks dapat memanfaatkan prapengambilan tambahan, terutama ketika waktu untuk menghasilkan satu elemen dapat bervariasi.

Sebaliknya, buffer_sizeargumen untuk tf.data.Dataset.shuffle()mempengaruhi keacakan transformasi. Kami merancang Dataset.shuffle()transformasi (seperti tf.train.shuffle_batch()fungsi yang digantikannya) untuk menangani kumpulan data yang terlalu besar untuk muat dalam memori. Alih-alih mengacak seluruh kumpulan data, ia mempertahankan buffer buffer_sizeelemen, dan secara acak memilih elemen berikutnya dari buffer itu (menggantinya dengan elemen input berikutnya, jika tersedia). Mengubah nilai buffer_sizemempengaruhi seberapa seragam pengacakan: jika buffer_sizelebih besar dari jumlah elemen dalam kumpulan data, Anda mendapatkan pengocokan seragam; jika memang1maka Anda tidak perlu menyeret sama sekali. Untuk kumpulan data yang sangat besar, pendekatan "cukup baik" yang umum adalah memecah data secara acak menjadi beberapa file satu kali sebelum pelatihan, lalu mengacak nama file secara seragam, lalu menggunakan buffer acak yang lebih kecil. Namun, pilihan yang tepat akan bergantung pada sifat pekerjaan pelatihan Anda.


mrry
sumber
Untuk penjelasan ini, saya masih memiliki beberapa kebingungan wrt tf.data.Dataset.shuffle(). Saya ingin tahu proses pengocokan yang tepat. Misalnya, batch_sizesampel pertama dipilih secara acak dari buffer_sizeelemen pertama , dan seterusnya.
Bs He
1
Nama file pengacakan @mrry IIUC penting karena jika tidak, setiap epoch akan melihat elemen yang sama dalam batch 0 ... 999; dan dalam batch 1000.1999; dll, di mana saya menganggap 1 file = 1000 batch. Bahkan dengan pengacakan nama file, masih ada beberapa non-keacakan: itu karena contoh dari file #k semuanya dekat satu sama lain di setiap zaman. Itu mungkin tidak terlalu buruk karena file #k itu sendiri acak; masih dalam beberapa kasus, bahkan itu bisa mengacaukan pelatihan. Satu-satunya cara untuk mendapatkan pengocokan yang sempurna adalah dengan mengatur buffer_sizeukuran file yang sama (dan mengacak file tentunya).
maks
Tensorflow rc 15.0. Dengan dataset.shuffle(buffer_size=1)pengocokan masih terjadi. Ada pemikiran?
Sergey Bushmanov
@SergeyBushmanov mungkin bergantung pada transformasi sebelum pengocokan Anda, misalnya list_files (), yang mengacak nama file di awal setiap epoch secara default.
Xiaolong
133

Pentingnya buffer_sizedalamshuffle()

Saya ingin menindaklanjuti jawaban sebelumnya dari @mrry untuk menekankan pentingnya dari buffer_sizedalam tf.data.Dataset.shuffle().

Memiliki nada rendah buffer_sizetidak hanya akan membuat Anda kalah dalam beberapa kasus: hal itu dapat mengacaukan seluruh latihan Anda.


Contoh praktis: pengklasifikasi kucing

Misalkan Anda melatih pengklasifikasi kucing pada gambar, dan data Anda diatur dengan cara berikut (dengan 10000gambar di setiap kategori):

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

Cara standar untuk memasukkan data dengan tf.datacan adalah memiliki daftar nama file dan daftar label yang sesuai, dan gunakan tf.data.Dataset.from_tensor_slices()untuk membuat dataset:

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

Masalah besar dengan kode di atas adalah bahwa dataset sebenarnya tidak akan dikocok dengan cara yang benar. Untuk sekitar paruh pertama zaman, kita hanya akan melihat gambar kucing, dan untuk paruh kedua hanya gambar bukan kucing. Ini akan sangat melukai latihan.
Pada awal pelatihan, kumpulan data akan mengambil 1000nama file pertama dan meletakkannya di buffernya, lalu memilih satu di antaranya secara acak. Karena semua 1000gambar pertama adalah gambar kucing, kami hanya akan memilih gambar kucing di awal.

Perbaikan di sini adalah untuk memastikan bahwa buffer_sizelebih besar dari 20000, atau untuk mengocok terlebih dahulu filenamesdan labels(dengan indeks yang sama jelas).

Karena menyimpan semua nama file dan label dalam memori bukanlah masalah, kami sebenarnya dapat menggunakan buffer_size = len(filenames)untuk memastikan bahwa semuanya akan dikocok bersama. Pastikan untuk memanggil tf.data.Dataset.shuffle()sebelum menerapkan transformasi berat (seperti membaca gambar, memprosesnya, menumpuk ...).

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

Kesimpulannya adalah selalu memeriksa ulang apa yang akan dilakukan pengacakan. Cara yang baik untuk menangkap kesalahan ini mungkin dengan memplot distribusi batch dari waktu ke waktu (pastikan bahwa batch berisi distribusi yang kira-kira sama dengan set pelatihan, setengah kucing dan setengah bukan kucing dalam contoh kita).

Olivier Moindrot
sumber
1
Sampel berikutnya selalu dipilih dari buffer (ukuran 1000 di sini). Jadi sampel pertama diambil dari 1000 nama file pertama. Buffer berkurang ke ukuran 999, jadi dibutuhkan input berikutnya ( filename_01001) dan menambahkannya. Sampel kedua diambil secara acak dari 1000 nama file ini (1001 nama file pertama dikurangi sampel pertama).
Olivier Moindrot
2
Masalah dengan ukuran buffer yang rendah ini adalah Anda hanya akan memiliki kucing dalam kelompok pertama Anda. Jadi, model tersebut akan dengan mudah belajar memprediksi hanya "kucing". Cara terbaik untuk melatih jaringan adalah dengan membuat kumpulan dengan jumlah yang sama dari "cat" dan "non cat".
Olivier Moindrot
1
Anda dapat menggunakan tf.summary.histogramuntuk merencanakan distribusi label dari waktu ke waktu.
Olivier Moindrot
3
Bukan salah ketik :) Dataset memiliki 10k gambar untuk setiap kelas sehingga ukuran buffer total harus di atas 20k. Tetapi pada contoh di atas, saya mengambil ukuran buffer 1k yang terlalu rendah.
Olivier Moindrot
1
Ya, mengatur ukuran buffer ke ukuran dataset umumnya baik-baik saja. Apa pun yang di atas ukuran kumpulan data akan menjadi tidak berguna (dan kecuali Anda mengulangi kumpulan data Anda sebelum mengacak, buffer tidak boleh lebih besar dari kumpulan data).
Olivier Moindrot
7

Kode

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

Keluaran

[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
sumber
2
Ini menunjukkan bahwa untuk setiap elemen yang dihasilkan oleh iterator, buffer sedang diisi dengan masing-masing elemen berikutnya dari dataset yang tidak ada di buffer sebelumnya.
Alex
2

Sebenarnya jawaban @ olivier-moindrot salah.

Anda dapat memverifikasinya dengan membuat nama file dan label saat dia menyebutkan dan mencetak nilai acak.

Anda akan melihat setiap prosedur shuffle akan menghasilkan sampel secara acak dengan ukuran sama dengan ukuran buffer dari dataset.

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
sumber
2

Saya menemukan bahwa @ olivier-moindrot memang benar, saya mencoba kode yang disediakan oleh @Houtarou Oreki, menggunakan modifikasi yang ditunjukkan oleh @max. Kode yang saya gunakan adalah sebagai berikut:

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

Output kode memang berupa angka mulai dari 1 hingga (buffer_size + (i * batch_size)), di mana i adalah berapa kali Anda menjalankan next_element . Saya pikir cara kerjanya adalah sebagai berikut. Pertama, sampel buffer_size dipilih secara berurutan dari fake_data . Kemudian satu per satu sampel batch_size diambil dari buffer. Setiap kali sampel batch diambil dari buffer, sampel tersebut akan diganti dengan yang baru, diambil secara berurutan dari fake_data . Saya menguji hal terakhir ini menggunakan kode berikut:

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)

Nilai maksimum yang dihasilkan oleh kode adalah 109. Jadi, Anda perlu memastikan sampel yang seimbang dalam batch_size Anda untuk memastikan pengambilan sampel yang seragam selama pelatihan.

Saya juga menguji apa yang dikatakan @mrry tentang kinerja, saya menemukan bahwa batch_size akan mengambil sampel sebanyak itu ke dalam memori. Saya menguji ini menggunakan kode berikut:

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

Mengubah jumlah dataset.prefetch (10) mengakibatkan tidak ada perubahan pada memori (RAM) yang digunakan. Ini penting ketika data Anda tidak cocok dengan RAM. Saya pikir cara terbaik adalah mengocok data / nama_file Anda sebelum memberi mereka makan ke tf.dataset, dan kemudian mengontrol ukuran buffer menggunakan buffer_size .

Ramiro RC
sumber