En los experimentos de aprendizaje por refuerzo, el seguimiento preciso de métricas durante el entrenamiento es fundamental para evaluar y mejorar el rendimiento de los algoritmos. El componente RLlib del framework Ray incluye una utilidad llamada MetricsLogger que centraliza la captura, agregación y reporte de estadísticas a lo largo de todo el ciclo de entrenamiento.
Filosofía de diseño
El equipo de RLlib concibió MetricsLogger para resolver tres desafíos críticos en la observabilidad de sistemas de aprendizaje por refuerzo:
- Interfaz uniforme: todos los módulos del sistema registran datos mediante el mismo conjunto de métodos
- Agregación jerárquica: las métricas fluyen desde los nodos hoja hasta los componentes superiores de forma estructurada
- Personalización granular: cada métrica puede definir su propio modo de reducción y ventana temporal
Gracias a este enfoque, elementos como recolectores de experiencias, optimizadores de políticas y orquestadores principales comparten un mecanismo común de telemetría.
Estructura jerárquica del sistema
La arquitectura de monitoreo funciona de manera descentralizada y escalonada:
Orquestador (instancia propia de MetricsLogger)
├── Recolector de experiencias A
├── Recolector de experiencias B
├── Optimizador de políticas 1
└── Optimizador de políticas 2
Cada nodo mantiene su propio registrador local. Al finalizar una unidad de trabajo —por ejemplo, un lote de muestras o una pasada de gradientes— el nodo consolida sus estadísticas y las envía hacia arriba. El padre recibe estos paquetes y los fusiona dentro de su propio repositorio.
Capacidades principales
Registro de valores escalares
El método esencial permite almacenar números con distintas estrategias de consolidación:
registrador.registrar("perdida", 0.023, modo_reduccion="media", ventana=3)
Los modos de reducción disponibles son:
"media": promedio aritmético (predeterminado)"minimo": valor más bajo registrado"maximo": valor más alto registrado"suma": acumulado totalNone: conserva la lista completa sin reducir
Control mediante ventanas deslizantes
El parámetro de ventana limita cuántos elementos recientes participan en la reducción:
# Configuración con ventana de tamaño 2
registrador.registrar("perdida", 0.012, ventana=2)
registrador.registrar("perdida", 0.028)
registrador.registrar("perdida", 0.041)
registrador.consultar("perdida") # Devuelve 0.0345 (media de 0.028 y 0.041)
Registros estructurados y anidados
Para métricas con múltiples sub-valores se puede pasar un diccionario completo:
resultados_jugadores = {"participante_a": 92.5, "participante_b": 110.3}
registrador.registrar_diccionario(resultados_jugadores, clave="puntuaciones_promedio")
Captura de datos complejos
Cuando se requiere almacenar imágenes, fotogramas u objetos no numéricos, se desactiva la reducción y se marca para limpieaz automática:
registrador.registrar("fotogramas", entorno.renderizar(), modo_reduccion=None, limpiar_al_reducir=True)
Medición de tiempos de ejecución
Existe un gestor de contexto para cronometrar bloques de código sin código repetitivo:
with registrador.cronometrar("paso_entrenamiento"):
# Lógica de actualización del modelo
optimizador.step()
Contadores acumulativos
Para llevar cuentas progresivas como pasos totales o episodios completados:
registrador.registrar("pasos_totales", 128, modo_reduccion="suma")
Cálculo automático de throughput
Activando el flag correspondiente, el registrador deriva una métrica de velocidad automáticamente:
registrador.registrar("pasos_totales", 1000, modo_reduccion="suma", calcular_throughput=True)
# Se crea automáticamente: pasos_totales_throughput
Patrones de integración
En un recolector de experiencias personalizado
class RecolectorPersonalizado(EnvRunner):
def __init__(self, configuracion):
super().__init__(configuracion)
self.registrador_local = MetricsLogger()
def sample(self):
# Registrar métricas durante la recolección
self.registrador_local.registrar("recompensa_episodio", recompensa)
self.registrador_local.registrar("duracion_episodio", pasos)
return self.registrador_local.reduce()
En una función de pérdida personalizada
def funcion_perdida_personalizada(politica, modelo, clase_dist, lote_entrenamiento):
registrador = politica.metrics
# Cálculo de la pérdida
valor_perdida = calcular_perdida(modelo, lote_entrenamiento)
# Registrar estadísticas relevantes
registrador.registrar("perdida_total", valor_perdida)
registrador.registrar("norma_gradientes", obtener_norma_gradientes(modelo))
return valor_perdida
Recomendaciones de uso
- Delegar la reducción: el framework invoca
reduce()automáticamente en los puntos apropiados del ciclo de vida; invocarlo manualmente puede provocar inconsistencias en los datos consolidados - Consultar sin destruir: para inspeccionar el valor agregado actual sin alterar el estado interno, utilizar
peek()en lugar dereduce() - Gestionar memoria en datos sin reducir: cuando se utiliza
modo_reduccion=None, establecer siemprelimpiar_al_reducir=Truepara evitar acumulación indefinida en memoria - Seleccionar el modo de reducción adecuado: la media es apropiada para pérdidas, la suma para contadores, y el máximo para métricas de mejor-valor
- Suavizar métricas ruidosas: configurar ventanas pequeñas para señales con alta varianza, facilitando la interpretación de tendencias