從零實踐強化學習之連續動作空間上求解RL(PARL)

回顧這五節課的內容,其實可以分成四大內容:

最後一節課的主要內容就是學習用強化學習來求解連續狀態空間的問題

連續動作空間

連續動作和離散動作是一個相對的概念,通過回顧離散動作來學習什麼是連續動作

連續動作 VS 離散動作

前面幾節課接觸到的,比如倒立擺、小烏龜還有雅達利的乒乓球,動作的步長都已經提前給定的,比如按一下就走一個單位長度
在這裏插入圖片描述
但是現實生活中,比如開車時方向盤的角度,或者是無人機的電壓都是連續的,也就是說,輸出的動作是不可數的,就好像倒立擺可以左右走,當然也可以設置一個浮點數,正數和負數就決定了小車往哪走,走多少

換句話說,要想更好地控制他們,那麼就要對精度有更高的要求,從整型變爲浮點型,這樣,可調節的範圍就變多了

對於這些連續動作空間,前面講的算法都無法處理,這時,"萬能"的神經網絡可以解決這一問題:
在這裏插入圖片描述

  • 對於前面的隨機性策略,輸入一個s,網絡會輸出在這個狀態下做出某個動作的概率
  • 但對於確定性策略來說,網絡的參數確定下來後,同樣的狀態必然輸出同樣的動作

具體來看的話,其實就是最後一層全連接層的激活函數不同:
在這裏插入圖片描述

  • softmax的輸出範圍是[0,1],所以它可以直接輸出概率,並且可以確保所有動作概率的和爲1
  • tanh的輸出範圍是[-1,1],它直接輸出每個action的值,然後再根據實際,縮放到合適的範圍內

在連續控制領域,比較經典的算法是DDPG

DDPG

DDPG的全稱是Deep Deterministic Policy Gradient

實際上,DDPG的特點也可以從它的名字裏拆解出來:
在這裏插入圖片描述

  • Policy Gradient
    上節課裏的更新方法是每個episode更新一次;和上節課有所不同的是這裏的更新頻率更高,DDPG的網絡裏,每個step都會更新一次,也就是說,它是單步更新的policy網絡

  • Deterministic
    Deterministic翻譯過來是確定性的意思,也就是說,DDPG能直接輸出確定的動作,可以用於連續動作的環境

  • Deep
    前面加了一個Deep,是因爲DDPG用了神經網絡,它借鑑了DQN的技巧,使用目標網絡+經驗回放

從DQN到DDPG

當初提出DDPG其實是爲了讓DQN可以擴展到連續控制動作空間

在這裏插入圖片描述

所以DDPG直接在DQN的基礎上加了一個策略網絡,用來直接輸出動作值,DDPG實際上一邊學習Q網絡(用w表示),一邊學習策略網絡(用θ表示)

這樣的結構稱爲Actor-Critic結構(評論家-演員)

Actor-Critic結構

在這裏插入圖片描述

  • 策略網絡扮演的是演員的角色,她負責對外展示(輸出動作)
  • Q網絡是評論家,他會在每個step都會演員的動作評估打分,估計一下這次的action在未來能有多少總收益,也就是演員的Q值

演員需要根據評委的打分調整動作;而評委也需要根據觀衆的反映以及演員的動作來調整自己的打分

所以策略網絡要做的是更新網絡的參數θ,爭取下次做的更好
而Q網絡要做的就是根據環境的反饋reward來調整網絡的參數w

在這樣的場景下,其實策略網絡和Q網絡在一開始的時候都是隨機的,隨機輸出動作,隨機打分,但是由於有環境反饋的reward的存在,所以Q網絡的評分會越來越準確,他也會帶着策略網絡,使它的輸出越來越好

既然是神經網絡,就要做參數更新,更新的方法跟DQN類似:
在這裏插入圖片描述
在計算Loss時,大的方向其實沒變,只不過變成了一個複合函數:
在這裏插入圖片描述
原來的動作action是直接獲取的,現在是神經網絡的輸出

目標網絡

因爲Q_target是不穩定的,所以爲了穩定Q_target,DDPG分別給策略網絡和Q網絡都搭建了一個target_network,專門用來穩定Q_target:
在這裏插入圖片描述
這就是在看DDPG的文章時,會有四個網絡的原因

另外.經驗回放的方法跟DQN是一樣的

用PARL實現DDPG

思路其實也很明顯了,也是3大部分:
在這裏插入圖片描述
最核心的其實就是這四個函數:
在這裏插入圖片描述

Model

Model裏實現了3個類,但是調用時只調用Model就可以了

Q網絡的結構放在了CriticModel裏:

class CriticModel(parl.Model):
    def __init__(self):
        hid_size = 100

        self.fc1 = layers.fc(size=hid_size, act='relu')
        self.fc2 = layers.fc(size=1, act=None)

    def value(self, obs, act):
        concat = layers.concat([obs, act], axis=1)
        hid = self.fc1(concat)
        Q = self.fc2(hid)
        Q = layers.squeeze(Q, axes=[1])
        return Q

actor的網絡結構放在了ActorModel裏:

class ActorModel(parl.Model):
    def __init__(self, act_dim):
        hid_size = 100

        self.fc1 = layers.fc(size=hid_size, act='relu')
        self.fc2 = layers.fc(size=act_dim, act='tanh')

    def policy(self, obs):
        hid = self.fc1(obs)
        means = self.fc2(hid)
        return means

value()和police()方法都在Model裏包了一層,所以直接調用Model就可以了:

class Model(parl.Model):
    def __init__(self, act_dim):
        self.actor_model = ActorModel(act_dim)
        self.critic_model = CriticModel()

    def policy(self, obs):
        return self.actor_model.policy(obs)

    def value(self, obs, act):
        return self.critic_model.value(obs, act)

    def get_actor_params(self):
        return self.actor_model.parameters()

另外,這個Model類還實現了一個方法get_actor_params(),它用來獲取actor所有參數的名稱,這個方法在PARL底層是已經實現好了的,直接用就行

至於爲什麼要用這個方法,獲取參數的名稱,這主要是在更新網絡參數時會用到,詳細的後面會說到

Algorithm

下面再來看看Algorithm裏的learn()函數

Critic網絡(Q網絡)更新

計算Q網絡的Loss函數是符合函數,在_critic_learn()這個方法裏定義:
在這裏插入圖片描述
首先是把經驗池輸入進去,計算下一個動作,然後拿到策略網絡的Q值

def _critic_learn(self, obs, action, reward, next_obs, terminal):
    next_action = self.target_model.policy(next_obs)
    next_Q = self.target_model.value(next_obs, next_action)

    terminal = layers.cast(terminal, dtype='float32')
    target_Q = reward + (1.0 - terminal) * self.gamma * next_Q
    target_Q.stop_gradient = True # 阻止梯度回傳

    Q = self.model.value(obs, action)
    cost = layers.square_error_cost(Q, target_Q)
    cost = layers.reduce_mean(cost)
    optimizer = fluid.optimizer.AdamOptimizer(self.critic_lr)
    optimizer.minimize(cost)
    return cost

Actor網絡( 策略網絡)更新

它的計算過程相對於前面來說,比較簡單
在這裏插入圖片描述
這裏要注意的是,和這個Loss有關的是兩個網絡的參數,這裏要更新的,只是策略網絡的參數,因此,前面提到的get_actor_params()就非常有用了

在optimizer.minimize()裏可以指定要更新的參數,它會自動地篩選,選擇對應的參數來更新

def _actor_learn(self, obs):
    action = self.model.policy(obs)
    Q = self.model.value(obs, action)
    cost = layers.reduce_mean(-1.0 * Q)
    optimizer = fluid.optimizer.AdamOptimizer(self.actor_lr)
    optimizer.minimize(cost, parameter_list=self.model.get_actor_params())
    return cost

Target network參數軟更新

在DQN裏,採用的是硬更新的方式,每隔一段時間,就直接把網絡複製過來
在這裏插入圖片描述
而DDPG採用的是軟更新,以非常平滑地方式來更新參數,每次只更新一點點,用一個參數來控制調整的幅度,每次只把參數的一部分拷貝進去

主要的目的就是不讓target網絡"大換血"

連續動作空間版本的CartPole

這裏的例子還是倒立擺:
在這裏插入圖片描述
代碼在PARL的lesson5下:
在這裏插入圖片描述

因爲在正式訓練前,它會隨機地大膽嘗試,不斷地試錯,這一部分需要等待一段時間:
在這裏插入圖片描述

在200多個episode之後,就有比較好的效果了,收斂的速度比DQN要快得多

下面是DDPG的效果:
在這裏插入圖片描述

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