深度 Q 网络(deep Q network,DQN)原理&实现

深度 Q 网络(deep Q network,DQN)原理&实现

1 Q-Learning 算法

1.1 算法过程

Q-learning是一种用于解决强化学习问题的无模型算法。强化学习是一种让智能体学习如何在环境中采取行动以最大化某种累积奖励的机器学习方法。

在Q-learning中,智能体根据称为Q-values的函数来选择行动。Q-values通常表示为Q(s, a),其中s是智能体所在的状态,a是智能体可能采取的行动。Q-value表示的是当智能体在状态s下采取行动a时所期望获得的未来奖励。

Q-learning算法的工作流程如下:

  1. 初始化:Q-values通常开始时被随机初始化,然后在训练过程中进行更新。

  2. 探索与利用:在每个时间步,智能体都需要选择一个行动。这可以通过一种叫做ε-greedy策略的方法来完成,该方法会在探索(随机选择行动)和利用(选择当前Q-value最高的行动)之间进行权衡。

  3. 学习:一旦智能体选择了一个行动并观察到了结果,就可以更新Q-value。更新是基于一个叫做贝尔曼等式的公式,它使用了从采取行动后观察到的奖励,以及预期的未来奖励(基于新状态下的最大Q-value)。

Q-learning算法的更新公式如下:

\[Q(s,a)=Q(s,a)+\alpha \times [r+\gamma \times Q(s',a')-Q(s,a)] \]

在这个公式中:

  • α 是学习率,它决定了新信息覆盖旧信息的速度。
  • γ 是折扣因子,它决定了智能体对未来奖励的看重程度。
  • r 是智能体采取行动后获得的即时奖励。
  • s' 和 a' 分别代表新的状态和在新的状态下可以选择的最佳行动。

随着足够数量的训练,Q-values会逐渐收敛,智能体最终会学会在给定状态下选择最佳的行动。这就是Q-learning的基本原理。

1.2 代码实现

这是一个使用Python实现Q-learning算法的简单例子。我们假设智能体在一个有四个状态(s0, s1, s2, s3)的环境中,并且在每个状态下都可以采取两个动作(a0, a1)。奖励函数和状态转移函数是已知的。

import numpy as np

# 建立状态转移和奖励矩阵
# 其中,R[s,a,s'] 是智能体从状态 s 采取动作 a 转移到状态 s' 的奖励
# P[s,a,s'] 是智能体从状态 s 采取动作 a 转移到状态 s' 的概率
R = np.zeros((4, 2, 4))
P = np.zeros((4, 2, 4))

# 初始化 Q 矩阵
Q = np.zeros((4, 2))

# 设定学习参数
alpha = 0.5
gamma = 0.95
epsilon = 0.1
n_episodes = 10000

# 对每个情节进行循环
for _ in range(n_episodes):
    # 初始化状态
    s = np.random.choice([0, 1, 2, 3])
    
    # 对每个时间步进行循环,限制最大步数为 100,防止陷入无限循环
    for _ in range(100):
        # 选择动作,部分时间用于探索,部分时间用于利用
        if np.random.rand() < epsilon:
            a = np.random.choice([0, 1])
        else:
            a = np.argmax(Q[s])
        
        # 根据状态转移概率选择新的状态
        s_ = np.random.choice([0, 1, 2, 3], p=P[s, a])
        
        # 更新 Q 值
        Q[s, a] = Q[s, a] + alpha * (R[s, a, s_] + gamma * np.max(Q[s_]) - Q[s, a])
        
        # 更新当前状态
        s = s_

# 输出最终的 Q 值
print(Q)

在这个例子中,我们使用ε-greedy策略来选择动作,这样既可以进行探索,也可以进行利用。Q 值的更新是基于贝尔曼等式的,我们采取当前的动作 a,然后观察新的状态 s' 和奖励 r,然后更新 Q(s, a)。

需要注意的是,为了简化问题,我们假设了奖励函数和状态转移函数是已知的,并且都可以用矩阵来表示。在实际问题中,这些函数可能不是已知的,可能需要从智能体与环境的交互中进行学习。

此外,上述代码中的状态转移概率和奖励需要你自己填写。在一个具体的问题中,它们将由问题本身的特性决定。

这只是一个非常基础的例子。在实际应用中,Q-learning算法可能会涉及更复杂的技术,例如使用神经网络来近似 Q 函数(这就是深度 Q 学习),以处理具有大量状态和动作的问题。

2 DQN 算法

2.1 算法介绍

DQN,全称Deep Q-Network,是一种强化学习算法,由DeepMind于2015年首次提出。它结合了深度学习和Q学习两种技术,可以解决具有大量状态和动作的复杂问题。

在传统的Q-learning中,我们用一个表(Q-table)来存储每个状态-动作对的Q值。然而,当状态和动作的数量非常大时,用表格存储的方式就会变得不现实,因为需要的存储空间和计算资源会非常巨大。

DQN的出现解决了这个问题。在DQN中,我们使用一个神经网络(通常是一个深度神经网络)来近似Q值函数。网络的输入是一个状态,输出是对应于各个可能动作的Q值。通过这种方式,我们就可以在连续的状态空间和大规模的动作空间中工作。

DQN中有几个关键的技术:

  1. 经验回放(Experience Replay):为了打破数据之间的相关性并提高学习的效率,DQN会将智能体的经验(状态、动作、奖励、新状态)存储在一个数据集中,然后从中随机抽取样本进行学习。

  2. 目标网络(Target Network):DQN使用了两个神经网络,一个是在线网络,用于选择动作;一个是目标网络,用于计算TD目标(Temporal-Difference Target)。这两个网络有相同的结构,但参数不同。在每一步学习过程中,我们使用在线网络的参数来更新目标网络的参数,但是更新的幅度较小。这样可以提高学习的稳定性。

在DQN中,Q值的更新公式为:

Q(s, a) = r + γ * max_a' Q_target(s', a')

其中,Q_target(s', a') 是通过目标网络计算出的Q值,而Q(s, a)则是通过在线网络计算出的Q值。

DQN算法的应用领域非常广泛,从玩电子游戏到控制机器人,都有其身影。其中最著名的应用就是在Atari 2600游戏上的表现,DQN能够在大量的游戏上达到超越人类的性能。

2.2 关于两个网络以及如何训练的两个网络

在DQN中,使用了两个不同的神经网络,这两个网络被称为在线网络(Online Network)和目标网络(Target Network)。这两个网络都是用来估计Q值的,但在学习过程中起到了不同的角色。

  1. 在线网络:在线网络用于根据当前的状态s选择智能体的动作a。这个网络会不断地进行学习和更新,以尽可能地提高对Q值的估计。在每个时间步,智能体都会根据在线网络提供的Q值来选择动作,然后根据这个动作和环境的反馈来更新网络的参数。

  2. 目标网络:目标网络用于计算Q值更新公式中的TD目标(Temporal-Difference Target),即下一个状态s'的最大Q值。这个网络的参数不会在每个时间步中都进行更新,而是在一定的间隔后,才将在线网络的参数复制过来。这样可以使学习过程更加稳定,避免因为在线网络的快速更新导致的震荡问题。

在线网络和目标网络的结构是相同的,都是用来估计Q值的深度神经网络。它们的输入是智能体的状态,输出是对应于各个可能动作的Q值。这种网络结构也被称为Q网络。

这两个网络在DQN的学习过程中都起到了重要的作用。在线网络负责智能体的决策,目标网络则保证了学习过程的稳定性。通过这两个网络的配合,DQN能够有效地学习在复杂环境中的最优策略。

如何训练?

在线网络和目标网络在DQN中的训练过程是稍有不同的,下面详细解释一下:

  1. 在线网络训练:在线网络的训练主要依靠智能体与环境的交互。每次当智能体在环境中执行一个动作并观察到结果(新状态和奖励)时,我们就可以获得一个样本(状态,动作,奖励,新状态),然后使用这个样本来更新网络的参数。我们希望网络预测的Q值(即 Q(s, a))接近于从这个样本中计算出的目标值,即 r + γ * max_a' Q_target(s', a')。这个目标值由实际得到的奖励和目标网络预测的未来奖励(discounted)之和构成。我们可以使用梯度下降算法来最小化网络预测的Q值和这个目标值之间的差距(通常使用平方损失函数)。

  2. 目标网络训练:目标网络的训练实际上不涉及到任何从数据中学习的过程,它的参数是直接从在线网络复制过来的。我们定期(每隔一定的步数)将在线网络的参数复制到目标网络。这样做的目的是为了增加学习的稳定性。由于在线网络在训练过程中参数会不断变化,如果我们直接使用在线网络来计算目标值,可能会导致目标值震荡,从而影响学习的稳定性。通过使用一个参数更新较慢的目标网络来计算目标值,可以有效地防止这种情况的发生。

在线网络和目标网络的配合使得DQN能够在复杂的环境中有效地学习。在线网络的参数通过与环境的交互不断更新,以逐渐逼近真实的Q值函数。而目标网络则提供了一个稳定的目标,帮助在线网络更稳定地学习。

2.3 算法过程&代码实现

DQN算法的大致流程如下:

  1. 初始化:首先,初始化在线网络和目标网络(它们具有相同的结构但是参数不同)。然后,创建一个经验回放缓冲区。

  2. 探索与利用:智能体在每个时间步会选择一个动作。动作的选择可以是随机的(探索),也可以是根据在线网络预测的Q值选择的(利用)。通常,我们会使用一个策略(如ε-greedy策略),使得智能体在初期更倾向于探索,在后期更倾向于利用。

  3. 交互与存储:智能体根据选择的动作与环境交互,然后观察到新的状态和奖励。这个过程产生了一个转移(状态,动作,奖励,新状态),这个转移被存储在经验回放缓冲区中。

  4. 学习:从经验回放缓冲区中随机抽取一批样本,然后使用这些样本来训练在线网络。具体来说,我们计算每个样本的目标值(r + γ * max_a' Q_target(s', a')),然后通过最小化网络预测的Q值和这个目标值之间的差距来更新网络的参数。

  5. 更新目标网络:每隔一定的步数,我们将在线网络的参数复制到目标网络。这样,目标网络的参数保持相对稳定,使得学习过程更加稳定。

  6. 迭代:重复上述步骤(步骤2-5),直到满足停止条件(如达到最大步数或达到预定的性能标准)。

下面是一个使用PyTorch实现的简单的DQN算法的例子。在这个例子中,我们假设环境是OpenAI Gym的CartPole环境。

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

# 定义Q网络
class QNetwork(nn.Module):
    def __init__(self, state_dim, action_dim):
        super(QNetwork, self).__init__()
        self.fc = nn.Sequential(
            nn.Linear(state_dim, 64),
            nn.ReLU(),
            nn.Linear(64, 64),
            nn.ReLU(),
            nn.Linear(64, action_dim)
        )

    def forward(self, state):
        return self.fc(state)

# 创建环境
env = gym.make('CartPole-v0')
state_dim = env.observation_space.shape[0]
action_dim = env.action_space.n

# 创建网络
online_net = QNetwork(state_dim, action_dim)
target_net = QNetwork(state_dim, action_dim)
target_net.load_state_dict(online_net.state_dict())

# 创建优化器
optimizer = optim.Adam(online_net.parameters())

# 创建经验回放缓冲区
replay_buffer = deque(maxlen=10000)

# 设置超参数
epsilon = 1.0  # 探索率
epsilon_decay = 0.995  # 探索率衰减
min_epsilon = 0.01  # 最小探索率
gamma = 0.99  # 折扣因子
batch_size = 64  # 批大小
update_target_every = 100  # 更新目标网络的频率
max_steps = 10000  # 最大步数

# 训练过程
for step in range(max_steps):
    # 选择动作
    state = env.reset()
    state = torch.tensor(state, dtype=torch.float32).unsqueeze(0)
    if np.random.rand() < epsilon:
        action = env.action_space.sample()  # 探索
    else:
        with torch.no_grad():
            action = torch.argmax(online_net(state)).item()  # 利用

    # 执行动作并存储转移
    next_state, reward, done, _ = env.step(action)
    next_state = torch.tensor(next_state, dtype=torch.float32).unsqueeze(0)
    reward = torch.tensor([reward], dtype=torch.float32)
    replay_buffer.append((state, action, reward, next_state, done))
    state = next_state

    # 学习
    if len(replay_buffer) >= batch_size:
        minibatch = random.sample(replay_buffer, batch_size)
        states, actions, rewards, next_states, dones = zip(*minibatch)
        states = torch.cat(states)
        actions = torch.tensor(actions, dtype=torch.long).unsqueeze(1)
        rewards = torch.cat(rewards)
        next_states = torch.cat(next_states)
        dones = torch.tensor(dones, dtype=torch.float32)

        q_values = online_net(states).gather(1, actions)
        with torch.no_grad():
            max_next_q_values = target_net(next_states).max(1)[0]
            target_q_values = rewards + gamma * (1 - dones) * max_next_q_values

        loss = nn.functional.mse_loss(q_values, target_q_values.unsqueeze(1))
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        # 更新目标网络
        if step % update_target_every == 0:
            target_net.load_state_dict(online_net.state_dict())

    # 更新探索率
    epsilon = max(min_epsilon, epsilon * epsilon_decay)

    # 检查是否完成
    if done:
        break

请注意,这只是一个基础的DQN实现,实际使用时可能需要根据具体的任务和环境进行一些调整。例如,你可能需要调整网络结构、优化器的参数、探索策略、经验回放缓冲区的大小等等。

此外,你可能还需要添加一些代码来跟踪和可视化训练过程,例如保存网络的参数、画出奖励的曲线等等。

posted @ 2023-08-06 16:02  缙云山车神  阅读(1211)  评论(0编辑  收藏  举报