4. 状态空间与动作空间设计
好,我们直接进入正题。状态空间和动作空间的设计,说白了就是定义「机器人能看到什么」和「机器人能做什么」。这两个东西没定好,后面的强化学习算法再牛也白搭。我在项目中见过太多人一上来就堆高维状态,结果训练了三天三夜,机器人还在原地打转。
4.1 本体感知状态:机器人的「身体感觉」
人形机器人要站稳、要走路,首先得知道自己身体啥姿势。这就像你闭着眼睛也能摸到鼻子——这叫本体感知。
关节角度与角速度:这是最基础的状态。每个关节的当前角度和角速度,直接反映了机器人的运动姿态。我个人习惯把关节角度归一化到[-1, 1]之间,这样神经网络收敛会快很多。
# 关节角度归一化示例
def normalize_joint_angle(angle, joint_range):
# joint_range: [min, max]
normalized = 2.0 * (angle - joint_range[0]) / (joint_range[1] - joint_range[0]) - 1.0
return np.clip(normalized, -1.0, 1.0)
IMU数据:IMU提供的是躯干的姿态信息。包括三轴加速度、三轴角速度,以及通过滤波得到的欧拉角(横滚、俯仰、偏航)。
嗯,这里要注意:IMU数据噪声很大。我曾经在仿真里跑得好好的模型,一上真机就摔。后来发现是IMU的原始数据没做低通滤波。你想想看,电机振动带来的高频噪声,直接喂给策略网络,它不懵才怪。
4.2 外部感知状态:机器人的「眼睛」
光知道自己啥姿势还不够。机器人得知道地面长啥样,不然一脚踩空就摔了。
地形高度扫描:这是我最常用的外部感知方式。用一个2D或3D的高度图,表示机器人周围的地形起伏。
举个例子:在机器人前方扫描一个5×5的网格,每个格子代表该位置的地面高度。这样策略网络就能「看到」前面有个台阶,或者有个坑。
| 扫描参数 | 典型值 | 说明 |
|---|---|---|
| 扫描范围 | 1.5m × 1.0m | 前方1.5米,左右各0.5米 |
| 网格分辨率 | 0.1m | 太密了计算量大,太稀了看不清细节 |
| 网格数量 | 15 × 10 = 150 | 这个维度对策略网络来说刚刚好 |
为什么会用网格而不是直接用点云?说白了,网格是结构化数据,CNN或者MLP处理起来更顺手。点云虽然信息更丰富,但处理起来麻烦得多。
4.3 动作空间设计:机器人如何「行动」
动作空间决定了机器人能做什么动作。在人形机器人领域,最常用的动作空间是PD目标位置。
什么叫PD目标位置?就是每个关节的PD控制器(比例-微分控制器)的目标角度。策略网络输出的是「我希望这个关节转到什么角度」,然后底层的PD控制器去执行。
# 动作空间定义示例
class ActionSpace:
def __init__(self, num_joints=12):
# 每个关节的目标位置范围(弧度)
self.joint_limits = {
'hip_roll': [-0.5, 0.5],
'hip_pitch': [-1.2, 1.2],
'knee_pitch': [0.0, 2.5],
'ankle_pitch': [-0.8, 0.8]
}
self.action_dim = num_joints
def clip_action(self, action):
# 将动作限制在安全范围内
return np.clip(action, -1.0, 1.0)
我个人习惯把动作空间归一化到[-1, 1]之间。这样策略网络输出的动作值天然就在这个范围,再映射到实际的关节角度范围。好处是网络更容易学习,不会因为关节范围差异大而导致梯度爆炸。
4.4 状态与动作的「组合拳」
把上面这些拼起来,就构成了完整的观测空间。我一般这样组织:
- 本体状态:关节角度(12维) + 关节角速度(12维) + IMU数据(6维) = 30维
- 外部状态:地形高度扫描(150维)
- 历史状态:前3步的本体状态(90维) —— 这个很重要,单帧状态信息不够
总状态维度:30 + 150 + 90 = 270维。动作空间:12维(12个关节的目标位置)。
4.5 知识体系总览
下面这张图总结了状态空间与动作空间的核心逻辑。我画这张图的时候,脑子里想的就是「机器人怎么感知世界,怎么做出反应」。
你看这张图,从左到右就是机器人的「感知-决策-行动」闭环。状态空间是输入,动作空间是输出,中间的策略网络负责把感知映射成行动。
好了,状态空间和动作空间的设计就讲到这里。记住:好的设计能让训练事半功倍,坏的设计能让你的GPU烧到冒烟也学不会。下次我们聊聊奖励函数的设计——那才是真正考验功力的地方。