Reward Shaping이란?
강화학습에서 Reward Shaping은 에이전트가 더 빠르고 효율적으로 학습할 수 있도록 보상 함수를 재설계하는 기법입니다. 원래의 sparse한 보상 신호만으로는 에이전트가 목표를 달성하기까지 수천만 번의 시행착오를 거쳐야 하지만, 적절한 보상 설계를 통해 학습 속도를 극적으로 향상시킬 수 있습니다.
핵심 아이디어: 최종 목표까지의 ‘이정표’를 제공하여 에이전트가 올바른 방향으로 학습하도록 유도
하지만 잘못된 Reward Shaping은 오히려 의도하지 않은 행동을 학습시키거나 최적 정책을 왜곡할 수 있습니다. 이 글에서는 안전하고 효과적인 Reward Shaping 전략을 실전 예제와 함께 살펴봅니다.
Potential-Based Shaping: 최적 정책을 보존하는 보상 설계
왜 Potential-Based인가?
일반적인 보상 수정은 최적 정책 자체를 바꿀 위험이 있습니다. 예를 들어 로봇 팔이 목표 지점에 도달하는 과제에서 “목표와의 거리를 줄이면 보상”이라는 단순한 shaping을 추가하면, 로봇이 목표 근처에서 왔다갔다 하며 보상을 farming하는 현상이 발생할 수 있습니다.
Potential-Based Reward Shaping (PBRS)은 이 문제를 수학적으로 해결합니다. Ng et al. (1999)이 증명한 바와 같이, 다음 형태의 shaping 보상은 최적 정책을 변경하지 않습니다:
수식 해설:
– : 상태 에서 로 전이할 때 추가되는 shaping 보상
– : 상태 의 잠재 함수(potential function), 목표까지의 ‘거리’ 개념
– : 할인율 (discount factor)
– : 다음 상태의 잠재 값
직관적으로, 이는 “목표에 가까워지면 +보상, 멀어지면 -보상”을 의미하지만, potential 차이로 계산함으로써 누적 보상의 순서(최적 정책)는 유지됩니다.
실전 구현 예제
로봇 네비게이션 환경에서 목표 지점까지의 유클리드 거리를 음수로 사용하는 경우:
import numpy as np
class PotentialBasedShaping:
def __init__(self, goal_position, gamma=0.99):
self.goal = goal_position
self.gamma = gamma
def potential(self, state):
"""상태의 잠재 함수: 목표까지 거리의 음수"""
distance = np.linalg.norm(state - self.goal)
return -distance
def shaping_reward(self, state, next_state):
"""PBRS 보상 계산"""
phi_s = self.potential(state)
phi_s_next = self.potential(next_state)
return self.gamma * phi_s_next - phi_s
# 사용 예시
shaper = PotentialBasedShaping(goal_position=np.array([10.0, 10.0]))
current_state = np.array([0.0, 0.0])
next_state = np.array([1.0, 1.0])
# 원래 환경 보상 (목표 도달 시에만 +100)
original_reward = 0
# Shaping 보상 추가
shaped_reward = original_reward + shaper.shaping_reward(current_state, next_state)
print(f"Shaped Reward: {shaped_reward:.3f}") # 목표에 가까워졌으므로 양수
PBRS 설계 가이드라인
| Potential 함수 선택 | 적합한 상황 | 주의사항 |
|---|---|---|
| 목표까지 유클리드 거리 | 격자 세계, 로봇 제어 | 장애물 무시, 비현실적 경로 유도 가능 |
| A* 휴리스틱 거리 | 경로 계획, 미로 탐색 | 계산 비용 높음 |
| 목표 상태와의 특징 유사도 | 추상 상태 공간 | 특징 선택이 성능 좌우 |
| 학습된 Value 함수 | 복잡한 환경 | 초기 학습 필요, bootstrap 문제 |
Curriculum Learning: 점진적 난이도 증가
개념과 동기
사람이 수학을 배울 때 덧셈→곱셈→미적분 순서로 학습하듯, 강화학습 에이전트도 쉬운 과제부터 점차 어려운 과제로 학습하면 더 빠르게 목표에 도달합니다.
Curriculum Learning의 핵심은 다음과 같습니다:
1. 초기 단계: 성공하기 쉬운 환경 (목표가 가까움, 장애물 적음)
2. 중간 단계: 점진적으로 난이도 증가
3. 최종 단계: 실제 목표 환경
실무 구현 패턴
class CurriculumScheduler:
def __init__(self, initial_distance=1.0, final_distance=10.0, total_steps=1_000_000):
self.initial_dist = initial_distance
self.final_dist = final_distance
self.total_steps = total_steps
self.current_step = 0
def get_goal_distance(self):
"""현재 step에 따른 목표 거리 계산 (선형 증가)"""
progress = min(self.current_step / self.total_steps, 1.0)
return self.initial_dist + (self.final_dist - self.initial_dist) * progress
def step(self):
self.current_step += 1
# 환경에 적용
curriculum = CurriculumScheduler()
for episode in range(10000):
goal_distance = curriculum.get_goal_distance()
# 목표 위치를 에이전트로부터 goal_distance만큼 떨어진 곳에 배치
env.reset(goal_distance=goal_distance)
# 에피소드 실행...
for step in range(max_steps):
# 학습 로직
curriculum.step()
Curriculum 설계 전략 비교
| 전략 | 방법 | 장점 | 단점 |
|---|---|---|---|
| 고정 스케줄 | 미리 정한 난이도 곡선 | 구현 간단, 재현성 높음 | 에이전트 학습 속도 무시 |
| 성능 기반 | 성공률 80% 달성 시 난이도 상승 | 에이전트 수준에 맞춤 | 성공 기준 설정 어려움 |
| Self-Play | 과거 자신과 대결 | 자동 난이도 조정 | 다중 에이전트 환경 필요 |
| 역방향 Curriculum | 목표에서 시작해 역으로 학습 | Sparse reward 극복 | 환경 역전이 가능해야 함 |
실전 팁: 고정 스케줄로 시작한 뒤, 학습 곡선을 관찰하며 성능 기반으로 전환하는 하이브리드 방식 추천
Hindsight Experience Replay (HER): 실패를 성공으로 전환
핵심 아이디어
로봇이 물체 A를 목표 위치 G로 옮기려다 실패하고 위치 G’에 놓았다면? 기존 RL은 이를 실패로 기록하지만, HER은 “G’ 위치로 옮기기”라는 목표를 사후에 설정하여 성공 경험으로 재해석합니다.
이를 통해 sparse reward 환경에서도 매 에피소드마다 학습 신호를 얻을 수 있습니다.
알고리즘 흐름
- 원래 목표 로 에피소드 실행 → 궤적 수집
- 실패했다면 (r_T < 성공 보상)
- Hindsight 목표 생성: 궤적 중 실제 도달한 상태 를 새 목표 로 설정
- 궤적을 관점에서 재계산:
– reward - 재생 버퍼에 저장
구현 예제 (PyTorch + Stable-Baselines3 스타일)
import numpy as np
class HindsightBuffer:
def __init__(self, capacity, k=4):
self.capacity = capacity
self.k = k # 에피소드당 생성할 hindsight 목표 수
self.buffer = []
def store_episode(self, episode_trajectory, original_goal):
"""에피소드 저장 + HER 적용"""
T = len(episode_trajectory)
# 1. 원래 목표로 저장
for transition in episode_trajectory:
self.buffer.append((*transition, original_goal))
# 2. Hindsight 목표 생성 (future strategy)
for _ in range(self.k):
# 궤적 중 랜덤 시점 선택
future_idx = np.random.randint(0, T)
hindsight_goal = episode_trajectory[future_idx][3] # next_state를 목표로
# 해당 목표로 보상 재계산하여 저장
for t, (s, a, r, s_next, done) in enumerate(episode_trajectory):
if t <= future_idx: # future_idx까지만 재계산
new_reward = self.compute_reward(s_next, hindsight_goal)
new_done = self.is_success(s_next, hindsight_goal)
self.buffer.append((s, a, new_reward, s_next, new_done, hindsight_goal))
# 버퍼 크기 유지
if len(self.buffer) > self.capacity:
self.buffer = self.buffer[-self.capacity:]
def compute_reward(self, state, goal):
"""목표 달성 여부에 따른 보상"""
distance = np.linalg.norm(state - goal)
return 0.0 if distance < 0.05 else -1.0
def is_success(self, state, goal):
distance = np.linalg.norm(state - goal)
return distance < 0.05
HER 전략 비교
| 전략 | Hindsight 목표 선택 방법 | 효과 |
|---|---|---|
| final | 에피소드 마지막 상태 | 구현 단순, 다양성 부족 |
| future | 현재 이후 랜덤 미래 상태 | 가장 효과적, 균형잡힌 학습 |
| episode | 에피소드 전체에서 랜덤 | 다양성 높음, 비효율적 목표 포함 |
| random | 버퍼 전체에서 랜덤 | 거의 효과 없음 |
논문 검증: OpenAI의 원 논문에서 future 전략이 로봇 조작 과제에서 가장 빠른 학습 속도를 보였습니다.
함정 회피: 흔한 실수와 해결책
1. Reward Hacking
문제: 에이전트가 의도와 다른 방식으로 보상을 최대화
예시: 레이싱 게임에서 “전진 거리에 비례한 보상”을 주자, 에이전트가 원을 그리며 달리는 현상
해결책:
– PBRS 사용으로 최적 정책 보존
– 보상 함수에 제약 조건 추가 (예: 후진 시 페널티)
– Reward modeling으로 사람 선호도 학습
2. Over-Shaping
문제: 너무 많은 보상 신호로 탐험 저해
해결책:
# 나쁜 예: 매 스텝마다 미세한 보상
reward = 0.1 * velocity + 0.05 * alignment - 0.01 * energy
# 좋은 예: sparse + PBRS
reward = (100 if goal_reached else 0) + potential_shaping(s, s_next)
3. Non-Stationary Curriculum
문제: 난이도를 너무 빨리 올려 에이전트가 따라가지 못함
해결책:
– 성공률 모니터링: 60% 이하로 떨어지면 난이도 유지/감소
– EMA 평활화: 급격한 난이도 점프 방지
success_rate = 0.9 * success_rate + 0.1 * current_success # Exponential Moving Average
if success_rate > 0.8:
increase_difficulty()
4. HER의 목표 공간 불일치
문제: 생성된 hindsight 목표가 실제로는 도달 불가능한 상태
해결책:
– 목표 공간과 상태 공간 명확히 분리 (예: 로봇 위치만 목표로, 속도 제외)
– Feasibility check 추가
실전 통합 예제: 로봇 팔 Reaching 과제
import gym
import numpy as np
from stable_baselines3 import SAC
class ShapedReachingEnv(gym.Env):
def __init__(self):
super().__init__()
self.gamma = 0.99
self.curriculum = CurriculumScheduler()
self.her_buffer = HindsightBuffer(capacity=100000)
def step(self, action):
# 환경 시뮬레이션
next_state, original_reward, done, info = self.base_env.step(action)
# PBRS 적용
shaping = self.potential_shaping(self.state, next_state)
shaped_reward = original_reward + shaping
self.episode_buffer.append((self.state, action, shaped_reward, next_state, done))
self.state = next_state
if done:
# HER 적용
self.her_buffer.store_episode(self.episode_buffer, self.current_goal)
self.episode_buffer = []
return next_state, shaped_reward, done, info
def potential_shaping(self, s, s_next):
phi_s = -np.linalg.norm(s[:3] - self.current_goal) # 손 끝-목표 거리
phi_s_next = -np.linalg.norm(s_next[:3] - self.current_goal)
return self.gamma * phi_s_next - phi_s
def reset(self):
# Curriculum에 따른 목표 거리 조정
goal_distance = self.curriculum.get_goal_distance()
self.current_goal = self.sample_goal(distance=goal_distance)
self.state = self.base_env.reset()
self.episode_buffer = []
return self.state
# 학습
env = ShapedReachingEnv()
model = SAC("MlpPolicy", env, verbose=1)
model.learn(total_timesteps=1_000_000)
마무리
이 글에서 다룬 핵심 전략을 정리하면:
| 기법 | 해결 문제 | 핵심 원리 | 적용 난이도 |
|---|---|---|---|
| PBRS | 최적 정책 왜곡 방지 | Potential 함수 차분 | ⭐⭐ |
| Curriculum Learning | 초기 학습 가속 | 점진적 난이도 증가 | ⭐⭐⭐ |
| HER | Sparse reward 극복 | 실패를 성공으로 재해석 | ⭐⭐⭐⭐ |
실무 적용 체크리스트:
– [ ] 원본 보상 함수가 실제 목표를 정확히 반영하는가?
– [ ] PBRS potential 함수가 domain knowledge를 반영하는가?
– [ ] Curriculum 난이도가 너무 급격하지 않은가?
– [ ] HER 목표 공간이 상태 공간과 일치하는가?
– [ ] Tensorboard로 shaping 보상과 원본 보상을 분리 추적하는가?
최종 조언: Reward Shaping은 강력하지만, 먼저 vanilla RL로 베이스라인을 확보한 뒤 점진적으로 적용하세요. 각 기법의 효과를 정량적으로 비교(Ablation Study)하는 것이 성공의 열쇠입니다.
이제 여러분의 강화학습 프로젝트에 이 전략들을 적용해 학습 속도를 극대화해보세요! 🚀
Did you find this helpful?
☕ Buy me a coffee
Leave a Reply