Escolar Flappy Bird: Um Tutorial de Aprendizagem por Reforço
Publicados: 2022-03-11Na programação clássica, as instruções de software são feitas explicitamente pelos programadores e nada é aprendido com os dados. Em contraste, o aprendizado de máquina é um campo da ciência da computação que usa métodos estatísticos para permitir que os computadores aprendam e extraiam conhecimento dos dados sem serem explicitamente programados.
Neste tutorial de aprendizado por reforço, mostrarei como podemos usar o PyTorch para ensinar uma rede neural de aprendizado por reforço a jogar Flappy Bird. Mas primeiro, precisaremos cobrir vários blocos de construção.
Algoritmos de aprendizado de máquina podem ser divididos em duas partes: algoritmos de aprendizado tradicionais e algoritmos de aprendizado profundo. Os algoritmos de aprendizado tradicionais geralmente têm muito menos parâmetros de aprendizado do que os algoritmos de aprendizado profundo e têm muito menos capacidade de aprendizado.
Além disso, os algoritmos de aprendizado tradicionais não são capazes de fazer extração de recursos : especialistas em inteligência artificial precisam descobrir uma boa representação de dados que é então enviada ao algoritmo de aprendizado. Exemplos de técnicas tradicionais de aprendizado de máquina incluem SVM, floresta aleatória, árvore de decisão e $k$-means, enquanto o algoritmo central no aprendizado profundo é a rede neural profunda .
A entrada para uma rede neural profunda pode ser imagens brutas, e um especialista em inteligência artificial não precisa encontrar nenhuma representação de dados — a rede neural encontra a melhor representação durante o processo de treinamento.
Muitas técnicas de aprendizado profundo são conhecidas há muito tempo, mas os avanços recentes em hardware impulsionaram rapidamente a pesquisa e o desenvolvimento de aprendizado profundo. A Nvidia é responsável pela expansão do campo porque suas GPUs permitiram experimentos rápidos de aprendizado profundo.
Parâmetros e hiperparâmetros que podem ser aprendidos
Os algoritmos de aprendizado de máquina consistem em parâmetros aprendíveis que são ajustados no processo de treinamento e parâmetros não aprendíveis que são definidos antes do processo de treinamento. Os parâmetros definidos antes do aprendizado são chamados de hiperparâmetros .
A pesquisa em grade é um método comum para encontrar os hiperparâmetros ideais. É um método de força bruta: significa tentar todas as combinações possíveis de hiperparâmetros em um intervalo definido e escolher a combinação que maximiza uma métrica predefinida.
Algoritmos de Aprendizado Supervisionado, Não Supervisionado e por Reforço
Uma maneira de classificar algoritmos de aprendizado é traçar uma linha entre algoritmos supervisionados e não supervisionados. (Mas isso não é necessariamente tão simples: o aprendizado por reforço está em algum lugar entre esses dois tipos.)
Quando falamos de aprendizagem supervisionada, olhamos para $ (x_i, y_i) $ pares. $ x_i $ é a entrada do algoritmo e $ y_i $ é a saída. Nossa tarefa é encontrar uma função que faça o mapeamento correto de $ x_i $ para $ y_i $.
Para ajustar os parâmetros que podem ser aprendidos para que eles definam uma função que mapeie $ x_i $ para $ y_i $, uma função de perda e um otimizador precisam ser definidos. Um otimizador minimiza a função de perda. Um exemplo de uma função de perda é o erro quadrático médio (MSE):
\[MSE = \sum_{i=1}^{n} (y_i - \widehat{y_i})^2\]Aqui, $ y_i $ é um rótulo de verdade e $ \widehat{y_i} $ é um rótulo previsto. Um otimizador que é muito popular em aprendizado profundo é a descida de gradiente estocástica . Existem muitas variações que tentam melhorar o método estocástico de descida de gradiente: Adam, Adadelta, Adagrad e assim por diante.
Algoritmos não supervisionados tentam encontrar estrutura nos dados sem serem explicitamente fornecidos com rótulos. $k$-means é um dos exemplos de algoritmos não supervisionados que tentam encontrar clusters ótimos nos dados. Abaixo está uma imagem com 300 pontos de dados. Os algoritmos $k$-means encontraram a estrutura nos dados e atribuíram um rótulo de cluster a cada ponto de dados. Cada cluster tem sua própria cor.
O aprendizado por reforço usa recompensas: rótulos esparsos e demorados. Um agente toma uma ação, que muda o ambiente, a partir do qual pode obter uma nova observação e recompensa. Uma observação é o estímulo que um agente percebe do ambiente. Pode ser o que o agente vê, ouve, cheira e assim por diante.
Uma recompensa é dada ao agente quando ele realiza uma ação. Diz ao agente quão boa é a ação. Ao perceber observações e recompensas, um agente aprende como se comportar de maneira otimizada no ambiente. Vou entrar nisso com mais detalhes abaixo.
Aprendizado por Reforço Ativo, Passivo e Inverso
Existem algumas abordagens diferentes para esta técnica. Em primeiro lugar, há o aprendizado por reforço ativo, que estamos usando aqui. Em contraste, há o aprendizado por reforço passivo, onde as recompensas são meramente outro tipo de observação, e as decisões são tomadas de acordo com uma política fixa.
Finalmente, o aprendizado por reforço inverso tenta reconstruir uma função de recompensa dada a história de ações e suas recompensas em vários estados.
Generalização, Overfitting e Underfitting
Qualquer instância fixa de parâmetros e hiperparâmetros é chamada de modelo. Os experimentos de aprendizado de máquina geralmente consistem em duas partes: treinamento e teste.
Durante o processo de treinamento, os parâmetros que podem ser aprendidos são ajustados usando dados de treinamento. No processo de teste, os parâmetros apreensíveis são congelados e a tarefa é verificar o quão bem o modelo faz previsões em dados não vistos anteriormente. A generalização é a capacidade de uma máquina de aprendizado de executar com precisão um exemplo ou tarefa nova e não vista depois de ter experimentado um conjunto de dados de aprendizado.
Se um modelo for muito simples em relação aos dados, ele não será capaz de ajustar os dados de treinamento e terá um desempenho ruim tanto no conjunto de dados de treinamento quanto no conjunto de dados de teste. Nesse caso, dizemos que o modelo está subajustado .
Se um modelo de aprendizado de máquina tiver um bom desempenho em um conjunto de dados de treinamento, mas um desempenho ruim em um conjunto de dados de teste, dizemos que está sendo superajustado . Overfitting é a situação em que um modelo é muito complexo em relação aos dados. Ele pode se ajustar perfeitamente aos dados de treinamento, mas é tão adaptado ao conjunto de dados de treinamento que apresenta um desempenho ruim nos dados de teste, ou seja, simplesmente não generaliza.
Abaixo está uma imagem mostrando underfitting e overfitting em comparação com uma situação equilibrada entre os dados gerais e a função de previsão.
Escalabilidade
Os dados são cruciais na construção de modelos de aprendizado de máquina. Normalmente, os algoritmos de aprendizado tradicionais não exigem muitos dados. Mas devido à sua capacidade limitada, o desempenho também é limitado. Abaixo está um gráfico que mostra como os métodos de aprendizado profundo são bem dimensionados em comparação com os algoritmos tradicionais de aprendizado de máquina.
Redes neurais
As redes neurais consistem em várias camadas. A imagem abaixo mostra uma rede neural simples com quatro camadas. A primeira camada é a camada de entrada e a última camada é a camada de saída. As duas camadas entre as camadas de entrada e saída são camadas ocultas.
Se uma rede neural tiver mais de uma camada oculta, chamamos de rede neural profunda. O conjunto de entrada $ X $ é fornecido à rede neural e a saída $ y $ é obtida. O aprendizado é feito usando um algoritmo de retropropagação que combina uma função de perda e um otimizador.
A retropropagação consiste em duas partes: um passe para frente e um passe para trás. Na passagem direta, os dados de entrada são colocados na entrada da rede neural e a saída é obtida. A perda entre a verdade do terreno e a previsão é calculada e, em seguida, na passagem para trás, os parâmetros das redes neurais são ajustados em relação à perda.
Rede Neural Convolucional
Uma variação da rede neural é a rede neural convolucional . É usado principalmente para tarefas de visão computacional.
A camada mais importante em redes neurais convolucionais é a camada convolucional (daí o nome). Seus parâmetros são feitos de filtros apreensíveis, também chamados de kernels. As camadas convolucionais aplicam uma operação de convolução à entrada, passando o resultado para a próxima camada. A operação de convolução reduz o número de parâmetros aprendíveis, funcionando como uma espécie de heurística e tornando a rede neural mais fácil de treinar.
Abaixo está como funciona um kernel convolucional em uma camada convolucional. O kernel é aplicado à imagem e um recurso convoluído é obtido.
As camadas ReLU são usadas para introduzir não linearidades na rede neural. As não linearidades são importantes porque com elas podemos modelar todos os tipos de funções, não apenas as lineares, tornando a rede neural um aproximador universal de funções. Isso faz com que uma função ReLU seja definida assim:
\[ReLU = \max(0, x)\]ReLU é um dos exemplos das chamadas funções de ativação usadas para introduzir não linearidades em redes neurais. Exemplos de outras funções de ativação incluem funções sigmóides e hipertangentes. ReLU é a função de ativação mais popular porque mostra que faz com que a rede neural seja treinada de forma mais eficiente em comparação com outras funções de ativação.
Abaixo está um gráfico de uma função ReLU.
Como você pode ver, esta função ReLU simplesmente altera os valores negativos para zeros. Isso ajuda a evitar o problema do gradiente de fuga. Se um gradiente desaparecer, não terá grande impacto no ajuste do peso da rede neural.
Uma rede neural convolucional consiste em várias camadas: camadas convolucionais, camadas ReLU e camadas totalmente conectadas. Camadas totalmente conectadas conectam cada neurônio em uma camada a cada neurônio em outra camada, como visto com as duas camadas ocultas na imagem no início desta seção. A última camada totalmente conectada mapeia as saídas da camada anterior para, neste caso, valores number_of_actions
.
Formulários
O aprendizado profundo é bem-sucedido e supera os algoritmos clássicos de aprendizado de máquina em vários subcampos de aprendizado de máquina, incluindo visão computacional, reconhecimento de fala e aprendizado por reforço. Esses campos de aprendizado profundo são aplicados em vários domínios do mundo real: finanças, medicina, entretenimento etc.
Aprendizado por Reforço
O aprendizado por reforço é baseado em um agente . Um agente realiza ações em um ambiente e obtém dele observações e recompensas. Um agente precisa ser treinado para maximizar a recompensa cumulativa. Conforme observado na introdução, com algoritmos clássicos de aprendizado de máquina, os engenheiros de aprendizado de máquina precisam fazer extração de recursos, ou seja, criar bons recursos que representem bem o ambiente e que sejam inseridos em um algoritmo de aprendizado de máquina.
Usando o aprendizado profundo, é possível criar um sistema de ponta a ponta que recebe entradas de alta dimensão – por exemplo, vídeo – e, a partir dele, aprende a estratégia ideal para um agente realizar boas ações.
Em 2013, a DeepMind, uma startup de IA de Londres, criou um grande avanço no aprendizado de controlar agentes diretamente de entradas sensoriais de alta dimensão. Eles publicaram um artigo, Playing Atari with Deep Reinforcement Learning , no qual mostraram como ensinaram uma rede neural artificial a jogar Atari apenas olhando para a tela. Eles foram adquiridos pelo Google e, em seguida, publicaram um novo artigo na Nature com algumas melhorias: Controle em nível humano por meio de aprendizado de reforço profundo .
Ao contrário de outros paradigmas de aprendizado de máquina, o aprendizado por reforço não possui um supervisor, apenas um sinal de recompensa. O feedback é atrasado: não é instantâneo como nos algoritmos de aprendizado supervisionado. Os dados são sequenciais e as ações de um agente afetam os dados subsequentes que ele recebe.
Agora, um agente está situado em seu ambiente, que está em um determinado estado. Para descrever isso com mais detalhes, usamos um processo de decisão de Markov, que é uma maneira formal de modelar esse ambiente de aprendizado por reforço. É composto de um conjunto de estados, um conjunto de ações possíveis e regras (por exemplo, probabilidades) para a transição de um estado para outro.
O agente é capaz de realizar ações, transformando o ambiente. Chamamos a recompensa de $ R_t $. É um sinal de feedback escalar que indica o desempenho do agente na etapa $t$.
Para um bom desempenho a longo prazo, não apenas recompensas imediatas, mas também recompensas futuras devem ser levadas em consideração. A recompensa total para um episódio da etapa de tempo $t$ é $ R_t = r_t + r_{t+1} + r_{t+2} + \ldots + r_n $. O futuro é incerto e quanto mais avançamos no futuro, mais as previsões futuras podem divergir. Por isso, uma recompensa futura com desconto é usada: $ R_t = r_t +\gamma r_{t+1} + \gamma^2r_{t+2} + \ldots + \gamma^{nt}r_n = r_t + \gamma R_{t+1} $. Um agente deve escolher a ação que maximiza a recompensa futura descontada.
Q-learning profundo
A função $ Q(s, a) $ representa a recompensa futura máxima descontada quando a ação $ a $ é realizada
A estimativa de uma recompensa futura é dada pela equação de Bellman: $ Q(s, a) = r + \gamma \max_{a'}Q(s', a') $ . Em outras palavras, a recompensa futura máxima dada a um estado $ s $ e uma ação $ a $ é a recompensa imediata mais a recompensa futura máxima para o próximo estado.
A aproximação de valores Q usando funções não lineares (redes neurais) não é muito estável. Por causa disso, a repetição da experiência é usada para estabilidade. A experiência durante os episódios de uma sessão de treinamento é armazenada em uma memória de repetição. Minilotes aleatórios da memória de repetição são usados em vez de usar a transição mais recente. Isso quebra a semelhança de amostras de treinamento subsequentes que, de outra forma, levariam a rede neural a um mínimo local.

Há dois aspectos mais importantes a serem mencionados sobre o Q-learning profundo: Exploração e exploração. Com a exploração, toma-se a melhor decisão dada a informação atual. A exploração reúne mais informações.
Quando o algoritmo executa uma ação proposta pela rede neural, está fazendo uma exploração: explora o conhecimento aprendido da rede neural. Em contraste, um algoritmo pode realizar uma ação aleatória, explorando novas possibilidades e introduzindo novos conhecimentos potenciais à rede neural.
O “algoritmo Deep Q-learning com Experience Replay” do artigo da DeepMind Playing Atari with Deep Reinforcement Learning é mostrado abaixo.
DeepMind refere-se a redes convolucionais treinadas com sua abordagem como Deep Q-networks (DQN).
Exemplo de Q-learning profundo usando Flappy Bird
Flappy Bird foi um popular jogo para celular originalmente desenvolvido pelo artista e programador vietnamita Dong Nguyen. Nele, o jogador controla um pássaro e tenta voar entre canos verdes sem atingi-los.
Abaixo está uma captura de tela de um clone do Flappy Bird codificado usando o PyGame:
O clone já foi bifurcado e modificado: o plano de fundo, os sons e os diferentes estilos de pássaros e tubos foram removidos e o código foi ajustado para que possa ser facilmente usado com estruturas simples de aprendizado por reforço. O mecanismo de jogo modificado é retirado deste projeto TensorFlow:
Mas, em vez de usar o TensorFlow, criei uma estrutura de aprendizado de reforço profundo usando o PyTorch. O PyTorch é uma estrutura de aprendizado profundo para experimentação rápida e flexível. Ele fornece tensores e redes neurais dinâmicas em Python com forte aceleração de GPU.
A arquitetura da rede neural é a mesma da DeepMind usada no artigo Controle em nível humano por meio de aprendizado de reforço profundo .
Camada | Entrada | Tamanho do filtro | Passo | Número de filtros | Ativação | Saída |
---|---|---|---|---|---|---|
conv1 | 84x84x4 | 8x8 | 4 | 32 | ReLU | 20x20x32 |
conv2 | 20x20x32 | 4x4 | 2 | 64 | ReLU | 9x9x64 |
conv3 | 9x9x64 | 3x3 | 1 | 64 | ReLU | 7x7x64 |
fc4 | 7x7x64 | 512 | ReLU | 512 | ||
fc5 | 512 | 2 | Linear | 2 |
Existem três camadas convolucionais e duas camadas totalmente conectadas. Cada camada usa ativação ReLU, exceto a última, que usa ativação linear. A rede neural gera dois valores que representam as únicas ações possíveis do jogador: “voar para cima” e “não fazer nada”.
A entrada consiste em quatro imagens consecutivas 84x84 em preto e branco. Abaixo está um exemplo de quatro imagens que são alimentadas à rede neural.
Você notará que as imagens são giradas. Isso ocorre porque a saída do mecanismo de jogo do clone é girada. Mas se a rede neural for ensinada e testada usando essas imagens, isso não afetará seu desempenho.
Você também pode notar que a imagem é cortada para que o piso seja omitido, pois é irrelevante para esta tarefa. Todos os pixels que representam tubos e o pássaro são brancos e todos os pixels que representam o fundo são pretos.
Isso faz parte do código que define a rede neural. Os pesos das redes neurais são inicializados para seguir a distribuição uniforme $\mathcal{U}(-0.01, 0.01)$. A parte de polarização dos parâmetros das redes neurais é definida como 0,01. Várias inicializações diferentes foram tentadas (Xavier uniforme, Xavier normal, Kaiming uniforme, Kaiming normal, uniforme e normal), mas a inicialização acima fez a rede neural convergir e treinar mais rápido. O tamanho da rede neural é de 6,8 MB.
class NeuralNetwork(nn.Module): def __init__(self): super(NeuralNetwork, self).__init__() self.number_of_actions = 2 self.gamma = 0.99 self.final_epsilon = 0.0001 self.initial_epsilon = 0.1 self.number_of_iterations = 2000000 self.replay_memory_size = 10000 self.minibatch_size = 32 self.conv1 = nn.Conv2d(4, 32, 8, 4) self.relu1 = nn.ReLU(inplace=True) self.conv2 = nn.Conv2d(32, 64, 4, 2) self.relu2 = nn.ReLU(inplace=True) self.conv3 = nn.Conv2d(64, 64, 3, 1) self.relu3 = nn.ReLU(inplace=True) self.fc4 = nn.Linear(3136, 512) self.relu4 = nn.ReLU(inplace=True) self.fc5 = nn.Linear(512, self.number_of_actions) def forward(self, x): out = self.conv1(x) out = self.relu1(out) out = self.conv2(out) out = self.relu2(out) out = self.conv3(out) out = self.relu3(out) out = out.view(out.size()[0], -1) out = self.fc4(out) out = self.relu4(out) out = self.fc5(out) return out
No construtor, você notará que existem hiperparâmetros definidos. A otimização de hiperparâmetros não é feita para esta postagem de blog. Em vez disso, os hiperparâmetros são usados principalmente nos artigos da DeepMind. Aqui, alguns dos hiperparâmetros são dimensionados para serem menores do que no artigo da DeepMind, porque o Flappy Bird é menos complexo do que os jogos Atari que eles usaram para ajustar.
Além disso, epsilon é alterado para ser muito mais razoável para este jogo. O DeepMind usa um epsilon de um, mas aqui usamos 0,1. Isso ocorre porque os epsilons mais altos forçam o pássaro a bater muito, o que empurra o pássaro em direção à borda superior da tela, sempre resultando na colisão do pássaro em um cano.
O código de aprendizado por reforço tem dois modos: Treinar e testar. Durante a fase de teste, podemos ver o quão bem o algoritmo de aprendizado por reforço aprendeu a jogar o jogo. Mas primeiro, a rede neural precisa ser treinada. Precisamos definir a função de perda a ser minimizada e os otimizadores que irão minimizar a função de perda. Usaremos o método de otimização de Adam e o erro quadrático médio para a função de perda:
optimizer = optim.Adam(model.parameters(), lr=1e-6) criterion = nn.MSELoss()
O jogo deve ser instanciado:
game_state = GameState()
A memória de repetição é definida como uma lista do Python:
replay_memory = []
Agora precisamos inicializar o primeiro estado. Uma ação é um tensor bidimensional:
- [1, 0] representa “não fazer nada”
- [0, 1] representa "voar para cima"
O método frame_step
nos dá a próxima tela, recompensa e informações sobre se o próximo estado é terminal. A recompensa é 0.1
para cada movimento do pássaro sem morrer quando não estiver passando por um cano, 1
se o pássaro passar com sucesso por um cano e -1
se o pássaro cair.
A função resize_and_bgr2gray
corta o piso, redimensiona a tela para uma imagem de 84x84 e altera o espaço de cores de BGR para preto e branco. A função image_to_tensor
converte a imagem em um tensor PyTorch e a coloca na memória da GPU se CUDA estiver disponível. Finalmente, as últimas quatro telas sequenciais são concatenadas e estão prontas para serem enviadas para a rede neural.
action = torch.zeros([model.number_of_actions], dtype=torch.float32) action[0] = 1 image_data, reward, terminal = game_state.frame_step(action) image_data = resize_and_bgr2gray(image_data) image_data = image_to_tensor(image_data) state = torch.cat((image_data, image_data, image_data, image_data)).unsqueeze(0)
O epsilon inicial é definido usando esta linha de código:
epsilon = model.initial_epsilon
O loop infinito principal segue. Os comentários são escritos no código e você pode comparar o código com o algoritmo Deep Q-learning com Experience Replay escrito acima.
O algoritmo amostra mini-lotes da memória de repetição e atualiza os parâmetros da rede neural. As ações são executadas usando a exploração gananciosa epsilon . Epsilon está sendo recozido ao longo do tempo. A função de perda sendo minimizada é $ L = \frac{1}{2}\left[\max_{a'}Q(s', a') - Q(s, a)\right]^2 $ . $ Q(s, a) $ é o valor real calculado usando a equação de Bellman e $ \max_{a'}Q(s', a') $ é obtido da rede neural. A rede neural fornece dois valores Q para as duas ações possíveis e o algoritmo executa a ação com o valor Q mais alto.
while iteration < model.number_of_iterations: # get output from the neural network output = model(state)[0] # initialize action action = torch.zeros([model.number_of_actions], dtype=torch.float32) if torch.cuda.is_available(): # put on GPU if CUDA is available action = action.cuda() # epsilon greedy exploration random_action = random.random() <= epsilon if random_action: print("Performed random action!") action_index = [torch.randint(model.number_of_actions, torch.Size([]), dtype=torch.int) if random_action else torch.argmax(output)][0] if torch.cuda.is_available(): # put on GPU if CUDA is available action_index = action_index.cuda() action[action_index] = 1 # get next state and reward image_data_1, reward, terminal = game_state.frame_step(action) image_data_1 = resize_and_bgr2gray(image_data_1) image_data_1 = image_to_tensor(image_data_1) state_1 = torch.cat((state.squeeze(0)[1:, :, :], image_data_1)).unsqueeze(0) action = action.unsqueeze(0) reward = torch.from_numpy(np.array([reward], dtype=np.float32)).unsqueeze(0) # save transition to replay memory replay_memory.append((state, action, reward, state_1, terminal)) # if replay memory is full, remove the oldest transition if len(replay_memory) > model.replay_memory_size: replay_memory.pop(0) # epsilon annealing epsilon = epsilon_decrements[iteration] # sample random minibatch minibatch = random.sample(replay_memory, min(len(replay_memory), model.minibatch_size)) # unpack minibatch state_batch = torch.cat(tuple(d[0] for d in minibatch)) action_batch = torch.cat(tuple(d[1] for d in minibatch)) reward_batch = torch.cat(tuple(d[2] for d in minibatch)) state_1_batch = torch.cat(tuple(d[3] for d in minibatch)) if torch.cuda.is_available(): # put on GPU if CUDA is available state_batch = state_batch.cuda() action_batch = action_batch.cuda() reward_batch = reward_batch.cuda() state_1_batch = state_1_batch.cuda() # get output for the next state output_1_batch = model(state_1_batch) # set y_j to r_j for terminal state, otherwise to r_j + gamma*max(Q) y_batch = torch.cat(tuple(reward_batch[i] if minibatch[i][4] else reward_batch[i] + model.gamma * torch.max(output_1_batch[i]) for i in range(len(minibatch)))) # extract Q-value q_value = torch.sum(model(state_batch) * action_batch, dim=1) # PyTorch accumulates gradients by default, so they need to be reset in each pass optimizer.zero_grad() # returns a new Tensor, detached from the current graph, the result will never require gradient y_batch = y_batch.detach() # calculate loss loss = criterion(q_value, y_batch) # do backward pass loss.backward() optimizer.step() # set state to be state_1 state = state_1
Agora que todas as peças estão no lugar, aqui está uma visão geral de alto nível do fluxo de dados usando nossa rede neural:
Aqui está uma sequência curta com uma rede neural treinada.
A rede neural mostrada acima foi treinada usando uma GPU Nvidia GTX 1080 de ponta por algumas horas; usando uma solução baseada em CPU, essa tarefa específica levaria vários dias. O FPS do mecanismo de jogo foi definido para um número muito grande durante o treinamento: 999…999 — em outras palavras, o maior número possível de quadros por segundo. Na fase de testes, o FPS foi definido para 30.
Abaixo está um gráfico que mostra como o valor Q máximo mudou durante as iterações. Cada 10.000ª iteração é mostrada. Um pico descendente significa que, para um quadro específico (uma iteração sendo um quadro), a rede neural prevê que o pássaro receberá uma recompensa muito baixa no futuro - ou seja, ele travará muito em breve.
O código completo e o modelo pré-treinado estão disponíveis aqui.
Aprendizado por reforço profundo: 2D, 3D e até mesmo a vida real
Neste tutorial de aprendizado por reforço do PyTorch, mostrei como um computador pode aprender a jogar Flappy Bird sem nenhum conhecimento prévio sobre o jogo, usando apenas uma abordagem de tentativa e erro, como um humano faria ao encontrar o jogo pela primeira vez.
É interessante que o algoritmo possa ser implementado em poucas linhas de código usando o framework PyTorch. O artigo no qual o método deste blog se baseia é relativamente antigo, e muitos artigos mais recentes com várias modificações que permitem uma convergência mais rápida estão disponíveis. Desde então, o aprendizado por reforço profundo tem sido usado para jogar jogos 3D e em sistemas robóticos do mundo real.
Empresas como DeepMind, Maluuba e Vicarious estão trabalhando intensamente no aprendizado por reforço profundo. Tal tecnologia foi utilizada no AlphaGo, que derrotou Lee Sedol, um dos melhores jogadores do mundo em Go. Na época, considerava-se que levaria pelo menos dez anos até que as máquinas pudessem derrotar os melhores jogadores do Go.
A enxurrada de interesse e investimentos em aprendizado de reforço profundo (e em inteligência artificial em geral) pode até levar a uma potencial inteligência geral artificial (AGI) – inteligência em nível humano (ou mesmo além) que pode ser expressa na forma de um algoritmo e simulados em computadores. Mas AGI terá que ser assunto de outro artigo.
Referências:
- Desmistificando o Aprendizado por Reforço Profundo
- Aprendizado de reforço profundo para Flappy Bird
- “Rede neural convolucional” na Wikipedia
- “Aprendizagem por reforço” na Wikipedia
- “Processo de decisão de Markov” na Wikipedia
- Curso da University College London em RL