【问题标题】:Chess: high branching factor国际象棋:高分支因子
【发布时间】:2013-05-11 19:08:47
【问题描述】:

我正在尝试开发一个简单的国际象棋引擎,但我正在努力解决它的性能问题。我已经使用 alpha-beta 修剪和迭代加深(没有任何额外的启发式方法)实现了 Negamax,但我无法获得超过 3-4 层的合理搜索时间。这是游戏开始时我的程序日志的摘录:

2013-05-11 18:22:06,835 [9] INFO  CoevolutionaryChess.Engine.MoveSearchers.NegamaxMoveSearcher [(null)] - Searching at depth 1
2013-05-11 18:22:06,835 [9] DEBUG CoevolutionaryChess.Engine.MoveSearchers.NegamaxMoveSearcher [(null)] - Leaves searched: 28
2013-05-11 18:22:06,835 [9] DEBUG CoevolutionaryChess.Engine.MoveSearchers.NegamaxMoveSearcher [(null)] - Nodes searched: 28
2013-05-11 18:22:06,835 [9] DEBUG CoevolutionaryChess.Engine.MoveSearchers.NegamaxMoveSearcher [(null)] - Found PV: A4->A6 
2013-05-11 18:22:06,835 [9] INFO  CoevolutionaryChess.Engine.MoveSearchers.NegamaxMoveSearcher [(null)] - Searching at depth 2
2013-05-11 18:22:06,897 [9] DEBUG CoevolutionaryChess.Engine.MoveSearchers.NegamaxMoveSearcher [(null)] - Leaves searched: 90
2013-05-11 18:22:06,897 [9] DEBUG CoevolutionaryChess.Engine.MoveSearchers.NegamaxMoveSearcher [(null)] - Nodes searched: 118
2013-05-11 18:22:06,897 [9] DEBUG CoevolutionaryChess.Engine.MoveSearchers.NegamaxMoveSearcher [(null)] - Found PV: A2->A3 B7->B6 
2013-05-11 18:22:06,897 [9] INFO  CoevolutionaryChess.Engine.MoveSearchers.NegamaxMoveSearcher [(null)] - Searching at depth 3
2013-05-11 18:22:08,005 [9] DEBUG CoevolutionaryChess.Engine.MoveSearchers.NegamaxMoveSearcher [(null)] - Leaves searched: 6027
2013-05-11 18:22:08,005 [9] DEBUG CoevolutionaryChess.Engine.MoveSearchers.NegamaxMoveSearcher [(null)] - Nodes searched: 6414
2013-05-11 18:22:08,005 [9] DEBUG CoevolutionaryChess.Engine.MoveSearchers.NegamaxMoveSearcher [(null)] - Found PV: A2->A3 A6->B8 A4->A7 
2013-05-11 18:22:08,005 [9] INFO  CoevolutionaryChess.Engine.MoveSearchers.NegamaxMoveSearcher [(null)] - Searching at depth 4
2013-05-11 18:22:10,485 [9] DEBUG CoevolutionaryChess.Engine.MoveSearchers.NegamaxMoveSearcher [(null)] - Leaves searched: 5629
2013-05-11 18:22:10,485 [9] DEBUG CoevolutionaryChess.Engine.MoveSearchers.NegamaxMoveSearcher [(null)] - Nodes searched: 6880
2013-05-11 18:22:10,485 [9] DEBUG CoevolutionaryChess.Engine.MoveSearchers.NegamaxMoveSearcher [(null)] - Found PV: D2->D4 A6->B8 C4->C5 A7->A6 
2013-05-11 18:22:10,485 [9] INFO  CoevolutionaryChess.Engine.MoveSearchers.NegamaxMoveSearcher [(null)] - Searching at depth 5
2013-05-11 18:22:34,353 [9] DEBUG CoevolutionaryChess.Engine.MoveSearchers.NegamaxMoveSearcher [(null)] - Leaves searched: 120758
2013-05-11 18:22:34,353 [9] DEBUG CoevolutionaryChess.Engine.MoveSearchers.NegamaxMoveSearcher [(null)] - Nodes searched: 129538
2013-05-11 18:22:34,353 [9] DEBUG CoevolutionaryChess.Engine.MoveSearchers.NegamaxMoveSearcher [(null)] - Found PV: D2->D4 A6->B8 C4->C5 A7->A6 A4->A6 

它显示分支因子大约是 10。我已经读过,通过正确的移动排序,我应该得到大约 6 的东西,所以我怀疑我的排序是错误的。它目前以这种方式工作:

  1. 游戏树节点有一个其子节点的链表;最初,捕获和提升是在悄悄行动之前进行的
  2. 在搜索过程中,增加 alpha 或导致截止的子项被放置在列表的开头
  3. 在下一次迭代加深 PV 时应先搜索

这是一种正确的方式来排序移动和我得到的分支因子是可以预期的吗?目前我正在使用一个简单的静态评估函数,它只考虑位置的材料差异 - 这可能是截止率低的原因(如果还考虑数字的流动性,我会得到类似的结果)?诸如减少无效移动或杀手启发式等技术是否会显着帮助(不是 10-15%,而是一个数量级)?我不希望我的引擎很强大,但我希望分支因子约为 6。

【问题讨论】:

  • 那是你从一开始就写的日志吗?如果是这样,这些 PV 在我看来不合法。
  • Claude Shannon 是在 1950 年代提出第一个计算机胸部算法的数学家。他的论文是香农数的基础,据说香农数是国际象棋可能的游戏数(大约 10^120)。在他的工作中,他得出的结论是,如果计算机每秒可以评估 10^6 次可能的移动,那么计算机需要 10^90 年以上才能到达第一个移动(宇宙中的原子数为估计在 10^80 左右)。
  • 这是第三步。以前是 C2->C4 和 D1->A4。
  • 您使用的是 .net 还是 Java?可能是一个因素...
  • 我用的是C#,问题不是代码执行慢,而是剪枝效率低,明显是算法的问题。

标签: chess alpha-beta-pruning iterative-deepening


【解决方案1】:

我还用 C# 开发了一个国际象棋引擎,它的分支因子约为 2.5。绝对有可能将您的引擎提高多个数量级。如今,一般策略是基于良好的移动顺序使用非常激进的移动修剪。为了能够看到一些深刻的战术路线,你牺牲了一些正确性。

以下是我认为最有效的技术概述。请注意,有些组件是补充,有些是替代品,所以我给出的结果是一般指导方针。如果你没有坚实的基础,那么列表末尾的巨大收益是不可能的。

  1. 只需 negamax 和 alpha-beta pruning:3 秒内深度 4。

  2. 添加iterative deepening 和null move heuristic:深度5。迭代深化在这一点上并没有真正的帮助,但很容易实现。空 移动包括跳过你的回合,看看你是否还能得到 带有浅搜索的 beta 截止值。如果可以,那大概就是 修剪树是安全的,因为它几乎总是有利于 移动。

  3. Killer heuristic:深度 6。这涉及存储移动 导致 beta 截止,如果下次合法,请先尝试 你在同一个深度。你似乎在做类似的事情 已经。

  4. MVV/LVA ordering: depth 8. 基本上,你想把捕获 在移动的顶部有很多潜在的物质净收益 列表。因此,如果一个棋子捕获了一个皇后,你显然应该先搜索它。

  5. Bitboard representation: 深度 10。这不会改善分支 因素,但这就是我在达到这一点时所做的。抛弃 数组,请改用 UInt64s,并使用 make/unmake 而不是 copy-make。如果您觉得困难,则无需使用魔术位板;有一些更简单的方法仍然非常快。位板极大地提高了性能 并使编写评估组件变得容易。我从 perft(6) 需要几分钟到 3 秒。 (顺便说一句,编写 perft 函数是确保移动生成正确性的好方法)

  6. Transposition table:深度 13。这提供了巨大的收益,但也 很难做对。绝对确定您的位置散列 在实施表格之前是正确的。大部分收益来自 订购桌子给你的惊人举动。永远储存最好的 移动到表中,每当您获得匹配的位置时,尝试一下 第一的。

  7. Late move reductions: 深度 16。这大大增加了您的搜索深度,但强度增益更多 人工比其他技术。基本上你的移动顺序 现在太好了,你只需要完全搜索前几个 在一个节点中移动,您可以通过浅搜索检查其他节点。

  8. Futility pruning: depth 17. 通过跳过移动修剪叶子节点 在查看潜力时提高节点价值的机会很小 物质上的收获。如果移动+静态评估仓位的净潜在收益低于仓位的当前值,则跳过 对搬家的评价。

还有许多其他组件也有帮助,但大多数都是次要的,有些是专有的。 :D 然而,这不仅仅是高搜索深度和低分支因子。 quiescence search 之类的东西会降低搜索深度,但对于任何引擎来说几乎都是必需品。没有它,您的引擎将遭受巨大的战术错误。您可能还想考虑check extensions 和single reply extensions。我还建议至少将piece-square tables 引入您的评估功能。这是一种非常简单的方法,可以极大地提高程序的位置知识;您可能会看到您的引擎播放更常见的开口。国际象棋编程是一种有趣的爱好,我希望信息量不会让您灰心!

【讨论】:

  • 感谢您提供出色而详尽的回答。我一直在寻找不同技术提供的性能提升,并且一定会使用您的提示。
  • 我几乎觉得这是一本关于如何优化 AI 问题的总体思路的必读。
【解决方案2】:

您可以使用多种启发式方法来减少分支因子。

首先,您应该使用transposition table (TT) 来存储位置结果、深度和最佳移动。在搜索移动之前,您首先检查它是否已经在深度 >= 到您计划搜索的深度进行搜索。如果是,您可以简单地使用表中的结果。如果不是,您可能仍会使用表格中的移动作为您搜索的第一步。

如果 TT 中没有匹配位置(在搜索中),您可以使用 Iterative Deepening (ID)。不是搜索到N 的深度,而是先搜索到N-2 的深度。这将非常快,并且会让您首先在深度搜索N。

还有Null Move Pruning。与 Alpha-Beta(Negamax 是 Alpha-Beta 的变体)结合使用将大大降低您的分支因子。这个想法是在搜索一个位置之前,你尝试一个空移动(不玩)并做一个减少搜索(N-2或N-3)。减少搜索将非常快。如果空着法搜索的结果仍然高于 beta,则意味着该位置非常糟糕,您无需再搜索它(并非总是如此,但大多数情况下都是如此)。

当然,您可以使用多种其他启发式方法来改进您的move ordering,它们都将改善您的分支因子。

【讨论】:

    猜你喜欢
    • 2022-06-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-02
    • 2015-07-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多