¿Ese frustrante dron flotante de? ¿El que aprendió a descender hacia la plataforma, pasar a través de ella y luego simplemente… quedarse debajo de ella para siempre? Sí, yo también. Pasé una tarde entera viéndolo flotar allí, acumulando recompensas negativas como un choque en cámara lenta, y ni siquiera podía enojarme porque técnicamente estaba haciendo exactamente lo que le dije que hiciera.
El problema fundamental era que mi función de recompensa sólo podía ver el estado actual, no la trayectoria. Cuando lo recompensé por estar cerca de la plataforma, no pudo notar la diferencia entre un dron que avanzaba hacia el aterrizaje y un dron que ya había atravesado la plataforma y ahora estaba explotando la estructura de recompensa desde abajo. La función de recompensa r(s') simplemente miraba dónde estaba el dron, no cómo llegó allí ni hacia dónde se dirigía. (Por cierto, esto se convertirá en un tema recurrente. La ingeniería de recompensas me persigue mientras duermo en este momento).
Pero aquí es donde las cosas se ponen interesantes. Mientras miraba mi dron flotando debajo de la plataforma por enésima vez, seguí pensando: ¿por qué estoy esperando a que termine todo el episodio antes de saber algo? REINFORCE me hizo recopilar una trayectoria completa, observar cómo se estrella el dron (u ocasionalmente aterrizar), calcular todos los retornos y luego actualizar la política. ¿Qué pasaría si pudiéramos simplemente… aprender después de cada paso? ¿Obtener información inmediata mientras el dron vuela? ¿No sería eso mucho más eficiente?
Eso es actor-crítico. Y alerta de spoiler: funciona mucho mejor de lo que esperaba. Bueno, después de arreglar tres errores importantes, reescribí mi función de recompensa dos veces, pasé dos días pensando que PyTorch estaba roto (no lo estaba, simplemente lo estaba usando mal) y finalmente entendí por qué mi factor de descuento hacía que las recompensas del terminal fueran completamente invisibles. Pero llegaremos a todo eso.
En esta publicación, lo guiaré a través de todo mi viaje implementando métodos Actor-Crítico para la tarea de aterrizaje de drones. Verá los éxitos, los frustrantes fracasos y las maratones de depuración. Esto es lo que cubrimos:
Actor-crítico básico con error TD, que me llevó a una tasa de éxito del 68% y convergió dos veces más rápido que REINFORCE. Esta parte funcionó sorprendentemente bien una vez que solucioné el error del objetivo en movimiento (más sobre esa pesadilla más adelante).
Mi intento de Estimación de Ventajas Generalizadas (GAE), que fracasó por completo. Pasé tres días enteros depurando por qué mis valores críticos se disparaban a miles, probé todas las soluciones que se me ocurrieron y, finalmente, simplemente… me di por vencido y seguí adelante. A veces es necesario saber cuándo girar. (Honestamente, todavía estoy un poco salado con este tema).
Optimización de políticas próximas (PPO), que finalmente me brindó un rendimiento sólido y estable y me enseñó por qué toda la industria de RL simplemente usa esto de forma predeterminada. Resulta que cuando OpenAI dice "ésta es la cuestión", probablemente tengan razón.
Pero lo más importante es que aprenderá sobre los tres errores críticos que casi descarrilaron todo. Estos no son pequeños errores tipo “ups, error tipográfico”. Estos son errores de "mirar fijamente las curvas de entrenamiento durante seis horas, preguntándote si fundamentalmente no entiendes bien las redes neuronales":
El problema del objetivo en movimiento que hizo que mi pérdida crítica oscilara para siempre porque no separé el objetivo TD (este me hizo cuestionar toda mi comprensión de la retropropagación). El valor gamma era demasiado bajo e hizo que las recompensas de aterrizaje valieran literalmente 0.00000006 después del descuento, por lo que mi agente aprendió a fallar de inmediato porque ¿para qué molestarse en intentarlo? (Imprimí los valores descontados reales y me reí, luego lloré) Las recompensas explotan donde mi dron aprendió a pasar la plataforma a máxima velocidad, recolectar recompensas de distancia en el camino y estrellarse lejos porque de alguna manera eso era mejor que aterrizar. Esto me enseñó que el 90% de RL realmente es ingeniería de recompensas, y el otro 90% es depurar por qué su ingeniería de recompensas no funcionó. (Sí, sé que es el 180%. Esa es la cantidad de trabajo que supone).
Vamos a sumergirnos. Toma un poco de café, lo vas a necesitar. Todo el código se puede encontrar en mi repositorio en mi github.
¿Qué es el actor-crítico?
REINFORCE tuvo un problema fundamental: tuvimos que esperar. Espere a que el dron se estrelle. Espere a que termine el episodio. Espere a calcular el rendimiento completo. Entonces, y sólo entonces, podremos actualizar la política. Una señal de aprendizaje por episodio. Para una trayectoria de 150 pasos, esa es una actualización después de ver cómo se desarrollan 150 acciones.
Ejecuté REINFORCE durante 1200 iteraciones (6 horas en mi máquina) para alcanzar una tasa de éxito del 55 %. Y todo el tiempo seguí pensando: esto parece un desperdicio. ¿Por qué no puedo aprender durante el episodio?
Actor-Critic soluciona este problema con una idea simple: entrenar una segunda red neuronal (la "crítica") para estimar los retornos futuros para cualquier estado. Luego utilice esas estimaciones para actualizar la política después de cada paso. No más esperas a que terminen los episodios. Solo aprendizaje continuo mientras el dron vuela.
¿El resultado? Tasa de éxito del 68% en 600 iteraciones (3 horas). La mitad del tiempo. Mejor rendimiento. Mismo hardware.
Cómo funciona: dos redes colaboran en tiempo real.
El Actor (π(a|s)): Misma red de políticas de REINFORCE. Toma el estado actual y genera probabilidades de acción. Esta es la red que realmente controla el dron.
El Crítico (V(s)): Nueva red. Toma el estado actual y estima "¿qué tan bueno es este estado?" Genera un valor único que representa las recompensas futuras esperadas. No está vinculado a ninguna acción específica, solo evalúa estados.
Aquí está la parte inteligente: el crítico proporciona retroalimentación inmediata. El actor realiza una acción, el entorno se actualiza y el crítico evalúa inmediatamente si eso nos llevó a un estado mejor o peor. El actor aprende de esta señal y se adapta. El crítico aprende simultáneamente a hacer mejores predicciones. Ambas redes mejoran juntas a medida que se desarrollan los episodios.
En código, se ven así:
class DroneGamerBoi(nn.Module): """El actor: genera probabilidades de acción""" def __init__(self, state_dim=15): super().__init__() self.network = nn.Sequential( nn.Linear(state_dim, 128), nn.LayerNorm(128), nn.ReLU(), nn.Linear(128, 128), nn.LayerNorm(128), nn.ReLU(), nn.Linear(128, 64), nn.LayerNorm(64), nn.ReLU(), nn.Linear(64, 3), # Tres propulsores independientes nn.Sigmoid() ) def forward(self, state): return self.network(state) # Salida: probabilidades para cada clase de propulsor DroneTeacherBoi(nn.Module): """El crítico: genera una estimación del valor del estado""" def __init__(self, state_dim=15): super().__init__() self.network = nn.Sequential( nn.Linear(state_dim, 128), nn.LayerNorm(128), nn.ReLU(), nn.Linear(128, 128), nn.LayerNorm(128), nn.ReLU(), nn.Linear(128, 64), nn.LayerNorm(64), nn.ReLU(), nn.Linear(64, 1) # Valor único: V(s) ) def forward(self, state): return self.network(state) # Salida: estimación de valor escalar
Observe que la red crítica es casi idéntica al actor, excepto que la capa final genera un valor único (¿qué tan bueno es este estado?) en lugar de probabilidades de acción.
El truco del arranque
Bien, aquí es donde se vuelve inteligente. En REINFORCE, necesitábamos la devolución completa para actualizar la política:
[ G_t = r_t + gamma r_{t+1} + gamma^2 r_{t+2} + cdots + gamma^{Tt} r_T ]
Tuvimos que esperar hasta que terminara el episodio para conocer todas las recompensas. Pero ¿y si… no lo hiciéramos? ¿Qué pasaría si simplemente estimáramos el futuro utilizando nuestra red de críticos?
En lugar de calcular el rendimiento real, lo estimamos:
[ G_t aprox r_t + gamma V(s_{t+1}) ]
Esto se llama arranque. El crítico “arranca” su propia estimación de valor para aproximarse al rendimiento total. Usamos su predicción de "¿qué tan bueno será el próximo estado?" para estimar el retorno ahora mismo.
¿Por qué ayuda esto?
Menor varianza. No estamos esperando la secuencia aleatoria real de recompensas futuras. Estamos utilizando una estimación basada en lo que hemos aprendido sobre los estados en general. Esto es más ruidoso que la verdad básica (¡el crítico podría estar equivocado!), pero es menos ruidoso que el resultado de cualquier episodio individual.
Aprendizaje en línea. Podemos actualizar inmediatamente en cada paso. No es necesario terminar el episodio primero. Tan pronto como el dron realiza una acción, conocemos la recompensa inmediata y podemos estimar lo que viene después, para poder aprender.
Mejor eficiencia de la muestra. En REINFORCE con 6 juegos paralelos, cada dron aprende una vez al completar el episodio. En Actor-Critic con 6 juegos paralelos, cada dron aprende en cada paso (unos 150 pasos por episodio). ¡Eso es 150 veces más señales de aprendizaje por episodio!
Por supuesto, hay una compensación: introducimos sesgos. Si nuestro crítico se equivoca (y lo estará, especialmente al principio del entrenamiento), nuestro agente aprende de estimaciones incorrectas. Pero el crítico no necesita ser perfecto. Sólo tiene que ser menos ruidoso que el resultado de un solo episodio. A medida que el crítico mejora gradualmente, el actor aprende de mejores comentarios. Se impulsan mutuamente hacia arriba. En la práctica, la reducción de la varianza es tan poderosa que vale la pena aceptar el pequeño sesgo.
Error TD: la nueva ventaja
Ahora debemos responder: ¿cuánto mejor o peor fue esta acción de lo esperado?
En REINFORCE, teníamos la ventaja: retorno real menos línea base. La línea de base fue un promedio global. Pero podemos hacerlo mucho mejor. En lugar de una línea de base global, utilizamos la estimación estatal específica del crítico.
El error TD (Diferencia Temporal) es nuestra nueva ventaja:
[ delta_t = r_t + gamma V(s_{t+1}) – V(s_t) ]
En términos sencillos:
(r_t + gamma V(s_{t+1})) = objetivo TD. La recompensa inmediata más nuestra estimación del valor del siguiente estado. (V(s_t)) = Nuestra predicción para el estado actual. (delta_t) = La diferencia. ¿Lo hicimos mejor o peor de lo esperado?
Si (delta_t > 0), lo hicimos mejor de lo esperado → reforzamos esa acción.
Si (delta_t < 0), lo hicimos peor de lo esperado → disminuyemos la probabilidad de esa acción.
Si (delta_t approx 0), acertamos → la acción fue promedio.
Esto es mucho más informativo que la base de referencia global de REINFORCE. La señal ahora es específica de cada estado. El dron en un giro complicado podría obtener una recompensa de -10 y eso en realidad es bastante bueno (generalmente obtiene -50 allí). Pero si flota pacíficamente sobre la plataforma, -10 es terrible. El crítico sabe la diferencia. El error TD capta eso.
Así es como esto fluye a través del ciclo de entrenamiento (simplificado):
# 1. Realizar una acción en cada juego paralelo acción = actor(estado) siguiente_estado, recompensa = env.step(acción) # 2. Obtener estimaciones de valor valor_actual = crítico(estado) valor_siguiente = crítico(siguiente_estado) # 3. Calcular el error TD (nuestra ventaja) td_error = recompensa + gamma * valor_siguiente – valor_actual # 4. Actualizar el crítico: debería haber predicho mejor # El crítico quiere minimizar el error de predicción, por eso usamos el error al cuadrado. # El gradiente luego acerca las predicciones de los críticos a los rendimientos reales. critic_loss = td_error ** 2 critic_loss.backward() critic_optimizer.step() # 5. Actualizar el actor: reforzar o desalentar según el error TD # (mismo gradiente de política que REINFORCE, pero con error TD en lugar de retornos) actor_loss = -log_prob(action) * td_error actor_loss.backward() actor_optimizer.step()
Observe que estamos actualizando ambas redes por paso, no por episodio. Esa es la magia del aprendizaje en línea.
Una comparación más para dejar esto muy claro:
El segundo es más preciso, más informativo y llega mucho antes.
Es por eso que Actor-Critic convergió en 600 iteraciones en mi máquina, mientras que REINFORCE necesitaba 1200. La misma función de recompensa, el mismo entorno, el mismo dron. ¿Pero recibir comentarios después de cada paso en lugar de cada 150 pasos? Esa es una ventaja de información de 150 veces por iteración.
Los tres bichos: una odisea de depuración
Muy bien, estoy a punto de contarles acerca de tres errores que casi me arruinan. No se ha roto el mensaje "Ups, error uno por uno". Me refiero al tipo de falla en la que miras las curvas de entrenamiento durante seis horas, te preguntas seriamente si entiendes la retropropagación, depuras tu código cinco veces y luego pasas otras dos horas leyendo artículos académicos para convencerte de que no estás loco.
Estos errores son lo suficientemente sutiles como para que incluso los profesionales experimentados de RL deban tener cuidado. La buena noticia: una vez que los entiendes, se vuelven obvios. La mala noticia: primero hay que entenderlos, y yo aprendí por las malas.
° 1: el problema del objetivo en movimiento
La configuración
Implementé Actor-Critic exactamente como parecía lógico. Tengo dos redes. Se predicen acciones, se predicen valores. Sencillo, ¿verdad? Escribí el cálculo del error TD:
# Calcular valores estimados de valores = critic(batch_data['states']) next_values = critic(batch_data['next_states']) # Calcular objetivos y errores de TD td_targets = recompensas + gamma * next_values * (1 – dones) td_errors = td_targets – value # Pérdida de críticos critic_loss = (td_errors ** 2).mean() # Pasar hacia atrás critic_loss.hacia atrás()
Esto me pareció completamente razonable. Calculamos lo que esperábamos (valores), calculamos lo que deberíamos haber obtenido (td_targets), medimos el error y actualizamos. Material de aprendizaje supervisado estándar.
El síntoma: nada funciona
Entrené durante 200 iteraciones y la pérdida crítica fue… estar sentado alrededor de 500-1000 y sin moverme. Ni disminuye ni aumenta, simplemente oscila salvajemente como una onda sinusoidal. Revisé mi función de recompensa. Se veía bien. Revisé la red de críticos. Arquitectura estándar, nada raro. Verifiqué los valores de error de TD. Estaban oscilando entre -50 y +50, lo que parecía razonable dada la escala de recompensas.
Pero la pérdida se negó a converger.
Pasé dos días en esto. Agregué la deserción, pensando que tal vez estaba sobreadaptado. (Problema incorrecto, no ayudó). Reduje la tasa de aprendizaje de 1e-3 a 1e-4, pensando que tal vez el optimizador se estaba excediendo. (No, simplemente aprendí más lento mientras oscilaba). Verifiqué si mi entorno devolvía NaN. (No lo fue). Incluso me pregunté si la autograduación de PyTorch tenía un error. (Spoiler: PyTorch está bien, yo era el error).
El gran avance
Estaba leyendo el capítulo Actor-Crítico de Sutton & Barto (nuevamente, por quinta vez) cuando algo me llamó la atención. El pseudocódigo tenía una línea sobre "calcular la siguiente estimación del valor". Y pensé: espera, cuando calculo next_values = critic(next_states), ¿qué sucede con esos gradientes durante la backprop?
Y entonces mi cerebro hizo clic. Oh, no. El objetivo se mueve a medida que intentamos optimizar hacia él. Esto se llama el problema del objetivo en movimiento.
Por qué esto lo rompe todo
Cuando calculamos next_values = critic(next_states) sin separarnos, el autogrado de PyTorch fluye gradientes a través de AMBOS V(s) y V(s'). Eso significa que estamos actualizando la predicción Y el objetivo simultáneamente: el crítico persigue un objetivo que se mueve cada vez que se actualiza. El gradiente se convierte en:
[ frac{partial L}{partial theta} = 2 cdot (r + gamma V(s') – V(s)) cdot left( gamma frac{partial V(s')}{partial theta} – frac{partial V(s)}{partial theta} right) ]
Ese término γ · ∂V(s')/∂θ es el problema: le estamos diciendo al crítico que cambie el objetivo, no solo la predicción. La pérdida oscila para siempre.
La solución (finalmente)
Necesitaba tratar el objetivo TD como una constante fija. En PyTorch, eso significa separar los degradados:
# ✅ Valores CORRECTOS = critic(batch_data['states']) con torch.no_grad(): # LÍNEA CRÍTICA next_values = critic(batch_data['next_states']) td_targets = recompensas + gamma * next_values * (1 – dones) td_errors = td_targets – valores critic_loss = (td_errors ** 2).mean() critic_loss.hacia atrás()
El administrador de contexto torch.no_grad() dice: "Calcule los siguientes valores, pero no recuerde cómo los calculó. Para fines de gradiente, trate esto como una constante". Ahora durante el pase hacia atrás:
[ frac{partial L}{partial theta} = 2 cdot (r + gamma V(s') – V(s)) cdot left( – frac{partial V(s)}{partial theta} right) ]
¡Ese término problemático ya no existe! Ahora solo estamos actualizando V(s), la predicción, para que coincida con el objetivo fijo r + γV(s'). Esto es exactamente lo que queremos.
El objetivo de TD se convierte en lo que debería ser: una etiqueta fija, como la verdad fundamental en el aprendizaje supervisado. Ya no intentamos alcanzar un objetivo en movimiento. Sólo estamos tratando de predecir algo estable.
Cambié exactamente una línea. La pérdida crítica pasó de oscilar caóticamente alrededor de 500-1000 a disminuir suavemente: 500 → 250 → 100 → 35 → 8 en 200 iteraciones. Este error es insidioso porque el código parece completamente razonable, pero siempre separe sus objetivos TD.
Error nº 2: Gamma demasiado baja (recompensas invisibles)
La configuración
Muy bien, el error número 1 fue sutil. En retrospectiva, este error es vergonzosamente obvio. ¿Pero sabes qué? A veces, los errores más obvios son los más fáciles de pasar por alto porque no esperas que el problema sea tan simple.
Arreglé el error del objetivo en movimiento y de repente la pérdida de críticos comenzó a converger. ¡Fantástico! Allí me sentí como un verdadero ingeniero por un momento. Pero luego ejecuté el agente para una iteración de entrenamiento completa y… nada. Absolutamente nada mejoró. El dron realizaría algunos movimientos aleatorios y luego inmediatamente se estrellaría contra el suelo o saldría volando de la pantalla. Sin aprendizaje. Ninguna mejora. No hay señales de vida.
En realidad, espera. El crítico estaba aprendiendo. La pérdida iba disminuyendo. Pero el dron no mejoraba. Eso parecía al revés. ¿Por qué el crítico aprendería a predecir valores si el agente no estuviera aprendiendo nada de esos valores?
El descubrimiento
Imprimí los objetivos TD y todos eran negativos, entre -5 y -30. No hay señales de la recompensa de aterrizaje de +500. Luego hice los cálculos: con episodios de 150 pasos y gamma=0,90:
[ 500 veces 0,90^{150} aprox 0,00000006 ]
La recompensa por el aterrizaje había quedado descartada para el olvido. El agente aprendió a estrellarse inmediatamente porque intentar aterrizar era literalmente invisible para la función de valor.
El factor de descuento γ controla el horizonte efectivo (≈ 1/(1-γ)). Con gamma = 0,90, son solo 10 pasos, demasiado cortos para episodios de 100 a 300 pasos.
La solución: cambie la gamma de 0,90 a 0,99.
El impacto
Cambié la gamma de 0,90 a 0,99. Misma red, mismas recompensas, igual todo lo demás.
Resultado: Iteración 5, el dron avanzó hacia la plataforma. Iteración 50, desaceleró al acercarse. Iteración 100, primer aterrizaje. En la iteración 600, tasa de éxito del 68%.
Un cambio de parámetro, comportamiento del agente completamente diferente. La recompensa terminal pasó de invisible a cristalina. Verifique siempre: el horizonte efectivo (1/(1-γ)) debe coincidir con la duración de su episodio.
Error nº 3: Explotaciones de recompensa (la carrera armamentista)
En este punto, solucioné tanto el problema del objetivo en movimiento como el problema de gamma. ¡Mi agente realmente estaba aprendiendo! Se acercó a la plataforma, disminuyó la velocidad de vez en cuando e incluso aterrizó en ocasiones. Estaba realmente emocionado. Luego comencé a observar los fallos con más atención y sucedió algo extraño.
Después de corregir los errores 1 y 2, el agente aprendió dos nuevos exploits:
Zoom-pasado: Acelera hacia la plataforma a máxima velocidad, sobrepasa, choca muy lejos. Recompensa neta: -140 (recompensas de aproximación +60, penalización por colisión -200). Mejor que estrellarse inmediatamente (-300), pero no aterrizar.
Flotando: acérquese a la plataforma y vibre en el lugar con pequeños movimientos (velocidad 0,01-0,02) para acumular recompensas de aproximación indefinidamente y evitar penalizaciones por colisión.
Por qué sucede esto: el problema fundamental
Esto es lo que me molestó: mi función de recompensa solo podía ver el estado actual, no la trayectoria.
La función de recompensa es r(s', a): dado el siguiente estado y la acción que acabo de realizar, calcula mi recompensa. No tiene memoria. No puede distinguir la diferencia entre:
Un dron que hace un progreso genuino hacia el aterrizaje: acercándose desde arriba con un descenso controlado y decidido. Un dron que cultiva la estructura de recompensa: flotando con micromovimientos sin sentido.
Ambos escenarios podrían tener:
distancia_a_plataforma < 0,3 (cerca del objetivo) velocidad > 0 (técnicamente en movimiento) velocidad_alineación > 0 (apuntado en la dirección correcta)
El agente no es tonto. Está haciendo exactamente lo que le dije: maximizar las recompensas escalares que le estoy dando. El problema es que las recompensas en realidad no codifican el aterrizaje, sino la proximidad y el movimiento. Y la proximidad sin aterrizaje es aprovechable.
Esta es la idea central del hacking de recompensas: el agente encontrará lagunas en su especificación de recompensa, no porque sea inteligente, sino porque usted no especificó la tarea.
La solución: recompensar las transiciones de estado, no las instantáneas
La solución: recompensa basada en las transiciones de estado r(s, s'), no solo en el estado actual r(s'). En lugar de preguntar “¿La distancia es < 0,3?”, pregunte “¿Nos acercamos (distance_delta > 0) Y nos movimos lo suficientemente rápido como para sentirlo (velocidad ≥ 0,15)?”
def calc_reward(estado: DroneState, prev_state: DroneState = Ninguno): si prev_state no es Ninguno: distancia_delta = prev_state.distance_to_platform – state.distance_to_platform velocidad = estado.velocidad velocidad_hacia_plataforma = calcular_alineación(estado) # similitud de coseno MIN_MEANINGFUL_SPEED = 0.15 si velocidad >= MIN_MEANINGFUL_SPEED y velocidad_hacia_plataforma > 0.1: speed_multiplier = 1.0 + velocidad * 2.0 recompensas['approach'] = distancia_delta * 15.0 * speed_multiplier elif velocidad < 0.05: recompensas['hovering_penalty'] = -1.0
Cambios clave: (1) Recompensa distancia_delta (progreso), no proximidad, (2) Bloques de umbral MIN_SPEED flotando, (3) El multiplicador de velocidad fomenta una acción decisiva.
Para usar esto, realice un seguimiento de prev_state en su ciclo de entrenamiento y páselo a calc_reward(next_state, prev_state).
El 90% de RL es ingeniería de recompensas. El otro 90% está depurando su ingeniería de recompensas. Las recompensas son una especificación del objetivo y el agente encontrará todas las lagunas.
Resultados básicos actor-crítico
Debo admitir que cuando solucioné el tercer error (esa función de recompensa ponderada por velocidad-magnitud) y lancé un nuevo entrenamiento con las tres correcciones implementadas, me sentí escéptico. Había pasado tanto tiempo persiguiendo mi cola con estos algoritmos que casi esperaba que Actor-Crítico alcanzara algún nuevo modo de falla creativa que no había anticipado. Pero sucedió algo sorprendente: simplemente… funcionó.
Y quiero decir que realmente funcionó. De hecho, mejor que REINFORCE: notablemente mejor. Después de cientos de horas depurando el hackeo de recompensas de REINFORCE, esperaba que Actor-Critic al menos igualara su desempeño. En cambio, lo pasó volando.
Por qué esto supera a REINFORCE (y por qué es importante):
Las actualizaciones en línea de Actor-Critic crean un circuito de retroalimentación que REINFORCE no puede igualar. A cada paso, el crítico le susurra al oído al actor: “Oye, ese estado es bueno” o “Ese estado es malo”. No es una línea de base global como la que utiliza REINFORCE. Es una evaluación específica de cada estado que mejora cada vez más a medida que el crítico aprende.
Por eso la convergencia es 2 veces más rápida. Por eso el rendimiento final es un 13% mejor. Por eso las curvas de aprendizaje son tan claras.
Y todo dependía de tres cosas: separar el objetivo de TD, usar el factor de descuento correcto y rastrear las transiciones de estado en la función de recompensa. No se necesitan nuevos trucos de algoritmos. Simplemente implementación correcta.
Lo que sigue: ir más allá del actor-crítico
Con Actor-Critic funcionando bien, es posible que hayas notado que la política es aterrizar constantemente el dron en el lado izquierdo de la plataforma y que los movimientos también son ligeramente nerviosos. Para resolver esto, estoy trabajando en la conversión de la Optimización de políticas próximas (PPO), que se supone que ayudará con esto al "hacer que el proceso de aprendizaje sea más estable". Lo bueno es que los investigadores de OpenAI han utilizado este método para entrenar sus modelos emblemáticos "GPT".
Referencias
Artículos fundamentales de RL
Sutton, RS y Barto, AG (2018). Aprendizaje por refuerzo: una introducción (2ª ed.). Prensa del MIT.
Métodos actor-crítico
Konda, VR y Tsitsiklis, JN (2000). "Algoritmos actor-crítico". Revista SIAM de Control y Optimización, 42(4), 1143-1166. Fundamentos teóricos del Actor-Crítico con pruebas de convergencia Mnih, V., Badia, AP, Mirza, M., et al. (2016). "Métodos asincrónicos para el aprendizaje por refuerzo profundo". Congreso Internacional sobre Aprendizaje Automático.
Aprendizaje de diferencias temporales
Sutton, RS (1988). "Aprender a predecir mediante los métodos de diferencias temporales". Aprendizaje automático, 3(1), 9-44. Documento de aprendizaje TD original
Publicaciones anteriores en esta serie
Jumle, V. (2025). "Aprendizaje por refuerzo profundo: 0 a 100 – Gradientes de políticas (REINFORCE)".
Repositorio e implementación de código
Jumle, V. (2025). "Aprendizaje por refuerzo 101: aterrizaje de drones de entrega".
Todas las imágenes de este artículo son generadas por IA (usando Gemini o Sora), hechas personalmente por mí o capturas de pantalla y gráficos que hice, a menos que se especifique lo contrario.