글쓰기 프리뷰
    DQN 변형 알고리즘: Double DQN부터 Rainbow까지

    DQN 변형 알고리즘: Double DQN부터 Rainbow까지

    (수정: 2026년 1월 3일 오전 04:15)

    DQN 변형 알고리즘: Double DQN부터 Rainbow까지

    기본 DQN이 혁신적이었지만, 여러 한계점이 있었습니다. 연구자들은 이를 개선하기 위해 다양한 변형 알고리즘을 제안했고, 결국 모든 개선점을 통합한 Rainbow가 탄생했습니다. 이 글에서는 각 변형의 핵심 아이디어와 구현 방법을 상세히 살펴봅니다.


    1. DQN의 한계점

    기본 DQN을 사용하다 보면 다음과 같은 문제를 마주하게 됩니다:

    문제설명해결책
    Q값 과대평가max 연산이 노이즈를 증폭시켜 Q값이 실제보다 높게 추정됨Double DQN
    비효율적인 표현모든 행동의 Q값을 개별적으로 학습Dueling DQN
    균등한 샘플링중요한 경험이나 별로 중요하지 않은 경험을 동일하게 취급Prioritized Experience Replay
    단일 스텝 학습한 스텝의 보상만 사용Multi-step Learning
    점 추정Q값의 분포가 아닌 기대값만 학습Distributional RL
    ε-greedy의 한계탐험이 비효율적Noisy Networks

    2. Double DQN (DDQN)

    문제: Q값 과대평가 (Overestimation)

    기본 DQN에서 타겟 계산:

    target = reward + gamma * max(Q_target(next_state))

    max 연산은 노이즈가 있을 때 Q값을 과대평가합니다. 여러 행동 중 가장 높은 값을 선택하면, 그 중 하나라도 우연히 높게 추정되면 그 값이 사용되기 때문입니다.

    해결책: 행동 선택과 평가 분리

    Double DQN은 행동 선택행동 평가를 다른 네트워크로 수행합니다:

    # DQN (기존) # 타겟 네트워크가 행동 선택 + 평가 모두 수행 next_q_values = target_net(next_states) target = reward + gamma * next_q_values.max(1)[0] # Double DQN (개선) # 메인 네트워크: 행동 선택 # 타겟 네트워크: 선택된 행동 평가 best_actions = policy_net(next_states).argmax(1, keepdim=True) next_q_values = target_net(next_states).gather(1, best_actions).squeeze() target = reward + gamma * next_q_values

    완전한 Double DQN 구현

    import torch import torch.nn as nn import torch.optim as optim import numpy as np from collections import deque import random class DoubleDQN(nn.Module): def __init__(self, state_dim, action_dim): super().__init__() self.network = nn.Sequential( nn.Linear(state_dim, 128), nn.ReLU(), nn.Linear(128, 128), nn.ReLU(), nn.Linear(128, action_dim) ) def forward(self, x): return self.network(x) class DoubleDQNAgent: def __init__(self, state_dim, action_dim, lr=1e-3, gamma=0.99): self.action_dim = action_dim self.gamma = gamma self.policy_net = DoubleDQN(state_dim, action_dim) self.target_net = DoubleDQN(state_dim, action_dim) self.target_net.load_state_dict(self.policy_net.state_dict()) self.optimizer = optim.Adam(self.policy_net.parameters(), lr=lr) self.memory = deque(maxlen=10000) def update(self, batch_size=64): if len(self.memory) < batch_size: return batch = random.sample(self.memory, batch_size) states, actions, rewards, next_states, dones = zip(*batch) states = torch.FloatTensor(np.array(states)) actions = torch.LongTensor(actions).unsqueeze(1) rewards = torch.FloatTensor(rewards) next_states = torch.FloatTensor(np.array(next_states)) dones = torch.FloatTensor(dones) # 현재 Q값 current_q = self.policy_net(states).gather(1, actions).squeeze() # Double DQN 타겟 계산 with torch.no_grad(): # 1. 메인 네트워크로 최적 행동 선택 best_actions = self.policy_net(next_states).argmax(1, keepdim=True) # 2. 타겟 네트워크로 해당 행동의 Q값 평가 next_q = self.target_net(next_states).gather(1, best_actions).squeeze() target_q = rewards + self.gamma * next_q * (1 - dones) # 손실 계산 및 역전파 loss = nn.MSELoss()(current_q, target_q) self.optimizer.zero_grad() loss.backward() self.optimizer.step() return loss.item()

    효과

    연구에 따르면 Double DQN은 아타리 게임에서 DQN 대비 평균적으로 더 높은 점수를 달성했으며, 특히 Q값 과대평가가 심했던 게임에서 큰 개선을 보였습니다.


    3. Dueling DQN

    핵심 아이디어: Q = V + A

    기본 DQN은 각 상태-행동 쌍의 Q값을 개별적으로 학습합니다. 하지만 생각해보면:

    • 어떤 상태는 어떤 행동을 하든 좋거나 나쁨 (상태 자체의 가치)
    • 어떤 행동은 특정 상태에서만 특별히 좋거나 나쁨 (행동의 이점)

    Dueling DQN은 이를 분리합니다:

    Q(s,a)=V(s)+A(s,a)1AaA(s,a)Q(s, a) = V(s) + A(s, a) - \frac{1}{|A|}\sum_{a'} A(s, a')

    • V(s): 상태 가치 (State Value) - 상태가 얼마나 좋은가
    • A(s, a): 이점 (Advantage) - 해당 행동이 평균 대비 얼마나 좋은가

    마지막 항은 A의 평균을 빼서 식별성(identifiability) 문제를 해결합니다.

    구조

    입력 (상태) 공유 CNN/FC 레이어 ├── Value Stream → V(s) [1개 출력] └── Advantage Stream → A(s,a) [행동 수만큼 출력] Q(s,a) = V(s) + A(s,a) - mean(A)

    구현

    class DuelingDQN(nn.Module): def __init__(self, state_dim, action_dim): super().__init__() # 공유 특징 추출 레이어 self.feature = nn.Sequential( nn.Linear(state_dim, 128), nn.ReLU(), nn.Linear(128, 128), nn.ReLU() ) # Value Stream: V(s) self.value_stream = nn.Sequential( nn.Linear(128, 64), nn.ReLU(), nn.Linear(64, 1) # 단일 값 출력 ) # Advantage Stream: A(s, a) self.advantage_stream = nn.Sequential( nn.Linear(128, 64), nn.ReLU(), nn.Linear(64, action_dim) # 각 행동별 이점 ) def forward(self, x): features = self.feature(x) value = self.value_stream(features) # [batch, 1] advantage = self.advantage_stream(features) # [batch, action_dim] # Q = V + (A - mean(A)) # advantage에서 평균을 빼서 식별성 확보 q_values = value + (advantage - advantage.mean(dim=1, keepdim=True)) return q_values # CNN 버전 (이미지 입력용) class DuelingDQN_CNN(nn.Module): def __init__(self, action_dim): super().__init__() # 공유 CNN (아타리 스타일) self.conv = nn.Sequential( nn.Conv2d(4, 32, kernel_size=8, stride=4), nn.ReLU(), nn.Conv2d(32, 64, kernel_size=4, stride=2), nn.ReLU(), nn.Conv2d(64, 64, kernel_size=3, stride=1), nn.ReLU() ) conv_out_size = 64 * 7 * 7 # Value Stream self.value = nn.Sequential( nn.Linear(conv_out_size, 512), nn.ReLU(), nn.Linear(512, 1) ) # Advantage Stream self.advantage = nn.Sequential( nn.Linear(conv_out_size, 512), nn.ReLU(), nn.Linear(512, action_dim) ) def forward(self, x): x = x / 255.0 conv_out = self.conv(x).view(x.size(0), -1) value = self.value(conv_out) advantage = self.advantage(conv_out) return value + (advantage - advantage.mean(dim=1, keepdim=True))

    직관적 이해

    게임에서 예를 들면:

    • V(s): "지금 체력이 만땅이고 적이 멀리 있어서 안전한 상태다" → 상태 가치 높음
    • A(s, 공격): "하지만 지금은 공격보다 회피가 더 유리하다" → 공격의 이점은 낮음

    이렇게 분리하면 많은 상태에서 행동과 무관하게 좋은/나쁜 상태를 더 빨리 학습할 수 있습니다.


    4. Prioritized Experience Replay (PER)

    문제: 모든 경험이 동등한가?

    기본 Experience Replay는 모든 경험을 균등하게 샘플링합니다. 하지만:

    • 희귀하고 중요한 경험 (예: 처음 골인한 경험)
    • 이미 잘 학습된 평범한 경험

    이 둘을 같은 확률로 샘플링하는 것은 비효율적입니다.

    해결책: TD 오차 기반 우선순위

    TD 오차가 큰 경험 = 예측이 크게 틀린 경험 = 더 많이 배울 수 있는 경험

    pi=TD_errori+ϵp_i = |TD\_error_i| + \epsilon

    P(i)=piαkpkαP(i) = \frac{p_i^\alpha}{\sum_k p_k^\alpha}

    • α: 우선순위 지수 (0이면 균등 샘플링, 1이면 완전 우선순위)
    • ε: 작은 양수 (TD 오차가 0이어도 샘플링 가능하게)

    중요도 샘플링 가중치 (IS Weight)

    우선순위 샘플링은 분포를 바꾸므로, 편향을 보정해야 합니다:

    wi=(1NP(i))βw_i = \left( \frac{1}{N \cdot P(i)} \right)^\beta

    β는 0에서 시작하여 학습이 진행될수록 1로 증가시킵니다.

    구현: Sum Tree 자료구조

    효율적인 우선순위 샘플링을 위해 Sum Tree를 사용합니다:

    import numpy as np class SumTree: """ Sum Tree: O(log n) 시간에 우선순위 기반 샘플링 - 리프 노드: 각 경험의 우선순위 - 내부 노드: 자식들의 합 - 루트: 전체 우선순위 합 """ def __init__(self, capacity): self.capacity = capacity self.tree = np.zeros(2 * capacity - 1) # 완전 이진 트리 self.data = np.zeros(capacity, dtype=object) self.write_idx = 0 self.n_entries = 0 def _propagate(self, idx, change): """우선순위 변경을 루트까지 전파""" parent = (idx - 1) // 2 self.tree[parent] += change if parent != 0: self._propagate(parent, change) def _retrieve(self, idx, s): """우선순위 합 s에 해당하는 리프 찾기""" left = 2 * idx + 1 right = left + 1 if left >= len(self.tree): return idx if s <= self.tree[left]: return self._retrieve(left, s) else: return self._retrieve(right, s - self.tree[left]) def total(self): return self.tree[0] def add(self, priority, data): """새 경험 추가""" idx = self.write_idx + self.capacity - 1 self.data[self.write_idx] = data self.update(idx, priority) self.write_idx = (self.write_idx + 1) % self.capacity self.n_entries = min(self.n_entries + 1, self.capacity) def update(self, idx, priority): """우선순위 업데이트""" change = priority - self.tree[idx] self.tree[idx] = priority self._propagate(idx, change) def get(self, s): """우선순위 합 s에 해당하는 (인덱스, 우선순위, 데이터) 반환""" idx = self._retrieve(0, s) data_idx = idx - self.capacity + 1 return idx, self.tree[idx], self.data[data_idx] class PrioritizedReplayBuffer: """우선순위 경험 재생 버퍼""" def __init__(self, capacity, alpha=0.6, beta_start=0.4, beta_frames=100000): self.tree = SumTree(capacity) self.capacity = capacity self.alpha = alpha self.beta_start = beta_start self.beta_frames = beta_frames self.frame = 1 self.epsilon = 1e-6 def beta(self): """β를 점진적으로 1까지 증가""" return min(1.0, self.beta_start + self.frame * (1.0 - self.beta_start) / self.beta_frames) def push(self, state, action, reward, next_state, done): """최대 우선순위로 새 경험 추가""" max_priority = np.max(self.tree.tree[-self.capacity:]) if max_priority == 0: max_priority = 1.0 experience = (state, action, reward, next_state, done) self.tree.add(max_priority, experience) def sample(self, batch_size): """우선순위 기반 샘플링""" batch = [] indices = [] priorities = [] segment = self.tree.total() / batch_size self.frame += 1 beta = self.beta() for i in range(batch_size): a = segment * i b = segment * (i + 1) s = np.random.uniform(a, b) idx, priority, data = self.tree.get(s) batch.append(data) indices.append(idx) priorities.append(priority) # 중요도 샘플링 가중치 계산 sampling_probs = np.array(priorities) / self.tree.total() weights = (self.tree.n_entries * sampling_probs) ** (-beta) weights /= weights.max() # 정규화 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), indices, weights ) def update_priorities(self, indices, td_errors): """TD 오차 기반으로 우선순위 업데이트""" for idx, td_error in zip(indices, td_errors): priority = (abs(td_error) + self.epsilon) ** self.alpha self.tree.update(idx, priority) def __len__(self): return self.tree.n_entries

    PER을 적용한 학습 루프

    def train_with_per(agent, buffer, batch_size=64): if len(buffer) < batch_size: return # 우선순위 기반 샘플링 states, actions, rewards, next_states, dones, indices, weights = buffer.sample(batch_size) # 텐서 변환 states = torch.FloatTensor(states) actions = torch.LongTensor(actions).unsqueeze(1) rewards = torch.FloatTensor(rewards) next_states = torch.FloatTensor(next_states) dones = torch.FloatTensor(dones) weights = torch.FloatTensor(weights) # Q값 계산 current_q = agent.policy_net(states).gather(1, actions).squeeze() with torch.no_grad(): next_q = agent.target_net(next_states).max(1)[0] target_q = rewards + agent.gamma * next_q * (1 - dones) # TD 오차 계산 (우선순위 업데이트용) td_errors = (current_q - target_q).detach().cpu().numpy() # 중요도 가중치를 적용한 손실 loss = (weights * (current_q - target_q) ** 2).mean() agent.optimizer.zero_grad() loss.backward() agent.optimizer.step() # 우선순위 업데이트 buffer.update_priorities(indices, td_errors) return loss.item()

    5. Multi-step Learning

    아이디어: n-스텝 부트스트래핑

    기본 DQN은 1-스텝 TD 학습을 사용합니다:

    y=rt+γmaxaQ(st+1,a)y = r_t + \gamma \max_a Q(s_{t+1}, a)

    Multi-step은 n-스텝 보상을 사용합니다:

    y=rt+γrt+1+γ2rt+2+...+γnmaxaQ(st+n,a)y = r_t + \gamma r_{t+1} + \gamma^2 r_{t+2} + ... + \gamma^n \max_a Q(s_{t+n}, a)

    장단점

    장점단점
    보상 신호가 더 빨리 전파됨Off-policy 학습과 충돌 가능
    부트스트래핑 편향 감소n이 크면 분산 증가

    구현

    class NStepReplayBuffer: def __init__(self, capacity, n_step=3, gamma=0.99): self.capacity = capacity self.n_step = n_step self.gamma = gamma self.buffer = deque(maxlen=capacity) self.n_step_buffer = deque(maxlen=n_step) def _get_n_step_info(self): """n-스텝 보상과 마지막 상태 계산""" reward, next_state, done = self.n_step_buffer[-1][-3:] # 역순으로 n-스텝 보상 계산 for transition in reversed(list(self.n_step_buffer)[:-1]): r, ns, d = transition[-3:] reward = r + self.gamma * reward * (1 - d) if d: next_state, done = ns, d return reward, next_state, done def push(self, state, action, reward, next_state, done): self.n_step_buffer.append((state, action, reward, next_state, done)) if len(self.n_step_buffer) < self.n_step: return # n-스텝 전이 계산 n_step_reward, n_step_next_state, n_step_done = self._get_n_step_info() first_state, first_action = self.n_step_buffer[0][:2] self.buffer.append(( first_state, first_action, n_step_reward, n_step_next_state, n_step_done )) def sample(self, batch_size): batch = random.sample(self.buffer, batch_size) return zip(*batch) def __len__(self): return len(self.buffer)

    6. Distributional RL (C51)

    아이디어: Q값의 분포 학습

    기본 DQN은 Q값의 기대값만 학습합니다. 하지만 같은 기대값이라도 분포가 다를 수 있습니다:

    • 행동 A: 항상 보상 5 (분산 0)
    • 행동 B: 50% 확률로 0, 50% 확률로 10 (기대값 5, 분산 높음)

    Distributional RL은 Q값의 전체 분포를 학습합니다.

    C51 알고리즘

    보상의 분포를 51개의 이산 원자(atom)로 근사합니다:

    class C51Network(nn.Module): def __init__(self, state_dim, action_dim, n_atoms=51, v_min=-10, v_max=10): super().__init__() self.action_dim = action_dim self.n_atoms = n_atoms self.v_min = v_min self.v_max = v_max self.delta_z = (v_max - v_min) / (n_atoms - 1) self.support = torch.linspace(v_min, v_max, n_atoms) self.network = nn.Sequential( nn.Linear(state_dim, 128), nn.ReLU(), nn.Linear(128, 128), nn.ReLU(), nn.Linear(128, action_dim * n_atoms) ) def forward(self, x): batch_size = x.size(0) # 각 행동에 대한 분포 출력 [batch, action * atoms] logits = self.network(x) logits = logits.view(batch_size, self.action_dim, self.n_atoms) # 소프트맥스로 확률 분포 변환 probs = torch.softmax(logits, dim=2) return probs def get_q_values(self, x): """분포에서 Q값(기대값) 계산""" probs = self(x) support = self.support.to(x.device) q_values = (probs * support.unsqueeze(0).unsqueeze(0)).sum(dim=2) return q_values

    7. Noisy Networks

    아이디어: 네트워크에 노이즈 주입

    ε-greedy 대신 네트워크 파라미터에 학습 가능한 노이즈를 추가하여 탐험합니다.

    구현

    class NoisyLinear(nn.Module): """노이즈가 있는 선형 레이어""" def __init__(self, in_features, out_features, sigma_init=0.5): super().__init__() self.in_features = in_features self.out_features = out_features self.sigma_init = sigma_init # 평균 파라미터 (학습됨) self.weight_mu = nn.Parameter(torch.empty(out_features, in_features)) self.bias_mu = nn.Parameter(torch.empty(out_features)) # 노이즈 스케일 파라미터 (학습됨) self.weight_sigma = nn.Parameter(torch.empty(out_features, in_features)) self.bias_sigma = nn.Parameter(torch.empty(out_features)) # 노이즈 (샘플링됨, 학습 안 됨) self.register_buffer('weight_epsilon', torch.empty(out_features, in_features)) self.register_buffer('bias_epsilon', torch.empty(out_features)) self.reset_parameters() self.reset_noise() def reset_parameters(self): mu_range = 1 / np.sqrt(self.in_features) self.weight_mu.data.uniform_(-mu_range, mu_range) self.bias_mu.data.uniform_(-mu_range, mu_range) self.weight_sigma.data.fill_(self.sigma_init / np.sqrt(self.in_features)) self.bias_sigma.data.fill_(self.sigma_init / np.sqrt(self.out_features)) def reset_noise(self): """새로운 노이즈 샘플링""" epsilon_in = self._scale_noise(self.in_features) epsilon_out = self._scale_noise(self.out_features) self.weight_epsilon.copy_(epsilon_out.outer(epsilon_in)) self.bias_epsilon.copy_(epsilon_out) def _scale_noise(self, size): x = torch.randn(size) return x.sign() * x.abs().sqrt() def forward(self, x): if self.training: weight = self.weight_mu + self.weight_sigma * self.weight_epsilon bias = self.bias_mu + self.bias_sigma * self.bias_epsilon else: weight = self.weight_mu bias = self.bias_mu return F.linear(x, weight, bias) class NoisyDQN(nn.Module): def __init__(self, state_dim, action_dim): super().__init__() self.fc1 = nn.Linear(state_dim, 128) self.fc2 = nn.Linear(128, 128) self.noisy_fc3 = NoisyLinear(128, action_dim) def forward(self, x): x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) return self.noisy_fc3(x) def reset_noise(self): self.noisy_fc3.reset_noise()

    8. Rainbow DQN: 모든 것을 합치다

    Rainbow = 6가지 개선의 결합

    1. Double DQN: Q값 과대평가 방지
    2. Dueling Network: 상태 가치와 이점 분리
    3. Prioritized Experience Replay: 중요한 경험 우선 학습
    4. Multi-step Learning: n-스텝 보상 사용
    5. Distributional RL (C51): Q값 분포 학습
    6. Noisy Networks: 파라미터 노이즈로 탐험

    Rainbow 구조

    class RainbowDQN(nn.Module): """ Rainbow DQN: 모든 개선점을 통합한 네트워크 - Dueling Architecture - Noisy Networks - Distributional RL (C51) """ def __init__(self, state_dim, action_dim, n_atoms=51, v_min=-10, v_max=10): super().__init__() self.action_dim = action_dim self.n_atoms = n_atoms self.v_min = v_min self.v_max = v_max self.support = torch.linspace(v_min, v_max, n_atoms) self.delta_z = (v_max - v_min) / (n_atoms - 1) # 공유 특징 추출 self.feature = nn.Sequential( nn.Linear(state_dim, 128), nn.ReLU() ) # Dueling + Noisy: Value Stream self.value_hidden = NoisyLinear(128, 128) self.value = NoisyLinear(128, n_atoms) # Dueling + Noisy: Advantage Stream self.advantage_hidden = NoisyLinear(128, 128) self.advantage = NoisyLinear(128, action_dim * n_atoms) def forward(self, x): batch_size = x.size(0) features = self.feature(x) # Value stream value = F.relu(self.value_hidden(features)) value = self.value(value).view(batch_size, 1, self.n_atoms) # Advantage stream advantage = F.relu(self.advantage_hidden(features)) advantage = self.advantage(advantage).view(batch_size, self.action_dim, self.n_atoms) # Dueling aggregation q_atoms = value + advantage - advantage.mean(dim=1, keepdim=True) # 확률 분포로 변환 probs = F.softmax(q_atoms, dim=2) return probs def get_q_values(self, x): probs = self(x) support = self.support.to(x.device) return (probs * support.unsqueeze(0).unsqueeze(0)).sum(dim=2) def reset_noise(self): self.value_hidden.reset_noise() self.value.reset_noise() self.advantage_hidden.reset_noise() self.advantage.reset_noise() class RainbowAgent: def __init__(self, state_dim, action_dim, n_atoms=51, v_min=-10, v_max=10, lr=1e-4, gamma=0.99, n_step=3, alpha=0.6, beta_start=0.4): self.gamma = gamma self.n_step = n_step self.n_atoms = n_atoms self.v_min = v_min self.v_max = v_max self.action_dim = action_dim # 네트워크 self.policy_net = RainbowDQN(state_dim, action_dim, n_atoms, v_min, v_max) self.target_net = RainbowDQN(state_dim, action_dim, n_atoms, v_min, v_max) self.target_net.load_state_dict(self.policy_net.state_dict()) self.optimizer = optim.Adam(self.policy_net.parameters(), lr=lr) # PER + N-step 버퍼 (실제로는 둘을 결합한 버퍼 필요) self.memory = PrioritizedReplayBuffer(capacity=100000, alpha=alpha, beta_start=beta_start) self.support = torch.linspace(v_min, v_max, n_atoms) self.delta_z = (v_max - v_min) / (n_atoms - 1) def select_action(self, state): with torch.no_grad(): state_tensor = torch.FloatTensor(state).unsqueeze(0) q_values = self.policy_net.get_q_values(state_tensor) return q_values.argmax().item() def update(self, batch_size=32): if len(self.memory) < batch_size: return # PER 샘플링 states, actions, rewards, next_states, dones, indices, weights = self.memory.sample(batch_size) # 텐서 변환 states = torch.FloatTensor(states) actions = torch.LongTensor(actions) rewards = torch.FloatTensor(rewards) next_states = torch.FloatTensor(next_states) dones = torch.FloatTensor(dones) weights = torch.FloatTensor(weights) # Distributional RL 업데이트 (C51) # ... (복잡한 분포 투영 로직) # 노이즈 리셋 self.policy_net.reset_noise() self.target_net.reset_noise() def update_target(self): self.target_net.load_state_dict(self.policy_net.state_dict())

    Rainbow의 성능

    DeepMind의 연구에 따르면 Rainbow는 아타리 게임에서:

    • 기본 DQN 대비 5배 이상 높은 점수
    • 개별 개선들의 단순 합보다 더 나은 성능
    • 학습 효율성도 크게 향상

    9. 각 기법의 기여도

    DeepMind의 ablation study 결과:

    제거된 기법성능 하락중요도
    Prioritized Replay가장 큼⭐⭐⭐⭐⭐
    Multi-step크게 하락⭐⭐⭐⭐
    Distributional크게 하락⭐⭐⭐⭐
    Noisy Nets중간⭐⭐⭐
    Dueling약간⭐⭐
    Double가장 작음

    결론: 모든 기법이 중요하지만, PER과 Multi-step이 특히 핵심적입니다.


    10. 실전 팁

    언제 어떤 기법을 사용할까?

    상황추천 기법
    처음 시작Double DQN (가장 쉽고 효과적)
    학습이 불안정Dueling + Double
    샘플 효율성 필요PER 추가
    장기 보상 중요Multi-step 추가
    최고 성능 필요Rainbow
    리소스 제한Double + Dueling만 사용

    구현 순서 추천

    1. 기본 DQN 구현 및 동작 확인 2. Double DQN 추가 (2줄 수정) 3. Dueling Architecture 적용 4. PER 추가 (가장 복잡) 5. 필요시 Multi-step, Noisy Networks 추가

    Quiz

    Double DQN이 해결하려는 DQN의 문제점은 무엇인가요?

    Dunde's Portfolio

    © 2026 Dunde. All rights reserved.

    Built with React, TypeScript, and Vite. Deployed on GitHub Pages.