# Reference

1. [飞行机械臂仿真环境](https://mubu.com/doc/o1TkigWxq)
2. [遥操作与模仿学习](https://mubu.com/doc/fHZt1nQhZK)
3. 课外阅读：[MimicGen系统](https://mp.weixin.qq.com/s/O1OVokb-K2WVPxmCOAf2yQ)：利用人类示教用于机器人操作学习的数据生成系统，[UMI](https://mp.weixin.qq.com/s/caes5225uDozYo8Oai1czg)：通用操纵界面，可将人类演示的技能直接转移到可部署的机器人策略中，[对比偏好学习](https://mp.weixin.qq.com/s/I43POxN4WKRySxgeqxUolA)，遥操作机器人：[ALOHA机器人](https://mp.weixin.qq.com/s/wAkbO9NSi_Tg5Z5MwLwPFA)，[ALOHA2机器人](https://mp.weixin.qq.com/s/hjHBf0VX6s_GNKL4AsYMFg)
4. 可能用到的虚拟机搭建平台：virtual box7.0虚拟机，[虚拟机镜像：roslearn1.ova](http://192.168.35.165:8090/nextcloud/index.php/apps/files/?dir=/23%E7%A7%8B-%E6%99%BA%E8%83%BD%E6%97%A0%E4%BA%BA%E6%9C%BA&fileid=166462)从实验室云盘下载
5. 相关论文：在Ubuntu下遥操作文件夹中，百度网盘中也有备份
6. 这个是那个机械臂课程里面讲到的一个行为克隆强化学习的示例，可能比上面那个纯强化学习策略，更依赖示教数据（遥操作更有意义），也可以参考看看。
   https://ai.googleblog.com/2022/04/efficiently-initializing-reinforcement.html
   如何使用先验策略有效地初始化强化学习？
   https://news.qq.com/rain/a/20220906A04CVX00
   D4RL的Adroit数据集原始环境安装CSDN:
   https://blog.csdn.net/konodiodaaa/article/details/131599877
   GITHUB：
   https://github.com/vikashplus/Adroit
   https://github.com/aravindr93/hand_dapg/

# My Study

## 学习资料

多旋翼飞行器：《多旋翼飞行器设计与控制》（[配套资料](https://rflysim.com/doc/zh/C/1.Multicopter_Design.html)）

机械臂：主要了解齐次变换矩阵和DH参数即可

深度强化学习：《深度强化学习》（[配套资料](https://github.com/wangshusen/DRL?tab=readme-ov-file)）

## 总体思路

1. **先机械后智能**：先掌握飞行器动力学与控制，再进入强化学习和模仿学习。
2. **理论+仿真+编程**并行：不要只读书，必须结合公式推导、编程实现和仿真实验。
3. **由浅入深，逐步交叉**：先分开学，再将飞行器控制和模仿学习结合起来，最后进入飞行机械臂（飞行器+机械臂）的复杂控制问题。

------

## 学习计划（前 6 个月）

### **第一阶段：打好基础 (0-2 个月)**

目标：掌握飞行器动力学、控制基础和强化学习基本概念。

**任务**

1. 《多旋翼飞行器设计与控制》
   - 第1-3章：飞行器建模（动力学、运动学、状态空间模型）
   - 第4-5章：姿态控制与位置控制（PID、LQR）
   - 配合推导公式，**用 MATLAB/Python 建立一个四旋翼动力学仿真模型**。
2. 《深度强化学习》
   - 第1-3章：强化学习基础（MDP、值函数、策略迭代）
   - 第4-5章：Q-learning、DQN
   - 实现经典环境（CartPole、MountainCar）的强化学习算法（建议用 Python + PyTorch）。

**产出**

- 一个四旋翼简化模型的仿真程序（Python 或 MATLAB）。
- 一个能跑通 OpenAI Gym 环境的 DQN 算法。

------



### **第二阶段：进阶与交叉 (2-4 个月)**

目标：将传统控制与强化学习方法联系起来，理解模仿学习的核心思想。

**任务**

1. 《多旋翼飞行器设计与控制》
   - 第6-8章：先进控制（非线性控制、鲁棒控制）。
   - 尝试在仿真中加入外部扰动（如风力），验证控制器性能。
2. 《深度强化学习》
   - 第6-9章：策略梯度方法（REINFORCE、Actor-Critic、PPO）。
   - 初步了解模仿学习（行为克隆、逆强化学习）。

**实践**

- 用 PPO 训练一个四旋翼的姿态/位置保持控制器（先在简化环境里）。
- 对比传统 PID 和 RL 控制效果。

**产出**

- 一个四旋翼姿态控制 RL 算法实验报告。
- 一份 RL 和传统控制对比的仿真数据。

------

### **第三阶段：结合飞行机械臂 (4-6 个月)**

目标：进入博士课题核心，理解飞行器与机械臂的耦合动力学，并在此基础上引入模仿学习。

**任务**

1. 学习飞行机械臂动力学建模（可查阅论文/教材补充，书中不一定有）。
   - 建立耦合模型：飞行器 + 机械臂关节动力学。
   - 重点理解重心偏移、扰动对飞行稳定性的影响。
2. 模仿学习
   - 行为克隆（BC）：用专家数据训练控制策略。
   - GAIL（生成对抗模仿学习）等方法。
3. 实践
   - 在仿真环境（Gazebo + ROS / Mujoco / PyBullet）中搭建飞行机械臂模型。
   - 收集专家示范（比如人工遥控、传统控制器轨迹）。
   - 用模仿学习训练机械臂操作任务（如抓取或物体移动）。

**产出**

- 一个飞行机械臂的仿真平台（ROS/Gazebo 或 Mujoco）。
- 一份模仿学习实验报告：用专家演示训练飞行机械臂完成任务。

------

## 学习方法建议

1. **读书方式**：每章不要死读，重点在于公式推导 + 代码实现。
2. **编程习惯**：用 GitHub 建立仓库，把每个阶段的代码和报告上传，方便迭代和复盘。
3. **科研习惯**：每个月写一份总结文档（学到什么，遇到什么问题，怎么解决）。
4. **论文积累**：从第 2 个月开始，每周至少读 2 篇相关论文（飞行机械臂、模仿学习）

## 1、搭建机械臂仿真环境

参考[Unitree机械臂仿真环境搭建](https://blog.csdn.net/eyas3/article/details/135505749?spm=1001.2014.3001.5502)，最后运行效果：

![w73j8-6xasa](./Picture/遥操作和模仿学习/w73j8-6xasa.gif)

## 2、使用panda-gym和stable-baseline3完成一个demo

完成机械臂强化学习的一个demo，使用panda-gym和stable baseline3完成，参考：

https://zhuanlan.zhihu.com/p/491909457

https://blog.csdn.net/qq_47997583/article/details/125009605

运行效果：[DDPG](https://www.bilibili.com/video/BV1SZBfYQELC/?vd_source=63856ba65bb4b008f5c3f3b0c598ac4b)，[TD3](https://www.bilibili.com/video/BV1SZBfYQE4J/?vd_source=63856ba65bb4b008f5c3f3b0c598ac4b)，[SAC](https://www.bilibili.com/video/BV1SZBfYQETq/?vd_source=63856ba65bb4b008f5c3f3b0c598ac4b)

## 3、使用panda-gym完成unitree z1机械臂仿真训练

[panda-gym2.0.0 docs](/home/mickey/APP/panda-gym-2.0.0/docs/build/html/index.html)

学习panda-gym自定义环境一节，按照文档提示成功导入urdf文件创建宇树机械臂模型，这里要注意导入urdf文件时要连同其所在的文件夹一起导入才行，否则会报错。之后在调用PandaReach-v2任务时出错，已向panda-gym开发者请求帮助。

2024.12.26代码修改完成，具体修改方法为：

1. 添加机器人模型：在```/home/mickey/miniforge3/envs/panda-gym/lib/python3.6/site-packages/panda_gym/envs/robots```文件夹下添加z1.py

```python
import numpy as np
from gym import spaces

from panda_gym.envs.core import PyBulletRobot
from panda_gym.pybullet import PyBullet


class z1(PyBulletRobot):
    """Panda robot in PyBullet.

    Args:
        sim (PyBullet): Simulation instance.
        block_gripper (bool, optional): Whether the gripper is blocked. Defaults to False.
        base_position (np.ndarray, optionnal): Position of the base base of the robot, as (x, y, z). Defaults to (0, 0, 0).
        control_type (str, optional): "ee" to control end-effector displacement or "joints" to control joint angles.
            Defaults to "ee".
    """

    def __init__(
        self,
        sim: PyBullet,
        # block_gripper: bool = False,
        base_position: np.ndarray = np.array([0.0, 0.0, 0.0]),
        control_type: str = "ee",
    ) -> None:
        # self.block_gripper = block_gripper
        self.control_type = control_type
        n_action = (
            3 if self.control_type == "ee" else 6
        )  # control (x, y z) if "ee", else, control the 6 joints
        # n_action += 0 if self.block_gripper else 1
        action_space = spaces.Box(-1.0, 1.0, shape=(n_action,), dtype=np.float32)
        super().__init__(
            sim,
            body_name="z1",
            file_name="/home/mickey/mickey_sync/project/my_panda_gym/z1_description/xacro/z1.urdf",
            base_position=base_position,
            action_space=action_space,
            joint_indices=np.array(
                [1, 2, 3, 4, 5, 6]
            ),  # list of the indices, as defined in the URDF
            joint_forces=np.array(
                [30.0, 60.0, 30.0, 30.0, 30.0, 30.0]
            ),  # force applied when robot is controled (Nm)
        )

        # self.fingers_indices = np.array([9, 10])
        self.neutral_joint_values = np.array([0.00, 0.00, 0.00, 0.00, 0.00, 0.00])
        self.ee_link = 6
        # self.sim.set_lateral_friction(self.body_name, self.fingers_indices[0], lateral_friction=1.0)
        # self.sim.set_lateral_friction(self.body_name, self.fingers_indices[1], lateral_friction=1.0)
        # self.sim.set_spinning_friction(self.body_name, self.fingers_indices[0], spinning_friction=0.001)
        # self.sim.set_spinning_friction(self.body_name, self.fingers_indices[1], spinning_friction=0.001)

    def set_action(self, action: np.ndarray) -> None:
        action = action.copy()  # ensure action don't change
        action = np.clip(action, self.action_space.low, self.action_space.high)
        if self.control_type == "ee":
            ee_displacement = action[:3]
            target_arm_angles = self.ee_displacement_to_target_arm_angles(
                ee_displacement
            )
        else:
            arm_joint_ctrl = action[:6]
            target_arm_angles = self.arm_joint_ctrl_to_target_arm_angles(arm_joint_ctrl)

        # if self.block_gripper:
        #     target_fingers_width = 0
        # else:
        #     fingers_ctrl = action[-1] * 0.2  # limit maximum change in position
        #     fingers_width = self.get_fingers_width()
        #     target_fingers_width = fingers_width + fingers_ctrl

        # target_angles = np.concatenate((target_arm_angles, [target_fingers_width / 2, target_fingers_width / 2]))

        target_angles = target_arm_angles
        self.control_joints(target_angles=target_angles)

    def ee_displacement_to_target_arm_angles(
        self, ee_displacement: np.ndarray
    ) -> np.ndarray:
        """Compute the target arm angles from the end-effector displacement.

        Args:
            ee_displacement (np.ndarray): End-effector displacement, as (dx, dy, dy).

        Returns:
            np.ndarray: Target arm angles, as the angles of the 7 arm joints.
        """
        ee_displacement = ee_displacement[:3] * 0.05  # limit maximum change in position
        # get the current position and the target position
        ee_position = self.get_ee_position()
        target_ee_position = ee_position + ee_displacement
        # Clip the height target. For some reason, it has a great impact on learning
        target_ee_position[2] = np.max((0, target_ee_position[2]))
        # compute the new joint angles
        target_arm_angles = self.inverse_kinematics(
            link=self.ee_link,
            position=target_ee_position,
            orientation=np.array([1.0, 0.0, 0.0, 0.0]),
        )
        target_arm_angles = target_arm_angles[:6]  # remove fingers angles
        return target_arm_angles

    def arm_joint_ctrl_to_target_arm_angles(
        self, arm_joint_ctrl: np.ndarray
    ) -> np.ndarray:
        """Compute the target arm angles from the arm joint control.

        Args:
            arm_joint_ctrl (np.ndarray): Control of the 7 joints.

        Returns:
            np.ndarray: Target arm angles, as the angles of the 7 arm joints.
        """
        arm_joint_ctrl = arm_joint_ctrl * 0.05  # limit maximum change in position
        # get the current position and the target position
        current_arm_joint_angles = np.array(
            [self.get_joint_angle(joint=i) for i in range(6)]
        )
        target_arm_angles = current_arm_joint_angles + arm_joint_ctrl
        return target_arm_angles

    def get_obs(self) -> np.ndarray:
        # end-effector position and velocity
        ee_position = np.array(self.get_ee_position())
        ee_velocity = np.array(self.get_ee_velocity())
        # new add
        obs = np.concatenate((ee_position, ee_velocity))
        # fingers opening
        # if not self.block_gripper:
        #     fingers_width = self.get_fingers_width()
        #     obs = np.concatenate((ee_position, ee_velocity, [fingers_width]))
        # else:
        #     obs = np.concatenate((ee_position, ee_velocity))
        return obs

    def reset(self) -> None:
        self.set_joint_neutral()

    def set_joint_neutral(self) -> None:
        """Set the robot to its neutral pose."""
        self.set_joint_angles(self.neutral_joint_values)

    # def get_fingers_width(self) -> float:
    #     """Get the distance between the fingers."""
    #     finger1 = self.sim.get_joint_angle(self.body_name, self.fingers_indices[0])
    #     finger2 = self.sim.get_joint_angle(self.body_name, self.fingers_indices[1])
    #     return finger1 + finger2

    def get_ee_position(self) -> np.ndarray:
        """Returns the position of the ned-effector as (x, y, z)"""
        return self.get_link_position(self.ee_link)

    def get_ee_velocity(self) -> np.ndarray:
        """Returns the velocity of the end-effector as (vx, vy, vz)"""
        return self.get_link_velocity(self.ee_link)

```

2. 在```/home/mickey/miniforge3/envs/panda-gym/lib/python3.6/site-packages/panda_gym/envs/panda_tasks```下添加z1的任务：

```python
import numpy as np

from panda_gym.envs.core import RobotTaskEnv
from panda_gym.envs.robots.z1 import z1
from panda_gym.envs.tasks.reach import Reach
from panda_gym.pybullet import PyBullet

# import sys

# sys.path.append("/home/mickey/mickey_sync/project/panda-gym/envs/robots")
# from z1 import z1

# sys.path.append("/home/mickey/mickey_sync/project/panda-gym/envs/tasks")
# from reach import Reach

# import gym
# from gym.wrappers import TimeLimit


class Z1ReachEnv(RobotTaskEnv):
    """Reach task wih Panda robot.

    Args:
        render (bool, optional): Activate rendering. Defaults to False.
        reward_type (str, optional): "sparse" or "dense". Defaults to "sparse".
        control_type (str, optional): "ee" to control end-effector position or "joints" to control joint values.
            Defaults to "ee".
    """

    def __init__(
        self,
        render: bool = False,
        reward_type: str = "sparse",
        control_type: str = "ee",
    ) -> None:
        sim = PyBullet(render=render)
        robot = z1(
            sim,
            # block_gripper=True,
            base_position=np.array([-0.6, 0.0, 0.0]),
            control_type=control_type,
        )
        task = Reach(
            sim, reward_type=reward_type, get_ee_position=robot.get_ee_position
        )
        super().__init__(robot, task)

```

3. 在```/home/mickey/miniforge3/envs/panda-gym/lib/python3.6/site-packages/panda_gym/__init__.py```文件夹下注册环境

```python
/home/mickey/miniforge3/envs/panda-gym/lib/python3.6/site-packages/panda_gym/__init__.py中添加以下内容：

register(
	id="Z1Reach{}{}-v1".format(control_suffix, reward_suffix),
	entry_point="panda_gym.envs:Z1ReachEnv",
    kwargs=kwargs,
  	max_episode_steps=50,
)
```

运行效果如下：

[DDPG](https://www.bilibili.com/video/BV1oLCnYvEsU/?vd_source=63856ba65bb4b008f5c3f3b0c598ac4b)，[TD3](https://www.bilibili.com/video/BV1iLCnYvEQH/?vd_source=63856ba65bb4b008f5c3f3b0c598ac4b)，[SAC](https://www.bilibili.com/video/BV1iLCnYvE5Z/?vd_source=63856ba65bb4b008f5c3f3b0c598ac4b)

* 需要解决的问题：改用Unitree z1机械臂的urdf文件后，强化学习训练效果成功率无法达到100%，只有90%左右，如下图：

![z1_tensorboard](./Picture/遥操作和模仿学习/z1_tensorboard.png)

* 虽然TD3和DDPG算法的成功率比SAC算法高，但是DDPG和TD3都有较大抖动，SAC稳定性则较好，后续考虑重新重新学习一些新的强化学习算法用于训练机械臂，而不仅仅局限于stable-baseline3中提供的算法。

* 后续计划
  * 整理当前代码环境，融合到pypi包中，并上传github，并跟导师商量看是否能发一篇论文。
  * 使用当前平台进行机械臂模仿学习的仿真。

----

## TODO

- [x] 导入大四轴模型（完成时间：2025.9.24）
- [ ] 修改vehicles.py代码，实现python调用无人机起飞
- [ ] 研究如何通过ros发布轨迹信息实现大四轴在isaacsim中的运行
- [ ] 研究如何通过ros控制机械臂在isaacsim中的运行
- [ ] 研究模仿学习算法

## 2025.9.16

在isaacsim中成功使无人机飞行，降落，详细配置方法：

https://github.com/PegasusSimulator/PegasusSimulator

## 2025.9.23

由于PegasusSimulator集成了px4，所以考虑将大四轴模型先导入px4的gazebo模型路径下，然后通过python接口调用

将模型导入px4 gazebo模型路径下的操作：

```bash
# px4 gazebo模型路径：/home/leona/sim_wt/PX4-Autopilot/Tools/simulation/gazebo-classic/sitl_gazebo-classic/models
# 需要编写model.config文件，从现有的模板中复制粘贴一个，修改文件名即可
gedit model.config

# 进入launch文件夹，路径：/home/leona/sim_wt/PX4-Autopilot/launch，编写新模型的launch文件，从现有的模版复制粘贴进行修改
gedit big_four.launch

# 在px4中注册airframe配置
cd /home/leona/sim_wt/PX4-Autopilot/ROMFS/px4fmu_common/init.d-posix/airframes
# 修改大四轴的参数配置，没有就新建一个
gedit 1112_gazebo-classic_big_four

# 大四轴参数配置开始
#!/bin/sh
#
# @name Big Quadrotor 0904
#
# @type Quadrotor X
# @class Copter
#
# @output MAIN1 motor1
# @output MAIN2 motor2
# @output MAIN3 motor3
# @output MAIN4 motor4
#
# @maintainer Your Name <your_email@example.com>
#

. ${R}etc/init.d/rc.mc_defaults

# 设置机型为多旋翼
param set-default CA_AIRFRAME 0
# 设置为四旋翼
param set-default CA_ROTOR_COUNT 4

# 电机1 (右前: CCW)
param set-default CA_ROTOR0_PX 0.60093   
param set-default CA_ROTOR0_PY 0.60093  
param set-default CA_ROTOR0_KM 0.05      

# 电机2 (右后: CW)
param set-default CA_ROTOR1_PX -0.60093  
param set-default CA_ROTOR1_PY -0.60093  
param set-default CA_ROTOR1_KM 0.05    

# 电机3 (左后: CCW)
param set-default CA_ROTOR2_PX 0.60093  
param set-default CA_ROTOR2_PY -0.60093  
param set-default CA_ROTOR2_KM -0.05     

# 电机4 (左前: CW)
param set-default CA_ROTOR3_PX -0.60093  
param set-default CA_ROTOR3_PY 0.60093   
param set-default CA_ROTOR3_KM -0.05     

# 设置PWM输出功能（将飞控输出映射到电机）
param set-default PWM_MAIN_FUNC1 101 # 电机1 (MAIN1输出)
param set-default PWM_MAIN_FUNC2 102 # 电机2 (MAIN2输出)
param set-default PWM_MAIN_FUNC3 103 # 电机3 (MAIN3输出)
param set-default PWM_MAIN_FUNC4 104 # 电机4 (MAIN4输出)
# 大四轴参数配置结束

# 编辑同目录下的CmakeLists.txt文件
px4_add_romfs_airframe(1112_gazebo-classic_big_four)

# 回到主目录，make编译
cd /home/leona/sim_wt/PX4-Autopilot
make

# 测试
roslaunch px4 big_four.launch
```

可以在gazebo里打开，但是在isaacsim中调用报错，后续还应继续修改代码

## 2025.9.24

本来以为pegasus simulator是把gazebo中的sdf文件导入isaacsim，结果搞了半天发现还是一样地用usd文件，其用的是它自身存储中的usd文件，路径：```/home/leona/mickey/PegasusSimulator/extensions/pegasus.simulator/pegasus/simulator/assets/Robots```，将之前的大四轴模型copy一份到该路径下，打开模型进行修改

* 在defaultPrim中右键，add—>physics—>articulation root，如果没有看到该选项，则在右下角的Property一栏查看是否已经添加，如果还是没有，就需要在其他xform处寻找是否已经添加过articulation root，找到并删除，重新在defaultPrim中添加，记住，**一定要在defaultPrim中添加**
* 选中所有的link，右键，add—>physics—>Rigid Body with Colliders Preset（或者Colliders Preset）
* 删除不必要的平面，以防无人机无法起飞

* 直接修改源代码```/home/leona/mickey/PegasusSimulator/examples/1_px4_single_vehicle.py```，添加以下代码，并把之前的相关代码注释掉

  ```bash
  # ROBOTS = {"Iris": ROBOTS_ASSETS + "/Iris/iris.usd"} #, "Flying Cube": ROBOTS_ASSETS + "/iris_cube.usda"}
  
  # 大四轴
  ROBOTS = {"big_four": ROBOTS_ASSETS + "/big_four/big_four.usd"}
  ```

修改完之后，通过isaacsim中的pegasus simulator扩展load scene和load vehicle，可以成功使无人机起飞，但是飞行状态十分扭曲，应该是px4中的飞行参数给的不对。

通过代码调用：

复制```/home/leona/mickey/PegasusSimulator/examples/1_px4_single_vehicle.py```到project文件夹，修改其中代码：

```python
        mavlink_config = PX4MavlinkBackendConfig({
            "vehicle_id": 0,
            "px4_autolaunch": True,
            "px4_dir": self.pg.px4_path,
            "px4_vehicle_model": 'big_four' # 修改
        })
        config_multirotor.backends = [PX4MavlinkBackend(mavlink_config)]

        Multirotor(
            "/World/quadrotor",
            ROBOTS['big_four'],	# 修改
            0,
            [0.0, 0.0, 0.07],
            Rotation.from_euler("XYZ", [0.0, 0.0, 0.0], degrees=True).as_quat(),
            config=config_multirotor,
        )
```

修改完以上代码后，可以通过python成功在isaacsim中打开大四轴，但是无法飞行，初步找到解决方法是修改```/home/leona/mickey/PegasusSimulator/extensions/pegasus.simulator/pegasus/simulator/logic/vehicles/vehicle.py```其中的代码，因为它的源代码中默认为机体名称为/body，而大四轴模型的机体坐标为/base_link，大致看了一下需要修改的地方还挺多，不太好改，明天继续。

# 相关文献阅读笔记

## 1、[模仿学习简介教程（南京大学）](https://alist-org.github.io/pdf.js/web/viewer.html?file=http%3A%2F%2Flocalhost%3A5244%2Fp%2Fubuntu%2Fimportant%2F%25E5%25AD%25A6%25E4%25B9%25A0%25E8%25B5%2584%25E6%2596%2599%2F%25E9%2581%25A5%25E6%2593%258D%25E4%25BD%259C%25E5%2592%258C%25E6%25A8%25A1%25E4%25BB%25BF%25E5%25AD%25A6%25E4%25B9%25A0%2F%25E8%25AF%25BE%25E9%25A2%2598-%25E6%25A8%25A1%25E4%25BB%25BF%25E5%25AD%25A6%25E4%25B9%25A0%2Fbook%2F%25E6%25A8%25A1%25E4%25BB%25BF%25E5%25AD%25A6%25E4%25B9%25A0%25E7%25AE%2580%25E6%25B4%2581%25E6%2595%2599%25E7%25A8%258B.pdf%3Fsign%3DdGjS4dRV7MwXxawrJmM-Roue96WNE0W7UMvrYaHbBEs%3D%3A0)

* **2.1.1 马尔可夫链相关笔记**

   [简述马尔可夫链.html](https://openlist.speedtest.mingqian.online/@s/xIc4DRmK/%E7%AE%80%E8%BF%B0%E9%A9%AC%E5%B0%94%E5%8F%AF%E5%A4%AB%E9%93%BE.html)

* **2.1.2 马尔可夫决策过程**

  原文：除此之外，我们用奖励函数 $r : S × A → R$ 来衡量某个状态-动作对的好坏。

  注释：

  1. **什么是 $S \times A$？**

  $s$ 是状态空间，表示系统可能处于的所有状态。$A$是动作空间，表示智能体在每个状态下可以采取的所有可能动作。$S \times A$表示所有状态-动作对的集合，形式上是“某个状态下采取某个动作”。比如：如果一个机器人在某个房间（状态）内选择往北走（动作），这就形成了一个状态-动作对$(s,a)$。

  2. **什么是$r : S × A → R$ ？**

  奖励函数$r$是一个从状态-动作对映射到实数的函数，它的意思是：你在某个状态$s$下做了一个动作$a$，会立刻获得一个数值型的奖励$r(s,a)$。这个奖励数值衡量了该行为的好坏：如果$r(s,a)$很高，说明这个动作在这个状态下是“好”的（系统鼓励这种行为）。如果$r(s,a)$很低（甚至为负），说明这种行为不受欢迎。
