web-dev-qa-db-ja.com

TensorFlow-大きなHDF5ファイルを読み取るtf.data.Dataset

ディープラーニングモデルの入力として大きな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()入力を調べましたが、複数のスレッドで実行されることはないようです。どんな提案も歓迎です。

17
verified.human

同様の問題に対処しながら、私はこの質問に出くわしました。 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()を呼び出す前に、ジェネレーターで外部状態を明示的にキャッシュすることをお勧めします。

16
mikkola

これを理解するのに少し時間がかかったので、ここに記録する必要があると思いました。 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であるため、generatorTensorに直接渡すことができないことです。 argsを介して渡す必要があり、テンソルフローはそれを評価して通常のpython変数に変換します。

4
Rong Ou