ホームページ  >  記事  >  テクノロジー周辺機器  >  Ray を使用して効率的な深層学習データ パイプラインを作成する

Ray を使用して効率的な深層学習データ パイプラインを作成する

WBOY
WBOY転載
2023-11-02 20:17:15759ブラウズ

ディープ ラーニング モデルのトレーニングに必要な GPU は強力ですが、高価です。 GPU を最大限に活用するには、開発者は、次のトレーニング ステップを計算する準備ができたときに GPU にデータを迅速に転送できる効率的なデータ転送チャネルが必要です。 Ray を使用すると、データ送信チャネルの効率を大幅に向上させることができます。

1. トレーニング データ パイプラインの構造

最初に、モデル トレーニングの疑似コードを見てみましょう

for step in range(num_steps):sample, target = next(dataset) # 步骤1train_step(sample, target) # 步骤2

ステップ 1 では、次のミニバッチのサンプルとラベルを取得します。ステップ 2 では、それらが train_step 関数に渡され、関数がそれらを GPU にコピーし、前方パスと後方パスを実行して損失と勾配を計算し、オプティマイザーの重みを更新します。

ステップ 1 について詳しくは、こちらをご覧ください。データ セットが大きすぎてメモリに収まらない場合、ステップ 1 で次のミニバッチがディスクまたはネットワークからフェッチされます。さらに、ステップ 1 には、ある程度の前処理も含まれます。入力データは、モデルに供給される前に、数値テンソルまたはテンソルのコレクションに変換する必要があります。場合によっては、モデルに渡される前に、正規化、軸を中心とした回転、ランダム シャッフルなどの他の変換もテンソルに対して実行されます。

ワークフローが厳密に順番に実行される場合、つまり、最初にステップ 1 を実行し、次にステップ 2 を実行する場合、モデルは常にデータの入力、出力、および前処理操作の次のバッチを待機する必要があります。 GPU は効率的に利用されず、次のデータのミニバッチをロードしている間はアイドル状態になります。

この問題を解決するには、データ パイプラインを生産者と消費者の問題として見ることができます。データ パイプラインはデータの小さなバッチを生成し、それらを境界付きバッファーに書き込みます。モデル/GPU はバッファーからのデータの小さなバッチを消費し、順方向/逆方向の計算を実行し、モデルの重みを更新します。データ パイプラインがモデル/GPU の消費と同じくらい早くデータの小さなバッチを生成できる場合、トレーニング プロセスは非常に効率的になります。

Ray を使用して効率的な深層学習データ パイプラインを作成する

2. Tensorflow tf.data API

Tensorflow tf.data API は、使用できる豊富な関数セットを提供します。データ パイプラインを効率的に作成し、バックグラウンド スレッドを使用してデータの小さなバッチを取得することで、モデルが待機する必要がなくなります。データをプリフェッチするだけでは不十分です。小さなデータ バッチの生成が GPU がデータを消費できるよりも遅い場合は、並列化を使用してデータの読み取りと変換を高速化する必要があります。この目的を達成するために、Tensorflow は、複数のスレッドを利用してデータを並列に読み取るインターリーブ機能と、複数のスレッドを使用してデータの小さなバッチを変換する並列マッピング機能を提供します。

これらの API はマルチスレッドに基づいているため、Python Global Interpreter Lock (GIL) によって制限される可能性があります。 Python の GIL は、バイトコードを一度に実行できるスレッドを 1 つだけに制限します。パイプラインで純粋な TensorFlow コードを使用する場合、TensorFlow コア実行エンジンは GIL の範囲外で動作するため、通常はこの制限の影響を受けません。ただし、使用されているサードパーティ ライブラリが GIL 制限を解除していない場合、または Python を使用して多数の計算を実行している場合、マルチスレッドに依存してパイプラインを並列化することは現実的ではありません

3. マルチプロセスを使用します。データ パイプラインの並列化

データ サンプルとラベルのミニバッチを生成するためのいくつかの計算のロードと実行をシミュレートする次のジェネレーター関数を考えてみましょう。

def data_generator():for _ in range(10):# 模拟获取# 从磁盘/网络time.sleep(0.5)# 模拟计算for _ in range(10000):passyield (np.random.random((4, 1000000, 3)).astype(np.float32), np.random.random((4, 1)).astype(np.float32))

次に、ダミー トレーニング パイプラインでジェネレーターを使用し、データのミニバッチの生成にかかる平均時間を測定します。

generator_dataset = tf.data.Dataset.from_generator(data_generator,output_types=(tf.float64, tf.float64),output_shapes=((4, 1000000, 3), (4, 1))).prefetch(tf.data.experimental.AUTOTUNE)st = time.perf_counter()times = []for _ in generator_dataset:en = time.perf_counter()times.append(en - st)# 模拟训练步骤time.sleep(0.1)st = time.perf_counter()print(np.mean(times))

平均所要時間は約 0.57 秒であることが観察されました (Intel Core i7 プロセッサを搭載した Mac ラップトップで測定)。これが実際のトレーニング ループである場合、GPU 使用率は非常に低くなり、計算に費やすのは 0.1 秒のみで、その後、次のデータ バッチを待機する 0.57 秒間アイドル状態になります。

データの読み込みを高速化するために、マルチプロセス ジェネレーターを使用できます。

from multiprocessing import Queue, cpu_count, Processdef mp_data_generator():def producer(q):for _ in range(10):# 模拟获取# 从磁盘/网络time.sleep(0.5)# 模拟计算for _ in range(10000000):passq.put((np.random.random((4, 1000000, 3)).astype(np.float32),np.random.random((4, 1)).astype(np.float32)))q.put("DONE")queue = Queue(cpu_count()*2)num_parallel_processes = cpu_count()producers = []for _ in range(num_parallel_processes):p = Process(target=producer, args=(queue,))p.start()producers.append(p)done_counts = 0while done_counts <p>ここで、次のデータのミニバッチを待機するのにかかる時間を測定すると、平均時間は 0.08 秒になります。ほぼ 7 倍速くなりますが、理想的にはこの時間を 0 に近づけたいと考えています。 </p><p>これを分析すると、データの逆シリアル化の準備に多くの時間が費やされていることがわかります。マルチプロセス ジェネレーターでは、プロデューサー プロセスは大きな NumPy 配列を返します。これは、メイン プロセスで準備してから逆シリアル化する必要があります。では、プロセス間で大きな配列を渡すときの効率を向上させるにはどうすればよいでしょうか? </p><h2>4. Ray を使用してデータ パイプラインを並列化する</h2><p>ここで Ray が活躍します。 Ray は、Python で分散コンピューティングを実行するためのフレームワークです。異なるプロセス間でオブジェクトを効率的に転送するための共有メモリ オブジェクト ストアが付属しています。特に、オブジェクト ストア内の Numpy 配列は、シリアル化や逆シリアル化を行わずに、同じノード上のワーカー間で共有できます。また、Ray を使用すると、複数のマシンにまたがるデータの読み込みを簡単に拡張でき、Apache Arrow を使用して大規模な配列を効率的にシリアル化および逆シリアル化することもできます。 </p><p>Ray には、並列反復子を作成できるユーティリティ関数 from_iterators が付属しており、開発者はそれを使用して data_generator ジェネレーター関数をラップできます。 </p><pre class="brush:php;toolbar:false">import raydef ray_generator():num_parallel_processes = cpu_count()return ray.util.iter.from_iterators([data_generator]*num_parallel_processes).gather_async()

ray_generator を使用すると、次のデータのミニバッチの待機に費やされる測定時間は 0.02 秒で、これはマルチプロセス処理を使用する場合より 4 倍高速です。

以上がRay を使用して効率的な深層学習データ パイプラインを作成するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事は51cto.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。