Advantage Actor-Critic (A2C)

[Actor-Critic 핵심 개념]

[Advantage Actor-Critic (A2C) 알고리즘]

[A2C 구현]

[A2C 한계 및 개선 방향]

[실습: Advantage Actor-Critic(A2C)을 이용한 CartPole 실습]

1. 라이브러리 가져오기 및 하이퍼 파라미터 설정

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

# 하이퍼파라미터
gamma = 0.99
learning_rate = 0.003
hidden_size = 128  # 기본값 (2층 신경망)
num_steps = 10
num_episodes = 2000
progress_interval = 50
early_stop_threshold = 400 # CartPole-v1 대체로 200 이상이면 성공으로 분류
early_stop_patience = 10
render_gif_path = "cartpole_simulation_a2c.gif"

2. A2C Neural Network 모델 (Actor-Critic) 정의

class ActorCritic(nn.Module):
    def __init__(self, input_size, num_actions, hidden_size=128, use_three_layers=False):
        super(ActorCritic, self).__init__()
        self.use_three_layers = use_three_layers
        self.fc1 = nn.Linear(input_size, hidden_size)
        if self.use_three_layers:
            self.fc_h = nn.Linear(hidden_size, hidden_size)
        self.fc2 = nn.Linear(hidden_size, hidden_size)
        self.fc_pi = nn.Linear(hidden_size, num_actions)
        self.fc_v = nn.Linear(hidden_size, 1)

    def forward(self, x):
        x = F.relu(self.fc1(x))
        if self.use_three_layers:
            x = F.relu(self.fc_h(x))
        x = F.relu(self.fc2(x))
        policy = F.softmax(self.fc_pi(x), dim=0)
        value = self.fc_v(x)
        return policy, value

3.학습 루프 함수 정의

정책/가치 예측 및 행동 선택

선택한 행동을 적용해 환경과 상호작용

경험 저장

롤아웃 데이터로부터 Advantage 및 손실 계산

최적화:

def train(model, optimizer, env, num_episodes, num_steps, gamma, check_interval, early_stop_threshold, early_stop_patience):
    scores = []
    for episode in range(num_episodes):
        state, _ = env.reset()
        done = False
        total_reward = 0
        memory = []

        while not done:
            for t in range(num_steps):
                state_tensor = torch.from_numpy(state).float()
                policy, value = model(state_tensor)
                action_dist = Categorical(policy)
                action = action_dist.sample()

                next_state, reward, terminated, truncated, _ = env.step(action.item())
                total_reward += reward
                done = terminated or truncated

                memory.append((state_tensor, action, reward, value, done))
                state = next_state

                if done:
                    break

            # 롤아웃 데이터로부터 Advantage 계산 및 손실 함수 계산
            loss = 0
            _, last_value = model(torch.from_numpy(next_state).float())
            returns = last_value.detach()

            for state_tensor, action, reward, value, done in reversed(memory):
                returns = reward + gamma * returns * (1 - done)
                advantage = returns - value
                loss += -Categorical(model(state_tensor)[0]).log_prob(action) * advantage.detach() + \
                        F.smooth_l1_loss(value, returns.detach()) # Huber loss

            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            memory = [] # 메모리 초기화

        scores.append(total_reward)

        # 진행 상황 확인 및 조기 종료 검사
        if (episode+1) % check_interval == 0:
            avg_score = np.mean(scores[-check_interval:])
            print(f"Episode: {episode+1}, Average Reward: {avg_score:.2f}")

        if np.min(scores[-early_stop_patience:]) >= early_stop_threshold:
            print(f"Early stopping triggered! at Episode: {episode+1}")
            break

4. 학습 실행 및 결과 확인

# 환경 생성
env = gym.make('CartPole-v1', render_mode = 'rgb_array')
input_size = env.observation_space.shape[0]
num_actions = env.action_space.n

# 모델 생성 (3층 신경망 사용 예시)
model = ActorCritic(input_size, num_actions, hidden_size, use_three_layers=True)
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

# 학습 실행
train(model, optimizer, env, num_episodes, num_steps, gamma, progress_interval, early_stop_threshold, early_stop_patience)

env.close()
Episode: 50, Average Reward: 16.78
Episode: 100, Average Reward: 24.02
Episode: 150, Average Reward: 18.66
Episode: 200, Average Reward: 37.02
Episode: 250, Average Reward: 39.84
Episode: 300, Average Reward: 31.26
Episode: 350, Average Reward: 55.00
Episode: 400, Average Reward: 104.56
Early stopping triggered! at Episode: 422

5. 학습된 모델 테스트 및 GIF 렌더링

state, _ = env.reset()
done = False
total_reward = 0
frames = []

with torch.no_grad():  # 기울기 계산 비활성화 (추론 모드)
    while not done:
        state_tensor = torch.from_numpy(state).float()
        policy, _ = model(state_tensor)
        action_dist = Categorical(policy)
        action = action_dist.sample()

        next_state, reward, terminated, truncated, _ = env.step(action.item())
        total_reward += reward
        state = next_state
        done = terminated or truncated

        # 렌더링 이미지를 프레임 리스트에 추가
        frames.append(env.render())

# GIF 파일로 저장
imageio.mimsave(render_gif_path, frames, duration=33)  # fps 조절 가능 duration = 1000/fps
print(f"Test Episode Reward: {total_reward}")
env.close()
Test Episode Reward: 500.0