漫談深度強化學習之手寫Deep Q-Network解決迷宮問題
1. Q-Learning回顧
上一期我們講了Q-Learning以及Sarsa的演算法流程,同時我們還手寫了基於Q-Learning以及Sarsa來解決OpenAI gym中的FrozenLake問題。今天,我們將藉助神經網路來重新解決這個問題。(FrozenLake問題簡單來說就是走迷宮,走錯了將不會有任何獎勵,走到了目標位置就會獲得1的獎勵。關於FrozenLake問題的更多描述,請參閱https://gym.openai.com/進行了解)
Q-Learning是一種off-policy的強化學習演算法,如前所述,Q-Learning的做法是通過觀察一組序列(s,a,r,s’),使得Q(s,a)越來越靠近R+gamma*maxQ(s’,:),這裡的行動a可以任意挑選,意思就是它是與策略無關的。Q-Learning的核心演算法可以使用如下公式來表示:Q(St,At) = Q(St,At) + lr [R(t+1) + discount * max Q(St+1,a) - Q(St,At)]
傳統的做法是將Q看作一張狀態-動作的二維表格,然後通過時序差分演算法來更新表格中的每一個元素。那麼這種傳統的做法有什麼缺點呢?顯然最大的缺點就是對於狀態數目較多的問題,更新Q表的做法很不現實。取而代之的是我們將使用神經網路來對Q值建模,藉助神經網路強大的表徵能力以及隨機梯度下降演算法,加上細心的優化調參,我們可以對Q值進行很好地建模。
2. 運用神經網路來模擬Q值
這裡我們的實驗仍然是基於OpenAI gym的FrozenLake環境,由於強化學習的兩大核心主體是Environment和Agent,這裡的Environment已經由OpenAI給我們編寫好了,因此我們只用專心編寫Agent的程式碼即可。
我們需要使用神經網路來取代傳統的Q表學習,我們把要編寫的類取名為QNetworkAgent類,QNetworkAgent類雖然將Q表換成了神經網路,但是它仍然是一種Q-Learning演算法,因此,QNetworkAgent類中的方法相比父類QAgent而言應該是類似的。我們之前已經編寫了QAgent類(程式碼請見:),現在只需要把要編寫的QNetworkAgent類繼承自QAgent類即可,這樣我們就可以省去一些程式碼,但是也要改動或增加一些程式碼。具體的改動細節,我們分以下三點來說明:
首先,就環境部分而言,QNetworkAgent類無需重寫render()、step()、reset()等方法,因為這些是由環境決定的,與Agent演算法毫無關係,我們直接繼承自QAgent類即可;
其次,我們需要構建神經網路來模擬Q值,那麼我們必然需要增加一個方法,即build_network(),而這個方法只在類內部呼叫,因此我們增加下劃線變成_build_network()方法;
最後,對於QAgent父類中已經存在的pick_action()方法以及learn()方法我們需要在子類中重寫,原因是父類中的這兩個方法都是基於Q表來操作的,而這裡我們不存在Q表,因此需要改寫一下,不過演算法核心還是不變,只是換了一個操作物件而已。
總結一下,QNetworkAgent類中需要寫的就是以下四個方法,我們會在下文中一一講解。
__init__()pick_action()_build_network()learn()
__init__函式
就建構函式而言,由於QNetworkAgent類中會涉及到神經網路,因此,除了繼承父類QAgent的建構函式之外,我們有必要在建構函式中增加啟用函式、損失函式、梯度下降優化器等引數。於是,整個建構函式如下所示:
pick_action函式
與之前QAgent類中程式碼類似,這裡我們仍然設定兩種選取動作的演算法,分別是e-epsilon演算法和add noise演算法,而這裡我們操作的物件由二維的Q錶轉變成了一維的動作概率分佈向量,此向量由Q-Network產生的。除此之外,我們仍然增加了shuffle選項和softmax選項,供讀者在其他複雜環境中選用。整個pick_action的程式碼如下:
_build_network函式
由於我們需要使用神經網路來代替Q表,從而實現對Q值的模擬,那麼我們需要的輸入是什麼?要訓練的引數怎麼構造?目標函式又是什麼?這些都是我們在運用神經網路進行建模之前所要思考的問題。(話說,人工選取神經網路結構相當困難,也是一項十分繁瑣的任務,最近,Google Brain團隊提出了使用深度學習的方法來選取神經網路架構,通過seq2seq模型與強化學習相結合,可以找到比較合適應用於cifar-10及PTB語言建模的CNN或RNN結構,具體論文請搜尋:Neural Architecture Search withReinforcement Learning)
回到這裡的FrozenLake問題上,我們知道,環境給予我們的反饋主要是價值和狀態,因此這裡我們要構建的網路的目的就是通過輸入當前的狀態就可以得到未來的Q的估計值。由於這裡問題相對簡單,我們只用構建一層神經網路即可,輸出就是動作空間的概率分佈。
如何確定損失函式呢?由於這一層神經網路輸出的是Q的估計值,而我們最終要接近的是Q的目標值,因此,我們可以把Q的估計值與Q的目標值的差的平方作為損失函式。而這個Q的目標值怎麼得到呢?今天我們只講通過傳統的公式得到,這種做法與之前的Q-Learning中一樣,Q的目標值是不是也可以通過神經網路得到呢?答案是可以的,不過這個問題要留到下次再說,這裡我們將Q的目標值也設為輸入,因為中間我們需要將Q的估計值和回報值一起代入公式計算才能得到Q的目標值,這一步在下文的learn函式中會提到。
訓練神經網路的過程就是選取優化演算法,讓該損失函式達到最小。整個函式的程式碼如下:
learn函式
在之前的QAgent中,learn函式的作用是根據目標Q表來更新當前的Q表,而在QNetworkAgent這裡,我們操作的物件不是Q表,而是由神經網路產生的Q值向量,儘管操作物件變了,但是我們仍然運用Q-Learning的思想,基於Q的估計值來得到Q的目標值,程式碼如下:
3. 執行QNetworkAgent
執行QNetworkAgent仍然是使用單步更新的方法,在每一步中,我們依次執行以下演算法:
獲取agent的當前狀態;
首先我們呼叫神經網路得到agent當前狀態的Q的估計值;
然後由當前狀態的Q的估計值選取合理的行動;
緊接著執行此行動並得到新的狀態和回報;
通過這個新的狀態我們再呼叫神經網路獲取新的狀態的Q的估計值;
將新的狀態的Q的估計值和回報傳入到learn函式中得到當前狀態的Q的目標值;
得到了當前狀態的Q的估計值和Q的目標值以後,我們就可以將它們傳入到loss中並且經隨機梯度下降演算法來更新神經網路的引數;
執行QNetworkAgent的核心程式碼如下:
4. 實驗結果
經過10000回合的模擬過後,我們發現回合成功率已經達到了57.76%。回合成功率的意思就是截至目前所有的回報值總和除以已經經歷的回合數目,由於回報值只在成功達到目標時為1,其餘情況均為0,因此回合成功率也就是成功達到目標的回合數除以總的回合數。回合成功率如下圖所示:
下一期我們將走進DeepMind的DQN,深入解讀其玩轉Atari遊戲的兩大利器:Fixed Target Network和Experience Reply。
題圖:Pawel Kuczynski