第十一章 策略梯度(Policy Gradient)-強化學習理論學習與代碼實現(強化學習導論第二版)

在這裏插入圖片描述
獲取更多資訊,趕快關注上面的公衆號吧!

【強化學習系列】

“前言”

在前面講到的 DQN 系列強化學習算法中,我們主要對價值函數進行了近似表示,基於價值來學習策略。這種基於價值的強化學習方法在很多領域都得到比較好的應用,但是也有很多侷限性,因此在另一些場景下需要藉助其他的方法。

從本片博文開始,我們將進入基於策略的強化學習,該類方法直接對策略進行參數化,從而顯式地將策略表示出來,策略的獲取不再和值函數有關,並根據期望報酬相對於策略參數的梯度進行更新。

11.1 強化學習分類

強化學習按照學習方式的不同可以分爲:基於值函數的強化學習、基於策略的強化學習和兩者結合的 Actor-Critic 方法。

  • 基於值函數的強化學習:通過學習值函數,隱式地獲取策略(如 ϵ\epsilon-貪婪策略);
  • 基於策略地強化學習:不需要學習值函數,而是直接通過對策略參數化進行學習,因而是顯式的;
  • Actor-Critic:既學習值函數又學習策略,集衆家之所長。

圖1 強化學習分類

11.2 基於值函數的強化學習方法的不足

DQN 系列強化學習算法主要的問題主要有三點。

  1. 連續動作的處理能力不足。DQN 之類的方法一般都是隻處理離散動作,無法處理連續動作,像開車這樣的環境中,動作往往是油門大小和方向盤角度,這兩個值都是一定範圍內的實數,有無數種選擇,以 DQN 爲代表的基於值函數的方法,需要通過最大化操作確定動作,最大化本質上是一個優化過程,那麼強化學習沒學習一步都要進行這樣的優化勢必導致學習過程相當緩慢,但是基於策略的強化學習方法卻很容易建模。
  2. 受限狀態下的問題處理能力不足。在使用特徵來描述狀態空間中的某一個狀態時,有可能因爲個體觀測的限制或者建模的侷限,導致真實環境下本來不同的兩個狀態卻在我們建模後擁有相同的特徵描述,進而很有可能導致我們的基於值函數的方法無法得到最優解。此時使用基於策略的強化學習方法也很有效。
  3. 無法解決隨機策略問題。基於值函數的強化學習方法對應的最優策略通常是確定性策略,因爲其是從衆多動作價值中選擇一個最大價值的動作,而有些問題的最優策略卻是隨機策略,這種情況下同樣是無法通過基於值函數的學習來求解的。這時也可以考慮使用基於策略的強化學習方法。

由於上面這些原因,基於值函數的強化學習方法不能通喫所有的場景,我們需要新的解決上述類別問題的方法,這就是今天要講的基於策略的強化學習方法。

11.3 基於策略的強化學習表示

在之前的部分我們都是使用參數 θ\theta 來近似值函數或動作值函數的:

Vθ(s)Vπ(s)Qθ(s,a)Qπ(s,a)\begin{aligned} V_{\theta}(s) & \approx V^{\pi}(s) \\ Q_{\theta}(s, a) & \approx Q^{\pi}(s, a) \end{aligned}
對於基於策略的方法我們採用了同樣的思路,不同的是現在直接對策略進行參數化:
πθ(s,a)=P(as,θ)π(as)\pi_{\theta}(s,a) = P(a|s,\theta)\approx \pi(a|s)

將策略表示成一個連續的函數後,我們就可以用連續函數的優化方法來尋找最優的策略了。而最常用的方法就是梯度上升法了,那麼這個梯度對應的優化目標如何定義呢?

11.4 策略目標函數

給定參數爲 θ\theta 的策略 πθ(s,a)\pi_{\theta}(s, a),目的是要找到最優的 θ\theta,但是如何評價一個策略 πθ\pi_{\theta} 的好壞呢?
在片段環境中我們可以使用初始值作爲優化目標,即:
J1(θ)=Vπθ(s1)=Eπθ[v1]J_{1}(\theta)=V^{\pi_{\theta}}\left(s_{1}\right)=\mathbb{E}_{\pi_{\theta}}\left[v_{1}\right]
但是在連續環境中是沒有明確的初始狀態的,那麼我們的優化目標可以定義平均價值,即:
JavV(θ)=sdπθ(s)Vπθ(s)J_{a v V}(\theta)=\sum_{s} d^{\pi_{\theta}}(s) V^{\pi_{\theta}}(s)
其中 dπθ(s)d^{\pi_{\theta}}(s) 是基於策略 πθ\pi_{\theta} 生成的馬爾科夫鏈關於狀態的靜態分佈。
或者定義爲每一時間步的平均獎勵,即:
JavR(θ)=sdπθ(s)aπθ(s,a)RsaJ_{\operatorname{avR}}(\theta)=\sum_{s} d^{\pi_{\theta}}(s) \sum_{a} \pi_{\theta}(s, a) \mathcal{R}_{s}^{a}
無論我們是採用 J1J_{1},JavVJ_{a v V} 還是 JavRJ_{\operatorname{avR}} 來表示優化目標,最終對 θ\theta 求導的梯度都可以表示爲:
θJ(θ)=Eπθ[θlogπθ(s,a)Qπθ(s,a)]\nabla_{\theta} J(\theta)=\mathbb{E}_{\pi_{\theta}}\left[\nabla_{\theta} \log \pi_{\theta}(s, a) Q^{\pi_{\theta}}(s, a)\right]
這就是策略梯度理論

當然我們還可以採用很多其他可能的優化目標來做梯度上升,此時我們的梯度式子裏面的 θlogπθ(s,a)\nabla_{\theta}log \pi_{\theta}(s,a) 部分並不改變,變化的只是後面的 Qπ(s,a)]Q_{\pi}(s,a)] 部分。對於 θlogπθ(s,a)\nabla_{\theta}log \pi_{\theta}(s,a),我們一般稱爲得分函數(score function)。

現在梯度的式子已經有了,後面剩下的就是策略函數 πθ(s,a)\pi_{\theta}(s,a) 的設計了。

11.5 策略函數的設計

現在我們回頭看一下策略函數 πθ(s,a)\pi_{\theta}(s,a) 的設計,在前面它一直是一個數學符號。

最常用的策略函數就是softmax策略函數了,它主要應用於離散空間中,softmax 策略使用描述狀態和動作的特徵 ϕ(s,a)\phi(s,a) 與參數 θ\theta 的線性組合來權衡一個動作發生的機率,即:
πθ(s,a)=eϕ(s,a)Tθbeϕ(s,b)Tθ\pi_{\theta}(s,a) = \frac{e^{\phi(s,a)^T\theta}}{\sum\limits_be^{\phi(s,b)^T\theta}}
則通過求導很容易求出對應的得分函數爲:
θlogπθ(s,a)=ϕ(s,a)Eπθ[ϕ(s,.)]\nabla_{\theta}log \pi_{\theta}(s,a) = \phi(s,a) - \mathbb{E}_{\pi_{\theta}}[\phi(s,.)]
在連續動作空間中常用的策略爲高斯策略,該策略對應的動作從高斯分佈 N(ϕ(s)Tθ,σ2)\mathbb{N(\phi(s)^T\theta, \sigma^2)} 中產生。高斯策略對應的得分函數求導可以得到爲:
θlogπθ(s,a)=(aϕ(s)Tθ)ϕ(s)σ2\nabla_{\theta}log \pi_{\theta}(s,a) = \frac{(a-\phi(s)^T\theta)\phi(s)}{\sigma^2}

11.6 Monte-Carlo 策略梯度(REINFORCE)

這裏我們討論最簡單的策略梯度算法,蒙特卡羅策略梯度 reinforce 算法, 使用價值函數 v(s)v(s) 來近似代替策略梯度公式裏面的 Qπ(s,a)Q_{\pi}(s,a)。算法的流程很簡單,如下所示:

11.7 代碼實現 REINFORCE

代碼針對的環境的是 CliffWalkingEnv,在該環境中智能體在一個 4x12 的網格中移動,狀態編號如下所示:

[[ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11],
 [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
 [24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35],
 [36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47]]

在任何階段開始時,初始狀態都是狀態 36,狀態 47 是唯一的終止狀態,懸崖對應的是狀態 37 到 46。智能體有 4 個可選動作(UP = 0,RIGHT = 1,DOWN = 2,LEFT = 3)。智能體每走一步都會得到-1 的獎勵,跌入懸崖會得到-100 的獎勵並重置到起點,當達到目標時,片段結束。

完整的代碼如下:

import gym
import itertools
import matplotlib
import numpy as np
import sys
import tensorflow as tf
import collections

if "../" not in sys.path:
  sys.path.append("../")
from Lib.envs.cliff_walking import CliffWalkingEnv
from Lib import plotting

matplotlib.style.use('ggplot')

env = CliffWalkingEnv()


class PolicyEstimator():
    """
    策略函數逼近器
    """

    def __init__(self, learning_rate=0.01, scope="policy_estimator"):
        with tf.variable_scope(scope):
            self.state = tf.placeholder(tf.int32, [], "state")
            self.action = tf.placeholder(dtype=tf.int32, name="action")
            self.target = tf.placeholder(dtype=tf.float32, name="target")

            # 使用狀態的獨熱形式表達
            state_one_hot = tf.one_hot(self.state, int(env.observation_space.n))
            self.output_layer = tf.contrib.layers.fully_connected(
                inputs=tf.expand_dims(state_one_hot, 0),
                num_outputs=env.action_space.n,
                activation_fn=None,
                weights_initializer=tf.zeros_initializer)

            self.action_probs = tf.squeeze(tf.nn.softmax(self.output_layer))
            self.picked_action_prob = tf.gather(self.action_probs, self.action)

            # 損失
            self.loss = -tf.log(self.picked_action_prob) * self.target

            self.optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
            self.train_op = self.optimizer.minimize(
                self.loss, global_step=tf.contrib.framework.get_global_step())

    def predict(self, state, sess=None):
        sess = sess or tf.get_default_session()
        return sess.run(self.action_probs, {self.state: state})

    def update(self, state, target, action, sess=None):
        sess = sess or tf.get_default_session()
        feed_dict = {self.state: state, self.target: target, self.action: action}
        _, loss = sess.run([self.train_op, self.loss], feed_dict)
        return loss


class ValueEstimator():
    """
    Value Function approximator.
    """

    def __init__(self, learning_rate=0.1, scope="value_estimator"):
        with tf.variable_scope(scope):
            self.state = tf.placeholder(tf.int32, [], "state")
            self.target = tf.placeholder(dtype=tf.float32, name="target")

            # This is just table lookup estimator
            state_one_hot = tf.one_hot(self.state, int(env.observation_space.n))
            self.output_layer = tf.contrib.layers.fully_connected(
                inputs=tf.expand_dims(state_one_hot, 0),
                num_outputs=1,
                activation_fn=None,
                weights_initializer=tf.zeros_initializer)

            self.value_estimate = tf.squeeze(self.output_layer)
            self.loss = tf.squared_difference(self.value_estimate, self.target)

            self.optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
            self.train_op = self.optimizer.minimize(
                self.loss, global_step=tf.contrib.framework.get_global_step())

    def predict(self, state, sess=None):
        sess = sess or tf.get_default_session()
        return sess.run(self.value_estimate, {self.state: state})

    def update(self, state, target, sess=None):
        sess = sess or tf.get_default_session()
        feed_dict = {self.state: state, self.target: target}
        _, loss = sess.run([self.train_op, self.loss], feed_dict)
        return loss


def reinforce(env, estimator_policy, estimator_value, num_episodes, discount_factor=1.0):
    """
    REINFORCE (Monte Carlo策略梯度)算法.使用策略梯度優化策略函數逼近器.

    參數:
        env: OpenAI環境.
        estimator_policy: 待優化的策略函數
        estimator_value: Value function approximator, used as a baseline
        num_episodes: 迭代次數
        discount_factor: 折扣因子

    返回值:
        EpisodeStats對象,包含兩個numpy數組,分別存儲片段長度和片段獎勵.
    """

    # 統計有用信息
    stats = plotting.EpisodeStats(
        episode_lengths=np.zeros(num_episodes),
        episode_rewards=np.zeros(num_episodes))

    Transition = collections.namedtuple("Transition", ["state", "action", "reward", "next_state", "done"])

    for i_episode in range(num_episodes):
        # 重置環境
        state = env.reset()

        episode = []

        # 環境中的每一步
        for t in itertools.count():

            # 單步執行
            action_probs = estimator_policy.predict(state)
            action = np.random.choice(np.arange(len(action_probs)), p=action_probs)
            next_state, reward, done, _ = env.step(action)

            # 記錄轉移
            episode.append(Transition(
                state=state, action=action, reward=reward, next_state=next_state, done=done))

            # 更新統計
            stats.episode_rewards[i_episode] += reward
            stats.episode_lengths[i_episode] = t

            print("\rStep {} @ Episode {}/{} ({})".format(
                t, i_episode + 1, num_episodes, stats.episode_rewards[i_episode - 1]), end="")
            # sys.stdout.flush()

            if done:
                break

            state = next_state

        # 遍歷每個片段並進行策略更新
        for t, transition in enumerate(episode):
            # The return after this timestep
            total_return = sum(discount_factor ** i * t.reward for i, t in enumerate(episode[t:]))
            # Calculate baseline/advantage
            # baseline_value = estimator_value.predict(transition.state)
            # advantage = total_return - baseline_value
            # Update our value estimator
            # estimator_value.update(transition.state, total_return)
            # 更新策略逼近器
            estimator_policy.update(transition.state, total_return, transition.action)

    return stats

tf.reset_default_graph()

global_step = tf.Variable(0, name="global_step", trainable=False)
policy_estimator = PolicyEstimator()
value_estimator = ValueEstimator()

with tf.Session() as sess:
    sess.run(tf.initialize_all_variables())
    # 注意,由於策略存在隨機性,爲了學習好的策略,一般片段數量爲2000~5000
    stats = reinforce(env, policy_estimator, value_estimator, 2000, discount_factor=1.0)

plotting.plot_episode_stats(stats, smoothing_window=25)

在這裏插入圖片描述
在這裏插入圖片描述

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