此后,新的智能体位置将用于更新测距和雷达传感器,以估计下一个时间步的新传感器输入:
self.update_rangefinder_sensors()
self.update_radar()
最后,以下函数测试智能体是否已到达迷宫出口:
distance = self.agent_distance_to_exit()
self.exit_found = (distance < self.exit_range)
return self.exit_found
如果已到达迷宫出口,则将exit_found字段的值设置为True以指示任务已成功完成,并且其值从函数调用中返回。
智能体记录存储
完成实验后,评估和可视化智能体在进化过程中各代种群的表现。这是通过在指定时间段内运行迷宫求解模拟后收集有关每个智能体的统计数据来实现的。智能体记录的收集由两个Python类实现:AgentRecord和AgentRecordStore。
从类的构造函数中可以看出,AgentRecord类包含以下数据字段:
def init(self,generation,agent_id):
self.generation = generation
self.agent_id = agent_id
self.x = -1
self.y = -1
self.fitness = -1
self.hit_exit = False
self.species_id = -1
self.species_age = -1
字段定义如下:
-
创建智能体记录时,generation保存世代的ID。
-
agent_id是唯一的智能体标识符。
-
x和y是完成模拟后智能体在迷宫中的位置。
-
fitness是智能体的适应度得分。
-
hit_exit是一个标志,指示代理是否已到达迷宫出口区域。
-
species_id和species_age是智能体所属的物种的ID和年龄。
AgentRecordStore类包含一个智能体记录列表,并提供用于将收集的记录从特定文件加载/转储到特定文件的功能。
在评估了基因组适应度之后,将新的AgentRecord实例添加到存储中,评估基因组适应度通过以下代码实现:
def eval_fitness(genome_id,genome,config,time_steps=400):
maze_env = copy.deepcopy(trialSim.orig_maze_environment)
control_net = neat.nn.FeedForwardNetwork.create(genome,config)
fitness = maze.maze_simulation_evaluate(
env=maze_env,net=control_net,time_steps=time_steps
)
record = agent.AgentRecord(
generation=trialSim.population.generation,
agent_id=genome_id
)
record.fitness = fitness
record.x = maze_env.agent.location.x
record.y = maze_env.agent.location.y
record.hit_exit = maze_env.exit_found
record.species_id = trialSim.population.species.get_species_id(genome_id)
record.species_age = record.generation - \
trialSim.population.species.get_species(genome_id).created
trialSim.record_store.add_record(record)
return fitness
该代码首先创建原始迷宫环境的副本,以避免评估运行之间的干扰。之后,它使用提供的NEAT配置从指定的基因组创建ANN,并在给定数量的时间步长下开始迷宫仿真。然后,将返回的智能体适应度得分以及其他统计信息存储到特定的AgentRecord实例中,并将其添加到记录存储中。
将收集的记录将保存到输出目录中的data.pickle文件中,并用于可视化所有评估智能体的性能。
智能体记录可视化
在神经进化过程中收集了所有智能体的评估记录后,可视化记录的数据以观察性能。可视化应包括所有智能体的最终位置,并允许设置物种适应度的阈值,以控制将物种添加到相应的绘图中。
该目标函数基于迷宫求解器的适应度得分的估算,该迷宫求解器通过在执行400个模拟步骤后测量其最终位置与迷宫出口之间的距离来进行估算。因此,目标函数是面向目标的,并且仅取决于实验的最终目标:到达迷宫出口区域。
该实验中使用的目标函数确定如下。首先,需要将损失函数定义为模拟结束时智能体的最终位置与迷宫出口的位置之间的欧几里得距离:
L = ∑ i = 1 2 ( a i − b i ) 2 \mathcal L=\sqrt{\sum_{i=1}2(a_i-b_i)2} L=i=1∑2(ai−bi)2
L \mathcal L L是损失函数, a a a是智能体的最终位置坐标, b b b是迷宫出口的坐标。
使用上述损失函数,定义适应度函数:
F = { 1.0 , L ≤ R e x i t F n , otherwise \mathcal F = \begin{cases} 1.0, & \text{ m a t h c a l L l e R _ e x i t \\mathcal L \\le R\_{exit} mathcalLleR_exit} \\ \mathcal F_n, & \text{otherwise} \end{cases} F={1.0,Fn,L≤Rexitotherwise
R e x i t R_{exit} Rexit是迷宫出口点周围出口区域的半径, F n \mathcal F_n Fn是归一化适应度得分。归一化的适应度评分如下:
F n = L − D i n i t D i n i t \mathcal F_n = {\frac {\mathcal L-D_{init}}{D_{init}}} Fn=DinitL−Dinit
D i n i t D_{init} Dinit是导航仿真开始时从智能体到迷宫出口的初始距离。
该公式将适应性评分标准化为 ( 0 , 1 ] (0,1] (0,1],但在极少数情况下,当智能体的最终位置远离其初始位置并且回合结束时,可能会导致负值。将应用标准化的适应度评分来避免出现负值:
F n = { 0.01 , F n ≤ 0 F n , otherwise \mathcal F_n = \begin{cases} 0.01, & \text{ m a t h c a l F _ n l e 0 \\mathcal F\_n \\le 0 mathcalF_nle0} \\ \mathcal F_n, & \text{otherwise} \end{cases} Fn={0.01,Fn,Fn≤0otherwise
当适应度得分小于或等于0.01时,将为其分配支持的最小适应度得分值(0.01);否则,将按原样使用。使最小适应度得分高于零,以使每个基因组都有繁殖的机会。
Python中的以下代码实现了面向目标的目标函数:
Calculate the fitness score based on distance from exit
fitness = env.agent_distance_to_exit()
if fitness <= self.exit_range:
fitness = 1.0
else:
Normalize fitness to range (0,1]
fitness = (env.initial_distance - fitness) / \
env.initial_distance
if fitness <= 0.01:
fitness = 0.01
下图显示了用于此实验的迷宫:
图中的迷宫有两个特定的位置,左上方的圆圈表示迷宫导航智能体的起始位置。右下角的圆圈标记迷宫出口的确切位置。迷宫求解器(智能体)需要到达迷宫出口的附近,该迷宫出口由其周围的特定出口范围区域表示,以完成任务。
NEAT-Python超参数选择
根据目标函数定义,可以到达迷宫出口区域而获得的导航智能体适应度得分的最大值为1.0。将人口规模设置为250。
[NEAT]
fitness_criterion = max
fitness_threshold = 1.0
pop_size = 250
reset_on_extinction = False
表型ANN的初始配置包括10个输入节点,2个输出节点和1个隐藏节点。输入和输出节点对应于输入传感器和控制信号输出。提供隐藏节点是为了从神经进化过程的开始就引入非线性,ANN配置如下:
num_hidden = 1
num_inputs