4주차 Double DQN

[핵심 목표]

[Q값 과대 추정 (Overestimation) 문제]

1. Q값 과대 추정 문제 예시: 게임 상황

2. DQN의 과대 추정 문제 발생 과정

3. 정책 왜곡 및 문제점

[Double DQN 아이디어]

[Double DQN 학습 업데이트]

[실습: Double DQN을 이용한 CartPole 학습]

1. 환경 설정

import gymnasium as gym
import random
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
from collections import deque

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

2. DQN 모델 정의

class DQNAgent(nn.Module):
    def __init__(self, state_size, action_size):
        super(DQNAgent, self).__init__()
        self.fc1 = nn.Linear(state_size, 128)
        self.fc2 = nn.Linear(128, 128)
        self.fc3 = nn.Linear(128, action_size)

    def forward(self, x):
        x = torch.relu(self.fc1(x))
        x = torch.relu(self.fc2(x))
        return self.fc3(x)

3. 하이퍼파라미터 설정

learning_rate = 0.0003
gamma = 0.99  # 할인율
epsilon = 1.0  # 초기 탐험 확률
epsilon_min = 0.01
epsilon_decay = 0.995
batch_size = 128
memory_size = 30000
target_update_frequency = 5  # 타겟 네트워크 업데이트 주기

4. DQN 에이전트 및 타겟 네트워크 생성

agent = DQNAgent(state_size, action_size)
target_agent = DQNAgent(state_size, action_size)
target_agent.load_state_dict(agent.state_dict())
optimizer = optim.Adam(agent.parameters(), lr=learning_rate)
criterion = nn.MSELoss()

5. 경험 리플레이 버퍼 초기화

memory = deque(maxlen=memory_size)

6. 학습 함수 (train)

def train(batch_size):
    minibatch = random.sample(memory, batch_size)
    states = torch.tensor(np.array([transition[0] for transition in minibatch]), dtype=torch.float32)
    actions = torch.tensor(np.array([transition[1] for transition in minibatch]), dtype=torch.long)
    rewards = torch.tensor(np.array([transition[2] for transition in minibatch]), dtype=torch.float32)
    next_states = torch.tensor(np.array([transition[3] for transition in minibatch]), dtype=torch.float32)
    dones = torch.tensor(np.array([transition[4] for transition in minibatch]), dtype=torch.float32)

    # Double DQN 핵심: agent 네트워크에서 다음 상태의 최대 Q-값을 가지는 행동 선택
    next_actions = agent(next_states).argmax(1).unsqueeze(1)
    # target 네트워크에서 선택된 행동의 Q-값 계산
    target_q_values = target_agent(next_states).gather(1, next_actions).squeeze()
    targets = rewards + gamma * target_q_values * (1 - dones)

    predicted_q_values = agent(states).gather(1, actions.unsqueeze(1)).squeeze()
    loss = criterion(predicted_q_values, targets)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

7. 학습 루프

# 학습 루프
episodes = 100
for e in range(episodes):
    state, _ = env.reset()
    state = torch.tensor(state, dtype=torch.float32)
    done = False
    total_reward = 0

    while not done:
        if random.random() < epsilon:
            action = env.action_space.sample()
        else:
            action = agent(state).argmax().item()
        next_state, reward, terminated, truncated, _ = env.step(action)
        next_state = torch.tensor(next_state, dtype=torch.float32)
        done = terminated or truncated

        total_reward += reward
        memory.append((state, action, reward, next_state, done))

        if len(memory) >= batch_size:
            train(batch_size)

        state = next_state
        if e % target_update_frequency == 0:
            target_agent.load_state_dict(agent.state_dict())

        if epsilon > epsilon_min:
            epsilon *= epsilon_decay

    if (e+1) % 20 == 0:
        print(f"Episode {e + 1}/{episodes}, Total Reward: {total_reward}, Epsilon: {epsilon:.3f}")
Episode 20/100, Total Reward: 302.0, Epsilon: 0.010
Episode 40/100, Total Reward: 133.0, Epsilon: 0.010
Episode 60/100, Total Reward: 132.0, Epsilon: 0.010
Episode 80/100, Total Reward: 124.0, Epsilon: 0.010
Episode 100/100, Total Reward: 138.0, Epsilon: 0.010

8. 학습된 에이전트 검증 (렌더링 with GIF)

import imageio
import os

frames = []
state, _ = env.reset()
state = torch.tensor(state, dtype=torch.float32)
done = False
for i in range(3000): # 3000 steps simulation
  action = agent(state).argmax().item()
  next_state, reward, terminated, truncated, _ = env.step(action)
  next_state = torch.tensor(next_state, dtype=torch.float32)
  state = next_state
  done = terminated or truncated
  frames.append(env.render())

  if done:
    break

imageio.mimsave('cartpole_simulation_doubleDQN.gif', frames, duration=33) # save to gif file
print("GIF saved as cartpole_simulation.gif")
GIF saved as cartpole_simulation.gif