DRQN学习——代码解读

更新2019.12.5

找到一个可以跑的DRQN,不用安装新的游戏环境,更新需要的库即可。做了一些修改。代码:https://github.com/Elly0723/DRQN-tensorflow

主要信息————————————

1、输入是[1,84,84,3]大小的图片batchsize:4,转化成[?,21168]大小的向量,再reshape成[?,84,84,3]进入四层卷积,经过单元数为512的lstm得到当前隐状态ht,以2:1比例分为advantage 和 value值,计算最终的Qout。

2、targetQ和MainQ用的是同一个Q网络。每5steps用主网络更新目标网络,更新lstm隐藏状态。

3、损失函数为target和Qout的平方误差,计算损失的时,用mask去掉了一半的值(现在还不知道原因)

主要修改——————————

1、去掉了gif的存储,因为存了一堆看不明白的图

2、输出每10次迭代的loss图

后期计划(目标是用RNN和RL做路径规划)———————

1、修改输入为一张黑白的障碍物地图,输出离散动作以到达目的地
2、修改网络框架,输出连续动作值,参考DDPG
3、DDPG、AC和Lstm的尝试结合
4、多智能体的路径规划

每次sample 4 个记录,关于lstm归零隐藏状态如下:

                if total_steps % (update_freq) == 0:
                    updateTarget(targetOps, sess)
#####################################在这里,每update_freq=5steps,更新一次lstm隐状态。
##################################################################################
                    # Reset the recurrent layer's hidden state
                    state_train = (np.zeros([batch_size, h_size]), np.zeros([batch_size, h_size]))

                    trainBatch = myBuffer.sample(batch_size, trace_length)  #[[s, a, r, s1, d],[...]...]batch_size*trace_length个

                    #Get a random batch of experiences.
                    # Below we perform the Double-DQN update to the target Q-values
                    Q1 = sess.run(mainQN.predict,feed_dict={ \
                        mainQN.scalarInput: np.vstack(trainBatch[:, 3] / 255.0), \
                        mainQN.trainLength: trace_length, mainQN.state_in: state_train, mainQN.batch_size: batch_size})
                    Q2 = sess.run(targetQN.Qout, feed_dict={ \
                        targetQN.scalarInput: np.vstack(trainBatch[:, 3] / 255.0), \
                        targetQN.trainLength: trace_length, targetQN.state_in: state_train,
                        targetQN.batch_size: batch_size})

                    end_multiplier = -(trainBatch[:, 4] - 1)
                    doubleQ = Q2[range(batch_size * trace_length), Q1]
                    targetQ = trainBatch[:, 2] + (y * doubleQ * end_multiplier)

 

——————————————————————-以上更新——————————————————————————

 

基于DRQN玩vizdoom,代码(09章):https://github.com/sudharsan13296/Hands-On-Reinforcement-Learning-With-Python/ 

DQN神经网络是用于像Atari游戏这样的游戏AI程序,它通过观察游戏屏幕内容,由卷积网络(CNN)捕获屏幕中每一帧的特征来使AI能够“理解”玩游戏的过程,通过强化学习的奖罚机制,让AI成为游戏大师。然而DQN存在一个重要问题:那就是对屏幕捕获的记忆量有限。

DRQN:在DQN中,输入s、a,用神经网络逼近Q(s,a)值,而在某些情况下,智能体不能只根据当前一个观测值做决策,而需要之前的几个观测状态。比如打乒乓球,不但要知道球现在在哪里,也要知道球的速度和方向。因此加入RNN(或lstm),让后续状态也能感知以前的信息。

首先,复习一下使用卷积层获得状态信息的DQN: 使用连续的四帧图像作为输入。

 

DRQN的结构如下图所示,DRQN中将DQN中的一个全链接层替换为了LSTM结构,每一次的输入由四帧画面变成了仅仅一张画面。LSTM的输出经过一个全链接层之后变为每个动作的Q值。

 

在原始DQN中增加RNN:h(t)=tanh(W*x(t)+U*h(t-1)+b),x(t)为inputs经过几个卷积层得到的结果;o(t)=softmax(V*h(t)+b),o(t)为输出。 最后用一层全连接y=W*o(t)+b,得到最终的Q值。

#搭建DRQN网络的代码
 # now let us build the network

# first convolutional layer
        self.conv1 = tf.nn.conv2d(
            input=tf.reshape(self.input, shape=(1, self.input_shape[0],            self.input_shape[1], self.input_shape[2])),
            filter=self.features1, strides=[1, self.stride, self.stride, 1], padding="VALID")
        self.relu1 = tf.nn.relu(self.conv1)
        self.pool1 = tf.nn.max_pool(self.relu1, ksize=[1, self.poolsize, self.poolsize, 1],
                                    strides=[1, self.stride, self.stride, 1], padding="SAME")

        # second convolutional layer
        self.conv2 = tf.nn.conv2d(input=self.pool1, filter=self.features2, strides=[1, self.stride, self.stride, 1],
                                  padding="VALID")
        self.relu2 = tf.nn.relu(self.conv2)
        self.pool2 = tf.nn.max_pool(self.relu2, ksize=[1, self.poolsize, self.poolsize, 1],
                                    strides=[1, self.stride, self.stride, 1], padding="SAME")

        # third convolutional layer
        self.conv3 = tf.nn.conv2d(input=self.pool2, filter=self.features3, strides=[1, self.stride, self.stride, 1],
                                  padding="VALID")
        self.relu3 = tf.nn.relu(self.conv3)
        self.pool3 = tf.nn.max_pool(self.relu3, ksize=[1, self.poolsize, self.poolsize, 1],
                                    strides=[1, self.stride, self.stride, 1], padding="SAME")

        # add dropout and reshape the input
        self.drop1 = tf.nn.dropout(self.pool3, self.dropout_probability[0])
        self.reshaped_input = tf.reshape(self.drop1, shape=[1, -1])

        # 现在我们建立的递归神经网络,输入为 卷积网络的最后一层
        self.h = tf.tanh(tf.matmul(self.reshaped_input, self.rW) + tf.matmul(self.h, self.rU) + self.rb)
        self.o = tf.nn.softmax(tf.matmul(self.h, self.rV) + self.rc)

        # add drop out to RNN
        self.drop2 = tf.nn.dropout(self.o, self.dropout_probability[1])

        # we feed the result of RNN to the feed forward layer
        self.output = tf.reshape(tf.matmul(self.drop2, self.fW) + self.fb, shape=[-1, 1])
        self.prediction = tf.argmax(self.output)

        # compute loss
        self.loss = tf.reduce_mean(tf.square(self.target_vector - self.output))

        # we use Adam optimizer for minimizing the error
        self.optimizer = tf.train.AdamOptimizer(self.learning_rate)

        # compute gradients of the loss and update the gradients
        self.gradients = self.optimizer.compute_gradients(self.loss)
        self.update = self.optimizer.apply_gradients(self.gradients)

 

LSTM更新方式:参考:https://zhuanlan.zhihu.com/p/54898904

每次更新循环网络,需要包含一段时间连续的若干观测值 与奖励值 。此外,在每次训练时,LSTM隐含层的初始状态可以是0,也可以从上一次的值继承过来。因此具有两种更新学习方式:

a. Bootstrapped 序列更新

从经验回放内存中随机选取一次游戏过程(episode),从这次游戏过程的开始一直学习到游戏结束。在每一个时刻t,其目标状态值还是通过目标网络来获取。在一次游戏过程中,每一时刻LSTM隐含层的状态值从上一时刻继承而来。

b. Bootstrapped 随机更新

从经验回放内存中随机选取一次游戏过程(episode),再在这个游戏过程中随机选择一个时刻点,再选择若干步进行学习(可以是一步)。在每一个时刻t,其目标状态值还是通过目标网络 来获取。在每一次训练前需要将LSTM隐含层的状态置为0。

 

序列更新能够更好的让LSTM学习一次游戏过程的所有时间序列记忆,更有利于时序推理。但是由于序列化采样学习,违背了DQN随机采样的策略(因为神经网络要求学习数据独立同分布,由于时序数据之间有马尔科夫性,则会损害神经网络的学习效果)。

随机更新更符合DQN的随机采样策略,但是需要每次更新前将LSTM隐含层状态置为0,这将损害LSTM的记忆能力。实验证明这两种更新方法都能得到收敛的策略以及相似的效果。原文中主要采用随机更新的方法。

 

我的疑问:代码中貌似采用随机更新的方法,但是不知道如何体现:选择连续的几步学习。难道是每隔5帧,只随机选择一步来学习?而且在代码里面,没有看到把RNN隐层状态置0.  

 for episode in range(num_episodes):
            # start the new episode
            game.new_episode()
            # play the episode till it reaches the episode length
            for frame in range(episode_length):
                # get the game state
                state = game.get_state()
                s = state.screen_buffer
                # select the action
                a = actionDRQN.prediction.eval(feed_dict={actionDRQN.input: s})[0]
                action = actions[a]
                # perform the action and store the reward
                reward = game.make_action(action)
                # update total rewad
                total_reward += reward
                # if the episode is over then break
                if game.is_episode_finished():
                    break
                # store transistion to our experience buffer
                if (frame % store) == 0:
                    experiences.appendToBuffer((s, action, reward))
                # sample experience form the experience buffer
                if (frame % sample) == 0:
###############################################################################
###在这里,难道每次只选取一步来学习?!???????????????????????
###############################################################################3
                    memory = experiences.sample(1)
                    mem_frame = memory[0][0]
                    mem_reward = memory[0][2]
                    # now, train the network
                    Q1 = actionDRQN.output.eval(feed_dict={actionDRQN.input: mem_frame})
                    Q2 = targetDRQN.output.eval(feed_dict={targetDRQN.input: mem_frame})
                    # set learning rate
                    learning_rate = actionDRQN.learning_rate.eval()
                    # calculate Q value
                    Qtarget = old_q_value + learning_rate * (mem_reward + discount_factor * Q2 - old_q_value)
                    # update old Q value
                    old_q_value = Qtarget
                    # compute Loss
                    loss = actionDRQN.loss.eval(
                        feed_dict={actionDRQN.target_vector: Qtarget, actionDRQN.input: mem_frame})
                    # update total loss
                    total_loss += loss
                    # update both networks
                    actionDRQN.update.run(feed_dict={actionDRQN.target_vector: Qtarget, actionDRQN.input: mem_frame})
                    targetDRQN.update.run(feed_dict={targetDRQN.target_vector: Qtarget, targetDRQN.input: mem_frame})
            rewards.append((episode, total_reward))
            losses.append((episode, total_loss))
            print("Episode %d - Reward = %.3f, Loss = %.3f." % (episode, total_reward, total_loss))
            total_reward = 0
            total_loss = 0

 

参考:https://zhuanlan.zhihu.com/p/54898904

           https://www.jianshu.com/p/305aee09ec31

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章