站長資訊網
        最全最豐富的資訊網站

        淺談Node.js如何實現蒙特卡洛樹搜索

        本篇文章給大家介紹一下使用Node.js如何實現蒙特卡洛樹搜索,并用蒙特卡洛樹搜索(MCTS)算法來玩一個給定規則的游戲,下面一起來看看吧!

        淺談Node.js如何實現蒙特卡洛樹搜索

        本文假設讀者具備一定的計算機科學知識,尤其是數據結構中關于樹結構的工作原理,還需要具備 JavaScript(ES6+)的中級知識。推薦學習:《nodejs 教程》】

        本文的目標很簡單:

        實現蒙特卡洛樹搜索(MCTS)算法來玩一個給定規則的游戲。

        這整個過程將是指導性和實踐性的,并且忽略掉性能優化的部分。我將會對鏈接的代碼片段進行簡要解釋,希望你能跟上我的腳步并花一些時間理解代碼中復雜難懂的部分。

        讓我們開始吧!

        創建骨架文件

        game.js 文件中:

        /** 代表游戲棋盤的類。 */ class Game {    /** 生成并返回游戲的初始狀態。 */   start() {     // TODO     return state   }    /** 返回當前玩家在給定狀態下的合法移動。 */   legalPlays(state) {     // TODO     return plays   }    /** 將給定的狀態提前并返回。 */   nextState(state, move) {     // TODO     return newState   }    /** 返回游戲的勝利者。 */   winner(state) {     // TODO     return winner   } }  module.exports = Game

        monte-carlo.js 文件中:

        /** 表示蒙特卡洛樹搜索的類。 */ class MonteCarlo {    /** 從給定的狀態中,反復運行 MCTS 來建立統計數據。 */   runSearch(state, timeout) {     // TODO   }    /** 從現有的統計數據中獲取最佳的移動。 */   bestPlay(state) {     // TODO     // return play   } }  module.exports = MonteCarlo

        index.js 文件中:

        const Game = require('./game.js') const MonteCarlo = require('./monte-carlo.js')  let game = new Game() let mcts = new MonteCarlo(game)  let state = game.start() let winner = game.winner(state)  // 從初始狀態開始輪流進行游戲,直到有玩家勝利為止 while (winner === null) {   mcts.runSearch(state, 1)   let play = mcts.bestPlay(state)   state = game.nextState(state, play)   winner = game.winner(state) }  console.log(winner)

        先花點時間梳理一下代碼吧。在腦海中搭建一個子版塊的腳手架,然后嘗試去明白一下這個東西。這是一個思維上的檢查點,先確保你明白它是如何組合在一起的,如果感到無法理解,就請留言吧,讓我看看我能為你做些什么。

        找到合適的游戲

        在開發一個 MCTS 游戲智能體的背景下,我們可以把我們真正的程序看作是實現 MCTS 框架的代碼,也就是 monte-carlo.js 文件中的代碼。在 game.js 文件中的游戲專用代碼是可以互換并且即插即用的,它是我們使用 MCTS 框架的接口。我們主要是想做 MCTS 背后的大腦,它應該真的能在任何游戲上運行。畢竟,我們感興趣的是一般性的游戲玩法。

        不過,為了測試我們的 MCTS 框架,我們需要選擇一個特定的游戲,并使用該游戲運行我們的框架。我們希望看到 MCTS 框架在每個步驟中都做出對我們選擇的游戲有意義的決策。

        做一個井字游戲(Tic-Tac-Toe)怎么樣呢?幾乎所有的游戲入門教學都會用到它,它還有著一些非常令我們滿意的特性:

        • 大家之前都玩過。
        • 它的規則很簡單,可以用算法實現。
        • 它具有一份確定的完善的信息。
        • 它是一款對抗性的雙人游戲。
        • 狀態空間很簡單,可以在心理上進行建模。
        • 狀態空間的復雜程度足以證明算法的強大。

        但是,井字游戲真的很無聊,不是嗎?另外,你大概已經知道井字游戲的最佳策略,這就失去了一些吸引力。有這么多游戲可以選擇,我們再選一個:四子棋(Connect Four)怎么樣?除了可能比井字游戲享有更低的人氣外,它不僅有上面所列舉的特性,還可能讓玩家不那么容易地建立四子棋狀態空間的心理模型。

        淺談Node.js如何實現蒙特卡洛樹搜索

        在我們的實現中,我們將使用 Hasbro(孩之寶:美國著名玩具公司)的尺寸和規則,即是 6 行 7 列,其中垂直、水平和對角線棋子數相連為 4 就算勝利。棋子會從上方落下,并借助重力落在自底向上數的第一個空槽。

        不過在我們繼續講述之前,要先說明一下。如果你有信心,你可以自己去實現任何你想要的游戲,只要它遵守給定的游戲 API。只是當你搞砸了,不能用的時候不要來抱怨。請記住,像國際象棋和圍棋這樣的游戲太復雜了,即使是 MCTS 也無法(有效地)獨自解決;谷歌在 AlphaGo 中通過向 MCTS 添加有效的機器學習策略來解決這個問題。如果你想玩自己的游戲,你可以跳過接下來的兩個部分。

        實現四子棋游戲

        現在,直接將 game.js 改名為 game-c4.js,將類改名為 Game_C4。同時,創建兩個新類:State_C4state-c4.js 中表示游戲狀態,Play_C4play-c4.js 中表示狀態轉換。

        雖然這不是本文的主要內容,但是你自己會如何構建呢?

        • 你會如何在 State_C4 中表示一個游戲狀態呢?
        • Play_C4 中,你將如何表示一個狀態轉換(例如一個動作)呢?
        • 你會如何把 State_C4Play_C4 和四子棋游戲規則 —— 用冰冷的代碼放在 Game_C4 中嗎?

        注意,我們需要通過骨架文件 game-c4.js 中定義的高級 API 方法所要求的形式實現四子棋游戲。

        你可以獨立思考完成或者直接使用我完成的 play-c4.jsstate-c4.jsgame-c4.js 文件。


        這是一個工作量很大的活,不是嗎?至少對我來說是這樣的。這段代碼需要一些 JavaScript 知識,但應該還是很容易讀懂的。最重要的工作在 Game_C4.winner() 中 —— 它用于在四個獨立的棋盤中建立積分系統,而所有的棋盤都在 checkBoards 里面。每個棋盤都有一個可能的獲勝方向(水平、垂直、左對角線或右對角線)。我們需要確保棋盤的三個面比實際棋盤大,方便為算法提供零填充。

        我相信還有更好的方法。Game.winner() 的運行時性能并不是很好,具體來說,在大 O 表示法中,它是 O(rows * cols),所以性能并不是很好。通過在狀態對象中存儲 checkBoards,并且只更新 checkBoards 中最后改變狀態的單元格(也會包含在狀態對象中),可以大幅改善運行時性能,也許你以后可以嘗試這個優化方法。

        運行四子棋游戲

        此時,我們將通過模擬 1000 次四子棋游戲來測試 Game_C4。點擊獲取 test-game-c4.js 文件。

        在終端上運行 node test-game-c4.js。在一個相對現代的處理器和最新版本的 Node.js 上,運行 1000 次迭代應該會在一秒鐘內完成:

        $ node test-game-c4.js  [ [ 0, 0, 0, 0, 0, 0, 2 ],   [ 0, 2, 0, 0, 0, 0, 2 ],   [ 0, 1, 0, 1, 2, 1, 2 ],   [ 0, 2, 1, 2, 2, 1, 2 ],   [ 0, 1, 1, 2, 1, 2, 1 ],   [ 0, 1, 2, 1, 1, 2, 1 ] ] 0.549

        二號棋手在內部用 -1 表示,這是為了方便 game-c4.js 的計算。用 2 代替 -1 的那段代碼只是為了對齊棋盤輸出結果。為了簡便起見,程序只輸出了一塊棋盤,但它確實玩了另外的 999 次四子棋游戲。在單個棋盤輸出之后,它應該輸出一號棋手在所有 1000 盤棋中獲勝的分數 —— 預計數值在 55% 左右,因為第一個棋手有先發優勢。

        分析現在的狀況

        我們已經實現一個帶有 API 方法并且可以運行的游戲,這些 API 方法可以與 State 對象表示的游戲狀態協同運行。我們現在的狀況如何?

        目標:實現蒙特卡洛樹搜索(MCTS)算法來玩一個給定規則的游戲。

        當然,我們還沒有達到目的。剛才我們完成了一件非常重要的事情:讓它設立一個切實的目標,并組成測試我們實現 MCTS 的核心模塊。現在,我們進入正題。

        實現蒙特卡洛樹搜索

        在這里,我將按照 MCTS 詳解中類似的組織方式,我也會在一些地方用自己的話來闡明某些觀點。

        實現搜索樹節點

        淺談Node.js如何實現蒙特卡洛樹搜索

        為了存儲從這些模擬中獲得的統計信息,MCTS 從頭開始建立了自己的搜索樹。

        現在請你回顧樹結構知識。MCTS 是一個樹結構搜索,因此我們需要使用樹節點。我們將在 monte-carlo-node.jsMonteCarloNode 類中實現這些節點。然后,我們將在 MonteCarlo 中使用它來構建搜索樹。

        /** 代表搜索樹中一個節點的類。 */ class MonteCarloNode {    constructor(parent, play, state, unexpandedPlays) {          this.play = play     this.state = state      // 蒙特卡洛的內容     this.n_plays = 0     this.n_wins = 0      // 樹結構的內容     this.parent = parent     this.children = new Map()     for (let play of unexpandedPlays) {       this.children.set(play.hash(), { play: play, node: null })     }   }    ...

        先再確認一下是否能夠理解這些:

        • parentMonteCarloNode 父節點。
        • play 是指從父節點到這個節點所做的 Play
        • state 是指與該節點相關聯的游戲 State
        • unexpandedPlaysPlays 的一個合法數組,可以從這個節點進行。
        • this.children 是由 unexpandedPlays 創建的,是 Plays 指向子節點 MonteCarloNodes 的一個 Map 對象(不完全是,見下文)。

        MonteCarloNode.children 是一個從游戲哈希到對象的映射,包含游戲對象和相關的子節點。我們在這里包含了游戲對象,以便從它們的哈希中恢復游戲對象。

        重要的是,PlayState 應該提供 hash() 方法。我們將在一些地方使用這些哈希作為 JavaScript 的 Map 對象,比如在 MonteCarloNode.children 中。

        請注意,兩個 State 對象應該被 State.hash() 認為是不同的 —— 即使它們有相同的棋盤狀態 —— 如果每個對象通過不同的下棋順序達到相同的棋盤狀態。考慮到這一點,我們可以簡單地讓 State.hash() 返回一個字符串化的 Play 對象的有序數組,代表為達到該狀態而下的棋。如果你獲取了我的 state-c4.js,這個已經完成了。

        現在我們將為 MonteCarloNode 添加成員方法。

          ...    /** 獲取對應于給定游戲的 MonteCarloNode。 */   childNode(play) {     // TODO     // 返回 MonteCarloNode   }    /** 展開指定的 child play,并返回新的 child node。*/   expand(play, childState, unexpandedPlays) {     // TODO     // 返回 MonteCarloNode   }    /** 從這個節點 node 獲取所有合法的 play。*/   allPlays() {     // TODO     // 返回 Play[]   }    /** 從這個節點 node 獲取所有未展開的合法 play。 */   unexpandedPlays() {     // TODO     // 返回 Play[]   }    /** 該節點是否完全展開。 */   isFullyExpanded() {     // TODO     // 返回 bool   }    /** 該節點 node 在游戲樹中是否為終端,     不包括因獲勝而終止游戲。 */   isLeaf() {     // TODO     // 返回 bool   }      /** 獲取該節點 node 的 UCB1 值。 */   getUCB1(biasParam) {     // TODO     // 返回 number   } }  module.exports = MonteCarloNode

        方法可真多!

        特別是,MonteCarloNode.expand()MonteCarloNode.children 中未展開的空節點替換為實節點。這個方法將是四階段的 MCTS 算法中階段二:擴展的一部分,其他方法自行理解。

        通常你可以自己實現這些,也可以獲取完成的 monte-carlo-node.js。即使你自己做,我也建議在繼續之前對照我完成的程序進行檢查,以確保正常運行。

        如果你剛獲取到我完成的程序,請快速瀏覽一下源代碼,就當是另一個心理檢查點,重新梳理你的整體理解。這些都是簡短的方法,你會在短時間內看懂它們。

        淺談Node.js如何實現蒙特卡洛樹搜索

        尤其是 MonteCarloNode.getUCB1() 幾乎是將上面的公式直接翻譯成代碼。這整個公式在上一篇文章中有詳細的解釋,再去看一下吧,這并不難理解,也是值得看的。

        更新蒙特卡洛的類

        目前的版本是 monte-carlo-v1.js,只是一個骨架文件。該類的第一個更新是增加 MonteCarloNode,并創建一個構造函數。

        const MonteCarloNode = require('./monte-carlo-node.js')  /** 表示蒙特卡洛搜索樹的類。 */ class MonteCarlo {        constructor(game, UCB1ExploreParam = 2) {     this.game = game     this.UCB1ExploreParam = UCB1ExploreParam     this.nodes = new Map() // map: State.hash() => MonteCarloNode   }    ...

        MonteCarlo.nodes 允許我們獲取任何給定狀態的節點,這將是有用的。至于其他的成員變量,將它們與 MonteCarlo 聯系起來就很有意義了。

          ...    /** 如果給定的狀態不存在,就創建空節點。 */   makeNode(state) {     if (!this.nodes.has(state.hash())) {       let unexpandedPlays = this.game.legalPlays(state).slice()       let node = new MonteCarloNode(null, null, state, unexpandedPlays)       this.nodes.set(state.hash(), node)     }   }    ...

        以上代碼讓我們可以創建根節點,還可以創建任意節點,這可能很有用。

          ...    /** 從給定的狀態,反復運行 MCTS 來建立統計數據。 */   runSearch(state, timeout = 3) {      this.makeNode(state)      let end = Date.now() + timeout * 1000     while (Date.now() < end) {        let node = this.select(state)       let winner = this.game.winner(node.state)        if (node.isLeaf() === false && winner === null) {         node = this.expand(node)         winner = this.simulate(node)       }       this.backpropagate(node, winner)     }   }    ...

        最后,我們來到了算法的核心部分。引用第一篇文章的內容,以下是過程描述:

        • 在第 (1) 階段,利用現有的信息反復選擇連續的子節點,直至搜索樹的末端。

        • 接下來,在第 (2) 階段,通過增加一個節點來擴展搜索樹。

        • 然后,在第 (3) 階段,模擬運行到最后,決定勝負。

        • 最后,在第 (4) 階段,所選路徑中的所有節點都會用模擬游戲中獲得的新信息進行更新。

        這四個階段的算法反復運行,直至收集到足夠的信息,產生一個好的移動結果。

          ...    /** 從現有的統計數據中獲得最佳的移動。 */   bestPlay(state) {     // TODO     // 返回 play   }    /** 第一階段:選擇。選擇直到不完全展開或葉節點。 */   select(state) {     // TODO     // 返回 node   }    /** 第二階段:擴展。隨機展開一個未展開的子節點。 */   expand(node) {     // TODO     // 返回 childNode   }    /** 第三階段:模擬。游戲到終止狀態,返回獲勝者。 */   simulate(node) {     // TODO     // 返回 winner   }    /** 第四階段:反向傳播。更新之前的統計數據。 */   backpropagate(node, winner) {     // TODO   } }

        接下來講解四個階段具體的實現方法,我們現在的版本是 monte-carlo-v2.js。

        實現 MCTS 第一階段:選擇

        淺談Node.js如何實現蒙特卡洛樹搜索

        從搜索樹的根節點開始,我們通過反復選擇一個合法移動,前進到相應的子節點來向下移動。如果一個節點中的一個、幾個或全部合法移動在搜索樹中沒有對應的節點,我們就停止選擇。

          ...      /** 第一階段:選擇。選擇直到不完全展開或葉節點。 */   select(state) {      let node = this.nodes.get(state.hash())     while(node.isFullyExpanded() && !node.isLeaf()) {        let plays = node.allPlays()       let bestPlay       let bestUCB1 = -Infinity        for (let play of plays) {         let childUCB1 = node.childNode(play)                             .getUCB1(this.UCB1ExploreParam)         if (childUCB1 > bestUCB1) {           bestPlay = play           bestUCB1 = childUCB1         }       }       node = node.childNode(bestPlay)     }     return node   }    ...

        該函數通過查詢每個子節點的 UCB1 值,使用現有的 UCB1 統計。選擇 UCB1 值最高的子節點,然后對所選子節點的子節點重復這個過程,以此類推。

        當循環終止時,保證所選節點至少有一個未展開的子節點,除非該節點是葉子節點。這種情況是由調用函數 MonteCarlo.runSearch() 處理的,所以我們在這里不必擔心。

        實現 MCTS 第二階段:擴展

        淺談Node.js如何實現蒙特卡洛樹搜索

        停止選擇后,搜索樹中至少會有一個未展開的移動。現在,我們隨機選擇其中的一個,然后我們創建該移動對應的子節點(圖中加粗)。我們將這個節點作為子節點添加到選擇階段最后選擇的節點上,擴展搜索樹。節點中的統計信息初始化為 0 次模擬中的 0 次勝利。

          ...    /** 第二階段:擴展。隨機展開一個未展開的子節點。 */   expand(node) {      let plays = node.unexpandedPlays()     let index = Math.floor(Math.random() * plays.length)     let play = plays[index]      let childState = this.game.nextState(node.state, play)     let childUnexpandedPlays = this.game.legalPlays(childState)     let childNode = node.expand(play, childState, childUnexpandedPlays)     this.nodes.set(childState.hash(), childNode)      return childNode   }    ...

        再來看一下 MonteCarlo.runSearch()。擴展是在檢查 if (node.isLeaf() === false && winner === null) 時完成的。很明顯,如果在游戲樹中沒有可能的子節點 —— 例如,當棋盤滿了的時候,是不可能進行擴展的。如果有贏家的話,我們也不想擴展 —— 這就像說當你的對手贏了的時候你應該停止玩游戲一樣明顯。

        那么如果是葉子節點,會發生什么呢?我們只需用在該節點中獲勝的人進行反向傳播 —— 無論是玩家 1,玩家 -1,甚至是 0(平局)。同樣,如果在任何節點上有一個非空的贏家,我們只需跳過擴展和模擬,并立即與該贏家(1-10)進行反向傳播。

        反向傳播 0 贏家是什么意思?用 MCTS 真的可以嗎?真的可以用,后面再細講。

        實現 MCTS 第三階段:模擬

        淺談Node.js如何實現蒙特卡洛樹搜索

        從擴張階段新建立的節點開始,隨機選擇棋步,反復推進對局狀態。這樣重復進行,直到對局結束,出現贏家。在此階段不創建新節點。

          ...      /** 第三階段:模擬。游戲到終止狀態,返回獲勝者。 */   simulate(node) {      let state = node.state     let winner = this.game.winner(state)      while (winner === null) {       let plays = this.game.legalPlays(state)       let play = plays[Math.floor(Math.random() * plays.length)]       state = this.game.nextState(state, play)       winner = this.game.winner(state)     }      return winner   }    ...

        因為這里沒有保存任何東西,所以這主要涉及到 Game,而 MonteCarloNode 的內容不多。

        再看一下 MonteCarlo.runSearch(),模擬是在與擴展一樣的檢查 if (node.isLeaf() === false && winner === null) 時完成的。原因是:如果這兩個條件之一成立,那么最后的贏家就是當前節點的贏家,我們只是用這個贏家進行反向傳播。

        實現 MCTS 第四階段:反轉

        淺談Node.js如何實現蒙特卡洛樹搜索

        模擬階段結束后,所有被訪問的節點(圖中粗體)的統計數據都會被更新。每個被訪問的節點的模擬次數都會遞增。根據哪個玩家獲勝,其獲勝次數也可能遞增。在圖中,藍節點贏了,所以每個被訪問的紅節點的勝利數都會遞增。這種反轉是由于每個節點的統計數據是用于其父節點的選擇,而不是它自己的。

          ...    /** 第四階段:反向傳播。更新之前的統計數據。 */   backpropagate(node, winner) {     while (node !== null) {       node.n_plays += 1       // 父節點的選擇       if (node.state.isPlayer(-winner)) {         node.n_wins += 1       }       node = node.parent     }   } }  module.exports = MonteCarlo

        這是影響下一次迭代搜索中選擇階段的部分。請注意,這假設是一個兩人游戲,允許在 node.state.isPlayer(-winner) 中進行反轉。你也許可以把這個函數泛化為 n 人游戲,做成 node.parent.state.isPlayer(winner) 之類的。

        想一想,反向傳播 0 贏家是什么意思?這相當于一盤平局,每個訪問節點的 n_plays 統計數據都會增加,而玩家 1 和玩家 -1n_wins 統計數據都不會增加。這種更新的行為就像兩敗俱傷的游戲,將選擇推向其他游戲。最后,以平局結束的游戲和以失敗結束的游戲一樣,都有可能得不到充分的開發。這并沒有破壞任何東西,但它導致了當平局比輸棋更可取時的次優發揮。一個快速的解決方法是在平局時將雙方的 n_wins 遞增一半。

        實現最佳游戲選擇

        淺談Node.js如何實現蒙特卡洛樹搜索

        MCTS(UCT) 的妙處在于,由于它的不對稱性,樹的選擇和成長逐漸趨向于更好的移動。最后,你得到模擬次數最多的子節點,那就是你根據 MCTS 的最佳移動結果。

          ...      /** 從現有的統計數據中獲得最佳的移動結果。 */     bestPlay(state) {      this.makeNode(state)      // 如果不是所有的子節點都被擴展,則信息不足     if (this.nodes.get(state.hash()).isFullyExpanded() === false)       throw new Error("Not enough information!")      let node = this.nodes.get(state.hash())     let allPlays = node.allPlays()     let bestPlay     let max = -Infinity      for (let play of allPlays) {       let childNode = node.childNode(play)       if (childNode.n_plays > max) {         bestPlay = play         max = childNode.n_plays       }     }      return bestPlay   }    ...

        需要注意的是,選擇最佳玩法有不同的策略。這里所采用的策略在文獻中叫做 robust child,選擇最高的 n_plays。另一種策略是 max child,選擇最高的勝率 n_wins/n_plays

        實現統計自檢和顯示

        現在,你應該可以在當前版本 index-v1.js 上運行 node index.js。但是,你不會看到很多東西。要想看到里面發生了什么,我們需要完成以下事情。

        monte-carlo.js 文件中:

          ...        // 工具方法    /** 返回該節點和子節點的 MCTS 統計信息 */   getStats(state) {      let node = this.nodes.get(state.hash())     let stats = { n_plays: node.n_plays,                    n_wins: node.n_wins,                    children: [] }          for (let child of node.children.values()) {       if (child.node === null)          stats.children.push({ play: child.play,                                n_plays: null,                                n_wins: null})       else          stats.children.push({ play: child.play,                                n_plays: child.node.n_plays,                                n_wins: child.node.n_wins})     }      return stats   } }  module.exports = MonteCarlo

        這讓我們可以查詢一個節點及其直接子節點的統計數據。做完這些,我們就完成了 MonteCarlo。你可以用你所擁有的東西來運行,也可以選擇獲取我完成的 monte-carlo.js。請注意,在我完成的版本中,bestPlay() 上有一個額外的參數來控制使用的最佳玩法策略。

        現在,將 MonteCarlo.getStats() 整合到 index.js 中,或者獲取我的完整版 index.js 文件。

        接著運行 node index.js

        $ node index.js  player: 1 [ [ 0, 0, 0, 0, 0, 0, 0 ],   [ 0, 0, 0, 0, 0, 0, 0 ],   [ 0, 0, 0, 0, 0, 0, 0 ],   [ 0, 0, 0, 0, 0, 0, 0 ],   [ 0, 0, 0, 0, 0, 0, 0 ],   [ 0, 0, 0, 0, 0, 0, 0 ] ] { n_plays: 3996,   n_wins: 1664,   children:     [ { play: Play_C4 { row: 5, col: 0 }, n_plays: 191, n_wins: 85 },      { play: Play_C4 { row: 5, col: 1 }, n_plays: 513, n_wins: 287 },      { play: Play_C4 { row: 5, col: 2 }, n_plays: 563, n_wins: 320 },      { play: Play_C4 { row: 5, col: 3 }, n_plays: 1705, n_wins: 1094 },      { play: Play_C4 { row: 5, col: 4 }, n_plays: 494, n_wins: 275 },      { play: Play_C4 { row: 5, col: 5 }, n_plays: 211, n_wins: 97 },      { play: Play_C4 { row: 5, col: 6 }, n_plays: 319, n_wins: 163 } ] } chosen play: Play_C4 { row: 5, col: 3 }  player: 2 [ [ 0, 0, 0, 0, 0, 0, 0 ],   [ 0, 0, 0, 0, 0, 0, 0 ],   [ 0, 0, 0, 0, 0, 0, 0 ],   [ 0, 0, 0, 0, 0, 0, 0 ],   [ 0, 0, 0, 0, 0, 0, 0 ],   [ 0, 0, 0, 1, 0, 0, 0 ] ] { n_plays: 6682,   n_wins: 4239,   children:     [ { play: Play_C4 { row: 5, col: 0 }, n_plays: 577, n_wins: 185 },      { play: Play_C4 { row: 5, col: 1 }, n_plays: 799, n_wins: 277 },      { play: Play_C4 { row: 5, col: 2 }, n_plays: 1303, n_wins: 495 },      { play: Play_C4 { row: 4, col: 3 }, n_plays: 1508, n_wins: 584 },      { play: Play_C4 { row: 5, col: 4 }, n_plays: 1110, n_wins: 410 },      { play: Play_C4 { row: 5, col: 5 }, n_plays: 770, n_wins: 265 },      { play: Play_C4 { row: 5, col: 6 }, n_plays: 614, n_wins: 200 } ] } chosen play: Play_C4 { row: 4, col: 3 }  ...  winner: 2 [ [ 0, 0, 2, 2, 2, 0, 0 ],   [ 1, 0, 2, 2, 1, 0, 1 ],   [ 2, 0, 2, 1, 1, 2, 2 ],   [ 1, 0, 1, 1, 2, 1, 1 ],   [ 2, 0, 2, 2, 1, 2, 1 ],   [ 1, 0, 2, 1, 1, 2, 1 ] ]

        完美!

        總結

        本文主要講述如何使用 Node.js 實現蒙特卡洛樹搜索,希望大家喜歡。下一篇文章將介紹如何優化,以及蒙特卡洛樹搜索(MCTS)的現狀。

        感謝你的閱讀!

        英文原文地址:https://medium.com/@quasimik/implementing-monte-carlo-tree-search-in-node-js-5f07595104df

        原文作者:Michael Liu

        贊(0)
        分享到: 更多 (0)
        網站地圖   滬ICP備18035694號-2    滬公網安備31011702889846號
        主站蜘蛛池模板: 精品国产一区二区三区在线观看| 精品午夜福利1000在线观看| www.99精品| 久久久99精品成人片中文字幕 | 真实国产乱子伦精品一区二区三区| 精品无码国产污污污免费网站国产| 国产网红无码精品视频| 亚洲精品无码日韩国产不卡?V| 国产国拍亚洲精品福利| 精品乱人伦一区二区三区| 国产精品揄拍100视频| 无码精品国产一区二区三区免费| 青青热久久国产久精品| 亚洲av午夜福利精品一区人妖| 精品91自产拍在线观看| 99久久人人爽亚洲精品美女| 国产成人精品白浆久久69| 熟女精品视频一区二区三区| 亚洲精品一级无码鲁丝片| 久久国产精品免费一区| 九九久久精品国产| 久久精品成人免费观看97| 国产精品自产拍在线观看花钱看| 2021国产成人精品久久| 欧美日韩精品一区二区三区不卡 | 欧美成人精品高清在线观看| 99精品视频在线观看re| 久久久精品人妻一区二区三区四| 中文字幕亚洲精品| 亚洲AV第一页国产精品| 午夜DY888国产精品影院| 中文字幕精品久久久久人妻| 亚洲精品岛国片在线观看| 亚洲国产精品自在拍在线播放| 久久久久亚洲精品男人的天堂| 精品99久久aaa一级毛片| 国产午夜精品理论片免费观看| 国产三级精品三级在线观看专1| 国产精品爽爽ⅴa在线观看| 精品国产一区二区三区2021| 欧美亚洲日本久久精品|