Se você sempre sonhou em criar um piloto de corrida virtual que detona nas pistas do “CarRacing-v3” do Gymnasium, mas se sentiu como um carro atolado na lama, este post é para você! Domine o Car Racing do Gymnasium hoje mesmo.
Vamos juntos, passo a passo, transformar esse desafio em uma experiência super divertida e recompensadora. Prepare-se para acelerar seu conhecimento em Inteligência Artificial e Reinforcement Learning!
O que é o car racing do Gymnasium?
Imagine um videogame onde você precisa dirigir um carro em uma pista gerada aleatoriamente. O objetivo? Completar a volta o mais rápido possível, sem sair da pista (ou pelo menos, não tanto a ponto de ser desclassificado!).
O Gymnasium (antigo OpenAI Gym) é uma biblioteca do Python que oferece diversos ambientes para testar algoritmos de Inteligência Artificial. O “CarRacing-v3” é um desses ambientes, e é um desafio clássico no mundo do Reinforcement Learning.
Por que o car racing é tão legal (e desafiador)?
- Controle Contínuo vs. Discreto: No nosso caso, vamos usar o modo discreto, onde o carro tem um número limitado de ações (acelerar, frear, virar à esquerda, virar à direita, etc.). Isso simplifica um pouco o problema, mas ainda é um baita desafio!
- Recompensas: O carro recebe recompensas por andar na pista e penalidades por sair dela. O objetivo do algoritmo é aprender a maximizar essas recompensas.
- Visão: O algoritmo “vê” o mundo através de uma imagem da tela do jogo. Ele precisa aprender a interpretar essa imagem para tomar as melhores decisões.
Preparando o terreno: Criando o wrapper do ambiente do car racing
Aqui, vamos dar uma “turbinada” no ambiente original para facilitar o aprendizado do nosso piloto virtual. A ideia é criar uma “zona de segurança” ao redor da pista, para que o carro não fique andando sem rumo pelo mapa. Para isso, criaremos um wrapper no ambiente original e então vamos introduzir as modificações necessárias.
Instalando as bibliotecas
Primeiro, precisamos as bibliotecas necessárias e de uma biblioteca chamada shapely para trabalhar com geometria. Abra seu terminal e mande bala:
pip install numpy tensorflow shapely "gymnasium[all]"
Ajustando o ambiente
Crie um arquivo chamado safedriving_wrapper.py
, e adicione o seguinte código:
import gymnasium as gym
from shapely import affinity
from shapely.geometry import Point, Polygon
class SafeDrivingWrapper(gym.Wrapper):
"""
A wrapper to create a 'safe zone' around the track and penalize the agent
for going too far off-road.
"""
def __init__(self, env, border_width=0.5):
super(SafeDrivingWrapper, self).__init__(env)
self.border_width = border_width
def car_on_track(self):
car_on_track = False
x, y = self.unwrapped.car.hull.position
point = Point(x, y)
for poly in self.unwrapped.road_poly:
polygon = Polygon(poly[0])
# Create a larger polygon representing the track + safe border
if self.border_width > 0:
border_scale = 1 + self.border_width
polygon = affinity.scale(polygon, xfact=border_scale, yfact=border_scale)
if polygon.contains(point):
car_on_track = True
break
return car_on_track
def step(self, action):
next_state, reward, terminated, truncated, info = self.env.step(action)
# Apply a heavy penalty if the car is outside the safe zone
if not self.car_on_track():
reward -= 100
terminated = True # End the episode immediately
return next_state, reward, terminated, truncated, info
Este wrapper é responsável por criar uma espécie de cerca de segurança ao redor da pista. Desta forma, se o carro se afastar demais, podemos reiniciar a simulação, e agilizar o aprendizado.
Construindo o piloto de IA: O algoritmo DQN
Agora que o ambiente está preparado, vamos construir nosso piloto virtual usando o algoritmo DQN (Deep Q-Network).
O Que é o DQN?
O DQN é um algoritmo de Reinforcement Learning que usa uma rede neural para aprender a melhor ação a ser tomada em cada situação. Ele funciona da seguinte forma:
- Observa o estado: O algoritmo recebe uma imagem da tela do jogo (o estado).
- Prevê os valores Q: A rede neural prevê os valores Q para cada ação possível. O valor Q representa a recompensa esperada ao tomar essa ação no estado atual.
- Escolhe a ação: O algoritmo escolhe a ação com o maior valor Q previsto (com uma pitada de aleatoriedade para explorar novas possibilidades).
- Recebe a recompensa: O algoritmo executa a ação no ambiente e recebe uma recompensa.
- Atualiza a rede neural: O algoritmo usa a recompensa recebida para ajustar os pesos da rede neural, de forma a prever valores Q mais precisos no futuro.
Mão na massa: código passo a passo
import random
from collections import deque
import gymnasium as gym
import numpy as np
import tensorflow as tf
from safedriving_wrapper import SafeDrivingWrapper
from tensorflow.keras import layers
# Configuração do ambiente
env_raw = gym.make("CarRacing-v3", continuous=False, render_mode="human")
env = SafeDrivingWrapper(env_raw)
# Hiperparâmetros
STATE_SHAPE = (96, 96, 1)
ACTION_SIZE = env.action_space.n
MEMORY_SIZE = 10000
BATCH_SIZE = 32
GAMMA = 0.99
ALPHA = 0.00025
EPSILON = 1.0
EPSILON_DECAY = 0.99999
EPSILON_MIN = 0.01
TARGET_UPDATE = 25
WEIGHTS_FILE = "car_racing.weights.h5"
SAVE_WEIGHTS = False
LOAD_EXISTING_WEIGHTS = True
SAVE_WEIGHTS_INTERVAL = 100
- Importando as bibliotecas: Aqui, importamos as bibliotecas necessárias para o nosso algoritmo, como gymnasium para o ambiente, numpy para manipulação de arrays, tensorflow e keras para construir a rede neural.
- Configurando o ambiente: Criamos o ambiente “CarRacing-v3” com o modo discreto e renderização visual. Em seguinda, instanciamos o SafeDrivingWrapper utilizando o ambiente criado.
- Hiperparâmetros: Essa é a “receita” do nosso algoritmo. Vamos detalhar cada um deles:
- STATE_SHAPE: O formato da imagem que o algoritmo recebe (96×96 pixels em escala de cinza).
- ACTION_SIZE: O número de ações possíveis (quantas opções de controle o carro tem).
- MEMORY_SIZE: O tamanho da memória do algoritmo, onde ele armazena as experiências passadas.
- BATCH_SIZE: O número de experiências usadas para atualizar a rede neural a cada passo.
- GAMMA: O fator de desconto, que determina a importância das recompensas futuras.
- ALPHA: A taxa de aprendizado, que controla a velocidade com que a rede neural se ajusta.
- EPSILON: A taxa de exploração, que determina a probabilidade de o algoritmo escolher uma ação aleatória em vez da melhor ação prevista. Isso é importante para o algoritmo explorar novas possibilidades e não ficar preso em soluções subótimas.
- EPSILON_DECAY: A taxa de decaimento do épsilon, que diminui a taxa de exploração ao longo do tempo.
- EPSILON_MIN: O valor mínimo do épsilon.
- TARGET_UPDATE: A frequência com que o modelo alvo é atualizado.
- WEIGHTS_FILE: O nome do arquivo onde os pesos da rede neural são salvos.
- SAVE_WEIGHTS: Se os pesos devem ser salvos.
- LOAD_EXISTING_WEIGHTS: Se os pesos pré-treinados devem ser carregados.
- SAVE_WEIGHTS_INTERVAL: A frequência com que os pesos são salvos.
class DQNAgent:
def __init__(self):
self.memory = deque(maxlen=MEMORY_SIZE)
self.epsilon = EPSILON
self.model = self._build_model()
self.target_model = self._build_model()
self.update_target_model()
self.optimizer = tf.keras.optimizers.Adam(learning_rate=ALPHA)
def _build_model(self):
"""Builds the CNN model."""
model = tf.keras.Sequential([
layers.Input(STATE_SHAPE),
layers.Conv2D(32, (8, 8), strides=4, activation='relu'),
layers.Conv2D(64, (4, 4), strides=2, activation='relu'),
layers.Conv2D(64, (3, 3), strides=1, activation='relu'),
layers.Flatten(),
layers.Dense(512, activation='relu'),
layers.Dense(ACTION_SIZE, activation='linear') # Q-values
])
return model
def update_target_model(self):
"""Copies weights from the main model to the target model."""
self.target_model.set_weights(self.model.get_weights())
def act(self, state):
"""Chooses an action using an epsilon-greedy policy."""
if np.random.rand() <= self.epsilon:
return random.randrange(ACTION_SIZE)
q_values = self.predict(self.model, tf.expand_dims(state, axis=0))
return np.argmax(q_values[0].numpy())
def remember(self, state, action, reward, next_state, done):
"""Stores an experience in the replay buffer."""
self.memory.append((state, action, reward, next_state, done))
def replay(self):
"""Trains the model using a random batch of experiences from memory."""
if len(self.memory) < BATCH_SIZE:
return
batch = random.sample(self.memory, BATCH_SIZE)
states, actions, rewards, next_states, dones = zip(*batch)
states = np.array(states)
next_states = np.array(next_states)
# Predict Q-values for current states and next states
q_values_current = self.predict(self.model, states).numpy()
q_values_next = self.predict(self.target_model, next_states).numpy()
for i in range(BATCH_SIZE):
if dones[i]:
q_values_current[i, actions[i]] = rewards[i]
else:
q_values_current[i, actions[i]] = rewards[i] + GAMMA * np.amax(q_values_next[i])
self.train_step(states, q_values_current)
# Decay epsilon
if self.epsilon > EPSILON_MIN:
self.epsilon *= EPSILON_DECAY
def save_weights(self):
if SAVE_WEIGHTS:
self.model.save_weights(WEIGHTS_FILE)
def load_weights(self):
if LOAD_EXISTING_WEIGHTS:
try:
self.model.load_weights(WEIGHTS_FILE)
self.update_target_model()
self.epsilon = EPSILON_MIN
print("Pre-trained weights found. Resuming training...")
except FileNotFoundError:
print("No pre-trained weights found. Starting from scratch...")
@tf.function
def predict(self, model, states):
return model(states, training=False)
@tf.function
def train_step(self, states, targets):
with tf.GradientTape() as tape:
q_values = self.model(states, training=True)
loss = tf.keras.losses.MeanSquaredError()(targets, q_values)
grads = tape.gradient(loss, self.model.trainable_variables)
self.optimizer.apply_gradients(zip(grads, self.model.trainable_variables))
- DQNAgent: Essa classe representa o nosso piloto virtual. Ela contém a rede neural, a memória e os métodos para escolher ações, armazenar experiências e treinar a rede.
- __init__: O construtor da classe. Ele inicializa a memória, a taxa de exploração (epsilon), o modelo principal, o modelo alvo e o otimizador.
- build_model: Essa função cria a rede neural. A rede é composta por camadas convolucionais, que são ótimas para processar imagens, e camadas densas, que são usadas para fazer a previsão final dos valores Q.
- update_target_model: Essa função copia os pesos do modelo principal para o modelo alvo. O modelo alvo é usado para calcular os valores Q de destino durante o treinamento. Isso ajuda a estabilizar o treinamento e evitar que o algoritmo fique instável.
- act: Essa função escolhe uma ação com base na política epsilon-greedy. Com probabilidade epsilon, a função escolhe uma ação aleatória. Caso contrário, ela escolhe a ação com o maior valor Q previsto pelo modelo.
- remember: Essa função armazena a experiência na memória. A experiência é composta pelo estado atual, a ação escolhida, a recompensa recebida, o próximo estado e se o episódio terminou.
- replay: Essa função treina o modelo com amostras da memória. A função seleciona aleatoriamente um lote de experiências da memória, calcula os valores Q de destino e atualiza os pesos do modelo para que ele preveja valores Q mais precisos no futuro.
- save_weights: Essa função salva os pesos do modelo em um arquivo.
- load_weights: Essa função carrega os pesos do modelo de um arquivo.
- predict: Essa função faz a previsão dos valores Q para múltiplos estados.
- train_step: Essa função realiza um passo de treinamento otimizado com @tf.function.
def preprocess_state(state):
"""Converts the image to grayscale, resizes, and normalizes it."""
# [Nota do revisor: A conversão para TF tensor é mais eficiente]
state_tensor = tf.convert_to_tensor(state)
state_tensor = tf.image.rgb_to_grayscale(state_tensor)
return tf.cast(state_tensor, tf.float32) / 255.0
def train_agent(episodes=1000):
agent = DQNAgent()
agent.load_weights()
for episode in range(episodes):
state, _ = env.reset()
state = preprocess_state(state)
total_reward = 0
done = False
while not done:
action = agent.act(state)
next_state, reward, done, _, _ = env.step(action)
next_state = preprocess_state(next_state)
# Custom Reward Shaping
car_on_track = env.car_on_track()
if car_on_track:
speed = np.linalg.norm(env.unwrapped.car.hull.linearVelocity)
# Reward for high speed, penalize for low speed
speed_bonus = max(0, speed * 0.1)
low_speed_penalty = -1 if speed < 1.0 else 0
reward += speed_bonus + low_speed_penalty
else:
# This penalty is now handled by the wrapper, but we can add more
reward -= 2
agent.remember(state, action, reward, next_state, done)
state = next_state
total_reward += reward
agent.replay()
if episode % TARGET_UPDATE == 0:
agent.update_target_model()
if episode > 0 and episode % SAVE_WEIGHTS_INTERVAL == 0:
agent.save_weights()
print(f"Episode {episode+1}, Total Reward: {total_reward:.2f}, Epsilon: {agent.epsilon:.4f}")
# Let's train!
train_agent()
- train_agent: Essa função é responsável por treinar o nosso piloto virtual. Ela cria um objeto DQNAgent, carrega os pesos pré-treinados (se existirem) e executa o treinamento por um número determinado de episódios.
- Para cada episódio, a função reseta o ambiente, preprocessa o estado inicial e entra em um loop que executa até o episódio terminar.
- Dentro do loop, a função escolhe uma ação com base na política épsilon-greedy, executa a ação no ambiente, preprocessa o próximo estado, calcula a recompensa e armazena a experiência na memória.
- A função também atualiza os pesos do modelo a cada passo, usando a função replay.
- A cada TARGET_UPDATE episódios, a função atualiza o modelo alvo.
- A cada SAVE_WEIGHTS_INTERVAL episódios, a função salva os pesos do modelo em um arquivo.
- No final de cada episódio, a função imprime informações sobre o progresso do treinamento, como o número do episódio, a recompensa total e a taxa de exploração.
Para uma aprendizado mais rápido, eu modifiquei a recompensa do ambiente padrão do Gym para favorecer a velocidade do veículo se ele estiver na pista (usando o método car_on_track criado antes), e aplicar uma penalidade caso o veículo não esteja na pista.
Desta forma, o algoritmo aprende que é melhor andar na pista e ir mais rápido, aumentando a pontuação.
Execute o código e acompanhe o aprendizado do algoritmo em tempo real. Nos meus testes, por volta da época 500 já é possível notar que o carro busca permanecer na pista, e corre um pouco mais rápido. No repositódio do Github, você pode encontrar o código fonte completo e o arquivo com os pesos da rede pré treinada caso não queira passar por toda a etapa de treinamento novamente.
A rede neural
A rede neural é o cérebro do nosso piloto virtual. Ela recebe a imagem da tela do jogo como entrada e produz os valores Q para cada ação possível como saída.
A rede é composta por:
- Camadas Convolucionais: Essas camadas são responsáveis por extrair características importantes da imagem, como as bordas da pista e a posição do carro.
- Camadas Densas: Essas camadas são responsáveis por combinar as características extraídas pelas camadas convolucionais e fazer a previsão final dos valores Q.
O processo de treinamento
O processo de treinamento é onde o nosso piloto virtual aprende a dirigir. Ele funciona da seguinte forma:
- Exploração: No início do treinamento, o piloto explora o ambiente, experimentando diferentes ações e aprendendo o que funciona e o que não funciona.
- Exploitation: À medida que o treinamento avança, o piloto começa a explorar o ambiente com mais inteligência, escolhendo ações que maximizem a recompensa esperada.
- Convergência: Eventualmente, o piloto converge para uma política ótima, onde ele sabe exatamente qual ação tomar em cada situação para maximizar a recompensa.
Dicas extras para turbinar seu piloto
- Ajuste os Hiperparâmetros: Experimente diferentes valores para os hiperparâmetros para ver o que funciona melhor para o seu caso.
- Recompensa: Ajuste a função de recompensa para incentivar o comportamento desejado.
- Arquitetura da Rede: Experimente diferentes arquiteturas de rede neural.
- Aumente o Tempo de Treinamento: Quanto mais tempo você treinar o seu piloto, melhor ele vai ficar.
Conclusão
E aí, curtiu o guia completo para dominar o Car Racing do Gymnasium com Inteligência Artificial?
Agora é a sua vez de colocar a mão na massa e criar o seu próprio piloto virtual!
Compartilhe este post com seus amigos que também curtem programação e Inteligência Artificial. E não se esqueça de deixar um comentário aqui embaixo com suas dúvidas, sugestões e resultados!
🚀 Vamos juntos acelerar o aprendizado e dominar o mundo da Inteligência Artificial! 🏆
2 comentários em “Car Racing com Inteligência Artificial: Um guia completo para iniciantes (e não tão iniciantes) 🚗💨”