Ý nghĩa của buffer_size trong Dataset.map, Dataset.prefetch và Dataset.shuffle


100

Theo TensorFlow tài liệu hướng dẫn , các prefetchmapphương pháp của tf.contrib.data.Datasetlớp, cả hai đều có một tham số gọi buffer_size.

Đối với prefetchphương thức, tham số được gọi là buffer_sizevà theo tài liệu:

buffer_size: Một tf.Tensor vô hướng tf.int64, đại diện cho số phần tử tối đa sẽ được lưu vào bộ đệm khi tìm nạp trước.

Đối với mapphương thức, tham số được gọi là output_buffer_sizevà theo tài liệu:

output_buffer_size: (Tùy chọn.) Một tf.Tensor vô hướng tf.int64, đại diện cho số lượng phần tử được xử lý tối đa sẽ được lưu vào bộ đệm.

Tương tự đối với shufflephương pháp, cùng một số lượng xuất hiện và theo tài liệu:

buffer_size: Một tf.Tensor vô hướng tf.int64, đại diện cho số phần tử từ tập dữ liệu này mà từ đó tập dữ liệu mới sẽ lấy mẫu.

Mối quan hệ giữa các tham số này là gì?

Giả sử tôi tạo một Datasetđối tượng như sau:

 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)

Các bufferthông số trong đoạn mã trên đang đóng vai trò gì ?


1
Không tìm thấy liên kết 404 tới "tài liệu".
Pradeep Singh

Câu trả lời:


150

TL; DR Mặc dù có tên giống nhau, nhưng các đối số này có ý nghĩa khác nhau. Nội dung buffer_sizetrong Dataset.shuffle()có thể ảnh hưởng đến tính ngẫu nhiên của tập dữ liệu của bạn và do đó, thứ tự các phần tử được tạo ra. Các buffer_sizetrong Dataset.prefetch()chỉ ảnh hưởng đến thời gian cần thiết để sản xuất các phần tử tiếp theo.


Đối buffer_sizesố trong tf.data.Dataset.prefetch()output_buffer_sizeđối số trong tf.contrib.data.Dataset.map()cung cấp một cách để điều chỉnh hiệu suất của đường dẫn đầu vào của bạn: cả hai đối số đều yêu cầu TensorFlow tạo bộ đệm của hầu hết buffer_sizecác phần tử và một luồng nền để lấp đầy bộ đệm đó trong nền. (Lưu ý rằng chúng tôi đã xóa output_buffer_sizeđối số từ Dataset.map()khi nó chuyển từ tf.contrib.datasang tf.data. Mã mới sẽ sử dụngDataset.prefetch() sau map()để có cùng hành vi.)

Thêm bộ đệm tìm nạp trước có thể cải thiện hiệu suất bằng cách chồng chéo quá trình xử lý trước dữ liệu với tính toán xuôi dòng. Thông thường, hữu ích nhất là thêm một bộ đệm tìm nạp trước nhỏ (có lẽ chỉ với một phần tử duy nhất) vào cuối đường ống, nhưng các đường ống phức tạp hơn có thể được hưởng lợi từ việc tìm nạp trước bổ sung, đặc biệt khi thời gian để tạo ra một phần tử duy nhất có thể thay đổi.

Ngược lại, buffer_sizeđối số tf.data.Dataset.shuffle()ảnh hưởng đến tính ngẫu nhiên của phép biến đổi. Chúng tôi đã thiết kế Dataset.shuffle()phép chuyển đổi (giống như tf.train.shuffle_batch()hàm mà nó thay thế) để xử lý các tập dữ liệu quá lớn để vừa với bộ nhớ. Thay vì xáo trộn toàn bộ tập dữ liệu, nó duy trì một bộ đệm các buffer_sizephần tử và chọn ngẫu nhiên phần tử tiếp theo từ bộ đệm đó (thay thế nó bằng phần tử đầu vào tiếp theo, nếu có). Việc thay đổi giá trị của buffer_sizeảnh hưởng đến mức độ đồng nhất của xáo trộn: nếu buffer_sizelớn hơn số phần tử trong tập dữ liệu, bạn sẽ nhận được xáo trộn đồng nhất; nếu nó là1sau đó bạn không có xáo trộn ở tất cả. Đối với các tập dữ liệu rất lớn, một cách tiếp cận "đủ tốt" điển hình là chia ngẫu nhiên dữ liệu thành nhiều tệp một lần trước khi huấn luyện, sau đó xáo trộn các tên tệp một cách thống nhất và sau đó sử dụng một bộ đệm trộn nhỏ hơn. Tuy nhiên, sự lựa chọn phù hợp sẽ phụ thuộc vào tính chất chính xác của công việc đào tạo của bạn.



Đối với lời giải thích này, tôi vẫn có một số nhầm lẫn wrt tf.data.Dataset.shuffle(). Tôi muốn biết chính xác quá trình xáo trộn. Giả sử, các batch_sizemẫu đầu tiên được chọn ngẫu nhiên từ các buffer_sizephần tử đầu tiên , v.v.
Bs He

1
@mrry IIUC xáo trộn tên tệp là quan trọng vì nếu không, mỗi kỷ nguyên sẽ thấy cùng một phần tử trong các lô 0 ... 999; và theo lô 1000.1999; vv, trong đó tôi giả sử 1 tệp = 1000 lô. Ngay cả khi xáo trộn tên tệp, vẫn có một số điểm không ngẫu nhiên: đó là bởi vì các ví dụ từ tệp #k đều gần nhau trong mọi kỷ nguyên. Điều đó có thể không quá tệ vì bản thân tệp #k là ngẫu nhiên; vẫn còn trong một số trường hợp, thậm chí điều đó có thể làm rối loạn quá trình đào tạo. Cách duy nhất để có được xáo trộn hoàn hảo là đặt buffer_sizekích thước tệp bằng nhau (và tất nhiên là xáo trộn tệp).
tối đa

Tensorflow rc 15.0. Với việc dataset.shuffle(buffer_size=1)xáo trộn vẫn xảy ra. Có suy nghĩ gì không?
Sergey Bushmanov

@SergeyBushmanov nó có thể phụ thuộc vào quá trình chuyển đổi trước khi xáo trộn của bạn, ví dụ: list_files (), theo mặc định, sẽ xáo trộn các tên tệp vào đầu mỗi kỷ nguyên.
Xiaolong

129

Tầm quan trọng của buffer_sizetrongshuffle()

Tôi muốn theo dõi câu trả lời trước từ @mrry để nhấn mạnh tầm quan trọng của buffer_sizetrong tf.data.Dataset.shuffle().

Có một điểm thấp buffer_sizesẽ không chỉ khiến bạn xáo trộn kém hơn trong một số trường hợp: nó có thể làm rối loạn cả quá trình luyện tập của bạn.


Một ví dụ thực tế: trình phân loại mèo

Ví dụ: giả sử bạn đang đào tạo một bộ phân loại mèo trên hình ảnh và dữ liệu của bạn được sắp xếp theo cách sau (với các 10000hình ảnh trong mỗi danh mục):

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

Một cách tiêu chuẩn để nhập dữ liệu có tf.datathể là có danh sách tên tệp và danh sách các nhãn tương ứng và sử dụng tf.data.Dataset.from_tensor_slices()để tạo tập dữ liệu:

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

Các vấn đề lớn với các mã trên là các tập dữ liệu sẽ thực sự không được xáo trộn một cách đúng đắn. Trong khoảng nửa đầu của kỷ nguyên, chúng ta sẽ chỉ thấy hình ảnh mèo và trong nửa sau chỉ có hình ảnh không phải mèo. Điều này sẽ ảnh hưởng đến việc tập luyện rất nhiều.
Khi bắt đầu đào tạo, tập dữ liệu sẽ lấy 1000tên tệp đầu tiên và đặt chúng vào bộ đệm của nó, sau đó chọn ngẫu nhiên một trong số chúng. Vì tất cả các 1000hình ảnh đầu tiên là hình ảnh con mèo, chúng tôi sẽ chỉ chọn hình ảnh con mèo ở phần đầu.

Cách khắc phục ở đây là đảm bảo rằng buffer_sizenó lớn hơn 20000, hoặc xáo trộn trước filenameslabels(rõ ràng là với các chỉ số giống nhau).

Vì việc lưu trữ tất cả các tên tệp và nhãn trong bộ nhớ không phải là một vấn đề, chúng tôi thực sự có thể sử dụng buffer_size = len(filenames)để đảm bảo rằng mọi thứ sẽ được trộn cùng nhau. Hãy nhớ gọi tf.data.Dataset.shuffle()trước khi áp dụng các phép biến đổi nặng (như đọc ảnh, xử lý chúng, chia lô ...).

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

Bài học rút ra là luôn kiểm tra kỹ xem việc xáo trộn sẽ làm gì. Một cách tốt để nắm bắt những lỗi này có thể là vẽ biểu đồ phân phối các lô theo thời gian (đảm bảo rằng các lô có cùng phân phối với tập huấn luyện, nửa mèo và nửa không mèo trong ví dụ của chúng tôi).


1
Mẫu tiếp theo luôn được chọn từ bộ đệm (có kích thước 1000 tại đây). Vì vậy, mẫu đầu tiên được lấy từ 1000 tên tệp đầu tiên. Bộ đệm giảm xuống kích thước 999, vì vậy nó lấy đầu vào tiếp theo ( filename_01001) và thêm nó. Mẫu thứ hai được lấy ngẫu nhiên từ 1000 tên tệp này (1001 tên tệp đầu tiên trừ đi mẫu đầu tiên).
Olivier Moindrot

1
Vấn đề với kích thước bộ đệm thấp này là bạn sẽ chỉ có mèo trong các lô đầu tiên của mình. Vì vậy, mô hình sẽ học cách dự đoán chỉ "mèo" một cách tầm thường. Cách tốt nhất để huấn luyện mạng là có các lô với cùng số lượng "mèo" và "không mèo".
Olivier Moindrot

1
Bạn có thể sử dụng tf.summary.histogramđể vẽ biểu đồ phân bố các nhãn theo thời gian.
Olivier Moindrot

3
Không phải lỗi đánh máy :) Tập dữ liệu có 10k hình ảnh của mỗi lớp nên tổng dung lượng bộ đệm phải trên 20k. Nhưng trong ví dụ trên, tôi lấy kích thước bộ đệm là 1k, quá thấp.
Olivier Moindrot

1
Có, thiết lập kích thước bộ đệm thành kích thước tập dữ liệu nói chung là tốt. Dù sao thì bất kỳ thứ gì cao hơn kích thước tập dữ liệu sẽ vô dụng (và trừ khi bạn lặp lại tập dữ liệu của mình trước khi xáo trộn, bộ đệm không được lớn hơn tập dữ liệu).
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()

Đầu ra

[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
Điều này chỉ ra rằng đối với mọi phần tử được tạo bởi trình lặp, bộ đệm đang được lấp đầy bằng phần tử tiếp theo tương ứng của tập dữ liệu mà trước đó không có trong bộ đệm.
Alex

2

Thực ra câu trả lời của @ olivier-moindrot là không đúng.

Bạn có thể xác minh điều đó bằng cách tạo tên tệp và nhãn khi anh ấy / cô ấy đề cập và in các giá trị xáo trộn.

Bạn sẽ thấy mỗi thủ tục xáo trộn sẽ tạo mẫu ngẫu nhiên với kích thước bằng với kích thước bộ đệm từ tập dữ liệu.

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

Tôi thấy rằng @ olivier-moindrot thực sự là chính xác, tôi đã thử mã do @Houtarou Oreki cung cấp, sử dụng các sửa đổi được chỉ ra bởi @max. Mã tôi đã sử dụng như sau:

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

Đầu ra mã thực sự là một số từ 1 đến (buffer_size + (i * batch_size)), trong đó tôi là số lần bạn chạy next_element . Tôi nghĩ cách nó đang hoạt động như sau. Đầu tiên, các mẫu buffer_size được chọn theo thứ tự từ fake_data . Sau đó, lần lượt các mẫu batch_size được chọn từ bộ đệm. Mỗi khi chọn một mẫu hàng loạt từ bộ đệm, nó sẽ được thay thế bằng một mẫu mới, được lấy theo thứ tự từ fake_data . Tôi đã kiểm tra điều cuối cùng này bằng cách sử dụng mã sau:

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)

Giá trị tối đa do mã tạo ra là 109. Vì vậy, bạn cần đảm bảo một mẫu cân bằng trong batch_size của mình để đảm bảo lấy mẫu đồng nhất trong quá trình đào tạo.

Tôi cũng đã thử nghiệm những gì @mrry nói về hiệu suất, tôi thấy rằng batch_size sẽ tìm nạp trước lượng mẫu đó vào bộ nhớ. Tôi đã kiểm tra điều này bằng cách sử dụng mã sau:

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

Thay đổi số lượng dataset.prefetch (10) dẫn đến không thay đổi bộ nhớ (RAM) được sử dụng. Điều này rất quan trọng khi dữ liệu của bạn không vừa với RAM. Tôi nghĩ cách tốt nhất là xáo trộn dữ liệu / file_names của bạn trước khi đưa chúng vào tf.dataset, sau đó kiểm soát kích thước bộ đệm bằng cách sử dụng buffer_size .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.