Policy Gradient란 무엇인가?
Policy Gradient는 강화학습에서 정책(policy)을 직접 최적화하는 방법론입니다. Q-learning 같은 Value-based 방법과 달리, 정책 를 파라미터 로 표현하고 기댓값 보상을 최대화하는 방향으로 그래디언트를 계산합니다.
핵심 아이디어: 좋은 행동의 확률은 높이고, 나쁜 행동의 확률은 낮춘다.
목적 함수는 다음과 같습니다:
여기서 는 궤적(trajectory), 는 누적 보상입니다. Policy Gradient 정리에 따르면:
- : 행동 확률의 로그 그래디언트 (“어떤 방향으로 정책을 바꿀지”)
- : 시점 부터의 누적 보상 (“얼마나 좋았는지”)
REINFORCE: 가장 기본적인 Policy Gradient
알고리즘 원리
REINFORCE는 Monte Carlo 방식으로 전체 에피소드를 수집한 뒤 그래디언트를 추정합니다.
import torch
import torch.nn as nn
import torch.optim as optim
class PolicyNetwork(nn.Module):
def __init__(self, state_dim, action_dim):
super().__init__()
self.net = nn.Sequential(
nn.Linear(state_dim, 128),
nn.ReLU(),
nn.Linear(128, action_dim),
nn.Softmax(dim=-1)
)
def forward(self, state):
return self.net(state)
def reinforce(env, policy, optimizer, gamma=0.99, episodes=1000):
for episode in range(episodes):
states, actions, rewards = [], [], []
state = env.reset()
done = False
# 에피소드 수집
while not done:
state_tensor = torch.FloatTensor(state)
probs = policy(state_tensor)
action = torch.multinomial(probs, 1).item()
next_state, reward, done, _ = env.step(action)
states.append(state_tensor)
actions.append(action)
rewards.append(reward)
state = next_state
# Return 계산 (역방향)
returns = []
G = 0
for r in reversed(rewards):
G = r + gamma * G
returns.insert(0, G)
returns = torch.FloatTensor(returns)
returns = (returns - returns.mean()) / (returns.std() + 1e-8) # 정규화
# Policy Gradient 업데이트
loss = 0
for state, action, G in zip(states, actions, returns):
probs = policy(state)
log_prob = torch.log(probs[action])
loss += -log_prob * G # 음수: gradient ascent를 descent로 변환
optimizer.zero_grad()
loss.backward()
optimizer.step()
REINFORCE의 문제점
| 문제 | 설명 |
|---|---|
| 높은 분산 | Monte Carlo 샘플링으로 인해 그래디언트 추정치의 분산이 매우 큼 |
| 샘플 비효율 | 에피소드가 끝나야 학습 가능 (장기 태스크에서 치명적) |
| 불안정한 학습 | 분산이 크면 학습이 요동치고 수렴 느림 |
A2C: Advantage를 이용한 분산 감소
Baseline과 Advantage 함수
분산을 줄이기 위해 baseline 를 도입합니다. 보통 Value 함수 를 사용:
여기서 를 Advantage라 하며, “이 행동이 평균보다 얼마나 좋은가”를 나타냅니다.
- A_t > 0: 평균보다 좋음 → 확률 증가
- A_t < 0: 평균보다 나쁨 → 확률 감소
Actor-Critic 구조
A2C는 두 개의 네트워크를 동시에 학습합니다:
- Actor : 정책 (행동 선택)
- Critic : 가치 함수 (상태 평가)
class ActorCritic(nn.Module):
def __init__(self, state_dim, action_dim):
super().__init__()
self.shared = nn.Sequential(
nn.Linear(state_dim, 128),
nn.ReLU()
)
self.actor = nn.Linear(128, action_dim)
self.critic = nn.Linear(128, 1)
def forward(self, state):
features = self.shared(state)
action_probs = torch.softmax(self.actor(features), dim=-1)
value = self.critic(features)
return action_probs, value
def a2c_update(model, optimizer, states, actions, rewards, next_states, dones, gamma=0.99):
states = torch.FloatTensor(states)
actions = torch.LongTensor(actions)
rewards = torch.FloatTensor(rewards)
next_states = torch.FloatTensor(next_states)
dones = torch.FloatTensor(dones)
# Current policy and value
probs, values = model(states)
_, next_values = model(next_states)
# TD Target과 Advantage 계산
td_target = rewards + gamma * next_values.squeeze() * (1 - dones)
advantage = td_target - values.squeeze()
# Actor loss (Policy Gradient with Advantage)
log_probs = torch.log(probs.gather(1, actions.unsqueeze(1)).squeeze())
actor_loss = -(log_probs * advantage.detach()).mean()
# Critic loss (MSE)
critic_loss = advantage.pow(2).mean()
# Entropy bonus (exploration)
entropy = -(probs * torch.log(probs + 1e-8)).sum(dim=1).mean()
loss = actor_loss + 0.5 * critic_loss - 0.01 * entropy
optimizer.zero_grad()
loss.backward()
optimizer.step()
A2C의 개선점
- 분산 감소: Baseline 사용으로 그래디언트 추정치가 안정적
- 온라인 학습: 매 스텝 업데이트 가능 (에피소드 종료 불필요)
- 병렬화: 여러 환경을 동시에 실행하여 샘플 효율 향상
PPO: 안전한 정책 업데이트
Clipped Surrogate Objective
PPO는 정책이 급격히 변하는 것을 방지합니다. importance sampling ratio를 클리핑:
PPO 목적 함수:
- : 새 정책과 옛 정책의 확률 비율
- : 클리핑 범위 (보통 0.1~0.2)
- : 를 범위로 제한
왜 클리핑?: 가 너무 크면 정책이 급변하여 학습이 불안정해집니다. 클리핑으로 보수적인 업데이트를 강제합니다.
PyTorch 구현
def ppo_update(model, optimizer, memory, epochs=4, clip_epsilon=0.2, gamma=0.99, gae_lambda=0.95):
states, actions, old_log_probs, rewards, next_states, dones = memory
states = torch.FloatTensor(states)
actions = torch.LongTensor(actions)
old_log_probs = torch.FloatTensor(old_log_probs)
# GAE (Generalized Advantage Estimation) 계산
with torch.no_grad():
_, values = model(states)
_, next_values = model(next_states)
advantages = []
gae = 0
for t in reversed(range(len(rewards))):
delta = rewards[t] + gamma * next_values[t] * (1 - dones[t]) - values[t]
gae = delta + gamma * gae_lambda * (1 - dones[t]) * gae
advantages.insert(0, gae)
advantages = torch.FloatTensor(advantages)
returns = advantages + values.squeeze()
advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-8)
# Multiple epochs of SGD
for _ in range(epochs):
probs, values = model(states)
log_probs = torch.log(probs.gather(1, actions.unsqueeze(1)).squeeze())
# Ratio와 Clipped Objective
ratio = torch.exp(log_probs - old_log_probs)
surr1 = ratio * advantages
surr2 = torch.clamp(ratio, 1 - clip_epsilon, 1 + clip_epsilon) * advantages
actor_loss = -torch.min(surr1, surr2).mean()
# Value loss
critic_loss = (returns - values.squeeze()).pow(2).mean()
# Entropy bonus
entropy = -(probs * torch.log(probs + 1e-8)).sum(dim=1).mean()
loss = actor_loss + 0.5 * critic_loss - 0.01 * entropy
optimizer.zero_grad()
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), 0.5) # Gradient clipping
optimizer.step()
GAE (Generalized Advantage Estimation)
PPO는 보통 GAE를 사용하여 Advantage를 추정합니다:
여기서 (TD error).
- : 1-step TD (분산 낮음, 편향 높음)
- : Monte Carlo (분산 높음, 편향 없음)
- : 적절한 균형 (실무 표준)
세 알고리즘 비교
| 특징 | REINFORCE | A2C | PPO |
|---|---|---|---|
| 그래디언트 추정 | Monte Carlo | TD (Temporal Difference) | Clipped Importance Sampling |
| Baseline | 없음 (또는 단순 평균) | Value 함수 | Value 함수 + GAE |
| 업데이트 빈도 | 에피소드 종료 후 | 매 스텝 | 배치 수집 후 여러 epoch |
| 안정성 | 낮음 (분산 큼) | 중간 | 높음 (클리핑으로 보수적 업데이트) |
| 샘플 효율 | 낮음 | 중간 | 높음 (데이터 재사용) |
| 구현 복잡도 | 쉬움 | 중간 | 어려움 |
| 실무 활용 | 간단한 실험 | 실시간 학습 | 대부분의 SOTA 벤치마크 |
실전 팁
1. 하이퍼파라미터 튜닝
- Learning rate: Actor는 작게 (1e-4~3e-4), Critic은 크게 (1e-3)
- Discount factor : 0.99 (장기 보상), 0.9 (단기 보상)
- GAE : 0.95가 안정적
- PPO clip : 0.1~0.2 (클수록 공격적 업데이트)
2. 트릭들
| 기법 | 효과 |
|---|---|
| Reward normalization | 보상 스케일 안정화 |
| Advantage normalization | 그래디언트 안정화 |
| Gradient clipping | 폭발 방지 |
| Entropy bonus | 탐험 유도 (초반에 크게, 후반에 작게) |
| Learning rate decay | 후반 미세 조정 |
3. 디버깅 체크리스트
- [ ] 정책 엔트로피가 점점 감소하는가? (탐험→활용)
- [ ] Critic loss가 수렴하는가?
- [ ] Clipping fraction이 5~30%인가? (너무 높으면 업데이트 제한 과도)
- [ ] Explained variance가 0.7 이상인가? (Critic이 Return을 잘 예측)
마무리
Policy Gradient는 강화학습의 핵심 방법론입니다. REINFORCE는 개념적으로 단순하지만 분산이 크고, A2C는 Baseline으로 분산을 줄이고 온라인 학습을 가능케 하며, PPO는 클리핑과 GAE로 안정적이고 샘플 효율적인 학습을 달성합니다.
실무에서는 PPO가 사실상의 표준입니다. 로봇 제어, 게임 AI, 대규모 언어 모델의 RLHF(Reinforcement Learning from Human Feedback)까지 폭넓게 사용되며, 안정성과 성능이 검증되었습니다.
코드를 직접 실행해보며 각 알고리즘의 차이를 체감하고, 하이퍼파라미터를 조정하면서 그래디언트 추정과 분산 감소의 중요성을 경험해보세요. Policy Gradient를 마스터하면 최신 강화학습 논문을 이해하는 데 필수적인 기반을 갖추게 됩니다.
Did you find this helpful?
☕ Buy me a coffee
Leave a Reply