web-dev-qa-db-ja.com

GPUが存在する場合、TensorFlowで単一のスクリプトで複数のモデルをどのようにトレーニングしますか?

私は単一のマシンでいくつかのGPUにアクセスできると仮定します(議論のために、それぞれのRAMおよびディスク)の1つのマシンでそれぞれ最大8GBのメモリを持つ8GPUを想定します。 1つのスクリプトで実行し、1つのマシンで、TensorFlowの複数のモデル(たとえば50または200)を評価するプログラムを実行し、それぞれが異なるハイパーパラメーター設定(たとえば、ステップサイズ、減衰レート、バッチサイズ、エポック/イテレーションなど)トレーニングの最後に、その精度を記録してモデルを取り除くと仮定します(モデルが頻繁にチェックポイントされていると仮定する場合は、スローするだけで構いません)また、特定のハイパーパラメータ、トレーニング、検証、トレーニング中にトレーニングエラーが記録されるなど、他のデータが記録されることもあります)。

現在、次のような(疑似)スクリプトがあります。

def train_multiple_modles_in_one_script_with_gpu(arg):
    '''
    trains multiple NN models in one session using GPUs correctly.

    arg = some obj/struct with the params for trianing each of the models.
    '''
    #### try mutliple models
    for mdl_id in range(100):
        #### define/create graph
        graph = tf.Graph()
        with graph.as_default():
            ### get mdl
            x = tf.placeholder(float_type, get_x_shape(arg), name='x-input')
            y_ = tf.placeholder(float_type, get_y_shape(arg))
            y = get_mdl(arg,x)
            ### get loss and accuracy
            loss, accuracy = get_accuracy_loss(arg,x,y,y_)
            ### get optimizer variables
            opt = get_optimizer(arg)
            train_step = opt.minimize(loss, global_step=global_step)
        #### run session
        with tf.Session(graph=graph) as sess:
            # train
            for i in range(nb_iterations):
                batch_xs, batch_ys = get_batch_feed(X_train, Y_train, batch_size)
                sess.run(fetches=train_step, feed_dict={x: batch_xs, y_: batch_ys})
                # check_point mdl
                if i % report_error_freq == 0:
                    sess.run(step.assign(i))
                    #
                    train_error = sess.run(fetches=loss, feed_dict={x: X_train, y_: Y_train})
                    test_error = sess.run(fetches=loss, feed_dict={x: X_test, y_: Y_test})
                    print( 'step %d, train error: %s test_error %s'%(i,train_error,test_error) )

基本的には、1回の実行で多数のモデルを試行しますが、各モデルを個別のグラフで作成し、個別のセッションで各モデルを実行します。

私の主な心配は、内部のテンソルフローが使用されるGPUのリソースをどのように割り当てるかがはっきりしないことです。たとえば、セッションの実行時にのみデータセット(の一部)をロードしますか?グラフとモデルを作成すると、すぐにGPUに取り込まれますか、それともいつGPUに挿入されますか? GPUが新しいモデルを試行するたびに、GPUをクリア/解放する必要がありますか?モデルが複数のGPUで並列に実行される場合(実際にはニースの追加になる可能性があります)、実際にはあまり気にしませんが、最初にすべてをクラッシュせずにシリアルで実行したいです。これが機能するために私がする必要がある特別なことはありますか?


現在、次のようなエラーが表示されています。

I tensorflow/core/common_runtime/bfc_allocator.cc:702] Stats:
Limit:                   340000768
InUse:                   336114944
MaxInUse:                339954944
NumAllocs:                      78
MaxAllocSize:            335665152

W tensorflow/core/common_runtime/bfc_allocator.cc:274] ***************************************************xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
W tensorflow/core/common_runtime/bfc_allocator.cc:275] Ran out of memory trying to allocate 160.22MiB.  See logs for memory state.
W tensorflow/core/framework/op_kernel.cc:975] Resource exhausted: OOM when allocating tensor with shape[60000,700]

そしてさらに、それが言う行の下:

ResourceExhaustedError (see above for traceback): OOM when allocating tensor with shape[60000,700]
         [[Node: standardNN/NNLayer1/Z1/add = Add[T=DT_FLOAT, _device="/job:localhost/replica:0/task:0/gpu:0"](standardNN/NNLayer1/Z1/MatMul, b1/read)]]

I tensorflow/core/common_runtime/gpu/gpu_device.cc:975] Creating TensorFlow device (/gpu:0) -> (device: 0, name: Tesla P100-SXM2-16GB, pci bus id: 0000:06:00.0)

ただし、出力ファイル(印刷する場所)のさらに下には、トレーニングの進行に伴って表示されるエラー/メッセージが細かく印刷されているようです。これは、リソースが不足しなかったことを意味しますか?それとも、実際にGPUを使用できましたか? CPUではなくCPUを使用できた場合、GPUが使用されようとしているときにエラーが発生するのはなぜですか?

奇妙なことに、データセットはそれほど大きくなく(60Kポイントはすべて24.5Mです)、自分のコンピューターで1つのモデルをローカルで実行すると、プロセスで使用されるのは5GB未満のようです。 GPUには少なくとも8GBがあり、それらを搭載したコンピューターには多くのRAMとディスク(少なくとも16GB)があります。したがって、tensorflowが私に投げかけているエラーは非常に不可解です。何をするのか、なぜ発生するのか?


マルチプロセッシングライブラリの使用を提案する回答を読んだ後、次のスクリプトを思い付きました。

def train_mdl(args):
    train(mdl,args)

if __name__ == '__main__':
    for mdl_id in range(100):
        # train one model with some specific hyperparms (assume they are chosen randomly inside the funciton bellow or read from a config file or they could just be passed or something)
        p = Process(target=train_mdl, args=(args,))
        p.start()
        p.join()
    print('Done training all models!')

正直なところ、彼の答えがプールを使用することを示唆している理由、または奇妙なタプル括弧がある理由はわかりませんが、これは私にとって理にかなっています。上記のループで新しいプロセスが作成されるたびに、テンソルフローのリソースが再割り当てされますか?

21
Charlie Parker

1つのスクリプトですべてのモデルを実行することは、長期的には悪い習慣になると思います(より良い代替策については、以下の私の提案を参照してください)。ただし、それを実行したい場合は、解決策があります:multiprocessingモジュールを使用してTFセッションをプロセスにカプセル化できます。これにより、プロセスが完了するとTFがセッションメモリを解放します。コードスニペットは次のとおりです。

_from multiprocessing import Pool
import contextlib
def my_model((param1, param2, param3)): # Note the extra (), required by the pool syntax
    < your code >

num_pool_worker=1 # can be bigger than 1, to enable parallel execution 
with contextlib.closing(Pool(num_pool_workers)) as po: # This ensures that the processes get closed once they are done
     pool_results = po.map_async(my_model,
                                    ((param1, param2, param3)
                                     for param1, param2, param3 in params_list))
     results_list = pool_results.get()
_

OPからの注意:乱数ジェネレータシードは、使用することを選択した場合、マルチプロセッシングライブラリでは自動的にリセットされません。詳細はこちら: pythonプロセスごとに異なるランダムシードを使用したマルチプロセッシング =

TFリソース割り当てについて:通常、TFは必要以上に多くのリソースを割り当てます。多くの場合、各プロセスが総GPUメモリの一部を使用するように制限し、スクリプトが必要とする割合を試行錯誤で発見できます。

次のスニペットでそれを行うことができます

_gpu_memory_fraction = 0.3 # Choose this number through trial and error
gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=gpu_memory_fraction,)
session_config = tf.ConfigProto(gpu_options=gpu_options)
sess = tf.Session(config=session_config, graph=graph)
_

TFは実行を高速化するためにメモリ使用量を増やすことがあることに注意してください。したがって、メモリ使用量を減らすと、モデルの実行が遅くなる可能性があります。

編集/コメントの新しい質問への回答:

  1. はい、Tensorflowは新しいプロセスが作成されるたびに再割り当てされ、プロセスが終了するとクリアされます。

  2. 編集中のforループも仕事をするはずです。代わりにPoolを使用することをお勧めします。1つのGPUで複数のモデルを同時に実行できるためです。 _gpu_memory_fraction_の設定と「プロセスの最大数の選択」に関するメモを参照してください。また、次の点にも注意してください。(1)プールマップはループを実行するため、一度使用すると外側のforループは不要です。 (2)あなたの例では、train()を呼び出す前にmdl=get_model(args)のようなものが必要です。

  3. 奇妙なタプル括弧:プールは単一の引数のみを受け入れるため、複数の引数を渡すためにタプルを使用します。詳細については、 multiprocessing.pool.mapと2つの引数を持つ関数 を参照してください。 1つの答えで示唆されているように、次のようにして読みやすくすることができます

    _def train_mdl(params):
        (x,y)=params
        < your code >
    _
  4. @Sevenが示唆したように、CUDA_VISIBLE_DEVICES環境変数を使用して、プロセスに使用するGPUを選択できます。プロセス関数(_train_mdl_)の先頭で次を使用して、pythonスクリプト内から実行できます。

    _import os # the import can be on the top of the python script
    os.environ["CUDA_VISIBLE_DEVICES"] = "{}".format(gpu_id)
    _

実験を実行するためのより良いプラクティスは、ハイパーパラメータ/モデル検索コードからトレーニング/評価コードを分離することです。例えば。 _train.py_という名前のスクリプトを使用します。これは、ハイパーパラメーターとデータへの参照の特定の組み合わせを引数として受け入れ、単一のモデルのトレーニングを実行します。

次に、可能なすべてのパラメーターの組み合わせを反復処理するために、単純なタスク(ジョブ)キューを使用し、可能なすべてのハイパーパラメーターの組み合わせを個別のジョブとして送信できます。タスクキューは、ジョブを1つずつマシンに送ります。通常、複数のプロセスを同時に実行するようにキューを設定することもできます(以下の詳細を参照)。

具体的には、 タスクスプーラー を使用します。これはインストールが非常に簡単で、ほんの一握りです(管理者権限は不要です。詳細は以下を参照)。

基本的な使用法は次のとおりです(タスクスプーラーの使用法については、以下の注を参照)

_ts <your-command>
_

実際には、実験を管理し、特定の実験ごとにすべての引数を設定し、tsキューにジョブを送信する別個のpythonスクリプトがあります。

python実験マネージャーからのコード:

_run_bash_はbashコマンドを実行します

_def run_bash(cmd):
    p = subprocess.Popen(cmd, Shell=True, stdout=subprocess.PIPE, executable='/bin/bash')
    out = p.stdout.read().strip()
    return out  # This is the stdout from the Shell command
_

次のスニペットは、実行する同時プロセスの数を設定します(プロセスの最大数の選択については、以下の注を参照してください)。

_max_job_num_per_gpu = 2
run_bash('ts -S %d'%max_job_num_per_gpu)
_

次のスニペットは、ハイパーパラメーター/モデルパラメーターのすべての組み合わせのリストを反復処理します。リストの各要素は辞書であり、キーは_train.py_スクリプトのコマンドライン引数です。

_for combination_dict in combinations_list:

    job_cmd = 'python train.py ' + '  '.join(
            ['--{}={}'.format(flag, value) for flag, value in combination_dict.iteritems()])

    submit_cmd = "ts bash -c '%s'" % job_cmd
    run_bash(submit_cmd)
_

プロセスの最大数の選択に関する注意:

GPUが不足している場合、見つけた_gpu_memory_fraction_を使用して、プロセス数をmax_job_num_per_gpu=int(1/gpu_memory_fraction)として設定できます。

タスクスプーラーに関する注意(ts):

  1. 実行する同時プロセス(「スロット」)の数を次のように設定できます。

    _ts -S <number-of-slots>_

  2. tsのインストールには管理者権限は必要ありません。単純なmakeを使用してソースからダウンロードしてコンパイルし、パスに追加して完了です。

  3. 複数のキューを設定できます(複数のGPUに使用します)。

    _TS_SOCKET=<path_to_queue_name> ts <your-command>_

    例えば.

    _TS_SOCKET=/tmp/socket-ts.gpu_queue_1 ts <your-command>_

    _TS_SOCKET=/tmp/socket-ts.gpu_queue_2 ts <your-command>_

  4. 詳細な使用例については、 here を参照してください

パス名とファイル名の自動設定に関する注意:実験マネージャーからメインコードを分離したら、ファイルを生成する効率的な方法が必要になりますハイパーパラメーターを指定した名前とディレクトリ名。私は通常、重要なハイパーパラメータを辞書に保持し、次の関数を使用して、辞書のキーと値のペアから単一の連鎖文字列を生成します。これを行うために使用する関数は次のとおりです。

_def build_string_from_dict(d, sep='%'):
    """
     Builds a string from a dictionary.
     Mainly used for formatting hyper-params to file names.
     Key-value pairs are sorted by the key name.

    Args:
        d: dictionary

    Returns: string
    :param d: input dictionary
    :param sep: key-value separator

    """

    return sep.join(['{}={}'.format(k, _value2str(d[k])) for k in sorted(d.keys())])


def _value2str(val):
    if isinstance(val, float): 
        # %g means: "Floating point format.
        # Uses lowercase exponential format if exponent is less than -4 or not less than precision,
        # decimal format otherwise."
        val = '%g' % val
    else:
        val = '{}'.format(val)
    val = re.sub('\.', '_', val)
    return val
_
17
Yuval Atzmon

私が理解しているように、最初にテンソルフローはシンボリックグラフを構築し、チェーンルールに基づいて導関数を推測します。次に、すべての(必要な)テンソルにメモリを割り当てます。これには、効率のためにレイヤーの入力と出力が含まれます。セッションを実行すると、データはグラフにロードされますが、一般に、メモリ使用量はこれ以上変化しません。

出会ったエラーは、1つのGPUに複数のモデルを構築することによって生じたと思われます。

@ user2476373が提案したように、ハイパーパラメータからトレーニング/評価コードを分離することは良い選択です。しかし、私はタスクスプーラではなくbashスクリプトを直接使用しています(より便利かもしれません)。

CUDA_VISIBLE_DEVICES=0 python train.py --lrn_rate 0.01 --weight_decay_rate 0.001 --momentum 0.9 --batch_size 8 --max_iter 60000 --snapshot 5000
CUDA_VISIBLE_DEVICES=0 python eval.py 

または、bashスクリプトで「for」ループを作成できます。必ずしもpython script。では使用しません。CUDA_VISIBLE_DEVICES=0スクリプトの開始時(1台のマシンに8つのGPUがある場合、インデックスは7になります)。私の経験に基づいて、このようなコードでどのGPUを使用する操作を指定しなかった場合、テンソルフローは1台のマシンですべてのGPUを使用することがわかりました

with tf.device('/gpu:0'):

マルチGPU実装を試したい場合は、いくつかの があります。

これがあなたを助けることを願っています。

2
LI Xuhong

おそらくこれはしたくないでしょう。

データに対して何千ものモデルを実行し、最も評価の高いモデルを選択する場合、機械学習を行っていません。代わりに、データセットを記憶しているため、選択したモデルがそのデータセットの外部で実行されるという保証はありません。

言い換えれば、そのアプローチは、数千の自由度を持つ単一のモデルを持つことに似ています。このように複雑度の高いモデルを作成することには問題があります。実際に保証されている以上にデータに適合することができるからです。このようなモデルは、トレーニングデータのノイズ(外れ値、測定誤差など)を迷惑なほど記憶することができるため、ノイズがわずかに異なる場合でもモデルのパフォーマンスが低下します。

(これを回答として投稿することをおologiesびします。このサイトではコメントを追加できません。)

0
Bass

簡単な解決策:各モデルに一意のセッションとグラフを提供します。

このプラットフォームで動作します:TensorFlow 1.12.0、Keras 2.1.6-tf、Python 3.6.7、Jupyter Notebook。

キーコード:

with session.as_default():
    with session.graph.as_default():
        # do something about an ANN model

完全なコード:

import tensorflow as tf
from tensorflow import keras
import gc

def limit_memory():
    """ Release unused memory resources. Force garbage collection """
    keras.backend.clear_session()
    keras.backend.get_session().close()
    tf.reset_default_graph()
    gc.collect()
    #cfg = tf.ConfigProto()
    #cfg.gpu_options.allow_growth = True
    #keras.backend.set_session(tf.Session(config=cfg))
    keras.backend.set_session(tf.Session())
    gc.collect()


def create_and_train_ANN_model(hyper_parameter):
    print('create and train my ANN model')
    info = { 'result about this ANN model' }
    return info

for i in range(10):
    limit_memory()        
    session = tf.Session()
    keras.backend.set_session(session)
    with session.as_default():
        with session.graph.as_default():   
            hyper_parameter = { 'A set of hyper-parameters' }  
            info = create_and_train_ANN_model(hyper_parameter)      
    limit_memory()

このリンクに触発された:Keras(Tensorflow backend)Error-指定されたTensor input_1:0、feed_devicesまたはfetch_devicesで見つかりませんでしたグラフ

0
liudonggalaxy