Aprendizado por Reforço #5 — Programação Dinâmica

Crie um agente que aprende sozinho a navegar um ambiente.

Stephanie Miho Urashima
Turing Talks
7 min readMar 29, 2020

--

Escrito por: Stephanie Urashima e Bernardo Coutinho.

Na edição dessa semana do Turing Talks, vamos dar continuidade à nossa série de Aprendizado por Reforço! Se você ainda não leu os textos anteriores dessa série, deixamos aqui o link para caso você tenha interesse. Caso contrário, vamos direto ao assunto!

Nessa publicação, abordaremos o conceito de Programação Dinâmica, trataremos de suas aplicações no Aprendizado por Reforço e, por fim, apresentaremos como criar seu próprio agente que aprenda sozinho a navegar um ambiente.

O que é Programação Dinâmica?

A Programação Dinâmica é um método muito útil de se resolver problemas de computação, que consiste em pegar um problema complexo cuja solução é difícil de ser calculada diretamente, e dividir sua resolução em diversos subp’roblemas mais fáceis de serem resolvidos. Seu uso objetiva diminuir o custo computacional do programa, bem como simplificá-lo.

Contudo, esse método só pode ser aplicado quando nosso problema atende a dois requisitos:

  • Subestrutura Ótima — Um problema atende a esse requisito quando sua solução ideal depende das soluções ideias de subproblemas mais simples.
  • Superposição de Subproblemas — Um problema segue essa propriedade se sua resolução requer a re-examinação dos mesmos subproblemas diversas vezes. A solução desses subproblemas podem (e devem) ser guardadas para uso futuro.

Esse método é chamado dinâmico pois esses requisitos pressupõem que o problema possui um aspecto sequencial/temporal, já que a solução de cada subproblema depende de um outro anterior.

Mas como aplicamos a Programação Dinâmica em um problema?

Exemplo de Programação Dinâmica

Vamos supor que nosso problema é calcular o n-ésimo número da Sequência de Fibonacci. Essa sequência é caracterizada da seguinte forma: seus dois primeiros valores são iguais a 1, e cada número seguinte é obtido calculando a soma dos dois anteriores. Logo, o início da sequência seria o seguinte:

1 1 2 3 5 8 13 21

Uma maneira bem simples de resolver esse problema é utilizando uma abordagem recursiva: cada vez que quisermos encontrar o n-ésimo termo da sequência, nós chamamos a mesma função para descobrir os dois anteriores para depois somá-los, da seguinte forma:

Fibonacci com Recursão

Entretanto, essa é uma abordagem pouco eficaz, já que você está chamando a função várias vezes para um mesmo número da sequência. Por exemplo, quando calculamos o Fibonacci(5), nós chamamos as funções Fibonacci(4) e Fibonacci(3). Mas dentro do Fibonacci(4), estamos novamente chamando o Fibonacci(3), mesmo já tendo calculado ele.

Dessa forma, o que podemos fazer é aplicar a Programação Dinâmica na Sequência de Fibonacci, já que é um problema que atende à Subestrutura Ótima, um termo depende de outros termos menores, e atende à Superposição de Problemas, um termo é usado diversas vezes para calcular os termos seguintes.

Assim, podemos guardar os termos da sequência em um vetor f, sem precisar calculá-los várias vezes, como no código a seguir:

Fibonacci com Programação Dinâmica

Programação Dinâmica em Aprendizado por Reforço

Quando buscamos resolver um problema de Aprendizado por Reforço, nós estamos tentando calcular a Função Valor e a Política ótimas para o nosso ambiente. E, como vimos no último post, calcular a Função Valor envolve resolver a Equação de Esperança de Bellman:

Equação de Esperança de Bellman

Só que as Equações de Bellman atendem os requisitos da programação dinâmica, uma vez que o valor ótimo V(s) de um estado supõe que o valor dos estados seguintes V(s’) seja ótimo (Subestrutura Ótima), e cada vez que calculamos um V(s), nós precisamos calcular os valores de todos os estados seguintes (Superposição de Subproblemas).

Dessa forma, nós aplicamos Programação Dinâmica nas Equações de Bellman e guardamos todos os valores em uma tabela V[s], que atualizaremos ao longo do tempo até descobrir o valor ótimo de cada estado!

Policy Evaluation e Policy Improvement

A partir de Bellman derivam-se as seguintes formulas de atualização da função valor a partir de uma politica:

E com isso definimos 2 ações possíveis:

  • Iterative Policy Evaluation: O ato de encontrar a Função Valor (V) de uma política já existente. Uma forma de encontrá-la é aplicar a equação de Bellman.
  • Policy Improvement: É o processo de criação de uma nova política que procura com base na função valor de uma política passada maximizar os ganhos imediatos, ou seja, a nova política é uma política greedy em relação a antiga política, garantindo assim que a política nova sempre será melhor ou igual a antiga.

Atenção: Só é possível garantir a existência e a unicidade da função valor caso o processo tenha um fim (seja episódico) ou o gamma seja menor que 1 (constante que define a importância das recompensas do futuro, ou seja, mesmo que o processo seja infinito o conjunto de ações futuras relevantes são finitas).

Com essas duas ações é possível encontrar a politica ótima no processo chamado de Generalized Policy Iteration.

Generalized Policy Iteration

Generalized Policy Iteration (GPI) é o termo dado à ideia geral de intercalar processos de Policy Evaluation com processos de Policy Improvement (a ordem e a quantidade de cada um pode variar).

Uma parte dos métodos de Aprendizado por Reforço podem ser classificados como GPIs, isto é, têm politicas e funções de valor identificáveis, com uma politica sempre sendo atualizada de acordo com a função valor enquanto esta é atualizada para uma nova politica. Um esquema simples seria o seguinte:

A seguir, vamos explicar alguns exemplos específicos de GPI: Policy Iteration e Value Iteration. Para deixar as aplicações mais ilustrativas iremos fazer elas para resolver o ambiente Frozen Lake, do Gym:

Nesse ambiente, o nosso agente deve percorrer um lago congelado com vários buracos até chegar em um objetivo. Se ele chegar no espaço final, ele recebe uma recompensa de +1, mas se ele cair em um buraco, ele perde aquela rodada.

  • Policy Iteration

É a forma mais simples de se aplicar os dois processos discutidos previamente, a ideia é intercalar as duas ações até a política se estabilizar. Abaixo há o código sobre o passo a passo desta técnica para o Frozen Lake:

Obs: Essa técnica sempre funcionará em um MDP finito, uma vez que como o próprio nome diz o processo tem ações finitas e estados finitos e portanto há um numero finito de políticas, possibilitando assim encontrar a política ótima em um numero finito de iterações.

  • Value Iteration

Ao ler o código acima provavelmente deve ter notado que Policy Evaluation é um processo que pode, dependendo das circunstâncias, exigir muito poder computacional se feita iterativamente, uma vez que só é possível garantir a convergência da função valor no infinito.

Portanto, em Value Iteration, em vez de fazer Policy Evaluation até que a função valor estabilize, atualiza-se somente uma vez a função valor com o valor da ação de maior retorno.

Para implementar isso foi utilizado a variável valor_ação que guarda os valores de cada ação possível. E no fim a função_valor só recebe o maior valor guardado em valor_ação.

Depois de rodar o Value Iteration, nós obtemos a função de valor V para cada estado do Frozen Lake!

Valores de cada estado do Frozen Lake

Obs: A função valor é zero para o estado final, pois o ambiente do Gym dá a recompensa na transição entre os estados, por isso que somente o estado anterior a chegar no objetivo tem valor 1.

Agora que obtivemos a Função Valor ótima, nosso agente conseguirá agir de forma a sempre chegar no objetivo, conforme o gif abaixo!

Todo o código que utilizamos nesse post pode ser encontrado aqui!

Conclusão

A Programação Dinâmica é uma ferramenta essencial para resolver diversos problemas computacionais, e agora você entende como esse conceito se relaciona com a área de Aprendizado por Reforço.

Talvez você tenha percebido que para utilizar essa abordagem em problemas de RL é necessário conhecer todas informações sobre o ambiente, as quais nem sempre estarão disponíveis em outras situações, são elas: dinâmica de transição entre os estados, recompensas e/ou ações. O que deixa a aplicação desse conceito limitado a pouquíssimos problemas.

Felizmente, existem alternativas aos problemas de falta de informações, as quais serão abordadas em futuros posts aqui no Turing Talks =).

Se você estiver mesmo interessado em saber mais sobre, além de continuar nos acompanhando aqui, não se esqueçam de conferir nossas redes: Medium, Facebook, Instagram e LinkedIn e fique ligado nas postagens.

Ficamos por aqui,

Até a próxima!

--

--