ディープラーニングモデルの入力として大きなHDF5ファイルを読み取るためのTensorFlowパイプラインを設定しています。各HDF5ファイルには、圧縮されたJPGイメージのコレクションとして保存された可変サイズの長さのビデオが100個含まれています(ディスク上のサイズを管理しやすくするため)。 _tf.data.Dataset
_と_tf.py_func
_へのマップを使用すると、カスタムPythonロジックを使用してHDF5ファイルからサンプルを読み取るのは非常に簡単です。例:
_def read_examples_hdf5(filename, label):
with h5py.File(filename, 'r') as hf:
# read frames from HDF5 and decode them from JPG
return frames, label
filenames = glob.glob(os.path.join(hdf5_data_path, "*.h5"))
labels = [0]*len(filenames) # ... can we do this more elegantly?
dataset = tf.data.Dataset.from_tensor_slices((filenames, labels))
dataset = dataset.map(
lambda filename, label: Tuple(tf.py_func(
read_examples_hdf5, [filename, label], [tf.uint8, tf.int64]))
)
dataset = dataset.shuffle(1000 + 3 * BATCH_SIZE)
dataset = dataset.batch(BATCH_SIZE)
iterator = dataset.make_one_shot_iterator()
next_batch = iterator.get_next()
_
この例は機能しますが、問題は_tf.py_func
_が一度に1つの例をしか処理できないように見えることです。私のHDF5コンテナには100個のサンプルが保存されているため、この制限により、ファイルを常に開いたり、読み取ったり、閉じたり、再度開いたりする必要があるため、大きなオーバーヘッドが発生します。 100個すべてのビデオの例をすべてデータセットオブジェクトに読み込んでから、次のHDF5ファイルに進むのがはるかに効率的です(複数のスレッドで、各スレッドが独自のHDF5ファイルのコレクションを処理することが望ましい)。
したがって、私が望むのは、バックグラウンドで実行され、HDF5ファイルからビデオフレームを読み取り、それらをJPGからデコードし、データセットオブジェクトにフィードするスレッドです。 _tf.data.Dataset
_パイプラインが導入される前は、これはRandomShuffleQueue
および_enqueue_many
_ opsを使用して非常に簡単でしたが、現在これを行うエレガントな方法(またはドキュメント)はないようです欠けています)。
私の目標を達成するための最良の方法は何かを知っていますか?また、tfrecord
ファイルを使用してパイプラインを調査(および実装)しましたが、tfrecord
ファイルに格納されたビデオフレームのランダムサンプルを取得することはまったく不可能に思えます( here を参照) =)。また、_tf.data.Dataset
_のfrom_generator()
入力を調べましたが、複数のスレッドで実行されることはないようです。どんな提案も歓迎です。
同様の問題に対処しながら、私はこの質問に出くわしました。 PythonジェネレーターとTFデータセット構築メソッド from_generator
の使用に基づくソリューションを思い付きました。ジェネレーターを使用するため、HDF5ファイルは一度だけ読み取り用に開き、読み取るエントリがある限り開いたままにしておく必要がありますので、次のデータ要素を取得するための呼び出しごとに開いたり、読み取ったり、閉じたりすることはありません。
ユーザーがHDF5ファイル名を引数として渡すことができるようにするために、__call__
はジェネレーターを呼び出し可能にする必要があることを指定しているため、from_generator
メソッドを持つクラスを生成しました。これはジェネレータです:
import h5py
import tensorflow as tf
class generator:
def __init__(self, file):
self.file = file
def __call__(self):
with h5py.File(self.file, 'r') as hf:
for im in hf["train_img"]:
yield im
ジェネレーターを使用することで、コードは最初からすべてを実行するのではなく、最後に結果を返したときの各呼び出しで中断した場所から再開する必要があります。この場合、内側のfor
ループの次の反復で行われます。したがって、これは、読み取りのためにファイルを再度開くことをスキップし、yield
へのデータがある限り開いたままにします。ジェネレーターの詳細については、 この優れたQ&A を参照してください。
もちろん、with
ブロック内のすべてのものを置き換えて、データセットの構築方法と取得する出力を一致させる必要があります。
ds = tf.data.Dataset.from_generator(
generator(hdf5_path),
tf.uint8,
tf.TensorShape([427,561,3]))
value = ds.make_one_shot_iterator().get_next()
# Example on how to read elements
while True:
try:
data = sess.run(value)
print(data.shape)
except tf.errors.OutOfRangeError:
print('done.')
break
繰り返しますが、私の場合、データセットにuint8
画像の高さ427
、幅561
、および3
のカラーチャンネルを保存しているので、これらを変更する必要があります上記の呼び出しを使用例に合わせてください。
複数のHDF5ファイルを処理するための提案されたソリューションがあります。基本的な考え方は、通常どおりファイル名からDataset
を構築し、 interleave
メソッドを使用して多くの入力ファイルを同時に処理し、それぞれからサンプルを取得することです。たとえば、バッチを作成します。
アイデアは次のとおりです。
ds = tf.data.Dataset.from_tensor_slices(filenames)
# You might want to shuffle() the filenames here depending on the application
ds = ds.interleave(lambda filename: tf.data.Dataset.from_generator(
generator(filename),
tf.uint8,
tf.TensorShape([427,561,3])),
cycle_length, block_length)
これはcycle_length
ファイルを同時に開き、次のファイルに移動する前にそれぞれからblock_length
アイテムを生成します-詳細についてはinterleave
ドキュメントを参照してください。ここで値を設定して、アプリケーションに適したものに一致させることができます。たとえば、一度に1つのファイルを処理する必要があるか、複数のファイルを同時に処理する必要があるか、各ファイルから一度に1つのサンプルのみを取得する必要があるかなどです。 。
Edit:並列バージョンの場合、 tf.contrib.data.parallel_interleave
!
ソリューションを使用する場合は、from_generator
を使用することの特性に注意してください。 Tensorflow 1.6.0の場合、 from_generator
のドキュメント はこれら2つの注意事項に言及しています。
これを異なる環境に適用したり、分散トレーニングを適用したりするのは難しい場合があります。
注:Dataset.from_generator()の現在の実装はtf.py_funcを使用し、同じ制約を継承します。特に、Dataset.from_generator()を呼び出したPythonプログラムと同じプロセスで、DatasetおよびIterator関連の操作をデバイスに配置する必要があります。ジェネレーターの本体はGraphDefでシリアル化されます。モデルをシリアル化して別の環境で復元する必要がある場合は、このメソッドを使用しないでください。
ジェネレータが外部状態に依存する場合は注意してください:
注:ジェネレーターが可変グローバル変数またはその他の外部状態に依存する場合、ランタイムはジェネレーターを複数回(Datasetの繰り返しをサポートするために)呼び出し、Dataset.from_generator()の呼び出しとジェネレータの最初の要素。グローバル変数または外部状態を変更すると、未定義の動作が発生する可能性があります。Dataset.from_generator()を呼び出す前に、ジェネレーターで外部状態を明示的にキャッシュすることをお勧めします。
これを理解するのに少し時間がかかったので、ここに記録する必要があると思いました。 mikkolaの答えに基づいて、これは複数のファイルを処理する方法です:
import h5py
import tensorflow as tf
class generator:
def __call__(self, file):
with h5py.File(file, 'r') as hf:
for im in hf["train_img"]:
yield im
ds = tf.data.Dataset.from_tensor_slices(filenames)
ds = ds.interleave(lambda filename: tf.data.Dataset.from_generator(
generator(),
tf.uint8,
tf.TensorShape([427,561,3]),
args=(filename,)),
cycle_length, block_length)
重要なのは、filename
であるため、generator
をTensor
に直接渡すことができないことです。 args
を介して渡す必要があり、テンソルフローはそれを評価して通常のpython変数に変換します。