CNTK與深度強化學習筆記之一: 環境搭建和基本概念
如需轉載,請指明出處。
前言
深度強化學習是人工智慧當前的熱點,CNTK也是微軟力推的深度學習框架,2.x版本比之前有了長足的進步。目前國內將這兩者融合起來的文章還不多。因此寫作了這個學習筆記,希望能對大家有所幫助。
硬體,開發環境以及CNTK安裝
CNTK可能是為數不多的在Windows平臺同樣支援CUDA和cuDNN加速的框架了。CNTK某些示例,雖然在CPU的環境下面也可以執行,但是速度實在是太慢了。因此推薦支援CUDA的Nvidia GPU,支援列表這裡可以查到:CUDA GPUs。從列表中可以看到,如果想要買個遊戲本做深度學習,GeForce GTX 1060是移動版本里面最便宜的支援CUDA的GPU。我正是使用了這個型號的遊戲本。大概測試了一下CPU和GPU訓練的效能差別,在用CNTK的ConvNet的示例訓練MNIST時,使用GPU一個Epoch在2秒以內,使用“nvidia-smi -l 1”命令檢視GPU的使用率,在95%以上。CPU(i7-7700HQ)需要大概50秒,使用率達到了100%。
開發環境需要在Ubuntu上面(推薦使用Ubuntu 14.04或者Ubuntu 16.04,本文使用的是Ubuntu 16.04),並且推薦使用Anaconda3的最新版本(本文使用的是Anaconda3-4.4.0,Python 3.6版本)。必須用Ubuntu的原因是,官方的DQN示例使用了gym。gym是由OpenAI開發的工具集,提供了強化學習中的環境(environment)介面,用來開發和對比強化學習演算法。gym目前只支援Linux(只有有限的環境可以在Windows上面執行)。gym的安裝配置官方文件也很清楚了:gym。
需要說明的是,Ubuntu 16.04預設的是開源版本的驅動,不支援GeForce GTX 1060,需要安裝Nvidia的官方閉源驅動。我直接使用了Ubuntu的repo,命令"sudo apt-get install nvidia-375"就可以了。如果需要最新的驅動,也可以去
CNTK官方文件給出了非常詳細的安裝過程,這裡就不再重複。請參考CNTK的主頁: CNTK。本文寫作的時候,CNTK的版本是2.1(2017-07-31. CNTK 2.1)。
深度強化學習和DQN
深度強化學習
2013年12月19日,DeepMind公司在Arxiv上發表了一篇論文:Playing Atari with Deep Reinforcement Learning。論文展示了他們如何使用強化學習,僅僅通過螢幕畫素和分數作為獎賞,讓電腦玩2600個雅達利的視訊遊戲。這個結果的意義在於,這些遊戲和遊戲要達到的目標都是不同的,並且是設計來挑戰人類的。論文中提到的模型,不需要任何改變,就可以用來學習七個不同的遊戲,並且在其中三個遊戲中,模型的成績比人類還好。這是邁向通用人工智慧(強人工智慧)的第一步:AI可以適應不同的環境,而不是限定於某個領域,例如玩象棋。發表了這個論文之後,DeepMind立刻就被Google收購了,並且一直引領了深度學習的研究。2015年2月,DeepMind又在自然雜誌封面發表了論文
深度學習和強化學習的結合,無疑是近幾年人工智慧領域的熱點。按照Yoshua Bengio的觀點,目前的深度學習和所謂智慧系統的表現,表明當前我們只做到了非常膚淺的部分,還遠遠沒有觸及智慧的本源。我們必須去研究機器如何觀察世界、理解世界,研究高層抽象,進行認知方面的探索。這個世界既包括真實世界,也可以是簡單如視訊遊戲的虛擬環境。詳見Andrew Ng對Bengio的訪談視訊:Heroes of Deep Learning: Andrew Ng interviews Yoshua Bengio。後面我們也可以看到,用深度強化學習處理問題,更加的自然。
將深度學習和強化學習結合,有幾個問題需要解決,後面的內容詳細探討這些問題:
- 強化學習的主要挑戰:功勞分配問題(credit assignment problem),以及探索和開發困境(exploration-exploitation dilemma,也有翻譯為探索和利用)。
- 如何用數學形式表示深度學習問題:MDP(馬爾科夫決策過程,Markov Decision Process)。
- 如何形成長期策略:折扣的未來獎勵(discounted future reward)。
- 如何估計和逼近未來獎勵:Q-learning演算法。
- 如何表示巨大的狀態空間:深度神經網路。
- 如何穩定學習:經歷重放技術(experience replay technique)。
強化學習
考慮Breakout這個打磚塊遊戲。假定我們要教一個神經網路玩這個遊戲。網路的輸入應該是螢幕影象,輸出應該是三個動作:左,右和發射球。如下圖:
我們可能想到的是,這個問題可以作為一個分類問題,每個遊戲螢幕對應一個動作。但是這樣我們就需要大量的訓練樣本。當然我們可以找高手來玩並且錄製遊戲,但是這樣不是人類真正如何學習的。我們不需要別人上百萬次的告訴我們,哪個螢幕應該如何應對。我們只需要偶爾有點反饋說我們做對了,然後我們自己就可以搞明白怎麼玩了。
這就是強化學習要解決的問題。強化學習介於監督學習和非監督學習之間。監督學習需要每個訓練資料都被標註,而非監督學習完全不需要標註。強化學習有稀疏的時延的標註,即獎勵(Reward)。Agent從這些獎勵中,學習如何與環境(Environment)互動。
這個概念是很直觀的,但是在實踐中有很多挑戰。例如當我們擊中了某個磚塊並拿到了一個分數獎勵,它通常跟剛剛拿到獎勵之前的動作沒有關係,所有需要的工作都已經完成了。這被稱為功勞分配問題(credit assignment problem),例如,之前的哪些動作(actions)是獲得獎勵的原因,並且在多大程度上。
當使用某個策略得到了一些獎勵,我們應該繼續使用這個策略,還是應該嘗試一些新的可能產生更好結果的策略?這被稱為探索和開發困境(exploration-exploitation dilemma):我們應該繼續開發並且最大化已知策略,還是應該探索可能的更好策略。
###馬爾科夫決策過程(MDP)
如何表示一個強化學習問題,使得我們可以推演它呢?最通用的方法是MDP。假定有一個Agent,被至於一個環境中(例如Breakout遊戲)。環境被至於一個確定的狀態(擋板的位置,球的位置和方向,存在的磚塊等等)。Agent在Environment中可以執行一些Action(例如移動擋板到左邊或者右邊)。這些Action可能產生Reward(例如分數增加)。Action使得Environment發生State遷移,Agent可以執行另外一個Action,如此往復。選擇這些Action的規則被成為策略。Environment通常是隨機的,意味著下一個State可能是隨機的(例如,當我們損失了一個球,發射一個新的球,它會飛向一個隨機的方向)。
State和Action的合集,加上從一個State轉換到另一個State,以得到Reward的策略,構成了MDP。一段這個過程(例如一局遊戲)組成了有限的State,Action和Reward的序列。
\\(s_0, a_0, r_1, s_1, a_1, r_2, s_2, …, s_{n-1}, a_{n-1}, r_n, s_n\\)
這裡\\(s_i\\)代表State,\\(a_i\\)是Action,\\(r_{i+1}\\)是執行Action之後的Reward。這段序列結束於最終狀態\\(s_n\\)(例如遊戲結束螢幕)。馬爾科夫決策過程依賴於馬爾科夫假設,即下一個State \\(s_{i+1}\\)只依賴於當前State \\(s_i\\),與之前的State和Action無關。
折扣的未來獎勵
為了能有好的長期結果,我們需要考慮的不僅僅是當前的Reward,並且還有將會得到的Reward。但是如何做到呢?
在一個馬爾科夫過程中,我們可以很容易的計算一段序列的全部Reward:
\\(R=r_1+r_2+r_3+…+r_n\\)
同理,\\(t\\)之後所以的Reward,可以表示為:
\\(R_t=r_t+r_{t+1}+r_{t+2}+…+r_n\\)
但是因為環境是隨機的,我們永遠也沒有辦法保證,即使我們執行了相同的Action序列,我們還可以得到相同的Reward。序列進行的越遠,分歧可能會越大。因此,通常都會使用折扣的未來獎勵:
\\(R_t=r_t+\gamma r_{t+1}+\gamma^2 r_{t+2}…+\gamma^{n-t} r_n\\)
這裡\\(\gamma\\)是折扣係數,取值在0和1之間:越遠的Reward,我們越少考慮。容易看出,\\(t\\)之後的折扣的未來獎勵,可以用\\(t_{+1}\\)表示:
\\(R_t=r_t+\gamma (r_{t+1}+\gamma (r_{t+2}+…))=r_t+\gamma R_{t+1}\\)
如果設定\\(\gamma=0\\),那麼我們的策略是短視的,只依賴於當前的Reward。如果想平衡當前和將來Reward,一般設定\\(\gamma=0.9\\)。如果我們的環境是確定的,同樣的Action序列總是得到相同的Reward,那麼可以設定\\(\gamma=1\\)。
Agent的一個好的策略,是總去選擇折扣的未來獎勵最大的Action。
Q-learning演算法
Q-learning演算法中,定義了一個函式\\(Q(s,a)\\),表示在狀態\\(s\\),採取動作\\(a\\)之後的折扣的未來獎勵,然後在此基礎上繼續優化。
\\(Q(s_t,a_t)=max_{\pi} R_{t+1}\\)
對於\\(Q(s,a)\\),應該這樣理解:在狀態\\(s\\)採取動作\\(a\\)之後,遊戲結束時能拿到的最好分數。它被稱為\\(Q\\)函式(Q-function),因為它表示了在某個State下面,某個Action的質量。這聽起來有點令人費解。我們知道當前的State和Action,但是不知道下面的State和Action,如何才能估計遊戲結束時候的分數?我們確實不能。但是理論上,我們可以假定有這樣一個函式。
那麼這個函式應該是什麼樣子呢?假定在狀態\\(s\\),要決定是採取動作\\(a\\)還是\\(b\\),我們想選擇一個動作,使得遊戲結束時候的分數最高。當使用Q-function時,答案就很簡單:取Q-value最大的動作:
\\(\pi(s) =argmax_a Q(s,a)\\)
這裡\\(\pi(s) \\)代表策略,即我們在每個State,選擇Action的規則。
但是我們如何得到Q-function?讓我們先看一下一個狀態轉移的情況:\\(<s,a,r,s’>\\),我們可以用下一個狀態\\(s’\\)的Q-value,來表示狀態\\(s\\)和動作\\(a\\)的Q-value:
\\(Q(s,a)=r + \gamma max_{a’}Q(s’,a’)\\)
這個方程被稱為貝爾曼方程:當前State和Action的最大的未來獎勵,等於現在的獎勵加上下一個狀態的最大未來獎勵。
Q-learning主要的思想是,我們可以用貝爾曼方程,迭代逼近Q-function。最簡單的實現是把Q-function函式實現為一個表格(Q-table),State是行,Action是列。那麼Q-learning演算法的虛擬碼如下:
initialize Q[numstates,numactions] arbitrarily
observe initial state s
repeat
select and carry out an action a
observe reward r and new state s'
Q[s,a] = Q[s,a] + α(r + γmaxa' Q[s',a'] - Q[s,a])
s = s'
until terminated
即從初始狀態\\(s\\)開始,從Q-table中遍歷所以的行動\\(a\\),檢視\\(a\\)對應的新狀態\\(s’\\),用新狀態\\(s’\\)最大的獎勵\\(r\\),更新當前\\(Q[s,a]\\)。\\(α\\)是學習率。當學效率為1時,上面虛擬碼的等式,就完全和貝爾曼方程一樣了。\\(maxa’ Q[s’,a’]\\)在初始階段只是一個估計值,可能完全是錯誤的,但是隨著迭代的進行,這個估計值會越來越精確。已經被證明,當迭代足夠多次以後,\\(Q\\)函式會收斂,並且得到真實的值(Q-value)。
Deep Q Network(DQN)
上面的模型中,打磚塊遊戲的環境State,是由擋板的位置,球的位置和方向,還有每個磚塊的位置定義的。但是這種直觀的定義是遊戲相關的(記得前面說過DeepMind只需要一個模型就可以玩49個遊戲嗎?)。那麼我們能不能給出更加通用的,適合所有遊戲的模型哪?一個明顯的選擇是用螢幕畫素:隱含了所有的遊戲狀態,除了球的速度和方向,但是用兩個連貫的螢幕就可以解決。
如果我們使用DeepMind論文中相同的預處理方法,擷取四個最新的螢幕影象,將大小調整為84x84,並且轉換成256級灰度,那麼我們有\\(256^{84 \times 84 \times 4} \approx 10{67970}\\)個狀態。相當於上面的Q-table有\\(10{67970}\\)行。這個數目實在是太大了,並且某些狀態可能永遠也不能被訪問到。因此這個方法不可行。
這裡就需要用到深度學習了。神經網路(NN)特別適合結構化資料的特徵提取
。我們可以用NN來表示Q-function,將state(四個遊戲螢幕)和action作為輸入,輸出為相應的Q-value。我們也可以將一個遊戲螢幕作為輸入,輸出為每個可能action的Q-value。後面這種方法更有優勢,因為如果我們想更新Q-value,或者選擇Q-value最大的action,我們只需要對網路前向傳播一次,所有action的Q-value就都有了。下圖是兩種方法的區別:
DeepMind使用的網路結構如下:
這是一個包含了3個卷積層和兩個連線層的CNN,不過沒有池化層。這個也很好理解,因為池化層提供了平移不變性(translation invariance):網路對物件在影象中的位置不敏感。但是對於遊戲來說,小球的位置對於我們決定reward是至關重要的!我們不能丟棄這個資訊!
網路的輸入是84x84灰度的遊戲螢幕,輸出是每個可能狀態的Q-value,可以是實數。這是一個迴歸任務,可以用簡單的平方誤差損失作優化。
經歷重放
現在我們可以在每個狀態下面,使用CNN,逼近Q-function,並用Q-learning估算每個state的未來reward。但是實際上,用非線性函式去逼近Q-value不是很穩定。使Q-value收斂,有很多技巧,並且還需要很長的時間(在一個GPU上面,可能需要一週)。
一個最重要的技巧是經歷重放。在玩遊戲的過程中,所有經歷過的\\(<s,a,r,s’>\\)都被儲存在重放記憶體中。當訓練網路的時候,重放記憶體中會被隨機取樣,來替代最近的狀態轉換。這樣就打破了其後訓練樣本的相關性,從而避免了網路進入一個區域性最小值。並且經歷重放使得訓練變得和通常的監督學習類似,這樣可以簡化除錯和測試。其實人類玩遊戲也是這樣的。
探索和開發困境
Q-learning解決了功勞分配問題:倒流時光反向傳播reward,直到到達真正引起reward的決策點(通過折扣的未來獎勵)。現在來看看怎麼解決探索和開發困境。
首先,Q-table或者Q-network是隨機初始化的,隨後初始的預測也是隨機的。如果我們選擇了最高的Q-value,那麼這個選擇也是隨機的,相當於Agent在進行探索。當Q-function收斂的時候,它會返回穩定的Q-value,探索隨之減少。所以我們可以認為,探索是Q-learning演算法的一部分。但是這個探索是貪婪的:探索停止於找到的第一個可用策略。
ε-greedy exploration可以簡單有效的修正這個問題。ε是隨機選擇一個Action的概率,否則用貪婪的辦法選擇Q-value最高的Action。即按照一定概率,選擇隨機的Action。
深度Q-learning演算法
前面給出了基於Q-table的Q-learning演算法的虛擬碼,現在看看帶經歷重放的深度Q-learning的演算法:
initialize replay memory D
initialize action-value function Q with random weights
observe initial state s
repeat
select an action a
with probability ε select a random action
otherwise select a = argmaxa’Q(s,a’)
carry out action a
observe reward r and new state s’
store experience <s, a, r, s’> in replay memory D
sample random transitions <ss, aa, rr, ss’> from replay memory D
calculate target for each minibatch transition
if ss’ is terminal state then tt = rr
otherwise tt = rr + γmaxa’Q(ss’, aa’)
train the Q network using (tt - Q(ss, aa))^2 as loss
s = s'
until terminated
從虛擬碼可以看到,經歷重放就是從重放記憶體中,隨機取樣,用取樣的State的Q-value去訓練網路。
除了這個演算法,DeepMind還使用了很多其它的技巧,超出了本文的介紹範圍。這個演算法最神奇之處在於,它確實學習了。想想看,Q-function是隨機初始化的。它一開始的輸出都是垃圾,沒有任何意義。我們把這個垃圾(下一個State的最大Q-value)作為網路的目標,偶爾加入一點Reward。聽起來很愚蠢,它怎麼能學到點有意義的東西呢?實際上,它學到了。
和最早的版本相比,Q-learning已經有了很大的發展,包括 Double Q-learning, Prioritized Experience Replay, Dueling Network Architecture和 extension to continuous action space等。不過請注意,深度Q-learning已經被Google申請了專利。