<cite id="ffb66"></cite><cite id="ffb66"><track id="ffb66"></track></cite>
      <legend id="ffb66"><li id="ffb66"></li></legend>
      色婷婷久,激情色播,久久久无码专区,亚洲中文字幕av,国产成人A片,av无码免费,精品久久国产,99视频精品3
      網(wǎng)易首頁 > 網(wǎng)易號 > 正文 申請入駐

      讓 Q 值估計更準確:從 DQN 到 Double DQN 的改進方案

      0
      分享至


      DQN 用 max Q(s',a') 計算目標值,等于在挑 Q 值最高的動作,但是這些動作中包括了那些因為估計噪聲而被高估的動作,素以就會產(chǎn)生過估計偏差,直接后果是訓(xùn)練不穩(wěn)定、策略次優(yōu)。

      這篇文章要解決的就是這個問題,內(nèi)容包括:DQN 為什么會過估計、Double DQN 怎么把動作選擇和評估拆開、Dueling DQN 怎么分離狀態(tài)值和動作優(yōu)勢、優(yōu)先經(jīng)驗回放如何讓采樣更聰明,以及用 PyTorch 從頭實現(xiàn)這些改進。最后還會介紹一個 CleanRL 的專業(yè)實現(xiàn)。



      過估計問題

      DQN 的目標值如下:

      y = r + γ·max?' Q(s', a'; θ?)

      問題就在于,同一個網(wǎng)絡(luò)既負責選動作(a* = argmax Q),又負責評估這個動作的價值。Q 值本身是帶噪聲的估計所以有時候噪聲會讓差動作的 Q 值偏高,取 max 操作天然偏向選那些被高估的動作。

      數(shù)學上有個直觀的解釋:

      E[max(X?, X?, ..., X?)] ≥ max(E[X?], E[X?], ..., E[X?])

      最大值的期望總是大于等于期望的最大值,這是凸函數(shù)的 Jensen 不等式。

      過估計會導(dǎo)致收斂變慢,智能體把時間浪費在探索那些被高估的動作上。其次是策略質(zhì)量打折扣,高噪聲的動作可能比真正好的動作更受青睞。更糟的是過估計會不斷累積,導(dǎo)致訓(xùn)練發(fā)散。泛化能力也會受損——在狀態(tài)空間的噪聲區(qū)域,智能體會表現(xiàn)得過于自信。

      Double DQN:把選擇和評估拆開

      標準 DQN 一個網(wǎng)絡(luò)干兩件事:

      a* = argmax?' Q(s', a'; θ?) # 選最佳動作
      y = r + γ · Q(s', a*; θ?) # 評估這個動作(同一個網(wǎng)絡(luò))

      Double DQN 用兩個網(wǎng)絡(luò),各管一件:

      a* = argmax?' Q(s', a'; θ) # 用當前網(wǎng)絡(luò)選
      y = r + γ · Q(s', a*; θ?) # 用目標網(wǎng)絡(luò)評估

      當前網(wǎng)絡(luò)(θ)選動作,目標網(wǎng)絡(luò)(θ?)評估。兩個網(wǎng)絡(luò)的誤差不相關(guān)這樣最大化偏差就被打破了。

      為什么有效呢?

      假設(shè)當前網(wǎng)絡(luò)把動作 a 的價值估高了,目標網(wǎng)絡(luò)(參數(shù)不同)大概率不會犯同樣的錯。誤差相互獨立,傾向于抵消而非累加。

      最通俗的解釋就是DQN 像是自己給菜打分、自己挑菜吃,這樣爛菜可能就混進來了,而Double DQN 讓朋友打分、你來挑,兩邊的誤差對沖掉了。

      Standard DQN: E[Q(s, argmax? Q(s,a))] ≥ max? E[Q(s,a)] (有偏)
      Double DQN: E[Q?(s, argmax? Q?(s,a))] ≈ max? E[Q(s,a)] (無偏)

      從 DQN 到 Double DQN,只需要改一行:

      # DQN 目標
      next_q_values = target_network(next_states).max(1)[0]
      target = rewards + gamma * next_q_values * (1 - dones)
      # Double DQN 目標
      next_actions = current_network(next_states).argmax(1) # <- 用當前網(wǎng)絡(luò)選
      next_q_values = target_network(next_states).gather(1, next_actions.unsqueeze(1)) # <- 用目標網(wǎng)絡(luò)評估
      target = rewards + gamma * next_q_values.squeeze() * (1 - dones)

      就這一行改動極小,效果卻很明顯。

      實現(xiàn):Double DQN

      擴展 DQN Agent

      class DoubleDQNAgent(DQNAgent):
      """
      Double DQN: 通過解耦動作選擇和評估來減少過估計偏差。
      """
      def __init__(self, *args, **kwargs):
      """
      初始化 Double DQN agent。
      從 DQN 繼承所有內(nèi)容,只改變目標計算。
      """
      super().__init__(*args, **kwargs)
      def update(self) -> Dict[str, float]:
      """
      執(zhí)行 Double DQN 更新。
      Returns:
      metrics: 訓(xùn)練指標
      """
      if len(self.replay_buffer) < self.batch_size:
      return {}
      # 采樣批次
      states, actions, rewards, next_states, dones = self.replay_buffer.sample(
      self.batch_size
      )
      states = states.to(self.device)
      actions = actions.to(self.device)
      rewards = rewards.to(self.device)
      next_states = next_states.to(self.device)
      dones = dones.to(self.device)
      # 當前 Q 值 Q(s,a;θ)
      current_q_values = self.q_network(states).gather(1, actions.unsqueeze(1))
      # Double DQN 目標計算
      with torch.no_grad():
      # 使用當前網(wǎng)絡(luò)選擇動作
      next_actions = self.q_network(next_states).argmax(1)
      # 使用目標網(wǎng)絡(luò)評估動作
      next_q_values = self.target_network(next_states).gather(
      1, next_actions.unsqueeze(1)
      ).squeeze()
      # 計算目標
      target_q_values = rewards + (1 - dones) * self.gamma * next_q_values
      # 計算損失
      loss = F.mse_loss(current_q_values.squeeze(), target_q_values)
      # 梯度下降
      self.optimizer.zero_grad()
      loss.backward()
      torch.nn.utils.clip_grad_norm_(self.q_network.parameters(), max_norm=10.0)
      self.optimizer.step()
      self.training_step += 1
      return {
      'loss': loss.item(),
      'q_mean': current_q_values.mean().item(),
      'q_std': current_q_values.std().item(),
      'target_q_mean': target_q_values.mean().item()
      }

      訓(xùn)練函數(shù):

      def train_double_dqn(
      env_name: str,
      n_episodes: int = 1000,
      max_steps: int = 500,
      train_freq: int = 1,
      eval_frequency: int = 50,
      eval_episodes: int = 10,
      verbose: bool = True,
      **kwargs
      ) -> Tuple:
      """
      訓(xùn)練 Double DQN agent(使用 DoubleDQNAgent 而不是 DQNAgent)。
      """
      # 與 train_dqn 相同但使用 DoubleDQNAgent
      env = gym.make(env_name)
      eval_env = gym.make(env_name)
      state_dim = env.observation_space.shape[0]
      action_dim = env.action_space.n
      # 使用 DoubleDQNAgent
      agent = DoubleDQNAgent(
      state_dim=state_dim,
      action_dim=action_dim,
      **kwargs
      )
      # 訓(xùn)練循環(huán)(與 DQN 相同)
      stats = {
      'episode_rewards': [],
      'episode_lengths': [],
      'losses': [],
      'q_values': [],
      'target_q_values': [],
      'eval_rewards': [],
      'eval_episodes': [],
      'epsilons': []
      }
      print(f"Training Double DQN on {env_name}")
      print(f"State dim: {state_dim}, Action dim: {action_dim}")
      print("=" * 70)
      for episode in range(n_episodes):
      state, _ = env.reset()
      episode_reward = 0
      episode_length = 0
      episode_metrics = []
      for step in range(max_steps):
      action = agent.select_action(state, training=True)
      next_state, reward, terminated, truncated, _ = env.step(action)
      done = terminated or truncated
      agent.store_transition(state, action, reward, next_state, done)
      if step % train_freq == 0:
      metrics = agent.update()
      if metrics:
      episode_metrics.append(metrics)
      episode_reward += reward
      episode_length += 1
      state = next_state
      if done:
      break
      # 更新目標網(wǎng)絡(luò)
      if (episode + 1) % kwargs.get('target_update_freq', 10) == 0:
      agent.update_target_network()
      agent.decay_epsilon()
      # 存儲統(tǒng)計信息
      stats['episode_rewards'].append(episode_reward)
      stats['episode_lengths'].append(episode_length)
      stats['epsilons'].append(agent.epsilon)
      if episode_metrics:
      stats['losses'].append(np.mean([m['loss'] for m in episode_metrics]))
      stats['q_values'].append(np.mean([m['q_mean'] for m in episode_metrics]))
      stats['target_q_values'].append(np.mean([m['target_q_mean'] for m in episode_metrics]))
      # 評估
      if (episode + 1) % eval_frequency == 0:
      eval_reward = evaluate_dqn(eval_env, agent, eval_episodes)
      stats['eval_rewards'].append(eval_reward)
      stats['eval_episodes'].append(episode + 1)
      if verbose:
      avg_reward = np.mean(stats['episode_rewards'][-50:])
      avg_loss = np.mean(stats['losses'][-50:]) if stats['losses'] else 0
      avg_q = np.mean(stats['q_values'][-50:]) if stats['q_values'] else 0
      print(f"Episode {episode + 1:4d} | "
      f"Reward: {avg_reward:7.2f} | "
      f"Eval: {eval_reward:7.2f} | "
      f"Loss: {avg_loss:7.4f} | "
      f"Q: {avg_q:6.2f} | "
      f"ε: {agent.epsilon:.3f}")
      env.close()
      eval_env.close()
      print("=" * 70)
      print("Training complete!")
      return agent, stats

      LunarLander-v3

      # 訓(xùn)練 Double DQN
      if __name__ == "__main__":
      device = 'cuda' if torch.cuda.is_available() else 'cpu'
      agent_ddqn, stats_ddqn = train_double_dqn(
      env_name='LunarLander-v3',
      n_episodes=4000,
      max_steps=1000,
      learning_rate=5e-4,
      gamma=0.99,
      epsilon_start=1.0,
      epsilon_end=0.01,
      epsilon_decay=0.9995,
      buffer_capacity=100000,
      batch_size=128,
      target_update_freq=20,
      train_freq=4,
      eval_frequency=100,
      eval_episodes=10,
      hidden_dims=[256, 256],
      device=device,
      verbose=True
      )
      # 保存模型
      agent_ddqn.save('doubledqn_lunar_lander.pth')

      輸出:

      Training Double DQN on LunarLander-v3
      State dim: 8, Action dim: 4
      ======================================================================
      Episode 100 | Reward: -155.24 | Eval: -885.72 | Loss: 52.9057 | Q: 0.20 | ε: 0.951
      Episode 200 | Reward: -148.85 | Eval: -85.94 | Loss: 37.2449 | Q: 2.14 | ε: 0.905
      Episode 300 | Reward: -111.61 | Eval: -172.48 | Loss: 37.4279 | Q: 3.52 | ε: 0.861
      Episode 400 | Reward: -99.21 | Eval: -198.43 | Loss: 41.5296 | Q: 8.15 | ε: 0.819
      Episode 500 | Reward: -80.75 | Eval: -103.26 | Loss: 56.2701 | Q: 11.70 | ε: 0.779
      ...
      Episode 3200 | Reward: 102.04 | Eval: 159.71 | Loss: 16.5263 | Q: 27.94 | ε: 0.202
      Episode 3300 | Reward: 140.37 | Eval: 191.79 | Loss: 22.5564 | Q: 29.81 | ε: 0.192
      Episode 3400 | Reward: 114.08 | Eval: 269.40 | Loss: 23.2846 | Q: 32.40 | ε: 0.183
      Episode 3500 | Reward: 166.33 | Eval: 244.32 | Loss: 21.8558 | Q: 32.51 | ε: 0.174
      Episode 3600 | Reward: 150.80 | Eval: 265.42 | Loss: 21.6430 | Q: 33.18 | ε: 0.165
      Episode 3700 | Reward: 148.59 | Eval: 239.56 | Loss: 23.8328 | Q: 34.65 | ε: 0.157
      Episode 3800 | Reward: 162.82 | Eval: 233.36 | Loss: 28.3445 | Q: 37.46 | ε: 0.149
      Episode 3900 | Reward: 177.70 | Eval: 259.99 | Loss: 36.2971 | Q: 40.22 | ε: 0.142
      Episode 4000 | Reward: 156.60 | Eval: 251.17 | Loss: 46.7266 | Q: 42.15 | ε: 0.135
      ======================================================================
      Training complete!

      Dueling DQN:分離值和優(yōu)勢

      很多狀態(tài)下,選哪個動作其實差別不大。CartPole 里桿子剛好平衡時,向左向右都行;開車走直線方向盤微調(diào)的結(jié)果差不多;LunarLander 離地面還遠的時候,引擎怎么噴影響也有限。

      標準 DQN 對每個動作單獨學 Q(s,a),把網(wǎng)絡(luò)容量浪費在冗余信息上。Dueling DQN 的思路是把 Q 拆成兩部分:V(s) 表示"這個狀態(tài)本身值多少",A(s,a) 表示"這個動作比平均水平好多少"。

      架構(gòu)如下

      標準 DQN:
      Input -> Hidden Layers -> Q(s,a?), Q(s,a?), ..., Q(s,a?)
      Dueling DQN:
      |-> Value Stream -> V(s)
      Input -> Shared Layers |
      |-> Advantage Stream -> A(s,a?), A(s,a?), ..., A(s,a?)
      Q(s,a) = V(s) + (A(s,a) - mean(A(s,·)))

      為什么要減去均值?不減的話,任何常數(shù)加到 V 再從 A 減掉,得到的 Q 完全一樣,網(wǎng)絡(luò)學不出唯一解。

      數(shù)學表達如下:

      Q(s,a) = V(s) + A(s,a) - (1/|A|)·Σ?' A(s,a')

      也可以用 max 代替 mean:

      Q(s,a) = V(s) + A(s,a) - max?' A(s,a')

      實踐中 max 版本有時效果更好。

      舉個例子:V(s) = 10,好動作的 A 是 +5,差動作的 A 是 -3,平均優(yōu)勢 = (+5-3)/2 = +1。那么 Q(s, 好動作) = 10 + 5 - 1 = 14,Q(s, 差動作) = 10 - 3 - 1 = 6。

      實現(xiàn)

      class DuelingQNetwork(nn.Module):
      """
      Dueling DQN 架構(gòu),分離值和優(yōu)勢。
      理論: Q(s,a) = V(s) + A(s,a) - mean(A(s,·))
      """
      def __init__(
      self,
      state_dim: int,
      action_dim: int,
      hidden_dims: List[int] = [128, 128]
      ):
      """
      初始化 Dueling Q 網(wǎng)絡(luò)。
      Args:
      state_dim: 狀態(tài)空間維度
      action_dim: 動作數(shù)量
      hidden_dims: 共享層大小
      """
      super(DuelingQNetwork, self).__init__()
      self.state_dim = state_dim
      self.action_dim = action_dim
      # 共享特征提取器
      shared_layers = []
      input_dim = state_dim
      for hidden_dim in hidden_dims:
      shared_layers.append(nn.Linear(input_dim, hidden_dim))
      shared_layers.append(nn.ReLU())
      input_dim = hidden_dim
      self.shared_network = nn.Sequential(*shared_layers)
      # 值流: V(s) = 狀態(tài)的標量值
      self.value_stream = nn.Sequential(
      nn.Linear(hidden_dims[-1], 128),
      nn.ReLU(),
      nn.Linear(128, 1)
      )
      # 優(yōu)勢流: A(s,a) = 每個動作的優(yōu)勢
      self.advantage_stream = nn.Sequential(
      nn.Linear(hidden_dims[-1], 128),
      nn.ReLU(),
      nn.Linear(128, action_dim)
      )
      # 初始化權(quán)重
      self.apply(self._init_weights)
      def _init_weights(self, module):
      """初始化網(wǎng)絡(luò)權(quán)重。"""
      if isinstance(module, nn.Linear):
      nn.init.kaiming_normal_(module.weight, nonlinearity='relu')
      nn.init.constant_(module.bias, 0.0)
      def forward(self, state: torch.Tensor) -> torch.Tensor:
      """
      通過 dueling 架構(gòu)的前向傳播。
      Args:
      state: 狀態(tài)批次, 形狀 (batch_size, state_dim)
      Returns:
      q_values: 所有動作的 Q(s,a), 形狀 (batch_size, action_dim)
      """
      # 共享特征
      features = self.shared_network(state)
      # 值: V(s) -> 形狀 (batch_size, 1)
      value = self.value_stream(features)
      # 優(yōu)勢: A(s,a) -> 形狀 (batch_size, action_dim)
      advantages = self.advantage_stream(features)
      # 組合: Q(s,a) = V(s) + A(s,a) - mean(A(s,·))
      q_values = value + advantages - advantages.mean(dim=1, keepdim=True)
      return q_values
      def get_action(self, state: np.ndarray, epsilon: float = 0.0) -> int:
      """
      使用 ε-greedy 策略選擇動作。
      """
      if random.random() < epsilon:
      return random.randint(0, self.action_dim - 1)
      else:
      with torch.no_grad():
      state_tensor = torch.FloatTensor(state).unsqueeze(0).to(
      next(self.parameters()).device
      )
      q_values = self.forward(state_tensor)
      return q_values.argmax(dim=1).item()

      Dueling 架構(gòu)的好處:在動作影響不大的狀態(tài)下學得更好,梯度流動更通暢所以收斂更快,值估計也更穩(wěn)健。

      還可以把兩種改進疊在一起,做成Double Dueling DQN

      class DoubleDuelingDQNAgent(DoubleDQNAgent):
      """
      結(jié)合 Double DQN 和 Dueling DQN 的智能體。
      """
      def __init__(
      self,
      state_dim: int,
      action_dim: int,
      hidden_dims: List[int] = [128, 128],
      **kwargs
      ):
      """
      初始化 Double Dueling DQN 智能體。
      使用 DuelingQNetwork 而不是標準 QNetwork。
      """
      # 暫不調(diào)用 super().__init__()
      # 我們需要以不同方式設(shè)置網(wǎng)絡(luò)
      self.state_dim = state_dim
      self.action_dim = action_dim
      self.gamma = kwargs.get('gamma', 0.99)
      self.batch_size = kwargs.get('batch_size', 64)
      self.target_update_freq = kwargs.get('target_update_freq', 10)
      self.device = torch.device(kwargs.get('device', 'cpu'))
      # 探索
      self.epsilon = kwargs.get('epsilon_start', 1.0)
      self.epsilon_end = kwargs.get('epsilon_end', 0.01)
      self.epsilon_decay = kwargs.get('epsilon_decay', 0.995)
      # 使用 Dueling 架構(gòu)
      self.q_network = DuelingQNetwork(
      state_dim, action_dim, hidden_dims
      ).to(self.device)
      self.target_network = DuelingQNetwork(
      state_dim, action_dim, hidden_dims
      ).to(self.device)
      self.target_network.load_state_dict(self.q_network.state_dict())
      self.target_network.eval()
      # 優(yōu)化器
      learning_rate = kwargs.get('learning_rate', 1e-3)
      self.optimizer = torch.optim.Adam(self.q_network.parameters(), lr=learning_rate)
      # 回放緩沖區(qū)
      buffer_capacity = kwargs.get('buffer_capacity', 100000)
      self.replay_buffer = ReplayBuffer(buffer_capacity)
      # 統(tǒng)計
      self.episode_count = 0
      self.training_step = 0
      # update() 方法繼承自 DoubleDQNAgent

      優(yōu)先經(jīng)驗回放

      不是所有經(jīng)驗都同等有價值。TD 誤差大的轉(zhuǎn)換說明預(yù)測偏離現(xiàn)實,能學到東西;TD 誤差小的轉(zhuǎn)換說明已經(jīng)學得差不多了再采到也沒多大用。

      均勻采樣把所有轉(zhuǎn)換一視同仁,浪費了學習機會。優(yōu)先經(jīng)驗回放的思路是:讓重要的轉(zhuǎn)換被采到的概率更高。

      優(yōu)先級怎么算

      p? = |δ?| + ε
      其中:
      δ? = r + γ·max Q(s',a') - Q(s,a) (TD 誤差)
      ε = 小常數(shù),保證所有轉(zhuǎn)換都有被采到的可能

      采樣概率:

      P(i) = p?^α / Σ? p?^α
      α 控制優(yōu)先化程度:
      α = 0 -> 退化成均勻采樣
      α = 1 -> 完全按優(yōu)先級比例采樣

      優(yōu)先采樣改了數(shù)據(jù)分布,會引入偏差。所以解決辦法是用重要性采樣比率來加權(quán)更新:

      w? = (N · P(i))^(-β)
      β 控制校正力度:
      β = 0 -> 不校正
      β = 1 -> 完全校正

      通常 β 從 0.4 開始,隨訓(xùn)練逐漸增大到 1.0。

      實現(xiàn)

      class PrioritizedReplayBuffer:
      """
      優(yōu)先經(jīng)驗回放緩沖區(qū)。
      理論: 按 TD 誤差比例采樣轉(zhuǎn)換。
      我們可以從中學到更多的轉(zhuǎn)換會被更頻繁地采樣。
      """
      def __init__(self, capacity: int, alpha: float = 0.6, beta: float = 0.4):
      """
      Args:
      capacity: 緩沖區(qū)最大容量
      alpha: 優(yōu)先化指數(shù)(0=均勻, 1=比例)
      beta: 重要性采樣指數(shù)(退火到 1.0)
      """
      self.capacity = capacity
      self.alpha = alpha
      self.beta = beta
      self.beta_increment = 0.001 # 隨時間退火 beta
      self.buffer = []
      self.priorities = np.zeros(capacity, dtype=np.float32)
      self.position = 0
      def push(self, state, action, reward, next_state, done):
      """
      以最大優(yōu)先級添加轉(zhuǎn)換。
      理論: 新轉(zhuǎn)換獲得最大優(yōu)先級(會很快被采樣)。
      它們的實際優(yōu)先級在首次 TD 誤差計算后更新。
      """
      max_priority = self.priorities.max() if self.buffer else 1.0
      if len(self.buffer) < self.capacity:
      self.buffer.append((state, action, reward, next_state, done))
      else:
      self.buffer[self.position] = (state, action, reward, next_state, done)
      self.priorities[self.position] = max_priority
      self.position = (self.position + 1) % self.capacity
      def sample(self, batch_size: int):
      """
      按優(yōu)先級比例采樣批次。
      Returns:
      batch: 采樣的轉(zhuǎn)換
      indices: 采樣轉(zhuǎn)換的索引(用于優(yōu)先級更新)
      weights: 重要性采樣權(quán)重
      """
      if len(self.buffer) == self.capacity:
      priorities = self.priorities
      else:
      priorities = self.priorities[:len(self.buffer)]
      # 計算采樣概率
      probs = priorities ** self.alpha
      probs /= probs.sum()
      # 采樣索引
      indices = np.random.choice(len(self.buffer), batch_size, p=probs, replace=False)
      # 獲取轉(zhuǎn)換
      batch = [self.buffer[idx] for idx in indices]
      # 計算重要性采樣權(quán)重
      total = len(self.buffer)
      weights = (total * probs[indices]) ** (-self.beta)
      weights /= weights.max() # 歸一化以保持穩(wěn)定性
      # 退火 beta
      self.beta = min(1.0, self.beta + self.beta_increment)
      # 轉(zhuǎn)換為 tensor
      states, actions, rewards, next_states, dones = zip(*batch)
      states = torch.FloatTensor(np.array(states))
      actions = torch.LongTensor(actions)
      rewards = torch.FloatTensor(rewards)
      next_states = torch.FloatTensor(np.array(next_states))
      dones = torch.FloatTensor(dones)
      weights = torch.FloatTensor(weights)
      return (states, actions, rewards, next_states, dones), indices, weights
      def update_priorities(self, indices, td_errors):
      """
      根據(jù) TD 誤差更新優(yōu)先級。
      Args:
      indices: 采樣轉(zhuǎn)換的索引
      td_errors: 那些轉(zhuǎn)換的 TD 誤差
      """
      for idx, td_error in zip(indices, td_errors):
      self.priorities[idx] = abs(td_error) + 1e-6
      def __len__(self):
      return len(self.buffer)

      生產(chǎn)環(huán)境會用 sum-tree 數(shù)據(jù)結(jié)構(gòu),采樣復(fù)雜度是 O(log N) 而不是這里的 O(N)。這個簡化版本以可讀性為優(yōu)先。

      DQN 變體對比

      幾個變體各自解決什么問題呢?

      DQN 是基線,用單一網(wǎng)絡(luò)選動作、評估動作。它引入了目標網(wǎng)絡(luò)來穩(wěn)定"移動目標"問題,但容易過估計 Q 值,噪聲讓智能體去追逐根本不存在的"幽靈獎勵"。

      Double DQN 把選和評拆開。在線網(wǎng)絡(luò)選動作,目標網(wǎng)絡(luò)評估價值。實測下來能有效壓低不切實際的 Q 值,學習曲線明顯更平滑。

      Dueling DQN 換了網(wǎng)絡(luò)架構(gòu),單獨學 V(s) 和 A(s,a)。它的核心認知是:很多狀態(tài)下具體動作的影響不大。在 LunarLander 這種存在大量"冗余動作"的環(huán)境里,樣本效率提升明顯——不用為每次引擎脈沖都重新學狀態(tài)值。

      Double Dueling DQN 把兩邊的好處結(jié)合起來,既減少估計噪聲,又提高表示效率。實測中這個組合最穩(wěn)健,達到峰值性能的速度和可靠性都優(yōu)于單一改進。

      實踐建議

      變體選擇對比



      Double DQN 跑得比 DQN 還差?可能是訓(xùn)練不夠長(Double DQN 起步偶爾慢一點),或者目標網(wǎng)絡(luò)更新太頻繁,或者學習率偏高。這時可以將訓(xùn)練時間翻倍,target_update_freq 調(diào)大,學習率砍 2-5 倍。

      Dueling 架構(gòu)沒帶來改善?可能是環(huán)境本身不適合(所有狀態(tài)都很關(guān)鍵),或者網(wǎng)絡(luò)太小,或者值流/優(yōu)勢流太淺。需要對網(wǎng)絡(luò)加寬加深,確認環(huán)境里確實有"中性"狀態(tài)。

      PER 導(dǎo)致不穩(wěn)定?可能是 β 退火太快、α 設(shè)太高、重要性采樣權(quán)重沒歸一化。可以減慢 β 增量、α 降到 0.4-0.6、確認權(quán)重做了歸一化。

      首選 Double DQN 起步,代碼改動極小,收益明確,沒有額外復(fù)雜度。

      什么時候加 Dueling:狀態(tài)值比動作優(yōu)勢更重要的環(huán)境,大量狀態(tài)下動作值差不多,需要更快收斂。

      什么時候加 PER:樣本效率至關(guān)重要,有算力預(yù)算(PER 比均勻采樣慢),獎勵稀疏(幫助關(guān)注少見的成功經(jīng)驗)。

      最后Rainbow 把六項改進疊在一起:Double DQN、Dueling DQN、優(yōu)先經(jīng)驗回放、多步學習(n-step returns)、分布式 RL(C51)、噪聲網(wǎng)絡(luò)(參數(shù)空間探索)。

      多步學習把 1-step TD 換成 n-step 回報:

      # 1-step TD:
      y = r? + γ·max Q(s???, a)
      # n-step:
      y = r? + γ·r??? + γ2·r??? + ... + γ?·max Q(s???, a)

      好處是信用分配更清晰,學習更快。

      小結(jié)

      這篇文章從 DQN 的過估計問題講起,沿著 Double DQN、Dueling 架構(gòu)、優(yōu)先經(jīng)驗回放等等介紹下來,每種改進對應(yīng)一個具體的失敗模式:max 算子的偏差、低效的狀態(tài)-動作表示、浪費的均勻采樣。

      從頭實現(xiàn)這些方法,能搞清楚它們?yōu)槭裁从行В缓芏?高級" RL 算法不過是簡單想法的組合,理解這些想法本身才是真正可擴展的東西。

      https://avoid.overfit.cn/post/4c5835f419d840b0acb0a1eb72f92b6f

      作者: Jugal Gajjar

      特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺“網(wǎng)易號”用戶上傳并發(fā)布,本平臺僅提供信息存儲服務(wù)。

      Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

      相關(guān)推薦
      熱點推薦
      歐爾班宣布反制措施:在我們耗盡石油之前,烏克蘭人將先耗盡資金

      歐爾班宣布反制措施:在我們耗盡石油之前,烏克蘭人將先耗盡資金

      陳恧侃故事
      2026-03-07 11:31:12
      現(xiàn)場直擊:伊朗防空系統(tǒng)攔截美以目標

      現(xiàn)場直擊:伊朗防空系統(tǒng)攔截美以目標

      新華社
      2026-03-06 10:54:01
      鄭爽分享美國近況,穿搭不輸當紅明星,自曝做醫(yī)美網(wǎng)友直呼認不出

      鄭爽分享美國近況,穿搭不輸當紅明星,自曝做醫(yī)美網(wǎng)友直呼認不出

      萌神木木
      2026-03-06 17:33:33
      河南女孩六年前為救父親性命,稱誰給40萬就嫁給誰,如今過得如何

      河南女孩六年前為救父親性命,稱誰給40萬就嫁給誰,如今過得如何

      牛鍋巴小釩
      2026-03-06 19:45:04
      致敬西虹市首富?切爾西眾人開球前將球圍在中間,解說員啞然失笑

      致敬西虹市首富?切爾西眾人開球前將球圍在中間,解說員啞然失笑

      懂球帝
      2026-03-07 13:08:08
      廣西女子發(fā)現(xiàn)罕見青竹鯉,時不時側(cè)身蹭水底,網(wǎng)友:魚生天花板!

      廣西女子發(fā)現(xiàn)罕見青竹鯉,時不時側(cè)身蹭水底,網(wǎng)友:魚生天花板!

      貍貓之一的動物圈
      2026-03-06 09:38:48
      小學生實名投訴極氪 建議取消讓她寫作業(yè)的小桌板!極氪回應(yīng)

      小學生實名投訴極氪 建議取消讓她寫作業(yè)的小桌板!極氪回應(yīng)

      快科技
      2026-03-06 23:12:33
      王震堅決反對中顧委副主任排名,薄一波:我是常務(wù),就這么定了

      王震堅決反對中顧委副主任排名,薄一波:我是常務(wù),就這么定了

      芊芊子吟
      2026-03-06 09:45:07
      莫雷加德全家抵達重慶:對這座城市印象深刻,會請樊振東推薦美食

      莫雷加德全家抵達重慶:對這座城市印象深刻,會請樊振東推薦美食

      乒談
      2026-03-07 00:19:01
      中國女籃72-66再勝巴西,不是張子宇王思雨,她17+7成新核

      中國女籃72-66再勝巴西,不是張子宇王思雨,她17+7成新核

      林子說事
      2026-03-07 08:15:14
      速度滑冰世錦賽:寧忠?guī)r收獲短距離全能、男子1000米兩項季軍

      速度滑冰世錦賽:寧忠?guī)r收獲短距離全能、男子1000米兩項季軍

      懂球帝
      2026-03-07 07:21:57
      高市早苗被逼到絕路:派也死,不派也死

      高市早苗被逼到絕路:派也死,不派也死

      鯨探所長
      2026-03-07 12:02:36
      霍爾木茲海峽船只遭襲4死3重傷!兩萬海員被困,伊朗稱不會關(guān)閉海峽,但與以美有關(guān)船只不得通行;普京與伊總統(tǒng)通話:通過多種渠道保持聯(lián)系

      霍爾木茲海峽船只遭襲4死3重傷!兩萬海員被困,伊朗稱不會關(guān)閉海峽,但與以美有關(guān)船只不得通行;普京與伊總統(tǒng)通話:通過多種渠道保持聯(lián)系

      大風新聞
      2026-03-07 10:05:06
      霍震霆也沒想到,46歲的霍啟剛,會在兩會上憑一個舉動給霍家長臉

      霍震霆也沒想到,46歲的霍啟剛,會在兩會上憑一個舉動給霍家長臉

      攬星河的筆記
      2026-03-06 23:55:22
      結(jié)束了!整整27年生涯!曝冠軍主帥最后一舞

      結(jié)束了!整整27年生涯!曝冠軍主帥最后一舞

      籃球?qū)崙?zhàn)寶典
      2026-03-06 18:57:43
      晴好周末,出游安排起來 | 天氣早知道

      晴好周末,出游安排起來 | 天氣早知道

      上觀新聞
      2026-03-07 11:57:06
      針對“不敢休、不讓休”怪圈,國家出手了!

      針對“不敢休、不讓休”怪圈,國家出手了!

      國是直通車
      2026-03-07 09:12:15
      村里紅白事從不回,男子母親離世,鄰居等著看笑話,結(jié)果長了見識

      村里紅白事從不回,男子母親離世,鄰居等著看笑話,結(jié)果長了見識

      子芫伴你成長
      2026-02-23 12:21:40
      重回國乒?塵埃落定,劉國梁發(fā)聲,崗位曝光,布局國乒男隊發(fā)展

      重回國乒?塵埃落定,劉國梁發(fā)聲,崗位曝光,布局國乒男隊發(fā)展

      卿子書
      2026-03-06 09:25:27
      比賽還沒開打,上海申花先迎來兩個壞消息,新賽季斬獲開門紅懸了

      比賽還沒開打,上海申花先迎來兩個壞消息,新賽季斬獲開門紅懸了

      零度眼看球
      2026-03-07 08:58:12
      2026-03-07 13:40:49
      deephub incentive-icons
      deephub
      CV NLP和數(shù)據(jù)挖掘知識
      1940文章數(shù) 1456關(guān)注度
      往期回顧 全部

      科技要聞

      OpenClaw爆火,六位"養(yǎng)蝦人"自述與AI共生

      頭條要聞

      特朗普突然放話"先解決伊朗后解決古巴" 梅西聽懵了

      頭條要聞

      特朗普突然放話"先解決伊朗后解決古巴" 梅西聽懵了

      體育要聞

      塔圖姆歸來:凱爾特人的春之綠

      娛樂要聞

      周杰倫田馥甄的“JH戀” 被扒得底朝天

      財經(jīng)要聞

      針對"不敢休、不讓休"怪圈 國家出手了

      汽車要聞

      逃離ICU,上汽通用“止血”企穩(wěn)

      態(tài)度原創(chuàng)

      健康
      本地
      藝術(shù)
      公開課
      軍事航空

      轉(zhuǎn)頭就暈的耳石癥,能開車上班嗎?

      本地新聞

      食味印象|一口入魂!康樂烤肉串起千年絲路香

      藝術(shù)要聞

      Mark Grantham | 城市街景

      公開課

      李玫瑾:為什么性格比能力更重要?

      軍事要聞

      伊朗:使用無人機擊中美軍"林肯"號航母

      無障礙瀏覽 進入關(guān)懷版