OpenDuckMini强化学习框架入门教程-动作过滤
纠错,疑问,交流: 请进入讨论区或 请点击进入页面,扫码加入微信群或Q群进行交流
获取最新文章: 扫一扫加入“创客智造”公众号
欢迎加入我们的openduckmini交流群,微信扫描右侧二维码立即进群交流
动作过滤
- 理解动作过滤,包含过滤方式,动作过滤流程和代码中的使用等
概述
动作过滤是确保策略输出平滑、自然的关键环节。Open Duck Playground 提供了三种不同层次的动作过滤方式,从软约束(奖励函数鼓励)到硬约束(物理限制),全面防止机器人动作中出现不可取的抖动或突变。
三种过滤方式
┌──────────────────────────────────────────────────────────┐
│ 动作过滤方案对比 │
├──────────────────────────────────────────────────────────┤
│ │
│ 1. 动作变化率代价 (cost_action_rate) │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ Soft │───→│ 鼓励 │───→│ 平滑 │ │
│ └─────┘ └─────┘ └─────┘ │
│ 软约束,通过奖励引导策略输出平滑动作 │
│ │
│ 2. 电机速度限制 (motor velocity limits) │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │ Hard │───→│ 限制 │───→│ 安全 │ │
│ └──────┘ └──────┘ └──────┘ │
│ 硬约束,物理上限制每步最大角度变化 │
│ │
│ 3. 低通滤波器 (LowPassActionFilter) │
│ ┌─────┐ ┌──────┐ ┌──────┐ │
│ │ DSP │───→│ 滤波 │───→│ 平滑 │ │
│ └─────┘ └──────┘ └──────┘ │
│ 数字信号处理,一阶 RC 低通滤波 │
│ │
└──────────────────────────────────────────────────────────┘
1. 动作变化率代价
位置:playground/common/rewards.py → cost_action_rate()
工作原理: 通过奖励函数惩罚相邻动作之间的差异,鼓励策略自学产生平滑的动作序列。
def cost_action_rate(act, last_act):
return jp.nan_to_num(jp.sum(jp.square(act - last_act)))
效果:
- 动作变化越小 → 代价越低 → 策略倾向于输出稳定的动作
- 平方放大大的变化 → 大幅度抖动的惩罚远大于轻微变化
- 这是一种软约束——策略可以选择违反它,但会付出奖励代价
权重配置:
| 环境 | 权重 | 效果 |
|---|---|---|
| Joystick | -0.5 | 中度平滑约束 |
| Standing | -0.375 | 较弱的平滑约束(站立动作较慢) |
2. 电机速度限制
位置:joystick.py 和 mujoco_infer.py → USE_MOTOR_SPEED_LIMITS
工作原理: 限制每个控制周期内电机目标位置的最大变化量,模拟真实舵机的最大旋转速度。
max_motor_velocity = 5.24 # rad/s
# 每步最大允许变化
max_delta = max_motor_velocity * dt # dt = 0.02s (控制周期)
# max_delta = 5.24 * 0.02 = 0.1048 rad ≈ 6°
# 限制动作
motor_targets = jp.clip(
motor_targets,
prev_motor_targets - max_delta,
prev_motor_targets + max_delta,
)
参数说明:
| 参数 | 值 | 物理意义 |
|---|---|---|
max_motor_velocity |
5.24 rad/s | 约 300°/s,接近 STS3215 舵机实际最大速度 |
控制周期 ctrl_dt |
0.02 s | 50 Hz 控制频率 |
| 每步最大变化 | 0.1048 rad | 约 6° |
仿真步长 sim_dt |
0.002 s | 500 Hz 物理仿真 |
为什么这很重要:
无速度限制:
action: [-1.0 → +1.0] (瞬间反转)
motor_targets: [-0.25 → +0.25] (0.5 rad 的突变)
→ 物理上不可能,舵机会丢步
有速度限制:
action: [-1.0 → +1.0] (策略输出)
motor_targets: [-0.25 → -0.145 → -0.038 → ... → +0.25]
→ 需要约 5 个控制周期 (0.1s) 完成反转,舵机的实际行为
3. 低通滤波器 (LowPassActionFilter)
位置:playground/common/utils.py → LowPassActionFilter
工作原理: 一阶 IIR 低通滤波器,类似于模拟 RC 电路的数字实现。
class LowPassActionFilter:
def __init__(self, control_freq, cutoff_frequency=37.5):
self.alpha = (1/cutoff_freq) / (1/control_freq + 1/cutoff_freq)
def push(self, action):
self.current_action = action
def get_filtered_action(self):
# 一阶指数平滑
self.last_action = (
self.alpha * self.last_action +
(1 - self.alpha) * self.current_action
)
return self.last_action
频率响应:
传递函数: H(s) = 1 / (1 + s/ωc)
其中: ωc = 2π * cutoff_frequency
幅频特性:
- 截止频率: f_c = 37.5 Hz
- 低于 f_c: 几乎无衰减
- 高于 f_c: -20 dB/decade 衰减
- 在 f_c: -3 dB
时间常数:
τ = 1 / (2π * f_c)
= 1 / (2π * 37.5)
≈ 4.24 ms
这意味滤波器对约 4ms 以上的变化基本无影响,
但对更快的高频抖动有显著抑制效果。
当前状态:在 joystick.py 和 mujoco_infer.py 中,低通滤波器代码已被注释,因为电机速度限制提供了更符合物理实际的平滑效果。
三种方式的对比分析
| 特性 | 动作变化率代价 | 电机速度限制 | 低通滤波 |
|---|---|---|---|
| 约束类型 | 软约束 (奖励) | 硬约束 (物理) | 信号处理 |
| 训练/推理 | 训练 + 推理 | 训练 + 推理 | 仅推理 |
| 是否启用 | ✅ 始终 | ✅ 默认 | ❌ 已注释 |
| 引入延迟 | 无 | 有 (1-5 步) | 有 (约 τ) |
| 物理逼真度 | 中 | 高 (模拟舵机行为) | 中 |
| 实现复杂度 | 低 (单行) | 中 (clip 操作) | 低 (状态保持) |
动作过滤流程(推理时)
策略输出 (action in [-1, 1])
│
▼
┌─────────────────────────────────────┐
│ 可选: LowPassActionFilter │
│ (当前已注释,未使用) │
└─────────────────────────────────────┘
│
▼
action * action_scale (0.25)
│
▼
┌───────────────────────────────┐
motor_targets = ───→│ 电机速度限制 (硬约束) │
default_actuator │ max_motor_velocity = 5.24 rad/s│
+ scaled_action └───────────────┬───────────────┘
│ │
▼ ▼
无限制目标位置 ───────────────→ 限制后的目标位置
│
▼
MuJoCo 物理仿真
│
▼
实际关节位置
代码中的使用
训练时 (joystick.py)
# 默认启用速度限制
USE_MOTOR_SPEED_LIMITS = True
if USE_MOTOR_SPEED_LIMITS:
prev_motor_targets = state.info["motor_targets"]
motor_targets = jp.clip(
motor_targets,
prev_motor_targets - self._config.max_motor_velocity * self.dt,
prev_motor_targets + self._config.max_motor_velocity * self.dt,
)
推理时 (mujoco_infer.py)
# 低通滤波器 (已注释)
# self.action_filter.push(action)
# action = self.action_filter.get_filtered_action()
# 使用速度限制
self.motor_targets = self.default_actuator + action * self.action_scale
if USE_MOTOR_SPEED_LIMITS:
self.motor_targets = np.clip(
self.motor_targets,
self.prev_motor_targets - self.max_motor_velocity * dt,
self.prev_motor_targets + self.max_motor_velocity * dt,
)
self.prev_motor_targets = self.motor_targets.copy()
最佳实践
- 训练时:同时使用
cost_action_rate(软约束)+ 速度限制(硬约束) - 推理时:只使用速度限制(不需要奖励函数)
- 调参:
- 增大
max_motor_velocity→ 动作更敏捷但可能抖动 - 减小
max_motor_velocity→ 动作更平滑但响应变慢 - 调整
cost_action_rate的权重 → 平衡平滑性和响应速度
- 增大
- 部署到真实机器人:始终启用速度限制,必要时增加低通滤波作为额外平滑层
纠错,疑问,交流: 请进入讨论区或 请点击进入页面,扫码加入微信群或Q群进行交流
获取最新文章: 扫一扫加入“创客智造”公众号
欢迎加入我们的openduckmini交流群,微信扫描右侧二维码立即进群交流


















