콘텐츠로 이동
Study Note강화학습

9. 학습을 돌리는 법 — 환경·도구·디버깅

강화학습 디버깅에는 손실 곡선이라는 나침반이 없다. 그래서 순서를 정해 두고 밟는다

이 장에서 처음 나오는 말6개
Gymnasium짐네이지엄
환경의 표준 인터페이스. reset()으로 시작하고 step()으로 한 걸음 나가는 규약. OpenAI Gym의 후계다.
래퍼Wrapper
환경을 감싸서 관측·보상·종료를 가공하는 얇은 층. 정규화·프레임 스택이 전부 이것으로 붙는다.
벡터 환경Vectorized Env
환경 여러 개를 한 객체처럼 다뤄 배치로 step하는 것. 학습 속도를 좌우한다.
롤아웃Rollout
정책으로 환경을 굴려 경험을 모으는 구간. on-policy 학습의 한 사이클 단위다.
시드Seed
난수 초기값. 강화학습은 시드만 바꿔도 결과가 갈리므로 반드시 여러 개로 돌린다.
SPSSteps Per Second
초당 환경 스텝 수. 강화학습 실험의 처리량 지표이자 병목 진단의 출발점이다.

강화학습의 모든 도구가 이 인터페이스를 전제한다. 직접 문제를 정의할 때는 이 규약에 맞춰 클래스를 하나 쓰는 것이 시작이다.

import gymnasium as gym
from gymnasium import spaces
import numpy as np
class TradingEnv(gym.Env):
def __init__(self, data):
self.data = data
# 행동: 목표 비중 −1(최대 공매도) ~ +1(최대 매수)
self.action_space = spaces.Box(low=-1.0, high=1.0, shape=(1,), dtype=np.float32)
# 관측: 특징 20개 + 내 포지션 + 남은 현금
self.observation_space = spaces.Box(-np.inf, np.inf, shape=(22,), dtype=np.float32)
def reset(self, seed=None, options=None):
super().reset(seed=seed) # 난수 시드 처리 — 빠뜨리면 재현이 안 된다
self.t = 0
self.position = 0.0
return self._obs(), {} # (관측, info)
def step(self, action):
...
terminated = self.equity <= self.ruin_level # 문제 정의상 끝
truncated = self.t >= len(self.data) - 1 # 외부 데이터 경계로 잘렸을 뿐
return self._obs(), reward, terminated, truncated, info
반환값뜻틀리면
terminated문제 정의상 종료 (파산 · 넘어짐)—
truncated과제 밖의 사유로 잘림 (외부 데이터 경계 · 평가 예산)외부 중단을 실패로 배운다
info학습에 안 쓰는 부가 정보 (진단용)보상 항별 값을 여기 담아 로깅한다

래퍼 — 정규화는 여기서 붙인다

섹션 제목: “래퍼 — 정규화는 여기서 붙인다”

환경 본체를 건드리지 않고 관측·보상을 가공하는 층이다. 6장에서 본 정규화도 이 자리에 붙는다.

env = gym.make("MyEnv-v0")
env = gym.wrappers.RecordEpisodeStatistics(env) # 에피소드 리턴/길이 자동 기록
env = gym.wrappers.ClipAction(env) # 행동 범위 밖을 잘라 낸다
env = gym.wrappers.NormalizeObservation(env) # 러닝 평균·분산으로 관측 정규화
env = gym.wrappers.NormalizeReward(env) # 리턴 표준편차로 보상 스케일 조정
env = gym.wrappers.TransformObservation(env, lambda o: np.clip(o, -10, 10), env.observation_space)
래퍼무엇을 막나
NormalizeObservation스케일 큰 관측 하나가 학습을 지배하는 것
NormalizeReward가치 손실 폭주
ClipAction분포에서 뽑힌 극단 행동
FrameStackObservation부분 관측 (속도 정보 부재)
TimeLimit무한 에피소드

벡터 환경 — 처리량이 곧 실험 속도

섹션 제목: “벡터 환경 — 처리량이 곧 실험 속도”

on-policy 알고리즘은 배치가 커야 안정적이라, 환경을 여러 개 동시에 굴린다.

envs = gym.make_vec("MyEnv-v0", num_envs=32, vectorization_mode="async")
obs, _ = envs.reset(seed=0)
obs, rewards, terminations, truncations, infos = envs.step(actions) # 전부 배열로
모드언제
sync환경이 가벼울 때 (파이썬 계산). 프로세스 오버헤드가 더 크다
async환경이 무거울 때 (물리 시뮬 · 데이터 로딩)
GPU 네이티브Isaac Lab · MJX 등. 환경 자체가 GPU에서 수천 개 병렬 (15장)

마지막 것이 로봇 학습의 판을 바꿨다. CPU 환경 32개와 GPU 환경 4096개는 같은 알고리즘으로도 결과가 다른 세계다.

도구성격고를 때
Stable-Baselines3 2.9검증된 구현, scikit-learn풍 API기준선을 빨리 잡을 때. 첫 실험은 여기서
CleanRL알고리즘 하나 = 파일 하나알고리즘을 뜯어고칠 때, 공부할 때
RSL-RL / rl_games / skrl대규모 GPU 병렬 학습에 최적화Isaac Lab 기반 로봇 학습
Ray RLlib분산 학습·프로덕션 지향여러 노드로 확장할 때
  • 디렉터리envs/
    • my_env.py Gymnasium 환경 — 상태·행동·보상의 단일 원본
    • wrappers.py 정규화·마스킹 등
  • 디렉터리configs/
    • ppo_base.yaml 하이퍼파라미터는 코드가 아니라 설정 파일로
  • train.py 학습 진입점 (시드·설정을 인자로)
  • eval.py 학습과 분리된 평가 — 10장
  • 디렉터리runs/ 로그·체크포인트·정규화 통계
    • …

하이퍼파라미터를 코드에 박지 않는 것이 중요하다. 시드 5개 × 설정 3개를 돌리는 것이 기본이라, 설정을 인자로 받지 않으면 실험 관리가 즉시 무너진다.

손실 곡선이 지표가 아니므로, 볼 것을 미리 정해 둔다.

지표정상이상하면
에피소드 누적 보상과제에 맞는 개선 추세정체하면 기준선·환경·보상을 함께 점검
에피소드 길이과제에 맞게너무 짧으면 조기 종료가 학습을 막는다
보상 항별 기여도의도한 비율한 항이 다 먹고 있으면 가중치 문제
정책 엔트로피서서히 감소급락하면 탐색 붕괴 (5장)
근사 KL (PPO)절대 정상값 없음 (0.01은 흔한 출발 기준)평소보다 급증하면 학습률·에폭·클립 점검
클립 비율 (PPO)절대 정상값 없음 (0.1~0.3은 참고 범위)지속적으로 높으면 갱신 폭을 점검
설명 분산 (크리틱)1에 가까울수록 예측력이 높음0 이하면 크리틱이 상수 예측보다 낫지 않음
Q값 평균 (off-policy)실제 리턴과 비슷훨씬 크면 과대추정 (4장)
SPS안정떨어지면 버퍼·로깅·환경 병목
  1. 랜덤 정책의 성적을 먼저 잰다.

    기준선이 없으면 “안 오른다”를 판정할 수 없다. 랜덤 정책과 아무것도 안 하는 정책의 리턴을 둘 다 기록해 둔다. 학습된 정책이 후자를 못 이기면 보상 설계 문제다.

  2. 환경을 사람 손으로 굴려 본다.

    좋은 행동을 손으로 넣었을 때 보상이 실제로 커지는가. 여기서 안 커지면 알고리즘이 배울 수 있는 문제가 아니다.

  3. 아주 쉬운 버전으로 줄인다.

    상태를 최소로, 에피소드를 짧게, 목표를 가깝게. 줄인 문제도 못 풀면 환경·보상 쪽에 버그가 있다.

  4. 관측을 찍어 본다.

    범위가 −10~10을 크게 벗어나는가. NaN이 있는가. 정규화 래퍼가 붙어 있는가. 여기서 잡히는 문제가 제일 많다.

  5. 보상 항별 기여도를 본다.

    의도한 비율인가. 한 항이 지배하고 있지 않은가 (8장).

  6. 검증된 구현체로 바꿔 돌린다.

    SB3 기본값으로 되면 내 구현 문제, 안 되면 환경 문제.

  7. 그다음에야 하이퍼파라미터를 만진다.

    순서는 학습률 → 배치·롤아웃 길이 → 엔트로피 계수 → 네트워크 크기. γ는 문제 정의라 함부로 안 만진다 (2장).

해야 할 것이유
reset(seed=...) 로 환경 시드 고정환경 난수를 통제
파이썬·NumPy·PyTorch 시드 고정초기화·샘플링 통제
시드를 최소 5개 돌린다하나로는 아무 말도 못 한다 (10장)
라이브러리 버전을 고정구현 디테일이 성능을 바꾼다
정규화 통계를 체크포인트와 함께 저장위 함정 2

GPU에서 완전한 결정성은 대개 포기한다 — 비결정적 커널을 끄면 크게 느려진다. 대신 시드를 여러 개 돌려 분포로 보고하는 쪽이 표준이다.

과제대략적인 스텝 수비고
카트폴류~10만노트북 몇 분
MuJoCo 연속 제어100만~1000만GPU 한 장 몇 시간
사족 보행 (GPU 병렬 시뮬)1억~10억환경 4096개면 수십 분
조작 (픽셀 입력)수억~며칠
트레이딩 (일봉)데이터가 부족한 쪽20년 일봉 ≈ 5천 스텝. 스텝 수가 아니라 데이터 양이 한계
  • 환경은 Gymnasium 규약으로 쓴다 — 과제 종료는 terminated, 외부 중단은 truncated
  • 정규화는 환경 본체가 아니라 래퍼로 붙이고, 평가 시 학습 통계를 그대로 사용한다
  • 정규화 통계를 체크포인트와 함께 저장하지 않으면 배포 시 성능이 무너진다
  • 벡터 환경의 처리량이 곧 실험 속도다. GPU 네이티브 병렬은 다른 세계를 연다
  • 첫 실험은 검증된 구현(SB3)의 기본값으로 — 환경 문제와 구현 문제를 가르기 위해서다
  • 손실 곡선은 지표가 아니다. 누적 보상 · 엔트로피 · 설명 분산 · 항별 보상을 본다
  • 진단은 순서대로 — 기준선 → 손으로 굴려 보기 → 쉬운 버전 → 관측 확인 → 보상 확인 → 검증 구현 → 그다음에야 하이퍼파라미터
  • 시드는 최소 5개. GPU 완전 결정성은 포기하고 분포로 보고한다
  • 로봇은 계산이 병목이고, 트레이딩은 데이터가 병목이다