1. Antes de comenzar
El increíble avance de AlphaGo y AlphaStar demostró el potencial de usar el aprendizaje automático para compilar agentes de juegos de nivel sobrehumano. Es un ejercicio divertido para compilar un pequeño juego potenciado por AA a fin de adquirir las habilidades necesarias para crear agentes de juego potentes.
En este codelab, aprenderás a compilar un juego de mesa usando lo siguiente:
- Agente de TensorFlow para entrenar a un agente de juego con aprendizaje por refuerzo
- TensorFlow Serving para entregar el modelo
- Flutter para crear una app de juegos de mesa multiplataforma
Requisitos previos
- Conocimientos básicos sobre el desarrollo de Flutter con Dart
- Conocimientos básicos del aprendizaje automático con TensorFlow, como el entrenamiento y la implementación
- Conocimientos básicos de Python, terminales y Docker
Qué aprenderás
- Cómo entrenar a un agente de personaje que no es un jugador (NPC) con agentes de TensorFlow
- Cómo entregar el modelo entrenado con TensorFlow Serving
- Cómo crear un juego de mesa multiplataforma de Flutter
Requisitos
- SDK de Flutter
- Configuración de iOS y Android para Flutter
- Configuración de Flutter para computadoras
- Configuración de Flutter para la Web
- Configuración de Visual Studio Code (VS Code) para Flutter y Dart
- Docker
- Bash
- Python 3.7 y versiones posteriores
2. Juego de Plane Strike
El juego que compilarás en este codelab se llama "Plane Strike", un pequeño juego de mesa para 2 jugadores similar al juego de mesa "Battleship". Las reglas son muy simples:
- El jugador juega contra un agente de NPC entrenado con aprendizaje automático. El jugador puede iniciar el juego presionando cualquier celda en el tablero del agente.
- Al comienzo del juego, el jugador humano y el agente tienen un "avión" (8 celdas verdes que forman un "plano", como se puede ver en el tablero del jugador humano en la siguiente animación) en sus propios tableros. estos "aviones" se colocan al azar, solo visibles para los propietarios del tablero y escondidas para sus oponentes.
- El jugador humano y el agente se turnan para golpear una celda del tablero del otro. El jugador puede presionar cualquier celda en el tablero del agente, mientras que el agente tomará la decisión automáticamente en función de la predicción de un modelo de aprendizaje automático. La celda que intentas hacer se vuelve roja si es un "avión". celda ("hit") de lo contrario, se vuelve amarillo ("miss").
- Quien consiga 8 celdas rojas primero gana el juego; y, luego, el juego se reinicia con tableros nuevos.
Este es un ejemplo del juego:
3. Configura tu entorno de desarrollo de Flutter
Para el desarrollo de Flutter, necesitas dos tipos de software para completar este codelab: el SDK de Flutter y un editor.
Puedes ejecutar el codelab con cualquiera de estos dispositivos o modalidades:
- El simulador de iOS (requiere instalar las herramientas de Xcode)
- Android Emulator (requiere configuración en Android Studio)
- Un navegador (se requiere Chrome para la depuración)
- Como una aplicación para computadoras que ejecuten Windows, Linux o macOS (debes desarrollarla en la plataforma donde tengas pensado realizar la implementación; por lo tanto, si quieres desarrollar una app de escritorio para Windows, debes desarrollarla en ese SO a fin de obtener acceso a la cadena de compilación correcta; encuentra detalles sobre los requisitos específicos del sistema operativo en docs.flutter.dev/desktop).
4. Prepárate
Para descargar el código de este codelab, haz lo siguiente:
- Navega al repositorio de GitHub de este codelab.
- Haz clic en Code > Download ZIP para descargar todo el código de este codelab.
- Descomprime el archivo ZIP descargado para desempaquetar una carpeta raíz
codelabs-main
con todos los recursos que necesitas.
Para este codelab, solo necesitas los archivos del subdirectorio tfagents-flutter/
del repositorio, que contiene varias carpetas:
- Las carpetas
step0
astep6
contienen el código de partida en el que se basa cada paso de este codelab. - La carpeta
finished
contiene el código completado de la app de ejemplo finalizada. - Cada carpeta contiene una subcarpeta
backbend
, que incluye el código de backend y una subcarpetafrontend
, que incluye el código del frontend de Flutter.
5. Descarga las dependencias del proyecto
Backend
Abre la terminal y ve a la subcarpeta tfagents-flutter
. Ejecuta lo siguiente:
pip install -r requirements.txt
Frontend
- En VS Code, haz clic en File > Abre la carpeta y, luego, selecciona la carpeta
step0
del código fuente que descargaste antes. - Abre el archivo
step0/frontend/lib/main.dart
. Si aparece un diálogo de VS Code en el que se te solicita que descargues los paquetes necesarios para la app de partida, haz clic en Get packages. - Si no ves este diálogo, abre la terminal y, luego, ejecuta el comando
flutter pub get
en la carpetastep0/frontend
.
6. Paso 0: Ejecuta la app de partida
- Abre el archivo
step0/frontend/lib/main.dart
en VS Code y asegúrate de que Android Emulator o el simulador de iOS estén configurados correctamente y aparezcan en la barra de estado.
Por ejemplo, a continuación, se muestra lo que ves cuando usas un Pixel 5 con Android Emulator:
A continuación, se muestra lo que ves cuando usas un iPhone 13 con el simulador de iOS:
- Haz clic en Start debugging.
Ejecuta y explora la app
La app debe iniciarse en Android Emulator o el simulador de iOS. La IU es bastante sencilla. Hay 2 tableros de juego. un jugador puede presionar en la parte superior de cualquier celda del tablero del agente como una posición de golpe. Entrenarás a un agente inteligente para que prediga automáticamente dónde golpear según el tablero del jugador humano.
De forma interna, la app de Flutter enviará el tablero actual del jugador humano al backend, que ejecuta un modelo de aprendizaje por refuerzo y muestra una posición prevista para la celda a continuación. El frontend mostrará el resultado en la IU después de recibir la respuesta.
Si haces clic en cualquier celda del panel del agente ahora, no sucederá nada porque la app aún no puede comunicarse con el backend.
7. Paso 1: Crea un entorno de Python para agentes de TensorFlow
El objetivo principal de este codelab es diseñar un agente que aprenda interactuando con un entorno. Si bien el juego de Plane Strike es relativamente simple y es posible elaborar reglas para el agente de NPC, puedes usar el aprendizaje por refuerzo para entrenar a un agente, de modo que aprendas las habilidades y puedas crear agentes fácilmente para otros juegos en el futuro.
En la configuración estándar de aprendizaje por refuerzo (RL), el agente recibe una observación en cada paso y elige una acción. La acción se aplica al entorno y este devuelve una recompensa y una nueva observación. El agente entrena una política para elegir acciones a fin de maximizar la suma de recompensas, también conocida como devolución. Al jugar muchas veces el juego, el agente puede aprender los patrones y perfeccionar sus habilidades para dominar el juego. Para formular el juego de Plane Strike como un problema de RL, piensa en el estado del tablero como la observación, la posición de una huelga como la acción y la señal de golpe/error como recompensa.
Para entrenar al agente de NPC, debes aprovechar TensorFlow Agents, una biblioteca de aprendizaje por refuerzo confiable, escalable y fácil de usar para TensorFlow.
TF Agents es excelente para el aprendizaje por refuerzo porque cuenta con un amplio conjunto de codelabs, ejemplos y una amplia documentación para que puedas comenzar. Puedes usar TF Agents para resolver problemas de RL realistas y complejos con escalabilidad y desarrollar nuevos algoritmos de RL con rapidez. Puedes intercambiar con facilidad entre distintos agentes y algoritmos para la experimentación. También está bien probado y es fácil de configurar.
Hay muchos entornos de juego compilados previamente que se implementaron en el Gym de OpenAI (p.ej., juegos de Atari), Mujuco, etc., que los agentes de TF pueden aprovechar fácilmente. Pero como el juego Plane Strike es un juego completamente personalizado, primero debes implementar un nuevo entorno desde cero.
Para implementar un entorno de Python de TF Agents, debes implementar los siguientes 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."""
La más importante es la función _step()
, que realiza una acción y muestra un nuevo objeto time_step
. En el caso del juego Plane Strike, tienes un tablero de juego; Cuando aparece una nueva posición de falta, en función de la condición del tablero de juego, el entorno descubre lo siguiente:
- Cómo debería verse el tablero de juego a continuación (¿la celda debería cambiar de color a rojo o amarillo según la ubicación del plano oculto?)
- ¿Qué recompensa debe recibir el jugador por esa posición (recompensa por golpe o penalización fallida?)
- ¿El juego debería terminar? (¿Alguien ganó?)
- Agrega el siguiente código a la función
_step()
en el archivo_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. Paso 2: Entrena al agente de juego con agentes de TensorFlow
Con el entorno de agentes de TF implementado, puedes entrenar al agente de juego. En este codelab, usarás un agente de REFUERZO. REINFORCE es un algoritmo de gradiente de políticas en RL. Su idea básica es ajustar los parámetros de la red neuronal de política en función de los indicadores de recompensa recopilados durante el juego, de modo que la red de políticas pueda maximizar el retorno en futuras jugadas.
- Primero, debes crear una instancia de los entornos de entrenamiento y evaluación. Agrega este código a la función
train_agent()
en el archivostep2/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)
- A continuación, debes crear un agente de aprendizaje por refuerzo que se entrenará. En este codelab, usarás el agente REFUERZO, que es un agente basado en políticas. Agrega este código justo debajo del código anterior:
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 último, entrena al agente en el ciclo de entrenamiento. En el bucle, primero se recopilan algunos episodios de jugadas en un búfer y, luego, se entrena al agente con los datos almacenados en el búfer. Agrega este código a la función
train_agent()
en el archivostep2/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()
- Ahora puedes iniciar la capacitación. En tu terminal, ve a la carpeta
step2/backend
de tu computadora y ejecuta lo siguiente:
python training.py
El entrenamiento tarda entre 8 y 12 horas en completarse, según la configuración de hardware (no es necesario que termines todo el entrenamiento por tu cuenta, ya que en step3
se proporciona un modelo previamente entrenado). Mientras tanto, puedes supervisar el progreso con TensorBoard. Abre una terminal nueva, ve a la carpeta step2/backend
en tu computadora y ejecuta lo siguiente:
tensorboard --logdir tf_agents_log/
tf_agents_log
es la carpeta que contiene el registro de entrenamiento. A continuación, se muestra un ejemplo de una ejecución de entrenamiento:
Puedes ver que la duración promedio de los episodios disminuye y el retorno promedio aumenta a medida que avanza el entrenamiento. De manera intuitiva, puedes comprender que si el agente es más inteligente y hace mejores predicciones, la duración del juego se reduce y el agente junta más recompensas. Esto tiene sentido, ya que el agente quiere terminar el juego en menos pasos para minimizar los grandes descuentos en recompensas en los pasos posteriores.
Cuando finaliza el entrenamiento, el modelo entrenado se exporta a la carpeta policy_model
.
9. Paso 3: Implementa el modelo entrenado con TensorFlow Serving
Ahora que entrenaste el agente del juego, puedes implementarlo con TensorFlow Serving.
- En tu terminal, ve a la carpeta
step3/backend
en tu computadora y, luego, inicia TensorFlow Serving con 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
Docker descarga automáticamente la imagen de TensorFlow Serving primero, lo cual tarda un minuto. Luego, TensorFlow Serving debería iniciarse. El registro debería verse como este fragmento 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 ...
Puedes enviar una solicitud de muestra al extremo para asegurarte de que funcione según lo 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
El extremo mostrará una posición predicha 45
, que es (5, 5) en el centro del tablero (para los curiosos, puedes intentar averiguar por qué el centro del tablero es una buena suposición para la primera posición de golpe).
{ "predictions": [45] }
Eso es todo. Compilaste correctamente un backend para predecir la siguiente posición de falta del agente de NPC.
10. Paso 4: Crea la app de Flutter para iOS y Android
El backend está listo. Puedes comenzar a enviarle solicitudes para recuperar predicciones de posición de faltas desde la app de Flutter.
- Primero, debes definir una clase que una las entradas que se enviarán. Agrega este código al archivo
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; } }
Ahora puedes enviar la solicitud a TensorFlow Serving para hacer predicciones.
- Agrega este código a la función
predict()
en el archivostep4/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'); }
Una vez que la app recibe la respuesta del backend, debes actualizar la IU del juego para que refleje el progreso.
- Agrega este código a la función
_gridItemTapped()
en el archivostep4/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(() {});
Ejecuta la app
- Haz clic en Start debugging y espera a que se cargue la app.
- Presiona cualquier celda en el tablero del agente para iniciar el juego.
11. Paso 5: Habilita la app de Flutter para las plataformas de escritorio
Además de iOS y Android, Flutter también admite plataformas de escritorio, como Linux, Mac y Windows.
Linux
- Asegúrate de que el dispositivo de destino esté configurado como en la barra de estado de VSCode.
- Haz clic en Start debugging y espera a que se cargue la app.
- Haz clic en cualquier celda del tablero del agente para iniciar el juego.
Mac
- En Mac, deberás configurar los derechos adecuados, ya que la app enviará solicitudes HTTP al backend. Para obtener más información, consulta Derechos y zona de pruebas de apps.
Agrega este código a step4/frontend/macOS/Runner/DebugProfile.entitlements
y step4/frontend/macOS/Runner/Release.entitlements
respectivamente:
<key>com.apple.security.network.client</key>
<true/>
- Asegúrate de que el dispositivo de destino esté configurado como en la barra de estado de VSCode.
- Haz clic en Start debugging y espera a que se cargue la app.
- Haz clic en cualquier celda del tablero del agente para iniciar el juego.
Windows
- Asegúrate de que el dispositivo de destino esté configurado como en la barra de estado de VSCode.
- Haz clic en Start debugging y espera a que se cargue la app.
- Haz clic en cualquier celda del tablero del agente para iniciar el juego.
12. Paso 6: Habilita la app de Flutter para la plataforma web
Algo más que puedes hacer es agregar compatibilidad web a la app de Flutter. De forma predeterminada, la plataforma web se habilita automáticamente para las apps de Flutter, por lo que solo debes iniciarla.
- Asegúrate de que el dispositivo de destino esté configurado como en la barra de estado de VSCode.
- Haz clic en Start debugging y espera a que la app se cargue en el navegador Chrome.
- Haz clic en cualquier celda del tablero del agente para iniciar el juego.
13. Felicitaciones
Creaste una aplicación de juego de mesa con un agente con tecnología de AA para jugar contra los jugadores.