Loading
第一代将会是一场灾难。在写下第一行代码之前,我就知道了。
这是实现之前的设计日志。
我正在构建一个基于物理的 2D 生物模拟器,让生物通过遗传算法进化出步态——没有手工编码的运动,没有强化学习,只有选择压力和时间。在编写物理引擎、基因组编码或变异算子之前,我想先思考一下我预期会发现什么。这篇文章就是关于这个的。
第一代将会是一场灾难。
五十个生物。我预计大多数会立即倒塌——在模拟开始后的第一个半秒内向前或向侧面倾倒,关节锁定在任何恢复启发式都无法挽救的位置。其中一个可能会在原地振动,关节以高频率振荡,耗费巨大的表观努力却哪儿也去不了。另一个可能以惊人的速度向后推进然后停下来。还有一个可能会完全冻结——没有任何运动,没有任何距离,但也没有倒下的惩罚,因为如果你不动就不会倒。这种做法最终被证明不是愚蠢而是理性的,以一种狭隘而令人沮丧的方式。
它们中没有一个会行走。没有一个会接近任何可被识别为运动的东西。
这只是进化,在最开始,在它有时间找到任何东西之前。我发现这是在触碰代码之前最值得深思的部分。
高分仍可能奖励错误行为。目标函数决定进化会利用什么。
显而易见的问题:为什么不写一个行走算法?你知道行走是什么样子的,你理解其中的力学,写出步态,调整参数,完成。
答案是编程行走和进化行走是在解决不同的问题,而只有第一个是你的问题。当你编程行走时,你定义方法,系统执行它。当你进化行走时,你定义成功,算法找到方法。这两种方法不可互换。你写出的方法会很脆弱——它只适用于你为其设计的特定身体、特定地形和特定条件,在此之外就会失败。进化出的步态应该更健壮,因为选择压力作用于健壮性;在轻微扰动下摔倒的生物不会产生后代。
核心洞见: 适应度函数是对胜利的定义,不是解决方案。算法提供解决方案。你的工作只是知道你在寻找什么。
这听起来显而易见,直到你盯着一个生物——它找到了一种在你的适应度函数上得分良好的方法,同时做着你从未预想到的事情。那时你才明白,定义成功比看起来更难,而一个盲目的优化器会以无情的效率利用这个定义中的任何歧义。
设置在其简洁中是故意的:
头部 ○
|
躯干 ▬▬▬
/ \
/ \
腿部 腿部进化的完全是行为层面的:六个控制关节运动的浮点数。每个关节有三个参数——频率(振荡速度)、幅度(摆动幅度)和相位偏移(相对于另一个关节,它在周期中的起始位置)。两个关节,每个三个参数——共六个数字。整个基因组是 [0, 1] 范围内的六个浮点值。
我计划的适应度函数:
def fitness(creature, duration=10.0):
start_x = creature.body.position.x
simulate(creature, duration)
end_x = creature.body.position.x
distance = end_x - start_x
falling_penalty = -50 if creature.is_fallen() else 0
return max(0, distance + falling_penalty)行走距离减去倒下惩罚。没有其他的。不奖励看起来像行走。不奖励能量效率。不奖励流畅性或类人性。只是:你向前移动了吗,最后你还站立着吗?
简洁是有意为之的。复杂的适应度函数会引入隐式约束,这些约束可能会将进化困在由这些约束而非实际目标所塑造的局部最优中。适应度函数越简单,算法就有越大的自由去找到我不曾想到的解决方案。
在运行任何东西之前,我已经可以预测三种主要的失败策略。这些不是 bug——它们是对我正在定义的适应度函数的理性回应。
倒下者 ——我预计会早早看到这种情况。它会发现积极地向前倾会在倒下之前将身体重心向前移动,此时腿部会接住它。然后重复。它的步态看起来像受控的跌倒:倾斜、接住、倾斜、接住。适应度得分会不错。按照函数衡量的每个指标,它都在成功。
它不会在行走。但适应度函数不知道什么是行走,算法也不知道。这个教训是即时而令人不安的:适应度函数不在乎方法。如果向前跌倒能达到结果,那你得到的就是向前跌倒。如果你关心生物如何移动,而不仅仅是移动多远,你的适应度函数必须对此进行衡量。隐式约束不是约束。
振动者 ——最大关节频率,最小幅度,净位移为零。它看起来非常努力。它哪儿也去不了。但问题是:在这个模拟中,高频低幅度的运动能量消耗很低,而适应度函数没有能量项。振动者会找到一种消耗最少努力的策略,同时产生足够的结构振动来避免我添加的任何最小运动惩罚。它在玩弄指标——没有恶意,没有理解,只有纯粹的有效性。
局部最优从内部看起来像解决方案。振动者的适应度在各代中将保持稳定。它不会改善,但也不会被淘汰,因为它的生态位——低努力、低奖励、高存活——是一贯的。进化不需要进步,它需要生存,而生存是一个比卓越低得多的门槛。
冻结者 ——零运动,零距离。但也没有倒下惩罚,因为你不动就不会倒。冻结的生物会正确地识别出,在早期几代中尝试运动比获得收益更可能产生倒下。在当前种群技能水平下,静止不动是风险最优的。
这将持续下去,直到我添加最小运动要求。只有那时,进化才应该开始认真探索运动。这是三者中最深刻的:探索必须被强制。盲目的优化器会找到局部最安全的策略,而不是全局最佳的策略。好奇心奖励、多样性压力和最小探索约束不是作弊——它们是让适应度景观可导航而非陷阱所必需的。
如果失败模式得到正确处理,我认为在 50 到 100 代内可能会出现真正令人惊讶的东西。
我的猜测是,解决方案不会是我天真预期的对称两足步态。我预计算法会在对称之前找到不对称——一个关节以高频运行短弧,另一个以低频运行宽弧。不对称性可能会产生轻微的旋转力矩,与向前动量结合,产生每个周期的净位移超过任何对称策略所能达到的程度。
这会违反直觉,看起来是错误的,但可能有效。
算法对对称性没有审美偏见。我有,因为我看过人类和动物行走。这种偏见在这里是负担,而不是资产。搜索将覆盖我凭直觉会排除的参数空间,而解决方案可能就住在那里。
这与我在构建 Exira-X 时不断回归的问题相连:能力与理解之间的关系。它们以重要的方式分离。
一个系统可以非常擅长某件事,却对它一无所知。进化不理解适应——它就是适应,在化学和时间中实例化。语言模型不理解推理——它产生的输出在包含人类推理的文本分布上得分很高。市场不理解效率——它找到供需出清的价格,而不需要任何参与者对整个系统建模。
没有理解的优化是宇宙中主导的智能模式。需要明确理解的那种智能——人类所做的那种——是近期的,可能是不寻常的。
我期望这个项目改变我的不是关于机器人技术的信念,而是关于设计系统的信念。如果你能定义胜利是什么样子的,你可能不需要定义如何获胜。这个原则贯穿了我在这里构建的一切。
模拟将在 /projects/creature-evolution 随着开发进展而展示。我不知道第 100 代是什么样子,这正是重点所在。