Escrito por Erick M.Sirpa en Planeta Chatbot. 

Q-learning es un algoritmo de aprendizaje reforzado basado en el cambio de estados y la retroalimentación, comúnmente usados para la resolución de laberintos en su más básica implementación.

Si deseas profundizar más acerca del tema, antes de empezar el proyecto, te invito a que puedas leer el siguiente artículo:

Un robot rastrero o un crawling robot (prefiero su nombre en inglés), es básicamente un robot que utiliza el algoritmo Q-learning para poder “aprender” los mejores movimientos para trasladarse

[cta]

Tabla de contenidos

Materiales

  • Sensor de distancia ultrasónico (En este tutorial se hace uso de un HCSR_04)
  • 2 servomotores

Te recomiendo que adquieras un kit de robótica, en este caso yo utilicé:

El robot Blui modificado para tener el sensor de distancia por detras

Conexiones

Sensor ultrasónico

int TRIGGER=8, ECHO=7;

En el caso de blui, sus entradas son estas:

REWARD

Estados

Cada servomotor se encargará de moverse en 4 posiciones, lo cual nos dá un total de 16 posiciones posibles para el brazo del robot

Acciones

Se tendrá 2 acciones por cada servomotor, en el primero de +20º y -20º, y para el segundo de +45º y -45º

Por lo tanto llegamos a la siguiente matriz 16×4 :

float R[16][4] = {   {0, 0, 0, 0},//0   {0, 0, 0, 0},//1   {0, 0, 0, 0},//2   {0, 0, 0, 0},//3   {0, 0, 0, 0},//4   {0, 0, 0, 0},//5   {0, 0, 0, 0},//6   {0, 0, 0, 0},//7   {0, 0, 0, 0},//8   {0, 0, 0, 0},//9   {0, 0, 0, 0},//10   {0, 0, 0, 0},//11   {0, 0, 0, 0},//12   {0, 0, 0, 0},//13   {0, 0, 0, 0},//14   {0, 0, 0, 0}};//15

Donde la primera y segunda columna son los movimientos +20 y -20 respectivamente (para el primer servo) y, la 3ra y 4ta columna son de +45 y -45 para el segundo servo

Restricciones

El robot sólo puede realizar ciertas acciones, en determinados estados. Por ejemplo, cuando el 2do servo se encuentre en 0º, no puede realizar la acción -45º. Por lo tanto la matriz queda modificada de la siguiente forma:

float R[16][4] = {   { 0, -1, 0, -1},    {-1, -1, 0, 0},    {-1, -1, 0, 0},    {-1, -1, -1, 0},   { 0, 0, 0, -1},    {-1, 0, 0, 0},    {-1, 0, 0, 0},   {-1, 0, -1, 0},   { 0, 0, 0, -1},    {-1, 0, 0, 0},   {-1, 0, 0, 0},   {-1, 0, -1, 0},   {-1, 0, 0, -1},   {-1, -1, 0, 0},   {-1, -1, 1000,0},   {-1, 0, -1, 0}};

Donde 1000 es el objetivo o target, el cual es el último estado. Siendo en el estado 15, donde el mejor movimiento (1000) será de moverse +45 en el segundo servo.

Sensor de distancia

float dist() {digitalWrite(TRIGGER, LOW);   delayMicroseconds(2);   digitalWrite(TRIGGER, HIGH);   delayMicroseconds(10);   digitalWrite(TRIGGER, LOW);   // Calcula la distancia midiendo el tiempo del estado alto del pin ECHO   tiempo = pulseIn(ECHO, HIGH);   // La velocidad del sonido es de 340m/s o 29 microsegundos por centimetro   distancia = tiempo / 58.00;   //manda la distancia al monitor serie/*   Serial.print(distancia);   Serial.println("cm");   delay(100);   */return distancia;  }

Movimiento de servos por pasos

Para reducir el movimiento brusco de los servos y evitar que el robot vibre (que puede causar falsos positivos al momento de medir distancias)

void servoVelocidad(Servo servo, int anguloA, int anguloB, int velocidad) {if (anguloA<anguloB) {   for (int angulo = anguloA; angulo <= anguloB; angulo=angulo+2)   {   servo.write(angulo);   delay(velocidad);   }}   else {for (int angulo = anguloA; angulo >= anguloB; angulo=angulo-2)   {   servo.write(angulo);   delay(velocidad);   }}}

Refuerzo o reward por distancia recorrida

Establecí un sistema de puntuaciones, que modifica la matriz R, cuando el robot recorre una distancia positiva. Refuerzo positivo

if(diff>=1.9 ){     point=map(diff,1,4,5,10);     R[nstate][action]=point;     Serial.println(point);   }

Donde si la diferencia de distancia recorrida es mayor o igual a 1.9 cm, le da un puntaje del 1 al 10, en la matriz R, dependiendo de la acción que esté ejecutando

Q-LEARNING

Y por último el algoritmo que determina los valores para la matriz Q

a = -10;   for (int i = 0; i < 4; i++) {   if (a < Q[nstate][i]) {   a = Q[nstate][i];   }   }     Qmax = a * gamma;   Q[state][action] = R[state][action] + Qmax;   state = nstate;

PROGRAMA COMPLETO

/*  Q_Learning Robot  by: Erick M. Sirpa  */#include <Servo.h>   void Mostrar(float Q[][4]);  float distancia;  float tiempo;  int TRIGGER=8,ECHO=7;Servo servo1,servo2;   int valor=0;  int act=0;int ang=40;   int ang2=0;  int ang_t=0;   int ang2_t=0;    float Q[16][4]={{ 0, 0, 0, 0},   {0, 0, 0, 0},   {0, 0, 0, 0},   {0, 0, 0, 0},   { 0, 0, 0, 0},   {0, 0, 0, 0},   {0, 0, 0, 0},   {0, 0, 0, 0},   { 0, 0, 0, 0},   {0, 0, 0, 0},   {0, 0, 0, 0},   {0, 0, 0, 0},   {0, 0, 0, 0},   {0, 0, 0, 0},   {0, 0, 0, 0},   {0, 0, 0, 0}};int action=0;  int state=0;  int cont=0;  float gamma = 0.8;  float Qmax=0;  float a=0,b=0;  int x=0;  int goal=15;void setup (){   servo1.attach(9);    servo2.attach(6);    pinMode(TRIGGER, OUTPUT);    pinMode(ECHO, INPUT);  Serial.begin(9600);  float R[16][4] = {   { 0, -1, 0, -1},    {-1, -1, 0, 0},    {-1, -1, 0, 0},    {-1, -1, -1, 0},   { 0, 0, 0, -1},    {-1, 0, 0, 0},    {-1, 0, 0, 0},   {-1, 0, -1, 0},   { 0, 0, 0, -1},    {-1, 0, 0, 0},   {-1, 0, 0, 0},   {-1, 0, -1, 0},   {-1, 0, 0, -1},   {-1, -1, 0, 0},   {-1, -1, 1000,0},   {-1, 0, -1, 0}};  int pos[16][3]={{0,2,0},  {2,3,0},  {2,3,0},  {3,3,0},{0,1,2},  {2,3,0},  {2,3,0},  {3,3,0},{0,1,2},  {2,3,0},  {2,3,0},  {3,3,0},{1,2,1},  {2,3,3},  {2,3,3},  {1,3,3},  };  int nstate=0;  float diff=0,d_prom=0,d_ant=0, d_new=0;  float point=0;  int cc=0;  for(int d=0;d<40;d++){     d_prom=dist()+d_prom;   delay(100);     }  d_ant=d_prom/20;  Serial.println(d_ant);  delay(1000);  for (int epoca=0;epoca<10;epoca++)  {  randomSeed(analogRead(0));   state=random(15);   ang=40;   ang2=0;     while(state!=goal){   ang_t=ang;   ang2_t=ang2;   cc=0;   cont++;   x=random(2);     action=pos[state][x];    if(action==0){nstate=state+4;   ang=ang+20;   ang2=0;     }    else if(action==1){nstate=state-4;   ang=ang-20;   ang2=0;     }    else if(action==2){   nstate=state+1;   ang2=ang2+45;     }    else if(action==3){   nstate=state-1;   ang2=ang2-45;     }servoVelocidad(servo1,ang_t,ang,5);  servoVelocidad(servo2,ang2_t,ang2,5);d_new=dist();   diff=d_new-d_ant;  d_ant=d_new;     if(diff>=1.9 ){     point=map(diff,1,4,5,10);     R[nstate][action]=point;     Serial.println(point);   }   Serial.println(" ");a = -10;   for (int i = 0; i < 4; i++) {   if (a < Q[nstate][i]) {   a = Q[nstate][i];   }   }     Qmax = a * gamma;   Q[state][action] = R[state][action] + Qmax;   state = nstate;}}Mostrar(R);   Serial.println(" ");   Serial.println(" ");  Mostrar(Q);}  void loop(){state = random(3);  ang=40;  ang2=0;  while(state!=goal){ b = -10;     for (int i = 0; i < 4; i++) {   if (b <= Q[state][i]) {   b = Q[state][i];   act = i;   }   }   ang_t=ang;   ang2_t=ang2;   if(act==0){state=state+4;   ang=ang+20;   ang2=0;     }    else if(act==1){state=state-4;   ang=ang-20;   ang2=0;     }    else if(act==2){   state=state+1;   ang2=ang2+45;     }    else if(act==3){   state=state-1;   ang2=ang2-45;     }servoVelocidad(servo1,ang_t,ang,25);  servoVelocidad(servo2,ang2_t,ang2,25);   }}void Mostrar(float Q[][4]){  for (int i=0;i<16;i++){   for(int j=0;j<4;j++){   Serial.print(Q[i][j]);   Serial.print(" ");       }   Serial.println(" ");    }  }  float dist() {digitalWrite(TRIGGER, LOW);   delayMicroseconds(2);   digitalWrite(TRIGGER, HIGH);   delayMicroseconds(10);   digitalWrite(TRIGGER, LOW);   // Calcula la distancia midiendo el tiempo del estado alto del pin ECHO   tiempo = pulseIn(ECHO, HIGH);     distancia = tiempo / 58.00;  /*   Serial.print(distancia);   Serial.println("cm");   delay(100);   */return distancia;  }  void servoVelocidad(Servo servo, int anguloA, int anguloB, int velocidad) {if (anguloA<anguloB) {   for (int angulo = anguloA; angulo <= anguloB; angulo=angulo+2)   {   servo.write(angulo);   delay(velocidad);   }}   else {for (int angulo = anguloA; angulo >= anguloB; angulo=angulo-2)   {   servo.write(angulo);   delay(velocidad);   }}}

Y… LISTO!

 

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *