Deep Learning - Clases

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 48

DEEP LEARNING

La Inteligencia Artificial es el comportamiento humano realizado por máquinas. Es una disciplina


científica que se ocupa de crear programas informáticos que ejecutan operaciones comparables a las
que realiza la mente humana, como el aprendizaje o el razonamiento lógico.

El Aprendizaje Automático es una estrategia para alcanzar la inteligencia artificial. Es el estudio y la


construcción de algoritmos que pueden aprender de y hacer predicciones sobre datos. Si hay datos,
es aprendizaje automático. En la práctica, el aprendizaje maquinal puede resolver problemas de
inteligencia artificial “estrechos” (Narros AI). Es decir, son modelos que pueden resolver muy bien un
problema pero no otro. Hoy en día, tenemos modelos que son muy buenos para resolver una única
tarea: si los corremos de ahí no sabe qué hacer y el modelo no es bueno. Segmentación y
clasificación de imágenes, reconocimiento de voz, problemas de predicción a través de un conjunto
de datos tabulares, son todos problemas que se pueden resolver muy bien con ML. En el aprendizaje
maquinal (o automático) se tienen datos, con los que se entrena un modelo para predecir a partir de
datos desconocidos.
Tenemos distintos paradigmas dentro del aprendizaje automático: el Aprendizaje Supervisado donde
se tiene un conjunto de datos con etiquetas que supervisan el proceso de aprendizaje y se hace una
predicción de datos nuevos con el modelo entrenado. Los algoritmos de Aprendizaje No
Supervisado intentan aprender la estructura de los datos. Y los algoritmos de Aprendizaje Semi
Supervisado utilizan tanto datos etiquetados como no etiquetados durante el proceso de
aprendizaje. Por medio del uso de datos no etiquetados, se pueden aprender representaciones de
los datos y usar esto para hacer predicciones más certeras (se conoce mejor cómo se distribuyen los
datos sobre los que se hacen predicciones). El Aprendizaje por Refuerzo es el aprendizaje a partir de
la interacción con el ambiente. Es decir, se le da un refuerzo al modelo cuando hace bien la tarea.
Hay un agente que interactúa con el entorno realizando acciones, y si realiza bien la acción se le da
una recompensa. No es supervisado porque no hay etiquetas.

Hay dos tipos de modelos de aprendizaje automático: modelos discriminativos y modelos


generativos. Los Modelos Discriminativos son modelos que terminan aprendiendo una especie de
probabilidad condicionada en el dato, P(Y|X). Cuál es la probabilidad que dado un dato, sea de
determinada clase. Se aprende un modelo predictivo-discriminativo, donde se trata de discriminar y
separar los datos en, por ejemplo, 2 conjuntos. Por otro lado, los Modelos Generativos buscan
generar datos en vez de clasificiar; crear cosas que tengan sentido. En este caso, se aprende la
distribución de los datos, es decir P(X).
A nosotros nos va a interesar resolver problemas discriminativos supervisados (ej. clasificación de
imágenes)

Clasificación de Imágenes
Se predice cuál es la probabilidad de que en una determinada imagen haya un gato, por ejemplo.
Para Python, las imágenes son matrices de números (intensidades). En general, son valores que van
entre 0 y 255 o pueden ir entre 0 y 1. Si se trabaja con una imágen a color, se tienen 3 RGB, 3
“rebanadas de pan” una atrás de la otra. Es decir, 3 matrices del mismo tamaño donde cada una
representa un canal de color diferente.

Antes se extraían características/features de las imágenes manualmente por las rutinas de código y
una vez que se tenían las características relevantes para el problema que se quería resolver, se podía
decir, por medio de un clasificador, si lo de la imágen es un perro salchicha o no.
EJEMPLO: tenemos la imagen de una bicicleta, se generan otras representaciones por medio de
rutinas que sirven para hacer una extracción de características (ej. color promedio de la imágen,
desvió estándar en los colores de la imagen, dividir la imagen en cuadrantes y ver el color promedio
de cada cuadrante). Se seleccionan las características más relevantes para el problema, se meten
estás características en un clasificador y se hace la predicción.
El problema estaba en que las características eran extraídas de forma manual, por lo que era difícil
resolver un problema en computer vision.
El Deep Learning es una técnica para implementar aprendizaje maquinal. Los modelos basados en
deep learning son capaces de aprender representaciones de los datos de entrenamiento en múltiples
niveles de abstracción (capas), componiendo módulos simples que sucesivamente transforman
dichas representaciones en otras con mayor nivel de abstracción. Se combinan módulos que son muy
simples pero que combinados, uno atrás del otro en distintas capas (niveles de abstracción),
empiezan a especializarse y se logra aprender características que son útiles para resolver el problema
en cuestión. Las primeras capas se especializan en features de bajo nivel (ej. en imágenes pueden ser
los bordes, las esquinas, etc) y las sucesivas van a empezar a combinar esas capas para construir
conceptos más complejos y finalmente vamos a tener conceptos muy complejos que se basan en los
más simples. Este es un modelo entrenado “end to end” donde todo esto se entrena junto. Ya no se
tienen etapas desacopladas sino que se aprende junto. El componente fundamental son las neuronas
artificiales.

Redes Neuronales Artificiales


Son el producto interno entre un vector de datos de entrada o features (Xi) y un vector de pesos
(Wi). Los pesos se van a aprender en el proceso de aprendizaje durante el entrenamiento del
modelo. Se pasa esta suma por una función no lineal de activación. Esta función toma features de
entrada y tiene pesos que la parametrizan.

En una neurona se tienen entradas, Xi. Se tienen pesos, Wi,


que se aprenden. Se suma el producto de cada combinación y
se pasa esta suma por una función de activación. Esta función
es una función no lineal.
Perceptrón Simple
A una neurona se la llama perceptrón simple. Es decir, un perceptrón simple es la red neuronal más
simple. Un ejemplo de perceptrón simple es predecir si va a llover o no va a llover en base a datos
atmosféricos. La función de activación es no lineal porque solo toma valores 1 y -1 (llueve o no
llueve). Una neurona tiene entrada de datos y una predicción como salida. Estos no son modelos
profundos

Perceptrón Multi Capa


Una neurona puede modelar una determinada cantidad de problemas y si queremos modelar
problemas más complejos necesitamos combinar las neuronas. Al combinarlas armamos
perceptrones multi capa, donde se ponen muchas neuronas en paralelo que toman esas entradas,
procesan y devuelven una salida y esta salida se vuelve a poner dentro de una nueva capa que
procesa lo que salió de la capa anterior. Estos son modelos profundos (porque tienen varias capas).
● El típico modelo profundo, o por lo menos el más básico, tiene capa de entrada, capa oculta
y capa de salida.
¿Cómo aprendemos los pesos de la red neuronal?
Con una función de pérdida. Por ejemplo, en un problema de regresión el error cuadrático medio
podría ser la función de pérdida. La función de pérdida compara la salida de la red con el ground
truth o las etiquetas. La función de pérdida, puede tener muchas formas.
Vamos a aprender w con un algoritmo de optimización que se llama gradiente descendiente.

Gradiente Descendiente
Con el gradiente descendiente se grafica el error de pérdida. Es decir, el cómputo de la función de
costo en los datos de entrenamiento. Se computa la función de pérdida para los datos de
entrenamiento y para un determinado valor de w1 y w2 (si tenemos más features, serán también
más pesos), tenemos un error de pérdida.

En los ejes de este gráfico tenemos los valores de los


diferentes pesos (w1 y w2). Este es un modelo de 2
features, si tuviésemos más features el gráfico sería de
más dimensiones.

Queremos llegar al mínimo de la superficie de error


(punto B) ya que este es el conjunto de parámetros que
minimiza la función de pérdida en los datos de
entrenamiento.

Para movernos del punto A al punto B usamos la derivada (función matemática que nos dice en qué
dirección caminar para que la función cambie lo más que pueda). Cuando extendemos la derivada a
funciones de múltiples variables tenemos un gradiente, que es el vector de derivadas parciales. En
este caso, son derivadas de la función de pérdida respecto a cada uno de los parámetros que
representan a mi neurona (los ws). El vector gradiente apunta en la dirección en la que la función
crece rápido, pero como buscamos la dirección en la que la función decrece rápido, tomamos el
negativo del gradiente descendiente.
Cómo calcular el gradiente
● Derivación análitica: derivar a mano y escribir el código. Se escribe una expresión que dice la
forma que tiene la derivada a partir de la función. Es fácil de hacer para una neurona, pero
no para más de una.
● Derivación numérica: diferencias finitas. Se computa el valor de la función en dos puntos y se
hace la recta. Estos métodos tienen muchos problemas de estabilidad y son caros.
● Derivación simbólica: se realiza utilizando las reglas estudiadas en Análisis matemático pero
automatizadas (ej. Maple, Mathematica). Devuelven una expresión análitica que puede ser
muy fea.
● Derivación automática: se definen las derivadas para operaciones “primitivas” como por
ejemplo, una suma o un producto (matemáticas y de control). Se construye un grafo de
operaciones y se deriva siguiendo la regla de la cadena. Existen frameworks que
implementan estos algoritmos de diferenciación automática (TensorFlow, Pytorch).

Neurona Artificial: Perceptrón Simple


En general, vamos a preguntarnos si el valor que sale de la neurona es mayor a cierto umbral. Este
término se llama bias. Pero una forma más fácil de expresar el perceptrón es pasando este bias (b) a
la sumatoria de productos. b es un parámetro que también vamos a aprender, por lo que podemos
verlo como un peso de la red. Se lo mete dentro de la red como un peso más dentro del vector de
pesos y se lo fija con una entrada en -1 para que se comporte como resta. Esto se llama bias trick.

¿Qué es una neurona? → La operación más básica que podemos representar en una neurona es el
producto interno entre la entrada y los pesos.
Funciones de Activación
● Función signo: pasamos z (lo que sale de la sumatoria) por la función signo. Si z<0, la función
vale -1 y si z>0, la función vale 1.

● Función signo lineal: devuelve -1 hasta un determinado valor de a.

● Función Sigmoidea: manda cualquier cosa del rango de los reales al rango (0,1). Podría servir
para representar, por ejemplo, una probabilidad (ej. para clasificador binario). La regresión
logística, por ejemplo, es un perceptrón simple con función de activación sigmoidea.

Frontera de Decisión (para perceptrón simple)


Queremos resolver un problema donde tenemos 2 features de entrada (x1 y x2). La frontera de
decisión de un clasificador marca el límite donde el espacio deja de corresponder a una clase para
empezar a corresponder a otra. Separa el espacio entre las diferentes clases. Para calcularla y tener
la ecuación de la recta, igualamos a 0 la sumatoria del perceptrón y despejamos una variable en
función de la otra. Para poder dibujar la ecuación de la recta tenemos que definir w1 y w2. A medida
que vayamos variando w1 y w2 vamos a obtener diferentes clasificadores, es decir, diferentes
fronteras de decisión. La clave está en cómo encontrar los valores de w1 y w2.
Modelando la operación X1 OR X2
En las operaciones lógicas tenemos variables booleanas (sólo pueden tomar valores 0 y 1) y esto es
lo conoce como tabla de verdad de esa operación:
0 (falso) 0 (falso) 0

1 (verdadero) 0 (falso) 1

0 1 1

1 1 1

El dataset tiene 4 elementos, es decir, 4 posibles valores de verdad que tiene el problema. La idea es
trazar una recta que actúa como frontera de decisión dejando de un lado las combinaciones que dan
1, del otro lado, las combinaciones que dan -1. Solo da negativa (falso) la que vale falso/falso.
Quedan las clases verdaderas y falsas separadas.

A la frontera de decisión le agregamos el término de bias (w0) para desplazar la recta del origen.

¿Cómo entrenar mi perceptrón?


Hasta ahora los pesos los pusimos a mano, pero vamos aprender a poner estos datos con un dataset
de entrenamiento formado por parejas de datos (x) y etiquetas deseadas (d). Para esto, se tiene que
definir una función de pérdida.

Función de pérdida (o función de costo)


En el contexto del aprendizaje automático, es una función que mide la calidad de una predicción
basándose en el acuerdo entre dicha predicción y la etiqueta provista en el dataset de
entrenamiento. Mide cuán buena es la predicción de mi modelo.
- Devuelve un valor alto si nuestra predicción es incorrecta, y bajo si la predicción es correcta.
- Sirve como guía en el proceso de búsqueda de los parámetros de nuestro modelo a entrenar.
- A la función de pérdida la vamos a llamar L. L toma datos y los parámetros de la neurona.
- La función de pérdida más simple es la del perceptrón simple con salida lineal (sin función de
activación). Y se calcula como el error cuadrático de la distancia entre la etiqueta y el
producto de pesos y los features.

Aprendizaje como problema de optimización


Vamos a ver los problemas de aprendizaje como si fueran un problema de optimización. En un
primer lugar, sumamos el valor de la función de pérdida, computada para esos datos de
entrenamiento, con el estado actual de los parámetros. Se busca minimizar la pérdida promedio para
todos los datos del dataset, seleccionado de todos los posibles ws. Existen infinitos ws para un
perceptrón, es decir, el espacio de búsqueda es infinito. Por esto es que son problemas difíciles. El
argumento encontrado que minimiza la pérdida promedio, se llama 𝑤. Es decir, 𝑤 son los parámetros
óptimos que resuelven el problema para los datos de entrenamiento. Los w van a ser el modelo, es
decir, lo que se guarde en memoria. A esto se le llama principio de minimización del riesgo empírico:
se trata de encontrar el mejor modelo haciendo uso de los pocos datos que se tienen, bajo la
hipótesis de que los datos que van a venir después son datos que se comportan similar a los datos de
entrenamiento.

Estrategias para buscar los parámetros óptimos


Búsqueda aleatoria
Es la forma más simple. Se tiran aleatoriamente parámetros. Para el algoritmo, inicializamos una
variable “el mejor valor de la función de pérdida” e iteramos una determinada cantidad de
iteraciones. En cada iteración sampleamos aleatoriamente el peso w. Evaluamos el valor de la
función de pérdida para el perceptrón con ese w y nos preguntamos si el valor de la función de
pérdida es más chico que el mejor valor que tenemos hasta el momento o no. En caso de ser, nos
guardamos ese valor de la función de pérdida y nos guardamos esos pesos como los mejores que
tenemos hasta el momento. En cada paso descartamos el valor anterior, solo nos quedamos con el
mejor pero no usamos el resto de la información. La calidad de la solución va a depender de la
cantidad de veces que iteremos. No es el mejor algoritmo ya que no tenemos garantía de encontrar
nada.
Método del gradiente descendiente
Actualizamos los pesos en la dirección que garantice una reducción de la función de pérdida. Esta
dirección está dada por el gradiente.

Se inicializa a w aleatoriamente, después se empiezan a dar pasos en la dirección del gradiente y se


va re-computando al gradiente en cada paso y actualizando la función. Este método únicamente
garantiza convergencia a mínimos locales. Si la función es convexa, el mínimo local es a su vez el
mínimo global. El error cuadrático utilizado como función de pérdida del perceptrón simple en el
caso lineal resulta en una función convexa. Es decir, si estamos entrenando un modelo para optimizar
este perceptrón, tenemos garantía de que vamos a encontrar el mínimo global. Nunca tenemos esta
garantía.

Algoritmo: se inicializan los pesos aleatoriamente y dado el dataset, actualizamos w (valor de los
pesos) como el valor que tiene w en ese momento menos el gradiente multiplicado por el tamaño
del paso. Esto lo que va a hacer es escalar el tamaño de ese gradiente para que no se den saltos muy
grandes.
EJEMPLO: cálculo del gradiente en caso lineal de perceptrón

Modelando el XOR
XOR es la operación del OR exclusivo. Es decir, es otra operación lógica donde para que la salida sea
verdadera sólo una de las dos tiene que ser verdadera (cuando son las dos verdaderas, es falso).
Cambia entonces el mapa de puntos cartesianos.

No podemos plantear una frontera de decisión que resuelva el problema del XOR con un perceptrón
simple lineal. Ni siquiera con un perceptrón simple que tenga función de activación no lineal, no se
puede. Se necesita entonces un perceptrón multicapa (multi layer perceptron MLP). Es decir, se
necesita que la salida de la neurona (que va a ser una recta como la del segundo gráfico de arriba)
ahora entre a otra neurona que va a combinar la salida de varias neuronas de la capa anterior. En un
perceptrón multicapa se meten muchas neuronas en paralelo para poder tomar las salidas de esas
neuronas combinadas en una predicción. Se empiezan a combinar rectas por medio de la
concatenación de neuronas. Cuando se dan ciertas características, al MLP se lo conoce como
aproximador universal ya que este perceptrón puede aproximar cualquier función.
Un perceptrón multicapa (MLP) es una estructura que combina varias neuronas en un sólo modelo.
Empezamos a hablar de arquitectura de redes neuronales. La arquitectura está dada por: cuántas
capas tiene el perceptrón, cuántas neuronas tiene cada capa, que función de activación se usa en
cada capa, si la capa tiene bias o no. En un paradigma de aprendizaje supervisado como en el que
estamos, vamos a entrenar al modelo usando etiquetas. Cuando se empieza a entrenar al modelo se
tienen que comparar cada una de las salidas del modelo con la etiqueta que queremos que
corresponda a ese dato. Todo se va a guiar a partir de la función de pérdida.
En el MLP ya no tenemos un sólo vector de peso (antes teníamos un vector de pesos w por cada
neurona) sino que se tiene una matriz donde hay un vector de pesos por cada neurona de la capa y
cada neurona tendrá la misma cantidad de pesos que de features de entrada. Siguiendo el ejemplo
del dibujo siguiente, en la primera capa vamos a tener 5 vectores de pesos que se acomodan en una
matriz donde cada vector tiene 4 pesos (porque hay 4 features de entrada).
En estas redes feedforward donde se alimentan sólo hacia adelante, la salida de la capa anterior es lo
que entra a la siguiente (cada flecha es una x). Entonces, cada neurona de la segunda capa oculta va
a tener 5 pesos porque están entrando 5 features a las neuronas de la siguiente capa. A la última
capa la llamamos capa de salida.
Todas las neuronas de una capa tienen la misma función de activación, pero 2 capas pueden tener
distinta función de activación.
Dato: tener muchos parámetros puede llevar a overfitear.

Interpretación del dibujo: tenemos 3 salidas. Podemos pensar en, por ejemplo, un problema de
clasificación donde entra un dato y lo podemos clasificar en 3 clases diferentes. Decimos que cada
neurona, por ejemplo, escupe la probabilidad de que mi entrada sea de clase 1, 2 o 3. También
podríamos estar regresionando 3 valores como edad, altura y peso de una persona, a partir de un
montón de parámetros de una persona.
Ejemplo: problema con 2 features de entrada
Por ejemplo, podemos estar estimando la lluvia (si va a llover o no) y la temperatura de mañana a
partir de la presión atmosférica y humedad de hoy.
La primera capa es una capa oculta que tiene 3 neuronas, cada neurona va a tener 2 pesos: uno que
multiplica a la primera entrada y otro que multiplica a la segunda. Entonces, la matriz va a tener una
columna por cada neurona (estos son los pesos). Cuando llega el dato vamos a hacer un producto
entre el vector rosa y la matriz Wh. Vamos a multiplicar una primera matriz de 1x2 y una segunda
matriz de 2x3 por lo que la matriz de salida será de 1x3, que representa las salidas de cada neurona.
Tamaño de una matriz de pesos de una capa: cantidad de features de entrada x cantidad de
neuronas de salida. Las matrices son el resultado de hacer XxW.

Teorema de aproximación universal


Establece que una red feed-forward con una sola capa intermedia es suficiente para aproximar, con
una precisión arbitraria, cualquier función con un número finito de discontinuidades, siempre y
cuando las funciones de activación de las neuronas ocultas sean no lineales.
● Es un teorema de existencia: dice que la solución existe pero no dice que sea fácil
encontrarla.

¿Cómo entrenar mi perceptrón multicapa?


Los perceptrones simples nos da una superficie de error convexa, sin embargo, cuando entrenamos
perceptrones multicapa obtenemos superficies de error no convexas (con muchísimos mínimos
locales). Es decir, no tenemos garantía, por medio del gradiente descendiente, de encontrar el
mínimo global de la función.
Función de pérdida no convexa en MLP
● El gradiente descendiente no garantiza encontrar el mínimo global en funciones no-convexas.
● Sin embargo, para redes neuronales grandes (con muchos parámetros), la mayoría de los
mínimos locales son similares y presentan performance similar en los datasets de test
(siempre y cuando los datos de test se distribuyan similar a los datos de training).
● La probabilidad de encontrar mínimos locales “malos” decrece con el tamaño de la red.
● Focalizarse demasiado en la búsqueda del mínimo global en el dataset de entrenamiento no
es útil para la práctica porque podemos overfittear.

Método del gradiente para MLP


Dado el dataset, se calcula la función de pérdida para todos los elementos del dataset, se saca el
promedio de la función de pérdida para todos los elementos (se suma y se divide por la cantidad de
elementos del dataset), se toma la derivada de esto respecto a los ws y se vuelve hasta satisfacer
algún criterio de convergencia (ej. mínimo local).

Cuando entrenamos nuestro modelo de perceptrón multicapa vamos a tomar el gradiente de la


función de pérdida respecto a los parámetros del modelo (ws).

● Ahora los W son matrices, ya no es un solo vector como era antes.


● El gradiente de la función de pérdida lo vamos a sacar de los Ws. Por lo que el vector
gradiente va a tener un componente por cada parámetro.
● Se tienen tantas matrices como capas ocultas. Cada capa oculta tiene una matriz de pesos
distinta.

Variantes del método del gradiente


● Gradiente descendiente clásico → se toma cada elemento del dataset, se lo pasa por
adentro del perceptrón y tenemos una predicción por cada elemento del dataset. Una vez
que tenemos esto computamos la función de pérdida L para cada uno de esos elementos
procesados respecto al ground truth asociado a ese elemento. Se calcula el promedio de la
función de pérdida; este número es un escalar. Buscamos el gradiente de este número. Esto
esta bueno con cierta cantidad de elementos, cuando pasamos a tener muchos (ej. imágenes
grandes) el gradiente clásico no es escalable ya que en cada iteración vemos el dataset
entero.
● Gradiente descendiente estocástico → en lugar de hacer la sumatoria de todos los términos
de la función de pérdida con todos los datos, sólo se toma un dato. Se toma un dato, se hace
la predicción, se calcula la función de pérdida de únicamente este dato y se obtiene el
gradiente. Este gradiente indica la dirección en la que nos tenemos que mover para que la
función de pérdida disminuya para ese dato. Con el gradiente descendiente clásico
calculamos un promedio por lo que el gradiente del promedio era mucho más significativo
(tenía información de todos los elementos del dataset y ahora, solo de un elemento). Se
samplea un elemento del dataset aleatoriamente y se actualizan los pesos. A este gradiente
se lo llama estocástico porque se samplea aleatoriamente un elemento del dataset
(tomamos un elemento, computamos la función de pérdida y actualizamos; tomamos otro,
actualizamos y así). Es una forma de dar pasos mucho más rápido, pero también es más
errático porque los vectores apuntan en la dirección que benefician a un solo elemento. Esto
suele funcionar bastante bien.
● Gradiente descendiente por mini-batches → es un punto intermedio (no son ni 10.000.000
de imágenes ni 1, son 32 imágenes). Se samplea un batch de 32 elementos, se procesan los
32 elementos juntos, se hace la predicción sumando 32 términos en la función de pérdida, se
computa el gradiente de esto, se actualiza y ahora se samplean otros 32 elementos sin
reposición. Esto se hace hasta consumir todo el dataset, una vez que se consume todo el
dataset decimos que se cumplió una época de entrenamiento. Una época de entrenamiento
es haber consumido el dataset toda una vez durante el algoritmo de gradiente
descendiente. Cuando hacemos entrenamientos por mini-batches el criterio de convergencia
directamente es entrenar por 20 épocas, es decir, mirar 20 veces el dataset (pero
actualizamos el w muchas más veces porque se actualiza por cada mini-batch). Nos solemos
quedar con el mejor modelo en validation. Todos los parámetros (ws) se actualizan juntos.
Batches de tamaño 3: 3 muestras/datos con un solo feature cada una. La primera capa tiene una
matriz de pesos de tamaño 2 porque cada neurona tiene únicamente una feature de entrada. La
matriz verde tiene tamaño 3x2 porque cada columna corresponde a una neurona y cada fila
corresponde a un dato. Procesamos los 3 datos a la vez por medio de un producto. Cuando tenemos
un batch, lo metemos dentro de una matriz, hacemos el producto y procesamos todo el batch junto.
Las matrices de B son el bias. El bias también son pesos de la red. Después de haber hecho el
producto de X y Wh, obtenemos Zh y le sumamos un elemento a cada una de las salidas de la
neurona. Cada neurona tiene un solo bias, por eso la matriz de bias es de 1x2 (tenemos un bias para
la neurona 1 y otro bias para la neurona 2). Una vez que hicimos la salida, sumamos el bias y
obtenemos la salida de la capa. Si tuviesemos que aplicar funciones de activación (ej. ReLU), las
aplicamos en este paso.

Batches de tamaño 4: 4 muestras/datos con 2 features de entrada cada una. El producto de la matriz
de entrada y la matriz de pesos es de 4x3 donde tenemos una columna por cada neurona y una fila
por cada observación. El tamaño de las matrices de pesos de mi perceptrón no depende de la
cantidad de elementos del batch, sino que depende de la cantidad de features de entrada y la
cantidad de neuronas. Es decir, podemos entrenar un perceptrón con batch de tamaño 40 y en test
meterle únicamente 1 dato.
Cómo calcular el gradiente: Derivación Automática / Backpropagation
Expresamos la operación matemática como si fuera un grafo de operaciones. Si sabemos cómo
procesar cada una de las operaciones independientemente, después podemos componer el
gradiente por medio de la regla de la cadena y obtener la estimación final del gradiente. El algoritmo
de backpropagation es un algoritmo para obtener gradientes, nos da el gradiente de una función
respecto de ciertos parámetros. El algoritmo de gradiente descendiente es el algoritmo de
optimización, que usa backpropagation cuando haya que calcular el gradiente.

La derivada de la función en un punto dado es la pendiente de la recta tangente a la curva en ese


punto y la misma indica cómo varía la función en ese punto. En este caso, vemos si la función crece o
decrece. Cuando h es muy chiquitita podemos aproximar la función usando la derivada. La derivada
nos indica entonces la “sensibilidad” de la función al cambio en la variable. El valor de la derivada me
indica cómo cambia la función, permite entender lo que le pasa a una función localmente. La
derivada es la razón de cambio.

Necesitamos poder derivar para poder hacer el algoritmo de gradiente descendiente, en algunos
casos como en la función máximo hay puntos donde la derivada no está definida entonces tomamos
un sub-gradiente. La función máximo no es diferenciable, es decir que no podemos derivarla en todo
su espectro pero se suele usar una generalización del gradiente para funciones no diferenciables que
se llama sub-gradiente.
La derivada de f respecto a X es 1 cuando X es mayor o igual a Y, y 0 en otro caso. Cuando nos
movemos en X, si X es mayor a Y la función se va a incrementar, pero si Y es mayor a X la función no
cambia.

Vamos a usar la idea de la regla de la cadena para hacer derivadas de forma fácil en redes
neuronales porque podemos pensar a cada operación como si fuera un grafo. La regla de la cadena
optimiza el proceso de cálculo del gradiente porque reutiliza operaciones ya calculadas (ej. la
derivada de f con respecto a q:: primero la usamos para la derivada de f con respecto a x, y después
para la derivada de f con respecto a y). Al plantear las operaciones como un grafo, una vez que
tenemos derivada cada operación vamos a poder ir reutilizandolas y tener un proceso mucho más
optimizado de cálculo del gradiente. Backpropagation es un flujo de gradientes encadenados en un
circuito de operaciones aritméticas.
Flujo de gradientes:

EJEMPLO 1

Grafo que representa la operación:

Para aplicar el algoritmo de backpropagation tenemos que saber en qué punto vamos a instanciar a
esa función derivada. Necesitamos saber en qué punto queremos calcular la derivada. Todo el
algoritmo sirve para calcular la derivada de la función en un punto especial, si queremos la derivada
en otro punto tenemos que volver a correr el algoritmo. Entonces definimos un punto, le damos
valores a los parámetros. En este caso: 2, -1, -3, -2 y -3 (colores en rojo). Estos son los valores en los
que vamos a instanciar la derivada. Hacemos entonces la pasada forward. 0,73 es el valor de haber
evaluado la función en la entrada que le dimos. Hacemos ahora la pasada backward (que le da
nombre a este algoritmo). Se computan los gradientes de cada una de las operaciones
independientemente. Empezamos de atrás para adelante y multiplicamos los gradientes locales (los
calculados en cada operación del grafo) con el gradiente que venimos retro-propagando. Vamos a ir
aplicando parcialmente la regla de la cadena (en verde).
1. Hacemos la derivada de la función en función a la variable
2. Evaluamos el resultado en lo que viene entrando (si el resultado es una constante, no
tenemos nada para evaluar)
3. Multiplicamos por el gradiente que venimos retropropagando
4. Siempre que tenemos una operación de suma copiamos el gradiente en las dos salidas
porque las dos derivadas van a ser lo mismo (esto es así porque la derivada de (x+y) con
respecto a x es igual a la derivada de (x+y) con respecto a y)

Finalmente obtenemos el valor del vector gradiente de la función respecto a cada una de las
variables. Es un vector gradiente con todos números, no tenemos expresiones matemáticas porque
ya está todo instanciado. El algoritmo de backpropagation nos va a dar el gradiente de la función
instanciada en un punto. Es un algoritmo de derivación automática que permite calcular el gradiente.
El algoritmo de backpropation sirve para calcular un gradiente de una forma eficiente.

Cuando la salida de un nodo es compartida, los gradientes retropropagados se suman antes de


multiplicarlos con el gradiente local.

Patrones en el grafo de operaciones


● La suma distribuye el gradiente de igual forma a todas sus entradas
● La operación max envía el gradiente sólo hacia la mayor entrada
● La operación producto multiplica la otra entrada por el gradiente retropropagado
PyTorch
PyTorch es un framework de deep learning open source desarrollado para ser flexible y modular para
la investigación, pero con la estabilidad y el soporte necesarios para su uso en producción.
● PyTorch implementa un montón de operaciones entre tensores (matrices
multidimensionales de más de 3 dimensiones).
● Permite hacer optimización por medio de grafos de cómputo dinámicos (dinámico porque a
medida que se va ejecutando se va escribiendo el grafo). Autograd es la biblioteca que
implementa retropropagación.
● Soporta cómputo acelerado en GPU. La diferencia entre GPU y CPU es enorme en términos
de velocidad.

Es fundamental que para poder pedirle gradiente a cualquier operación de cosas con PyTorch la
hayamos construido como operaciones entre tensores de PyTorch. Es decir, que no se corte el grafo
de PyTorch. Todas las operaciones definidas tienen que ser entre tensores de PyTorch. Si rompemos
el grafo porque usamos una función que no es PyTorch, se corta el gradiente y no hay
retropropagación y no vamos a poder calcular los gradientes.

Componentes de PyTorch
Está estructurado en clases. PyTorch nos ofrece un montón de componentes distintos que siempre se
necesitan cuando se implementa un algoritmo de Deep Learning. Las clases son:
● Datos: para levantar datos PyTorch nos da objetos de tipo datasets y tensores. Al dataset le
vamos a decir que levante los datos y una vez levantados los mete dentro de tensores.
● Data Loaders: una vez que metemos los datos dentro de tensores, creamos un data loader al
que le damos un dataset. Iteramos el data loader y nos devuelve de un batch a la vez.
● Redes: PyTorch también nos permite crear redes neuronales (no tenemos que codear de
cero el producto entre x y w). Le pedimos que nos de capas densas (densas porque cada
neurona está densamente conectada con todas las de la capa anterior, son las que
estudiamos hasta el momento). A PyTorch entonces le pedimos que directamente nos de
una capa lineal/densa, nosotros lo que le pasamos son los features de entrada, features de
salida y la función de activación.
● Funciones de entrenamientos: se pueden construir bucles de entrenamiento donde se hace
una pasada forward con los datos del dataset, se computa la función de costo, se hace la
pasada backward, se actualizan los pesos de la red y se itera.
● TorchScript: se usa para pre-compilar los modelos armados para que se ejecuten de forma
mucho más optimizada. Cuando se hace un modelo y se lo quiere llevar deployment, en
general, se compila al modelo (se le hacen optimizaciones) para que ande más rápido.
Tensores en PyTorch
En PyTorch podemos:
● Computar operaciones en forma paralela en GPU

● Distribuir operaciones en múltiples máquinas


● Mantener un seguimiento del grafo de operaciones que les dan origen. Podemos escribir
grafos de operaciones a partir de código.

→ Cada una de estas


variables (x, w, b, y) son tensores.
¿Cómo procesamos una imagen con un perceptrón?
Si tuviéramos que procesar una imagen con un perceptrón tenemos que extraer características de la
imagen a mano y luego las alimentamos al perceptrón. Otra opción que podríamos hacer es meterle
directamente los píxeles al perceptrón como si fueran features.
Si pensamos en imágenes de NxN, en general tomamos la imagen, la vectorizamos (se pone una fila
al lado de la otra) y eso es lo que entra al perceptrón.

En una imagen de 1.024x1.024, por ejemplo, cada neurona de la primera capa tendrá ~1 millón de
conexiones/pesos. Y si tenemos, por ejemplo, 1.000 neuronas vamos a tener mil millones de
parámetros únicamente en la primera capa, lo cual no es escalable.

Entonces procesar imágenes con un perceptrón tiene las siguientes desventajas:


● Se pierde la estructura original de los datos. Al principio, en la imágen los píxeles de la
primera fila y los de la segunda fila son pixeles vecinos pero al vectorizar la imágen se pierde
esa noción. Se rompe la estructura natural del dato estructura que estamos usando.
● Para grandes imágenes, la cantidad de neuronas (y sus consecuentes conexiones) crece
exponencialmente
● No tenemos una noción clara de análisis multi-escala/multi-resolución (algo que resulta útil
en general en análisis de imágenes). El análisis multi-escala es mirar a un dato a múltiples
escalas (ej. un imágen en múltiples resoluciones).
Redes neuronales inspiradas en el sistema visual
Las neuronas de la corteza visual temprana (las que se encuentran más cerca de la retina) se
organizan de forma jerárquica, donde las primeras reaccionan a patrones simples como líneas, y las
posteriores capas responden a patrones más complejos combinando las activaciones que reciben. En
el modelo propuesto, las neuronas en las capas superiores tienen un mayor campo receptivo y son
menos sensibles a la posición desde la cual proviene dicho estímulo.

Suponiendo que estamos viendo un carácter, cada una de las neuronas de la primera capa van a
mirar a una zona muy chica de la entrada. Es decir, el valor que la neurona larga va a estar afectado
solamente por una parte de la entrada. Es decir, estas neuronas tienen un campo receptivo acotado.
El campo receptivo es el área de la entrada que afecta el valor de salida de la neurona.
Una neurona de la última capa combina la información que proviene de las capas anteriores (en este
caso, de las 3 capas anteriores) y cada neurona anterior miró una parte específica del dato por lo que
el campo receptivo de cualquier neurona es siempre mayor al de una neurona anterior. Es decir, las
capas superiores tienen un campo receptivo cada vez más amplio y reaccionan a patrones cada vez
más complejos.

Los modelos de deep learning hacen el análisis en múltiples niveles de abstracción y además integran
como parte del modelo al clasificador. El clasificador es perceptrón multicapa. El resto son neuronas
convolucionales que extraen características. Todo esto forma un grafo de operaciones y se lo puede
derivar como si fuese un perceptrón chico y muy simple. Resumiendo: entra un dato, se le extraen
características, se lo pasa por clasificador, se hace una predicción, se computa la función de pérdida y
se retropropaga el gradiente de la función de pérdida respecto a los parámetros del modelo. Se
pueden entrenar juntos al clasificador y al extractor de características, por eso lo llamamos
entrenamiento “end to end”.
Redes Neuronales Convolucionales
Una red neuronal convolucional es cualquier red que usa en algún lugar una capa convolucional.

Convolución 1D
Una convolución es una operación matemática que se realiza entre funciones y representa la integral
de un producto de una función x con una versión trasladada y reflejada de otra w. A x la llamamos
señal de entrada, a w la llamamos filtro y al resultado lo llamamos señal convolucionada. Tenemos
dos funciones, y el resultado es otra función. En el continuo, desplazamos al filtro y vamos sacando
como valor de la salida en nuestra señal convolucionada el área contenida entre las 2 funciones (por
eso, llega al máximo cuando el filtro ocupa toda el área de la señal de entrada).

La señal no suele ser una función continua, en general es una función muestreada ya que tenemos
en un instante de tiempo un número. Ahora la señal x es un vector de números donde cada una de
las posiciones es un valor de la señal. W también es un vector. En la señal discreta vamos a sumar el
valor del producto entre el vector w y la parte de señal de entrada en la que estamos parados en el
momento (ej. la cuenta del primer cuadrado es: (1x1)+(4x2)+(-1x0)+(0x-1) = 9).

Convolución 2D
Si trabajamos con imágenes, las señales son bi-dimensionales, por lo que en lugar de trabajar con un
único índice, trabajamos con dos índices. i es la imágen, k es el filtro convolucional. Vamos a ir
moviéndonos a lo largo de la imagen con el filtro y haciendo el producto interno entre el filtro y lo
que queda abajo de la imágen.
El filtro convolucional también se llama Kernel. En una imágen, desplazamos el kernel por la imágen.
El kernel también es una imágen. Dependiendo de lo que hayamos dibujado en el kernel es lo que
vamos a obtener como señal convolucionada. Lo que sale de haber ejecutado una operación de
convolución, en el contexto de redes neuronales, se llama Feature Map. Valores muy blancos
representan valores altos, mientras que valores muy negros representan valores bajos. Al hacer la
operación de convolución en la imagen, los valores más altos de salida van a estar donde haya algo
en la imagen que se parezca mucho a lo dibujado en el kernel. Esto sucede porque estamos
calculando un producto escalar, y el producto escalar es máximo cuando los dos vectores están
alineados (son iguales). Es una forma de hacer template matching, es decir, buscar cosas en
imágenes. En un feature map, vamos a haber transformado a la imágen en algo que representa una
característica específica de la imagen (ej. un mapa de bordes). Cuando hacemos una convolución
podemos pensar que cada uno de los píxeles de salida es el resultado de lo que “escupe” una
neurona. Una neurona que fue parametrizada por los parámetros del kernel. Es decir, ahora los pesos
de nuestras neuronas van a ser los kernels. El filtro convolucionado es justamente lo que vamos a
aprender, se lo inicializa con valores aleatorios pero como son los parámetros de la neurona los
vamos a aprender. Si queremos aprender a extraer características, dejamos que los parámetros
inicializados con un valor aleatorio sean libres y pueden ser aprendidos a través del proceso de
entrenamiento (exactamente igual que lo visto hasta ahora).
Este proceso forma parte de la pasada forward de tu análisis de una imágen.

Este es el resultado de convolucionar la imágen del fondo con un kernel. En este caso tenemos un
filtro de resaltado de borde, es decir, se resaltan bordes que estén orientados como está orientado el
filtro (kernel). Lo que tiene valores más altos son los bordes.

Capas totalmente conectadas (densas) vs Capas convolucionales


Cada línea del dibujo siguiente es un parámetro. Los círculos verdes son las entradas y los azules, las
neuronas. En un caso totalmente conectado (denso) cada neurona tiene tantos pesos como entradas
haya. En el caso de la red convolucional, cada neurona va a tener tantos parámetros como elementos
haya en el filtro. En el ejemplo, cada neurona tiene 3 parámetros y esos 3 parámetros son los mismos
en todas las neuronas. Pasamos de tener 30 parámetros, a sólo 3.
Podemos tener que procesar una imágen de entrada que no es solamente en la escala de grises, sino
que es una imagen RGB (3 canales de color: rojo, verde, azul). Cuando pensamos en la
representación de esta imágen, en realidad, son 3 imágenes (una que representa el canal R, otra el G
y otra el B). C es la cantidad de canales de entrada, H es la altura de la imagen y W es el ancho de la
imagen. El kernel convoluciona en el espacio, es decir, en las dimensiones W y H, pero en
profundidad los kernels suelen tener tantos canales como canales de entrada tiene el dato que
estamos por analizar. El kernel no se puede mover en la dimensión C. Si estamos procesando
imágenes 2D multicanal, tenemos kernels 3D porque tenemos una dimensión más que corresponde
a los canales. Para crear múltiples “fetas de pan lactal” y tener múltiples feature maps en la salida
tenemos que agregar kernels (agregamos el rojo en la imagen). La cantidad de feature maps de salida
equivale a la cantidad de kernels que tenemos. Cada feature map es un extractor de características.

● La conectividad del kernel es local en el espacio y total en profundidad.


● Además, implementan el mecanismo de weight-sharing que es esta idea de que todas las
neuronas de salida de un mismo feature map comparten los mismos pesos. Esto no solo nos
permite disminuir la cantidad de parámetros sino también nos permite introducir la
invarianza a la traslación. Y es una propiedad deseable cuando estamos haciendo
clasificación de imágenes.
● Las funciones de activación no lineal (ej. ReLU) son aplicadas a cada elemento
individualmente.
Hiperparámetros de una capa convolucional
Cuando definimos una capa convolucional en PyTorch le tenemos que decir los siguientes
parámetros:
● Kernel: tamaño del kernel. Solemos usar 3x3.
● Stride: tamaño del paso. De a cuántos píxeles vamos a saltar. Un slide de 2 reduce mucho la
dimensionalidad de la salida (segunda imagen).
● Padding: agregar basura alrededor de la imagen para que no se achique y poder mantener
las dimensiones. El tamaño del padding depende del tamaño del kernel. Con un kernel de
3x3, alcanza tener un padding de 1. Con un kernel de 5x5, necesitamos un padding de 2.
Podemos tener un cero padding donde agregamos un borde de ceros, pero también existe el
mirror padding donde se copia la última fila y se la desplaza.

Capa de Max Pooling


La capa de pooling toma la entrada y reduce la dimensionalidad de la entrada por medio de una
operación de agrupación. Por lo general, se usa al máximo como operación de agrupación: se toma
una ventana y nos quedamos con un máximo. De esta manera logramos reducir drásticamente la
resolución del feature map ya que tenemos menos parámetros en el procesamiento de la red. Y
además, logramos introducir invarianza a la traslación más agresiva que en una convolución común
(ej. al hacer max pooling traemos el dato de que había un gato en esa ventana pero nos olvidamos en
dónde estaba). Contribuye a la invarianza respecto a pequeñas traslaciones en las imágenes de
entrada. Además, esta capa no suma parámetros al modelo. El max pooling no suele usarse en las
capas de entrada sino más bien en las capas del medio.
Al construir una red neuronal convolucional construímos algo como lo siguiente:
Tenemos una imágen de entrada y la primera capa convolucional. La primera capa convolucional
tiene 4 feature maps de salida (es decir, 4 kernels). Esta primera capa convolucional agrega al modelo
4 parámetros, 4 kernels. Después aplicamos sub-sampling como max o average pooling donde
reducimos la dimensionalidad. Seguimos teniendo 4 feature maps porque max pooling no mezcla
nada, solamente agrupa (conservando la cantidad de feature maps). Pasamos a un nuevo step de
convolución, esta vez con 6 feature maps. El kernel de esta capa tiene tamaño 4 de profundidad
porque la capa anterior tenía 4 (la profundidad de un kernel depende de la cantidad de feature
maps de entrada). Al final, tenemos varios feature maps, las vectorizamos y las metemos en un
perceptrón multicapa. Este MLP es nuestro clasificador.
Entonces con las etapas de extracción de características y de clasificación, vamos a meter una imagen
y obtener una predicción.

Ventajas de Redes Convolucionales


● Naturalmente adaptadas a la estructura regular de las imágenes (por medio de la operación
de convolución). No se pierde la noción de espacialidad.
● Invariantes respecto a traslaciones.
● Aprendizaje end-to-end. Me permiten aprender juntas las features y el clasificador.
● Bajos requerimientos de memoria porque implementa weight sharing.
● Eficientes en test-time.
● Buen grado de generalización si se entrena con suficientes datos.

Algunas arquitecturas clásicas para clasificación


LeNet-5 (porque tiene 5 capas). Esta red tiene convoluciones con kernels de 5x5 y stride de 1. Este
modelo tiene 60.000 parámetros.
AlexNet: a diferencia de antes, ahora cada capa tiene más feature maps. En vez de tener 6 feature
maps en la primera capa ahora tenemos 96, en la segunda 286 y en la tercera 384, etc. Al final
tenemos un perceptrón con 4.000 neuronas. En total tiene 60 millones de parámetros, de los cuales
58 millones corresponden a las últimas capas densas. Es decir, por usar capas convolucionales nos
ahorramos tener una millonada más de parámetros.

VGG: propusieron hacer las redes más profundas. Para lograr esto sin incrementar la cantidad de
parámetros se usan kernels muy chicos (de 3x3). El hecho de que las redes sean más profundas hace
que los filtros de capa (kernels) no se entiendan mucho visualmente (son muy chicos).

GoogLeNet: al final se hace una operación que llamamos global average pooling donde por cada
feature map se saca el promedio. Es decir, se tiene un valor por cada feature map. El GAP es lo que se
mete en el MLP. Este modelo tiene solamente 4 millones de parámetros.
Entrenando una red neuronal para clasificación
Hasta ahora siempre entrenamos redes para regresión, pero si queremos entrenar una red para
clasificación nos faltan: cómo asegurarnos de que lo que sale de la red es una distribución de
probabilidad y una función de pérdida que podamos usar para este tipo de problemas. Para entrenar
redes y asegurarnos que su salida sea algo que se parezca a una función de probabilidad vamos a
usar una función de activación especial llamada softmax.

Softmax como función activación de salida


Permite obtener una “interpretación probabilística” de la salida ya que normaliza las salida de los
perceptrones. Vamos a aplicar la función softmax al vector de salida de la red. Softmax nos garantiza
que la sumatoria de la salida es igual a 1 y que cada valor de salida está entre 0 y 1 (probabilidad). A
diferencia de Softmax, la sigmoide normaliza cada valor entre 0 y 1 pero no garantiza que sume 1.
● Zj = salidas del perceptrón

Función de pérdida
La función de pérdida que solemos usar se llama Entropía Cruzada que nos permite encontrar
diferencias entre dos distribuidores de probabilidad p y q. Es decir, tenemos una distribución “ground
truth” a la que nos queremos acercar y una distribución estimada que va a ser la salida del
perceptrón. Vamos a sumar por cada uno de los valores posibles que puede tomar esa variable
aleatoria y comparar el valor de la distribución Ground Truth (p) con el logaritmo de lo que el modelo
le atribuyó a esa etiqueta. La idea de entropía cruzada es como una especie de distancia entre
distribuciones de probabilidad. Vamos a tener un valor bajo si las probabilidades son similares, y un
valor alto si las probabilidades son muy distintas.
Entropía Cruzada vs Accuracy
Cada columna es un dato y las filas representan la probabilidad de que sea de una clase o de la otra.
La predicción del modelo arriba y la etiqueta, o ground truth, abajo.
Primer clasificador: en el primer caso, el modelo no le acertó a la etiqueta real (porque la clase más
alta de la predicción fue la 3 y la clase correcta era la clase 1). Tanto en el caso 2 como en el 3, si
acertó. Si computamos la accuracy, nos da ⅔, y si computamos la entropía cruzada nos da 4.14.
Segundo clasificador: mismo accuracy pero entropía cruzada muchísimo menor.
Si tuviésemos que definir cuál clasificador es mejor, diríamos el segundo porque le asigna más
probabilidad a la clase correcta. Entonces, si usamos la entropía cruzada como función de pérdida
para guiar el aprendizaje de la red, en un caso como el ejemplo, ésta va a llevar a que un modelo con
el segundo clasificador sea mejor. Mientras que si usaramos accuracy ambos modelos serían igual de
buenos ya que es una medida discreta.

● Clasificación Binaria: en el caso binario se tiene una única salida del perceptrón, la cual
representa la probabilidad de clase verdadera. La probabilidad de clase falsa sería 1 - este
valor. En este caso, la entropía se llama entropía cruzada binaria.
● Clasificación Multi-Clase: cuando tenemos múltiples etiquetas estamos en un caso de
entropía cruzada categórica. Se tienen varias categorías y la sumatoria se ejecuta sobre todas
las posibles categorías.
● Clasificación Multi-Etiqueta: se pueden tener varias etiquetas a la vez (ej. en una radiografía
de tórax, una persona puede tener más de una patología a la vez). En este caso, podríamos
usar a la sigmoide como función de activación porque cada etiqueta es independiente de la
otra.

Funciones de activación
Cuando una capa, ya sea convolucional o totalmente conectada, procesa un dato; salen muchos
valores, uno por cada neurona, y a eso se le aplica una función de activación. Básicamente cada
neurona tiene una función de activación.
En un caso de una capa fully connected, se aplica la función sigma que puede ser cualquier cosa. En
el caso de una capa convolucional es lo mismo, a diferencia que el producto interno se hace
únicamente en el cuadradito del kernel.
La función de activación sigmoide, tiene ciertos problemas. Si la usamos en un perceptrón multicapa
muy profundo, el gradiente tiende a 0 cuando X tiende a ∞ o -∞. Vamos a tener señal nula de
gradiente, por lo que los pesos de la red no cambian. Es decir, la red no aprende. Cuando
computamos los gradientes usamos la regla de la cadena y retropropagamos gradientes del final
hasta el principio de la red neuronal. Si tenemos funciones del tipo sigmoide y empiezan a pasar por
esta sigmoide valores muy positivos o muy negativos, los valores que recuperamos se hacen cada vez
más chicos. Se producen entonces gradientes que son muy chicos y el producto entre gradientes es
cada vez más chico también. La multiplicación de pequeños gradientes por la regla de la cadena hace
que el gradiente se desvanezca. Se conoce como el problema del gradiente que se desvanece.

Se propuso entonces usar la función de activación ReLu (rectified linear units), que es el máximo
entre 0 y lo que está entrando a la función (x). Es decir, todo lo que es negativo se transforma en 0 y
todo lo positivo se deja como está. Esta es una función no lineal que tiene puntos donde no es
derivable. En todos los puntos positivos la función si es derivable y tiene gradiente, por lo que, si lo
que fluye por dentro de la red es positivo, vamos a tener señal de gradiente. Hay menos probabilidad
de que el gradiente se desvanezca que en el caso de la función de activación sigmoidea. De todas
formas, el gradiente es 0 para valores negativos, y puede desencadenar un proceso de Dying Relu. Es
por esto que se propuso la Leaky ReLu que evita el problema de la Dying Relu ya que también genera
gradientes no nulos para valores de x negativos. Es decir, se usa una función con pequeña pendiente
para los números negativos.

Las funciones de activación tienen que ser siempre no lineales porque sino no rige el teorema de
aproximación universal. No se tiene poder de modelado.

Sobreajuste y regularización
Las redes neuronales tienen muchos parámetros por lo que son super propensas a overfittear. De
hecho, solemos overfittear cuando atacamos un problema nuevo. Si no logramos que la red
overfitee, el modelo no está aprendiendo. No es una mala práctica empezar entrenando a la red y
ver que overfitea.
El error de predicción es el valor de la función de pérdida, las iteraciones van a estar divididas en
épocas. Se computa una función de pérdida tanto para los datos de entrenamiento como para los
datos de validación (no aprendemos con respecto a estos datos; los miramos durante el
entrenamiento pero no se usan para entrenar). Llega un momento que la función de pérdida en los
datos de entrenamiento sigue bajando y la función de pérdida en los datos de validación empieza a
subir. Este es el momento en el que decimos que el modelo empieza a overfittear ya que se está
especializando en los datos de entrenamiento y empieza a ser malo en los datos que no vio.

En los casos de overfitting podemos usar mecanismos de regularización. La regularización es


cualquier modificación que le hagamos al algoritmo de proceso de aprendizaje para mejorar
problemas de generalización.

Lo primero que podemos hacer es early stopping, es decir, vamos monitoreando la función de
pérdida en validación y en el momento en el que vemos que durante varias iteraciones seguidas la
función de pérdida en validación empieza a subir, frenamos el entrenamiento.

Otra técnica que podemos usar es la regularización por norma L2 (weight decay). Cuando
entrenamos una red neuronal exploramos un espacio de parámetros; el espacio de posibles ws.
Como en principio los ws pueden tomar cualquier valor, para un único problema hay infinitas
posibles redes neuronales. Esto hace que el modelo tenga una complejidad alta. Reducir la cantidad
de parámetros de un modelo, en general, es una forma de regularizar. Le damos menor libertad al
modelo. Pero esta es una forma muy agresiva de reducir la complejidad del modelo. La regularización
por norma L2 podemos verla como una forma “suave” de bajar la complejidad del modelo ya que se
le agrega un término de regularización a la función de pérdida que sea la norma euclídea cuadrada
de los pesos. Es decir, agarramos todos los ws que tenga la red, los metemos dentro de un vector
largo, tomamos la norma y esto es lo que sumamos a la función de pérdida. De esta forma, los ws
van a tender al origen. Le estamos diciendo al modelo que preferimos los valores de w que estén
más cerca del origen. Acotamos el espacio de parámetros que el modelo puede tomar a aquellos
que están cerca del origen. Esto lo hacemos porque si el valor de w es muy grande el modelo le está
dando mucha importancia a esa feature. Por medio de esta regularización estamos evitando que un
feature se lleve toda la responsabilidad; distribuímos la responsabilidad. No lo hacemos de manera
explícita y dura, únicamente le decimos que tienen que estar cerca del origen (sin decirle que tan
cerca). Es una forma soft de acotar el espacio de parámetros. Se disminuye indirectamente la
complejidad del modelo.
Existen muchos w que pueden minimizar una función de pérdida para un dataset de entrenamiento
dado. Podemos reducir la complejidad del modelo acotando la cantidad de posibles modelos a
construir. Una forma de hacerlo es evitando que existan wi muy grandes, es decir, agregando una
restricción sobre el valor que pueden tomar los pesos wi.

Influencia del parámetro λ: λ nos dice cuánta atención le préstamos al término de regularización. Un
λ muy grande, underfitea. El modelo entonces no aprende ya que se le deja de prestar atención al
término de dato. Y si es muy chico, hay overfitting.

La aumentación de datos funciona muy bien sobre todo en imágenes. En tiempo de entrenamiento,
se aumenta artificialmente el dataset utilizando transformaciones en los datos y conservando las
etiquetas. Ej. rotar, agrandar, mover, cambiar el color, agregar ruido gaussiano a las imágenes (el gato
sigue siendo gato pero para la red es un dato nuevo). Estas redes necesitan muchos datos para
generalizar medianamente bien, por lo que usar aumento de datos en tiempo de entrenamiento es
una de las técnicas que más se usan en la práctica. Siempre es importante usar una aumentación de
datos si estamos trabajando con imágenes. En tiempo de prueba, es posible generar N versiones de
la imagen de test, estimar las predicciones y promediarlas.
No se usa únicamente con imágenes, también se pueden usar sobre series, por ejemplo, donde
podemos aplicar transformaciones sobre la serie que no destruyan el dato por completo.

Los mecanismos de aumentación de datos me permiten introducir invarianzas a transformaciones


que le hagamos a la imagen. La convolución como operación no es invariante a la rotación, sin
embargo, si siempre rotamos a las imágenes en distintas posiciones el modelo aprende a detectar
eso independientemente de cómo esté rotada la imagen. Cuando ganamos invarianza, al modelo no
le importa que las imágenes estén o no rotadas. El modelo clasifica igual.

Otra técnica de regularización que se usa mucho es dropout donde se propone apagar
aleatoriamente a algunas neuronas durante el entrenamiento para que el modelo no asigne
demasiada responsabilidad a una neurona o a alguna feature en particular. Cuando decimos apagar
nos referimos a ignorar la salida de las neuronas. Aleatoriamente, con una probabilidad (1-p), las
neuronas son ignoradas en la pasada forward durante entrenamiento. Apagamos neuronas con una
probabilidad determinada en cada iteración del algoritmo de gradiente descendiente. En una
iteración tiene una estructura, en la siguiente iteración una nueva estructura y así. Esto permite que
el modelo no termine confiando demasiado en ninguna neurona en particular. Es una forma indirecta
de entrenar muchos modelos a la vez porque estamos cambiando la arquitectura; y entrenar muchos
modelos y hacerlos predecir todos juntos en general tiene mejor rendimiento que no hacerlo. El
dropout se hace por capas.
Sólo apagamos neuronas en tiempo de entrenamiento, en tiempo de test (cuando ya vamos a hacer
predicciones) todas las neuronas son utilizadas y los pesos de salida de la neurona son multiplicados
por p. No se apagan neuronas aleatoriamente.

Otra cosa que es importante hacer es normalizar las entradas del modelo. Sirve, por lo general,
cuando tenemos datos tabulares. La media y los desvíos son siempre calculados usando sólo los
datos de training (si metemos los de test hacemos leaking de datos), pero todos los datos (test y
validación también) son normalizados.
En el contexto de redes neuronales, está bueno normalizar los datos para tener una “linda”
superficie de error donde es fácil llegar al mínimo. Si los datos no están normalizados, se mueven
todos en diferentes escalas por lo que se tiene una superficie de error donde en algunas direcciones
tenemos que dar pasos muy grandes y en otras pasos más chicos. Hacer esto es difícil cuando
estamos entrenando. Que sea difícil llegar al mínimo implica que el rendimiento del modelo va a ser
peor.
En el caso de las imágenes, el rango de las features (píxeles) es el mismo (0-255), por lo tanto, en
general no es necesario dividir por el desvío. Lo usual es considerar el valor de la media y desvío
calculado sobre todos los pixeles de todas las imágenes de entrenamiento, en lugar de considerarlo
por feature (pixel). La estadística se calcula en cada imagen, distinto del caso con datos tabulares
donde calculamos la estadística con todo el dataset completo. De alguna manera, hacemos
normalización por imágenes. En imágenes multi-canal, cada canal suele ser normalizado
independientemente (una media y un desvío por cada canal).

Variantes del gradiente descendiente


El Gradiente descendiente con Momentum suaviza los pasos dados por el gradiente descendiente
en las dimensiones más oscilantes. Tiene que ver con “aprovechar el momento”. Sin momentum,
llegar al mínimo se hace más costoso. Con momentum, lo que hacemos, es “ganar momento” y
seguir yendo en la dirección hacia el mínimo. Es una manera de encontrar el mínimo más rápido. Al
ver que siempre vamos en la misma dirección, damos pasos más grandes para llegar más rápido.
Acelera la convergencia del método. Para hacerlo, calculamos un vector de velocidad antes de aplicar
directamente el gradiente descendiente. Calculamos entonces la media exponencial que está pesada
por un α. α me dice cuánto peso el vector actual respecto a los anteriores, es decir, cuánta memoria
tenemos. En general, usamos un α de 0.9. El momento ignora la dirección en la que se oscila mucho.

Variando el learning rate (tamaño del paso):


● LR alto: el algoritmo puede oscilar caóticamente y nunca converja.
● LR bajo: el algoritmo puede dejar de aprender (o tomar mucho tiempo) y toma mucho
tiempo converger.

Algo que se suele hacer es anilear/schedulear el learning rate, por ejemplo arrancando con un
learning rate muy alto para explorar la superficie de error a los saltos y a medida que vamos
avanzando en las iteraciones, empezamos a reducir el learning rate a lo largo del entrenamiento.
Empezamos con un delta grande y lo vamos disminuyendo a medida que avanzamos en el proceso de
entrenamiento en las iteraciones de gradiente descendiente.
● Step decay: reducir el learning rate (ej. dividir por 2) cada cantidad fija de épocas, o cuando
el error en validación no mejora.
● Exponential Decay: decaer el paso con una exp negativa.

También se suele tener un learning rate adaptativo por cada parámetro del vector W. Los métodos
anteriores aplican el mismo LR global a todos los parámetros W. Los métodos adaptativos escalan el
LR por cada parámetro.
- RMSProp
- Adadelta
- Adagrad
- Adam (Momentum + RMSProp)

Redes neuronales más allá de la clasificación de imágenes


Estos tipos de redes también pueden usarse para resolver otros problemas que no son de
clasificación o regresión. Particularmente en las imágenes, en el área de computer vision, tenemos
varios problemas como clasificación (cuando se recibe una imagen queremos decir que categoría
tiene), segmentación semántica (se le asigna categoría a cada pixel), detección de objetos (cuando
ponemos un bounding box alrededor del objeto y decimos de qué clase es el objeto dentro de la
bounding box) y segmentación de instancias (similar al problema de segmentación semántica pero
queremos además decir la clase del píxel, también decir la instancia del mismo).

Técnicas clásicas de segmentación de imágenes


● Umbralado → se miran las intensidades y se fijan umbrales (ej. todo lo que la intensidad
valga más de 128 es tumor y lo que vale menos no es tumor -- pero quedaba, por ejemplo, el
cráneo y el tumor del mismo tumor).

● Crecimiento de regiones → se pone una semilla en el tumor, la cual empieza a crecer hasta
que se choca con los bordes. Donde se chocan los bordes no se crece más.
● Modelos deformables explícitos → contornos activos que se empiezan a mover hasta
chocarse con bordes.

Redes neuronales convolucionales para la segmentación de imágenes


● Segmentación de imágenes binaria (ej. segmentar piletas en imágenes satelitales)
● Segmentación de imágenes multi-clase (ej. segmentar un cerebro en estructuras anatómicas)

Una primera aproximación al problema, la más ineficiente y básica de todas, es la sliding window en
la que se corta a la imagen en muchos parches, se entrenan redes de clasificación y se va pasando a
cada parche por la red. Se mira al problema como si fuese un problema de clasificación. Podemos
pensar que la segmentación es un problema de clasificación a nivel pixel. Se clasifica a cada píxel
independientemente. Se toman parches de la imágen y la etiqueta en la que se va a clasificar al
parche es la etiqueta que le corresponde al píxel central. Siguiendo con el ejemplo de la imagen
siguiente el primer y segundo pixel son de clase vaca y el tercer pixel es de clase pasto. Es muy
ineficiente porque no hace uso de las features compartidas entre parches.

Se creó en 2015 un nuevo tipo de arquitectura que sirve para resolver este problema de forma más
eficiente. Fully Convolutional Networks son redes totalmente convolucionales, es decir, son redes
sin capas densas (totalmente conectadas). En estas redes solo hay convoluciones y pooling. Se
procesa a la imagen tal cual vimos hasta ahora y cuando llegamos a algún nivel de baja resolución
sacamos tantos features maps como clases posibles tengamos en el problema de segmentación (ej. si
tenemos 21 clases posibles, sacamos 21 feature maps del mismo tamaño que la imagen de entrada)
y decimos que cada feature map corresponde a la probabilidad de que un píxel sea de la clase que
nos interesa. Armamos como si fuese un mapa de probabilidades por cada píxel (cada píxel ahora
tiene vectores de probabilidad) y la etiqueta que le asignamos es la de la clase con mayor
probabilidad. Es decir, por medio de convoluciones, terminamos transformando a la imagen en una
matriz de probabilidades donde cada uno de los píxeles de la matriz de probabilidades es en realidad
la probabilidad de que ese píxel tenga esa clase en particular. Las probabilidades para un único píxel
siempre suman 1. El último feature map tiene tantos canales como posibles clases. Computamos la
función de pérdida para cada pixel y promediamos; se mira cada pixel como si fuera un problema de
clasificación independiente, se calcula la entropía cruzada y después se calcula el promedio de todo
eso.
UNet es una arquitectura encoder-decoder para segmentación de imágenes. Se toma la imagen de
entrada y se hace convolución-convolución-pooling (en cada convolución no se altera el tamaño pero
en cada pooling el tamaño se baja a la mitad) hasta llegar a una especie de “cuello de botella” donde
desde ahí se empieza a subirle devuelta la resolución a la imagen con convolución-convolución-up
convolution hasta llegar a la resolución original. Se hace un camino en U donde se disminuye la
resolución y después se la empieza a aumentar con operaciones inversas a lo que se hizo
anteriormente. Primero se codifica (encoder) y después se decodifica (decoder).
Las flechas grises se llaman skip connections y sirven para saltar pedazos de la red. Sigue siendo
feed-forward pero nos salteamos conexiones. Esto sirve para no perdernos detalles y además para
que el gradiente salte capas; es decir, las skip connections ayudan a que las primeras capas reciban
gradiente con señal y no desvanecido.

Para subirle la resolución a un feature map se aplica interpolación

Al pasar la imagen por la UNet, obtenemos mapas de probabilidades. En el ejemplo siguiente, donde
se tiene que clasificar a la imagen en 3 clases se obtiene un mapa de probabilidades con 3 “rodajas
de pan lactal” donde un valor blanco implica alta probabilidad y un valor negro, baja probabilidad.
Cada pixel suma 1. Al mapa de probabilidades se la hace el ArgMax (nos quedamos con la etiqueta
que corresponde a la clase de mayor probabilidad) y con eso reconstruimos la segmentación. Se
calcula la función de pérdida por pixel (ej. Entropía Cruzada) de los mapas de probabilidad de salida
comparándolos con la versión One-hot del ground-truth, y luego se promedia por todos los píxeles
de la imagen.
A diferencia de las arquitecturas que ya vimos (AlexNet, LeNet, etc), a las redes totalmente
convolucionales podemos entrenarlas con una imagen de un tamaño y poder segmentar imágenes
de otro tamaño porque todo se adapta al tamaño de la entrada. Entonces algo que solemos hacer
mucho es el entrenamiento por parches donde se parchea la imagen (especialmente cuando son
imágenes muy grandes): se cortan pedazos, se entrena con los pedazos y cuando se hace la
predicción se mete el volumen completo. Los mini-batches se componen de parches, no de imágenes
completas. En test time, si la red es totalmente convolucional, basta con insertar la nueva imagen y la
predicción será del tamaño acorde. Si la red no es del tamaño acorde, se puede hacer el tiling fuera
de la red, y luego reconstruir la segmentación.

Si la red no cuenta con ninguna capa totalmente conectada (densa), entonces se dice totalmente
convolucional. Las capas densas pueden ser implementadas como capas convolucionales. La gran
ventaja de una red totalmente convolucional es que puede procesar imágenes de cualquier
tamaño, y puede ser entrenada y evaluada en imágenes de diferentes tamaños.

Arquitectura básica para localización


Dada la imagen siguiente, se tiene que decir dónde está el perro con una cajita. El dataset son fotos
de perros, el tamaño de la caja es el ground truth y la arquitectura podría ser alguna arquitectura de
clasificación pero en lugar de sólo hacer clasificación le sumamos un par de salidas más (valores
continuos que los trabajamos aparte) que son la coordenada central (x,y) y el tamaño de la caja.
Entonces, cuando definimos la función de pérdida para entrar al modelo, tenemos 2 términos: la
etiqueta de clasificación (entropía cruzada) y el segundo término de regresión donde se compara con
el ground truth (coordenadas de la caja y tamaño de la caja).

Redes neuronales convolucionales para el procesamiento de series de tiempo


Vamos a usar redes neuronales para procesar series de tiempo. Podemos querer resolver distintos
tipos de problemas como predecir eventos futuros (ej. valor del dólar/temperatura media de AR en 1
año) o clasificar eventos de una serie de tiempo (ej. ver si un electrocardiograma tiene un evento
anormal o no). Ambos problemas se pueden atacar con redes neuronales.
En un clasificador de series de tiempo vamos a usar las redes convolucionales tal cual las vimos
hasta hoy pero aplicando convoluciones 1D (vectores). Tenemos entonces a nuestro vector de
entrada, le hacemos una convolución y obtenemos feature maps (que dejan de ser rodajas de pan
lactal). Podemos aplicar después pooling, convolución, etc. Uno de los problemas que solemos tener
al agarrar una arquitectura común (ej. LeNet) es que el tamaño de las entradas siempre tiene que ser
igual, por lo que si queremos procesar series de tiempo de distinto tamaño/largo en principio
deberíamos recortarla o hacer algo de ese tipo. Entonces, si le sacamos la parte densa necesitamos
llegar igual a alguna clasificación. Esto se suele hacer con una capa de global pooling al final donde se
promedian todos los features maps (en la imagen llegan 4 feature maps y al aplicar global max
pooling tenemos un vector de 4 elementos). El tamaño del vector de salida del global pooling va a
estar determinado por la cantidad de feature maps que llegan, no por el largo. Esta entonces es una
forma de pasar de algo de una longitud arbitraria a algo que siempre tiene la misma longitud; y aca si
podemos conectarla después con el perceptrón para hacer la clasificación final.

En el caso de las predicciones en series de tiempo podemos trabajar con un perceptrón y mirar
nuestros datos como si fueran simplemente tiras de una tabla cada uno. Podemos tener, por
ejemplo, 4 features (ej. datos de los 4 días anteriores) y tratamos de predecir el 5to feature (día 5).
Se parte en pedazos la serie de tiempo y se arma una tabla donde el ground truth es la etiqueta de
mañana y se usan todos los días anteriores como features de entrada.

Lo interesante de usar convoluciones para predecir series de tiempo es que no es necesario que
recortemos el dato a la hora de testear, porque en las redes totalmente convolucionales se adapta el
tamaño de la salida al tamaño de la entrada. Podríamos entrenar a una red totalmente convolucional
para que dado 4 datos saque 1 (predicción del día de mañana) y poder meter el dataset entero en
tiempos de test. Al ser totalmente convolucional, la red va a sacar un valor por cada una de las
entradas que procesó. De todas maneras, las redes convolucionales no son muy usadas para
procesar series de tiempo. Si estamos implementando una red convolucional para cruzar series de
tiempo y hacer predicciones futuras, las convoluciones tienen que ser causales (los datos que van a
entrar a la red tienen que ser previos a lo que estamos prediciendo).
Si tenemos que hacer predicciones a partir de una serie de tiempo, podemos meter como entrada el
dato recién predecido, descartar el primer día usado hasta el momento, reemplazarlo por el valor
predecido y volver a predecir un nuevo día. De todas formas, haciendo esto se reduce mucho la
accuracy a lo largo de cada uno de los pasos que damos en el tiempo. Las predicciones van a ser
menos seguras porque vamos a estar metiendo como entradas datos que nosotros mismos
predecimos. Ahora, en un modelo que genera música (le damos un modelo de canción y empieza a
generar lo que sigue) este no sería un problema.

Las redes neuronales recurrentes si están pensadas específicamente para procesar series de tiempo
(datos secuenciales). Estas redes tienen bucles, es decir, van a ir consumiendo cada instante de
tiempo y actualizando la memoria. Podemos entonces procesar datos de cualquier longitud porque
vamos a estar consumiendo datos, actualizando la memoria y en algún momento
haciendo la predicción. Así como las redes totalmente convolucionales pueden
escalar a cualquier tamaño de imagen, las redes recurrentes pueden en principio
procesar cualquier longitud de secuencia. La clave de las redes recurrentes son los
pesos compartidos (weight sharing) a través del tiempo. Así como una red
convolucional tenía pesos que se compartían a lo largo del espacio, estas redes
tienen esa memoria que son como si fueran pesos que compartimos para procesar a
lo largo del tiempo. Usamos siempre los mismos pesos para procesar a lo largo del
tiempo, pero vamos actualizando la memoria.

Con esto se pueden resolver problemas de distinto tipo:


● One to one → tiene una simple entrada y una simple salida
● One to many → consume un solo dato pero saca muchos (ej. image captioning donde
pasamos de una imagen a una secuencia de palabras)
● Many to one → tiene varias entradas y una única salida que es la etiqueta (ej. análisis de
sentimiento donde tenemos una secuencia de palabras y queremos definir si el texto es o no
agresivo)
● Many to many → tiene varias entradas y varias salidas, pero la cantidad de entrada no es la
misma que la de salida (ej. traducción de textos donde se pasa de una secuencia de palabras
a otra secuencia de palabras)
● Many to many → tiene varias entradas y varias salidas, con la misma cantidad de entrada
que de salida (ej. clasificación de acciones en un video: decimos que sucede en cada frame
del video)
H es lo que vamos a llamar estado oculto de la neurona. En un instante t, vamos a consumir un
instante de tiempo de mi dato xt, lo vamos a procesar con una función que toma el dato y el estado
del dato anterior. Combinando la memoria y el dato, se genera el estado en el tiempo actual. La
función f está parametrizada por una matriz de pesos W (que es siempre la misma para todos los
instantes de tiempo). En la función f tenemos la entrada xt y el estado en el tiempo anterior ht-1. A xt
se lo multiplica por una matriz de pesos, mientras que a ht-1 se lo multiplica por otra matriz de
pesos. Se suman ambos productos, se pasa al resultado por una función de activación (tanh va de -1
a 1) y así terminamos actualizando el estado en el tiempo actual. Finalmente, cuando en algún
momento queramos hacer una predicción, la predicción se hace tomando el estado y multiplicando
por otra matriz de pesos. Los pesos de la red recurrente ahora son estas 3 matrices: la matriz
asociada a la entrada, la matriz asociada al estado y la matriz asociada a la salida (la que multiplica al
estado para producir la etiqueta de salida). Con estas matrices vamos a controlar el tamaño de la
salida, del estado anterior, etc. Este es el modelo recurrente más básico que podemos pensar.

El estado es un vector de números que se inicializa en algún valor y se va actualizando. El estado se


actualiza mezclando información proveniente del dato y del estado anterior por medio de productos
matriciales. El estado no es la predicción.

La propiedad distinta que tienen estas redes con respecto a las que venimos viendo es que los pesos
se comparten a lo largo del tiempo. Entra el estado h en su estado inicial, entra el primer dato y
aplicamos la función f. La función f devuelve un estado actualizado (vector de valores actualizado).
Este vector entra de nuevo a la función con el dato 2, pero los pesos que aplicamos de la función son
los mismos que aplicamos antes. Las matrices W son las mismas en cada instante de tiempo mientras
estamos en un paso de gradiente descendiente.

Una vez que tenemos producimos las salidas, las usamos para computar la función de pérdida.
Computamos la función de pérdida para cada uno de los términos y la retropropagamos de la misma
forma vista hasta ahora. Esto lo llamamos retropropagación a través del tiempo porque si bien el
algoritmo es el mismo, el grafo de cómputo se construye a lo largo del tiempo. Cuando tomemos la
derivada, vamos a tomar la derivada de L (función de pérdida) respecto a W.
Problemas abiertos en DL y ML
¿Cómo generalizar a múltiples dominios de datos?
Muchas veces cuando hay un cambio de dominio entre los datos que usamos para entrenar y los
datos que usamos en la vida real los modelos no funcionan tan bien. En estos casos existen ciertas
técnicas que se pueden aplicar. Tiene que ver con la robustez de los modelos que entrenamos y es
clave entre un modelo exitoso en producción y uno no exitoso.

Cuando entrenamos un modelo para resolver una tarea (ej. clasificación de imágenes en distintas
categorías) puede pasar que entrenemos imágenes con un dominio (ej. imágenes sacadas de
Amazon) y después testemos en imágenes que provienen de un dominio distinto (ej. tienen fondo de
color). Un cambio de dominio es un cambio en la distribución de los datos. Cuando estamos en un
problema en el que nos enfrentamos a cambios de dominio, la tarea que queremos resolver es la
misma (ej. queremos predecir las mismas clases), lo que cambia es la distribución de los datos.
Entonces, existen técnicas de adaptación de dominio que permiten adaptar los modelos de un
dominio al otro.
● Problemas multi-sitio: un modelo funciona bien en un hospital pero no en otro porque se usa
un aparato diferente para tomar imágenes médicas (cambia un poco la distribución de los
pixeles).

Si tenemos etiquetas en todos los dominios: Adaptación de dominio supervisada


Solución 1: entrenar con datos de todos los sitios (en caso de tener anotaciones para todos ellos). En
general, al hacer esto no solo no perdemos performance sino que además pasa lo contrario porque
estas redes tienen que aprender las features y para aprender a extraer características esta bueno ver
mucha diversidad de datos.
Solución 2: utilizamos la transferencia de aprendizaje por medio de fine-tuning. Transferimos
conocimiento de un modelo a otro. Es decir, tomamos el modelo que fue entrenado con datos del
dominio source y le transferimos los pesos aprendidos al nuevo modelo con datos del dominio
target. Usamos este W para inicializar el modelo y entrenamos el nuevo modelo con dominio target.
Al inicializar W con los valores del modelo que está entrenado para resolver particularmente la tarea
que se quiere resolver, es muy probable empezar desde un lugar mucho mejor en la superficie de
error y terminar convergiendo a un lugar mejor también. Estos pesos ya son buenos extractores de
características y pueden servir para inicializar un nuevo modelo y a partir de ahí seguir entrenando.
● Si tenemos diferentes arquitecturas, no podemos hacer transferencia de pesos

Si no tenemos etiquetas en el dominio target: Adaptación de dominio no supervisada


En este caso, se tienen datos en el dominio target pero no se tienen etiquetas. Una de las cosas que
se pueden hacer es usar los datos por medio de algún aprendizaje de representación
auto-supervisado, aprendemos representación con esos datos y después se fine-tunea con los datos
de los que se tienen etiquetas.

Ideas para resolverlo:


1. Mapear la distribución de los datos del sitio 1 al sitio 2, y re-entrenar nuestro modelo
Se transforman los datos del dominio target a los del dominio source. Si tenemos un modelo que
funciona muy bien en el dominio source (separando al dataset en clase 1 y clase 2) y cuando llegan
los datos del dominio target los mismos tienen distribution shift, lo que podemos hacer es aprender
una función que transforme los datos de un dominio al otro para poder usar el clasificador que ya
tenemos entrenado o re-entrenar al modelo con los datos trasladados. Aprendemos funciones de
mapeo.
Ej. el dominio source son imágenes de día y el dominio target son imágenes de noche. Podríamos
entrenar algún modelo que transforme imágenes de día en imágenes de noche. De esta manera, si
nuestro dominio target son imágenes de noche, transformamos todas las imágenes de día en
imágenes de noche (tenemos la etiqueta porque ya la teníamos) y entrenamos al modelo de noche.
2. Aprendizaje de features invariantes al dominio
Forzamos al modelo a que las características que use para resolver el problema que se tiene que
resolver sean independientes del cambio del dominio que se está produciendo. Una forma de
lograrlo es con aprendizaje adversario.
Tenemos una red con una parte extractora de features (verde) que son las capas convolucionales y
otra parte clasificadora (azul) que es el perceptrón multicapa. Por más de no tener datos con
etiqueta en el dominio target, lo que sí sabemos es que hay datos que vienen de un dominio y datos
que vienen de otro dominio. Lo que vamos a hacer, entonces, es entrenar una red clasificadora de
dominio (rosa) que va a aprender a decirme de qué dominio viene el dato. En lugar de decirnos si el
dato viene de un dominio u otro a partir de la imagen, nos lo va a decir a partir de los feature maps
que se generan en la parte extractora de features. Vamos a usar los features para decir si los datos
vienen de un dominio u otro. Tenemos 2 cabezales en esta red: uno que predice y otro que dice de
qué dominio viene la imagen. Vamos a entrenar al modelo para que sepa clasificar bien y al mismo
tiempo engañe a la red para que ésta no sepa distinguir de qué dominio vienen los datos. Es decir,
forzamos a que los features aprendidos no sirvan para distinguir de qué dominio vienen los datos.
Vamos a estar forzando invarianza en los features aprendidos.
La parte extractora de features sólo se puede entrenar con datos del dominio source porque se
necesita la etiqueta para clasificar. Sin embargo, cuando pasamos datos a la red clasificadora de
dominio lo podemos hacer con datos de ambos dominios porque no se necesita tener etiquetas de
clase para computar la función de pérdida. Vamos a entrenar esto iterativamente: pasamos imágenes
del dominio source para entrenar la parte de arriba y después cada tanto vamos a meter imágenes
de los dominios (sin etiqueta) para que la red distinga de que dominio proviene. En cada iteración, a
veces hacemos una cosa y a veces otra.
El clasificador de dominio le aporta un segundo término a la función de pérdida que tiene que ver
con clasificar de qué dominio viene la imagen. Pero lo interesante es que lo hace mirando a los
features, no a la imágen. Entonces tenemos definida una función de pérdida con 2 términos y
tratamos de maximizar el término de clasificación y de minimizar el término de clasificador de
dominio. Se trata de engañar a la red. Engañando a la red es que las features se tornan invariantes a
la representación que estamos aprendiendo (invariantes al dominio). Esto sirve para clasificar mejor
imágenes del dominio target.
¿Cómo interpretar los modelos entrenados?
Qué mecanismos tenemos nosotros para preguntarle a la red por qué está tomando las decisiones
que está tomando.

Mapas de saliencia por medio de oclusión


Un mapa de saliencia es una especie de mapa de calor de la imágen que nos indica a dónde va la
atención en esa imágen. En este caso, queremos generar mapas que nos indiquen a dónde miró la
red para decirnos que la clase que está ahí es una clase dada. La técnica más simple que se conoce
para hacer esto es la técnica de oclusión (tapar una parte de la imágen). Vamos a taparle partes a la
imágen sistemáticamente (vamos moviendo el cuadrado gris por toda la imagen) y registrar cuál es la
probabilidad de la clase que nos interesa. Se registran los diferentes valores de probabilidad como si
fuesen mapas de calor. Esta técnica es agnóstica a la arquitectura.

Mapas de saliencia por retropropagación


Hacemos la pasada forward de la red y después se computa el gradiente del score de la clase de
interés respecto a los píxeles de entrada. Esta derivada va a tener tantos componentes como pixeles
haya en la entrada. Indica cómo cambia la predicción cuando nos movemos un poquito el valor del
pixel. Ej: tomamos la predicción para perro y computamos la derivada de la predicción (score) para
perro respecto a la imágen x.

Class activation maps


También genera mapas de calor que sirven para verificar que la red esté haciendo predicciones con
cosas que tengan sentido o si está, por ejemplo, prediciendo que hay un perro mirando la esquina
superior donde no hay nada.
Tomamos el promedio de cada feature map y creamos el GAP, al que después conectamos con una
capa a la cantidad de clases que tiene el modelo. Para construir un class activation map, vamos a
tomar al promedio pesado de los feature maps (antes de aplicar GAP) pesandolo por el peso que le
corresponde al valor de haber hecho GAP en ese feature map para la clase que nos interesa. Es decir,
tomamos los feature maps, multiplicamos a cada uno por el peso que le corresponde y sumamos
todo el promedio pesado para reconstruir el mapa de activación.

¿Cómo evitar el sesgo en los modelos entrenados?


Hablamos de sesgo cuando el modelo tiene un mejor rendimiento en una determinada población
que en otra. El sesgo algorítmico es una especie de reproducción sistemática de error de los modelos
en determinados conjuntos poblacionales (ej. tener menor rendimiento en una población que en la
otra).

Sesgos en los modelos de IA


● Sesgo de género en la traducción de textos → probablemente en los datos de training haya
habido una sobre-representación de hombres.
● Sesgo de distribución geográfica en clasificación de imágenes (ej.novia o disfraz) → cuando
al modelo de clasificación se le muestran novias occidentales, las clasifica como novias.
Cuando se le muestran novias indias, las clasifica como disfraces.
● Sesgo en el reconocimiento de rostros → el rendimiento de los modelos de reconocimiento
facial comerciales es mejor en personas con piel clara que en personas con piel oscura.
● Datos sesgados → el sesgo en los datos suele reflejar imbalances en infraestructuras
institucionales y relaciones de poder social.

Atributos protegidos: son variables contra las cuales queremos proteger el sesgo algorítmico como
género, etnia, país de procedencia, edad, pesos, color de piel, etc. No queremos que el modelo esté
sesgado con respecto a estos atributos.
Ej. en un algoritmo que recomienda si deberíamos o no otorgarle un crédito a una persona, el género
podría ser una variable protegida. No queríamos darle un crédito a alguien en base al género.

¿Cómo hacemos nosotros para saber si nuestro modelo es justo?


Paridad demográfica: la clasificación tiene que ser independiente del atributo protegido (ej. la
probabilidad de darle un crédito a una persona de género femenino es la misma que la probabilidad
de darle un crédito a una persona de género masculino)

También podría gustarte