정책 기반 접근법 - REINFORCE 알고리즘

[정책 기반 접근 (Policy-based Method)]

[REINFORCE 알고리즘]

[REINFORCE 구현]

Input: State (s)↓Layer 1: Fully Connected (Linear), ϕ=W1s+b1↓Activation Function: ReLU (Rectified Linear Unit), a=max⁡(0,ϕ)↓Layer 2: Fully Connected (Linear), ψ=W2a+b2↓Output Activation: Softmax,π(a|s)=eψa∑a′eψa′↓Output: Action Probabilities π(a|s)\begin{array}{c} \boxed{\text{Input: State }(s)} \\ \downarrow \\ \boxed{\text{Layer 1: Fully Connected (Linear), } \phi = W_1 s + b_1} \\ \downarrow \\ \boxed{\text{Activation Function: ReLU (Rectified Linear Unit), } a = \max(0, \phi)} \\ \downarrow \\ \boxed{\text{Layer 2: Fully Connected (Linear), } \psi = W_2 a + b_2} \\ \downarrow \\ \boxed{\text{Output Activation: Softmax}, \pi(a|s) = \frac{e^{\psi_a}}{\sum_{a'} e^{\psi_{a'}}}} \\ \downarrow \\ \boxed{\text{Output: Action Probabilities } \pi(a|s)\;} \end{array}

1. 기본 표기

2. Policy Gradient (Policy Gradient) 계산

∇θJ(θ)=𝔼τ∼πθ[∑t=0T∇θlogπθ(at|st)Rt]\nabla_\theta J(\theta) = \mathbb{E}_{\tau \sim \pi_\theta} \left[ \sum_{t=0}^{T} \nabla_\theta \log \pi_\theta(a_t|s_t) R_t \right]

3. 정책 업데이트 (Policy Update) 계산

4. 몬테카를로 근사 (Monte Carlo Approximation)

5. 최종 업데이트 규칙 (Final Update Rule)

[한계]

[실습: REINFORCE]

1. 필수 라이브러리 설치 및 가져오기

import gymnasium as gym
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.distributions import Categorical
import numpy as np
import matplotlib.pyplot as plt
import imageio

2. 하이퍼파라미터 설정

learning_rate = 0.0003
gamma = 0.99
n_episodes = 3000  # 최대 에피소드 수
print_interval = 50 # 중간 결과 출력 주기
early_stopping_patience = 10 # 연속으로 target_score를 넘기는 횟수
target_score = 400  # CartPole-v1 성공 기준 (보통 200점 이상 성공)

3. Neural Network 모델 (Policy Network) 정의

class Policy(nn.Module):
    def __init__(self, state_size, action_size, hidden_size=32):
        super(Policy, self).__init__()
        self.fc1 = nn.Linear(state_size, hidden_size)
        self.fc2 = nn.Linear(hidden_size, action_size)

    def forward(self, x):
        x = F.relu(self.fc1(x))
        x = self.fc2(x)
        return F.softmax(x, dim=0)  # 단일 state에 대한 확률

4. REINFORCE 알고리즘 구현 (train 함수)

def train(env, policy, optimizer, n_episodes, gamma, print_interval, early_stopping_patience, target_score):
    scores = []

    for i_episode in range(1, n_episodes + 1):
        state, _ = env.reset()
        if isinstance(state, tuple):
            state = state[0]  # gym 버전 호환
        state = torch.from_numpy(state).float()

        rewards = []
        log_probs = []
        done = False

        while not done:
            probs = policy(state)
            m = Categorical(probs)
            action = m.sample()
            log_prob = m.log_prob(action)
            next_state, reward, terminated, truncated, _ = env.step(action.item())
            rewards.append(reward)
            log_probs.append(log_prob)
            state = torch.from_numpy(next_state).float()
            done = terminated or truncated

        # Return 계산
        R = 0
        returns = []
        for r in reversed(rewards):
            R = r + gamma * R
            returns.insert(0, R)

        # Policy Loss 계산 (기본적인 형태)
        returns = torch.tensor(returns) # list를 tensor로
        log_probs = torch.stack(log_probs) # list of tensor를 tensor로
        policy_loss = torch.sum(-log_probs * returns)

        optimizer.zero_grad()
        policy_loss.backward()
        optimizer.step()

        total_reward = sum(rewards)
        scores.append(total_reward)

        if (i_episode+1) % print_interval == 0:
            avg_score = np.mean(scores[-print_interval:])
            print(f"Episode {(i_episode+1)}\tAverage Score: {avg_score:.2f}")

        if np.min(scores[-early_stopping_patience:])>=target_score:
            print(f"Early stopping after {i_episode+1} episodes.  Target score reached.")
            break

    return scores

5. 학습 실행 및 결과 확인

env = gym.make('CartPole-v1', render_mode = 'rgb_array')
state_size = env.observation_space.shape[0]
action_size = env.action_space.n

policy = Policy(state_size, action_size)
optimizer = optim.Adam(policy.parameters(), lr=learning_rate)

scores = train(env, policy, optimizer, n_episodes, gamma, print_interval, early_stopping_patience, target_score)

plt.plot(scores)
plt.xlabel("Episode")
plt.ylabel("Score")
plt.show()
Episode 50	Average Score: 29.59
Episode 100	Average Score: 29.46
Episode 150	Average Score: 22.94
Episode 200	Average Score: 28.10
Episode 250	Average Score: 33.60
Episode 300	Average Score: 31.22
Episode 350	Average Score: 35.42
Episode 400	Average Score: 38.36
Episode 450	Average Score: 33.10
Episode 500	Average Score: 42.88
Episode 550	Average Score: 46.48
Episode 600	Average Score: 55.06
Episode 650	Average Score: 52.90
Episode 700	Average Score: 56.98
Episode 750	Average Score: 60.50
Episode 800	Average Score: 49.84
Episode 850	Average Score: 61.66
Episode 900	Average Score: 68.46
Episode 950	Average Score: 70.76
Episode 1000	Average Score: 87.88
Episode 1050	Average Score: 95.34
Episode 1100	Average Score: 104.16
Episode 1150	Average Score: 112.28
Episode 1200	Average Score: 161.80
Episode 1250	Average Score: 148.26
Episode 1300	Average Score: 151.28
Episode 1350	Average Score: 194.38
Episode 1400	Average Score: 209.04
Episode 1450	Average Score: 223.46
Episode 1500	Average Score: 213.40
Episode 1550	Average Score: 205.94
Episode 1600	Average Score: 242.70
Episode 1650	Average Score: 268.14
Episode 1700	Average Score: 300.26
Episode 1750	Average Score: 250.06
Episode 1800	Average Score: 279.60
Episode 1850	Average Score: 279.02
Episode 1900	Average Score: 287.54
Episode 1950	Average Score: 301.08
Episode 2000	Average Score: 335.34
Episode 2050	Average Score: 351.32
Episode 2100	Average Score: 341.72
Episode 2150	Average Score: 298.76
Episode 2200	Average Score: 313.38
Episode 2250	Average Score: 331.06
Episode 2300	Average Score: 368.56
Episode 2350	Average Score: 353.54
Episode 2400	Average Score: 340.40
Episode 2450	Average Score: 376.02
Early stopping after 2482 episodes.  Target score reached.
<Figure size 640x480 with 1 Axes>
실행 결과
state, _ = env.reset()
if isinstance(state, tuple):
    state = state[0]
state = torch.from_numpy(state).float()

frames = []
total_reward = 0
done = False

while not done:
    probs = policy(state)
    m = Categorical(probs)
    action = m.sample()

    next_state, reward, terminated, truncated, _ = env.step(action.item())
    total_reward += reward
    state = torch.from_numpy(next_state).float()
    done = terminated or truncated
    frames.append(env.render())

imageio.mimsave("cartpole_simulation_reinforce.gif", frames, duration=33)  # duration 조절
print(f"Total reward: {total_reward}")
env.close()
Total reward: 500.0