Inteligencia Artificial

Retropropagación (Backpropagation): qué es y cómo funciona en el entrenamiento de redes neuronales

Por Antonio Richaud, Publicado el 10 de Octubre de 2024

Las redes neuronales artificiales han revolucionado el campo de la inteligencia artificial, permitiendo avances significativos en tareas como el reconocimiento de voz, procesamiento de imágenes, predicción de enfermedades y más. Una de las claves detrás del éxito de estas redes es el algoritmo de retropropagación (backpropagation), un método de optimización que permite ajustar los pesos de las conexiones neuronales, minimizando los errores durante el entrenamiento del modelo.

La retropropagación es el corazón de las redes neuronales modernas, ya que, sin ella, las redes tendrían dificultades para aprender de manera eficiente. Este algoritmo permite a la red "aprender de sus errores" ajustando los parámetros de manera automática a través de un proceso iterativo. En este artículo, exploraremos en detalle qué es la retropropagación, cómo funciona y cómo se usa para entrenar redes neuronales, utilizando un ejemplo práctico donde se predice la probabilidad de que una persona tenga dengue, basándose en sus síntomas.

A lo largo del artículo, también mostraremos cómo implementar este algoritmo en Python, explicando paso a paso el proceso de entrenamiento de una red neuronal. Además, visualizaremos el progreso del entrenamiento con gráficos que ilustran cómo la red ajusta sus predicciones con el tiempo.


¿Qué es la retropropagación?

La retropropagación, también conocida como backpropagation, es un algoritmo utilizado para entrenar redes neuronales artificiales. Se basa en el concepto de propagación del error hacia atrás, lo que significa que, después de calcular la salida de la red, se compara con el valor esperado para determinar cuánto error se cometió. A partir de ese error, el algoritmo ajusta los pesos de la red para minimizar el error en las siguientes iteraciones.

La clave del éxito de la retropropagación radica en el uso del gradiente descendente. Este método de optimización permite encontrar los valores de los pesos que minimizan la función de pérdida, es decir, la diferencia entre la salida predicha y el valor real. El proceso de ajuste se realiza en dos fases principales: la propagación hacia adelante y la propagación hacia atrás.

1. Propagación hacia adelante

Durante la fase de propagación hacia adelante, los datos de entrada pasan a través de las distintas capas de la red neuronal, donde cada neurona aplica una función de activación (como ReLU o Sigmoide) y genera una salida. Esta salida es luego enviada a la siguiente capa hasta llegar a la capa de salida, donde se obtiene la predicción final.

2. Propagación hacia atrás

En la fase de propagación hacia atrás, se calcula el error entre la predicción de la red y el valor real esperado. Este error es propagado hacia atrás a través de la red, calculando los gradientes de la función de pérdida con respecto a los pesos en cada capa. Finalmente, los pesos se actualizan usando el método del gradiente descendente, ajustándose en la dirección que minimiza el error.

En resumen, la retropropagación es el algoritmo clave para entrenar redes neuronales, permitiendo que estas aprendan de manera eficiente a través de ajustes iterativos en sus pesos. En la siguiente sección, veremos cómo este algoritmo se puede implementar en Python.


Construcción de una red neuronal para predecir el dengue

Ahora que comprendemos cómo funciona el algoritmo de retropropagación, implementemos una red neuronal simple en Python que prediga la probabilidad de que una persona tenga dengue basado en tres síntomas: fiebre, dolor de cabeza y dolor muscular.

1. Definición del problema

Los datos de entrada consisten en tres síntomas que pueden presentarse en una persona con posible dengue. Estos serán representados por valores numéricos entre 0 y 1:

  • Fiebre alta (x1): 0.9 (muy alta fiebre)
  • Dolor de cabeza (x2): 0.8 (fuerte dolor de cabeza)
  • Dolor muscular (x3): 1 (presencia de dolor muscular)

El objetivo es entrenar una red neuronal que tome estos síntomas como entrada y prediga si la persona tiene dengue (salida 1) o no tiene dengue (salida 0).

2. Implementación de la red neuronal en Python

Aquí está el código para definir la red neuronal simple y aplicar el algoritmo de retropropagación:


import numpy as np
                  
# Función de activación ReLU
def relu(x):
  return np.maximum(0, x)  # Este bloque debe estar indentado
      
# Derivada de la función ReLU
def relu_deriv(x):
  return np.where(x > 0, 1, 0)  # Indentación correcta
  
# Función de activación sigmoide
def sigmoid(x):
  return 1 / (1 + np.exp(-x))  # Indentación correcta
  
# Derivada de la función sigmoide
def sigmoid_deriv(x):
  return sigmoid(x) * (1 - sigmoid(x))  # Indentación correcta
  
# Definir los datos de entrada (síntomas)
X = np.array([[0.9, 0.8, 1]])
                    
# Salida esperada (1 = tiene dengue, 0 = no tiene dengue)
y = np.array([[1]])
                    
# Pesos iniciales para la capa oculta y la capa de salida
W1 = np.array([[0.5, 0.2, 0.8], [0.9, 0.7, 0.3]])
W2 = np.array([[1.2, 0.5]])
                    
# Sesgos iniciales
b1 = np.array([[-0.1], [0.2]])
b2 = np.array([[-0.3]])
                    
# Paso 1: Propagación hacia adelante
h1 = relu(np.dot(W1, X.T) + b1)  # Capa oculta
output = sigmoid(np.dot(W2, h1) + b2)  # Capa de salida
                    
print("Salida inicial:", output)

                    

Salida Inicial


En este código, definimos una red neuronal con tres entradas, dos nodos en la capa oculta y un solo nodo en la capa de salida. Utilizamos la función de activación ReLU para la capa oculta y Sigmoide para la capa de salida, que nos proporciona una probabilidad entre 0 y 1.

3. Cálculo de la retropropagación

Después de la propagación hacia adelante, procedemos a calcular el error y aplicar la retropropagación para ajustar los pesos y minimizar el error. Aquí está el código para realizar la retropropagación:


# Paso 2: Cálculo del error (función de pérdida)
error = y - output
                  
# Paso 3: Retropropagación del error
d_output = error * sigmoid_deriv(output)  # Derivada del error en la salida
d_h1 = np.dot(W2.T, d_output) * relu_deriv(h1)  # Error en la capa oculta
                  
# Ajuste de los pesos
W2 += np.dot(d_output, h1.T) * 0.1  # Actualización de los pesos de la capa de salida
W1 += np.dot(d_h1, X) * 0.1  # Actualización de los pesos de la capa oculta
                  
# Actualización de los sesgos
b2 += d_output * 0.1
b1 += d_h1 * 0.1
                  
print("Nueva salida después de retropropagación:", sigmoid(np.dot(W2, relu(np.dot(W1, X.T) + b1)) + b2))

                  

Salida después de retropropagación


En este código, se calcula el error como la diferencia entre la salida esperada (y) y la salida actual (output). Luego, se retropropaga este error calculando las derivadas en cada capa. Los pesos (W1 y W2) y los sesgos (b1 y b2) se actualizan usando una tasa de aprendizaje de 0.1. Este proceso reduce el error y mejora las predicciones de la red neuronal.

Después de aplicar la retropropagación, la red neuronal vuelve a realizar la predicción con los pesos ajustados, lo que debería producir un valor de salida más cercano al valor real esperado.


Visualización de los resultados

Una parte importante del proceso de entrenamiento es visualizar cómo evoluciona el error a lo largo de las iteraciones. A continuación, implementaremos un bucle de entrenamiento donde se aplicará la retropropagación repetidamente y graficaremos la disminución del error en cada iteración.

Código para visualizar el progreso del entrenamiento


import matplotlib.pyplot as plt
                
# Definir variables para registrar el error en cada iteración
errors = []
                
# Función de entrenamiento
epochs = 1000  # Número de iteraciones de entrenamiento
                
for epoch in range(epochs):
  # Paso 1: Propagación hacia adelante
  h1 = relu(np.dot(W1, X.T) + b1)  # Capa oculta
  output = sigmoid(np.dot(W2, h1) + b2)  # Capa de salida
                    
  # Paso 2: Cálculo del error (función de pérdida)
  error = y - output
  errors.append(np.mean(np.abs(error)))  # Registro del error medio absoluto
                    
  # Paso 3: Retropropagación del error
  d_output = error * sigmoid_deriv(output)  # Derivada del error en la salida
  d_h1 = np.dot(W2.T, d_output) * relu_deriv(h1)  # Error en la capa oculta
                    
  # Ajuste de los pesos
  W2 += np.dot(d_output, h1.T) * 0.1  # Actualización de los pesos de la capa de salida
  W1 += np.dot(d_h1, X) * 0.1  # Actualización de los pesos de la capa oculta
                    
  # Actualización de los sesgos
  b2 += d_output * 0.1
  b1 += d_h1 * 0.1
                
  # Graficar el error a lo largo de las épocas
  plt.plot(errors)
  plt.title("Progreso del entrenamiento")
  plt.xlabel("Iteraciones")
  plt.ylabel("Error medio absoluto")
  plt.show()
                
                  

En este código, entrenamos la red neuronal durante 1000 iteraciones (épocas) y registramos el error medio absoluto en cada una de ellas. Luego, usamos matplotlib para graficar cómo disminuye el error con el tiempo, lo que nos da una visualización clara de cómo la red está aprendiendo.

Interpretación de los resultados

En el gráfico generado, deberías ver una curva que desciende a medida que avanzan las iteraciones. Esto indica que el error va disminuyendo, lo que significa que la red neuronal está ajustando sus pesos de manera efectiva para hacer mejores predicciones.

Si bien la curva de error puede no ser completamente suave (pueden aparecer pequeñas fluctuaciones), el objetivo es que, en general, el error disminuya conforme avanzan las iteraciones, lo que refleja un entrenamiento exitoso.


Mejores prácticas en redes neuronales y retropropagación

El algoritmo de retropropagación es fundamental para entrenar redes neuronales, pero su uso efectivo requiere varios ajustes y consideraciones. A continuación, se presentan algunas de las mejores prácticas para asegurar que tu red neuronal esté bien entrenada y que obtengas los mejores resultados posibles.

1. Ajuste de la tasa de aprendizaje

La tasa de aprendizaje es un parámetro crucial que controla cuánto se ajustan los pesos en cada iteración. Una tasa de aprendizaje muy alta puede hacer que la red nunca converja correctamente, mientras que una tasa demasiado baja puede hacer que el entrenamiento sea demasiado lento. Experimenta con diferentes tasas de aprendizaje y observa cómo afecta al error. Un buen punto de partida es un valor entre 0.01 y 0.001.

2. Regularización para evitar el sobreajuste

El sobreajuste ocurre cuando la red neuronal aprende demasiado bien los detalles específicos de los datos de entrenamiento y no generaliza bien a nuevos datos. Para evitar este problema, se puede aplicar regularización. Algunas técnicas comunes incluyen:

  • Regularización L2: Añade una penalización al valor de los pesos más grandes, haciendo que la red prefiera soluciones más simples.
  • Dropout: Durante el entrenamiento, se "apagan" aleatoriamente algunas neuronas para evitar que la red dependa demasiado de ciertos patrones.

3. Optimización con algoritmos avanzados

En lugar de usar solo el gradiente descendente simple, considera usar optimizadores avanzados como:

  • Adam: Combina las ventajas del gradiente descendente con momento y el método de AdaGrad, lo que resulta en una convergencia más rápida y estable.
  • RMSProp: Divide la tasa de aprendizaje por la media de los gradientes recientes, adaptándose bien a problemas con datos muy ruidosos.

4. Normalización de los datos

Asegúrate de que los datos de entrada estén normalizados para que todas las características tengan una escala similar. Esto ayuda a que la red neuronal converja más rápido y evita que las entradas con valores más grandes dominen el entrenamiento.

5. Uso de validación cruzada

La validación cruzada es una técnica que te permite evaluar el rendimiento de tu red en varios subconjuntos de los datos. Esto asegura que la red está generalizando bien y no está simplemente aprendiendo de memoria los datos de entrenamiento.

6. Control de las épocas de entrenamiento

El número de épocas (iteraciones completas a través de los datos de entrenamiento) es otro parámetro importante. Entrenar por demasiadas épocas puede llevar al sobreajuste, mientras que muy pocas épocas pueden resultar en un modelo subentrenado. Usa la técnica de "early stopping" para detener el entrenamiento cuando el error en los datos de validación ya no mejora.


Conclusión

A lo largo de este artículo, hemos explorado el papel crucial del algoritmo de retropropagación en el entrenamiento de redes neuronales. Este proceso, que ajusta los pesos y sesgos a través del gradiente descendente, permite que las redes neuronales aprendan de manera eficiente y mejoren sus predicciones en cada iteración.

Además, hemos implementado una red neuronal simple en Python para predecir si una persona tiene dengue a partir de tres síntomas principales. Hemos visto cómo el proceso de propagación hacia adelante y la retropropagación trabajan juntos para reducir el error, y cómo es posible visualizar el progreso del entrenamiento mediante gráficos.

También hemos discutido varias mejores prácticas, como el ajuste de la tasa de aprendizaje, la regularización para evitar el sobreajuste, el uso de optimizadores avanzados como Adam, y la importancia de la validación cruzada para asegurarnos de que nuestra red neuronal generalice bien en datos nuevos.

En resumen, la retropropagación es una herramienta esencial en el arsenal de cualquier científico de datos o desarrollador de inteligencia artificial. Entender su funcionamiento y saber aplicarla correctamente es clave para construir modelos robustos y eficientes que puedan resolver problemas complejos en una amplia variedad de aplicaciones.

Si estás comenzando en el mundo de las redes neuronales o ya tienes experiencia, aplicar las técnicas y prácticas discutidas aquí te permitirá optimizar tus modelos y mejorar su rendimiento. :)


Recursos adicionales

  • Documentación de NumPy: Biblioteca fundamental para computación numérica en Python. Es utilizada ampliamente en el desarrollo de redes neuronales.
  • Guía de TensorFlow: Una biblioteca muy popular de código abierto para construir y entrenar redes neuronales profundas.
  • Tutoriales de PyTorch: Excelente punto de partida para aprender PyTorch, una de las bibliotecas más usadas para redes neuronales en la investigación y desarrollo.
  • Documentación de Matplotlib: Biblioteca esencial para crear gráficos y visualizaciones de datos en Python, ideal para visualizar el progreso del entrenamiento de redes neuronales.
  • DeepLearning.AI: Un recurso increíble para aprender más sobre redes neuronales y aprendizaje profundo. Cursos y material de estudio desde lo básico hasta lo avanzado.
  • Towards Data Science: Blog con artículos actualizados sobre machine learning, deep learning y retropropagación, con aplicaciones prácticas.
  • Curso de redes neuronales y deep learning en Coursera: Un curso impartido por Andrew Ng que cubre desde los fundamentos de las redes neuronales hasta técnicas avanzadas de entrenamiento.

Antonio Richaud

Soy un Data Scientist con experiencia en machine learning, deep learning y análisis financiero. Transformo grandes volúmenes de datos en insights y desarrollo soluciones que integran análisis avanzado con programación.