使用Keras和DDPG玩賽車游戲(自動(dòng)駕駛)

Using Keras and Deep Deterministic Policy Gradient to play TORCS——300行python代碼展示DDPG(基于Keras)——視頻
可以先看新手向——使用Keras+卷積神經(jīng)網(wǎng)絡(luò)玩小鳥(niǎo)

為什么選擇TORCS游戲

  • 《The Open Racing Car Simulator》(TORCS)是一款開(kāi)源3D賽車模擬游戲
  • 看著AI學(xué)會(huì)開(kāi)車是一件很酷的事
  • 可視化并考察神經(jīng)網(wǎng)絡(luò)的學(xué)習(xí)過(guò)程,而不是僅僅看最終結(jié)果
  • 容易看出神經(jīng)網(wǎng)絡(luò)陷入局部最優(yōu)
  • 幫助理解自動(dòng)駕駛中的機(jī)器學(xué)習(xí)技術(shù)

安裝運(yùn)行

sudo apt-get install xautomation
sudo pip3 install numpy
sudo pip3 install gym
  • 再下載gym_torcs源碼(建議迅雷+download zip,比較快),解壓壓縮包。
  • 然后將gym_torcs/vtorcs-RL-color/src/modules/simu/simuv2/simu.cpp 中第64行替換為if (isnan((float)(car->ctrl->gear)) || isinf(((float)(car->ctrl->gear)))) car->ctrl->gear = 0;,否則新的gcc會(huì)報(bào)錯(cuò),Ubuntu14可能不用管。
    代碼修改
  • 然后cd進(jìn)gym_torcsvtorcs-RL-color目錄,執(zhí)行以下命令:
sudo apt-get install libglib2.0-dev  libgl1-mesa-dev libglu1-mesa-dev  freeglut3-dev  libplib-dev  libopenal-dev libalut-dev libxi-dev libxmu-dev libxrender-dev  libxrandr-dev libpng12-dev 
./configure
make
sudo make install
sudo make datainstall
  • 檢查TORCS是否正確安裝:打開(kāi)一個(gè)終端,輸入命令torcs,然后會(huì)出現(xiàn)圖形界面,然后依次點(diǎn)擊Race –> Practice –> New Race –> 會(huì)看到一個(gè)藍(lán)屏輸出信息“Initializing Driver scr_server1”。此時(shí)再打開(kāi)一個(gè)終端,輸入命令python3 snakeoil3_gym.py可以立刻看到一個(gè)演示,則安裝成功。
  • 然后
git clone https://github.com/yanpanlau/DDPG-Keras-Torcs.git #建議下載zip
cd DDPG-Keras-Torcs
cp *.* ../gym_torcs
cd ../gym_torcs
python3 ddpg.py 

作者使用的是python2,所以他將snakeoil3_gym.py文件做了一些修改。我用的是python3,還需要將snakeoil3_gym.py文件再改回來(lái),應(yīng)該是在上面cp命令中不要復(fù)制覆蓋snakeoil3_gym.py文件就對(duì)了。如果覆蓋了就將snakeoil3_gym.py文件中python2的一些語(yǔ)法改成python3的:如print要加個(gè)括號(hào),except要改成except socket.error as emsgunicode()改成str()。這樣就可以成功運(yùn)行了。

背景

  • 在上一篇譯文新手向——使用Keras+卷積神經(jīng)網(wǎng)絡(luò)玩小鳥(niǎo)中,展示了如何使用深度Q學(xué)習(xí)神經(jīng)網(wǎng)絡(luò)來(lái)玩耍FlapyBird。但是,深Q網(wǎng)絡(luò)的一個(gè)很大的局限性在于它的輸出(是所有動(dòng)作的Q值列表)是離散的,也就是對(duì)游戲的輸入動(dòng)作是離散的,而像在賽車游戲中的轉(zhuǎn)向動(dòng)作是一個(gè)連續(xù)的過(guò)程。一個(gè)顯而易見(jiàn)的使DQN適應(yīng)連續(xù)域的方法就是簡(jiǎn)單地將連續(xù)的動(dòng)作空間離散化。但是馬上我們就會(huì)遭遇‘維數(shù)災(zāi)難’問(wèn)題。比如說(shuō),如果你將轉(zhuǎn)盤從-90度到+90度的轉(zhuǎn)動(dòng)劃分為5度一格,然后將將從0km到300km的加速度每5km一劃分,你的輸出組合將是36種轉(zhuǎn)盤狀態(tài)乘以60種速度狀態(tài)等于2160種可能的組合。當(dāng)你想讓機(jī)器人進(jìn)行一些更為專業(yè)化的操作時(shí)情況會(huì)更糟,比如腦外科手術(shù)這樣需要精細(xì)的行為控制的操作,想要使用離散化來(lái)實(shí)現(xiàn)需要的操作精度就太naive了。
  • Google Deepmind 已經(jīng)設(shè)計(jì)了一種新的算法來(lái)解決這種連續(xù)動(dòng)作空間問(wèn)題,它將3種技術(shù)結(jié)合在一起構(gòu)成了Deep Deterministic Policy Gradients (DDPG)算法:
    1. Deterministic Policy-Gradient Algorithms 確定性策略梯度算法(對(duì)于非機(jī)器學(xué)習(xí)研究者來(lái)說(shuō)較難)
    2. Actor-Critic Methods 演員-評(píng)論家方法
    3. Deep Q-Network 深度Q學(xué)習(xí)神經(jīng)網(wǎng)絡(luò)

策略網(wǎng)絡(luò)

  • 首先,我們將要定義一個(gè)策略網(wǎng)絡(luò)來(lái)實(shí)現(xiàn)我們的AI-司機(jī)。這個(gè)網(wǎng)絡(luò)將接收游戲的狀態(tài)(例如,賽車的速度,賽車和賽道中軸之間的距離等)并且決定我們?cè)撟鍪裁矗ǚ较虮P向左打向右打,踩油門還是踩剎車)。它被叫做基于策略的強(qiáng)化學(xué)習(xí),因?yàn)槲覀冎苯訉⒉呗詤?shù)化:
\pi_\theta(s, a) = P [a | s, \theta]

這里,s是狀態(tài),a是行為/動(dòng)作,θ是策略網(wǎng)絡(luò)的模型參數(shù),π是常見(jiàn)的表示策略的符號(hào)。我們可以設(shè)想策略是我們行為的代理人,即一個(gè)從狀態(tài)到動(dòng)作的映射函數(shù)。

確定性VS隨機(jī)策略

  • 確定性策略: a=μ(s)
  • 隨機(jī)策略: π(a∣s)=P[a∣s]

    為什么在確定性策略之外我們還需要隨機(jī)策略呢?理解一個(gè)確定性政策是容易的。我看到一個(gè)特定的狀態(tài)輸入,然后我采取特定的動(dòng)作。但有時(shí)確定性策略不起作用,當(dāng)你面對(duì)的第一個(gè)狀態(tài)是個(gè)類似下面的白板時(shí):

    如果你還使用相同的確定性策略,你的網(wǎng)絡(luò)將總是把棋子放在一個(gè)“特別”的位置,這是一個(gè)非常不好的行為,它會(huì)使你的對(duì)手能夠預(yù)測(cè)你。在這種情況下,一個(gè)隨機(jī)策略比確定性策略更合適。

策略目標(biāo)函數(shù)

所以我們?cè)趺凑业?code>π_?θ??(s,a)呢?實(shí)際上,我們能夠使用增強(qiáng)技術(shù)來(lái)解決它。例如,假設(shè)AI正在努力學(xué)習(xí)如何左轉(zhuǎn)。在一開(kāi)始,AI可能根本就不會(huì)轉(zhuǎn)方向盤并撞上路邊,獲得一個(gè)負(fù)獎(jiǎng)勵(lì)(懲罰),所以神經(jīng)網(wǎng)絡(luò)將調(diào)整模型參數(shù)θ,避免下一次再撞上路邊。多次嘗試之后,它會(huì)發(fā)現(xiàn),“啊哈,如果我把方向盤往更左打一點(diǎn),我就不會(huì)這么早撞到路邊了”。用數(shù)學(xué)語(yǔ)言來(lái)說(shuō),這就是策略目標(biāo)函數(shù)。
未來(lái)的總獎(jiǎng)勵(lì)函數(shù)定義為從離散的時(shí)間t開(kāi)始的每一階段的獎(jiǎng)勵(lì)之和:
R_t = r_t + r_{t+1} + r_{t+2} ... + r_n


上面的函數(shù)其實(shí)是馬后炮函數(shù),因?yàn)槭虑榈目偑?jiǎng)勵(lì)在事情結(jié)束之前是不會(huì)確定的,說(shuō)不定有轉(zhuǎn)機(jī)呢(未來(lái)的動(dòng)作數(shù)一般是很多的,也可能是不確定的),所謂俗語(yǔ):"不到最后一刻絕不罷休"和"蓋棺定論"講得就是這個(gè)道理,而且復(fù)雜的世界中,同樣的決策它的結(jié)果也可能是不一樣的,總有人運(yùn)氣好,也有人運(yùn)氣差,"一個(gè)人的命運(yùn),不光要看個(gè)人的奮斗,還要考慮歷史的行程",也就是說(shuō)決策的結(jié)果可能還受一個(gè)不可掌控的未知參數(shù)影響。
所以,作為一種提供給當(dāng)前狀態(tài)做判斷的預(yù)期,我們構(gòu)造一個(gè)相對(duì)簡(jiǎn)單的函數(shù),既充分考慮又在一定程度上弱化未來(lái)的獎(jiǎng)勵(lì)(這個(gè)未來(lái)的獎(jiǎng)勵(lì)其實(shí)是基于經(jīng)驗(yàn)得到,也就是訓(xùn)練的意義所在),得到未來(lái)的總折扣獎(jiǎng)勵(lì)(貼現(xiàn)獎(jiǎng)勵(lì))函數(shù):
R_t = r_t + \gamma r_{t+1} + \gamma^{2} r_{t+2} ... + \gamma^{n-t} r_n——\gammaγ是折扣系數(shù),一般取在(0,1)區(qū)間中

一個(gè)直觀的策略目標(biāo)函數(shù)將是總折扣獎(jiǎng)勵(lì)的期望:
L(\theta) = E[r_1 + \gamma r_2 + \gamma^{2} r_3 + ... | \pi_\theta(s,a)],這里暫時(shí)取t為1,總獎(jiǎng)勵(lì)為R


L(\theta) = E_{x\sim p(x|\theta)}[R]

在這里,總獎(jiǎng)勵(lì)R的期望是在 由參數(shù)θ調(diào)整的某一概率分布p(x∣θ) 下計(jì)算的。

這時(shí),又要用到我們的Q函數(shù)了,先回想一下上一篇譯文的內(nèi)容。
由上文的未來(lái)總折扣獎(jiǎng)勵(lì)R_t可以看出它能表示為遞歸的形式:
R_t = r_t + \gamma * R_{t+1},將上文的R_t中的t代換為t+1代入此式即可驗(yàn)證


而我們的Q函數(shù)(在s狀態(tài)下選擇動(dòng)作a的最大貼現(xiàn)獎(jiǎng)勵(lì))是
Q(s_t, a_t) = max R_{t+1}

這里等式左邊的t和右邊的t+1可能看上去有些錯(cuò)位,因?yàn)樗前聪旅孢@個(gè)圖走的,不用太糾結(jié)。

但是接下來(lái)我們并沒(méi)有和Q-learning采取同樣的Q值更新策略,重點(diǎn)來(lái)了:
我們采用了SARSA —— State-Action-Reward-State-Action代表了狀態(tài)-動(dòng)作-獎(jiǎng)勵(lì)-狀態(tài)-動(dòng)作。在SARSA中,我們開(kāi)始于狀態(tài)1,執(zhí)行動(dòng)作1,然后得到獎(jiǎng)勵(lì)1,于是我們到了狀態(tài)2,在返回并更新在狀態(tài)1下執(zhí)行動(dòng)作1的Q值之前,我們又執(zhí)行了另一個(gè)動(dòng)作(動(dòng)作2)然后得到獎(jiǎng)勵(lì)2。相反,在Q-learning中,我們開(kāi)始于狀態(tài)1,執(zhí)行動(dòng)作1,然后得到獎(jiǎng)勵(lì)1,接著就是查看在狀態(tài)2中無(wú)論做出任一動(dòng)作的最大可能獎(jiǎng)勵(lì),并用這個(gè)值來(lái)更新?tīng)顟B(tài)1下執(zhí)行動(dòng)作1的Q值。所以不同的是未來(lái)獎(jiǎng)勵(lì)被發(fā)現(xiàn)的方式。在Q-learning中它只是在狀態(tài)2下最可能采取的最有利的動(dòng)作的最大預(yù)期值,而在SARSA中它就是實(shí)際執(zhí)行的動(dòng)作的獎(jiǎng)勵(lì)值。
這意味著SARSA考慮到了賽車(游戲代理)移動(dòng)的控制策略(由控制策略我們連續(xù)地執(zhí)行了兩步),并集成到它的動(dòng)作值的更新中,而Q-learning只是假設(shè)一個(gè)最優(yōu)策略被執(zhí)行。不考慮所謂的最優(yōu)而遵循一定的策略有時(shí)會(huì)是好事。
于是乎,在連續(xù)的情況下,我們使用了SARSA,Q值公式去掉了max,它還是遞歸的,只是去掉了'武斷'的max,而包含了控制策略,不過(guò)它并沒(méi)有在這個(gè)Q值公式里表現(xiàn)出來(lái),在更新公式的迭代中可以體現(xiàn)出來(lái):
Q(s_t, a_t) = R_{t+1}

Q值的更新公式從Q-learning的
Q-learning更新公式

變?yōu)?br>
SARSA更新公式

所以,接著我們可以寫出確定性策略a=μ(s)的梯度:
\frac{\partial L(\theta)}{\partial \theta} = E_{x\sim~p(x|\theta)}[\frac{\partial Q}{\partial \theta}]


然后應(yīng)用高數(shù)中的鏈?zhǔn)椒▌t:

它已經(jīng)被證明(Silver el at. 2014)是策略梯度,即只要你按照上述的梯度公式來(lái)更新你的模型參數(shù),你就會(huì)得到最大期望獎(jiǎng)勵(lì)。

補(bǔ)充

演員-評(píng)論家算法

演員-評(píng)論家算法本質(zhì)上是策略梯度算法和值函數(shù)方法的混合算法。策略函數(shù)被稱為演員,而價(jià)值函數(shù)被稱為評(píng)論家。本質(zhì)上,演員在當(dāng)前環(huán)境的給定狀態(tài)s下產(chǎn)生動(dòng)作a,而評(píng)論家產(chǎn)生一個(gè)信號(hào)來(lái)批評(píng)演員做出的動(dòng)作。這在人類世界中是相當(dāng)自然的,其中研究生(演員)做實(shí)際工作,導(dǎo)師(評(píng)論家)批評(píng)你的工作來(lái)讓你下一次做得更好:)。在我們的TORCS例子中,我們使用了SARSA作為我們的評(píng)論家模型,并使用策略梯度算法作為我們的演員模型。它們的關(guān)系如圖:

關(guān)系圖

回到之前的公式,我們將Q做近似代換,其中w是神經(jīng)網(wǎng)絡(luò)的權(quán)重。所以我們得到深度策略性梯度公式(DDPG):
\frac{\partial L(\theta)}{\partial \theta} = \frac{\partial Q(s,a,w)}{\partial a}\frac{\partial a}{\partial \theta}

其中策略參數(shù)θ可以通過(guò)隨機(jī)梯度上升來(lái)更新。
此外,還有我們的損失函數(shù),與SARSA的Q函數(shù)迭代更新公式一致:
Loss = [r + \gamma Q (s^{'},a^{'}) - Q(s,a)]^{2}

Q值用于估計(jì)當(dāng)前演員策略的值。
下圖是演員-評(píng)論家模型的結(jié)構(gòu)圖:


演員-評(píng)論家結(jié)構(gòu)圖

Keras代碼說(shuō)明

演員網(wǎng)絡(luò)

首先我們來(lái)看如何在Keras中構(gòu)建演員網(wǎng)絡(luò)。這里我們使用了2個(gè)隱藏層分別擁有300和600個(gè)隱藏單元。輸出包括3個(gè)連續(xù)的動(dòng)作。

  1. 轉(zhuǎn)方向盤。是一個(gè)單元的輸出層,使用tanh激活函數(shù)(輸出-1意味著最大右轉(zhuǎn),+1表示最大左轉(zhuǎn))
  2. 加速。是一個(gè)單元的輸出層,使用sigmoid激活函數(shù)(輸出0代表不加速,1表示全加速)。
  3. 剎車。是一個(gè)單元的輸出層,也使用sigmoid激活函數(shù)(輸出0表示不制動(dòng),1表示緊急制動(dòng))。
    def create_actor_network(self, state_size,action_dim):
        print("Now we build the model")
        S = Input(shape=[state_size])  
        h0 = Dense(HIDDEN1_UNITS, activation='relu')(S)
        h1 = Dense(HIDDEN2_UNITS, activation='relu')(h0)
        Steering = Dense(1,activation='tanh',init=lambda shape, name: normal(shape, scale=1e-4, name=name))(h1)   
        Acceleration = Dense(1,activation='sigmoid',init=lambda shape, name: normal(shape, scale=1e-4, name=name))(h1)   
        Brake = Dense(1,activation='sigmoid',init=lambda shape, name: normal(shape, scale=1e-4, name=name))(h1)   
        V = merge([Steering,Acceleration,Brake],mode='concat')          
        model = Model(input=S,output=V)
        print("We finished building the model")
        return model, model.trainable_weights, S

我們使用了一個(gè)Keras函數(shù)Merge來(lái)合并三個(gè)輸出層(concat參數(shù)是將待合并層輸出沿著最后一個(gè)維度進(jìn)行拼接),為什么我們不使用如下的傳統(tǒng)的定義方式呢:

V = Dense(3,activation='tanh')(h1) 

使用3個(gè)不同的Dense()函數(shù)允許每個(gè)連續(xù)動(dòng)作有不同的激活函數(shù),例如,對(duì)加速使用tanh激活函數(shù)的話是沒(méi)有意義的,tanh的輸出是[-1,1],而加速的范圍是[0,1]。
還要注意的是,在輸出層我們使用了μ = 0,σ = 1e-4的正態(tài)分布初始化來(lái)確保策略的初期輸出接近0。

評(píng)論家網(wǎng)絡(luò)

評(píng)論家網(wǎng)絡(luò)的構(gòu)造和上一篇的小鳥(niǎo)深Q網(wǎng)絡(luò)非常相似。唯一的區(qū)別是我們使用了2個(gè)300和600隱藏單元的隱藏層。此外,評(píng)論家網(wǎng)絡(luò)同時(shí)接受了狀態(tài)和動(dòng)作的輸入。根據(jù)DDPG的論文,動(dòng)作輸入直到網(wǎng)絡(luò)的第二個(gè)隱藏層才被使用。同樣我們使用了Merge函數(shù)來(lái)合并動(dòng)作和狀態(tài)的隱藏層。

    def create_critic_network(self, state_size,action_dim):
        print("Now we build the model")
        S = Input(shape=[state_size])
        A = Input(shape=[action_dim],name='action2')    
        w1 = Dense(HIDDEN1_UNITS, activation='relu')(S)
        a1 = Dense(HIDDEN2_UNITS, activation='linear')(A)
        h1 = Dense(HIDDEN2_UNITS, activation='linear')(w1)
        h2 = merge([h1,a1],mode='sum')    
        h3 = Dense(HIDDEN2_UNITS, activation='relu')(h2)
        V = Dense(action_dim,activation='linear')(h3)  
        model = Model(input=[S,A],output=V)
        adam = Adam(lr=self.LEARNING_RATE)
        model.compile(loss='mse', optimizer=adam)
        print("We finished building the model")
        return model, A, S 

目標(biāo)網(wǎng)絡(luò)

有一個(gè)眾所周知的事實(shí),在很多環(huán)境(包括TORCS)下,直接利用神經(jīng)網(wǎng)絡(luò)來(lái)實(shí)現(xiàn)Q值函數(shù)被證明是不穩(wěn)定的。Deepmind團(tuán)隊(duì)提出了該問(wèn)題的解決方法——使用一個(gè)目標(biāo)網(wǎng)絡(luò),在那里我們分別創(chuàng)建了演員和評(píng)論家網(wǎng)絡(luò)的副本,用來(lái)計(jì)算目標(biāo)值。這些目標(biāo)網(wǎng)絡(luò)的權(quán)重通過(guò) 讓它們自己慢慢跟蹤學(xué)習(xí)過(guò)的網(wǎng)絡(luò) 來(lái)更新:
\theta^{'} \leftarrow \tau \theta + (1 - \tau) \theta^{'}????


\tauτ << 1。這意味著目標(biāo)值被限制為慢慢地改變,大大地提高了學(xué)習(xí)的穩(wěn)定性。
在Keras中實(shí)現(xiàn)目標(biāo)網(wǎng)絡(luò)時(shí)非常簡(jiǎn)單的:

    def target_train(self):
        actor_weights = self.model.get_weights()
        actor_target_weights = self.target_model.get_weights()
        for i in xrange(len(actor_weights)):
            actor_target_weights[i] = self.TAU * actor_weights[i] + (1 - self.TAU)* actor_target_weights[i]
        self.target_model.set_weights(actor_target_weights)

主要代碼

在搭建完神經(jīng)網(wǎng)絡(luò)后,我們開(kāi)始探索ddpg.py主代碼文件。
它主要做了三件事:

  1. 接收數(shù)組形式的傳感器輸入
  2. 傳感器輸入將被饋入我們的神經(jīng)網(wǎng)絡(luò),然后網(wǎng)絡(luò)會(huì)輸出3個(gè)實(shí)數(shù)(轉(zhuǎn)向,加速和制動(dòng)的值)
  3. 網(wǎng)絡(luò)將被訓(xùn)練很多次,通過(guò)DDPG(深度確定性策略梯度算法)來(lái)最大化未來(lái)預(yù)期回報(bào)。

傳感器輸入

在TORCS中有18種不同類型的傳感器輸入,詳細(xì)的說(shuō)明在這篇文章中Simulated Car Racing Championship : Competition Software Manual。在試錯(cuò)后得到了有用的輸入:

名稱 范圍 (單位) 描述
ob.angle [-π,+π] (rad) 汽車方向和道路軸方向之間的夾角
ob.track (0, 200) (m) 19個(gè)測(cè)距儀傳感器組成的矢量,每個(gè)傳感器返回200米范圍內(nèi)的車和道路邊緣的距離
ob.trackPos (-oo, +oo) 車和道路軸之間的距離,這個(gè)值用道路寬度歸一化了:0表示車在中軸上,大于1或小于-1表示車已經(jīng)跑出道路了
ob.speedX (-oo, +oo) (km/h) 沿車縱向軸線的車速度(good velocity)
ob.speedY (-oo, +oo) (km/h) 沿車橫向軸線的車速度
ob.speedZ (-oo, +oo) (km/h) 沿車的Z-軸線的車速度
ob.wheelSpinVel (0,+oo) (rad/s) 4個(gè)傳感器組成的矢量,表示車輪的旋轉(zhuǎn)速度
ob.rpm (0,+oo) (rpm) 汽車發(fā)動(dòng)機(jī)的每分鐘轉(zhuǎn)速

請(qǐng)注意,對(duì)于某些值我們歸一化后再饋入神經(jīng)網(wǎng)絡(luò),并且有些傳感器輸入并沒(méi)有暴露在gym_torcs中。高級(jí)用戶需要修改gym_torcs.py來(lái)改變參數(shù)。(查看函數(shù)make_observaton()

策略選擇

現(xiàn)在我們可以使用上面的輸入來(lái)饋入神經(jīng)網(wǎng)絡(luò)。代碼很簡(jiǎn)單:

    for j in range(max_steps):
        a_t = actor.model.predict(s_t.reshape(1, s_t.shape[0]))
        ob, r_t, done, info = env.step(a_t[0])

然而,我們馬上遇到兩個(gè)問(wèn)題。首先,我們?nèi)绾未_定獎(jiǎng)勵(lì)?其次,我們?nèi)绾卧谶B續(xù)的動(dòng)作空間探索?

獎(jiǎng)勵(lì)設(shè)計(jì)

在原始論文中,他們使用的獎(jiǎng)勵(lì)函數(shù),等于投射到道路軸向的汽車速度,即V?x*??cos(θ),如圖:


但是,我發(fā)現(xiàn)訓(xùn)練正如原始論文中說(shuō)的那樣并不是很穩(wěn)定。有些時(shí)候可以學(xué)到合理的策略并成功完成任務(wù),有些時(shí)候則不然,并不能習(xí)得明智的策略。
我相信原因是,在原始的策略中,AI會(huì)嘗試拼命踩油門油來(lái)獲得最大的獎(jiǎng)勵(lì),然后它會(huì)撞上路邊,這輪非常迅速地結(jié)束。因此,神經(jīng)網(wǎng)絡(luò)陷入一個(gè)非常差的局部最小中。新提出的獎(jiǎng)勵(lì)函數(shù)如下:
R_t = V_x cos(\theta) - V_y sin(\theta) - V_x \mid trackPos \mid

簡(jiǎn)單說(shuō)來(lái),我們想要最大化軸向速度(第一項(xiàng)),最小化橫向速度(第二項(xiàng)),并且我們懲罰AI如果它持續(xù)非常偏離道路的中心(第三項(xiàng))。
這個(gè)新的獎(jiǎng)勵(lì)函數(shù)大幅提高了穩(wěn)定性,降低了TORCS學(xué)習(xí)時(shí)間。

探索算法的設(shè)計(jì)

另一個(gè)問(wèn)題是在連續(xù)空間中如何設(shè)計(jì)一個(gè)正確的探索算法。在上一篇文章中,我們使用了ε貪婪策略,即在某些時(shí)間片,我們嘗試一個(gè)隨機(jī)的動(dòng)作。但是這個(gè)方法在TORCS中并不有效,因?yàn)槲覀冇?個(gè)動(dòng)作(轉(zhuǎn)向,加速,制動(dòng))。如果我只是從均勻分布的動(dòng)作中隨機(jī)選取,會(huì)產(chǎn)生一些無(wú)聊的組合(例如:制動(dòng)的值大于加速的值,車子根本就不會(huì)動(dòng))。所以,我們使用奧恩斯坦 - 烏倫貝克(Ornstein-Uhlenbeck)過(guò)程添加噪聲來(lái)做探索。

Ornstein-Uhlenbeck處理

簡(jiǎn)單說(shuō)來(lái),它就是具有均值回歸特性的隨機(jī)過(guò)程。
dx_t = \theta (\mu - x_t)dt + \sigma dW_t


這里,θ反應(yīng)變量回歸均值有多快。μ代表平衡或均值。σ是該過(guò)程的波動(dòng)程度。有趣的事,奧恩斯坦 - 烏倫貝克過(guò)程是一種很常見(jiàn)的方法,用來(lái)隨機(jī)模擬利率,外匯和大宗商品價(jià)格。(也是金融定量面試的常見(jiàn)問(wèn)題)。下表展示了在代碼中使用的建議值。

Action θ μ σ
steering 0.6 0.0 0.30
acceleration 1.0 [0.3-0.6] 0.10
brake 1.0 -0.1 0.05

基本上,最重要的參數(shù)是加速度μ,你想要讓汽車有一定的初始速度,而不要陷入局部最?。ù藭r(shí)汽車一直踩剎車,不再踩油門)。你可以隨意更改參數(shù)來(lái)實(shí)驗(yàn)AI在不同組合下的行為。奧恩斯坦的 - 烏倫貝克過(guò)程的代碼保存在OU.py中。
AI如果使用合理的探索策略和修訂的獎(jiǎng)勵(lì)函數(shù),它能在一個(gè)簡(jiǎn)單的賽道上在200回合左右學(xué)習(xí)到一個(gè)合理的策略。

經(jīng)驗(yàn)回放

類似于深Q小鳥(niǎo),我們也使用了經(jīng)驗(yàn)回放來(lái)保存所有的階段(s, a, r, s')在一個(gè)回放存儲(chǔ)器中。當(dāng)訓(xùn)練神經(jīng)網(wǎng)絡(luò)時(shí),從其中隨機(jī)小批量抽取階段情景,而不是使用最近的,這將大大提高系統(tǒng)的穩(wěn)定性。

        buff.add(s_t, a_t[0], r_t, s_t1, done)
        # 從存儲(chǔ)回放器中隨機(jī)小批量抽取N個(gè)變換階段 (si, ai, ri, si+1)
        batch = buff.getBatch(BATCH_SIZE)
        states = np.asarray([e[0] for e in batch])
        actions = np.asarray([e[1] for e in batch])
        rewards = np.asarray([e[2] for e in batch])
        new_states = np.asarray([e[3] for e in batch])
        dones = np.asarray([e[4] for e in batch])
        y_t = np.asarray([e[1] for e in batch])

        target_q_values = critic.target_model.predict([new_states, actor.target_model.predict(new_states)])    #Still using tf
       
        for k in range(len(batch)):
            if dones[k]:
                y_t[k] = rewards[k]
            else:
                y_t[k] = rewards[k] + GAMMA*target_q_values[k]

請(qǐng)注意,當(dāng)計(jì)算了target_q_values時(shí)我們使用的是目標(biāo)網(wǎng)絡(luò)的輸出,而不是模型自身。使用緩變的目標(biāo)網(wǎng)絡(luò)將減少Q(mào)值估測(cè)的振蕩,從而大幅提高學(xué)習(xí)的穩(wěn)定性。

訓(xùn)練

神經(jīng)網(wǎng)絡(luò)的實(shí)際訓(xùn)練非常簡(jiǎn)單,只包含了6行代碼:

        loss += critic.model.train_on_batch([states,actions], y_t) 
        a_for_grad = actor.model.predict(states)
        grads = critic.gradients(states, a_for_grad)
        actor.train(states, grads)
        actor.target_train()
        critic.target_train()

首先,我們最小化損失函數(shù)來(lái)更新評(píng)論家。
L = \frac{1}{N} \displaystyle\sum_{i} (y_i - Q(s_i,a_i | \theta^{Q}))^{2}


然后演員策略使用一定樣本的策略梯度來(lái)更新
\nabla_\theta J = \frac{\partial Q^{\theta}(s,a)}{\partial a}\frac{\partial a}{\partial \theta}

回想一下,a是確定性策略:a=μ(s∣θ)
因此,它能被寫作:
\nabla_\theta J = \frac{\partial Q^{\theta}(s,a)}{\partial a}\frac{\partial \mu(s|\theta)}{\partial \theta}

最后兩行代碼更新了目標(biāo)網(wǎng)絡(luò)
\theta^{Q^{'}} \leftarrow \tau \theta^{Q} + (1 - \tau) \theta^{Q^{'}} \theta^{\mu^{'}} \leftarrow \tau \theta^{\mu} + (1 - \tau) \theta^{\mu^{'}}

結(jié)果

為了測(cè)試策略,選擇一個(gè)名為Aalborg的稍微困難的賽道,如下圖:

Aalborg

神經(jīng)網(wǎng)絡(luò)被訓(xùn)練了2000個(gè)回合,并且令?yuàn)W恩斯坦 - 烏倫貝克過(guò)程在100000幀中線性衰變。(即沒(méi)有更多的開(kāi)發(fā)在100000幀后被應(yīng)用)。然后測(cè)試一個(gè)新的賽道(3倍長(zhǎng))來(lái)驗(yàn)證我們的神經(jīng)網(wǎng)絡(luò)。在其它賽道上測(cè)試是很重要的,這可以確認(rèn)AI是否只是簡(jiǎn)單地記憶住了賽道(過(guò)擬合),而非學(xué)習(xí)到通用的策略。
Alpine

測(cè)試結(jié)果視頻,賽道:AalborgAlpine。
結(jié)果還不錯(cuò),但是還不理想,因?yàn)樗€沒(méi)太學(xué)會(huì)使用剎車。

學(xué)習(xí)如何剎車

事實(shí)證明,要求AI學(xué)會(huì)如何剎車比轉(zhuǎn)彎和加速難多了。原因在于當(dāng)剎車的時(shí)候車速降低,因此,獎(jiǎng)勵(lì)也會(huì)下降,AI根本就不會(huì)熱心于踩剎車。另外, 如果允許AI在勘探階段同時(shí)踩剎車和加速,AI會(huì)經(jīng)常急剎,我們會(huì)陷入糟糕的局部最小解(汽車不動(dòng),不會(huì)受到任何獎(jiǎng)勵(lì))。
所以如何去解決這個(gè)問(wèn)題呢?不要急剎車,而是試著感覺(jué)剎車。我們?cè)赥ORCS中添加隨機(jī)剎車的機(jī)制:在勘探階段,10%的時(shí)間剎車(感覺(jué)剎車),90%的時(shí)間不剎車。因?yàn)橹辉?0%的時(shí)間里剎車,汽車會(huì)有一定的速度,因此它不會(huì)陷入局部最小(汽車不動(dòng)),而同時(shí),它又能學(xué)習(xí)到如何去剎車。
“隨機(jī)剎車”使得AI在直道上加速很快,在快拐彎時(shí)適當(dāng)?shù)貏x車。這樣的行為更接近人類的做法。

總結(jié)和進(jìn)一步的工作

我們成功地使用 Keras和DDPG來(lái)玩賽車游戲。盡管DDPG能學(xué)習(xí)到一個(gè)合理的策略,但和人學(xué)會(huì)開(kāi)車的復(fù)雜機(jī)制還是有很大區(qū)別的,而且如果是開(kāi)飛機(jī)這種有更多動(dòng)作組合的問(wèn)題,事情會(huì)復(fù)雜得多。
不過(guò),這個(gè)算法還是相當(dāng)給力的,因?yàn)槲覀冇辛艘粋€(gè)對(duì)于連續(xù)控制的無(wú)模型算法,這對(duì)于機(jī)器人是很有意義的。

雜項(xiàng)

  1. 要更換賽道,需要命令行輸入 sudo torcs –> Race –> Practice –> Configure Race。
  2. 關(guān)閉聲音,需要命令行輸入sudo torcs –> Options –> Sound –> Disable sound。
  3. snakeoil3_gym.py是與TORCS服務(wù)器溝通的腳本。

參考

[1] Lillicrap, et al. Continuous control with Deep Reinforcement Learning
[2] @karpathyDeep Reinforcement Learning: Pong from Pixels——理解策略梯度

其它

作者的致謝

I thank to Naoto Yoshida, the author of the gym_torcs and his prompt reply on various TORCS setup issue. I also thank to @karpathy his great post Deep Reinforcement Learning: Pong from Pixels which really helps me to understand policy gradient. I thank to @hardmaru and @flyyufelix for their comments and suggestions.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容