AI 生活






4.96/5 (230投票s)
游戏中的引导行为、遗传算法和神经网络
提示:要使用该程序,请通过“设置”子菜单设置所需的参数,然后初始化相应的类型。
引言
该项目旨在开发一个能够演示和模拟人工智能的程序。该项目实现了三种主要技术,这些技术广泛应用于游戏行业和机器人技术中,用于编写智能代理。
- 神经网络
- 遗传算法
- 引导行为
该项目将实现其中的每一种技术,并展示它们在创建智能代理方面的有用性。
演示的算法几乎是所有游戏的核心。该项目的功能将直接使其成为理解创建智能代理的各种技术的工具。经过改进的版本,适合特定需求,可用于训练现实世界的机器人执行实际应用,如巡逻、探雷、进化等。该项目还可以用作研究进化的手段。此外,该项目还将允许创建包含智能代理的游戏。
应用
- 游戏开发
- 模拟器开发
- 机器学习
- 智能玩具
背景
神经网络
人工神经网络 (ANN),通常简称为“神经网络” (NN),是基于生物神经网络的数学模型或计算模型。它由一组相互连接的人工神经元组成,并使用计算的连接主义方法来处理信息。在大多数情况下,ANN是一种自适应系统,在学习阶段,它会根据流入网络的外部或内部信息改变其结构。
更实际地说,神经网络是非线性统计数据建模工具。它们可用于对输入和输出之间的复杂关系进行建模,或在数据中查找模式。
也许,ANN最大的优势在于它们可以作为一种“从观察数据中学习”的任意函数逼近机制。然而,使用它们并非易事,并且需要对底层理论有相当好的理解。
- 模型选择:这将取决于数据表示和应用程序。过于复杂的模型往往会导致学习问题。
- 学习算法:学习算法之间存在许多权衡。几乎任何算法在具有正确类型的参数以在特定的固定数据集上进行训练时都能很好地工作。然而,选择和调整算法以在未见过的数据上进行训练需要大量的实验。
- 鲁棒性:如果模型、成本函数和学习算法选择得当,生成的 ANN 可以非常鲁棒。
通过正确的实现,ANN可以自然地用于在线学习和大型数据集应用。它们简单的实现以及结构中存在的大多数字段依赖性,使得在硬件中可以进行快速的并行实现。
遗传算法
遗传算法 (GA) 是一种在计算中用于查找优化和搜索问题的精确或近似解的搜索技术。遗传算法被归类为全局搜索启发式方法。遗传算法是进化算法(也称为进化计算)的一个特定类别,它使用受进化生物学启发的技巧,如遗传、突变、选择和交叉(也称为重组)。
遗传算法作为计算机模拟实现,其中一对优化问题的候选解(称为个体、生物或表型)的抽象表示(称为染色体、基因型或基因组)的种群会向着更好的解决方案演进。传统上,解决方案以二进制字符串表示,由0和1组成,但其他编码也是可能的。
演进通常从随机生成的个体种群开始,并在几代中进行。在每一代中,都会评估种群中每个个体的适应度,从当前种群中(基于其适应度)随机选择多个个体,并进行修改(重组,并可能随机突变)以形成新的种群。然后在新种群用于算法的下一次迭代。通常,算法在产生最大代数时终止,或者在种群达到令人满意的适应度水平时终止。如果算法因最大代数而终止,则可能已达到令人满意的解决方案,也可能未达到。
遗传算法应用于生物信息学、系统发生学、计算机科学、工程学、经济学、化学、制造、数学、物理学等领域。
引导行为
引导行为为动画和游戏中的自主角色提供了一种导航方式:能够在逼真和即兴的方式下在世界中导航。这些引导行为在很大程度上独立于角色的移动方式。引导行为的组合可用于实现更高级别的目标(例如:到达某处并避开障碍物,沿着这条走廊走,加入那群角色……)。
自主角色是一种自主代理,用于计算机动画和交互式媒体,如游戏和虚拟现实。这些代理代表故事或游戏中的角色,并具有一定的即兴创作能力。这与动画电影中的角色(其动作是预先编写的)以及游戏或虚拟现实中的“头像”(其动作由人类玩家或参与者实时控制)形成对比。在游戏中,自主角色有时被称为非玩家角色。自主角色必须结合自主机器人的方面和即兴戏剧中人类演员的一些技能。这些角色通常不是真正的机器人,当然也不是人类演员,但它们具有两者的某些特性。
术语“行为”有很多含义。它可以指人类或其他动物基于意志或本能的复杂行为。它可以指简单机械系统的基本可预测行为,或混沌系统的复杂行为。在虚拟现实和多媒体应用程序中,它有时用作“动画”的同义词。在这里,“行为”一词用于指自主角色的即兴和逼真的动作。
设计
该项目有三个子项目,即
- 引导行为
- EStrings
- 蚂蚁
项目中的类组织结构如下所示

上面的图像描述了类之间的关系,而不是继承关系。简而言之,MainForm
类拥有 SBC
(引导行为控制器)、Cosmos
和 EStrings
(进化字符串)的对象。MainForm
使用它们来管理程序。
在这里,SBC
创建 Vehicle
类的对象;每个 Vehicle
都可以访问 SteeringBehaviours
类,它使用该类来确定在当前行为下应用于自身的力(方向和速度)。SteeringBehaviours
和 Vehicle
都使用 Vector
进行向量计算。
Cosmos
类代表了其中蚂蚁的世界。它是面板的绘图区域。Cosmos
类创建 Ant
s 的对象;Ant
s 反过来创建 Network
(它们的大脑)的对象,它们使用该对象来确定位置、速度和旋转。
EStrings
类是遗传算法及其如何用于收敛到解决方案的一个简单演示。在这里,目标是一个字符串,但实际上,它可以是任何可以用计算机表示的东西。尝试找出您的名字需要多少代来进化!
Using the Code
现在,我将逐一详细解释。第一个是
引导行为
简而言之,引导行为是一种移动自主游戏代理的方式,使其看起来好像它有大脑。几乎所有的游戏都使用它们来产生人类般的行为。但实际上,这并没有什么智能,这只是一种错觉,完全是数学。我实现了以下行为:
行为名称 | 描述 |
1. Seek |
Seek 引导行为返回一个将代理导向目标位置的力。 |
2. Flee |
Flee 引导行为返回一个将代理导离目标位置的力。 |
3. Arrive |
Arrive 是一种行为,它引导代理以减速的方式到达目标位置。 |
4. Wander |
Wander 是一种以随机方式引导代理,没有目标位置的行为。 |
5. PathFollowing |
PathFollowing 创建一个引导力,使车辆沿着一系列航点移动,形成一条路径。 |
6. Cohesion |
Cohesion 产生一个引导力,将车辆导向其邻居的质心。 |
7. Alignment |
Alignment 尝试使车辆的航向与其邻居对齐。 |
8. Separation |
Separation 创建一个力,将车辆从其邻近区域内的车辆推开。 |
描述
引导行为使用以下模型作为模拟各种行为的基础。

有关更多详细信息,请参阅本文。
简单的车辆模型
变量 | 类型 |
质量 |
标量 |
位置 |
向量 |
速度 |
向量 |
最大力 |
标量 |
最大速度 |
标量 |
此处由...表示
private float mass;
private int max_speed, max_force, vehicleNo;
private Vector2 currentPosition, velocity, acceleration,
heading, steerForce, targetPosition;
这些变量维护有关车辆当前状态的数据。引导行为的函数使用它们来计算力。我认为它们都显而易见。然后,该力用于确定车辆的新位置、速度、加速度和航向。现在,在获得新值后,它们又被用于计算新的值!以此类推,循环继续。这个循环连续更新车辆的位置,正如引导行为所描述的那样。
以下代码行精确地演示了如何做到这一点。在此,steerForce
和 velocity
被截断,因为在现实生活中,车辆确实有其限制。对 max_force
、max_speed
和 mass
的微小改变可以使最好的跑车和最差的跑车之间产生差异(如果你在赛车游戏中使用这个;这似乎很奇怪,但它就是这样!)。
steerForce = Vector2.Truncate(steerForce, max_force);
acceleration = steerForce / mass;
velocity = Vector2.Truncate(velocity + acceleration, max_speed);
currentPosition = Vector2.Add(velocity, currentPosition);
1. Seek (寻找)
现在,让我们谈谈 Seek
!它是所有行为中最简单理解和编码的。这是代码
public static Vector2 Seek(ref Vector2 targetPosition, ref Vector2 currentPosition,
ref Vector2 Velocity, int max_speed)
{
Vector2 desired_V = Vector2.Normalize(
Vector2.Subtract(targetPosition, currentPosition)) * max_speed;
return Vector2.Subtract(desired_V, Velocity);
}
首先,计算期望速度。这是代理在理想世界中到达目标位置所需的速度。它表示从代理到目标的向量,其长度被缩放到代理的最大可能速度。
此方法返回的转向力是所需的力,将其添加到代理的当前速度向量会产生期望速度。要实现这一点,只需将代理的当前速度从期望速度中减去即可。
这是一个它对 100 个游戏代理的效果(我让它们看起来像警车,也许它们在追捕罪犯!)。

是的,通过使用左上角提示的按键,你可以实时控制第一辆车的变量,并观察它如何影响车辆的行为。绿色高亮的变量是通用的,黄色高亮的变量是特定于某个引导行为的。
2. Flee (逃离)
现在是 Flee
。它与 Seek
完全相反。在这里,我们从 currentPosition
中减去 targetPosition
,而不是从 targetPosition
中减去 currentPosition
。这是代码(请看 else
部分)
public static Vector2 Flee(Graphics g,ref Vector2 targetPosition,
ref Vector2 currentPosition, ref Vector2 Velocity,
int max_speed, int FOV,int vehicleNo)
{
if (mainForm.steeringBehaviour != SB.CF && mainForm.steeringBehaviour !=
SB.FCS && mainForm.steeringBehaviour != SB.FCAS && vehicleNo ==1)
{
g.DrawEllipse(Pens.Red, new RectangleF(new PointF(targetPosition.X - FOV,
targetPosition.Y - FOV), new SizeF(FOV*2, FOV*2)));
}
if (Vector2.Length(Vector2.Subtract(targetPosition, currentPosition)) > FOV)
{
return None();
}
else
{
Vector2 desired_V = Vector2.Normalize(Vector2.Subtract(
currentPosition, SBC.targetPosition)) * max_speed;
return Vector2.Subtract(desired_V, Velocity);
}
}
现在,您想知道其他代码是关于什么的。好吧,我必须告诉您,引导行为有很多不同的实现方式;有些很简单,有些则更复杂。这个实现使用 FOV(视野)的概念,即代理可见的区域。这使得代理在看到危险时会做出逃跑的反应。(这是有道理的;在现实生活中,每个代理都有有限的 FOV,例如,一只鹿会跑,如果它**看到**了狮子(即,如果狮子在它的 FOV 中))。所以很简单!现在,第一个 if()
是关于是否显示/绘制 FOV。下一个 if()
确定是否逃跑(即,威胁是否在 FOV 中?)。
这是 100 个代理在害怕时如何反应。**注意,您可以使用 Y 和 H 键改变 FOV。**

3. Arrive (到达)
现在是 Arrive
。Seek
对于让代理朝正确的方向移动很有用,但通常,您希望您的代理在目标位置轻柔地停止,正如您所见,Seek
在优雅停止方面并不擅长。Arrive
是一种行为,它引导代理以减速的方式到达目标位置。
这是代码(注意 arriveRadius
)
public static Vector2 Arrive(Graphics g,ref Vector2 targetPosition,
ref Vector2 currentPosition,ref Vector2 Velocity,
int arriveRadius, int max_speed,int vehicleNo)
{
if (vehicleNo == 1)
{
g.DrawEllipse(Pens.SkyBlue, new RectangleF(
new PointF(targetPosition.X - arriveRadius,
targetPosition.Y - arriveRadius),
new SizeF(arriveRadius * 2, arriveRadius * 2)));
}
Vector2 toTarget = Vector2.Subtract(targetPosition, currentPosition);
double distance = toTarget.Length();
if (distance > 0)
{
double speed = max_speed * (distance / arriveRadius);
speed = Math.Min(speed, max_speed);
Vector2 desired_V = toTarget * (float)(speed/distance);
return Vector2.Subtract(desired_V, Velocity);
}
return new Vector2(0, 0);
}
不要被 if()
语句的用法弄糊涂;它之所以存在,是为了让只有一辆车绘制到达半径,而不是所有车。这提高了性能,而且也合乎逻辑,因为 100 个圆圈重叠在一起没有意义。在这里,(distance / arriveRadius)
导致车辆在接近目标位置时速度逐渐减慢,最终停止。
这是 100 辆车停放时的样子!或者,它们可能是在讨论什么的人!**此行为和其他引导行为的确切用途取决于游戏;这完全取决于设计师的想象力。**

4. Wander (漫游)
现在是 Wander
。Wander
是一种随机转向。一种简单的实现方式是在每一帧中生成一个随机的转向力,但这会产生相当不有趣的运动。它很不稳定,而且不会产生持续的转弯。一种更有趣的方法是保留转向方向状态,并在每一帧中对其进行小的随机位移。因此,在一帧中,角色可能会向上向右转,而在下一帧中,它将仍然朝几乎相同的方向转动。转向力从一个方向随机走向另一个方向。为了产生下一帧的转向力,将一个随机位移添加到先前的值,然后再次将总和限制在球体的表面。球体的半径(大圆)决定了最大的漫游强度,而随机位移的大小(小圆)决定了漫游速率。
这是代码
public static Vector2 Wander(Graphics g, ref Vector2 wanderTarget,
ref Vector2 currentPosition, ref Vector2 Velocity, ref Vector2 heading,
float wanderRadius, float wanderDistance, int wanderJitter)
{
heading = Vector2.Normalize(Velocity);
wanderTarget += new Vector2(random.Next(-wanderJitter, wanderJitter),
random.Next(-wanderJitter, wanderJitter));
wanderTarget = Vector2.Normalize(wanderTarget);
wanderTarget *= wanderRadius / 2;
PointF circleCenterG = new PointF((heading.X * wanderDistance + currentPosition.X) -
wanderRadius / 2, (heading.Y * wanderDistance +
currentPosition.Y) - wanderRadius / 2);
Vector2 circleCenterM = new Vector2((heading.X * wanderDistance) + currentPosition.X,
(heading.Y * wanderDistance) + currentPosition.Y);
Vector2 pointOnCircle = new Vector2(circleCenterM.X + wanderTarget.X,
circleCenterM.Y + wanderTarget.Y);
g.DrawEllipse(Pens.LightGreen, new RectangleF(new PointF(circleCenterG.X,
circleCenterG.Y), new SizeF(wanderRadius, wanderRadius)));
g.FillEllipse(Brushes.LightYellow, new RectangleF(new PointF(
pointOnCircle.X - 4, pointOnCircle.Y - 4), new SizeF(8, 8)));
return Vector2.Subtract(pointOnCircle, currentPosition);
}
这是它的样子

并开启“留下轨迹”

留下轨迹的作用是让你看到行进的路径确实是随机且平滑的。任务完成!别忘了玩转左上角的变量。
5. PathFollowing (路径跟随)
接下来是 PathFollowing
。PathFollowing
创建一个引导力,使车辆沿着一系列航点移动,形成一条路径。有时,路径有起点和终点,有时,它们会自我循环形成一条永无止境的闭合路径。
您会发现使用路径在您的游戏中无尽的用途。您可以使用它们来创建在地图重要区域巡逻的代理,使部队能够穿越困难的地形,或帮助赛车在赛道上导航。它们在代理必须访问一系列检查点的几乎所有情况下都很有用。
跟随路径的最简单方法是将当前航点设置为列表中的第一个,使用 Seek
朝该航点转向,直到车辆到达目标距离,然后获取下一个航点并 Seek
到该航点,依此类推,直到当前航点是列表中的最后一个航点。当这种情况发生时,车辆应该到达当前航点,或者,如果路径是闭环,则当前航点应再次设置为列表中的第一个,并且车辆会继续 Seek
。
这是 PathFollowing
的代码
public static Vector2 PathFollowing(ref Vector2 targetPosition,
ref Vector2 currentPosition, ref Vector2 Velocity,
ref Point[] pathPoints, ref int currentPathPoint,
int maxPathPoints, int max_speed)
{
if (currentPathPoint > maxPathPoints - 1)
{
currentPathPoint = 0;
}
int nextPathPoint = currentPathPoint + 1;
if ((nextPathPoint > (maxPathPoints - 1)))
{
nextPathPoint = 0;
}
Vector2 currentPath = new Vector2(pathPoints[currentPathPoint].X,
pathPoints[currentPathPoint].Y);
Vector2 differenceV = Vector2.Subtract(currentPosition, currentPath);
if (Math.Abs(differenceV.Length()) < 5)
{
targetPosition = new Vector2(pathPoints[nextPathPoint].X,
pathPoints[nextPathPoint].Y);
++currentPathPoint;
return Seek(ref targetPosition, ref currentPosition, ref Velocity,max_speed);
}
else
{
targetPosition = new Vector2(pathPoints[currentPathPoint].X,
pathPoints[currentPathPoint].Y);
//currentPathPoint++;
return Seek(ref targetPosition, ref currentPosition, ref Velocity,max_speed);
}
}
提示:减小 max_speed
以观察士兵巡逻,并增大它以观察赛车穿过检查点!
这是一个代理跟随路径

6. Separation (分离)
接下来是 Separation
。Separation
引导行为使角色能够与附近的其他人保持一定的分离距离。这可用于防止角色挤在一起。为了计算 Separation
的转向,首先进行搜索以查找指定邻域内的其他角色。这可能是对模拟世界中所有角色的详尽搜索,或者可能使用某种空间分区或缓存方案来将搜索限制在本地角色。对于每个附近的角色,通过减去我们角色和附近角色的位置来计算排斥力,进行归一化,然后应用 1/r 加权。(也就是说,位置偏移向量按 1/r² 缩放。)请注意,1/r 只是一个效果好的设置,而不是一个基本值。这些对于每个附近角色的排斥力被加在一起以产生整体转向力。
这是代码
public static Vector2 Separation(ref Vehicle[] allCars, Vehicle me,
ref Vector2 currentPosition, ref Vector2 velocity, int max_speed)
{
int j = 0;
Vector2 separationForce = new Vector2(0);
Vector2 averageDirection = new Vector2(0);
Vector2 distance = new Vector2(0);
for (int i = 0; i < allCars.Length; i++)
{
distance = Vector2.Subtract(currentPosition, allCars[i].CurrentPosition);
if (Vector2.Length(distance) < 100 && allCars[i] != me)
{
j++;
separationForce += Vector2.Subtract(currentPosition,
allCars[i].CurrentPosition);
separationForce = Vector2.Normalize(separationForce);
separationForce = Vector2.Multiply(separationForce, 1 / .7f);
averageDirection = Vector2.Add(averageDirection, separationForce);
}
}
if (j == 0)
{
return None();
}
else
{
//averageDirection = averageDirection / j;
return averageDirection;
}
}
这是它的样子(这就是当每个人都试图在如此拥挤的世界中找到自己安静的地方时会发生什么!)

7. Cohesion (聚集)
接下来是 Cohesion
。Cohesion
引导行为使角色能够与附近的其他人聚集(靠近并形成一个群体)。可以通过查找本地邻域中的所有角色(如上面 Separation
所述)并计算附近角色的平均位置(或质心)来计算 Cohesion
的转向。转向力可以施加在该平均位置的方向上,或者可以作为 Seek
引导行为的目标。
这是代码
public static Vector2 Cohesion(ref Vehicle[] allCars,Vehicle me,
Vector2 currentPosition, Vector2 velocity,
int max_speed, int cohesionRadius)
{
int j = 0;
Vector2 averagePosition = new Vector2(0);
Vector2 distance = new Vector2(0);
for (int i = 0; i < allCars.Length; i++)
{
distance = Vector2.Subtract(currentPosition, allCars[i].CurrentPosition);
if (Vector2.Length(distance) < cohesionRadius && allCars[i] != me)
{
j++;
averagePosition = Vector2.Add(averagePosition, allCars[i].CurrentPosition);
//averagePosition = Vector2.Multiply(averagePosition, 10);
//averagePosition = Vector2.Add(averagePosition,
Vector2.Normalize(distance) / Vector2.Length(distance));
}
}
if (j == 0)
{
return None();
}
else
{
averagePosition = averagePosition / j;
return Seek(ref averagePosition, ref currentPosition,
ref velocity, max_speed);
}
}
提示:尝试调整聚集半径,看看它对群体数量的影响。它看起来像这样(注意群体是如何形成的,它们在形成帮派吗?哦,我的天哪!)

也尝试思考这种行为的用途(谁会想形成一个群体?)。
8. Alignment (对齐)
接下来是 Alignment
。Alignment
引导行为使角色能够与附近的其他人对齐(即,以相同的方向和/或速度行驶)。可以通过查找本地邻域中的所有角色(如上面 Separation
所述)并对附近角色的速度(或单位前向向量)进行平均来计算对齐的转向。该平均值是期望速度,因此转向向量是平均值与我们角色当前速度(或其单位前向向量)之间的差值。这种转向将倾向于使我们的角色与邻居对齐。
这是代码
public static Vector2 Alignment(ref Vehicle[] allCars, Vehicle me,
ref Vector2 currentPosition, ref Vector2 velocity, int max_speed)
{
int j = 0;
Vector2 averageDirection = new Vector2(0);
Vector2 distance = new Vector2(0);
for (int i = 0; i < allCars.Length; i++)
{
distance = Vector2.Subtract(currentPosition, allCars[i].CurrentPosition);
if (Vector2.Length(distance) < 100 && allCars[i] != me)
{
j++;
averageDirection = Vector2.Add(averageDirection, allCars[i].Velocity);
}
}
if (j == 0)
{
return None();
}
else
{
averageDirection = averageDirection / j;
return Vector2.Subtract(averageDirection, velocity);
}
}
这是它的样子(您是否见过鸟在飞?当您想将这种行为融入您自己的游戏中时,您会怎么做?)

游戏中许多有趣的现象并非由单一行为产生,而是由多种行为组合而成。力的组合方式取决于问题。在这里,我演示了一种这种**组合:逃离 (Flee)、分离 (Separation)、聚集 (Cohesion) 和对齐 (Alignment) (FCAS)**。
当所有 FCAS 力同时作用时,代理会发生什么?好吧,每个代理都会试图逃离目标 (F),试图形成一个群体 (C),朝其他方向移动 (A),同时还要争取自己的私人空间 (S)。观察这种组合确实很有趣。
下图有一个设置为漫游行为的车辆,所有其他车辆都将该车辆视为威胁并实现 FCAS 行为。它看起来像一头狮子在追赶一群牛,或者一条鲨鱼在追逐小鱼!这完全取决于一个人的想象力。

至此,引导行为部分结束。 还有许多其他可能的引导行为,但它们涉及矩阵计算且更复杂,但观看起来也更有趣;您也可以尝试它们!
EStrings
EStrings 将帮助刚接触 GA(遗传算法)的人更好地理解 GA。GA 是一种搜索解决方案的技术,用于查找我们知之甚少的问题的解决方案,或者在传统微积分技术失败或效率低下时使用。该 GA 实现试图从一些随机生成的字符串收敛到一个字符串。GA 基于达尔文的进化论,因此使用突变、交叉、适应度、亲本等概念,这些概念在典型应用中找不到。
在游戏中,GA 用于训练神经网络,神经网络通常是代理的大脑。因此,我们使用 GA 来找到代理的“最佳大脑状态”,以便它能更好地工作,或适应环境等。尽管此实现可能没有直接用途,但对于初学者来说,它是一个不错的起点。有些人可能会惊讶,GA 可以用于几乎任何问题,任何可以用计算机表示并且我们知道如何评估其解决方案的问题。
第一步是表示问题。由于我们在这里要收敛到一个 string
,所以 string
是最佳表示。接下来,我们生成随机种群(这里是 string
s)。每个 string
被称为染色体,每个字符代表生物的一个特征。
static void Initialize(ref List<OneString> ones, ref List<OneString> two,
ref List<OneString> temp)
{
string rString = "";
for (int i = 0; i < popSize; i++)
{
rString = "";
for (int j = 0; j < sLen; j++)
{
rString += (char)rand.Next(32, 142);
}
ones.Add(new OneString(rString, 0));
two.Add(new OneString(rString, 0));
temp.Add(new OneString(rString, 0));
}
看看我们是如何生成 ASCII 码 32 到 142 之间的随机字符的。这个的排列构成了我们的搜索空间。由于我们的目标字符串必须从当前一代进化而来,因此拥有一个大的种群会更好,这样我们找到特定特征(这里是字符)的机会就很大。在生成足够好的种群后,我们对其进行评估并尝试找到最佳种群;这是通过适应度函数完成的
private static void CalculateFitness(ref List<OneString> ones)
{
int fitness;
for (int i = 0; i < popSize; i++)
{
fitness = 0;
for (int j = 0; j < sLen; j++)
{
fitness += Math.Abs(ones[i].name[j] - target[j]);
}
ones[i].fitness = fitness;
}
}
这里,目标和种群中的字符串之间的 ASCII 差的绝对值就是误差。如果这个误差为零,这意味着我们找到了一个解决方案,搜索就会停止。否则,我们使用突变、精英主义、交叉等方法生成新的一代,并用新一代重复这个过程。
这是它的样子

蚂蚁
这是本项目最后一部分,它将结合 GA 和 NN(神经网络)。在这里,我们创建能够学会寻找食物的蚂蚁!我们首先创建很多蚂蚁(使用典型的蚂蚁模型),给它们大脑(NN,每只蚂蚁都有自己的),将它当前的运动数据输入网络,获取输出,并再次使用它们来移动它们(希望更接近食物)。我们这样做一段时间,然后看看哪只蚂蚁通过它的大脑(NN)获得了最多的食物。GA 适应度函数用于评估。评估后,我们按一定顺序对它们进行排序,选择最好的,让它们成为亲本并繁殖后代。我们循环进行,直到蚂蚁真正学会为止。
NN 由数字列表表示(**请注意**,在 EStrings 中,它是一个字符列表,而在这里它是一个数字列表),这些数字构成了 NN 的权重。解决方案空间又是它们的排列,问题是找到能够引导蚂蚁走向最近食物的组合。
下图显示了网络的基本结构

private void Update()
{
int totalFit = CalculateFitness();
PrintBest(totalFit);
lastTotalFit = totalFit;
NewGeneration();
inCG = 0;
generationCount++;
}
上述方法显示了所使用的步进方法,下面是适应度方法。
private int CalculateFitness()
{
int totalFit = 0;
for (int i = 0; i < numAnts; i++)
{
totalFit += ants[i].GetFoodCollected;
}
return totalFit;
}
输出:

在这里,绿色圆点代表食物,每只蚂蚁旁边的绿色数字是收集到的食物数量,黄色数字是蚂蚁编号,右侧的列表显示了上一代的信息。
请尝试开启“地雷”进行实验。
参考文献
历史
- 2008/08/26:首次发布
- 2008/10/09:小改动与代码改进