« Complex Networks Tool… « || Inicio || » La Tiranía de las Pub… »

Entrenamiento de Redes Neuronales: mejorando el Gradiente Descendente

Última modificación: 25 de Diciembre de 2020, y ha tenido 30920 vistas

Etiquetas utilizadas: || || || ||

Como hemos visto en entradas anteriores, el procedimiento utilizado para llevar a cabo el proceso de aprendizaje en una red neuronal se denomina entrenamiento. Aunque hay uno, el de Descenso del Gradiente (versión diferencial del Ascenso de la Colina), que popularmente se impone sobre los demás por su facilidad y extensión de uso, hay muchos otros que pueden resultar interesantes, presentando diferentes características y rendimiento que los pueden hacer más, o menos, adecuados dependiendo de las características del problema concreto al que nos enfrentemos.

Comencemos formalizando el problema del aprendizaje: El problema de aprendizaje en las redes neuronales se formula en términos de la minimización de la función de error (o pérdida) asociada, y que notaremos en esta entrada por medio de \(f\).

Normalmente, esta función está compuesta por dos términos, uno que evalúa cómo se ajusta la salida de la red neuronal al conjunto de datos de que disponemos, y que se denomina término de error, y otro que se denomina término de regularización, y que se utiliza para evitar el sobreaprendizaje por medio del control de la complejidad efectiva de la red neuronal.

Por supuesto, el valor de la función de error depende por completo de los parámetros de la red neuronal: los pesos sinápticos entre neuronas, y los bias asociados a ellas, que, como suele ser ya habitual, se pueden agrupar adecuadamente en un único vector de peso de la dimensión adecuada, que denotaremos por \(w\). En este sentido, podemos escribir \(f(w)\) para indicar que el valor del error que comete la red neuronal depende de los pesos asociados a la misma. Con esta formalización, nuestro objetivo es encontrar el valor \(w^*\) para el que se obtiene un mínimo global de la función \(f\), convirtiendo el problema de aprendizaje en un problema de optimización, como va siendo habitual.

En general, la función de error es una función no lineal, por lo que no disponemos de algoritmos sencillos y exactos para encontrar sus mínimos. En consecuencia, tendremos que hacer uso de una búsqueda a través del espacio de parámetros que, idealmente, se aproxime de forma iterada a a un (error) mínimo de la red para los parámetros adecuados.

De esta forma, se comienza con una red neuronal con algún vector inicial de parámetros (a menudo elegido al azar), a continuación se genera un nuevo vector de parámetros, esperando que con ellos la función de error se reduzca (aunque dependiendo del método elegido, no es obligatorio, y temporalmente se puede admitir un empeoramiento del error siempre y cuando conduzca a una disminución posterior más acusada). Este proceso se repite, normalmente, hasta haber reducido el error bajo un umbral tolerable, o cuando se satisfaga una condición específica de parada.

En el caso en que \(f\) cumpla con condiciones suficientes de derivabilidad, podemos calcular las derivadas primera y segunda de esta función en cualquier punto, proporcionando, respectivamente, el vector gradiente (formado por las posibles derivadas parciales):

$$\frac{\partial f}{\partial w_i},\mbox{ para }i = 1, \dots, n$$

y la matriz Hessiana (formada por las posibles derivadas parciales de cada función del vector gradiente):

$$H_{ij}f(w) = \frac{\partial^2 f}{\partial w_i \partial w_j}, \mbox{ para } i, j = 1, \dots, n$$

De esta forma, podemos utilizar una información adicional ya que sabemos que un punto (parámetros) óptimo (máximo o mínimo) el gradiente debe representarse por el vector nulo y, adicionalmente, para que sea mínimo deben verificarse algunas condiciones sobre el signo del Hessiano.

Aunque el proceso de optimización es inherentemente multidimensional (ya que siempre tendremos más de un parámetro en la función de error), muchos de los algoritmos de optimización que se utilizan en el entrenamiento de redes neuronales hacen uso de una optimización unidimensional de la siguiente forma: primero se calcula una dirección de entrenamiento, \(d\), en la que se espera que el cambio del error sea máximo, y posteriormente se usa un factor de entrenamiento, \(\nu\), que indica la cantidad de movimiento en la que nos desplazaremos siguiendo esa dirección, \(f(w+\nu d)\).

En este sentido, y considerando solo la función en esa dirección, los métodos de optimización unidimensional buscan el mínimo de la función unidimensional que se obtiene a partir de \(f\) restringiéndonos únicamente a ese hiperplano. Algunos de los algoritmos que usan este método son el método de Brent, y el método de la sección dorada, que se basan en ir reduciendo la amplitud de un intervalo que contiene a ese mínimo hasta que la amplitud del intervalo esta por debajo de una tolerancia predefinida.

Aunque los métodos de entrenamiento de redes neuronales más conocidos e implementados son aquellos basados en propiedades de derivabilidad de la función de error, se debe recordar que también existen otros métodos que pueden resultar muy interesantes bajo determinadas circunstancias (algunas veces por obligación, como por ejemplo que la función no sea derivable), como son los Algoritmos Genéticos, el Templado Simulado, las Colonias de Hormigas, o PSO.

Como estos métodos ya han sido analizados en otras entradas de este mismo sitio, nos vamos a centrar aquí los más usuales (y simples) para redes neuronales que hacen uso de aproximaciones numéricas basadas en propiedades diferenciales de la función de error, concretamente, aquellos basados en el método del Descenso del Gradiente. En todo caso, solo recorreremos los métodos numéricos más usuales, pero no las variadas implementaciones ni mejoras que se han realizado y que suelen tener a alguno de ellos como parte central (puedes encontrar una comparativa de diversos métodos en este review de Sebastian Ruder).

En lo que sigue notaremos por:

  • \(w_0,\ w_1\, \dots,\ w_i,\ \dots\) la sucesión de vectores de parámetros que se contruye con el proceso iterado por cualquiera de los métodos,
  • \(f_i=f(w_i)\), el valor del error en el paso \(i\)-ésimo de la iteración,
  • \(g_i=\nabla f (w_i)\), el valor del gradiente de la función de error en el paso \(i\)-ésimo de la iteración.

Descenso del Gradiente

El Descenso del Gradiente es el algoritmo de entrenamiento más simple y también el más extendido y conocido. Solo hace uso del vector gradiente, y por ello se dice que es un método de primer orden.

Este método para construir el punto \(w_{i+1}\) a partir de \(w_i\) se traslada este punto en la dirección de entrenamiento \(d_i = -g_i\). Es decir:

$$w_{i + 1} = w_i - g_i \nu_i$$

Donde el parámetro \(\nu\) se denomina tasa de entrenamiento, que puede fijarse a priori o calcularse mediante un proceso de optimización unidimensional a lo largo de la dirección de entrenamiento para cada uno de los pasos (aunque esta última opción es preferible, a menudo se usa un valor fijo, \(\nu_i=\nu\) con el fin de simplificar el proceso).

Aunque es muy sencillo, este algoritmo tiene el gran inconveniente de que, para funciones de error con estructuras con valles largos y estrechos, requiere muchas iteraciones. Se debe a que, aunque la dirección elegida es en la que la función de error disminuye más rápidamente, esto no significa que necesariamente produzca la convergencia más rápida.

Por ello, es el algoritmo recomendado cuando tenemos redes neuronales muy grandes, con muchos miles de parámetros, ya que sólo almacena el vector gradiente (de tamaño \(n\)), pero no hace uso de la Hessiana (de tamaño \(n^2\)).

Método de Newton

El método de Newton es uno de los algoritmos conocidos como de segundo orden, ya que hace uso de la Hessiana. Tiene como objetivo encontrar las mejores direcciones de variación de los parámetros haciendo uso de las derivadas segundas de la función de error. Para ello, se hace uso del desarrollo de Taylor de orden \(2\) de \(f\), que se puede aproximar por alrededor del conjunto inicial de parámetros, \(w_0\) (donde \(H_0\) es el Hessiano de \(f\) en este punto):

$$f(w) = f_0 + g_0 (w - w_0) + \frac{1}{2}(w - w_0)^2 H_0$$

Si tenemos en cuenta que \(g\) (el gradiente de \(f\)) debe ser \(0\) para el mínimo de \(f\), obtenemos la siguiente ecuación:

$$g(w) = g_0 + H_0 (w - w_0 ) = 0$$

Por lo que, partiendo de un vector de parámetros \(w_0\), el método de Newton permite generar la sucesión de parámetros de la siguiente forma (para ver exactamente de dónde sale la expresión siguiente puedes mirar el caso unidimensional tal y como viene explicado aquí):

$$w_{i + 1} = w_i - H_i^{-1} g_i$$

Por las condiciones impuestas, podría ocurrir que la sucesión de parámetros tienda a un máximo en lugar de un mínimo (que ocurriría si el Hessiano no es definido positivo). Por lo que no se garantiza la reducción del error en cada iteración. Para evitar este problema, la ecuación anterior del método de Newton se suele encontrar modificada, donde de nuevo, la velocidad de entrenamiento, \(\nu\), se puede fijar o encontrarse por minimización unidimensional:

$$w_{i + 1} = w_i - (H_i^{-1} g_i)\nu$$

El algoritmo seguido por este método obtiene primero la dirección de entrenamiento y, posteriormente, una velocidad de entrenamiento adecuada. En general, el método de Newton requiere menos pasos que el de Descenso del Gradiente para encontrar el mínimo de la función de error. Sin embargo, tiene el inconveniente de que el cálculo del Hessiano y su inversa tienen un coste computacional muy elevado. En la imagen siguiente podemos ver una comparativa del funcionamiento del método de Newton con el de Gradiente Descendiente lineal sobre un ejemplo concreto.

Gradiente Conjugado

El método del Gradiente Conjugado puede considerarse como un método intermedio entre el Descenso del Gradiente y el método de Newton. Viene motivado por el deseo de acelerar la convergencia, habitualmente lenta, obtenida con el Descenso del Gradiente, y a la vez evitar los requisitos de computación asociados a la evaluación, almacenamiento e inversión del Hessiano, como requiere el método de Newton.

En este algoritmo de entrenamiento la búsqueda se realiza a lo largo de direcciones conjugadas, lo que normalmente produce una convergencia más rápida que las direcciones obtenidas con el Descenso del Gradiente. Dos direcciones, \(u\) y \(v\) se dicen conjugadas respecto de una matriz \(A\) si \(u^T A v=0\). En el caso que nos ocupa estas direcciones se conjugan con respecto a la matriz Hessiana.

El método del Gradiente Conjugado construye una sucesión de direcciones de entrenamiento dada por:

$$d_{i + 1} = g_{i + 1} + d_i \gamma_i$$

Donde \(\gamma\) se denomina parámetro conjugado, y hay diferentes maneras de calcularlo. Dos de las más utilizadas se deben a Fletcher y Reeves, y a Polak y Ribiere. Pero sea cual sea la forma, la dirección de entrenamiento se restablece periódicamente a la negativa del gradiente para evitar la acumulación de errores en las aproximaciones.

Los valores de los parámetros de la función se calculan entonces con la forma habitual:

$$w_{i + 1}= w_i + d_i \nu$$

Este método ha demostrado ser más eficaz que el de Descenso del Gradiente, y como no requiere el cálculo del Hessiano también se recomienda cuando tenemos redes neuronales muy grandes.

La siguiente figura muestra una comparativa del comportamiento del Descenso del Gradiente usual (verde) frente al comportamiento del Gradiente Conjugado (rojo).

Método Cuasi-Newton

Como hemos visto, el método de Newton es muy costoso desde el punto de vista computacional, ya que requiere muchas operaciones para calcular el Hessiano y su inversa. Los métodos alternativos que se han construido para resolver este problema se conocen genéricamente como métodos Cuasi-Newton. En estos métodos, en lugar de calcular el Hessiano directamente y después su inversa, se construye una aproximación de la inversa en cada iteración utilizando sólo información sobre las primeras derivadas de la función de error. Si llamamos a esta aproximación \(G_i\) (en la iteración \(i\)), la fórmula de cuasi-Newton se puede expresar como:

$$w_{i + 1} = w_i - (G_i g_i ) \nu$$

Los dos métodos más utilizados para la aproximación de la inversa de la Hessiana son la fórmula de Davidon-Fletcher-Powell (DFP) y la fórmula de Broyden-Fletcher-Goldfarb-Shanno (BFGS).

Este es el método por defecto para usar en la mayoría de los casos, ya que es más rápido que los anteriores y no conlleva el inconveniente del cálculo del Hessiano y su inversa.

Algoritmo de Levenberg-Marquardt

El algoritmo de Levenberg-Marquardt, también conocido como método de mínimos cuadrados amortiguado, ha sido diseñado para trabajar específicamente con funciones de error que se expresan como suma de errores cuadráticos (algo muy habitual). Funciona sin calcular la matriz Hessiana exacta, ya que en su lugar hace uso del vector de gradiente y de la matriz Jacobiana.

Supongamos que la función de error se puede expresar como:

$$f (w)= \sum_{i=0}^m e_i^2(w)$$

Donde \(m\) es el número de instancias (tamaño) del conjunto de datos de entrenamiento, y \(e_i(w)\) es el error que se comete en la instancia \(i\)-ésima.

La matriz Jacobiana de la función de error se calcula como la formada por las derivadas de los errores con respecto a los parámetros, es decir:

$$J_{ij}(w) = \frac{\partial e_i}{\partial w_j},\mbox{ donde } i = 1, \dots, m \mbox{ y } j = 1, \dots, n$$

(donde, recordamos que \(m\) es tamaño del conjunto de datos, y \(n\) el número de parámetros en la red neuronal)

El vector gradiente de la función de error se puede calcular ahora como (donde \(e\) es el vector de todos los términos de error):

$$\nabla f = 2 J^T e$$

Finalmente, el Hessiano se puede aproximar por (donde \(\lambda\) es el factor de amortiguamiento que asegura la positividad del Hessiano, e \(I\) es la matriz de identidad):

$$H \approx 2 J^T J + \lambda I$$

A partir de las igualdades anteriores se define el proceso de mejora de parámetros con el algoritmo de Levenberg-Marquardt:

$$w_{i+1} = w_i - (J_i^T J_i + \lambda_i I)^{-1} (2 J_i^T e_i)$$

Cuando \(\lambda= 0\) se obtiene el método de Newton utilizando el Hessiano aproximado. Si \(\lambda\) es muy grande, se convierte en el algoritmo de Descenso del Gradiente con una tasa de entrenamiento pequeña. Por ello, \(\lambda_0\) se inicializa para que sea grande, de modo que las primeras actualizaciones sean pequeños pasos en la dirección de descenso del gradiente. Si alguna iteración genera un fallo, entonces \(\lambda\) se incrementa por algún factor, si no, a medida que disminuye el error, \(\lambda\) disminuye, de manera que el algoritmo de Levenberg-Marquardt se aproxima al método de Newton. Este proceso normalmente acelera la convergencia al mínimo.

Este algoritmo es un método adaptado para funciones de un tipo particular, lo que hace que sea muy rápido cuando se entrenan redes neuronales medidas con ese tipo de errores. Sin embargo, este algoritmo tiene algunas desventajas. El primero es que no puede aplicarse a funciones de error que tengan expresiones distintas. Además, no es compatible con los términos de regularización. Finalmente, para conjuntos de datos y redes neuronales muy grandes, la matriz jacobiana se hace enorme, y por lo tanto requiere mucha memoria. En consecuencia, el algoritmo de Levenberg-Marquardt no se recomienda cuando tenemos grandes conjuntos de datos y/o redes neuronales con muchas unidades.

Comparación de memoria y velocidad

El siguiente gráfico (extraído de aquí) da una idea de las variaciones de velocidad computacional y requerimientos de memoria de los algoritmos de entrenamiento discutidos en esta entrada. 

Como refleja el gráfico, el algoritmo de entrenamiento más lento es el de Descenso del Gradiente, pero es el que requiere menos memoria. Por el contrario, el más rápido puede llegar a ser el algoritmo de Levenberg-Marquardt, pero normalmente requiere mucha memoria y solo compensará en casos en los que el entrenamiento se hace muy laborioso. Un buen compromiso podría ser alguno de los métodos Cuasi-Newton, que consiguen una mejora considerable en la velocidad, con respecto al método simple, sin necesidad de incrementar el consumo de memoria de forma dramática.

Aunque no son exactamente los mismos 5 métodos que se han presentado en esta entrada, puede resultar muy instructivo visitar la entrada del blog de Ben Frederickson de título An Interactive Tutorial on Numerical Optimization, donde se pueden ver en acción algunos algoritmos de optimización sobre funciones concretas de ejemplo.

« Complex Networks Tool… « || Inicio || » La Tiranía de las Pub… »