Arduino | Aprende Machine Learning https://www.aprendemachinelearning.com en Español Sun, 01 Mar 2020 16:36:43 +0000 en-US hourly 1 https://wordpress.org/?v=6.4.3 https://www.aprendemachinelearning.com/wp-content/uploads/2017/11/cropped-icon_red_neuronal-1-32x32.png Arduino | Aprende Machine Learning https://www.aprendemachinelearning.com 32 32 134671790 Programa un coche Arduino con Inteligencia Artificial https://www.aprendemachinelearning.com/programa-un-coche-arduino-con-inteligencia-artificial/ https://www.aprendemachinelearning.com/programa-un-coche-arduino-con-inteligencia-artificial/#comments Wed, 01 Aug 2018 08:00:00 +0000 https://www.aprendemachinelearning.com/?p=5906 El Machine Learning nos permitirá utilizar Redes Neuronales para que un coche Arduino conduzca sólo evitando obstáculos. En el artículo anterior, creamos una red neuronal desde cero en Python. En este artículo mejoraremos esa red y copiaremos sus pesos a una red con propagación hacia...

The post Programa un coche Arduino con Inteligencia Artificial first appeared on Aprende Machine Learning.

]]>
El Machine Learning nos permitirá utilizar Redes Neuronales para que un coche Arduino conduzca sólo evitando obstáculos.

En el artículo anterior, creamos una red neuronal desde cero en Python. En este artículo mejoraremos esa red y copiaremos sus pesos a una red con propagación hacia adelante en Arduino que permitirá que el coche robot conduzca sólo sin chocar.

La Nueva Red Neuronal

Por simplificar el modelo de aprendizaje, en el post anterior teníamos una red de tres capas con 2 neuronas de entrada 3 ocultas y 2 de salida: giro y dirección. Para este ejercicio haremos que la red neuronal tenga 4 salidas: una para cada motor. Además las salidas serán entre 0 y 1 (apagar o encender motor). También cambiaremos las entradas para que todas comprendan valores entre -1 y 1 y sean acordes a nuestra función tangente hiperbólica. Aquí vemos los cambios en esta tabla:

Entrada:
Sensor Distancia
Entrada:
Posición Obstáculo
Salida:
Motor 1
Salida:
Motor 2
Salida:
Motor 3
Salida:
Motor 4
-101001
-111001
-1-11001
0-11010
010101
001001
110110
1-10110
100110

Siendo el valor de los motores 1 y 0:

AcciónMotor 1Motor 2Motor 3Motor 4
Avanzar1001
Retroceder0110
Giro Derecha0101
Giro Izquierda1010

Para instanciar nuestra red ahora usaremos este código:

# Red Coche para Evitar obstáculos
nn = NeuralNetwork([2,3,4],activation ='tanh')
X = np.array([[-1, 0],   # sin obstaculos
              [-1, 1],   # sin obstaculos
              [-1, -1],  # sin obstaculos
              [0, -1],   # obstaculo detectado a derecha
              [0,1],     # obstaculo a izq
              [0,0],     # obstaculo centro
              [1,1],     # demasiado cerca a derecha
              [1,-1],    # demasiado cerca a izq
              [1,0]      # demasiado cerca centro
             ])
# las salidas 'y' se corresponden con encender (o no) los motores
y = np.array([[1,0,0,1], # avanzar
              [1,0,0,1], # avanzar
              [1,0,0,1], # avanzar
              [0,1,0,1], # giro derecha
              [1,0,1,0], # giro izquierda (cambie izq y derecha)
              [1,0,0,1], # avanzar
              [0,1,1,0], # retroceder
              [0,1,1,0], # retroceder
              [0,1,1,0]  # retroceder
             ])
nn.fit(X, y, learning_rate=0.03,epochs=40001)

def valNN(x):
    return (int)(abs(round(x)))

index=0
for e in X:
    prediccion = nn.predict(e)
    print("X:",e,"esperado:",y[index],"obtenido:", valNN(prediccion[0]),valNN(prediccion[1]),valNN(prediccion[2]),valNN(prediccion[3]))
    index=index+1

Aquí podemos ver el código Python Completo modificado de la Jupyter Notebook. Y también vemos la gráfica del coste, que disminuye a medida que se entrena tras 40.000 iteraciones.

¿¿No es impresionante cómo con apenas 9 datos de entrada podemos enseñar a un robot a conducir??

El coche Arduino

En mi caso es un coche Arduino Elegoo Uno V3 de 4 motores. Si eres Maker, te resultará fácil construir el tuyo o puede que ya tengas uno en casa para programarlo. El coche puede ser cualquier otro, de hecho podría ser de 2 motores y modificando apenas la red funcionaría. En caso de querer construirlo tu mismo explicaré brevemente los requerimientos:

Necesitaremos:

  • Una placa Arduino Uno y una placa de expansión de IO
    • o puede ser una placa Arduino Mega
  • El controlador de motor L298N
  • 4 motores DC (o podrían ser 2) y sus ruedas
  • Servo Motor SG90
  • Sensor Ultrasónico
  • Baterias para alimentar los motores (y la placa obviamente!)
  • Chasis para el coche
  • Cables!

Circuito del coche

No entraré en detalle, ya que va más allá de este tutorial (si a muchos lectores les interesa, podría ampliar el artículo en el futuro) pero básicamente tenemos el siguiente circuito (ignorar el bluetooth y los sensores infrarrojos):

Montar el coche

Utilizaremos un Servo en la parte delantera del coche que moverá al sensor de distancia de izquierda a derecha, a modo de radar, para detectar obstáculos.

Más allá de eso… es un coche! pondremos las 4 ruedas y las placas Arduino encima del chasis. (El objetivo de este artículo es enseñar a programar una red neuronal en la IDE de Arduino)

Este es el video tutorial oficial de ensamblaje de Elegoo de este coche.

Así nos quedará montado… ta taáan:

Copiar la red neuronal

Una vez obtenida la red neuronal Python, haremos copiar y pegar de la matriz de pesos en el código Arduino (reemplazaremos las lineas 23 y 24):

Copiamos los pesos que obtenemos en la Jupyter Notebook de nuestra red neuronal en el código Arduino, reemplazando las variables por los nuevos valores.

El código Arduino

El código Arduino controlará el servo motor con el sensor de distancia que se moverá de izquierda a derecha y nos proveerá las entradas de la red: Distancia y Dirección(ó giro).

El resto, lo hará la red neuronal! En realidad, la red ya “aprendió” (en Python) es decir, sólo hará multiplicaciones y sumas de los pesos para obtener salidas. Realizará el camino forward propagation. Y las salidas controlarán directamente los 4 motores.

Hay código adicional para darle ciclos de tiempo a las ruedas a moverse (variable accionEnCurso) y dar más o menos potencia a los motores al cambiar de dirección. Son relativamente pocas líneas de código y logramos que la red neuronal conduzca el coche!

Nota: Para el movimiento del Servo motor se utiliza una librería “Servo” estándard.

Aquí vemos el código Arduino completo. Tal vez la parte más interesante sea la función conducir().

#include <Servo.h>  //servo library
Servo myservo;      // create servo object to control servo

int Echo = A4;  
int Trig = A5; 
#define ENA 5
#define ENB 6
#define IN1 7
#define IN2 8
#define IN3 9
#define IN4 11

/******************************************************************
   Network Configuration
 ******************************************************************/
const int InputNodes = 3; // incluye neurona de BIAS
const int HiddenNodes = 4; //incluye neurona de BIAS
const int OutputNodes = 4;
int i, j;
double Accum;
double Hidden[HiddenNodes];
double Output[OutputNodes];
float HiddenWeights[3][4] = {{1.8991509504079183, -0.4769472541445052, -0.6483690220539764, -0.38609165249078925}, {-0.2818610915467527, 4.040695699457223, 3.2291858058243843, -2.894301104732614}, {0.3340650864625773, -1.4016114422346901, 1.3580053902963762, -0.981415976256285}};
float OutputWeights[4][4] = {{1.136072297461121, 1.54602394937381, 1.6194612259569254, 1.8819066696635067}, {-1.546966506764457, 1.3951930739494225, 0.19393826092602756, 0.30992504138547006}, {-0.7755982417649826, 0.9390808625728915, 2.0862510744685485, -1.1229484266101883}, {-1.2357090352280826, 0.8583930286034466, 0.724702079881947, 0.9762852709700459}};
/******************************************************************
   End Network Configuration
 ******************************************************************/

void stop() {
  digitalWrite(ENA, LOW); //Desactivamos los motores
  digitalWrite(ENB, LOW); //Desactivamos los motores
  Serial.println("Stop!");
} 

//Medir distancia en Centimetros
int Distance_test() {
  digitalWrite(Trig, LOW);
  delayMicroseconds(2);
  digitalWrite(Trig, HIGH);
  delayMicroseconds(20);
  digitalWrite(Trig, LOW);
  float Fdistance = pulseIn(Echo, HIGH);
  Fdistance= Fdistance / 58;
  return (int)Fdistance;
}

void setup() {
  myservo.attach(3);  // attach servo on pin 3 to servo object
  Serial.begin(9600);
  pinMode(Echo, INPUT);
  pinMode(Trig, OUTPUT);
  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);
  pinMode(IN3, OUTPUT);
  pinMode(IN4, OUTPUT);
  pinMode(ENA, OUTPUT);
  pinMode(ENB, OUTPUT);
  stop();
  myservo.write(90);  //posicion inicial en el centro
  delay(500); 
} 

unsigned long previousMillis = 0;   // para medir ciclos de tiempo
const long interval = 25;           // intervalos cada x milisegundos
int grados_servo = 90;                // posicion del servo que mueve el sensor ultrasonico
bool clockwise = true;              // sentido de giro del servo
const long ANGULO_MIN = 30; 
const long ANGULO_MAX = 150; 
double ditanciaMaxima = 50.0;       // distancia de lejania desde la que empieza a actuar la NN
int incrementos = 9;                // incrementos por ciclo de posicion del servo
int accionEnCurso = 1;              // cantidad de ciclos ejecutando una accion
int multiplicador = 1000/interval;  // multiplica la cant de ciclos para dar tiempo a que el coche pueda girar
const int SPEED = 100;              // velocidad del coche de las 4 ruedas a la vez.

void loop() {
  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;

  /******************************************************************
    MANEJAR GIRO de SERVO
  ******************************************************************/
    if(grados_servo<=ANGULO_MIN || grados_servo>=ANGULO_MAX){
      clockwise=!clockwise; // cambio de sentido
      grados_servo = constrain(grados_servo, ANGULO_MIN, ANGULO_MAX);
    }
    if(clockwise)
      grados_servo=grados_servo+incrementos;
    else
      grados_servo=grados_servo-incrementos;
    
    if(accionEnCurso>0){
      accionEnCurso=accionEnCurso-1;
    }else{
  /******************************************************************
    LLAMAMOS a la FUNCION DE CONDUCCION
  ******************************************************************/
       conducir();      
    }
    myservo.write(grados_servo);    
  }

}

//USA LA RED NEURONAL YA ENTRENADA
void conducir()
{
    double TestInput[] = {0, 0,0};
    double entrada1=0,entrada2=0;
    
  /******************************************************************
    OBTENER DISTANCIA DEL SENSOR
  ******************************************************************/
    double distance = double(Distance_test());
    distance= double(constrain(distance, 0.0, ditanciaMaxima));
    entrada1= ((-2.0/ditanciaMaxima)*double(distance))+1.0; //uso una funcion lineal para obtener cercania
    accionEnCurso = ((entrada1 +1) * multiplicador)+1; // si esta muy cerca del obstaculo, necestia mas tiempo de reaccion

  /******************************************************************
    OBTENER DIRECCION SEGUN ANGULO DEL SERVO
  ******************************************************************/
    entrada2 = map(grados_servo, ANGULO_MIN, ANGULO_MAX, -100, 100);
    entrada2 = double(constrain(entrada2, -100.00, 100.00));

  /******************************************************************
    LLAMAMOS A LA RED FEEDFORWARD CON LAS ENTRADAS
  ******************************************************************/
  Serial.print("Entrada1:");
  Serial.println(entrada1);
  Serial.print("Entrada2:");
  Serial.println(entrada2/100.0);

  TestInput[0] = 1.0;//BIAS UNIT
  TestInput[1] = entrada1;
  TestInput[2] = entrada2/100.0;

  InputToOutput(TestInput[0], TestInput[1], TestInput[2]); //INPUT to ANN to obtain OUTPUT

  int out1 = round(abs(Output[0]));
  int out2 = round(abs(Output[1]));
  int out3 = round(abs(Output[2]));
  int out4 = round(abs(Output[3]));
  Serial.print("Salida1:");
  Serial.println(out1);
  Serial.print("Salida2:");
  Serial.println(out2);
  Serial.println(Output[1]);
  Serial.print("Salida3:");
  Serial.println(out3);
  Serial.print("Salida4:");
  Serial.println(out4);

  /******************************************************************
    IMPULSAR MOTORES CON LA SALIDA DE LA RED
  ******************************************************************/
  int carSpeed = SPEED; //hacia adelante o atras
  if((out1+out3)==2 || (out2+out4)==2){ // si es giro, necesita doble fuerza los motores
    carSpeed = SPEED * 2;
  }
  analogWrite(ENA, carSpeed);
  analogWrite(ENB, carSpeed);
  digitalWrite(IN1, out1 * HIGH); 
  digitalWrite(IN2, out2 * HIGH); 
  digitalWrite(IN3, out3 * HIGH);
  digitalWrite(IN4, out4 * HIGH);
}

void InputToOutput(double In1, double In2, double In3)
{
  double TestInput[] = {0, 0,0};
  TestInput[0] = In1;
  TestInput[1] = In2;
  TestInput[2] = In3;

  /******************************************************************
    Calcular las activaciones en las capas ocultas
  ******************************************************************/

  for ( i = 0 ; i < HiddenNodes ; i++ ) {
    Accum = 0;//HiddenWeights[InputNodes][i] ;
    for ( j = 0 ; j < InputNodes ; j++ ) {
      Accum += TestInput[j] * HiddenWeights[j][i] ;
    }
    //Hidden[i] = 1.0 / (1.0 + exp(-Accum)) ; //Sigmoid
    Hidden[i] = tanh(Accum) ; //tanh
  }

  /******************************************************************
    Calcular activacion y error en la capa de Salida
  ******************************************************************/

  for ( i = 0 ; i < OutputNodes ; i++ ) {
    Accum = 0;//OutputWeights[HiddenNodes][i];
    for ( j = 0 ; j < HiddenNodes ; j++ ) {
        Accum += Hidden[j] * OutputWeights[j][i] ;
    }
    Output[i] = tanh(Accum) ;//tanh
  }

}

El Coche en Acción!

Conecta tu coche, sube el código y ¡pruébalo!

Veamos un video del coche funcionando con su propia inteligencia artificial en el planeta tierra.

Conclusión

Aplicamos Machine Learning y sus redes neuronales a un objeto del mundo real y vimos cómo funciona, haciendo que el coche evite obstáculos y tome las decisiones por sí mismo, sin haberle dado instrucciones ni código explícito.

Puedes leer aquí sobre Redes Neuronales a lo largo de la historia

Mejoras a Futuro

Tengamos en cuenta que estamos teniendo como entradas los datos proporcionados por un sólo sensor de distancia y un servo motor que nos indica si está a izquierda o derecha. Podríamos tener más sensores de distancia, infrarrojos, medir velocidad, luz, sonido… en fin. Si tuviéramos que programar “manualmente” ese algoritmo, tendría una complejidad enorme y sería muy difícil de mantener o modificar. En cambio, hacer que una red neuronal aprenda sería muy sencillo. Tan sólo agregaríamos features (columnas) a nuestro código  y volveríamos a entrenar nuevamente la red. Voila!. Copiar y pegar los pesos obtenidos en Arduino, y nuestro coche tendría la inteligencia de manejarse por sí mismo nuevamente.

Suscripción al Blog

Puedes suscribirte al Blog y ser el primero en recibir los artículos cada 15 días. 

Puedes hacer más ejercicios de Aprendizaje Automático en Python  en nuestra categoría d Ejercicios paso a paso

Recursos

The post Programa un coche Arduino con Inteligencia Artificial first appeared on Aprende Machine Learning.

]]>
https://www.aprendemachinelearning.com/programa-un-coche-arduino-con-inteligencia-artificial/feed/ 76 5906