Threes-AI 玩小三傳奇 (上)

🤖 AI for the Threes! game. 🎮html

GitHub Repo:Halfrost-Field前端

Follow: halfrost · GitHubnode

Source: github.com/halfrost/th…python

靈感來源

1 個月前和另外二位小夥伴一塊兒參加了一個 AI 的比賽。雖然比賽結果不理想,至少我享受到了編程過程當中的樂趣。從此次比賽中讓我認識到 Go 除了寫服務端,寫遊戲模擬器,寫 AI 都是拿手好戲。最近微信跳一跳的輔助,衝頂大會的輔助基本也都是 Go 寫的。因而我更坐不住了,也寫一個來記念咱們那次比賽。linux

因爲本人也是客戶端出身,因此這個 AI 必須也能在手機上刷分。因此要找一個手機遊戲,三我的能夠玩的,或者名字帶「三」字的,由此:nginx

Threes preson join one AI competition ---> Threes-AIgit

「炫耀」分數

目前這個 Go 版本的 AI 在 3 個地方跑了分,都分別跑了 200 盤。拿到高分的比例差很少就 20% 左右。因此也但願能在項目第二階段——機器學習階段,能把跑高分的比率提升到 100%github

1. play threes game 官方網站

這個網站就是官方遊戲的 web 版了。web

這個高分視頻在這裏,騰訊視頻連接算法

2. threes Android 客戶端

這裏之因此沒有跑 iOS 客戶端的遊戲截圖,是由於 iOS 客戶端須要越獄才能運行,筆者手頭上的機器都在 iOS 11.2+,等之後越獄了能夠再從新來跑跑分。

3. threes game 自建網站

爲了能本身經過機器學習訓練模型,也爲了能公開展現這個 AI 的實力,因而按照官方的遊戲規則,原汁原味的復刻了一個 web 版。

這個高分視頻在這裏,騰訊視頻連接

在網絡上流程着這樣一個「謠傳」:當合成出 12288 磚塊的時候,即 2個 6144 磚塊合併,遊戲就會結束,開始播放遊戲製做人的名單。在這個網站上並無這個規則,能合成出多高的磚塊均可以。分數沒有上線,這樣也能夠充分檢驗 AI 的智慧。

固然針對官方的前 2 個遊戲地址,筆者還真的沒有合成出一次 12288 磚塊,因此也沒法驗證「謠傳」的真僞。100% 合成出 12288 磚塊,也是本 AI 的目標。暫時尚未達到目標

運行方法

1. threes game 自建網站

Docker

// 先把 go 服務端跑起來,端口是 9000
docker container run --rm -p 9000:9000 -it halfrost/threes-ai:go-0.0.1

// 再把 web 前端跑起來,http://127.0.0.1:9888
docker container run --rm -p 9888:9888 -it halfrost/threes-ai:web-0.0.1

複製代碼

Local

本地構建就稍微麻煩一點,也分兩步進行,先構建 go server,再構建 web。

先構建 go server:

// 回到項目主目錄
cd threes-ai
go run main.go

複製代碼

上述命令會構建出 go server ,服務器會監聽 9000 端口發來的信息。

再構建 web :

因爲項目是基於 meteor 的,因此請先配置好 meteor 的本地環境,安裝 meteor 手冊連接

// 進入 threes! web 主目錄
cd threes-ai/threes!
meteor 

複製代碼

上述命令會把 web 跑在 http://localhost:3000 上。

到這裏本地就已經跑起來了。

再談談本地如何打包 docker 鏡像。

先打包 go server,因爲 docker 內部是 linux 的,因此在打包的時候要注意交叉編譯,不然最終的 docker 沒法執行。具體打包步驟請見 Dockerfile_go 這個文件裏面的步驟了。

docker image build -t threes_go:0.0.1 .
docker container run --rm -p 9000:9000 -it threes_go:0.0.1

複製代碼

再打包 web:

cd threes-ai/threes!
meteor build ./dist

複製代碼

上述命令執行結束,會在當前目錄下生成 dist 文件夾,而且裏面會包含 threes!.tar.gz 壓縮包。解壓這個壓縮包,會獲得一個 bundle 文件,這個文件就是咱們須要部署的。

此時也能夠在本地把這個生產環境的 web 跑起來。使用以下命令:

cd dist/bundle
ROOT_URL=http://127.0.0.1 PORT=9888 node main.js

複製代碼

這時候 web 也會被跑在 http://127.0.0.1:9888 上。注意這裏 node 的版本必須是 8.9.4 。node 的版本要求是 meteor 版本要求的。meteor 1.6 就是對應的 node 8.9.4 。筆者也在 .nvmrc 文件中限制了 node 版本信息。言歸正傳,再回到打包 web docker 鏡像的問題上來。

以後打包成 docker 鏡像的步驟就請看 Dockerfile_web 這個文件裏面的步驟了。

docker image build -t threes_web:0.0.1 .
docker container run --rm -p 9888:9888 -it threes_web:0.0.1

複製代碼

先跑 go server 的 docker 鏡像,再把 web 的 docker 鏡像跑起來,便可。

2. play threes game 官方網站

第一步須要先把 go 的程序打包成一個動態庫,方便 python 調用。

go build -buildmode=c-shared -o threes.so main.go

複製代碼

上述命令把 go 打包成了 threes.so 動態庫。

接下來須要利用 chrome 的 remote debug 模式,創建一個 ws 鏈接。threes_ai_web.py 乾的事情就是把 web 上的棋盤數字信息傳遞給 go,go 函數處理完成之後返回下一步要移動的方向。最後經過 ws 返回到 web 頁面,模擬移動。

sudo /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9162

python threes_ai_web.py -b chrome -p 9162

複製代碼

有時候不加 sudo 命令的話,可能出現一些異常,好比 GPU 分配失敗。

已在現有的瀏覽器會話中建立新的窗口。
[29819:45571:0225/225036.004108:ERROR:browser_gpu_channel_host_factory.cc(121)] Failed to launch GPU process.

複製代碼

遇到上面的錯誤,把 Chrome 徹底退出,再執行 --remote-debugging-port=9162 便可。通常會創建新的 websocket 鏈接,例如:

DevTools listening on ws://127.0.0.1:9162/devtools/browser/86c6deb3-3fc1-4833-98ab-0177ec50f1fa

複製代碼

threes_ai_web.py 這個腳本還有些問題,有時候出錯會致使 ws 鏈接中斷。這個稍後改完再放出來。

3. FAQ

1. halfrost/threes-ai:web-0.0.1 這個鏡像有 0.0.2 版本,爲什麼上述命令中用的是 0.0.1 版本?

這個問題牽扯到部署服務器的問題了。在當前源代碼中,threes-ai/threes!/client/js/game.js 中 367 行,0.0.1 版本這裏端口寫的是 9000,而 0.0.2 版本這裏寫的是 8999 。

爲什麼兩個版本就這裏端口號的差異呢?由於 ssl 的緣由。部署到服務器上是 https,因此 ws 就變成了 wss 鏈接。而跑在本地無所謂,反正是 localhost 或者 127.0.0.1 ,都是 http 的。因爲服務器端須要加 ssl,因此須要用 nginx 加一層反向代理,來支持 wss。nginx 監聽 web server 8999 端口,加上 ssl 之後轉發到 go server 9000 端口。這樣就完成了 web 和 go 的 wss 交互。本地運行的話就不用這麼麻煩,直接都是 9000 端口便可,web server 經過 9000 端口直接鏈接 go server 的 9000 端口,進行 ws 通訊。

2. 服務器部署過程當中,出現了 WebSocket connection to 'wss://XXXX' failed: Error in connection establishment: net::ERR_CONNECTION_REFUSED 的錯誤,怎麼解決?

通常出現上面 CONNECTION_REFUSED 的錯誤,可能有如下 3 個緣由:

  • 一、多是服務器 iptables 屏蔽了端口
  • 二、多是 ip 或者端口出錯或者不支持協議
  • 三、服務端沒啓動

筆者在部署的時候就出現了上述的問題,先經過 iptables 檢測,發現沒有問題。第二種可能就是不支持 wss 了,經過 openssl 命令檢測端口:

openssl s_client [-connect host:port>] [-verify depth] [-cert filename] [-key filename] 
 [-CApath directory] [-CAfile filename][-reconnect] [-pause] [-showcerts] [-debug] [-msg] 
 [-nbio_test] [-state] [-nbio] [-crlf] [-ign_eof] [-quiet]

複製代碼

經過檢測發現端口沒有支持 ssl,因而加一層 nginx 代理下 ssl 就解決了上述問題。

遊戲分析

Threes 的難點在於,這是一個必輸的遊戲。當遊戲到了後半段,合成出了 6144 以後,很大一部分時間這個磚塊所在的位置就不能動了,至關於 4 * 4 = 16 個格子,減去一個給它。場地上的磚塊到後期也沒法一會兒合併,因此預留的空間不多,經常由於週轉不開或者連續來 1 或者連續來 2,沒法合成 3 ,活活被「擠死」了。

網頁版設計過程當中,並無向客戶端那樣考慮「跳級」的模式,即出現的磚塊能出現 3 的倍數的,好比出現 3,6,12,24,96,192,384…… 這些大額的磚塊。因此網頁版的遊戲過程可能會比客戶端上的簡單一點。

爲什麼說會簡單一點?由於雖然不會出大的磚塊(大磚塊分值高),玩的時間會比較長,可是這樣存活率也稍微高一點。若是連續的來,96,384,768,都來單個的,這樣會致使棋盤上一會兒出來不少不能合成的磚塊,雖然分數會一會兒暴漲,可是也會由於沒法被合併,致使沒法移動,迅速結束遊戲。

在客戶端上就存在「跳級」的設定,就可能一段時間就會出現這些磚塊。我在測試 AI 的時候也發現了這個問題,被連續來單個的 1 或者連續的來單個的 2 逼死的概率不大,卻是被高分大磚塊逼死的狀況不少,這樣致使存活時間不長,分數也沒有網頁版的高。

關於遊戲佈局,確實是有技巧可言。

遊戲的佈局是以單調性爲最優佈局,見下面兩張圖:

能夠看到,這兩張圖的佈局是最好的,由於相鄰的磚塊經過合成之後能夠繼續合成更高一級的,這樣合成速度也最快。能達到最快消除棋盤上的磚塊。若是沒有按照相似這種單調性來佈局的話,那麼經常最後都是由於本身佈局混亂,致使一些磚塊沒法合成,最終被本身活活的「逼死」了。

算法思想

本 repo 是用 Expectimax Search 實現的,固然這個問題還有其餘的解法,這裏也稍微說起一下算法思想,對應的是算法二和算法三,可是不用代碼實現了。

一. Expectimax Search Trees 最大指望搜索樹

在平常生活中,有些狀況下,咱們通過深思熟慮也不能判斷這個抉擇會致使什麼樣的結果?是好仍是壞?

    1. 摸撲克牌的時候,永遠不知道下一張摸到的會是什麼牌,拿到這張未知的牌會對牌局產生什麼樣的影響?
    1. 掃雷遊戲,每次隨時點擊一個方格,有多是雷,也有多是數字,雷的隨機位置會直接影響該回合是否直接遊戲結束。
    1. 吃豆人遊戲,幽靈的位置隨機出現,直接致使接下來的路線規劃。
    1. Threes!遊戲中,1 和 2 的方塊隨機出現,將會影響咱們該怎麼移動方塊。

以上這些狀況均可以用 Expectimax Search Trees 最大指望搜索樹去解決這個問題。這類問題都是想求一個最大值(分數)。主要思想以下:

最大值節點和 minimax search 極大極小值搜索同樣,做爲整棵樹的根節點。中間插入「機會」節點 Chance nodes,和最小節點同樣,可是要除去結果不肯定的節點。最後利用加權平均的方式求出最大指望即最終結果。

這類問題也能夠被歸結爲 Markov Decision Processes 馬爾科夫決策過程,根據當前棋面狀態,肯定下一步動做。

1. Expectimax 最大指望值的一些特性

其餘的節點也並不是是敵對的節點,它們也不受咱們控制。緣由就是由於它們的未知性。咱們並不知道這些節點會致使發生什麼。

每一個狀態也一樣具備最大指望值。也不能一味的選擇最大指望值 expectimax,由於它不是 100% 安全的,它有可能致使咱們整個樹「鬆動」。

機會節點是由加權平均值機率管理的,而不是一味的選擇最小值。

2. 關於剪枝

在 expectimax 中不存在剪枝的概念

首先,對手是不存在「最佳遊戲」的概念,由於對手的行爲是隨機的,也是未知的。因此無論目前指望值是多少,將來隨機出現的狀況均可能把當前的狀況推翻。也因爲這個緣由,尋找 expectimax 是緩慢的(不過有加速的策略)。

3. 機率函數

在 Expectimax Search 指望最大值搜索中,咱們有一個在任何狀態下對手行爲的機率模型。這個模型能夠是簡單的均勻分佈(例如擲骰子),模型也多是複雜的,須要通過大量計算才能獲得一個機率。

最不肯定的因素就是對手的行爲或者隨機的環境變換。假設針對這些狀態,咱們都能有一個「神奇的」函數能產生對應的機率。機率會影響到最終的指望值。函數的指望值是其平均值,由輸入的機率加權分佈。

舉個例子:計算去機場的時間。行李的重量會影響到行車時間。

L(無)= 20,L(輕)= 30,L(重)= 60

複製代碼

三種狀況下,機率分佈爲:

P(T)= {none:0.25,light:0.5,heavy:0.25}

複製代碼

那麼預計駕車時間記爲

E [L(T)] =  L(無)* P(無)+ L(輕)* P(輕)+ L(重)* P (重)
E [L(T)] =  20 * 0.25)+(30 * 0.5)+(60 * 0.25)= 35

複製代碼

4. 數學理論

在機率論和統計學中,數學指望(或均值,亦簡稱指望)是試驗中每次可能結果的機率乘以其結果的總和,是最基本的數學特徵之一。它反映隨機變量平均取值的大小。

須要注意的是,指望值並不必定等同於常識中的「指望」——「指望值」也許與每個結果都不相等。指望值是該變量輸出值的平均數。指望值並不必定包含於變量的輸出值集合裏。

大數定律規定,隨着重複次數接近無窮大,數值的算術平均值幾乎確定地收斂於指望值。

5. 具體

下面來談談具體的思路。

從上面兩張圖能夠看出,針對每種狀況,均可以出現 4 種操做,每種操做都會出現新的磚塊,新的磚塊出現的位置都是一個穩定的機率。針對每一種狀況都進行指望值的計算。因而就會出現上述的樹狀的結構了。

舉個更加詳盡的例子,以下圖,假設當前的棋盤狀況以下:

下一個磚塊是 2 。那麼會出如今哪裏呢?總共有 16 種狀況。

若是進行向上移動 UP 的操做,2 磚塊出現的位置會有 4 種。 若是進行向下移動 DOWN 的操做,2 磚塊出現的位置會有 4 種。 若是進行向左移動 LEFT 的操做,2 磚塊出現的位置會有 4 種。 若是進行向右移動 RIGHT 的操做,2 磚塊出現的位置會有 4 種。

獲得這 16 種狀況之後,接着繼續往下遞歸。遞歸公式以下:

上面公式就是不斷進行指望值的計算。

可是遞歸不能無限的遞歸,遞歸須要臨界條件。我這裏設置的收斂條件是當機率小於某個值的時候就算遞歸結束了。這個值具體是多少能夠根據遞歸的層次去選一個合適的值。

遞歸收斂之後,就開始計算本次的指望,這個指望值是由權重矩陣和棋盤矩陣相乘獲得的值。權重矩陣裏面的值也是須要本身調整的,調整的很差會致使遞歸層次不少,影響效率;遞歸層次太少,又會影響指望結果計算的準確性。這個權重矩陣的「調教」也許能夠交給機器學習的無監督學習去作。

上述公式就是遞歸收斂條件下的指望值計算公式。

經過上述指望值遞歸計算之後,能夠迴歸到初始狀態。那麼怎麼決定到底是往哪一個方向移動呢?

和上面舉的去機場的例子同樣,計算好各條路線的指望值。這裏計算好最大指望值之後,再求一個均值就行了,值最大的就是下一步須要移動的方向。

不過在實際遞歸過程是會出現下面這種狀況:

大面積的空白區域,這樣須要遞歸的次數會不少,間接的致使計算量變的很大,AI 思考一次的時間變長了。解決這個問題的辦法就是限制遞歸深度。

利用樣本方差來評估均值:

S 越大表示樣本和均值差別越大,越分散。間接的經過它來限制一下遞歸深度。

Reference:

[1]:ExpectimaxSearch

[2]:What is the optimal algorithm for the game 2048?

[3]:2048 AI – The Intelligent Bot

二. Minimax search 極小極大值搜索

馮·諾依曼於 1928 年提出的極小化極大理論(minimax)爲以後的對抗性樹搜索方法鋪平了道路,而這些在計算機科學和人工智能剛剛成立的時候就成爲了決策理論的根基。

詳情見下面這個 repo :

github.com/rianhunter/…

三. Monte Carlo tree search 蒙特卡洛樹搜索

蒙特卡洛方法經過隨機採樣解決問題,隨後在 20 世紀 40 年代,被做爲了一種解決模糊定義問題而不適合直接樹搜索的方法。Rémi Coulomb 於 2006 年將這兩種方法結合,來提供一種新的方法做爲圍棋中的移動規劃,現在稱爲蒙特卡洛樹搜索(MCTS)。理論上 MCTS 能夠應用於任何可以以 {狀態,動做} 形式描述,經過模擬來預測結果的領域。

蒙特卡羅 是一種基於平均樣本回報來解決加強學習問題的方法。AlphaGo 利用 蒙特卡洛樹搜索 快速評估棋面位置價值的。咱們一樣能夠用這種方法來評估 Threes 當前移動的拿到最高分的機率。

圍棋棋盤橫豎各有 19 條線,共有 361個 落子點,雙方交替落子,這意味着圍棋總共可能有 10^171(1後面有171個零) 種可能性。這超過了宇宙中的原子總數是 10^80(1後面80個零)!

而傳統AI通常採用的是暴力搜索方法(深藍就是這樣乾的),就全部可能存在的下法構建一個樹。因爲狀態空間巨大,想經過暴力枚舉的方法枚舉全部的狀態是不可能的。

蒙特卡羅樹搜索大概能夠被分紅四步。選擇(Selection),拓展(Expansion),模擬(Simulation),反向傳播(Backpropagation)。

在開始階段,搜索樹只有一個節點,也就是咱們須要決策的局面。搜索樹中的每個節點包含了三個基本信息:表明的局面,被訪問的次數,累計評分。

選擇:從根 R 開始,並選擇連續的子節點到葉節點L.下面的部分更多地介紹了選擇子節點的方法,讓遊戲樹擴展到最有但願的移動,這是蒙特卡羅樹搜索的本質。

擴張:除非L以任何一方的共贏結束遊戲,不然建立一個(或多個)子節點並從其中選擇一個節點 C.

模擬:從節點 C 播放隨機播放。此步驟有時也稱爲 playout 或 rollout。

反向傳播:使用 playout 的結果更新從 C 到 R 路徑上的節點中的信息.

該圖顯示了一個決定所涉及的步驟,每一個節點顯示從該點所表明的玩家的角度來看該玩家獲勝/玩的次數。因此,在選擇圖中,黑色即將移動。 11/21 是從這個位置到目前爲止 playouts 的白棋總數。它反映了在它下面的三個黑色節點所顯示的總共 10/21 個黑棋勝利,每一個黑色勝利表示可能的黑色移動。

當白色模擬失敗時,沿着選擇的全部節點增長了他們的模擬計數(分母),可是其中只有黑色節點被記入勝利(分子)。若是取而代之的是白方,那麼所選的全部節點仍然會增長他們的模擬計數,可是其中只有白方節點會獲勝。這就保證了在選擇過程當中,每一個玩家的選擇都會擴展到該玩家最有但願的移動反映每一個玩家的目標,以最大化他們的行動的價值。

只要分配給移動的時間保持不變,就會重複搜索。而後選擇最多的模擬(即最高的分母)做爲最終答案。

從這裏咱們能夠看出 蒙特卡洛樹搜索 一種啓發式的搜索策略,它利用了頻率去估算了機率,當樣本的頻率採集足夠多的時候,頻率近似於機率。就至關於咱們隨機拋硬幣,只要拋的次數足夠多,那麼,正面朝上的頻率會無限接近於 0.5。

Reference:

[1]:Browne C B, Powley E, Whitehouse D, et al. A Survey of Monte Carlo Tree Search Methods[J]. IEEE Transactions on Computational Intelligence & Ai in Games, 2012, 4:1(1):1-43.

[2]:P. Auer, N. Cesa-Bianchi, and P. Fischer, 「Finite-time Analysis of the Multiarmed Bandit Problem,」 Mach. Learn., vol. 47, no. 2, pp. 235–256, 2002.

To-Do

原本覺得這個項目就這樣終結了,結果近幾天阿里發佈了 「黃皮書」 之後,忽然以爲有了新的思路,我決定繼續用加強學習來完成第二版的。但願更加智能的 AI 能 100% 完成 12288 的最高成就。等訓練完成之後,我會回來繼續完成接下來的文章。

GitHub Repo:Halfrost-Field

Follow: halfrost · GitHub

Source: github.com/halfrost/th…

相關文章
相關標籤/搜索