1

前言

今年的10.24程序员节相比往年投入巨大,部门开发了土耳其跳棋(Dama)AI对战平台,可以提交AI并和他人对战,10.24当天凌晨做最后一次匹配对战,按排名发放奖励。奖励很微薄,但过程很有意思。这里对参赛过程做一下回顾。
这篇blog不能直接告诉你如何实现一个对弈ai,在阅读了所有引用的情况下,可以作为实现对弈ai的选型参考。

比赛规则简述

cpu:0.5 核
mem: 2GB
不访问外部网络,AI策略不得抄袭已有开源项目
60 秒超时

过程

初次尝试:再次发现 min max 算法

最开始提交ai时,我没有上网搜索任何资料。用了3个小时实现了第一版算法。下棋是双方零和博弈。我必须选择一个无论对方怎么下,都对我较为有利的结果。于是我再次发现了Min Max

选型:Deep Blue(chess computer) or AlphaGo

Deep BlueAlphaGo都是从新闻中知道的。因为Dama的状态空间较小,我认为用Deep Blue的技术足以应付,而monte carlo tree search算法乍看起来较复杂,而且,mcts是搭配神经网络训练使用的。
考虑实现的时间有限,计算资源有限,一个更古早的技术更有参考价值,所以,直接用了领导推荐的:象棋百科全书作为实现纲要。即alpha beta剪枝
回头看,直接看最新的最佳实践会更好。

c++ get_all_moves 函数

初次尝试时,使用了python的demo,demo带有获得当前所有的移动函数。而c++版本demo没有,所以需要自行实现。
获得所有可走步骤是一个典型的回溯应用场景,如不使用回溯,会导致大量内存分配。而在最佳移动的搜索中,会频繁调用此函数,所以性能非常关键。我优先探索了向前的步骤,从规则上,向前更容易产生吃子交换,从而有更高的概率在alpha beta剪掉更多的分支。
最大的卡点是,存在吃子路径时,强制吃子,我忘记了这个规则,当我bug fix完这个函数的时候,几乎只剩下最后一个周末了。
做的比较好的地方是,一开始我就写了单测,用单测驱动开发。因为搞错了规则,导致几乎所有的单测都失效了,干脆全部注释掉。

alpha beta 剪枝,进入了前五

万幸周六上午(2024-10-19)提交的一个中间版本居然进入了前五,这个中间版本甚至同时保留了两种局面评估代码:基于中间过程/最终盘面计算。
这给我很大信心,以及一个可贵的基准版本。周六剩下的时间里,我快速实现了本地对弈,往后所有的版本都会先和该版本进行对弈。

最后冲刺:zobrist 哈希表和benchmark

2024-10-19至2024-10-20,基于zobrist实现了历史表,在迭代加深时,可以利用过往迭代中的计算结果。周日最后的时间中,对程序做了一些benchmark和性能优化,还做了防和棋等,我本以为会就此封版。

真最后冲刺:bugfix

alpha beta 搜索在层数较多时,因为指数级的搜索空间,和不可预估的指数底数,不能在外层迭代中去中断搜索,我改为在迭代的中层做判断,让迭代加深能把时间用到极限。
另外,最后一天还发现 ai 会走出蠢棋,定位发现是 zobrist 有哈希冲突,这里显然是有问题的,状态空间大于 key 的 max bit 数字,如果搜索层次足够深,状态空间较大时,显然会出现问题,而我测试时,往往没有足够的搜索深度。定位到问题时,距离最后提交只剩下1小时左右了,我只能简单地增加了 zobrist key的长度。最终拿了第四,有奖已经很开心 ^-^。

后日谈,重新复盘 ai 实现过程

和冠军同学差在哪里?

程序员实现ai的过程,相当于实现一个产品,用户价值是ai的棋力。我们不断进行下面的迭代:

  • 用户价值
  • 需求分析
  • 设计
  • 开发
  • 测试
  • 反馈

ai棋力最终由下面的维度决定:

  • 代码运行效率 & 搜索效率,在更短的时间内搜索到更好的步骤,对应棋手的算力。
  • 更好的局面评估函数,历史表,神经网络辅助局面评估,对应棋手的棋感。
  • 开发调试效率,在更短的时间做到 bug free。

和冠军的差距是全方面的。
冠军甚至做了一个前端页去可视化搜索树,另一个关键的差异是,针对dama强制吃子的特性,对吃子路径进行更深的搜索,于是我们有更深的搜索深度(我们人下棋也会选择性地对部分分支考虑得更深),这样更容易发现 killer move(直接致胜的一手),和冠军同学交流,这确实是一个巨大的改进。

了解mcts

在1024比赛后,前五需要做技术分享。而我的选题需要对比alpha beta和mcts。下面是ppt中的摘要:

alpha beta 和 Monte Carlo tree search 的核心概念

minimax-2.png

alpha beta: min玩家(圆形节点)在遍历到比max玩家(方块节点)最好选择(15) 更坏的选择(1)时,不用继续遍历其它分支(12, 20, 22)。
优先遍历最好的子树,可以最大化剪枝(3, 12, 20, 22)。

mcts.png

mcts: Q(s, a)代表节点的预期收益,优先选择收益高的节点遍历。
N(s, a),状态s下,动作a被选择的次数,N(s)表示状态s被访问的次数,优先选择访问次数少的节点遍历。
C作为系数,可以平衡搜索深度和广度

mcts和人的思维过程类似,优先选择更有价值的分支进行搜索。而alpha beta是更为机械的对称搜索。实际实现中,对吃子路径优先搜索,会让alpha beta搜索不完全对称,提高了搜索效率,这个是和前面同学的核心差异。

alpha beta 和 monte carlo tree search 的共性

  • 都是Best-First Search, 适用于双方博弈场景。
  • 搜索树形展开,计算任务可以调度到多个线程或分布式节点。

alpha beta 和 monte carlo tree search 的差异

  • ab 搜索得更广,剪枝掉的节点必然无探索价值。mcts走得更深,通过随机采样来逐步逼近最佳决策。
  • ab搜索适合完全信息的双方博弈,mcts不局限于双方博弈,更适用于不完全信息游戏。
  • ab搜索需要指定搜索深度,搜索不能随时终止。mcts搜索可以随时终止。

在dama游戏规则中,ab和mcts哪个更好?

对ab有利的规则:

  1. dama的状态空间较小,属于完全信息博弈。

对mcts有利的规则:

  1. 只有单步计算时长限制,mcts可以随时终止。

总结

领导先尝试了alpha beta,后尝试了mcts,这个技术路线非常赞,毕竟alpha beta更加可解释,也可以提供一个基准版本。而mcts会更难于解释和调试。甚至mcts在第一层随机搜索的情况下(Q(s,a)各个节点相等),也可以找到一个好的结果,我们都没有做预训练,也没有gpu可供使用。

从现在的眼光看来,在2006年mcts被提出后,对Q(s,a)用强化学习做持续改进实在太自然不过了。
而alpha beta因为优先遍历更高价值分支有更好剪枝效率的特性,让alpha beta也可以利用强化学习持续迭代(我在搜索资料之前,从技术角度判断强化学习也适合alpha beta算法)。
现今最强的国际象棋ai stockfish就是alpha beta结合强化学习,而不是更“高级”的mcts。

最开始选型,我选择alpha beta,因为deep blue的威名广为人知。而现在,我还是会选择alpha beta,因为我认为alpha beta相较mcts更适合dama游戏规则。

know how 比 know what 更重要,因为更深入的理解,我更有可能将上述算法应用在其它场景。
以彻底搞懂的角度来看,先后尝试 alpha beta 和 mcts 再进行左右互搏,会是一个很好的学习路径。

代码

https://github.com/enjolras1205/dama_c

参考

https://en.wikipedia.org/wiki/Turkish_draughts
https://en.wikipedia.org/wiki/Deep_Blue_(chess_computer
https://en.wikipedia.org/wiki/Minimax
https://oi-wiki.org/search/alpha-beta/
https://zh.wikipedia.org/wiki/AlphaGo#
https://zh.wikipedia.org/wiki/%E8%92%99%E7%89%B9%E5%8D%A1%E6%...
https://gibberblot.github.io/rl-notes/single-agent/mcts.html#...
https://www.codemotion.com/magazine/ai-ml/the-ultimate-checkm...
https://computerchess.org.uk/ccrl/4040/rating_list_all.html
https://github.com/breakwa11/GoAIRatings
https://www.xqbase.com/computer.htm
https://stockfishchess.org/


enjolras1205
77 声望9 粉丝