手把手搭建遊戲AI—如何使用深度學習搞定《流放之路》

翻譯 | 彭碩,姜沂,reason_W

編校 | reason_W

DeepMind開源《星際2》AI平台,OpenAI人工智慧系統打敗Dota2遊戲頂級玩家......越來越多的科技巨頭開始進入到遊戲AI的領域,並相繼開放了他們的介面和數據集。複雜的訓練數據,即時多變的對戰環境,對多智能體協作能力的要求等等使得《星際爭霸》這樣的遊戲被稱為通用智能的關鍵,預示著AI將在越來越真實的混亂環境里向人類的心智靠近。

那麼小白玩家該如何入坑遊戲AI呢?遊戲AI到底是如何和遊戲進行介面交互,判斷角色狀態,執行動作,規劃策略的呢?

本期推送AI科技大本營(微信ID:rgznai100)精選了國外一位博主遊戲AI系列的文章,來,手把手開始搭建一個遊戲AI。

快到碗里來吧~

基於深入學習和其他機器學習技術,我們將為「流亡之路」(以下簡稱PoE)打造一個遊戲AI。本篇教程總共分為5個階段,下面是原貼列表。

  1. 第一部分:概述
  2. 第二部分:為《流放之路》標定投影矩陣
  3. 第三部分:移動和導航
  4. 第四部分:實時屏幕捕捉及底層命令
  5. 第五部分:基於TensorFlow的CNN實時障礙物和敵對目標檢測

(由於我們把五部分整合成了一篇文章,因此本文共分5部分,20小節,8300+字,閱讀花費約17分鐘。)

我們這個項目的目標是打造一個基於視覺輸入的遊戲AI,它可以成功地在遊戲地圖中進行自主巡航和自主防禦。當然,你也可以在這個過程中一邊玩遊戲,一邊學習打造遊戲AI的樂趣。

第一部分:概述

本部分共2節,原post鏈接

A Deep Learning Based AI for Path of Exile: A Series

nicholastsmith.wordpress.com

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

nicholastsmith.wordpress.com

在這一部分中,我們將探索如何利用遊戲靜態畫面,更新其世界的內部表徵。

1.視覺輸入的挑戰

讓遊戲AI與視覺輸入進行交互的一個難點在於圖像數據是2D的,但是(遊戲)世界是3D的。所以最可能的是,遊戲引擎在3D環境中使用它自己的世界內部表徵,然後使用投影技術將遊戲渲染為2D並顯示在屏幕上。通過逆向工程,從遊戲世界的表徵中獲得數據非常有用,但是因為我們的最終目的是要打造遊戲AI,所以暫時不對這個逆向工程進行深入的探索。

為了更精確地模擬世界,遊戲的投影矩陣要和世界儘可能地相似。這個矩陣被用來確定與屏幕上的2D坐標相對應的3D坐標,(再進行一些假設)反之亦然。圖3說明了投影映射的基本概念。左矩形表示屏幕,而右坐標軸代表世界坐標。灰線(投影映射)將藍點從世界坐標映射到屏幕上的位置。

圖3:投影影射

給定2D圖像來近似投影矩陣的過程被稱為相機標定。

2.相機標定

相機標定是通過一幅包含一個(已知三維空間尺寸的)物體的圖像來完成的。從三維坐標到二維坐標的映射,構造了一種求解變換矩陣的優化問題。這個思想可以表示為在方程1。

方程式1:投影轉換

在上面的方程中,A是投影矩陣,w是世界點(3D)坐標矩陣,而p是投影點(2D)坐標矩陣。

為了標定PoE的相機,也就是確定上面等式中的 A,我們會使用幾個固定大小的箱子。標定相機的過程如下面的截圖所示。

圖4:相機標定

請注意,這些箱子的大致頂角是用一個點指定的,並將其標記為對應的世界點。這個過程比較麻煩,而且需要手動進行。我們把畫面中間那個箱子的右下角指定成坐標原點(0,0,0)(小編註:當然這個原點可以隨意指定,這裡是為了方便),並且假設這個箱子是一個單元立方體。箱子之間的間距也是有單位長度的。另外一個注意的點是,這個投影是用於解析度為800*600的屏幕的,其他屏幕解析度的話,像素大小會發生變化,需要重新標定。

其數據點集合(縮寫)如表1所示:世界點坐標&投影點坐標

表1:數據映射

接下來,我們構建一個轉換矩陣A,將3D點投射到2D的點上。

3.執行擬合

我們通過TensorFlow構建一個非線性擬合,注意:將標定問題看成一個齊次最小二乘問題的方法是比較常見的;Adam 方法看起來可以為這個特殊的圖像提供更好的結果。

(點擊查看大圖)

運行上述代碼所產生的投影矩陣如等式2所示。由於初始化的差異,最終的值可能會稍有不同。

方程式2:得到的投影矩陣

用等式3給的公式,可以在世界坐標中恢復出相機上的位置C。注意,Q是一個3x3矩陣,m是一個3x1矩陣。

方程式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(matplotlib.org/)來構造一個XY平面的動畫,它模擬了世界中角色的運動。在下面的動畫中,相機通過幾個隨機產生的點進行線性移動。

圖6:相機平移運動

有了上述代碼,屏幕上的距離就可以更精確了。為了簡單起見,我們假設玩家總是在XY平面上移動。然而,在某些高度上,這並不是一個可靠的假設。考慮到AI的性能,這一部分可能需要重新考慮。

第三部分:移動和導航

本部分共4節,本節是文章的第三部分:PoE AI Part 3: Movement and Navigation

原post鏈接:

nicholastsmith.wordpress.com

這一部分我們主要探索在同一高度的平面上移動遊戲角色的技巧。

1.移動地圖類

在PoE中,玩家移動角色一般會通過單擊某個位置來實現,接著角色就會移動到滑鼠點擊的位置。圖7展示了通過點擊滑鼠移動角色的一個例子。

圖7:角色移動

現在,為了完成導航,AI會維持一個代表世界地圖的數據結構。賦予坐標和位置類型之間一種對應關係,就可以實現這一點。例如,在給定的時間,在其內部地圖中,AI可能就具有表2所示的數據。

世界點坐標 & 類型

表2:內部地圖

地圖會記錄已訪問的位置及其類型。這個類型標記的是,玩家能否移動到該位置。位置類型分別為「開放」類型或「障礙」類型。有了這樣的地圖,就可以使用廣度優先遍歷找到從一個位置到另一個位置的最短路徑。

2.維度之間的映射

現在,我們假設玩家在位置(0,0,0),並且要移動到(1,1,0)。應該怎麼用滑鼠在屏幕上進行操作呢?想一下前幾部分的內容,一個標定好的投影矩陣,能讓我們在3D坐標中更準確地逼近玩家的位置。因此,利用投影矩陣來變換該點(1,1,0)就可以確定其在屏幕上的位置。這就是滑鼠要點擊的位置。

在實際中,我發現,在玩家為角色指定移動的目標點時,位移技能其實很不準確。特別是當我們在障礙物上單擊時。在這種情況下,角色通常會移動到單擊位置的附近。

3.閃電傳送

不幸的是,像以上的情況可能會導致AI的內部地圖與現實不一致。為了解決這個問題,我決定用閃電傳送來移動。

在角色移動方面,閃電傳送的優點是在運動的結果只有兩項,易於確定; 即玩家移動到了指定位置或者玩家沒有移動到指定位置。這有助於將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鏈接

nicholastsmith.wordpress.com

正如我們在本系列第一部分中說的那樣,AI程序會獲取遊戲的屏幕截圖,並使用它來進行預測,以更新其內部狀態。這一部分中,我們將探索捕捉遊戲截圖的方法。

圖11:AI邏輯流程圖

1.可用的庫

有很多Python庫都可以用來捕捉遊戲截圖,比如 pyscreenshot(pypi.python.org/pypi/py)和 ImageGrab from PIL(pillow.readthedocs.io/e)。這裡有一個簡單的程序可以來測試圖像捕捉的效果。代碼如下:

(點擊查看大圖)

不幸的是,測試的效果看起來實在差強人意。要是沒有其他處理的話,頂多只能盼著這程序每秒處理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鏈接

nicholastsmith.wordpress.com

正如我們在本系列第一部分中說的那樣,AI程序會獲取遊戲的屏幕截圖,並使用它來進行預測,以更新其內部狀態。這部分中,我們將討論從遊戲畫面獲取視覺輸入,並對信息進行分類和識別的方法。我已經把源碼放到了我的github(github.com/nicholastodd)上,開心O(∩_∩)O~~

1.分類系統架構圖

圖12:AL邏輯流程圖

回憶一下第三部分的文章,移動地圖維持了一個從3D點到標籤的字典。例如,在給定時間內,機器人在內部地圖上可能具有表3所示的數據。

世界點坐標 & 投影點

表3:內部地圖

回憶一下第二部分的內容,投影地圖類允許畫面上的任何像素映射到3D坐標(假設玩家總是在xy平面上,然後該3D坐標會被量化為某個任意精度,讓AI的世界地圖變成均勻間隔網格的點)。

因此,我們需要的是能夠識別屏幕上的給定像素到底是障礙物的一部分、敵人還是物品等的方法。這個任務本質上是目標檢測。而實時目標檢測其實是一個困難且計算複雜度很高的問題。 這裡我們會介紹一種簡化的方案,可以在性能和精度之間實現很好的平衡。

為了簡化目標檢測任務,遊戲畫面被劃分成相等大小的矩形區域。對於800×600解析度的畫面,我們選擇由m = 7行和n = 9列組成的網格。從畫面的底部,左側和右側邊緣分別移除十二個,四個和四個像素,使得所得到的尺寸(792和588)能夠分別被9和7整除。因此,屏幕網格中的每個矩形的寬度和高度分別為88和84像素。圖2展示出了使用上述方案分割的遊戲畫面圖像。

圖13:遊戲畫面分塊

判斷畫面單元格內是否包含障礙物或開放的分類任務使用了一個卷積神經網路(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:CNN架構

match

整個數據集大約20到30次epochs交叉驗證的準確率在從中升高到90%之間。 通過從訓練數據中隨機抽取大小為32的batch來執行epochs,直到繪製出適當數量的樣本。 NVIDIA GTX 970的培訓大概需要5到10分鐘。訓練在NVIDIA GTX 970上大概花費5到10分鐘。

4.使用並行以獲得更好的表現

為了提高AI的性能,CNN檢測要並行執行。這個程序允許加速,因為numpy和TensorFlow代碼避免了普通Python代碼的全局解釋器鎖定問題。針對敵方分類線程的啟動代碼如下。

(點擊查看大圖)

圖17:線程邏輯組織

因此,並行執行分類,並且使用互斥鎖以線程安全的方式將包含預測的數據成員提供給主線程。圖6說明了線程和互斥鎖的邏輯組織。在圖中,ecp和pct分別是包含敵方單元格位置和預測單元格類型的Bot類的數據成員。

5.結果

下面這個6分鐘多的視頻對該項目進行了總結,並且其中有長達四分鐘的時間展示了AI如何玩流放之路(PoE)。

圖18:PoE AI素材

視頻地址(需翻牆):youtu.be/UrrZOswJaow

更多最新遊戲AI的視頻可以到作者的Youtube主頁觀看。

youtube.com/channel/UCd

看完作者這麼用心的教程,你心動了嗎?還不趕緊clone一份~

作者介紹:Nicholas T Smith,從事AI和機器學習軟體開發,加利福尼亞州立大學計算機碩士畢業生。

原文鏈接:

nicholastsmith.wordpress.com

Github地址:github.com/nicholastodd

更多內容敬請關注我的公眾號:AI科技大本營(rgznai100),一個專註於發布人工智慧前沿信息的敬業狗。


推薦閱讀:

IT謝幕,AI上場?商業模式將怎麼變?
LeCun親授的深度學習入門課:從飛行器的發明到卷積神經網路
三角獸CTO亓超:人工智慧語義技術是如何「解歧義」的|Xtecher人物
【願景學城】24小時AI熱點新聞的匯總(2018/03/30)
Cousera deeplearning.ai筆記 — 超參數調試、批標準化、多分類、深度學習框架

TAG:人工智慧 | 深度學習DeepLearning | 遊戲 |