二维世界遗传算法控制的生物未实现有效学习的问题排查
我的目标
我尝试实现遗传算法,优化二维模拟世界中某类生物的适应度。该世界包含随机分布的可食用食物与怪物(基础僵尸),需算法找出让生物保持饱腹且存活的行为。
已完成的工作
- 用NumPy生成11×9的二维数组作为染色体,填充0到1的随机浮点数;
- 通过
np.matmul将染色体每行的随机权重与所有感知器(percepts)计算得到动作(actions); - 适应度计算公式为:
(能量 + 死亡时间×100); - 选择适应度高于平均值的精英个体,保留最优精英至下一代种群,剩余空位通过两种交叉方式生成新个体:每行双点交叉、交替选取父母染色体行。
遇到的核心问题
生物完全没表现出有效学习能力:75回合的模拟里,偶尔出现1个幸存者,偶尔有3-4个,但完全是随机的,看不到任何逐步进化的趋势。
核心代码片段
#!/usr/bin/env python from cosc343world import Creature, World import numpy as np import time import matplotlib.pyplot as plt import random import itertools # 可配置参数 numGenerations = 2000 numTurns = 75 worldType=2 gridSize=24 repeatableMode=False class MyCreature(Creature): def __init__(self, numPercepts, numActions): # 染色体为11×9的随机浮点数数组 self.chromosome = np.random.rand(11,9) self.fitness = 0 Creature.__init__(self) def AgentFunction(self, percepts, numActions): # 通过矩阵乘法将感知映射为动作 actions = np.matmul(self.chromosome, percepts) return actions.tolist() # 注:原始代码未完成种群更新的后续逻辑
问题排查与优化建议
我帮你梳理下几个最可能导致进化停滞的点,以及对应的修复方案:
1. 染色体与动作/感知的维度匹配问题
你用了11×9的染色体矩阵做np.matmul,这里必须满足感知器的维度是9,同时输出的动作维度是11,但代码里的numActions参数如果不是11的话,这就会出现不匹配的问题——模拟世界可能期望你输出固定维度的动作,但你给的维度不对,导致生物行为完全混乱。
修复建议:
- 先在
__init__里加print(numPercepts, numActions),确认感知器和动作的实际维度; - 调整染色体形状为
(numActions, numPercepts),这样np.matmul(chromosome, percepts)就能得到刚好numActions维度的动作向量。
2. 适应度函数的设计偏差
当前的适应度公式(能量 + 死亡时间×100)有很大问题:死亡时间的权重太高了,会导致那些“啥也不干,躲在角落苟活更久”的个体,比“主动觅食但不小心被怪物打死”的个体适应度高得多。这种导向下,种群根本不会朝着“有效生存”的方向进化,反而会往消极避战的方向走,自然看不到进化趋势。
修复建议:
重新设计适应度公式,强化“存活到最后”和“保持高能量”的正向反馈,比如:
# 示例:能量权重2,存活回合权重5,活到最后的额外奖励100 fitness = (creature.energy * 2) + (creature.time_alive * 5) + (100 if creature.is_alive else 0)
这样既能鼓励存活,也能鼓励主动觅食获取能量。
3. 缺少变异操作,种群多样性不足
你只实现了交叉,但完全没有变异操作!遗传算法里,交叉是组合已有基因,而变异是引入新的基因多样性。如果种群一开始的基因池没有能产生有效行为的个体,交叉来交叉去还是那些无效基因,根本跳不出随机混乱的状态。
修复建议:
给新生成的个体添加高斯变异,比如:
def mutate_chromosome(chromosome, mutation_rate=0.03, sigma=0.1): # 遍历染色体的每个权重,以mutation_rate的概率进行变异 for i in range(chromosome.shape[0]): for j in range(chromosome.shape[1]): if random.random() < mutation_rate: # 给权重加一个小的高斯噪声 chromosome[i][j] += np.random.normal(0, sigma) # 把权重限制在0-1之间(和初始化的范围一致) chromosome[i][j] = np.clip(chromosome[i][j], 0, 1) return chromosome
在交叉生成新个体后,调用这个函数对其染色体进行变异,保持种群的基因多样性。
4. 动作输出未标准化
np.matmul输出的动作值可能范围非常大(比如从负几十到正几十),但模拟世界大概率期望动作是标准化后的数值(比如0-1之间的概率值,或者-1到1的方向向量)。如果动作值超出了世界能处理的范围,生物的行为就会完全随机,根本无法体现染色体的控制作用。
修复建议:
在AgentFunction里对动作进行归一化处理:
def AgentFunction(self, percepts, numActions): actions = np.matmul(self.chromosome, percepts) # 归一化到0-1范围 if np.max(actions) != np.min(actions): actions = (actions - np.min(actions)) / (np.max(actions) - np.min(actions)) else: actions = np.zeros_like(actions) return actions.tolist()
或者用sigmoid函数把值压缩到0-1之间,确保动作在合理范围内。
5. 精英保留策略的细节优化
你提到保留最优精英,但如果只保留1个最优个体,很容易导致种群被这个个体的基因垄断,失去多样性;如果精英比例太高,同样会限制进化的可能性。
修复建议:
- 每代保留Top 10%的精英个体(比如种群规模是50的话,保留5个);
- 精英个体直接进入下一代,不参与交叉和变异,保证优秀基因的延续;
- 剩余的种群空位由交叉+变异的方式生成。
6. 种群规模与进化代数的匹配
2000代看起来很多,但如果你的种群规模太小(比如少于30个个体),基因多样性根本不够,进化就会陷入随机波动。
修复建议:
- 把种群规模调整到50-100个个体,保证足够的基因多样性;
- 先把进化代数降到500代,观察是否有明显的进化趋势,再逐步调整代数值。
内容的提问来源于stack exchange,提问作者eZ_Harry

