在强化学习(十一) Prioritized Replay DQN中,我们讨论了对DQN的经验回放池按权重采样来优化DQN算法的方法,本文讨论另一种优化方法,Dueling DQN。本章内容主要参考了ICML 2016的deep RL tutorial和Dueling DQN的论文<Dueling Network Architectures for Deep Reinforcement Learning>(ICML 2016)。
1. Dueling DQN的优化点考虑在前面讲到的DDQN中,我们通过优化目标Q值的计算来优化算法,在Prioritized Replay DQN中,我们通过优化经验回放池按权重采样来优化算法。而在Dueling DQN中,我们尝试通过优化神经网络的结构来优化算法。
具体如何优化网络结构呢?Dueling DQN考虑将Q网络分成两部分,第一部分是仅仅与状态$S$有关,与具体要采用的动作$A$无关,这部分我们叫做价值函数部分,记做$V(S,w,\alpha)$,第二部分同时与状态状态$S$和动作$A$有关,这部分叫做优势函数(Advantage Function)部分,记为$A(S,A,w,\beta)$,那么最终我们的价值函数可以重新表示为:$$Q(S,A, w, \alpha, \beta) = V(S,w,\alpha) + A(S,A,w,\beta)$$
其中,$w$是公共部分的网络参数,而$\alpha$是价值函数独有部分的网络参数,而$\beta$是优势函数独有部分的网络参数。
2. Dueling DQN网络结构由于Q网络的价值函数被分为两部分,因此Dueling DQN的网络结构也和之前的DQN不同。为了简化算法描述,这里不使用原论文的CNN网络结构,而是使用前面文中用到的最简单的三层神经网络来描述。是否使用CNN对Dueling DQN算法本身无影响。
在前面讲到的DDQN等DQN算法中,我使用了一个简单的三层神经网络:一个输入层,一个隐藏层和一个输出层。如下左图所示:
而在Dueling DQN中,我们在后面加了两个子网络结构,分别对应上面上到价格函数网络部分和优势函数网络部分。对应上面右图所示。最终Q网络的输出由价格函数网络的输出和优势函数网络的输出线性组合得到。
我们可以直接使用上一节的价值函数的组合公式得到我们的动作价值,但是这个式子无法辨识最终输出里面$V(S,w,\alpha)$和$A(S,A,w,\beta)$各自的作用,为了可以体现这种可辨识性(identifiability),实际使用的组合公式如下:$$Q(S,A, w, \alpha, \beta) = V(S,w,\alpha) + (A(S,A,w,\beta) - \frac{1}{\mathcal{A}}\sum\limits_{a' \in \mathcal{A}}A(S,a', w,\beta))$$
其实就是对优势函数部分做了中心化的处理。以上就是Duel DQN的主要算法思路。由于它仅仅涉及神经网络的中间结构的改进,现有的DQN算法可以在使用Duel DQN网络结构的基础上继续使用现有的算法。由于算法主流程和其他算法没有差异,这里就单独讲Duel DQN的算法流程了。
3. Dueling DQN实例下面我们用一个具体的例子来演示DQN的应用。仍然使用了OpenAI Gym中的CartPole-v0游戏来作为我们算法应用。CartPole-v0游戏的介绍参见这里。它比较简单,基本要求就是控制下面的cart移动使连接在上面的pole保持垂直不倒。这个任务只有两个离散动作,要么向左用力,要么向右用力。而state状态就是这个cart的位置和速度, pole的角度和角速度,4维的特征。坚持到200分的奖励则为过关。
这个实例代基于Nature DQN,并将网络结构改为上图中右边的Dueling DQN网络结构,完整的代码参见我的github: https://github.com/ljpzzz/machinelearning/blob/master/reinforcement-learning/duel_dqn.py
这里我们重点关注Dueling DQN和Nature DQN的代码的不同之处。也就是网络结构定义部分,主要的代码如下,一共有两个相同结构的Q网络,每个Q网络都有状态函数和优势函数的定义,以及组合后的Q网络输出,如代码红色部分:
def create_Q_network(self): # input layer self.state_input = tf.placeholder("float", [None, self.state_dim]) # network weights with tf.variable_scope('current_net'): W1 = self.weight_variable([self.state_dim,20]) b1 = self.bias_variable([20]) # hidden layer 1 h_layer_1 = tf.nn.relu(tf.matmul(self.state_input,W1) + b1) # hidden layer for state value with tf.variable_scope('Value'): W21= self.weight_variable([20,1]) b21 = self.bias_variable([1]) self.V = tf.matmul(h_layer_1, W21) + b21 # hidden layer for action value with tf.variable_scope('Advantage'): W22 = self.weight_variable([20,self.action_dim]) b22 = self.bias_variable([self.action_dim]) self.A = tf.matmul(h_layer_1, W22) + b22 # Q Value layer self.Q_value = self.V + (self.A - tf.reduce_mean(self.A, axis=1, keep_dims=True)) with tf.variable_scope('target_net'): W1t = self.weight_variable([self.state_dim,20]) b1t = self.bias_variable([20]) # hidden layer 1 h_layer_1t = tf.nn.relu(tf.matmul(self.state_input,W1t) + b1t) # hidden layer for state value with tf.variable_scope('Value'): W2v = self.weight_variable([20,1]) b2v = self.bias_variable([1]) self.VT = tf.matmul(h_layer_1t, W2v) + b2v # hidden layer for action value with tf.variable_scope('Advantage'): W2a = self.weight_variable([20,self.action_dim]) b2a = self.bias_variable([self.action_dim]) self.AT = tf.matmul(h_layer_1t, W2a) + b2a # Q Value layer self.target_Q_value = self.VT + (self.AT - tf.reduce_mean(self.AT, axis=1, keep_dims=True))