TICS-579-Deep Learning

Clase 3: Feed Forward Networks

Alfonso Tobar-Arancibia

Feed Forward Networks

Feed Forward Networks

Teorema de aproximación Universal

Se dice que una Red Neuronal puede aproximar cualquier función en una región cerrada.

Es decir,

\[\underset{x \in \mathbb{D}}{max}|f(x) - \hat{f(x)}| \le \epsilon\]

con \(D \subset \mathbb{R}\) y \(\epsilon >0\).

  • Existen muchas otras funciones como Splines, KNN y otras que también tienen esta propiedad.
  • Si se escogen puntos suficientemente cercanos, cumplir esta propiedad es trivial.

Feed Forward Networks (FFN)

Es un tipo de Arquitectura caracterizada por Nodos en un nivel que se conectan con todos los nodos del siguiente nivel. Este es probablemente el tipo de Arquitectura de Red Neuronal más común.

Este tipo de Redes tiene distintos nombres que son usados de manera intercambiable:

  • Capas Lineales: Probablemente por su denominación en Pytorch.
  • Capas/Redes Densas: Probablemente por su denominación en Tensorflow.
  • Multilayer Perceptron: O también conocido como MLP, debido a que es la generalización del Perceptrón, la primera propuesta de Redes Neuronales de Rosenblatt en 1958.

Comunmente

  • Cada suma-producto de parámetros e inputs corresponde a una capa. Nosotros llamamos capas de parámetros a cada grupo de conexiones.
  • Cada nodo corresponde a una Neurona. Nosotros consideramos que eso es la nueva dimensión a la que se mueve nuestro vector de entrada.
    • SPOILER: El número de nodos/neuronas no es importante, nos importan más los parámetros necesarios.
  • La capa de salida corresponde a nuestra Hipótesis.

FFN: Formalmente

\[ Z_1 = X\] \[Z_{i +1} = \sigma_i(Z_i W_i + b_i)\] \[h_\theta(X) = Z_{L + 1}\]

con \(\theta = \{W_{1:L}, b_{1:L}\}\)

para \(i=1,...,L\)

\(Z_{i}\) corresponde a la salida de la capa \(i\), \(W_i\) corresponde al conjunto de parámetros de la capa \(i\), y \(b_i\) corresponde al bias de la capa \(i\).

  • \(b_i\) es otro set de parámetros, llamado bias (el cual se traduce como sesgo, pero lo vamos a mantener en inglés para evitar confusiones semánticas).
  • Al agregar este componente, ya no se tiene una Transformación Lineal, sino que una Transformación Affine.

FFN - Broadcasting

\[Z_{i + 1} = \sigma_i(Z_i W_i + b_i^T)\]

Si chequeamos las dimensiones:

  • \(Z_i \in \mathbb{R}^{m \times n_i}\)
  • \(W_i \in \mathbb{R}^{n_i \times n_{i+1}}\)
  • Pero, \(b_i \in \mathbb{R}^{n_i+1}\)

Tenemos un problema y es que esto hace que las dimensiones no calcen. Esto sería una operación no válida en términos matriciales. Sin embargo es posible realizarla aplicando Broadcasting.

Broadcasting

Corresponde a una replica de una dimensión de manera de permitir alguna operación que requiera que ciertas dimensiones calcen.

Broadcasting Rules

  • Cada tensor debe tener al menos una dimensión.
  • Moviéndose de derecha a izquierda por cada dimensión una vez alineadas a la derecha, las dimensiones deben:
    • Ser iguales,
    • iguales a 1,
    • o no debe existir.

FFN - Broadcasting

Matemáticamente el Broadcasting en este caso corresponde a:

\[ b_i^T = 1 b_i^T\]

donde 1, es un vector de unos de \(m \times 1\) y \(b_i\) es de dimensión \(n_{i+1} \times 1\), al cuál se está aplicando el producto externo.

El Broadcasting permitirá que \(b_i\) tenga ahora dimensiones \(m \times n_{i+1}\), lo cuál permitirá que la operación de suma se pueda realizar.

El Broadcasting evita que se tenga que almacenar información repetida, lo cual permite que las implementaciones sean más eficientes en términos de memoria. Siempre que se pueda se debe utilizar Broadcasting para simplificar un cálculo.

Más info ver: Numpy Docs

Ya pero formalmente, ¿Cómo entrenamos una red Neuronal?

Hiperparámetros de una Red Neuronal

Hiperparámetros

Valores necesarios para el cómputo de una red neuronal que deben ser determinados por el modelador. Estos valores NO pueden ser aprendidos de manera autónoma por la red neuronal.

  • Learning Rate (Karpathy Constant: 3e-4)
  • ¿Cuáles son las dimensiones de los Pesos (Weights) y de los sesgos (Biases)? (De qué tamaño es cada capa)
  • ¿Qué funciones de Activación se utilizarán?
  • ¿Qué funciones de perdida se utilizarán?
  • ¿Qué optimizadores se utilizarán?
  • ¿Cómo se inicializarán los parámetros de la Red Neuronal?
  • ¿Cuánto tiempo entrenanaremos nuestro modelo? ¿Cómo sabemos si es que convergió o no?

Tipos de Hipótesis

En el aprendizaje supervisado contamos principalmente con la resolución de dos tipos de Problemas: Clasificación y Regresión. Dependiendo del tipo de Problema armaremos nuestra hipótesis.

\[h_\theta(X) = Z_{L+1} \in \mathbb{R}^{m \times k}\]


Clasificación

  • Clasificación Binaria: Se requiere un \(k=1\). Se usa una Función Sigmoide para transformar el Output en la probabilidad de que ocurra la clase positiva.
  • Clasificación Multiclase: Se requiere un \(k=C\), donde C es el número de clases a clasificar. Se usa una función Softmax para transformar el output en una distribución de probabilidades.
  • Clasificación Multilabel: Se requiere un \(k=C\) donde C es el número de clases a clasificar. Se usa una función Sigmoide para transformar cada clase en probabilidades.

Regresión

  • Regresión Simple: Se requiere un \(k=1\). No requiere de funciones adicionales.
  • Regresión Multiple: Se requiere un \(k=V\) con V el número de valores a predecir.

Adicionalmente se pueden utilizar funciones como la sigmoide o ReLU para forzar salidas entre 0 y 1 o entre 0 e \(\infty\) respectivamente.

Normalmente las funciones necesarias en la capa de salida van embebidas en la Loss Function. Normalmente estas funciones sí deben aplicarse al momento de la Predicción del modelo.

Funciones de Activación

Activation Functions
Corresponden a las funciones que agregarán características no lineales a nuestra hipótesis, impidiendo la composición de transformaciones lineales (o Affine).

Como convención, las funciones de activación sólo se aplicarán a las Hidden Layers. Es decir \(\sigma_{L+1}(x) = x\).

Otras convenciones utilizan funciones de activación para la capa de salida. Esto bajo el abánico de Pytorch no es correcto ya que la Activación de la última capa esta embebida en la Loss Function (Recordar como Softmax es parte del Cross Entropy).

Ahora, sí es posible utilizar funciones de Activación a la salida de una predicción, pero dichas funciones tienen otro propósito y no son del todo estrictamente necesarias.

¿Puedo aplicar distintas Funciones de Activación a cada Neurona?

Para ver más Activation Functions y detalles de su funcionamiento, ir directamente a la Documentación de Pytorch.

Funciones de Activación

Derivadas:

  • Sigmoide: \(g'(z) = g(z)(1 - g(z))\)
  • Tanh: \(g'(z) = 1 - g^2(z)\)
  • ReLU: \(g'(z) = \begin{cases} 0, & \text{if $z \le$ 0} \\[2ex] 1, & \text{if $z > 0$} \end{cases}\)

Funciones de Activación Modernas

Leaky ReLU

\[g(z) = max(0.1z, z)\]

Parametrized ReLU (PReLU)

\[g(z) = max(az, z)\]

Funciones de Activación

ELU

\(g(z) = \begin{cases} z, & \text{if $z \ge$ 0} \\[2ex] \alpha(e^{z}-1), & \text{if $z < 0$} \end{cases}\)

GELU

\[\begin{align} g(z) &= z \cdot \Phi(z) \\ g(z)&= 0.5 \cdot z \cdot \left(1 + Tanh\left(\sqrt{2/\pi}\right) \cdot \left(z + 0.044715 \cdot z^3\right)\right)\end{align}\]

Funciones de Activación

SELU

\[ g(z) = scale \cdot (max(0,z) + min(0,\alpha(e^z - 1)))\]

con \(\alpha=1.6732632423543772848170429916717\) y \(scale = 1.0507009873554804934193349852946\)

Swish

\[g(z) = z \cdot sigmoid(z)\]

Loss Functions

Al igual que el caso de la Hipótesis, la Loss Function dependerá del tipo de problema a resolver. Existen muchas Loss Functions, pero los más comunes para problemas generales son las siguientes:

Clasificación Binaria: Binary Cross Entropy

\[BCE_i = - \left[y_i \cdot log(h(x_i)) + (1-y_i) log(1-h(x_i))\right]\]

donde \(h(x)\) corresponde a un valor de probabilidad de la clase positiva (debe ir entre 0 y 1).

En Pytorch se suele utilizar BCEWithLogitsLoss ya que aplica una función Sigmoide a la capa de salida además de ser una clase numericamente más estable. Esto garantiza que la salida de la Red tiene valores entre 0 y 1 como se necesita.

Clasificación Multiclase: CrossEntropy

\[CE_i = -log \left(\frac{exp(h_{(i=y)}(x_i))}{\sum_{j=1}^k exp(h_j(x_i))}\right)\]

En Pytorch se suele utilizar CrossEntropyLoss ya que combina aplica una función Softmax a la capa de salida además de ser una clase numericamente más estable.

Loss Functions

Clasificación Multilabel: CrossEntropy

Para este tipo de problema se debería aplicar un Negative LogLoss combinado con la salidas de una red que van entre 0 y 1 (es decir, que se aplica una Sigmoide)

En Pytorch se suele utilizar BCEWithLogitsLoss ya que combina aplica una función Softmax a la capa de salida y permite resultados de más de una dimensión.

Regresión
  • Mean Absolute Error o L1Loss

\[L1_i = |y_i - h(x_i)|\]

  • Mean Squared Error Loss o L2Loss

\[L2_i = (y_i - h(x_i))^2\]

Es importante recordar que en general se debe calcular un valor agregado de la Loss Function. En Pytorch a esto se le llama reduction. Donde el más utilizado es reduction="mean". Es decir,

\[l = \frac{1}{m}\sum_{i=1}^m L_i\]

Optimizers

Gradient Descent corresponde al algoritmo de Optimización más popular, pero no necesariamente el más eficiente. Distintas variantes han ido apareciendo para ir mejorando eventuales deficiencias de la proposición inicial.

Normal Gradient Descent

\[\theta := \theta - \frac{\alpha}{m}\nabla_\theta l(h_\theta(X), y), \text{donde $X \in \mathbb{R}^{m \times n}$ e $y \in \mathbb{R}^{m \times 1}$}\]

La dirección del Gradiente utilizando menos puntos debería ser más o menos similar. Sin duda, más ruidoso, pero a la larga debería dirigir en casi la misma dirección. Por lo que podríamos hacer actualizaciones de parámetros utilizando B datos con B << m.

Esto entrega como beneficio, menos requerimientos de memoria, ya que operarían matrices más pequeñas, por lo tanto, requiere de menos RAM tanto en CPU como en GPU.

Stochastic Gradient Descent (MiniBatch)

\[\theta := \theta - \frac{\alpha}{B}\nabla_\theta l(h_\theta(X), y), \text{donde $X \in \mathbb{R}^{B \times n}$ e $y \in \mathbb{R}^{B \times 1}$}\]

Se van tomando \(B\) muestras de manera incremental hasta utilizar la totalidad de datos de entrenamiento

SGD with Momentum

Update Rule

\[u_{t + 1} = \beta u_t + (1-\beta) \nabla_\theta f(\theta_t)\] \[\theta_{t+1} = \theta_t - \alpha u_{t + 1}\]

donde \(0<\beta<1\), pero normalmente \(\beta=0.9\).

Este cálculo se denomina un Exponential Moving Average de los Gradientes.

\[\begin{align} u_{t+1}&=(1-\beta)\nabla_\theta f(\theta_{t}) + \beta u_t \\ u_{t+1}&=(1-\beta)\nabla_\theta f(\theta_{t}) + \beta \left[(1-\beta) \nabla_\theta f(\theta_{t-1}) + \beta u_{t-1}\right] \\ u_{t+1}&=(1-\beta)\nabla_\theta f(\theta_{t}) + \beta (1-\beta) \nabla_\theta f(\theta_{t-1}) + \beta^2 (1-\beta) \nabla_\theta f(\theta_{t-2})... \\ \end{align}\]

La componente de momento, está tomando en consideración todos los otros Gradientes en pasos anteriores para escoger correctamente la dirección del Gradiente actual.

SGD with Nesterov Momentum

\[u_{t + 1} = \beta u_t + (1-\beta) \nabla_\theta f(\theta_t - \alpha u_t)\] \[\theta_{t+1} = \theta_t - \alpha u_{t + 1}\]

Notar que la lógica es casi la misma, sólo que el Gradiente se evalúa en un punto futuro. Es decir, \(\theta_t-\alpha u_t\) corresponde al punto siguiente utilizando SGD con Momentum.

Momentum

Nesterov

Métodos Adaptativos: Adagrad

¿Qué tal, si la tasa de aprendizaje se va adaptando en el tiempo y deja de ser estática?

Idea

  • Normalizar por la historia de los gradientes al cuadrado.

\[r_{t+1} = r_t + \nabla_\theta f(\theta_t)^2\] \[\theta_{t+1} = \theta_t - \frac{\alpha}{\sqrt{r_{t+1}}}\nabla_\theta f(\theta_t)\]

Métodos Adaptativos: RMSProp

Idea

  • Normalizar por el Exponential Moving Average de los Gradientes al cuadrado.

\[s_{t+1} = \beta r_t + (1-\beta) \nabla_\theta f(\theta_t)^2\] \[\theta_{t+1} = \theta_t - \frac{\alpha}{\sqrt{s_{t+1}}}\nabla_\theta f(\theta_t)\]

Métodos Adaptativos: Adam

Idea

  • Combinar Descenso con Momentum y RMSProp.

\[v_{t+1} = \beta_1 v_t + (1-\beta_1) \nabla_\theta f(\theta_t)\] \[s_{t+1} = \beta_2 s_t + (1-\beta_2) \nabla_\theta f(\theta_t)^2\] \[\theta_{t+1} = \theta_t - \frac{\alpha}{\sqrt{s'_{t+1}}} v'_{t+1}\]

Correcciones Iniciales

\[v'_{t+1} = \frac{v_{t+1}}{1-\beta_1^{t+1}}\] \[s'_{t+1} = \frac{v_{t+1}}{1-\beta_2^{t+1}}\]

Pytorch utiliza 0.9 y 0.999 como valores de \(\beta_1\) y \(\beta_2\) respectivamente.

Eso es todo amigos