算法实战篇(一),Tensorflow实现经典DQN算法

我们在“基础算法篇(四)值函数逼近方法解决强化学习问题”中介绍了经典的DQN算法,今天,我们就来点实际的,正式实现一下相关算法。

Tensorflow实现经典DQN算法

  • 一、基础游戏背景介绍
  • 二、建立文件与撰写主函数
  • 三、Agent功能介绍
    • (一)DQN类的初始化函数
    • (二)建立深度神经网络
    • (三)设计网络参数更新方法
    • (四)Agent行为生成(即利用网络输出行为)
    • (五)存储数据
    • (六)训练网络更新参数
    • (七)更新目标网络参数
  • 总结

一、基础游戏背景介绍

我们这次代码实现中,使用的对象,是Gym中的活动杆游戏,如下图所示:
算法实战篇(一),Tensorflow实现经典DQN算法_第1张图片游戏的规则非常简单,即操控黑色小车左右移动,使它上面的木棒能够保持平衡。当小车偏离中心4.8个单位,或杆的倾斜超过24度,任务失败。
这个游戏的观测空间是一个Box(4,)对象,即四个float组成的数组;而行动空间是一个Discrete(2),即0或1。这两个值其实就决定了我们后续建立神经网络的输入和输出。
下面,我们正式进入代码编写环节。
备注一下:关于编程环境的搭建,请大家参考“番外篇,强化学习基础环境搭建”,本篇使用的tensorflow安装,使用pip install 命令即可,在这里不再赘述。

二、建立文件与撰写主函数

首先,我们可以建立一个Python文件,并按照前面我们介绍的DQN算法,写出主函数:

env = gym.make(ENV_NAME)
agent = DQN(env.observation_space, env.action_space)
for episode in range(EPISODES):
    # get the initial state
    state = env.reset()
    for step in range(STEPS):
        # get the action by state
        action = agent.Choose_Action(state)
        # step the env forward and get the new state
        next_state, reward, done, info = env.step(action)
        # store the data in order to update the network in future
        agent.Store_Data(state, action, reward, next_state, done)
        if len(agent.replay_buffer) > BATCH_SIZE:
            agent.Train_Network(BATCH_SIZE)
        if step % UPDATE_STEP == 0:
            agent.Update_Target_Network()
        state = next_state
        if done:
            break

我们逐行进行介绍:

  • 1.生成环境对象env
  • 2.根据观测空间和行动空间,生成agent对象 [对应(一)、(二)、(三)]
  • 3.对于每一幕游戏
  • 4.首先初始化环境,得到一个初始状态
  • 5.开始进行游戏,对于每一步
  • 6.agent根据状态得到一个行动action [对应(四)]
  • 7.将这个行动action输入环境,推动游戏向前走一步,并得到新的状态、即时奖励,以及游戏是否结束的标识
  • 8.将这一步的相关数据存入一个缓存池内 [对应(五)]
  • 9.如果缓存池内的数据量大于我们设置的进行学习的随机抽取量,则进行学习 [对应(六)]
  • 10.如果步数达到我们设置的更新频率,则更新DQN算法中目标网络的权值参数**[对应(七)]**
  • 11.最后如果这一幕游戏结束,则循环进行下一幕游戏,直到我们设计的所有幕数游戏都进行完,则结束游戏

由上面可以看出,算法中需要重点关注的,就是由DQN类生成的agent对象,下面我们对这个对象进行详细介绍。

三、Agent功能介绍

本算法中的agent,就是由DQN类实例化后的对象,这个对象主要完成的功能,就是根据环境状态生成行动,同时优化网络参数,最终实现DQN算法中介绍的TD差分的最小化,下面详细介绍相关部分。

(一)DQN类的初始化函数

DQN 类的初始化函数主要是通过输入的状态空间和行动空间参数,生成拟合Q值的深度神经网络,并设计网络的参数更新方法,如下:

def __init__(self, observation_space, action_space):
    # the state is the input vector of network, in this env, it has four dimensions
    self.state_dim = observation_space.shape[0]
    # the action is the output vector and it has two dimensions
    self.action_dim = action_space.n
    # init experience replay, the deque is a list that first-in & first-out
    self.replay_buffer = deque()
    # you can create the network by the two parameters
    self.create_Q_network()
    # after create the network, we can define the training methods
    self.create_updating_method()
    # set the value in choose_action
    self.epsilon = INITIAL_EPSILON
    # Init session
    self.session = tf.InteractiveSession()
    self.session.run(tf.global_variables_initializer())

具体的神经网络建立方法和网络参数更新方法,我们在下面进行介绍。

(二)建立深度神经网络

DQN算法中设计了两个网络,current_net和target_net,两个网络结构相同,都是四层的全连接网络(state–>50–>20–>action),在后续算法中current_net用来生成行动,target_net用来计算TD目标值,具体代码如下:

def create_Q_network(self):
    # first, set the input of networks
    self.state_input = tf.placeholder("float", [None, self.state_dim])
    # second, create the current_net
    with tf.variable_scope('current_net'):
        # first, set the network's weights
        W1 = self.weight_variable([self.state_dim, 50])
        b1 = self.bias_variable([50])
        W2 = self.weight_variable([50, 20])
        b2 = self.bias_variable([20])
        W3 = self.weight_variable([20, self.action_dim])
        b3 = self.bias_variable([self.action_dim])
        # second, set the layers
        # hidden layer one
        h_layer_one = tf.nn.relu(tf.matmul(self.state_input, W1) + b1)
        # hidden layer two
        h_layer_two = tf.nn.relu(tf.matmul(h_layer_one, W2) + b2)
        # the output of current_net
        self.Q_value = tf.matmul(h_layer_two, W3) + b3
    # third, create the current_net
    with tf.variable_scope('target_net'):
        # first, set the network's weights
        t_W1 = self.weight_variable([self.state_dim, 50])
        t_b1 = self.bias_variable([50])
        t_W2 = self.weight_variable([50, 20])
        t_b2 = self.bias_variable([20])
        t_W3 = self.weight_variable([20, self.action_dim])
        t_b3 = self.bias_variable([self.action_dim])
        # second, set the layers
        # hidden layer one
        t_h_layer_one = tf.nn.relu(tf.matmul(self.state_input, t_W1) + t_b1)
        # hidden layer two
        t_h_layer_two = tf.nn.relu(tf.matmul(t_h_layer_one, t_W2) + t_b2)
        # the output of current_net
        self.target_Q_value = tf.matmul(t_h_layer_two, t_W3) + t_b3
    # at last, solve the parameters replace problem
    # the parameters of current_net
    e_params = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope='current_net')
    # the parameters of target_net
    t_params = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scope='target_net')
    # define the operation that replace the target_net's parameters by current_net's parameters
    with tf.variable_scope('soft_replacement'):
        self.target_replace_op = [tf.assign(t, e) for t, e in zip(t_params, e_params)]

在这里我们还定义了一个拷贝网络参数的操作,self.target_replace_op,即用current_net的参数替换target_net的参数。

(三)设计网络参数更新方法

在DQN算法中,我们需要设置TD目标值为 y j y_j yj,然后将 ( y j − Q ( ϕ t , a ; θ ) ) 2 \left(y_j-Q\left(\phi_t,a;\theta\right)\right)^2 (yjQ(ϕt,a;θ))2作为误差,利用梯度下降法更新 Q Q Q函数的参数 θ \theta θ。在这里我们代码实现如下:

def create_updating_method(self):
    # this the input action, use one hot presentation
    self.action_input = tf.placeholder("float", [None, self.action_dim])
    # this the TD aim value
    self.y_input = tf.placeholder("float", [None])
    # this the action's Q_value
    Q_action = tf.reduce_sum(tf.multiply(self.Q_value, self.action_input), reduction_indices=1)
    # this is the lost
    self.cost = tf.reduce_mean(tf.square(self.y_input - Q_action))
    # use the loss to optimize the network
    self.optimizer = tf.train.AdamOptimizer(0.0001).minimize(self.cost)

上面代码中:

  • self.action_input:就是我们网络输出的行为值
  • self.y_input:即上面的TD目标值 y j y_j yj
  • Q_action:即为这个行为对应的状态行为值 Q ( ϕ t , a ; θ ) Q\left(\phi_t,a;\theta\right) Q(ϕt,a;θ)
  • self.cost:即为误差 ( y j − Q ( ϕ t , a ; θ ) ) 2 \left(y_j-Q\left(\phi_t,a;\theta\right)\right)^2 (yjQ(ϕt,a;θ))2
  • self.optimizer:即为利用梯度下降法更新 Q Q Q函数的参数 θ \theta θ

(四)Agent行为生成(即利用网络输出行为)

在DQN算法中,我们使用current_net,根据 ε − g r e e d y \varepsilon-greedy εgreedy方法,在状态state基础上,输出行为,代码如下所示:

def Choose_Action(self, state):
    # the output is a tensor, so the [0] is to get the output as a list
    Q_value = self.Q_value.eval(feed_dict={
        self.state_input: [state]
    })[0]
    # use epsilon greedy to get the action
    if random.random() <= self.epsilon:
        # if lower than epsilon, give a random value
        self.epsilon -= (INITIAL_EPSILON - FINAL_EPSILON) / 10000
        return random.randint(0, self.action_dim - 1)
    else:
        # if bigger than epsilon, give the argmax value
        self.epsilon -= (INITIAL_EPSILON - FINAL_EPSILON) / 10000
        return np.argmax(Q_value)

上面代码中,首先通过self.Q_value得到输出,这里需要注意的是self.Q_value的输出是一个tensor,因此,这里使用[0]来获取其输出的list。然后根据随机值进行判断,如果小于 ε \varepsilon ε,那就随机一个行为作为输出,否则就是用argmax来输出Q_value最大值对应的索引,作为输出。

(五)存储数据

DQN算法中,为了实现更新,要初始化存储单元 D D D,存储大小为 N N N,并将每一步生成的 { ϕ t , a t , r t , ϕ t + 1 } \left\{\phi_t,a_t,r_t,\phi_{t+1}\right\} {ϕt,at,rt,ϕt+1}存入 D D D中,具体代码如下:

def Store_Data(self, state, action, reward, next_state, done):
    # generate a list with all 0,and set the action is 1
    one_hot_action = np.zeros(self.action_dim)
    one_hot_action[action] = 1
    # store all the elements
    self.replay_buffer.append((state, one_hot_action, reward, next_state, done))
    # if the length of replay_buffer is bigger than REPLAY_SIZE
    # delete the left value, make the len is stable
    if len(self.replay_buffer) > REPLAY_SIZE:
        self.replay_buffer.popleft()

上面的self.replay_buffer就是我们的存储单元 D D D,它是一个deque的对象,我们可以将其看作一个列表,存储方式是“先进先出”,如果大于我们设置的 N N N(即REPLAY_SIZE),则从左边插入新数据。

(六)训练网络更新参数

我们在前面设定网络参数更新方法中,已经将更新需要进行的计算公式进行了定义,那么在具体的计算中,就需要我们将数据流(flow)代入公式即可。这种使用flow的计算方式,也是我们使用的tensorflow的独特之处。具体代码如下:

def Train_Network(self, BATCH_SIZE):
    # Step 1: obtain random minibatch from replay memory
    minibatch = random.sample(self.replay_buffer, BATCH_SIZE)
    state_batch = [data[0] for data in minibatch]
    action_batch = [data[1] for data in minibatch]
    reward_batch = [data[2] for data in minibatch]
    next_state_batch = [data[3] for data in minibatch]

    # Step 2: calculate TD aim value
    y_batch = []
    # give the next_state_batch flow to target_Q_value and caculate the next state's Q_value
    Q_value_batch = self.target_Q_value.eval(feed_dict={self.state_input: next_state_batch})
    # caculate the TD aim value by the formulate
    for i in range(0, BATCH_SIZE):
        done = minibatch[i][4]
        if done:
            y_batch.append(reward_batch[i])
        else:
            # the Q value caculate use the max directly
            y_batch.append(reward_batch[i] + GAMMA * np.max(Q_value_batch[i]))

    # step 3: update the network
    self.optimizer.run(feed_dict={
        self.y_input: y_batch,
        self.action_input: action_batch,
        self.state_input: state_batch
    })

上面的训练网络主要分为三步:

  • 一是从存储单元 D D D中随机抽取BATCH_SIZE大小的数据;
  • 二是根据公式 r j + γ ⋅ m a x a ′ Q ^ ( ϕ t + 1 , a ′ ; θ − ) r_j+\gamma\cdot max_{a'}\widehat Q\left(\phi_{t+1},a';\theta^-\right) rj+γmaxaQ (ϕt+1,a;θ)进行TD目标值 y j y_j yj的计算;
  • 三是将相关数据流导入优化操作中,自动进行梯度下降计算,以优化current_net的参数。

(七)更新目标网络参数

在DQN算法中,需要每隔C步,用current_net的参数替换target_net参数,具体代码如下:

def Update_Target_Network(self):
    # update target Q netowrk
    self.session.run(self.target_replace_op)

由于我们在建立网络时,就进行了self.target_replace_op操作的定义,在这里只需要调用相关操作即可。

总结

以上就是经典DQN算法的实现,具体的完整代码,参见:https://github.com/samurasun/RL。
本篇中的代码参考了刘建平相关博客和代码中的内容,在这里对刘老师表示感谢。

你可能感兴趣的:(强化学习笔记,强化学习,人工智能)