Entorno Gym personalizado en Python: MarsExplorerEnv y aprendizaje por refuerzo
Cómo definir un entorno personalizado en OpenAI Gym, solucionar los errores AttributeError: metadata y NotImplementedError: render(), y registrarlo correctamente con gym.envs.registration.register() para usarlo con gym.make().
MarsExplorerEnv: entorno de aprendizaje por refuerzo en OpenAI Gym
El código define un entorno personalizado en Gym donde un agente debe moverse entre posiciones (0–9) para llegar a una zona de interés evitando zonas peligrosas. Es un ejemplo directo de aprendizaje por refuerzo (RL) aplicado a exploración autónoma — el mismo tipo de lógica que usan los rovers en entornos desconocidos.
Este tipo de entornos personalizados se usa para entrenar agentes en toma de decisiones secuenciales: el agente recibe una recompensa positiva al llegar a la zona de interés, negativa si entra en una zona peligrosa y neutra en el resto de estados. La política se aprende iterando sobre episodios.
Dos errores concretos: metadata y registro del entorno
Al ejecutar el entorno sin las correcciones, Gym lanza dos errores que bloquean la ejecución. Ambos tienen causa y solución directa.
AttributeError: 'MarsExplorerEnv' object has no attribute 'metadata'
Gym espera que cualquier subclase de gym.Env tenga el atributo de clase metadata con los modos de renderizado soportados. Si no está definido, el método render() lanza un AttributeError al intentar leerlo. La solución es añadir metadata = {'render.modes': ['human']} directamente en la definición de la clase.
gym.make() no reconoce el entorno personalizado
Cuando se llama a gym.make('MarsExplorer-v0') sin haber registrado el entorno previamente, Gym devuelve un error porque no encuentra el ID en su registro interno. La solución es llamar a gym.envs.registration.register() con el id y el entry_point antes de cualquier llamada a gym.make().
Tres cambios, dos errores resueltos: metadata, render() y register()
Cada corrección es puntual y no requiere restructurar el código — se añaden en los lugares exactos donde Gym los espera.
metadata como atributo de clase
Añadir metadata = {'render.modes': ['human']} justo después de la declaración de la clase, antes de __init__. Es un atributo de clase, no de instancia — va al nivel de la clase, no dentro de ningún método. Gym lo lee antes de instanciar el objeto.
render()
Implementar render(self, mode='human') con la validación explícita: si mode != 'human', lanzar NotImplementedError. Esto comunica claramente qué modos están soportados en lugar de dejar que Gym falle de forma críptica en otro punto de la ejecución.
register() antes de gym.make()
Llamar a gym.envs.registration.register(id='MarsExplorer-v0', entry_point='__main__:MarsExplorerEnv') antes de gym.make(). El entry_point debe apuntar al módulo donde está la clase — si el código está en un archivo externo, sustituir __main__ por el nombre del módulo.
Implementación completa: entorno, registro y bucle de episodios
import gym
import numpy as np
import random
from gym.envs.registration import register
class MarsExplorerEnv(gym.Env):
metadata = {'render.modes': ['human']} # Corrección 1
def __init__(self):
super().__init__()
self.action_space = gym.spaces.Discrete(2)
self.observation_space = gym.spaces.Discrete(10)
self.state = 5
self.interest_zone = 9
self.danger_zones = [2, 4, 7]
def step(self, action):
if action == 0:
self.state = max(0, self.state - 1)
else:
self.state = min(self.observation_space.n - 1, self.state + 1)
done = self.state == self.interest_zone
reward = 10 if done else (-5 if self.state in self.danger_zones else 0)
return self.state, reward, done, {}
def reset(self):
self.state = 5
return self.state
def render(self, mode='human'): # Corrección 2
if mode != 'human':
raise NotImplementedError("Solo se admite el modo 'human'")
print(f"Estado actual: {self.state}")
# Corrección 3 — registrar antes de gym.make()
register(
id='MarsExplorer-v0',
entry_point='__main__:MarsExplorerEnv',
)
env = gym.make('MarsExplorer-v0')
episodes = 10
for episode in range(1, episodes + 1):
state = env.reset()
done = False
total_reward = 0
while not done:
action = env.action_space.sample()
state, reward, done, info = env.step(action)
total_reward += reward
env.render()
print(f"Episodio: {episode}, Recompensa total: {total_reward}")
Cuatro buenas prácticas para entornos Gym personalizados
- Define siempre
metadataen la clase - Cualquier subclase de
gym.Envdebe declararmetadata = {'render.modes': ['human']}aunque no uses todos los modos. Es un contrato que Gym espera respetar y cuya ausencia genera errores silenciosos en versiones más recientes de la librería. - Registra el entorno antes de
gym.make() - La llamada a
register()debe ejecutarse antes de cualquiergym.make(). Si el entorno está en un módulo externo, asegúrate de que ese módulo se importa antes de hacer elmake, o Gym no encontrará elentry_pointen el registro. - Usa
seed()para experimentos reproducibles - Implementar el método
seed(self, seed=None)en tu entorno y llamar aenv.seed(42)antes del bucle de episodios garantiza que los resultados sean reproducibles entre ejecuciones — imprescindible para comparar políticas o depurar comportamientos del agente. - Prueba con
env.action_space.sample()antes de tu política - Usar acciones aleatorias con
sample()es la forma más rápida de verificar que el entorno funciona correctamente antes de integrar una política de RL más avanzada. Si el bucle de episodios termina condone=Truede forma consistente, el entorno está correctamente implementado.
Yel Martínez, Digital Strategist & Technologist — especialista en Python, automatización y ciencia de datos aplicada a proyectos técnicos y sostenibilidad digital. Para proyectos de desarrollo Python, automatización o IA aplicada: contacto.
