“Una vida sin examen no merece ser vivida.”
Me permito afirmar que Backpropagation constituye uno de los conceptos más interesantes y elegantes de la inteligencia artificial. Un algoritmo altamente
eficiente, bello por donde se le mire, y cuyo uso hoy es prácticamente universal en redes neuronales.
Piense usted que este principio puede incluso extrapolarse a nuestra propia vida, como aquel que vuelve la mirada sobre sí mismo para reconocer sus errores y, en ellos, encontrar la posibilidad de mejorar.
En este post nos adentraremos en las entrañas de esta comúnmente percibida caja negra. Exploraremos la lógica que da vida al algoritmo, la matemática que lo sustenta y algunos ejemplos que, espero, resulten de interés y agraden al lector.
Conviene señalar que este post está inspirado en el trabajo de Andrej Karpathy, específicamente en Micrograd, cuya lectura y visualización recomiendo ampliamente.
¿Qué es Backpropagation?
En palabras propias: Backpropagation es un algoritmo cuyo objetivo es calcular la influencia que tiene cada parámetro de la red en el error de la predicción final. Lo logra propagando ese error desde la salida hacia cada una de las capas anteriores, calculando el gradiente local de cada parámetro a lo largo del camino. De esta manera, tras una predicción, cada parámetro puede ser ajustado en función de su influencia en el error.
Una manera interesante de entender este concepto es la siguiente:
Piense usted que una persona tiene como objetivo bajar 5 kg en un mes. Para lograrlo, controla algunas variables:
- Horas de sueño
- Calorías consumidas
- Horas de ejercicio
- Cantidad de suplementos
Al final del mes, solo bajó 3 kg. Si esta persona se pone a analizar qué tanto influyó cada variable en ese resultado,
estaría aplicando la filosofía de Backpropagation.
Sus siguientes acciones estarían basadas en este análisis: una vez que sabe qué variables tienen más o menos impacto, decide ajustarlas. Si descubre que dormir más horas le ayuda a bajar de peso, aumenta las horas de sueño. Si nota que, a más calorías consumidas, mayor es el peso, reduce las calorías. Similar, por no decir igual, a una red neuronal, que sube o baja el peso de sus parámetros con el fin de disminuir el error en la siguiente predicción.
Loss Function
Ahora que entendemos la filosofía del algoritmo, surge una pregunta natural: ¿cómo sabe la red cuánto se equivocó?
La respuesta es la loss function: la función que cuantifica el error entre el resultado esperado y el resultado obtenido — es decir, qué tan lejos quedó la predicción de la verdad. De una manera más simple, podemos pensarla como la función que nos dirá si estamos cerca o lejos del resultado esperado. Se calcula a partir de dos valores:
- Ground truth: el valor real.
- Predicción: el valor estimado por el modelo.
Mientras más pequeño sea este valor, menor será el error del modelo. Podría pensarse que, idealmente, si la función de pérdida (loss)
llegara a 0, el modelo alcanzaría un desempeño perfecto sobre los datos evaluados, siendo digno de orgullo.
Sin embargo, en la práctica, una loss de entrenamiento igual a 0 no es
buena noticia — suele ser señal de overfitting: el modelo memorizó los ejemplos
en vez de aprender el patrón general, y probablemente falle al ver datos nuevos.
Existen muchas loss functions, y la elección depende del tipo de problema a resolver. Una de las más utilizadas, acaso la más utilizada, en regresión es el MSE (Mean Squared Error):
Donde:
y_i es el valor real de la muestrai (el ground truth).\hat{y}_i es el valor predicho por el modelo.n es el número total de muestras.
En palabras cortas, esta ecuación:
- Calcula la diferencia entre el valor real y el predicho.
- La eleva al cuadrado.
- Promedia el resultado sobre todas las muestras.
Un caso práctico puede ser el de predecir el coste de 3 casas. Si los precios reales de las tres casas son $50k, $60k y $70k, y el modelo predijo $48k, $65k y $40k respectivamente, el MSE o loss function sería:
Juntas, estas piezas forman el ciclo de entrenamiento: la loss function cuantifica el error entre la predicción y el valor esperado; Backpropagation calcula cómo influye cada parámetro en ese error; y un algoritmo de optimización ajusta los parámetros para reducirlo. De una forma repetitiva, se da una nueva predicción, una nueva loss, un nuevo backpropagation… y así sucesivamente, dando un modelo cada vez mejor.
Derivadas
Con los conceptos anteriores explicados, es momento de entrar en la matemática que hace posible esto, y con ello introducirnos en el concepto de derivadas.
Empecemos entonces con una analogía. Imagínese usted en la siguiente situación: está en la cima de una montaña, vendado y con neblina. Su objetivo es llegar al punto más bajo de la montaña. Ya que no puede ver, no sabe a dónde moverse a simple vista; sin embargo, sí puede sentir la inclinación del terreno bajo sus pies a medida que se mueve. Notará que:
- Si da un pequeño paso a la derecha, el piso sube.
- Si da un pequeño paso a la izquierda, el piso sube también.
- Si da un pequeño paso hacia adelante, el piso baja.
Dar el paso hacia adelante es su dirección. Si continúa dando pasos en esa dirección y el terreno sigue bajando, sabe que va por buen camino. Puede suceder que, si da pasos muy pequeños, el terreno baje y baje, pero le tome demasiado tiempo llegar al fondo. Entonces viene a su mente dar grandes saltos para acelerar el proceso.
Sin embargo, en un terreno con un relieve irregular, esos saltos pueden hacerlo sobrepasar el punto más bajo y empezar a subir nuevamente. Cuando eso ocurre, sabe que se pasó del mínimo: entonces reduce el tamaño de sus pasos, cambia ligeramente la dirección y continúa descendiendo. Al repetir este proceso una y otra vez, encontrará un punto donde cualquier pequeño movimiento haga que el terreno vuelva a subir. Es ahí donde usted habrá encontrado el punto más bajo posible de la montaña.
Esta analogía es útil, más no perfecta, pero nos permite entender conceptos importantes: al igual que usted en esa montaña, la red neuronal quiere minimizar su función de pérdida, llegar al punto más bajo posible. Para lograrlo, necesita saber cómo cambia el terreno respecto a cada pequeño paso que da: si comienza a subir, a bajar o se mantiene prácticamente plano. Y esto es lo que explica la derivada: la tasa de cambio de una variable respecto a otra.
Esto nos da dos piezas de información críticas:
- Dirección: el signo de la derivada indica si el parámetro debe aumentar o disminuir. En la montaña, sería si la pendiente va hacia arriba o hacia abajo.
- Sensibilidad: el valor absoluto de la derivada indica qué tan sensible es la loss function respecto a un cambio en el parámetro. En otras palabras, nos permite decidir si dar pasos pequeños o grandes con el fin de llegar al punto más bajo posible.
¿Qué es matemáticamente?
Para tener esto más claro, representemos nuestra loss function en una parábola sencilla. El eje X serán
nuestros parámetros
Como vemos en la parábola, al estar parados en un punto con un peso
Para saber qué dirección tomar y con qué magnitud hacerlo, lo ideal sería conocer de antemano cómo cambia la loss respecto a una modificación del parámetro,
sin tener que probar diferentes movimientos.
Claro, para nosotros esto resulta
evidente, puesto que vemos la parábola: en el punto
Sin embargo, una red neuronal no tiene acceso a la forma completa de esta función. Solo conoce los valores actuales de sus parámetros y la pérdida obtenida. Por ello, necesita alguna forma de conocer la pendiente en ese punto para saber cómo ajustar sus parámetros.
El problema es que no podemos conocer la pendiente de algo sin comparar al menos dos puntos. Entonces, para simular lo más cercano posible a "no movernos", damos un paso ridículamente pequeño — una distancia casi invisible. Al comparar nuestro punto original con ese nuevo punto sumado a este paso ridículamente pequeño, podemos extraer tanto la dirección como la magnitud del cambio.
La ecuación que nos lo permite es el cociente de Newton:
Donde:
f(w) es el Loss en nuestra posición actual.h es ese paso infinitesimalmente pequeño que damos.f(w + h) es el nuevo Loss tras haber dado ese micro-paso.
Pongámoslo en números concretos. Supongamos que nuestra loss function es
Queremos saber la derivada (la pendiente) en
Aplicamos el cociente de Newton:
El resultado es 6. Eso es la derivada de
¿Qué nos dice ese 6?
Nos dice la tasa de cambio de la loss en el punto
Ese 6 es una
propiedad del punto donde estamos parados — realmente no depende del tamaño del paso
Con la derivada o pendiente obtenida, podemos calcular cuánto cambia realmente la loss cuando damos ese paso específico.
Si
multiplicamos la pendiente por el tamaño del paso,
Donde
El concepto de límite
Como mencionamos, en el cálculo formal no podemos hacer que
Con esto, tenemos la definición formal de la derivada:
Gracias a esta fórmula podemos demostrar, por ejemplo, que la derivada de
El mismo resultado que obtuvimos al aproximar la pendiente mediante el cociente de Newton con un valor pequeño de h. La diferencia es que ahora, usando el límite, obtenemos la pendiente exacta en ese punto, sin depender de elegir manualmente un valor de h.
Derivadas en un grafo de operaciones
Hasta ahora hemos trabajado con una función sencilla:
Consideremos los siguientes valores y operaciones:
a = 2.0
b = -3.0
c = 10.0
e = a * b → e = -6
d = e + c → d = 4
f = -2.0
L = d * f → L = -8
Aquí,
Empezando desde el final: dL/dL
Backpropagation, tal y como su nombre indica, propaga información hacia atrás. El primer paso es calcular la derivada
de
Tiene sentido: si
L = d \cdot f : la multiplicación
El siguiente paso es calcular cómo afectan
El resultado: la derivada de una
multiplicación simple respecto a una variable es simplemente el valor de la otra. ¿Por qué? Porque la derivada pregunta: "¿Qué pasa si solo modifico d?",
y
El gradiente de
La regla de la cadena
Nuestra anterior operación fue bastante rápida puesto que
Para lograr esto de una manera eficiente, usaremos la regla de la cadena. Veamos una analogía para entender mejor cómo funciona esto.
La intuición: humano, bicicleta y coche
Tomemos tres variables de velocidad:
h = velocidad de un humanob = velocidad de una bicicleta(b = 5h , 5 veces la velocidad humana)v = velocidad de un coche(v = 10b , 10 veces la velocidad de la bicicleta)
Si quisiéramos saber cuánto más rápido es un coche respecto a un humano, tenemos que pasar por la bicicleta. Por lo que:
La derivada del coche respecto al humano es igual a la derivada del coche respecto a la bicicleta, multiplicada por la derivada de la bicicleta respecto al humano.
En notación matemática:
En este ejemplo, un coche es 50 veces la velocidad humana. Eso es la regla de la cadena: encadenar derivadas parciales para calcular el impacto a través de varias transformaciones.
Ahora bien, ¿qué pasa si agregamos un avión donde
Pero ya sabemos que
Esto es justamente lo que hace Backpropagation en la práctica: en lugar de calcular el impacto de cada parámetro sobre
Aplicando la regla de la cadena al grafo
Volviendo a nuestras operaciones. Tenemos que
Ya sabemos que
La derivada de una constante es 0, así que
Y lo mismo para
Llegando a a y b
El último nodo es
El gradiente de
¿Cómo actúa la red con estos gradientes?
Con los gradientes calculados, sabemos si cada parámetro afecta positiva o negativamente a la loss, y la sensibilidad de la loss respecto a cada uno de ellos. Ahora viene la actualización.
Si queremos bajar la loss function, necesitamos mover cada parámetro en la dirección opuesta a su gradiente. La fórmula es:
Donde
El gradiente de
El learning rate es un hiperparámetro que elegimos nosotros: define el tamaño de los pasos que debe dar la red al actualizar sus pesos.
- Learning rate alto: la red se mueve rápido, pero puede pasarse del mínimo y no llegar a converger.
- Learning rate bajo: la red es más precisa, pero necesita muchas más iteraciones para llegar a un peso óptimo.
Existen técnicas para elegir un buen learning rate — optimizadores como Adam o técnicas de learning rate scheduling — pero en general es un parámetro que se ajusta iterando y observando el comportamiento de nuestro entrenamiento.
De los números a la neurona
Habiendo obtenido el concepto del gradiente, la filosofía de Backpropagation, y sabiendo cómo funciona el ajuste de nuestros pesos para reducir la loss function, queridos amigos, nos hemos graduado en el núcleo de lo que significa el entrenamiento de una red neuronal artificial.
El grafo anterior — con sus sumas, multiplicaciones y regla de la cadena — no era un ejemplo aislado: es exactamente el mismo mecanismo que ocurre dentro de cada neurona y entre capas, solo que a mayor escala. Lo siguiente es entender de manera más clara cómo se organizan esas neuronas en la práctica — donde las layers entran en juego. Pero antes de jugar con estas layers, vea usted la propia neurona que alberga en su interior humano.
La neurona: de lo biológico a lo matemático
La unidad computacional básica del cerebro es la neurona. En el sistema nervioso humano hay aproximadamente
86 mil millones de ellas, conectadas entre sí por unas
Cada neurona recibe señales de entrada a través de sus dendritas y produce una señal de salida a lo largo de su axón. Ese axón eventualmente se ramifica y se conecta, mediante sinapsis, a las dendritas de otras neuronas. Es, literalmente, una red neuronal: la salida de una es la entrada de la siguiente.
Puede ser del interés del lector investigar un poco más sobre la neurona biológica. En ese camino se topará con conceptos fascinantes como los spikes, los neurotransmisores, los PEPS y PIPS, y quizá se maraville al descubrir que pensamientos, recuerdos y emociones emergen de la interacción de millones de señales sorprendentemente simples, análogas, en cierto sentido, a los 1 y 0 de una computadora.
Ahora comparemos nuestra neurona con el modelo matemático que usamos en redes neuronales artificiales:
En esta neurona artificial podemos identificar cada pieza biológica que acabamos de describir:
-
x_0 : es el input que llega por la línea verde. Esta es una entrada a nuestra neurona y proviene del axón de otra — el mismo axón que vimos en el dibujo biológico, encargado de transmitir la señal hacia afuera. -
w_0 : en la sinapsis ocurre la multiplicación entre el input y el peso, dandow_0 x_0 . La sinapsis es la conexión entre el axón de una neurona y la dendrita de la siguiente — siendo esta última la que recibe la señal entrante. Nuestra neurona artificial le asigna un peso aleatorio a esta conexión. -
Vemos también otros pares
w_1 x_1 ,w_2 x_2 — la neurona tiene varias entradas y, por lo tanto, varias dendritas y sinapsis a la vez, exactamente como ocurre en nuestro propio cerebro.
En el cell body (cuerpo celular) ocurre la suma:
Cada neurona suma la multiplicación de sus entradas por sus pesos, más un bias — de forma
muy similar a como, en nuestro grafo de operaciones de las secciones anteriores, unos nodos eran el
resultado de otros. El resultado de esa suma pasa por una función de activación
Layers y neuronas
Lo que queda a partir de este punto es atar los cabos: una red neuronal artificial está compuesta por decenas, cientos, miles o incluso millones de las neuronas que acabamos de ver. Estas se agrupan en layers (capas), donde cada capa tiene la cantidad de neuronas que nosotros definamos.
Las capas normalmente se dividen en tres tipos:
- Input Layer: es la capa que recibe los inputs de la red. Si vamos a procesar imágenes, aquí recibimos la cantidad de píxeles. Si nuestro problema es predecir el precio de una casa a partir del tamaño y el número de habitaciones, tendremos 2 entradas en nuestra input layer.
- Output Layer (capa de salida): también la define el problema. En el ejemplo de la casa, ya que debemos predecir el precio, eso es una sola salida — por lo que solo necesitamos 1 neurona en esta capa.
- Hidden Layer: aquí es donde realmente sucede la magia y la ingeniería. Puede haber millones de neuronas en estas capas, y pueden ser varias capas ocultas encadenadas.
En la siguiente imagen observamos la representación de estas capas:
Una forma curiosa de pensar en cómo funcionan estas capas es — y de manera bastante similar ahora que lo concibo — como
un consejo de expertos que nos ayudan a
tomar una decisión.
Piense usted en el escenario siguiente: queremos saber si una pintura surrealista de Salvador Dalí es auténtica
o no. Nuestra red tendrá 2 hidden layers, cada una con 10 neuronas, y una output layer de una sola neurona
que nos dará el resultado final:
- Input layer: este grupo recibe los píxeles de la pintura de Dalí. Cada "asistente" mira un píxel y anota si es rojo, azul, verde, etc. Solo reciben los píxeles, sin analizarlos todavía.
- Hidden Layer 1: este grupo de 10 expertos recibe, cada uno, los píxeles completos de la pintura (esto es importante: cada neurona recibe todos los inputs), asignando un peso a cada píxel. Empiezan a detectar patrones propios de Dalí — sus líneas curvas, su técnica de trazo, la forma en que construye sus figuras. Cada experto da su propio valor de análisis, lo que produce 10 valores distintos en esta capa.
- Hidden Layer 2: en este segundo grupo de 10 expertos, cada uno recibe los 10 valores del grupo anterior, asignando un peso a cada uno de ellos, y empieza a ver el "ecosistema" de la pintura: ¿es surrealismo? ¿este cielo se parece más a Van Gogh? ¿esto recuerda a otra obra? Cada experto analiza el concepto considerando también las opiniones anteriores, y nuevamente entrega su propio valor — 10 valores nuevos en esta capa.
- Output: finalmente, en la salida hay un solo experto. Recibe los 10 valores del grupo anterior, asignando un peso a cada uno de ellos, y da su veredicto: "basado en la opinión de estas neuronas y sus análisis, esta pintura es de Dalí con un 97% de confianza."
Podemos notar que cada capa hizo una abstracción diferente y se alimentó del input de las neuronas anteriores — tal y como vimos en nuestros nodos anteriores, donde los nodos que aparecen después son el resultado de los cálculos de los nodos previos.
Aquí está el puente con Backpropagation: cada peso de cada experto es un parámetro como
Cerrando el círculo: de las capas a Backpropagation
Si aún hay alguna pieza que no termina de encajar, hagamos algo apropiado para un post sobre Backpropagation: un backward de los conceptos. Comencemos desde las capas de la red y retrocedamos paso a paso.
-
Acabamos de ver que las capas de neuronas son como un grupo de expertos. Cada capa recibe los valores de la anterior, hace su propia abstracción, y pasa su resultado hacia la siguiente capa.
-
¿Qué pasa cuando esta red da un valor que no es el esperado? Tal como ya pensó el lector, esa red necesita ajustar sus pesos para poder dar un mejor resultado la próxima vez.
-
¿Y cómo sabe la red qué tan lejos está del resultado esperado? A través de la loss function. Con algo como el MSE —
(\text{predicción} - \text{real})^2 — medimos ese error y obtenemos un número que Backpropagation podrá descomponer hacia atrás. -
¿Y cómo reducimos esa loss function? Necesitamos obtener el gradiente de cada peso de la red, y para eso usamos la regla de la cadena y las derivadas.
-
¿Y de dónde salen esos nodos que derivamos? De la propia neurona: si las neuronas dentro del cell body hacen
x_0 w_0 + x_1 w_1 + \cdots + x_n w_n + b , y luego pasan por una función de activación como ReLU o Tanh, entonces Backpropagation puede calcular la derivada de la loss function respecto a cada peso y al bias. -
¿Y qué hacemos con esos gradientes una vez calculados? Ajustamos cada peso con la misma fórmula que ya conocemos:
w_{\text{nuevo}} = w_{\text{antiguo}} - \alpha \cdot \frac{\partial L}{\partial w} Y repetimos este ciclo una y otra vez, hasta lograr que la loss function sea lo más baja posible.
Felicidades, entonces, al lector, que ha descendido a las entrañas de las redes neuronales artificiales tal cual Teseo en el Laberinto, y ha salido victorioso. Ha desmembrado cada uno de sus mecanismos, comprendido la filosofía que gobierna su aprendizaje y examinado cada pieza con paciencia y curiosidad, hasta conocer en profundidad la presa que hoy decidió enfrentar.
Backpropagation en código
Se deja a disposición el repositorio de micrograd de Andrej Karpathy, y mi propio notebook aplicándolo — parte del repositorio de código abierto de este blog.
Micrograd- Repositorio original de micrograd, de Andrej Karpathy — se muestran los conceptos de Neuron, Layer, Grad y Perceptron implementados en código.
Mi notebook aplicando micrograd
- Mi propio notebook aplicando micrograd, disponible en el repositorio de este blog. Puede tener funciones que no estén en el orden más pedagógico, por lo que siempre recomiendo revisar primero el repositorio original como estándar.