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


94

TensorFlowのドキュメントprefetchおよびクラスのmapメソッドにはtf.contrib.data.Dataset、両方ともというパラメータがあります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。

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

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リンクが見つかりません。
プラディープシン

回答:


145

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.datatf.data。新しいコードを使用する必要がありますDataset.prefetch()後にmap()同じ動作を取得すること。)

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

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



この説明のために、私にはまだいくつかの混乱がありtf.data.Dataset.shuffle()ます。シャッフルの正確なプロセスを教えてください。たとえば、最初のbatch_sizeサンプルは最初のbuffer_size要素からランダムに選択され、以下同様に続きます。
Bs He

1
@mrry IIUCファイル名をシャッフルすることは重要です。そうしないと、各エポックがバッチ0〜999で同じ要素を参照するためです。バッチで1000.1999; など、1ファイル= 1000バッチと想定しています。ファイル名のシャッフルを行っても、いくつかの非ランダム性があります。それは、ファイル#kの例がすべてのエポックですべて互いに近いためです。ファイル#k自体はランダムなので、それはそれほど悪くないかもしれません。まだいくつかのケースでは、それでもトレーニングを台無しにする可能性があります。完璧なシャッフルを取得する唯一の方法buffer_sizeは、ファイルサイズを等しく設定することです(もちろんファイルをシャッフルします)。
最大

Tensorflow rc 15.0。dataset.shuffle(buffer_size=1)シャッフル依然として発生します。何かご意見は?
Sergey Bushmanov

@SergeyBushmanovそれはあなたのシャッフル前の変換に依存するかもしれません、例えばlist_files()はデフォルトですべてのエポックの始めにファイル名をシャッフルします。
小龍

125

重要性buffer_sizeshuffle()

@mrryからの以前の回答をフォローアップして、in の重要性を強調しbuffer_sizeましたtf.data.Dataset.shuffle()

ロー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_sizeは、がより大きいことを確認するか20000、事前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...

要点は、シャッフルが何をするかを常にダブルチェックすることです。これらのエラーをキャッチするための良い方法は、時間の経過に伴うバッチの分布をプロットすることです(バッチにトレーニングセットとほぼ同じ分布が含まれていることを確認してください。この例では、猫の半分と猫以外の半分)。


1
次のサンプルは常にバッファ(ここではサイズ1000)から選択されます。したがって、最初のサンプルは最初の1000個のファイル名から取得されます。バッファーのサイズは999に減少するため、次の入力(filename_01001)を受け取って追加します。2番目のサンプルは、これらの1000個のファイル名からランダムに取得されます(最初のファイル名から最初のサンプルを1001引いたもの)。
Olivier Moindrot 2018

1
この低いバッファサイズの問題は、最初のバッチに猫しかいないことです。したがって、モデルは「猫」のみを予測することを自明に学習します。ネットワークをトレーニングする最良の方法は、同じ量の「猫」と「猫以外」のバッチを用意することです。
Olivier Moindrot 2018

1
を使用tf.summary.histogramして、ラベルの分布を経時的にプロットできます。
Olivier Moindrot 2018

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
これは、イテレータによって生成されたすべての要素について、バッファが以前にバッファになかったデータセットのそれぞれの次の要素で満たされていることを示しています。
アレックス

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))の範囲の数値でした。ここで、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を使用してバッファサイズを制御するのが最善の方法だと思います。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.