目录
- DQN2013 —— Playing Atari with Deep Reinforcement Learning
- DQN2015 —— Human-level control through deep reinforcement learning
- 引用文献
DQN2013 —— Playing Atari with Deep Reinforcement Learning
-
论文下载地址
-
论文介绍
该论文提出了一个基于卷积神经网络的深度强化学习模型,该模型是Q-learning算法的变体,该模型的输入数据是Atari游戏的原始原素,输出是预测未来的动作价值奖励(Q-value),其神经网络结构可以简单表示如下:
输入数据为[batch_size, width, height, channel]
(不同深度学习框架中的channel的位置可能不一样,在tf中输入的数据是将channel设置 在最后一个维度,我们暂以此描述)形式的多维数据,输入数据经过卷积神经网络得到特征向量 [batch_size, hidden_dim]
,将特征向量作为全连接神经网络的输入,得到输出的动作价值函数 Q ( s , a ) Q(s,a) Q(s,a)[batch_size, action_dim]
,使用完全贪婪算法或者epsilon贪婪算法基于 Q ( s , a ) Q(s,a) Q(s,a)的值选择对应的动作。 -
前人相关工作:
- TD-gammon:TD-gammon是一种双陆棋游戏程序,通过强化学习和自我对弈来学习并达到了超人类的游戏水平。TD-gammon使用了类似的无模型强化学习算法Q-learning,并使用具有一个隐藏层的多层感知器来近似价值函数1 (事实上,TD-gammon近似的是状态价值函数 V ( s ) V(s) V(s)而不是动作价值函数 Q ( s , a ) Q(s,a) Q(s,a))。然而,通过改进TD-gammon或者直接使用相同的方法应用于国际象棋、围棋和跳棋都不太成功,这导致人们普遍认为TD-gammon是一种特殊情况,仅适用于双陆棋,也许是因为掷骰子的随机性有助于探索状态空间,并使价值函数特别平滑2。
- 研究表明,将无模型强化学习算法(如Q-learning)与非线性函数逼近器(近似器)3或者与离线策略学习4相结合可能会导致Q-network发散。随后,强化学习的大部分工作都集中在具有更好收敛保证的线性函数逼近器上3。
- 最近,人们对将深度学习与强化学习相结合重新产生了兴趣。深度神经网络已经被用来评估环境 ϵ \epsilon ϵ,受限玻尔兹曼机已经被用来预测价值函数5或者策略6。此外,Q-learning的发散问题已经通过梯度时间差分得到部分解决。这些方法被证明在使用非线性函数逼近器7预测固定策略或使用Q-learning的受限变体8通过线性函数逼近器学习控制策略时是收敛的。然而,这些方法尚未扩展到非线性控制。
- 也许与该论文的方法最相似的前人工作是神经拟合Q-learning(neural fitted Q-learning,NFQ)9,NFQ优化了公式(1) L i ( θ i ) = E s , a ∼ ρ ( ⋅ ) [ ( y i − Q ( s , a ; θ i ) ) 2 ] (1) L_{i}(\theta_{i})=\mathbb{E}_{s,a\sim \rho(·)}[(y_i-Q(s,a;\theta_i))^{2}] \tag{1} Li(θi)=Es,a∼ρ(⋅)[(yi−Q(s,a;θi))2](1)的损失函数序列,使用RPROP算法更新Q-learning的参数。然后,它使用批量更新参数的方式,每次迭代的计算成本和数据集的大小成正比,而该论文DQN使用随机梯度更新,每次迭代的成本比较低,并且可以扩展到大型数据集,NFQ还成功应用于使用纯视觉输入的简单现实世界控制任务,首先使用深度自动编码器学习任务的低维表示,然后将低维表示作为NFQ的输入10。相比之下,DQN直接使用端到端的强化学习方法将视觉数据作为神经网络的输入,因此,DQN可以学习到与区分动作价值直接相关的特征。Q-learning之前也曾将经验回放和简单的神经网络11相结合,但同样是以低维状态而不是原始视觉作为神经网络的输入。
-
DQN2013伪代码
- 初始化经验回放池 D D D,回放池可存放数据数量为 N N N;
- 随机初始化动作值函数网络 Q Q Q的模型参数;
- 在每个回合(episode)获取环境初始状态
s
1
s_1
s1,同时对状态进行预处理
ϕ
1
=
ϕ
(
s
1
)
\phi_1=\phi(s_1)
ϕ1=ϕ(s1);
- 根据网络 Q ( ϕ s t , a ; θ ) Q(\phi_{s_t},a;\theta) Q(ϕst,a;θ),以 ϵ \epsilon ϵ贪婪策略选择动作 a t a_t at;
- 执行动作 a t a_t at与模拟器进行交互,获取奖励 r t r_t rt和下一个环境状态 s t + 1 s_{t+1} st+1,对 s t + 1 s_{t+1} st+1进行预处理 ϕ ( s t + 1 ) \phi(s_{t+1}) ϕ(st+1);
- 将 ( ϕ t , a t , r t , ϕ t + 1 ) (\phi_{t},a_t,r_t,\phi_{t+1}) (ϕt,at,rt,ϕt+1)存储到回放池 D D D中,如果回放池数据量达到最大值 N N N,抛弃最先存储的数据;
- 当回放池中的数据量足够,从回放池中随机采样 m i n i b a t c h minibatch minibatch个数据;
- 对每个数据,根据是否为终止状态,通过公式 y j = { r j r j + γ m a x a ′ Q ( ϕ j + 1 , a ′ ; θ ) y_j=\begin{cases} r_j \\ r_j + \gamma \space max_{a'}Q(\phi_{j+1,a';\theta}) \end{cases} yj={rjrj+γ maxa′Q(ϕj+1,a′;θ)计算 y j y_j yj;
- 最小化目标损失 ( y j − Q ( ϕ j , a j ; θ ) ) 2 (y_j-Q(\phi_{j,a_j;\theta}))^2 (yj−Q(ϕj,aj;θ))2,以此更新网络 Q Q Q;
- 伪代码中的公式3是指下面的公式(2); ∇ θ i L i ( θ i ) = E s , a ∼ ρ ( ⋅ ) ; s ′ ∼ E [ ( r + γ max a ′ Q ( s ′ , a ′ ; θ i − 1 ) − Q ( s , a ; θ i ) ) ∇ θ i Q ( s , a ; θ i ) ] (2) \nabla_{\theta_i} L_i\left(\theta_i\right)=\mathbb{E}_{s, a \sim \rho(\cdot) ; s^{\prime} \sim \mathcal{E}}\left[\left(r+\gamma \max _{a^{\prime}} Q\left(s^{\prime}, a^{\prime} ; \theta_{i-1}\right)-Q\left(s, a ; \theta_i\right)\right) \nabla_{\theta_i} Q\left(s, a ; \theta_i\right)\right] \tag{2} ∇θiLi(θi)=Es,a∼ρ(⋅);s′∼E[(r+γa′maxQ(s′,a′;θi−1)−Q(s,a;θi))∇θiQ(s,a;θi)](2) L i L_i Li如公式(3)所示: L i ( θ i ) = E s , a ∼ ρ ( ⋅ ) [ ( y i − Q ( s , a ; θ i ) ) 2 ] (3) L_i\left(\theta_i\right)=\mathbb{E}_{s, a \sim \rho(\cdot)}\left[\left(y_i-Q\left(s, a ; \theta_i\right)\right)^2\right] \tag{3} Li(θi)=Es,a∼ρ(⋅)[(yi−Q(s,a;θi))2](3)
-
训练流程
- 收集训练数据:我们可以用任何策略函数 π \pi π控制智能体与环境交互,这个 π \pi π可以叫做行为策略,dqn中使用的是 ϵ − g r e e d y \epsilon-greedy ϵ−greedy策略,如公式(4)所示: a t = { a r g m a x a Q ( s t , a ; ω ) , 以概率 ( 1 − ϵ ) 均匀抽取动作空间 A 中的一个动作 以概率 ϵ (4) a_t=\begin{cases} argmax_a \space Q(s_t,a;\omega),\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space以概率(1-\epsilon) \\ 均匀抽取动作空间\mathcal{A}中的一个动作 \space\space\space\space\space以概率\epsilon \end{cases} \tag{4} at={argmaxa Q(st,a;ω), 以概率(1−ϵ)均匀抽取动作空间A中的一个动作 以概率ϵ(4)把一个回合获取到的轨迹划分成 n n n个四元组 ( s t , a t , r t , s t + 1 ) (s_t,a_t,r_t,s_{t+1}) (st,at,rt,st+1),存入经验回放池。
- 更新DQN参数
w
\boldsymbol{w}
w:随机从经验回放池中取出一个四元组,记作
(
s
j
,
a
j
,
r
j
,
s
j
+
1
)
(s_j,a_j,r_j,s_{j+1})
(sj,aj,rj,sj+1),设DQN的当前参数为
w
n
o
w
\boldsymbol{w}_{now}
wnow,执行下面的步骤对参数做一次更新,得到新的参数
w
n
e
w
\boldsymbol{w}_{new}
wnew。
(1)对DQN做正向传播,得到 s t s_t st和 s t + 1 s_{t+1} st+1的Q-value值,如公式(5)和公式(6)所示: q ^ j = Q ( s j , a j , w n o w ) (5) \hat{q}_j=Q(s_j,a_j,\boldsymbol{w}_{now}) \tag{5} q^j=Q(sj,aj,wnow)(5) q ^ j + 1 = max a ∈ A Q ( s j + 1 , a ; w now ) (6) \widehat{q}_{j+1}=\max _{a \in \mathcal{A}} Q\left(s_{j+1}, a ; \boldsymbol{w}_{\text {now }}\right) \tag{6} q j+1=a∈AmaxQ(sj+1,a;wnow )(6)
(2)计算目标Q-value值和误差,如公式(7)和公式(8)所示: y ^ j = r j + γ ⋅ q ^ j + 1 (7) \widehat{y}_j=r_j+\gamma \cdot \widehat{q}_{j+1} \tag{7} y j=rj+γ⋅q j+1(7) δ j = q ^ j − y ^ j (8) \delta_j=\widehat{q}_j-\widehat{y}_j \tag{8} δj=q j−y j(8)
(3)对DQN做反向传播,得到梯度,如公式(9)所示: g j = ∇ w Q ( s j , a j ; w now ) (9) \boldsymbol{g}_j=\nabla_{\boldsymbol{w}} Q\left(s_j, a_j ; \boldsymbol{w}_{\text {now }}\right) \tag{9} gj=∇wQ(sj,aj;wnow )(9)
(4)做梯度下降更新DQN的参数,如公式(10)所示: w new ← w now − α ⋅ δ j ⋅ g j (10) \boldsymbol{w}_{\text {new }} \leftarrow \boldsymbol{w}_{\text {now }}-\alpha \cdot \delta_j \cdot \boldsymbol{g}_j \tag{10} wnew ←wnow −α⋅δj⋅gj(10)
-
基于gym的DQN2013代码实现:
-
requirement:
- python: 3.8
- gym: 0.17.3
- tensorflow: 2.9
-
code
import gym import random import collections import numpy as np import tensorflow as tf import matplotlib.pyplot as plt from tqdm import tqdm class ReplayBuffer: """经验回放池""" def __init__(self, capacity): self.buffer = collections.deque(maxlen=capacity) # 队列,先进先出 def add(self, state, action, reward, next_state, done): # 将数据加入buffer self.buffer.append((state, action, reward, next_state, done)) def sample(self, batch_size): # 从buffer中采样数据,数量为batch_size transitions = random.sample(self.buffer, batch_size) state, action, reward, next_state, done = zip(*transitions) return np.array(state), action, reward, np.array(next_state), done def size(self): # 目前buffer中数据的数量 return len(self.buffer) class Q_net(tf.keras.Model): """只有一层隐藏层的Q网络""" def __init__(self, hidden_dim, action_dim): super(Q_net, self).__init__() self.fc1 = tf.keras.layers.Dense(hidden_dim, activation=tf.keras.activations.relu) self.fc2 = tf.keras.layers.Dense(action_dim) def call(self, x): return self.fc2(self.fc1(x)) class DQN: def __init__(self, hidden_dim, action_dim, learning_rate, gamma, epsilon): self.action_dim = action_dim self.q_net = Q_net(hidden_dim, self.action_dim) # Q网络 # 使用Adam优化器 self.optimizer = tf.optimizers.Adam(learning_rate=learning_rate) # 折扣因子 self.gamma = gamma # epsilon-贪婪策略,这里没有对epsilon进行退火处理 self.epsilon = epsilon def take_action(self, state): # epsilon-贪婪策略采取动作 if np.random.random() < self.epsilon: action = np.random.randint(self.action_dim) else: state = tf.cast(tf.expand_dims(state, axis=0), dtype=tf.float32) action = tf.argmax(self.q_net.call(state), axis=1).numpy()[0] return action def update(self, transition_dict): states = tf.cast(transition_dict['states'], dtype=tf.float32) actions = tf.reshape(transition_dict['actions'], shape=(-1)) rewards = tf.reshape(tf.cast(transition_dict['rewards'], dtype=tf.float32), shape=(-1, 1)) next_states = tf.cast(transition_dict['next_states'], dtype=tf.float32) dones = tf.reshape(tf.cast(transition_dict['dones'], dtype=tf.float32), shape=(-1, 1)) # 梯度优化 with tf.GradientTape() as tape: # 获取当前状态动作对应的Q值 q_values = tf.gather(self.q_net.call(states), actions, batch_dims=1) # 下个状态的最大Q值,下个状态不进行梯度优化,使用tf.stop_gradient max_next_q_values = tf.stop_gradient(tf.reduce_max(self.q_net.call(next_states), axis=1, keepdims=True)) # TD误差目标 q_targets = rewards + self.gamma * max_next_q_values * (1 - dones) # 均方差损失函数 loss_func = tf.keras.losses.MeanSquaredError() # 计算均方误差损失函数 dqn_loss = tf.reduce_mean(loss_func(q_values, q_targets)) # 计算均方误差损失函数 grads = tape.gradient(dqn_loss, self.q_net.trainable_variables) self.optimizer.apply_gradients(zip(grads, self.q_net.trainable_variables)) lr = 2e-3 num_episodes = 500 hidden_dim = 128 gamma = 0.98 epsilon = 0.01 buffer_size = 10000 minimal_size = 500 batch_size = 64 env_name = 'CartPole-v0' env = gym.make(env_name) random.seed(0) np.random.seed(0) env.seed(0) tf.random.set_seed(0) replay_buffer = ReplayBuffer(buffer_size) state_dim = env.observation_space.shape[0] # 输入状态维数 action_dim = env.action_space.n # 动作维数 agent = DQN(hidden_dim, action_dim, lr, gamma, epsilon) return_list = [] for i in range(10): with tqdm(total=int(num_episodes / 10), desc='Iteration %d' % i) as pbar: for i_episode in range(int(num_episodes / 10)): episode_return = 0 state = env.reset() done = False while not done: action = agent.take_action(state) next_state, reward, done, _ = env.step(action) replay_buffer.add(state, action, reward, next_state, done) state = next_state episode_return += reward # 当buffer数据的数量超过一定值后,才进行Q网络训练 if replay_buffer.size() > minimal_size: b_s, b_a, b_r, b_ns, b_d = replay_buffer.sample(batch_size) transition_dict = { 'states': b_s, 'actions': b_a, 'next_states': b_ns, 'rewards': b_r, 'dones': b_d } agent.update(transition_dict) return_list.append(episode_return) if (i_episode + 1) % 10 == 0: pbar.set_postfix({ 'episode': '%d' % (num_episodes / 10 * i + i_episode + 1), 'return': '%.3f' % np.mean(return_list[-10:]) }) pbar.update(1) episodes_list = list(range(len(return_list))) plt.plot(episodes_list, return_list) plt.xlabel('Episodes') plt.ylabel('Returns') plt.title('DQN on {}'.format(env_name)) plt.show()
-
result
-
-
DQN2013的高估问题
Q-learning算法有一个缺陷,用Q-learning训练的DQN会高估真实的价值,而且高估通常是非均匀的,这个缺陷导致DQN的表现很差。高估问题不是DQN模型的问题,是Q-learning的问题。
Q-learning产生高估的原因有两个:
(1)自举导致偏差的传播;
(2)动作价值函数最大化导致目标动作价值高估真实价值;下面简单分析一下导致高估的两个原因:
-
自举导致偏差的传播
在强化学习中,自举意思是“用一个估算去更新同类的估算”,类似于“自己把自己给举起来” 。我们回顾一下训练DQN使用的Q-learning算法,研究其中存在的自举。
算法每次从经验回放池中抽取四元组 ( s j , a j , r j , s j + 1 ) (s_j, a_j, r_j, s_{j+1}) (sj,aj,rj,sj+1),然后执行下面步骤更新DQN的参数:
(1)计算目标动作值函数: y ^ j = r j + γ ⋅ max a j + 1 ∈ A Q ( s j + 1 , a j + 1 ; w now ) ⏟ D Q N 自己做出的估计 \widehat{y}_j=r_j+\gamma \cdot \underbrace{\max _{a_{j+1} \in \mathcal{A}} Q\left(s_{j+1}, a_{j+1} ; \boldsymbol{w}_{\text {now }}\right)}_{\mathrm{DQN} \text { 自己做出的估计 }} y j=rj+γ⋅DQN 自己做出的估计 aj+1∈AmaxQ(sj+1,aj+1;wnow )(2)计算损失函数: L ( w ) = 1 2 [ Q ( s j , a j ; w ) − y ^ j ⏟ 让 DQN 拟合 y ^ j ] 2 L(\boldsymbol{w})=\frac{1}{2}[\underbrace{Q\left(s_j, a_j ; \boldsymbol{w}\right)-\widehat{y}_j}_{\text {让 DQN 拟合 } \widehat{y}_j}]^2 L(w)=21[让 DQN 拟合 y j Q(sj,aj;w)−y j]2(3)进行梯度下降更新DQN参数: w new ← w now − α ⋅ ∇ w L ( w now ) \boldsymbol{w}_{\text {new }} \leftarrow \boldsymbol{w}_{\text {now }}-\alpha \cdot \nabla_{\boldsymbol{w}} L\left(\boldsymbol{w}_{\text {now }}\right) wnew ←wnow −α⋅∇wL(wnow )第一步计算目标动作值 y ^ j \hat{y}_j y^j是基于DQN自己做出的估计,第二步让DQN去拟合 y ^ j \hat{y}_j y^j,这意味着用DQN自己做出的估计去更新DQN自身,这属于自举。
DQN中 Q ( s , a , w ) Q(s,a,\boldsymbol{w}) Q(s,a,w)是对于最优动作价值函数 Q ⋆ ( s , a ) Q_{\star}(s, a) Q⋆(s,a)的近似,最理想的情况下 Q ( s , a ; w ) = Q ⋆ ( s , a ) , ∀ s , a Q(s, a ; \boldsymbol{w})=Q_{\star}(s, a), \forall s, a Q(s,a;w)=Q⋆(s,a),∀s,a,假如碰巧 Q ( s j + 1 , a j + 1 ; w ) Q\left(s_{j+1}, a_{j+1} ; \boldsymbol{w}\right) Q(sj+1,aj+1;w)低估(或高估)真实价值 Q ⋆ ( s j + 1 , a j + 1 ) Q_{\star}\left(s_{j+1}, a_{j+1}\right) Q⋆(sj+1,aj+1),则会发生下面的情况: Q ( s j + 1 , a j + 1 ; w ) 低估(或高估) Q ⋆ ( s j + 1 , a j + 1 ) ⟹ y ^ j 低估(或高估) Q ⋆ ( s j , a j ) ⟹ Q ( s j , a j ; w ) 低估(或高估) Q ⋆ ( s j , a j ) . \begin{aligned} & \space \space \space \space \space \space \space \space \space Q\left(s_{j+1}, a_{j+1} ; \boldsymbol{w}\right) \text { 低估(或高估) } \space \space \space \space Q_{\star}\left(s_{j+1}, a_{j+1}\right) \\ & \Longrightarrow \quad \space \space \space \space \space \space \space \space \space \widehat{y}_j \space \space \space \space \space \space \space \space \space \quad \text { 低估(或高估) } \space \space \space \space Q_{\star}\left(s_j, a_j\right) \\ & \Longrightarrow \space \space \space \space Q\left(s_j, a_j ; \boldsymbol{w}\right) \quad \text { 低估(或高估) } \quad Q_{\star}\left(s_j, a_j\right) \text {. } \\ & \end{aligned} Q(sj+1,aj+1;w) 低估(或高估) Q⋆(sj+1,aj+1)⟹ y j 低估(或高估) Q⋆(sj,aj)⟹ Q(sj,aj;w) 低估(或高估) Q⋆(sj,aj).
如果 Q ( s j + 1 , a j + 1 ; w ) Q\left(s_{j+1}, a_{j+1} ; \boldsymbol{w}\right) Q(sj+1,aj+1;w)是对真实价值 Q ⋆ ( s j + 1 , a j + 1 ) Q_{\star}\left(s_{j+1}, a_{j+1}\right) Q⋆(sj+1,aj+1)的低估(或高估),就会导致 Q ( s j , a j ; w ) Q\left(s_{j}, a_{j} ; \boldsymbol{w}\right) Q(sj,aj;w)低估或高估价值 Q ⋆ ( s j , a j ) Q_{\star}\left(s_{j}, a_{j}\right) Q⋆(sj,aj)。也就是说低估(或高估)从 ( s j + 1 , a j + 1 ) (s_{j+1},a_{j+1}) (sj+1,aj+1)传播到 ( s j , a j ) (s_j,a_j) (sj,aj),让更多的价值被低估(或高估)。
-
DQN2015 —— Human-level control through deep reinforcement learning
-
论文下载地址
-
论文介绍
众所周知,当强化学习使用神经网络等非线性函数来表示动作值函数时是不稳定的,甚至是发散的。这种不稳定性有几个原因:(1)观测序列数据存在相关性,当前状态和下一状态具有高度关联性,动作价值函数的微小更新都有可能显著改变策略参数并因此改变数据分布;(2)动作值函数Q和目标动作值函数 r + γ m a x a ′ Q ( s ′ , a ′ ) r+\gamma \space max_{a'}Q(s',a') r+γ maxa′Q(s′,a′)的相关性,具体可以看DQN,计算当前动作值函数和target动作值函数都是使用一个Q神经网络。该论文提出一种基于Q-learning的变体模型来解决这些不稳定性,它使用了两种关键方法,第一,使用经验回放机制,对训练数据进行随机化,消除观测序列中数据的相关性并平滑数据分布的变化;第二,使用目标网络预测下一状态的动作值函数用于更新当前网络的参数,并周期性地更新目标网络的参数(目标网络复制当前网络的参数),以此减少动作值函数Q和目标动作值函数 r + γ m a x a ′ Q ( s ′ , a ′ ) r+\gamma \space max_{a'}Q(s',a') r+γ maxa′Q(s′,a′)的相关性。(可以简单理解为比DQN2013多了一个目标网络)
-
伪代码
- 初始化经验回放池 D D D,容量大小为 N N N;
- 初始化动作值函数 Q Q Q的参数 θ \theta θ;
- 初始化目标动作值函数 θ ^ \hat{\theta} θ^的参数 θ − \theta^{-} θ−,令 θ − = θ \theta^{-}=\theta θ−=θ;
- 在每个回合(episode)获取环境初始状态
s
1
s_1
s1,同时对状态进行预处理
ϕ
1
=
ϕ
(
s
1
)
\phi_1=\phi(s_1)
ϕ1=ϕ(s1);
- 根据网络 Q ( ϕ s t , a ; θ ) Q(\phi_{s_t},a;\theta) Q(ϕst,a;θ),以 ϵ \epsilon ϵ贪婪策略选择动作 a t a_t at;
- 执行动作 a t a_t at与模拟器进行交互,获取奖励 r t r_t rt和下一个环境状态 s t + 1 s_{t+1} st+1,对 s t + 1 s_{t+1} st+1进行预处理 ϕ ( s t + 1 ) \phi(s_{t+1}) ϕ(st+1);
- 将 ( ϕ t , a t , r t , ϕ t + 1 ) (\phi_{t},a_t,r_t,\phi_{t+1}) (ϕt,at,rt,ϕt+1)存储到回放池 D D D中,如果回放池数据量达到最大值 N N N,清理最先存储的数据;
- 当回放池中的数据量足够,从回放池中随机采样 m i n i b a t c h minibatch minibatch个数据;
- 对每个数据,根据是否为终止状态,通过公式 y j = { r j r j + γ m a x a ′ Q ^ ( ϕ j + 1 , a ′ ; θ ) y_j=\begin{cases} r_j \\ r_j + \gamma \space max_{a'}\hat{Q}(\phi_{j+1,a';\theta}) \end{cases} yj={rjrj+γ maxa′Q^(ϕj+1,a′;θ)计算 y j y_j yj,注意这里计算 y j y_j yj的时候使用的是目标网络 Q ^ \hat{Q} Q^,DQN2013使用的是 Q Q Q;
- 最小化目标损失 ( y j − Q ( ϕ j , a j ; θ ) ) 2 (y_j-Q(\phi_{j,a_j;\theta}))^2 (yj−Q(ϕj,aj;θ))2,以此更新网络 Q Q Q的模型参数;
- 每间 C C C步更新目标网络 Q ^ \hat{Q} Q^的模型参数,即将 Q Q Q的模型参数复制给目标网络 Q ^ \hat{Q} Q^;
-
训练流程
- 收集训练数据:我们可以用任何策略函数 π \pi π控制智能体与环境交互,这个 π \pi π可以叫做行为策略,dqn中使用的是 ϵ − g r e e d y \epsilon-greedy ϵ−greedy策略,如公式(11)所示: a t = { a r g m a x a Q ( s t , a ; ω ) , 以概率 ( 1 − ϵ ) 均匀抽取动作空间 A 中的一个动作 以概率 ϵ (11) a_t=\begin{cases} argmax_a \space Q(s_t,a;\omega),\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space\space以概率(1-\epsilon) \\ 均匀抽取动作空间\mathcal{A}中的一个动作 \space\space\space\space\space以概率\epsilon \end{cases} \tag{11} at={argmaxa Q(st,a;ω), 以概率(1−ϵ)均匀抽取动作空间A中的一个动作 以概率ϵ(11)把一个回合获取到的轨迹划分成 n n n个四元组 ( s t , a t , r t , s t + 1 ) (s_t,a_t,r_t,s_{t+1}) (st,at,rt,st+1),存入经验回放池。
- 更新DQN参数
w
\boldsymbol{w}
w:随机从经验回放池中取出一个四元组,记作
(
s
j
,
a
j
,
r
j
,
s
j
+
1
)
(s_j,a_j,r_j,s_{j+1})
(sj,aj,rj,sj+1),设DQN的当前参数为
w
n
o
w
\boldsymbol{w}_{now}
wnow,执行下面的步骤对参数做一次更新,得到新的参数
w
n
e
w
\boldsymbol{w}_{new}
wnew。
(1)对DQN做正向传播,得到 s t s_t st和 s t + 1 s_{t+1} st+1的Q-value值,如公式(12)和公式(13)所示: q ^ j = Q ( s j , a j , w n o w ) (12) \hat{q}_j=Q(s_j,a_j,\boldsymbol{w}_{now}) \tag{12} q^j=Q(sj,aj,wnow)(12) q ^ j + 1 = max a ∈ A Q ^ ( s j + 1 , a ; w now − ) (13) \widehat{q}_{j+1}=\max _{a \in \mathcal{A}} \hat{Q}\left(s_{j+1}, a ; \boldsymbol{w}^{-}_{\text {now }}\right) \tag{13} q j+1=a∈AmaxQ^(sj+1,a;wnow −)(13)
(2)计算目标Q-value值和误差,如公式(14)和公式(15)所示: y ^ j = r j + γ ⋅ q ^ j + 1 (14) \widehat{y}_j=r_j+\gamma \cdot \widehat{q}_{j+1} \tag{14} y j=rj+γ⋅q j+1(14) δ j = q ^ j − y ^ j (15) \delta_j=\widehat{q}_j-\widehat{y}_j \tag{15} δj=q j−y j(15)
(3)对DQN做反向传播,得到梯度,如公式(16)所示: g j = ∇ w Q ( s j , a j ; w now ) (16) \boldsymbol{g}_j=\nabla_{\boldsymbol{w}} Q\left(s_j, a_j ; \boldsymbol{w}_{\text {now }}\right) \tag{16} gj=∇wQ(sj,aj;wnow )(16)
(4)做梯度下降更新DQN的参数,如公式(17)所示: w new ← w now − α ⋅ δ j ⋅ g j (17) \boldsymbol{w}_{\text {new }} \leftarrow \boldsymbol{w}_{\text {now }}-\alpha \cdot \delta_j \cdot \boldsymbol{g}_j \tag{17} wnew ←wnow −α⋅δj⋅gj(17)
(5)间隔固定次数更新目标网络的参数: w n e w − = w n e w \boldsymbol{w}^{-}_{new}=\boldsymbol{w}_{new} wnew−=wnew
-
基于gym的DQN2015代码实现:
-
requirement:
- python: 3.8
- gym: 0.17.3
- tensorflow: 2.9
-
code
import gym import random import collections import numpy as np from tqdm import tqdm import tensorflow as tf import matplotlib.pyplot as plt class ReplayBuffer: """ 经验回放池 """ def __init__(self, capacity): self.buffer = collections.deque(maxlen=capacity) # 队列,先进先出 def add(self, state, action, reward, next_state, done): # 将数据加入buffer self.buffer.append((state, action, reward, next_state, done)) def sample(self, batch_size): # 从buffer中采样数据,数量为batch_size transitions = random.sample(self.buffer, batch_size) state, action, reward, next_state, done = zip(*transitions) return np.array(state), action, reward, np.array(next_state), done def size(self): # 目前buffer中数据的数量 return len(self.buffer) class Q_net(tf.keras.Model): """只有一层隐藏层的Q网络""" def __init__(self, state_dim, hidden_dim, action_dim): super(Q_net, self).__init__() self.fc1 = tf.keras.layers.Dense(hidden_dim, activation=tf.keras.activations.relu) self.fc2 = tf.keras.layers.Dense(action_dim) def call(self, x): return self.fc2(self.fc1(x)) class DQN: def __init__(self, state_dim, hidden_dim, action_dim, learning_rate, gamma, epsilon, target_update): self.action_dim = action_dim self.q_net = Q_net(state_dim, hidden_dim, self.action_dim) # Q网络 # 目标网络 self.target_q_net = Q_net(state_dim, hidden_dim, self.action_dim) # 使用Adam优化器 self.optimizer = tf.optimizers.Adam(learning_rate=learning_rate) self.gamma = gamma # 折扣因子 self.epsilon = epsilon # epsilon-贪婪策略 self.target_update = target_update # 目标网络更新频率 self.count = 0 # 计数器,记录更新次数 def take_action(self, state): # epsilon-贪婪策略采取动作 if np.random.random() < self.epsilon: action = np.random.randint(self.action_dim) else: state = tf.cast(tf.expand_dims(state, axis=0), dtype=tf.float32) action = tf.argmax(self.q_net.call(state), axis=1).numpy()[0] return action def update(self, transition_dict): states = tf.cast(transition_dict['states'], dtype=tf.float32) actions = tf.reshape(transition_dict['actions'], shape=(-1)) rewards = tf.reshape(tf.cast(transition_dict['rewards'], dtype=tf.float32), shape=(-1, 1)) next_states = tf.cast(transition_dict['next_states'], dtype=tf.float32) dones = tf.reshape(tf.cast(transition_dict['dones'], dtype=tf.float32), shape=(-1, 1)) with tf.GradientTape() as tape: q_values = tf.gather(self.q_net.call(states), actions, batch_dims=1) # Q值 # 下个状态的最大Q值 max_next_q_values = tf.reduce_max(self.target_q_net.call(next_states), axis=1, keepdims=True) q_targets = rewards + self.gamma * max_next_q_values * (1 - dones) # TD误差目标 loss_func = tf.keras.losses.MeanSquaredError() dqn_loss = tf.reduce_mean(loss_func(q_values, q_targets)) # 均方误差损失函数 grads = tape.gradient(dqn_loss, self.q_net.trainable_variables) self.optimizer.apply_gradients(zip(grads, self.q_net.trainable_variables)) if self.count % self.target_update == 0: self.target_q_net.set_weights(self.q_net.get_weights()) # 更新目标网络 self.count += 1 lr = 2e-3 num_episodes = 500 hidden_dim = 128 gamma = 0.98 epsilon = 0.01 target_update = 10 buffer_size = 10000 minimal_size = 500 batch_size = 64 env_name = 'CartPole-v0' env = gym.make(env_name) random.seed(0) np.random.seed(0) env.seed(0) tf.random.set_seed(0) replay_buffer = ReplayBuffer(buffer_size) state_dim = env.observation_space.shape[0] action_dim = env.action_space.n agent = DQN(state_dim, hidden_dim, action_dim, lr, gamma, epsilon, target_update) return_list = [] for i in range(10): with tqdm(total=int(num_episodes / 10), desc='Iteration %d' % i) as pbar: for i_episode in range(int(num_episodes / 10)): episode_return = 0 state = env.reset() done = False while not done: action = agent.take_action(state) next_state, reward, done, _ = env.step(action) replay_buffer.add(state, action, reward, next_state, done) state = next_state episode_return += reward # 当buffer数据的数量超过一定值后,才进行Q网络训练 if replay_buffer.size() > minimal_size: b_s, b_a, b_r, b_ns, b_d = replay_buffer.sample(batch_size) transition_dict = { 'states': b_s, 'actions': b_a, 'next_states': b_ns, 'rewards': b_r, 'dones': b_d } agent.update(transition_dict) return_list.append(episode_return) if (i_episode + 1) % 10 == 0: pbar.set_postfix({ 'episode': '%d' % (num_episodes / 10 * i + i_episode + 1), 'return': '%.3f' % np.mean(return_list[-10:]) }) pbar.update(1) episodes_list = list(range(len(return_list))) plt.plot(episodes_list, return_list) plt.xlabel('Episodes') plt.ylabel('Returns') plt.title('DQN on {}'.format(env_name)) plt.show()
-
result
-
引用文献
Gerald Tesauro. Temporal difference learning and td-gammon. Communications of the ACM,38(3):58–68, 1995. ↩︎
Jordan B. Pollack and Alan D. Blair. Why did td-gammon work. In Advances in Neural Information Processing Systems 9, pages 10–16, 1996. ↩︎
John N Tsitsiklis and Benjamin Van Roy. An analysis of temporal-difference learning with function approximation. Automatic Control, IEEE Transactions on, 42(5):674–690, 1997. ↩︎ ↩︎
Leemon Baird. Residual algorithms: Reinforcement learning with function approximation. In Proceedings of the 12th International Conference on Machine Learning (ICML 1995), pages 30–37. Morgan Kaufmann, 1995. ↩︎
Brian Sallans and Geoffrey E. Hinton. Reinforcement learning with factored states and actions. Journal of Machine Learning Research, 5:1063–1088, 2004. ↩︎
Nicolas Heess, David Silver, and Yee Whye Teh. Actor-critic reinforcement learning with energy-based policies. In European Workshop on Reinforcement Learning, page 43, 2012. ↩︎
Hamid Maei, Csaba Szepesvari, Shalabh Bhatnagar, Doina Precup, David Silver, and Rich Sutton. Convergent Temporal-Difference Learning with Arbitrary Smooth Function Approximation. In Advances in Neural Information Processing Systems 22, pages 1204–1212, 2009. ↩︎
Hamid Maei, Csaba Szepesv´ari, Shalabh Bhatnagar, and Richard S. Sutton. Toward off-policy learning control with function approximation. In Proceedings of the 27th International Conference on Machine Learning (ICML 2010), pages 719–726, 2010. ↩︎
Martin Riedmiller. Neural fitted q iteration–first experiences with a data efficient neural reinforcement learning method. In Machine Learning: ECML 2005, pages 317–328. Springer, 2005. ↩︎
Sascha Lange and Martin Riedmiller. Deep auto-encoder neural networks in reinforcement learning. In Neural Networks (IJCNN), The 2010 International Joint Conference on, pages 1–8. IEEE, 2010. ↩︎
Long-Ji Lin. Reinforcement learning for robots using neural networks. Technical report, DTIC Document, 1993. ↩︎