使用 TensorFlow Agents 和 Flutter 构建棋盘游戏

1. 准备工作

AlphaGoAlphaStar 的惊人突破展现出了使用机器学习技术构建超人级游戏代理的潜力。这是一个有趣的练习,可以构建一个由机器学习提供支持的小型游戏,学习创建强大的游戏代理所需的技能。

在此 Codelab 中,您将学习如何使用以下工具构建棋盘游戏:

  • 使用 TensorFlow Agent 通过强化学习来训练游戏代理
  • TensorFlow Serving 以提供模型
  • 利用 Flutter 打造跨平台棋盘游戏应用

前提条件

  • 了解有关使用 Dart 开发 Flutter 应用的基本知识
  • 了解有关使用 TensorFlow 进行机器学习(例如训练与部署)的基本知识
  • 具备 Python、终端和 Docker 方面的基础知识

学习内容

  • 如何使用 TensorFlow Agents 训练非玩家角色 (NPC) 代理
  • 如何使用 TensorFlow Serving 应用经过训练的模型
  • 如何构建跨平台 Flutter 棋盘游戏

所需条件

2. 飞机撞击游戏

您在此 Codelab 中构建的游戏名为“Plane Strike”,这是一款类似于“Battleship”的棋盘游戏的小型双人棋盘游戏。规则非常简单:

  • 真人玩家将与经过机器学习训练的 NPC 代理对战。真人玩家可以通过点按特工面板上的任意单元格来开始游戏。
  • 在游戏开始时,人类玩家和代理各自都有一架“飞机”对象(8 个绿色单元格组成一个“平面”,如下动画所示,在人类玩家的棋盘上可以看到);这些“飞机”是随机放置的,只对棋盘所有者可见,对对手隐藏。
  • 真人玩家和特工轮流攻击对方棋盘上的一个单元格。真人玩家可以点按智能体面板中的任意单元格,而智能体会根据机器学习模型的预测自动做出选择。如果尝试的单元格为“平面”,则会变为红色单元格(“匹配”)否则变成黄色(“miss”)。
  • 先拿到 8 个红细胞的人即获胜;然后用全新棋盘重新开始游戏。

以下是该游戏的示例玩法:

77cead530c5a4aff.gif

3. 设置您的 Flutter 开发环境

对于 Flutter 开发,您需要使用两款软件才能完成此 Codelab:Flutter SDK一款编辑器

您可使用以下任一设备学习此 Codelab:

  • iOS 模拟器(需要安装 Xcode 工具)。
  • Android 模拟器(需要在 Android Studio 中设置)。
  • 浏览器(需要使用 Chrome,以便进行调试)。
  • 对于 WindowsLinuxmacOS 桌面应用,您必须在打算部署到的平台上进行开发。因此,如果您要开发 Windows 桌面应用,则必须在 Windows 上进行开发,才能使用相应的构建链。如需详细了解针对各种操作系统的具体要求,请访问 docs.flutter.dev/desktop

4. 进行设置

如需下载此 Codelab 的代码,请执行以下操作:

  1. 找到此 Codelab 的 GitHub 代码库
  2. 点击 Code(代码)> Download zip(下载 Zip 文件),下载此 Codelab 的所有代码。

2cd45599f51fb8a2.png

  1. 解压缩下载的 ZIP 文件,这会解压缩 codelabs-main 根文件夹,其中包含您需要的所有资源。

对于此 Codelab,您只需要代码库的 tfagents-flutter/ 子目录(其中包含多个文件夹)中的文件:

  • step0step6 文件夹包含您在此 Codelab 中每一步进行构建的起始代码。
  • finished 文件夹包含已完成示例应用的完成后的代码。
  • 每个文件夹都包含一个 backbend 子文件夹(包含后端代码)和一个 frontend 子文件夹(包含 Flutter 前端代码)。

5. 下载项目的依赖项

后端

打开终端并进入 tfagents-flutter 子文件夹。运行以下命令:

pip install -r requirements.txt

前端

  1. 在 VS Code 中,点击 File >打开文件夹,然后从您之前下载的源代码中选择 step0 文件夹。
  2. 打开 step0/frontend/lib/main.dart 文件。如果您看到一个 VS Code 对话框,提示您下载起始应用所需的软件包,请点击 Get packages(获取软件包)。
  3. 如果您没有看到此对话框,请打开终端,然后在 step0/frontend 文件夹中运行 flutter pub get 命令。

7ada07c300f166a6.png

6. 第 0 步:运行起始应用

  1. 在 VS Code 中打开 step0/frontend/lib/main.dart 文件,确保 Android 模拟器或 iOS 模拟器已正确设置并显示在状态栏中。

例如,当您将 Pixel 5 与 Android 模拟器搭配使用时,会看到以下内容:

9767649231898791.png

当您将 iPhone 13 与 iOS 模拟器搭配使用时,会看到以下内容:

95529e3a682268b2.png

  1. 点击 a19a0c68bc4046e6.png Start debugging(开始调试)。

运行和探索应用

应用应在 Android 模拟器或 iOS 模拟器上启动。界面非常简单。共有 2 个游戏棋盘:人类玩家可以点按特工面板的顶部作为攻击位置。您将训练一个智能代理,让其根据真人玩家的棋盘自动预测要攻击哪里。

在后台,Flutter 应用会将真人玩家的当前开发板发送到后端,后端会运行一个强化学习模型并返回预测的细胞位置,以便接下来发动攻击。前端在收到响应后将在界面中显示结果。

734ab3d48a1133e1 15cba2e741149c95

如果您现在点击代理面板中的任何单元格,什么都不会发生,因为应用还不能与后端通信。

7. 第 1 步:创建 TensorFlow Agents Python 环境

此 Codelab 的主要目标是设计一个通过与环境交互进行学习的代理。虽然《Plane Strike》游戏相对简单,可以手动为 NPC 特工制定规则,但您可以使用强化学习来训练特工,以便学习相关技能,并在日后轻松为其他游戏构建特工。

在标准强化学习 (RL) 设置中,代理在每个时间步都会收到一个观察结果,并选择一个动作。相应操作会应用于环境,而环境会返回奖励和新的观察结果。智能体会训练一个策略,以选择能最大限度提高奖励总和(也称为回报率)的操作。通过多次玩游戏,智能体能够学习规律并磨炼游戏技能,从而掌握游戏玩法。若要将“Plane Strike”游戏表述为 RL 问题,可以将图板状态视为观察,将攻击位置视为操作,将击中/未命中信号视为奖励。

bc5da07bc45062f4.png

如需训练 NPC 代理,您可以使用 TensorFlow Agents,这是一个可靠、可扩缩且易于使用的 TensorFlow 强化学习库。

TF Agents 非常适用于强化学习,因为它提供了丰富的 Codelab、示例和丰富的文档来帮助您入门。您可以使用 TF Agents 来解决具有可伸缩性的现实复杂 RL 问题,并快速开发新的 RL 算法。您可以轻松切换不同的代理和算法来进行实验。此外,它还通过了充分测试,并且易于配置。

OpenAI Gym 中实现了许多预构建的游戏环境(例如,Atari 游戏)、Mujuco 等,TF Agent 可轻松利用它们。但由于《Plane Strike》游戏是一款完全自定义的游戏,因此您需要先从头开始实现一个新环境。

如需实现 TF Agents Python 环境,您需要实现以下方法:

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."""

最重要的函数是 _step() 函数,该函数接受操作并返回新的 time_step 对象。在《Plane Strike》游戏中,您有一个游戏棋盘;当出现新的攻击位置时,环境会根据棋盘状况判断出:

  • 游戏图板接下来应该是什么样子(鉴于隐藏的平面位置,该单元格是否应将其颜色更改为红色或黄色?)
  • 对于该位置,玩家应获得什么奖励(达成奖励或错过点球大战?)
  • 游戏是否应终止(有人赢了吗?)
  • 将以下代码添加到 _step() 函数的 _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. 第 2 步:使用 TensorFlow Agents 训练游戏代理

准备好 TF Agents 环境后,您可以训练游戏代理。在此 Codelab 中,您将使用 REINFORCE 代理。REINFORCE 是 RL 中的策略梯度算法。其基本思路是,根据在游戏过程中收集的奖励信号来调整策略神经网络参数,以便策略网络可以在未来的游戏中最大限度地提高回报。

  • 首先,您需要实例化训练和评估环境。将以下代码添加到 step2/backend/training.py 文件中的 train_agent() 函数:
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)
  • 接下来,您需要创建一个即将接受训练的强化学习代理。在此 Codelab 中,您将使用 REINFORCE 代理,它是一个基于政策的代理。将以下代码添加到上述代码的正下方:
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,
)
  • 最后,在训练循环中训练该代理。在循环中,您首先将几个游戏剧集收集到一个缓冲区中,然后使用缓冲的数据训练代理。将以下代码添加到 step2/backend/training.py 文件中的 train_agent() 函数:
# 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()
  • 现在,您可以开始培训了。在终端中,转到计算机上的 step2/backend 文件夹,然后运行以下命令:
python training.py

完成训练需要 8-12 小时,具体取决于您的硬件配置(您无需自行完成整个训练,因为 step3 中提供了预训练模型)。在此期间,您可以使用 TensorBoard 监控进度。打开一个新的终端,转到计算机上的 step2/backend 文件夹,然后运行以下命令:

tensorboard --logdir tf_agents_log/

tf_agents_log 是包含训练日志的文件夹。训练运行示例如下所示:

33e12e2b387c063a 8488632ccf43348a

您可以看到,随着训练的进行,平均剧集长度会减少,平均回报率也会增加。直观地说,您可以理解,如果智能体更聪明、预测能力更强,游戏时长就会变短,并且智能体可以收集到更多奖励。这是合理的,因为代理希望以更少的步骤完成游戏,从而最大限度地减少在后续步骤中提供沉重奖励折扣。

训练完成后,经过训练的模型将导出到 policy_model 文件夹。

9. 第 3 步:使用 TensorFlow Serving 部署经过训练的模型

现在,您已经训练了游戏代理,接下来可以使用 TensorFlow Serving 部署游戏代理了。

  • 在终端中,转到计算机上的 step3/backend 文件夹,然后启动带有 Docker 的 TensorFlow Serving:
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 会先自动下载 TensorFlow Serving 映像,此过程需要一分钟时间。之后,TensorFlow Serving 便会启动。日志应如下面的代码段所示:

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 ...

您可以向端点发送示例请求,以确保其按预期工作:

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

端点会返回一个预测位置 45,也就是 (5, 5) 在棋盘中央(出于好奇心,你可以尝试弄清楚为什么棋盘中心适合在棋盘中央做出第一次警示位置)。

{
    "predictions": [45]
}

大功告成!你已成功构建了一个后端,可以预测 NPC 代理的下一次警示位置了。

10. 第 4 步:创建适用于 Android 和 iOS 的 Flutter 应用

后端已准备就绪。您可以开始向其发送请求,以从 Flutter 应用检索警示位置预测。

  • 首先,您需要定义一个类,用于封装要发送的输入。将以下代码添加到 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;
  }
}

现在,您可以向 TensorFlow Serving 发送请求以进行预测。

  • 将以下代码添加到 step4/frontend/lib/game_agent.dart 文件中的 predict() 函数:
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');
}

当应用收到来自后端的响应后,您就可以更新游戏界面以反映游戏进度。

  • 将以下代码添加到 step4/frontend/lib/main.dart 文件中的 _gridItemTapped() 函数:
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(() {});

运行应用

  1. 点击 a19a0c68bc4046e6.png Start debugging(开始调试),然后等待应用加载。
  2. 点按特工面板上的任意单元格即可开始游戏。

852942d0de299c1f 6ae3601470c8e33a

11. 第 5 步:为桌面平台启用 Flutter 应用

除了 Android 和 iOS 之外,Flutter 还支持包括 Linux、Mac 和 Windows 在内的桌面平台。

Linux

  1. 确保目标设备在 VSCode 的状态栏中设置为 86cba523de82b4f9
  2. 点击 a19a0c68bc4046e6.png Start debugging(开始调试),然后等待应用加载。
  3. 点击代理方阵中的任意一个单元格即可开始游戏。

48594c7c0a589733

Mac

  1. 对于 Mac,您需要设置适当的使用权,因为应用会向后端发送 HTTP 请求。如需了解详情,请参阅使用权和应用沙盒

将以下代码分别添加到 step4/frontend/macOS/Runner/DebugProfile.entitlementsstep4/frontend/macOS/Runner/Release.entitlements

<key>com.apple.security.network.client</key>
<true/>
  1. 确保目标设备在 VSCode 的状态栏中设置为 eb4b0b5563824138.png
  2. 点击 a19a0c68bc4046e6.png Start debugging(开始调试),然后等待应用加载。
  3. 点击代理方阵中的任意一个单元格即可开始游戏。

55a5de3674194e89

Windows

  1. 确保目标设备在 VSCode 的状态栏中设置为 9587be1bb375bc0f
  2. 点击 a19a0c68bc4046e6.png Start debugging(开始调试),然后等待应用加载。
  3. 点击代理方阵中的任意一个单元格即可开始游戏。

41d9f87d84c5e755

12. 第 6 步:为 Web 平台启用 Flutter 应用

您还可以向 Flutter 应用添加 Web 支持。默认情况下,系统会自动为 Flutter 应用启用 Web 平台,因此您只需启动该应用即可。

  1. 确保目标设备在 VSCode 的状态栏中设置为 71db93efa928d15d
  2. 点击 a19a0c68bc4046e6.png Start debugging(开始调试),然后等待应用在 Chrome 浏览器中加载。
  3. 点击代理方阵中的任意一个单元格即可开始游戏。

fae7490304e28dfe.png

13. 恭喜

您利用基于机器学习的代理构建了一个棋盘游戏应用,用来与真人玩家对战!

了解详情