이 글은 서강대 가상융합 전문대학원(구 메타버스 전문대학원) 강화학습 자료를 기반으로 작성되었습니다.
3강: 첫 번째 실습 CartPole: 엡실론-그리디(ε-greedy)로 균형 잡기
강화학습은 목표 지향적 학습과 의사결정을 자동화하는 계산적 접근으로, 에이전트가 환경과 상호작용하며 보상 신호를 통해 누적 보상을 극대화하도록 학습한다.에이전트는 각 시점 t에서 관찰 $o_t$를 받아 정책 $π(a|o)$에 따라 행동$a_t$를 선택하고, 환경은 다음 상태로 전이하며 보상 $r_{t+1}$을 제공한다. 상태 $S_t$는 완전한 환경 정보를 포함하지만 관찰 $o_t$는 불완전할 수 있다.
Action space는 이산형과 연속형으로 구분된다. 이산형은 왼쪽·오른쪽 등 유한 선택지이고, 연속형은 실수 벡터로 힘이나 각도 같은 제어를 표현한다.
에피소드는 시작부터 종료까지의 전체 상호작용 순서이며, 목표 달성·실패 또는 시간 제한(ex: 500 스텝)으로 끝난다. Gymnasium 에서는 terminated와 truncated 값으로 종료 여부를 구분한다. 보상은 행동 직후 환경이 주는 즉각적 피드백이다. Return $G_t$는 누적 보상의 합으로 표현되며, 감쇠계수 γ 를 도입해 시간이 지남에 따른 가치 감소를 반영한다. γ 가 0이면 즉시 보상만 중요하고 1이면 모든 미래 보상을 동등하게 취급하며, 0.9~0.99 구간이 일반적 균형점이다. 정책 $π$는 상태에 대한 행동 선택 전략으로, 결정론적 정책은 항상 같은 행동을 택하고 확률적 정책은 탐험을 허용한다. 강화학습의 핵심 문제는 현재 가장 좋은 행동을 선택하는 ‘활용’과 새로운 정보를 얻는 ‘탐험’ 간의 균형이다. 이는 식당 선택 또는 투자 결정처럼 실생활 의사결정에도 비유된다. 이 문제를 단순하고 효과적으로 해결하는 대표적인 방법이 ε-Greedy 전략이다. 이 전략은 확률 $1 - \epsilon$ 로 현재 가장 좋은 행동을 선택하고, 확률 $\epsilon$로 무작위 행동을 선택하여 일정 수준의 탐험을 보장한다. ε 값이 작을수록 활용(exploitation)이 강화되고, 값이 클수록 탐험(exploration)이 늘어난다. 실험 초기에는 ε을 0.1 또는 0.01로 설정해 다양한 시도를 하게 한 뒤, 시간이 지남에 따라 점진적으로 줄여 안정된 학습 결과를 얻는다.
CartPole 강화학습 구성
CartPole 환경은 강화학습의 기본 개념을 시각적으로 이해하기 좋은 고전적 예시다. 좁은 트랙 위에 카트가 놓여 있고, 그 위에 막대가 세워져 있다. 에이전트의 목표는 이 막대가 넘어지지 않도록 왼쪽이나 오른쪽으로 카트를 움직여 균형을 유지하는 것이다. 이러한 CartPole을 강화학습을 실제로 구현하기 위한 첫 단계는 환경(environment)을 정의하는 일이다. 이 환경은 수레(cart) 위에 세워진 막대(pole)가 넘어지지 않도록 균형을 유지하는 문제로, 에이전트는 수레를 왼쪽 또는 오른쪽으로 움직여 막대를 세워야 한다. 환경은 네 개의 변수, 즉 위치(position), 속도(velocity), 각도(angle), 각속도(angular velocity)로 상태를 표현하며, 에이전트는 두 가지 행동(왼쪽 또는 오른쪽으로 이동)을 선택할 수 있다. 폴이 12° 이상 기울거나 수레가 중심에서 ±2.4를 벗어나면 에피소드가 종료된다.
환경이 준비되면, 에이전트가 어떻게 행동해야 하는지를 정의해야 한다. 여기서는 학습 기반 정책 대신, 휴리스틱(heuristic)이라는 단순한 물리 법칙을 이용한다. 이는 “막대가 어느 방향으로 기울면, 그 방향으로 수레를 민다”라는 직관적인 규칙이다. 이를 기반으로 heuristic_policy()을 구현한다. 이 함수는 입력으로 주어진 상태 값(θ, θ̇)을 이용해 제어 신호를 계산하고, 그 값이 양수면 오른쪽으로, 음수면 왼쪽으로 이동하도록 설정한다. 이 설계는 수식적으로 복잡하지 않지만, 물리적으로 타당하다. 막대가 오른쪽으로 기울면 오른쪽으로 카트를 밀어 중심을 회복하려는 ‘피드백 제어’ 원리와 같다. 하지만 순수한 휴리스틱은 항상 같은 반응을 보여 환경의 다양한 상황에 적응하지 못한다. 이 한계를 극복하기 위해 ε-Greedy 정책을 도입한다. 이 방법은 대부분의 시간에는 휴리스틱 정책을 따르지만, 확률 ε만큼은 무작위 행동을 수행해 새로운 상태를 탐색한다. 예를 들어 ε=0.1이라면 10% 확률로 전혀 다른 행동을 시도한다. 이 전략을 구현한 함수는 create_epsilon_greedy_policy()로, 휴리스틱 정책의 결정론적 성격에 ‘탐험 확률’을 추가하는 래퍼(wrapper) 역할을 한다. 일반적으로 탐험이 과도하면 불안정해지고, 너무 적으면(ε=0) 환경을 충분히 학습하지 못할 수 있기 때문에, 적절한 탐험과 활용의 균형이 요구되기도 한다.
각 스텝마다 주어지는 보상은 즉각적인 피드백에 불과하다. 그러나 강화학습의 목표는 장기적으로 높은 누적 보상(Return)을 얻는 것이다. 이를 위해 calculate_returns() 함수를 구현한다. 이 함수는 에피소드가 끝난 후 보상 리스트를 역순으로 훑으며, 각 시점의 감쇠 반환

을 계산한다. 감쇠 계수 γ는 미래 보상의 가치가 얼마나 유지되는지를 나타내는 상수로, γ가 0이면 현재 보상만 고려하고, 1이면 모든 미래 보상을 동등하게 취급한다. 일반적으로 0.9~0.99가 가장 안정적인 학습 결과를 낸다. 이 함수는 보상을 단순히 누적하는 것이 아니라, 시간이 지남에 따라 ‘미래 가치’를 현재 가치로 환산하는 강화학습의 핵심 수학적 개념을 구현한다.
정책이 제대로 작동하는지를 판단하기 위해서는 반복적 평가가 필요하다. evaluate_policy() 함수는 동일한 정책을 여러 에피소드에 걸쳐 실행하고, 각 에피소드의 총 보상을 기록해 평균 성능을 계산하기 위해 구현한다. 이렇게 하면 단일 실행 결과의 우연성을 줄이고, 정책의 일반적인 안정성을 평가할 수 있다. 이후 실험의 결과와 로그를 TensorBoard에 기록하여 시각화한다. 이 시각화 도구는 정책별 에피소드 보상, 평균 보상, 보상 분포를 그래프로 표현하여, 학습의 추세를 한눈에 확인할 수 있게 한다. 이를 통해 연구자는 단순히 수치가 아니라, 정책의 변화를 “보이는 데이터”로 이해할 수 있다.
이 일련의 구현은 “에이전트가 세상을 이해하고, 실험을 통해 스스로 학습하며, 행동의 결과를 수치와 시각으로 평가한다”는 강화학습의 본질을 실습 중심으로 체화하도록 설계되어 있다. 즉, 이론이 코드가 되고, 코드가 실험으로, 실험이 이해로 이어지는 하나의 완결된 순환 구조를 구현한다.
위 내용을 구현한 예제 코드는 다음과 같다. 여기서 중요한 점은 policy, $\epsilon$ greedy policy, return 울 정의해주고, 나머지는 gymnasium의 라이브러리를 활용하면 된다는 점이다. 이 예제는 gymnasium에서 cartPole을 다루었기 때문이고, 실제 custom 강화학습을 구현한다면 __init__[행동/상태 정의], reset[초기화], step[법칙] 함수도 별도로 구현(하단 예제 참조)해야 한다.
import gymnasium as gym
import numpy as np
from torch.utils.tensorboard import SummaryWriter
import datetime
# ==========================================
# 1. 정책 정의 (Policy Definitions)
# ==========================================
# 만약 상태 데이터가 많다면, heuristic_policy 대신 DQN, PPO를 적용
def heuristic_policy(state):
"""
[물리 기반 휴리스틱 정책]
논리: 막대가 기울어지는 방향(각도)과 속도(각속도)를 고려하여
넘어지려는 방향으로 카트를 밀어 균형을 맞춥니다.
state = [ 위치, 속도, 각도, 각속도]
"""
theta = state[2] # 막대의 각도
theta_dot = state[3] # 막대의 각속도
# 가중치를 둔 합산으로 행동 결정 (단순 PD 제어와 유사)
action_value = theta + 0.1 * theta_dot
if action_value > 0:
return 1
else:
return 0
def random_policy(state):
"""
[무작위 정책]
논리: 상태를 전혀 보지 않고, 50% 확률로 찍기
"""
return np.random.choice([0, 1])
def create_epsilon_greedy_policy(base_policy, epsilon, env):
"""
[ε-Greedy 정책 래퍼]
논리: ε 확률로 무작위 행동(탐험)을 하고,
나머지 (1-ε) 확률로 선택된 기본 정책(활용)을 따릅니다.
"""
def policy_fn(state):
if np.random.rand() < epsilon:
# 탐험: 무작위 행동
return env.action_space.sample()
else:
# 활용: 선택된 기본 정책 (휴리스틱 or 랜덤)
return base_policy(state)
return policy_fn
# ==========================================
# 2. 유틸리티 함수 (Utilities)
# ==========================================
def calculate_returns(rewards, gamma=0.99):
"""
[감쇠 반환(Discounted Return) 계산]
"""
returns = []
G_t = 0
for r in reversed(rewards):
G_t = r + gamma * G_t
returns.insert(0, G_t)
return returns
# ==========================================
# 3. 메인 실험 루프 (Main Experiment Loop)
# ==========================================
def run_experiment(env_name='CartPole-v1', num_episodes=200, epsilon=0.1, gamma=0.99, policy_type='heuristic'):
"""
policy_type: 'heuristic' 또는 'random' 문자열을 받아 해당 정책을 실행합니다.
"""
# 환경 생성
env = gym.make(env_name)
# --- [수정된 부분] 정책 선택 로직 ---
if policy_type == 'heuristic':
base_policy_func = heuristic_policy
print(f"▶ 선택된 정책: 물리 기반 휴리스틱 (Heuristic Policy)")
elif policy_type == 'random':
base_policy_func = random_policy
print(f"▶ 선택된 정책: 완전 무작위 (Random Policy)")
else:
raise ValueError("policy_type은 'heuristic' 또는 'random' 이어야 합니다.")
# TensorBoard 로그 이름에 정책 타입을 포함 (비교 분석 용이)
log_dir = f"runs/cartpole_{policy_type}_" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
writer = SummaryWriter(log_dir)
# 선택된 base_policy_func를 사용하여 ε-Greedy 정책 생성
policy = create_epsilon_greedy_policy(base_policy_func, epsilon, env)
print(f"=== 실험 시작: {env_name} (Episodes: {num_episodes}, Epsilon: {epsilon}) ===")
for episode in range(num_episodes):
state, _ = env.reset()
done = False
episode_rewards = []
while not done:
action = policy(state)
next_state, reward, terminated, truncated, _ = env.step(action)
episode_rewards.append(reward)
state = next_state
done = terminated or truncated
returns = calculate_returns(episode_rewards, gamma)
total_reward = sum(episode_rewards)
# TensorBoard 기록
writer.add_scalar('Performance/Total_Reward', total_reward, episode)
writer.add_scalar('Performance/Discounted_Return_G0', returns[0], episode)
writer.add_scalar('Stats/Episode_Length', len(episode_rewards), episode)
if (episode + 1) % 20 == 0:
print(f"Episode {episode+1}/{num_episodes} | Total Reward: {total_reward:.1f} | Return(G0): {returns[0]:.2f}")
env.close()
writer.close()
print(f"=== 실험 종료 ===")
print(f"로그 저장 경로: {log_dir}")
print(f"결과 확인: tensorboard --logdir=runs")
# ==========================================
# 4. 실행 설정 (Configuration)
# ==========================================
if __name__ == "__main__":
# ★★★ 여기서 정책을 선택하세요 ('heuristic' 또는 'random') ★★★
SELECTED_POLICY = 'random'
run_experiment(
env_name='CartPole-v1',
num_episodes=300,
epsilon=0.1,
gamma=0.99,
policy_type=SELECTED_POLICY # 위에서 설정한 변수 적용
)
# custom 강화학습 구현시 추가로 구현 필요
import gymnasium as gym
from gymnasium import spaces
import numpy as np
class MyStockEnv(gym.Env): # gym.Env를 상속받아 규격을 맞춤
def __init__(self):
super(MyStockEnv, self).__init__()
# 행동 정의: 0=매수, 1=매도
self.action_space = spaces.Discrete(2)
# 상태 정의: [주가, 잔고]
self.observation_space = spaces.Box(low=0, high=np.inf, shape=(2,), dtype=np.float32)
def reset(self, seed=None, options=None):
# 초기화 로직 (1일차 주가, 초기 자금 설정)
self.state = np.array([10000.0, 1000000.0], dtype=np.float32)
return self.state, {}
def step(self, action):
# 1. 행동에 따른 상태 변화 (가장 어려운 부분: 주식 시장 시뮬레이션 구현)
current_price = self.state[0]
balance = self.state[1]
if action == 0: # 매수 로직
# ... 잔고 감소, 주식 보유 증가 ...
pass
# 2. 다음 상태 계산 (내일 주가 불러오기)
next_price = current_price * 1.05 # 예: 5% 상승 가정
self.state[0] = next_price
# 3. 보상 계산 (내가 정해야 함!)
reward = (self.state[1] - 1000000.0) # 수익금을 보상으로
# 4. 종료 조건
done = False
if self.state[1] < 0: # 파산하면 종료
done = True
return self.state, reward, done, False, {}'Reinforcement learning' 카테고리의 다른 글
| [강화학습 정복하기] 5강: Q-learning vs SARSA: 지도 없이 길을 찾는 방법 (0) | 2025.12.22 |
|---|---|
| [강화학습 정복하기] 4강: MDP와 벨만 방정식: 강화학습을 지탱하는 수학적 뼈대 (0) | 2025.12.22 |
| [강화학습 정복하기] 2강: 강화학습을 위한 PyTorch 기초: 텐서부터 오토그라드까지 (0) | 2025.12.22 |
| [강화학습 정복하기] 1강: 인공지능은 어떻게 걷는 법을 배우나? (0) | 2025.12.22 |
| Lecture 10: Classic Games (0) | 2025.03.28 |