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
パラメーターはどのような役割を果たしていますか?
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
の場合、シャッフルはまったく行われません。非常に大きなデータセットの場合、典型的な「十分な」アプローチは、トレーニングの前にデータを複数のファイルにランダムに分割し、ファイル名を均一にシャッフルしてから、より小さいシャッフルバッファーを使用することです。ただし、適切な選択は、トレーニングジョブの正確な性質によって異なります。
shuffle()
のbuffer_size
の重要性@mrryの以前の回答をフォローアップして、 tf.data.Dataset.shuffle()
のbuffer_size
のimportanceを強調したかったのです。
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
よりも大きいことを確認するか、事前に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...
重要なことは、シャッフルが何をするかを常に再確認することです。これらのエラーをキャッチする良い方法は、バッチの分布を経時的にプロットすることです(この例では、バッチにトレーニングセットとほぼ同じ分布、半分の猫と半分の非猫が含まれていることを確認してください)。
@ 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))の範囲の数値でした。ここで、iはnext_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を使用してバッファサイズを制御します。
コード
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]
実際、@ 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))