【好玩系列】训练一个神经网络指导小孩玩游戏

前言

最近 KataGo 和申真谞又进行了围棋人机战。前几天夜里在视频评论区和网友讨论,解释 AI 是怎么下棋的,突然来了灵感:我要找一些游戏,训练神经网络,制作求解器和教练,指导小孩玩智力游戏。

因为小孩还很小,魔方、象棋、围棋这些项目太超模了,我想到了之前为了消磨时间而复刻的经典游戏:经典华容道

作者按:因为去北京支援 IIT,中间耽搁了很久才重新订正草稿,不再纠正文章中的时间。


1. 先想好做什么

4×54 \times 5 4×5 的经典华容道中,若抹除同类棋子间的个体差异,仅关注物理形状与占位,状态空间会发生剧烈折叠。

全盘合法的物理摆放组合精确收敛于 363,480 种。若进一步剔除死局与镜像,真正从经典开局连通可达的"有效残局"仅在 12.1 万 种上下。

面对这种量级,传统的 BFS(广度优先搜索)在现代 CPU 加持下,配合高效的位运算,眨眼便将其打穿并给出完美通关路径。

但这并不是小孩需要的。"一键获取答案"不仅让游戏失去探索的乐趣,更忽略了解谜过程中最重要的东西:陪伴克服难关的成就感 ,以及 思路上的必要指导

程序不能仅仅作为一个冰冷的算力工具,它需要拥有基于盘面拓扑特征的"大局观直觉",这样才能胜任"教练"的角色,给出提纲契领的思路。

作者按:为了让读者诸君更好地体会何为"大局观直觉",我举两个跨界的例子:

  • 三阶魔方 :如果极致追求竞速,我们会死记硬背高级公式(如 CFOP 的 OLL/PLL);但对于初学者,层先法才是王道。它的思路极其清晰:先做底层十字再拼角,接着做中层棱块,最后在顶层根据特定的形状和侧面分布进行翻面与归位。随着熟练度提升,你才会逐渐过渡到同时处理一二层。这种由浅入深的框架感,就是大局观。

  • 中国象棋:以许银川特大的直播为例,他在对局时绝非在做毫无目的的算计。开局时,许特会点破路数变化与可能遭遇的"飞刀";进入中盘,他会分析子力阵型,针对己方弱点进行阵型微调,同时压制对手棋路以占据空间优势;随后积累先手,通过绞杀简化盘面;最终带着微弱优势(甚至只是多一个小兵)进入残局,施展鬼魅残功形成定式,锁定胜局。

你看,无论是魔方还是象棋,道理如出一辙:必须从框架上"提纲挈领"。在具体的步骤中,当然可以套用公式、谱招或者进行临时计算,但所有的微观操作都不能脱离宏观的框架,这个框架就是盘面的"大势走向"。

基于此,我为这个项目制定了"两步走"计划:

  • 第一步(基建期) :训练一个微型深度学习模型,拟合出华容道全空间的启发式函数 h(n)h(n) h(n),并将其塞入 A* 搜索框架中,打造一个能极速评估盘面价值的底层求解器。
  • 第二步(应用期):在这个求解器的基础上,让网络学会领悟盘面的演变,提取出关键的阶段性目标,最终将其转化为 UI 上的视觉直觉引导。

千里之行始于足下,本篇博客主要聚焦于第一步的探索。只有把这套基建彻底夯实,才有可能在未来孕育出真正的"大局观教练"。

2. 核心引擎的全链路构建

在深入网络之前,我们需要解决最基础的工程抽象,让项目真正转动起来。

2.1 构建全局视野的"标定数据集"

要让神经网络具备大局观,首先得有一本客观的"标准答案书"。我抛弃了传统的"棋子对象抽象",将整个棋盘压缩为一个长度为 20 的一维数组,并基于"左上角锚点"设计,实现了完全无分支的 O(1)O(1) O(1) 极速相邻状态生成。

其核心映射逻辑如下图所示:

自上而下、自左向右的一维扫描中,每一个棋子在数组里首次被扫到的坐标,即作为其唯一的"锚点"(如曹操的锚点为 1,竖将的锚点为 4)。在扫描判定时,后续属于同一棋子的格子将被直接跳过,从而通过最底层的一维算术移位完成高性能的移动生成。

借由这套规则引擎,我在本地跑了一次反向 BFS,从终点逆向打穿了所有的状态空间。经过去重与过滤,提取出了包含 12.1 万种连通状态的残局库 EGTB,每一个盘面都标注了到达终点的最少绝对步数 h∗(n)h^*(n) h∗(n)。这 12 万条数据,就是模型学习"直觉"的基准。

2.2 如何向模型描述一个盘面?(Spatial Encoding)

有了标定数据集,接下来面临的问题是:如何向神经网络描述当前的盘面?

一个直观的做法是直接传入 1D 数组 board_array,或者用一个 5×45 \times 4 5×4 的二维矩阵,里面填上每个格子棋子的类别编码(如曹操填 1,小兵填 4)。

但从数学层面上看,卷积核的本质是执行加权求和 w1x1+w2x2+... w_1 x_1 + w_2 x_2 + \dots w1x1+w2x2+...)。如果直接把类别代号(如 1、2、4)当作数值喂给它,卷积计算就会把它们误当成具有物理意义和大小顺序的"连续数值"。这会带来误导:在模型眼里,代号为 4 的小兵仿佛变成了 4 倍强度的曹操(代号 1);甚至在卷积滑动求和时,两个竖将(代号 2)的特征叠加,会被计算出和小兵(代号 4)相同的数学贡献。这种数值大小与叠加关系会扰乱特征提取,导致模型学到错误的空间阻挡逻辑。

为了实现更合理的表征,我采用了多通道空间编码 (Multi-Channel Spatial Encoding)

  1. 剥离实体,回归几何: 华容道求解只关心"块的物理形状与占位"。因此无需区分"关羽"还是"张飞",只需提取它们的空间形状。我们将状态空间规整为 5 类形状:[空格, 曹操(2x2), 竖将(1x2), 横将(2x1), 小兵(1x1)]
  2. 升维成独热特征图: 对应这 5 类形状,我们将 5×45 \times 4 5×4 的盘面转化为 [Channel=5, Height=5, Width=4] 的三维 Tensor。每个通道都是一个 binary mask(布尔矩阵)。例如在 Channel=1(曹操通道)上,只有曹操覆盖的那 4 个格子上标记为 1.0f,其余为 0.0f

作者按:为什么要大费周折进行多通道空间编码?因为华容道的规则本质上是关于『几何形状在受限空间中的物理阻挡』。在通道层面上剥离将领的离散身份,仅保留其物理几何边界与位置占位,相当于在二维网格中直接重建了物体的空间拓扑信息。这种表征方式保留了块的几何连续性与相邻排斥约束,从而引导卷积层能够聚焦于学习通道的开闭状态与卡位逻辑,而不是离散的符号语义。

2.3 网络结构设计与模型拟合

有了合理的空间输入,就可以构建模型,将这 12 万条数据熔炼成网络的"寻路直觉"。

网络结构设计为一个微型残差网络 MicroResNet

作者按:整个网络结构可划分为三大模块:

  1. 通道映射层 (Input Layer): 第一层使用 Conv2d(in_channels=5, hidden_channels=64, kernel_size=3, padding=1),将 5 个独热的空间形状通道映射到 64 维的隐特征空间,使网络在局部感受野内进行初步的空间邻接信息融合。
  2. 无下采样残差主干 (Residual Backbone): 核心是由 4 个同构的残差块(ResidualBlock)串联而成。每个残差块内部使用两个 3×33 \times 3 3×3 卷积,且填充 strictly 设为 1,确保分辨率自始至终锁死在 5×45 \times 4 5×4。 两个关键的设计原则:
    • 坚决禁用池化层 (No Pooling): 在传统的图像分类任务中,下采样与池化层被用来压缩空间分辨率并获得"平移不变性"。但华容道是精确的物理坐标约束。曹操如果横向偏移了一个格子,整盘棋的通断状态就会改变。任何池化降采样都会导致棋子的绝对边界变得模糊,扰乱空间拓扑计算。因此必须全程维持 5×45 \times 4 5×4 的空间绝对分辨率。
    • 残差短路设计: 每一个残差块的跳跃连接(Skip Connection)在数学上为梯度提供了直通路径。即使在小尺寸棋盘下进行深层堆叠,网络也能顺利收敛,并在深层提炼出各棋子形状在全局受限通道上的阻碍与咬合关系。
  3. 轻量化价值输出头 (Value Head):
    • 1x1 降维卷积: 堆叠残差提取后,引入 Conv2d(64, 16, kernel_size=1) 的降维通道,在通道维度上完成特征融合,同时降低参数量。
    • 多层 MLP 映射步数: 将降维后的特征图展平为 320 维向量,通过两层全连接层 Linear(320 -> 128 -> 1),拟合出预测的最优步数 h(n)h(n) h(n)。

使用 12.1 万条数据对网络进行拟合特训,让网络以数十万级别的轻量参数学会"看一眼盘面"就能感知当前状态到终点的剩余距离。

2.4 求解器集成:A* 算法与 ONNX 运行时

模型训练完成后,核心任务是将其与 A* 搜索算法进行融合。

在标准 A* 寻路公式 f(n)=g(n)+h(n)f(n) = g(n) + h(n) f(n)=g(n)+h(n) 中,我们直接将启发式函数 h(n)h(n) h(n) 替换为神经网络的预估步数 hθ(n) h_{\theta}(n) hθ(n)。传统的启发式函数(如曼哈顿距离)无法感知复杂的木块卡位与阻挡关系,而训练后的网络能基于空间拓扑输出更准的剩余步数预测,引导 A* 算法快速收敛。

为了让方案能在端侧(如 Compose 客户端)独立运行,我们通过 torch.onnx.export 将 PyTorch 模型导出为轻量级 ONNX 格式。

在实践中,为了进一步缩短每次搜索的耗时,做了两项工程优化:

  • 批处理推理 (Batch Inference):生成子状态时,将所有候选下一步打包成一个 Batch 送入 ONNX 运行时,减少频繁调用推理引擎的开销。
  • 减少内存拷贝:在 JVM 客户端中,利用 Direct Buffer 直通底层 C++ 引擎,规避数据拷贝带来的性能损耗。

3. 评估偏差分析:双驼峰拓扑、分层抽样与可采纳性诊断

将 A* 算法与 ONNX 运行时集成到 Kotlin Compose 客户端后,实际测试时求解器的寻路表现有些反常:对于一些临近通关的简单残局,寻路步数明显偏多,搜索过程容易走弯路。

排查状态转移代码与 g(n)g(n) g(n) 步数累加器后,确认算法底层逻辑没有问题。疑点便落在了神经网络输出的启发式估值 hθ(n) h_{\theta}(n) hθ(n) 上------*模型是否在浅水区高估了剩余步数,从而破坏了 A 算法的可采纳性(Admissibility)?**

为了验证这一猜想,我从全空间拓扑分布、抽样统计设计以及基线模型实测三个维度展开了分析。

3.1 真实数据分布:双驼峰拓扑结构

根据 A* 算法理论,启发式函数 h(n)h(n) h(n) 必须满足"可采纳性"(Admissibility),即对任意节点 nn n,估算代价不能大于真实剩余代价:

h(n)≤h∗(n)h(n) \le h^*(n) h(n)≤h∗(n)

否则,A* 算法就会错把最优路径当成高昂代价而放弃优先扩展,导致算法退化为局部贪婪搜索。

对生成的 1,284,563 条全连通状态空间进行全量统计,盘面在不同最短步数 h∗(n)h^*(n) h∗(n) 下的状态密度分布如下图所示:

作者按:读者诸君观察这两张图(上方为全空间全貌图,下方为 0~120 步细节放大图)会发现,分布情况并非单峰高斯分布,而是呈现出非常显著的"双波峰与中间波谷"的驼峰状!

  1. 驼峰一:残局出口聚集区 ( h∗∈6,15h^* \in 6, 15 h∗∈6,15 步,单步峰值 ~2.44 万条) 当曹操被推送至底部出口附近( h∗≤15h^* \le 15 h∗≤15)时,曹操的占位被锁定在底部。此时上方剩下的网格空间由其他 9 个棋子填充。由于空间相对小且存在大量局部组合摆放,使得临近通关的残局状态迅速聚集,形成了第一个密度驼峰。但在 h∗=0∼3h^*=0 \sim 3 h∗=0∼3 的极浅冲刺区,样本依然极其稀少(通关状态 h∗=0h^*=0 h∗=0 全库仅 2,669 条,仅占 0.208%)。
  2. 中间鞍部:阵型换位峡谷瓶颈 ( h∗∈17,33h^* \in 17, 33 h∗∈17,33 步,跌至单步仅 8,537 条) 在步数 20 到 32 之间,盘面数量急剧缩减了近 66%。在华容道解法中,要将曹操从上盘位转移至下盘位,必须经历极其苛刻的大阵型换位(如关羽横向穿梭、两竖将对调)。在这些关键步骤中,棋子互相卡死,能走的合法盘面组合极度狭窄,因此状态数量急剧收缩,形成了深凹的波谷。
  3. 驼峰二:主干中盘组合爆炸区 ( h∗∈34,80h^* \in 34, 80 h∗∈34,80 步,单步最高峰 25,212 条 at Step 48) 跨过换位瓶颈后,当曹操位于棋盘中上方时,四周棋子拥有最大的自由度进行排列组合与卡位,产生了全局数量最多的中盘组合(占全库 69.1%),形成了第二个更高的驼峰。
  4. 高难长尾水域 ( h∗>80h^* > 80 h∗>80 步) 延伸至 219 步的高难死锁盘面(如"横刀立马"等),占全库 8.7%。

结合双驼峰拓扑特征与基线模型残差跌穿零轴的实测交界点( h∗≈16∼17h^* \approx 16 \sim 17 h∗≈16∼17),我们将整个状态空间划分为三个区域:

  • 浅水区 ( 0≤h∗≤160 \le h^* \le 16 0≤h∗≤16):即"驼峰一"所在的残局区(共 30.6 万条状态,占 23.8%)。这一区间是基线模型高估击穿 Admissibility 的重灾区。
  • 主干深水区 ( 17≤h∗≤8017 \le h^* \le 80 17≤h∗≤80):跨越"换位波谷"与"驼峰二"的主战场(共 86.6 万条状态,占 67.5%)。
  • 长尾区 ( h∗>80h^* > 80 h∗>80):占 8.7% 的高难盘面。

使用标准损失函数时,其优化目标是最小化全量样本上的平均误差:

LHuber=1N ∑i=1N lδ (hθ(ni)−h∗(ni)) \mathcal{L}{\text{Huber}} = \frac{1}{N} \sum{i=1}^{N} l_{\delta}\left(h_{\theta}(n_i) - h^*(n_i)\right) LHuber=N1∑i=1Nlδ(hθ(ni)−h∗(ni))

在全局 Loss 梯度中,占据 67.5% 权重的深水区"驼峰二"数据形成了主导。神经网络在拟合多峰分布时,连续回归曲线难以适应中间陡峭的波谷,倾向于抹平波谷与驼峰一,将极浅水区( h∗∈0,5h^* \in 0, 5 h∗∈0,5)的预测输出系统性向上拉抬,产生正向预测偏差(Overestimation Bias)。

3.2 评估方法:按距离分层等量随机抽样

为了评估模型在不同离散步数 s∈0,120s \in 0, 120 s∈0,120 上的条件期望 Ehθ(n)∣h∗(n)=s \mathbb{E}h_{\\theta}(n) \\mid h\^\*(n)=s Ehθ(n)∣h∗(n)=s,需要设计合理的采样策略。

传统的采样方式在此场景下均有局限:

  • 全局随机采样 :由于深水区( h∗∈34,80h^* \in 34, 80 h∗∈34,80)占据近 70% 的数据量,全局无差别抽样会导致浅水区( h∗≤16h^* \le 16 h∗≤16)样本极少(如 h∗=0h^*=0 h∗=0 全库仅 2,669 条,随机抽样期望不足 2 条),无法暴露通关临近区的估计偏差。
  • 固定小样本采样(如每步抽 10 条):受不同步数下盘面拓扑复杂度与状态方差差异影响,小样本均值容易受离群值干扰,难以稳定估算残差。

为此,采用按距离分层等量随机抽样 (Equal-Size Stratified Sampling) 方案:在每个离散步数 s∈0,120s \in 0, 120 s∈0,120 上,无放回地随机抽取 Ns=200 N_s = 200 Ns=200 个独立盘面:

Ss=Sample (n∈D∣h∗(n)=s, Ns=200) ,∀s∈0,120 \mathcal{S}_s = \text{Sample}\left({n \in \mathcal{D} \mid h^*(n) = s},\ N_s = 200\right), \quad \forall s \in 0, 120 Ss=Sample(n∈D∣h∗(n)=s, Ns=200),∀s∈0,120

这一方案解耦了采样密度与数据分布权重的绑定,保证浅水区与深水区在评估时具备同等的统计有效性。

3.3 基线模型实测:可采纳性问题

基于上述分层抽样方案,我们对使用常规 HuberLoss 训练的基线模型(huber_baseline.pth)进行了评估,实测数据如下:

真实步数 h∗(n)h^*(n) h∗(n) 模型预测均值 E \mathbb{E}h_{\\theta} E 预测残差 ( hθ−h∗ h_{\theta} - h^* hθ−h∗) 可采纳性状态 ( h≤h∗h \le h^* h≤h∗)
0 0.54 步 +0.54 步 违背 ( h>h∗h > h^* h>h∗)
1 1.73 步 +0.73 步 违背 ( h>h∗h > h^* h>h∗)
3 3.65 步 +0.65 步 违背 ( h>h∗h > h^* h>h∗)
5 5.79 步 +0.79 步 违背 ( h>h∗h > h^* h>h∗)
10 10.48 步 +0.48 步 违背 ( h>h∗h > h^* h>h∗)
15 15.21 步 +0.21 步 违背 ( h>h∗h > h^* h>h∗)
16 16.30 步 +0.30 步 违背 ( h>h∗h > h^* h>h∗)
17 16.99 步 -0.01 步 满足 (临界交界点)
20 19.89 步 -0.11 步 满足
50 49.31 步 -0.69 步 满足
100 91.30 步 -8.70 步 满足

数据反映了基线模型的瓶颈:

  1. 浅水区系统性高估 :在 0≤h∗≤160 \le h^* \le 16 0≤h∗≤16 范围内,模型输出全线高于真实步数(高估 +0.21∼+0.79+0.21 \sim +0.79 +0.21∼+0.79 步)。即便对于已通关盘面( h∗=0h^*=0 h∗=0),模型仍预测为 0.54 步。直至 h∗=17h^*=17 h∗=17,残差才首次转负。
  2. A 搜索效率退化的根源 *:正是这 +0.5∼+0.8+0.5 \sim +0.8 +0.5∼+0.8 步的高估,导致浅水区节点的估计总代价 f(n)=g(n)+h(n)f(n) = g(n) + h(n) f(n)=g(n)+h(n) 被抬高。在 A* 优先队列中,这会使得算法误判浅水区节点的吸引力,优先去尝试其他深度分支,进而造成搜索节点数激增。

分析表明,标准的 HuberLoss 以全局均方误差最小化为目标,在倾斜数据下无法约束估计下界,无法保障可采纳性(Admissibility, h≤h∗h \le h^* h≤h∗)约束。

为此,首要任务是更换 Loss 函数重新训练模型。至于是否需要补充工程手段,取决于重训后模型在浅水区与长尾区的实测效果。

4. 实践演进:模型重训与工程兜底

重新训练模型并验证效果后,我们再使用必要的工程手段解决可能存在的浅水区与长尾区高估问题。

4.1 重新训练:非对称损失函数 KlotskiLoss

问题的根源在于 HuberLoss 对高估没有额外惩罚,且在极不均衡的数据分布下天然忽视长尾样本。我手写了一个专为这一场景设计的 KlotskiLoss,核心是两个加权叠加:

L=LHuber(y,y^)×wasym×wendgame \mathcal{L} = \mathcal{L}{\text{Huber}}(y, \hat{y}) \times w{\text{asym}} \times w_{\text{endgame}} L=LHuber(y,y^)×wasym×wendgame

  • wasym w_{\text{asym}} wasym:若 y^>y \hat{y} > y y^>y(高估),权重为 4 ;否则为 1。强惩罚高估,引导模型向下拟合。
  • wendgame w_{\text{endgame}} wendgame: 1+ 20y+1 1 + \frac{20}{y + 1} 1+y+120。当 y=0y=0 y=0 时权重约为 21,当 y=100y=100 y=100 时权重约 1.2,使浅水区残局在 Loss 计算中获得远高于深水区的关注度。

作者按:这两个权重并非经过严格数学推导,而是出于工程直觉------"高估要狠罚,浅水区要强调"。超参数 over_penalty=4.0 和权重系数 20.0 是在几次实验后拍定的,不必过度解读为某种公理。

在载入旧模型权重的基础上,清空优化器状态并重置学习率 (Warm Restart),使用 KlotskiLoss 重新进行了 100 轮特训。

从 1~120 步的宏观偏差对照图中可以看到,使用 KlotskiLoss 重训后,模型的全局预测残差从基准模型的正向高估显著拉低至负向区间(浅水区平均残差从 +0.52+0.52 +0.52 步转为 −0.19-0.19 −0.19 步)。宏观均值( μ\mu μ)的改善看似满足了可采纳性(Admissibility, h≤h∗h \le h^* h≤h∗)的要求。

然而,"平均数不等于个体保障"。对于 A* 这种依赖优先队列全局排序的搜索算法而言,哪怕极少数样本突破 zero-bound 发生正向高估,也会瞬间破坏估计代价 f(n)=g(n)+h(n)f(n) = g(n) + h(n) f(n)=g(n)+h(n) 的单调性,导致算法在关键节点处误判吸引力,优先尝试远离目标的无用分支。

为了寻找单靠 Loss 重训无法覆盖的盲区,我们将视角聚焦到 h∗(n)∈1,40h^*(n) \in 1, 40 h∗(n)∈1,40 区间,对样本级残差分布(Residual Scatter)与越界占比进行了细粒度评估:

这张细粒度越界分布图展现了单靠模型拟合的真实工程边界,并为后续的工程兜底提供了直观的微观证据:

  1. 浅水区仍存零星正向高估( 1≤h∗(n)≤151 \le h^*(n) \le 15 1≤h∗(n)≤15) : 尽管重训后浅水区的平均残差已转为 −0.19-0.19 −0.19 步,但样本散点分布显示仍有约 31.7% 的样本存在微小正向高估( hθ(n)>h∗(n) h_\theta(n) > h^*(n) hθ(n)>h∗(n)) ,最高高估峰值达到 +2.48 步 (如 h∗=8h^*=8 h∗=8 节点)。由于 A* 算法对浅水区高估零容忍,浅水区节点一旦被高估 +2+2 +2 步,就会导致搜索树在临近通关前发生无谓的扩展------这促使我们在 4.2 节针对性地引入 15 步内 EGTB 精确查表
  2. 中间过渡带存在极值离群点( 15<h∗(n)≤4015 < h^*(n) \le 40 15<h∗(n)≤40) : 虽然中间地带的平均残差已稳居 −0.40-0.40 −0.40 步安全线以下,但由于高维空间样本分布稀疏,模型输出偶尔会出现剧烈的正向高估波动(实测极值离群点高估高达 +22.72 步 ,且有 46 个样本高估超过 +1.0+1.0 +1.0 步)。这种孤立的异常高估若落入 A* 优先队列,会将正确分支的优先级拉到队尾------这促使我们在 4.3 节补充输出截断操作

作者按:在看宏观均值曲线时,我们很容易沉溺于" Loss 降了、均值转负了"的假象。但搜索算法是极度挑食的,均值转负只能保证"大部分节点估计偏保守",无法阻止个别"叛逆"节点拉垮整个优先队列。把细节放大、把离群点晾出来,工程上的应对思路自然就清晰了。

4.2 残局表:15 步以内直查 EGTB

在确定引入 15 步残局表(Endgame Tablebase, EGTB)之前,我曾陷入过一个典型的工程抉择:是继续加大 Loss 惩罚、试图过度训练神经网络去硬啃残局,还是直接用工程手段"切掉"浅水区?

最终我们选择了后者。从算法演进、游戏性以及华容道盘面的几何拓扑来看,这一决策基于三个核心权衡:

  1. 规避模型"过度训练"(Over-training)的副作用MicroResNet 的参数容量只有 64 通道、4 个 Residual Block。如果为了消灭 15 步内那 31.7% 的零星高估,而在 KlotskiLoss 中无限加大浅水区权重或延长训练轮次,极易导致模型过度训练 。神经网络会强行用有限的容量去死记硬背具体浅水区盘面,进而挤占并牺牲中深水区( h∗(n)>40h^*(n) > 40 h∗(n)>40)全局表征的拟合能力与泛化性能。
  2. 状态空间急剧收敛与游戏性契合 : 从游戏性与规则来看,华容道按"一步一格"进行移动,15 步已经属于极度临近终点的位置。随着步数倒数到 15 步以内,状态空间呈指数级收敛,合法盘面总数急剧减少。将这部分有限状态写入哈希表,仅需极小的内存开销,就能换取 O(1)O(1) O(1) 时间复杂度下的 100% 绝对精确值。在响应耗时与内存开销上,这是一个极具性价比的工程交换。
  3. 盘面拓扑与"大局观"枢纽效应 : 换个"大局观"视角审视高维盘面的拓扑结构:不论开局阵型如何千变万化,当局面推进到最后 15 步时,盘面的全局形貌已经收敛到了少数几个 "关键枢纽盘面",一旦走法不是最优,会显得很"可爱"。

具体实现非常简洁:利用 BFS 逆向穷举所有 h∗(n)≤15h^*(n) \le 15 h∗(n)≤15 的通关盘面建立倒排哈希表;在 A* 启发式评估时优先查表,命中即返回 h∗(n)h^*(n) h∗(n),未命中才送入神经网络

4.3 输出纠偏:中间地带的截断与安全下界兜底

跨过 15 步的 EGTB 残局查表覆盖区后,我们进入了 15<h∗(n)≤4015 < h^*(n) \le 40 15<h∗(n)≤40 的中间过渡地带。

虽然 4.1 节的均值评估显示,KlotskiLoss 重训后该区间的平均残差已成功降至 −0.40-0.40 −0.40 步的安全保守区间,但实测散点数据暴露了隐藏在均值下的波动

  • 离群点极值高达 +22.72 步 :在采样的 12,500 个中间地带状态中,仍有 25.52% 的样本存在轻微正残差 。最极端的离群点出现在 h∗(n)=28h^*(n) = 28 h∗(n)=28 的节点处,模型预测值达到了 y^=50.72 \hat{y} = 50.72 y^=50.72(高估 +22.72+22.72 +22.72 步)。
  • 优先队列的沉底风险 :在 A* 搜索中,一旦处于正确最优路径上的关键节点被误抬高了 22 步,其估计总代价 f(n)=g(n)+h(n)f(n) = g(n) + h(n) f(n)=g(n)+h(n) 会瞬间暴涨。在 heapq 优先队列中,该节点将被压至极其靠后的位置,导致搜索算法弃用正确路径,转而在无用分支中盲目试错。

针对神经网络在样本稀疏区必然存在的概率性极值抖动,我们结合 4.2 节的 EGTB 先验条件,在模型输出层设计了一套双重纠偏与下界截断机制:

y^shifted =y^−Δmargin \hat{y}{\text{shifted}} = \hat{y} - \Delta{\text{margin}} y^shifted=y^−Δmargin

hfinal(n)=max⁡ (16, y^shifted ) h_{\text{final}}(n) = \max\left(16,\ \hat{y}_{\text{shifted}}\right) hfinal(n)=max(16, y^shifted)

设计意图:

  1. 安全平移 (Safety Offset, Δmargin \Delta_{\text{margin}} Δmargin) :设置 Δmargin=2.5 \Delta_{\text{margin}} = 2.5 Δmargin=2.5 步的安全偏置,将神经网络原始输出 y^ \hat{y} y^ 统一向下压平。实测数据表明,这一平移能够将中间地带 99.6% 以上的正偏离群点拉回到 h≤h∗h \le h^* h≤h∗ 的安全线以下。
  2. 物理下界硬截断 (Floor Clamping) :根据 4.2 节的逻辑,任何未命中 EGTB 查表的盘面,其真实最优步数物理上必然满足 h∗(n)≥16h^*(n) \ge 16 h∗(n)≥16 。因此,即便安全平移将估计值拉得过低,截断函数也能强制将其锁定在 1616 16 步下界,防止启发式估计值在过渡边缘出现断崖跌落。

这两步轻量的输出层逻辑,以极低开销为神经网络建立了一道防范极值高估的工程防线------宁可保守低估(低估只会增加少量探索节点),绝不许向上高估(高估会毁掉路径搜索)

5. 总结

至此,求解器的第一阶段算是收尾了。它的能力边界很具体:给定任意一个华容道盘面,它能够通过神经网络启发式引导的 A* 搜索,在合理的时间内找到一条趋近最优的通关路径。

这个过程涉及了几件有意思的事:

  • 表征设计:用空间化独热编码代替整数 ID,保留了盘面的几何拓扑,让卷积核能真正"看"到棋子的形状与占位。
  • 数据与训练 :以 BFS 生成的标定数据集训练 MicroResNet,但数据集本身的长尾分布与回归损失函数的优化倾向之间存在天然的博弈,单靠调参解决不了。
  • 混合调度 :最终的可采纳性保障,是由重训练(KlotskiLoss)、残局精确查表(EGTB)和输出截断三层叠加共同完成的,而不是来自某一层的"完美设计"。

而本次"好玩"实践,于我而言最大的收获是:"应当收一收工程师的自傲,增加科研者的谦逊,及早消除不确定性,避免盲目自信或想当然导致的错误"。

目前,此模型并不具备"大局观",它只是在每一步估算"当前局面大约还需要多少步"。

下一篇的核心问题是:给定当前盘面,能否训练一个模型,直接输出"接下来往哪个方向努力"的目标阵型------而不是逐步的操作指令?

读者诸君,我们下期见!

相关推荐
Android-Flutter2 小时前
android LeakCanary 工作原理 详解
android·kotlin
恣逍信点3 小时前
《凌微经》静态自悖——变化之自因
人工智能·科技·算法·机器学习·业界资讯·拓扑学·哲学
AFinalStone3 小时前
Android 7系统无障碍服务(一)全景图与架构概览
android·无障碍服务
Android-Flutter4 小时前
android 自定义view 详解
android·kotlin
WAsbry4 小时前
协程任务的失败控制:取消、异常传播与Supervisor
android
bytemaster4 小时前
GPT-5.6 三模之争:Sol、Terra、Luna 加六档推理,到底该怎么选
人工智能·程序员
Android打工仔4 小时前
从 finally 理解程序的控制流:它为什么不是 catch 后面的代码?
android·kotlin
随遇丿而安4 小时前
第15周:Service 全功能 + 后台优化
android
两万五千个小时4 小时前
DeepSeek Harness 的动力引擎:Agent Loop 是怎么转起来的
人工智能·程序员·架构