我们的世界纷繁复杂,看起来完全不可捉摸。但在很多场景下,它运行的本质其实是通过付出最小的代价获得最大化收益。例如在自然界里的自然选择,光的运行路径。对于人的世界更是如此,由于我们做任何事情,任何选择都要付出相应的成本,因此选择一种决策方式让我们以最小的代价获得最大化的回报无疑是我们行动思考的核心。
围棋,以及一切棋类它的本质就是寻求一种最优化策略,但不同之处在于,它不是寻求即时回报,而是寻求最终回报,我所采取的一系列行动,很可能再当下某个时刻没有回报,乃至要付出代价,但只要我最终获得的收获能达到我的目标即可。
实现该目标的一种可行策略是,你在脑子里考虑一系列行动组合的可能性,然后评估这一系列行动获得的收益,从中选择收益最大化的那一中行动组合。这种策略落实到计算机上就叫树搜索。对于棋类而言,棋手会思考如果我下位置1,对方最有可能会下位置2,然后我会下位置3,对方最有可能接着下位置4…如此推演下去,然后他脑子里评估那种下法序列能让自己获得最佳回报,你能推演的层次越深,这意味着你的功力越高深。
显然人脑能思考的层次深度非常有限,但对于计算机而言,它可以仿造这种方法进行类似的运算,这种算法就叫最大最下树搜索。一个难题在于,如果每一步骤可选的落子方式很多,那样的话走法序列的可能性会随着层次深度的增加而呈现爆炸性增长,对于计算机而言它同样吃不消。
因此除了使用树搜索外,我们还需要好的方法尽可能的减少不必要的搜索,把搜索范围限定在可行性之内,同时还要确保限定范围内的走法序列能够得到好的回报,也就是选定的走法序列能最终战胜对手。每次走到分叉口处,你有n种选择,你有好的办法选出其中3种最佳选择,抛弃n-3种其他选择,这个抛弃过程就叫剪枝。
问题在于很多时候,你根本没有足够的信息作出选择。例如面对10条路,每条路看起来都没有区别,你如何确定走哪几条路距离目的地最近?在这种情况下,我们引入蒙特卡罗树搜索算法,它通过引入随机性的方式,帮我们以概率最大化的方式的走上正确的道路。只要基于这里提到的两种方法,我们可以设计出打败很多棋手的围棋算法,但是你要想打败像柯洁,李世石这样的绝顶高手,我们后面还得引入深度学习技术。
树搜索算法只能应用在特定的游戏规则,那就是游戏以循环方式依次进行,每次轮到你时,你总能有若干种选择。如果游戏不以这种方式进行,例如球类游戏像篮球足球,树搜索就完全用不上。
我们看看树搜索的基本流程:
上图中棋盘首先落子的是X,它有三种方式可选择,它选择其中一种后,对手O也有三种方式可选择,于是X要考虑O落子后自己又该如何选择,如此依次进行,直到棋盘都占据满后,再回溯到第一步,选择走到最后一步时能取得最大收益的那种走法。
在进行树搜索时,我们要遵守几个原则:
1,当前是否有致胜走法,有的话走那一步。
2,当前是否有对方的致胜走法,有的话我要赌住那一步。
3,判断当前能否有两步赢法,例如下图:
假设我们当前下的是三子棋,当三个同样的棋子在行,列,对角线上连在一起时能获胜,那么对于X而言,走中间位置就是两步赢法,因为它可以在对角线相连,也可以中中间那一列相连。
4,判断对方是否有两步赢法,有的话堵住那一步
5,如果上面条件都不成立,你选择经过给定层次后能够取得最大好处的那一步。
上面原则还有很多细节待考虑和待落实,我们将使用代码的方式,一步一步将上面的原则细化,落实,首先我们先定义三种状态:
class GameResult(enum.Enum):
loss = 1
#平局
draw = 2
win = 3
假设你现在有一个函数best_result,你从当前所有走法中选择一种后,调用该函数你就能得到最