- Authors

- Name
- Youngju Kim
- @fjvbn20031
強化学習方法論の分類
強化学習アルゴリズムは様々な基準で分類できます。全体像を理解すると各アルゴリズムの位置を把握しやすくなります。
モデルベース vs モデルフリー
- モデルベース(Model-Based): 環境の遷移確率と報酬関数を知っているか学習します。これを利用して計画(planning)を行います。
- モデルフリー(Model-Free): 環境モデルなしで直接経験から学習します。大部分の実用的な深層強化学習がここに該当します。
価値ベース vs 方策ベース
- 価値ベース(Value-Based): 状態や行動の価値を推定し、それに基づいて行動を選択します。DQNが代表的です。
- 方策ベース(Policy-Based): 方策自体を直接パラメータ化して最適化します。REINFORCE、PPOなどが該当します。
- アクター・クリティック(Actor-Critic): 価値ベースと方策ベースの利点を結合します。
オンポリシー vs オフポリシー
- オンポリシー(On-Policy): 現在の方策で収集したデータのみで学習します。データ効率は低いですが安定的です。
- オフポリシー(Off-Policy): 過去の方策や他の方策で収集したデータも活用します。データ効率が高いです。
| 分類 | アルゴリズム例 |
|---|---|
| 価値ベース、オフポリシー | DQN, Double DQN |
| 方策ベース、オンポリシー | REINFORCE, PPO |
| アクター・クリティック、オンポリシー | A2C, A3C |
| アクター・クリティック、オフポリシー | SAC, DDPG, TD3 |
Cross-Entropy方法の核心アイデア
Cross-Entropy方法は強化学習で最も単純なアルゴリズムの1つです。核心アイデアは以下の通りです:
- 現在の方策で複数のエピソードを実行する
- 全エピソードの中から報酬が高い上位エピソードのみ選別する(エリートエピソード)
- エリートエピソードの状態-行動ペアを使って方策を更新する
- 上記の過程を繰り返す
この方法は進化戦略の一種で、良いエピソードを模倣する方式で方策を改善します。
アルゴリズム疑似コード
1. 정책 네트워크 초기화
2. 반복:
a. N개의 에피소드를 현재 정책으로 수집
b. 각 에피소드의 총 보상을 계산
c. 상위 p%의 엘리트 에피소드를 선별 (보상 기준 상위 임계값)
d. 엘리트 에피소드의 (상태, 행동) 쌍으로 정책 네트워크 학습
- 손실 함수: Cross-Entropy Loss (분류 문제로 취급)
e. 평균 보상이 충분히 높으면 종료
CartPole実装
方策ネットワーク定義
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import gymnasium as gym
from collections import namedtuple
# 에피소드 데이터 구조
Episode = namedtuple('Episode', ['reward', 'steps'])
EpisodeStep = namedtuple('EpisodeStep', ['observation', 'action'])
class PolicyNetwork(nn.Module):
"""Cross-Entropy 방법을 위한 정책 네트워크"""
def __init__(self, obs_size, hidden_size, n_actions):
super().__init__()
self.net = nn.Sequential(
nn.Linear(obs_size, hidden_size),
nn.ReLU(),
nn.Linear(hidden_size, n_actions),
)
def forward(self, x):
return self.net(x)
エピソード収集
def generate_batch(env, net, batch_size, device="cpu"):
"""현재 정책으로 에피소드 배치를 생성"""
batch = []
episode_reward = 0.0
episode_steps = []
obs, _ = env.reset()
sm = nn.Softmax(dim=1)
while True:
obs_tensor = torch.tensor(np.array([obs]), dtype=torch.float32).to(device)
action_probs = sm(net(obs_tensor))
action_probs_np = action_probs.data.cpu().numpy()[0]
# 확률에 따라 행동 선택
action = np.random.choice(len(action_probs_np), p=action_probs_np)
next_obs, reward, terminated, truncated, _ = env.step(action)
episode_reward += reward
episode_steps.append(EpisodeStep(observation=obs, action=action))
if terminated or truncated:
batch.append(Episode(reward=episode_reward, steps=episode_steps))
episode_reward = 0.0
episode_steps = []
next_obs, _ = env.reset()
if len(batch) >= batch_size:
break
obs = next_obs
return batch
エリートエピソードフィルタリング
def filter_elite_episodes(batch, percentile):
"""상위 percentile%의 엘리트 에피소드 선별"""
rewards = [episode.reward for episode in batch]
reward_threshold = np.percentile(rewards, percentile)
elite_observations = []
elite_actions = []
for episode in batch:
if episode.reward >= reward_threshold:
for step in episode.steps:
elite_observations.append(step.observation)
elite_actions.append(step.action)
return (
torch.tensor(np.array(elite_observations), dtype=torch.float32),
torch.tensor(np.array(elite_actions), dtype=torch.long),
reward_threshold,
np.mean(rewards),
)
学習ループ
def train_cross_entropy_cartpole():
"""Cross-Entropy 방법으로 CartPole 학습"""
# 하이퍼파라미터
HIDDEN_SIZE = 128
BATCH_SIZE = 16
PERCENTILE = 70
LEARNING_RATE = 0.01
GOAL_REWARD = 475 # CartPole-v1의 목표 보상
env = gym.make("CartPole-v1")
obs_size = env.observation_space.shape[0]
n_actions = env.action_space.n
net = PolicyNetwork(obs_size, HIDDEN_SIZE, n_actions)
optimizer = optim.Adam(net.parameters(), lr=LEARNING_RATE)
loss_fn = nn.CrossEntropyLoss()
for iteration in range(100):
# 1. 에피소드 배치 생성
batch = generate_batch(env, net, BATCH_SIZE)
# 2. 엘리트 에피소드 선별
elite_obs, elite_actions, reward_threshold, mean_reward = \
filter_elite_episodes(batch, PERCENTILE)
# 3. 정책 네트워크 학습
optimizer.zero_grad()
action_logits = net(elite_obs)
loss = loss_fn(action_logits, elite_actions)
loss.backward()
optimizer.step()
print(
f"반복 {iteration:3d}: "
f"평균 보상={mean_reward:6.1f}, "
f"임계값={reward_threshold:6.1f}, "
f"손실={loss.item():.4f}"
)
if mean_reward >= GOAL_REWARD:
print(f"목표 달성! 평균 보상: {mean_reward:.1f}")
break
env.close()
return net
# 학습 실행
trained_net = train_cross_entropy_cartpole()
予想出力
반복 0: 평균 보상= 21.3, 임계값= 23.0, 손실=0.6897
반복 1: 평균 보상= 25.1, 임계값= 27.0, 손실=0.6753
...
반복 20: 평균 보상= 152.4, 임계값= 185.0, 손실=0.5832
...
반복 40: 평균 보상= 421.8, 임계값= 500.0, 손실=0.5106
반복 45: 평균 보상= 487.2, 임계값= 500.0, 손실=0.4923
목표 달성! 평균 보상: 487.2
学習済みエージェントの評価
def evaluate_agent(env_name, net, n_episodes=100, render=False):
"""학습된 에이전트의 성능을 평가"""
render_mode = "human" if render else None
env = gym.make(env_name, render_mode=render_mode)
rewards = []
for _ in range(n_episodes):
obs, _ = env.reset()
total_reward = 0
while True:
obs_tensor = torch.tensor(np.array([obs]), dtype=torch.float32)
with torch.no_grad():
action_logits = net(obs_tensor)
action = action_logits.argmax(dim=1).item()
obs, reward, terminated, truncated, _ = env.step(action)
total_reward += reward
if terminated or truncated:
break
rewards.append(total_reward)
env.close()
print(f"평가 결과 ({n_episodes}회):")
print(f" 평균 보상: {np.mean(rewards):.1f} +/- {np.std(rewards):.1f}")
print(f" 최소/최대: {np.min(rewards):.1f} / {np.max(rewards):.1f}")
# evaluate_agent("CartPole-v1", trained_net)
FrozenLake実装
FrozenLakeは4x4の氷の上でスタート地点からゴール地点まで到達する問題です。滑りやすい氷面のため意図した方向に動かないことがあります。
FrozenLakeの特性
- 状態:16個(4x4グリッドの各位置)
- 行動:4個(上、下、左、右)
- 報酬:ゴール到達時1、穴に落ちると0(エピソード終了)
- 滑りやすさ:意図した方向には1/3の確率で移動、残り2/3は横に滑る
ワンホットエンコーディングで状態表現
FrozenLakeの観測は整数1つ(現在位置)なので、ニューラルネットワークへの入力のためにワンホットエンコーディングを使用します。
class DiscreteOneHotWrapper(gym.ObservationWrapper):
"""이산 관찰을 원핫 벡터로 변환하는 래퍼"""
def __init__(self, env):
super().__init__(env)
n = env.observation_space.n
self.observation_space = gym.spaces.Box(
low=0.0, high=1.0, shape=(n,), dtype=np.float32
)
def observation(self, observation):
result = np.zeros(self.observation_space.shape[0], dtype=np.float32)
result[observation] = 1.0
return result
FrozenLakeにCross-Entropyを適用
FrozenLakeではいくつかの修正が必要です。報酬が非常に希薄(ゴール到達時のみ1)であるためです。
def train_cross_entropy_frozenlake():
"""Cross-Entropy 방법으로 FrozenLake 학습"""
HIDDEN_SIZE = 128
BATCH_SIZE = 100 # 더 많은 에피소드 필요
PERCENTILE = 30 # 더 낮은 퍼센타일 (성공 에피소드가 적으므로)
LEARNING_RATE = 0.001
env = DiscreteOneHotWrapper(gym.make("FrozenLake-v1"))
obs_size = env.observation_space.shape[0]
n_actions = env.action_space.n
net = PolicyNetwork(obs_size, HIDDEN_SIZE, n_actions)
optimizer = optim.Adam(net.parameters(), lr=LEARNING_RATE)
loss_fn = nn.CrossEntropyLoss()
for iteration in range(200):
batch = generate_batch(env, net, BATCH_SIZE)
elite_obs, elite_actions, reward_threshold, mean_reward = \
filter_elite_episodes(batch, PERCENTILE)
# 엘리트 에피소드가 없는 경우 건너뛰기
if len(elite_obs) == 0:
continue
optimizer.zero_grad()
action_logits = net(elite_obs)
loss = loss_fn(action_logits, elite_actions)
loss.backward()
optimizer.step()
if iteration % 10 == 0:
print(
f"반복 {iteration:3d}: "
f"평균 보상={mean_reward:.3f}, "
f"임계값={reward_threshold:.3f}, "
f"손실={loss.item():.4f}"
)
if mean_reward > 0.8:
print(f"목표 달성! 평균 보상: {mean_reward:.3f}")
break
env.close()
return net
# trained_net_fl = train_cross_entropy_frozenlake()
Cross-Entropy方法の理論的背景
なぜ「Cross-Entropy」なのか
この方法の名前は損失関数に由来します。エリートエピソードの行動を教師あり学習の正解ラベルとして扱い、方策ネットワークの出力と比較する際にCross-Entropy損失を使用します。
Cross-Entropy損失の数学的意味は2つの確率分布間の距離を測定することです:
H(p, q) = -sum( p(x) * log(q(x)) )
ここでpはエリートエピソードの行動分布(正解)、qは方策ネットワークが出力する行動確率です。
方法の長所と短所
長所
- 実装が非常に簡単
- ハイパーパラメータが少ない
- CartPoleのような簡単な問題で速く収束
- 安定した学習が可能
短所
- エピソードが終わらないと学習できない(オンライン学習不可)
- 報酬が希薄な環境では効果的でない
- 複雑な環境ではスケーラビリティが不足
- 連続行動空間には直接適用が困難
ハイパーパラメータの影響
def experiment_hyperparameters():
"""다양한 하이퍼파라미터 조합 실험"""
configs = [
{"batch_size": 8, "percentile": 70, "hidden": 64},
{"batch_size": 16, "percentile": 70, "hidden": 128},
{"batch_size": 32, "percentile": 80, "hidden": 128},
{"batch_size": 16, "percentile": 50, "hidden": 256},
]
for i, config in enumerate(configs):
print(f"\n=== 설정 {i+1}: batch={config['batch_size']}, "
f"percentile={config['percentile']}, hidden={config['hidden']} ===")
env = gym.make("CartPole-v1")
obs_size = env.observation_space.shape[0]
n_actions = env.action_space.n
net = PolicyNetwork(obs_size, config["hidden"], n_actions)
optimizer = optim.Adam(net.parameters(), lr=0.01)
loss_fn = nn.CrossEntropyLoss()
for iteration in range(50):
batch = generate_batch(env, net, config["batch_size"])
elite_obs, elite_actions, threshold, mean_reward = \
filter_elite_episodes(batch, config["percentile"])
if len(elite_obs) == 0:
continue
optimizer.zero_grad()
loss = loss_fn(net(elite_obs), elite_actions)
loss.backward()
optimizer.step()
if mean_reward >= 475:
print(f" 반복 {iteration}에서 목표 달성!")
break
env.close()
# experiment_hyperparameters()
まとめ
- 強化学習分類: モデルベース/フリー、価値/方策ベース、オン/オフポリシーで分類
- Cross-Entropy方法: 上位エピソードを選別して方策を教師あり学習方式で更新
- CartPole: 約40〜50回の反復で最大報酬に到達可能
- FrozenLake: 希薄報酬環境ではより多くのエピソードと低いパーセンタイルが必要
- 理論: Cross-Entropy損失でエリートエピソードの行動分布を模倣
Cross-Entropy方法は簡単ですが、より複雑な環境では限界があります。次の記事ではベルマン方程式と価値反復法を通じてより強力なアルゴリズムの基礎を築きます。