加入solve loop的原因
为了把解题规则应用到启发式上,我们引入了一个叫solve loop的东西。滑块拼图(以下简称TSP)原本并不是以对齐数字为目的的拼图。最开始带数字的盘面是练习盘,有点像教程;之后盘面上会出现图片,变成一个记住这张图片的图像记忆游戏。可是AI连第一个教程都很难通过。所以我们先教会了启发式解题过程。
1号和2号:只朝一个方向转
解题过程非常简单。假设要把1号移到第一个格子,就把空格放在1号后面。然后构建一个包含目标位置(1,1)坐标的环。只要一直朝一个方向转动这个环,1最终就会进入(1,1)坐标。接着用同样的方法对齐2,(1,2)也就对好了。

3号和4号:排好顺序一起放入
接下来才是问题。现在必须同时对齐3号和4号。如果数字向右进入正确区域,就必须3在前、4在后;反过来如果向左进入,就必须4在前、3在后,再转动环,才能进入正确区域。
如果3和4的位置是反的,就先在两个数字之间放入空格并转动环。这样就能交换3和4的顺序。然后把环转到3、4的坐标处,就能对齐第一行的1、2、3、4。这样一来,5、6、7、8也能轻松解开。

9号到15号:一次性放入
之后的9~15号,必须一次性解到所有坐标上。所以9~12要按顺序排列,接下来要按15、14、13的倒序构建环,才能得出答案。

修正的结果
之后,困难的工作交给编程AI,我们通过测试确认启发式是否正常运作。最初做的solve loop在500局中只解开了1局。第二天修正后的版本解开了全部4,000局,平均每局需要91步。同一时期用A*搜索做的教师,在搜索上限内解开了2,000局中的1,105局。

按阶段来看,同时放入3号和4号的区间平均22.5步,是最长的。因为要交换顺序再一起转进去,所以花的步数接近1号或2号的三倍。

在测试过程中我们发现,启发式对之后带图片的盘面也能很快解开。
接下来是AI训练
之后我们开始训练AI。一开始经常陷入无限循环的AI,随着训练的推进很快就能完成学习,但DQN没能解决这个问题。让模型模仿启发式解法的模仿学习,在重复40次的过程中,与启发式选择相同着法的比例从32%提高到了63%。重新运行训练好的模型的结果整理在第4篇中。
这一步的实际代码
这是放入1号的环。当空格和1号都位于环上时,先确定一次转动方向并锁定,然后只朝该方向一格一格地转动,直到1号进入(1,1)。
src/puzzle_planner.cpp
const bool use_snake = (target == 1) && tile1_snake_applicable(s);
const bool snake_rotate_ready =
use_snake && is_on_tile1_snake(s.blank_r, s.blank_c) && tile_position_on_snake(1, s);
if (snake_rotate_ready && loop_ctx && loop_ctx->locked_dir) {
if (!loop_ctx->locked_dir->has_value() || (loop_ctx->locked_target
&& *loop_ctx->locked_target != target)) {
lock_loop_dir_if_needed(loop_ctx,
target,
pick_best_snake_dir(s, target),
"snake until tile1→A1");
}
const SnakeLoopDirection dir = loop_ctx->locked_dir->value();
const auto chunk = snake_directed_one_step(s, target, dir);
if (!chunk || chunk->empty()) {
out.note = std::string("snake_") + snake_loop_direction_label(dir) + "_fail";
return out;
}
const int a = (*chunk)[0];
const TileCellMove mv = make_tile_move(s, static_cast<PuzzleAction>(a));
if (!apply_action_on_tile1_snake(s, static_cast<PuzzleAction>(a), target)) {
out.note = std::string("snake_") + snake_loop_direction_label(dir) + "_apply_fail";
return out;
}
out.moves.push_back(mv);
out.reached_goal = tile_at_goal(target, s);
out.note = std::string("snake_") + snake_loop_direction_label(dir);
return out;
}完整源代码和所用模型可以在“4. 完整源代码与使用的模型”一文中查看。
COMMENTS · CHAPTER 02
修正滑块拼图启发式 评论 0
还没有评论。阅读时有疑问或想补充的内容,欢迎留言。
发表评论前,请用 Google 开始并登记昵称和邮箱。
用 Google 开始