¿Cómo se entrenan varios modelos en un solo script en TensorFlow cuando hay GPUs presentes?

Digamos que tengo acceso a un número de GPU en una sola máquina (por el bien del argumento, supongamos 8GPU cada una con una memoria máxima de 8 GB en una sola máquina con cierta cantidad de RAM y disco). Quería ejecutar en un solo script y en una sola máquina un progtwig que evalúa múltiples modelos (por ejemplo, 50 o 200) en TensorFlow, cada uno con una configuración diferente de parámetros hiperactivos (por ejemplo, tamaño de paso, velocidad de decaimiento, tamaño de lote, épocas / iteraciones, etc). Al final del entrenamiento, supongamos que simplemente registramos su precisión y eliminamos el modelo (si quiere suponer que el modelo se está revisando de vez en cuando, así que está bien simplemente desechar el modelo y comenzar a entrenar desde cero. También puede Supongamos que algunos otros datos se pueden registrar, como los parámetros hiperactivos específicos, el tren, la validación, los errores del tren se registran a medida que entrenamos, etc.

Actualmente tengo un (pseudo-) script que se ve como sigue:

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) ) 

esencialmente, intenta muchos modelos en una sola ejecución pero construye cada modelo en un gráfico separado y ejecuta cada uno en una sesión separada.

Supongo que mi principal preocupación es que no me queda claro cómo tensorflow bajo el capó asigna recursos para el uso de las GPU. Por ejemplo, ¿carga el conjunto de datos (parte del) solo cuando se ejecuta una sesión? Cuando creo un gráfico y un modelo, ¿se introduce en la GPU inmediatamente o cuando se inserta en la GPU? ¿Necesito borrar / liberar la GPU cada vez que pruebe un nuevo modelo? En realidad, no me importa mucho si los modelos se ejecutan en paralelo en varias GPU (lo que puede ser una buena adición), pero primero quiero que se ejecute todo en serie sin que se bloquee. ¿Hay algo especial que deba hacer para que esto funcione?


Actualmente estoy recibiendo un error que comienza como sigue:

 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] 

y más abajo en la línea dice:

 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) 

sin embargo, más abajo en el archivo de salida (donde se imprime), parece que se imprimen bien los errores / mensajes que deberían aparecer a medida que avanza el entrenamiento. ¿Significa esto que no se quedó sin recursos? ¿O fue realmente capaz de usar la GPU? Si fue capaz de usar la CPU en lugar de la CPU, ¿por qué se produce un error cuando la GPU está a punto de usarse?

Lo extraño es que el conjunto de datos no es realmente tan grande (todos los 60K puntos son 24.5M) y cuando ejecuto un solo modelo localmente en mi propia computadora, parece que el proceso usa menos de 5GB. Las GPU tienen al menos 8 GB y la computadora con ellas tiene un montón de RAM y disco (al menos 16 GB). Por lo tanto, los errores que tensorflow me está lanzando son bastante desconcertantes. ¿Qué está tratando de hacer y por qué están ocurriendo? ¿Algunas ideas?


Después de leer la respuesta que sugiere usar la biblioteca de multiprocesamiento, se me ocurrió el siguiente script:

 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!') 

Honestamente, no estoy seguro de por qué su respuesta sugiere usar pool, o por qué hay paréntesis de tuplas raras, pero esto es lo que tendría sentido para mí. ¿Se volverían a asignar los recursos para tensorflow cada vez que se crea un nuevo proceso en el bucle anterior?

Creo que ejecutar todos los modelos en un solo script puede ser una mala práctica a largo plazo (vea mi sugerencia a continuación para una mejor alternativa). Sin embargo, si desea hacerlo, aquí tiene una solución: puede encapsular su sesión de TF en un proceso con el módulo de multiprocessing , esto asegurará que TF libere la memoria de la sesión una vez que se complete el proceso. Aquí hay un fragmento de código:

 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() 

Nota de OP: El generador de números aleatorios no se restablece automáticamente con la biblioteca de multiprocesamiento si elige usarlo. Detalles aquí: Uso de multiprocesamiento de python con diferentes semillas aleatorias para cada proceso

Acerca de la asignación de recursos de TF: Por lo general, TF asigna muchos más recursos de los que necesita. Muchas veces puede restringir cada proceso para usar una fracción de la memoria total de la GPU y descubrir mediante prueba y error la fracción que requiere su script.

Puedes hacerlo con el siguiente fragmento de código.

 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) 

Tenga en cuenta que a veces TF aumenta el uso de la memoria para acelerar la ejecución. Por lo tanto, reducir el uso de memoria puede hacer que su modelo funcione más lento.

Respuestas a las nuevas preguntas en su edición / comentarios:

  1. Sí, Tensorflow se reasignará cada vez que se cree un nuevo proceso y se borrará una vez que finalice el proceso.

  2. El for-loop en su edición también debería hacer el trabajo. Sugiero usar Pool en su lugar, porque le permitirá ejecutar varios modelos simultáneamente en una sola GPU. Ver mis notas sobre la configuración de gpu_memory_fraction y “elegir el número máximo de procesos”. También tenga en cuenta que: (1) El mapa de la piscina ejecuta el bucle por usted, por lo que no necesita un bucle for externo una vez que lo usa. (2) En tu ejemplo, deberías tener algo como mdl=get_model(args) antes de llamar a train ()

  3. Paréntesis extraño de la tupla: El grupo solo acepta un solo argumento, por lo tanto, usamos una tupla para pasar varios argumentos. Consulte multiprocessing.pool.map y la función con dos argumentos para obtener más detalles. Como se sugiere en una respuesta, puede hacerlo más legible con

     def train_mdl(params): (x,y)=params < your code > 
  4. Como sugirió @Seven, puede usar la variable de entorno CUDA_VISIBLE_DEVICES para elegir qué GPU usar para su proceso. Puede hacerlo desde su script de python usando lo siguiente al principio de la función de proceso ( train_mdl ).

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

Una mejor práctica para ejecutar sus experimentos sería aislar su código de entrenamiento / evaluación del código de búsqueda de modelos / hiper parámetros. Por ejemplo, tenga un script llamado train.py , que acepta una combinación específica de hiper parámetros y referencias a sus datos como argumentos, y ejecuta la capacitación para un solo modelo.

Luego, para recorrer todas las combinaciones posibles de parámetros, puede utilizar una cola de tareas simples (trabajos), y enviar todas las combinaciones posibles de hiper-parámetros como trabajos separados. La cola de tareas alimentará sus trabajos uno por uno a su máquina. Por lo general, también puede configurar la cola para que ejecute varios procesos simultáneamente (consulte los detalles a continuación).

Específicamente, utilizo la cola de tareas , que es muy fácil de instalar y muy poco (no requiere privilegios de administrador, detalles a continuación).

El uso básico es (consulte las notas a continuación sobre el uso del administrador de tareas):

 ts  

En la práctica, tengo una secuencia de comandos de Python independiente que administra mis experimentos, configuro todos los argumentos para cada experimento específico y envío los trabajos a la cola ts .

Aquí hay algunos fragmentos relevantes de código python de mi administrador de experimentos:

run_bash ejecuta un comando 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 

El siguiente fragmento de código establece el número de procesos simultáneos que se ejecutarán (consulte la nota a continuación sobre cómo elegir el número máximo de procesos):

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

El siguiente fragmento de código se itera a través de una lista de todas las combinaciones de parámetros hiper / parámetros param. Cada elemento de la lista es un diccionario, donde las claves son los argumentos de la línea de comando para el script 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) 

Una nota sobre la elección del número máximo de procesos:

Si no tienes GPU, puedes usar gpu_memory_fraction que encontraste para establecer el número de procesos como max_job_num_per_gpu=int(1/gpu_memory_fraction)

Notas sobre la tarea spooler ( ts ):

  1. Podría establecer el número de procesos concurrentes para ejecutar (“ranuras”) con:

    ts -S

  2. La instalación de ts no requiere privilegios de administrador. Puedes descargarlo y comstackrlo desde la fuente con una make simple, agregarlo a tu ruta y listo.

  3. Puede configurar varias colas (lo uso para varias GPU), con

    TS_SOCKET= ts

    p.ej

    TS_SOCKET=/tmp/socket-ts.gpu_queue_1 ts

    TS_SOCKET=/tmp/socket-ts.gpu_queue_2 ts

  4. Vea aquí para más ejemplos de uso

Una nota sobre la configuración automática de los nombres de ruta y los nombres de archivo: una vez que separe su código principal del administrador del experimento, necesitará una forma eficiente de generar nombres de archivos y nombres de directorios, dados los hiper-parámetros. Por lo general, mantengo mis parámetros importantes en un diccionario y uso la siguiente función para generar una cadena encadenada a partir de los pares clave-valor del diccionario. Aquí están las funciones que uso para hacerlo:

 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 

Según tengo entendido, en primer lugar, tensorflow construye un gráfico simbólico e infiere los derivados basados ​​en la regla de la cadena. Luego asigna memoria para todos los tensores (necesarios), incluidas algunas entradas y salidas de capas para mayor eficiencia. Al ejecutar una sesión, los datos se cargarán en el gráfico, pero en general, el uso de la memoria no cambiará más.

El error que conoció, supongo, puede deberse a la construcción de varios modelos en una GPU.

Aislar su código de entrenamiento / evaluación de los parámetros hiper es una buena opción, como propuso @ user2476373. Pero estoy usando el script bash directamente, no el spooler de tareas (puede ser que sea más conveniente), por ejemplo

 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 

O puede escribir un bucle ‘for’ en el script de bash, no necesariamente en el script de Python. Notando que usé CUDA_VISIBLE_DEVICES=0 al comienzo del script (el índice podría ser 7 si tiene 8 GPU en una máquina). Porque según mi experiencia, he encontrado que tensorflow usa todas las GPU en una máquina si no especificé las operaciones que usan qué GPU con el código como este

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

Si desea probar la implementación multi-GPU, hay algunos ejemplos .

Espero que esto pueda ayudarte.

Probablemente no quieras hacer esto.

Si ejecuta miles y miles de modelos en sus datos, y elige el que mejor evalúa, no está haciendo el aprendizaje automático; en lugar de eso, está memorizando su conjunto de datos, y no hay garantía de que el modelo que elija se desempeñe fuera del conjunto de datos.

En otras palabras, ese enfoque es similar a tener un modelo único, que tiene miles de grados de libertad. Tener un modelo con un orden de complejidad tan alto es problemático, ya que podrá ajustar sus datos mejor de lo que realmente se justifica; un modelo de este tipo es capaz de memorizar cualquier ruido (valores atípicos, errores de medición, etc.) en sus datos de entrenamiento, lo que hace que el modelo funcione mal cuando el ruido es incluso ligeramente diferente.

(Disculpas por publicar esto como una respuesta, el sitio no me permitió agregar un comentario).