(6-4)Deep Q Network (DQN)算法:DQN的优化与改进

6.4  DQN的优化与改进

Deep Q-Network (DQN) 是一种强化学习算法,最初由DeepMind提出,并已经在各种领域获得了成功。然而,DQN 在实际应用中仍然面临一些挑战,因此有许多优化和改进的技术,以增强其性能和稳定性。

6.4.1  Double DQN

Double DQN(Double Deep Q-Network)是对标准的深度 Q 学习(DQN)算法的一种改进,旨在解决 Q 值高估问题。在标准 DQN 中,用于选择最佳动作的 Q-network 可能会高估 Q 值,导致不稳定的训练和低效的策略。Double DQN 引入了一种机制,通过使用两个独立的 Q-network 来减小 Q 值的高估。Double DQN 的工作原理如下:

  1. 两个 Q-networks:Double DQN 使用两个神经网络,通常称为 Q1-network 和 Q2-network。这两个网络具有相同的架构,但参数是独立的。
  2. 动作选择和 Q 值估计:在每个训练步骤中,Double DQN 使用 Q1-network 来选择最佳动作(具有最高估计的 Q 值),然后使用 Q2-network 来估计所选动作的 Q 值。
  3. 目标 Q 值计算:与标准 DQN 不同,Double DQN 在计算目标 Q 值时使用了 Q1-network。具体来说,对于下一个状态,它使用 Q1-network 来选择最佳动作,然后使用 Q2-network 来估计该动作的 Q 值。这个目标 Q 值用于更新 Q1-network。
  4. 目标 Q 值更新:Double DQN 使用目标 Q 值计算来更新 Q1-network 的权重,而不是使用 Q1-network 自身的估计。

Double DQN的主要优点是它减小了 Q 值高估的影响,提高了训练的稳定性。这有助于在强化学习任务中更快地获得更好的策略。例如下面是一个创建Double DQN网络的例子。

实例6-6:创建Double DQN网络(源码路径:daima\6\dou.py

实例文件dou.py的主要实现代码如下所示。

import torch
import torch.nn as nn
import torch.optim as optim
import random

class DoubleDQN(nn.Module):
    def __init__(self, input_dim, output_dim):
        super(DoubleDQN, self).__init__()
        self.fc1 = nn.Linear(input_dim, 64)
        self.fc2 = nn.Linear(64, 64)
        self.fc3 = nn.Linear(64, output_dim)

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

# 创建两个 Q-networks
input_dim = 4  # 输入状态的维度
output_dim = 2  # 输出动作的维度
q1_network = DoubleDQN(input_dim, output_dim)
q2_network = DoubleDQN(input_dim, output_dim)

# 定义优化器
optimizer_q1 = optim.Adam(q1_network.parameters(), lr=0.001)
optimizer_q2 = optim.Adam(q2_network.parameters(), lr=0.001)

6.4.2  Dueling DQN

Dueling DQN 是一种改进的深度 Q 网络(DQN)算法,旨在更好地估计状态-动作值函数 Q(s, a)。与标准的 DQN 不同,Dueling DQN 将 Q(s, a) 分解为状态值函数 V(s) 和优势函数 A(s, a),这有助于更好地理解和估计状态-动作值函数。例如下面是一个 Dueling DQN 的例子,演示了使用自定义的迷宫环境来演示Dueling DQN的过程。

实例6-7:使用Dueling DQN解决自定义迷宫导航问题(源码路径:daima\6\Dueling.py

实例文件Dueling.py的主要实现代码如下所示。

# 创建一个简单的自定义迷宫环境
class CustomMaze:
    def __init__(self):
        self.grid = np.array([
            [0, 0, 0, 1, 0],
            [0, 1, 0, 1, 0],
            [0, 1, 0, 0, 0],
            [0, 1, 0, 1, 0],
            [0, 0, 0, 0, 0]
        ])
        self.start = (0, 0)
        self.goal = (4, 4)
        self.current_pos = self.start

    def reset(self):
        self.current_pos = self.start
        return self.current_pos

    def step(self, action):
        if action == 0:  # 上
            new_pos = (self.current_pos[0] - 1, self.current_pos[1])
        elif action == 1:  # 下
            new_pos = (self.current_pos[0] + 1, self.current_pos[1])
        elif action == 2:  # 左
            new_pos = (self.current_pos[0], self.current_pos[1] - 1)
        elif action == 3:  # 右
            new_pos = (self.current_pos[0], self.current_pos[1] + 1)

        if 0 <= new_pos[0] < 5 and 0 <= new_pos[1] < 5 and self.grid[new_pos[0], new_pos[1]] != 1:
            self.current_pos = new_pos

        if self.current_pos == self.goal:
            done = True
            reward = 1.0
        else:
            done = False
            reward = 0.0

        return self.current_pos, reward, done


# 定义Dueling DQN模型
class DuelingDQN(tf.keras.Model):
    def __init__(self, num_actions):
        super(DuelingDQN, self).__init__()
        self.dense1 = tf.keras.layers.Dense(128, activation='relu')
        self.advantage = tf.keras.layers.Dense(num_actions, activation='linear')
        self.value = tf.keras.layers.Dense(1, activation='linear')

    def call(self, state):
        x = self.dense1(state)
        advantage = self.advantage(x)
        value = self.value(x)
        q_values = value + (advantage - tf.reduce_mean(advantage, axis=1, keepdims=True))
        return q_values


# 定义经验回放缓冲区
class ReplayBuffer:
    def __init__(self, capacity):
        self.capacity = capacity
        self.buffer = []
        self.position = 0

    def push(self, state, action, reward, next_state, done):
        if len(self.buffer) < self.capacity:
            self.buffer.append(None)
        self.buffer[self.position] = (state, action, reward, next_state, done)
        self.position = (self.position + 1) % self.capacity

    def sample(self, batch_size):
        batch = random.sample(self.buffer, batch_size)
        state, action, reward, next_state, done = map(np.array, zip(*batch))
        return state, action, reward, next_state, done


# 定义epsilon-greedy策略
def epsilon_greedy_policy(q_values, epsilon):
    if np.random.rand() < epsilon:
        return np.random.randint(len(q_values))
    else:
        return np.argmax(q_values)


# 定义Dueling DQN代理
class DuelingDQNAgent:
    def __init__(self, num_actions, epsilon=0.1, gamma=0.99, lr=0.001, batch_size=64, buffer_capacity=10000):
        self.num_actions = num_actions
        self.epsilon = epsilon
        self.gamma = gamma
        self.batch_size = batch_size
        self.buffer = ReplayBuffer(buffer_capacity)
        self.q_network = DuelingDQN(num_actions)
        self.target_network = DuelingDQN(num_actions)
        self.optimizer = tf.optimizers.Adam(learning_rate=lr)
        self.update_target_network()

    def update_target_network(self):
        self.target_network.set_weights(self.q_network.get_weights())

    def train(self):
        if len(self.buffer.buffer) < self.batch_size:
            return
        state, action, reward, next_state, done = self.buffer.sample(self.batch_size)

        with tf.GradientTape() as tape:
            q_values = self.q_network(state)
            target_q_values = self.target_network(next_state)

            target_max_q_values = tf.reduce_max(target_q_values, axis=1)
            target_q = reward + (1.0 - done) * self.gamma * tf.expand_dims(target_max_q_values, axis=1)

            action_masks = tf.one_hot(action, self.num_actions)
            predicted_q_values = tf.reduce_sum(q_values * action_masks, axis=1)
            loss = tf.reduce_mean(tf.square(target_q - predicted_q_values))

        gradients = tape.gradient(loss, self.q_network.trainable_variables)
        self.optimizer.apply_gradients(zip(gradients, self.q_network.trainable_variables))

    def act(self, state):
        q_values = self.q_network(state)
        return epsilon_greedy_policy(q_values[0], self.epsilon)


# 训练Dueling DQN代理解决自定义迷宫导航问题
if __name__ == "__main__":
    env = CustomMaze()
    num_actions = 4  # 上、下、左、右四个动作
    agent = DuelingDQNAgent(num_actions)

    num_episodes = 1000

    for episode in range(num_episodes):
        state = env.reset()
        state = np.array([state])
        total_reward = 0.0

        while True:
            action = agent.act(state)
            next_state, reward, done = env.step(action)
            next_state = np.array([next_state])
            agent.buffer.push(state, action, reward, next_state, done)
            state = next_state
            total_reward += reward

            if done:
                break

        agent.train()
        agent.update_target_network()
        print(f"Episode: {episode + 1}, Total Reward: {total_reward}")

上述实现了一个强化学习任务,使用Dueling DQN算法来训练一个代理(agent)解决一个自定义的迷宫导航问题。具体实现流程如下所示:

  1. 类CustomMaze定义了一个自定义的迷宫环境。这个迷宫由一个二维网格表示,其中0表示可以通过的路径,1表示障碍物。代理的目标是从起始位置出发,到达目标位置(终点)。reset 方法用于重置环境状态,step 方法用于执行代理的动作并返回下一个状态、奖励和终止条件。
  2. 类DuelingDQN定义了Dueling DQN模型,它包括一个全连接层(dense1),一个表示优势函数(advantage)的全连接层,以及一个表示值函数(value)的全连接层。call 方法根据输入状态计算Q值,通过将优势函数和值函数的输出进行组合来获得最终的Q值。
  3. 类ReplayBuffer实现了经验回放缓冲区,用于存储代理的经验样本,以便后续训练。push 方法用于添加经验样本,sample 方法用于随机抽样小批量样本。
  4. epsilon_greedy_policy 是一个策略函数,根据Q值和epsilon值决定代理的行动。它以一定的概率随机选择动作,以便在探索和利用之间进行权衡。
  5. 类DuelingDQNAgent 定义了Dueling DQN代理。它包括一个Q网络(q_network)和一个目标网络(target_network),以及一些超参数。train 方法用于训练代理,包括计算损失、更新Q网络权重和目标网络同步。act 方法用于根据当前状态选择动作。
  6. 在if __name__ == "__main__": 中,创建了自定义迷宫环境,实例化了Dueling DQN代理,并对其进行了多个Episode的训练。在每个Episode中,代理根据当前状态选择动作,执行动作,收集经验,计算损失并进行训练。训练过程中,代理的目标是学习如何在迷宫中导航以最大化总奖励。最后,代码打印了每个Episode的总奖励,以跟踪代理的性能改善。这个训练过程会一直重复,直到达到指定的Episode数。

运行上述代码后会进行多次迭代的训练,每次迭代都会打印当前迭代的信息,包括当前的Episode号和该Episode的总奖励。会输出类似以下内容的信息:

Episode: 1, Total Reward: 0.0
Episode: 2, Total Reward: 0.0
Episode: 3, Total Reward: 0.0
...
Episode: 999, Total Reward: 1.0
Episode: 1000, Total Reward: 1.0

在每个Episode中,代理会尝试在自定义迷宫中导航,迭代会继续进行直到达到目标或遇到终止条件。总奖励是代理在该Episode中获得的奖励总和。在这个例子中,当代理成功到达目标时,它会获得1.0的奖励。我们可以根据输出的信息来监视训练的进展,以及代理在解决自定义迷宫导航问题上的性能如何改善。

6.4.3  Prioritized Experience Replay

Prioritized Experience Replay(PER)是一种经验回放机制,用于改善深度强化学习中的样本选择和训练效率。传统的经验回放方法随机选择样本进行训练,但这可能导致一些重要的经验被低概率选择,从而影响训练效果。PER通过引入优先级来解决这个问题,它将重要的经验样本更频繁地用于训练,从而提高学习的效率。Prioritized Experience Replay的关键知识点如下:

  1. 优先级分配:每个经验样本都被分配一个优先级,通常根据代理的预测误差(TD误差)来确定优先级。更大的误差通常表示更重要的样本,因此具有更高的优先级。
  2. 样本选择:在进行经验回放时,根据样本的优先级来选择样本。具有较高优先级的样本被选择的概率更高,以确保重要经验被更频繁地使用。
  3. 重要性采样权重:为了保持样本选择的公平性,引入了重要性采样权重,以纠正样本选择的偏差。这些权重确保代理学习不受到优先级的影响。
  4. 优先级更新:当代理训练后,根据预测误差的变化来更新经验样本的优先级。更重要的样本会得到更高的优先级,以适应学习进展。
  5. 经验存储:PER通常使用一种数据结构(通常是二叉树)来存储经验样本和对应的优先级。这使得样本选择和优先级更新的效率更高。

PER的主要优势在于它能够提高样本的有效利用率,加速学习的收敛速度,并提高深度强化学习算法的性能。然而,PER也引入了一些挑战,如需要额外的超参数调整和复杂性增加。

在实现PER时,通常需要考虑如何平衡优先级采样的偏差和方差,以及如何合理地设置优先级的初始化值。PER是许多强化学习算法中的重要改进之一,例如在DQN(深度Q网络)和其变种中广泛使用。下面是一个简单的Prioritized Experience Replay(PER)的示例,不涉及任何特定的环境,只是演示了PER的基本概念。

实例6-7:创建一个简单的PrioritizedReplayBuffer(源码路径:daima\6\pe.py

实例文件pe.py的主要实现代码如下所示。

import random
import numpy as np

# 定义经验回放缓冲区类
class PrioritizedReplayBuffer:
    def __init__(self, capacity):
        self.capacity = capacity
        self.buffer = []
        self.priorities = np.zeros(capacity, dtype=np.float32)
        self.position = 0

    def push(self, experience, priority):
        if len(self.buffer) < self.capacity:
            self.buffer.append(None)
        self.priorities[self.position] = priority
        self.buffer[self.position] = experience
        self.position = (self.position + 1) % self.capacity

    def sample(self, batch_size):
        priorities = self.priorities[:len(self.buffer)]
        prob = priorities / priorities.sum()
        indices = np.random.choice(len(self.buffer), batch_size, p=prob)
        samples = [self.buffer[idx] for idx in indices]
        return samples, indices

# 创建一个简单的经验元组类
class Experience:
    def __init__(self, state, action, reward, next_state, done):
        self.state = state
        self.action = action
        self.reward = reward
        self.next_state = next_state
        self.done = done

# 创建一个PrioritizedReplayBuffer对象
buffer_capacity = 10000
buffer = PrioritizedReplayBuffer(buffer_capacity)

# 添加一些虚拟经验元组到缓冲区中
for i in range(100):
    state = np.random.rand(4)  # 代表状态的简化示例
    action = np.random.randint(2)  # 0或1
    reward = np.random.rand()  # 随机奖励
    next_state = np.random.rand(4)  # 代表下一个状态的简化示例
    done = False  # 这里示例中没有终止条件
    experience = Experience(state, action, reward, next_state, done)
    buffer.push(experience, priority=1.0)

# 从缓冲区中采样一批经验元组
batch_size = 64
samples, indices = buffer.sample(batch_size)

# 打印采样的经验元组
for sample in samples:
    print(f"State: {sample.state}, Action: {sample.action}, Reward: {sample.reward}, Next State: {sample.next_state}, Done: {sample.done}")

# 打印被选中的样本索引
print("Selected Indices:", indices)

上述代码创建了一个简单的PrioritizedReplayBuffer,然后向其中添加了虚拟的经验元组。然后,它从缓冲区中采样一批经验元组,并打印出采样的内容以及被选中的样本索引。执行后会输出:

State: [0.06553838 0.79793838 0.83191107 0.20735608], Action: 1, Reward: 0.5546894149936815, Next State: [0.05097349 0.33601156 0.11562567 0.85548575], Done: False
State: [0.36528259 0.6671806  0.08346176 0.54937993], Action: 0, Reward: 0.5950038059630215, Next State: [0.16240237 0.80662676 0.62011377 0.98715585], Done: False
State: [0.33058664 0.57697594 0.00883182 0.37177207], Action: 0, Reward: 0.9942192797183348, Next State: [0.4768401  0.75973517 0.49737887 0.58094162], Done: False
######省略部分输出
State: [0.05007888 0.71787515 0.81902615 0.24681485], Action: 1, Reward: 0.2851874124135627, Next State: [0.6792876  0.00236248 0.0588185  0.12687168], Done: False
State: [0.91436844 0.18640032 0.44182336 0.6882996 ], Action: 0, Reward: 0.15472129635299292, Next State: [0.41523829 0.49050478 0.88478906 0.4958476 ], Done: False
Selected Indices: [20 19 94 15 46 91 98  4 71 74 56 35 79 34 18 25  1 55 95  0 56 55 95 16
 66 47  8 32 62 98 61 95 14  5 23 87  3  4 23 48 91 81  8 16 41 14 44 66
 51  0 33  4  9 11 49 37 74 59 71 15 38 91 48 22]

由此可见,已经成功运行了这个简单的Prioritized Experience Replay(PER)示例,并从缓冲区中采样了一批经验元组。输出显示了采样的经验元组,以及被选中的样本索引。

未完待续

  • 27
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农三叔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值