Análisis de los Optimizadores de Redes Neuronales con TensorFlow
Published:
Optimización$^{[1]}$
La optimización es parte de la vida. En nuestra vida cotidiana, tomamos decisiones que creemos que pueden maximizar o minimizar nuestro conjunto de objetivos. Incluso el proceso de evolución en la naturaleza revela que sigue un proceso de optimización.
El origen de una primera teoría de optimización nació el 4 de junio de 1694, cuando John Bernoulli planteó el problema Brachistochrone (en griego, “tiempo más corto”) y desafió públicamente al mundo matemático a resolverlo. El problema planteado fue: "¿Cuál es una trayectoria de deslizamiento por la que un objeto sin fricción se deslizaría en el menor tiempo posible?"
Cálculo de variaciones. Ecuación de Euler-Lagrange
\(J[u] = \int_a^b L(x, u, u')dx\)
"Since the fabric of the universe is most perfect, and is the work of a most wise Creator, nothing whatsoever takes place in the universe in which some form of maximum and minimum does not appear."
–Leonard Euler
Optimización en Inteligencia Artificial $^{[2]}$
En la inteligencia artificial, la optimización de funciones es una área fundamental y sus técnicas de métodos númericos son utilizadas ampliamente.
En específico, para el aprendizaje de máquina se utiliza una base de datos como entrentamiento. El APRENDIZAJE es una forma de optimización que se utiliza para predecir cualquier tipo de evento o variable.
La optimización de funciones involucra tres elementos:
- Input $x$. Valores de entrada de la función a evaluar
- Función $f(x)$. Función objetivo que evalúa el dominio, modelo.
- Error o Costo. Resultado de la evaluación de un elemento del dominio con la función. </ol>
El espacio de búsqueda es todo el conjunto parámetros de la función $f(x)$ que minimizan la función de costo $y$.
Aprendizaje con cálculos analíticos, e.g. Regresión Lineal $^{[3]}$
Sea $X^T$ un conjunto de datos $i.i.d. \backsim \mathcal{N} \left(\mu, \sigma^2\right) $ , $\hat{y}$ la respueta del modelo con el parámetro $\beta$.
$$\hat{y} =\beta_0 + \sum_{j=1}^pX_j\hat{\beta_j}= X^T\hat{\beta}$$ Para entrenar el modelo se pueden utilizar distintos métodos.Mínimos Cuadrados
Se selecionan los parámetros del modelo minimizando la función de error definida por la suma de los residuos al cuadrado (RSS, residual sum of squares) $$RSS(\beta) = \sum_{i=1}^N\left(y_i-x_i^T\beta\right)^2=\left(y-X\beta\right)^T\left(y-X\beta\right)$$
Es una función cuadrática de los parámetros; entonces, siempre existe un mínimo. Diferenciando respecto a $\beta$ se obtienen las ecuaciones normales $$X^T(y-X\beta) = 0$$ Si $X$ es no-singular, la única solución, está dada por $$\hat{\beta} = \left(X^TX\right)^{-1}X^Ty$$
Recordemos que por el teorema de Gauss-Markov, los parámetros del modelo de regresión lineal es el de menor varianza, si cumple algunos principios: no colonialidad, independencia, homocedasticidad, no covarianza, los errores siguen una distribución normal con un media cero y varianza constante.Estimación de Máxima Verosimilitud con error Gaussiano $^{[3]}$
Entrenar el modelo con distribuciones de probabilidad condicional que permitan calcular los parámetros $w$ con mayor probabilidad describan los datos del proceso que siguen una ecuación lineal:
$$y = x^Tw + \epsilon$$
donde $\epsilon \in \Re$ es una variable aleatoria que describe el ruido. Se puede inferir que la variable sigue una distribución Gaussiana con varianza $\sigma^2$ con media$\mu = 0$. Entonces, la probabilidad de condicional es: $$y_n|\mu, \sigma^2 \backsim \mathcal{N} \left(y_n | 0, \sigma^2\right) $$
La función de la densidad de probabilidad asociada con esta distribución condicional es de la familia de las Gaussianas univariadas: $$Pr\left(y_n|\mathbf{x}_n, \mathbf{w}, \sigma^2\right) = \frac{1}{\sigma\sqrt{2\pi}}exp\left[-\frac{1}{2\sigma^2}(y_n-\mathbf{x}_n^t\mathbf{w})^2\right]$$ Cada observación tiene un ruido asociado $\epsilon_n$, y estas variables son $i.i.d$, entonces la función de versosimilitud es un producto de las distribuciones de cada término: $$Pr\left([y_n]_{n=1}^N|[\mathbf{x}]_{n=1}^N, \mathbf{w}, \sigma^2\right) = \prod_{n=1}^N \frac{1}{\sigma\sqrt{2\pi}}exp\left[-\frac{1}{2\sigma^2}(y_n-\mathbf{x}_n^t\mathbf{w})^2\right]$$ Es posible expresar la versosimilitud como una disribución Gaussiana de dimensión-D, $Pr\left(z|\mathbf{\mu}, \mathbf{\Sigma}\right)$. La matriz de covarianza $\mathbf{\Sigma}$ debe ser cuadrada, simétrica y definida positivamente.
Entonces $$Pr\left(y\vert\mathbf{X}, \mathbf{w}, \sigma^2\right) = \mathcal{N} \left(y | \mathbf{Xw}, \sigma^2\mathbf{I}\right) =$$ $$(2\sigma^2\pi)^{-N/2}exp\left[-\frac{1}{2\sigma^2}(\mathbf{Xw}-y)^T(\mathbf{Xw}-y)\right]$$ Para encontrar el máxima verosimilitud, se extrae el algoritmo de la expresión anterior $$log Pr\left(y|\mathbf{X}, \mathbf{w}, \sigma^2\right) = -\frac{N}{2}log(2\sigma^2\pi) - \frac{1}{2\sigma^2}(\mathbf{Xw}-y)^T(\mathbf{Xw}-y)$$ Entonces, el problema es encontrar el argumento máximo de la expresión anterior con $\mathbf{w}$ $$\mathbf{w}^{MLE} = arg max_{\mathbf{w}} \left[- \frac{1}{2\sigma^2}(\mathbf{Xw}-y)^T(\mathbf{Xw}-y)\right]$$ Aquí el valor de constante $\frac{1}{2\sigma^2}$ no cambia la solución, cambiando el signo se trata de un problema de optimización, idéntico a mínimos cuadrados. $$\mathbf{w}^{MLE} = arg min_{\mathbf{w}}(\mathbf{Xw}-y)^T(\mathbf{Xw}-y)$$ ## EJEMPLO --- Regresión lineal multiple ## import libriries import numpy as np import pandas as pd import tensorflow as tf import sympy as sp import matplotlib.pyplot as plt import os import shutil import seaborn as sns import pathlib from sklearn.metrics import accuracy_score, confusion_matrix, classification_report from tensorflow import keras from keras.applications import ResNet50V2 from keras.layers import Dense, Flatten, GlobalAveragePooling2D, Dropout from keras.optimizers import Adam from tensorflow.keras.optimizers import * from keras import layers, optimizers, regularizers from keras.models import Sequential, load_model from keras.preprocessing.image import ImageDataGenerator import warnings from PIL import Image import cv2 import pandas as pd from sklearn import linear_model import statsmodels.api as sm from numpy import arange from numpy import asarray from numpy.random import rand from numpy.random import seed warnings.filterwarnings('ignore') plt.rcParams["figure.figsize"] = [12, 9] # ancho, alto de figuras plt.rcParams["font.size"] = 20 ## load data set ## https://www.kaggle.com/datasets/aungpyaeap/fish-market/download?datasetVersionNumber=2 ## https://www.kaggle.com/datasets/aungpyaeap/fish-market?resource=download fishData = pd.read_csv("Fish.csv") print(f"Las columnas de la tabla son: {list(fishData.columns)}") print(f"El tamaño de la tabla es de {fishData.shape[1]} columnas y de {fishData.shape[0]} filas") x = fishData[['Length1', 'Length2', 'Length3', 'Height', 'Width']] y = fishData['Weight'] # sklearn regr = linear_model.LinearRegression() regr.fit(x, y) print('Intercept: \n', regr.intercept_) print('Coefficients: \n', regr.coef_) #statsmodels x = sm.add_constant(x) model = sm.OLS(y, x).fit() predictions = model.predict(x) print_model = model.summary() print(print_model)Redes Neuronales $^{[3]}$
Modelo de aprendizaje
Modelo que procesa la información de forma semejante a la respuesta química-eléctrica de una neurona.
Modelo estadístico semejante a Projection pursuit regression. Se generan $Z_M$ elementos creados por combinaciones lineales de entrada, y a su vez, la función objetivo $f_k(X)$ es modelada con una función de combinaciones lineales de $Z_M$
$$Z_m = \sigma\left( \alpha_{0m}+ \alpha_m^TX \right), m = 1, \dots, M,$$ $$T_k = \beta_{0k} + \beta_k^TZ, k = 1, \dots, K,$$ $$f_k(X) = g_k(T), k = 1, \dots, K,$$
La función de activación $\sigma(v)$ se escoge con base en el problema- $\mathcal{Sigmoide}$ $\sigma(v) = 1/(1+e^{-v})$
- $\mathcal{ReLu}$ $\sigma(v) = max{0,z}$
- $\mathcal{Softmax}$ $\sigma(v) = \frac{e^{v_k}}{\sum_{l=1}^K e^{vl}}$
Modelo estadístico - Projection pursuit regression $^{[3]}$
Modelo estadístico no-paramétrico que consiste en combinaciones lineales de funciones de Ridge en un espacio $\Re^p$, que genera un conjunto grande de modelos. $$f\left(X\right) = \sum_{m=1}^M g_m\left(\omega_m^TX\right)$$
donde $\omega_m$, $m = 1, 2, \dots, M,$ es el vector-p unitario de parámetros desconocidos. Además, $V_m=\omega_m^TX$ es la variable escalar de la proyección de X en el vector unitario $\omega_m$ Si el número de parámetros en el modelo aumenta lo suficiente, los modelos son aproximadores universales. Sin embargo, el modelo es útil solo para predicción porque no produce un modelo interpretable y entendible que describan los datos.
Lo que se quiere lograr es aproximar los mínimos de la función de error $$\sum_{i=1}^N\left[y_i-\sum_{m=1}^Mg_m(\omega_m^Tx_i)\right]^2$$
sobre las funciones agregadas $g_m$ y la dirección de vectores $\omega_m$.Entrenar redes neuronales $^{[3]}$
El modelo de redes neuronales tiene parámetros desconocidos, llamados $\mathcal{pesos}$, durante el entrenamiento con los datos, se buscan estos valores.
$${\alpha_{0m}, \alpha_m; m = 1, 2, \dots, M} M(p+1) \mathcal{pesos}$$ $${\beta_{0k}, \beta_k; m = 1, 2, \dots, M} K(M+1) \mathcal{pesos}$$Por otra parte, las funciones típicas de error o pérdida son:
$$R(\theta)=\sum_{i=1}^N \sum_{k=1}^K(y_{ik}-f_k(x_i))^2$$ los errores al cuadrado para regresión. $$R(\theta)=-\sum_{i=1}^N \sum_{k=1}^Ky_{ik}logf_k(x_i)$$ la entropía cruzada para clasificación.Algoritmos de optimización $^{[2]}$
Como hemos visto en el curso, existen diversos métodos númericos para minimizar la función de pérdida $R(\theta)$, en un contexto de generalidad no se busca el mínimo global para evitar sobre entrenamiento (Bias-Variance Treatoff). Por otra parte, en ocasiones es posible que el problema de optimización se complique porque la función de pérdida es no convexa y/o inestable, o simplemente es muy compleja para calcular su derivada. Una forma de clasificar los algoritmos de optimización es con base en si la funciones objetivo son diferenciables o no.
- Diferenciable
- Algoritmo Bracketing
- Algoritmo de Fibonacci
- Algoritmo de busqueda por sección de oro (Golden Section Search)
- Algoritmo de bisección
- Algoritmo descendente local
- Algoritmo de búsqueda lineal
Algoritmos de optimización $^{[2]}$
- Diferenciable
- Algoritmo de primer orden
- Algoritmo de gradiente descendente
- Algoritmo de Momento
- Algoritmo de Adagrad
- Algoritmo de RMSProp
- Algoritmo de Adam
- Algoritmo de segundo orden
- Algoritmo del método de Newton
- Algoritmo del método de secante
- Algoritmos Quasi-Newton
- Algoritmo de Davidson-Fletcher-Powell
- Algoritmo de BFGS (Broyden, Fletcher, Goldfarb, and Shanno Algorithm)
- Algoritmo de Limited-memory BFGS (L-BFGS)
Los algoritmos de gradiente descendente son muy populares, ya que son algoritmos muy sencillos y efectivos; además, proveen las bases para extender o modificar el algoritmo para mayor desempeño. Existen diversas variantes que se implementan en paqueterías de python sobre deep learning como TensorFlow o Pytorch.
Algoritmos de optimización $^{[2]}$
- Diferenciable
- Algoritmo de primer orden
- Algoritmo de gradiente descendente
- Algoritmo de Momento
- Algoritmo de Adagrad
- Algoritmo de RMSProp
- Algoritmo de Adam
- Algoritmo de segundo orden
- Algoritmo del método de Newton
- Algoritmo del método de secante
- Algoritmos Quasi-Newton
- Algoritmo de Davidson-Fletcher-Powell
- Algoritmo de BFGS (Broyden, Fletcher, Goldfarb, and Shanno Algorithm)
- Algoritmo de Limited-memory BFGS (L-BFGS)
Los algoritmos de gradiente descendente son muy populares, ya que son algoritmos muy sencillos y efectivos; además, proveen las bases para extender o modificar el algoritmo para mayor desempeño. Existen diversas variantes que se implementan en paqueterías de python sobre deep learning como TensorFlow o Pytorch.
Algoritmos de optimización $^{[2]}$
Algoritmos de optimización no tan númericos.
- No Diferenciable
- Algoritmos directos
- Algoritmo Nelder-Mead
- Algoritmo de coordenadas cíclicas (Cyclic Coordinate Search)
- Algoritmo del método de Powell
- Algoritmo del método Hooke-Jeeves
- Algoritmos estocásticos
- Algoritmo de recocido simulado (Simulated Annealing algorithm)
- Algoritmo de estrategias de evolución (Evolution strategies algorithm)
- Algoritmo del método de entropía cruzada
- Algoritmo escalada de colinas estocásticas (Stochastic Hill climbing algorithm)
- Algoritmo Búsqueda local iterada (Iterated Local Search algorithm)
- Algoritmos poblacionales
- Algoritmo genéticos (genetic algorithm)
- Algoritmo de evolución diferencial (Differential evolution algorithm)
- Algoritmo de optimización de enjambre de partícula (Particle Swarm Optimization)
Algoritmo de gradiente descendente $^{[2]}$
Es un algoritmo de optimización de primer orden porque calcula explícitamente la primera derivada de la función de pérdida sobre todo el vector de parámetros. Sea la función de pérdida la suma de los errores al cuadrado.
$$R(\theta)=\sum_{i=1}^N \sum_{k=1}^K(y_{ik}-f_k(x_i))^2$$ donde $f_k(X) = g_k(T),$ $T_k = \beta_{0k} + \beta_k^TZ,$ y a su vez $Z_m = \sigma\left( \alpha_{0m}+ \alpha_m^TX \right),$ con derivadas $$\frac{\partial R_i}{\partial\beta_{km}}= -2\left(y_{ik}-f_k(x_i)\right )g_k'\left(\beta_k^Tz_i\right )z_{mi} = \sigma_{ki}z_{mi}$$ $$\frac{\partial R_i}{\partial\alpha_{ml}}= -\sum_{k=1}^K 2 \left(y_{ik}-f_k(x_i)\right )g_k'\left(\beta_k^Tz_i\right )\beta_{km}\sigma'\left(\alpha_m^Tx_i\right)x_{il} = s_{mi}x_{il} $$ Las cantidades $\sigma_{ki}$ y $s_{mi}$ son proporcionales al error del modelo de redes neuronales, el primero en la salida de capas externas y el segundo en capas escondidas.
Dado estas derivadas, el valor de los paramétros en la iteración $(r+1)$ tiene una actualización de la forma: $$\beta_{km}^{(r+1)}=\beta_{km}^{(r)}-\gamma_r\sum_{i=1}^N\frac{\partial R_i}{\partial\beta_{km}^{(r)}}$$ $$\alpha_{ml}^{(r+1)}=\alpha_{ml}^{(r)}-\gamma_r\sum_{i=1}^N\frac{\partial R_i}{\partial\alpha_{ml}^{(r)}}$$ donde $\gamma_r$ es la tasa de aprendizaje. Hiperparámetro que controla qué tanto nos movemos en el espacio de búsqueda en cada iteración.
Se reescribe el valor de $s_{mi}$ como $$s_{mi}= \sigma'\left(\alpha_m^Tx_i\right )\sum_{k=1}^K\beta_{km}\sigma_{ki}, $$ conocido como ecuación de propagación hacia atrás o regla-delta. # objective function def objective(x): return x**2.0 # derivative of objective function def derivative(x): return x * 2.0 def gradient_descent(objective, derivative, bounds, n_iter, step_size): # track all solutions solutions, scores = list(), list() # generate an initial random point solution = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0]) solution = solution[0] res = [[solution, objective(solution), np.nan]] # run the gradient descent for i in range(n_iter): # calculate gradient gradient = derivative(solution) # take a step solution = solution - step_size * gradient # evaluate candidate point solution_eval = objective(solution) # store solution solutions.append(solution) scores.append(solution_eval) res.append([solution, solution_eval, solution+step_size * gradient]) # report progress #print('>%d f(%s) = %.5f' % (i, solution, solution_eval)) return [solutions, scores], res #return res # define range for input bounds = asarray([[-1.0, 1.0]]) # define the total iterations n_iter = 30 # define the step size step_size = 0.1 # perform the gradient descent search [solutions, scores], res = gradient_descent(objective, derivative, bounds, n_iter, step_size) #res = gradient_descent(objective, derivative, bounds, n_iter, step_size) # sample input range uniformly at 0.1 increments inputs = arange(bounds[0,0], bounds[0,1]+0.1, 0.1) # compute targets results = objective(inputs) ###Plot optimización de función x^2 plt.plot(inputs, results, label="$f(x)$") plt.plot(solutions, scores, '.-', color='red', label="Trayectoria gradiente descendente") plt.annotate(r"$arg$ $min$ $x$", xy=[0.0, 0], xytext=[0.12, 0.25], arrowprops=dict(arrowstyle='->'),) plt.legend(loc=(0.160, 0.80)); plt.show() df = pd.DataFrame(res, index=range(len(res)), columns=["$x_n$", "$f(x_n)$", "$x_n - x_{n-1}$"]) df.style.format(dict(zip(df.columns, ["{:.6g}", "{:+.3e}", "{:+.3e}"])))