ความหมายของ buffer_size ใน Dataset.map, Dataset.prefetch และ Dataset.shuffle


101

เป็นต่อ TensorFlow เอกสารที่prefetchและmapวิธีการของการเรียนทั้งสองมีพารามิเตอร์ที่เรียกว่าtf.contrib.data.Datasetbuffer_size

สำหรับprefetchวิธีการพารามิเตอร์เรียกว่าbuffer_sizeและตามเอกสารประกอบ:

buffer_size: tf.int64 สเกลาร์ tf.Tensor แสดงถึงองค์ประกอบจำนวนสูงสุดที่จะถูกบัฟเฟอร์เมื่อดึงข้อมูลล่วงหน้า

สำหรับmapวิธีการนี้พารามิเตอร์เรียกว่าoutput_buffer_sizeและตามเอกสารประกอบ:

output_buffer_size: (ไม่บังคับ) tf.int64 สเกลาร์ tf.Tensor แสดงจำนวนองค์ประกอบที่ประมวลผลสูงสุดที่จะถูกบัฟเฟอร์

ในทำนองเดียวกันสำหรับshuffleวิธีการนี้ปริมาณเดียวกันจะปรากฏขึ้นและตามเอกสารประกอบ:

buffer_size: tf.int64 สเกลาร์ tf.Tensor ซึ่งแสดงจำนวนองค์ประกอบจากชุดข้อมูลนี้ซึ่งชุดข้อมูลใหม่จะสุ่มตัวอย่าง

ความสัมพันธ์ระหว่างพารามิเตอร์เหล่านี้คืออะไร?

สมมติว่าฉันสร้างDatasetวัตถุดังต่อไปนี้:

 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)

bufferพารามิเตอร์ในข้อมูลโค้ดด้านบนมีบทบาทอย่างไร


1
ไม่พบลิงก์ 404 ไปยัง "เอกสารประกอบ"
Pradeep Singh

คำตอบ:


151

TL; DRแม้จะมีชื่อคล้ายกัน แต่ข้อโต้แย้งเหล่านี้มีความหมายที่แตกต่างกันมาก buffer_sizeในDataset.shuffle()จะมีผลต่อการสุ่มของชุดข้อมูลของคุณและด้วยเหตุนี้ลำดับที่องค์ประกอบที่มีการผลิต ค่าbuffer_sizeอินDataset.prefetch()ส่งผลต่อเวลาที่ใช้ในการสร้างองค์ประกอบถัดไปเท่านั้น


buffer_sizeโต้แย้งในtf.data.Dataset.prefetch()และoutput_buffer_sizeโต้แย้งในtf.contrib.data.Dataset.map()ให้วิธีการปรับแต่งประสิทธิภาพการทำงานของท่อป้อนข้อมูลของคุณ: ข้อโต้แย้งทั้งบอก TensorFlow เพื่อสร้างกันชนของที่มากที่สุดbuffer_sizeองค์ประกอบและด้ายพื้นหลังเพื่อเติมบัฟเฟอร์ที่ในพื้นหลัง (โปรดทราบว่าเราลบoutput_buffer_sizeอาร์กิวเมนต์Dataset.map()เมื่อย้ายจากtf.contrib.dataไปยังtf.dataโค้ดใหม่ควรใช้Dataset.prefetch()หลังmap()เพื่อให้ได้พฤติกรรมเดียวกัน)

การเพิ่มบัฟเฟอร์การดึงข้อมูลล่วงหน้าสามารถปรับปรุงประสิทธิภาพได้โดยการซ้อนทับการประมวลผลข้อมูลล่วงหน้าด้วยการคำนวณดาวน์สตรีม โดยทั่วไปแล้วจะมีประโยชน์มากที่สุดในการเพิ่มบัฟเฟอร์ prefetch ขนาดเล็ก (อาจเป็นเพียงองค์ประกอบเดียว) ที่ส่วนท้ายสุดของไปป์ไลน์ แต่ไปป์ไลน์ที่ซับซ้อนกว่าจะได้รับประโยชน์จากการดึงข้อมูลล่วงหน้าเพิ่มเติมโดยเฉพาะเมื่อเวลาในการสร้างองค์ประกอบเดียวอาจแตกต่างกัน

ในทางตรงกันข้ามการbuffer_sizeโต้แย้งtf.data.Dataset.shuffle()มีผลต่อการสุ่มของการเปลี่ยนแปลง เราออกแบบการDataset.shuffle()แปลง (เช่นtf.train.shuffle_batch()ฟังก์ชันที่แทนที่) เพื่อจัดการกับชุดข้อมูลที่มีขนาดใหญ่เกินกว่าที่จะใส่ลงในหน่วยความจำได้ แทนที่จะสับชุดข้อมูลทั้งหมดมันจะรักษาบัฟเฟอร์ของbuffer_sizeองค์ประกอบและสุ่มเลือกองค์ประกอบถัดไปจากบัฟเฟอร์นั้น (แทนที่ด้วยองค์ประกอบอินพุตถัดไปหากมี) การเปลี่ยนค่าของbuffer_sizeมีผลต่อความสม่ำเสมอของการสับเปลี่ยน: ถ้าbuffer_sizeมากกว่าจำนวนองค์ประกอบในชุดข้อมูลคุณจะได้รับการสุ่มแบบสม่ำเสมอ ถ้ามันเป็น1จากนั้นคุณจะไม่ต้องสับเลย สำหรับชุดข้อมูลที่มีขนาดใหญ่มากวิธีการที่ "ดีพอ" โดยทั่วไปคือการสุ่มแบ่งข้อมูลเป็นไฟล์หลาย ๆ ไฟล์ก่อนการฝึกอบรมจากนั้นสลับชื่อไฟล์อย่างสม่ำเสมอจากนั้นจึงใช้บัฟเฟอร์แบบสุ่มที่เล็กกว่า อย่างไรก็ตามทางเลือกที่เหมาะสมจะขึ้นอยู่กับลักษณะของงานฝึกอบรมของคุณ



สำหรับคำอธิบายนี้ฉันยังคงมีความสับสนtf.data.Dataset.shuffle()อยู่บ้าง ฉันต้องการทราบขั้นตอนการสับที่แน่นอน สมมติว่าbatch_sizeตัวอย่างแรกถูกสุ่มเลือกจากbuffer_sizeองค์ประกอบแรกและอื่น ๆ
Bs He

1
@mrry IIUC การสับชื่อไฟล์เป็นสิ่งสำคัญเพราะมิฉะนั้นแต่ละยุคจะเห็นองค์ประกอบเดียวกันในแบทช์ 0 ... 999; และในแบทช์ 1,000.1999; ฯลฯ โดยที่ฉันถือว่า 1 ไฟล์ = 1,000 แบทช์ แม้จะมีการสลับชื่อไฟล์ แต่ก็ยังมีความไม่สุ่มนั่นเป็นเพราะตัวอย่างจากไฟล์ # ทั้งหมดอยู่ใกล้กันในทุกยุค นั่นอาจจะไม่เลวร้ายนักเนื่องจากไฟล์ # เป็นแบบสุ่ม ในบางกรณีแม้ว่าอาจทำให้การฝึกอบรมยุ่งเหยิงได้ วิธีเดียวที่จะได้รับการสับเปลี่ยนที่สมบูรณ์แบบคือการตั้งค่าbuffer_sizeให้เท่ากับขนาดไฟล์ (และแน่นอนว่าจะต้องสับเปลี่ยนไฟล์)
สูงสุด

Tensorflow RC 15.0.0.01.2018 ด้วยการdataset.shuffle(buffer_size=1)สับยังคงเกิดขึ้น ความคิดใด ๆ ?
Sergey

@SergeyBushmanov อาจขึ้นอยู่กับการเปลี่ยนแปลงก่อนการสุ่มของคุณเช่น list_files () ซึ่งจะสับเปลี่ยนชื่อไฟล์ในจุดเริ่มต้นของทุกยุคตามค่าเริ่มต้น
Xiaolong

130

ความสำคัญของbuffer_sizeในshuffle()

ผมอยากจะติดตามคำตอบก่อนหน้าจาก @mrry เพื่อเน้นความสำคัญของในbuffer_sizetf.data.Dataset.shuffle()

การมีคะแนนต่ำbuffer_sizeไม่เพียง แต่ทำให้คุณมีการสับไพ่ที่ด้อยกว่าในบางกรณีเท่านั้น แต่อาจทำให้การฝึกทั้งหมดของคุณยุ่ง


ตัวอย่างที่ใช้ได้จริง: cat ลักษณนาม

ตัวอย่างเช่นสมมติว่าคุณกำลังฝึกลักษณนามแมวบนรูปภาพและข้อมูลของคุณได้รับการจัดระเบียบตามวิธีต่อไปนี้ (พร้อม10000รูปภาพในแต่ละหมวดหมู่):

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

วิธีมาตรฐานในการป้อนข้อมูลด้วยtf.dataอาจมีรายการชื่อไฟล์และรายการป้ายกำกับที่เกี่ยวข้องและใช้tf.data.Dataset.from_tensor_slices()สร้างชุดข้อมูล:

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

ปัญหาใหญ่ที่มีรหัสดังกล่าวคือชุดข้อมูลจริงจะไม่ได้รับการสับในทางที่ถูก ประมาณครึ่งแรกของยุคเราจะเห็นเฉพาะรูปแมวและครึ่งหลังจะมี แต่รูปแมวเท่านั้น สิ่งนี้จะทำร้ายการฝึกมาก
ในช่วงเริ่มต้นของการฝึกอบรมชุดข้อมูลจะใช้1000ชื่อไฟล์แรกและใส่ไว้ในบัฟเฟอร์จากนั้นเลือกหนึ่งชื่อแบบสุ่ม เนื่องจาก1000ภาพแรกทั้งหมดเป็นภาพของแมวเราจะเลือกภาพแมวที่จุดเริ่มต้นเท่านั้น

การแก้ไขในที่นี้คือตรวจสอบให้แน่ใจว่าbuffer_sizeมีขนาดใหญ่กว่า20000หรือสับเปลี่ยนล่วงหน้าfilenamesและlabels(เห็นได้ชัดว่าดัชนีเดียวกัน)

เนื่องจากการจัดเก็บชื่อไฟล์และป้ายกำกับทั้งหมดในหน่วยความจำไม่ใช่ปัญหาเราจึงสามารถใช้buffer_size = len(filenames)เพื่อให้แน่ใจว่าทุกอย่างจะถูกสับเข้าด้วยกัน ตรวจสอบให้แน่ใจว่าได้โทรtf.data.Dataset.shuffle()ก่อนที่จะใช้การแปลงที่หนักหน่วง (เช่นการอ่านภาพการประมวลผลการจัดชุด ... )

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

Takeaway คือการตรวจสอบอีกครั้งเสมอว่าการสับจะทำอย่างไร วิธีที่ดีในการตรวจจับข้อผิดพลาดเหล่านี้อาจเป็นการวางแผนการกระจายของแบทช์เมื่อเวลาผ่านไป (ตรวจสอบให้แน่ใจว่าแบทช์มีการแจกแจงแบบเดียวกันกับชุดฝึกครึ่งแมวและแมวครึ่งตัวในตัวอย่างของเรา)


1
ตัวอย่างถัดไปจะถูกเลือกจากบัฟเฟอร์เสมอ (ขนาด 1000 ที่นี่) ดังนั้นตัวอย่างแรกจึงนำมาจากชื่อไฟล์ 1,000 ไฟล์แรก บัฟเฟอร์ลดลงเหลือขนาด 999 ดังนั้นจึงใช้อินพุตถัดไป ( filename_01001) และเพิ่มเข้าไป ตัวอย่างที่สองสุ่มจากชื่อไฟล์ 1,000 ชื่อเหล่านี้ (1001 ชื่อไฟล์แรกลบด้วยตัวอย่างแรก)
Olivier Moindrot

1
ปัญหาเกี่ยวกับขนาดบัฟเฟอร์ที่ต่ำนี้คือคุณจะมีแมวในแบทช์แรกเท่านั้น ดังนั้นแบบจำลองจะเรียนรู้การทำนายเฉพาะ "แมว" เพียงเล็กน้อย วิธีที่ดีที่สุดในการฝึกอบรมเครือข่ายคือการมีแบทช์ที่มี "cat" และ "non cat" ในปริมาณเท่ากัน
Olivier Moindrot

1
คุณสามารถใช้tf.summary.histogramเพื่อวางแผนการแจกจ่ายป้ายกำกับเมื่อเวลาผ่านไป
Olivier Moindrot

3
ไม่ใช่พิมพ์ผิด :) ชุดข้อมูลมีภาพ 10k ของแต่ละคลาสดังนั้นขนาดบัฟเฟอร์ทั้งหมดควรสูงกว่า 20k แต่ในตัวอย่างด้านบนฉันใช้ขนาดบัฟเฟอร์ 1k ซึ่งต่ำเกินไป
Olivier Moindrot

1
ใช่การตั้งค่าขนาดบัฟเฟอร์เป็นขนาดชุดข้อมูลโดยทั่วไปจะใช้ได้ดี สิ่งใดที่อยู่เหนือขนาดชุดข้อมูลก็จะไร้ประโยชน์อยู่ดี (และหากคุณไม่ทำซ้ำชุดข้อมูลก่อนที่จะสับบัฟเฟอร์จะต้องไม่ใหญ่กว่าชุดข้อมูล)
Olivier Moindrot

7

รหัส

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

เอาต์พุต

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


2
สิ่งนี้บ่งชี้ว่าสำหรับทุกองค์ประกอบที่ได้รับจากตัววนซ้ำบัฟเฟอร์จะถูกเติมเต็มด้วยองค์ประกอบถัดไปของชุดข้อมูลที่ไม่ได้อยู่ในบัฟเฟอร์มาก่อน
Alex

2

จริงๆแล้วคำตอบของ @ olivier-moindrot ไม่ถูกต้อง

คุณสามารถตรวจสอบได้โดยสร้างชื่อไฟล์และป้ายกำกับตามที่เขาพูดถึงและพิมพ์ค่าแบบสุ่ม

คุณจะเห็นแต่ละขั้นตอนการสับเปลี่ยนจะสร้างตัวอย่างแบบสุ่มโดยมีขนาดเท่ากับขนาดบัฟเฟอร์จากชุดข้อมูล

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

2

ฉันพบว่า @ olivier-moindrot ถูกต้องจริงๆฉันลองใช้รหัสที่ @Houtarou Oreki ให้มาโดยใช้การปรับเปลี่ยนที่ @max ชี้ รหัสที่ฉันใช้มีดังต่อไปนี้:

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

เอาท์พุทรหัสเป็นจริงจำนวนตั้งแต่ 1 ถึง (buffer_size + (i * batch_size)) ที่ฉันเป็นจำนวนครั้งที่คุณวิ่งnext_element ฉันคิดว่าวิธีการทำงานมีดังต่อไปนี้ ครั้งแรกbuffer_sizeตัวอย่างจะเลือกในการสั่งซื้อจากfake_data จากนั้นทีละตัวอย่างbatch_sizeจะถูกเลือกจากบัฟเฟอร์ แต่ละครั้งตัวอย่างชุดจะเลือกจากบัฟเฟอร์จะถูกแทนที่ด้วยใหม่, การดำเนินการในการสั่งซื้อจากfake_data ฉันทดสอบสิ่งสุดท้ายนี้โดยใช้รหัสต่อไปนี้:

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)

ค่าสูงสุดที่สร้างโดยรหัสคือ 109 ดังนั้นคุณต้องตรวจสอบตัวอย่างที่สมดุลภายในbatch_sizeของคุณเพื่อให้แน่ใจว่ามีการสุ่มตัวอย่างสม่ำเสมอในระหว่างการฝึก

ฉันยังทดสอบสิ่งที่ @mrry พูดเกี่ยวกับประสิทธิภาพฉันพบว่าbatch_sizeจะดึงตัวอย่างจำนวนนั้นไว้ในหน่วยความจำล่วงหน้า ฉันทดสอบโดยใช้รหัสต่อไปนี้:

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

การเปลี่ยนจำนวนdataset.prefetch (10)ทำให้ไม่มีการเปลี่ยนแปลงหน่วยความจำ (RAM) ที่ใช้ นี่เป็นสิ่งสำคัญเมื่อข้อมูลของคุณไม่พอดีกับแรม ผมคิดว่าวิธีที่ดีที่สุดคือการสับเปลี่ยนข้อมูลของคุณ / file_names ก่อนที่จะให้อาหารพวกเขาจะ tf.dataset แล้วควบคุมขนาดของบัฟเฟอร์ใช้buffer_size

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.