DQN은 Deep Q-Network의 약자입니다.

예전에 것을 다시 간단히 정리해보겠습니다. 먼저 Q-Table을 보았는데, Q-Table을 이용하면 현재 상태에서 어떤 action을 하면 최대 보상을 취할 수 있을지를 알 수 있습니다.

하지만 현실세계에서는 Q-Table이 훌륭한 방법이지만, 현실세계에서는 Q-Table을 이용할 수 있는 경우가 극히 어렵습니다. 

Q-Table 크기 = state크기 X action space 크기  가지므로 state의 개수가 많아질 수록 Q-Table은 기하급수적으로 커집니다. 만약 state중에 연속형 변수가 있다면 Q-Table을 만들수 조차 없습니다.

그래서 Q-Table의 한계를 극복하고자 Q함수의 근사함수로 Deep-learning을 사용하고자 하였지만 성능이 그다지 좋지 못해 강화학습이 널리 사용되지 못했습니다.

 

DQN은 이러한 Q-Learning의 한계를 극복하기 위해 Deep Neural Network(신경망)를 이용하여 Q-값을 근사하는 강화학습 알고리즘입니다.

이는 2013년 2015년 DeepMind 연구진이 발표한 알고리즘으로, 연속적인 상태 공간에서도 효과적으로 작동합니다.

 

이번에도 cart-pole데이터셋을 사용할 예정인데 잠깐 cart-pole 데이터셋을 복습하겠습니다.

막대(Pole)를 수평으로 움직이는 카트(Cart)에 세운 상태에서, 막대가 넘어지지 않도록 카트를 좌우로 움직이는 문제입니다.

state가 카트의 위치, 속도, 막대의 각도, 막대의 회전속도 4개의 연속형 실수라서 state개수가 무한대가 되어서 Q-Table을 사용할 수 없습니다. 

Q함수의 근사로 Deep learning을 사용하였지만 평균 성능이 그다지 높지 않았습니다.

아래는 gym을 사용한 cartpole게임을 시각화한 간단한 예제 코드입니다.

import numpy as np
import gym
import time 

env = gym.make('CartPole-v1', render_mode='human')
state = env.reset()[0]

print('상태:', state) # 카트의 위치, 카트의 속도, 막대의 각도, 막대의 각속도(회전속도)
print('Action Space:', env.action_space) # 0: 왼쪽, 1: 오른쪽

count = 0
while count < 5:  # 에피소드 5회 반복 
    env.render()  # 진행 과정 시각화
    action = np.random.choice([0, 1])  # 행동 선택(무작위)
    next_state, reward, terminated, truncated, info = env.step(action)
    if terminated | truncated:      # 둘 중 하나만 True면 에피소드 종료                
        env.render() 
        time.sleep(0.7)
        state = env.reset()[0]
        count += 1        
        
env.close()
#상태: [ 0.0252869   0.00902431  0.01727916 -0.03231359]
#Action Space: Discrete(2)

Q-Network의 문제점

Q-Learning에서는 현재 상태의 가치를 추정할 때, 미래 상태에서 추정한 값을 다시 사용하여 값을 갱신합니다. 이러한 기법을 부트스트래핑(Bootstrapping)이라고 하며, 추정치를 기반으로 추정치를 갱신하는 방식입니다.

그러나, 부트스트래핑 기법은 아직 정확하지 않은 추정치를 사용하여 현재의 추정치를 갱신하기 때문에, 특히 인공신경망을 활용한 Q-값 근사 기법과 결합되면 결과가 더 불안정해질 수 있습니다.

Q-Network의 주요 문제점은 크게 두 가지로 요약됩니다:

1. 샘플 간 상관성 (Correlations between samples)

강화학습에서 에이전트(Agent)는 환경과 상호작용하며 데이터를 수집합니다. 이때 데이터는 시간적 종속성을 가지며, 특정 상태 s에서 수행한 행동 a가 다음 상태 s′와 밀접하게 연관됩니다. 이러한 데이터 특성을 그대로 학습하면, 지역적인 패턴에 과적합되기 쉽습니다.

예를 들어, 선형 회귀에서 기울기를 계산할 때 특정 지점에 데이터가 집중되어 있다면, 실제 기울기와 크게 다른 결과를 도출할 수 있는 것과 유사합니다. 마찬가지로, 샘플 간 상관성을 고려하지 않고 학습하면 네트워크가 특정 행동 패턴에만 과도하게 최적화될 위험이 있습니다.

2. 타겟 값의 변화 (Non-Stationary Targets)

딥러닝을 이용해 Q-값을 근사할 때, 현재의 예측 값 y_pred 목표 값 y_true​를 계산하는 데 동일한 Q-함수와 가중치 θ를 사용합니다.

문제는 손실 함수(Loss Function)를 통해 가중치 θ를 갱신하면, 이로 인해 미래 상태의 Q-값 추정치도 함께 변경된다는 점입니다.

즉, θ값을 학습할 때 현재의 Q값예측과 미래의 Q값 예측값 둘다를 사용할때,  target값 y(보상+미래의 Q값)를 예측하는데, θ값을 공유하여 학습할 때 y값이 움직입니다. Target값 y가 고정되지 않는다고 해서, Non-Stationary Target 문제라고 합니다.

Q-Network의 공식을 보면 역전파를 통해 예측값의 θ를 변경하면 y_true값도 같이 변화된다는것을 알 수 있습니다. 

 

이로 인해:

  • 타겟 값 y_true​가 계속 변화하면서 학습이 매우 불안정해집니다.
  • 네트워크가 타겟 값의 변화를 따라가지 못하면, 최적의 정책을 학습하기 어려워집니다.

DQN 알고리즘의 등장

2013년과 2015년에 딥마인드(DeepMind)는 아래 두 논문을 통해 위의 두 가지 문제를 해결하기 위한 DQN(Deep Q-Network) 알고리즘을 발표했습니다.
이 알고리즘은 Experience Replay(경험 재생)과 Target Network(목표 신경망)를 도입하여 학습의 안정성과 효율성을 개선하였습니다.

Playing Atari with Deep Reinforcement Learning(2013) : https://arxiv.org/pdf/1312.5602

Human-level control through deep reinforcement learning(2015) : https://web.stanford.edu/class/psych209/Readings/MnihEtAlHassibis15NatureControlDeepRL.pdf

 

경험 재생(Experience Replay)

샘플 간 상관성(Correlations between samples)의 문제를 위해 도입된 기법으로 2013년 Playing Atari with Deep Reinforcement Learning 논문에서 제안되었습니다.
에이전트가 환경과 상호작용하며 얻은 경험 데이터를 저장한 뒤, 이를 반복적으로 재사용하여 학습에 활용합니다.

다음은 2013년 논문의 알고리즘입니다.

Q러닝은 Agent가 행동을 할 때마다 E = (S,A,R,S') 데이터를 생성합니다. 이 데이터들은 t와 t+1사이에 강한 상관관계가 있습니다. Q러닝은 상관관계가 높은 편향된 데이터를 사용하여 학습한다는 뜻입니다.

이 문제를 해결하는 아이디어는 매우 간단합니다. 경험 데이터 E를 버퍼 D에 저장합니다. 그리고 무작위로 샘플링 후 미티배치로 꺼내 사용합니다. 노란색으로 표시된 알고리즘의 2줄의 내용입니다.

경험 재생은 데이터를 무한정 저장할 수 없습니다.

따라서 최대 크기를 미리 정해놓습니다.

경험데이터는 E는 아래같이 저장됩니다.

(S0, A0, R0, S1)

(S1, A1, R1, S2)

(S2, A2, R2, S3)

(S3, A3, R3, S4)

...

등으로 저장합니다.

Reply Buffer에 실제 저장된 예입니다.

((array([[-0.64218944, -0.35716006, -0.00715424, -0.1738039 ]]),  0,  1.0,   array([-0.64933264, -0.5521789 , -0.01063032,  0.11661354]), False),
 (array([[-0.64933264, -0.5521789 , -0.01063032,  0.11661354]]),  1,  1.0,  array([-0.66037625, -0.35690627, -0.00829805, -0.17940412]), False),
 (array([[-0.66037625, -0.35690627, -0.00829805, -0.17940412]]),  0,  1.0,  array([-0.6675144 , -0.5519085 , -0.01188613,  0.11064956], False))

 

 

다음은 Reply Buffer의 코드 작성예입니다.

from collections import deque
import random

class ReplayBuffer:
    def __init__(self, max_size):
        """Replay Buffer 초기화"""
        self.buffer = deque(maxlen=max_size)  # 고정 크기의 버퍼

    def add(self, state, action, reward, next_state, done):
        """경험 데이터를 버퍼에 추가"""
        experience = (state, action, reward, next_state, done)
        self.buffer.append(experience)

    def sample(self, batch_size):
        """버퍼에서 랜덤하게 데이터를 샘플링"""
        batch = random.sample(self.buffer, batch_size)
        states, actions, rewards, next_states, dones = zip(*batch)
        return states, actions, rewards, next_states, dones

    def __len__(self):
        """현재 버퍼에 저장된 데이터 개수 반환"""
        return len(self.buffer)

# 사용 예제
D = ReplayBuffer(max_size=10000)

# 경험 데이터 추가
D.add("S0", "A0", 0, "S1", False)
D.add("S1", "A1", 3, "S2", False)
D.add("S2", "A2", 1, "S3", False)
D.add("S3", "A3", 2, "S4", True)

# 현재 버퍼 크기 확인
print(f"Buffer size: {len(D)}")

# 데이터 샘플링
batch_size = 1
states, actions, rewards, next_states, dones = D.sample(batch_size)
print('batch size:', batch_size)
print("States:", states, "Actions:", actions, "Rewards:", rewards,"Next States:", next_states, "Dones:", dones)

batch_size = 2
states, actions, rewards, next_states, dones = D.sample(batch_size)
print('\nbatch size:', batch_size)
print("States:", states, "Actions:", actions, "Rewards:", rewards,"Next States:", next_states, "Dones:", dones)
#Buffer size: 4
#batch size: 1
#States: ('S0',) Actions: ('A0',) Rewards: (0,) Next States: ('S1',) Dones: (False,)

#batch size: 2
#States: ('S1', 'S0') Actions: ('A1', 'A0') Rewards: (3, 0) Next States: ('S2', 'S1') Dones: (False, False)

목표 신경망(Target Network)

목표 신경망(Target Network)은 DQN(Deep Q-Network)에서 타겟 값의 변화(Non-Stationary Targets) 문제를 위해 도입된 개념입니다. Non-Stationary Targets 문제는 separate target network 방법으로 해결습니다.

즉, 신명망을 동일한 신경망을 더 준비합니다.

수식에서 θ를 사용하는 신경망과 θ˜ 를 Q-값 업데이트 시 사용하는 별도의 신경망입니다.

목표 신경망은 학습 중인 네트워크와 동일한 구조를 가집니다.

첫번째 신경망은 일반적인 Q값을 갱신합니다. target network는 가중치 매개변수를 고정된 상태로 주기로 학습 네트워크의 의 가중치로 업데이트됩니다.

[목표신경망 Target network 절차]

action-value의 신경망과 target action-value의 신경망을 분리합니다. (2개를 생성합니다.)

초기에는 동일한 가중치를 복사(load_state_dict)하여 생성됩니다.

q_network = QNetwork(state_size, hidden_size, action_size)
target_network = QNetwork(state_size, hidden_size, action_size)
target_network.load_state_dict(q_network.state_dict())
  • Target network의  θ˜ 를 이용하여 target value 를 계산합니다. q_network대신 target network값을 사용합니다.
  • 이렇게 하면, θ에 의해 target의θ˜값이 변하지 않아 비교적 안정적으로 유지되어 학습이 안정됩니다. 
with torch.no_grad():
    max_next_q_values = target_network(next_states).max(dim=1, keepdim=True)[0]
    targets = rewards + (1 - dones) * gamma * max_next_q_values
  • Main Q-network  θ를 이용하여 Q(s,a;  θ) 를 계산합니다.
  • q_network(states): 주어진 states(상태 집합)에 대해 모든 가능한 행동의 Q-값을 계산합니다. 출력은 크기가 [batch_size, action_size]인 행렬입니다.
  • .gather(1, actions) : 수행한 actions에 해당하는 Q-값만 추출합니다. [batch_size, 1]의 형태로 반환됩니다.
q_values = q_network(states).gather(1, actions)
  • Loss function (y-Q(s,a;  θ˜ ))**2 이 최소화되도록 main Q-network  θ를 갱신합니다.
  • 매 C 스텝마다 target network  θ˜ 를 main Q-network  θ˜로 업데이트합니다.
  • 이를 통해 target_network도 최신 정보를 반영할 수 있습니다.
if episode % update_target_every == 0:
    target_network.load_state_dict(q_network.state_dict())

 

목표 신경망 포함한 전체 코드입니다.

import torch
import torch.nn as nn
import torch.optim as optim
import random
import numpy as np
import gym
from collections import deque
import matplotlib.pyplot as plt

# Q-Network 정의
class QNetwork(nn.Module):
    def __init__(self, state_size, hidden_size, action_size):
        super(QNetwork, self).__init__()
        self.fc1 = nn.Linear(state_size, hidden_size)
        self.fc2 = nn.Linear(hidden_size, hidden_size)
        self.fc3 = nn.Linear(hidden_size, action_size)

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

# 리플레이 버퍼 정의
class ReplayBuffer:
    def __init__(self, max_size):
        self.buffer = deque(maxlen=max_size)

    def add(self, state, action, reward, next_state, done):
        self.buffer.append((state, action, reward, next_state, done))

    def sample(self, batch_size):
        batch = random.sample(self.buffer, batch_size)
        states, actions, rewards, next_states, dones = zip(*batch)
        return (np.array(states), np.array(actions), np.array(rewards),
                np.array(next_states), np.array(dones))

    def size(self):
        return len(self.buffer)

# 하이퍼파라미터 설정
episodes = 3000
batch_size = 32
gamma = 0.998
epsilon = 1.0
epsilon_min = 0.01
epsilon_decay = 0.995
learning_rate = 0.0005
update_target_every = 30
hidden_size = 16  # 은닉층 크기


# 환경 초기화 및 파라미터 설정
env = gym.make('CartPole-v1')
state_size = env.observation_space.shape[0]
action_size = env.action_space.n

q_network = QNetwork(state_size, hidden_size, action_size)
target_network = QNetwork(state_size, hidden_size, action_size)
target_network.load_state_dict(q_network.state_dict())

buffer = ReplayBuffer(max_size=50000)


mRewards = []

# DQN 학습
optimizer = optim.Adam(q_network.parameters(), lr=learning_rate)
loss_fn = nn.MSELoss()

for episode in range(episodes):
    state, _ = env.reset()
    state = torch.tensor(state, dtype=torch.float32).unsqueeze(0)
    total_reward = 0

    for t in range(500):
        # Epsilon-greedy 정책으로 행동 선택
        if random.random() < epsilon:
            action = env.action_space.sample()
        else:
            with torch.no_grad():
                q_values = q_network(state)
                action = torch.argmax(q_values).item()

        # 환경과 상호작용
        next_state, reward, terminated, truncated, _ = env.step(action)
        reward = -100.0 if terminated else reward
            
        done = terminated or truncated

        next_state_tensor = torch.tensor(next_state, dtype=torch.float32).unsqueeze(0)
        buffer.add(state.numpy(), action, reward, next_state, done)

        state = next_state_tensor
        total_reward += 0 if reward < 0 else reward

        # 리플레이 버퍼에서 샘플링하여 학습
        if buffer.size() >= batch_size:
            states, actions, rewards, next_states, dones = buffer.sample(batch_size)

            states = torch.tensor(states, dtype=torch.float32).squeeze(1)
            actions = torch.tensor(actions, dtype=torch.int64).unsqueeze(1)
            rewards = torch.tensor(rewards, dtype=torch.float32).unsqueeze(1)
            next_states = torch.tensor(next_states, dtype=torch.float32).squeeze(1)
            dones = torch.tensor(dones, dtype=torch.float32).unsqueeze(1)

            # Q-Learning 목표값 계산
            with torch.no_grad():
                max_next_q_values = target_network(next_states).max(dim=1, keepdim=True)[0]
                targets = rewards + (1 - dones) * gamma * max_next_q_values

            # 현재 Q-값 계산
            q_values = q_network(states).gather(1, actions)

            # 손실 계산 및 역전파
            loss = loss_fn(q_values, targets)
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

        if done:
            break

    # Epsilon 감소
    if epsilon > epsilon_min:
        epsilon *= epsilon_decay

    mRewards.append(total_reward)    
    # 목표 신경망 업데이트
    if episode % update_target_every == 0:
        target_network.load_state_dict(q_network.state_dict())

    print(f"Episode {episode + 1}/{episodes}, Total Reward: {total_reward}")

def moving_average(data, window_size):
    return np.convolve(data, np.ones(window_size) / window_size, mode='valid')

# 결과 출력
print("평균 보상:", np.mean(mRewards))

window_size = 30
ma = moving_average(mRewards, window_size)

# 이동 평균
plt.plot(mRewards)
plt.plot(range(window_size - 1, len(mRewards)), ma, label="5-Point Moving Average", color='red', linewidth=2)


plt.xlabel("Episode")
plt.ylabel("Reward")
plt.title("CartPole Rewards with DQN")
plt.show()
평균 보상: 291.274

 

Carpole v1의 reward가 500회 입니다. 에피소드를 2000외부터 최대 점수 500점으로 올라가는 것을 확인할 수 있었습니다.

Experience Replay와 Target Network를 적용 후 평균 12점에서 291점으로 올갔습니다.

 

CartPole-v1 환경에서 DQN을 적용한 결과, 에피소드 초기 평균 점수가 약 12점에서 학습 후 평균 291점으로 크게 향상되었습니다. 약 2000 에피소드 이후 최대 점수인 500점에 도달하는 것을 확인하였으며, 이는 Experience Replay와 Target Network의 적용 덕분으로 보입니다. 

그러나 하이퍼파라미터 튜닝 과정에서 학습을 안정화시키는 것이 매우 어려웠으며,  하이퍼 파라미터(학습률과 탐험 전략e-greedy, 의 설정등)에 의해 결과가 크게 달라졌습니다. 이러한 결과를 통해 강화학습 모델이 하이퍼파라미터에 매우 민감하며, 성능을 극대화하기 위해 추가적인 실험과 분석이 필요함을 알 수 있었습니다.

 

DQN은 발전하며 이후 수많은 기법이 연구되었습니다.

이중 많이 사용되는 PER과 Double DQN에 대해 알아보겠습니다.

 

1. 우선순위 경험 재생(PER)

우선순위 경험재생(PER, Prioritized Experience Reply)는 일반적인 경험재생(Experience Reply)의 개선한것으로, 경험 데이터를 무작위로 선택하는 대신 우선순위에 따라 선택하도록 한 기법입니다.

우선 순위는 y_true와 y_pred의 차이의 절대값이 큰 데이터를 중요한 데이터로 간주합니다. 차이가 크면 더 배울것이 많다는 것으로 간주할 수 있습니다.

실제값과 예측값의 차이를 TD오차 델타 $\delta$로 하고 공식으로 표현하면 아래와 같습니다.

 

$ \delta_t = \left| R_t + \gamma \cdot \max_{a'} Q(S_{t+1}, a') - Q(S_t, A_t) \right| $

 

우선순위 경험재생에서는 $\delta_t$를 경험데이터에 포함 시켜 ${S_t, A_t, R_t, S_{t+1}, \delta_t }$를 버퍼에 추가합니다.

이후 버퍼에서 데이터를 꺼낼때 $\delta_t$를 이용하여 데이터가 선택될 확률을 구합니다.

선택될 확률은 아래와 같은 수식으로 표현됩니다.

 

$P(i) = \frac{\delta_i}{\sum_{k=0}^N \delta_k}$

 

데이터를 꺼낼때 이 확률을 기반으로 추출하여 오차가 큰것을 더 우선적으로 학습하여 더빨리 수렴하는 것을 기대할 수 있습니다.

 

2.  Double DQN

Double DQN은 기존 DQN의 Q-Value 과추정(overestimation)문제를 해결하기 위해 제안된 방법입다.

먼저 Q-Value과추정 문제에 대해 알아보겠습니다.

 

Q-value 과추정(over estimation)이란, 다음 상태 $S_{t+1}$의 Action중 최대값을 사용하는데, 이 최대값이 추정값으로 노이즈가 포함됨으로 이러한 Noise 값 중 가장 큰 Q값을 취하면 평균적으로 Over estimation된다는 의미입니다. 즉 추정 오차까지  증폭시키게 됩니다.

 

과추정의 예 출처 : https://horomary.hatenablog.com/entry/2021/02/06/013412

 

(1) 가정

 

  • 어떤 상태 s에서 가능한 5개의 행동 $a_0, a_1, ..., a_4가 있음.
  • 각 행동에 대한 진짜 Q값은 모두 0임:$Q^*(s, a) = [0, 0, 0, 0, 0]$
  • 노이즈가 전혀 없으면,$\mathbb{E}[\max_a Q^*(s, a)] = 0$ : 과대평가 없음.

(2) 현실세계

  • 실제 학습 시에는 예측값에 노이즈가 섞임:
  • $Q^*(s, a) = [\epsilon_0, \epsilon_1, \epsilon_2, \epsilon_3, \epsilon_4]$
  • 이 경우,$\mathbb{E}[\max_a Q^*(s, a)] > 0$  평균적으로 최대값은 0보다 커짐, 즉 과대평가 발생.

통계적으로 최대값의 기대값은 평균보다 크다는 원리와 통일합니다.

아래는 위 링크의 코드로 300개의 샘플을 수집하여 분포를 히스토그램으로 그리면 실제 기대값 0보다 크게 과추정된 결과를 확인할 수 있습니다.

https://cdn-ak.f.st-hatena.com/images/fotolife/h/horomary/20210205/20210205010153.png

(3) 해결 아이디어

이 과추정 문제는 Action 선택을 평균값은 같지만 다른 노이즈가 붙은 네트워크(target-Q-network에 대한 Q-network)로 수행함으로써 줄일 수 있다는 것이 Double DQN의 핵심 원리 입니다.두 개의 Q-network로 노이즈를 감소시킨다는 Double DQN의 개념은 연속값 제어를 위한 오프폴리시 기법인 TD3(DDPG의 후속 기법)에서도 clipped-double-Q-learning이라는 명칭으로 채택되고 있습니다. 즉, 서로 다른 network의 예측값을 사용하면 노이즈가 덜 겹치므로 과추정이 완화된다는 아이디어입니다.

아래는 위 사이트의 코드를 일부 수정하여, 두개의 네트워크를 사용했을 때 해결이 되는지 확인한 코드입니다.

2개의 네트워크를 사용했을 때 기대값이 0과 유사해짐을 확인할 수 있습니다.

import numpy as np 
import matplotlib.pyplot as plt 
import seaborn as sns 

action_space = 5

maxQ_list = []
for i in range(300):
    Q = np.zeros(action_space) + np.random.normal(0, 1, size=action_space)
    maxQ = Q[np.argmax(Q)]
    maxQ_list.append(maxQ)

sns.displot(maxQ_list, kde=True)  # ← KDE 추가!
plt.axvline(x= 0,  linestyle="--", color="darkred")
plt.axvline(x= np.mean(maxQ_list),  linestyle="--", color="orange")
plt.xlabel("maxQ")

plt.show()

기대값이 0(붉은색)이지만 과대 추정으로 큰값(오랜지)

import numpy as np 
import matplotlib.pyplot as plt 
import seaborn as sns 

action_space = 5

maxQ_list = []
for i in range(300):
    Q = np.zeros(action_space) + np.random.normal(0, 1, size=action_space)
    Q_prime =  np.zeros(action_space) + np.random.normal(0, 1, size=action_space)
    maxQ = Q[np.argmax(Q_prime)]
    maxQ_list.append(maxQ)

sns.displot(maxQ_list, kde=True)  # ← KDE 추가!
plt.axvline(x= 0,  linestyle="--", color="red", alpha=0.5)
plt.axvline(x= np.mean(maxQ_list),  linestyle="--", color="orange", alpha=0.5)
plt.xlabel("maxQ")

plt.show()

Q와 Q_prime 사용시 거의 0에 일치함

(4) Double DQN 수식

이제 실제 Double DQN의 수식을 살펴 보겠습니다.

DQN에서는 목표 신경망이라는 기법을 사용해서, 두개의 신경망을 사용합니다. 두 신경망의 매개변수를 $\theta$와 target 신경망 $\theta^{\prime}$ 이라고 할때 다음 식으로 표현됩니다.

$y_t = R_t + \gamma \cdot \max_{a} Q_{\theta^{\prime}}(S_{t+1}, a)$

 

Double DQN에서 노이즈의 효과를 감소시키기 위해 a의 선택을 다른 원래 네트워크의 $\theta$ 를 사용해서 수식은 아래와 같이 변경됩니다.

 

$y_t = R_t + \gamma \cdot Q_{\theta^{\prime}} \left( S_{t+1}, \arg\max_{a} Q_{\theta}(S_{t+1}, a) \right)$

 

action을 두개의 Q함수를 구분하여 사용해서 과추정 이슈를 감소시키고 학습이 안정적으로 이루어집니다.

 

전체 코드

my_dqn.ipynb
0.28MB

 

+ Recent posts