1. 事前準備
AlphaGo 和 AlphaStar 正展現創新突破,展現運用機器學習技術來打造超人遊戲虛擬服務專員的潛力。如果想打造一個採用機器學習技術的小型遊戲,藉此培養強大的遊戲虛擬服務專員,這項有趣的練習效果會比較好。
在本程式碼研究室中,您將瞭解如何使用以下項目建構桌遊:
- TensorFlow Agent 透過增強學習功能訓練遊戲代理
- 使用 TensorFlow Serving 提供模型
- 利用 Flutter 建構跨平台桌遊應用程式
必要條件
- 使用 Dart 進行 Flutter 開發的基本知識
- 使用 TensorFlow 進行機器學習的基本知識,例如訓練和部署
- 對 Python、終端機和 Docker 有基本瞭解
課程內容
- 如何使用 TensorFlow 代理程式訓練非玩家角色 (NPC) 代理程式
- 如何使用 TensorFlow Serving 提供經過訓練的模型
- 如何打造跨平台的 Flutter 桌遊
軟硬體需求
- Flutter SDK
- Flutter 適用的 Android 和 iOS 設定
- Flutter 的電腦設定
- Flutter 的網路設定
- 適用於 Flutter 和 Dart 的 Visual Studio Code (VS Code) 設定
- Docker
- Bash
- Python 3.7 以上版本
2. 戰艦遊戲
您在本程式碼研究室中建構的遊戲稱為「Plane Strike」,這是與桌遊《Battleship》類似的桌遊。規則非常簡單:
- 真人玩家與透過機器學習技術訓練的 NPC 虛擬服務專員對戰。人類玩家可以輕觸虛擬服務專員遊戲板中的任何儲存格來啟動遊戲。
- 在遊戲開始時,人類和虛擬服務專員都有一個「飛機」自己的遊戲板上物件 (下方動畫中人類玩家棋盤中可看出 8 個綠色儲存格),這些細胞會形成一個「飛機」;這些「飛機」會隨機放置,只有董事會擁有者才看得到,不看對手。
- 人類和服務專員會輪流在彼此的棋盤格上攻擊。真人玩家可以輕觸服務專員格局上的任何細胞,專員會根據機器學習模型的預測結果自動進行選擇。如果嘗試輸入的儲存格是「飛機」,就會變成紅色儲存格 (‘hit');否則就會變成黃色 (「miss」)。
- 每達成 8 個紅色細胞都優先贏得遊戲。遊戲就會以新遊戲板重新開始
以下是遊戲的遊戲玩法範例:
3. 設定 Flutter 開發環境
進行 Flutter 開發作業時,需要有兩件軟體才能完成這個程式碼研究室:Flutter SDK 和編輯器。
您可以使用下列任一裝置執行程式碼研究室:
- iOS 模擬器 (需要安裝 Xcode 工具)。
- Android Emulator (需要在 Android Studio 中設定)。
- 瀏覽器 (必須使用 Chrome 進行偵錯)。
- 下載 Windows、Linux 或 macOS 桌面應用程式。您必須在要部署的平台上進行開發。因此,如果您想要開發 Windows 電腦版應用程式,就必須在 Windows 上進行開發,以便存取適當的建構鏈結。如要進一步瞭解作業系統的特定需求,請參閱 docs.flutter.dev/desktop。
4. 做好準備
如要下載本程式碼研究室的程式碼:
- 前往本程式碼研究室的 GitHub 存放區。
- 按一下「程式碼」>下載 ZIP 檔案即可下載這個程式碼研究室的所有程式碼。
- 解壓縮下載的 ZIP 檔案,將包含所需所有資源的
codelabs-main
根資料夾解壓縮。
在本程式碼研究室中,您只需要位於存放區內 tfagents-flutter/
子目錄中的檔案,該檔案包含多個資料夾:
step0
到step6
資料夾內含您可以依本程式碼研究室中每個步驟建構的範例程式碼。finished
資料夾包含已完成的範例應用程式的程式碼。- 每個資料夾都含有一個
backbend
子資料夾 (內含後端程式碼) 和一個frontend
子資料夾,其中含有 Flutter 前端程式碼
5. 下載專案的依附元件
後端
開啟終端機並前往 tfagents-flutter
子資料夾。執行以下指令:
pip install -r requirements.txt
前端
- 在 VS Code 中,按一下「File」(檔案) > 開啟資料夾,然後從先前下載的原始碼中選取
step0
資料夾。 - 開啟
step0/frontend/lib/main.dart
檔案如果系統顯示 VS Code 對話方塊,提示您下載範例應用程式所需的套件,請按一下「Get package」(取得套件)。 - 如果沒有看到這個對話方塊,請開啟終端機,然後在
step0/frontend
資料夾中執行flutter pub get
指令。
6. 步驟 0:執行範例應用程式
- 在 VS Code 中開啟
step0/frontend/lib/main.dart
檔案,確認 Android Emulator 或 iOS 模擬器已正確設定,且會顯示在狀態列中。
舉例來說,搭配 Android Emulator 使用 Pixel 5 時,畫面上顯示的內容如下:
搭載 iOS 模擬器的 iPhone 13 會出現以下內容:
- 按一下「開始偵錯」圖示 。
執行並探索應用程式
應用程式應該會在 Android Emulator 或 iOS 模擬器上啟動。使用者介面非常簡單,共有 2 個遊戲板。人類玩家可以輕觸頂端探員板上的任何細胞作為攻擊位置。您將訓練智慧型虛擬服務專員,根據人類玩家的遊戲板自動預測攻擊點。
實際上,Flutter 應用程式會將人類玩家目前的遊戲板傳送到後端,後端執行強化學習模型,並傳回預測的細胞位置,直到下一個出現攻擊的情況。前端會在收到回應後在 UI 中顯示結果。
現在點選代理程式面板中的任何儲存格,系統目前不會執行任何動作,因為應用程式目前無法與後端通訊。
7. 步驟 1:建立 TensorFlow 代理程式 Python 環境
本程式碼研究室的主要目標,是設計能藉由與環境互動來學習的代理程式。雖然 Plane Strike 遊戲相對簡單,也可以為 NPC 服務專員的手工藝規則,但您可以使用強化學習來訓練代理程式,這樣您將會瞭解這些技能,也能輕鬆為日後其他遊戲的虛擬服務專員建立代理程式。
在標準強化學習 (RL) 設定中,代理程式會在每個步驟每次都獲得觀察結果並選擇動作。動作套用至環境後,環境就會傳回獎勵和新的觀察結果。服務專員會訓練政策來選擇動作,盡可能提高獎勵總額,也稱為退貨。在多次玩遊戲的情況下,虛擬服務專員能夠學習模式,並磨練掌握遊戲的技巧。如要設計「飛機擊球」遊戲做為 RL 問題,請將棋盤狀態視為觀察結果,並以提示位置做為獎勵,而命中/失誤信號則視為獎勵。
如要訓練 NPC 虛擬服務專員,你可以使用 TensorFlow Agents,這個可靠、可擴充且簡單易用的 TensorFlow 強化學習程式庫。
TF Agents 提供一系列程式碼研究室、範例和詳盡的說明文件,非常適合強化學習。您可以使用 TF Agents 具備擴充性,解決真實複雜的 RL 問題,並快速開發新的 RL 演算法。您可以輕鬆切換不同的代理程式和演算法來進行實驗。此外,也經過完整測試且易於設定。
OpenAI Gym 目前部署了許多預先建構的遊戲環境 (例如Atari 遊戲)、Mujuco 等,這些 TF 代理程式可輕鬆運用。但《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
物件。以《星際大戰》遊戲中,您有一個遊戲棋盤;出現新攻擊位置時,環境會根據遊戲板的狀況找出以下現象:
- 接著,遊戲面板看起來會是什麼樣子 (如果儲存格中的隱藏飛機位置,將顏色改成紅色或黃色?)
- 玩家在該立場應獲得什麼獎勵 (達成目標的獎勵或失誤?)
- 比賽應該終止 (誰贏了?)
- 將下列程式碼新增至
_planestrike_py_environment.py
檔案的_step()
函式中:
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 代理程式訓練遊戲代理程式
有了 TF Agents 環境,您就可以訓練遊戲代理程式。在本程式碼研究室中,您將使用 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)
- 接下來,您需要建立一個要接受訓練的強化學習虛擬服務專員。在本程式碼研究室中,您將使用 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
是包含訓練記錄的資料夾。訓練執行作業的範例如下所示:
您可以看到單集節目的平均長度減少,平均報酬率則隨著訓練進展而增加。直觀來說,如果服務專員聰明且能做出更好的預測,遊戲持續時間就會變短,服務專員可以收集更多獎勵。這個情況很合理,因為服務專員想要以更少的步驟結束遊戲,盡量避免後續步驟中的大量獎勵折扣。
訓練完成後,訓練過的模型會匯出至 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'); }
應用程式從後端收到回應後,您就可以更新遊戲 UI 來反映遊戲進度。
- 請將以下程式碼新增至
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(() {});
開始執行
- 按一下「Start debugging」圖示 ,然後等待應用程式載入。
- 輕觸服務專員板中的任何儲存格,即可開始遊戲。
11. 步驟 5:啟用電腦版平台的 Flutter 應用程式
除了 Android 和 iOS 之外,Flutter 也支援電腦平台,包括 Linux、Mac 和 Windows。
Linux
- 確認 VSCode 狀態列中的目標裝置已設為 。
- 按一下「Start debugging」圖示 ,然後等待應用程式載入。
- 按一下服務專員面板中的任一儲存格,即可開始遊戲。
Mac
- 在 Mac 中,由於應用程式會將 HTTP 要求傳送至後端,因此您必須設定適當的授權。詳情請參閱「授權與應用程式沙箱」。
請分別將此程式碼新增至 step4/frontend/macOS/Runner/DebugProfile.entitlements
和 step4/frontend/macOS/Runner/Release.entitlements
:
<key>com.apple.security.network.client</key>
<true/>
- 確認 VSCode 狀態列中的目標裝置已設為 。
- 按一下「Start debugging」圖示 ,然後等待應用程式載入。
- 按一下服務專員面板中的任一儲存格,即可開始遊戲。
Windows
- 確認 VSCode 狀態列中的目標裝置已設為 。
- 按一下「Start debugging」圖示 ,然後等待應用程式載入。
- 按一下服務專員面板中的任一儲存格,即可開始遊戲。
12. 步驟 6:為網路平台啟用 Flutter 應用程式
您還可以在 Flutter 應用程式中新增網頁支援。根據預設,Flutter 應用程式會自動啟用網路平台,你只需啟動這個平台即可。
- 確認 VSCode 狀態列中的目標裝置已設為 。
- 按一下「開始偵錯」圖示 ,然後等待 Chrome 瀏覽器載入應用程式。
- 按一下服務專員面板中的任一儲存格,即可開始遊戲。
13. 恭喜
您建構了一款採用機器學習技術的虛擬服務專員,用來與人類玩家對戰的桌遊應用程式!