The post Aprendizaje por Refuerzo first appeared on Aprende Machine Learning.
]]>En este artículo aprenderemos qué es el aprendizaje por refuerzo, lo más novedoso y ambicioso a día de hoy en Inteligencia artificial, veremos cómo funciona, sus casos de uso y haremos un ejercicio práctico completo en Python: una máquina que aprenderá a jugar al pong sóla, sin conocer las reglas ni al entorno.
Los temas que veremos incluyen:
Comencemos!!
Seguramente ya conocerás las 2 grandes áreas de aprendizaje tradicional del Machine Learning, el aprendizaje supervisado y el aprendizaje no supervisado. Parece difícil que aquí hubiera espacio para otras opciones; sin embargo sí la hay y es el Aprendizaje por refuerzo. En aprendizaje por refuerzo (ó Reinforcement Learning en inglés) no tenemos una “etiqueta de salida”, por lo que no es de tipo supervisado y si bien estos algoritmos aprenden por sí mismos, tampoco son de tipo no supervisado, en donde se intenta clasificar grupos teniendo en cuenta alguna distancia entre muestras.
Si nos ponemos a pensar, los problemas de ML supervisados y no supervisados son específicos de un caso de negocio en particular, sea de clasificación ó predicción, están muy delimitados, por ejemplo, clasificar “perros ó gatos“, ó agrupar “k=5” clusters. En contraste, en el mundo real contamos con múltiples variables que por lo general se interrelacionan y que dependen de otros casos de negocio y dan lugar a escenarios más grandes en donde tomar decisiones. Para conducir un coche no basta una inteligencia que pueda detectar un semáforo en rojo, verde ó amarillo; tendremos muchísimos factores -todos a la vez- a los que prestar atención: a qué velocidad vamos, estamos ante una curva?, hay peatones?, es de noche y debemos encender las luces?.
Una solución sería tener múltiples máquinas de ML supervisadas y que interactúan entre si -y esto no estaría mal- ó podemos cambiar el enfoque… Y ahí aparece el Reinforcement Learning (RL) como una alternativa, tal vez de las más ambiciosas en las que se intenta integrar el Machine Learning en el mundo real, sobre todo aplicado a robots y maquinaria industrial.
El Reinforcement Learning entonces, intentará hacer aprender a la máquina basándose en un esquema de “premios y castigos” -cómo con el perro de Pablov- en un entorno en donde hay que tomar acciones y que está afectado por múltiples variables que cambian con el tiempo.
En los modelos de Aprendizaje Supervisado (o no supervisado) como redes neuronales, árboles, knn, etc, se intenta “minimizar la función coste”, reducir el error.
En cambio en el RL se intenta “maximizar la recompensa“. Y esto puede ser, a pesar de a veces cometer errores ó de no ser óptimos.
El Reinforcement Learning propone un nuevo enfoque para hacer que nuestra máquina aprenda, para ello, postula los siguientes 2 componentes:
Entre ellos hay una relación que se retroalimenta y cuenta con los siguientes nexos:
Entonces, la “foto final” nos queda así:
En un primer momento, el agente recibe un estado inicial y toma una acción con lo cual influye é interviene en el ambiente. Esto está muy bien, pues es muy cierto que cuando tomamos decisiones en el mundo real lo estamos modificando, ¿no?. Y esa decisión tendrá sus consecuencias: en la siguiente iteración el ambiente devolverá al agente el nuevo estado y la recompensa obtenida. Si la recompensa es positiva estaremos reforzando ese comportamiento para el futuro. En cambio si la recompensa es negativa lo estaremos penalizando, para que ante la misma situación el agente actúe de manera distinta. El esquema en el que se apoya el Reinforcement Learning es en el de Proceso de Decisión de Markov.
El aprendizaje por refuerzo puede ser usado en robots, por ejemplo en brazos mecánicos en donde en vez de enseñar instrucción por instrucción a moverse, podemos dejar que haga intentos “a ciegas” e ir recompensando cuando lo hace bien.
También puede usarse en ambientes que interactúan con el mundo real, como en otro tipo de maquinaria industrial y para el mantenimiento predictivo, pero también en el ambiente financiero, por ejemplo para decidir cómo conformar una cartera de inversión sin intervención humana.
Otro caso de uso que está ganando terreno es el de usar RL para crear “webs personalizadas” para cada internauta. Y si lo piensas… tiene algo de sentido tomar el concepto de “premiar” al algoritmo si acierta con las sugerencias que hace al usuario si hace clic ó penalizar al modelo si sus recomendaciones no le son de utilidad.
También se utiliza el Reinforcement Learning para entrenar sistemas de navegación de coches, drones ó aviones.
Los videojuegos suelen ser ejemplos del uso de RL, ¿porque? te preguntarás. Pues porque los videojuegos son un entorno YA programado en el que se está simulando un ambiente y en el que ocurren eventos a la vez. Por lo general el jugador es el agente que debe decidir qué movimientos hacer. En el Starcraft tenemos ejércitos enemigos movilizados e intentando aniquilarnos, hay que desplazar distintas unidades que tienen variadas cualidades y hay que hacerlo rápido, atacar, defender, conquistar. ¿Cómo haríamos esto con un modelo de ML tradicional? es suficiente una sola red neuronal? muchas? cómo interactúan?. Pero sobre todo… ¿cómo crearíamos el grupo de “etiquetas de salida” para entrenar a la red, ante un juego imprevisible? Estamos diciendo que hay cientos de miles de combinaciones de salidas posibles.
Bien, vamos a comentar cómo funcionaría la secuencia de un algoritmo que aprende por refuerzo.
Cómo dijimos antes, el agente deberá tomar decisiones para interactuar con el ambiente, dado un estado. Pero, de qué manera tomar esas decisiones?
Al principio de todo, nuestro agente está “en blanco”, es decir, no sabe nada de nada de lo que tiene que hacer ni de cómo comportarse. Entonces podemos pensar en que tomará una de las posibles acciones aleatoriamente. E irá recibiendo pistas de si lo está haciendo bien ó mal en base a las recompensas. Entonces irá “tomando nota”, esto bien, esto mal.
Una recompensa para un humano es algún estímulo que le de placer. Podría ser un aumento de sueldo, chocolate, una buena noticia. Para nuestro modelo de ML la recompensa es sencillamente un Score: un valor numérico.
Supongamos que la acción “A” nos recompensa con 100 puntos. El Agente podría pensar “genial, voy a elegir A nuevamente para obtener 100 puntos” y puede que el algoritmo se estanque en una única acción y nunca logre concretar el objetivo global que queremos lograr.
Es decir que tenemos que lograr un equilibrio entre “explorar lo desconocido y explotar los recursos” en el ambiente. Eso es conocido como el dilema de exploración/explotación.
El agente explorará el ambiente e irá aprendiendo “cómo moverse” y cómo ganar recompensas (y evitar las penalizaciones). Al final almacenará el conocimiento en unas normas también llamadas “políticas“.
Pero… debo decir que es probable que el agente “muera” ó pierda la partida las primeras… ¿mil veces? Con esto me refiero a que deberemos entrenar miles y miles de veces al agente para que cometa errores y aciertos y pueda crear sus políticas hasta ser un buen Agente.
Bueno a decir la verdad si… esto es un poco vergonzoso… pero cierto. La realidad es que para hacerle aprender a un coche autónomo a conducir, debemos hacerlo chocar, acelerar, conducir contramano y cometer todo tipo de infracciones para decirle “eso está mal, te quito los puntos” y para ello, hay que hacer que ejecute miles y miles de veces en un entorno de simulado.
Para entrenar a DeepMind a dominar al Starcraft ha tenido que jugar el equivalente a miles de horas humanas de juego, y miles de partidas, puede que lo que le llevaría a una persona años, se logra en 8 horas. Y con ese aprendizaje logra vencer a los campeones jugadores humanos.
Esto tiene un lado bueno y uno malo. El malo ya lo vemos; tenemos que usar la fuerza bruta para que aprenda. Lo bueno es que contamos con equipos muy potentes que nos posibilitan realizar esta atrocidad. Por otra parte, recordemos que estamos apuntando a un caso de uso mucho más grande y ambicioso que el de “sólo distinguir entre perritos y gatitos”
Ahora vamos a comentar uno de los modelos usados en Reinforcement Learning para poder concretar un ejemplo de su implementación. Es el llamado “Q-Learning”.
Repasemos los elementos que tenemos:
El objetivo principal al entrenar nuestro modelo a través de las simulaciones será ir “rellenando” la tabla de Políticas de manera que las decisiones que vaya tomando nuestro agente obtengan “la mayor recompensa” a la vez que avanzamos y no nos quedamos estancados, es decir, pudiendo cumplir el objetivo global (ó final) que deseamos alcanzar.
A la política la llamaremos “Q” por lo que:
Q(estado, acción) nos indicará el valor de la política para un estado y una acción determinados.
Y para saber cómo ir completando la tabla de políticas nos valemos de la ecuación de Bellman.
La ecuación matemática que utilizaremos será:
No lo explicaré en detalle, pues tomaría mucho, pero en resumen; lo que explica la ecuación es cómo ir actualizando las políticas Q^(s,a) en base al valor actual más una futura recompensa que recibiremos, en caso de tomar dicha acción. Hay dos ratios que afectan a la manera en que influye esa recompensa: el ratio de aprendizaje, que regula “la velocidad” en la que se aprende, y la “tasa de descuento” que tendrá en cuenta la recompensa a corto o largo plazo.
Hagamos una máquina que aprenda a jugar al Pong sóla (código completo en github).
Para no tener que instalar ningún paquete adicional… usaremos el propio matplotlib como interface gráfica del juego.
Este es el plan: simularemos el ambiente del juego y su compotamiento en la Jupyter Notebook.
El agente será el “player 1” y sus acciones posible son 2:
Y las reglas del juego:
Agreguemos los imports que usaremos:
import numpy as np import matplotlib.pyplot as plt from random import randint from time import sleep from IPython.display import clear_output from math import ceil,floor %matplotlib inline
Dentro de la clase Agente encontraremos la tabla donde iremos almacenando las políticas. En nuestro caso la tabla cuenta de 3 coordenadas:
Además en esta clase, definiremos el factor de descuento, el learning rate y el ratio de exploración.
Los métodos más importantes:
class PongAgent: def __init__(self, game, policy=None, discount_factor = 0.1, learning_rate = 0.1, ratio_explotacion = 0.9): # Creamos la tabla de politicas if policy is not None: self._q_table = policy else: position = list(game.positions_space.shape) position.append(len(game.action_space)) self._q_table = np.zeros(position) self.discount_factor = discount_factor self.learning_rate = learning_rate self.ratio_explotacion = ratio_explotacion def get_next_step(self, state, game): # Damos un paso aleatorio... next_step = np.random.choice(list(game.action_space)) # o tomaremos el mejor paso... if np.random.uniform() <= self.ratio_explotacion: # tomar el maximo idx_action = np.random.choice(np.flatnonzero( self._q_table[state[0],state[1],state[2]] == self._q_table[state[0],state[1],state[2]].max() )) next_step = list(game.action_space)[idx_action] return next_step # actualizamos las politicas con las recompensas obtenidas def update(self, game, old_state, action_taken, reward_action_taken, new_state, reached_end): idx_action_taken =list(game.action_space).index(action_taken) actual_q_value_options = self._q_table[old_state[0], old_state[1], old_state[2]] actual_q_value = actual_q_value_options[idx_action_taken] future_q_value_options = self._q_table[new_state[0], new_state[1], new_state[2]] future_max_q_value = reward_action_taken + self.discount_factor*future_q_value_options.max() if reached_end: future_max_q_value = reward_action_taken #maximum reward self._q_table[old_state[0], old_state[1], old_state[2], idx_action_taken] = actual_q_value + \ self.learning_rate*(future_max_q_value -actual_q_value) def print_policy(self): for row in np.round(self._q_table,1): for column in row: print('[', end='') for value in column: print(str(value).zfill(5), end=' ') print('] ', end='') print('') def get_policy(self): return self._q_table
En la clase de Ambiente encontramos implementada la lógica y control del juego del pong. Se controla que la pelotita rebote, que no se salga de la pantalla y se encuentran los métodos para graficar y animar en matplotlib.
Por Defecto se define una pantalla de 40 pixeles x 50px de alto y si utilizamos la variable “movimiento_px = 5” nos quedará definida nuestra tabla de políticas en 8 de alto y 10 de ancho (por hacer 40/5=8 y 50/5=10). Estos valores se pueden modificar a gusto!
Además, muy importante, tenemos el control de cuándo dar las recompensas y penalizaciones, al perder cada vida y detectar si el juego a terminado
class PongEnvironment: def __init__(self, max_life=3, height_px = 40, width_px = 50, movimiento_px = 3): self.action_space = ['Arriba','Abajo'] self._step_penalization = 0 self.state = [0,0,0] self.total_reward = 0 self.dx = movimiento_px self.dy = movimiento_px filas = ceil(height_px/movimiento_px) columnas = ceil(width_px/movimiento_px) self.positions_space = np.array([[[0 for z in range(columnas)] for y in range(filas)] for x in range(filas)]) self.lives = max_life self.max_life=max_life self.x = randint(int(width_px/2), width_px) self.y = randint(0, height_px-10) self.player_alto = int(height_px/4) self.player1 = self.player_alto # posic. inicial del player self.score = 0 self.width_px = width_px self.height_px = height_px self.radio = 2.5 def reset(self): self.total_reward = 0 self.state = [0,0,0] self.lives = self.max_life self.score = 0 self.x = randint(int(self.width_px/2), self.width_px) self.y = randint(0, self.height_px-10) return self.state def step(self, action, animate=False): self._apply_action(action, animate) done = self.lives <=0 # final reward = self.score reward += self._step_penalization self.total_reward += reward return self.state, reward , done def _apply_action(self, action, animate=False): if action == "Arriba": self.player1 += abs(self.dy) elif action == "Abajo": self.player1 -= abs(self.dy) self.avanza_player() self.avanza_frame() if animate: clear_output(wait=True); fig = self.dibujar_frame() plt.show() self.state = (floor(self.player1/abs(self.dy))-2, floor(self.y/abs(self.dy))-2, floor(self.x/abs(self.dx))-2) def detectaColision(self, ball_y, player_y): if (player_y+self.player_alto >= (ball_y-self.radio)) and (player_y <= (ball_y+self.radio)): return True else: return False def avanza_player(self): if self.player1 + self.player_alto >= self.height_px: self.player1 = self.height_px - self.player_alto elif self.player1 <= -abs(self.dy): self.player1 = -abs(self.dy) def avanza_frame(self): self.x += self.dx self.y += self.dy if self.x <= 3 or self.x > self.width_px: self.dx = -self.dx if self.x <= 3: ret = self.detectaColision(self.y, self.player1) if ret: self.score = 10 else: self.score = -10 self.lives -= 1 if self.lives>0: self.x = randint(int(self.width_px/2), self.width_px) self.y = randint(0, self.height_px-10) self.dx = abs(self.dx) self.dy = abs(self.dy) else: self.score = 0 if self.y < 0 or self.y > self.height_px: self.dy = -self.dy def dibujar_frame(self): fig = plt.figure(figsize=(5, 4)) a1 = plt.gca() circle = plt.Circle((self.x, self.y), self.radio, fc='slategray', ec="black") a1.set_ylim(-5, self.height_px+5) a1.set_xlim(-5, self.width_px+5) rectangle = plt.Rectangle((-5, self.player1), 5, self.player_alto, fc='gold', ec="none") a1.add_patch(circle); a1.add_patch(rectangle) #a1.set_yticklabels([]);a1.set_xticklabels([]); plt.text(4, self.height_px, "SCORE:"+str(self.total_reward)+" LIFE:"+str(self.lives), fontsize=12) if self.lives <=0: plt.text(10, self.height_px-14, "GAME OVER", fontsize=16) elif self.total_reward >= 1000: plt.text(10, self.height_px-14, "YOU WIN!", fontsize=16) return fig
Finalmente definimos una función para jugar, donde indicamos la cantidad de veces que queremos iterar la simulación del juego e iremos almacenando algunas estadísticas sobre el comportamiento del agente, si mejora el puntaje con las iteraciones y el máximo puntaje alcanzado.
def play(rounds=5000, max_life=3, discount_factor = 0.1, learning_rate = 0.1, ratio_explotacion=0.9,learner=None, game=None, animate=False): if game is None: game = PongEnvironment(max_life=max_life, movimiento_px = 3) if learner is None: print("Begin new Train!") learner = PongAgent(game, discount_factor = discount_factor,learning_rate = learning_rate, ratio_explotacion= ratio_explotacion) max_points= -9999 first_max_reached = 0 total_rw=0 steps=[] for played_games in range(0, rounds): state = game.reset() reward, done = None, None itera=0 while (done != True) and (itera < 3000 and game.total_reward<=1000): old_state = np.array(state) next_action = learner.get_next_step(state, game) state, reward, done = game.step(next_action, animate=animate) if rounds > 1: learner.update(game, old_state, next_action, reward, state, done) itera+=1 steps.append(itera) total_rw+=game.total_reward if game.total_reward > max_points: max_points=game.total_reward first_max_reached = played_games if played_games %500==0 and played_games >1 and not animate: print("-- Partidas[", played_games, "] Avg.Puntos[", int(total_rw/played_games),"] AVG Steps[", int(np.array(steps).mean()), "] Max Score[", max_points,"]") if played_games>1: print('Partidas[',played_games,'] Avg.Puntos[',int(total_rw/played_games),'] Max score[', max_points,'] en partida[',first_max_reached,']') #learner.print_policy() return learner, game
Para entrenar ejecutamos la función con los siguientes parámetros:
learner, game = play(rounds=6000, discount_factor = 0.2, learning_rate = 0.1, ratio_explotacion=0.85)
Y vemos la salida del entreno, luego de unos 2 minutos:
Begin new Train! -- Partidas[ 500 ] Avg.Puntos[ -234 ] AVG Steps[ 116 ] Max Score[ 10 ] -- Partidas[ 1000 ] Avg.Puntos[ -224 ] AVG Steps[ 133 ] Max Score[ 100 ] -- Partidas[ 1500 ] Avg.Puntos[ -225 ] AVG Steps[ 134 ] Max Score[ 230 ] -- Partidas[ 2000 ] Avg.Puntos[ -223 ] AVG Steps[ 138 ] Max Score[ 230 ] -- Partidas[ 2500 ] Avg.Puntos[ -220 ] AVG Steps[ 143 ] Max Score[ 230 ] -- Partidas[ 3000 ] Avg.Puntos[ -220 ] AVG Steps[ 145 ] Max Score[ 350 ] -- Partidas[ 3500 ] Avg.Puntos[ -220 ] AVG Steps[ 144 ] Max Score[ 350 ] -- Partidas[ 4000 ] Avg.Puntos[ -217 ] AVG Steps[ 150 ] Max Score[ 350 ] -- Partidas[ 4500 ] Avg.Puntos[ -217 ] AVG Steps[ 151 ] Max Score[ 410 ] -- Partidas[ 5000 ] Avg.Puntos[ -216 ] AVG Steps[ 153 ] Max Score[ 510 ] -- Partidas[ 5500 ] Avg.Puntos[ -214 ] AVG Steps[ 156 ] Max Score[ 510 ] Partidas[ 5999 ] Avg.Puntos[ -214 ] Max score[ 510 ] en partida[ 5050 ]
En las salidas vemos sobre todo cómo va mejorando en la cantidad de “steps” que da el agente antes de perder la partida.
Ya contamos con nuestro agente entrenado, ahora veamos qué tal se comporta en una partida de pong, y lo podemos ver jugar, pasando el parámetro animate=True.
Antes de jugar, instanciamos un nuevo agente “learner2” que utilizará las políticas que creamos anteriormente. A este agente le seteamos el valor de explotación en 1, para evitar que tome pasos aleatorios.
learner2 = PongAgent(game, policy=learner.get_policy()) learner2.ratio_explotacion = 1.0 # con esto quitamos las elecciones aleatorias al jugar player = play(rounds=1, learner=learner2, game=game, animate=True)
Y veremos nuestro juego de Pong en acción!
En mi caso, con las 6 mil iteraciones de entrenamiento fue suficiente alcanzar los 500 puntos y ganar (puedes ir variando el objetivo a 500 puntos ó a 1000, la cantidad de vidas, etc.)
Quiero brevemente comentar la tabla de políticas que hemos creado luego de entrenar.
En este ejemplo, mostraré una tabla de 3 coordenadas. La primera toma valores del 0 al 7 (posición del jugador), la segunda también 8 valores (altura de la bola de pong) y la tercera va del 0 al 9 con el desplazamiento horizontal de la pelota.
Supongamos que el player está situado en la posición “de abajo de todo”, es decir, en la posición cero.
Dentro de esa posición queda conformada la siguiente tabla:
Si nos fijamos en la coordenada de la bola (x8, y1) vemos los valores 1.9 para subir y -9 para bajar. Claramente la recompensa mayor está en la acción de subir. Pero si la pelotita estuviera en (x9,y4) la mejor acción será Bajar, aunque tenga un puntaje negativo de -16,7 será mejor que restar 46.
Hay muchos más detalles y lecturas adicionales para dominar el tema, pero en este artículo hemos explicado los conceptos básicos del reinforcement learning, sus diferencias con el aprendizaje supervisado y sus características.
Además conocimos su implementación más conocida, el Q-Learning y realizamos un juego completo en Python en donde el Agente sin tener conocimiento previo de las reglas ni del entorno logra aprender y volverse un muy buen jugador de Pong tras miles de simulaciones.
Debo decir que una evolución muy interesante del Aprendizaje por Refuerzo es el Aprendizaje por Refuerzo Profundo en donde aparecen las redes neuronales a mejorar y perfeccionar al modelo. Escibiré sobre ello en un próximo artículo!
Aprovecho a desearles un muy buen fin de año y a que puedan empezar el 2021 con muchos planes y muchas ganas de seguir aprendiendo sobre Machine Learning y la ciencia de datos.
También les invito a descargar ó comprar “el libro del blog” en formato digital y como novedad, he logrado publicar en la tienda de Amazon la versión del libro en formato papel, en gran parte por algunos de vosotros que me lo pidieron. Así que mil gracias porque gracias a ese empuje y ánimo que me dieron, puedo decir que termino el año con mi primer libro publicado, lo cual para mi es un sueño cumplido! Y -perdón la insistencia con esto- pero ciertamente este año ha sido un año muy difícil para mi al igual que para todos y jamás hubiera pensado haberlo podido conseguir. Es un hito en mi vida.
Muchas gracias querido lector, desde aquí te envío un sincero abrazo virtual!.
Descarga la notebook completa desde GitHub aqui
Otros artículos relacionados:
Recibe los próximos artículos sobre Machine Learning, estrategias, teoría y código Python en tu casilla de correo!
NOTA: algunos usuarios reportaron que el email de confirmación y/o posteriores a la suscripción entraron en su carpeta de SPAM. Te sugiero que revises y recomiendo que agregues nuestro remitente info @ aprendemachinelearning.com a tus contactos para evitar problemas. Gracias!
Si te gustan los contenidos del blog y quieres darme una mano, puedes comprar el libro en papel, ó en digital.
The post Aprendizaje por Refuerzo first appeared on Aprende Machine Learning.
]]>