Threes-AI 玩小三傳奇 (上)

一縷殤流化隱半邊冰霜發表於2018-03-05

? AI for the Threes! game. ?

GitHub Repo:Halfrost-Field

Follow: halfrost · GitHub

Source: github.com/halfrost/th…

靈感來源

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

由於本人也是客戶端出身,所以這個 AI 必須也能在手機上刷分。所以要找一個手機遊戲,三個人可以玩的,或者名字帶“三”字的,由此:

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

“炫耀”分數

目前這個 Go 版本的 AI 在 3 個地方跑了分,都分別跑了 200 盤。拿到高分的比例差不多就 20% 左右。所以也希望能在專案第二階段——機器學習階段,能把跑高分的比率提高到 100%

1. play threes game 官方網站

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

Threes-AI 玩小三傳奇 (上)

Threes-AI 玩小三傳奇 (上)

Threes-AI 玩小三傳奇 (上)

這個高分視訊在這裡,騰訊視訊連結

2. threes Android 客戶端

這裡之所以沒有跑 iOS 客戶端的遊戲截圖,是因為 iOS 客戶端需要越獄才能執行,筆者手頭上的機器都在 iOS 11.2+,等以後越獄了可以再重新來跑跑分。

Threes-AI 玩小三傳奇 (上)

Threes-AI 玩小三傳奇 (上)

3. threes game 自建網站

為了能自己通過機器學習訓練模型,也為了能公開展示這個 AI 的實力,於是按照官方的遊戲規則,原汁原味的復刻了一個 web 版。

Threes-AI 玩小三傳奇 (上)

Threes-AI 玩小三傳奇 (上)

這個高分視訊在這裡,騰訊視訊連結

在網路上流程著這樣一個“謠傳”:當合成出 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 個原因:

  • 1、可能是伺服器 iptables 遮蔽了埠
  • 2、可能是 ip 或者埠出錯或者不支援協議
  • 3、服務端沒啟動

筆者在部署的時候就出現了上述的問題,先通過 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 逼死的機率不大,倒是被高分大磚塊逼死的情況很多,這樣導致存活時間不長,分數也沒有網頁版的高。

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

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

Threes-AI 玩小三傳奇 (上)

Threes-AI 玩小三傳奇 (上)

可以看到,這兩張圖的佈局是最好的,因為相鄰的磚塊通過合成以後可以繼續合成更高一級的,這樣合成速度也最快。能達到最快消除棋盤上的磚塊。如果沒有按照類似這種單調性來佈局的話,那麼常常最後都是因為自己佈局混亂,導致一些磚塊無法合成,最終被自己活活的“逼死”了。

演算法思想

本 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. 具體

下面來談談具體的思路。

Threes-AI 玩小三傳奇 (上)

Threes-AI 玩小三傳奇 (上)

從上面兩張圖可以看出,針對每種情況,都可以出現 4 種操作,每種操作都會出現新的磚塊,新的磚塊出現的位置都是一個穩定的概率。針對每一種情況都進行期望值的計算。於是就會出現上述的樹狀的結構了。

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

Threes-AI 玩小三傳奇 (上)

下一個磚塊是 2 。那麼會出現在哪裡呢?總共有 16 種情況。

Threes-AI 玩小三傳奇 (上)

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

得到這 16 種情況以後,接著繼續往下遞迴。遞迴公式如下:

Threes-AI 玩小三傳奇 (上)

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

但是遞迴不能無限的遞迴,遞迴需要臨界條件。我這裡設定的收斂條件是當概率小於某個值的時候就算遞迴結束了。這個值具體是多少可以根據遞迴的層次去選一個合適的值。

遞迴收斂以後,就開始計算本次的期望,這個期望值是由權重矩陣和棋盤矩陣相乘得到的值。權重矩陣裡面的值也是需要自己調整的,調整的不好會導致遞迴層次很多,影響效率;遞迴層次太少,又會影響期望結果計算的準確性。這個權重矩陣的“調教”也許可以交給機器學習的無監督學習去做。

Threes-AI 玩小三傳奇 (上)

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

通過上述期望值遞迴計算以後,可以迴歸到初始狀態。那麼怎麼決定究竟是往哪個方向移動呢?

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

Threes-AI 玩小三傳奇 (上)

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

Threes-AI 玩小三傳奇 (上)

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

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

Threes-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一般採用的是暴力搜尋方法(深藍就是這樣乾的),就所有可能存在的下法構建一個樹。由於狀態空間巨大,想通過暴力列舉的方法列舉所有的狀態是不可能的。

Threes-AI 玩小三傳奇 (上)

蒙特卡羅樹搜尋大概可以被分成四步。選擇(Selection),擴充(Expansion),模擬(Simulation),反向傳播(Backpropagation)。

在開始階段,搜尋樹只有一個節點,也就是我們需要決策的局面。搜尋樹中的每一個節點包含了三個基本資訊:代表的局面,被訪問的次數,累計評分。

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

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

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

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

Threes-AI 玩小三傳奇 (上)

該圖顯示了一個決定所涉及的步驟,每個節點顯示從該點所代表的玩家的角度來看該玩家獲勝/玩的次數。所以,在選擇圖中,黑色即將移動。 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…

相關文章