qcoding

[강화학습-9] 정책 기반 방법 (Policy Gradient) – REINFORCE 본문

머신러닝 딥러닝

[강화학습-9] 정책 기반 방법 (Policy Gradient) – REINFORCE

Qcoding 2025. 5. 28. 17:25
반응형
9. 정책 기반 방법 (Policy Gradient) – REINFORCE

9. 정책 기반 방법 (Policy Gradient)

가치기반(Q) 방법이 “가치를 올리며 탐욕적 행동”에 의존한다면, 정책 기반(Policy Gradient)“정책 그 자체”를 매개변수화해 곧바로 최적화를 시도합니다.


9-1. REINFORCE 알고리즘

Montezuma’s Revenge처럼 희소 · 고차원 보상 환경에서, 미끄러운 Q 값 추정 대신 정책 확률 $\pi_\theta(a\mid s)$ 를 직접 조정하는 접근입니다.

$$\nabla_\theta J(\theta)= \mathbb{E}_{\pi_\theta}\!\bigl[ G_t \,\nabla_\theta \log\pi_\theta(A_t\mid S_t) \bigr] \quad\text{(REINFORCE)}$$
  • $J(\theta)=\mathbb{E}_{\pi_\theta}[G_0]$ : 기대 Return.
  • $G_t$ : 에피소드 종단까지 할인 누적 보상.
  • “로그 likelihood trick” 덕분에 환경 동적모델 $P$ 미분 불필요.

9-2. 기울기 추정과 분산 감소

기법수식효과
Baseline
(상수 or $b(s)$)
$$\nabla_\theta J = \mathbb{E}\bigl[(G_t-b)\nabla_\theta \log\pi_\theta\bigr]$$ $b$ 는 기댓값에 영향을 주지 않지만
분산을 크게 낮춤
Advantage $$A_t = G_t - V_\phi(S_t)$$ 상태가치 함수 $V_\phi$ 학습 후 사용
REINFORCE + Baseline 의 일반화
GAE(λ) $$A_t^{(\lambda)}=\sum_{k=0}^{\infty} (\gamma\lambda)^k\,\delta_{t+k}$$ $\lambda$ 로 Bias–Variance 절충

첫걸음으로는 “상수 베이스라인 ↔ 에피소드 평균” 만으로도 학습 안정성이 꽤 오릅니다.


9-3. 예제 – 1차원 연속 제어 (MountainCarContinuous-v0)

MountainCarContinuous-v0하나의 연속 행동($a\in[-1,1]$) 만으로 언덕을 넘어야 하는 1-D 컨트롤 환경입니다. 아래 코드는 Gaussian 정책 REINFORCE + Baseline (≈ 120 줄) 로 문제를 해결합니다.

"""
pip install gymnasium torch numpy
"""
import gymnasium as gym
import numpy as np
import torch, torch.nn as nn, torch.optim as optim

ENV_ID     = "MountainCarContinuous-v0"
EPISODES   = 1200
GAMMA      = 0.99
LR_ACTOR   = 1e-3
LR_CRITIC  = 2e-3
BATCH_SIZE = 5         # 에피소드 묶음 학습
DEVICE     = "cuda" if torch.cuda.is_available() else "cpu"

env = gym.make(ENV_ID)
obs_dim = env.observation_space.shape[0]
act_dim = env.action_space.shape[0]

# --- 네트워크 -----------------------------------------------------------
class Actor(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(obs_dim, 128), nn.Tanh(),
            nn.Linear(128, 128),     nn.Tanh()
        )
        self.mu     = nn.Linear(128, act_dim)
        self.logstd = nn.Parameter(torch.zeros(act_dim))   # 학습 가능

    def forward(self, x):
        z = self.net(x)
        return self.mu(z)

    def get_dist(self, x):
        mu = self.forward(x)
        std = self.logstd.exp()
        return torch.distributions.Normal(mu, std)

class Critic(nn.Module):
    def __init__(self):
        super().__init__()
        self.v = nn.Sequential(
            nn.Linear(obs_dim, 128), nn.Tanh(),
            nn.Linear(128, 1)
        )
    def forward(self, x):
        return self.v(x)

actor  = Actor().to(DEVICE)
critic = Critic().to(DEVICE)
opt_a  = optim.Adam(actor.parameters(),  lr=LR_ACTOR)
opt_c  = optim.Adam(critic.parameters(), lr=LR_CRITIC)

# --- Rollout 저장용 ------------------------------------------------------
class Trajectory:
    def __init__(self):
        self.states, self.actions = [], []
        self.rewards, self.dones  = [], []

    def push(self, s,a,r,d):
        self.states.append(s); self.actions.append(a)
        self.rewards.append(r); self.dones.append(d)

    def finish(self):
        # Discounted Return + Advantage
        R, G, A = 0, [], []
        for r in self.rewards[::-1]:
            R = r + GAMMA * R
            G.insert(0, R)
        with torch.no_grad():
            V = critic(torch.tensor(self.states, dtype=torch.float32, device=DEVICE)).squeeze().cpu().numpy()
        A = np.array(G) - V
        return np.array(self.states), np.array(self.actions), np.array(G), A

buffer = []

# --- 학습 루프 ----------------------------------------------------------
for ep in range(1, EPISODES+1):
    state, _ = env.reset()
    traj = Trajectory()
    ep_reward = 0
    done = False

    while not done:
        state_v = torch.tensor(state, dtype=torch.float32, device=DEVICE)
        dist = actor.get_dist(state_v)
        action = dist.sample().clamp(-1.0, 1.0)    # env limits
        logprob = dist.log_prob(action).sum()

        next_state, reward, terminated, truncated, _ = env.step(action.cpu().numpy())
        done = terminated or truncated

        traj.push(state, action.cpu().numpy(), reward, done)
        state = next_state
        ep_reward += reward

    buffer.append(traj.finish())

    # --- 배치 학습 ------------------------------------------------------
    if ep % BATCH_SIZE == 0:
        states = torch.tensor(np.concatenate([b[0] for b in buffer]),
                              dtype=torch.float32, device=DEVICE)
        actions= torch.tensor(np.concatenate([b[1] for b in buffer]),
                              dtype=torch.float32, device=DEVICE)
        returns= torch.tensor(np.concatenate([b[2] for b in buffer]),
                              dtype=torch.float32, device=DEVICE)
        adv     = torch.tensor(np.concatenate([b[3] for b in buffer]),
                              dtype=torch.float32, device=DEVICE)

        # [1] Critic update (MSE)
        v_pred  = critic(states).squeeze()
        loss_v  = nn.functional.mse_loss(v_pred, returns)
        opt_c.zero_grad(); loss_v.backward(); opt_c.step()

        # [2] Actor update (Policy Gradient with baseline)
        dist    = actor.get_dist(states)
        logprob = dist.log_prob(actions).sum(dim=1)
        loss_pi = -(logprob * adv).mean()
        opt_a.zero_grad(); loss_pi.backward(); opt_a.step()

        buffer.clear()   # flush

    if ep % 50 == 0:
        print(f"Ep {ep:4d} | Return {ep_reward:6.1f}")

print("학습 완료!")
env.close()
  • MountainCarContinuous 는 평균 Return ≥ 90 (200 만점) 이면 솔브(OpenAI Gym 기준).
  • Tip : 표준편차 logstd 를 학습가능 파라미터로 두면 탐험 강도를 자동 조절.

9-4. 요약 & 다음 편 예고

  • 정책 그 자체를 매개변수화하면 확률적 행동·연속 행동 공간 모두 자연스럽게 지원.
  • REINFORCE 기울기는 $\log\pi$ trick 으로 구하고, Baseline / Advantage 로 분산을 낮춘다.
  • 1-D 연속 제어 문제는 “Gaussian 정책 + REINFORCE + Critic Baseline” 조합만으로 충분히 해결.

다음 글 : Advantage 를 좀 더 효율적으로 사용하는 Actor-Critic 구조 (A2C/A3C, PPO, SAC) 를 살펴보고, 보다 복잡한 연속 제어(MuJoCo HalfCheetah 등)에 도전합니다.


참고 자료

  • Sutton et al., “Policy Gradient Methods for Reinforcement Learning with Function Approximation,” 1999
  • Williams, “Simple Statistical Gradient-Following Algorithms for Connectionist RL,” 1992 (REINFORCE 원논문)
  • Schulman et al., “High-Dimensional Continuous Control Using Generalized Advantage Estimation,” 2016
반응형
Comments