2. 数学基础回顾:马尔可夫决策过程(MDP)与状态、动作、奖励的定义
好,咱们正式开始啃第一块硬骨头——马尔可夫决策过程。
说实话,我当年刚接触强化学习时,觉得MDP就是个花哨的数学名词。直到我在实验室调一个四足机器人平衡控制器,死活跑不稳,才意识到:问题建模不对,后面全白费。MDP就是那个帮你把控制问题“翻译”成数学语言的工具。
2.1 为什么力矩控制离不开MDP?
你想想看,机器人控制本质上是个序列决策问题:当前该输出多大扭矩?下一秒关节位置会变成什么样?怎么让整个运动过程既平滑又省力?
MDP恰好提供了描述这类问题的标准框架。它把控制过程拆解成三个核心要素:状态(State)、动作(Action)、奖励(Reward)。说白了,就是告诉算法:你现在在哪(状态),你能做什么(动作),做得好不好(奖励)。
核心观点:MDP不是算法,而是问题的“坐标系”。坐标系建对了,后面的强化学习算法才能找到最优解。
2.2 MDP的五元组定义
一个标准的MDP用五元组 (S, A, P, R, γ) 来描述。我习惯把它理解成一张“决策地图”:
| 符号 | 名称 | 力矩控制中的含义 |
|---|---|---|
| S | 状态空间 | 关节角度、角速度、末端位置等传感器读数 |
| A | 动作空间 | 电机输出的扭矩值(连续量)或离散档位 |
| P | 状态转移概率 | 施加扭矩后,下一时刻关节状态的概率分布 |
| R | 奖励函数 | 跟踪误差、能耗、冲击度的加权评分 |
| γ | 折扣因子 | 0~1之间,控制算法“眼光”的长短 |
我的经验:刚开始做力矩控制时,我总把状态维度设得特别高,觉得“多测几个传感器总没错”。结果算法收敛极慢。后来才明白——状态空间要够用但别冗余。比如控制单腿摆动,关节角度+角速度通常就够了,没必要把电机温度也塞进去。
2.3 状态(State):你“看”到了什么?
状态是算法做决策的依据。在力矩控制里,状态通常来自编码器、IMU、力传感器等。
举个例子,控制一个二连杆机械臂画圆:
- 关节空间状态:
[θ₁, θ₂, ω₁, ω₂]—— 两个关节的角度和角速度 - 任务空间状态:
[x, y, vx, vy]—— 末端位置和速度 - 混合状态: 两者都包含,再加一个目标轨迹点
我个人更推荐用关节空间+任务空间混合状态。为什么?因为纯关节空间会让算法“看不见”末端轨迹误差,纯任务空间又丢失了关节限位信息。我在做六轴机械臂力控时吃过这个亏——只用了末端位姿做状态,结果算法试图让关节转到物理极限之外,差点烧了电机。
避坑指南:状态必须满足马尔可夫性——当前状态包含了做出最优决策所需的全部历史信息。如果你发现算法“记不住”之前发生的事,那就得把历史帧也拼进状态里。我曾经在足式机器人上把过去3步的关节轨迹都堆进状态,效果立竿见影。
2.4 动作(Action):你能“做”什么?
动作就是控制器输出的指令。在力矩控制中,动作通常是电机目标扭矩或电流指令。
动作空间有两种常见设计:
- 连续动作空间:直接输出扭矩值,比如
[-10 N·m, 10 N·m]。精度高,但算法搜索难度大。 - 离散动作空间:把扭矩分成几个档位,比如
{-10, -5, 0, 5, 10}。简单粗暴,适合入门。
我个人的建议是:能用连续就别用离散。为什么?因为力矩控制对精度要求高,离散动作会让机器人出现“抖动”——扭矩在档位间跳变,关节像得了帕金森。我在做协作机器人拖动示教时,一开始用了离散动作,结果末端轨迹全是锯齿。换成连续动作后,曲线立刻平滑了。
# 连续动作空间示例(PyTorch)
import torch
import torch.nn as nn
class TorquePolicy(nn.Module):
def __init__(self, state_dim, action_dim, max_torque):
super().__init__()
self.net = nn.Sequential(
nn.Linear(state_dim, 128),
nn.ReLU(),
nn.Linear(128, 64),
nn.ReLU(),
nn.Linear(64, action_dim),
nn.Tanh() # 输出范围 [-1, 1]
)
self.max_torque = max_torque # 最大扭矩限幅
def forward(self, state):
# 输出归一化到 [-max_torque, max_torque]
return self.net(state) * self.max_torque
2.5 奖励(Reward):你“做”得好不好?
奖励函数是强化学习的“指挥棒”。你设计什么奖励,算法就学会什么行为。力矩控制中常见的奖励项包括:
| 奖励项 | 公式示例 | 作用 |
|---|---|---|
| 跟踪误差 | -||q_des - q_act||² |
让关节跟随目标轨迹 |
| 能耗惩罚 | -||τ||² |
减少不必要的力矩输出 |
| 平滑性惩罚 | -||τ_t - τ_{t-1}||² |
抑制力矩突变 |
| 安全约束 | -1000(越界时) |
防止关节超限或碰撞 |
关键原则:奖励函数要稀疏但明确。别搞一堆小奖励让算法“猜”你的意图。我见过有人给力矩控制加了十几个奖励项,结果机器人学会了原地抖动——因为抖动能让每个小奖励都“蹭”到一点。后来我只保留跟踪误差和能耗两项,机器人反而跑得稳了。
2.6 MDP的“闭环”流程
理解了三个要素,咱们来看看MDP在力矩控制中是怎么转起来的。下面这张图是我自己画的标准流程:
这个闭环每走一圈,就是一个时间步。算法通过不断“试错”来优化策略,最终学会在什么状态下该输出多大扭矩。
2.7 折扣因子 γ:算法该“看”多远?
γ 决定了算法是“急功近利”还是“高瞻远瞩”。
- γ = 0:只看眼前,只最大化当前奖励。适合纯跟踪控制。
- γ = 0.9:兼顾近期和远期。力矩控制中常用这个值。
- γ → 1:考虑无限远未来。适合需要规划长周期运动的场景。
我的经验:做力矩控制时,γ 别设太大。为什么?因为机器人动力学有延迟——你这一秒输出的扭矩,可能半秒后才体现在关节位置上。γ 太大,算法会“幻想”自己能瞬间改变未来,导致控制震荡。我一般从 0.95 开始调,如果发现抖动就降到 0.9。
2.8 一个完整的MDP实例
咱们用一个简单的单关节位置控制来串一遍:
# 单关节MDP定义示例
class SingleJointMDP:
def __init__(self):
# 状态:关节角度 θ (rad) 和角速度 ω (rad/s)
self.state = [0.0, 0.0]
# 目标位置
self.target = 1.57 # 90度
def step(self, torque):
# 动作:扭矩 τ (N·m),范围 [-5, 5]
# 简化动力学:τ = I * α,假设转动惯量 I = 1
alpha = torque # 角加速度
dt = 0.01
# 更新状态
self.state[1] += alpha * dt # 角速度
self.state[0] += self.state[1] * dt # 角度
# 计算奖励:负的跟踪误差平方 - 能耗
error = self.target - self.state[0]
reward = -error**2 - 0.01 * torque**2
# 判断是否终止
done = abs(error) < 0.01
return self.state, reward, done
这个例子虽然简单,但麻雀虽小五脏俱全。你可以在上面跑任何强化学习算法——DQN、PPO、SAC都行。
注意:实际机器人上千万别直接用这个简化动力学。真实系统有摩擦、柔性、延迟,你得用更精确的模型或直接上真实环境交互。我见过有人把简化模型训好的策略直接部署到真机上,结果机器人直接“抽风”——因为模型忽略了摩擦力矩。
好了,MDP的数学基础就聊到这儿。记住三个核心:状态告诉算法“你在哪”,动作告诉算法“你能做什么”,奖励告诉算法“做得好不好”。后面的章节,咱们会在这个框架上搭建真正的强化学习控制器。