He leído la documentación , pero no entiendo qué significa: The delayed function is a simple trick to be able to create a tuple (function, args, kwargs) with a function-call syntax.
Lo estoy usando para iterar sobre la lista en la que quiero operar (allImages) de la siguiente manera:
def joblib_loop(): Parallel(n_jobs=8)(delayed(getHog)(i) for i in allImages)Esto devuelve mis funciones HOG, como quiero (y con la ganancia de velocidad usando todos mis 8 núcleos), pero no estoy seguro de lo que realmente está haciendo.
Mi conocimiento de Python está bien en el mejor de los casos, y es muy posible que me esté perdiendo algo básico. Cualquier puntero en la dirección correcta sería muy apreciado.
Quizá las cosas se aclaren si nos fijamos en lo que sucedería si, en cambio, simplemente escribiésemos
Parallel(n_jobs=8)(getHog(i) for i in allImages)que, en este contexto, podría expresarse de manera más natural como:
Parallel con n_jobs=8[getHog(i) for i in allImages]Parallel ¿Cuál es el problema? En el momento en que la lista pasa al objeto Parallel , todas getHog(i) ya han regresado, ¡así que no queda nada para ejecutar en Parallel! Todo el trabajo ya estaba hecho en el hilo principal, secuencialmente.
Lo que realmente queremos es decirle a Python qué funciones queremos llamar con qué argumentos, sin llamarlas realmente ; en otras palabras, queremos retrasar la ejecución.
Esto es lo que nos permite convenientemente delayed , con una sintaxis clara. Si queremos decirle a Python que nos gustaría llamar a foo(2, g=3) en algún momento posterior, simplemente podemos escribir delayed(foo)(2, g=3) . Se devuelve la tupla (foo, [2], {g: 3}) , que contiene:
foo2g=3 Entonces, al escribir Parallel(n_jobs=8)(delayed(getHog)(i) for i in allImages) , en lugar de la secuencia anterior, ahora sucede lo siguiente:
Se crea una instancia Parallel con n_jobs=8
La lista
[delayed(getHog)(i) for i in allImages]se crea, evaluando a
[(getHog, [img1], {}), (getHog, [img2], {}), ... ] Esa lista se pasa a la instancia Parallel .
La instancia de Parallel crea 8 subprocesos y les distribuye las tuplas de la lista
Finalmente, cada uno de esos subprocesos comienza a ejecutar las tuplas, es decir, llaman al primer elemento con el segundo y el tercer elementos desempaquetados como argumentos tup[0](*tup[1], **tup[2]) , convirtiendo la tupla volvamos a la llamada que realmente pretendíamos hacer, getHog(img2) .
necesitamos un bucle para probar una lista de diferentes configuraciones de modelos. Esta es la función principal que impulsa el proceso de búsqueda de cuadrícula y llamará a la función score_model() para cada configuración de modelo. Podemos acelerar drásticamente el proceso de búsqueda de cuadrículas evaluando las configuraciones del modelo en paralelo. Una forma de hacerlo es usar la biblioteca Joblib. Podemos definir un objeto paralelo con la cantidad de núcleos a usar y configurarlo con la cantidad de puntajes detectados en su hardware.
executor = Parallel(n_jobs=cpu_count(), backend= 'multiprocessing' )
luego cree una lista de tareas para ejecutar en paralelo, que será una llamada a la función score model() para cada configuración de modelo que tengamos.
supongamos def score_model(data, n_test, cfg): ........................
tasks = (delayed(score_model)(data, n_test, cfg) for cfg in cfg_list)
podemos usar el objeto Parallel para ejecutar la lista de tareas en paralelo.
scores = executor(tasks)
Entonces, lo que desea poder hacer es acumular un conjunto de llamadas a funciones y sus argumentos de tal manera que pueda pasarlos de manera eficiente a un programador/ejecutor. Delayed es un decorador que toma una función y sus argumentos y los envuelve en un objeto que se puede poner en una lista y sacar según sea necesario. Dask tiene lo mismo que usa en parte para alimentar su programador de gráficos.