Click here to Skip to main content
15,878,852 members
Articles / Artificial Intelligence / Neural Networks

AI: Genetic Evolution of Virtual Fish

Rate me:
Please Sign up or sign in to vote.
4.93/5 (26 votes)
28 Jan 2016CPOL4 min read 25.2K   1.4K   37   3
Genetic Mutations of Neural Networks to produce better offspring in fish like virtual creatures

Introduction

Another one of evolution experiments. I am using the Parallel Neural Networks library from nuget for its BackPropagationNetwork class for reinforcement learning. Genetic mutations, reinforcement learning and proper selection makes the fish learn how to find food. Here, a Fish is an entity with a neural network, a color, a size, aerodynamic efficiency and a sensor, which feeds the location of food by its angle and distance to the corresponding neural input. The properties of fish are contained within a FishChromosomes, which can be mated with another chromosome to yield a mutated offspring.

Image 1

The output of the neural network is calculated and is used to compute the heading angle and the speed of the fish, which completes the loop and the fish ‘lives’ in our environment. Number of input neurons corresponds to the resolution of the sensor on the fish. All this happens inside the Fish class.

Every FishChromzomes has physical genes and mental genes, physical genes being the size, aerodynamic efficiency (which control the speed), and color. Mental genes is just the NetworkData of the neural network running the fish. A fish of high aerodynamic efficiency moves fast and a bigger fish requires more food to survive, Color does not affect the performance of the fish.

Reinforcement Learning

The fish has a short term memory of previous 10 steps it has taken till that instance, which is used to train the fish after it successfully feeds itself. When the fish successfully reaches the food, previous 10 steps stored in queue are used to train the neural network on the fish, to make the fish better at finding food in such manner. Hence, a fish learns as it goes. All this is implemented in the FishLearn Class. Each step taken by the fish is added to a queue to be used on the event when the fish feeds.

C#
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);
    }
}

Reinforcement learning results in a constantly changing neural network of each fish in the simulation, learning the previous steps when the fish feeds.

Genetic Algorithm

When properties of two entities are genetically mutated, both of their properties are mashed up to produce a new entity with different features. As our fish in the simulation are feeding, the feed count is maintained by each fish and when the timer is reset, the best fish are chosen and interbred to produce new individuals. As neural networks are all different for each fish, the offspring will have a unique neural network of their own, hence, different behavior. In our application, the physical genes of the fish are mixed in the following manner.

Mental Genes

A crossover point to selection to enable merging of the two neural networks. NetworkData objects are used for this merging.

Image 2

C#
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;
}

Physical Genes

A crossover point is randomly taken with the length of the list as the maximum value. Two lists of genes are copied on both sides of the crossover points.

C#
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;
}

The Simulation

The food in the simulation is autogenerated each time it is consumed. The fish cannot see each other but can see all food particle locations at any given time. The sensor values are calculated and applied to the neural networks, the output is read, and the fish’s heading angle and forward speed are calculated and applied.

C#
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));
}

Image 3

Backpropagation class from the neural network library is directly used for this purpose.

Selection Procedure

Selecting only one or two fish to re-spawn the entire next generation may result in loss of desired features, hence the two fishes from top 3 fishes are selected and mated to produce the offspring.

C#
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));
    }
}

Result

Initially the fish barely know how to reach the food, some keep circling around like baby zebra fish in petri dish. Gradually, generation after generation, the fish acquire traits which enable them to consume more food. The result is visible, as after a few generations, the fish directly go for the food, gaining a flock behavior. Overall fitness of the population is nothing but the total food consumed by the fish plotted in the graph window and a consistence increase is seen, generation after generation. This proves that the simulation is a success and genetic mutation is successfully producing fitter individuals. Another trait that emerged is shooting for the mid point of two food particles. Experiments have shown that when a frog is shown two insects some distance away, the frog initially moves towards the mid-direction of the food sources, then after a threshold distance, it chooses one side. Same behavior is seen in our fish after a few generations.

Image 4

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
csharpbd7-Feb-16 3:56
professionalcsharpbd7-Feb-16 3:56 
Questionfunny behaviour Pin
PGT28-Jan-16 23:40
PGT28-Jan-16 23:40 
GeneralMy vote of 5 Pin
Cryptonite28-Jan-16 11:12
Cryptonite28-Jan-16 11:12 
This is awesomesauce. Thank you for sharing. I would love to know: could a similar process be devised in order to solve the discrete log problem?

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.