
강화학습(Reinforcement Learning) 기초 개념 정리
강화학습(Reinforcement Learning) 기초 개념 정리
인공지능의 세 가지 주요 학습 방법 중 하나인 강화학습(Reinforcement Learning, RL) 은 AlphaGo, ChatGPT, 자율주행 등 현대 AI의 핵심 기술입니다. 이 글에서는 강화학습의 기본 개념부터 핵심 알고리즘의 분류까지, AI 입문자도 이해할 수 있도록 상세히 설명합니다.
1. 강화학습이란?
세 가지 학습 패러다임
| 학습 방법 | 데이터 | 목표 | 예시 |
|---|---|---|---|
| 지도학습 | 입력-정답 쌍 | 정답 예측 | 이미지 분류, 번역 |
| 비지도학습 | 정답 없는 데이터 | 패턴 발견 | 클러스터링, 차원 축소 |
| 강화학습 | 환경과의 상호작용 | 보상 최대화 | 게임 AI, 로봇 제어 |
강화학습은 에이전트(Agent) 가 환경(Environment) 과 상호작용하며, 보상(Reward) 을 최대화하는 행동(Action) 을 스스로 학습하는 방법입니다.
강화학습의 핵심 아이디어
┌─────────────────────────────────────────────────────┐ │ 환경 (Environment) │ │ │ │ 상태(State) ──────────────────────► 에이전트 │ │ ▲ │ │ │ │ │ │ │ │ ▼ │ │ 보상(Reward) ◄──────────────────── 행동(Action) │ │ │ └─────────────────────────────────────────────────────┘
실생활 비유: 강아지 훈련
- 강아지(에이전트)가 "앉아" 명령에 앉으면(행동) 간식(보상)을 받습니다
- 강아지는 어떤 행동이 간식을 가져오는지 스스로 학습합니다
- 시행착오를 통해 점점 더 좋은 행동을 선택하게 됩니다
2. 핵심 개념과 용어
MDP (Markov Decision Process)
강화학습 문제는 대부분 마르코프 결정 과정(MDP) 으로 모델링됩니다.
MDP의 구성 요소:
- S: 상태 공간 (State Space) - 가능한 모든 상태의 집합
- A: 행동 공간 (Action Space) - 가능한 모든 행동의 집합
- P(s'|s,a): 전이 확률 - 상태 s에서 행동 a를 취했을 때 s'로 이동할 확률
- R(s,a,s'): 보상 함수 - 전이에 대한 즉각적인 보상
- γ (gamma): 할인율 - 미래 보상의 현재 가치 (0~1)
마르코프 속성 (Markov Property)
"미래는 현재에만 의존하고, 과거에는 의존하지 않는다"
# 마르코프 속성 예시 P(s_{t+1} | s_t, a_t) = P(s_{t+1} | s_0, a_0, s_1, a_1, ..., s_t, a_t)
현재 상태만 알면 미래를 예측할 수 있다는 가정입니다. 체스에서 현재 보드 상태만 보면 게임을 진행할 수 있는 것과 같습니다.
정책 (Policy)
정책 π(a|s) 는 상태 s에서 행동 a를 선택할 확률을 정의합니다.
# 결정론적 정책 (Deterministic Policy) # 상태마다 하나의 행동을 확정적으로 선택 def deterministic_policy(state): if state == "배고픔": return "밥 먹기" elif state == "졸림": return "자기" else: return "공부하기" # 확률론적 정책 (Stochastic Policy) # 상태에서 여러 행동을 확률적으로 선택 def stochastic_policy(state): if state == "배고픔": return {"밥 먹기": 0.8, "간식 먹기": 0.2} else: return {"공부하기": 0.6, "게임하기": 0.4}
가치 함수 (Value Function)
상태 가치 함수 V(s): 상태 s에서 시작하여 정책 π를 따랐을 때 얻을 수 있는 기대 누적 보상
행동 가치 함수 Q(s,a): 상태 s에서 행동 a를 취한 후 정책 π를 따랐을 때의 기대 누적 보상
할인율 (Discount Factor) γ
할인율은 미래 보상의 현재 가치를 조절합니다.
# 할인율의 효과 예시 gamma = 0.99 # 미래 보상을 거의 그대로 반영 # gamma = 0.9 # 미래 보상을 90%만 반영 # gamma = 0.5 # 미래 보상을 절반만 반영 # 시간에 따른 보상 가치 rewards = [1, 1, 1, 1, 1] # 매 시점 보상 1 discounted_sum = sum(gamma**t * r for t, r in enumerate(rewards)) # gamma=0.99: 약 4.9 # gamma=0.5: 약 1.9
- γ ≈ 1: 장기적 보상 중시 (체스, 바둑)
- γ ≈ 0: 즉각적 보상 중시 (긴급한 상황)
3. 벨만 방정식 (Bellman Equation)
가치 함수의 핵심인 벨만 방정식은 "현재 가치 = 즉각 보상 + 할인된 미래 가치" 관계를 나타냅니다.
벨만 기대 방정식
벨만 최적 방정식
최적 정책 π*에서의 가치 함수:
코드로 이해하기
import numpy as np def bellman_update(V, state, actions, transitions, rewards, gamma=0.99): """ 벨만 최적 방정식을 이용한 가치 함수 업데이트 """ max_value = float('-inf') for action in actions: value = 0 for next_state, prob in transitions[state][action].items(): reward = rewards[state][action][next_state] value += prob * (reward + gamma * V[next_state]) max_value = max(max_value, value) return max_value # 예시: 간단한 그리드 월드 states = ['A', 'B', 'C', 'goal'] V = {s: 0 for s in states} # 값 반복(Value Iteration) 알고리즘 for iteration in range(100): new_V = {} for state in states: if state == 'goal': new_V[state] = 0 # 종료 상태 else: new_V[state] = bellman_update(V, state, ...) V = new_V
4. 탐험과 활용 (Exploration vs Exploitation)
강화학습의 가장 중요한 딜레마 중 하나입니다.
딜레마 이해하기
- 활용 (Exploitation): 현재까지 알고 있는 최선의 행동 선택
- 탐험 (Exploration): 더 좋은 행동을 찾기 위해 새로운 시도
예시: 새로운 도시에서 저녁 식사
- 활용: 어제 맛있었던 식당에 다시 가기
- 탐험: 새로운 식당 시도하기
ε-greedy 전략
가장 간단하고 널리 사용되는 방법:
import random def epsilon_greedy(Q, state, epsilon=0.1): """ 확률 ε으로 무작위 행동, (1-ε)으로 최적 행동 선택 """ if random.random() < epsilon: # 탐험: 무작위 행동 return random.choice(list(Q[state].keys())) else: # 활용: Q값이 가장 높은 행동 return max(Q[state], key=Q[state].get) # ε 감소 스케줄 (학습이 진행될수록 탐험 줄임) def get_epsilon(episode, min_eps=0.01, decay=0.995): return max(min_eps, 1.0 * (decay ** episode))
다른 탐험 전략들
import numpy as np # 1. Boltzmann (Softmax) 탐험 def boltzmann_action(Q, state, temperature=1.0): """온도가 높으면 더 무작위, 낮으면 탐욕적""" q_values = np.array(list(Q[state].values())) exp_q = np.exp(q_values / temperature) probs = exp_q / exp_q.sum() actions = list(Q[state].keys()) return np.random.choice(actions, p=probs) # 2. UCB (Upper Confidence Bound) def ucb_action(Q, state, N, c=2): """불확실한 행동에 보너스 부여""" total = sum(N[state].values()) ucb_values = {} for action in Q[state]: if N[state][action] == 0: return action # 한 번도 시도 안 한 행동 우선 bonus = c * np.sqrt(np.log(total) / N[state][action]) ucb_values[action] = Q[state][action] + bonus return max(ucb_values, key=ucb_values.get)
5. 강화학습 알고리즘 분류
대분류: Model-Based vs Model-Free
강화학습 알고리즘 ├── Model-Based (모델 기반) │ ├── 환경의 전이 확률 P(s'|s,a)를 알거나 학습 │ ├── 계획(Planning)이 가능 │ └── 예: Dynamic Programming, MCTS, MuZero │ └── Model-Free (모델 없음) ├── 환경 모델 없이 직접 경험으로 학습 ├── 더 범용적이지만 샘플 효율성 낮음 └── 예: Q-Learning, SARSA, Policy Gradient
Model-Free의 세부 분류
Model-Free ├── Value-Based (가치 기반) │ ├── 가치 함수를 학습하여 정책 도출 │ ├── 이산 행동 공간에 적합 │ └── 예: Q-Learning, DQN, Double DQN │ ├── Policy-Based (정책 기반) │ ├── 정책을 직접 학습 │ ├── 연속 행동 공간에 적합 │ └── 예: REINFORCE, PPO, TRPO │ └── Actor-Critic (결합) ├── 가치 함수(Critic)와 정책(Actor) 동시 학습 ├── 두 방법의 장점 결합 └── 예: A2C, A3C, SAC, TD3
On-Policy vs Off-Policy
| 특성 | On-Policy | Off-Policy |
|---|---|---|
| 학습 데이터 | 현재 정책으로 수집 | 다른 정책으로 수집 가능 |
| 샘플 효율성 | 낮음 (재사용 어려움) | 높음 (경험 재사용) |
| 안정성 | 높음 | 상대적으로 낮음 |
| 예시 | SARSA, A2C, PPO | Q-Learning, DQN, SAC |
# On-Policy: SARSA # 현재 정책이 선택한 다음 행동 a'를 사용 Q[s][a] += alpha * (r + gamma * Q[s_next][a_next] - Q[s][a]) # Off-Policy: Q-Learning # 다음 상태에서 최적 행동을 사용 (현재 정책과 무관) Q[s][a] += alpha * (r + gamma * max(Q[s_next]) - Q[s][a])
6. 기초 알고리즘 구현
Q-Learning 완전 구현
import numpy as np import gymnasium as gym from collections import defaultdict class QLearningAgent: def __init__(self, action_space, learning_rate=0.1, gamma=0.99, epsilon=1.0, epsilon_min=0.01, epsilon_decay=0.995): self.action_space = action_space self.lr = learning_rate self.gamma = gamma self.epsilon = epsilon self.epsilon_min = epsilon_min self.epsilon_decay = epsilon_decay # Q-테이블 초기화 self.Q = defaultdict(lambda: np.zeros(action_space.n)) def get_action(self, state): """ε-greedy 정책으로 행동 선택""" if np.random.random() < self.epsilon: return self.action_space.sample() return np.argmax(self.Q[state]) def update(self, state, action, reward, next_state, done): """Q-Learning 업데이트""" current_q = self.Q[state][action] if done: target = reward else: target = reward + self.gamma * np.max(self.Q[next_state]) # Q-value 업데이트 self.Q[state][action] += self.lr * (target - current_q) # ε 감소 self.epsilon = max(self.epsilon_min, self.epsilon * self.epsilon_decay) def train_q_learning(env_name='FrozenLake-v1', episodes=10000): env = gym.make(env_name, is_slippery=False) agent = QLearningAgent(env.action_space) rewards_history = [] for episode in range(episodes): state, _ = env.reset() total_reward = 0 done = False while not done: action = agent.get_action(state) next_state, reward, terminated, truncated, _ = env.step(action) done = terminated or truncated agent.update(state, action, reward, next_state, done) state = next_state total_reward += reward rewards_history.append(total_reward) if episode % 1000 == 0: avg_reward = np.mean(rewards_history[-100:]) print(f"Episode {episode}, Avg Reward: {avg_reward:.3f}, ε: {agent.epsilon:.3f}") env.close() return agent, rewards_history if __name__ == "__main__": agent, history = train_q_learning() print(f"\n최종 평균 보상: {np.mean(history[-100:]):.3f}")
SARSA 구현 (On-Policy)
class SARSAAgent: def __init__(self, action_space, learning_rate=0.1, gamma=0.99, epsilon=1.0, epsilon_min=0.01, epsilon_decay=0.995): self.action_space = action_space self.lr = learning_rate self.gamma = gamma self.epsilon = epsilon self.epsilon_min = epsilon_min self.epsilon_decay = epsilon_decay self.Q = defaultdict(lambda: np.zeros(action_space.n)) def get_action(self, state): if np.random.random() < self.epsilon: return self.action_space.sample() return np.argmax(self.Q[state]) def update(self, state, action, reward, next_state, next_action, done): """SARSA 업데이트 - 다음 행동(next_action)을 사용""" current_q = self.Q[state][action] if done: target = reward else: # Q-Learning과의 차이: max 대신 next_action의 Q값 사용 target = reward + self.gamma * self.Q[next_state][next_action] self.Q[state][action] += self.lr * (target - current_q) self.epsilon = max(self.epsilon_min, self.epsilon * self.epsilon_decay) def train_sarsa(env_name='FrozenLake-v1', episodes=10000): env = gym.make(env_name, is_slippery=False) agent = SARSAAgent(env.action_space) for episode in range(episodes): state, _ = env.reset() action = agent.get_action(state) # 초기 행동 선택 done = False while not done: next_state, reward, terminated, truncated, _ = env.step(action) done = terminated or truncated next_action = agent.get_action(next_state) # 다음 행동 미리 선택 agent.update(state, action, reward, next_state, next_action, done) state = next_state action = next_action # SARSA의 핵심! return agent
7. 간단한 환경 만들기
커스텀 그리드 월드
import numpy as np class SimpleGridWorld: """ 간단한 4x4 그리드 월드 - 시작: (0, 0) - 목표: (3, 3) - 함정: (1, 1), (2, 2) """ def __init__(self, size=4): self.size = size self.start = (0, 0) self.goal = (3, 3) self.traps = [(1, 1), (2, 2)] self.state = self.start def reset(self): self.state = self.start return self._get_obs() def _get_obs(self): return self.state[0] * self.size + self.state[1] def step(self, action): """ 행동: 0=상, 1=하, 2=좌, 3=우 """ x, y = self.state moves = {0: (-1, 0), 1: (1, 0), 2: (0, -1), 3: (0, 1)} dx, dy = moves[action] # 새 위치 계산 (경계 확인) nx = max(0, min(self.size - 1, x + dx)) ny = max(0, min(self.size - 1, y + dy)) self.state = (nx, ny) # 보상 및 종료 조건 if self.state == self.goal: return self._get_obs(), 10, True, False, {} elif self.state in self.traps: return self._get_obs(), -10, True, False, {} else: return self._get_obs(), -0.1, False, False, {} def render(self): grid = [['.' for _ in range(self.size)] for _ in range(self.size)] grid[self.goal[0]][self.goal[1]] = 'G' for trap in self.traps: grid[trap[0]][trap[1]] = 'X' grid[self.state[0]][self.state[1]] = 'A' print("\n".join([" ".join(row) for row in grid])) print() # 사용 예시 if __name__ == "__main__": env = SimpleGridWorld() state = env.reset() env.render() # 랜덤 에이전트 for _ in range(10): action = np.random.randint(4) next_state, reward, done, _, _ = env.step(action) env.render() if done: print(f"Episode ended with reward: {reward}") break
8. 학습 시각화
import matplotlib.pyplot as plt import numpy as np def plot_training_results(rewards, window=100): """학습 곡선 시각화""" fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5)) # 에피소드별 보상 ax1.plot(rewards, alpha=0.3, label='Episode Reward') # 이동 평균 if len(rewards) >= window: moving_avg = np.convolve(rewards, np.ones(window)/window, mode='valid') ax1.plot(range(window-1, len(rewards)), moving_avg, color='red', linewidth=2, label=f'{window}-Episode Moving Avg') ax1.set_xlabel('Episode') ax1.set_ylabel('Total Reward') ax1.set_title('Learning Curve') ax1.legend() ax1.grid(True, alpha=0.3) # 보상 분포 히스토그램 ax2.hist(rewards, bins=50, edgecolor='black', alpha=0.7) ax2.axvline(np.mean(rewards), color='red', linestyle='--', label=f'Mean: {np.mean(rewards):.2f}') ax2.set_xlabel('Reward') ax2.set_ylabel('Frequency') ax2.set_title('Reward Distribution') ax2.legend() ax2.grid(True, alpha=0.3) plt.tight_layout() plt.savefig('training_results.png', dpi=150) plt.show() def visualize_q_table(Q, size=4): """Q-테이블을 그리드로 시각화""" action_symbols = ['↑', '↓', '←', '→'] fig, axes = plt.subplots(2, 2, figsize=(12, 12)) axes = axes.flatten() for action_idx, ax in enumerate(axes): q_grid = np.zeros((size, size)) for state in range(size * size): row, col = state // size, state % size q_grid[row, col] = Q[state][action_idx] im = ax.imshow(q_grid, cmap='RdYlGn', aspect='equal') ax.set_title(f'Action: {action_symbols[action_idx]}') # 값 표시 for i in range(size): for j in range(size): ax.text(j, i, f'{q_grid[i, j]:.1f}', ha='center', va='center', fontsize=10) ax.set_xticks(range(size)) ax.set_yticks(range(size)) plt.colorbar(im, ax=ax) plt.suptitle('Q-Table Visualization', fontsize=14) plt.tight_layout() plt.savefig('q_table.png', dpi=150) plt.show()
9. 다음 단계 로드맵
강화학습 기초를 마스터했다면, 다음 순서로 학습을 이어가세요:
입문자 추천 학습 순서
1. Q-Learning / SARSA (테이블 기반) ↓ 2. DQN (신경망 + Q-Learning) ↓ 3. Double DQN, Dueling DQN, Rainbow ↓ 4. Policy Gradient (REINFORCE) ↓ 5. Actor-Critic (A2C/A3C) ↓ 6. PPO (현재 가장 인기) ↓ 7. MCTS, AlphaZero, MuZero (Model-Based) ↓ 8. RLHF (LLM 정렬)
추천 도구 및 라이브러리
| 목적 | 라이브러리 |
|---|---|
| 환경 | Gymnasium (OpenAI Gym 후속) |
| 알고리즘 | Stable-Baselines3, CleanRL, RLlib |
| 딥러닝 | PyTorch, JAX |
| 실험 관리 | Weights & Biases, TensorBoard |
10. 핵심 정리
| 개념 | 설명 |
|---|---|
| MDP | 상태, 행동, 보상, 전이로 구성된 강화학습 문제의 수학적 모델 |
| 정책 (π) | 상태에서 행동을 선택하는 전략 |
| 가치 함수 (V, Q) | 상태 또는 상태-행동의 장기적 가치 |
| 벨만 방정식 | 가치 함수의 재귀적 관계식 |
| 탐험 vs 활용 | 새로운 시도와 알려진 최선 사이의 균형 |
| 할인율 (γ) | 미래 보상의 현재 가치 비율 |
강화학습에서 할인율(γ)이 1에 가까울수록 어떤 특성을 가지나요?