Discusión sobre el artículo "Redes neuronales: así de sencillo (Parte 9): Documentamos el trabajo realizado"

 

Artículo publicado Redes neuronales: así de sencillo (Parte 9): Documentamos el trabajo realizado:

Ya hemos recorrido un largo camino y el código de nuestra biblioteca ha crecido de manera considerable. Resulta difícil monitorear todas las conexiones y dependencias. Y, obviamente, antes de proseguir con el desarrollo del proyecto, necesitaremos documentar el trabajo ya realizado y actualizar la documentación en cada paso posterior. Una documentación debidamente redactada nos ayudará a ver la integridad de nuestro trabajo.

Después de elaborar el programa, obtendremos la documentación lista para usar. A continuación, mostramos algunas capturas de pantalla. El lector podrá encontrar la documentación completa en los anexos.



Autor: Dmitriy Gizlyk

 

Gran y útil material de artículo

Gracias

 
¡Genial! La documentación está disponible - puedes hacerlo en SB. Aunque sería necesario atornillar LSTM en los núcleos, ¿piensas hacerlo?
 
Aleksey Mavrin:
¡Genial! La documentación está disponible - se puede hacer en SB. Aunque sería necesario atornillar LSTM en los núcleos, ¿está previsto?

Sí, está previsto :)

 
Dmitriy Gizlyk:

Sí, está previsto :)

Dmitry, por favor, al principio no me di cuenta de por qué el seno y el coseno se añaden a los valores de entrada.

neuron.setOutputVal(inputVals.At(i)+(i%2==0 ? sin(i) : cos(i)) );

También necesito algún consejo - ¿debería intentar normalizar los datos de entrada de alguna manera para mis tareas?

Ahora en los ejemplos, como yo lo entiendo, todo se da "tal cual". Pero incluso en los ejemplos con fractales hay algunos osciladores de 0 a 1, y los precios pueden ser mucho más altos que 1 dependiendo del instrumento.

¿No crea esto un sesgo inicial cuando se entrena con entradas no normalizadas?

 
Aleksey Mavrin:

Dimitri, dime por qué el seno y el coseno se añaden a los valores de entrada.

También necesito algún consejo - ¿debo tratar de normalizar los datos de entrada de alguna manera para mis tareas?

Ahora en los ejemplos, como yo lo entiendo, todo se da "tal cual". Pero incluso en los ejemplos con fractales algunos osciladores son de 0 a 1, y los precios pueden ser mucho más altos que 1 dependiendo del instrumento.

¿No crea esto un sesgo inicial cuando se entrena con entradas no normalizadas?

Esto es para la incrustación temporal. Voy a entrar en más detalles en el próximo artículo.
 
Dmitriy Gizlyk:
Esto es para incrustar el tiempo. Entraré en más detalles en el próximo artículo.

Me esfuerzo por entender el significado)

Los valores de entrada se ajustan aritméticamente a una matriz constante de 0 a 1, independientemente de cuáles sean los datos de entrada y sus valores absolutos.

Entiendo el time-embedding en este sentido de la siguiente manera - se superpone una onda sinusoidal a una serie temporal, de modo que el significado de las velas pasadas fluctúa en el tiempo.

Ok, está claro, aparentemente no importa que las fluctuaciones de los datos de entrada de cada barra con una fase diferente, o es una característica.

Pero entonces la cuestión sobre la normalización se vuelve más relevante. El significado es muy diferente para EURUSD y SP500, por ejemplo.

Y aparentemente es correcto transferir esta incrustación temporal de la biblia a la función Tren.

[Eliminado]  
Sería interesante leer sobre las incrustaciones posicionales. Pero hay dudas de que sean muy necesarias. Usted puede utilizar la volatilidad, es como una sinusoide. Pero me doy cuenta de que es una práctica común para las series temporales. Puede haber algún otro "know-how" inventado allí.
 

@Dmitriy Gizlyk, la pregunta surgió mientras estudiaba y trabajaba con la biblioteca:

en el método de cálculo de gradiente para las capas ocultas que añadir outputVal

esto es para compensar su valor más tarde en el método calcOutputGradients para la universalidad, ¿verdad?

También has añadido la normalización del gradiente.

bool CNeuron::calcHiddenGradients(CLayer *&nextLayer)
  {
   double targetVal=sumDOW(nextLayer)+outputVal;
   return calcOutputGradients(targetVal);
  }
//+------------------------------------------------------------------+
bool CNeuron::calcOutputGradients(double targetVal)
  {
   double delta=(targetVal>1 ? 1 : targetVal<-1 ? -1 : targetVal)-outputVal;
   gradient=(delta!=0 ? delta*activationFunctionDerivative(outputVal) : 0);
   return true;
  }

La pregunta es si sería más correcto normalizar no el objetivo sino el delta final así.

double delta=targetVal-outputVal;
delta=delta>1?1:delta<-1?-1:delta;

¿por qué? Ejemplo: Si outputVal es cercano a 1, y el gradiente ponderado total de la siguiente capa es también alto y positivo, ahora obtenemos un delta final cercano a cero, lo que parece incorrecto.

Al fin y al cabo, el delta del gradiente debería ser proporcional al error de la capa siguiente, es decir, en otras palabras, cuando el peso efectivo de una neurona es negativo (y posiblemente en algunos otros casos), la neurona es penalizada por un error menor que con un peso positivo. Puede que lo haya explicado de forma resumida, pero espero que los que estén en el tema entiendan la idea :) A lo mejor ya te has dado cuenta de este punto y has tomado esa decisión, sería interesante que aclararas los motivos.

También el mismo punto para el código OCL

__kernel void CalcHiddenGradient(__global double *matrix_w,
                                 __global double *matrix_g,
                                 __global double *matrix_o,
                                 __global double *matrix_ig,
                                 int outputs, int activation)
  {
..............   
switch(activation)
     {
      case 0:
        sum=clamp(sum+out,-1.0,1.0)-out;
        sum=sum*(1-pow(out==1 || out==-1 ? 0.99999999 : out,2));
 
Aleksey Mavrin:

@Dmitriy Gizlyk, la pregunta surgió mientras estudiaba y trabajaba con la biblioteca:

en el método de cálculo de gradiente para capas ocultas se añade outputVal

esto es para la posterior compensación de su valor en el método calcOutputGradients para la universalidad, ¿verdad?

También has añadido la normalización del gradiente

La pregunta es si sería más correcto normalizar no el objetivo, sino el delta final así

¿por qué? Ejemplo: Si outputVal es cercano a 1, y el gradiente ponderado total de la siguiente capa es también alto y positivo, entonces ahora obtenemos un delta final cercano a cero, lo que parece incorrecto.

Al fin y al cabo, el delta del gradiente debería ser proporcional al error de la capa siguiente, es decir, en otras palabras, cuando el peso efectivo de una neurona es negativo (y posiblemente en algunos otros casos), la neurona es penalizada por un error menor que con un peso positivo. Puede que lo haya explicado de forma resumida, pero espero que los que estén en el tema entiendan la idea :) A lo mejor ya te has dado cuenta de este punto y has tomado una decisión así, sería interesante aclarar las razones.

También el mismo punto para el código OCL

La verdad es que no. Comprobamos los valores objetivo, igual que en las capas ocultas añadimos outpuVal al gradiente para obtener el objetivo y comprobar su valor. El punto es que sigmoide tiene un rango limitado de resultados: función logística de 0 a 1, tanh - de -1 a 1. Si penalizamos la neurona por desviación y aumentamos el coeficiente de peso indefinidamente, llegaremos a desbordamiento de peso. Después de todo, si llegamos a un valor de la neurona igual a 1, y la capa posterior transmitir un error dice que debemos aumentar el valor a 1,5. La neurona obedientemente aumentará los pesos en cada iteración, y la función de activación cortará los valores en el nivel de 1. Por lo tanto, limito los valores del objetivo a los rangos de valores aceptables de la función de activación. Y dejo el ajuste fuera del rango a los pesos de la capa posterior.

 
Dmitriy Gizlyk:

La verdad es que no. Comprobamos los valores del objetivo, igual que en las capas ocultas añadimos outpuVal al gradiente para obtener el objetivo y comprobamos su valor. La cuestión es que sigmoide tiene un rango limitado de resultados: función logística de 0 a 1, tanh - de -1 a 1. Si penalizamos la neurona por desviación y aumentamos el factor de ponderación indefinidamente, llegaremos al desbordamiento del peso. Después de todo, si llegamos a un valor de la neurona igual a 1, y la capa posterior de transmitir un error dice que debemos aumentar el valor a 1,5. La neurona obedientemente aumentará los pesos en cada iteración, y la función de activación cortará los valores en el nivel de 1. Por lo tanto, limito los valores del objetivo a los rangos de valores aceptables de la función de activación. Y dejo el ajuste fuera del rango a los pesos de la capa posterior.

Creo que lo he conseguido. Pero me sigo preguntando si este es el enfoque correcto, un ejemplo como este:

si la red se equivoca dando 0 cuando en realidad es 1. A partir de la última capa entonces el gradiente ponderado en la capa anterior viene (lo más probable, según tengo entendido) positivo y puede ser más de 1, digamos 1,6.

Supongamos que hay una neurona en la capa anterior que produjo +0,6, es decir, produjo el valor correcto - su peso debe aumentar en más. Y con esta normalización cortamos el cambio en su peso.

El resultado es norm(1,6)=1. 1-0,6=0,4, y si lo normalizamos como he sugerido, será 1. En este caso, inhibimos la amplificación de la neurona correcta.

¿Qué piensa usted?

Sobre el aumento infinito de pesos, algo así como que he oído que ocurre en caso de "mala función de error", cuando hay muchos mínimos locales y ningún global expreso, o la función no es convexa, algo así, no soy un super experto, sólo creo que se puede y se debe luchar con pesos infinitos y otros métodos.

Pido un experimento para probar ambas variantes. Si se me ocurre como formular la prueba )