MCTS的一些简单理解/扩展及其实现细节
主要上学期写NoGo的时候这个算法基本没写对
这学期写Hex的时候,大概弄明白了之后就想简单记录一下
(主要面向PKU的两门屑课的屑作业,说的就是你!计概!AI基础!
主要是写给我的女孩看的,希望她能看懂,能会写,不要比别人差
简单理解
mcts算法基于很简单的蒙特卡洛算法,即随机撒点,利用概率期望收敛的原理来求近似精确解
然后还要借助一个公式:UCB公式
其中\(Q(u, a)\)为\(u\)状态下走\(a\)这一步的所有后续迭代中,多次模拟的总收益,\(N(u, a)\)为\(u\)状态下走\(a\)这一步的总次数,\(N(u)\)为走到\(u\)状态的总次数
这个c是一个常数,表示exploration即探索指数
MCTS的算法是基于搜索树展开的,一个状态,下一步棋,就得到它的几个子状态,也就是子节点
我们以当前棋盘状态为根节点开始展开,在一定时间内往后探索许多步,然后根据搜索结果确定当前状态的下一步下在哪里
可以看到跟"走一步看五步"的思路有些像
我们每次迭代,也称之为tree policy,是在树上找一个还没被扩展过的叶子进行扩展
接下来,在这个叶子所处的棋局状态下进行随机落子直到终局,这一步,称之为default policy
在随机落子之后,我们会得到一个结局,也就是哪一方赢了,然后我们顺着这个叶子不停地往父亲跳(也就是回到自己的上一步状态),往上更新
如果根据当前状态落子方和最后胜负方进行更新收益,打个比方,比如当前是下了一步白棋,最后白棋赢了,那么这一步收益加一,反之不加。(但是不管有没有收益,次数都要加一)
这就是back propagate 反向传播
那么我们还有很大的疑问,回到tree policy处,你说的轻巧,那怎么找这个扩展的叶子呢
简单思考,我们需要从最初状态(树根)往下走
1'如果当前的节点所有儿子都被访问过(当前状态的所有子状态都被访问过),那么我们就会选一个儿子走下去,然后继续这个过程
2'不然我们就可以随机选一个没访问过的子状态,它就是我们要的叶子啦
那1'中这个儿子怎么选呢?
这个时候就要请出UCB公式啦
好像这个算法又被称作UCT,即UCB for Tree
这个算法的灵魂就在利用这个公式进行扩展的儿子的选取
当我们面临选择儿子的时候,我们选这个UCB值最大的,准没啥大问题!
它可以帮助我们的蒙特卡洛过程尽快收敛
于是我们的框架就搭建完啦
利用UCB公式选叶子(Tree Policy) -> 随机落子直到终局(Default Policy) -> 把随机落子的结果向上反向传播更新节点权值(Back Propagate)
我自己写的样例代码放ubuntu paste上了
还有一些实现的小细节
1' UCB公式里常数c怎么选取?
可以看到,c越大我们的搜索树会越宽,c越小我们的搜索树会越深
按理说,c大一点会很好,可以有更多选择,能选出更好的点
但是我们的算力是有限的,尤其是通常只有一秒供计算(例如botzone上)
那此时,我们应该尽可能搜的深,确保选出相对好的点
于是我们选择较小的c效果会很不错
2' 搜索结束后我们怎么做出决策得到下一步该落在哪里
不是直接利用UCB公式!(我因为这里写错吃过不少亏)
而是把探索指数设为0,也就是只考虑胜率
这是容易理解的,因为UCB值最高只意味着我们最想探索它,而不是它一定最好
3' 一些卡常加速技巧
为了加快速度,建议用指针写搜索树代替map存状态,因为一个节点存的信息多,会比较大,hash/比较起来会很慢
棋盘不要存在结点内,因为复制很慢
可以每次搜索都把根节点处的棋盘复制一遍,然后跟着搜索的过程一步步带下去
一些扩展
我的女孩暂时还用不到
但简单更一下
我翻阅论文学到的扩展主要是三个方面下功夫
1' 在default policy上
(1) 在simulation中加入一些专家策略来加快蒙特卡洛方法的收敛
但一定要注意随机性,不然会影响效果
专家策略可以是一些简单的落子策略
在Hex中,我用的方法是加入bridge-fix
(把两个横纵坐标各差1的且中间没有对方棋子的一对棋子,叫一个桥,可以看到无论对方在中间哪一个位置落子,我方在另一个位置落子是依然能把这两个棋子连起来的)
(在模拟中,当一方破坏了另一方的桥时,另一方立即随机选一个被破坏的桥补上)
(这个策略的效果非常好,在最终测试中,我仅凭在MCTS上加入这一个策略,获得了rk5)
关于专家策略,还可以对棋盘利用预学习好的估值函数,得到下一步的所有情况估值,用softmax正则化一下(随机性),依概率选择下一步
这里的估值函数可以利用较简单的线性算法,也可以使用神经网络训练
(2) 在simulation中加入对整体输赢情况的判断
即利用神经网络/专家策略,在simulation前简单判断当前叶子的输赢概率
将这个概率以一个权重混合simulation的结果,作为本次simulation的reward向上更新
参考AlphaGo就是在default policy中同时加入了使用神经网络的(1)和(2)
2' 在tree policy里,选择儿子节点时
普通mcts是纯利用UCB公式选择儿子节点
我们考虑提升UCB公式的效果
一种方式是
利用预准备好的一些规则初始化UCB公式里的Q和N
比如说:使其与父亲的父亲(即上一步)一致,先手时给它赋一个不为0的初值....
感觉有点偏人类智慧了
另一种方式是
我们考虑在UCB公式时增大数据量
这个时候就可以加入RAVE值(Rapid Value Estimation)
也就是考虑在棋局开局时,我们的一些落子之间,先后顺序意义不会很大
于是我们在back propagate时,把当前点的所有子节点中和当前步相同的动作,都用来更新当前点的rave值
rave值如何计算可以类似UCB公式
然后我们利用一个逐渐衰减的值来平衡rave和ucb值
比如利用这个函数的值\(\beta = \sqrt{\frac{K}{3N(s)+ K}}\),其中K是一个常数,N(s)是棋盘当前已经走了多少步
虽然我两次使用RAVE效果都很差,不知道是不是我不会调超参数
加入rave值的完整版本在这里(虽然效果不太好,但我写都写了,就把它贴上了)
3' 在最终作决策时
加入一些人类智慧/神经网络对落子价值进行估值(有点像default policy里的)
在最终决策时按比例与模拟时得到的胜率权值求和,再计算最大值来决定下一次落子的位置
好像本次比赛有人加入了二距离估值啥的,不是很懂,应该是有用的
最后,我用来比赛的版本在这里(只加入了fix-bridge策略),仅供参考!