国际象棋程序设计(五):高级搜索方法

国际象棋程序设计(五):高级搜索方法
 
Franç ois Dominic Laramé e/
 
  哇,看上去好像有很多人在看我的连载,我的脸都红了!   这是倒数第二篇文章,我们会介绍和搜索有关的高级技术,他们既能提高速度,又能增强棋力 (或者只有一个作用 )。他们中有很多,概念上 (程序代码可能不行 )可以运用到任何 2人游戏中,然而让它们用到一些具体问题上,对很多读者来说还需要加工。   干吗要那么麻烦?     到此为止,我们知道的所有搜索算法,都把局面推演到固定的深度。但是这未必是件好事。例如,假设你的程序最多可以用迭代加深的 Alpha-Beta算法搜索到 5层,那么来看下这几个例子:      1. 沿着某条路线,你发现在第 3层有将死或逼和的局面。显然你不想再搜索下去了,因为游戏的最终目的达到了。搜索 5层不仅是浪费时间,而且可能会让电脑自己把自己引入不合理的状态。    2. 现在假设在 5层你吃到了兵。程序可能会认为这个局面稍稍有利,并且会这么走下去。然而,如果你看得更深远些,可能会发现吃了兵以后你的后就处于被攻击的状态,完了!    3. 最后,假设你的后被捉了,不管你怎么走,她会在第 4层被对手吃掉,但是有一条路线可以让她坚持到第 6层。如果你的搜索深度是 5,那么后在第 4层被吃掉的路线会被检测出来,这些情况会评估成灾难局面,但是唯一能使后在第 6(超出了搜索树 )捉到的那条路线,对于电脑来说被吃是不能被发现的,因此它会认为后很安全,从而打一个较高的分数。现在,为了让后被吃的情况排除在搜索树以外,唯一的办法就是调虎离山,这样做是很冒险的——牺牲一些局面,还是有可能让对手犯下错误让你的后溜掉的。那么如果你要通过牺牲一个车来延缓后的被吃呢?对电脑来说,在第 4层丢车要比丢后损失小,所以在这个搜索水平上,它情愿丢一个那么大的子,来推迟那个可怜的后的被吃了。 (当然在随后一回合里,电脑会发现无论怎么走,它的后会在第 4层被吃掉,并且车丢得莫名其妙。 )很早以前 Hans Berliner就把这个情况称为“水平线效应”,这在很大程度上可以通过后面即将介绍的“静态搜索”来改善。     最后要说一句:象棋中的很多局面 (其他游戏也一样 )太不可预测了,实在很难恰当地评估。评价函数只能在“安静”的局面下起作用,即这些局面在不久的将来不可能发生很大的变化。这将是我们介绍下一个内容。   请安静!     有两种评估局面的办法——动态评估 (看局面会如何发展 )和静态评估 (看它像什么样子,不去管将来怎样 )。动态评估需要深入的搜索。我们刚刚提到,局面在不久的将来不可能发生很大的变化的情况下,静态评估才是可行的。这些相对稳定的局面称为“安静” (Quiet)或“寂静” (Quiescent)的局面,它们需要通过“静态搜索” (Quiescence Search)来达到。   静态搜索的最基本的概念是指:当程序搜索到固定深度时 (比如 6),我们选择性地继续各条路线,只搜索“非静态”的着法,直到找到静态着法为止,这样才开始评估。   找到静态局面是需要游戏知识的。例如,什么样的着法可能引起棋盘上子力平衡的巨大改变?对于象棋来说,子力平衡会导致局面的剧烈变化,所以任何改变子力的着法就是——吃 (特别是吃主要棋子 )、兵的升变都是,而将军也是值得一看的 (仅仅是能导致将死的将军 )【译注:我认为任何将军都应该考虑进去,因为它会导致抽吃、长将等决定性局面的产生】。在西洋棋里,吃子和升变 【西洋棋的棋子分兵棋 (Man) 和王棋 (King) ,兵棋冲到底线就变成王棋,因此我断定它是国际象棋的前身】都是选择。在黑白棋中,每一步都必须吃子,并且“子力平衡” 【仅仅指目前棋子的多少,它和最终棋子的多少没多大联系】在短时间内翻覆无常,所以可以说它根本不存在“静态局面”!   我自己的程序用了简单的静态搜索,它只考虑所有带吃子着法的线路 (x层完全搜索以后 )。由于通常局面下没有太多合理的吃子着法,所以静态搜索的分枝因子非常小 (平均在 4-6,双方在吃子后会迅速下降到 0)。但是,静态搜索算法要分析大量的局面,它可能会占用整个处理器一半以上的时间。当你的程序使用这个方案以前,你要确定你是否需要用它。   当没有吃子发生时,我的程序才开始评价局面。其结果就是将层数固定的搜索树作选择性的延伸,它能克服大多数由“水平线效应”产生的后果。   重要的空着     有个加快象棋程序速度的有效方法,就是引入空着的概念。   简而言之,空着就是自己不走而让对手连走两次。大多数局面中,什么事都不做显然不是办法,你总是必须做点事情来改善局面。 (老实说,有一些“走也不是,不走也不是”的局面,空着确实是你的最佳选择,但不能走,这种 “被迫移动” (Zugzwang)局面是没有指望的,所以不必对电脑感到失望。 )   在搜索中让电脑走空着,可以提高速度和准确性。例如:      1. 假设局面对你来说是压倒性优势,即便你什么都不走,对手也无法挽回。 (用程序的术语来说,你不走棋也可以产生 Beta截断。 )假设这个局面本来准备搜索 N层,而空着取代了整个搜索树 (你的所有合理着法用空着取代了 ),并且你的分枝因子是 B,那么搜索空着就相当于只搜索了一个 N-1层的分枝,而不是 B个这样的分枝。在中局阶段通常 B=35,所以空着搜索只消耗了完整搜索所需的 3%的资源。如果空着搜索表明你已经强大到没有必要再走棋 (即会产生截断 )的地步,那么你少花了 97%的力气。如果没有,你就必须检查合理的着法,这只是多花了 3%的力气。平均来说,收益是巨大的。 【当然,空着搜索对于处理“被迫移动”局面还是有负面作用的,特别是在残局中,这个作用相当明显。可以参考《对弈程序基本技术》专题之《 高级搜索方法——空着裁剪 》一文。】    2. 假设在静态搜索中,你面对一个只有车吃兵一种吃子着法的局面,然而接下来对手就会走马吃车。你最好不去吃子而走其他不吃子的着法对吗?你可以在静态搜索中插入空着来模拟这种情况,如果在某个局面下空着比其他吃子着法有利,那么你继续吃子就是坏的选择。并且由于最佳着法是静态着法,所以这个局面就是评估函数可以作用的局面。     总的来说,空着启发会减少 20%75%的搜索时间。这当然值得,特别是当你把这个方法用在静态搜索算法上的时候,就像改变“走子的一方”这种代码一样简单,用不了十行就行了。   【很多书上把“空着”这一技术称为“空着启发” (Null-Move Heuristic) ,本文就是这个意思,事实上在历史表、迭代加深等启发的作用下,空着启发已经意义不大了。现在绝大多数程序都使用了称为“空着的向前裁剪” (Null-Move Forward Pruning) 的搜索 ( 它跟空着启发是有区别的 ) ,尽管是一种不完全搜索,但它却是诸多向前裁剪的搜索中最有效的一个。】   期望搜索和 MTD(f)     普通的老式 Alpha-Beta搜索对某个局面最终的“最小 -最大”值没有假设。看上去它考虑到任何情况,无论有多反常。但是,如果你有一个非常好的主意 (例如由于你在做迭代加深,从而想到前一次的结果 ),你就会找出那些和你预期的差得远的路线,预先把它们截断。   例如,假设一个局面的值接近于 0,因为非常均衡。现在来假设对一个内部结点作先前的评价,它的值在 +20,000 【这里的单位应该是“千分兵值”, 即 1000 相当于一个兵的价值,那么马和象等于 3000 ,车 5000 ,后 9000 ,其他因素也折算成这个值,而 UCI 协议中则用“百分兵值”,因为没有必要过于精确】,那么你可以有充分信心对它截断。   这就是“期望搜索” (Aspiration Search)背后的思想,一个 Alpha-Beta搜索的变种,开始时用从负无穷大到正无穷大来限定搜索范围,然后在期望值附近设置小的窗口。如果实际数值恰好落在窗口以内,那么你赢了,你会准确无误地找到路线,并且比其他的路线快 (因为很多路线都被截断了 )。如果没有,那么算法就失败了,但是这个错误是很容易被检测的 (因为“最小 -最大”值就是其中一条边界 ),你必须浪费一点时间,用一个更大的窗口重新搜索。如果前面的情况比后面的情况多,那么总体上你还是赢了。很明显,你预先猜的数值越好,这个技术的收效就越大。   在上世纪 90年代中期,研究员 Aske Plaat把期望搜索拓展为一个逻辑问题:如果你把带期望的 Alpha-Beta搜索的窗口大小设定成 0,将会发生什么事?它当然永远不会成功。但是如果它成功了,那速度将是惊人的,因为它把几乎所有的路线全都截断了。现在,如果失败意味着实际数值低于你的估计,那么你用稍低点的宽度为零的窗口再试一次,重复下去。这样,你就等于用 Alpha-Beta搜索来做某个“最小 -最大”值的拆半查找 (Binary Search),直到你最终找到那个宽度为零的窗口。   这个伟大的设想发表在一个网站上: http://theory.lcs.mit.edu/~plaat/mtdf.html,它的具体实现称为 MTD(f)搜索算法,只有十多行。加上 Alpha-Beta搜索和置换表的运用, MTD(f)呈现出惊人的效率,还善于做并行计算。它在“粗糙” (简单且快速 )的局面分析中运行得更好,很明显,如果局面评估的最小单位越大 (例如从 0.001个兵增加到 0.1个兵 ),它搜索的步数就越少。   在 Alpha-Beta搜索的变种当中,还有很多具有广泛用途的算法 (例如名声狼藉的 NegaScout,我宁可给白痴讲广义相对论,也不想给你们讲这些 ) 【之所以说 NegaScout 名声狼藉,是因为它的发明者 Reinefeld 首次发表该算法时,程序中有一个致命错误,导致搜索效率大幅度降低,甚至低于普通的 Alpha-Beta 搜索,如今这个算法更多地被 PVS( 主要变例搜索 ) 取代,因为它更容易理解】,但是 Plaat坚持认为 MTD(f)是至今为止效率最高的算法。我就信了他的话,所以我的程序里用了 MTD(f),你们可能会感叹这个算法是多么简短啊!   【 MTD(f) 在整个过程中只使用极小窗口,并且每次都从根结点开始的,这个过程极大程度地依赖于置换表,称为“用存储器增强的试探驱动器” (Memory-enhanced Test Driver ,简称 MTD) ,它只需要传递两个参数 ( 深度 n 和试探值 f) ,故得名为 MTD(n,f) ,缩写为 MTD(f) 。实际运作中 MTD(f) 是以迭代的形式收敛的,而不是原作者所说的拆半查找。   在 Plaat 的文章中, MTD(f) 的代码有 10 行,而跟它异曲同工的算法 PVS ,则只比普通的 Alpha-Beta 多了 5 行左右,因此很奇怪原作者 (Laram é e) 为什么如此看好 MTD(f) MTD(f) 在并行计算上确实比 PVS 有优势,由于 Plaat 等人拿 MTD(f) PVS 算法的比较是在并行机上完成的,才得出 MTD(f) 优于 PVS 的结论,而事实上大部分的程序用的都是 PVS 。】   单步延伸     在我们结束这个主题以前,这是最后一个话题。在象棋中,有些着法明显比其他的好,这样就可能没必要搜索其他的变化了。   例如,假设你在迭代加深过程中正在做深度为 N  -  1的搜索,发现某步的评分为 +9000(即你吃了对方的后 ),而其他都低于 0。如果像比赛一样想节约时间,你会跳过前面的 N层搜索而对这步进行 N层搜索 【对于这步来说,搜索加深了一层,对于优势局面来说,优势应该是越来越大的,所以加深一层后评分应通常要高】,如果这步额外搜索的评分不比预期的低,那么你可以假设这步棋会比其他着法都好,这样你就可以提前结束搜索了。 (记住,如果平均每层有 35种合理着法,那么你就可能节省 97%的时间! )   深蓝的小组发展了这个思想并提出了“单步延伸” (Singular Extension)的概念。如果在搜索中某步看上去比其他变化好很多,它就会加深这步搜索以确认里边没有陷阱。 (实际过程远比这里说的要复杂,当然基本思想没变。 )单步延伸是耗费时间的,对一个结点增加一层搜索会使搜索树的大小翻一番,评估局面的计算量同时也翻一番。换句话说,只有深蓝那种硬件水平才吃得消它,我那笨拙的 Java代码肯定不行。但是它的成效是不可否认的,不是吗? 【原作者的意思可能是指,单步延伸技术会明显提高棋力,同时也会增加搜索时间。】   下一个月     在第六部分中,我们会着重讨论局面评估函数,它才真正告诉程序一个局面是好是坏。这个主题具有极其广泛的内容,可以花几年时间来改进评估方法 (也确实有人这样做 ),因此我们必须对这些内容进行彻底讨论,包括它们的可行性和重要程度。 【在这篇普及型的连载中,作者怎么可能给你们讲那么多呢?】如果任何事情都按照计划进行,我就该用一些 Java代码来给你们填饱肚子,但是这很难办到,不是吗?      Franç ois Dominic Laramé e20009月     原文: http://www.gamedev.net/reference/programming/features/chess5/   译者:象棋百科全书网  ( webmaster@xqbase.com )   类型:全译加译注
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值