深度強化學習課程文件
快速入門
並獲得增強的文件體驗
開始使用
入門:
要開始,請從這裡下載專案(點選GDRL-IL-Project.zip旁邊的下載圖示)。zip檔案包含“Starter”和“Complete”專案。
遊戲程式碼已在 starter 專案中實現,並且節點已配置。我們將重點關注:
- 實現 AIController 節點的程式碼,
- 記錄專家演示,
- 訓練代理並匯出 .onnx 檔案,我們可以在 Godot 中使用該檔案進行推理。
在 Godot 中開啟入門專案
解壓 zip 檔案,開啟 Godot,點選“匯入”,然後導航到解壓後的存檔中的Starter\Godot資料夾。
開啟機器人場景
這個場景包含幾個不同的節點,包括robot節點,它包含機器人的視覺形狀,CameraXRotation節點用於在人工控制模式下使用滑鼠旋轉相機“上下”。AI 代理不控制此節點,因為它對於學習任務不是必需的。RaycastSensors節點包含兩個 Raycast 感測器,可幫助代理“感知”遊戲世界的某些部分,包括牆壁、地板等。
點選 AIController3D 旁邊的卷軸以開啟指令碼進行編輯
將 get_obs() 和 get_reward() 方法替換為以下實現:
func get_obs() -> Dictionary:
var observations: Array[float] = []
for raycast_sensor in raycast_sensors:
observations.append_array(raycast_sensor.get_observation())
var level_size = 16.0
var chest_local = to_local(chest.global_position)
var chest_direction = chest_local.normalized()
var chest_distance = clampf(chest_local.length(), 0.0, level_size)
var lever_local = to_local(lever.global_position)
var lever_direction = lever_local.normalized()
var lever_distance = clampf(lever_local.length(), 0.0, level_size)
var key_local = to_local(key.global_position)
var key_direction = key_local.normalized()
var key_distance = clampf(key_local.length(), 0.0, level_size)
var raft_local = to_local(raft.global_position)
var raft_direction = raft_local.normalized()
var raft_distance = clampf(raft_local.length(), 0.0, level_size)
var player_speed = player.global_basis.inverse() * player.velocity.limit_length(5.0) / 5.0
(
observations
.append_array(
[
chest_direction.x,
chest_direction.y,
chest_direction.z,
chest_distance,
lever_direction.x,
lever_direction.y,
lever_direction.z,
lever_distance,
key_direction.x,
key_direction.y,
key_direction.z,
key_distance,
raft_direction.x,
raft_direction.y,
raft_direction.z,
raft_distance,
raft.movement_direction_multiplier,
float(player._is_lever_pulled),
float(player._is_chest_opened),
float(player._is_key_collected),
float(player.is_on_floor()),
player_speed.x,
player_speed.y,
player_speed.z,
]
)
)
return {"obs": observations}
func get_reward() -> float:
return reward在get_obs()中,我們首先從檢查器中新增到AIController3D節點的兩個 Raycast 感測器獲取觀測值,並將它們新增到觀測值中,然後獲取到寶箱、槓桿、鑰匙和木筏的相對位置向量,我們將其分為方向和距離,然後也將它們新增到觀測值中。
我們還將其他遊戲狀態資訊新增到觀測值中
- 槓桿是否已被拉動,
- 鑰匙是否已收集,
- 寶箱是否已開啟,
- 玩家是否在地板上(也決定玩家是否可以跳躍),
- 玩家的歸一化區域性速度。
我們將布林值(如_is_lever_pulled)轉換為浮點數(0 或 1)。
在get_reward()中,我們只需返回當前獎勵。
將 _physics_process() 和 reset() 方法替換為以下實現:
func _physics_process(delta: float) -> void:
# Reset on timeout, this is implemented in parent class to set needs_reset to true,
# we are re-implementing here to call player.game_over() that handles the game reset.
n_steps += 1
if n_steps > reset_after:
player.game_over()
# In training or onnx inference modes, this method will be called by sync node with actions provided,
# For expert demo recording mode, it will be called without any actions (as we set the actions based on human input),
# For human control mode the method will not be called, so we call it here without any actions provided.
if control_mode == ControlModes.HUMAN:
set_action()
# Reset the game faster if the lever is not pulled.
steps_without_lever_pulled += 1
if steps_without_lever_pulled > 200 and (not player._is_lever_pulled):
player.game_over()
func reset():
super.reset()
steps_without_lever_pulled = 0將 get_action_space()、get_action() 和 set_action() 方法替換為以下實現:
# Defines the actions for the AI agent ("size": 2 means 2 floats for this action)
func get_action_space() -> Dictionary:
return {
"movement": {"size": 2, "action_type": "continuous"},
"rotation": {"size": 1, "action_type": "continuous"},
"jump": {"size": 1, "action_type": "continuous"},
"use_action": {"size": 1, "action_type": "continuous"}
}
# We return the action values in the same order as defined in get_action_space() (important), but all in one array
# For actions of size 1, we return 1 float in the array, for size 2, 2 floats in the array, etc.
# set_action is called just before get_action by the sync node, so we can read the newly set values
func get_action():
return [
# "movement" action values
player.requested_movement.x,
player.requested_movement.y,
# "rotation" action value
player.requested_rotation.x,
# "jump" action value (-1 if not requested, 1 if requested)
-1.0 + 2.0 * float(player.jump_requested),
# "use_action" action value (-1 if not requested, 1 if requested)
-1.0 + 2.0 * float(player.use_action_requested)
]
# Here we set human control and AI control actions to the robot
func set_action(action = null) -> void:
# If there's no action provided, it means that AI is not controlling the robot (human control),
if not action:
# Only rotate if the mouse has moved since the last set_action call
if previous_mouse_movement == mouse_movement:
mouse_movement = Vector2.ZERO
player.requested_movement = Input.get_vector(
"move_left", "move_right", "move_forward", "move_back"
)
player.requested_rotation = mouse_movement
var use_action = Input.is_action_pressed("requested_action")
var jump = Input.is_action_pressed("requested_jump")
player.use_action_requested = use_action
player.jump_requested = jump
previous_mouse_movement = mouse_movement
else:
# If there is action provided, we set the actions received from the AI agent
player.requested_movement = Vector2(action.movement[0], action.movement[1])
# The agent only rotates the robot along the Y axis, no need to rotate the camera along X axis
player.requested_rotation = Vector2(action.rotation[0], 0.0)
player.jump_requested = bool(action.jump[0] > 0)
player.use_action_requested = bool(action.use_action[0] > 0)對於get_action()(僅在使用演示記錄模式時需要),我們需要提供當代理遇到相同狀態時希望它傳送的動作。重要的是,這些值必須在正確的範圍內(-1.0 到 1.0),這就是為什麼布林狀態使用-1 + 2 * variable,並且順序正確,如get_action_space()中定義的那樣。
在演示記錄模式下,呼叫set_action()時不需要提供動作,因為我們需要根據人類輸入設定動作值。在訓練/推理模式下,呼叫此方法時會帶有一個action引數,其中包含由強化學習模型提供的所有動作的值,因此我們有一個if/else來處理這兩種情況。
更多資訊包含在程式碼註釋中。
將 _input 方法替換為以下實現:
# Record mouse movement for human and demo_record modes
# We don't directly rotate in input to allow for frame skipping (action_repeat setting) which
# will also be applied to the AI agent in training/inference modes.
func _input(event):
if not (heuristic == "human" or heuristic == "demo_record"):
return
if event is InputEventMouseMotion:
var movement_scale: float = 0.005
mouse_movement.y = clampf(event.relative.y * movement_scale, -1.0, 1.0)
mouse_movement.x = clampf(event.relative.x * movement_scale, -1.0, 1.0)此程式碼部分在人工控制和演示記錄模式下記錄滑鼠移動。
最後,儲存指令碼。我們已為下一步做好準備。
開啟演示錄製場景,然後點選 AIController3D 節點
您無需進行任何更改,因為所有內容都已預設好,但讓我們回顧一下您需要在自己的環境中設定的內容
場景包含修改後的Level > Robot > AIController3D節點設定
Control Mode設定為Record Expert DemosExpert Demo Save Path已填寫Action Repeat設定為與training_scene和onnx_inference_scene中的Sync節點相同的值。這意味著代理設定的每個動作都會重複 3 個物理幀。AIController中的設定將相同的動作重複新增到人類輸入(這會引入一些延遲)以匹配相同的行為。這是一個相當低的值,不會引入太多延遲。如果您更改此值,請務必在所有 3 個地方進行更改。Remove Last Episode鍵允許我們設定一個鍵,該鍵可用於在錄製過程中刪除失敗的劇集,而無需重新啟動整個會話。例如,如果機器人掉入水中並且遊戲重置,我們可以使用此鍵在錄製下一個劇集時刪除之前錄製的劇集。它設定為R,但您可以透過單擊它,然後單擊Configure按鈕將其更改為任何鍵。
在挑戰性環境中使情節錄制更容易的另一種方法是在錄製期間減慢環境速度。這可以透過單擊場景中的Sync節點並調整Speed Up屬性(預設為 1)輕鬆完成。
讓我們錄製一些演示:
確保您仍處於demo_record_scene中,按 F6,演示錄製將開始。
控制
- 滑鼠控制攝像機(如果您需要調整滑鼠靈敏度,開啟
robot場景,點選Robot節點並調整Rotation Speed,在錄製演示、訓練和推理時保持相同的值), WASD控制玩家移動,SPACE跳躍,E啟用槓桿並開啟寶箱
您可以先練習幾次以熟悉環境。如果您希望跳過錄制演示,您也可以在完成的專案中找到預先錄製的演示,並使用其中的expert_demos.json檔案。
錄製的演示應至少包含 22-24 個完整的成功劇集。在訓練階段也可以使用多個演示檔案,因此您不必一次性錄製所有演示(您可以使用之前提到的Expert Demo Save Path屬性更改檔名)。
錄製 23 個劇集花費了我大約 10 分鐘(因為鑰匙有 2 個交替的生成位置,22 或 24 個劇集將提供演示中鑰匙位置的均勻分佈,但這已經相當接近了)。當接近槓桿或寶箱時,我按住E鍵的時間稍長一些,以確保在靠近這些物體時,該動作在多個步驟中被記錄下來。我還透過在下一集期間按下R鍵刪除了幾個我未成功完成的劇集。
這是演示錄製過程的加速影片
匯出遊戲進行訓練:
您可以使用Project > Export從 Godot 匯出遊戲。