策略梯度(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,都是等價的。

 

 

 

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