gymnasium

Par mkurman · zorai

npx skills add https://github.com/mkurman/zorai --skill gymnasium

name: gymnasium description: API standard pour les environnements d'apprentissage par renforcement mono-agent (Gymnasium). Fournit des environnements Classic Control, Box2D, Toy Text, MuJoCo et Atari avec une interface unifiée env.step()/env.reset(). Pour l'RL multi-agent, utilisez PettingZoo. Pour les implémentations d'algorithmes, utilisez stable-baselines3 ou CleanRL. license: MIT license tags: [single-agent-rl, rl-environments, control-benchmarks, environment-wrappers, gymnasium] metadata: skill-author: K-Dense Inc. -----|------|---------| | observation | ndarray / dict | Observation d'état actuelle | | reward | float | Récompense immédiate | | terminated | bool | État terminal atteint (succès/échec) | | truncated | bool | Épisode terminé par limite de temps/signal externe | | info | dict | Infos diagnostiques auxiliaires |

Distinction critique : terminated signifie que le MDP s'est terminé naturellement. truncated signifie qu'il a atteint une limite de temps. Les deux doivent déclencher reset(), mais votre algorithme doit les gérer différemment (pas de bootstrap de valeur sur terminated).

3. Familles d'environnements disponibles

Famille Exemples Installation Cas d'usage
Classic Control CartPole, MountainCar, Pendulum, Acrobot pip install gymnasium Débogage d'algorithmes, tests rapides
Box2D LunarLander, BipedalWalker, CarRacing pip install "gymnasium[box2d]" Problèmes jouets basés sur la physique
Toy Text FrozenLake, Taxi, Blackjack pip install gymnasium RL discret, enseignement
MuJoCo HalfCheetah, Hopper, Humanoid, Ant pip install "gymnasium[mujoco]" Benchmarks de contrôle continu
Atari Breakout, Pong, SpaceInvaders pip install "gymnasium[atari]" (ALE) RL basé pixels, développement DQN

Installer tous :

pip install "gymnasium[all]"

4. Espaces d'observation et d'action

import gymnasium as gym
from gymnasium import spaces

env = gym.make("CartPole-v1")

print(env.observation_space)  # Box([-4.8 ...], [4.8 ...], (4,), float32)
print(env.action_space)        # Discrete(2)

# Vérifier les propriétés de l'espace
assert isinstance(env.observation_space, spaces.Box)
print(env.observation_space.shape)   # (4,)
print(env.observation_space.dtype)   # float32
print(env.observation_space.low)     # [-4.8 -inf -0.418 -inf]
print(env.observation_space.high)    # [4.8 inf 0.418 inf]

assert isinstance(env.action_space, spaces.Discrete)
print(env.action_space.n)            # 2

5. Créer des environnements personnalisés

import gymnasium as gym
from gymnasium import spaces
import numpy as np

class CustomEnv(gym.Env):
    metadata = {"render_modes": ["human", "rgb_array"], "render_fps": 30}

    def __init__(self, render_mode=None, size=5):
        super().__init__()

        self.size = size
        self.observation_space = spaces.Dict({
            "agent": spaces.Box(0, size - 1, shape=(2,), dtype=int),
            "target": spaces.Box(0, size - 1, shape=(2,), dtype=int),
        })
        self.action_space = spaces.Discrete(4)  # 0=up, 1=right, 2=down, 3=left

        self._action_to_direction = {
            0: np.array([1, 0]),
            1: np.array([0, 1]),
            2: np.array([-1, 0]),
            3: np.array([0, -1]),
        }
        self.render_mode = render_mode

    def _get_obs(self):
        return {"agent": self._agent_location, "target": self._target_location}

    def reset(self, seed=None, options=None):
        super().reset(seed=seed)
        self._agent_location = self.np_random.integers(0, self.size, size=2)
        self._target_location = self._agent_location.copy()
        while np.array_equal(self._target_location, self._agent_location):
            self._target_location = self.np_random.integers(0, self.size, size=2)
        return self._get_obs(), {}

    def step(self, action):
        direction = self._action_to_direction[action]
        self._agent_location = np.clip(
            self._agent_location + direction, 0, self.size - 1
        )
        terminated = np.array_equal(self._agent_location, self._target_location)
        reward = 1 if terminated else -0.01
        return self._get_obs(), reward, terminated, False, {}

    def render(self):
        if self.render_mode == "human":
            grid = np.full((self.size, self.size), ".")
            grid[self._target_location[0], self._target_location[1]] = "T"
            grid[self._agent_location[0], self._agent_location[1]] = "A"
            print("\n".join(" ".join(row) for row in grid) + "\n")

    def close(self):
        pass

Enregistrer et utiliser :

gym.register(id="CustomEnv-v0", entry_point=CustomEnv, max_episode_steps=100)
env = gym.make("CustomEnv-v0")

6. Wrappers essentiels

from gymnasium import wrappers

env = gym.make("CartPole-v1")

# Normaliser les observations (moyenne/écart-type courants)
env = wrappers.NormalizeObservation(env)

# Normaliser les récompenses
env = wrappers.NormalizeReward(env, gamma=0.99)

# Écrêter les actions à la plage valide
env = wrappers.ClipAction(env)

# Redimensionner les actions de [-1,1] aux bornes de l'environnement
env = wrappers.RescaleAction(env, min_action=-1, max_action=1)

# Convertir en observation unique (aplatir les espaces dict)
env = wrappers.FlattenObservation(env)

# Redimensionner les observations d'image
env = wrappers.ResizeObservation(env, shape=(84, 84))

# Empilement de frames (style Atari)
env = wrappers.FrameStackObservation(env, stack_size=4)

# Application de la limite de temps
env = wrappers.TimeLimit(env, max_episode_steps=500)

# Enregistrer les épisodes comme vidéos
env = wrappers.RecordVideo(env, "videos/", episode_trigger=lambda x: x % 100 == 0)

# Transformer les récompenses
from gymnasium.wrappers import TransformReward
env = TransformReward(env, lambda r: np.clip(r, -1, 1))

7. Environnements vectorisés

from gymnasium.vector import SyncVectorEnv, AsyncVectorEnv

def make_env(env_id, seed):
    def _init():
        env = gym.make(env_id)
        env.reset(seed=seed)
        return env
    return _init

# Synchrone (séquentiel)
envs = SyncVectorEnv([make_env("CartPole-v1", i) for i in range(4)])
obs, _ = envs.reset()
obs, rewards, terminateds, truncateds, infos = envs.step(actions)

# Asynchrone (processus parallèles)
envs = AsyncVectorEnv([make_env("CartPole-v1", i) for i in range(8)])

8. Versioning des environnements

Gymnasium utilise le versioning sémantique : CartPole-v0, CartPole-v1. Quand la dynamique, la fonction de récompense ou l'espace d'observation change, le numéro de version s'incrémente. Fixez toujours les versions d'environnement dans vos expériences pour la reproductibilité.

9. Vérifier la validité d'un environnement

from gymnasium.utils.env_checker import check_env

env = gym.make("CartPole-v1")
check_env(env, warn=True)  # Vérifie la conformité API

Patterns clés

  1. Utilisez toujours seed dans reset() pour des expériences reproductibles
  2. Distinguez terminated de truncated dans le bootstrap de valeur
  3. Utilisez wrappers.RecordVideo pour déboguer et partager les résultats
  4. Préférez Gymnasium à l'ancien Gym — Gym n'est plus maintenu
  5. Utilisez AsyncVectorEnv pour les environnements limités par CPU, SyncVectorEnv pour les légers
  6. info["terminal_observation"] est disponible après auto-reset dans les envs vectorisés

Références

Skills similaires