介绍
另一个进化实验。我正在使用来自nuget 的并行神经网络库来BackPropagationNetwork
进行强化学习。遗传突变,强化学习和适当的选择使得鱼学习如何找到食物。在这里,a Fish
是具有神经网络,颜色,尺寸,空气动力学效率和传感器的实体,其将食物的位置与其相应的神经输入的角度和距离进行馈送。鱼的性质包含在一个FishChromosomes
,可以与另一个染色体交配,以产生突变的后代。
计算神经网络的输出,并用于计算鱼类的航向角度和速度,从而在我们的环境中完成鱼的循环和鱼的生命。输入神经元的数量对应于鱼上传感器的分辨率。所有这些都发生在Fish
课堂内。
每个FishChromzomes
都有物理基因和心理基因,物理基因是大小,空气动力学效率(控制速度)和颜色。心理基因只是NetworkData
运行鱼的神经网络。高空气动力效率的鱼快速移动,较大的鱼需要更多的食物来生存,颜色不影响鱼的表现。
加强学习
鱼类有一个短暂的记忆,以前的10个步骤,直到那个例子,这是用来训练鱼成功饲养后。当鱼成功到达食物时,先前10个队列中存储的步骤用于训练鱼类上的神经网络,使鱼类以更好的方式寻找食物。因此,一条鱼学会了。所有这些都在FishLearn
类中实现。鱼的每个步骤都被添加到队列中,以便在鱼饲料时用于事件。
![](https://www.codeproject.com/images/arrow-up-16.png)
- 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));
- }
- }
结果
最初,鱼几乎不知道如何到达食物,有些人继续围绕着像斑马鱼一样在陪替氏盘中盘旋。一代又一代,鱼获得了能够消耗更多食物的特质。结果是可见的,就像几代人一样,鱼直接去吃食物,获得群众行为。人口的总体适应度只不过是在图形窗口中绘制的鱼的总食物,而且一代又一代就可以看出一致性的增加。这证明模拟是成功的,遗传突变正在成功地生产适应者。出现的另一个特点是拍摄两颗食物颗粒的中点。实验表明,当一只青蛙在两距离远的地方显示两只昆虫时,青蛙最初朝着食物来源的中间方向移动,然后在一个阈值距离之后,它选择一边。几代之后,我们的鱼也有相似的行为。