Answers:
对国际象棋引擎进行编程是非常复杂的领域,因此,在前面我将向您介绍国际象棋编程维基,它在这个主题上有很多有用的信息。
国际象棋计算(以及许多类似的事物)通常被建模并认为是“游戏树”或“ 决策树 ”。广义上讲,该树是有向图,在顶部(当前位置)有一个节点,每个可能的动作导致一个节点,对于每个可能的下一步动作,每个节点都导致更多的节点,依此类推。
国际象棋引擎以其最简单的暴力方式生成该树上的所有位置,直到某个深度限制(“层”),并根据一些复杂的标准1评估每个结果位置。然后,它起到了导致最佳结果的作用。如今,已经开发了许多非常复杂的技术来限制引擎必须查看的位置数量,但是出于这个答案的目的,我将忽略这些位置,因为它们并没有改变真正的问题。手。
引擎通常花费相同时间来考虑每个动作的基本原因是决策树的大小随深度(k
)呈指数增长。
考虑起始位置。树(k=0
)的顶部是一个节点。怀特有二十种可能的第一步,因此深度有二十个节点k=1
。然后,黑方还为白方的每个选项提供了20个可用的动作:因此,在k=2
,有20 * 20 = 400
可能的位置!而且只会随着球员的发展而变得更糟!
例如,让我们假设在任何给定时间2,每个玩家总是有20个可能的动作。您指示计算机为每个播放器预告五步动作(十层)。让我们看一下每个级别的暴力树的大小。为了娱乐,我们还将查看树中的总位置数(从顶部到给定级别)。
Ply | Positions | Total Tree Size
----------------------------------------
0 | 1 | 1
1 | 20 | 21
2 | 400 | 421
3 | 8000 | 8421
4 | 160000 | 168421
5 | 3200000 | 3368421
6 | 64000000 | 67368421
7 | 1280000000 | 1347368421
8 | 25600000000 | 26947368421
9 | 512000000000 | 538947368421
10 | 10240000000000 | 10778947368421
每个级别的结果都比上一个级别成倍增加,结果是整个树的大小由最低级别决定。考虑上面的示例:仅最后一个级别包含十万亿个节点。整个树只剩下五千亿。第十层包含整个树中约95%的节点(实际上在每个级别都是如此)。实际上,这意味着所有搜索时间都花费在评估“最后”动作上。
那么这与您的问题有什么关系?好吧,假设计算机如上所述设置为十层,并且进一步“记住”了评估结果。它计算一个动作,进行操作,然后进行动作。现在已经执行了两个动作,因此它将内存中与未发生的动作相关的所有位置修剪掉,并留下一棵树,该树会降低已经计算出的其余八个动作:26,947,368,421个位置!
行!因此,我们只需要计算最后两层!使用我们每个深度20个动作的估算,我们需要在此处计算的动作总数仍超过10万亿。我们已经计算出的头寸仅占可能性的2.5%!因此,即使通过缓存最后一步的结果,我们所能期望的最好结果就是速度提高2.5%!从本质上讲,这就是为什么即使程序缓存了先前的结果,通常也不会在两次移动之间看到明显的加速(当然,当计算机找到强制配合之类的情况时除外!)。
有很多参与了这个问题,这就是为什么我挂编程维基最顶部,只试图解释在广泛数学术语答案的复杂性。实际上,程序通常从移动到移动都缓存了树的某些部分,还有其他原因导致其自身不足-一些简单的原因(例如,某行在8个移动中看起来不错,但以结尾结尾-在第9步时排成队友!)和许多非常复杂的(通常与各种巧妙的修剪方法有关)。因此,计算机必须继续保持前瞻性,以避免基于前一动作的截止深度做出错误的假设。
1我在这里不打算介绍启发式函数,因为那是它自己非常复杂的区域,但是这里也经常可以通过位置缓存方案获得一些收益。
2平均分支因子20 可能太低。
典型的国际象棋引擎会将一些位置及其包围的alpha-beta分数存储在换位表中,可在后续搜索中查阅。不能直接参考该表来选择下一步,但可以通过两种方式更有效地搜索该步骤。
一个位置可能会在搜索树中多次遇到,通过一系列移动的移位或排列可以达到。由于可以查询该表,因此只需要对位置进行几次评估(针对不同的固定搜索深度),而不是在访问和重新访问该位置时评估数十次。
alpha-beta搜索的标准技术是使用迭代加深,以更大的搜索深度反复探测树,直到达到终端深度为止。在较早的迭代中计算的评估分数用于对在较晚的迭代中搜索的移动进行排序。如果在差动之前先搜索好动,则Alpha-beta的性能会更好(即修剪更多的搜索树)。
证明引擎内存的示例:
考虑发现深刻的理论新颖性的位置,尤其是今年玩的Caruana vs Topalov游戏。当您让发动机在移动12或多或少的较短时间(例如10-15分钟)后分析位置时,您可能会检查建议的移动,并发现TN(
13.Re2!
)没有出现在其中。自己介绍移动,返回移动,让引擎或多或少地在同一时间再次分析相同的位置。令人惊讶的是,经过一番思考,现在引擎确实将TN视为最佳动作之一并获得批准。
编辑:原始答案(保留在下面)是错误的,但是,它提供了一个有用的引擎内存示例,已在顶部引用。
据我所知,他们没有这样做,也就是说,他们几乎从零开始就从头开始搜索树。
但是,它们必须具有某种函数来实现每次移动的值,并且该函数肯定具有一些短期记忆。一些例子是发现深刻的理论新颖性的立场,特别是今年玩的Caruana vs Topalov游戏。当您让发动机在移动12或多或少的较短时间(例如10-15分钟)后分析位置时,您可能会检查建议的移动,并发现TN(13.Re2!
)没有出现在其中。自己介绍移动,返回移动,让引擎或多或少地在同一时间再次分析相同的位置。令人惊讶的是,经过一番思考,现在引擎确实将TN视为最佳动作之一并获得批准。
我不是国际象棋软件方面的专家,但这确实发生了。如果(如所述)评估位置移动的函数具有一些记忆,则可以至少部分地解释。
亨利·基特(Henry Keiter)已经给您一个一般性的答案,我将为您提供更多的技术性答案。全部与换位表,搜索深度和截止值有关。这里的讨论比其他答案要技术性强得多,但是对任何想学习国际象棋编程的人来说都是有益的。
一个普遍的误解是,如果以前评估过一个职位,则只要有足够的内存来存储移动,评估分数就可以重用。国际象棋编程比这复杂得多。即使有无限的内存,您仍然必须再次搜索位置。对于每一步,评估得分均附有其深度和界限。例如,如果引擎通过故障高位存储移动,则表条目将具有下限。这意味着,如果您要寻找职位,则仍然必须检查界限,是否可以使用以前的评估分数。
除此之外,每个评估都具有深度。在迭代加深的框架中,随着您增加每次迭代的深度,您仍然必须搜索在先前迭代中已经搜索过的那些位置。
这个问题的简短答案是,引擎确实会存储所有先前分析的位置(只要有足够的内存),但是这些存储的结果无法像您想象的那样容易地重用。在重复次数较少的开始阶段,这些存储的结果对于移动顺序和许多减少移动的启发式方法最有用。例如,假设从最后深度开始的最佳移动是当前深度中的最佳移动,因此我们对移动列表进行排序,并在其他任何移动之前搜索最佳移动。希望我们能早日达到失败的高分界线。
我们没有用于存储位置的无限内存。我们需要定义一个哈希算法。Zobrist散列算法为我们提供了伪随机分布,但是迟早我们仍然必须替换一些现有条目。
每个引擎都有自己的时间管理方案。一些引擎和GUI可让您设置引擎的播放速度。在时间管理子例程或用户设置所施加的约束条件下,引擎始终会尽可能地计算/评估/最小最大值。如果引擎思考了很长时间,很可能是因为游戏的时间控制很慢,或者用户已将其设置为玩慢。
引擎计算出的位置和评估值存储在哈希表中。用户可以在大多数UCI引擎的设置中设置可用哈希的大小。引擎本身会使用一定数量的RAM,并且如果您将哈希表的大小设置得太大,计算机将开始以虚拟RAM的形式将哈希存储在硬盘上。硬盘驱动器的访问速度比RAM慢,通常您将能够听到硬盘驱动器的运转声。许多用户设置哈希表的大小,使其适合可用的RAM。
由于所考虑的其他位置不再相关,因此在引擎及其对手进行移动之后,任何哈希表中的很大一部分将变得无用。引擎将重新使用存储在哈希中的评估结果,但是一旦引擎深入同一行,由于地平线效应,其中一些评估结果将被证明是错误的,因此通常必须重新排序其候选动作。
由于哈希的数量是有限的,因此引擎在添加新信息时还必须决定要从哈希中删除哪些信息。引擎事先不知道将播放什么动作,因此它可能会无意中删除在添加新数据时本会有用的信息。
引擎通常不会检查所有合法移动到一定深度的情况。它们从基于正向和反向修剪的考虑中消除了树的某些分支。同样,如果尚未捕获或检查叶节点位置,则引擎将继续沿该行向下移动,直到到达安静(静止)位置为止。实际的树在某些地方可能很深,而在经过少量移动后其他行可能已被截断。