web-dev-qa-db-ja.com

Dataset.map、Dataset.prefetchおよびDataset.shuffleのbuffer_sizeの意味

TensorFlow documentation によると、tf.contrib.data.Datasetクラスのprefetchおよびmapメソッドには、両方ともbuffer_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。新しいデータセットがサンプリングするこのデータセットの要素の数を表します。

これらのパラメーターの関係は何ですか?

次のようにaDatasetオブジェクトを作成するとします。

 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パラメーターはどのような役割を果たしていますか?

63
Ujjwal

TL; DR名前が似ているにもかかわらず、これらの引数の意味はまったく異なります。 Dataset.shuffle()buffer_sizeは、データセットのランダム性に影響を与える可能性があるため、要素が生成される順序に影響します。 Dataset.prefetch()buffer_sizeは、次の要素を生成するのにかかる時間にのみ影響します。


tf.data.Dataset.prefetch()buffer_size引数と tf.contrib.data.Dataset.map()output_buffer_size引数は、パフォーマンスを調整する方法を提供します入力パイプラインの:両方の引数は、TensorFlowに最大でbuffer_size要素のバッファーを作成するように指示し、バックグラウンドでそのバッファーを埋めるためのバックグラウンドスレッドを指示します。 (output_buffer_sizeからtf.contrib.dataに移動したときにDataset.map()からtf.data引数を削除したことに注意してください。同じ動作を得るには、新しいコードでDataset.prefetch()の後にmap()を使用する必要があります。)

プリフェッチバッファーを追加すると、データの前処理とダウンストリームの計算を重複させることでパフォーマンスを向上させることができます。通常、パイプラインの最後に小さなプリフェッチバッファー(おそらく単一の要素のみ)を追加するのが最も便利ですが、特に単一の要素を生成する時間が変化する可能性がある場合、より複雑なパイプラインは追加のプリフェッチの恩恵を受けることができます。

対照的に、 tf.data.Dataset.shuffle()buffer_size引数は、変換のrandomnessに影響します。 Dataset.shuffle()変換(置き換えられる tf.train.shuffle_batch() 関数のような)は、大きすぎてメモリに収まらないデータセットを処理するように設計されています。データセット全体をシャッフルする代わりに、buffer_size要素のバッファーを維持し、そのバッファーから次の要素をランダムに選択します(利用可能な場合は、次の入力要素に置き換えます)。 buffer_sizeの値を変更すると、シャッフルの均一性に影響します。buffer_sizeがデータセット内の要素の数より大きい場合、均一なシャッフルが得られます。 1の場合、シャッフルはまったく行われません。非常に大きなデータセットの場合、典型的な「十分な」アプローチは、トレーニングの前にデータを複数のファイルにランダムに分割し、ファイル名を均一にシャッフルしてから、より小さいシャッフルバッファーを使用することです。ただし、適切な選択は、トレーニングジョブの正確な性質によって異なります。


97
mrry

shuffle()buffer_sizeの重要性

@mrryの以前の回答をフォローアップして、 tf.data.Dataset.shuffle()buffer_sizeimportanceを強調したかったのです。

buffer_sizeが低いと、場合によっては劣ったシャッフルが得られないだけでなく、トレーニング全体が台無しになります。


実用的な例:猫の分類器

たとえば、画像で猫の分類器をトレーニングしており、データが次のように整理されているとします(各カテゴリに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ファイル名を取得してバッファーに格納し、その中からランダムに1つを選択します。最初の1000画像はすべて猫の画像であるため、最初は猫の画像のみを選択します。

ここでの修正は、buffer_size20000よりも大きいことを確認するか、事前にfilenameslabelsをシャッフルすることです(明らかに同じインデックスを使用)。

すべてのファイル名とラベルをメモリに保存することは問題ではないため、実際に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...

重要なことは、シャッフルが何をするかを常に再確認することです。これらのエラーをキャッチする良い方法は、バッチの分布を経時的にプロットすることです(この例では、バッチにトレーニングセットとほぼ同じ分布、半分の猫と半分の非猫が含まれていることを確認してください)。

84

@ olivier-moindrotは確かに正しいことがわかりました。@ maxが指す変更を使用して、@ Houtarou Orekiが提供するコードを試しました。使用したコードは次のとおりです。

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))の範囲の数値でした。ここで、inext_elementを実行した回数です。私はそれが働いている方法は次のようだと思います。まず、buffer_sizeサンプルがfake_dataから順番に選択されます。次に、バッファからbatch_sizeサンプルが1つずつ選択されます。バッファからバッチサンプルが選択されるたびに、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)は変更されませんでした。これは、データがRAMに収まらない場合に重要です。データ/ファイル名をシャッフルしてからtf.datasetに送るのが最善の方法だと思います。そしてbuffer_sizeを使用してバッファサイズを制御します。

2
Ramiro R.C.

コード

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
Vladimir

実際、@ 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))
1
Isaac Cheng