Discusión sobre el artículo "Redes neuronales: así de sencillo (Parte 5): Cálculos multihilo en OpenCL"

 

Artículo publicado Redes neuronales: así de sencillo (Parte 5): Cálculos multihilo en OpenCL:

Ya hemos analizado algunos tipos de implementación de redes neuronales. Podemos ver con facilidad que se repiten las mismas operaciones para cada neurona de la red. Y aquí sentimos el legítimo deseo de aprovechar las posibilidades que ofrece la computación multihilo de la tecnología moderna para acelerar el proceso de aprendizaje de una red neuronal. En el presente artículo, analizaremos una de las opciones para tal implementación.

Una vez hemos decidido la tecnología a usar, es momento de pensar en el proceso de división de los cálculos en subprocesos. Recordemos el algoritmo del perceptrón completamente conectado con la propagación hacia adelante. La señal se mueve secuencialmente desde la capa de entrada a las capas ocultas y luego hacia la capa de salida. La asignación de un hilo para cada capa no dará un resultado, ya que los cálculos deben realizarse de forma secuencial. No podemos empezar a calcular una capa hasta que obtengamos el resultado de la anterior. Al mismo tiempo, el recálculo de una neurona individual en una capa no depende de los resultados del recálculo de las otras neuronas en esta capa. Es decir, podemos asignar con seguridad un hilo para cada neurona y enviar inmediatamente todas las neuronas de la capa al cálculo paralelo.  

Perceptrón completamente conectado

Si descendemos al nivel de las operaciones de una neurona, podemos analizar la posibilidad de paralelizar el cálculo de los productos de los valores de entrada por sus coeficientes de peso. Pero posterior la suma de los valores obtenidos y el cálculo del valor de la función de activación se unen en un solo hilo. Tras considerar todas las ventajas y desventajas, decidimos implementar estas operaciones en un solo núcleo OpenCL utilizando funciones vectoriales.

Autor: Dmitriy Gizlyk

 

El artículo es extremadamente complicado para alguien que no se dedica a OpenCL, he intentado entender la esencia, he leído los artículos que has indicado en tu sitio y ..... No entendí nada, o mejor dicho extremadamente parcial.

Tu articulo es probablemente super, pero demasiado complicado, creo que necesitas tener en cuenta lo mas basico de OpenCL, y quizas trabajar con librerias incorporadas como OpenCL.mqh, porque sin ellas no habra entendimiento, como lo demuestra la falta de discusion del articulo. No entiendo tu código, porque contiene referencias a muchas librerías de las que no tengo ni idea.

La segunda cuestión que no entiendo es cómo usar OpenCL en modo optimización. Qué pasará si varias instancias de OpenCL están accediendo simultáneamente a la tarjeta de vídeo????. Luego hay un montón de métodos para acelerar los cálculos de incluso una misma cosa en OpenCL. Por ejemplo, deshacerse de double en favor de int. etc. .... Hay muchas preguntas.

Propongo empezar con un ejemplo muy sencillo en código de programación multihilo. Supongamos que hay UN perceptrón que tiene n entradas, respectivamente necesitamos escribir un código simple usando OpenCL para calcular las funciones de normalización en el rango [-1,1] y calcular el valor de la neurona por tanh. Y el propio código en OpenCL debe ser comentado. Y considerar un Asesor Experto de trading muy sencillo con optimización en el probador sobre el histórico utilizando UN indicador, y además sencillo, es decir, que dé un solo valor, como el RSI o el CCI. Es decir, todo lo más simplificado posible, sin artificios, simplemente lo más simple posible. En el futuro, creo que no hay problema en extender esto a muchos perceptrones y escribir tu propio Asesor Experto.

Gracias por el artículo.

 
El artículo es justo lo que necesita. Gracias al autor. Entendí los cálculos paralelos en neuronas desde la primera vez. Sólo falta "madurar")).
 
Реter Konow:
El artículo es justo lo que necesita. Gracias al autor. Entendí los cálculos paralelos en neuronas desde la primera vez. Sólo falta "madurar")).

Bueno, uno no ha madurado, el otro ..... Pero supongo que todos quieren hacerlo.

Estoy de acuerdo en que el artículo es necesario

Me esfuerzo por entenderlo.

Tal vez puedas escribir lo que te pedí, si es tan fácil para ti.
 
Boris Egorov:

Bueno, uno no está maduro, el otro es ..... y supongo que todo el mundo quiere uno.

Estoy de acuerdo en que es un buen artículo.

Me esfuerzo por entenderlo.

Tal vez puedas escribir lo que te he pedido, si te resulta tan fácil.
Por lo que tengo entendido, no necesitas usar OpenCL en el modo de optimización porque la optimización en sí es multihilo. La red neuronal se optimiza con el entrenamiento, no con el probador interno.

El multihilo en la red es necesario para acelerar el procesamiento de una capa de su volumen de información (datos) y pasar el testigo a la capa siguiente.

Cada neurona procesa su propio "trozo" del espacio de valores en su propio hilo sin hacer esperar a las demás neuronas. Esta es la ventaja de utilizar multihilo en NS, que proporciona la tecnología OpenCL.
 
Todo esto es interesante, aunque es poco probable que yo pueda repetirlo, pero la pregunta que me intrigaba era: ¿de dónde procedía el aumento de 10 veces si en el entrenamiento sólo participaba un núcleo más?
 
Aleksey Vyazmikin:
Todo esto es interesante, aunque es poco probable que lo repita, pero me ha desconcertado la pregunta: ¿por qué el aumento de 10 veces si sólo interviene un núcleo más en el entrenamiento?

Por lo que tengo entendido, cuando se entrena en OpenCL, se hace el cálculo paralelo de varios indicadores a la vez.

 
Aleksey Vyazmikin:
Todo esto es interesante, aunque es poco probable que yo pueda repetirlo, pero me desconcertó la pregunta: ¿por qué un aumento de 10 veces si sólo se utilizó un núcleo más en el entrenamiento?

El núcleo no es un núcleo físico o lógico, sino un firmware que se ejecuta en todos losnúcleos lógicos de un procesador o tarjeta de vídeo en paralelo.

 

Aquí hay algunos puntos que no entiendo

__kernel void FeedForward(__global double *matrix_w,
                          __global double *matrix_i,
                          __global double *matrix_o,
                          int inputs, 
                          int activation)
  {
   int i=get_global_id(0); //¿Qué hace esta línea?
   double sum=0.0;
   double4 inp, weight;
   int shift=(inputs+1)*i; //¿Qué hace esta línea?
   for(int k=0; k<=inputs; k=k+4)
     {
      switch(inputs-k)
        {
         case 0:
           inp=(double4)(1,0,0,0); //¿Qué hace esta línea?
           weight=(double4)(matrix_w[shift+k],0,0,0);
           break;
         case 1:
           inp=(double4)(matrix_i[k],1,0,0);
           weight=(double4)(matrix_w[shift+k],matrix_w[shift+k+1],0,0);
           break;
         case 2:
           inp=(double4)(matrix_i[k],matrix_i[k+1],1,0);
           weight=(double4)(matrix_w[shift+k],matrix_w[shift+k+1],matrix_w[shift+k+2],0);
           break;
         case 3:
           inp=(double4)(matrix_i[k],matrix_i[k+1],matrix_i[k+2],1);
           weight=(double4)(matrix_w[shift+k],matrix_w[shift+k+1],matrix_w[shift+k+2],matrix_w[shift+k+3]);
           break;
         default:
           inp=(double4)(matrix_i[k],matrix_i[k+1],matrix_i[k+2],matrix_i[k+3]);
           weight=(double4)(matrix_w[shift+k],matrix_w[shift+k+1],matrix_w[shift+k+2],matrix_w[shift+k+3]);
           break;
        }
      sum+=dot(inp,weight); //¿Qué hace esta línea?
     }
   switch(activation)
     {
      case 0:
        sum=tanh(sum);
        break;
      case 1:
        sum=pow((1+exp(-sum)),-1);
        break;
     }
   matrix_o[i]=sum;
  }

Creo que es necesario explicarlos para gente "lejana", y lo principal es que no entiendo del código donde se inicializa todo y donde se llama a todo.

 
Boris Egorov:

He aquí algunas cosas que no entiendo

Creo que es necesario explicarlas para gente "lejana", y lo principal es que no entiendo del código dónde se inicializa todo y dónde se llama a todo.

Buenos días, Boris.
Has adjuntado el código del kernel. Como ha escrito Maxim más arriba, es un microprograma que se ejecuta en los núcleos del microprocesador o de la tarjeta de vídeo (depende del contexto de inicialización inicial). La cuestión es que se llaman varios programas de este tipo a la vez y se ejecutan en paralelo en todos los núcleos. Cada programa trabaja con sus propios datos.

int i=get_global_id(0); //¿Qué hace esta línea?

Esta línea sólo obtiene el número de serie del microprograma del conjunto de copias que se ejecutan en paralelo. En el código, este número se utiliza para determinar qué trozo de datos hay que dar para procesar. En este caso, corresponderá al número de neurona en la capa.

Además, para una mayor paralelización de las acciones dentro del microprograma, se utilizan variables vectoriales, que son pequeñas matrices de tamaño fijo. En este caso, la dimensión de los vectores es 4, es decir, matrices de 4 elementos.

double4 inp, weight;

Pero el tamaño de la matriz de entrada no siempre será múltiplo de 4. Por lo tanto, para evitar una referencia incorrecta a la matriz, se utiliza swith y los valores que faltan se rellenan con "0". Al mismo tiempo, para cada neurona, la dimensionalidad de la matriz de pesos es 1 elemento mayor que la dimensionalidad de los elementos de entrada, este elemento se utiliza como sesgo bayesiano. Anteriormente utilizamos una neurona adicional para este propósito, en la que corregíamos sólo los pesos y no recalculábamos el valor de salida, que siempre permanecía igual a "1". Aquí, no añadí constantemente "1" a la matriz de entrada, sino que lo escribí directamente en el código, y el tamaño de la matriz de entrada no cambia.

           inp=(double4)(1,0,0,0); //¿Qué hace esta línea?
           weight=(double4)(matrix_w[shift+k],0,0,0);

La función dot devuelve el producto escalar de dos vectores, es decir, en nuestro caso contamos la suma de 4 productos de valores de entrada por pesos en una línea.

sum+=dot(inp,weight); //¿Qué hace esta línea?
 
Maxim Dmitrievsky:

Unkernel no es un núcleo físico o lógico, sino un firmware que se ejecuta en todos losnúcleos lógicos de un procesador o tarjeta de vídeo en paralelo

Así que no es noticia - había 1 núcleo y la carga estaba en él, y ahora hay dos núcleos, la carga se reduce a la mitad.... Lo más probable es que los cambios sean más significativos y la comparación no sea correcta.