返回列表

Final notes and parameter tweaking

548. Lux AI Season 2 | lux-ai-season-2

开始: 2023-01-25 结束: 2023-05-08 游戏AI 数据算法赛
Final notes and parameter tweaking - Lux AI 2 竞赛总结

Final notes and parameter tweaking

当 Lux AI 2 的最终比赛仍在进行,而我尚未确定最终排名(看起来可能在 2-5 名之间)时,我想做一个(较长的)总结。我认为这是一次令人兴奋的比赛,游戏深度十足且平衡性极佳,真正奖励了不同类型的策略。我想我们可能只是触及了这个游戏的表层,即使比赛结束后,我也强烈推荐大家为了乐趣或研究而体验一下。

方法

我在本次比赛中采用的主要方法是使用 C++ 逻辑作为骨架,构建约 1500 个自定义参数,并使用定制的统计分析工具进行调优。从目标选择到战斗微操和移动冲突管理,一切都深受这些参数的影响。这些参数范围广泛,从更远地块的欲望调整、工厂生成地块评估,到可以调整关键 if 语句甚至关闭整个功能的布尔值,再到像 META_SIMCITY 这样的总体乘数,它会调整我在战略层面对和平行动(收集资源、清理碎石)与侵略行动(试图击杀敌方单位或挖掘敌方苔藓)的偏好。我试图将大多数"有主见的"逻辑至少用某个参数来捕捉,以便发现我的直觉是否准确。大多数时候确实如此,但也有一些真正的糟糕判断。

我选择这种方法是因为我知道这是我取得好成绩的可靠途径,而且非常有趣。我认为它结合了基于规则的机器人的优点,比如能够在某些地方计算出最优解(例如寻路),以及在策略和游戏机制上的一定灵活性,同时也吸收了强化学习的一些优点,比如你的软件慢慢领悟不同类型行动的最佳比例。我确实相信强化学习策略在这里可能有更高的潜力,但我没有采用,因为这个过程对我来说总是感觉有点不可预测,而且不够有趣。而且,我不是一个拥有充足硬件资金的研究团队,无法可靠地实现它。

参数优化

我试图通过运行大量游戏来调整参数,在每一局中我会随机化相当比例的参数(浮点数用正态分布,整数用均匀分布)。每局游戏中,我都会生成一个文件,记录所有参数的值、胜负结果,以及我认为可能有用的各种游戏统计数据,例如我建造了轻型单位数量、单位花费在挖掘碎石上的回合数、地图上起始碎石量等。然后,在游戏外的统计工具中(这个工具是我为 Halite 2 构建的),我会检查是否有改进参数的方法。

为此,我尝试了多种方法和奖励指标。对于奖励,我选择了一些选项:胜率、与对手的苔藓差异、我产生的能量量、相关版本的匹配评分,甚至是一个神经网络输出,试图基于诸如我的单位开采冰的时间比例等统计数据来估计我的匹配评分。

使用这些奖励,我会尝试通过几种自动和手动方法来寻找更好的值:我使用了基本统计工具,如皮尔逊相关系数和斯皮尔曼等级相关系数来查看数据中是否存在趋势(也许如果我的单位多开采一点矿石,我会赢得更多)。神经网络和随机森林树学习方法来尝试找到更好的值。简单的整数平均值或浮点数的加权平均值。有时我只是自己盯着图表,希望能理解其中的含义。基本上,我将参数奖励空间视为一个未知的、有噪声的多维函数来寻找最大值,但通常假设参数之间复杂的相互作用会小到足以让我通过单独调整每个参数在其个体贡献上的表现来进行相当有效的爬山法优化。我只是无法生成足够的数据来尝试其他方法。

我选择这种方法而不是模拟退火等更标准化的方法,是因为我能运行的游戏数量与参数数量相比太少了,而且存在大量噪声,我在过程中不断修改代码。在这种情况下,那种方法往往表现不佳。

参数更改后,我会检查排行榜上是否有改进,如果没有就回滚。排行榜极其嘈杂,所以这并不总是正确的判断,但我认为多数时候是正确的。这种数据导向方法的一个很好的优势是,我经常可以用低时间和脑力投入做出改进。我经常会在上班前花5分钟自动调整并上传一两个新版本,睡觉前也做同样的事。在我没有时间折腾代码的那些星期,这种学习方式带来了一些最大的成长期。

最初我 mostly 让游戏中的两个智能体都随机化并学习,实现双重学习率。后来我切换到静态参考玩家,以更好地追踪我的进度。最终我仍然不太确定哪种更好。静态参考玩家提供更纯粹的数据,但数据量更少,我觉得与更多随机化玩家对战也有一些优势,可以防止过拟合。

这里学到的一些教训是:

  • 这种学习方法,尽管存在所有这些随机性,在寻找改进的"低垂果实"方面相当有效
  • 它也非常不一致,在噪声中很难微调不太重要的参数
  • 你会认为,如果你有成千上万干净的、明确存在因果关系的数据点,你取某个参数,你的胜率得到皮尔逊相关系数1.05和对应的p值0.0000001。最重要的是,该参数的平均胜率实际上在图表上可观察地更高。那么如果你将这个参数向上调整,你至少会在局部开始赢得更多。没有什么比这更远离真相的了,几乎每次它都完全没有可衡量的影响。我不知道这意味着什么,也许只是我统计学得不好,但我觉得这里一定存在某种持续夸大或虚构不存在效果的基本效应。我知道这不仅仅是很多时候参数太多导致你刚好撞上低p值的概率,因为它持续发生在比你预期的更多的参数上。我觉得我可能在比赛后会花些时间更适当地探索这个问题
  • 胜率可能是总体上最有效的获得结果的方式。优化mmr或对手胜率这次几乎没给我带来什么效果(但在Halite中非常有效)
  • 我发现改变我使用的奖励似乎比一直应用相同的奖励效果更好。调整值的方法也是如此。一些奖励我认为有隐性的副作用,例如如果你优化挖掘所花费的回合比例,战斗单位可能会故意开始送死。通过改变奖励,这种效应会有所限制
  • 结合皮尔逊相关系数和我的游戏洞察力的手动调整通常给我带来最大的收益。使用皮尔逊的自动调整也相当有效。我通常使用:newvalue = oldvalue + (abs(oldvalue) * pearson * scaler),有时加上最大变化量,或要求p值低于某个水平。开始时我非常小心,使用较低的scaler(< 1),后来我发现了使用假参数模拟的方法,高scaler和频繁调整实际上让你更快到达最佳值,即使有很多噪声和小样本(< 1000)。这似乎效果更好,尽管我经常需要手动纠正漂移。我有一些完全不做事的参数,它们从1漂移到0.88,2到3.81,3到3.7,4到2.75,-5到-5.39。这其实还不算太糟。同时,我最初肯定估算错误的实际参数可以在整个比赛过程中移动10-100倍的因子,我推测是为了更好
  • 使用神经网络和随机森林树来尝试预测改进是浪费时间。我认为只是数据不够,参数中的随机噪声太多导致严重过拟合
  • 本地结果与排行榜之间的相关性不太好但还能接受
  • 拥有一个指示我版本号的参数并基于该版本的平均值调整奖励是至关重要的。这消除了很多奇怪的伪影,并让我能够相当可靠地在不同版本间使用数据,而不会产生太大影响
  • 最终我可能如果没花那么多时间在参数微调上,而是花更多时间设计深层游戏特定算法、观看比赛和修复bug,我会做得更好一点,但我认为使用这种方法获得的经验可能更有价值

图表与能量

作为收集游戏内数据的一个有趣的副作用,我可以轻松发现有趣的观察并绘制酷炫的图表。老实说,我大部分时间没有充分利用这一点,但有一张图表对我的策略产生了巨大影响。但首先,让我们向那些不太熟悉的人介绍一下苔藓发电。
对于你拥有的每一块苔藓地块,你每回合会产生1点能量。能量是一个超级关键的瓶颈,所以这很不错。要创造苔藓,你需要在工厂花费水来浇灌你的地块。这对新老苔藓地块都会消耗ceil(地块数/10)的水。在你没有浇水的回合,所有苔藓地块会衰减1。在大多数游戏中,大部分水来自重型单位开采的冰。你可以预期每单位水至少花费12点能量,加上可能的一些低效损失。现在已经有很多关于这个机制的分析,但基础是,忽略浇水的奇怪整数阈值,你可以预期在你浇水的回合,每块地成本为1.2能量,获得1,总变化为-0.2。在你没浇水的地块,你每块地获得1点能量。所以如果你交替浇水和不浇水,你可以预期每块地每回合获得0.4能量的收益,同时保持你的苔藓田完好。
这听起来相当不错。只需花费一点前期能量,你就能长出巨大的、有利可图的苔藓田,这还是游戏结束时你的分数来源。通过能量收益,你可以投资回工厂,收集更多冰,更多苔藓,更多能量。大约1-2个月进入比赛,我认为主流想法是,这是让经济运转的完全显而易见的方式。

主要的陷阱是,它实际上并没有真正起作用。现在,其他玩家的结果可能有所不同,但对我来说看起来是这样的:

冰收益图表

这是一张图表,x轴上是我在整个游戏中收集的冰量。y轴是我的总冰收益。两者都除以起始工厂数量。这里的冰收益是我从苔藓获得的能量,减去我花在挖掘苔藓用冰上的能量(工厂维护用的冰不计入)。蓝线是不同的拟合线,紫色点是胜利,红点是失败。绿/红线显示运行平均值,红色表示数据点很少。

现在我看到了两个主要问题。首先,我在任何游戏中都没有真正从冰中获得太多利润。其次,我收集的冰量并不像你希望的那样影响利润。你似乎在最右端获得更多利润的唯一原因是那些是压倒性的胜利。对于大部分游戏和失败,甚至可能有一点下降趋势。现在重要的是,这张图表没有包括该过程所需的碎石清理成本和移动/重排成本。如果我包含碎石清理成本,你会得到这张图:

含碎石成本的冰收益图表

除了这种看似缺乏利润的情况,大部分收益往往出现在游戏末期,那时能量已经不那么有用了。基本上,大型苔藓发电是个骗局!这里没有利润。原因从苔藓田经常不得不形成的尴尬形状(这意味着你需要更长的投资浇水阶段),到敌方单位清理你的碎石等等。

当然你需要分数,所以你不如早点浇水,至少保持收支平衡。但它就是不太像宣传的那样。无论如何,所有这些都是为了说明收集这类数据可能非常有用。在这种情况下,它让我更早地转向基于单位的能量生成,并让我意识到流行的苔藓运河实际上并不是个好主意。虽然它们节省了你在工厂周围挖掘所有东西的碎石挖掘成本,但苔藓的形状实际上意味着你在苔藓本身上损失更大,而且运河非常容易受到骚扰。
另一方面,建造单位进行能量生成显然更有利可图。你只需要让你的单位经常静止不动,这样它们就不会只是燃烧它们产生的能量。在我这种多单位风格的方法中,这远非易事。特别是工厂周围的堵塞情况,当可怕的敌方单位经过时,会迫使你进行多次移动,你必须处理冲突,防止单位相互碰撞。让单位冷静下来停止堵塞一直是我最大的战斗之一。最终,我的大多数单位都实现了自给自足,从未从工厂获取任何能量,这样我就可以将大部分工厂能量用于制造更多单位。这里有两张图表显示了我典型游戏中冰和矿石的能量利润(这里的冰利润忽略了碎石成本):

冰能量利润图表
矿石能量利润图表

回放查看器

除了我的统计工具,我还使用了一个为我自己回放格式设计的自定义回放查看器,这可以说更有用。我在我的智能体代码内生成回放数据,让我不仅能呈现客观的游戏数据,还能呈现各种调试数据。

这里是我的回放查看器的一个快速预览。单位的形状显示他们的移动。单位的填充显示他们的能量,便于一目了然地查看。还有一些小功能,如单位死亡高亮(洋红色)和传输(黄色)以及水运输者高亮。

回放查看器界面

这张图片显示了地图上每个地块的"基础"欲望,这对应于单位想去那里的程度,在单位特定调整(如与地块的距离)之前。

调试信息展示

这个查看器对于调试来说极其有用。当你能像这样在整个屏幕上轻松看到发生了什么,而不是必须挖掘日志时,会容易得多。例如,我之前的生成逻辑中有一个bug,我搞混了x和y坐标,所以工厂会优先在与冰相邻的地块同一列生成。如果我没有在我的生成欲望输出中发现奇怪的垂直线,可能永远找不到这个问题。

对于任何喜欢参加这类比赛的人来说,我强烈建议花点时间投资构建一个可重用的自定义回放查看器,可能还有一个分析统计数据的工具。在任何此类基础设施上投入的时间都能通过简化的调试轻松收回成本,否则这也是一个很棒的学习体验。

说到学习体验。我 mostly 选择C++作为我的智能体语言,因为我想学习这门语言。这个任务 mostly 失败了,因为我最终只是坚持使用了一些非常基本的语言用法,但至少它比替代方案性能要高得多。我 mostly 使用了一种丑陋但有效的方式,我有大量全局变量,可以在任何地方以极低的性能成本轻松访问。一个关键技巧是尽可能预计算一切。也许我在整个代码库中最常用的东西是迭代一个地块的邻居。只需在开始时将这一切预计算一次,就再也不用担心性能影响和字面意义上的边缘情况,这帮助巨大。除了一个在~900个单位附近失控的尴尬O(n^2)算法外,我从未真正遇到过超时。游戏本地仍然相当慢,但很大一部分是I/O,如果不彻底重新设计就无法加速。哦对了,使用预处理宏来避免日志开销也很酷。

长而准确的行动队列在这里非常有益,我 mostly 通过逐回合进入未来来做队列,让每个单位确定该回合的行动方案,解决任何移动、传输或拾取冲突,然后向前模拟下一回合。不幸的是,它经常失败,特别是当单位需要为战斗调整时,让其他人移开,这会阻止传输落地等。不过总的来说,我的重排成本还不错,因为我至少让重型单位在工厂(和太阳能板)相邻的矿上可预测地工作。我真正希望我能做到的一件事是某种系统,能让预测的更远回合的重要信息真正传递回更早的回合。这对我来说证明有点太具挑战性,无法想出一个好的性能系统,也许在后续比赛中!

在整个比赛的大部分时间里,直到最后,我一直与其他几位竞争者进行着激烈的竞争。特别是ry_andy_、Tigga和Harm Buisman。后来加入了强化学习玩家flg。感谢他们让这场比赛非常激动人心,当然也要感谢Stone Tao和Bovard Doerschuk-Tiberi组织这场比赛并创造了如此有趣的游戏。希望这些信息对某人有所帮助,我想我会在未来的比赛中见到你们中的一些人!

作者:SiestaGuru (danmctree)
发布时间:2023-04-25
竞赛排名:第3名
总得票数:43票
同比赛其他方案