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é:
- Blui [ https://www.bluecircuits.com.bo/blui ]
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!