人工智能: 虚拟鱼的遗传进化






4.93/5 (26投票s)
神经网络的基因突变,
引言
又一个进化实验。我使用了来自 nuget 的 Parallel Neural Networks 库,用于其用于强化学习的 BackPropagationNetwork
类。 基因突变、强化学习和适当的选择使鱼学会如何寻找食物。 在这里,Fish
是一个具有神经网络、颜色、大小、空气动力学效率和传感器的实体,传感器通过其角度和距离将食物的位置馈送到相应的神经输入。 鱼的属性包含在 FishChromosomes
中,该染色体可以与另一个染色体交配以产生突变的后代。
计算神经网络的输出,并将其用于计算鱼的航向角和速度,从而完成循环,并且鱼在我们的环境中“生活”。 输入神经元的数量对应于鱼传感器上的分辨率。 所有这些都发生在 Fish
类中。
每个 FishChromzomes
都有物理基因和精神基因,物理基因是大小、空气动力学效率(控制速度)和颜色。 精神基因只是运行鱼的神经网络的 NetworkData
。 空气动力学效率高的鱼移动速度快,而较大的鱼需要更多的食物才能生存,颜色不会影响鱼的性能。
强化学习
鱼有一个短期记忆,记录了到目前为止它所采取的先前 10 个步骤,该记忆用于在鱼成功喂食后训练鱼。 当鱼成功到达食物时,队列中存储的先前 10 个步骤用于训练鱼上的神经网络,以使鱼更擅长以这种方式寻找食物。 因此,鱼会边走边学。 所有这些都在 FishLearn
类中实现。 鱼采取的每个步骤都会添加到队列中,以便在鱼喂食时使用。
public class FishLearn
{
public Fish Fish;
public Queue<neuralnetworks.dataset> LearnQueue;
public BackgroundWorker worker;
public FishLearn(Fish fish)
{
Fish = fish;
LearnQueue = new Queue<neuralnetworks.dataset>(15);
worker = new BackgroundWorker();
worker.DoWork += ((object e, DoWorkEventArgs w) =>
{
Fish.FishNeural.BatchBackPropogate(LearnQueue.ToArray(),
(int)w.Argument, 0.1, 0.1, worker);
});
}
public void AddStep(IEnumerable<double> neuralInputs, IEnumerable<double> neuralOutputs)
{
NeuralNetworks.DataSet fishio = new NeuralNetworks.DataSet() {
Inputs = neuralInputs.ToArray(),
Outputs = neuralOutputs.ToArray()
};
try
{
LearnQueue.Enqueue(fishio);
}
catch
{
LearnQueue.Dequeue();
LearnQueue.Enqueue(fishio);
}
}
public void LearnPreviousSteps(int iterations)
{
worker.WorkerReportsProgress = true;
if(!worker.IsBusy)
worker.RunWorkerAsync(iterations);
}
}
强化学习导致模拟中每条鱼的神经网络不断变化,学习鱼喂食时的先前步骤。
遗传算法
当两个实体的属性发生基因突变时,它们的属性会混合在一起,以产生具有不同特征的新实体。 当我们模拟中的鱼正在喂食时,每个鱼都会维护喂食计数,并且当计时器重置时,会选择最好的鱼并进行杂交以产生新的个体。 由于每条鱼的神经网络都不同,因此后代将拥有自己独特的神经网络,因此具有不同的行为。 在我们的应用程序中,鱼的物理基因以以下方式混合。
精神基因
一个交叉点,用于选择以启用两个神经网络的合并。 NetworkData
对象用于此合并。
if (frontback)
{
foreach (ConnectionData c in guppiedata.Connections.Where
(r => r.From.Layer == guppiedata.InputLayerId))
c.Weight = one.Connections.Find(r => (r.From.Layer == c.From.Layer)
&& (r.From.Node == c.From.Node)
&& (r.To.Layer == c.To.Layer)
&& (r.To.Node == c.To.Node)).Weight;
foreach (ConnectionData c in guppiedata.Connections.Where
(r => r.To.Layer == guppiedata.OutputLayerId
&& r.To.Node < crossoverpoint1))
c.Weight = one.Connections.Find(r => (r.From.Layer == c.From.Layer)
&& (r.From.Node == c.From.Node)
&& (r.To.Layer == c.To.Layer)
&& (r.To.Node == c.To.Node)).Weight;
foreach (ConnectionData c in guppiedata.Connections.Where(r =>
r.To.Layer == guppiedata.OutputLayerId
&& r.To.Node >= crossoverpoint1))
c.Weight = two.Connections.Find(r => (r.From.Layer == c.From.Layer)
&& (r.From.Node == c.From.Node)
&& (r.To.Layer == c.To.Layer)
&& (r.To.Node == c.To.Node)).Weight;
}
物理基因
随机获取一个交叉点,列表的长度作为最大值。 两个基因列表被复制到交叉点的两侧。
public static List<gene> MixGenesUp(List<gene> one, List<gene> two)
{
int crossoverpoint = RandomProvider.Random.Next(one.Count);
List<gene> genes = new List<gene>();
for (int i = 0; i < one.Count; i++)
{
if (i <= crossoverpoint)
genes.Add(new Gene() { Name = one[i].Name, Value = one[i].Value });
else
genes.Add(new Gene() { Name = two[i].Name, Value = two[i].Value });
}
return genes;
}
模拟
模拟中的食物在每次消耗时都会自动生成。 鱼看不到彼此,但在任何给定时间都可以看到所有食物颗粒的位置。 计算传感器值并将其应用于神经网络,读取输出,并计算并应用鱼的航向角和前进速度。
public void Live(FoodGenerator foodGen, IEnumerable<fish> fishes)
{
Sensor.UpdateSensors(foodGen.FoodParticles);
FishNeural.ApplyInput(Sensor.FoodSensors);
FishNeural.CalculateOutput();
CheckFood(foodGen);
Age += 0.005;
if (Age > 10)
{
IsDead = true;
if (Dead != null)
Dead(this, new DeadEventArgs() { });
}
IEnumerable<double> Outputs = FishNeural.ReadOutput();
FishLearn.AddStep(Sensor.FoodSensors, Outputs);
HeadingAngle = (Math.Atan(Outputs.ElementAt(1)/Outputs.ElementAt(2))-(Math.PI/4));
MoveForward(Outputs.ElementAt(0));
}
神经网络库中的 Backpropagation
类直接用于此目的。
选择程序
仅选择一两条鱼来重新生成整个下一代可能会导致所需特征的丢失,因此从前 3 条鱼中选择两条鱼并进行交配以产生后代。
public void NextGeneration()
{
IEnumerable<fish> topFishes = fishes.OrderByDescending(r => r.NumFoodEaten).Take(5);
int totalfood = 0;
generation ++;
fishes.ForEach(r => totalfood += r.NumFoodEaten);
status_text_block.Text = "Generation: " +
generation.ToString() +
", Total Food: " +
totalfood.ToString();
graphWindow.AddDataPoint(generation, totalfood);
for (int i = 0; i < fishes.Count; i++)
{
if(i < fishes.Count/3)
fishes[i] = new Fish(FishChromozomes.Mate
(topFishes.ElementAt(0).Chromozomes, topFishes.ElementAt(1).Chromozomes));
else if(i < fishes.Count*2/3)
fishes[i] = new Fish(FishChromozomes.Mate
(topFishes.ElementAt(0).Chromozomes, topFishes.ElementAt(2).Chromozomes));
else
fishes[i] = new Fish(FishChromozomes.Mate
(topFishes.ElementAt(1).Chromozomes, topFishes.ElementAt(2).Chromozomes));
}
}
结果
最初,鱼几乎不知道如何到达食物,有些鱼像培养皿中的小斑马鱼一样不停地绕圈。 逐渐地,一代又一代,鱼获得了使它们能够消耗更多食物的特征。 结果是可见的,因为经过几代之后,鱼直接去寻找食物,获得了一种群集行为。 总体的种群适应度只不过是鱼消耗的总食物量绘制在图形窗口中,并且可以看到一致的增长,一代又一代。 这证明模拟是成功的,并且基因突变成功地产生了更健康的个体。 出现的另一个特征是射向两个食物颗粒的中点。 实验表明,当一只青蛙被展示两个相距一定距离的昆虫时,青蛙最初会朝着食物来源的中间方向移动,然后在达到阈值距离后,它会选择一侧。 在几代之后,我们的鱼也看到了相同的行为。