Aprendizado por reforço: recompensa e retorno
No aprendizado por reforço, a recompensa é um sinal imediato. Ela indica se uma ação foi boa ou ruim. Por exemplo, ganhar um ponto em um jogo. O retorno, por outro lado, é a soma total das recompensas futuras. Ele considera não apenas o agora, mas também o amanhã. Portanto, o agente busca maximizar o retorno ao longo do tempo.
Definição formal de recompensa e retorno
A cada passo t, o agente recebe uma recompensa r_t. Essa recompensa é um número real. Ela pode ser positiva, negativa ou zero. O retorno G_t é definido como a soma das recompensas futuras. Frequentemente, usamos um fator de desconto γ. Assim, o retorno é escrito como \( G_t = r_{t+1} + \gamma r_{t+2} + \gamma^2 r_{t+3} + \dots = \sum_{k=0}^{\infty} \gamma^k r_{t+k+1} \). Esse fator γ está entre 0 e 1. Ele dá menos peso a recompensas distantes. Consequentemente, o agente prefere recompensas imediatas.
Uma escolha comum é γ = 0.9 ou 0.99. Valores altos incentivam planejamento de longo prazo. Valores baixos tornam o agente míope. O retorno também pode ser definido sem desconto. Nesse caso, γ = 1, mas isso exige episódios finitos. A recompensa é projetada pelo desenvolvedor. Ela guia o comportamento desejado. Por exemplo, em um jogo de xadrez, dar +1 por vitória e 0 por derrota.
Hiperparâmetros relacionados e arquitetura
Além de γ, a taxa de aprendizado α é crucial. Ela controla quão rápido o agente atualiza suas estimativas. A taxa de exploração ε também impacta o retorno. Explorar pode levar a descobrir recompensas maiores no futuro. Portanto, há um trade-off entre exploração e explotação. Redes neurais profundas aproximam a função de valor. Elas estimam o retorno esperado para cada estado ou ação. Essa arquitetura chama-se DQN (Deep Q-Network). Nela, a perda é calculada com base no erro do retorno previsto.
A equação de Bellman conecta retorno e recompensa. Para a função valor V(s), temos \( V(s) = \mathbb{E}[r + \gamma V(s’)] \). Para a função ação-valor Q(s,a), escrevemos \( Q(s,a) = \mathbb{E}[r + \gamma \max_{a’} Q(s’, a’)] \). O erro temporal (TD error) é a diferença entre o retorno estimado e o atual: \( \delta = r + \gamma \max_{a’} Q(s’, a’) – Q(s,a) \). Esse erro é usado para atualizar os parâmetros do modelo.
Exemplo clássico: o problema do bandido (k-armed bandit)
Suponha uma máquina caça-níqueis com 5 braços. Cada braço dá uma recompensa média diferente. O agente não conhece essas médias. Ele deve descobrir qual braço maximiza o retorno. Cada tentativa (ação) produz uma recompensa imediata. O objetivo é maximizar o retorno total após 1000 jogadas. O código abaixo resolve isso com uma estratégia ε-greedy. Ele também mostra gráficos da evolução do retorno médio.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
import numpy as np import matplotlib.pyplot as plt # Configuração do problema: 5 braços num_braços = 5 recompensas_reais = np.random.normal(0, 1, num_braços) print(f"Recompensas reais de cada braço: {recompensas_reais}") # Hiperparâmetros epsilon = 0.1 # taxa de exploração alpha = 0.1 # taxa de aprendizado (para média incremental) episodios = 1000 # Inicialização Q = np.zeros(num_braços) # estimativa do retorno para cada braço N = np.zeros(num_braços) # número de vezes que cada braço foi puxado recompensas_por_passos = [] acao_escolhida = [] def escolher_acao(epsilon, Q): if np.random.rand() < epsilon: return np.random.randint(len(Q)) # explora return np.argmax(Q) # explota # Loop principal for passo in range(episodios): acao = escolher_acao(epsilon, Q) # Gera recompensa com distribuição normal + ruído recompensa = np.random.normal(recompensas_reais[acao], 1.0) N[acao] += 1 # Atualização incremental da média (retorno estimado) Q[acao] = Q[acao] + alpha * (recompensa - Q[acao]) recompensas_por_passos.append(recompensa) acao_escolhida.append(acao) # Cálculo do retorno acumulado ao longo do tempo retorno_acumulado = np.cumsum(recompensas_por_passos) retorno_medio_por_passo = retorno_acumulado / (np.arange(episodios) + 1) # Gráfico 1: Evolução do retorno médio por passo plt.figure(figsize=(12,4)) plt.subplot(1,2,1) plt.plot(retorno_medio_por_passo) plt.xlabel('Passo') plt.ylabel('Retorno médio acumulado') plt.title('Aprendizado do retorno esperado') plt.grid(True) plt.axhline(y=max(recompensas_reais), color='r', linestyle='--', label=f'Ótimo teórico = {max(recompensas_reais):.2f}') plt.legend() # Gráfico 2: Porcentagem de vezes que o melhor braço foi escolhido melhor_braco = np.argmax(recompensas_reais) escolheu_melhor = [1 if acao == melhor_braco else 0 for acao in acao_escolhida] media_movel = np.convolve(escolheu_melhor, np.ones(50)/50, mode='valid') plt.subplot(1,2,2) plt.plot(media_movel) plt.xlabel('Passo') plt.ylabel('Frequência do melhor braço') plt.title('Exploração vs. Explotação (janela 50)') plt.ylim(0, 1) plt.grid(True) plt.tight_layout() plt.show() print(f"Após {episodios} passos, as estimativas Q são: {Q}") print(f"O melhor braço real é o {melhor_braco} com recompensa {recompensas_reais[melhor_braco]:.2f}") print(f"O agente escolheu o melhor braço em {np.mean(escolheu_melhor)*100:.1f}% das vezes") |
O código simula um problema clássico de bandido. O agente aprende a estimar o retorno de cada ação. Com ε-greedy, ele explora braços desconhecidos. Gradualmente, o retorno médio se aproxima do ótimo. O segundo gráfico mostra a taxa de escolha do melhor braço. Esse exemplo ilustra perfeitamente os conceitos de recompensa e retorno.