【笔记】蒙特卡洛树搜素(MCTS)与三子棋
蒙特卡洛方法與蒙特卡洛樹
蒙特卡洛(Monter Carlo)是一座著名的賭城。既然是賭,那多多少少會帶有隨機的成分。借用其名的蒙特卡洛方法就是一種隨機方法。
在概率論的課堂上有過利用蒙特卡洛方法計算圓周率的例子(投針法):
當d = 2L時,π = 1 / p。證明方法主要有兩種(課堂上用的是第二種):
當然用蒙特卡羅方法計算圓周率還有更加直觀的方法,比如統計大量隨機點落入圓形的概率,結合圓形的面積公式計算圓周率。
與這種方法類似,蒙特卡羅樹搜索就是用大量的、隨機的搜索次數尋找最佳的選擇;但蒙特卡洛樹搜素的方法并非完全隨機——在利用置信區間選擇出最佳節點之后,隨機進行模擬而找到葉節點。利用置信區間選擇時會用到相關的啟發函數,因此,蒙特卡洛樹搜素可以視為一種啟發式算法。
基本博弈理論
完全信息博弈
完全信息博弈指博弈雙方信息完全共享的博弈,其中,動態博弈是指博弈雙方輪流決策,靜態博弈是指博弈雙方同時決策。
組合博弈
組合博弈是指滿足以下條件的博弈:
零和博弈
若記博弈中勝利為1,平局為0,失敗為-1,那么在零和博弈中博弈雙方的總收益之和為零;換言之,在零和博弈中沒有雙贏和雙輸。
蒙特卡洛樹搜素適用條件
置信區間上界(Upper Confidence Bound,UCB)
這里采用UCB1策略衡量節點探索的價值:
V ̄i+2lnNni\overline V_i+ \sqrt \frac{2lnN}{n_i}Vi?+ni?2lnN??
其中,V ̄i\overline V_iVi?表示當前節點的平均價值;
NNN表示總的探索次數;
nin_ini?表示當前節點的探索次數。
基本步驟
蒙特卡洛樹搜索有四個基本步驟:選擇、擴展、模擬、反向傳播。
選擇(Selection)
若該節點已經是葉子節點(博弈結束),則直接進入反向傳播;否則,選擇當前節點下UBC值最大的、未被完全擴展的子節點,若UCB值最大的節點已經被擴展,需要進一步選擇其UCB值最大的子節點,直到找到未被完全擴展的節點。
擴展(Expansion)
從選擇的節點出發,隨機選擇一個合法動作創建子節點(或多個子節點,再從中隨機選一個)。
仿真(Simulation)
順著這個動作,隨機地模擬游戲,直到游戲結束,判斷模擬的結果,勝記為1,負記為-1,平局記為0。
反向傳播(Backpropagation)
將仿真的結果從葉子節點向上更新,所經過的節點的值加上該葉子節點的值(勝負結果),探索次數增加1.
利用蒙特卡洛樹搜素實現三子棋
簡單地用C語言實現了一下,但效果并不好……可能是因為隨機數或搜索次數太少了……
創建棋局
#include<stdlib.h> #include<stdio.h> #include<math.h> #include<time.h> #define Inf 99999 #define TIME 3000 //搜索次數 #define C 1 //電腦走子命令 #define P 0 //玩家走子命令char map[3][3] = {0}; //當前棋局 int T = 9, N = 0; //T是還可以走的空格數,N是總的訪問次數typedef struct NODE {int t; //未被填滿的空格數int loc; //loc=x+y*10; loc%10=x; loc/10=ychar map[3][3];char vis[3][3];float val;int order; //該步操作的命令方,取值為P或Cint N; //該節點的探索次數struct NODE *sib;struct NODE *fth;struct NODE *kid; }Node, *Node_ptr;Node_ptr new(Node_ptr ptr) { //從ptr新建葉子節點并初始化Node_ptr p = (Node_ptr)malloc(sizeof(Node));p->loc = -1;p->t = 0;int i, j;for (i = 0; i < 3; i++)for (j = 0; j < 3; j++){if(ptr){p->map[i][j] = ptr->map[i][j];if (ptr->map[i][j] != ' ') {p->vis[i][j] = 1;p->t++;}elsep->vis[i][j] = 0;}if(!ptr){p->map[i][j] = map[i][j];p->vis[i][j] = 0;if(map[i][j] != ' '){p->vis[i][j] = 1;p->t++;}}}p->val = 0;p->N = 0;p->sib = NULL;if (ptr) {p->sib = ptr->kid;ptr->kid = p;}if (ptr == NULL || ptr->order == P)p->order = C;elsep->order = P;p->fth = ptr;p->kid = NULL;return p; }int win(int x, int y, char map[3][3]) { //判斷輸贏if (map[x][0] == map[x][1] && map[x][1] == map[x][2])return 1;if (map[0][y] == map[1][y] && map[1][y] == map[2][y])return 1;if (x - y == 0 && (map[0][0] == map[1][1] && map[1][1] == map[2][2])) return 1;if (x + y == 2 && map[0][2] == map[1][1] && map[1][1] == map[2][0])return 1;return 0; }void print(char map[3][3]) { //打印棋盤int i = 0;printf(" --- --- ---\n");for (i = 0; i < 3;i++)printf(" | %c | %c | %c |\n --- --- ---\n", map[i][0], map[i][1], map[i][2]); }接下來定義電腦和玩家的走子
void computer_MCTS(){int t = TIME; //重置需要搜索的次數限制N = 0; //重置總訪問次數Node_ptr p = new (NULL), root, sol;while(t--) {root = selection(p);root = expansion(root);rollout(root);N++;}root = p->kid;sol = p->kid;while(root){if (root->N > sol->N)sol = root;root = root->sib;}int x, y;x = sol->loc % 10;y = sol->loc / 10;map[x][y] = '-';printf("Computer's step: <%d, %d>\n", x + 1, y + 1);print(map);Dis(p);if (win(x, y, map)) printf("Sorry, you lost!\n");else if(!--T){printf("Draw!\n");return;}else playerplay(); }void computerplay(){computer_MCTS(); }void playerplay(){int x, y;printf("Your step: ");scanf("%d%d", &x, &y);while (1){if(x < 1 || x > 3 || y < 1 || y > 3){printf("Illegal input!\n");scanf("%d%d", &x, &y);}else if (map[x - 1][y - 1]!=' ') {printf("Already existed!\n");scanf("%d%d", &x, &y);}elsebreak;}map[x - 1][y - 1] = '+';print(map);if (win(x - 1, y - 1, map))printf("Congratulations, you win!\n");else if(!--T){printf("Draw!\n");return;}else computerplay(); }選擇
float UCB(Node_ptr p) { //計算UCB值if (p->N == 0)return Inf;elsereturn p->val / p->N + sqrt(2 * N / p->N); }Node_ptr selection(Node_ptr p){int i, j;//判斷是否為葉子節點,若是返回當前節點進入反向傳播for (i = 0; i < 3; i++)for (j = 0; j < 3; j++)if (p->vis[i][j] != 0)return p;int max = -Inf;Node_ptr M;Node_ptr m = p;while(m){M = m;while (m) {if (UCB(m) > max) {max = UCB(m);M = m;}m = m->sib;}m = M->kid;}return M; }擴展
Node_ptr expansion(Node_ptr p) {int i, j;Node_ptr n, g;for (i = 0; i < 3; i++)for (j = 0; j < 3; j++) {if (p->map[i][j] != ' ' || p->vis[i][j])continue;n = new (p);if (n->order == P)n->map[i][j] = '-';elsen->map[i][j] = '+';n->vis[i][j] = 1;n->t++;n->fth = p;n->loc = i + j * 10;if(p->vis[i][j] == 0)p->vis[i][j] = 1;}int r = rand() % T;g = p->kid;while(r--) {if (g == NULL)g = p->kid;else g = g->sib;}if (g == NULL)g = p->kid;return g; }仿真
定義隨機走子的操作
Node_ptr playR(int order, Node_ptr p, int t) {if(!t){p->val = 0;return p;}Node_ptr q = new (p);//隨機走子int r = rand() % t;int x, y;x = r / 3;y = r % 3;while (q->map[x][y] != ' ') {x = (++r / 3) % 3;y = r % 3;}p->vis[x][y] = 1;q->loc = x + y * 10;if(order == C){q->map[x][y] = '-';if (win(x, y, q->map)) {q->val = 1;return q;}else return playR(P, q, t - 1);}else if(order == P) {q->map[x][y] = '+';if (win(x, y, q->map)) {q->val = -1;return q;}else return playR(C, q, t-1);} }定義仿真
void rollout(Node_ptr p) {Node_ptr ptr = playR(p->order, p, 9 - p->t);backpropagation(ptr->fth, ptr->val); }反向傳播
void backpropagation(Node_ptr p, int order) {if (p == NULL)return;else {backpropagation(p->fth, order);p->val += order;p->N++;} }主函數
int main ( ) {int i, j;char order;srand((unsigned)time(NULL));for (i = 0; i < 3; i++)for (j = 0; j < 3; j++)map[i][j] = ' ';printf("Do you wanna play first? y/n\n");scanf("%c", &order);getchar();if (order == 'y') {playerplay();}else if (order == 'n') {computerplay();}return 0; }結果的選擇
完成蒙特卡羅樹搜索后該如何作出最優決策呢?主要有三種選擇:
一般會采用訪問次數最多的節點作為最終的決策。
總結
以上是生活随笔為你收集整理的【笔记】蒙特卡洛树搜素(MCTS)与三子棋的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java微信刷卡支付demo,微信刷卡支
- 下一篇: 对象base64转码_什么是 Base6