Pouse um módulo lunar com Deep Q-learning

Um pequeno passo em Q-learning, mas um grande salto em IA!

William Fukushima
Turing Talks
9 min readJun 7, 2020

--

Texto escrito por William Abe Fukushima e Stephanie Miho Urashima.

Bom dia, boa tarde ou boa noite.

Bem-vindos a mais um Turing Talks! Prepare sua dose de cafeína porque hoje implementaremos o famoso Deep Q-Learning, um algoritmo que se baseia em Q-learning, e utiliza conceitos de Redes Neurais e RL juntos para solucionar problemas, criar jogadores ou resolver ambientes com estados contínuos. É o início da abordagem de aprendizado profundo na área de Aprendizado por Reforço, que abre muitas portas para otimizações mais eficientes assim como uma maior versatilidade em aplicações.

Técnicas de aprendizado profundo na área de aprendizado por reforço podem ser aplicadas em outras áreas de machine learning (Processamento de Linguagem Natural, Ciência de Dados, Visão Computacional) para obter melhores resultados.

Introdução

Neste post, abordaremos os seguintes tópicos:

  • Adaptação da Equação de Bellman de Q-learning para Deep Q-learning
  • Huber Loss
  • Repetição de Experiências

E para tanto, estaremos resolvendo o ambiente ”LunarLander-v2” da biblioteca gym utilizando Tensorflow 2.0.

Em nosso programa, importaremos as seguintes bibliotecas:

import gym
import tensorflow as tf
import numpy as np

O Ambiente

Photo by Mike Petrucci on Unsplash

Neste ambiente, o agente é um módulo lunar que está tentando pousar suavemente na lua.

As possíveis ações do módulo lunar são:

  • ativar o motor central;
  • ativar o motor da direita;
  • ativar o motor da esquerda.

E os estados do ambiente são listas “s”:

  • s[0] é a coordenada horizontal da nave;
  • s[1] é a coordenada vertical da nave;
  • s[2] é a velocidade horizontal da nave;
  • s[3] é a velocidade vertical da nave;
  • s[4] é o angulo da nave;
  • s[5] é a velocidade angular da nave;
  • s[6] 1 se o primeiro pé da nave tem contato, 0 caso contrário;
  • s[7] 1 se o segundo pé da nave tem contato, 0 caso contrário.
Agente não treinado

DQN — Intuição e teoria

Para compreender como nosso algoritmo funciona, vamos primeiro recordar o algoritmo de Q-learning (caso não lembre do algoritmo, vale a pena dar uma olhada em nosso post anterior sobre Q-learning onde criamos uma IA que joga pong):

inicializamos todos os valores das ações “Q” em cada estado aleatoriamente em uma tabela e conforme adquirimos informação sobre o valor das ações de cada estado, atualizamos da seguinte maneira:

Q-learning

iterando para cada “dia da marmota”.

A diferença entre o método tratado no post anterior e o tratado neste é que faremos uma abstração da tabela Q-valor para uma rede neural.

“- Mas como assim?? Uma tabela pode ser substituída por uma rede neural??”, você deve estar se perguntando.

Vamos desenvolver um pouco isto:

“Para cada valor discreto de s, temos valores de ação Q(s,an) correspondentes”

“Para cada valor discreto de s, temos valores de ação Q(s,an) correspondentes”

Bom, a tabela é simplesmente uma maneira de guardar associações entre estados e valores de ações de maneira discreta, ou seja, para cada valor de “s” que temos anotado na tabela, temos um conjunto de valores de ações associados.

Essencialmente, isto é uma função. Entram alguns valores (s = (s1, s2, sn)) e saem outros (Q(s,a1), Q(s,a2), Q(s,an)). No entanto, como estávamos trabalhando com tabelas, só era possível para nosso algoritmo de Q-learning olhar para alguns pontos dessas funções.

Em uma função, “Para valores contínuos de s, temos valores de ação Q(s,an) correspondentes”

Q(a1) em verde, Q(a2) em vermelho e Q(a3) em azul. Todos em função de S.

A rede neural é basicamente a representação de uma função contínua que estará fazendo o papel da tabela associando variáveis contínuas de estado à valores de ações, o que significa que não trabalharemos com um número fixo de estados, mas sim com uma gama contínua de estados.

A rede neural utilizada é chamada de Deep Q-Network(DQN) e terá a seguinte estrutura:

Os parâmetros são:

  • alpha: taxa de aprendizado;
  • n_acoes: número de ações distintas que o agente pode tomar;
  • input_dims: dimensões da variavel de entrada (que no caso será a lista contendo os estados do ambiente).
  • fc1 e fc2: número de neurônios nas camadas.

Caso queira relembrar a teoria por trás de redes neurais visite a nossa série sobre Redes neurais: Redes Neurais | Teoria — Turing Talks, ou caso queira algo mais direto veja nosso post sobre Tensorflow 2.0: Redes Neurais | Redes Neurais com Keras e TensorFlow 2.0

Outra cena do filme Groundhog Day

Agora que nosso “Phil Connors” ganhou um cérebro em vez de uma planilha para aprender, precisamos definir algumas modificações da Equação de Bellman para adaptar à função de custo da rede neural.

Adaptação da Equação de Bellman de Q-learning para Deep Q-learning

Como toda rede neural, para aplicar o gradiente descendente, precisamos de uma função de custo que envolve o que estamos tentando prever.

Neste caso, o que estamos prevendo é o valor de uma ação dado um estado.

Como já visto em Q-learning, durante as iterações, conseguimos obter os valores de Q a partir da seguinte atualização:

O valor de uma ação em um estado é a recompensa mais o fator de desconto vezes o maior Q-valor do estado seguinte.

Utilizaremos para nossa função de custo, esta definição como Q target e tentaremos aproximar o Q previsto do Q target, obtendo:

Q(s,a) é o valor previsto por nossa rede neural.

Note que, para calcularmos o valor de Loss, precisamos do Q valor futuro. Por isso, para cada treino, são necessárias 2 predições. Uma para os Q valores presentes (Q(s,a)) e outra para os futuros (Q(s’,a)).

Huber Loss

No caso da imagem anterior, está sendo implementado o erro quadrático. No entanto, utilizar tanto erro quadrático quanto o erro quadrático médio (MSE) faz com que nosso agente tente corrigir erros grandes de maneira muito acentuada. Como as DQNs utilizam como métrica a própria predição para verificar se sua predição foi correta, não podemos ter correções grandes nos parâmetros dos neurônios pois isto torna o aprendizado muito instável. É como se a rede neural estivesse “perseguindo a própria cauda” já que ao tentar corrigir uma predição errada, ela também está afetando as futuras predições que serão usadas como Q target.

Por isso, adotamos a função de perda de Huber (Huber loss) que se comporta de maneira mais linear conforme o erro aumenta enquanto mantém seu comportamento quadrático quando próximo de 0. Propiciando dessa forma, alterações menos bruscas na rede neural e estabilizando o aprendizado.

Veremos mais a frente que essa situação da rede neural está “perseguindo a própria cauda” tem outras formas de estabilização, mas para nos mantermos mais próximos de Q-learning por hora, vamos apenas mexer na função de custo.

Huber loss
Huber Loss (com δ = 1) em verde e Erro Quadrático Médio em azul. Ambos em função de a

Repetição de Experiências

A ideia de utilizar uma rede neural no lugar de uma tabela para abranger os problemas com espaços contínuos foi importante, no entanto, a ideia que tornou DQN realmente aplicável é o uso de Experience Replay ou, em outras palavras, pegar dados passados de uma memória criada para guardar transições de estado (o conjunto de estado passado, ação tomada, recompensa e novo estado).

Photo by Laura Fuhrman on Unsplash

Para facilitar a implementação da Repetição de Experiências, criamos uma classe da memória que possui um tamanho máximo de experiências armazenadas de tal forma que, quando a memória é lotada, as experiências mais antigas passam a ser deletadas a fim de abrir espaço para novas experiências:

As variáveis internas representam:

  • mem_max: o tamanho máximo de memórias que podem ser guardados.
  • mem_counter: conta quantos elementos foram adicionados até o momento.
  • s: array com os estados das experiências.
  • s2: coraçãozinho…. quer dizer, array com os estados após a tomada da ação
  • r: recompensa da ação.
  • a: ação tomada.
  • terminal: indica se o estado ao qual a ação leva é terminal ou não (estados terminais tem valor 0, vide Processo de Decisão de Markov).

A memória precisa ter as seguintes funcionalidades:

  • Salvar uma experiência;
  • Fornecer uma amostra aleatória de experiências guardadas.

Experience Replay é importante devido ao fato de que redes neurais precisam treinar informações em ordem não correlacionada para obter bons resultados. Sem a memória, as informações que são fornecidas para a rede neural treinar estariam fortemente correlacionadas uma vez que o estado atual tem correlação com os estados passados afetando o aprendizado da rede neural que necessita de amostras independentes e bem distribuídas (De que adianta uma ferramenta de treino que se adapta a várias situações se você está treinando a mesma situação várias vezes seguidas sem variações). Por isso, alternar o treino da rede neural com dados tanto do presente quanto do passado minimiza a correlação e traz resultados melhores além de que faz um uso mais eficiente das experiências passadas já que elas são utilizadas múltiplas vezes para treinar o modelo.

Agente

Apollo 11 Módulo Lunar Eagle

Finalmente, juntaremos todos esses conceitos para criar nosso agente.

Da mesma maneira que em nosso Talks de Q-learning, faremos um agente com a estratégia epsilon-greedy.

Nosso agente deve possuir: ações, epsilons (inicial, decaimento e final), um fator de desconto, uma memória de experiências e uma DQN. Adicionalmente, uma vez que a execução de algoritmos de Aprendizado por Reforço profundo costumam levar bastante tempo, será passado também uma string com um nome de arquivo para implementar persistência de arquivo.

Nosso agente terá os seguintes métodos:

  • salvar experiência: salva a experiência na memória.
  • escolher ação: escolhe uma ação com base no estado, probabilidade epsilon de ser aleatória e 1- epsilon de ser a melhor avaliada.
  • aprender: treina sua DQN em uma amostra aleatória da memória construída até o momento. Se o tamanho da amostra pedido é maior do que o número de experiências coletadas, a função não faz nada. Para treinarmos, geramos um array de Q targets utilizando a recompensa, gamma e maxQ(s’,a) (obtido a partir de s2) na equação de Bellman de Q-learning e tentamos prevê-lo com o estado.
  • save model: salva os parametros da DQN do agente
  • load model: carrega os parametros da DQN do agente.

Treino

Cena do filme Karate Kid

O que nosso agente deve fazer?

Seguindo a política epsilon-greedy, nosso agente deve em ordem:

  • Observar o estado atual;
  • Escolher uma ação segundo a política e o estado;
  • Armazenar o conjunto [Estado anterior(s), ação tomada(a), recompensa recebida(r), Estado atual(s2) , se o estado atual é terminal(done)] na memória.
  • Treinar em uma amostra das experiências armazenadas.
  • Repetir todo o processo.

Ao final, teremos o agente treinado salvo em ‘dqn_save.h5’, a curva de aprendizado do algoritmo e também a curva de decaimento do epsilon.

Conclusão

Agente treinado

Como podemos perceber, o agente obteve resultados aceitáveis, mas a curva de aprendizado possui certa instabilidade ainda. É possível notar que fizemos o epsilon decair muito rapidamente, mas grande parte do aprendizado ocorre enquanto estamos tomando as ações ótimas. Em ambientes mais complexos, o Deep Q-learning da forma implementada não funciona tão bem pois há uma instabilidade muito grande provocada pelas constantes mudanças no agente e, consequentemente, a inconsistência dos valores de Q target.

No entanto felizmente há formas de estabilizar a DQN sendo uma delas com o uso de Fixed Q-targets, tanto ele como as outras formas serão explicadas em um dos próximos Turing Talks.

Então fiquem ligados com os próximos posts no Medium, esperamos que vocês tenham gostado do texto! Se quiserem conhecer um pouco mais sobre o que fazemos no grupo, não deixem de seguir as nossas redes sociais:

Facebook, Instagram, LinkedIn.

--

--