欢迎光临
我们一直在努力

步态参数化:环境如何把命令整理成四条腿的节律

在四足机器人强化学习(如 Walk These Ways 代码库)的中间任务逻辑里,最关键的一环之一就是步态命令的处理。

很多人初看代码时会存在几个误区,比如认为 p/o/b 是策略网络直接输出的步态参数,或者以为 obs_buf 里塞入了 sin/cos 成对编码,甚至以为接触状态是靠 sin 的正负号硬切出来的。实际上,代码背后的核心思想并不是“让网络直接学会四条腿什么时候抬落”,而是:

先从 curriculum 中采样连续 gait 参数,再把这些参数整理到若干结构化步态家族附近,最后把整理后的相位节律显式送进 observation 和 reward。

这段逻辑主要由两个核心函数完成:

  • _resample_commands():采样并整理 gait command。
  • _step_contact_targets():把 gait command 推进成四条腿的相位、时钟输入和目标接触状态。

下面,我们将抽丝剥茧,详细梳理这套优雅的步态参数化机制。


一、先有 Gait Command,再有四腿相位

与其让策略直接输出 “FL(左前腿)现在抬腿、FR(右前腿)现在落地”这种离散指令,环境采用了一种更平滑的表示方式:用周期相位来描述每条腿在一个步态循环中走到了哪一段。

我们可以先把一条腿的迈步想成一个长度为 1 的圆环时间轴:

  • 0 ~ 1 表示一个完整的步态周期。
  • 走到 1 之后并不会结束,而是回到 0 重新开始。
  • 所以,它天然适合用 “相位” 来表示,而不是用一次性的时间戳。

在这个框架里,环境先维护了一个统一的“全局节拍器” g。
它不是某条腿自己的状态,而是整个机器人当前步态周期推进到了哪里:

gt+1=(gt+f⋅Δt) mod 1g_{t+1} = (g_t + f \\cdot \\Delta t) \\bmod 1gt+1=(gt+fΔt)mod1

在这里:

  • f 决定节拍走得多快,也就是步频。
  • Δt 是每一步环境推进的时间。
  • mod 1 表示走完一整圈后重新回到起点(即数值被限制在 0 到 1 之间循环)

如果只有这个全局时钟 g,那么四条腿会完全同步,因为大家都跟着同一个节拍走。

但真实步态的关键恰恰不在“节拍本身”,而在于:四条腿相对于这个节拍器各自提前多少、滞后多少。

这就是 p、o、b 出现的原因。它们不是绝对时间,而是相位偏移量。环境通过给不同腿叠加不同的偏移,来制造“哪条腿先动、哪条腿后动”的关系。

在默认 pacing_offset = False 的定义下:

ϕFL=(g+p+o+b) mod 1\\phi_{FL} = (g + p + o + b) \\bmod 1ϕFL=(g+p+o+b)mod1
ϕFR=(g+o) mod 1\\phi_{FR} = (g + o) \\bmod 1ϕFR=(g+o)mod1
ϕRL=(g+b) mod 1\\phi_{RL} = (g + b) \\bmod 1ϕRL=(g+b)mod1
ϕRR=(g+p) mod 1\\phi_{RR} = (g + p) \\bmod 1ϕRR=(g+p)mod1

这里的理解重点不是死记哪条腿加了哪几个量,而是看出这个结构:

  • g 是四条腿共享的全局节拍。
  • p / o / b 是人为施加的相对时差。
  • 每条腿都在“同一个周期”里,但各自站在这个周期上的不同位置。
  • 所以,“相位”可以直观地理解成:一条腿在当前步态循环中的进度条。

    • 比如两条腿的相位如果相差 0.5,就意味着它们正好错开半个周期,一条腿在前半程时,另一条腿在后半程。
    • 如果相位差接近 0,那它们就几乎同步起落。

    这样一来,环境要表达 Trot(对角步)、Pace(溜步)、Bound(跳步),本质上就不再是硬编码“第几条腿现在接触”,而是只需要规定:四条腿之间应该保持什么样的相位关系。

    这也解释了为什么后面的代码操作都在改 p / o / b,而不是直接改“接触标签”。因为一旦四条腿的相位关系被定下来,后续的时钟输入(Clock Input)、目标接触状态、摆动期奖励,都会顺着这个节律自然生成!


    二、为什么要把连续参数“投影”到标准步态附近?

    如果完全让 p、o、b 在 [0,1) 内自由漂移,四条腿之间就可能形成大量模糊、难学、甚至不稳定的相位组合。为此,_resample_commands() 在采样完连续 gait 参数后,并不会直接原样使用它们,而是通过一组简单但非常有效的映射,把这些参数投影到若干更稳定的结构附近。具体来说,
    x' = x/2 + 0.25 会把相位差限制在靠近半周期差的区间内,即远离同相区域;
    而 x' = (x/2 – 0.25) mod 1 则会把相位差压到周期意义下接近 0 的邻域,也就是趋向同步。于是,这段代码并不是在凭空硬编码几种 gait,而是在把连续采样得到的 gait 参数约束到“接近同相”或“接近反相”的可学习步态结构附近,从而显著降低策略搜索空间的复杂度。

    如果 x ∈ [0,1),那么先看不取模之前:
    x/2−0.25∈[−0.25,0.25)
    这时左半段是负数,所以再做 mod 1 之后:
    [-0.25, 0) 会被折回到 [0.75, 1)
    [0, 0.25) 保持不变
    所以最终范围就是:
    x ′∈[0,0.25)∪[0.75,1)
    这也正是为什么说它把相位“推向同相区域”:
    在周期变量里,0 和 1 是同一个位置
    所以 [0,0.25) 和 [0.75,1) 实际上都是“靠近 0 相位”的邻域


    三、四种经典步态在代码里是如何整理出来的?

    在 gaitwise_curricula=True 时,环境会先把 env 分配到 pronk / trot / pace / bound 四类,再对参数做特定投影:

    1. Pronk (跳跃)

    p′=(p/2−0.25) mod 1,o′=(o/2−0.25) mod 1,b′=(b/2−0.25) mod 1p' = (p/2 – 0.25) \\bmod 1,\\quad o' = (o/2 – 0.25) \\bmod 1,\\quad b' = (b/2 – 0.25) \\bmod 1p=(p/20.25)mod1,o=(o/20.25)mod1,b=(b/20.25)mod1
    三项都被压向“同相”,四腿大体同步。理想化情况下:
    ϕFL≈ϕFR≈ϕRL≈ϕRR≈g\\phi_{FL} \\approx \\phi_{FR} \\approx \\phi_{RL} \\approx \\phi_{RR} \\approx gϕFLϕFRϕRLϕRRg
    表现为四腿一起起落。

    2. Trot (小跑)

    p′=p/2+0.25,o′=0,b′=0p' = p/2 + 0.25,\\quad o' = 0,\\quad b' = 0p=p/2+0.25,o=0,b=0
    于是:
    ϕFL=g+p′,ϕFR=g,ϕRL=g,ϕRR=g+p′\\phi_{FL} = g + p',\\quad \\phi_{FR} = g,\\quad \\phi_{RL} = g,\\quad \\phi_{RR} = g + p'ϕFL=g+p,ϕFR=g,ϕRL=g,ϕRR=g+p
    p′≈0.5p' \\approx 0.5p0.5 时,就是对角腿两两同步、两组相差半周期。

    3. Pace (溜步)

    p′=0,o′=o/2+0.25,b′=0p' = 0,\\quad o' = o/2 + 0.25,\\quad b' = 0p=0,o=o/2+0.25,b=0
    于是:
    ϕFL=g+o′,ϕFR=g+o′,ϕRL=g,ϕRR=g\\phi_{FL} = g + o',\\quad \\phi_{FR} = g + o',\\quad \\phi_{RL} = g,\\quad \\phi_{RR} = gϕFL=g+o,ϕFR=g+o,ϕRL=g,ϕRR=g
    在默认 pacing_offset=False 公式下,这更像“前腿一组、后腿一组”相差半周期。

    4. Bound (跃步)

    p′=0,o′=0,b′=b/2+0.25p' = 0,\\quad o' = 0,\\quad b' = b/2 + 0.25p=0,o=0,b=b/2+0.25
    于是:
    ϕFL=g+b′,ϕFR=g,ϕRL=g+b′,ϕRR=g\\phi_{FL} = g + b',\\quad \\phi_{FR} = g,\\quad \\phi_{RL} = g + b',\\quad \\phi_{RR} = gϕFL=g+b,ϕFR=g,ϕRL=g+b,ϕRR=g
    在默认公式下,这更像“左腿一组、右腿一组”相差半周期。

    💡 核心提示: 如果在代码中开启 pacing_offset=True,FR 和 RL 的相位公式会发生交换。此时 pace 会更接近标准的“同侧同步”,而 bound 会更接近“前后同步”。


    四、从相位到 Observation:给策略喂多组正弦时钟

    相位 ϕ\\phiϕ 本身是模 1 的周期变量,在 0.99→0.000.99 \\rightarrow 0.000.990.00 处会发生物理突变。为了平滑地传递这个信息,代码并没有直接喂入 ϕ\\phiϕ,而是构造了多组正弦时钟:

    clocki=sin⁡(2πϕi)\\text{clock}_i = \\sin(2\\pi \\phi_i)clocki=sin(2πϕi)
    doubletimei=sin⁡(4πϕi)\\text{doubletime}_i = \\sin(4\\pi \\phi_i)doubletimei=sin(4πϕi)
    halftimei=sin⁡(πϕi)\\text{halftime}_i = \\sin(\\pi \\phi_i)halftimei=sin(πϕi)

    需要特别注意的两点:

  • 代码里默认是 sin-only,并不是有些框架里常用的 sin/cos 成对编码。
  • 真正进入普通 observation 的默认只有 clock_inputs(即 sin⁡(2πϕ)\\sin(2\\pi\\phi)sin(2πϕ) 这一组)。如果需要,可以显式打开 observe_clock_inputs 把其他项拼入 obs_buf。
  • 更准确地说:策略看到的是环境整理好的“节拍器信号”,而不是让神经网络自己从原始接触中去盲猜周期。


    五、从相位到目标接触:Duration 重映射与平滑窗口

    这是最容易写错、也最容易被误解的一步。

    很多直觉化讲法会说:“当 sin⁡(2πϕ)>0\\sin(2\\pi\\phi) > 0sin(2πϕ)>0 时要求落地,否则要求摆动。”但这份代码处理得更加精细:它先根据 duration(ddd)把原始相位重新拉伸。

    • 落在 stance (支撑) 区间 (ϕ<d\\phi < dϕ<d):
      ϕ′=ϕ⋅0.5d\\phi' = \\phi \\cdot \\frac{0.5}{d}ϕ=ϕd0.5
    • 落在 swing (摆动) 区间 (ϕ>d\\phi > dϕ>d):
      ϕ′=0.5+(ϕ−d)⋅0.51−d\\phi' = 0.5 + (\\phi – d)\\cdot \\frac{0.5}{1-d}ϕ=0.5+(ϕd)1d0.5

    这意味着,不管真实的 stance 占整个周期的比例是多少,代码都会把 stance 压到 [0,0.5)[0, 0.5)[0,0.5),把 swing 压到 [0.5,1)[0.5, 1)[0.5,1)

    完成重映射后,desired_contact_states 也不是硬性的 0/1,而是用正态分布 CDF 拼出了一个平滑窗口。直观理解就是:

    • 相位接近 stance 中心时,目标接触值接近 1。
    • 相位接近 swing 中心时,目标接触值接近 0。
    • 在边界附近平滑过渡,避免突然跳变。

    因此,desired_contact_states 的物理含义其实是:“这一刻这条腿应该有多大程度处于接触状态”,而不是粗暴的“非接触即接触”。


    六、Reward:围绕目标状态的“软塑形”

    接触目标生成之后,Reward 会以此来做塑形(Shaped Reward),但绝不是简单的“接触错了就狠狠扣分”。与 gait 接触最相关的三项奖励:

    • tracking_contacts_shaped_force:当某条腿本来不该接触时,如果接触力还很大,受罚。
    • tracking_contacts_shaped_vel:当某条腿本来应该接触时,如果脚端速度还很大,受罚。
    • feet_clearance_cmd_linear:在摆动相,鼓励脚的高度接近期望摆动高度。这里显式使用了 (1−desired_contact_states)(1 – \\text{desired\\_contact\\_states})(1desired_contact_states) 来强调 swing 期的权重。

    从训练机制看,环境并未“硬编码”步态,而是:提供一个平滑的参考节律,再用 reward 把策略往这个节律附近推。


    七、Curriculum 与过滤机制:给策略“加扶手”

    为了防止策略在高维 gait 空间里彻底迷失,代码还额外包了几层限制:

    • gaitwise_curricula:将 env 分配到四大类步态,并投影到对应结构附近。
    • exclusive_phase_offset:只允许 p / o / b 里的一项起作用,其余两项强制清零。等同于强迫每次只沿一种主导相位关系变化。
    • balance_gait_distribution:按固定比例分配环境,避免某一类 gait 在训练中“赢家通吃”导致占比失衡。
    • binary_phases:将相位差强制量化为同相/反相关系:
      x=round(2x)2 mod 1x = \\frac{\\mathrm{round}(2x)}{2} \\bmod 1x=2round(2x)mod1

    此外,对平面速度命令还加了一个非常实用的小 Deadband(死区)过滤:
    (vx,vy)←0,if vx2+vy2≤0.2(v_x, v_y) \\leftarrow 0,\\quad \\text{if } \\sqrt{v_x^2 + v_y^2} \\le 0.2(vx,vy)0,if vx2+vy20.2
    这完美避免了“几乎静止但又不完全为零”的极小速度命令让机器人原地鬼畜抖腿。


    总结

    这套步态参数化机制,并不是让神经网络用纯黑盒的方式自己去摸索“四条腿各自什么时候抬和落”。它呈现的是一套非常典型的现代 Locomotion 环境设计范式:

  • 环境先采样速度和连续 gait command。
  • 通过巧妙的数学变换,把参数整理到稳定的结构化步态附近。
  • 利用全局时钟推进四条腿的相位。
  • 将相位转换为拉伸重映射后的平滑时钟输入和软性接触目标。
  • 最终,通过 Observation 和 Shaped Reward 将节律信息传递给策略网络。
  • 一言以蔽之:策略负责在给定节律条件下输出合适的底层扭矩动作,而环境负责把“想走成什么样”整理成可学、稳定、可优化的时序约束。

    精简回答:

  • 第一步:生成原始相位进度条(定骨架)
    环境维护一个统一的全局时钟 ggg,结合指令中的相位偏移参数 p,o,bp, o, bp,o,b,为四条腿分别生成在 000111 之间循环的原始相位 ϕ\\phiϕ,这就确立了四条腿的时序骨架。

  • 第二步:兵分第一路 —— 给网络当“节拍器”(做 Observation)
    为了避免 ϕ\\phiϕ 归零时产生跳变,我们将它卷成连续的正弦波 sin⁡(2πϕ)\\sin(2\\pi\\phi)sin(2πϕ) 作为观测值喂给网络。网络只看连续的波形来感知当前的周期节奏,它本身并不知道(也不需要知道)这一刻到底该踩地还是抬腿。

  • 第三步:兵分第二路 —— 给环境当“裁判尺”(做 Reward 考核)
    环境拿着原始相位 ϕ\\phiϕ,结合指令中的支撑期比例(Duration ddd),做一个空间重映射计算,捏出一个虚拟相位 ϕ′\\phi'ϕ。在这个系统里被死死规定:ϕ′<0.5\\phi' < 0.5ϕ<0.5 就是目标踩地,>0.5> 0.5>0.5 就是目标抬腿。环境根据它算出目标接触概率,交给 Reward 函数,精准惩罚机器人不按节律的动作。

  • 赞(0)
    未经允许不得转载:171主机测评 » 步态参数化:环境如何把命令整理成四条腿的节律
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址