강의자료 · 전기공학을 위한 강화학습

3주차 Deep Q Network (DQN)

[핵심 목표]

[DQN 알고리즘 개요]

Input: State (s)↓Layer 1: Fully Connected (Linear), ϕ=W1s+b1↓Activation Function: ReLU, a=max⁡(0,ϕ)↓Layer 2: Fully Connected (Linear), v=W2a+b2↓Output: State Value (V(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, } a = \max(0, \phi)} \\ \downarrow \\ \boxed{\text{Layer 2: Fully Connected (Linear), } v = W_2 a + b_2} \\ \downarrow \\ \boxed{\text{Output: State Value } (V(s))} \end{array}

[경험 리플레이 (Replay Buffer)]

[ε-탐욕적 (Epsilon-Greedy) 정책]

[타겟 네트워크 (Target Network)]

[DQN 학습 업데이트]

[ε-탐욕적 정책 액션 선택]

[실습: DQN (Deep Q-Network) 구현 (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))  # 활성화 함수 적용 (ReLU)
        x = torch.relu(self.fc2(x))
        return self.fc3(x)  # Q-값 반환

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()) # Q 네트워크값 복사
optimizer = optim.Adam(agent.parameters(), lr=learning_rate)
criterion = nn.MSELoss()

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

memory = deque(maxlen=memory_size)

6. 학습 함수

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)

  # 타겟 Q값 계산 (Target Network 사용)
  target_q_values = target_agent(next_states).max(1)[0].detach() # 타겟 네트워크에서 최대 Q값 가져오기
  targets = rewards + (gamma * target_q_values * (1 - dones)) # done = 1 이면 다음 상태의 Q값 0

  # 예측 Q값 계산
  predicted_q_values = agent(states).gather(1, actions.unsqueeze(1)) # 학습된 네트워크에서 계산된 Q값 가져오기

  # 손실 함수 계산 및 역전파
  loss = criterion(predicted_q_values.squeeze(), targets)
  optimizer.zero_grad()
  loss.backward()
  optimizer.step()

6. 학습 루프

# 학습 루프
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)
        total_reward += reward
        done = terminated or truncated

        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: 11.0, Epsilon: 0.220
Episode 40/100, Total Reward: 24.0, Epsilon: 0.067
Episode 60/100, Total Reward: 118.0, Epsilon: 0.010
Episode 80/100, Total Reward: 158.0, Epsilon: 0.010
Episode 100/100, Total Reward: 199.0, Epsilon: 0.010

7. 학습된 에이전트 검증 (렌더링 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.gif', frames, duration=33) # save to gif file
print("GIF saved as cartpole_simulation.gif")
GIF saved as cartpole_simulation.gif