程式設計師帶你一步步分析 AI 如何玩 Flappy Bird
以下內容來源於一次部門內部的分享,主要針對AI初學者,介紹包括CNN、Deep Q Network以及TensorFlow平臺等內容。由於筆者並非深度學習演算法研究者,因此以下更多從應用的角度對整個系統進行介紹,而不會進行詳細的公式推導。
關於Flappy Bird
Flappy Bird(非官方譯名:笨鳥先飛)是一款2013年鳥飛類遊戲,由越南河內獨立遊戲開發者阮哈東(Dong Nguyen)開發,另一個獨立遊戲開發商GEARS Studios釋出。—— 以上內來自《維基百科》
Flappy Bird操作簡單,通過點選手機螢幕使Bird上升,穿過柱狀障礙物之後得分,碰到則遊戲結束。由於障礙物高低不等,控制Bird上升和下降需要反應快並且靈活,要得到較高的分數並不容易,筆者目前最多得過10分。
本文主要介紹如何通過AI(人工智慧)的方式玩Flappy Bird遊戲,分為以下四個部分內容:
- Flappy Bird 遊戲展示
- 模型:卷積神經網路
- 演算法:Deep Q Network
- 程式碼:TensorFlow實現
一、Flappy Bird 遊戲展示
在介紹模型、演算法前先來直接看下效果,上圖是剛開始訓練的時候,畫面中的小鳥就像無頭蒼蠅一樣亂飛,下圖展示的是在本機(後面會給出配置)訓練超過10小時後(訓練步數超過2000000)的情況,其最好成績已經超過200分,人類玩家已基本不可能超越。
訓練數小於10000步(剛開始訓練)
訓練步數大於2000000步(10小時後)
由於本機配置了CUDA以及cuDNN,採用了NVIDIA的顯示卡進行平行計算,所以這裡提前貼一下執行時的日誌輸出。
關於CUDA以及cuDNN的配置,其中有一些坑包括:安裝CUDA之後迴圈登入,螢幕解析度無法正常調節等等,都是由於NVIDIA驅動安裝的問題,這不是本文要討論的主要內容,讀者可自行Google。
載入CUDA運算庫
TensorFlow執行裝置 /gpu:0
/gpu:0 這是TensorFlow平臺預設的配置方法,表示使用系統中的第一塊顯示卡。
本機軟硬體配置:
系統:Ubuntu 16.04
顯示卡:NVIDIA GeForce GTX 745 4G
版本:TensorFlow 1.0
軟體包:OpenCV 3.2.0、Pygame、Numpy、…細心的朋友可能發現,筆者的顯示卡配置並不高,GeForce GTX 745,視訊記憶體3.94G,可用3.77G(桌面佔用了一部分),屬於入門中的入門。對於專業做深度學習演算法的朋友,這個顯示卡必然是不夠的。知乎上有帖子教大家怎麼配置更專業的顯示卡,有興趣的可以移步。
二、模型:卷積神經網路
神經網路演算法是由眾多的神經元可調的連線權值連線而成,具有大規模並行處理、分散式資訊儲存、良好的自組織自學習能力等特點。人工神經元與生物神經元結構類似,其結構對比如下圖所示。
人工神經元的輸入(x1,x2…xm)類似於生物神經元的樹突,輸入經過不同的權值(wk1, wk2, ….wkn),加上偏置,經過啟用函式得到輸出,最後將輸出傳輸到下一層神經元進行處理。
啟用函式為整個網路引入了非線性特性,這也是神經網路相比於迴歸等演算法擬合能力更強的原因。常用的啟用函式包括sigmoid、tanh等,它們的函式表示式如下:
這裡可以看出,sigmoid函式的值域是(0,1),tanh函式的值域是(-1,1)。
卷積神經網路起源於動物的視覺系統,主要包含的技術是:
- 區域性感知域(稀疏連線);
- 引數共享;
- 多卷積核;
- 池化。
1. 區域性感知域(稀疏連線)
全連線網路的問題在於:
- 需要訓練的引數過多,容器導致結果不收斂(梯度消失),且訓練難度極大;
- 實際上對於某個區域性的神經元來講,它更加敏感的是小範圍內的輸入,換句話說,對於較遠的輸入,其相關性很低,權值也就非常小。
人類的視覺系統決定了人在觀察外界的時候,總是從區域性到全域性。
比如,我們看到一個美女,可能最先觀察到的是美女身上的某些部位(自己體會)。
因此,卷積神經網路與人類的視覺類似,採用區域性感知,低層的神經元只負責感知區域性的資訊,在向後傳輸的過程中,高層的神經元將區域性資訊綜合起來得到全域性資訊。
全連線與區域性連線的對比(圖片來自網際網路)
從上圖中可以看出,採用區域性連線之後,可以大大的降低訓練引數的量級。
2. 引數共享
雖然通過區域性感知降低了訓練引數的量級,但整個網路需要訓練的引數依然很多。
引數共享就是將多個具有相同統計特徵的引數設定為相同,其依據是影像中一部分的統計特徵與其它部分是一樣的。其實現是通過對影像進行卷積(卷積神經網路命名的來源)。
可以理解為,比如從一張影像中的某個區域性(卷積核大小)提取了某種特徵,然後以這種特徵為探測器,應用到整個影像中,對整個影像順序進行卷積,得到不同的特徵。
卷積過程(圖片來自網際網路)
每個卷積都是一種特徵提取方式,就像一個篩子,將影像中符合條件(啟用值越大越符合條件)的部分篩選出來,通過這種卷積就進一步降低訓練引數的量級。
3. 多卷積核
如上,每個卷積都是一種特徵提取方式,那麼對於整幅影像來講,單個卷積核提取的特徵肯定是不夠的,那麼對同一幅影像使用多種卷積核進行特徵提取,就能得到多幅特徵圖(feature map)。
不同的卷積核提取不同的特徵(圖片來自網際網路)
多幅特徵圖可以看成是同一張影像的不同通道,這個概念在後面程式碼實現的時候用得上。
4. 池化
得到特徵圖之後,可以使用提取到的特徵去訓練分類器,但依然會面臨特徵維度過多,難以計算,並且可能過擬合的問題。從影像識別的角度來講,影像可能存在偏移、旋轉等,但影像的主體卻相同的情況。也就是不同的特徵向量可能對應著相同的結果,那麼池化就是解決這個問題的。
池化過程(圖片來自網際網路)
池化就是將池化核範圍內(比如2*2範圍)的訓練引數採用平均值(平均值池化)或最大值(最大值池化)來進行替代。
終於到了展示模型的時候,下面這幅圖是筆者手畫的(用電腦畫太費時,將就看吧),這幅圖展示了本文中用於訓練遊戲所用的卷積神經網路模型。
卷積神經網路模型
影像的處理過程
- 初始輸入四幅影像80×80×4(4代表輸入通道,初始時四幅影像是完全一致的),經過卷積核8×8×4×32(輸入通道4,輸出通道32),步距為4(每步卷積走4個畫素點),得到32幅特徵圖(feature map),大小為20×20;
- 將20×20的影像進行池化,池化核為2×2,得到影像大小為10×10;
- 再次卷積,卷積核為4×4×32×64,步距為2,得到影像5×5×64;
- 再次卷積,卷積核為3×3×64*64,步距為2,得到影像5×5×64,雖然與上一步得到的影像規模一致,但再次卷積之後的影像資訊更為抽象,也更接近全域性資訊;
- Reshape,即將多維特徵圖轉換為特徵向量,得到1600維的特徵向量;
- 經過全連線1600×512,得到512維特徵向量;
- 再次全連線512×2,得到最終的2維向量[0,1]和[1,0],分別代表遊戲螢幕上的是否點選事件。
可以看出,該模型實現了端到端的學習,輸入的是遊戲螢幕的截圖資訊(程式碼中經過opencv處理),輸出的是遊戲的動作,即是否點選螢幕。深度學習的強大在於其資料擬合能力,不需要傳統機器學習中複雜的特徵提取過程,而是依靠模型發現資料內部的關係。
不過這也帶來另一方面的問題,那就是深度學習高度依賴大量的標籤資料,而這些資料獲取成本極高。
三、演算法:Deep Q Network
有了卷積神經網路模型,那麼怎樣訓練模型?使得模型收斂,從而能夠指導遊戲動作呢?機器學習分為監督學習、非監督學習和強化學習,這裡要介紹的Q Network屬於強化學習(Reinforcement Learning)的範疇。在正式介紹Q Network之前,先簡單說下它的光榮歷史。
2014年Google 4億美金收購DeepMind的橋段,大家可能聽說過。那麼,DeepMind是如何被Google給盯上的呢?最終原因可以歸咎為這篇論文:
DeepMind團隊通過強化學習,完成了20多種遊戲,實現了端到端的學習。其用到的演算法就是Q Network。2015年,DeepMind團隊在《Nature》上發表了一篇升級版:
自此,在這類遊戲領域,人已經無法超過機器了。後來又有了AlphaGo,以及Master,當然,這都是後話了。其實本文也屬於上述論文的範疇,只不過基於TensorFlow平臺進行了實現,加入了一些筆者自己的理解而已。
回到正題,Q Network屬於強化學習,那麼先介紹下強化學習。
強化學習模型
這張圖是從UCL的課程中拷出來的,課程連結地址(YouTube):
https://www.youtube.com/watch?v=2pWv7GOvuf0
強化學習過程有兩個組成部分:
- 智慧代理(學習系統)
- 環境
如圖所示,在每步迭代過程中,首先智慧代理(學習系統)接收環境的狀態st
,然後產生動作at
作用於環境,環境接收動作at
,並且對其進行評價,反饋給智慧代理rt
。不斷的迴圈這個過程,就會產生一個狀態/動作/反饋的序列:(s1, a1, r1, s2, a2, r2…..,sn, an, rn),而這個序列讓我們很自然的想起了:
馬爾科夫決策過程
MDP:馬爾科夫決策過程
馬爾科夫決策過程與著名的HMM(隱馬爾科夫模型)相同的是,它們都具有馬爾科夫特性。那麼什麼是馬爾科夫特性呢?簡單來說,就是未來的狀態只取決於當前的狀態,與過去的狀態無關。
HMM(馬爾科夫模型)在語音識別,行為識別等機器學習領域有較為廣泛的應用。條件隨機場模型(Conditional Random Field)則用於自然語言處理。兩大模型是語音識別、自然語言處理領域的基石。
上圖可以用一個很形象的例子來說明。比如你畢業進入了一個公司,你的初始職級是T1(對應圖中的 s1
),你在工作上刻苦努力,追求上進(對應圖中的a1
),然後領導覺得你不錯,準備給你升職(對應圖中的r1
),於是,你升到了T2;你繼續刻苦努力,追求上進……不斷的努力,不斷的升職,最後升到了sn
。當然,你也有可能不努力上進,這也是一種動作,換句話說,該動作a也屬於動作集合A,然後得到的反饋r就是沒有升職加薪的機會。
這裡注意下,我們當然希望獲取最多的升職,那麼問題轉換為:如何根據當前狀態s(s屬於狀態集S),從A中選取動作a執行於環境,從而獲取最多的r,即r1 + r2 ……+rn的和最大 ?這裡必須要引入一個數學公式:狀態值函式。
狀態值函式模型
公式中有個摺合因子γ,其取值範圍為[0,1],當其為0時,表示只考慮當前動作對當前的影響,不考慮對後續步驟的影響,當其為1時,表示當前動作對後續每步都有均等的影響。當然,實際情況通常是當前動作對後續得分有一定的影響,但隨著步數增加,其影響減小。
從公式中可以看出,狀態值函式可以通過迭代的方式來求解。增強學習的目的就是求解馬爾可夫決策過程(MDP)的最優策略。
策略就是如何根據環境選取動作來執行的依據。策略分為穩定的策略和不穩定的策略,穩定的策略在相同的環境下,總是會給出相同的動作,不穩定的策略則反之,這裡我們主要討論穩定的策略。
求解上述狀態函式需要採用動態規劃的方法,而具體到公式,不得不提:
貝爾曼方程
其中,π代表上述提到的策略,Q π (s, a)相比於V π (s),引入了動作,被稱作動作值函式。對貝爾曼方程求最優解,就得到了貝爾曼最優性方程。
求解該方程有兩種方法:策略迭代和值迭代。
策略迭代
策略迭代分為兩個步驟:策略評估和策略改進,即首先評估策略,得到狀態值函式,其次,改進策略,如果新的策略比之前好,就替代老的策略。
值迭代
從上面我們可以看到,策略迭代演算法包含了一個策略估計的過程,而策略估計則需要掃描(sweep)所有的狀態若干次,其中巨大的計算量直接影響了策略迭代演算法的效率。而值迭代每次只掃描一次,更新過程如下:
即在值迭代的第k+1次迭代時,直接將能獲得的最大的Vπ(s)值賦給Vk+1。
Q-Learning
Q-Learning是根據值迭代的思路來進行學習的。該演算法中,Q值更新的方法如下:
雖然根據值迭代計算出目標Q值,但是這裡並沒有直接將這個Q值(是估計值)直接賦予新的Q,而是採用漸進的方式類似梯度下降,朝目標邁近一小步,取決於α,這就能夠減少估計誤差造成的影響。類似隨機梯度下降,最後可以收斂到最優的Q值。具體演算法如下:
如果沒有接觸過動態規劃的童鞋看上述公式可能有點頭大,下面通過表格來演示下Q值更新的過程,大家就明白了。
狀態 | a1 | a2 | a3 | a4 |
---|---|---|---|---|
s1 | Q(1, 1) | Q(1, 2) | Q(1, 3) | Q(1, 4) |
s2 | Q(2, 1) | Q(2, 2) | Q(2, 3) | Q(2, 4) |
s3 | Q(3, 1) | Q(3, 2) | Q(3, 3) | Q(3, 4) |
s4 | Q(4, 1) | Q(4, 2) | Q(4, 3) | Q(4, 4) |
Q-Learning演算法的過程就是儲存Q值的過程。上表中,橫列為狀態s,縱列為Action a,s和a決定了表中的Q值。
第一步:初始化,將表中的Q值全部置0;
第二步:根據策略及狀態s,選擇a執行。假定當前狀態為s1,由於初始值都為0,所以任意選取a執行,假定這裡選取了a2執行,得到了reward為1,並且進入了狀態s3。根據Q值更新公式:
來更新Q值,這裡我們假設α是1,λ也等於1,也就是每一次都把目標Q值賦給Q。那麼這裡公式變成:
所以在這裡,就是
那麼對應的s3狀態,最大值是0,所以
Q表格就變成:
狀態 | a1 | a2 | a3 | a4 |
---|---|---|---|---|
s1 | 0 | 1 | 0 | 0 |
s2 | 0 | 0 | 0 | 0 |
s3 | 0 | 0 | 0 | 0 |
s4 | 0 | 0 | 0 | 0 |
然後置位當前狀態s為s3。
第三步:繼續迴圈操作,進入下一次動作,當前狀態是s3,假設選擇動作a3,然後得到reward為2,狀態變成s1,那麼我們同樣進行更新:
所以Q的表格就變成:
狀態 | a1 | a2 | a3 | a4 |
---|---|---|---|---|
s1 | 0 | 1 | 0 | 0 |
s2 | 0 | 0 | 0 | 0 |
s3 | 0 | 0 | 3 | 0 |
s4 | 0 | 0 | 0 | 0 |
第四步: 繼續迴圈,Q值在試驗的同時反覆更新,直到收斂。
上述表格演示了具有4種狀態/4種行為的系統,然而在實際應用中,以本文講到的Flappy Bird遊戲為例,介面為80*80個畫素點,每個畫素點的色值有256種可能。那麼實際的狀態總數為256的80*80次方,這是一個很大的數字,直接導致無法通過表格的思路進行計算。
因此,為了實現降維,這裡引入了一個價值函式近似的方法,通過一個函式表近似表達價值函式:
其中,ω 與 b 分別為引數。看到這裡,終於可以聯絡到前面提到的神經網路了,上面的表示式不就是神經元的函式嗎?
Q-network
下面這張圖來自論文《Human-level Control through Deep Reinforcement Learning》,其中詳細介紹了上述將Q值神經網路化的過程。(感興趣的可以點之前的連結瞭解原文~)
以本文為例,輸入是經過處理的4個連續的80×80影像,然後經過三個卷積層,一個池化層,兩個全連線層,最後輸出包含每一個動作Q值的向量。
現在已經將Q-learning神經網路化為Q-network了,接下來的問題是如何訓練這個神經網路。神經網路訓練的過程其實就是一個最優化方程求解的過程,定義系統的損失函式,然後讓損失函式最小化的過程。
訓練過程依賴於上述提到的DQN演算法,以目標Q值作為標籤,因此,損失函式可以定義為:
上面公式是s'
,a'
即下一個狀態和動作。確定了損失函式,確定了獲取樣本的方式,DQN的整個演算法也就成型了!
值得注意的是這裡的D
—Experience Replay,也就是經驗池,就是如何儲存樣本及取樣的問題。
由於玩Flappy Bird遊戲,採集的樣本是一個時間序列,樣本之間具有連續性,如果每次得到樣本就更新Q值,受樣本分佈影響,效果會不好。因此,一個很直接的想法就是把樣本先存起來,然後隨機取樣如何?這就是Experience Replay的思想。
演算法實現上,先反覆實驗,並且將實驗資料儲存在D
中;儲存到一定程度,就從中隨機抽取資料,對損失函式進行梯度下降。
四、程式碼:TensorFlow實現
終於到了看程式碼的時候。首先申明下,當筆者從Deep Mind的論文入手,試圖用TensorFlow實現對Flappy Bird遊戲進行實現時,發現github已有大神完成demo。思路相同,所以直接以公開程式碼為例進行分析說明了。
如有原始碼需要,請移步github:Using Deep Q-Network to Learn How To Play Flappy Bird。
程式碼從結構上來講,主要分為以下幾部分:
- GameState遊戲類,frame_step方法控制移動
- CNN模型構建
- OpenCV-Python影像預處理方法
- 模型訓練過程
1. GameState遊戲類及frame_step方法
通過Python實現遊戲必然要用pygame庫,其包含時鐘、基本的顯示控制、各種遊戲控制元件、觸發事件等,對此有興趣的,可以詳細瞭解pygame。frame_step方法的入參為shape為 (2,) 的ndarray,值域: [1,0]:什麼都不做; [0,1]:提升Bird。來看下程式碼實現:
if input_actions[1] == 1: if self.playery > -2 * PLAYER_HEIGHT: self.playerVelY = self.playerFlapAcc self.playerFlapped = True # SOUNDS['wing'].play()
後續操作包括檢查得分、設定介面、檢查是否碰撞等,這裡不再詳細展開。
frame_step方法的返回值是:
return image_data, reward, terminal
分別表示介面影像資料,得分以及是否結束遊戲。對應前面強化學習模型,介面影像資料表示環境狀態 s,得分表示環境給予學習系統的反饋 r。
2. CNN模型構建
該Demo中包含三個卷積層,一個池化層,兩個全連線層,最後輸出包含每一個動作Q值的向量。因此,首先定義權重、偏置、卷積和池化函式:
# 權重 def weight_variable(shape): initial = tf.truncated_normal(shape, stddev=0.01) return tf.Variable(initial) # 偏置 def bias_variable(shape): initial = tf.constant(0.01, shape=shape) return tf.Variable(initial) # 卷積 def conv2d(x, W, stride): return tf.nn.conv2d(x, W, strides=[1, stride, stride, 1], padding="SAME") # 池化 def max_pool_2x2(x): return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME")
然後,通過上述函式構建卷積神經網路模型(對程式碼中引數不解的,可直接往前翻,看上面那張手畫的圖)。
def createNetwork(): # 第一層卷積 W_conv1 = weight_variable([8, 8, 4, 32]) b_conv1 = bias_variable([32]) # 第二層卷積 W_conv2 = weight_variable([4, 4, 32, 64]) b_conv2 = bias_variable([64]) # 第三層卷積 W_conv3 = weight_variable([3, 3, 64, 64]) b_conv3 = bias_variable([64]) # 第一層全連線 W_fc1 = weight_variable([1600, 512]) b_fc1 = bias_variable([512]) # 第二層全連線 W_fc2 = weight_variable([512, ACTIONS]) b_fc2 = bias_variable([ACTIONS]) # 輸入層 s = tf.placeholder("float", [None, 80, 80, 4]) # 第一層隱藏層+池化層 h_conv1 = tf.nn.relu(conv2d(s, W_conv1, 4) + b_conv1) h_pool1 = max_pool_2x2(h_conv1) # 第二層隱藏層(這裡只用了一層池化層) h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2, 2) + b_conv2) # h_pool2 = max_pool_2x2(h_conv2) # 第三層隱藏層 h_conv3 = tf.nn.relu(conv2d(h_conv2, W_conv3, 1) + b_conv3) # h_pool3 = max_pool_2x2(h_conv3) # Reshape # h_pool3_flat = tf.reshape(h_pool3, [-1, 256]) h_conv3_flat = tf.reshape(h_conv3, [-1, 1600]) # 全連線層 h_fc1 = tf.nn.relu(tf.matmul(h_conv3_flat, W_fc1) + b_fc1) # 輸出層 # readout layer readout = tf.matmul(h_fc1, W_fc2) + b_fc2 return s, readout, h_fc1
3. OpenCV-Python影像預處理方法
在Ubuntu中安裝opencv的步驟比較麻煩,當時也踩了不少坑,各種Google解決。建議安裝opencv3。
這部分主要對frame_step方法返回的資料進行了灰度化和二值化,也就是最基本的影像預處理方法。
x_t, r_0, terminal = game_state.frame_step(do_nothing) # 首先將影像轉換為80*80,然後進行灰度化 x_t = cv2.cvtColor(cv2.resize(x_t, (80, 80)), cv2.COLOR_BGR2GRAY) # 對灰度影像二值化 ret, x_t = cv2.threshold(x_t, 1, 255, cv2.THRESH_BINARY) # 四通道輸入影像 s_t = np.stack((x_t, x_t, x_t, x_t), axis=2)
4. DQN訓練過程
這是程式碼部分要講的重點,也是上述Q-learning演算法的程式碼化。
i. 在進入訓練之前,首先建立一些變數:
# define the cost function a = tf.placeholder("float", [None, ACTIONS]) y = tf.placeholder("float", [None]) readout_action = tf.reduce_sum(tf.multiply(readout, a), axis=1) cost = tf.reduce_mean(tf.square(y - readout_action)) train_step = tf.train.AdamOptimizer(1e-6).minimize(cost) # open up a game state to communicate with emulator game_state = game.GameState() # store the previous observations in replay memory D = deque()
在TensorFlow中,通常有三種讀取資料的方式:Feeding、Reading from files和Preloaded data。Feeding是最常用也最有效的方法。即在模型(Graph)構建之前,先使用placeholder進行佔位,但此時並沒有訓練資料,訓練是通過feed_dict傳入資料。
這裡的a
表示輸出的動作,即強化學習模型中的Action,y
表示標籤值,readout_action
表示模型輸出與a
相乘後,在一維求和,損失函式對標籤值與輸出值的差進行平方,train_step
表示對損失函式進行Adam
優化。
賦值的過程為:
# perform gradient step train_step.run(feed_dict={ y: y_batch, a: a_batch, s: s_j_batch} )
ii. 建立遊戲及經驗池 D
# open up a game state to communicate with emulator game_state = game.GameState() # store the previous observations in replay memory D = deque()
經驗池 D採用了佇列的資料結構,是TensorFlow中最基礎的資料結構,可以通過dequeue()
和enqueue([y])
方法進行取出和壓入資料。經驗池 D用來儲存實驗過程中的資料,後面的訓練過程會從中隨機取出一定量的batch進行訓練。
變數建立完成之後,需要呼叫TensorFlow系統方法tf.global_variables_initializer()新增一個操作實現變數初始化。執行時機是在模型構建完成,Session建立之初。比如:
# Create two variables. weights = tf.Variable(tf.random_normal([784, 200], stddev=0.35), name="weights") biases = tf.Variable(tf.zeros([200]), name="biases") ... # Add an op to initialize the variables. init_op = tf.global_variables_initializer() # Later, when launching the model with tf.Session() as sess: # Run the init operation. sess.run(init_op) ... # Use the model ...
iii. 引數儲存及載入
採用TensorFlow訓練模型,需要將訓練得到的引數進行儲存,不然一關機,就一夜回到解放前了。TensorFlow採用Saver來儲存。一般在Session()建立之前,通過tf.train.Saver()
獲取Saver例項。
saver = tf.train.Saver()
變數的恢復使用saver
的restore
方法:
# Create some variables. v1 = tf.Variable(..., name="v1") v2 = tf.Variable(..., name="v2") ... # Add ops to save and restore all the variables. saver = tf.train.Saver() # Later, launch the model, use the saver to restore variables from disk, and # do some work with the model. with tf.Session() as sess: # Restore variables from disk. saver.restore(sess, "/tmp/model.ckpt") print("Model restored.") # Do some work with the model ...
在該Demo訓練時,也採用了Saver進行引數儲存。
# saving and loading networks saver = tf.train.Saver() checkpoint = tf.train.get_checkpoint_state("saved_networks") if checkpoint and checkpoint.model_checkpoint_path: saver.restore(sess, checkpoint.model_checkpoint_path) print("Successfully loaded:", checkpoint.model_checkpoint_path) else: print("Could not find old network weights")
首先載入CheckPointState檔案,然後採用saver.restore
對已存在引數進行恢復。
在該Demo中,每隔10000步,就對引數進行儲存:
# save progress every 10000 iterations if t % 10000 == 0: saver.save(sess, 'saved_networks/' + GAME + '-dqn', global_step=t)
iv. 實驗及樣本儲存
首先,根據ε 概率選擇一個Action。
# choose an action epsilon greedily readout_t = readout.eval(feed_dict={s: [s_t]})[0] a_t = np.zeros([ACTIONS]) action_index = 0 if t % FRAME_PER_ACTION == 0: if random.random() <= epsilon: print("----------Random Action----------") action_index = random.randrange(ACTIONS) a_t[random.randrange(ACTIONS)] = 1 else: action_index = np.argmax(readout_t) a_t[action_index] = 1 else: a_t[0] = 1 # do nothing
這裡,readout_t
是訓練資料為之前提到的四通道影像的模型輸出。a_t
是根據ε 概率選擇的Action。
其次,執行選擇的動作,並儲存返回的狀態、得分。
# run the selected action and observe next state and reward x_t1_colored, r_t, terminal = game_state.frame_step(a_t) x_t1 = cv2.cvtColor(cv2.resize(x_t1_colored, (80, 80)), cv2.COLOR_BGR2GRAY) ret, x_t1 = cv2.threshold(x_t1, 1, 255, cv2.THRESH_BINARY) x_t1 = np.reshape(x_t1, (80, 80, 1)) # s_t1 = np.append(x_t1, s_t[:,:,1:], axis = 2) s_t1 = np.append(x_t1, s_t[:, :, :3], axis=2) # store the transition in D D.append((s_t, a_t, r_t, s_t1, terminal))
經驗池D
儲存的是一個馬爾科夫序列。(s_t, a_t, r_t, s_t1, terminal)
分別表示t
時的狀態s_t
,執行的動作a_t
,得到的反饋r_t
,以及得到的下一步的狀態s_t1
和遊戲是否結束的標誌terminal
。
在下一訓練過程中,更新當前狀態及步數:
# update the old values s_t = s_t1 t += 1
重複上述過程,實現反覆實驗及樣本儲存。
v. 通過梯度下降進行模型訓練
在實驗一段時間後,經驗池D
中已經儲存了一些樣本資料後,就可以從這些樣本資料中隨機抽樣,進行模型訓練了。這裡設定樣本數為OBSERVE = 100000.
。隨機抽樣的樣本數為BATCH = 32
。
if t > OBSERVE: # sample a minibatch to train on minibatch = random.sample(D, BATCH) # get the batch variables s_j_batch = [d[0] for d in minibatch] a_batch = [d[1] for d in minibatch] r_batch = [d[2] for d in minibatch] s_j1_batch = [d[3] for d in minibatch] y_batch = [] readout_j1_batch = readout.eval(feed_dict={s: s_j1_batch}) for i in range(0, len(minibatch)): terminal = minibatch[i][4] # if terminal, only equals reward if terminal: y_batch.append(r_batch[i]) else: y_batch.append(r_batch[i] + GAMMA * np.max(readout_j1_batch[i])) # perform gradient step train_step.run(feed_dict={ y: y_batch, a: a_batch, s: s_j_batch} )
s_j_batch
、a_batch
、r_batch
、s_j1_batch
是從經驗池D
中提取到的馬爾科夫序列(Java童鞋羨慕Python的列表推導式啊),y_batch
為標籤值,若遊戲結束,則不存在下一步中狀態對應的Q值(回憶Q值更新過程),直接新增r_batch
,若未結束,則用摺合因子(0.99)和下一步中狀態的最大Q值的乘積,新增至y_batch
。
最後,執行梯度下降訓練,train_step的入參是s_j_batch
、a_batch
和y_batch
。差不多經過2000000步(在本機上大概10個小時)訓練之後,就能達到本文開頭動圖中的效果啦。
相關文章
- 玩遊戲學程式設計:Code.org推出Flappy Bird程式設計課遊戲程式設計APP
- Flappy Bird 惡意程式詳細分析APP
- Scratch3之AI整合 - flappy bird AI版本AIAPP
- 程式設計師大殺器?帶你玩轉ChatGPT程式設計師ChatGPT
- Flappy Bird成功之道:極簡設計 貴人相助APP
- Flappy Bird下架的真相APP
- 65行 JavaScript 程式碼實現 Flappy Bird 遊戲JavaScriptAPP遊戲
- 好程式設計師帶你認識“jQuery”程式設計師jQuery
- Flappy Bird 8月將重返App StoreAPP
- MFC實現桌面版Flappy BirdAPP
- 程式設計師如何玩轉《衝頂大會》?程式設計師
- 普通程式設計師如何入門AI程式設計師AI
- 程式設計師如何玩轉力扣刷題?程式設計師力扣
- Flappy Bird 的啟示:不要相信成功學APP
- Flappy Bird的啟示:不要相信成功學APP
- 程式設計師玩的遊戲程式設計師遊戲
- 普通程式設計師如何轉向AI方向程式設計師AI
- 教你從頭到尾利用DQN自動玩flappy bird(全程命令提示,GPU+CPU版)APPGPU
- @程式設計師,你該如何磨快你的鋸子程式設計師
- 程式設計師你是如何降低NPE的?程式設計師
- AI程式設計師薪資 5W+,你卻只能……AI程式設計師
- 教你用 HTML5 製作Flappy Bird(上)HTMLAPP
- 教你用HTML5製作Flappy Bird(一)HTMLAPP
- 教你用HTML5製作Flappy Bird(二)HTMLAPP
- 如果你是塞爾達的設計師,會如何一步步走到今天?
- 一步步帶你設計MySQL索引資料結構MySql索引資料結構
- 如果你不是程式設計師,你怎麼僱傭程式設計師呢程式設計師
- 程式設計師,請你不要在坑程式設計師了?程式設計師
- 1024 程式設計師節,帶你走程式序員的世界—程式設計師
- Flappy Bird開發者接受採訪,解釋上癮的危害APP
- 一步步分析:C語言如何物件導向程式設計C語言物件程式設計
- 架構師帶你玩轉分散式鎖架構分散式
- 程式設計師 你幸福嗎?程式設計師
- 程式設計師,你不是民工!程式設計師
- 好程式設計師帶你認識HTML5中的WebSocket程式設計師HTMLWeb
- 程式設計師你是如何使用映象中心Harbor的?程式設計師
- 如何讓你的程式設計師不要厭倦工作?程式設計師
- 騰訊大牛帶你上路 AI 工程師AI工程師