Volver a proyectos
Aprendizaje por refuerzo para sistemas de control de vuelo

Aprendizaje por refuerzo para sistemas de control de vuelo

Este repositorio implementa aprendizaje profundo por refuerzo para entrenar sistemas de control de vuelo (FCS) para una aeronave de ala fija. El proyecto incluye un simulador, una aplicación web para probar el simulador mediante entradas de teclado y un entorno completo para entrenar agentes de RL. Este entorno se utiliza para entrenar agentes mediante el algoritmo Proximal Policy Optimization (PPO) de la biblioteca Stable Baselines3 para seguir trayectorias de vuelo específicas dadas por puntos de paso.

Requisitos e instalación

Clona el repositorio:

git clone <repository-url>
cd rl-fcs

Para configurar el entorno, necesitas instalar los paquetes de Python necesarios. Puedes hacerlo mediante pip:

pip install -r requirements.txt

Para la integración con Weights & Biases (WandB), necesitas iniciar sesión en tu cuenta de WandB o descomentar las líneas correspondientes en el script de entrenamiento para el modo sin conexión. Si utilizas el modo en línea, también necesitas crear un nuevo proyecto en WandB llamado rl-fcs y establecer la entidad con tu nombre de usuario o de equipo en training/waypoint/train_waypoint_control.py.

wandb login

Para utilizar la aplicación web, accede al directorio web-app e instala Node si aún no lo has hecho. Después, instala los paquetes de npm necesarios siguiendo las instrucciones del archivo web-app/README.md. Estos son los pasos principales:

cd simulator_webapp
npm install

Para ejecutar la aplicación web, utiliza:

npm run dev

Simulador

Para disponer de un modelo de dinámica de vuelo (FDM) de la aeronave e implementar el entorno, se desarrolló un simulador sencillo en Python utilizando las ecuaciones del movimiento linealizadas a partir de una condición de referencia e integrándolas mediante un método de Runge-Kutta de cuarto orden. Los pasos completos para deducir las ecuaciones se encuentran en el capítulo 14 del libro [1] y la integración de Runge-Kutta en la página 905 de [2].

Las ecuaciones del movimiento de Euler son el resultado de aplicar el teorema de la cantidad de movimiento y el teorema del momento cinético a un sólido rígido en vuelo. A continuación, utilizando el teorema de Coriolis para expresar la derivada temporal de un sistema de referencia inercial en un sistema de ejes cuerpo, suponiendo que el avión es simétrico, de modo que solo Ix,Iy,Iz,JxzI_{x}, I_{y}, I_{z}, J_{xz} son distintos de cero, y separando finalmente las fuerzas y los momentos en sus componentes de empuje, aerodinámica y peso, obtenemos las siguientes ecuaciones en ejes cuerpo:

F⃗=d(mV⃗)dt⇒{−mgsin⁡θ+FTx+FAx=m(u˙−rv+qw)mgcos⁡θsin⁡ϕ+FTy+FAy=m(v˙+ru−pw)mgcos⁡θcos⁡ϕ+FTz+FAz=m(w˙−qu+pv)\vec{F} = \frac{\mathrm{d}(m \vec{V})}{\mathrm{d} t} \Rightarrow \begin{cases} -m g \sin \theta + F_{T x} + F_{A x} = m(\dot{u} - r v + q w) \\ m g \cos \theta \sin \phi + F_{T y} + F_{A y} = m(\dot{v} + r u - p w) \\ m g \cos \theta \cos \phi + F_{T z} + F_{A z} = m(\dot{w} - q u + p v) \end{cases} G⃗=d(Iω⃗)dt⇒{LT+LA=Ixp˙−Jxzr˙+(Iz−Iy)qr−JxzpqMT+MA=Iyq˙−(Iz−Ix)pr+Jxz(p2−r2)NT+NA=Izr˙−Jxzp˙−(Ix−Iy)pq+Jxzqr\vec{G} = \frac{\mathrm{d}(I \vec{\omega})}{\mathrm{d} t} \Rightarrow \begin{cases} L_T + L_A = I_x \dot{p} - J_{x z} \dot{r} + (I_z - I_y) q r - J_{x z} p q \\ M_T + M_A = I_y \dot{q} - (I_z - I_x) p r + J_{x z} (p^2 - r^2) \\ N_T + N_A = I_z \dot{r} - J_{x z} \dot{p} - (I_x - I_y) p q + J_{x z} q r \end{cases}

Además, las ecuaciones cinemáticas angulares se obtienen proyectando las velocidades angulares de Euler de cada eje de rotación de Tait-Bryan sobre el sistema de ejes cuerpo:

p=ϕ˙−ψ˙sin⁡θq=θ˙cos⁡ϕ+ψ˙cos⁡θsin⁡ϕr=−θ˙sin⁡ϕ+ψ˙cos⁡θcos⁡ϕ\begin{array}{l} p = \dot{\phi} - \dot{\psi} \sin \theta \\ q = \dot{\theta} \cos \phi + \dot{\psi} \cos \theta \sin \phi \\ r = -\dot{\theta} \sin \phi + \dot{\psi} \cos \theta \cos \phi \end{array}

Finalmente, las ecuaciones cinemáticas de traslación se obtienen proyectando las velocidades del sistema de ejes cuerpo sobre el sistema de referencia inercial:

{x˙e=(cos⁡θcos⁡ψ)u+(sin⁡ϕsin⁡θcos⁡ψ−cos⁡ϕsin⁡ψ)v+(cos⁡ϕsin⁡θcos⁡ψ+sin⁡ϕsin⁡ψ)wy˙e=(cos⁡θsin⁡ψ)u+(sin⁡ϕsin⁡θsin⁡ψ+cos⁡ϕcos⁡ψ)v+(cos⁡ϕsin⁡θsin⁡ψ−sin⁡ϕcos⁡ψ)wz˙e=−sin⁡θu+sin⁡ϕcos⁡θv+cos⁡ϕcos⁡θw\begin{cases} \dot{x}_e &= (\cos \theta \cos \psi) u+(\sin \phi \sin \theta \cos \psi-\cos \phi \sin \psi) v+(\cos \phi \sin \theta \cos \psi+\sin \phi \sin \psi) w \\ \dot{y}_e &= (\cos \theta \sin \psi) u+(\sin \phi \sin \theta \sin \psi+\cos \phi \cos \psi) v+(\cos \phi \sin \theta \sin \psi-\sin \phi \cos \psi) w \\ \dot{z}_e &= -\sin \theta u+\sin \phi \cos \theta v+\cos \phi \cos \theta w \end{cases}

Estas ecuaciones se linealizan después alrededor de una condición de referencia que consiste en un vuelo estacionario, rectilíneo, simétrico y con las alas niveladas, suponiendo ejes de estabilidad (αs=0\alpha_s=0) y ψs=0\psi_s=0:

u˙s=v˙s=w˙s=0ps=qs=rs=0αs=βs=0ϕs=θs=ψs=0\begin{aligned} \dot{u}_s=\dot{v}_s=\dot{w}_s=0 \\ p_s=q_s=r_s=0 \\ \alpha_s=\beta_s=0 \\ \phi_s= \theta_s = \psi_s=0 \\ \end{aligned}

A partir de esta condición de referencia, se aplica una perturbación a cada variable de estado, por ejemplo u=us+Δuu=u_s+\Delta u, y, tras despreciar los términos de segundo orden, se obtienen las ecuaciones linealizadas del movimiento:

{−mgcos⁡θsΔθ+ΔFTx+ΔFAx=mΔu˙mgcos⁡θsΔϕ+ΔFTy+ΔFAy=m(Δv˙+usΔr)−mgsin⁡θsΔθ+ΔFTz+ΔFAz=m(Δw˙−usΔq)ΔLT+ΔLA=IxΔp˙−JxzΔr˙ΔMT+ΔMA=IyΔq˙ΔNT+ΔNA=−JxzΔp˙+IzΔr˙\begin{cases} -m g \cos \theta_s \Delta \theta+\Delta F_{T x}+\Delta F_{A x} &=m \Delta \dot{u} \\ m g \cos \theta_s \Delta \phi+\Delta F_{T y}+\Delta F_{A y} &=m\left(\Delta \dot{v}+u_s \Delta r\right) \\ -m g \sin \theta_s \Delta \theta+\Delta F_{T z}+\Delta F_{A z} &=m\left(\Delta \dot{w}-u_s \Delta q\right) \\ \Delta L_T+\Delta L_A &=I_x \Delta \dot{p}-J_{x z} \Delta \dot{r} \\ \Delta M_T+\Delta M_A &=I_y \Delta \dot{q} \\ \Delta N_T+\Delta N_A &=-J_{x z} \Delta \dot{p}+I_z \Delta \dot{r} \end{cases} {Δϕ˙=Δp+tan⁡θsΔrΔθ˙=ΔqΔψ˙=sec⁡θsΔr\begin{cases} \Delta \dot{\phi} &= \Delta p+\tan \theta_s \Delta r \\ \Delta \dot{\theta} &= \Delta q \\ \Delta \dot{\psi} &= \sec \theta_s \Delta r \end{cases} {Δx˙e=cos⁡θsΔu+sin⁡θsΔw−sin⁡θsusΔθΔy˙e=Δv+cos⁡θsusΔψΔz˙e=−sin⁡θsΔu+cos⁡θsΔw−cos⁡θsusΔθ\begin{cases} \Delta \dot{x}_e &= \cos \theta_s \Delta u+\sin \theta_s \Delta w-\sin \theta_s u_s \Delta \theta \\ \Delta \dot{y}_e &= \Delta v+\cos \theta_s u_s \Delta \psi \\ \Delta \dot{z}_e &= -\sin \theta_s \Delta u+\cos \theta_s \Delta w-\cos \theta_s u_s \Delta \theta \end{cases}

El siguiente paso tras obtener las ecuaciones linealizadas del movimiento es expresar las fuerzas y los momentos aerodinámicos en función de las variables de estado y sus derivadas. Este proceso se conoce como método de Bryan y consiste en agrupar las perturbaciones de las fuerzas y los momentos aerodinámicos y de empuje y expresarlas en términos de derivadas de estabilidad, que son los coeficientes que relacionan el cambio en las fuerzas y los momentos con un cambio en cada variable de estado o deflexión de las superficies de control. Tras algunas simplificaciones e hipótesis, cada fuerza y momento se puede expresar de la siguiente manera:

{ΔX=XuΔu+XwΔw+XδeΔδeΔY=YvΔv+YpΔp+YrΔr+YδrΔδrΔZ=ZuΔu+ZwΔw+ZqΔq+Zw˙Δw˙+ZδeΔδeΔL=LvΔv+LpΔp+LrΔr+LδaΔδa+Lδ˙aΔδ˙a+LδrΔδrΔM=MuΔu+MwΔw+MqΔq+Mw˙Δw˙+MδeΔδe+Mδ˙eΔδ˙eΔN=NvΔv+NpΔp+NrΔr+NδaΔδa+NδrΔδr+Nδ˙rΔδ˙r\begin{cases} \Delta X &= X_u \Delta u+X_w \Delta w+X_{\delta e} \Delta \delta_e \\ \Delta Y &= Y_v \Delta v+Y_p \Delta p+Y_r \Delta r+Y_{\delta_r} \Delta \delta_r \\ \Delta Z &= Z_u \Delta u+Z_w \Delta w+Z_q \Delta q+Z_{\dot{w}} \Delta \dot{w}+Z_{\delta_e} \Delta \delta_e \\ \Delta L &= L_v \Delta v+L_p \Delta p+L_r \Delta r+L_{\delta_a} \Delta \delta_a+L_{\dot{\delta}_a} \Delta \dot{\delta}_a+L_{\delta_r} \Delta \delta_r \\ \Delta M &= M_u \Delta u+M_w \Delta w+M_q \Delta q+M_{\dot{w}} \Delta \dot{w}+M_{\delta_e} \Delta \delta_e+M_{\dot{\delta} e} \Delta \dot{\delta}_e \\ \Delta N &= N_v \Delta v+N_p \Delta p+N_r \Delta r+N_{\delta a} \Delta \delta_a+N_{\delta_r} \Delta \delta_r+N_{\dot{\delta} r} \Delta \dot{\delta}_r \end{cases}

Finalmente, la representación en el espacio de estados se obtiene reordenando las ecuaciones linealizadas del movimiento y sustituyendo las expresiones de las perturbaciones de las fuerzas y los momentos. Las ecuaciones longitudinales y laterales-direccionales se desacoplan debido a las hipótesis de simetría y a que las derivadas de estabilidad de acoplamiento cruzado son pequeñas o despreciables. Las ecuaciones longitudinales proporcionan la evolución de u,w,θ,qu, w, \theta, q, que representan el movimiento en el plano vertical (movimiento de cabeceo), mientras que las ecuaciones laterales-direccionales proporcionan la evolución de v,p,r,ϕ,ψv, p, r, \phi, \psi, que representan el movimiento en los ejes lateral (alabeo) y direccional (guiñada).

{(Xu−mddt)Δu+XwΔw−mgcos⁡θsΔθ=−XδeΔδeZuΔu+(Zw+(Zw˙−m)ddt)Δw+((Zq+mus)ddt−mgsin⁡θs)Δθ=−ZδeΔδeMuΔu+(Mw+Mw˙ddt)Δw+(Mqddt−Iyd2 dt2)Δθ=−(Mδe+Mδe˙ddt)ΔδedΔθdt=Δq\begin{cases} \left(X_u-m \frac{\mathrm{d}}{\mathrm{d} t}\right) \Delta u+X_w \Delta w-m g \cos \theta_s \Delta \theta &= -X_{\delta_e} \Delta \delta_e \\ Z_u \Delta u+\left(Z_w+\left(Z_{\dot{w}}-m\right) \frac{\mathrm{d}}{\mathrm{d} t}\right) \Delta w+\left(\left(Z_q+m u_s\right) \frac{\mathrm{d}}{\mathrm{d} t}-m g \sin \theta_s\right) \Delta \theta &= -Z_{\delta_e} \Delta \delta_e \\ M_u \Delta u+\left(M_w+M_{\dot{w}} \frac{\mathrm{d}}{\mathrm{d} t}\right) \Delta w+\left(M_q \frac{\mathrm{d}}{\mathrm{d} t}-I_y \frac{\mathrm{d}^2}{\mathrm{~d} t^2}\right) \Delta \theta &= -\left(M_{\delta_e}+M_{\dot{\delta_e}} \frac{\mathrm{d}}{\mathrm{d} t}\right) \Delta \delta_e \\ \frac{\mathrm{d} \Delta \theta}{\mathrm{d} t} &= \Delta q \end{cases} {(Yv−mddt)Δv+YpΔp+(Yr−mus)Δr+mgcos⁡θsΔϕ=−YδrΔδrLvΔv+(Lp−Ixddt)Δp+(Lr+Jxzddt)Δr=−(Lδa+Lδ˙addt)Δδa−LδrΔδrNvΔv+(Np+Jxzddt)Δp+(Nr−Izddt)Δr=−NδaΔδa−(Nδr+Nδ˙rddt)ΔδrdΔϕdt=Δp+tan⁡θsΔrdΔψdt=sec⁡θsΔr\begin{cases} \left(Y_v-m \frac{\mathrm{d}}{\mathrm{d} t}\right) \Delta v+Y_p \Delta p+\left(Y_r-m u_s\right) \Delta r+m g \cos \theta_s \Delta \phi &= -Y_{\delta_r} \Delta \delta_r \\ L_v \Delta v+\left(L_p-I_x \frac{\mathrm{d}}{\mathrm{d} t}\right) \Delta p+\left(L_r+J_{x z} \frac{\mathrm{d}}{\mathrm{d} t}\right) \Delta r &= -\left(L_{\delta_a}+L_{\dot{\delta}_a} \frac{\mathrm{d}}{\mathrm{d} t}\right) \Delta \delta_a-L_{\delta_r} \Delta \delta_r \\ N_v \Delta v+\left(N_p+J_{x z} \frac{\mathrm{d}}{\mathrm{d} t}\right) \Delta p+\left(N_r-I_z \frac{\mathrm{d}}{\mathrm{d} t}\right) \Delta r &= -N_{\delta_a} \Delta \delta_a-\left(N_{\delta_r}+N_{\dot{\delta}_r} \frac{\mathrm{d}}{\mathrm{d} t}\right) \Delta \delta_r \\ \frac{\mathrm{d} \Delta \phi}{\mathrm{d} t} &= \Delta p+\tan \theta_s \Delta r \\ \frac{\mathrm{d} \Delta \psi}{\mathrm{d} t} &= \sec \theta_s \Delta r \end{cases}

Para implementar el simulador, cada representación longitudinal y lateral-direccional en el espacio de estados se expresa en forma matricial y se resuelve numéricamente mediante un método de Runge-Kutta de cuarto orden. La implementación completa se encuentra en envs/core/simulator.py. Para obtener las matrices Alon,Blon,Alat,BlatA_{lon}, B_{lon}, A_{lat}, B_{lat} se utilizó una biblioteca simbólica de Python llamada SymPy para realizar las manipulaciones algebraicas. El método run de la clase Simulator recibe el estado actual y las deflexiones de las superficies de control y devuelve el estado actualizado tras un paso de tiempo. Para calcular la ubicación del avión en el sistema de referencia inercial, también se integran las ecuaciones cinemáticas de traslación utilizando las velocidades en ejes cuerpo calculadas previamente.

La clase Simulator carga una aeronave definida mediante un archivo XML que contiene sus propiedades y derivadas de estabilidad. Se implementó un analizador XML en envs/core/xml_parser.py para leer el archivo de la aeronave, analizar los datos y las unidades de entrada y convertirlas a unidades del SI mediante la biblioteca unyt. Los datos se agrupan en distintas clases utilizando pydantic para su validación y fácil acceso. Dentro de la clase Simulator, el constructor recibe los datos de la aeronave y calcula las matrices del espacio de estados para las ecuaciones longitudinales y laterales-direccionales, evitando recalcularlas en cada paso de tiempo. Además del método run, la clase Simulator también implementa un método get_state para devolver el estado actual de la aeronave y un método reset para restablecer el estado a la condición de referencia.

Aplicación web del simulador

Para visualizar y probar el simulador, se desarrolló una aplicación web utilizando React, Three.js y Vite. La aplicación web permite controlar la aeronave mediante entradas de teclado y ver su respuesta en tiempo real. La aplicación web se creó con fines de validación durante el desarrollo del simulador y del entorno, pero también se puede utilizar como un simulador de vuelo sencillo. Está basada en el proyecto R3F-takes-flight y se comunica con el simulador mediante llamadas a una API REST para enviar las deflexiones de las superficies de control y recibir el estado de la aeronave para actualizar el modelo 3D. A continuación se muestra una imagen de la aplicación web:

Aplicación web del simulador

La sencilla API REST se implementa utilizando FastAPI y se encuentra en tests/sim_api. Los endpoints principales son /step, para enviar las deflexiones de las superficies de control y recibir el estado actualizado, y /reset, para restablecer el simulador a la condición de referencia y guardar algunos registros en un archivo CSV. El código de la aplicación web se encuentra en el directorio simulator_webapp. Las instrucciones para configurar y ejecutar la aplicación web se encuentran en el archivo simulator_webapp/README.md.

Entorno de RL

El entorno de RL se implementa utilizando la interfaz OpenAI Gym y se encuentra en envs/waypoint_navigation_env.py. El entorno utiliza la clase Simulator para simular la dinámica de la aeronave y proporciona una tarea de navegación por puntos de paso para el agente de RL. El agente debe aprender a controlar la aeronave para seguir una serie de puntos de paso evitando condiciones de fallo como superar los límites máximos de ángulo de alabeo, ángulo de cabeceo o altitud. La frecuencia de interacción del agente se puede establecer en un valor inferior al del paso temporal del simulador para permitir entradas de control más realistas. El entorno también proporciona una función de recompensa que incentiva al agente a reducir la distancia al siguiente punto de paso y a alcanzarlo mediante una recompensa adicional, al tiempo que penaliza entradas de control excesivas o errores de rumbo y cabeceo elevados, así como la proximidad a las condiciones de fallo.

De nuevo, los tipos se definen utilizando pydantic para facilitar su validación y acceso y se encuentran en envs/env_types.py. La clase Navigation define información útil para la navegación, como el punto de paso actual, la distancia al punto de paso, el error de rumbo, si se ha alcanzado el punto de paso y otros parámetros relevantes. La clase RewardInfo reúne la información de estado, acción y navegación en una misma estructura, que se utiliza como entrada para calcular la recompensa y guardar registros durante el entrenamiento para análisis posteriores o intermedios. La clase FailConditions define los límites inferiores y superiores de las condiciones de fallo, como los ángulos máximos de alabeo y cabeceo, los límites de altitud y otros parámetros relevantes. El entorno utilizará estos límites para determinar si el episodio debe terminar debido a una condición de fallo.

La clase del entorno WaypointNavigationEnv implementa los métodos estándar de Gym, como reset y step. El método reset borra los estados del simulador y sitúa la aeronave en el estado inicial. Durante el entrenamiento, los puntos de paso se generan aleatoriamente con una separación y un número determinados, definidos en la clase WaypointConfig. En cada estado se genera un cambio de rumbo aleatorio dentro del cambio máximo de rumbo definido y después se genera cada punto de paso a una distancia aleatoria dentro de la separación definida, en la dirección del nuevo rumbo. La altitud de cada punto de paso también se genera aleatoriamente dentro del rango de altitud definido. Para obtener las coordenadas globales x e y del punto de paso en cada paso, se actualizan los versores de cada sistema de coordenadas de los puntos de paso utilizando el rumbo actual, de modo que las coordenadas relativas de los puntos de paso se puedan proyectar sobre el sistema de coordenadas global. Las ecuaciones utilizadas para generar los puntos de paso son las siguientes:

{Δψ∼U(−Δψmax/2,Δψmax/2)Δh∼U(hmin,hmax)xk+1=cos⁡Δψx^k+sin⁡Δψy^kyk+1=cos⁡Δψx^k+sin⁡Δψy^kzk+1=zk+Δhx^k=cos⁡ψki^+sin⁡ψkj^y^k=−sin⁡ψki^+cos⁡ψkj^ψk+1=ψk+Δψ\begin{cases} \Delta \psi \sim U\left(-\Delta \psi_{max} / 2, \Delta \psi_{max}/2\right) \\ \Delta h \sim U\left(h_{min}, h_{max}\right) \\ x_{k+1} = \cos \Delta \psi \hat{x}_{k} + \sin \Delta \psi \hat{y}_{k} \\ y_{k+1} = \cos \Delta \psi \hat{x}_{k} + \sin \Delta \psi \hat{y}_{k} \\ z_{k+1} = z_{k} + \Delta h \\ \hat{x}_{k} = \cos \psi_{k} \hat{i} + \sin \psi_{k} \hat{j} \\ \hat{y}_{k} = -\sin \psi_{k} \hat{i} + \cos \psi_{k} \hat{j} \\ \psi_{k+1} = \psi_{k} + \Delta \psi \\ \end{cases}

En modo de evaluación, los puntos de paso se pueden establecer manualmente para realizar pruebas consistentes. El método reset llama a un método para generar los puntos de paso si está en modo de entrenamiento o utiliza los puntos de paso establecidos previamente si está en modo de evaluación. Por su parte, el método step recibe una acción del agente, la aplica al simulador, calcula la recompensa, comprueba las condiciones de fallo y devuelve la observación, la recompensa, el indicador de finalización e información adicional.

Espacio de observaciones

El vector de observaciones puede incluir variables dimensionales o estar normalizado entre -1 y 1 en función de la configuración. Durante el entrenamiento, se observó un mejor comportamiento de aprendizaje al utilizar observaciones y acciones normalizadas. El vector de observaciones incluye variables de estado como las velocidades en ejes cuerpo, los ángulos de Euler y las velocidades angulares, así como la diferencia en cada coordenada respecto al siguiente punto de paso y algunas métricas de error, como la distancia al punto de paso, la distancia horizontal al punto de paso, la diferencia de altitud respecto al punto de paso, el error de rumbo y el error de cabeceo. El vector completo de observaciones es el siguiente:

{state=[u^,v^,w^,cos⁡(ϕ),sin⁡(ϕ),cos⁡(θ),sin⁡(θ),cos⁡(ψ),sin⁡(ψ),p^,q^,r^]waypoint diff=[dx,wpdmax,dy,wpdmax,dz,wpdmax]errors=[dwpdmax,dxy,wpdmax,hwpzmax−zmin,ψerrπ,θerrπ]\begin{cases} \text{state} = \left[ \hat{u}, \hat{v}, \hat{w}, \cos(\phi), \sin(\phi), \cos(\theta), \sin(\theta), \cos(\psi), \sin(\psi), \hat{p}, \hat{q}, \hat{r} \right] \\ \text{waypoint diff} = \left[ \frac{d_{x, wp}}{d_{max}}, \frac{d_{y, wp}}{d_{max}}, \frac{d_{z, wp}}{d_{max}} \right] \\ \text{errors} = \left[ \frac{d_{wp}}{d_{max}}, \frac{d_{xy, wp}}{d_{max}}, \frac{h_{wp}}{z_{max} - z_{min}}, \frac{\psi_{err}}{\pi}, \frac{\theta_{err}}{\pi} \right] \end{cases}

donde u^,v^,w^,p^,q^,r^\hat{u}, \hat{v}, \hat{w}, \hat{p}, \hat{q}, \hat{r} son las velocidades y velocidades angulares normalizadas en ejes cuerpo dadas por −1+value−minmax−min-1 + \frac{\text{value} - \text{min}}{\text{max} - \text{min}}, dx,wp,dy,wp,dz,wpd_{x, wp}, d_{y, wp}, d_{z, wp} son las diferencias en cada coordenada respecto al siguiente punto de paso, dwpd_{wp} es la distancia al punto de paso, dxy,wpd_{xy, wp} es la distancia horizontal al punto de paso, hwph_{wp} es la diferencia de altitud respecto al punto de paso, ψerr\psi_{err} es el error de rumbo y θerr\theta_{err} es el error de cabeceo. El error de cabeceo se calcula a partir de la diferencia de altitud y la distancia horizontal respecto al punto de paso, en un rango entre −π/2-\pi/2 y π/2\pi/2, y después se normaliza entre -1 y 1. Por otro lado, el error de rumbo se calcula como la diferencia entre el rumbo actual y el rumbo deseado hacia el punto de paso, ajustada entre −π-\pi y π\pi y después normalizada entre -1 y 1.

El error de rumbo deseado se calcula de manera diferente según se trate o no del último punto de paso. Si es el último, el rumbo deseado se calcula como el ángulo entre la posición actual y el último punto de paso. Si no es el último, el rumbo deseado se calcula como una media ponderada entre el rumbo al siguiente punto de paso y el rumbo al punto de paso posterior. Este último se calcula como el ángulo de la recta que pasa por ambos puntos de paso. De esta forma, se proporciona al agente cierta información sobre el siguiente rumbo requerido para evitar giros bruscos al alcanzar un punto de paso. El rumbo deseado se calcula de la siguiente manera:

ψdes=0.98⋅atan2(ywp−y,xwp−x)+0.02⋅atan2(ywp,next−ywp,xwp,next−xwp)\psi_{des} = 0.98 \cdot \text{atan2}(y_{wp} - y, x_{wp} - x) + 0.02 \cdot \text{atan2}(y_{wp, next} - y_{wp}, x_{wp, next} - x_{wp})

Condiciones de finalización

El episodio termina cuando la aeronave alcanza el último punto de paso o cuando se cumple una condición de fallo. Como se ha mencionado antes, las condiciones de fallo se definen en la clase FailConditions e incluyen el ángulo máximo de alabeo, el ángulo máximo de cabeceo, la altitud mínima y máxima y la distancia máxima al siguiente punto de paso. Si se cumple alguna de estas condiciones, el episodio termina y el agente recibe una recompensa negativa. Los ángulos máximos de alabeo y cabeceo se definen para evitar maniobras excesivas que puedan provocar una entrada en pérdida o una pérdida de control. La distancia máxima al siguiente punto de paso se define para evitar que la aeronave se aleje demasiado de los puntos de paso y, en ese caso, finalizar el episodio de forma anticipada para no desperdiciar pasos de tiempo.

Como la aeronave no siempre alcanzará el último punto de paso, también se implementa una condición de puntos de paso sobrepasados. Esta condición comprueba si la aeronave ha sobrepasado el siguiente punto de paso calculando el producto escalar entre el vector que va de la aeronave al punto de paso y el vector que va del punto de paso al siguiente punto de paso, dwp⋅dwp,next\mathbf{d_{wp}} \cdot \mathbf{d_{wp, next}}. Si el producto escalar es negativo, significa que la aeronave ha sobrepasado el punto de paso y el entorno cambia al siguiente. De este modo, si la aeronave sobrepasa un punto de paso, puede continuar hacia el siguiente sin tener que volver atrás, evitando un comportamiento típico de oscilación alrededor del punto de paso.

Función de recompensa

La función de recompensa está diseñada para incentivar al agente a seguir los puntos de paso cada vez más de cerca, al tiempo que penaliza entradas de control excesivas y errores elevados de rumbo, cabeceo y alabeo para evitar maniobras agresivas. La primera función de recompensa utilizada se basaba en la distancia, de acuerdo con la utilizada en el artículo [3], donde se define una función de recompensa exponencial basada en la distancia. La expresión es la siguiente:

R=−1+eK⋅dK=0.001R = -1 + e^{K \cdot d} \quad K = 0.001

donde dd es la distancia absoluta en metros al siguiente punto de paso y KK es un factor de escala. Cuanto más cerca está la aeronave del punto de paso, mayor es la recompensa, con una recompensa máxima de 0 cuando la aeronave se encuentra exactamente en el punto de paso. Sin embargo, la aeronave no alcanzará exactamente el punto de paso para evitar oscilaciones a su alrededor, por lo que se otorga una recompensa adicional cuando la aeronave se encuentra dentro de un determinado radio definido en la clase WaypointConfig. Esto se considera como haber alcanzado el punto de paso y se cambia al siguiente. Como alternativa, si la aeronave sobrepasa el punto de paso, también se cambia al siguiente, pero sin recibir la recompensa adicional. Esto incentiva al agente a alcanzar el punto de paso sin sobrepasarlo y evita oscilaciones a su alrededor.

Aunque esta función de recompensa puede funcionar en algunos casos, se comprobó que el agente aprende más rápido utilizando una recompensa que penaliza o incentiva directamente la mejora de la distancia al punto de paso con respecto a la distancia del paso anterior. Se probaron muchas variantes, desde una suma ponderada de un término de progreso y penalizaciones por acciones, error de rumbo y error de cabeceo, pero la que mejor funcionó fue la siguiente:

{R=Kpro⋅F(d)⋅(dn,prev−dn)⋅e−Kψ⋅ψerr⋅e−Kθ⋅θerr⋅e−Kϕ⋅(ϕ−ϕth)⋅e−Ka⋅δa2+δe2+δr2F(d)=6−5dth−dreach⋅(d−dreach)if d<dth else 1\begin{cases} R = K_{pro} \cdot F(d) \cdot (d_{n, prev} - d_{n}) \cdot e^{- K_{\psi} \cdot \psi_{err}} \cdot e^{- K_{\theta} \cdot \theta_{err}} \cdot e^{- K_{\phi} \cdot (\phi - \phi_{th})} \cdot e^{- K_{a} \cdot \sqrt{ \delta_a^2 + \delta_e^2 + \delta_r^2}} \\ F(d) = 6 - \frac{5}{d_{th} - d_{reach}} \cdot (d - d_{reach}) \quad \text{if } d < d_{th} \text{ else } 1 \end{cases}

donde la primera parte es el término de progreso, dn,prevd_{n, prev} es la distancia normalizada al punto de paso en el paso anterior, dnd_{n} es la distancia actual normalizada al punto de paso y KproK_{pro} es un factor de escala para el término de progreso. Ambas distancias normalizadas escalan xy por 1000 m y z por 100 m para tener un rango similar. Además, se aplica un factor de distancia F(d)F(d) para aumentar la recompensa cuando la aeronave está más cerca del punto de paso. Este factor aumenta linealmente de 1 a 6 cuando la aeronave se encuentra dentro de una distancia umbral determinada dthd_{th} al punto de paso, hasta alcanzar el radio del punto de paso dreachd_{reach}.

La segunda parte consiste en términos de penalización exponencial para el error de rumbo, el error de cabeceo, el ángulo de alabeo y las deflexiones de las superficies de control. Aquí ψerr\psi_{err} es el error de rumbo, θerr\theta_{err} es el error de cabeceo, ϕ\phi es el ángulo de alabeo, ϕth\phi_{th} es un umbral de ángulo de alabeo por debajo del cual no se aplica penalización, δa,δe,δr\delta_a, \delta_e, \delta_r son las deflexiones de las superficies de control correspondientes a alerones, timón de profundidad y timón de dirección, respectivamente, y Kψ,Kθ,Kϕ,KaK_{\psi}, K_{\theta}, K_{\phi}, K_{a} son factores de escala para cada término de penalización. El uso del término de progreso incentiva al agente a acercarse al punto de paso en cada paso, mientras que los términos de penalización desincentivan las entradas de control excesivas y los errores elevados de rumbo, cabeceo y alabeo. La penalización por ángulo de alabeo solo se aplica cuando el ángulo de alabeo supera un determinado umbral, para permitir las maniobras de alabeo necesarias durante los giros sin recibir penalizaciones. Los factores de escala se pueden ajustar para equilibrar la importancia de cada término de la función de recompensa.

Además, se otorga una recompensa adicional cuando la aeronave alcanza un punto de paso y una recompensa negativa cuando el avión sobrepasa un punto de paso sin alcanzarlo o cuando se cumple una condición de fallo. La recompensa adicional por alcanzar un punto de paso incentiva al agente a alcanzar los puntos de paso, mientras que se define otra recompensa negativa para evitar ángulos de alabeo elevados cercanos al límite del ángulo de alabeo. Esta recompensa negativa anticipa la condición de fallo e incentiva al agente a evitar ángulos de alabeo elevados. La expresión de estas recompensas es la siguiente:

{Rhigh_roll=−e5(ϕ−ϕth,i)Rreach_wp=+20Rpass_wp=−10Rfail=−20Rreach_wp,all=+50\begin{cases} R_{high\_roll} = - e^{5 (\phi - \phi_{th, i})} \\ R_{reach\_wp} = +20 \\ R_{pass\_wp} = -10 \\ R_{fail} = - 20 \\ R_{reach\_wp, all} = +50 \end{cases}

donde ϕth,i\phi_{th, i} es un umbral intermedio de ángulo de alabeo inferior al límite máximo de ángulo de alabeo definido en la clase FailConditions. La recompensa adicional por alcanzar un punto de paso se establece en +20, mientras que la recompensa negativa por sobrepasar un punto de paso sin alcanzarlo se establece en -10. Si la aeronave alcanza todos los puntos de paso, se otorga una recompensa adicional de +50. La recompensa negativa por condiciones de fallo se establece en -20. Estos valores se pueden ajustar para equilibrar la importancia de cada término de recompensa en la función de recompensa global.

La función de recompensa se implementa en el módulo envs/rewards.py y se llama desde el método step del entorno. La función de recompensa recibe como entrada el estado actual, la acción y la información de navegación y devuelve la recompensa calculada. La función de recompensa se puede modificar o sustituir fácilmente para experimentar con diferentes estrategias de recompensa.

Entrenamiento del agente de RL

El agente de RL se entrena utilizando la biblioteca Stable Baselines3 y el algoritmo PPO. El script de entrenamiento se encuentra en training/waypoint/train_waypoint_control.py. El script crea el entorno de navegación por puntos de paso, define el agente PPO con una política MLP personalizada y entrena al agente durante un número determinado de pasos de tiempo definido en el archivo training/configs/train_config.yaml.

Registros y visualización

El progreso del entrenamiento se registra utilizando WandB para facilitar la supervisión y el análisis de algunas métricas, como la pérdida del agente o la pérdida de valor, mientras que se implementa un sistema de registro personalizado para guardar información adicional para su análisis posterior y el modelo entrenado se guarda en un archivo para su uso posterior dentro del directorio models.

Durante el entrenamiento, el entorno guarda registros del estado, la acción y la información de navegación cada n episodios, según se define en el archivo de configuración del entrenamiento training/configs/train_config.yaml. Los registros se guardan como archivos pickle en el directorio training/waypoint/logs y se pueden utilizar para su análisis o visualización posteriores. Los registros incluyen información como el estado de la aeronave, la acción realizada, la información de navegación (como la distancia al punto de paso, el error de rumbo, el error de cabeceo, etc.) y la recompensa recibida, así como algunas estadísticas, como la duración del episodio, la recompensa total y el número de puntos de paso alcanzados. Los registros se pueden utilizar para analizar el rendimiento y el comportamiento del agente durante el entrenamiento o en una evaluación posterior.

Para facilitar el análisis de los registros, se proporciona un script de visualización en training/waypoint/logs_visualizer.py. Este script utiliza la biblioteca Gradio para crear una interfaz web sencilla que permite cargar y visualizar los registros. El script representa la trayectoria 3D de la aeronave, los ángulos de Euler y las acciones a lo largo del tiempo, muestra los datos de estado en una tabla y otra información para su análisis. A continuación se muestra un ejemplo de la interfaz web:

Trayectoria 3D y recompensa del visualizador de registros Dataframe de estado y estadísticas del visualizador de registros

Ajuste de hiperparámetros

El script de entrenamiento utiliza un conjunto de hiperparámetros definidos en el archivo training/configs/train_config.yaml. Estos hiperparámetros incluyen:

Para encontrar los mejores hiperparámetros para el entrenamiento, se proporciona un script de ajuste de hiperparámetros en training/waypoint/hyperparameter_search.py. Este script utiliza la biblioteca Optuna para realizar una búsqueda de hiperparámetros utilizando un espacio de búsqueda definido. El espacio de búsqueda incluye los principales hiperparámetros mencionados anteriormente y se puede ajustar para incluir o excluir determinados parámetros. El script ejecuta múltiples pruebas con diferentes combinaciones de hiperparámetros y evalúa el rendimiento del agente utilizando una métrica definida, como la recompensa media a lo largo de un determinado número de episodios. Los resultados se guardan en hyperparameter_search/results, incluidos los mejores hiperparámetros encontrados durante la búsqueda.

Entrenamiento y pruebas

Para entrenar al agente de RL, tras configurar los hiperparámetros del agente y otros parámetros de entrenamiento, como los pasos de tiempo totales, la frecuencia de evaluación, la frecuencia de registro y las rutas de guardado en el archivo training/configs/train_config.yaml, el siguiente paso es ejecutar el archivo training/waypoint/train_waypoint_control.py, que carga el archivo de configuración, crea el entorno y el agente PPO e inicia el proceso de entrenamiento. El entrenamiento se puede ejecutar mediante el siguiente comando:

python training/waypoint/train_waypoint_control.py

El proceso de entrenamiento se dividió en etapas. Primero, se entrenó al agente para alcanzar un único punto de paso, estableciendo el número mínimo y máximo de puntos de paso en 1 en el archivo training/configs/train_config.yaml. Esto permite al agente aprender el control básico de la aeronave y cómo alcanzar un punto de paso. Las siguientes gráficas muestran la pérdida de entrenamiento y la pérdida de valor durante el entrenamiento con un único punto de paso. Como resultado del progreso definido en la función de recompensa, la pérdida de entrenamiento disminuye en pocos episodios y el agente es capaz de alcanzar el punto de paso de forma consistente tras aproximadamente 100.000 pasos de tiempo.

Pérdida de entrenamiento con un único punto de paso Pérdida de valor con un único punto de paso

Una vez que el agente fue capaz de alcanzar un único punto de paso de forma consistente, el entrenamiento continuó con varios puntos de paso, estableciendo el número mínimo y máximo de puntos de paso en un valor superior. Esto permite al agente aprender a navegar por varios puntos de paso y a gestionar los giros y los cambios de altitud. El entrenamiento con solo 2 puntos de paso también es rápido y el agente es capaz de alcanzar ambos de forma consistente tras aproximadamente 500.000 pasos de tiempo. Para un número mayor de puntos de paso, se detectó un problema por el que el agente alcanzaba el límite de alabeo en la transición de algunos puntos de paso. Como solución, se implementó una recompensa negativa mayor para los ángulos de alabeo elevados, con el fin de incentivar al agente a evitarlos. De esta forma, el agente pudo aprender a evitar ángulos de alabeo elevados y alcanzar todos los puntos de paso de forma consistente tras 1 millón de pasos de tiempo. Las siguientes gráficas muestran la trayectoria, la recompensa, los errores, los ángulos de Euler y las deflexiones de las superficies de control de un episodio con 10 puntos de paso tras entrenar durante 1 millón de pasos de tiempo:

Trayectoria 3D, recompensa, errores, ángulos de Euler y controles del visualizador de registros con diez puntos de paso

Para probar visualmente el comportamiento del agente, se implementa otra API REST sencilla en tests/agent_api, que implementa los mismos endpoints que la API del simulador, pero, en lugar de recibir deflexiones de las superficies de control, primero ejecuta un episodio completo utilizando el agente entrenado y después devuelve el estado en cada paso. De esta forma, la aplicación web se puede utilizar para visualizar el comportamiento del agente del mismo modo que con el simulador. Para ejecutar la API del agente, utiliza el siguiente comando:

# Python API REST
cd tests
python agent_api.py

# In another terminal, run the web-app
cd simulator_webapp
npm run dev

En el siguiente vídeo se puede ver un ejemplo de la aplicación web que muestra el comportamiento del agente:

donde los puntos de paso se muestran como toros blancos y la aeronave los sigue mediante maniobras suaves de alabeo. Aunque se entrenó para seguir 10 puntos de paso, en este caso es capaz de seguir 15 con éxito. En el simulador web, la traslación de la aeronave está escalada para mejorar la visualización, como se puede ver en la pantalla de estado de la esquina superior izquierda.

Agradecimientos