策略梯度(Policy Gradient,PG)强化学习方法的实现代码及代码解读

理论推导

基础&入门了解PG:https://www.cnblogs.com/pinard/p/10137696.html

策略梯度实例

这里给出REINFORCE 算法蒙特卡洛策略梯度算法)的一个实例。仍然使用了OpenAI Gym中的CartPole-v0游戏来作为我们算法应用。CartPole-v0游戏的介绍参见这里。它比较简单,基本要求就是控制下面的cart移动使连接在上面的pole保持垂直不倒。这个任务只有两个离散动作,要么向左用力,要么向右用力。而state状态就是这个cart的位置和速度, pole的角度和角速度,4维的特征。坚持到200分的奖励则为过关。

完整的代码参见github:https://github.com/ljpzzz/machinelearning/blob/master/reinforcement-learning/policy_gradient.py

这里我们采用softmax策略作为我们的策略函数,同时,softmax的前置部分,也就是我们的策略模型用一个三层的softmax神经网络来表示。这样好处就是梯度的更新可以交给神经网络来做。

整体代码

#######################################################################
# Copyright (C)                                                       #
# 2016 - 2019 Pinard Liu([email protected])                      #
# https://www.cnblogs.com/pinard                                      #
# Permission given to modify the code as long as you keep this        #
# declaration at the top                                              #
#######################################################################
## https://www.cnblogs.com/pinard/p/10137696.html ##
## 强化学习(十三) 策略梯度(Policy Gradient) ##

import gym
import tensorflow as tf
import numpy as np
import random
from collections import deque

# Hyper Parameters
GAMMA = 0.95 # discount factor
LEARNING_RATE=0.01

class Policy_Gradient():
    def __init__(self, env):
        # init some parameters
        self.time_step = 0
        self.state_dim = env.observation_space.shape[0]
        self.action_dim = env.action_space.n
        self.ep_obs, self.ep_as, self.ep_rs = [], [], []
        self.create_softmax_network()

        # Init session
        self.session = tf.InteractiveSession()
        self.session.run(tf.global_variables_initializer())

    def create_softmax_network(self):
        # network weights
        W1 = self.weight_variable([self.state_dim, 20])
        b1 = self.bias_variable([20])
        W2 = self.weight_variable([20, self.action_dim])
        b2 = self.bias_variable([self.action_dim])
        # input layer
        self.state_input = tf.placeholder("float", [None, self.state_dim])
        self.tf_acts = tf.placeholder(tf.int32, [None, ], name="actions_num")
        self.tf_vt = tf.placeholder(tf.float32, [None, ], name="actions_value")
        # hidden layers
        h_layer = tf.nn.relu(tf.matmul(self.state_input, W1) + b1)
        # softmax layer
        self.softmax_input = tf.matmul(h_layer, W2) + b2
        #softmax output
        self.all_act_prob = tf.nn.softmax(self.softmax_input, name='act_prob')
        self.neg_log_prob = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=self.softmax_input,
                                                                      labels=self.tf_acts)
        self.loss = tf.reduce_mean(self.neg_log_prob * self.tf_vt)  # reward guided loss

        self.train_op = tf.train.AdamOptimizer(LEARNING_RATE).minimize(self.loss)

    def weight_variable(self, shape):
        initial = tf.truncated_normal(shape)
        return tf.Variable(initial)

    def bias_variable(self, shape):
        initial = tf.constant(0.01, shape=shape)
        return tf.Variable(initial)

    def choose_action(self, observation):
        prob_weights = self.session.run(self.all_act_prob, feed_dict={self.state_input: observation[np.newaxis, :]})
        action = np.random.choice(range(prob_weights.shape[1]), p=prob_weights.ravel())  # select action w.r.t the actions prob
        return action

    def store_transition(self, s, a, r):
        self.ep_obs.append(s)
        self.ep_as.append(a)
        self.ep_rs.append(r)

    def learn(self):

        discounted_ep_rs = np.zeros_like(self.ep_rs)
        running_add = 0
        for t in reversed(range(0, len(self.ep_rs))):
            running_add = running_add * GAMMA + self.ep_rs[t]
            discounted_ep_rs[t] = running_add

        discounted_ep_rs -= np.mean(discounted_ep_rs)
        discounted_ep_rs /= np.std(discounted_ep_rs)

        # train on episode
        self.session.run(self.train_op, feed_dict={
             self.state_input: np.vstack(self.ep_obs),
             self.tf_acts: np.array(self.ep_as),
             self.tf_vt: discounted_ep_rs,
        })

        self.ep_obs, self.ep_as, self.ep_rs = [], [], []    # empty episode data
# Hyper Parameters
ENV_NAME = 'CartPole-v0'
EPISODE = 3000 # Episode limitation
STEP = 3000 # Step limitation in an episode
TEST = 10 # The number of experiment test every 100 episode

def main():
  # initialize OpenAI Gym env and dqn agent
  env = gym.make(ENV_NAME)
  agent = Policy_Gradient(env)

  for episode in range(EPISODE):
    # initialize task
    state = env.reset()
    # Train
    for step in range(STEP):
      action = agent.choose_action(state) # e-greedy action for train
      next_state,reward,done,_ = env.step(action)
      agent.store_transition(state, action, reward)
      state = next_state
      if done:
        #print("stick for ",step, " steps")
        agent.learn()
        break

    # Test every 100 episodes
    if episode % 100 == 0:
      total_reward = 0
      for i in range(TEST):
        state = env.reset()
        for j in range(STEP):
          env.render()
          action = agent.choose_action(state) # direct action for test
          state,reward,done,_ = env.step(action)
          total_reward += reward
          if done:
            break
      ave_reward = total_reward/TEST
      print ('episode: ',episode,'Evaluation Average Reward:',ave_reward)

if __name__ == '__main__':
  main()

 

代码解读及相关问题释疑

发现作者的实现思路基本是,先根据网络结果选动作,进行蒙特卡洛采样以获得一个完整的episode,即具有序列时间步的(s,a,r);利用(s,a)作为训练数据,训练三层的神经网络,神经网络的最后一层是softmax函数。损失函数设置为交叉熵损失(s状态下真实动作a和网络预测动作a的差距)和价值函数(在一个episode中,每个时间步状态的值函数)的乘积。由于tensorflow内部自行实现了梯度下降,故我们不再考虑。强调一下,作者用状态值函数近似地代替了动作值函数。

  • 理解tf.nn.sparse_softmax_cross_entropy_with_logits函数的计算过程。简单说,就是先对网络输出通过softmax即类别的概率分布,接着根据分类维度将正确标签转换为one-hot向量,最后计算交叉熵。
  • 理解tf.reduce_mean(self.neg_log_prob * self.tf_vt) : 比如一个episode有100个state-action对(100个样本),action有两种,那么网络输出就是[100,2]的张量,对应[batch_size, num_class],通过交叉熵,得到正确标签的损失[100,1],将提前算好的价值函数v(一个状态对应一个价值函数),价值函数也是[100,1]的,对应位置相乘,再求各个样本的loss总和,之后求平均(也就是将100个样本看作是一个batch,一次性送入网络求其平均loss,不再关注当前样本是位于该batch的第几个),结果看作该episode最终的loss值。
  • 第三部分:策略梯度目标函数的设计,明明提到了优化目标是求期望,为什么在描述算法的时候,没有任何体现期望的地方?这是因为,代码中,每个episode生成后,都要根据产生的state-action对更新网络。之前的期望是对于所有采样的episode而言的,现在是一个episode,也就可以把期望符号给去掉了。这个地方同时可以参看PG,AC,A3C原理介绍。 该博文是介绍的将期望转换为求平均,由于我们一次一更新,因此也就把取平均这一步给去掉了。
  • 第三部分:策略梯度目标函数的设计,分析得出需要最大化获得的奖励,包括第五部分算法实现伪代码也是梯度上升算法,为何到了github中,变成了最小化损失函数?这里的策略π,用的是最后一层为softmax的三层神经网络。代码中用到tf.nn.sparse_softmax_cross_entropy_with_logits函数。该函数先计算当前state下的action的概率分布,随后使用交叉熵计算loss损失。从交叉熵的定义来看,恰好包含了log函数的计算,也就是说,该函数完成了计算logπ的功能。并且,交叉熵损失本身带负号,因此现在的问题变成了求最小,即梯度下降问题。
  • 第四部分列举了对策略π求导后的形式,而且都是先计算logπ的梯度,再与价值函数v相乘。代码中变成了,先与v相乘,再求梯度。这样是一致的吗?可以看出,v我们是单独计算的,与网络参数无关,因此是先乘v再对网络参数求梯度,还是说先求梯度再乘以v,都是等价的。

 

 

 

你可能感兴趣的:(深度学习,强化学习)