赞
踩
DQN(Deep Q-Network)是一种基于深度学习和强化学习的算法,由DeepMind提出,用于解决离散动作空间下的马尔科夫决策过程(MDP)问题。它是首个成功将深度学习应用于解决强化学习任务的算法之一。DQN,即深度Q网络(Deep Q-network),是指基于深度学习的Q-Learing算法。
那什么是Q-leaning?可以看上一篇文章
Q-learning是一种经典的强化学习算法,用于解决马尔可夫决策过程(Markov Decision Process,MDP)中的控制问题。它是基于值迭代(Value Iteration)的思想,通过估计每个状态动作对的价值函数Q值来指导智能体在每个状态下选择最佳的动作。
其算法的基本思想跟主要优势如下:
Q-Learning是强化学习算法中value-based的算法,Q即为Q(s,a),就是在某一个时刻的state状态下,采取动作a能够获得收益的期望,环境会根据agent的动作反馈相应的reward奖赏,所以算法的主要思想就是将state和action构建成一张Q_table表来存储Q值,然后根据Q值来选取能够获得最大收益的动作。
但是这种算法存在很大的局限性。在现实中很多情况下,强化学习任务所面临的状态空间是连续的,存在无穷多个状态,这种情况就不能再使用表格的方式存储价值函数。
为了解决这个问题,我们可以用一个函数Q(s,a;w)来近似动作-价值Q(s,a),称为价值函数近似Value Function Approximation,我们用神经网络来生成这个函数Q(s,a;w),称为Q网络(Deep Q-network),w是神经网络训练的参数。
如上图所示,神经网络的的输入是状态s,输出是对所有动作a的打分
神经网络的训练是一个最优化问题,我们需要表示网络输出和标签值之间的差值,作为损失函数,目标是让损失函数最小化,手段是通过反向传播使用梯度下降的方法来更新神经网络的参数。
那么Q网络的标签值/目标值是什么呢?
TD target: y t = r t + γ ⋅ max a Q ( s t + 1 , a ; w ) y_{t}=r_{t}+\gamma \cdot \max _{a} Q\left(s_{t+1}, a ; w\right) yt=rt+γ⋅maxaQ(st+1,a;w)
1、初始化网络,输入状态 s t s_t st,输出 s t s_t st下所有动作的Q值
2、 利用策略(例如 ε − greddy),选择一个动作 a t ,把 a t 输入到环境中,获得新状态 s t + 1 和 r ; \text { 利用策略(例如 } \varepsilon-\text { greddy),选择一个动作 } a_{t} \text { ,把 } a_{t} \text { 输入到环境中,获得新状态 } s_{t+1} \text { 和 } \mathrm{r} \text {; } 利用策略(例如 ε− greddy),选择一个动作 at ,把 at 输入到环境中,获得新状态 st+1 和 r;
3、 计算TD target: y t = r t + γ ⋅ max a Q ( s t + 1 , a ; w ) \text { 计算TD target: } y_{t}=r_{t}+\gamma \cdot \max _{a} Q\left(s_{t+1}, a ; w\right) 计算TD target: yt=rt+γ⋅maxaQ(st+1,a;w)
4、 计算损失函数: L = 1 / 2 [ y t − Q ( s , a ; w ) ] 2 \text { 计算损失函数: } L=1 / 2\left[y_{t}-Q(s, a ; w)\right]^{2} 计算损失函数: L=1/2[yt−Q(s,a;w)]2
5、 更新 Q 参数,使得 Q ( s t , a t ) 尽可能接近 y t ,可以把它当做回归问题,利用梯度下降做更新工作 ; 更新 \mathrm{Q} 参数,使得 \mathrm{Q}\left(s_{t}, a_{t}\right) 尽可能接近 y_{t} ,可以把它当做回归问题,利用梯度下降做更新工作; 更新Q参数,使得Q(st,at)尽可能接近yt,可以把它当做回归问题,利用梯度下降做更新工作;
6、 从以上步骤我们得到一个四元组 transition: ( s t , a t , r t , s t + 1 ) ,用完之后丟弃掉; 从以上步骤我们得到一个四元组 \operatorname{transition:~}\left(s_{t}, a_{t}, r_{t}, s_{t+1}\right) ,用完之后丟弃掉; 从以上步骤我们得到一个四元组transition: (st,at,rt,st+1),用完之后丟弃掉;
7、输入新的状态,重复更新工作
如下图所示:
其实DQN就是 Q-Learning 算法 + 神经网络,可以这样理解Q-learning和DQN的区别
我们知道,Q-Learning 算法需要维护一张 Q 表格,按照下面公式来更新:
Q
(
s
t
,
a
t
)
←
Q
(
s
t
,
a
t
)
+
α
⋅
[
r
t
+
γ
max
π
Q
(
s
t
+
1
,
a
t
)
−
Q
(
s
t
,
a
t
)
]
Q\left(s_{t}, a_{t}\right) \leftarrow Q\left(s_{t}, a_{t}\right)+\alpha \cdot\left[r_{t}+\gamma \max _{\pi} Q\left(s_{t+1}, a_{t}\right)-Q\left(s_{t}, a_{t}\right)\right]
Q(st,at)←Q(st,at)+α⋅[rt+γπmaxQ(st+1,at)−Q(st,at)]
然后学习的过程就是更新 这张 Q表格,如下图所示:
而DQN就是用神经网络来代替这张 Q 表格,其余相同,如下图:
但是他的更新方式哦发生了变化
Q
(
S
t
,
A
t
,
w
)
←
Q
(
S
t
,
A
t
,
w
)
+
α
[
R
t
+
1
+
γ
max
a
q
^
(
s
t
+
1
,
a
t
,
w
)
−
Q
(
S
t
,
A
t
,
w
)
]
Q\left(S_{t}, A_{t}, w\right) \leftarrow Q\left(S_{t}, A_{t}, w\right)+\alpha\left[R_{t+1}+\gamma \max _{a} \hat{q}\left(s_{t+1}, a_{t}, w\right)-Q\left(S_{t}, A_{t}, w\right)\right]
Q(St,At,w)←Q(St,At,w)+α[Rt+1+γamaxq^(st+1,at,w)−Q(St,At,w)]
$$
其中 \Delta w :
\Delta w=\alpha\left(R_{t+1}+\gamma \max {a} \hat{q}\left(s{t+1}, a_{t}, w\right)-\hat{q}\left(s_{t}, s_{t}, w\right)\right) \cdot \nabla_{w} \hat{q}\left(s_{t}, a_{t}, w\right)
$$
DQN 中还有两个非常重要的模块——经验回放和目标网络,它们能够帮助 DQN 取得稳定、出色的性能。
DQN 第一个特色是使用 Experience replay ,也就是经验回放,为何要用经验回放?还请看下文慢慢详述
在理解经验回放之前,先看看原始DQN算法的缺点:
1、
用完一个transition:
(
s
t
,
a
t
,
r
t
,
s
t
+
1
)
就丢弃,会造成对经验的浪费;
\text { 用完一个transition: }\left(s_{t}, a_{t}, r_{t}, s_{t+1}\right) \text { 就丢弃,会造成对经验的浪费; }
用完一个transition: (st,at,rt,st+1) 就丢弃,会造成对经验的浪费;
2、之前,我们按顺序使用transition,前一个transition和后一个transition相关性很强,这种相关性对学习Q网络是有害的。
经验回放可以克服上面两个缺点:
1.把序列打散,消除相关性,使得数据满足独立同分布,从而减小参数更新的方差,提高收敛速度。
2.能够重复使用经验,数据利用率高,对于数据获取困难的情况尤其有用。
经验回放会构建一个回放缓冲区(replay buffer),存储n条transition,称为经验池。某一个策略π与环境交互,收集很多条transition,放入回放缓冲区,回放缓冲区中的经验transition可能来自不同的策略。回放缓冲区只有在它装满的时候才会吧旧的数据丢掉
每次随机抽出一个batch大小的transition数据训练网络,算出多个随机梯度,用梯度的平均更新Q网络参数w
下面举一个例子来说明经验回放的优点:
对于网络输入,DQN 算法是把整个游戏的像素作为 神经网络的输入,具体网络结构如下图所示:
第一个问题就是样本相关度的问题,因为在强化学习过程中搜集的数据就是一个时序的玩游戏序列,游戏在像素级别其关联度是非常高的,可能只是在某一处特别小的区域像素有变化,其余像素都没有变化,所以不同时序之间的样本的关联度是非常高的,这样就会使得网络学习比较困难。
DQN的解决办法就是 经验回放(Experience replay)。具体来说就是用一块内存空间 D ,用来存储每次探索获得数据然后按照以下步骤重复进行:
利用经验回放,可以充分发挥 off-policy 的优势,behavior policy 用来搜集经验数据,而 target policy 只专注于价值最大化。
**我们在训练网络的时候,动作价值估计和权重w有关。当权重变化时,动作价值的估计也会发生变化。在学习的过程中,动作价值试图追逐一个变化的回报,容易出现不稳定的情况。意思就是我们使用 q ^ ( s t , a t , w ) \hat{q}\left(s_{t}, a_{t}, w\right) q^(st,at,w)来代替 TD Target,在TD Target 中已经包含我了我们要优化的 参数 w。也就是说在训练的时候 监督数据 target 是不固定的,所以就使得训练比较困难。**所以我们需要使用目标网络.
下面举个例子来说明目标网络
我们把 我们要估计的 Q ^ \hat{Q} Q^ 叫做 Q estimation,把它看做一只猫。把我们的监督数据 Q Target 看做是一只老鼠,现在可以把训练的过程看做猫捉老鼠的过程(不断减少之间的距离,类比于我们的 Q estimation 网络拟合 Q Target 的过程)。现在问题是猫和老鼠都在移动,这样猫想要捉住老鼠是比较困难的,如下所示:
那么我们让老鼠在一段时间间隔内不动(固定住),而这期间,猫是可以动的,这样就比较容易抓住老鼠了。在 DQN 中也是这样解决的,我们有两套一样的网络,分别是 Q estimation 网络和 Q Target 网络。要做的就是固定住 Q target 网络,那如何固定呢?比如我们可以让 Q estimation 网路训练10次,然后把 Q estimation 网络更新后的参数 w 赋给 Q target 网络。然后我们再让Q estimation 网路训练10次,如此往复下去,试想如果不固定 Q Target 网络,两个网络都在不停地变化,这样 拟合是很困难的,如果我们让 Q Target 网络参数一段时间固定不变,那么拟合过程就会容易很多。
换成公式中的数据就是:
我们使用第二个网络,称为目标网络, Q ( s , a ; w − ) ,网络结构和原来的网络 Q ( s , a ; w ) 一样,只是参数不同 w − ≠ w ,原来的网络称为评估网络 我们使用第二个网络,称为目标网络, Q\left(s, a ; w^{-}\right) ,网络结构和原来的网络 Q(s, a ; w) 一样,只是参数不同 w^{-} \neq w ,原来的网络称为评估网络 我们使用第二个网络,称为目标网络,Q(s,a;w−),网络结构和原来的网络Q(s,a;w)一样,只是参数不同w−=w,原来的网络称为评估网络
两个网络的作用不一样:评估网络 Q ( s , a ; w ) 负责控制智能体,收集经验;目标网络 Q ( s , a ; w − ) 用于计算 T D t a r g e t : 两个网络的作用不一样:评估网络 Q(s, a ; w) 负责控制智能体,收集经验;目标网络 Q\left(s, a ; w^{-}\right) 用于计算TD target: 两个网络的作用不一样:评估网络Q(s,a;w)负责控制智能体,收集经验;目标网络Q(s,a;w−)用于计算TDtarget:
y t = r t + γ ⋅ max a Q ( s t + 1 , a ; w − ) y_{t}=r_{t}+\gamma \cdot \max _{a} Q\left(s_{t+1}, a ; w^{-}\right) yt=rt+γ⋅maxaQ(st+1,a;w−)
在更新过程中,只更新评估网络 Q(s, a ; w) 的权重w,目标网络 Q ( s t + 1 , a ; w − ) Q\left(s_{t+1}, a ; w^{-}\right) Q(st+1,a;w−) 的权重 $ w^{-}$ 保持不变。在更新一定次数后,再将更新过的评估网络的权重复制给目标网络,进行下一批更新,这样目标网络也能得到更新。由于在目标网络没有变化的一段时间内回报的目标值是相对固定的,因此目标网络的引入增加了学习的稳定性。
下面是 DQN 算法流程图:
如上图所示,首先智能体不断与环境交互,获取交互数据<s,a,r,s′>存入replay memory
,当经验池中有足够多的数据后,从经验池中 随机取出一个 batch_size
大小的数据,利用当前网络计算 Q的预测值,使用 Q-Target 网络计算出 Q目标值,然后计算两者之间的损失函数,利用梯度下降来更新当前 网络参数,重复若干次后,把当前网络的参数 复制给 Q-Target 网络。
深度 Q 网络(Deep Q-Network,DQN)是一种基于深度学习和强化学习的算法,用于解决离散动作空间的强化学习问题。它是由 DeepMind 提出的,并在解决 Atari 游戏中取得了显著的成功。以下是关于 DQN 的总结:
基本思想:
经验回放:
目标网络:
贝尔曼方程:
探索策略:
应用领域:
优点:
缺点与挑战:
``
import torch # 导入torch import torch.nn as nn # 导入torch.nn import torch.nn.functional as F # 导入torch.nn.functional import numpy as np # 导入numpy import gym # 导入gym # 超参数 BATCH_SIZE = 32 # 样本数量 LR = 0.01 # 学习率 EPSILON = 0.9 # greedy policy GAMMA = 0.9 # reward discount TARGET_REPLACE_ITER = 100 # 目标网络更新频率 MEMORY_CAPACITY = 2000 # 记忆库容量 env = gym.make('CartPole-v0', render_mode="human").unwrapped # 使用gym库中的环境:CartPole,且打开封装(若想了解该环境,请自行百度) N_ACTIONS = env.action_space.n # 杆子动作个数 (2个) N_STATES = env.observation_space.shape[0] # 杆子状态个数 (4个) """ torch.nn是专门为神经网络设计的模块化接口。nn构建于Autograd之上,可以用来定义和运行神经网络。 nn.Module是nn中十分重要的类,包含网络各层的定义及forward方法。 定义网络: 需要继承nn.Module类,并实现forward方法。 一般把网络中具有可学习参数的层放在构造函数__init__()中。 只要在nn.Module的子类中定义了forward函数,backward函数就会被自动实现(利用Autograd)。 """ # 定义Net类 (定义网络) class Net(nn.Module): def __init__(self): # 定义Net的一系列属性 # nn.Module的子类函数必须在构造函数中执行父类的构造函数 super(Net, self).__init__() # 等价与nn.Module.__init__() self.fc1 = nn.Linear(N_STATES, 50) # 设置第一个全连接层(输入层到隐藏层): 状态数个神经元到50个神经元 self.fc1.weight.data.normal_(0, 0.1) # 权重初始化 (均值为0,方差为0.1的正态分布) self.out = nn.Linear(50, N_ACTIONS) # 设置第二个全连接层(隐藏层到输出层): 50个神经元到动作数个神经元 self.out.weight.data.normal_(0, 0.1) # 权重初始化 (均值为0,方差为0.1的正态分布) def forward(self, x): # 定义forward函数 (x为状态) x = F.relu(self.fc1(x)) # 连接输入层到隐藏层,且使用激励函数ReLU来处理经过隐藏层后的值 actions_value = self.out(x) # 连接隐藏层到输出层,获得最终的输出值 (即动作值) return actions_value # 返回动作值 # 定义DQN类 (定义两个网络) class DQN(object): def __init__(self): # 定义DQN的一系列属性 self.eval_net, self.target_net = Net(), Net() # 利用Net创建两个神经网络: 评估网络和目标网络 self.learn_step_counter = 0 # for target updating self.memory_counter = 0 # for storing memory self.memory = np.zeros((MEMORY_CAPACITY, N_STATES * 2 + 2)) # 初始化记忆库,一行代表一个transition self.optimizer = torch.optim.Adam(self.eval_net.parameters(), lr=LR) # 使用Adam优化器 (输入为评估网络的参数和学习率) self.loss_func = nn.MSELoss() # 使用均方损失函数 (loss(xi, yi)=(xi-yi)^2) def choose_action(self, x): # 定义动作选择函数 (x为状态) x = torch.unsqueeze(torch.FloatTensor(x), 0) # 将x转换成32-bit floating point形式,并在dim=0增加维数为1的维度 if np.random.uniform() < EPSILON: # 生成一个在[0, 1)内的随机数,如果小于EPSILON,选择最优动作 actions_value = self.eval_net.forward(x) # 通过对评估网络输入状态x,前向传播获得动作值 action = torch.max(actions_value, 1)[1].data.numpy() # 输出每一行最大值的索引,并转化为numpy ndarray形式 action = action[0] # 输出action的第一个数 else: # 随机选择动作 action = np.random.randint(0, N_ACTIONS) # 这里action随机等于0或1 (N_ACTIONS = 2) return action # 返回选择的动作 (0或1) def store_transition(self, s, a, r, s_): # 定义记忆存储函数 (这里输入为一个transition) transition = np.hstack((s, [a, r], s_)) # 在水平方向上拼接数组 # 如果记忆库满了,便覆盖旧的数据 index = self.memory_counter % MEMORY_CAPACITY # 获取transition要置入的行数 self.memory[index, :] = transition # 置入transition self.memory_counter += 1 # memory_counter自加1 def learn(self): # 定义学习函数(记忆库已满后便开始学习) # 目标网络参数更新 if self.learn_step_counter % TARGET_REPLACE_ITER == 0: # 一开始触发,然后每100步触发 self.target_net.load_state_dict(self.eval_net.state_dict()) # 将评估网络的参数赋给目标网络 self.learn_step_counter += 1 # 学习步数自加1 # 抽取记忆库中的批数据 sample_index = np.random.choice(MEMORY_CAPACITY, BATCH_SIZE) # 在[0, 2000)内随机抽取32个数,可能会重复 b_memory = self.memory[sample_index, :] # 抽取32个索引对应的32个transition,存入b_memory b_s = torch.FloatTensor(b_memory[:, :N_STATES]) # 将32个s抽出,转为32-bit floating point形式,并存储到b_s中,b_s为32行4列 b_a = torch.LongTensor(b_memory[:, N_STATES:N_STATES+1].astype(int)) # 将32个a抽出,转为64-bit integer (signed)形式,并存储到b_a中 (之所以为LongTensor类型,是为了方便后面torch.gather的使用),b_a为32行1列 b_r = torch.FloatTensor(b_memory[:, N_STATES+1:N_STATES+2]) # 将32个r抽出,转为32-bit floating point形式,并存储到b_s中,b_r为32行1列 b_s_ = torch.FloatTensor(b_memory[:, -N_STATES:]) # 将32个s_抽出,转为32-bit floating point形式,并存储到b_s中,b_s_为32行4列 # 获取32个transition的评估值和目标值,并利用损失函数和优化器进行评估网络参数更新 q_eval = self.eval_net(b_s).gather(1, b_a) # eval_net(b_s)通过评估网络输出32行每个b_s对应的一系列动作值,然后.gather(1, b_a)代表对每行对应索引b_a的Q值提取进行聚合 q_next = self.target_net(b_s_).detach() # q_next不进行反向传递误差,所以detach;q_next表示通过目标网络输出32行每个b_s_对应的一系列动作值 q_target = b_r + GAMMA * q_next.max(1)[0].view(BATCH_SIZE, 1) # q_next.max(1)[0]表示只返回每一行的最大值,不返回索引(长度为32的一维张量);.view()表示把前面所得到的一维张量变成(BATCH_SIZE, 1)的形状;最终通过公式得到目标值 loss = self.loss_func(q_eval, q_target) # 输入32个评估值和32个目标值,使用均方损失函数 self.optimizer.zero_grad() # 清空上一步的残余更新参数值 loss.backward() # 误差反向传播, 计算参数更新值 self.optimizer.step() # 更新评估网络的所有参数 dqn = DQN() # 令dqn=DQN类 for i in range(400): # 400个episode循环 print('<<<<<<<<<Episode: %s' % i) s = env.reset()[0] # 重置环境 episode_reward_sum = 0 # 初始化该循环对应的episode的总奖励 while True: # 开始一个episode (每一个循环代表一步) env.render() # 显示实验动画 a = dqn.choose_action(s) # 输入该步对应的状态s,选择动作 s_, r, done, info, _ = env.step(a) # 执行动作,获得反馈 # 执行动作,获得反馈 # 修改奖励 (不修改也可以,修改奖励只是为了更快地得到训练好的摆杆) x, x_dot, theta, theta_dot = s_ r1 = (env.x_threshold - abs(x)) / env.x_threshold - 0.8 r2 = (env.theta_threshold_radians - abs(theta)) / env.theta_threshold_radians - 0.5 new_r = r1 + r2 dqn.store_transition(s, a, new_r, s_) # 存储样本 episode_reward_sum += new_r # 逐步加上一个episode内每个step的reward s = s_ # 更新状态 if dqn.memory_counter > MEMORY_CAPACITY: # 如果累计的transition数量超过了记忆库的固定容量2000 # 开始学习 (抽取记忆,即32个transition,并对评估网络参数进行更新,并在开始学习后每隔100次将评估网络的参数赋给目标网络) dqn.learn() if done: # 如果done为True # round()方法返回episode_reward_sum的小数点四舍五入到2个数字 print('episode%s---reward_sum: %s' % (i, round(episode_reward_sum, 2))) break # 该episode结束 网络参数进行更新,并在开始学习后每隔100次将评估网络的参数赋给目标网络) dqn.learn() if done: # 如果done为True # round()方法返回episode_reward_sum的小数点四舍五入到2个数字 print('episode%s---reward_sum: %s' % (i, round(episode_reward_sum, 2))) break # 该episode结束
参考:https://hrl.boyuai.com/chapter/2/dqn%E7%AE%97%E6%B3%95#76-%E5%B0%8F%E7%BB%93
https://blog.csdn.net/weixin_44732379/article/details/127821138?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171247514916800222877057%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=171247514916800222877057&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-127821138-null-null.142v100pc_search_result_base6&utm_term=dqn&spm=1018.2226.3001.4187
https://www.cnblogs.com/jsfantasy/p/13623592.html
本文章只用于学习笔记使用,并无其他用途
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。