logo

Gymnasium과 Stable-Baselines3

Gymnasium

  • Gymnasium: 강화학습 환경을 같은 방식으로 다루기 위한 파이썬 라이브러리
    • OpenAI의 gym을 이어받아 Farama 재단이 관리
    • reset()으로 시작하고 step(action)으로 한 단계씩 진행하는 공통 인터페이스 제공
  • 기본 제공 환경
    • Classic Control: CartPole, MountainCar, Pendulum 등 간단한 물리 문제
    • Box2D: LunarLander, BipedalWalker 등 2차원 물리 시뮬레이션
    • Toy Text: FrozenLake, Taxi 등 격자 세계
    • MuJoCo: 로봇 관절 제어 등 3차원 물리 시뮬레이션
  • 환경 인터페이스가 같으므로 같은 학습 코드를 여러 환경에 그대로 적용 가능

Stable-Baselines3

  • Stable-Baselines3(SB3): 파이토치(PyTorch)로 구현한 강화학습 알고리즘 라이브러리
    • Gymnasium 환경을 그대로 받아 학습
    • 검증된 구현을 제공하므로 알고리즘을 직접 구현하지 않고 실험 가능
  • 제공 알고리즘
    • 가치 기반: DQN
    • Actor-Critic 계열: A2C, PPO
    • 연속 행동용: DDPG, TD3, SAC
  • 모든 알고리즘의 사용법이 같음
    • model = 알고리즘("정책 종류", 환경): 모델 생성
    • model.learn(total_timesteps=...): 학습
    • model.predict(관찰): 행동 선택
    • model.save(...), 알고리즘.load(...): 저장과 불러오기
  • 수업 실습에 적합한 이유
    • pip 설치 한 번으로 Colab에서 바로 사용 가능
    • 일반 파이썬 코드처럼 한 프로세스에서 실행되어 오류를 찾기 쉬움
    • 대규모 분산 학습이 필요하면 Ray RLlib 같은 라이브러리를 고려

실습 준비

  • Colab에서 SB3와 CartPole 화면 출력에 필요한 패키지를 설치
    • gymnasium[classic-control]: CartPole 화면을 그리는 pygame 포함
%pip install -q stable-baselines3 "gymnasium[classic-control]"
import gymnasium as gym  # 강화학습 환경 라이브러리

CartPole 환경

  • CartPole: 카트를 좌우로 밀어 카트 위의 막대가 쓰러지지 않게 버티는 환경
    • 막대가 버틴 매 스텝마다 보상 +1
    • 막대가 약 12도 이상 기울거나 카트가 화면 밖으로 나가면 종료
    • CartPole-v1은 500스텝에 도달하면 중단(truncated) → 최대 수익 500
env = gym.make("CartPole-v1")  # 카트 위 막대 세우기 환경 생성
env.observation_space  # 관찰 공간
실행 결과
Box([-4.8               -inf -0.41887903        -inf], [4.8               inf 0.41887903        inf], (4,), float32)
  • 관찰: 실수 4개로 이뤄진 Box 공간
    • 카트 위치(±4.8), 카트 속도, 막대 각도(±0.42라디안 ≈ ±24도), 막대 각속도
    • 속도와 각속도는 범위 제한이 없어 inf로 표시
env.action_space  # 행동 공간
실행 결과
Discrete(2)
  • 행동: 0은 카트를 왼쪽으로 밀기, 1은 오른쪽으로 밀기

무작위 정책

  • 학습 전 비교 기준으로, 행동을 무작위로 골라 에피소드 1개를 진행
obs, info = env.reset(seed=0)  # 환경 초기화(seed로 시작 상태 고정)
env.action_space.seed(0)  # 무작위 행동도 매번 같게 나오도록 고정
steps = 0  # 막대를 버틴 스텝 수
terminated = truncated = False  # 종료 여부, 시간 제한에 따른 중단 여부
while not (terminated or truncated):  # 에피소드가 끝날 때까지 반복
    action = env.action_space.sample()  # 무작위로 행동 선택
    obs, reward, terminated, truncated, info = env.step(action)  # 행동을 환경에 반영
    steps += 1  # 버틴 스텝 수 1 증가
steps  # 무작위 정책이 버틴 스텝 수
실행 결과
18
  • 무작위 정책은 18스텝 만에 막대를 쓰러뜨림

PPO 모델 만들기

from stable_baselines3 import PPO  # PPO 알고리즘
model = PPO(
    "MlpPolicy",  # 다층 퍼셉트론(MLP)으로 정책과 가치 함수를 구성
    env,          # 학습에 사용할 환경
    seed=0,       # 결과 재현을 위한 난수 고정
    verbose=0,    # 학습 중 로그 출력 생략
)
model.policy  # 정책 신경망 구조
실행 결과
ActorCriticPolicy(
  (features_extractor): FlattenExtractor(
    (flatten): Flatten(start_dim=1, end_dim=-1)
  )
  (pi_features_extractor): FlattenExtractor(
    (flatten): Flatten(start_dim=1, end_dim=-1)
  )
  (vf_features_extractor): FlattenExtractor(
    (flatten): Flatten(start_dim=1, end_dim=-1)
  )
  (mlp_extractor): MlpExtractor(
    (policy_net): Sequential(
      (0): Linear(in_features=4, out_features=64, bias=True)
      (1): Tanh()
      (2): Linear(in_features=64, out_features=64, bias=True)
      (3): Tanh()
    )
    (value_net): Sequential(
      (0): Linear(in_features=4, out_features=64, bias=True)
      (1): Tanh()
      (2): Linear(in_features=64, out_features=64, bias=True)
      (3): Tanh()
    )
  )
  (action_net): Linear(in_features=64, out_features=2, bias=True)
  (value_net): Linear(in_features=64, out_features=1, bias=True)
)
  • ActorCriticPolicy: 행위자와 비평가를 함께 가진 구조
    • 행위자: policy_net → action_net, 관찰 4개를 받아 행동 2개의 점수를 출력
    • 비평가: value_net → value_net, 관찰 4개를 받아 상태의 가치 1개를 출력
    • 두 신경망 모두 64개 뉴런의 은닉층 2개를 사용

단계별 학습과 녹화

  • 학습 과정을 눈으로 보기 위해 학습 중간중간 에피소드 1개를 녹화
  • Colab에는 모니터가 없으므로 render_mode="rgb_array"로 화면을 이미지 배열로 받아 모음
import cv2  # 프레임에 글자를 쓰기 위한 OpenCV


def record_episode(model, max_steps=500):
    """현재 모델로 에피소드 1개를 실행하고 화면 프레임을 모아 반환"""
    env = gym.make("CartPole-v1", render_mode="rgb_array")  # 화면을 이미지 배열로 받는 환경
    obs, info = env.reset(seed=0)  # 매번 같은 시작 상태에서 비교
    label = f"steps: {model.num_timesteps:,}"  # 지금까지 학습한 스텝 수
    frames = []  # 프레임을 모을 리스트
    for _ in range(max_steps):  # 최대 500스텝까지 실행
        action, _ = model.predict(obs, deterministic=True)  # 가장 확률이 높은 행동 선택
        obs, reward, terminated, truncated, info = env.step(action)  # 행동을 환경에 반영
        frame = env.render().copy()  # 현재 화면을 이미지 배열로 받음
        cv2.putText(frame, label, (10, 30),  # 왼쪽 위 (10, 30) 위치에
                    cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 0), 2)  # 글꼴, 크기, 검은색, 두께
        frames.append(frame)  # 프레임 저장
        if terminated or truncated:  # 막대가 쓰러지거나 시간 제한에 도달하면
            break  # 녹화 종료
    env.close()  # 녹화용 환경 정리
    print(f"{label} -> {len(frames)} steps")  # 학습량과 버틴 스텝 수 출력
    return frames
  • deterministic=True: 확률적으로 고르지 않고 확률이 가장 높은 행동만 선택
    • 학습된 정책의 실력을 일관되게 비교하기 위함
  • .copy(): env.render()가 돌려준 배열을 직접 고치지 않도록 복사본에 글자를 씀
frames = record_episode(model)  # 학습 전 모습 녹화
실행 결과
steps: 0 -> 70 steps
for chunk in [5_000, 5_000, 10_000, 20_000]:  # 단계별 추가 학습량
    model.learn(total_timesteps=chunk, reset_num_timesteps=False)  # 이어서 학습
    frames += record_episode(model)  # 학습 후 모습을 녹화해 이어 붙임
실행 결과
steps: 6,144 -> 163 steps
steps: 12,288 -> 367 steps
steps: 22,528 -> 500 steps
steps: 43,008 -> 500 steps
  • reset_num_timesteps=False: 학습 스텝 수를 0으로 되돌리지 않고 누적
  • 학습량이 5,000이 아니라 6,144로 표시되는 이유
    • PPO는 기본적으로 2,048스텝(n_steps)씩 경험을 모은 뒤 학습
    • 5,000스텝을 요청하면 2,048 × 3 = 6,144스텝까지 진행
  • 결과 해석
    • 학습 전 70스텝 → 약 6천 스텝 학습 후 163스텝 → 약 1만 2천 스텝 후 367스텝
    • 약 2만 2천 스텝부터 최대치인 500스텝을 버팀

동영상 만들기

import imageio  # 프레임을 동영상 파일로 저장

imageio.mimsave("training.mp4", frames, fps=30)  # 초당 30프레임 mp4로 저장
  • 프레임 크기 600×400을 608×400으로 바꾼다는 경고가 나올 수 있음
    • 동영상 코덱이 가로세로를 16의 배수로 요구하기 때문으로, 무시해도 됨
  • 저장한 training.mp4는 Colab 왼쪽 파일 탐색기에서 내려받기 가능
from IPython.display import Video  # 노트북에서 동영상 재생

Video("training.mp4", embed=True)  # 동영상을 노트북 안에 넣어 재생
  • embed=True: 동영상을 노트북 출력에 직접 넣어 Colab에서도 재생 가능
  • 영상 왼쪽 위의 학습 스텝 수가 늘어날수록 막대를 더 오래 세우는 모습 확인

학습 후 평가

  • 영상은 에피소드 1개만 보여 주므로, 여러 에피소드의 평균 보상으로 성능을 확인
from stable_baselines3.common.evaluation import evaluate_policy  # 정책 평가 함수
mean_reward, std_reward = evaluate_policy(model, env, n_eval_episodes=10)  # 에피소드 10개로 평가
  • 환경을 Monitor로 감싸라는 경고가 나올 수 있으나, 이 실습에서는 결과에 영향이 없음
mean_reward  # 평균 보상
실행 결과
np.float64(500.0)
std_reward  # 보상의 표준편차
실행 결과
np.float64(0.0)
  • 에피소드 10개 모두 최대 수익 500을 얻음 → CartPole을 사실상 해결
Previous
Actor-Critic