Skip to content

Implementando um agente para lidar com um ambiente um pouco mais complexo

Até este momento, trabalhamos com diversos ambientes que tem uma quantidade razoavelmente pequena de estados e ações discretas. O objetivo deste exercício é mostrar o uso do Q-Learning em um cenário onde é necessário discretizar o espaço de estados.

Para isto, vamos utilizar o ambiente MountainCar-v0 da biblioteca Gymnasium. Neste ambiente temos que aprender a controlar um carro que precisar sair da base de uma montanha e chegar no topo da mesma.

Mountain car environment

As ações do agente são discretas:

  • 0: acelera para a esquerda.
  • 1: não acelera.
  • 2: acelera para a direita.

Dado uma ação, o carro se move segundo esta dinâmica:

\(velocity_{t+1} = velocity_{t} + (action - 1) * force - cos(3 * position_{t}) * gravity\)

\(position_{t+1} = position_{t} + velocity_{t+1}\)

onde \(force = 0.001\) e \(gravity = 0.0025\).

Entendendo o ambiente

Considere o código abaixo:

import gymnasium as gym

env = gym.make('MountainCar-v0')
(state,_) = env.reset()

print('State space: ', env.observation_space)
print('Action space: ', env.action_space)

print(env.observation_space.low)
print(env.observation_space.high)

Ao executar o código acima, você irá ver que a variável state é um vetor com duas posições. O primeiro elemento é a posição do carro e o segundo é a velocidade do carro:

array([-0.46128714,  0.        ], dtype=float32)

Você também verá que o espaço de estados é um espaço contínuo e que as ações são discretas.

O ambiente também tem um limite inferior e superior para o espaço de estados:

[-1.2  -0.07]
[0.6  0.07]

Discretizando o espaço de estados

Uma abordagem possível para treinar um agente usando os algoritmos vistos até agora seria discretizar o espaço de estados e usar uma Q-table de três dimensões: posição do carro, velocidade do carro e ação.

Ao executar o código abaixo, você verá que o espaço de estados é discretizado em 19x15:

num_states = (env.observation_space.high - env.observation_space.low)*np.array([10, 100])
num_states = np.round(num_states, 0).astype(int) + 1

Incluindo as ações então temos uma Q-table de dimensões 19x15x3. A transformação do estado acontece da seguinte forma:

(state,_) = env.reset()
state_adj = (state - env.observation_space.low)*np.array([10, 100])
state_adj = np.round(state_adj, 0).astype(int)

Treinando o agente

Para lidar com este ambiente, mesmo discretizando o espaço de estados, é necessário fazer algumas modificações no código do Q-Learning.

Na inicialização da Q-table

def __init__(self, env, alpha, gamma, epsilon, epsilon_min, epsilon_dec, episodes):

    self.env = env

    # discretizando o espaco de estados
    self.num_states = (env.observation_space.high - env.observation_space.low)*np.array([10, 100])
    self.num_states = np.round(self.num_states, 0).astype(int) + 1

    #inicializando uma q-table com 3 dimensoes: x, velocidade, acao
    self.Q = np.zeros([self.num_states[0], self.num_states[1], env.action_space.n])

Na escolha da ação

def select_action(self, state_adj):
    if np.random.random() < 1 - self.epsilon:
        return np.argmax(self.Q[state_adj[0], state_adj[1]]) 
    return np.random.randint(0, self.env.action_space.n)

Ao manipular o estado

Podemos definir uma função:

def transform_state(self, state):
    state_adj = (state - self.env.observation_space.low)*np.array([10, 100])
    return np.round(state_adj, 0).astype(int)

Para então ser utilizada nas seguintes situações:

(state,_) = self.env.reset()
state_adj = self.transform_state(state)

e

action = self.select_action(state_adj)
next_state, reward, done, truncated, _ = self.env.step(action) 
next_state_adj = self.transform_state(next_state)

A implementação completa desta nova versão do algoritmo Q-Learning está disponível aqui.

Conectando o ambiente com o treinamento do agente

Para treinar o agente e avaliar os resultados o procedimento é o mesmo que foi feito anteriormente em outros casos. Um exemplo de código que faz isto é o seguinte:

import gymnasium as gym
import numpy as np
import matplotlib.pyplot as plt
from QLearningBox import QLearningBox

env = gym.make('MountainCar-v0')

print('State space: ', env.observation_space)
print('Action space: ', env.action_space)

print(env.observation_space.low)
print(env.observation_space.high)

qlearn = QLearningBox(env, alpha, gamma, epsilon, epsilon_min, epsilon_dec, episodes)
qtable = qlearn.train('data/q-table-mountain-car.csv', 'results/rewards_MountainCar-v0')

env = gym.make('MountainCar-v0', render_mode='human')
(state,_) = env.reset()
done = False

while not done:
    env.render()
    state_adj = qlearn.transform_state(state)
    action = np.argmax(qtable[state_adj[0], state_adj[1]])
    state2, reward, done, truncated, _ = env.step(action)
    state = state2

input("enter a key...")
env.close()

Atividades propostas

  • Defina os valores para os hiperparâmetros.

  • Execute o código acima e observe o comportamento do agente.

  • Analise o gráfico gerado na pasta results. O aprendizado do agente converge rapidamente? Fica estável?

  • Depois de treinado, o agente consegue chegar ao topo em todas as vezes? Quantas ações em média são necessárias?

Atividades complementares

  • Esta implementação não tem como usar a função savetxt do numpy para gravar a Q-table porque a Q-table neste caso é 3D. Implemente uma função que permite armazenar e ler uma Q-table 3D.

  • Faça uma análise do aprendizado do agente mais robusta. Execute o treinamento do agente diversas vezes (por exemplo, 100 vezes) e analise a variabilidade do aprendizado. Faça um gráfico com a mesma estrutura que os gráficos apresentandos na aula sobre avaliação.

  • Teste diferentes hiperparâmetros e analise o impacto no aprendizado do agente. Faça um gráfico com várias curvas de aprendizado.


Last update: March 13, 2024