翻譯 | 彭碩,姜沂,reason_W
編校 | reason_W
DeepMind開源《星際2》AI平臺,OpenAI人工智慧系統打敗Dota2遊戲頂級玩家……越來越多的科技巨頭開始進入到遊戲AI的領域,並相繼開放了他們的介面和資料集。複雜的訓練資料,即時多變的對戰環境,對多智慧體協作能力的要求等等使得《星際爭霸》這樣的遊戲被稱為通用智慧的關鍵,預示著AI將在越來越真實的混亂環境裡向人類的心智靠近。
那麼小白玩家該如何入坑遊戲AI呢?遊戲AI到底是如何和遊戲進行介面互動,判斷角色狀態,執行動作,規劃策略的呢?
本期推送AI科技大本營(微信ID:rgznai100)精選了國外一位博主遊戲AI系列的文章,來,手把手開始搭建一個遊戲AI。
快到碗裡來吧~
基於深入學習和其他機器學習技術,我們將為“流亡之路”(以下簡稱PoE)打造一個遊戲AI。本篇教程總共分為5個階段,下面是原貼列表。
-
第一部分:概述
-
第二部分:為《流放之路》標定投影矩陣
-
第三部分:移動和導航
-
第四部分:實時螢幕捕捉及底層命令
-
第五部分:基於TensorFlow的CNN實時障礙物和敵對目標檢測
(由於我們把五部分整合成了一篇文章,因此本文共分5部分,20小節,8300+字,閱讀花費約17分鐘。)
我們這個專案的目標是打造一個基於視覺輸入的遊戲AI,它可以成功地在遊戲地圖中進行自主巡航和自主防禦。當然,你也可以在這個過程中一邊玩遊戲,一邊學習打造遊戲AI的樂趣。
第一部分:概述
本部分共2節,原post連結
A Deep Learning Based AI for Path of Exile: A Series
https://nicholastsmith.wordpress.com/2017/07/08/a-deep-learning-based-ai-for-path-of-exile-a-series/
PoE是一款和暗黑破壞神、泰坦之旅等的暗黑型RPG類似的動作類遊戲,遊戲截圖如下圖1所示:
圖1:遊戲截圖
玩家主要是通過滑鼠進行人物移動、打怪、開箱等和遊戲的互動。也可以設定鍵盤熱鍵來進行特殊攻擊、加藍和其他選單快捷鍵。
1.頂層設計
我們打造這個AI的想法是利用卷積神經網路(CNN)對遊戲中的影像進行分類,從而建立遊戲世界的內部表徵。通過這個內部表徵來引導遊戲世界內的角色。下面這個流程圖表示出了遊戲AI的基本設計思路:
圖2:人工智慧邏輯的流程圖
AI程式的主迴圈會不停滴從遊戲中獲取一個靜態的影像,並將它傳遞給一個CNN。CNN會預測這幅靜態的影像中正在發生什麼。這些預測隨後被傳遞給世界內部地圖,並根據最新的預測更新世界內部地圖。接下來,遊戲AI會根據當前世界內部地圖的狀態,進行一系列的動作。最後,這些動作被轉換成滑鼠和鍵盤輸入,然後傳送給滑鼠和鍵盤。這個迴圈是在不停重複的。是不是聽起來很容易?沒錯。
我們選擇Python(3.6)作為這次的程式語言。主要使用的庫是
-
scikit-learn
-
TensorFlow
-
PyUserInput
-
win32gui
-
scikit-image
接下來幾部分,我們將研究如何進一步分解上述任務並一步步實現它們。
2.免責宣告
PoE的logo、美工均屬於遊戲開放商Grinding Gear Games(GGG)的財產,作者與GGG沒有任何關係,作者的思想和觀點並不代表GGG的觀點。本文的目的是探索人工智慧和深度學習,沒有侵犯版權或服務條款的行為。
第二部分:為《流放之路》標定投影矩陣
本部分共5節,原post連結:
Calibrating a Projection Matrix for Path of Exile
https://nicholastsmith.wordpress.com/2017/07/09/calibrating-a-projection-matrix-for-path-of-exile/
在這一部分中,我們將探索如何利用遊戲靜態畫面,更新其世界的內部表徵。
1.視覺輸入的挑戰
讓遊戲AI與視覺輸入進行互動的一個難點在於影像資料是2D的,但是(遊戲)世界是3D的。所以最可能的是,遊戲引擎在3D環境中使用它自己的世界內部表徵,然後使用投影技術將遊戲渲染為2D並顯示在螢幕上。通過逆向工程,從遊戲世界的表徵中獲得資料非常有用,但是因為我們的最終目的是要打造遊戲AI,所以暫時不對這個逆向工程進行深入的探索。
為了更精確地模擬世界,遊戲的投影矩陣要和世界儘可能地相似。這個矩陣被用來確定與螢幕上的2D座標相對應的3D座標,(再進行一些假設)反之亦然。圖3說明了投影對映的基本概念。左矩形表示螢幕,而右座標軸代表世界座標。灰線(投影對映)將藍點從世界座標對映到螢幕上的位置。
圖3:投影影射
給定2D影像來近似投影矩陣的過程被稱為相機標定。
2.相機標定
相機標定是通過一幅包含一個(已知三維空間尺寸的)物體的影像來完成的。從三維座標到二維座標的對映,構造了一種求解變換矩陣的優化問題。這個思想可以表示為在方程1。
方程式1:投影轉換
在上面的方程中,A是投影矩陣,w是世界點(3D)座標矩陣,而p是投影點(2D)座標矩陣。
為了標定PoE的相機,也就是確定上面等式中的 A,我們會使用幾個固定大小的箱子。標定相機的過程如下面的截圖所示。
請注意,這些箱子的大致頂角是用一個點指定的,並將其標記為對應的世界點。這個過程比較麻煩,而且需要手動進行。我們把畫面中間那個箱子的右下角指定成座標原點(0,0,0)(小編注:當然這個原點可以隨意指定,這裡是為了方便),並且假設這個箱子是一個單元立方體。箱子之間的間距也是有單位長度的。另外一個注意的點是,這個投影是用於解析度為800*600的螢幕的,其他螢幕解析度的話,畫素大小會發生變化,需要重新標定。
其資料點集合(縮寫)如表1所示:世界點座標&投影點座標
接下來,我們構建一個轉換矩陣A,將3D點投射到2D的點上。
3.執行擬合
我們通過TensorFlow構建一個非線性擬合,注意:將標定問題看成一個齊次最小二乘問題的方法是比較常見的;Adam 方法看起來可以為這個特殊的影像提供更好的結果。
(點選檢視大圖)
執行上述程式碼所產生的投影矩陣如等式2所示。由於初始化的差異,最終的值可能會稍有不同。
方程式2:得到的投影矩陣
用等式3給的公式,可以在世界座標中恢復出相機上的位置C。注意,Q是一個3×3矩陣,m是一個3×1矩陣。
方程式3:相機位置的恢復
下面的程式碼對投影矩陣進行擬合,恢復出來的相機位置用變數CP表示。
4.結果
恢復出來的的相機位置是 (5.322,-4.899,10.526)。現在再回頭看看一開始的截圖,這個值和我們直覺上感受的方向是一致的。世界空間座標分別以一個箱子的高度、寬度和深度作為單位長度。因此,相機位置大概是在x軸正方向上5個箱子長度,y軸負方向4個箱子長度,z軸正方向上10個箱子長度處。 利用這個投影矩陣,我們就可以把點投影到原始影像上了。下圖展示了怎樣把一個xy平面表示的網格點投影到原始影像上。
圖5:原始影像和將投影的XY平面
上面的投影看起來還蠻合理的。在投影矩陣標定好的情況下,可以使用下面的函式把一個三維點(每一行為1點)矩陣投影到平面上。
(點選檢視大圖)
5.假設和平移
如果假定角色僅在xy平面上移動,那麼角色的3D位置就可以通過角色的畫素座標恢復。我們假設z=0,然後在投影方程中解出x和y,就可以給出這個角色的畫素座標。完成此任務的程式碼如下。
(點選檢視大圖)
在上述兩個函式中,投影矩陣的轉置計算是影響效率的主要因素。有了以上兩個函式之後,我們就可以用下面的程式碼計算在800*600螢幕上xy平面的網格點。下面這個函式將是後面跟蹤玩家在一級平面上位置的關鍵。
(點選檢視大圖)
在PoE中,當玩家移動時,相機也會移動(相機角度固定)。為了跟蹤移動的相機和玩家,世界點在被投影之前會被轉平移回原始位置。在實際中,這是通過將投影矩陣乘以一個平移矩陣得到最終的投影矩陣來實現的。方程4中顯示了一個平移矩陣,它可以用向量(x,y,z)來表示一組點的平移。
方程式4:一個平移矩陣
我們可以利用matplotlib(https://matplotlib.org/)來構造一個XY平面的動畫,它模擬了世界中角色的運動。在下面的動畫中,相機通過幾個隨機產生的點進行線性移動。
有了上述程式碼,螢幕上的距離就可以更精確了。為了簡單起見,我們假設玩家總是在XY平面上移動。然而,在某些高度上,這並不是一個可靠的假設。考慮到AI的效能,這一部分可能需要重新考慮。
第三部分:移動和導航
本部分共4節,本節是文章的第三部分:PoE AI Part 3: Movement and Navigation
原post連結:
https://nicholastsmith.wordpress.com/2017/07/18/poe-ai-part-3-movement-and-navigation/
這一部分我們主要探索在同一高度的平面上移動遊戲角色的技巧。
1.移動地圖類
在PoE中,玩家移動角色一般會通過單擊某個位置來實現,接著角色就會移動到滑鼠點選的位置。圖7展示了通過點選滑鼠移動角色的一個例子。
現在,為了完成導航,AI會維持一個代表世界地圖的資料結構。賦予座標和位置型別之間一種對應關係,就可以實現這一點。例如,在給定的時間,在其內部地圖中,AI可能就具有表2所示的資料。
世界點座標 & 型別
表2:內部地圖
地圖會記錄已訪問的位置及其型別。這個型別標記的是,玩家能否移動到該位置。位置型別分別為“開放”型別或“障礙”型別。有了這樣的地圖,就可以使用廣度優先遍歷找到從一個位置到另一個位置的最短路徑。
2.維度之間的對映
現在,我們假設玩家在位置(0,0,0),並且要移動到(1,1,0)。應該怎麼用滑鼠在螢幕上進行操作呢?想一下前幾部分的內容,一個標定好的投影矩陣,能讓我們在3D座標中更準確地逼近玩家的位置。因此,利用投影矩陣來變換該點(1,1,0)就可以確定其在螢幕上的位置。這就是滑鼠要點選的位置。
在實際中,我發現,在玩家為角色指定移動的目標點時,位移技能其實很不準確。特別是當我們在障礙物上單擊時。在這種情況下,角色通常會移動到單擊位置的附近。下面這幅圖就是一個這樣的例子。
圖8:向障礙物移動
這幅圖顯示了在障礙物上點選滑鼠的結果。請注意,玩家雖然會向滑鼠點選的地方移動,但到了障礙物面前就會停下來。
3.閃電傳送
不幸的是,像圖8所示的情況可能會導致AI的內部地圖與現實不一致。為了解決這個問題,我決定用閃電傳送來移動。圖9展示了3次傳送的效果。
圖9:閃電傳送
在角色移動方面,閃電傳送的優點是在運動的結果只有兩項,易於確定; 即玩家移動到了指定位置或者玩家沒有移動到指定位置。這有助於將AI的位置保持在其內部地圖中,並且和玩家的實際位置保持同步。因此,為了移動到位置x,AI首先將點x投影到螢幕上,然後將滑鼠移動到該位置,並觸發適當的鍵執行閃電傳送。
4.運動檢測器
現在,我們剩下的唯一一個挑戰就是檢測傳送是否成功執行。如果在障礙物上方單擊,角色就不會執行傳送。為了準確地預測這一點,我們構建了一個二值分類器,它將螢幕畫面的一部分作為輸入,並預測當前是否發生傳送。程式首先從畫面中將角色周圍70×70的矩形提取出來,作為模型的輸入。
為了構建模型,我們用遊戲靜態影像來手動構造資料集。圖10顯示了從資料集中取出的樣本。
圖10:閃電傳送分類器資料
執行預測的程式碼如下。以下程式碼假定檔案 LWTrain.csv 有多行這樣的格式:檔名,Y / N。在每行中,filename是上述影像檔案的路徑,Y表示影像顯示正在執行傳送,而N表示相反,表示沒有傳送。
(點選檢視大圖)
因此,在觸發適當的鍵之後,AI會(重複地)呼叫 DetectLW 函式來檢查移動是否成功。成功後,角色在地圖上的位置就會更新。如果在一定時間內沒有檢測到傳送,則假定移動失敗,玩家在地圖上的位置也就不會改變。
第四部分:實時螢幕捕捉和底層命令
本節是文章的第四部分:PoE AI Part 4:
Real-Time Screen Capture and Plumbing
原post連結
https://nicholastsmith.wordpress.com/2017/08/10/poe-ai-part-4-real-time-screen-capture-and-plumbing/
正如我們在本系列第一部分中說的那樣,AI程式會獲取遊戲的螢幕截圖,並使用它來進行預測,以更新其內部狀態。這一部分中,我們將探索捕捉遊戲截圖的方法。
1.可用的庫
有很多Python庫都可以用來捕捉遊戲截圖,比如 pyscreenshot(https://pypi.python.org/pypi/pyscreenshot)和 ImageGrab from PIL(http://pillow.readthedocs.io/en/3.1.x/reference/ImageGrab.html)。這裡有一個簡單的程式可以來測試影像捕捉的效果。程式碼如下:
(點選檢視大圖)
不幸的是,測試的效果看起來實在差強人意。要是沒有其他處理的話,頂多只能盼著這程式每秒處理5到6幀的畫面。此外,在上面的程式碼中,螢幕捕捉要在主執行緒上執行。所以整個程式要等待獲取到影像,在此期間不會和遊戲發生處理或互動。另一個問題是,捕捉到的應該只有遊戲畫面(在視窗模式下),而不應該包括電腦桌面上其餘的部分。
2.使用Windows API
我們可以通過呼叫幾個Windows API來減輕這些問題,並提高程式效能。
(點選檢視大圖)
在上面的GetHWND函式中,我們使用了win32gui.FindWindow(None,wname)來獲取遊戲視窗的控制程式碼。在這種情況下,wname應該是“Path of Exile”或win32gui.FindWindow(None,“Path of Exile”)。
遊戲視窗的控制程式碼,win32gui.GetWindowRect(self.hwnd) 給出了遊戲視窗在螢幕上的位置。這些值對於將遊戲視窗(大小800×600)中滑鼠的移動轉換為螢幕上的絕對值(通常類似於1920×1080)是很必要的。
上面的GetScreenImg函式是用來實際捕獲遊戲畫面影像的,並將其儲存在numpy矩陣中的程式碼。上述程式碼的有3個主要注意事項。首先,遊戲視窗有一個對AI程式沒有用的邊框,可以丟棄。變數self.bl,self.br,self.bt和self.bb分別儲存視窗的左,右,頂部和底部的邊框。第二,影像的邊緣需要丟棄一些畫素,使得影像的高度和寬度分別為7和9的倍數。其原因我們將在本系列的下一部分中進行介紹。第三,來自Windows API的點陣圖資料分別被組織為4個8位整數BGRA,分別代表藍色,綠色,紅色和阿爾法通道。大多數python影像庫都需要像RGB這樣的3個通道來顯示影像。 GetScreenImg中的最後一行會反轉通道的順序並丟棄Alpha通道,這裡沒有使用。
3.使用並行
由於遊戲需要不停地捕捉畫面影像,所以我們會把捕捉程式單獨寫進一個執行緒,並以非同步和執行緒安全的方式為其他執行緒提供一個讀取影像的介面。這樣一來畫面影像就總是可以即時獲取。這一操作可以通過執行緒庫中的執行緒和鎖定物件來實現。
(點選檢視大圖)
4.結果
為了對新的程式碼進行計時,應該在ScreenUpdateT函式中進行測量。這裡有一個快速但不考慮後果的方法,最終計時程式如下:
(點選檢視大圖)
時間變快了一個數量級。 現在,AI處理速度的理論最大值大約為64 FPS。 主AI程式使用與以下程式碼相似的ScreenViewer型別的資料成員訪問畫面影像。
(點選檢視大圖)
本系列的最後一部分將介紹如何使用卷積神經網路(CNN)來處理畫面的影像以更新AI的狀態。(小編注:終於快完了…)
第五部分:基於TensorFlow的CNN實時障礙物和敵對目標檢測
本節是文章的第五小節:AI Plays Path of Exile Part 5: Real-Time Obstacle and Enemy Detection using CNNs in TensorFlow
原post連結
https://nicholastsmith.wordpress.com/2017/08/25/poe-ai-part-5-real-time-obstacle-and-enemy-detection-using-cnns-in-tensorflow
正如我們在本系列第一部分中說的那樣,AI程式會獲取遊戲的螢幕截圖,並使用它來進行預測,以更新其內部狀態。這部分中,我們將討論從遊戲畫面獲取視覺輸入,並對資訊進行分類和識別的方法。我已經把原始碼放到了我的github(https://github.com/nicholastoddsmith/poeai)上,開心O(∩_∩)O~~
1.分類系統架構圖
回憶一下第三部分的文章,移動地圖維持了一個從3D點到標籤的字典。例如,在給定時間內,機器人在內部地圖上可能具有表3所示的資料。
世界點座標 & 投影點
表3:內部地圖
回憶一下第二部分的內容,投影地圖類允許畫面上的任何畫素對映到3D座標(假設玩家總是在xy平面上,然後該3D座標會被量化為某個任意精度,讓AI的世界地圖變成均勻間隔網格的點)。
因此,我們需要的是能夠識別螢幕上的給定畫素到底是障礙物的一部分、敵人還是物品等的方法。這個任務本質上是目標檢測。而實時目標檢測其實是一個困難且計算複雜度很高的問題。 這裡我們會介紹一種簡化的方案,可以在效能和精度之間實現很好的平衡。
為了簡化目標檢測任務,遊戲畫面被劃分成相等大小的矩形區域。對於800×600解析度的畫面,我們選擇由m = 7行和n = 9列組成的網格。從畫面的底部,左側和右側邊緣分別移除十二個,四個和四個畫素,使得所得到的尺寸(792和588)能夠分別被9和7整除。因此,螢幕網格中的每個矩形的寬度和高度分別為88和84畫素。圖2展示出了使用上述方案分割的遊戲畫面影像。
判斷畫面單元格內是否包含障礙物或開放的分類任務使用了一個卷積神經網路(CNN)。障礙物意味著有一些佔用單元格的東西,使得玩家不能站在那裡(例如巨石)。開放和閉合單元格的例項如圖3所示。
圖14:影像單元格標籤
識別物品和敵人的任務第二次使用了CNN。給定畫面上的單元格,CNN將單元格分類為包含敵人,物品還是什麼也不包含。
為了只瞄準活著的敵人,判斷是否發生移動的二進位制分類器第三次使用了CNN。 給定畫面上的單元格,第三個CNN確定單元格中是否發生移動。只有包含移動的單元格才能傳入第二個CNN。這個CNN然後預測這些單元格是否包含物品或敵人。通過在連續畫面截圖中切換物品的突出顯示來檢測物品標籤的移動。
用於移動檢測的影像資料是通過快速連續地捕獲畫面的2幀影像並且僅保留影像中顯著不同的區域得到的。這是使用numpy.where函式實現的(16是任意選擇的閾值)。
總而言之,從遊戲畫面中捕獲的截圖將輸入到3個CNN中的每一個之中。第一個CNN檢測畫面單元格中的障礙物。然後在運動圖中相應地標記畫面上每個單元格內的3D網格點。內部地圖保留每個單元格的預測結果,並在查詢單元格時報告預測最頻繁的類別。第二和第三個CNN需要聯合使用以檢測敵人和物品。
2.資料集
使用ScreenViewer類獲取的畫面截圖,來手動構建訓練資料集。目前,該資料集僅包含遊戲行為4中的“Dried Lake”級資料。資料集由11個資料夾中的14,000多個檔案組成,大小為164MB。 資料集的截圖如圖4所示。
圖15:訓練資料集
在資料集中,Closed資料夾中的影像是包含障礙物的單元格。 第一個CNN使用資料夾Closed,Open和Enemy。第二個CNN使用資料夾Open,Enemy和Item。 第三個CNN使用資料夾Move和NoMove。
3.訓練
AI採用了稍微適度的CNN架構,卷積和池化層的兩個序列之後是3個全連線層。該架構如下圖5所示。
圖16:CN架構
match
整個資料集大約20到30次epochs交叉驗證的準確率
在從中升高到90%之間。 通過從訓練資料中隨機抽取大小為32的batch來執行epochs,直到繪製出適當數量的樣本。 NVIDIA GTX 970的培訓大概需要5到10分鐘。訓練在NVIDIA GTX 970上大概花費5到10分鐘。
4.使用並行以獲得更好的表現
為了提高AI的效能,CNN檢測要並行執行。這個程式允許加速,因為numpy和TensorFlow程式碼避免了普通Python程式碼的全域性直譯器鎖定問題。針對敵方分類執行緒的啟動程式碼如下。
(點選檢視大圖)
因此,並行執行分類,並且使用互斥鎖以執行緒安全的方式將包含預測的資料成員提供給主執行緒。圖6說明了執行緒和互斥鎖的邏輯組織。在圖中,ecp和pct分別是包含敵方單元格位置和預測單元格型別的Bot類的資料成員。
5.結果
下面這個6分鐘多的視訊對該專案進行了總結,並且其中有長達四分鐘的時間展示了AI如何玩流放之路(PoE)。
圖18:PoE AI素材
視訊地址(需翻牆):
https://youtu.be/UrrZOswJaow
更多最新遊戲AI的視訊可以到作者的Youtube主頁觀看。
https://www.youtube.com/channel/UCdkASWTlm-9EuAdZmbhkxgQ
看完作者這麼用心的教程,你心動了嗎?還不趕緊clone一份~
作者介紹:Nicholas T Smith,從事AI和機器學習軟體開發,加利福尼亞州立大學計算機碩士畢業生。
原文連結:
https://nicholastsmith.wordpress.com/2017/08/25/poe-ai-part-5-real-time-obstacle-and-enemy-detection-using-cnns-in-tensorflow/
Github地址:
https://github.com/nicholastoddsmith/poeai