天天看點

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

以下内容來源于一次部門内部的分享,主要針對ai初學者,介紹包括cnn、deep q network以及tensorflow平台等内容。由于筆者并非深度學習算法研究者,是以以下更多從應用的角度對整個系統進行介紹,而不會進行詳細的公式推導。

關于flappy bird  flappy bird操作簡單,通過點選手機螢幕使bird上升,穿過柱狀障礙物之後得分,碰到則遊戲結束。由于障礙物高低不等,控制bird上升和下降需要反應快并且靈活,要得到較高的分數并不容易,筆者目前最多得過10分。

本文主要介紹如何通過ai(人工智能)的方式玩flappy bird遊戲,分為以下四個部分内容:

1. flappy bird 遊戲展示 2. 模型:卷積神經網絡 3. 算法:deep q network 4. 代碼:tensorflow實作

在介紹模型、算法前先來直接看下效果,上圖是剛開始訓練的時候,畫面中的小鳥就像無頭蒼蠅一樣亂飛,下圖展示的是在本機(後面會給出配置)訓練超過10小時後(訓練步數超過2000000)的情況,其最好成績已經超過200分,人類玩家已基本不可能超越。

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

訓練數小于10000步(剛開始訓練)

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

訓練步數大于2000000步(10小時後)

由于本機配置了 cuda 以及 cudnn,采用了 nvidia 的顯示卡進行并行計算,是以這裡提前貼一下運作時的日志輸出。

關于cuda以及cudnn的配置,其中有一些坑包括:安裝cuda之後循環登入,螢幕分辨率無法正常調節等等,都是由于nvidia驅動安裝的問題,這不是本文要讨論的主要内容,讀者可自行google。

● 加載cuda運算庫

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

加載cuda運算庫

● tensorflow運作裝置 /gpu:0

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

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(桌面占用了一部分),屬于入門中的入門。對于專業做深度學習算法的朋友,這個顯示卡必然是不夠的。知乎上有文章教大家怎麼配置更專業的顯示卡,有興趣的可以移步。

神經網絡算法是由衆多的神經元可調的連接配接權值連接配接而成,具有大規模并行處理、分布式資訊存儲、良好的自組織自學習能力等特點。人工神經元與生物神經元結構類似,其結構對比如下圖所示。

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

生物神經元

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

人工神經元

人工神經元的輸入(x1,x2...xm)類似于生物神經元的樹突,輸入經過不同的權值(wk1, wk2, ....wkn),加上偏置,經過激活函數得到輸出,最後将輸出傳輸到下一層神經元進行處理。

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

單神經元輸出函數

激活函數為整個網絡引入了非線性特性,這也是神經網絡相比于回歸等算法拟合能力更強的原因。常用的激活函數包括sigmoid、tanh等,它們的函數表達式如下:

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

sigmoid函數

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

tanh雙曲正切函數

這裡可以看出,sigmoid函數的值域是(0,1),tanh函數的值域是(-1,1)。

卷積神經網絡起源于動物的視覺系統,主要包含的技術是:

1. 局部感覺域(稀疏連接配接); 2. 參數共享; 3. 多卷積核; 4. 池化。

● 1. 局部感覺域(稀疏連接配接)

全連接配接網絡的問題在于:

1. 需要訓練的參數過多,容器導緻結果不收斂(梯度消失),且訓練難度極大;

2. 實際上對于某個局部的神經元來講,它更加敏感的是小範圍内的輸入,換句話說,對于較遠的輸入,其相關性很低,權值也就非常小。

人類的視覺系統決定了人在觀察外界的時候,總是從局部到全局。

比如,我們看到一個美女,可能最先觀察到的是美女身上的某些部位(自己體會)。

是以,卷積神經網絡與人類的視覺類似,采用局部感覺,低層的神經元隻負責感覺局部的資訊,在向後傳輸的過程中,高層的神經元将局部資訊綜合起來得到全局資訊。

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

全連接配接與局部連接配接的對比(圖檔來自網際網路)

從上圖中可以看出,采用局部連接配接之後,可以大大的降低訓練參數的量級。

● 2. 參數共享

雖然通過局部感覺降低了訓練參數的量級,但整個網絡需要訓練的參數依然很多。

參數共享就是将多個具有相同統計特征的參數設定為相同,其依據是圖像中一部分的統計特征與其它部分是一樣的。其實作是通過對圖像進行卷積(卷積神經網絡命名的來源)。

可以了解為,比如從一張圖像中的某個局部(卷積核大小)提取了某種特征,然後以這種特征為探測器,應用到整個圖像中,對整個圖像順序進行卷積,得到不同的特征。

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

卷積過程(圖檔來自網際網路)

每個卷積都是一種特征提取方式,就像一個篩子,将圖像中符合條件(激活值越大越符合條件)的部分篩選出來,通過這種卷積就進一步降低訓練參數的量級。

● 3. 多卷積核

如上,每個卷積都是一種特征提取方式,那麼對于整幅圖像來講,單個卷積核提取的特征肯定是不夠的,那麼對同一幅圖像使用多種卷積核進行特征提取,就能得到多幅特征圖(feature map)。

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

不同的卷積核提取不同的特征(圖檔來自網際網路)

多幅特征圖可以看成是同一張圖像的不同通道,這個概念在後面代碼實作的時候用得上。

● 4. 池化

得到特征圖之後,可以使用提取到的特征去訓練分類器,但依然會面臨特征次元過多,難以計算,并且可能過拟合的問題。從圖像識别的角度來講,圖像可能存在偏移、旋轉等,但圖像的主體卻相同的情況。也就是不同的特征向量可能對應着相同的結果,那麼池化就是解決這個問題的。

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

池化過程(圖檔來自網際網路)

池化就是将池化核範圍内(比如2*2範圍)的訓練參數采用平均值(平均值池化)或最大值(最大值池化)來進行替代。

終于到了展示模型的時候,下面這幅圖是筆者手畫的(用電腦畫太費時,将就看吧),這幅圖展示了本文中用于訓練遊戲所用的卷積神經網絡模型。

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

卷積神經網絡模型

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

圖像的處理過程

1. 初始輸入四幅圖像80×80×4(4代表輸入通道,初始時四幅圖像是完全一緻的),經過卷積核8×8×4×32(輸入通道4,輸出通道32),步距為4(每步卷積走4個像素點),得到32幅特征圖(feature map),大小為20×20;

2. 将20×20的圖像進行池化,池化核為2×2,得到圖像大小為10×10;

3. 再次卷積,卷積核為4×4×32×64,步距為2,得到圖像5×5×64;

4. 再次卷積,卷積核為3×3×64*64,步距為2,得到圖像5×5×64,雖然與上一步得到的圖像規模一緻,但再次卷積之後的圖像資訊更為抽象,也更接近全局資訊;

5. reshape,即将多元特征圖轉換為特征向量,得到1600維的特征向量;

6. 經過全連接配接1600×512,得到512維特征向量;

7. 再次全連接配接512×2,得到最終的2維向量[0,1]和[1,0],分别代表遊戲螢幕上的是否點選事件。

可以看出,該模型實作了端到端的學習,輸入的是遊戲螢幕的截圖資訊(代碼中經過opencv處理),輸出的是遊戲的動作,即是否點選螢幕。深度學習的強大在于其資料拟合能力,不需要傳統機器學習中複雜的特征提取過程,而是依靠模型發現資料内部的關系。

不過這也帶來另一方面的問題,那就是深度學習高度依賴大量的标簽資料,而這些資料擷取成本極高。

有了卷積神經網絡模型,那麼怎樣訓練模型?使得模型收斂,進而能夠指導遊戲動作呢?機器學習分為監督學習、非監督學習和強化學習,這裡要介紹的q network屬于強化學習(reinforcement learning)的範疇。在正式介紹q network之前,先簡單說下它的光榮曆史。

2014年google 4億美金收購deepmind的橋段,大家可能聽說過。那麼,deepmind是如何被google給盯上的呢?最終原因可以歸咎為這篇論文:

<a href="https://arxiv.org/abs/1312.5602" target="_blank">playing atari with deep reinforcement learning</a>

deepmind團隊通過強化學習,完成了20多種遊戲,實作了端到端的學習。其用到的算法就是q network。2015年,deepmind團隊在《nature》上發表了一篇更新版:

<a href="http://diyhpl.us/~nmz787/pdf/human-level_control_through_deep_reinforcement_learning.pdf" target="_blank">human-level control through deep reinforcement learning</a>

自此,在這類遊戲領域,人已經無法超過機器了。後來又有了alphago,以及master,當然,這都是後話了。其實本文也屬于上述論文的範疇,隻不過基于tensorflow平台進行了實作,加入了一些筆者自己的了解而已。

回到正題,q network屬于強化學習,那麼先介紹下強化學習。

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

強化學習模型

這張圖是從ucl的課程中拷出來的,課程連結位址(youtube):

強化學習過程有兩個組成部分:

● 智能代理(學習系統) ● 環境

如圖所示,在每步疊代過程中,首先智能代理(學習系統)接收環境的狀态st,然後産生動作at作用于環境,環境接收動作at,并且對其進行評價,回報給智能代理rt。不斷的循環這個過程,就會産生一個狀态/動作/回報的序列:(s1, a1, r1, s2, a2, r2.....,sn, an, rn),而這個序列讓我們很自然的想起了:

● 馬爾科夫決策過程

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

mdp:馬爾科夫決策過程

馬爾科夫決策過程與著名的hmm(隐馬爾科夫模型)相同的是,它們都具有馬爾科夫特性。那麼什麼是馬爾科夫特性呢?簡單來說,就是未來的狀态隻取決于目前的狀态,與過去的狀态無關。

hmm(馬爾科夫模型)在語音識别,行為識别等機器學習領域有較為廣泛的應用。條件随機場模型(conditional random field)則用于自然語言處理。兩大模型是語音識别、自然語言處理領域的基石。

上圖可以用一個很形象的例子來說明。比如你畢業進入了一個公司,你的初始職級是t1(對應圖中的 s1),你在工作上刻苦努力,追求上進(對應圖中的a1),然後上司覺得你不錯,準備給你升職(對應圖中的r1),于是,你升到了t2;你繼續刻苦努力,追求上進......不斷的努力,不斷的升職,最後升到了sn。當然,你也有可能不努力上進,這也是一種動作,換句話說,該動作a也屬于動作集合a,然後得到的回報r就是沒有升職加薪的機會。

這裡注意下,我們當然希望擷取最多的升職,那麼問題轉換為:如何根據目前狀态s(s屬于狀态集s),從a中選取動作a執行于環境,進而擷取最多的r,即r1 + r2 ……+rn的和最大 ?這裡必須要引入一個數學公式:狀态值函數。

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

狀态值函數模型

公式中有個折合因子γ,其取值範圍為[0,1],當其為0時,表示隻考慮目前動作對目前的影響,不考慮對後續步驟的影響,當其為1時,表示目前動作對後續每步都有均等的影響。當然,實際情況通常是目前動作對後續得分有一定的影響,但随着步數增加,其影響減小。

從公式中可以看出,狀态值函數可以通過疊代的方式來求解。增強學習的目的就是求解馬爾可夫決策過程(mdp)的最優政策。

政策就是如何根據環境選取動作來執行的依據。政策分為穩定的政策和不穩定的政策,穩定的政策在相同的環境下,總是會給出相同的動作,不穩定的政策則反之,這裡我們主要讨論穩定的政策。

求解上述狀态函數需要采用動态規劃的方法,而具體到公式,不得不提:

● 貝爾曼方程

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

貝爾曼方程

其中,π代表上述提到的政策,q π (s, a)相比于v π (s),引入了動作,被稱作動作值函數。對貝爾曼方程求最優解,就得到了貝爾曼最優性方程。

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

狀态值函數最優解

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

動作值函數最優解

求解該方程有兩種方法:政策疊代和值疊代。

● 政策疊代

政策疊代分為兩個步驟:政策評估和政策改進,即首先評估政策,得到狀态值函數,其次,改進政策,如果新的政策比之前好,就替代老的政策。

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

政策疊代

● 值疊代

從上面我們可以看到,政策疊代算法包含了一個政策估計的過程,而政策估計則需要掃描(sweep)所有的狀态若幹次,其中巨大的計算量直接影響了政策疊代算法的效率。而值疊代每次隻掃描一次,更新過程如下:

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

值疊代

即在值疊代的第k+1次疊代時,直接将能獲得的最大的vπ(s)值賦給vk+1。

● q-learning

q-learning是根據值疊代的思路來進行學習的。該算法中,q值更新的方法如下:

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

q值更新方法

雖然根據值疊代計算出目标q值,但是這裡并沒有直接将這個q值(是估計值)直接賦予新的q,而是采用漸進的方式類似梯度下降,朝目标邁近一小步,取決于α,這就能夠減少估計誤差造成的影響。類似随機梯度下降,最後可以收斂到最優的q值。具體算法如下:

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

q-learning算法

如果沒有接觸過動态規劃的童鞋看上述公式可能有點頭大,下面通過表格來示範下q值更新的過程,大家就明白了。

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

q-learning算法的過程就是存儲q值的過程。上表中,橫列為狀态s,縱列為action a,s和a決定了表中的q值。

● 第一步:初始化,将表中的q值全部置0;

● 第二步:根據政策及狀态s,選擇a執行。假定目前狀态為s1,由于初始值都為0,是以任意選取a執行,假定這裡選取了a2執行,得到了reward為1,并且進入了狀态s3。根據q值更新公式:

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

q值更新公式

來更新q值,這裡我們假設α是1,λ也等于1,也就是每一次都把目标q值賦給q。那麼這裡公式變成:

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

是以在這裡,就是

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

本次q值更新

那麼對應的s3狀态,最大值是0,是以

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

q值

q表格就變成:

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

然後置位目前狀态s為s3。

● 第三步:繼續循環操作,進入下一次動作,目前狀态是s3,假設選擇動作a3,然後得到reward為2,狀态變成s1,那麼我們同樣進行更新:

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

q值更新

是以q的表格就變成:

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

● 第四步: 繼續循環,q值在試驗的同時反複更新,直到收斂。

上述表格示範了具有4種狀态/4種行為的系統,然而在實際應用中,以本文講到的flappy bird遊戲為例,界面為80*80個像素點,每個像素點的色值有256種可能。那麼實際的狀态總數為256的80*80次方,這是一個很大的數字,直接導緻無法通過表格的思路進行計算。

是以,為了實作降維,這裡引入了一個價值函數近似的方法,通過一個函數表近似表達價值函數:

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

價值函數近似

其中,ω 與 b 分别為參數。看到這裡,終于可以聯系到前面提到的神經網絡了,上面的表達式不就是神經元的函數嗎?

● q-network

下面這張圖來自論文《human-level control through deep reinforcement learning》,其中詳細介紹了上述将q值神經網絡化的過程。(感興趣的可以點之前的連結了解原文~)

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

q-network

以本文為例,輸入是經過處理的4個連續的80x80圖像,然後經過三個卷積層,一個池化層,兩個全連接配接層,最後輸出包含每一個動作q值的向量。

現在已經将q-learning神經網絡化為q-network了,接下來的問題是如何訓練這個神經網絡。神經網絡訓練的過程其實就是一個最優化方程求解的過程,定義系統的損失函數,然後讓損失函數最小化的過程。

訓練過程依賴于上述提到的dqn算法,以目标q值作為标簽,是以,損失函數可以定義為:

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

dqn損失函數(來源于論文)

上面公式是s',a'即下一個狀态和動作。确定了損失函數,确定了擷取樣本的方式,dqn的整個算法也就成型了!

還記得 Flappy Bird 麼?這篇文章教你如何用神經網絡破朋友圈紀錄!

dqn算法(來源于論文)

值得注意的是這裡的d—experience replay,也就是經驗池,就是如何存儲樣本及采樣的問題。

由于玩flappy bird遊戲,采集的樣本是一個時間序列,樣本之間具有連續性,如果每次得到樣本就更新q值,受樣本分布影響,效果會不好。是以,一個很直接的想法就是把樣本先存起來,然後随機采樣如何?這就是experience replay的思想。

算法實作上,先反複實驗,并且将實驗資料存儲在d中;存儲到一定程度,就從中随機抽取資料,對損失函數進行梯度下降。

終于到了看代碼的時候。首先申明下,當筆者從deep mind的論文入手,試圖用tensorflow實作對flappy bird遊戲進行實作時,發現github已有大神完成demo。思路相同,是以直接以公開代碼為例進行分析說明了。

代碼從結構上來講,主要分為以下幾部分:

● gamestate遊戲類,frame_step方法控制移動 ● cnn模型建構 ● opencv-python圖像預處理方法 ● 模型訓練過程

1. gamestate遊戲類及frame_step方法

後續操作包括檢查得分、設定界面、檢查是否碰撞等,這裡不再詳細展開。

frame_step方法的傳回值是:

return image_data, reward, terminal

分别表示界面圖像資料,得分以及是否結束遊戲。對應前面強化學習模型,界面圖像資料表示環境狀态 s,得分表示環境給予學習系統的回報 r。

2. cnn模型建構

該demo中包含三個卷積層,一個池化層,兩個全連接配接層,最後輸出包含每一個動作q值的向量。是以,首先定義權重、偏置、卷積和池化函數:

然後,通過上述函數建構卷積神經網絡模型(對代碼中參數不解的,可直接往前翻,看上面那張手畫的圖)。

3. opencv-python圖像預處理方法

在ubuntu中安裝opencv的步驟比較麻煩,當時也踩了不少坑,各種google解決。建議安裝opencv3。

這部分主要對frame_step方法傳回的資料進行了灰階化和二值化,也就是最基本的圖像預處理方法。

4. dqn訓練過程

這是代碼部分要講的重點,也是上述q-learning算法的代碼化。

i. 在進入訓練之前,首先建立一些變量:

在tensorflow中,通常有三種讀取資料的方式:feeding、reading from files和preloaded data。feeding是最常用也最有效的方法。即在模型(graph)建構之前,先使用placeholder進行占位,但此時并沒有訓練資料,訓練是通過feed_dict傳入資料。

這裡的a表示輸出的動作,即強化學習模型中的action,y表示标簽值,readout_action表示模型輸出與a相乘後,在一維求和,損失函數對标簽值與輸出值的差進行平方,train_step表示對損失函數進行adam優化。

指派的過程為:

ii. 建立遊戲及經驗池 d

經驗池 d采用了隊列的資料結構,是tensorflow中最基礎的資料結構,可以通過dequeue()和enqueue([y])方法進行取出和壓入資料。經驗池 d用來存儲實驗過程中的資料,後面的訓練過程會從中随機取出一定量的batch進行訓練。

變量建立完成之後,需要調用tensorflow系統方法tf.global_variables_initializer()添加一個操作實作變量初始化。運作時機是在模型建構完成,session建立之初。比如:

iii. 參數儲存及加載

采用tensorflow訓練模型,需要将訓練得到的參數進行儲存,不然一關機,就一夜回到解放前了。tensorflow采用saver來儲存。一般在session()建立之前,通過tf.train.saver()擷取saver執行個體。

saver = tf.train.saver()

變量的恢複使用saver的restore方法:

在該demo訓練時,也采用了saver進行參數儲存。

首先加載checkpointstate檔案,然後采用saver.restore對已存在參數進行恢複。

在該demo中,每隔10000步,就對參數進行儲存:

iv. 實驗及樣本存儲

首先,根據ε 機率選擇一個action。

這裡,readout_t是訓練資料為之前提到的四通道圖像的模型輸出。a_t是根據ε 機率選擇的action。

其次,執行選擇的動作,并儲存傳回的狀态、得分。

經驗池d儲存的是一個馬爾科夫序列。(s_t, a_t, r_t, s_t1, terminal)分别表示t時的狀态s_t,執行的動作a_t,得到的回報r_t,以及得到的下一步的狀态s_t1和遊戲是否結束的标志terminal。

在下一訓練過程中,更新目前狀态及步數:

重複上述過程,實作反複實驗及樣本存儲。

v. 通過梯度下降進行模型訓練

在實驗一段時間後,經驗池d中已經儲存了一些樣本資料後,就可以從這些樣本資料中随機抽樣,進行模型訓練了。這裡設定樣本數為observe = 100000.。随機抽樣的樣本數為batch = 32。

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個小時)訓練之後,就能達到本文開頭動圖中的效果啦。

以上。

====================================分割線================================

本文作者:ai研習社

繼續閱讀