1. Antes de começar
A incrível inovação do AlphaGo e na AlphaStar demonstrou o potencial de usar aprendizado de máquina para criar agentes de jogos de níveis sobre-humanos. Criar um pequeno jogo com tecnologia de ML é um exercício divertido para aprender as habilidades necessárias para criar agentes de jogo poderosos.
Neste codelab, você vai aprender a criar um jogo de tabuleiro usando:
- Agente do TensorFlow para treinar um agente de jogo com aprendizado por reforço
- TensorFlow Serving para disponibilizar o modelo
- Flutter para criar um app de jogo de tabuleiro multiplataforma
Pré-requisitos
- Conhecimento básico de desenvolvimento do Flutter com Dart
- Conhecimento básico de machine learning com o TensorFlow, como treinamento ou implantação
- Conhecimento básico de Python, terminais e Docker
O que você vai aprender
- Como treinar um agente de personagem não jogador (NPC) usando agentes do TensorFlow
- Como disponibilizar o modelo treinado usando o TensorFlow Serving
- Como criar um jogo de tabuleiro multiplataforma do Flutter
O que é necessário
- SDK do Flutter (em inglês)
- Configuração do Android e iOS para o Flutter
- Configuração de computadores para o Flutter
- Configuração da Web para o Flutter
- Configuração do Visual Studio Code (VS Code) para Flutter e Dart
- Docker
- Bash
- Python 3.7 ou superior
2. Jogo The Plane Strike
O jogo criado neste codelab é chamado de Plane Strike, um pequeno jogo de tabuleiro para duas pessoas semelhante ao Battleship. As regras são muito simples:
- O jogador humano joga contra um agente NPC treinado por machine learning. O jogador humano pode iniciar o jogo tocando em qualquer célula no tabuleiro do agente.
- No começo do jogo, o jogador humano e o agente têm um "plano" (oito células verdes que formam um "plano", como você pode ver no tabuleiro do jogador humano na animação abaixo) nos seus próprios tabuleiros; esses "aviões" são colocados aleatoriamente e visíveis apenas para os donos do tabuleiro e escondidos para seus oponentes.
- O jogador humano e o agente se revezam para atacar uma célula do tabuleiro um do outro. O jogador humano pode tocar em qualquer célula no quadro do agente, enquanto o agente faz a escolha automaticamente com base na previsão de um modelo de machine learning. A célula desejada fica vermelha se for um "plano" célula (‘hit'); caso contrário, ele fica amarelo ("perder").
- Vence o jogo quem atingir 8 células vermelhas primeiro. o jogo é reiniciado com novos tabuleiros.
Aqui está um exemplo de jogabilidade:
3. Configurar o ambiente de desenvolvimento do Flutter
Para desenvolver o Flutter, você precisa de dois softwares para concluir este codelab: o SDK do Flutter e um editor (links em inglês).
É possível executar o codelab usando qualquer um destes dispositivos:
- O simulador para iOS, que exige a instalação de ferramentas do Xcode.
- O Android Emulator, que requer configuração no Android Studio.
- Um navegador (o Chrome é necessário para depuração).
- Como um aplicativo para computador Windows, Linux ou macOS. Você precisa desenvolver na plataforma em que planeja implantar. Portanto, se quiser desenvolver um app para um computador Windows, você terá que desenvolver no Windows para acessar a cadeia de builds adequada. Há requisitos específicos de cada sistema operacional que são abordados em detalhes em docs.flutter.dev/desktop.
4. Começar a configuração
Para fazer o download do código para este codelab, faça o seguinte:
- Navegue até o repositório do GitHub deste codelab.
- Clique em Code > Download zip para fazer o download de todo o código para este codelab.
- Descompacte o arquivo ZIP salvo para acessar uma pasta raiz
codelabs-main
com todos os recursos necessários.
Para este codelab, você só precisa dos arquivos no subdiretório tfagents-flutter/
do repositório, que contém várias pastas:
- As pastas de
step0
astep6
contêm o código inicial baseado em cada etapa deste codelab. - A pasta
finished
contém o código concluído do app de exemplo finalizado. - Cada pasta contém uma subpasta
backbend
, que inclui o código de back-end, e uma subpastafrontend
, que inclui o código de front-end do Flutter.
5. Fazer o download das dependências do projeto
Back-end
Abra o terminal e acesse a subpasta tfagents-flutter
. Execute o comando a seguir:
pip install -r requirements.txt
Front-end
- No VS Code, clique em File > Abra a pasta e selecione a pasta
step0
no código-fonte que você salvou antes. - Abra
step0/frontend/lib/main.dart
. Se aparecer uma caixa de diálogo do VS Code solicitando o download dos pacotes necessários para o app inicial, clique em Get packages. - Se a caixa de diálogo não aparecer, abra o terminal e execute o comando
flutter pub get
na pastastep0/frontend
.
6. Etapa 0: executar o app inicial
- Abra o arquivo
step0/frontend/lib/main.dart
no VS Code, verifique se o Android Emulator ou o simulador de iOS está configurado corretamente e aparece na barra de status.
Por exemplo, veja o que você verá ao usar o Pixel 5 com o Android Emulator:
Veja o que você verá ao usar o iPhone 13 com o iOS Simulator:
- Clique em Iniciar depuração.
Executar e explorar o app
O app será iniciado no Android Emulator ou no iOS Simulator. A interface é bem direta. Há dois tabuleiros de jogos: um jogador humano pode tocar em qualquer célula no tabuleiro do agente na parte superior como uma posição de ataque. Você vai treinar um agente inteligente para prever automaticamente onde atacar com base no tabuleiro humano.
Internamente, o app Flutter envia o tabuleiro atual do jogador humano para o back-end, que executa um modelo de aprendizado por reforço e retorna uma posição de célula prevista para atacar em seguida. O front-end vai exibir o resultado na interface depois de receber a resposta.
Se você clicar em qualquer célula no quadro do agente agora, nada acontecerá porque o app ainda não pode se comunicar com o back-end.
7. Etapa 1: criar um ambiente Python do TensorFlow Agents
O principal objetivo deste codelab é projetar um agente que aprenda interagindo com um ambiente. Embora o jogo Plane Strike seja relativamente simples e seja possível criar regras para o agente NPC, você usa o aprendizado por reforço para treinar o agente e aprender as habilidades dele e criar facilmente agentes para outros jogos no futuro.
Na configuração padrão do aprendizado por reforço (RL, na sigla em inglês), o agente recebe uma observação em cada etapa de tempo e escolhe uma ação. A ação é aplicada ao ambiente, que retorna uma recompensa e uma nova observação. O agente treina uma política para escolher ações e maximizar a soma das recompensas, também conhecidas como devoluções. Ao jogar muitas vezes, o agente consegue aprender os padrões e aprimorar as habilidades para dominar o jogo. Para formular o jogo Plane Strike como um problema de RL, pense no estado do tabuleiro como a observação, uma posição de ataque como a ação e o sinal de acertar/agarrar como a recompensa.
Para treinar o agente NPC, use o TensorFlow Agents, uma biblioteca de aprendizado por reforço confiável, escalonável e fácil de usar.
O TF Agents é ótimo para o aprendizado por reforço porque vem com um extenso conjunto de codelabs, exemplos e documentação extensiva para você começar. É possível usar o TF Agents para resolver problemas realistas e complexos de aprendizado por reforço com escalonabilidade e desenvolver novos algoritmos de aprendizado de máquina rapidamente. Alterne facilmente entre diferentes agentes e algoritmos para a experimentação. Ele também é bem testado e fácil de configurar.
Existem muitos ambientes de jogos pré-criados no OpenAI Gym (por exemplo, jogos de Atari), Mujuco e outros, que os agentes do TF podem usar facilmente. Mas como o Plane Strike é totalmente personalizado, primeiro você precisa implementar um novo ambiente do zero.
Para implementar um ambiente Python do TF Agents, é preciso implementar os seguintes métodos:
class YourGameEnv(py_environment.PyEnvironment): def __init__(self): """Initialize environment.""" def action_spec(self): """Return action_spec.""" def observation_spec(self): """Return observation_spec.""" def _reset(self): """Return initial_time_step.""" def _step(self, action): """Apply action and return new time_step."""
A mais importante é a função _step()
, que realiza uma ação e retorna um novo objeto time_step
. No caso do jogo Plane Strike, você tem um tabuleiro, quando uma nova posição de ataque é aplicada, com base na condição do tabuleiro do jogo, o ambiente identifica:
- Como deve ser a aparência do tabuleiro do jogo (a célula deve mudar de cor para vermelho ou amarelo, considerando a localização oculta do plano?)
- Qual recompensa o jogador deve receber por essa posição (recebimento de recompensa ou erro de penalidade?)
- O jogo deve ser encerrado (alguém ganhou?)
- Adicione o seguinte código à função
_step()
ao arquivo_planestrike_py_environment.py
:
if self._hit_count == self._plane_size: self._episode_ended = True return self.reset() if self._strike_count + 1 == self._max_steps: self.reset() return ts.termination( np.array(self._visible_board, dtype=np.float32), UNFINISHED_GAME_REWARD ) self._strike_count += 1 action_x = action // self._board_size action_y = action % self._board_size # Hit if self._hidden_board[action_x][action_y] == HIDDEN_BOARD_CELL_OCCUPIED: # Non-repeat move if self._visible_board[action_x][action_y] == VISIBLE_BOARD_CELL_UNTRIED: self._hit_count += 1 self._visible_board[action_x][action_y] = VISIBLE_BOARD_CELL_HIT # Successful strike if self._hit_count == self._plane_size: # Game finished self._episode_ended = True return ts.termination( np.array(self._visible_board, dtype=np.float32), FINISHED_GAME_REWARD, ) else: self._episode_ended = False return ts.transition( np.array(self._visible_board, dtype=np.float32), HIT_REWARD, self._discount, ) # Repeat strike else: self._episode_ended = False return ts.transition( np.array(self._visible_board, dtype=np.float32), REPEAT_STRIKE_REWARD, self._discount, ) # Miss else: # Unsuccessful strike self._episode_ended = False self._visible_board[action_x][action_y] = VISIBLE_BOARD_CELL_MISS return ts.transition( np.array(self._visible_board, dtype=np.float32), MISS_REWARD, self._discount,
8. Etapa 2: treinar o agente do jogo com os agentes do TensorFlow
Com o ambiente do TF Agents implantado, é possível treinar o agente de jogo. Neste codelab, você vai usar um agente REINFORCE. REINFORCE é um algoritmo de gradiente de política em RL. A ideia básica é ajustar os parâmetros da rede neural de política com base nos sinais de recompensa coletados durante o jogo, para que a rede possa maximizar o retorno em jogadas futuras.
- Primeiro você precisa instanciar os ambientes de treinamento e avaliação. Adicione este código à função
train_agent()
no arquivostep2/backend/training.py
:
train_py_env = planestrike_py_environment.PlaneStrikePyEnvironment( board_size=BOARD_SIZE, discount=DISCOUNT, max_steps=BOARD_SIZE**2 ) eval_py_env = planestrike_py_environment.PlaneStrikePyEnvironment( board_size=BOARD_SIZE, discount=DISCOUNT, max_steps=BOARD_SIZE**2 ) train_env = tf_py_environment.TFPyEnvironment(train_py_env) eval_env = tf_py_environment.TFPyEnvironment(eval_py_env)
- Em seguida, crie um agente de aprendizado por reforço que será treinado. Neste codelab, você vai usar o agente REINFORCE, que é um agente baseado em políticas. Adicione este código logo abaixo do código acima:
actor_net = tfa.networks.Sequential( [ tfa.keras_layers.InnerReshape([BOARD_SIZE, BOARD_SIZE], [BOARD_SIZE**2]), tf.keras.layers.Dense(FC_LAYER_PARAMS, activation="relu"), tf.keras.layers.Dense(BOARD_SIZE**2), tf.keras.layers.Lambda(lambda t: tfp.distributions.Categorical(logits=t)), ], input_spec=train_py_env.observation_spec(), ) optimizer = tf.keras.optimizers.Adam(learning_rate=LEARNING_RATE) train_step_counter = tf.Variable(0) tf_agent = reinforce_agent.ReinforceAgent( train_env.time_step_spec(), train_env.action_spec(), actor_network=actor_net, optimizer=optimizer, normalize_returns=True, train_step_counter=train_step_counter, )
- Por fim, treine o agente no loop de treinamento. No loop, primeiro você coleta alguns episódios de jogadas em um buffer e depois treina o agente com os dados armazenados em buffer. Adicione este código à função
train_agent()
no arquivostep2/backend/training.py
:
# Collect a few episodes using collect_policy and save to the replay buffer. collect_episode( train_py_env, collect_policy, COLLECT_EPISODES_PER_ITERATION, replay_buffer_observer, ) # Use data from the buffer and update the agent's network. iterator = iter(replay_buffer.as_dataset(sample_batch_size=1)) trajectories, _ = next(iterator) tf_agent.train(experience=trajectories) replay_buffer.clear()
- Agora você pode começar o treinamento. No terminal, acesse a pasta
step2/backend
no computador e execute:
python training.py
O treinamento leva de 8 a 12 horas para ser concluído, dependendo das configurações de hardware. Você não precisa concluir todo o treinamento por conta própria, já que um modelo pré-treinado é fornecido em step3
. Enquanto isso, você pode monitorar o progresso com o TensorBoard. Abra um novo terminal, acesse a pasta step2/backend
no computador e execute:
tensorboard --logdir tf_agents_log/
tf_agents_log
é a pasta que contém o registro de treinamento. Veja abaixo um exemplo de execução de treinamento:
Observe que a duração média do episódio diminui e o retorno médio aumenta à medida que o treinamento avança. Intuitivamente, é possível entender que, se o agente for mais inteligente e fizer previsões melhores, a duração do jogo ficará menor e o agente coletará mais recompensas. Isso faz sentido porque o agente quer terminar o jogo em menos etapas para minimizar os descontos pesados em recompensas nas etapas posteriores.
Após a conclusão do treinamento, o modelo treinado é exportado para a pasta policy_model
.
9. Etapa 3: implantar o modelo treinado com o TensorFlow Serving
Agora que você treinou o agente de jogo, é possível implantá-lo com o TensorFlow Serving.
- No seu terminal, acesse a pasta
step3/backend
no computador e inicie o TensorFlow Serving com o Docker:
docker run -t --rm -p 8501:8501 -p 8500:8500 -v "$(pwd)/backend/policy_model:/models/policy_model" -e MODEL_NAME=policy_model tensorflow/serving
Primeiro, o Docker faz o download automático da imagem do TensorFlow Serving em um minuto. Depois disso, o TensorFlow Serving será iniciado. O registro será semelhante a este snippet de código:
2022-05-30 02:38:54.147771: I tensorflow_serving/model_servers/server.cc:89] Building single TensorFlow model file config: model_name: policy_model model_base_path: /models/policy_model 2022-05-30 02:38:54.148222: I tensorflow_serving/model_servers/server_core.cc:465] Adding/updating models. 2022-05-30 02:38:54.148273: I tensorflow_serving/model_servers/server_core.cc:591] (Re-)adding model: policy_model 2022-05-30 02:38:54.262684: I tensorflow_serving/core/basic_manager.cc:740] Successfully reserved resources to load servable {name: policy_model version: 123} 2022-05-30 02:38:54.262768: I tensorflow_serving/core/loader_harness.cc:66] Approving load for servable version {name: policy_model version: 123} 2022-05-30 02:38:54.262787: I tensorflow_serving/core/loader_harness.cc:74] Loading servable version {name: policy_model version: 123} 2022-05-30 02:38:54.265010: I external/org_tensorflow/tensorflow/cc/saved_model/reader.cc:38] Reading SavedModel from: /models/policy_model/123 2022-05-30 02:38:54.277811: I external/org_tensorflow/tensorflow/cc/saved_model/reader.cc:90] Reading meta graph with tags { serve } 2022-05-30 02:38:54.278116: I external/org_tensorflow/tensorflow/cc/saved_model/reader.cc:132] Reading SavedModel debug info (if present) from: /models/policy_model/123 2022-05-30 02:38:54.280229: I external/org_tensorflow/tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations: AVX2 FMA To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags. 2022-05-30 02:38:54.332352: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:206] Restoring SavedModel bundle. 2022-05-30 02:38:54.337000: I external/org_tensorflow/tensorflow/core/platform/profile_utils/cpu_utils.cc:114] CPU Frequency: 2193480000 Hz 2022-05-30 02:38:54.402803: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:190] Running initialization op on SavedModel bundle at path: /models/policy_model/123 2022-05-30 02:38:54.410707: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:277] SavedModel load for tags { serve }; Status: success: OK. Took 145695 microseconds. 2022-05-30 02:38:54.412726: I tensorflow_serving/servables/tensorflow/saved_model_warmup_util.cc:59] No warmup data file found at /models/policy_model/123/assets.extra/tf_serving_warmup_requests 2022-05-30 02:38:54.417277: I tensorflow_serving/core/loader_harness.cc:87] Successfully loaded servable version {name: policy_model version: 123} 2022-05-30 02:38:54.419846: I tensorflow_serving/model_servers/server_core.cc:486] Finished adding/updating models 2022-05-30 02:38:54.420066: I tensorflow_serving/model_servers/server.cc:367] Profiler service is enabled 2022-05-30 02:38:54.428339: I tensorflow_serving/model_servers/server.cc:393] Running gRPC ModelServer at 0.0.0.0:8500 ... [warn] getaddrinfo: address family for nodename not supported 2022-05-30 02:38:54.431620: I tensorflow_serving/model_servers/server.cc:414] Exporting HTTP/REST API at:localhost:8501 ... [evhttp_server.cc : 245] NET_LOG: Entering the event loop ...
Você pode enviar um exemplo de solicitação ao endpoint para verificar se está funcionando conforme o esperado:
curl -d '{"signature_name":"action","instances":[{"0/discount":0.0,"0/observation":[[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]],"0/reward":0.0,"0/step_type":0}]}' -X POST http://localhost:8501/v1/models/policy_model:predict
O endpoint vai retornar uma posição prevista 45
, que é (5, 5) no centro do tabuleiro (para os curiosos, você pode tentar descobrir por que o centro do tabuleiro é um bom palpite para a primeira posição de golpes).
{ "predictions": [45] }
Pronto! Você criou um back-end para prever a próxima posição de aviso do agente NPC.
10. Etapa 4: criar o app do Flutter para Android e iOS
O back-end está pronto. Você pode começar a enviar solicitações a ele para extrair previsões de posição do aviso do app Flutter.
- Primeiro, defina uma classe que encapsule as entradas a serem enviadas. Adicione este código ao arquivo
step4/frontend/lib/game_agent.dart
:
class Inputs { final List<double> _boardState; Inputs(this._boardState); Map<String, dynamic> toJson() { final Map<String, dynamic> data = <String, dynamic>{}; data['0/discount'] = [0.0]; data['0/observation'] = [_boardState]; data['0/reward'] = [0.0]; data['0/step_type'] = [0]; return data; } }
Agora é possível enviar a solicitação ao TensorFlow Serving para fazer previsões.
- Adicione este código à função
predict()
no arquivostep4/frontend/lib/game_agent.dart
:
var flattenedBoardState = boardState.expand((i) => i).toList(); final response = await http.post( Uri.parse('http://$server:8501/v1/models/policy_model:predict'), body: jsonEncode(<String, dynamic>{ 'signature_name': 'action', 'instances': [Inputs(flattenedBoardState)] }), ); if (response.statusCode == 200) { var output = List<int>.from( jsonDecode(response.body)['predictions'] as List<dynamic>); return output[0]; } else { throw Exception('Error response'); }
Quando o app receber a resposta do back-end, atualize a interface do jogo para refletir o progresso dele.
- Adicione este código à função
_gridItemTapped()
no arquivostep4/frontend/lib/main.dart
:
int agentAction = await _policyGradientAgent.predict(_playerVisibleBoardState); _agentActionX = agentAction ~/ _boardSize; _agentActionY = agentAction % _boardSize; if (_playerHiddenBoardState[_agentActionX][_agentActionY] == hiddenBoardCellOccupied) { // Non-repeat move if (_playerVisibleBoardState[_agentActionX][_agentActionY] == visibleBoardCellUntried) { _agentHitCount++; } _playerVisibleBoardState[_agentActionX][_agentActionY] = visibleBoardCellHit; } else { _playerVisibleBoardState[_agentActionX][_agentActionY] = visibleBoardCellMiss; } setState(() {});
Executar
- Clique em Start debugging e aguarde o app carregar.
- Toque em qualquer célula no tabuleiro do agente para iniciar o jogo.
11. Etapa 5: ativar o app do Flutter para as plataformas para computador
Além do Android e iOS, o Flutter também oferece suporte a plataformas de computador, incluindo Linux, Mac e Windows.
Linux
- Verifique se o dispositivo de destino está definido como na barra de status do VSCode.
- Clique em Start debugging e aguarde o app carregar.
- Clique em qualquer célula no tabuleiro do agente para iniciar o jogo.
Mac
- No Mac, você precisa configurar os direitos apropriados, já que o app vai enviar solicitações HTTP para o back-end. Consulte Direitos e aplicativos sandbox para mais detalhes.
Adicione este código a step4/frontend/macOS/Runner/DebugProfile.entitlements
e step4/frontend/macOS/Runner/Release.entitlements
, respectivamente:
<key>com.apple.security.network.client</key>
<true/>
- Verifique se o dispositivo de destino está definido como na barra de status do VSCode.
- Clique em Start debugging e aguarde o app carregar.
- Clique em qualquer célula no tabuleiro do agente para iniciar o jogo.
Windows
- Verifique se o dispositivo de destino está definido como na barra de status do VSCode.
- Clique em Start debugging e aguarde o app carregar.
- Clique em qualquer célula no tabuleiro do agente para iniciar o jogo.
12. Etapa 6: ativar o app do Flutter para a plataforma da Web
Mais uma coisa que você pode fazer é adicionar suporte da Web ao app Flutter. Por padrão, a plataforma da Web é ativada automaticamente para apps Flutter, portanto, tudo o que você precisa fazer é iniciá-la.
- Verifique se o dispositivo de destino está definido como na barra de status do VSCode.
- Clique em Start debugging e aguarde o app carregar no navegador Chrome.
- Clique em qualquer célula no tabuleiro do agente para iniciar o jogo.
13. Parabéns
Você criou um app de jogo de tabuleiro com um agente com tecnologia de ML para jogar contra um usuário humano.