LMR(末招剪裁) 介绍

来源:互联网 发布:c语言中条件表达式 编辑:程序博客网 时间:2024/05/18 01:29

LMR(末招剪裁) 介绍

原文引自:http://www.glaurungchess.com/lmr.html

翻译:李理

简介

在计算机象棋以及其他棋盘类游戏中最常使用的选择性搜索技术是通过对一些高失败率的节点进行剪裁。递归空招裁剪(recursive null move),复合剪裁(multi-cut),以及ProbCut 剪裁全都属于这一类。所有这些技术有一个共性:他们使用剪裁后的更深的树去评估一个节点(或只是得到一个下线),以得到一个评估分数。当评估的分数低于beta,就发生剪裁。

以上这些技术都具备一个共同的问题,虽然他们被单独拿出来用的时候都表现出很好的性能,但是他们被共同使用时没有明显的性能提升。在国际象棋中,递归空招裁剪已经被证明是非常有效的,但有一部分在空招剪裁上成功添加了复合剪裁或ProbCut的程序员并没有得到明显的性能提升。对这一问题最有可能的原因是,这三个技术所要剪裁的分支近乎于相同。对于那些ProbCut要剪裁的节点,递归空招裁剪一样会去剪裁。

当我们尝试进一步使用递归空招剪切来提升标准PVS搜索的时候,若能想出一种进一步裁剪不良招法的技术就好了。现如今,部分研究成果已经被发布出来了,LMR就是其中之一。这个技术自古有之,谁也不知道是谁发明的。它一直没受到人们的重视,直到2004年,我跟一叫Sergei Markoff的哥们讨论了这个事后,这个技术才流行起来。后来啊,好多国际象棋软件都开始用这个技术。比如2005年,开源的软件Fruit就是其中之一。

由于还没有统一术语,不同人使用不同名字称呼这个技术。我个人更喜欢管这个技术叫LMR。业内对它的其它称呼有:基于顺序剪裁(ordering-based reductions), 低分剪裁(fail low reductions), 以及历史裁剪(history pruning)。最后这名字被好多人用,这其实挺离谱的。我会在下文中解释原因。

基本思想

LMR是基于简单的观察那些合理招法的顺序来实现的,Bate剪裁通常发生在第一个招法上,当然也可能始终未发生。我们可以先对前几个着法进行全深度的搜索,然后对其他招法仅进行浅层的搜索。除非出于其他原因认为某个节点特别重要,我们不需要对其用完整深度进行搜索。如果发现后续着法中有高于alpha的值了,则我们对其用全深度从新搜索一遍。以下伪代码展示了一个基于递归空招裁剪的LMR的PVS搜索:(LMR的部分采用黑体显示)

const int FullDepthMoves = 4;const int ReductionLimit = 3;int search(int alpha, int beta, int depth) {int value, moves_searched;move_t move;if(depth == 0) return qsearch(alpha, beta, depth);// Null move search:if(ok_to_do_nullmove_at_this_node()) {make_nullmove();value = -search(-beta, -beta, -(beta-1), depth-4);unmake_nullmove();if(value >= beta) return value;}moves_searched = 0;while((move = pick_move()) && alpha < beta) {make_move(move);if(moves_searched == 0) // First move, use full-window searchvalue = -search(-beta, -alpha, depth-1);else {if(moves_searched >= FullDepthMoves && depth >= ReductionLimit && ok_to_reduce(move))// Search this move with reduced depth:value = -search(-(alpha+1), -alpha, depth-2);else value = alpha+1; // Hack to ensure that full-depth search// is done.if(value > alpha) {value = -search(-(alpha+1), -alpha, depth-1);if(value > alpha && value < beta)value = -search(-beta, -alpha, depth-1);}}unmake_move(move);if(value > alpha) alpha = value;moves_searched++;}return alpha;}

加强版及其变体

显而易见的,以上伪代码并没有给出ok_toreduce()函数的具体实现。如果盲目的剪裁掉所有除了前三四个着法后的其他所有着法,恐怕会得到一个灾难性的结果。我们需要寻找一些附加条件来判断什么时候能减,什么时候不能减,还不能减的过于频繁。显然,将军(还有类似的延展性着法)永远不该被剪裁。大部分程序会避免剪裁吃子以及子的升变(译者注:国际象棋兵到底线可变化成任意子,叫做升变)。还有就是,不同程序广泛的时候不同裁剪条件,比如以下这些:
  •  历史计数: 那些过去经常发生裁剪的着法
  •  静态或动态的危险检测:如果一个候选着法包含重要战略位置,避免剪裁
  •  评估数据:这个有很多方法可以用。其中一个例子是仅当评估值低于alpha。再比如说,我们可以去检查每个着法对评估值的影响,然后避免剪裁那些提升当前评估值的着法。
  •  节点类型:显然的不该剪切PV节点

对于第一个裁剪条件,历史计数,就是为啥有人管这个技术称之为历史裁剪(history pruning)的原因。我很不喜欢这个名字,因为他搞的好像历史计数条件是必不可少的一样。再说了,我们所指的裁剪是reduction而不是pruning。有好多成功使用LMR的程序一点都不需要使用历史计数裁剪条件。

并不是说所有的使用LMR技术的程序都要完全的遵循以上的这个伪代码。比如有的程序省略掉了重新搜索被剪裁后返回值高于alpha着法的过程。大多数程序会重新搜索,但这不是必须的。举例来说,Fruit2.0 使用的LMR就不会从新搜索。历史从新搜索(History re-search)仅仅被添加在Fruit 2.1版本。

一些国际象棋作者已经实验了不止一种对LMR的实现方法,他们从不同的角度入手都取得了成功。

这可行么?

正如大部分其他技术一样,LMR的有效性很大程度上取决于程序自身。对于一些程序,它能带来100 Elo以上的性能提升,而对于一些程序,性能提升是微乎其微的,甚至毫无提升。还有的程序可能完全不工作了,去了LMR后性能反倒提升了。唯一能判断是否LMR对你程序有效的方法是,动手实验。

然而,显而易见的是,他们是有很大提升空间的,即使对于很强了的程序也是一样。据我所知,有两个国际象棋的商业软件使用了LMR技术,而且还有第三个软件我强烈怀疑他们也用了LMR。我认为使用者的数量恐怕比我知道的多。

我认为LMR还仍然是一个简陋而粗糙的技术,它在将来还有很大的提升空间。

样本代码

两个很流行的使用了LMR的开源国际象棋软件是Fruit和Glaurung. 这俩引擎在具体实现上的区别是显著的,Fruit的剪裁是基于历史计数条件以及节点类似,而Glaurung的则是基于评估数据以及静态或动态的危险检测。


原创粉丝点击