Artificial Neural Network "robot"

Hello All,

First I want to start out by stating that I have no clue what I'm doing. That being said I'm going to build a "robot" (I'll be using that term from here on out loosely) that teaches itself to face the brightest light in the room by using an Artificial Neural Network (ANN). Of course this is not the most practical way of making a robot do this, it would be more easily accomplished by simply using two Light Dependent Resistors (LDR)s facing in the forward direction and one slightly turned to the left and the other to the right. Then telling it to turn towards the brighter side until the light hitting the both LDRs was equal. BUT that's not what we are going to be doing!

Once again, I am learning this as I move through the project. I have a loose idea and a little research under my belt about ANNs. Here is my crude explanation of what an ANN is and how it works. Basically, you have a number of inputs connected to a number of "Hidden Layers" then connected to a(or multiple) output. Each layer to include the input and output layers have what is called neurons. This is just a spot to hold a value. Each neuron is connected to another neuron, in the ANN, and is assigned a strength (or what the big brains call, "Weight"). That means when a value is passed from one neuron to the next, it is affected by the strength. The value can either be increased or decreased depending on the strength. This keeps happening all the way through the network until it reaches the output layer. Then the ANN take the output with the highest value as the correct answer. This is a really simplified explanation of ANNs and if you want to learn more about it then GTS.

So back to the robot stuff. First I needed to decide how big and complex of a network I wanted to build. I picked a smaller network for my learning experience. I will be using a network with 10 neurons, in a arrangement of 4 neuron on the input layer, 4 on a single hidden layer and then 2 on the output layer(4-4-2?). The 4 inputs will come from 4 LDRs on the corners of the robot facing outward at 45 degree increments. these inputs will pass to the hidden layer and then to the outputs. The output will correspond to a motor turning the robot one way or the other.

So far I have coded the network to accept some stand-in input values, until I build the LDR circuit, and pass those the network and give me an output. I have no idea now what to do with that data now. Obviously activate the motor and turn the robot. Also i need to figure out how to train the network.

This is the layout of the inputs

my code is in the text doc, it was to large to attach. IT'S OVER 9000!......characters

ANN.txt (9.86 KB)

So you want 10 neurons to do the job of one? That seems feasible.

Your ANN references don't say how to train one? That is pretty common because the training part is the "handwaving" part. Training requires you to be able to evaluate the result and tell the NN if it is right or wrong. If you can train a NN to do task X then you already know how to do task X so why did you spend all that time training?

I love the one where the US Army spent millions on a neural network to find tanks hidden in aerial surveillance photos. It turned out that the training photos of tanks were all taken on a sunny day and their expensive computer was just recognising sunshine.

I don't know neural network maths but I notice that you have two functions generateLayer1Weights() and generateInputWeights() which are used as part of the setup() process - makes things nice and neat.

But then you have all the rest of the code in loop() so it is impossible to see the wood for the trees. Break the code in loop() into a number of short single purpose functions that can be called from loop().

That way you can test each function separately and also see an overview of the process you are trying to implement. Have a look at Planning and Implementing a Program

...R

Sounds like you are making a Braitenburg vehicle.

For training neural networks of just about any size and depth, you can use Google's free TensorFlow package. It has become quite popular and useful for recognition of shapes in images, and works very well if you have the patience to go through lots of input examples.

As pointed out above, you need to know how to recognize the output you want, for feedback to the network. Then you get a set of weights.

Naturally, all the training happens on a personal computer, not the Arduino.

jremington:
Naturally, all the training happens on a personal computer, not the Arduino.

Ohh. :frowning:

I thought you just gave the robot a treat when it reaches the end of the course. It works with dogs. :slight_smile:

...R

Robin2:
Ohh. :frowning:

I thought you just gave the robot a treat when it reaches the end of the course. It works with dogs. :slight_smile:

...R

The treat is you don't smash it with a large hammer.

I would like to have all the training done on the Arduino, yes that's a dumb idea. But i basically want it to power on and start training itself without input from the user. once it finishes training it will alert the user that the training is complete by flashing a light or something. the user then presses a button and the robot does what it was trained to do. KILL HUMA cough face towards the brightest light.

this is not a practical application of an ANN but more or less a teaching project where I can expand upon what I learned.

But i basically want it to power on and start training itself without input from the user.

You have a lot of reading to do.

The "treat" is not a completely irrelevant idea. You need something in the program that "tells" the network that it is getting better.

That's part of the reason why I suggested breaking the code into functions. You can have a function to collect data. And then another function to figure out whether there has been an improvement. And then a function to decide what to do next. etc etc

...R

my code is in the text doc, it was to large to attach. IT'S OVER 9000!......characters

Your could have been posted the code if you'd done some simple code factoring - the initialisation functions could easily be reduced to a single loop each.
Also, you'd save a ton(ne) of RAM by using the F() macro for the serial print string literals.

The massive, space-wasting comments could go too.

TolpuddleSartre:
Your could have been posted the code if you'd done some simple code factoring

Something tells me that this is one of those projects where the code is only going to get longer and longer :slight_smile:

...R

Moist_Plasma:
it would be more easily accomplished by simply using two Light Dependent Resistors (LDR)s facing in the forward direction and one slightly turned to the left and the other to the right. Then telling it to turn towards the brighter side until the light hitting the both LDRs was equal. BUT that's not what we are going to be doing!

Thinking some more about this ...

My feeling is that the task of facing towards the brightest light in the room is too simple to provide a basis for developing a neural network example program.

I think you need to conceive of a problem that cannot be solved with a pair of LDRs and simple "point-me-at-the-light" code because then you will be faced with a real learning challenge (both you and the robot).

If you think about it, if you already had a complex functioning neural network and presented it with the "find a light" problem it would very quickly reduce itself to the two-LDR solution. Otherwise it would be a pretty crap neural network.

...R

Edited to add the words in green - see Reply #12 below

My feeling is that the task of facing towards the brightest light in the room is too simple to provide a basis for developing a neural network example program.

Braitenberg got a lot of great mileage in thinking about his simple vehicles (the basis for the OP's post) as models for simple sensor/response behavioral systems.

For example, simply crossing the wires leading from the sensors to the motors leads to astonishingly different behavior compared to uncrossed.

These thought experiments become very popular and A. K. Dewdney published a good summary in Scientific American some years ago (attached PDF). It is really fun to read and highly recommended!

Braitenberg_vehicles.pdf (1.01 MB)

jremington:
It is really fun to read and highly recommended!

Thanks. I have taken a copy and I have had a quick browse.

Maybe I did not express myself clearly earlier because that article (IMHO) supports what I was trying to say.

In the article the interesting vehicles are those with the added complexity of the "neurodes" because they do things that could not be achieved with a pair of LDRs and simple "point-me-at-the-light" code - the sort of code you would use to make solar panels face the sun as it moves across the sky during the day.

Now that I have written the previous paragraph I see that I should have included and simple "point-me-at-the-light" code in my earlier Reply. I will update it.

...R

Thank everyone of the input. some of it made me look at this from a different angle. I know that this is not a great use for a NN but now I'm just curious if I can do it or not :slight_smile: I revamped the code and I think I'm on to something. The next step is to build a quick prototype robot that can actually rotate.(unless someone informs me of some critical mistake.)

The code below takes the four "stand-in" input values from the LDR and converts them to decimals. Then it passes those to the hidden layer which have a "pseudo-randomly" generated weights. The values at each neuron in the hidden layer has a Sigmoid function applies to it so it can be a number between 0 and 1. After that the hidden layer is passed to 2 output neurons and another sigmoid is applied. The MC then decides which output is bigger and checks the NN's answer by adding inputs 1 & 3(left side) and inputs 2 & 4 together.

If output 1 is larger then inputs 2 & 4(summed) should also be larger. if this is correct then the MC activates the motor associated with output 1. it delays the difference between output 1 and output 2. Then deactivates the motor. (this hopefully gets smaller as the robot get closer to facing the light source.)

if output 1 is larger and inputs 2 & 4(summed) are smaller then the NN goes to a training cycle. in the training cycle, the difference between the outputs is set as the Error. Once that is done the MC checks if what if did on the previous training cycle reduced the error or not. If the Error was reduced it picks a random weight from the network and adds 0.1. If the error was larger, then it subtracts 0.2 from the same weight it changed on the previous cycle.

the output check also works the other way( if output 2 is larger then inputs 1 & 3 should be larger....)

int input1 = A0;
int input2 = A1;
int input3 = A2;
int input4 = A3;

float input1Val;
float input2Val;
float input3Val;
float input4Val;

float input1Weight[4];
float input2Weight[4];
float input3Weight[4];
float input4Weight[4];

float layer1_1Val;
float layer1_2Val;
float layer1_3Val;
float layer1_4Val;

float layer1_1Weight[2];
float layer1_2Weight[2];
float layer1_3Weight[2];
float layer1_4Weight[2];

float output1;
float output2;

int leftMotor = 12;
int rightMotor = 11;

float learningRate = 0.1;
int randomWeight;
int randomWeightVal;
int lastChange;
float error;
float lastError;
int TREAT;
int  trainCycle;

void setup()
{
  Serial.begin(9600);
  pinMode(13, OUTPUT);
  generateInputWeights();
  generateLayer1Weights();
}

void loop()
{
  /* 
     input1Val = anaglogRead(input1);
     input2Val = anaglogRead(input2);
     input3Val = anaglogRead(input3);
     input4Val = anaglogRead(input4);
  */

  //temp random values before LDR circuit is built.
  input1Val = 276;
  input2Val = 754;
  input3Val = 335;
  input4Val = 656;

  inputMapAndConvert();
  layer1Val();

  output1 = layer1_1Val * layer1_1Weight[0] + layer1_2Val * layer1_2Weight[0] + layer1_3Val * layer1_3Weight[0] + layer1_4Val * layer1_4Weight[0];
  output2 = layer1_1Val * layer1_1Weight[1] + layer1_2Val * layer1_2Weight[1] + layer1_3Val * layer1_3Weight[1] + layer1_4Val * layer1_4Weight[1];
  output1 = 1 / (1 + exp(-1 * output1));
  output2 = 1 / (1 + exp(-1 * output2));

  if (input1Val + input3Val > input2Val + input4Val)
  {
    if (output1 < output2)
    {
      digitalWrite(rightMotor, HIGH);
      delay(output2 - output1);
      digitalWrite(rightMotor, LOW);
      TREAT++;
    }

    else
    {
      training();
    }
  }

  if (input1Val + input3Val < input2Val + input4Val)
  {
    if (output1 > output2)
    {
      digitalWrite(leftMotor, HIGH);
      delay(output1 - output2);
      digitalWrite(leftMotor, LOW);
      TREAT++;
    }

    else
    {
      training();
    }
  }
  Serial.print("TREAT  ");
  Serial.print(TREAT);
  Serial.print("     ");
  Serial.print("Training Cycle  ");
  Serial.println(trainCycle);
}

void generateInputWeights()
{
  //generate input 1 weights
  for (int x = 0; x <= 3; x++)
  {
    input1Weight[x] = random(1, 101);  //101 because random number generator subtracts 1 from max.

  }

  //generate input 2 weights
  for (int x = 0; x <= 3; x++)
  {
    input2Weight[x] = random(1, 101);  //101 because random number generator subtracts 1 from max.
  }

  //generate input 3 weights
  for (int x = 0; x <= 3; x++)
  {
    input3Weight[x] = random(1, 101);  //101 because random number generator subtracts 1 from max.
  }

  //generate input 4 weights
  for (int x = 0; x <= 3; x++)
  {
    input4Weight[x] = random(1, 101);  //101 because random number generator subtracts 1 from max.
  }

  //-----convert input weights to float values---
  for (int x = 0; x <= 3; x++)
  {
    input1Weight[x] = input1Weight[x] / 100;
  }

  for (int x = 0; x <= 3; x++)
  {
    input2Weight[x] = input2Weight[x] / 100;
  }

  for (int x = 0; x <= 3; x++)
  {
    input3Weight[x] = input3Weight[x] / 100;
  }

  for (int x = 0; x <= 3; x++)
  {
    input4Weight[x] = input4Weight[x] / 100;
  }

}

void generateLayer1Weights()
{
  //generate layer 1-1 weights
  for (int x = 0; x <= 1; x++)
  {
    layer1_1Weight[x] = random(1, 101);  //101 because random number generator subtracts 1 from max.
  }

  //generate layer 1-2 weights
  for (int x = 0; x <= 1; x++)
  {
    layer1_2Weight[x] = random(1, 101);  //101 because random number generator subtracts 1 from max.
  }

  //generate layer 1-3 weights
  for (int x = 0; x <= 1; x++)
  {
    layer1_3Weight[x] = random(1, 101);  //101 because random number generator subtracts 1 from max.
  }

  //generate layer 1-4 weights
  for (int x = 0; x <= 1; x++)
  {
    layer1_4Weight[x] = random(1, 101);  //101 because random number generator subtracts 1 from max.
  }
  //convert layer values to decimal
  for (int x = 0; x <= 1; x++)
  {
    layer1_1Weight[x] =  layer1_1Weight[x] / 100;
  }

  for (int x = 0; x <= 1; x++)
  {
    layer1_2Weight[x] =  layer1_2Weight[x] / 100;
  }

  for (int x = 0; x <= 1; x++)
  {
    layer1_3Weight[x] =  layer1_3Weight[x] / 100;
  }

  for (int x = 0; x <= 1; x++)
  {
    layer1_4Weight[x] =  layer1_4Weight[x] / 100;
  }
}

void training()
{
  trainCycle++;
  error = output1 - output2;
  if (error < lastError)
  {

    lastError = error;
    randomWeight = random(1, 9);
    if (randomWeight == 1)
    {
      randomWeightVal = random(0, 3);
      input1Weight[randomWeightVal] = input1Weight[randomWeightVal] + learningRate;
    }

    if (randomWeight == 2)
    {
      randomWeightVal = random(0, 3);
      input2Weight[randomWeightVal] = input2Weight[randomWeightVal] + learningRate;
    }

    if (randomWeight == 3)
    {
      randomWeightVal = random(0, 3);
      input3Weight[randomWeightVal] = input3Weight[randomWeightVal] + learningRate;
    }

    if (randomWeight == 4)
    {
      randomWeightVal = random(0, 3);
      input4Weight[randomWeightVal] = input4Weight[randomWeightVal] + learningRate;
    }

    if (randomWeight == 5)
    {
      randomWeightVal = random(0, 2);
      layer1_4Weight[randomWeightVal] = layer1_4Weight[randomWeightVal] + learningRate;
    }

    if (randomWeight == 6)
    {
      randomWeightVal = random(0, 2);
      layer1_4Weight[randomWeightVal] = layer1_4Weight[randomWeightVal] + learningRate;
    }
    if (randomWeight == 7)
    {
      randomWeightVal = random(0, 2);
      layer1_4Weight[randomWeightVal] = layer1_4Weight[randomWeightVal] + learningRate;
    }
    if (randomWeight == 8)
    {
      randomWeightVal = random(0, 2);
      layer1_4Weight[randomWeightVal] = layer1_4Weight[randomWeightVal] + learningRate;
    }

  }

  if (error > lastError)
  {

    if (randomWeight == 1)
    {
      randomWeightVal = random(0, 3);
      input1Weight[randomWeightVal] = input1Weight[randomWeightVal] - (learningRate * 2);
    }

    if (randomWeight == 2)
    {
      randomWeightVal = random(0, 3);
      input2Weight[randomWeightVal] = input2Weight[randomWeightVal] - (learningRate * 2);
    }

    if (randomWeight == 3)
    {
      randomWeightVal = random(0, 3);
      input3Weight[randomWeightVal] = input3Weight[randomWeightVal] - (learningRate * 2);
    }

    if (randomWeight == 4)
    {
      randomWeightVal = random(0, 3);
      input4Weight[randomWeightVal] = input4Weight[randomWeightVal] - (learningRate * 2);
    }

    if (randomWeight == 5)
    {
      randomWeightVal = random(0, 2);
      layer1_4Weight[randomWeightVal] = layer1_4Weight[randomWeightVal] - (learningRate * 2);
    }

    if (randomWeight == 6)
    {
      randomWeightVal = random(0, 2);
      layer1_4Weight[randomWeightVal] = layer1_4Weight[randomWeightVal] - (learningRate * 2);
    }
    if (randomWeight == 7)
    {
      randomWeightVal = random(0, 2);
      layer1_4Weight[randomWeightVal] = layer1_4Weight[randomWeightVal] - (learningRate * 2);
    }
    if (randomWeight == 8)
    {
      randomWeightVal = random(0, 2);
      layer1_4Weight[randomWeightVal] = layer1_4Weight[randomWeightVal] - (learningRate * 2);
    }

  }

}

void inputMapAndConvert()
{
  //make LDR values between 0 and 1000 so thay can be can be converted to a value between 0 and 1.
  input1Val = map(input1Val, 0, 1023, 0, 1000);
  input2Val = map(input2Val, 0, 1023, 0, 1000);
  input3Val = map(input3Val, 0, 1023, 0, 1000);
  input4Val = map(input4Val, 0, 1023, 0, 1000);

  // convert input value to a decimal between 0 and 1.
  input1Val = input1Val / 1000;
  input2Val = input2Val / 1000;
  input3Val = input3Val / 1000;
  input4Val = input4Val / 1000;
}
void layer1Val()
{
  //determine layer 1 values based upon inputs and weights.
  layer1_1Val = input1Val * input1Weight[0] + input2Val * input2Weight[0] + input3Val * input3Weight[0] + input4Val * input4Weight[0];
  layer1_2Val = input1Val * input1Weight[1] + input2Val * input2Weight[1] + input3Val * input3Weight[1] + input4Val * input4Weight[1];
  layer1_3Val = input1Val * input1Weight[2] + input2Val * input2Weight[2] + input3Val * input3Weight[2] + input4Val * input4Weight[2];
  layer1_4Val = input1Val * input1Weight[3] + input2Val * input2Weight[3] + input3Val * input3Weight[3] + input4Val * input4Weight[3];

  layer1_1Val = 1 / (1 + exp(-1 * layer1_1Val));
  layer1_2Val = 1 / (1 + exp(-1 * layer1_2Val));
  layer1_3Val = 1 / (1 + exp(-1 * layer1_3Val));
  layer1_4Val = 1 / (1 + exp(-1 * layer1_4Val));
}

You have posted code in Reply #14 but you have not told us what it does or whether there is some part that you want help with.

...R

I updated post 13 with the code information.

Moist_Plasma:
I updated post 13 with the code information.

It is very hard to follow the conversation when you make substantial changes to earlier Replies. Correcting typos is fine. Otherwise at least make it very clear what you have changed.

Your description reads to me like a description that is written by someone who knows how to solve the problem. To my mind the whole purpose of neural networks is to let the system find solutions which are not obvious to the programmer. That's why I think your goal for this project is too simple - it is not forcing you to think sufficiently and consequently I think you will arrive at a narrow solution to the problem that really does not provide a platform from which to build more complex systems.

What about a system that turns towards the brightest light and, when the light goes off, seeks another light but if the first light comes on again it immediately returns to it and next time it goes off it stays in position and waits for it to come back on. But does not wait forever.

Just my 3 cents ...

...R

my apologies, rookie mistake. what was added to post 13.

Moist_Plasma:
The code below takes the four "stand-in" input values from the LDR and converts them to decimals. Then it passes those to the hidden layer which have a "pseudo-randomly" generated weights. The values at each neuron in the hidden layer has a Sigmoid function applies to it so it can be a number between 0 and 1. After that the hidden layer is passed to 2 output neurons and another sigmoid is applied. The MC then decides which output is bigger and checks the NN's answer by adding inputs 1 & 3(left side) and inputs 2 & 4 together.

If output 1 is larger then inputs 2 & 4(summed) should also be larger. if this is correct then the MC activates the motor associated with output 1. it delays the difference between output 1 and output 2. Then deactivates the motor. (this hopefully gets smaller as the robot get closer to facing the light source.)

if output 1 is larger and inputs 2 & 4(summed) are smaller then the NN goes to a training cycle. in the training cycle, the difference between the outputs is set as the Error. Once that is done the MC checks if what if did on the previous training cycle reduced the error or not. If the Error was reduced it picks a random weight from the network and adds 0.1. If the error was larger, then it subtracts 0.2 from the same weight it changed on the previous cycle.

the output check also works the other way( if output 2 is larger then inputs 1 & 3 should be larger....)

Robin2:
What about a system that turns towards the brightest light and, when the light goes off, seeks another light but if the first light comes on again it immediately returns to it and next time it goes off it stays in position and waits for it to come back on. But does not wait forever.

not a bad idea, let me ponder that. I'm guessing i would need a way to measure wheel rotations and so i can recall where the robot was facing when the light turn off.